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 { private readonly EntityAllocator _allocator; private readonly ComponentStore _components; private readonly CommandQueue _commands; private readonly RelationshipIndex _relationships; private readonly ChangeBuffer _changes; private readonly Dictionary _singletonEntities = new(); private bool _disposed; // Deferred structural mutation support: when inside a batching scope // (e.g., a foreach iteration or a system run), AddComponent, // RemoveComponent, and DestroyEntity are buffered and applied when // the outermost scope ends. private int _batchingDepth; 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 a batching scope, component accesses via // GetComponent are tracked. When the scope ends, all accessed // components are automatically marked as modified. // Outside of a batching scope, MarkModified must be called explicitly. private readonly HashSet<(Entity Entity, Type ComponentType)> _accessedComponents = new(); private readonly HashSet<(Entity Entity, Type ComponentType)> _markedModified = new(); // Interrupt store: injected by SystemGroup. Null when no SystemGroup is managing // this world, in which case Interrupt() is a no-op. internal InterruptStore? _interruptStore; 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 (_batchingDepth > 0) { _pendingMutations.Add(new PendingMutation { Kind = PendingMutationKind.DestroyEntity, Entity = entity }); return; } DestroyEntityImpl(entity); } private void DestroyEntityImpl(Entity entity) { if (!_allocator.IsAlive(entity)) 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 (_batchingDepth > 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 (_batchingDepth > 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 (_batchingDepth > 0) _accessedComponents.Add((entity, typeof(T))); return ref _components.Get(entity); } /// /// Tracks a component access for auto-dirty-marking without returning /// a ref. Used by iterators when they expose /// component refs directly from the sparse set. /// internal void TrackAccess(Entity entity, Type componentType) { if (_batchingDepth > 0) _accessedComponents.Add((entity, componentType)); } /// /// 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 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 component-level changes matching /// the given query's With types and excluding its Without types. /// public Observable ObserveQuery(Query query = default) where T1 : struct { return _changes.ObserveQuery(query); } /// /// Returns an observable that emits component-level changes matching /// the given query. /// public Observable ObserveQuery(Query query = default) where T1 : struct where T2 : struct { return _changes.ObserveQuery(query); } // ── Singletons ─────────────────────────────────────────────────── /// /// Sets (adds or replaces) a singleton component of type . /// Each singleton component type gets its own dedicated entity. /// public void SetSingleton(T component) where T : struct { if (!_singletonEntities.TryGetValue(typeof(T), out var entity)) { entity = _allocator.Allocate(); _singletonEntities[typeof(T)] = entity; } AddComponent(entity, 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 { return ref GetComponent(GetSingletonEntity()); } /// /// 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 { return ReadComponent(GetSingletonEntity()); } /// /// Returns true if a singleton component of type exists. /// public bool HasSingleton() where T : struct { return _singletonEntities.TryGetValue(typeof(T), out var entity) && HasComponent(entity); } /// /// Removes the singleton component of type . /// No-op if the singleton does not have the component. /// public void RemoveSingleton() where T : struct { if (_singletonEntities.TryGetValue(typeof(T), out var entity)) RemoveComponent(entity); } /// /// Gets the entity backing the singleton of type , /// creating it if it doesn't exist yet. /// private Entity GetSingletonEntity() where T : struct { if (!_singletonEntities.TryGetValue(typeof(T), out var entity)) { entity = _allocator.Allocate(); _singletonEntities[typeof(T)] = entity; } return entity; } // ── Relationships ──────────────────────────────────────────────── /// /// Returns all source entities that have a relationship of type /// pointing to the given target entity. /// Sources are iterated in insertion order. /// public IReadOnlyCollection GetSources(Entity target) where T : struct, IRelationship { return _relationships.GetSources(target); } /// /// Reorders the source set for the given relationship type and target /// to match the order of entities in . /// All entities currently in the set must appear exactly once in the /// provided collection; no entities are added or removed. /// Fires a change event /// on the target entity. /// public void ReorderSources(Entity target, IReadOnlyList ordered) where T : struct, IRelationship { _relationships.ReorderSources(target, ordered); _changes.Pending.MarkRelationshipReordered(target, typeof(T)); } // ── 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); } // ── Interrupts ──────────────────────────────────────────────────── /// /// Issues an interrupt of type , blocking the /// next tick from running systems until a matching /// resolves it. /// /// Only one interrupt of a given type may be pending at a time. /// If no is managing this world, interrupts /// are silently ignored (calls are no-ops). /// public void Interrupt(T interrupt) where T : struct, IInterrupt { _interruptStore?.Add(interrupt); } /// /// Returns true if an interrupt of type is /// currently pending. /// public bool HasInterrupt() where T : struct, IInterrupt { return _interruptStore != null && _interruptStore.Has(); } /// /// Retrieves a pending interrupt for the handler's default Execute. /// internal bool TryGetPendingInterrupt(out T interrupt) where T : struct, IInterrupt { if (_interruptStore != null) return _interruptStore.TryGet(out interrupt); interrupt = default; return false; } /// /// Resolves and removes a pending interrupt. Called by the default /// implementation. /// internal void ResolveInterrupt() where T : struct, IInterrupt { _interruptStore?.Remove(); } // ── Iteration Lifecycle ────────────────────────────────────────── /// /// Begins a batching scope. Structural mutations (AddComponent, /// RemoveComponent, DestroyEntity) are buffered until the outermost /// call. Component accesses via /// are auto-tracked for modification. /// internal void BeginBatching() { _batchingDepth++; } /// /// Ends a batching scope. When the outermost scope ends, all /// buffered structural mutations are applied and auto-tracked /// component modifications are posted. /// internal void EndBatching() { _batchingDepth--; if (_batchingDepth == 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(); } } public 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; /// /// Gets the entity that backs the singleton of type , /// or if the singleton has not been set. /// internal Entity GetSingletonEntityOrNull() where T : struct { _singletonEntities.TryGetValue(typeof(T), out var entity); return entity; } /// /// Registers an existing entity as the backing entity for a singleton component. /// Used during deserialization to restore singleton state. /// internal void RegisterSingletonEntity(Type componentType, Entity entity) { _singletonEntities[componentType] = entity; } /// /// Returns true if the given entity is a singleton entity (backs any singleton component). /// internal bool IsSingletonEntity(Entity entity) { return _singletonEntities.ContainsValue(entity); } // ── Helpers ─────────────────────────────────────────────────────── private void ThrowIfNotAlive(Entity entity) { if (!_allocator.IsAlive(entity)) throw new InvalidOperationException($"Entity {entity} is not alive."); } // ── Cleanup ─────────────────────────────────────────────────────── public void Dispose() { if (_disposed) return; _disposed = true; _changes.Dispose(); } }