oecs-sharp/docs/api-surface.md

6.7 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; }                    // 24-bit identifier
    public uint Version { get; }               // 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
{
    // --- 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 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 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,
        Action<Entity, ref T1> action) where T1 : struct;

    // Overloads for 26 component types:
    public void ForEach<T1, T2>(QueryDescriptor, Action<Entity, ref T1, ref T2>)
        where T1 : struct where T2 : struct;
    // ... up to T6

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

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

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

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

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

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 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 int Count { get; }
    public IReadOnlyList<Exception> Errors { get; }
}

IRelationship

namespace OECS;

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

EntityChange

namespace OECS;

public readonly struct 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
}

Usage Example

using OECS;
using R3;

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

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

// 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, 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();
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.
RelationshipIndex Reverse lookup from target entity to source entities.
EntityAllocator Free-list + bump allocator for entity IDs.