6.7 KiB
6.7 KiB
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 2–6 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. |