using System.Diagnostics; using R3; namespace OECS; /// /// The central container for all ECS state. /// /// Manages entity lifecycle, component storage, and provides the primary /// API for adding, removing, and querying components. /// public class World : IDisposable { /// /// The reserved entity ID for the singleton entity. /// public static readonly Entity SingletonEntity = new(1, 1); private readonly EntityAllocator _allocator; private readonly ComponentStore _components; private readonly CommandQueue _commands; private readonly RelationshipIndex _relationships; private readonly ChangeBuffer _changes; private bool _singletonCreated; private bool _disposed; // Deferred structural mutation support: when iterating entities, // AddComponent, RemoveComponent, and DestroyEntity are buffered and // applied after the iteration completes. private int _iterationDepth; private readonly List _pendingMutations = new(); private enum PendingMutationKind { AddComponent, RemoveComponent, DestroyEntity } private struct PendingMutation { public PendingMutationKind Kind; public Entity Entity; public object? Component; // boxed struct for AddComponent public Type ComponentType; } // Auto-dirty-marking: during iteration, component accesses via // GetComponent are tracked. When the iteration scope ends, // all accessed components are automatically marked as modified. // Outside of iteration, MarkModified must be called explicitly. private readonly HashSet<(Entity Entity, Type ComponentType)> _accessedComponents = new(); private readonly HashSet<(Entity Entity, Type ComponentType)> _markedModified = new(); public World() { _allocator = new EntityAllocator(); _components = new ComponentStore(); _commands = new CommandQueue(); _relationships = new RelationshipIndex(); _changes = new ChangeBuffer(); } // ── Entity Management ──────────────────────────────────────────── /// /// Creates a new entity and returns its handle. /// Automatically marks an change. /// public Entity CreateEntity() { var entity = _allocator.Allocate(); _changes.Pending.MarkEntityAdded(entity); return entity; } /// /// Creates an entity with a specific handle. Used during deserialization /// to restore entities with their original IDs and versions. /// Does NOT mark an EntityAdded change (caller is responsible for /// posting changes after the full load). /// internal Entity CreateEntity(Entity handle) { _allocator.Reserve(handle); return handle; } /// /// Destroys an entity, removing all its components and recycling its ID. /// /// Cascade behavior: /// - All relationships where this entity is the source are removed. /// - All relationships where this entity is the target are removed /// from the source entities. /// public void DestroyEntity(Entity entity) { if (_iterationDepth > 0) { _pendingMutations.Add(new PendingMutation { Kind = PendingMutationKind.DestroyEntity, Entity = entity }); return; } DestroyEntityImpl(entity); } private void DestroyEntityImpl(Entity entity) { if (!_allocator.IsAlive(entity)) return; // Singleton entity is never destroyed. if (entity.Id == 1) return; // Remove all relationships where this entity is the target. // Materialize to avoid collection-modified-during-enumeration when // OnRemoved mutates the same HashSet we're iterating. var incoming = _relationships.GetIncomingRelationships(entity) .Select(r => (r.RelationshipType, r.Sources.ToList())) .ToList(); foreach (var (relType, sources) in incoming) { foreach (var source in sources) { _relationships.OnRemoved(relType, source, entity); _components.Remove(source, relType); _changes.Pending.MarkComponentRemoved(source, relType); } } // Remove all relationships where this entity is the source. _relationships.RemoveAllSourcesForEntity(entity); // Mark component removals for each component type before removing them. foreach (var componentType in _components.ComponentTypes.ToList()) { if (_components.GetSet(componentType)?.Contains(entity) == true) { _changes.Pending.MarkComponentRemoved(entity, componentType); } } _components.RemoveAll(entity); _allocator.Free(entity); _changes.Pending.MarkEntityRemoved(entity); } /// /// Returns true if the entity is currently alive (not destroyed). /// public bool IsAlive(Entity entity) { return _allocator.IsAlive(entity); } // ── Component Management ───────────────────────────────────────── /// /// Adds a component to the entity. Replaces the existing component if /// the entity already has one of type . /// /// If implements , /// the reverse index is updated automatically. /// public void AddComponent(Entity entity, T component) where T : struct { if (_iterationDepth > 0) { _pendingMutations.Add(new PendingMutation { Kind = PendingMutationKind.AddComponent, Entity = entity, Component = component, ComponentType = typeof(T) }); return; } AddComponentImpl(entity, component); } /// /// Adds a boxed component to the entity. Used by /// during deserialization after relationship Source fixup. /// Calls via reflection to avoid /// duplicating the relationship index logic. /// internal void AddComponentBoxed(Entity entity, object component, Type componentType) { var method = s_addComponentImpl.MakeGenericMethod(componentType); method.Invoke(this, [entity, component]); } private static readonly System.Reflection.MethodInfo s_addComponentImpl = typeof(World).GetMethod( nameof(AddComponentImpl), System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)!; private void AddComponentImpl(Entity entity, T component) where T : struct { ThrowIfNotAlive(entity); // If replacing an existing relationship, remove the old index entry first. if (component is IRelationship) { if (_components.TryGet(entity, out var old)) { var oldRel = (IRelationship)(object)old; _relationships.OnRemoved(typeof(T), entity, oldRel.Target); } } bool isNew = !_components.Has(entity); _components.Add(entity, component); // Update relationship index. if (component is IRelationship rel) { _relationships.OnAdded(typeof(T), entity, rel.Target); } // Mark change. if (isNew) _changes.Pending.MarkComponentAdded(entity, typeof(T)); } /// /// Removes the component of type from the entity. /// No-op if the entity does not have the component. /// /// If implements , /// the reverse index is updated automatically. /// public void RemoveComponent(Entity entity) where T : struct { if (_iterationDepth > 0) { _pendingMutations.Add(new PendingMutation { Kind = PendingMutationKind.RemoveComponent, Entity = entity, ComponentType = typeof(T) }); return; } RemoveComponentImpl(entity); } private void RemoveComponentImpl(Entity entity) where T : struct { // Update relationship index before removal. if (typeof(IRelationship).IsAssignableFrom(typeof(T))) { if (_components.TryGet(entity, out var existing)) { var rel = (IRelationship)(object)existing; _relationships.OnRemoved(typeof(T), entity, rel.Target); } } bool existed = _components.Has(entity); _components.Remove(entity); if (existed) _changes.Pending.MarkComponentRemoved(entity, typeof(T)); } /// /// Returns a reference to the component of type /// for the given entity. Throws if the entity does not have the component. /// /// During iteration, this access is auto-marked as modified since it /// returns a mutable ref. Use if you /// only need to read the value. /// public ref T GetComponent(Entity entity) where T : struct { if (_iterationDepth > 0) _accessedComponents.Add((entity, typeof(T))); return ref _components.Get(entity); } /// /// Returns a copy of the component of type /// for the given entity. Never auto-marks as modified — use this when /// you only need to read the value. /// public T ReadComponent(Entity entity) where T : struct { return _components.Get(entity); } /// /// Tries to get the component of type for the /// given entity. Returns true and copies the value to /// if the component exists; otherwise returns false. /// Never auto-marks as modified. /// public bool TryGetComponent(Entity entity, out T value) where T : struct { return _components.TryGet(entity, out value); } /// /// Returns true if the entity has a component of type . /// public bool HasComponent(Entity entity) where T : struct { return _components.Has(entity); } // ── Change Tracking ────────────────────────────────────────────── /// /// Marks a component of type on the given entity /// as modified. Only needed outside of iteration scopes — during /// or a Select loop, components accessed /// via are auto-marked. /// public void MarkModified(Entity entity) where T : struct { _changes.Pending.MarkComponentModified(entity, typeof(T)); _markedModified.Add((entity, typeof(T))); } /// /// Posts all pending changes to R3 subscribers, then clears the pending set. /// Called automatically by after each system /// and after the full tick. /// public void PostChanges() { _changes.Post(); } /// /// Returns an observable that emits all entity changes (adds, removes, /// component adds, component removes, component modifications). /// public Observable ObserveEntityChanges() { return _changes.ObserveEntityChanges(); } /// /// Returns an observable that emits changes for a specific component type /// (added, removed, or modified). /// public Observable ObserveComponentChanges() where T : struct { return _changes.ObserveComponentChanges(typeof(T)); } /// /// Returns an observable that emits changes matching the given query. /// Only component-level changes whose component type is in the query's /// "with" set and not in the "without" set are emitted. /// public Observable ObserveQuery(QueryDescriptor query) { return _changes.ObserveQuery(query); } // ── Singletons ─────────────────────────────────────────────────── /// /// Sets (adds or replaces) a singleton component of type . /// Singletons are stored on a reserved entity that is never destroyed /// and excluded from normal queries. /// public void SetSingleton(T component) where T : struct { EnsureSingleton(); AddComponent(SingletonEntity, component); } /// /// Returns a reference to the singleton component of type . /// Throws if the singleton has not been set. Auto-marks as modified during iteration. /// public ref T GetSingleton() where T : struct { EnsureSingleton(); return ref GetComponent(SingletonEntity); } /// /// Returns a copy of the singleton component of type . /// Never auto-marks as modified — use this when you only need to read the singleton. /// public T ReadSingleton() where T : struct { EnsureSingleton(); return ReadComponent(SingletonEntity); } /// /// Returns true if a singleton component of type exists. /// public bool HasSingleton() where T : struct { return HasComponent(SingletonEntity); } /// /// Removes the singleton component of type . /// No-op if the singleton does not have the component. /// public void RemoveSingleton() where T : struct { RemoveComponent(SingletonEntity); } internal void EnsureSingleton() { if (_singletonCreated) return; _singletonCreated = true; // Manually register the singleton entity in the allocator so // IsAlive returns true for it. We bypass Allocate() because // the singleton has a fixed ID and version. _allocator.RegisterSingleton(SingletonEntity); } // ── Relationships ──────────────────────────────────────────────── /// /// Returns all source entities that have a relationship of type /// pointing to the given target entity. /// public IReadOnlyCollection GetSources(Entity target) where T : struct, IRelationship { return _relationships.GetSources(target); } // ── Commands ────────────────────────────────────────────────────── /// /// The command queue for deferred, serializable commands. /// Systems enqueue commands via world.Commands.Enqueue(...). /// public CommandQueue Commands => _commands; /// /// Manually drains the command queue, executing all pending commands. /// Called automatically by after each system /// and after the full tick. /// public void ExecuteCommands() { _commands.ExecuteAll(this); } // ── Queries ────────────────────────────────────────────────────── /// /// Returns a new for constructing queries. /// public QueryBuilder Query() => new(); /// /// Iterates all entities matching the query, providing ref access to /// one component type. Structural mutations (AddComponent, RemoveComponent, /// DestroyEntity) made during iteration are deferred and applied after /// the iteration completes. /// public void ForEach( QueryDescriptor query, ForEachAction action) where T1 : struct { ValidateQueryTypes(query, typeof(T1)); BeginIteration(); try { QueryExecutor.ForEach(_components, query, action); } finally { EndIteration(); } } /// /// Iterates all entities matching the query, providing ref access to /// two component types. Structural mutations are deferred. /// public void ForEach( QueryDescriptor query, ForEachAction action) where T1 : struct where T2 : struct { ValidateQueryTypes(query, typeof(T1), typeof(T2)); BeginIteration(); try { QueryExecutor.ForEach(_components, query, action); } finally { EndIteration(); } } /// /// Iterates all entities matching the query, providing ref access to /// three component types. Structural mutations are deferred. /// public void ForEach( QueryDescriptor query, ForEachAction action) where T1 : struct where T2 : struct where T3 : struct { ValidateQueryTypes(query, typeof(T1), typeof(T2), typeof(T3)); BeginIteration(); try { QueryExecutor.ForEach(_components, query, action); } finally { EndIteration(); } } /// /// Iterates all entities matching the query, providing ref access to /// four component types. Structural mutations are deferred. /// public void ForEach( QueryDescriptor query, ForEachAction action) where T1 : struct where T2 : struct where T3 : struct where T4 : struct { ValidateQueryTypes(query, typeof(T1), typeof(T2), typeof(T3), typeof(T4)); BeginIteration(); try { QueryExecutor.ForEach(_components, query, action); } finally { EndIteration(); } } /// /// Iterates all entities matching the query, providing ref access to /// five component types. Structural mutations are deferred. /// public void ForEach( QueryDescriptor query, ForEachAction action) where T1 : struct where T2 : struct where T3 : struct where T4 : struct where T5 : struct { ValidateQueryTypes(query, typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5)); BeginIteration(); try { QueryExecutor.ForEach(_components, query, action); } finally { EndIteration(); } } /// /// Iterates all entities matching the query, providing ref access to /// six component types. Structural mutations are deferred. /// public void ForEach( QueryDescriptor query, ForEachAction action) where T1 : struct where T2 : struct where T3 : struct where T4 : struct where T5 : struct where T6 : struct { ValidateQueryTypes(query, typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5), typeof(T6)); BeginIteration(); try { QueryExecutor.ForEach(_components, query, action); } finally { EndIteration(); } } /// /// Begins an iteration scope. Structural mutations are buffered /// until is called. /// internal void BeginIteration() { _iterationDepth++; } /// /// Ends an iteration scope. When the outermost scope ends, all /// buffered structural mutations are applied and auto-tracked /// component modifications are posted. /// internal void EndIteration() { _iterationDepth--; if (_iterationDepth == 0) { // Auto-mark all components accessed via GetComponent // during the iteration as modified. foreach (var (entity, componentType) in _accessedComponents) { if (!_markedModified.Contains((entity, componentType))) { _changes.Pending.MarkComponentModified(entity, componentType); } } _accessedComponents.Clear(); _markedModified.Clear(); FlushPendingMutations(); } } private void FlushPendingMutations() { if (_pendingMutations.Count == 0) return; foreach (var m in _pendingMutations) { switch (m.Kind) { case PendingMutationKind.AddComponent: // Use reflection to call AddComponentImpl. var addMethod = typeof(World).GetMethod( nameof(AddComponentImpl), System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)!; var genericAdd = addMethod.MakeGenericMethod(m.ComponentType); genericAdd.Invoke(this, [m.Entity, m.Component]); break; case PendingMutationKind.RemoveComponent: var removeMethod = typeof(World).GetMethod( nameof(RemoveComponentImpl), System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)!; var genericRemove = removeMethod.MakeGenericMethod(m.ComponentType); genericRemove.Invoke(this, [m.Entity]); break; case PendingMutationKind.DestroyEntity: DestroyEntityImpl(m.Entity); break; } } _pendingMutations.Clear(); } // ── Internal Access ─────────────────────────────────────────────── /// /// The component store. Exposed internally for query execution and /// system infrastructure. /// internal ComponentStore Components => _components; // ── Helpers ─────────────────────────────────────────────────────── private void ThrowIfNotAlive(Entity entity) { if (!_allocator.IsAlive(entity)) throw new InvalidOperationException($"Entity {entity} is not alive."); } /// /// Validates that the ForEach type parameters match the query's /// With set. When the With set is empty (only Without filters are /// specified), any ForEach type parameters are accepted. /// private static void ValidateQueryTypes(QueryDescriptor query, params Type[] forEachTypes) { // When With is empty, the query is using only Without filters. // The ForEach type parameters are the sole source of truth. if (query.With.Count == 0) return; if (query.With.Count != forEachTypes.Length) ThrowMismatch(query, forEachTypes); foreach (var t in forEachTypes) { if (!query.With.Contains(t)) ThrowMismatch(query, forEachTypes); } } private static void ThrowMismatch(QueryDescriptor query, Type[] forEachTypes) { var queryTypes = string.Join(", ", query.With.Select(t => t.Name)); var forEachTypeNames = string.Join(", ", forEachTypes.Select(t => t.Name)); throw new InvalidOperationException( $"ForEach type parameters [{forEachTypeNames}] do not match " + $"the query's With types [{queryTypes}]. " + $"Ensure the ForEach type parameters exactly match the types " + $"passed to QueryBuilder.With()."); } // ── Cleanup ─────────────────────────────────────────────────────── public void Dispose() { if (_disposed) return; _disposed = true; _changes.Dispose(); } }