339 lines
6.7 KiB
Markdown
339 lines
6.7 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; } // 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
|
|||
|
|
|
|||
|
|
```csharp
|
|||
|
|
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
|
|||
|
|
|
|||
|
|
```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 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 int Count { get; }
|
|||
|
|
public IReadOnlyList<Exception> Errors { get; }
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## IRelationship
|
|||
|
|
|
|||
|
|
```csharp
|
|||
|
|
namespace OECS;
|
|||
|
|
|
|||
|
|
public interface IRelationship
|
|||
|
|
{
|
|||
|
|
Entity Source { get; }
|
|||
|
|
Entity Target { get; }
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## EntityChange
|
|||
|
|
|
|||
|
|
```csharp
|
|||
|
|
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
|
|||
|
|
|
|||
|
|
```csharp
|
|||
|
|
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. |
|