using Ghost.Core; using Misaki.HighPerformance.Collections; using Misaki.HighPerformance.LowLevel.Buffer; using Misaki.HighPerformance.LowLevel.Collections; using Misaki.HighPerformance.LowLevel.Utilities; using System.Diagnostics; namespace Ghost.Entities; internal struct EntityLocation : IComparable { public int archetypeID; public int chunkIndex; public int rowIndex; public readonly int CompareTo(EntityLocation other) { var archComp = chunkIndex.CompareTo(other.chunkIndex); if (archComp != 0) { return archComp; } var chunkComp = chunkIndex.CompareTo(other.chunkIndex); if (chunkComp != 0) { return chunkComp; } return rowIndex.CompareTo(other.rowIndex); } } /// /// A manager for creating, destroying, and managing entities and their components. /// /// /// All methods in this class are not thread-safe and all of them will cause structural changes if not mentioned otherwise. /// Use to defer structural changes to a safe point. /// Use to get a thread-local command buffer for multithreaded scenarios. /// public unsafe partial class EntityManager : IDisposable { private readonly World _world; private UnsafeSlotMap _entityLocations; private bool _disposed; public World World => _world; internal EntityManager(World world, int initialCapacity) { _world = world; _entityLocations = new UnsafeSlotMap(initialCapacity, Allocator.Persistent, AllocationOption.Clear); _scriptComponents = new SlotMap>(initialCapacity / 2); // _storages = new IManagedComponentStorage[16]; } ~EntityManager() { Dispose(); } internal ErrorStatus UpdateEntityLocation(Entity entity, Identifier newArchetypeID, int newChunkIndex, int newRowIndex) { ref var location = ref _entityLocations.GetElementReferenceAt(entity.ID, entity.Generation, out var exist); if (!exist) { return ErrorStatus.NotFound; } location.archetypeID = newArchetypeID; location.chunkIndex = newChunkIndex; location.rowIndex = newRowIndex; return ErrorStatus.None; } internal Result GetEntityLocation(Entity entity) { if (_entityLocations.TryGetElementAt(entity.ID, entity.Generation, out var location)) { return location; } return ErrorStatus.NotFound; } /// /// Create an entity with no components. /// /// The created entity. public Entity CreateEntity() { var entities = (Span)stackalloc Entity[1]; CreateEntities(entities); return entities[0]; } /// /// Create an entity with specified components. /// /// A set of component space IDs to add to the entities. /// The created entity. public Entity CreateEntity(ComponentSet set) { var entities = (Span)stackalloc Entity[1]; CreateEntities(entities, set); return entities[0]; } /// /// Create multiple entities with no components. /// /// The span to store the created entities. public void CreateEntities(Span entities) { ref var emptyArchetype = ref _world.ComponentManager.GetArchetypeReference(World.EmptyArchetypeID); emptyArchetype.AllocateEntity(out var chunkIndex, out var rowIndex); for (var i = 0; i < entities.Length; i++) { var id = _entityLocations.Add(new EntityLocation { archetypeID = World.EmptyArchetypeID, chunkIndex = chunkIndex, rowIndex = rowIndex }, out var generation); var entity = new Entity(id, generation); emptyArchetype.SetEntity(chunkIndex, rowIndex, entity); entities[i] = entity; } } /// /// Create multiple entities with no components. /// /// The number of entities to create. public void CreateEntities(int count) { ref var emptyArchetype = ref _world.ComponentManager.GetArchetypeReference(World.EmptyArchetypeID); emptyArchetype.AllocateEntity(out var chunkIndex, out var rowIndex); for (var i = 0; i < count; i++) { var id = _entityLocations.Add(new EntityLocation { archetypeID = World.EmptyArchetypeID, chunkIndex = chunkIndex, rowIndex = rowIndex }, out var generation); var entity = new Entity(id, generation); emptyArchetype.SetEntity(chunkIndex, rowIndex, entity); } } /// /// Create multiple entities with specified components. /// /// The span to store the created entities. /// A set of component space IDs to add to the entities. /// An array of the created entities. public void CreateEntities(Span entities, ComponentSet set) { var hash = set.GetHashCode(); var arcID = _world.ComponentManager.GetArchetypeIDBySignatureHash(hash); if (arcID.IsInvalid) { arcID = _world.ComponentManager.CreateArchetype(set.Components, hash); } ref var archetype = ref _world.ComponentManager.GetArchetypeReference(arcID); for (var i = 0; i < entities.Length; i++) { archetype.AllocateEntity(out var chunkIndex, out var rowIndex); var id = _entityLocations.Add(new EntityLocation { archetypeID = arcID, chunkIndex = chunkIndex, rowIndex = rowIndex }, out var generation); var entity = new Entity(id, generation); archetype.SetEntity(chunkIndex, rowIndex, entity); entities[i] = entity; } } /// /// Create multiple entities with specified components. /// /// The number of entities to create. /// A set of component space IDs to add to the entities. public void CreateEntities(int count, ComponentSet set) { var hash = set.GetHashCode(); var arcID = _world.ComponentManager.GetArchetypeIDBySignatureHash(hash); if (arcID.IsInvalid) { arcID = _world.ComponentManager.CreateArchetype(set.Components, hash); } ref var archetype = ref _world.ComponentManager.GetArchetypeReference(arcID); for (var i = 0; i < count; i++) { archetype.AllocateEntity(out var chunkIndex, out var rowIndex); var id = _entityLocations.Add(new EntityLocation { archetypeID = arcID, chunkIndex = chunkIndex, rowIndex = rowIndex }, out var generation); var entity = new Entity(id, generation); archetype.SetEntity(chunkIndex, rowIndex, entity); } } private void DestoryManagedEntityIfExists(ref readonly Archetype archetype, EntityLocation location) { var pManagedRef = archetype.GetComponentData(location.chunkIndex, location.rowIndex, ComponentTypeID.Value); if (pManagedRef != null) { DestroyManagedEntity(((ManagedEntityRef*)pManagedRef)->entity); } } /// /// Destroy the specified entity. /// /// The result status of the operation. public ErrorStatus DestroyEntity(Entity entity) { if (!_entityLocations.TryGetElementAt(entity.ID, entity.Generation, out var location)) { return ErrorStatus.NotFound; } ref var archetype = ref _world.ComponentManager.GetArchetypeReference(location.archetypeID); DestoryManagedEntityIfExists(in archetype, location); var r = archetype.RemoveEntity(location.chunkIndex, location.rowIndex); if (r != ErrorStatus.None) { return r; } if (!_entityLocations.Remove(entity.ID, entity.Generation)) { return ErrorStatus.NotFound; } return ErrorStatus.None; } /// /// Destroy the specified entities. /// /// The entities to destroy. public void DestroyEntities(ReadOnlySpan entities) { void RemoveManagedEntity(ReadOnlySpan rowIndicesCache, ref readonly Archetype archetype, int chunkIndex) { for (var j = 0; j < rowIndicesCache.Length; j++) { var rowIndex = rowIndicesCache[j]; var location = new EntityLocation { archetypeID = archetype.ID, chunkIndex = chunkIndex, rowIndex = rowIndex }; DestoryManagedEntityIfExists(in archetype, location); } } if (entities.Length == 0) { return; } using var scope = AllocationManager.CreateStackScope(); var batchDestroy = new UnsafeList(entities.Length, scope.AllocationHandle); var rowIndicesCache = new UnsafeList(32, scope.AllocationHandle); // 1. GATHER // Resolve all entities to their locations for (var i = 0; i < entities.Length; i++) { var entity = entities[i]; if (_entityLocations.TryGetElementAt(entity.ID, entity.Generation, out var location)) { batchDestroy.Add(location); } } if (batchDestroy.Count == 0) { return; } // 2. SORT // Sorting groups them by chunk automatically batchDestroy.AsSpan().Sort(); // 3. SWEEP // Iterate through the sorted list and batch process each chunk var firstLoc = batchDestroy[0]; var prevArchetypeID = firstLoc.archetypeID; var prevChunkIndex = firstLoc.chunkIndex; for (var i = 0; i < batchDestroy.Count; i++) { var loc = batchDestroy[i]; // Check if we have crossed a boundary (Different Chunk OR Different Archetype) var isNewBatch = (loc.chunkIndex != prevChunkIndex) || (loc.archetypeID != prevArchetypeID); if (isNewBatch) { // FLUSH PREVIOUS BATCH // We must retrieve the Archetype of the *Previous* batch, not the current 'loc' ref var prevArchetype = ref _world.ComponentManager.GetArchetypeReference(prevArchetypeID); // Remove Managed Entities first RemoveManagedEntity(rowIndicesCache.AsSpan(), in prevArchetype, prevChunkIndex); // Execute the hole-filling/swap logic prevArchetype.RemoveEntities(prevChunkIndex, rowIndicesCache.AsSpan()); // RESET rowIndicesCache.Clear(); prevArchetypeID = loc.archetypeID; prevChunkIndex = loc.chunkIndex; } rowIndicesCache.Add(loc.rowIndex); } // 4. FINAL FLUSH // Process the stragglers remaining in the cache if (rowIndicesCache.Count > 0) { ref var lastArchetype = ref _world.ComponentManager.GetArchetypeReference(prevArchetypeID); RemoveManagedEntity(rowIndicesCache.AsSpan(), in lastArchetype, prevChunkIndex); lastArchetype.RemoveEntities(prevChunkIndex, rowIndicesCache.AsSpan()); } // 5. Remove from Entity Locations for (var i = 0; i < entities.Length; i++) { var entity = entities[i]; _entityLocations.Remove(entity.ID, entity.Generation); } } /// /// Check if the specified entity exists. /// /// The entity to check. /// True if the entity exists, false otherwise. public bool Exists(Entity entity) { return _entityLocations.Contains(entity.ID, entity.Generation); } /// /// Create a singleton entity with the specified component. /// /// The component space ID of the singleton. /// Pointer to the component data. /// The result status of the operation. public ErrorStatus CreateSingleton(Identifier componentID, void* pComponent) { if (pComponent == null) { return ErrorStatus.InvalidArgument; } // Check if singleton already exists var signatureHash = ComponentRegistry.GetHashCode(componentID); var arcID = _world.ComponentManager.GetArchetypeIDBySignatureHash(signatureHash); if (arcID.IsValid) { return ErrorStatus.InvalidArgument; } arcID = _world.ComponentManager.CreateArchetype([componentID], signatureHash); ref var archetype = ref _world.ComponentManager.GetArchetypeReference(arcID); archetype.AllocateEntity(out var chunkIndex, out var rowIndex); var id = _entityLocations.Add(new EntityLocation { archetypeID = arcID, chunkIndex = chunkIndex, rowIndex = rowIndex }, out var generation); var entity = new Entity(id, generation); archetype.SetEntity(chunkIndex, rowIndex, entity); archetype.SetComponentData(chunkIndex, rowIndex, componentID, pComponent); return ErrorStatus.None; } /// /// Create a singleton entity with the specified component. /// /// The component space. /// The component data. /// The result status of the operation. public ErrorStatus CreateSingleton(T component = default) where T : unmanaged, IComponent { return CreateSingleton(ComponentTypeID.Value, &component); } /// /// Get a pointer to the singleton component data. /// /// The component space ID of the singleton. /// Pointer to the component data, or null if not found. public void* GetSingleton(Identifier componentID) { var signatureHash = ComponentRegistry.GetHashCode(componentID); var arcID = _world.ComponentManager.GetArchetypeIDBySignatureHash(signatureHash); if (arcID.IsInvalid) { return null; } ref var archetype = ref _world.ComponentManager.GetArchetypeReference(arcID); var layoutResult = archetype.GetLayout(componentID); if (layoutResult.Error != ErrorStatus.None) { return null; } var chunk = archetype._chunks[0]; var ptr = chunk.GetUnsafePtr() + layoutResult.Value.offset; return ptr; } /// /// Get a reference to the singleton component data. /// /// The component space. /// Reference to the component data. null ref if not found. public ref T GetSingleton() where T : unmanaged, IComponent { var ptr = GetSingleton(ComponentTypeID.Value); return ref *(T*)ptr; // This will return null ref if ptr is null. } private static void CopyData(ref Archetype oldArch, int oldChunk, int oldRow, ref Archetype newArch, int newChunk, int newRow) { // Iterate every component space in the OLD archetype for (var i = 0; i < oldArch._layouts.Count; i++) { var layout = oldArch._layouts[i]; var src = oldArch._chunks[oldChunk].GetUnsafePtr() + layout.offset + (layout.size * oldRow); var r = newArch.GetLayout(layout.componentID); if (r.Error != ErrorStatus.None) { // New archetype does not have this component, skip it. // This can happen when removing components. continue; } var dst = newArch._chunks[newChunk].GetUnsafePtr() + r.Value.offset + (layout.size * newRow); MemoryUtility.MemCpy(dst, src, (nuint)layout.size); } } /// /// Add a component to the specified entity. /// /// The entity to add the component to. /// The component space ID to add. /// Pointer to the component data. /// The result status of the operation. public ErrorStatus AddComponent(Entity entity, Identifier componentID, void* pComponent) { // Find current location ref var location = ref _entityLocations.GetElementReferenceAt(entity.ID, entity.Generation, out var exist); if (!exist) { return ErrorStatus.NotFound; } // Build new archetype signature ref var oldArchetype = ref _world.ComponentManager.GetArchetypeReference(location.archetypeID); var oldSignature = oldArchetype._signature; if (oldSignature.IsSet(componentID)) { // Component already exists return ErrorStatus.InvalidArgument; } var newArcID = oldArchetype.GetEdgeAdd(componentID); if (newArcID.IsInvalid) { var largestComponentID = Math.Max(oldSignature.Count, componentID); var length = UnsafeBitSet.RequiredLength(largestComponentID + 1); Span bits = stackalloc uint[length]; bits.Clear(); var newSignature = new SpanBitSet(bits); var oldIt = oldSignature.GetIterator(); var compCount = 0; while (oldIt.Next(out var index)) { newSignature.SetBit(index); compCount++; } compCount++; newSignature.SetBit(componentID); // Find or create new archetype var newSignatureHash = newSignature.GetHashCode(); newArcID = _world.ComponentManager.GetArchetypeIDBySignatureHash(newSignatureHash); if (newArcID.IsInvalid) { // Create new archetype Span> componentTypeIDs = stackalloc Identifier[compCount]; var newIt = newSignature.GetIterator(); var i = 0; while (newIt.Next(out var index)) { componentTypeIDs[i++] = index; } newArcID = _world.ComponentManager.CreateArchetype(componentTypeIDs, newSignatureHash); } oldArchetype.AddEdgeAdd(componentID, newArcID); } // Move entity data ref var newArchetype = ref _world.ComponentManager.GetArchetypeReference(newArcID); newArchetype.AllocateEntity(out var newChunkIndex, out var newRowIndex); CopyData(ref oldArchetype, location.chunkIndex, location.rowIndex, ref newArchetype, newChunkIndex, newRowIndex); newArchetype.SetEntity(newChunkIndex, newRowIndex, entity); newArchetype.SetComponentData(newChunkIndex, newRowIndex, componentID, pComponent); var r = oldArchetype.RemoveEntity(location.chunkIndex, location.rowIndex); Debug.Assert(r == ErrorStatus.None); // We assert it because the entity should exist if the whole system is consistent. if (r != ErrorStatus.None) { return r; } // Update location location.archetypeID = newArcID; location.chunkIndex = newChunkIndex; location.rowIndex = newRowIndex; return ErrorStatus.None; } /// /// Add a component to the specified entity. /// /// The component space. /// The entity to add the component to. /// The component data. /// The result status of the operation. public ErrorStatus AddComponent(Entity entity, T component = default) where T : unmanaged, IComponent { return AddComponent(entity, ComponentTypeID.Value, &component); } /// /// Remove a component from the specified entity. /// /// The entity to remove the component from. /// The component space ID to remove. /// The result status of the operation. public ErrorStatus RemoveComponent(Entity entity, Identifier componentID) { // Find current location ref var location = ref _entityLocations.GetElementReferenceAt(entity.ID, entity.Generation, out var exist); if (!exist) { return ErrorStatus.NotFound; } // Build new archetype signature ref var oldArchetype = ref _world.ComponentManager.GetArchetypeReference(location.archetypeID); var oldSignature = oldArchetype._signature; var newArcID = oldArchetype.GetEdgeRemove(componentID); if (newArcID.IsInvalid) { var largestComponentID = Math.Max(oldSignature.Count, componentID); var length = UnsafeBitSet.RequiredLength(largestComponentID + 1); Span bits = stackalloc uint[length]; bits.Clear(); var newSignature = new SpanBitSet(bits); var oldIt = oldSignature.GetIterator(); var compCount = 0; while (oldIt.Next(out var index)) { if (index != componentID) { newSignature.SetBit(index); compCount++; } } // Find or create new archetype var newSignatureHash = newSignature.GetHashCode(); newArcID = _world.ComponentManager.GetArchetypeIDBySignatureHash(newSignatureHash); if (newArcID.IsInvalid) { // Create new archetype Span> componentTypeIDs = stackalloc Identifier[compCount]; var newIt = newSignature.GetIterator(); var i = 0; while (newIt.Next(out var index)) { componentTypeIDs[i++] = index; } newArcID = _world.ComponentManager.CreateArchetype(componentTypeIDs, newSignatureHash); } oldArchetype.AddEdgeRemove(componentID, newArcID); } // Move entity data ref var newArchetype = ref _world.ComponentManager.GetArchetypeReference(newArcID); newArchetype.AllocateEntity(out var newChunkIndex, out var newRowIndex); CopyData(ref oldArchetype, location.chunkIndex, location.rowIndex, ref newArchetype, newChunkIndex, newRowIndex); newArchetype.SetEntity(newChunkIndex, newRowIndex, entity); var r = oldArchetype.RemoveEntity(location.chunkIndex, location.rowIndex); Debug.Assert(r == ErrorStatus.None); // We assert it because the entity should exist if the whole system is consistent. if (r != ErrorStatus.None) { return r; } var pManagedRef = oldArchetype.GetComponentData(location.chunkIndex, location.rowIndex, ComponentTypeID.Value); if (pManagedRef != null) { DestroyManagedEntity(((ManagedEntityRef*)pManagedRef)->entity); } // Update location location.archetypeID = newArcID; location.chunkIndex = newChunkIndex; location.rowIndex = newRowIndex; return ErrorStatus.None; } /// /// Remove a component from the specified entity. /// /// The component space. /// The entity to remove the component from. /// The result status of the operation. public ErrorStatus RemoveComponent(Entity entity) where T : unmanaged, IComponent { return RemoveComponent(entity, ComponentTypeID.Value); } /// /// Set the component data for the specified entity. /// /// The entity to set the component data for. /// The component space ID to set. /// Pointer to the component data. /// The result status of the operation. public ErrorStatus SetComponent(Entity entity, Identifier componentID, void* pComponent) { if (!_entityLocations.TryGetElementAt(entity.ID, entity.Generation, out var location)) { return ErrorStatus.NotFound; } ref var archetype = ref _world.ComponentManager.GetArchetypeReference(location.archetypeID); archetype.SetComponentData(location.chunkIndex, location.rowIndex, componentID, pComponent); return ErrorStatus.None; } /// /// Set the component data for the specified entity. /// /// The component space. /// The entity to set the component data for. /// The component data. public ErrorStatus SetComponent(Entity entity, T component) where T : unmanaged, IComponent { return SetComponent(entity, ComponentTypeID.Value, &component); } /// /// Get a pointer to the component data for the specified entity. /// /// The entity to get the component data for. /// The component space ID to get. /// Pointer to the component data, or null if not found. public void* GetComponent(Entity entity, Identifier componentID) { if (!_entityLocations.TryGetElementAt(entity.ID, entity.Generation, out var location)) { return null; } ref var archetype = ref _world.ComponentManager.GetArchetypeReference(location.archetypeID); return archetype.GetComponentData(location.chunkIndex, location.rowIndex, componentID); } /// /// Get a reference to the component data for the specified entity. /// /// The component space. /// The entity to get the component data for. /// Reference to the component data. null ref if not found. public ref T GetComponent(Entity entity) where T : unmanaged, IComponent { var ptr = GetComponent(entity, ComponentTypeID.Value); return ref *(T*)ptr; // This will return null ref if ptr is null. } /// /// Check if the specified entity has the specified component. /// /// The entity to check. /// The component space ID to check. /// True if the entity has the component, false otherwise. public bool HasComponent(Entity entity, Identifier componentID) { if (!_entityLocations.TryGetElementAt(entity.ID, entity.Generation, out var location)) { return false; } ref var archetype = ref _world.ComponentManager.GetArchetypeReference(location.archetypeID); return archetype.HasComponent(componentID); } /// /// Check if the specified entity has the specified component. /// /// The component space. /// The entity to check. /// True if the entity has the component, false otherwise. public bool HasComponent(Entity entity) where T : unmanaged, IComponent { return HasComponent(entity, ComponentTypeID.Value); } /// /// Set the enabled state of an enableable component for the specified entity. /// /// The entity to set the enabled state for. /// The component space ID of the enableable component.True to enable the component, false to disable it. /// The result status of the operation. public ErrorStatus SetEnabled(Entity entity, Identifier componentID, bool enabled) { if (!_entityLocations.TryGetElementAt(entity.ID, entity.Generation, out var location)) { return ErrorStatus.NotFound; } ref var archetype = ref _world.ComponentManager.GetArchetypeReference(location.archetypeID); var chunkIndex = location.chunkIndex; var rowIndex = location.rowIndex; var layoutResult = archetype.GetLayout(componentID); if (layoutResult.Error != ErrorStatus.None) { return layoutResult.Error; } ref var chunk = ref archetype.GetChunkReference(chunkIndex); var chunkBase = chunk.GetUnsafePtr(); var maskBase = chunkBase + layoutResult.Value.enableBitsOffset; var byteIndex = rowIndex >> Chunk.BIT_SHIFT; var bitIndex = rowIndex & Chunk.BIT_ALIGNMENT_MINUS_ONE; if (enabled) { maskBase[byteIndex] |= (byte)(1 << bitIndex); } else { maskBase[byteIndex] &= (byte)~(1 << bitIndex); } return ErrorStatus.None; } /// /// Set the enabled state of an enableable component for the specified entity. /// /// The enableable component space. /// The entity to set the enabled state for. /// True to enable the component, false to disable it.The result status of the operation. public ErrorStatus SetEnabled(Entity entity, bool enabled) where T : unmanaged, IEnableableComponent { return SetEnabled(entity, ComponentTypeID.Value, enabled); } public void Dispose() { if (_disposed) { return; } _entityLocations.Dispose(); _disposed = true; GC.SuppressFinalize(this); } }