From 3bbf485fcebd8d48f9e3da5733a9452b30a7311e Mon Sep 17 00:00:00 2001 From: Misaki Date: Thu, 4 Dec 2025 15:03:01 +0900 Subject: [PATCH] Update EntityManager and Archetype --- Ghost.ArcEntities/Archetype.cs | 93 +++++--- Ghost.ArcEntities/Component.cs | 62 +++-- Ghost.ArcEntities/Entity.cs | 27 +-- Ghost.ArcEntities/EntityCommandBuffer.cs | 6 +- Ghost.ArcEntities/EntityManager.cs | 219 +++++++++++++----- Ghost.ArcEntities/EntityQuery.cs | 4 +- Ghost.ArcEntities/Utility.cs | 5 + Ghost.ArcEntities/World.cs | 12 +- Ghost.Core/Handle.cs | 23 ++ Ghost.Core/Result.cs | 30 ++- Ghost.Entities.Test/ArcEntityTest.cs | 42 ++++ .../Ghost.Entities.Test.csproj | 1 + Ghost.Entities.Test/Program.cs | 4 +- 13 files changed, 397 insertions(+), 131 deletions(-) create mode 100644 Ghost.ArcEntities/Utility.cs create mode 100644 Ghost.Entities.Test/ArcEntityTest.cs diff --git a/Ghost.ArcEntities/Archetype.cs b/Ghost.ArcEntities/Archetype.cs index a6e45f3..cb62460 100644 --- a/Ghost.ArcEntities/Archetype.cs +++ b/Ghost.ArcEntities/Archetype.cs @@ -43,18 +43,19 @@ internal unsafe struct Chuck : IDisposable internal struct Edge { - public int componentID; + public Identifier componentID; public Identifier targetArchetype; } +internal struct ComponentMemoryLayout +{ + public int offset; + public int size; + public Identifier componentID; +} + internal unsafe struct Archetype : IIdentifierType, IDisposable { - private struct ComponentMemoryLayout - { - public int offset; - public int size; - } - private readonly Identifier _id; private readonly Identifier _worldID; @@ -63,18 +64,25 @@ internal unsafe struct Archetype : IIdentifierType, IDisposable private UnsafeArray _layouts; private UnsafeArray _componentIDToOffset; + // TODO: Is hash map better? private UnsafeList _edgesAdd; private UnsafeList _edgesRemove; + private int _hash; private int _entityCapacity; private int _maxComponentID; private int _entityIdsOffset; + public Identifier ID => _id; + public UnsafeBitSet Signature => _signature; + public UnsafeList Chunks => _chunks; + public UnsafeArray Layouts => _layouts; + public int EntityCapacity => _entityCapacity; public int ChunkCount => _chunks.Count; - public Archetype(Identifier id, Identifier worldID, ReadOnlySpan componentIds) + public Archetype(Identifier id, Identifier worldID, ReadOnlySpan> componentIds) { _id = id; _worldID = worldID; @@ -109,11 +117,13 @@ internal unsafe struct Archetype : IIdentifierType, IDisposable _edgesAdd = new UnsafeList(4, Allocator.Persistent); _edgesRemove = new UnsafeList(4, Allocator.Persistent); + _hash = _signature.GetHashCode(); + var pComponents = stackalloc ComponentInfo[componentIds.Length]; for (var i = 0; i < componentIds.Length; i++) { _signature.SetBit(componentIds[i]); - pComponents[i] = ComponentRegister.s_registeredComponents[componentIds[i]]; + pComponents[i] = ComponentRegister.GetComponentInfo(componentIds[i]); } CalculateLayout(new Span(pComponents, componentIds.Length)); @@ -129,10 +139,11 @@ internal unsafe struct Archetype : IIdentifierType, IDisposable var maxComponentID = 0; for (var i = 0; i < components.Length; i++) { - bytesPerEntity += components[i].size; - if (components[i].id > maxComponentID) + var comp = components[i]; + bytesPerEntity += comp.size; + if (comp.id > maxComponentID) { - maxComponentID = components[i].id; + maxComponentID = comp.id; } } @@ -144,6 +155,7 @@ internal unsafe struct Archetype : IIdentifierType, IDisposable _componentIDToOffset.AsSpan().Fill(-1); components.Sort((a, b) => b.alignment.CompareTo(a.alignment)); + var tempOffsets = stackalloc int[components.Length]; while (_entityCapacity > 0) { @@ -155,8 +167,6 @@ internal unsafe struct Archetype : IIdentifierType, IDisposable _entityIdsOffset = currentOffset; currentOffset += _entityCapacity * entitySize; - var tempOffsets = stackalloc int[components.Length]; - for (var i = 0; i < components.Length; i++) { var size = components[i].size; @@ -180,7 +190,8 @@ internal unsafe struct Archetype : IIdentifierType, IDisposable _layouts[i] = new ComponentMemoryLayout { offset = tempOffsets[i], - size = components[i].size + size = components[i].size, + componentID = components[i].id }; _componentIDToOffset[components[i].id] = tempOffsets[i]; @@ -227,6 +238,19 @@ internal unsafe struct Archetype : IIdentifierType, IDisposable MemoryUtility.MemCpy(&entity, pEntity, (nuint)sizeof(Entity)); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void SetComponentData(int chunkIndex, int rowIndex, Identifier componentID, void* pComponent) + { + var offset = _componentIDToOffset[componentID]; + var chunk = _chunks[chunkIndex]; + + var chunkBase = chunk.GetUnsafePtr(); + var size = ComponentRegister.GetComponentInfo(componentID).size; + var dst = chunkBase + offset + (size * rowIndex); + + MemoryUtility.MemCpy(pComponent, dst, (nuint)size); + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public ref Chuck GetChunkReference(int index) { @@ -292,7 +316,13 @@ internal unsafe struct Archetype : IIdentifierType, IDisposable } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void AddEdgeAdd(int componentID, Identifier targetArchetype) + public bool HasComponent(Identifier componentID) + { + return _signature.IsSet(componentID); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void AddEdgeAdd(Identifier componentID, Identifier targetArchetype) { _edgesAdd.Add(new Edge { @@ -302,21 +332,22 @@ internal unsafe struct Archetype : IIdentifierType, IDisposable } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Result GetEdgeAdd(int componentID) + public Identifier GetEdgeAdd(Identifier componentID) { for (var i = 0; i < _edgesAdd.Count; i++) { - if (_edgesAdd[i].componentID == componentID) + var edge = _edgesAdd[i]; + if (edge.componentID == componentID) { - return Result.Create(_edgesAdd[i].targetArchetype.value, ResultStatus.Success); + return edge.targetArchetype; } } - return Result.Create(-1, ResultStatus.NotFound); + return Identifier.Invalid; } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void AddEdgeRemove(int componentID, Identifier targetArchetype) + public void AddEdgeRemove(Identifier componentID, Identifier targetArchetype) { _edgesRemove.Add(new Edge { @@ -326,22 +357,23 @@ internal unsafe struct Archetype : IIdentifierType, IDisposable } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Result GetEdgeRemove(int componentID) + public Identifier GetEdgeRemove(Identifier componentID) { for (var i = 0; i < _edgesRemove.Count; i++) { - if (_edgesRemove[i].componentID == componentID) + var edge = _edgesRemove[i]; + if (edge.componentID == componentID) { - return Result.Create(_edgesRemove[i].targetArchetype.value, ResultStatus.Success); + return edge.targetArchetype; } } - return Result.Create(-1, ResultStatus.NotFound); + return Identifier.Invalid; } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public UnsafeArray GetComponentArray(int chunkIndex) - where T : unmanaged + public Span GetComponentArray(int chunkIndex) + where T : unmanaged, IComponent { var id = ComponentTypeID.value; if (id >= _componentIDToOffset.Count) @@ -356,7 +388,12 @@ internal unsafe struct Archetype : IIdentifierType, IDisposable } var chunk = _chunks[chunkIndex]; - return new UnsafeArray((T*)((byte*)chunk.GetUnsafePtr() + offset), _entityCapacity); + return new Span((T*)((byte*)chunk.GetUnsafePtr() + offset), chunk.Count); + } + + public override int GetHashCode() + { + return _hash; } public void Dispose() diff --git a/Ghost.ArcEntities/Component.cs b/Ghost.ArcEntities/Component.cs index acbdef3..dfa3cc6 100644 --- a/Ghost.ArcEntities/Component.cs +++ b/Ghost.ArcEntities/Component.cs @@ -1,43 +1,51 @@ +using Ghost.Core; +using Misaki.HighPerformance.LowLevel.Collections; using Misaki.HighPerformance.LowLevel.Utilities; namespace Ghost.ArcEntities; +public interface IComponent : IIdentifierType +{ +} + public struct ComponentInfo { // public FixedText64 stableName; // Do we actually need this? public int size; public int alignment; - public int id; + public Identifier id; } -internal static unsafe class ComponentTypeID - where T : unmanaged +public static unsafe class ComponentTypeID + where T : unmanaged, IComponent { - public static readonly int value = ComponentRegister.GetOrRegisterComponent(); + public static readonly Identifier value = ComponentRegister.GetOrRegisterComponent(); } internal static class ComponentRegister { private static int s_nextComponentTypeID = 0; - private static Dictionary s_typeHandleToID = new(); + private static Dictionary> s_typeHandleToID = new(); - internal static List s_registeredComponents = new(); - internal static Dictionary s_nameToRuntimeID = new(); + private static List s_registeredComponents = new(); + private static Dictionary> s_nameToRuntimeID = new(); - internal unsafe static int GetOrRegisterComponent() - where T : unmanaged + public unsafe static Identifier GetOrRegisterComponent() + where T : unmanaged, IComponent { var typeHandle = typeof(T).TypeHandle.Value; lock (s_registeredComponents) { - if (s_typeHandleToID.TryGetValue(typeHandle, out int existingID)) + if (s_typeHandleToID.TryGetValue(typeHandle, out var existingID)) { return existingID; } - int newID = s_nextComponentTypeID++; - string stableName = typeof(T).FullName ?? typeof(T).Name; + var newID = new Identifier(s_nextComponentTypeID); + s_nextComponentTypeID++; + + var stableName = typeof(T).FullName ?? typeof(T).Name; var info = new ComponentInfo { @@ -47,8 +55,8 @@ internal static class ComponentRegister id = newID, }; - while (s_registeredComponents.Count <= newID) s_registeredComponents.Add(default); - s_registeredComponents[newID] = info; + while (s_registeredComponents.Count <= newID.value) s_registeredComponents.Add(default); + s_registeredComponents[newID.value] = info; s_typeHandleToID[typeHandle] = newID; s_nameToRuntimeID[stableName] = newID; @@ -57,8 +65,32 @@ internal static class ComponentRegister } } - internal static ComponentInfo GetComponentInfo(int typeId) + public static ComponentInfo GetComponentInfo(Identifier typeId) { return s_registeredComponents[typeId]; } + + public static int GetHashCode(ReadOnlySpan> componentTypeIDs) + { + var largestID = 0; + foreach (var id in componentTypeIDs) + { + if (id.value > largestID) + { + largestID = id.value; + } + } + + var length = UnsafeBitSet.RequiredLength(largestID + 1); + var bits = (Span)stackalloc uint[length]; + bits.Clear(); + + var bitSet = new SpanBitSet(bits); + foreach (var id in componentTypeIDs) + { + bitSet.SetBit(id.value); + } + + return bitSet.GetHashCode(); + } } diff --git a/Ghost.ArcEntities/Entity.cs b/Ghost.ArcEntities/Entity.cs index 3f0e134..081f763 100644 --- a/Ghost.ArcEntities/Entity.cs +++ b/Ghost.ArcEntities/Entity.cs @@ -4,26 +4,26 @@ using System.Runtime.InteropServices; namespace Ghost.ArcEntities; [StructLayout(LayoutKind.Sequential, Size = 8)] -public struct Entity : IEquatable, IComparable +public readonly struct Entity : IEquatable, IComparable { public const EntityID INVALID_ID = -1; - private EntityID _id; - private GenerationID _generation; + private readonly EntityID _id; + private readonly GenerationID _generation; - public readonly EntityID ID + public EntityID ID { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => _id; } - public readonly GenerationID Generation + public GenerationID Generation { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => _generation; } - public readonly bool IsValid + public bool IsValid { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => ID != INVALID_ID; @@ -41,27 +41,24 @@ public struct Entity : IEquatable, IComparable _generation = generation; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal void IncrementGeneration() => _generation++; - - public readonly bool Equals(Entity other) + public bool Equals(Entity other) { return _id == other._id && _generation == other._generation; } - public readonly int CompareTo(Entity other) + public int CompareTo(Entity other) { return _id.CompareTo(other._id); } - public override readonly bool Equals(object? obj) + public override bool Equals(object? obj) { return obj is Entity other && Equals(other); } - public override readonly int GetHashCode() + public override int GetHashCode() { - return _id.GetHashCode(); + return _id ^ _generation << 16; } public static bool operator ==(Entity left, Entity right) @@ -74,7 +71,7 @@ public struct Entity : IEquatable, IComparable return !(left == right); } - public override readonly string ToString() + public override string ToString() { return $"Entity {{ Index: {ID}, Generation: {Generation} }}"; } diff --git a/Ghost.ArcEntities/EntityCommandBuffer.cs b/Ghost.ArcEntities/EntityCommandBuffer.cs index ce9df02..10645bb 100644 --- a/Ghost.ArcEntities/EntityCommandBuffer.cs +++ b/Ghost.ArcEntities/EntityCommandBuffer.cs @@ -23,7 +23,7 @@ public unsafe class EntityCommandBuffer : IDisposable } private readonly EntityManager _entityManager; - private UnsafeList _commands; + private UnsafeList _commands; // TODO: Maybe use UnsafeArray directly? public EntityCommandBuffer(EntityManager entityManager) { @@ -58,7 +58,7 @@ public unsafe class EntityCommandBuffer : IDisposable } public void AddComponent(Entity entity, T component) - where T : unmanaged + where T : unmanaged, IComponent { var data = new UnsafeArray(sizeof(T), Misaki.HighPerformance.LowLevel.Buffer.Allocator.Persistent); MemoryUtility.MemCpy(&component, data.GetUnsafePtr(), (nuint)sizeof(T)); @@ -75,7 +75,7 @@ public unsafe class EntityCommandBuffer : IDisposable } public void RemoveComponent(Entity entity) - where T : unmanaged + where T : unmanaged, IComponent { var command = new Command { diff --git a/Ghost.ArcEntities/EntityManager.cs b/Ghost.ArcEntities/EntityManager.cs index d1b9a52..d4ce56c 100644 --- a/Ghost.ArcEntities/EntityManager.cs +++ b/Ghost.ArcEntities/EntityManager.cs @@ -1,10 +1,12 @@ using Ghost.Core; using Misaki.HighPerformance.LowLevel.Buffer; using Misaki.HighPerformance.LowLevel.Collections; +using Misaki.HighPerformance.LowLevel.Utilities; +using System.Diagnostics; namespace Ghost.ArcEntities; -public class EntityManager : IDisposable +public unsafe class EntityManager : IDisposable { private struct EntityLocation { @@ -16,10 +18,51 @@ public class EntityManager : IDisposable private World _world; private UnsafeSlotMap _entityLocations; - internal EntityManager(World world) + internal EntityManager(World world, int initialCapacity) { _world = world; - _entityLocations = new UnsafeSlotMap(16, Allocator.Persistent); + _entityLocations = new UnsafeSlotMap(initialCapacity, Allocator.Persistent); + } + + internal ResultStatus 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 ResultStatus.NotFound; + } + + location.archetypeID = newArchetypeID; + location.chunkIndex = newChunkIndex; + location.rowIndex = newRowIndex; + + return ResultStatus.Success; + } + + public Entity CreateEntity(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); + 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); + + return entity; } public Entity CreateEntity() @@ -41,7 +84,7 @@ public class EntityManager : IDisposable return entity; } - public ResultStatus RemoveEntity(Entity entity) + public ResultStatus DestoryEntity(Entity entity) { if (!_entityLocations.TryGetElementAt(entity.ID, entity.Generation, out var location)) { @@ -63,23 +106,23 @@ public class EntityManager : IDisposable return ResultStatus.Success; } - internal ResultStatus UpdateEntityLocation(Entity entity, Identifier newArchetypeID, int newChunkIndex, int newRowIndex) + private static void CopyData(ref Archetype oldArch, int oldChunk, int oldRow, + ref Archetype newArch, int newChunk, int newRow) { - ref var location = ref _entityLocations.GetElementReferenceAt(entity.ID, entity.Generation, out var exist); - if (!exist) + // Iterate every component type in the OLD archetype + for (int i = 0; i < oldArch.Layouts.Count; i++) { - return ResultStatus.NotFound; + var layout = oldArch.Layouts[i]; + + var src = oldArch.Chunks[oldChunk].GetUnsafePtr() + layout.offset + (layout.size * oldRow); + var newOffset = newArch.GetOffset(layout.componentID); // O(1) Lookup + var dst = oldArch.Chunks[oldChunk].GetUnsafePtr() + newOffset + (layout.size * newRow); + + MemoryUtility.MemCpy(src, dst, (nuint)layout.size); } - - location.archetypeID = newArchetypeID; - location.chunkIndex = newChunkIndex; - location.rowIndex = newRowIndex; - - return ResultStatus.Success; } - public ResultStatus AddComponent(Entity entity) - where T : unmanaged + public ResultStatus AddComponent(Entity entity, Identifier componentID, void* component) { // Find current location ref var location = ref _entityLocations.GetElementReferenceAt(entity.ID, entity.Generation, out var exist); @@ -89,62 +132,82 @@ public class EntityManager : IDisposable } // Build new archetype signature - ref var archetype = ref _world.GetArchetypeReference(location.archetypeID); - var currentSignature = archetype.Signature; + ref var oldArchetype = ref _world.GetArchetypeReference(location.archetypeID); + var oldSignature = oldArchetype.Signature; - var compID = ComponentTypeID.value; - - var largestComponentID = Math.Max(currentSignature.Count, compID); - var length = UnsafeBitSet.RequiredLength(largestComponentID + 1); - - Span bits = stackalloc uint[length]; - bits.Clear(); - - var newSignature = new SpanBitSet(bits); - - var i = 0; - var compCount = 0; - do - { - i = currentSignature.NextSetBit(i); - if (i == -1) - { - break; - } - - newSignature.SetBit(i); - compCount++; - } while (true); - - compCount++; - newSignature.SetBit(compID); - - // Find or create new archetype - var newSignatureHash = newSignature.GetHashCode(); - var newArcID = _world.GetArchetypeIDBySignatureHash(newSignatureHash); + // TODO: Check edge cache first. + var newArcID = oldArchetype.GetEdgeAdd(componentID); if (newArcID.IsNotValid) { - // Create new archetype - Span componentTypeIDs = stackalloc int[compCount]; - componentTypeIDs[0] = compID; + var largestComponentID = Math.Max(oldSignature.Count, componentID); + var length = UnsafeBitSet.RequiredLength(largestComponentID + 1); - do + Span bits = stackalloc uint[length]; + bits.Clear(); + + var newSignature = new SpanBitSet(bits); + + var iterator = 0; + var compCount = 0; + while (true) { - i = currentSignature.NextSetBit(i); - if (i == -1) + int bit = oldSignature.NextSetBit(iterator); + if (bit == -1) { break; } - componentTypeIDs[--compCount] = i; - } while (true); + newSignature.SetBit(bit); + iterator = bit + 1; + compCount++; + } - _world.CreateArchetype(componentTypeIDs, newSignatureHash); + 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]; + componentTypeIDs[0] = componentID; + + iterator = 0; + while (true) + { + int bit = oldSignature.NextSetBit(iterator); + if (bit == -1) + { + break; + } + + componentTypeIDs[--compCount] = bit; + iterator = bit + 1; + } + + _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, component); + + var r = oldArchetype.RemoveEntity(location.chunkIndex, location.rowIndex); + Debug.Assert(r == ResultStatus.Success); // We assert it because the entity should exist if the whole system is consistent. + // if (r != ResultStatus.Success) + // { + // return r; + // } // Update location location.archetypeID = newArcID; @@ -154,6 +217,48 @@ public class EntityManager : IDisposable return ResultStatus.Success; } + public ResultStatus AddComponent(Entity entity, ref T component) + where T : unmanaged, IComponent + { + return AddComponent(entity, ComponentTypeID.value, UnsafeUtility.AddressOf(ref component)); + } + + public ResultStatus SetComponentData(Entity entity, Identifier componentID, void* pComponent) + { + if (!_entityLocations.TryGetElementAt(entity.ID, entity.Generation, out var location)) + { + return ResultStatus.NotFound; + } + + ref var archetype = ref _world.GetArchetypeReference(location.archetypeID); + archetype.SetComponentData(location.chunkIndex, location.rowIndex, componentID, pComponent); + + return ResultStatus.Success; + } + + public ResultStatus SetComponentData(Entity entity, ref T component) + where T : unmanaged, IComponent + { + return SetComponentData(entity, ComponentTypeID.value, UnsafeUtility.AddressOf(ref component)); + } + + 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); + } + + public bool HasComponent(Entity entity) + where T : unmanaged, IComponent + { + return HasComponent(entity, ComponentTypeID.value); + } + public void Dispose() { _entityLocations.Dispose(); diff --git a/Ghost.ArcEntities/EntityQuery.cs b/Ghost.ArcEntities/EntityQuery.cs index 611ca60..f1e36bf 100644 --- a/Ghost.ArcEntities/EntityQuery.cs +++ b/Ghost.ArcEntities/EntityQuery.cs @@ -1,8 +1,8 @@ namespace Ghost.ArcEntities; public unsafe class EntityQuery - where T1 : unmanaged - where T2 : unmanaged + where T1 : unmanaged, IComponent + where T2 : unmanaged, IComponent { // The Cache Struct struct ArchetypeCache diff --git a/Ghost.ArcEntities/Utility.cs b/Ghost.ArcEntities/Utility.cs new file mode 100644 index 0000000..5aefd8b --- /dev/null +++ b/Ghost.ArcEntities/Utility.cs @@ -0,0 +1,5 @@ +namespace Ghost.ArcEntities; + +public static class Utility +{ +} diff --git a/Ghost.ArcEntities/World.cs b/Ghost.ArcEntities/World.cs index 4fedd76..afdbcdc 100644 --- a/Ghost.ArcEntities/World.cs +++ b/Ghost.ArcEntities/World.cs @@ -57,11 +57,13 @@ public partial class World : IIdentifierType, IDisposable, IEquatable private UnsafeList _archetypes; private UnsafeHashMap> _archetypeLookup; // Signature Hash to Archetype ID private EntityManager _entityManager; + private EntityCommandBuffer _entityCommandBuffer; private bool _disposed = false; public Identifier ID => _id; public EntityManager EntityManager => _entityManager; + public EntityCommandBuffer EntityCommandBuffer => _entityCommandBuffer; private World(Identifier id, int entityCapacity) { @@ -70,10 +72,11 @@ public partial class World : IIdentifierType, IDisposable, IEquatable _archetypes = new UnsafeList(16, Allocator.Persistent); _archetypeLookup = new UnsafeHashMap>(16, Allocator.Persistent); - _entityManager = new EntityManager(this); + _entityManager = new EntityManager(this, entityCapacity); + _entityCommandBuffer = new EntityCommandBuffer(_entityManager); // Create the empty archetype - CreateArchetype(ReadOnlySpan.Empty, 0); + CreateArchetype(ReadOnlySpan>.Empty, 0); } ~World() @@ -81,11 +84,12 @@ public partial class World : IIdentifierType, IDisposable, IEquatable Dispose(); } - internal Identifier CreateArchetype(ReadOnlySpan componentTypeIDs, int signatureHash) + internal Identifier CreateArchetype(ReadOnlySpan> componentTypeIDs, int signatureHash) { var arcID = new Identifier(_archetypes.Count); _archetypes.Add(new Archetype(arcID, _id, componentTypeIDs)); _archetypeLookup.Add(signatureHash, arcID); + return arcID; } @@ -136,6 +140,8 @@ public partial class World : IIdentifierType, IDisposable, IEquatable return; } + _entityManager.Dispose(); + _archetypes.Dispose(); _archetypeLookup.Dispose(); diff --git a/Ghost.Core/Handle.cs b/Ghost.Core/Handle.cs index c5ae6aa..7307554 100644 --- a/Ghost.Core/Handle.cs +++ b/Ghost.Core/Handle.cs @@ -106,6 +106,29 @@ public readonly struct Identifier { return !a.Equals(b); } + + public static bool operator <(Identifier a, Identifier b) + { + return a.value < b.value; + } + + public static bool operator >(Identifier a, Identifier b) + { + return a.value > b.value; + } + + public static bool operator <=(Identifier a, Identifier b) + { + return a.value <= b.value; + } + + public static bool operator >=(Identifier a, Identifier b) + { + return a.value >= b.value; + } + + public static implicit operator int(Identifier id) => id.value; + public static implicit operator Identifier(int value) => new Identifier(value); } public readonly struct Key diff --git a/Ghost.Core/Result.cs b/Ghost.Core/Result.cs index 7db37c0..79e2ab0 100644 --- a/Ghost.Core/Result.cs +++ b/Ghost.Core/Result.cs @@ -152,6 +152,14 @@ public readonly ref struct RefResult public static class ResultExtensions { + public static void ThrowIfFailed(this ResultStatus result) + { + if (result != ResultStatus.Success) + { + throw new InvalidOperationException($"Operation failed: {result}"); + } + } + public static void ThrowIfFailed(this Result result) { if (!result.IsSuccess) @@ -170,11 +178,6 @@ public static class ResultExtensions return result.Value; } - public static T? GetValueOrDefault(this Result result, T? defaultValue = default) - { - return result.IsSuccess ? result.Value : defaultValue; - } - public static T GetValueOrThrow(this Result result, S expect) where S : Enum { @@ -186,12 +189,29 @@ public static class ResultExtensions return result.Value; } + public static T? GetValueOrDefault(this Result result, T? defaultValue = default) + { + return result.IsSuccess ? result.Value : defaultValue; + } + public static T? GetValueOrDefault(this Result result, S expect, T? defaultValue = default) where S : Enum { return (result.Status?.Equals(expect) ?? false) ? defaultValue : result.Value; } + public static bool TryGetValue(this Result result, out T value) + { + if (result.IsSuccess) + { + value = result.Value; + return true; + } + + value = default!; + return false; + } + public static Result OnSuccess(this Result result, Action action) { if (result.IsSuccess) diff --git a/Ghost.Entities.Test/ArcEntityTest.cs b/Ghost.Entities.Test/ArcEntityTest.cs new file mode 100644 index 0000000..5ba065f --- /dev/null +++ b/Ghost.Entities.Test/ArcEntityTest.cs @@ -0,0 +1,42 @@ +using Ghost.Test.Core; +using Ghost.ArcEntities; +using Misaki.HighPerformance.Mathematics; + +namespace Ghost.Entities.Test; + +public partial class ArcEntityTest : ITest +{ + private Ghost.ArcEntities.World _world = null!; + + public void Setup() + { + _world = Ghost.ArcEntities.World.Create(); + } + + public void Run() + { + var entity1 = _world.EntityManager.CreateEntity(ComponentTypeID.value); + Console.WriteLine($"{entity1} Has Transform: {_world.EntityManager.HasComponent(entity1)}"); + var mesh = new MeshData { index = 1 }; + _world.EntityManager.AddComponent(entity1, ref mesh); + Console.WriteLine($"{entity1} Has Mesh: {_world.EntityManager.HasComponent(entity1)}"); + + _world.EntityManager.DestoryEntity(entity1); + Console.WriteLine($"{entity1} Has Transform: {_world.EntityManager.HasComponent(entity1)}"); + } + + public void Cleanup() + { + _world.Dispose(); + } +} + +public struct TransformData : IComponent +{ + public float3 position; +} + +public struct MeshData : IComponent +{ + public int index; +} diff --git a/Ghost.Entities.Test/Ghost.Entities.Test.csproj b/Ghost.Entities.Test/Ghost.Entities.Test.csproj index 65612d3..9ab4993 100644 --- a/Ghost.Entities.Test/Ghost.Entities.Test.csproj +++ b/Ghost.Entities.Test/Ghost.Entities.Test.csproj @@ -9,6 +9,7 @@ + diff --git a/Ghost.Entities.Test/Program.cs b/Ghost.Entities.Test/Program.cs index b8fabd9..b60f772 100644 --- a/Ghost.Entities.Test/Program.cs +++ b/Ghost.Entities.Test/Program.cs @@ -1,5 +1,3 @@ - -using Ghost.Entities.Test; using Ghost.Test.Core; -TestRunner.Run(); +TestRunner.Run();