using Ghost.Core; using Misaki.HighPerformance.LowLevel.Buffer; using Misaki.HighPerformance.LowLevel.Collections; using Misaki.HighPerformance.LowLevel.Utilities; using System.Diagnostics; namespace Ghost.Entities; /// /// 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 struct EntityLocation { public Identifier archetypeID; public int chunkIndex; public int rowIndex; } private readonly World _world; private UnsafeSlotMap _entityLocations; private bool _disposed; internal EntityManager(World world, int initialCapacity) { _world = world; _entityLocations = new UnsafeSlotMap(initialCapacity, Allocator.Persistent, AllocationOption.Clear); } ~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; } /// /// Create an entity with no components. /// /// The created entity. public Entity CreateEntity() { var entities = (Span)stackalloc Entity[1]; CreateEntities(1, entities); return entities[0]; } /// /// Create an entity with specified components. /// /// The component type IDs to add to the entity. /// The created entity. public Entity CreateEntity(params ReadOnlySpan> componentTypeIDs) { var entities = (Span)stackalloc Entity[1]; CreateEntities(1, entities, componentTypeIDs); return entities[0]; } /// /// Create multiple entities with no components. /// /// The number of entities to create. /// The span to store the created entities. public void CreateEntities(int count, Span entities) { ref var emptyArchetype = ref _world.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); 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.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 number of entities to create. /// The allocator to use for the returned array. /// The component type IDs to add to the entities. /// An array of the created entities. public void CreateEntities(int count, Span entities, params ReadOnlySpan> componentTypeIDs) { var signatureHash = ComponentRegister.GetHashCode(componentTypeIDs); var arcID = _world.GetArchetypeIDBySignatureHash(signatureHash); if (arcID.IsNotValid) { arcID = _world.CreateArchetype(componentTypeIDs, signatureHash); } ref var archetype = ref _world.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); entities[i] = entity; } } /// /// Create multiple entities with specified components. /// /// The number of entities to create. /// The component type IDs to add to the entities. public void CreateEntities(int count, params ReadOnlySpan> componentTypeIDs) { var signatureHash = ComponentRegister.GetHashCode(componentTypeIDs); var arcID = _world.GetArchetypeIDBySignatureHash(signatureHash); if (arcID.IsNotValid) { arcID = _world.CreateArchetype(componentTypeIDs, signatureHash); } ref var archetype = ref _world.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); } } /// /// 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.GetArchetypeReference(location.archetypeID); 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; } /// /// 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 type 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 = ComponentRegister.GetHashCode(componentID); var arcID = _world.GetArchetypeIDBySignatureHash(signatureHash); if (arcID.IsValid) { return ErrorStatus.InvalidArgument; } arcID = _world.CreateArchetype([componentID], signatureHash); ref var archetype = ref _world.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 type. /// 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 type ID of the singleton. /// Pointer to the component data, or null if not found. public void* GetSingleton(Identifier componentID) { var signatureHash = ComponentRegister.GetHashCode(componentID); var arcID = _world.GetArchetypeIDBySignatureHash(signatureHash); if (arcID.IsNotValid) { return null; } ref var archetype = ref _world.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 type. /// 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 type 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); Debug.Assert(r.Error == ErrorStatus.None); // This should always be true if the system is consistent. if (r.Error != ErrorStatus.None) { 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 type 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.GetArchetypeReference(location.archetypeID); var oldSignature = oldArchetype._signature; // TODO: Check edge cache first. var newArcID = oldArchetype.GetEdgeAdd(componentID); if (newArcID.IsNotValid) { 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.GetArchetypeIDBySignatureHash(newSignatureHash); if (newArcID.IsNotValid) { // 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.CreateArchetype(componentTypeIDs, newSignatureHash); } oldArchetype.AddEdgeAdd(componentID, newArcID); } // Move entity data ref var newArchetype = ref _world.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 type. /// 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 type 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.GetArchetypeReference(location.archetypeID); var oldSignature = oldArchetype._signature; var newArcID = oldArchetype.GetEdgeRemove(componentID); if (newArcID.IsNotValid) { 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.GetArchetypeIDBySignatureHash(newSignatureHash); if (newArcID.IsNotValid) { // 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.CreateArchetype(componentTypeIDs, newSignatureHash); } oldArchetype.AddEdgeRemove(componentID, newArcID); } // Move entity data ref var newArchetype = ref _world.GetArchetypeReference(newArcID); newArchetype.AllocateEntity(out var newChunkIndex, out var 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; } // Update location location.archetypeID = newArcID; location.chunkIndex = newChunkIndex; location.rowIndex = newRowIndex; return ErrorStatus.None; } /// /// Remove a component from the specified entity. /// /// The component type. /// 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 type 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.GetArchetypeReference(location.archetypeID); archetype.SetComponentData(location.chunkIndex, location.rowIndex, componentID, pComponent); return ErrorStatus.None; } /// /// Set the component data for the specified entity. /// /// The component type. /// 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 type 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.GetArchetypeReference(location.archetypeID); return archetype.GetComponentData(location.chunkIndex, location.rowIndex, componentID); } /// /// Get a reference to the component data for the specified entity. /// /// The component type. /// 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 type 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.GetArchetypeReference(location.archetypeID); return archetype.HasComponent(componentID); } /// /// Check if the specified entity has the specified component. /// /// The component type. /// 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 type 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.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 type. /// 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); } }