2026-07-18 19:03:37 +08:00
|
|
|
namespace OECS;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// The central container for all ECS state.
|
|
|
|
|
///
|
|
|
|
|
/// Manages entity lifecycle, component storage, and provides the primary
|
|
|
|
|
/// API for adding, removing, and querying components.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public class World : IDisposable
|
|
|
|
|
{
|
|
|
|
|
private readonly EntityAllocator _allocator;
|
|
|
|
|
private readonly ComponentStore _components;
|
2026-07-18 19:38:10 +08:00
|
|
|
private readonly CommandQueue _commands;
|
2026-07-18 19:03:37 +08:00
|
|
|
|
|
|
|
|
public World()
|
|
|
|
|
{
|
|
|
|
|
_allocator = new EntityAllocator();
|
|
|
|
|
_components = new ComponentStore();
|
2026-07-18 19:38:10 +08:00
|
|
|
_commands = new CommandQueue();
|
2026-07-18 19:03:37 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ── Entity Management ────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Creates a new entity and returns its handle.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public Entity CreateEntity()
|
|
|
|
|
{
|
|
|
|
|
return _allocator.Allocate();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Destroys an entity, removing all its components and recycling its ID.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public void DestroyEntity(Entity entity)
|
|
|
|
|
{
|
|
|
|
|
if (!_allocator.IsAlive(entity))
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
// Singleton entity is never destroyed.
|
|
|
|
|
if (entity.Id == 1)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
_components.RemoveAll(entity);
|
|
|
|
|
_allocator.Free(entity);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Returns true if the entity is currently alive (not destroyed).
|
|
|
|
|
/// </summary>
|
|
|
|
|
public bool IsAlive(Entity entity)
|
|
|
|
|
{
|
|
|
|
|
return _allocator.IsAlive(entity);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ── Component Management ─────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Adds a component to the entity. Replaces the existing component if
|
|
|
|
|
/// the entity already has one of type <typeparamref name="T"/>.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public void AddComponent<T>(Entity entity, T component) where T : struct
|
|
|
|
|
{
|
|
|
|
|
ThrowIfNotAlive(entity);
|
|
|
|
|
_components.Add(entity, component);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Removes the component of type <typeparamref name="T"/> from the entity.
|
|
|
|
|
/// No-op if the entity does not have the component.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public void RemoveComponent<T>(Entity entity) where T : struct
|
|
|
|
|
{
|
|
|
|
|
_components.Remove<T>(entity);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Returns a reference to the component of type <typeparamref name="T"/>
|
|
|
|
|
/// for the given entity. Throws if the entity does not have the component.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public ref T GetComponent<T>(Entity entity) where T : struct
|
|
|
|
|
{
|
|
|
|
|
return ref _components.Get<T>(entity);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Returns true if the entity has a component of type <typeparamref name="T"/>.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public bool HasComponent<T>(Entity entity) where T : struct
|
|
|
|
|
{
|
|
|
|
|
return _components.Has<T>(entity);
|
|
|
|
|
}
|
2026-07-18 19:33:05 +08:00
|
|
|
|
2026-07-18 19:38:10 +08:00
|
|
|
// ── Commands ──────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// The command queue for deferred, serializable commands.
|
|
|
|
|
/// Systems enqueue commands via <c>world.Commands.Enqueue(...)</c>.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public CommandQueue Commands => _commands;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Manually drains the command queue, executing all pending commands.
|
|
|
|
|
/// Called automatically by <see cref="SystemGroup"/> after each system
|
|
|
|
|
/// and after the full tick.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public void ExecuteCommands()
|
|
|
|
|
{
|
|
|
|
|
_commands.ExecuteAll(this);
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-18 19:33:05 +08:00
|
|
|
// ── Queries ──────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Returns a new <see cref="QueryBuilder"/> for constructing queries.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public QueryBuilder Query() => new();
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Iterates all entities matching the query, providing ref access to
|
|
|
|
|
/// one component type.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public void ForEach<T1>(
|
|
|
|
|
QueryDescriptor query,
|
|
|
|
|
ForEachAction<T1> action)
|
|
|
|
|
where T1 : struct
|
|
|
|
|
{
|
|
|
|
|
QueryExecutor.ForEach(_components, query, action);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Iterates all entities matching the query, providing ref access to
|
|
|
|
|
/// two component types.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public void ForEach<T1, T2>(
|
|
|
|
|
QueryDescriptor query,
|
|
|
|
|
ForEachAction<T1, T2> action)
|
|
|
|
|
where T1 : struct where T2 : struct
|
|
|
|
|
{
|
|
|
|
|
QueryExecutor.ForEach(_components, query, action);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Iterates all entities matching the query, providing ref access to
|
|
|
|
|
/// three component types.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public void ForEach<T1, T2, T3>(
|
|
|
|
|
QueryDescriptor query,
|
|
|
|
|
ForEachAction<T1, T2, T3> action)
|
|
|
|
|
where T1 : struct where T2 : struct where T3 : struct
|
|
|
|
|
{
|
|
|
|
|
QueryExecutor.ForEach(_components, query, action);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Iterates all entities matching the query, providing ref access to
|
|
|
|
|
/// four component types.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public void ForEach<T1, T2, T3, T4>(
|
|
|
|
|
QueryDescriptor query,
|
|
|
|
|
ForEachAction<T1, T2, T3, T4> action)
|
|
|
|
|
where T1 : struct where T2 : struct where T3 : struct where T4 : struct
|
|
|
|
|
{
|
|
|
|
|
QueryExecutor.ForEach(_components, query, action);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Iterates all entities matching the query, providing ref access to
|
|
|
|
|
/// five component types.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public void ForEach<T1, T2, T3, T4, T5>(
|
|
|
|
|
QueryDescriptor query,
|
|
|
|
|
ForEachAction<T1, T2, T3, T4, T5> action)
|
|
|
|
|
where T1 : struct where T2 : struct where T3 : struct where T4 : struct where T5 : struct
|
|
|
|
|
{
|
|
|
|
|
QueryExecutor.ForEach(_components, query, action);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Iterates all entities matching the query, providing ref access to
|
|
|
|
|
/// six component types.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public void ForEach<T1, T2, T3, T4, T5, T6>(
|
|
|
|
|
QueryDescriptor query,
|
|
|
|
|
ForEachAction<T1, T2, T3, T4, T5, T6> action)
|
|
|
|
|
where T1 : struct where T2 : struct where T3 : struct where T4 : struct where T5 : struct where T6 : struct
|
|
|
|
|
{
|
|
|
|
|
QueryExecutor.ForEach(_components, query, action);
|
|
|
|
|
}
|
2026-07-18 19:03:37 +08:00
|
|
|
|
|
|
|
|
// ── Internal Access ───────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// The component store. Exposed internally for query execution and
|
|
|
|
|
/// system infrastructure.
|
|
|
|
|
/// </summary>
|
|
|
|
|
internal ComponentStore Components => _components;
|
|
|
|
|
|
|
|
|
|
// ── Helpers ───────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
private void ThrowIfNotAlive(Entity entity)
|
|
|
|
|
{
|
|
|
|
|
if (!_allocator.IsAlive(entity))
|
|
|
|
|
throw new InvalidOperationException($"Entity {entity} is not alive.");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ── Cleanup ───────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
public void Dispose()
|
|
|
|
|
{
|
|
|
|
|
// Future: dispose R3 subscriptions, etc.
|
|
|
|
|
}
|
|
|
|
|
}
|