oecs-sharp/docs/api-surface.md

12 KiB
Raw Blame History

API Surface: OECS

This document describes the public API surface of the OECS library. It serves as a contract for implementation and a reference for consumers.

All types reside in the OECS namespace unless otherwise noted.


Entity

namespace OECS;

public readonly struct Entity : IEquatable<Entity>
{
    public static Entity Null { get; }        // ID=0, Version=0
    public uint Id { get; }                    // lower 24-bit identifier
    public uint Version { get; }               // upper 8-bit generation
    public bool IsNull { get; }                // true for Entity.Null

    public bool Equals(Entity other);
    public override bool Equals(object? obj);
    public override int GetHashCode();
    public override string ToString();         // "Entity(42:v3)"
    public static bool operator ==(Entity left, Entity right);
    public static bool operator !=(Entity left, Entity right);
}

World

namespace OECS;

public class World : IDisposable
{
    // --- Singleton entity ---
    public static Entity SingletonEntity { get; }  // ID=1, Version=1

    // --- Lifecycle ---
    public World();

    // --- Entity Management ---
    public Entity CreateEntity();
    public void DestroyEntity(Entity entity);
    public bool IsAlive(Entity entity);

    // --- Component Management ---
    public void AddComponent<T>(Entity entity, T component) where T : struct;
    public void RemoveComponent<T>(Entity entity) where T : struct;
    public ref T GetComponent<T>(Entity entity) where T : struct;
    public T ReadComponent<T>(Entity entity) where T : struct;
    public bool TryGetComponent<T>(Entity entity, out T value) where T : struct;
    public bool HasComponent<T>(Entity entity) where T : struct;

    // --- Singleton ---
    public void SetSingleton<T>(T component) where T : struct;
    public ref T GetSingleton<T>() where T : struct;
    public T ReadSingleton<T>() where T : struct;
    public bool HasSingleton<T>() where T : struct;
    public void RemoveSingleton<T>() where T : struct;

    // --- Queries ---
    public QueryBuilder Query();

    // --- Query Execution ---
    public void ForEach<T1>(
        QueryDescriptor query,
        ForEachAction<T1> action) where T1 : struct;

    // Overloads for 26 component types:
    public void ForEach<T1, T2>(QueryDescriptor, ForEachAction<T1, T2>)
        where T1 : struct where T2 : struct;
    public void ForEach<T1, T2, T3>(QueryDescriptor, ForEachAction<T1, T2, T3>)
        where T1 : struct where T2 : struct where T3 : struct;
    public void ForEach<T1, T2, T3, T4>(QueryDescriptor, ForEachAction<T1, T2, T3, T4>)
        where T1 : struct where T2 : struct where T3 : struct where T4 : struct;
    public void ForEach<T1, T2, T3, T4, T5>(QueryDescriptor, ForEachAction<T1, T2, T3, T4, T5>)
        where T1 : struct where T2 : struct where T3 : struct where T4 : struct where T5 : struct;
    public void ForEach<T1, T2, T3, T4, T5, T6>(QueryDescriptor, ForEachAction<T1, T2, T3, T4, T5, T6>)
        where T1 : struct where T2 : struct where T3 : struct where T4 : struct where T5 : struct where T6 : struct;

    // --- Commands ---
    public CommandQueue Commands { get; }
    public void ExecuteCommands();

    // --- Reactivity ---
    public void MarkModified<T>(Entity entity) where T : struct;
    public void PostChanges();

    public Observable<EntityChange> ObserveEntityChanges();
    public Observable<EntityChange> ObserveComponentChanges<T>()
        where T : struct;
    public Observable<EntityChange> ObserveQuery(QueryDescriptor query);

    // --- Relationships ---
    public IReadOnlyCollection<Entity> GetSources<T>(Entity target)
        where T : struct, IRelationship;

    // --- Cleanup ---
    public void Dispose();
}

ForEachAction Delegates

Custom delegate types for query iteration with ref parameters. The built-in Action<...> delegates do not support ref parameters.

namespace OECS;

public delegate void ForEachAction<T1>(Entity entity, ref T1 c1) where T1 : struct;

public delegate void ForEachAction<T1, T2>(Entity entity, ref T1 c1, ref T2 c2)
    where T1 : struct where T2 : struct;

public delegate void ForEachAction<T1, T2, T3>(Entity entity, ref T1 c1, ref T2 c2, ref T3 c3)
    where T1 : struct where T2 : struct where T3 : struct;

public delegate void ForEachAction<T1, T2, T3, T4>(Entity entity, ref T1 c1, ref T2 c2, ref T3 c3, ref T4 c4)
    where T1 : struct where T2 : struct where T3 : struct where T4 : struct;

public delegate void ForEachAction<T1, T2, T3, T4, T5>(Entity entity, ref T1 c1, ref T2 c2, ref T3 c3, ref T4 c4, ref T5 c5)
    where T1 : struct where T2 : struct where T3 : struct where T4 : struct where T5 : struct;

public delegate void ForEachAction<T1, T2, T3, T4, T5, T6>(Entity entity, ref T1 c1, ref T2 c2, ref T3 c3, ref T4 c4, ref T5 c5, ref T6 c6)
    where T1 : struct where T2 : struct where T3 : struct where T4 : struct where T5 : struct where T6 : struct;

EntityIterator

Extension methods on World for foreach-style iteration over queries. Returns ref struct iterators that support zero-allocation iteration with ref access to components. Supports 13 component types.

namespace OECS;

public static class EntityIterator
{
    public static Select1<T1> Select<T1>(
        this World world, QueryDescriptor query) where T1 : struct;

    public static Select2<T1, T2> Select<T1, T2>(
        this World world, QueryDescriptor query)
        where T1 : struct where T2 : struct;

    public static Select3<T1, T2, T3> Select<T1, T2, T3>(
        this World world, QueryDescriptor query)
        where T1 : struct where T2 : struct where T3 : struct;
}

Usage:

using var iter = world.Select<Position, Velocity>(query);
foreach (ref var item in iter)
{
    item.Current1.X += item.Current2.X * dt;
}

QueryBuilder

namespace OECS;

public class QueryBuilder
{
    public QueryBuilder With<T>() where T : struct;
    public QueryBuilder Without<T>() where T : struct;
    public QueryDescriptor Build();
}

QueryDescriptor

namespace OECS;

public class QueryDescriptor
{
    public IReadOnlySet<Type> With { get; }
    public IReadOnlySet<Type> Without { get; }
}

ISystem

namespace OECS;

public interface ISystem
{
    QueryDescriptor Query { get; }
    void Run(World world);
}

ITickedSystem

namespace OECS;

public interface ITickedSystem : ISystem
{
    void Run(World world, Tick tick);
}

Tick

namespace OECS;

public readonly struct Tick
{
    public TickType Type { get; }
    public float DeltaTime { get; }

    public static Tick Timed(float deltaTime);
    public static Tick Logical();
}

public enum TickType
{
    Timed,
    Logical
}

SystemGroup

namespace OECS;

public class SystemGroup
{
    public SystemGroup(World world);
    public void Add(ISystem system);
    public void Remove(ISystem system);
    public void RunTimed(float deltaTime);
    public void RunLogical();
    public int Count { get; }
}

ICommand

namespace OECS;

public interface ICommand
{
    void Execute(World world);
}

CommandQueue

namespace OECS;

public class CommandQueue
{
    public void Enqueue<T>(T command) where T : struct, ICommand;
    public void ExecuteAll(World world);
    public void Clear();
    public void ClearErrors();
    public int Count { get; }
    public IReadOnlyList<Exception> Errors { get; }
}

IRelationship

namespace OECS;

public interface IRelationship
{
    Entity Source { get; }
    Entity Target { get; }
}

Relationship<TSelf, TTarget>

A convenience base struct for relationship components. The type parameters are phantom types that differentiate relationship kinds at the type level, enabling type-safe queries and reverse lookups.

namespace OECS;

[MessagePackObject]
public struct Relationship<TSelf, TTarget> : IRelationship
{
    [Key(0)] public Entity Source { get; set; }
    [Key(1)] public Entity Target { get; set; }
}

EntityChange

namespace OECS;

public readonly struct EntityChange : IEquatable<EntityChange>
{
    public Entity Entity { get; }
    public ChangeKind Kind { get; }
    public Type? ComponentType { get; }  // null for entity-level changes
}

public enum ChangeKind
{
    EntityAdded,
    EntityRemoved,
    ComponentAdded,
    ComponentRemoved,
    ComponentModified
}

WorldSerializer

Saves and loads a World to/from a MessagePack stream. Components are serialized by their runtime type.

namespace OECS;

public static class WorldSerializer
{
    public static void Save(World world, Stream stream);
    public static void Load(World world, Stream stream);
}

WorldSnapshot / EntitySnapshot / ComponentEntry

Serialization types used by WorldSerializer. These are public but intended primarily for serialization infrastructure.

namespace OECS;

[MessagePackObject]
public class WorldSnapshot
{
    [Key(0)] public EntitySnapshot[] Entities { get; set; }
}

[MessagePackObject]
public class EntitySnapshot
{
    [Key(0)] public uint Id { get; set; }
    [Key(1)] public uint Version { get; set; }
    [Key(2)] public ComponentEntry[] Components { get; set; }
    public Entity Entity { get; }
}

[MessagePackObject]
public class ComponentEntry
{
    [Key(0)] public string TypeName { get; set; }
    [Key(1)] public byte[] Data { get; set; }
}

Usage Example

using OECS;
using R3;

// Define components
[MessagePackObject]
public struct Position : IMessagePackSerializationCallbackReceiver
{
    [Key(0)] public float X;
    [Key(1)] public float Y;

    public void OnBeforeSerialize() { }
    public void OnAfterDeserialize() { }
}

[MessagePackObject]
public struct Velocity : IMessagePackSerializationCallbackReceiver
{
    [Key(0)] public float X;
    [Key(1)] public float Y;

    public void OnBeforeSerialize() { }
    public void OnAfterDeserialize() { }
}

// Define a system
public class MovementSystem : ITickedSystem
{
    public QueryDescriptor Query { get; }

    public MovementSystem(World world)
    {
        Query = world.Query()
            .With<Position>()
            .With<Velocity>()
            .Build();
    }

    public void Run(World world) => Run(world, Tick.Logical());

    public void Run(World world, Tick tick)
    {
        float dt = tick.DeltaTime;

        world.ForEach(Query, (Entity entity, ref Position pos, ref Velocity vel) =>
        {
            pos.X += vel.X * dt;
            pos.Y += vel.Y * dt;
            world.MarkModified<Position>(entity);
        });
    }
}

// Wire it up
var world = new World();
var group = new SystemGroup(world);
group.Add(new MovementSystem(world));

// Observe changes
world.ObserveComponentChanges<Position>()
    .Subscribe(change => Console.WriteLine($"{change.Entity} moved"))
    .AddTo(disposables);

// Create entities
var player = world.CreateEntity();
world.AddComponent(player, new Position { X = 0, Y = 0 });
world.AddComponent(player, new Velocity { X = 1, Y = 0 });

// Run a tick
group.RunTimed(0.016f); // ~60 FPS

Internal Types (not part of public API)

These types are implementation details and may change without notice:

Type Purpose
SparseSet<T> Dense/sparse array pair for component storage.
ComponentStore Registry of SparseSet<T> instances by type.
ChangeBuffer Accumulates EntityChange during system run, posts to R3 subjects.
ChangeSet Deduplicated change accumulator within a single batch.
RelationshipIndex Reverse lookup from target entity to source entities.
EntityAllocator Free-list + bump allocator for entity IDs.
QueryExecutor Query iteration logic with smallest-set driver optimization.