497 lines
12 KiB
Markdown
497 lines
12 KiB
Markdown
# 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
|
||
|
||
```csharp
|
||
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
|
||
|
||
```csharp
|
||
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 2–6 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.
|
||
|
||
```csharp
|
||
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 1–3 component types.
|
||
|
||
```csharp
|
||
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:
|
||
|
||
```csharp
|
||
using var iter = world.Select<Position, Velocity>(query);
|
||
foreach (ref var item in iter)
|
||
{
|
||
item.Current1.X += item.Current2.X * dt;
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## QueryBuilder
|
||
|
||
```csharp
|
||
namespace OECS;
|
||
|
||
public class QueryBuilder
|
||
{
|
||
public QueryBuilder With<T>() where T : struct;
|
||
public QueryBuilder Without<T>() where T : struct;
|
||
public QueryDescriptor Build();
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## QueryDescriptor
|
||
|
||
```csharp
|
||
namespace OECS;
|
||
|
||
public class QueryDescriptor
|
||
{
|
||
public IReadOnlySet<Type> With { get; }
|
||
public IReadOnlySet<Type> Without { get; }
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## ISystem
|
||
|
||
```csharp
|
||
namespace OECS;
|
||
|
||
public interface ISystem
|
||
{
|
||
QueryDescriptor Query { get; }
|
||
void Run(World world);
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## ITickedSystem
|
||
|
||
```csharp
|
||
namespace OECS;
|
||
|
||
public interface ITickedSystem : ISystem
|
||
{
|
||
void Run(World world, Tick tick);
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## Tick
|
||
|
||
```csharp
|
||
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
|
||
|
||
```csharp
|
||
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
|
||
|
||
```csharp
|
||
namespace OECS;
|
||
|
||
public interface ICommand
|
||
{
|
||
void Execute(World world);
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## CommandQueue
|
||
|
||
```csharp
|
||
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
|
||
|
||
```csharp
|
||
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.
|
||
|
||
```csharp
|
||
namespace OECS;
|
||
|
||
[MessagePackObject]
|
||
public struct Relationship<TSelf, TTarget> : IRelationship
|
||
{
|
||
[Key(0)] public Entity Source { get; set; }
|
||
[Key(1)] public Entity Target { get; set; }
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## EntityChange
|
||
|
||
```csharp
|
||
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.
|
||
|
||
```csharp
|
||
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.
|
||
|
||
```csharp
|
||
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
|
||
|
||
```csharp
|
||
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. | |