From 30c1d99959f2a187a981f9737bb18c5b25286ef1 Mon Sep 17 00:00:00 2001 From: Misaki Date: Fri, 5 Dec 2025 22:38:11 +0900 Subject: [PATCH] Support enableable components and query enhancements - Upgraded `Misaki.HighPerformance.LowLevel` to v1.2.8. - Added `IEquatable` to `Handle` and `Identifier`. - Improved `Result` extensions with `[CallerArgumentExpression]`. - Introduced `SetEnabled` in `EntityManager` to toggle components. - Refactored `Chunk` and `Archetype` for enableable components. - Added `EntityQueryMask` for filtering enabled/disabled components. - Enhanced `QueryBuilder` with new filtering methods (`WithAll`, etc.). - Improved `EntityQuery.ForEach` with entity validation. --- Ghost.Core/Ghost.Core.csproj | 2 +- Ghost.Core/Handle.cs | 6 +- Ghost.Core/Result.cs | 32 +- Ghost.Entities.Test/ArcEntityTest.cs | 24 +- Ghost.Entities/Archetype.cs | 164 ++++---- Ghost.Entities/Component.cs | 18 +- Ghost.Entities/EntityManager.cs | 56 ++- Ghost.Entities/EntityQuery.cs | 60 --- Ghost.Entities/Exceptions.cs | 5 + Ghost.Entities/Ghost.Entities.csproj | 24 ++ Ghost.Entities/Query.cs | 390 +++++++++++++----- .../Templates/EntityQuery.ForEach.gen.cs | 369 +++++++++++------ .../Templates/EntityQuery.ForEach.tt | 94 ++--- .../Templates/QueryBuilder.With.gen.cs | 278 +++++++++++++ Ghost.Entities/Templates/QueryBuilder.With.tt | 110 +++++ Ghost.Entities/World.cs | 19 +- 16 files changed, 1203 insertions(+), 448 deletions(-) delete mode 100644 Ghost.Entities/EntityQuery.cs create mode 100644 Ghost.Entities/Exceptions.cs create mode 100644 Ghost.Entities/Templates/QueryBuilder.With.gen.cs create mode 100644 Ghost.Entities/Templates/QueryBuilder.With.tt diff --git a/Ghost.Core/Ghost.Core.csproj b/Ghost.Core/Ghost.Core.csproj index eae157a..68c0063 100644 --- a/Ghost.Core/Ghost.Core.csproj +++ b/Ghost.Core/Ghost.Core.csproj @@ -21,7 +21,7 @@ - + diff --git a/Ghost.Core/Handle.cs b/Ghost.Core/Handle.cs index 7307554..ddd0071 100644 --- a/Ghost.Core/Handle.cs +++ b/Ghost.Core/Handle.cs @@ -4,7 +4,7 @@ public interface IHandleType; public interface IIdentifierType; public interface IKeyType; -public readonly struct Handle +public readonly struct Handle : IEquatable> where T : IHandleType { public readonly int id; @@ -57,7 +57,7 @@ public readonly struct Handle } } -public readonly struct Identifier +public readonly struct Identifier : IEquatable> where T : IIdentifierType { public readonly int value; @@ -74,7 +74,7 @@ public readonly struct Identifier public readonly override int GetHashCode() { - return value.GetHashCode(); + return value; } public readonly override bool Equals(object? obj) diff --git a/Ghost.Core/Result.cs b/Ghost.Core/Result.cs index 79e2ab0..6b26cc3 100644 --- a/Ghost.Core/Result.cs +++ b/Ghost.Core/Result.cs @@ -1,3 +1,6 @@ +using System.Runtime.CompilerServices; +using TerraFX.Interop.DirectX; + namespace Ghost.Core; public readonly struct Result @@ -152,38 +155,38 @@ public readonly ref struct RefResult public static class ResultExtensions { - public static void ThrowIfFailed(this ResultStatus result) + public static void ThrowIfFailed(this ResultStatus result, [CallerArgumentExpression(nameof(result))] string? op = null) { if (result != ResultStatus.Success) { - throw new InvalidOperationException($"Operation failed: {result}"); + throw new InvalidOperationException($"{op} failed: {result}"); } } - public static void ThrowIfFailed(this Result result) + public static void ThrowIfFailed(this Result result, [CallerArgumentExpression(nameof(result))] string? op = null) { if (!result.IsSuccess) { - throw new InvalidOperationException($"Operation failed: {result.Message}"); + throw new InvalidOperationException($"{op} failed: {result.Message}"); } } - public static T GetValueOrThrow(this Result result) + public static T GetValueOrThrow(this Result result, [CallerArgumentExpression(nameof(result))] string? op = null) { if (!result.IsSuccess) { - throw new InvalidOperationException($"Operation failed: {result.Message}"); + throw new InvalidOperationException($"{op} failed: {result.Message}"); } return result.Value; } - public static T GetValueOrThrow(this Result result, S expect) + public static T GetValueOrThrow(this Result result, S expect, [CallerArgumentExpression(nameof(result))] string? op = null) where S : Enum { if (!EqualityComparer.Default.Equals(result.Status, expect)) { - throw new InvalidOperationException($"Operation failed: expected status {expect}, but got {result.Status}"); + throw new InvalidOperationException($"{op} failed: expected status {expect}, but got {result.Status}"); } return result.Value; @@ -212,6 +215,19 @@ public static class ResultExtensions return false; } + public static bool TryGetValue(this Result result, S expect, out T value) + where S : Enum + { + if (EqualityComparer.Default.Equals(result.Status, expect)) + { + 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 index 387d0c6..a86197d 100644 --- a/Ghost.Entities.Test/ArcEntityTest.cs +++ b/Ghost.Entities.Test/ArcEntityTest.cs @@ -15,28 +15,28 @@ public partial class ArcEntityTest : ITest public void Run() { var entity1 = _world.EntityManager.CreateEntity(ComponentTypeID.value); - Console.WriteLine(entity1); - _world.EntityManager.AddComponent(entity1, new Mesh { index = 1 }); + _world.EntityManager.AddComponent(entity1, new Mesh { index = 1 }); var queryID = new QueryBuilder().WithAll().Build(_world); ref var query = ref _world.GetEntityQueryReference(queryID); + query.ForEach((ref t) => + { + t.position = new float3(1, 2, 3); + }); + foreach (var chunk in query.GetChunkIterator()) { var transforms = chunk.GetComponentData(); var entities = chunk.GetEntities(); - - for (var i = 0; i < chunk.Count; i++) + var bits = chunk.GetEnableBits(); + + var it = bits.GetIterator(); + while (it.Next(out var index) && index < chunk.Count) { - Console.WriteLine($"Entity {entities[i]} Position: {transforms[i].position}"); - transforms[i].position = new float3(1, 2, 3); + Console.WriteLine($"Entity {entities[index]} Updated Position: {transforms[index].position}"); } } - - query.ForEach((e, ref t) => - { - Console.WriteLine($"Entity {e} Updated Position: {t.position}"); - }); } public void Cleanup() @@ -45,7 +45,7 @@ public partial class ArcEntityTest : ITest } } -public struct Transform : IComponent +public struct Transform : IEnableableComponent { public float3 position; } diff --git a/Ghost.Entities/Archetype.cs b/Ghost.Entities/Archetype.cs index 2b2d68d..10807ee 100644 --- a/Ghost.Entities/Archetype.cs +++ b/Ghost.Entities/Archetype.cs @@ -9,10 +9,13 @@ namespace Ghost.Entities; internal unsafe struct Chunk : IDisposable { public const int CHUNK_SIZE = 16384; // 16 KB + public const int BIT_ALIGNMENT = 8; + public const int BIT_SHIFT = 3; // log2(BIT_ALIGNMENT) + public const int BIT_ALIGNMENT_MINUS_ONE = BIT_ALIGNMENT - 1; private UnsafeArray _data; private int _count; - private int _capacity; + private readonly int _capacity; public int Count { @@ -41,21 +44,22 @@ internal unsafe struct Chunk : IDisposable } } -internal struct Edge -{ - public Identifier componentID; - public int targetArchetype; // can't use Identifier because cycle causer -} - -internal struct ComponentMemoryLayout -{ - public int offset; - public int size; - public Identifier componentID; -} - internal unsafe struct Archetype : IIdentifierType, IDisposable { + internal struct ComponentMemoryLayout + { + public int componentID; + public int size; + public int offset; + public int enableBitsOffset; // TODO: Support enableable component + } + + private struct Edge + { + public int componentID; + public int targetArchetype; // can't use Identifier because cycle causer + } + private readonly Identifier _id; private readonly Identifier _worldID; @@ -63,7 +67,7 @@ internal unsafe struct Archetype : IIdentifierType, IDisposable internal UnsafeList _chunks; internal UnsafeArray _layouts; - private UnsafeArray _componentIDToOffset; + private UnsafeArray _componentIDToLayoutIndex; // TODO: Is hash map better? private UnsafeList _edgesAdd; private UnsafeList _edgesRemove; @@ -84,16 +88,16 @@ internal unsafe struct Archetype : IIdentifierType, IDisposable _id = id; _worldID = worldID; + _chunks = new UnsafeList(4, Allocator.Persistent); + _edgesAdd = new UnsafeList(4, Allocator.Persistent); + _edgesRemove = new UnsafeList(4, Allocator.Persistent); + if (componentIds.IsEmpty) { _signature = new UnsafeBitSet(1, Allocator.Persistent, AllocationOption.Clear); - _chunks = new UnsafeList(4, Allocator.Persistent); - - _edgesAdd = new UnsafeList(4, Allocator.Persistent); - _edgesRemove = new UnsafeList(4, Allocator.Persistent); + _hash = 0; _signature.ClearAll(); - _entityCapacity = Chunk.CHUNK_SIZE / sizeof(Entity); return; @@ -109,28 +113,23 @@ internal unsafe struct Archetype : IIdentifierType, IDisposable } _signature = new UnsafeBitSet(highestComponentID + 1, Allocator.Persistent, AllocationOption.Clear); - _chunks = new UnsafeList(4, Allocator.Persistent); - - _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.GetComponentInfo(componentIds[i]); - } - - CalculateLayout(new Span(pComponents, componentIds.Length)); + CalculateLayout(componentIds); } - private void CalculateLayout(Span components) + private void CalculateLayout(ReadOnlySpan> componentIds) { var entitySize = sizeof(Entity); var entityAlign = (int)MemoryUtility.AlignOf(); + var components = (Span)stackalloc ComponentInfo[componentIds.Length]; + for (var i = 0; i < componentIds.Length; i++) + { + _signature.SetBit(componentIds[i]); + components[i] = ComponentRegister.GetComponentInfo(componentIds[i]); + } + // Calculate total size per entity to get an initial capacity estimate var bytesPerEntity = entitySize; var maxComponentID = 0; @@ -147,12 +146,13 @@ internal unsafe struct Archetype : IIdentifierType, IDisposable _maxComponentID = maxComponentID; _entityCapacity = Chunk.CHUNK_SIZE / bytesPerEntity; _layouts = new UnsafeArray(components.Length, Allocator.Persistent); - _componentIDToOffset = new UnsafeArray(_maxComponentID + 1, Allocator.Persistent); + _componentIDToLayoutIndex = new UnsafeArray(_maxComponentID + 1, Allocator.Persistent); - _componentIDToOffset.AsSpan().Fill(-1); + _componentIDToLayoutIndex.AsSpan().Fill(-1); components.Sort((a, b) => b.alignment.CompareTo(a.alignment)); var tempOffsets = stackalloc int[components.Length]; + var tempBitmaskOffsets = stackalloc int[components.Length]; while (_entityCapacity > 0) { @@ -173,6 +173,19 @@ internal unsafe struct Archetype : IIdentifierType, IDisposable tempOffsets[i] = currentOffset; currentOffset += _entityCapacity * size; + var bitmaskOffset = -1; + if (components[i].isEnableable) + { + var bitmaskSize = (_entityCapacity + Chunk.BIT_ALIGNMENT_MINUS_ONE) / Chunk.BIT_ALIGNMENT; + // Reserve space for the bitmask (1 bit per entity) + + currentOffset = (currentOffset + Chunk.BIT_ALIGNMENT_MINUS_ONE) & ~Chunk.BIT_ALIGNMENT_MINUS_ONE; // Align + bitmaskOffset = currentOffset; + currentOffset += bitmaskSize; + } + + tempBitmaskOffsets[i] = bitmaskOffset; + if (currentOffset > Chunk.CHUNK_SIZE) { fits = false; @@ -188,10 +201,11 @@ internal unsafe struct Archetype : IIdentifierType, IDisposable { offset = tempOffsets[i], size = components[i].size, - componentID = components[i].id + componentID = components[i].id, + enableBitsOffset = tempBitmaskOffsets[i], }; - _componentIDToOffset[components[i].id] = tempOffsets[i]; + _componentIDToLayoutIndex[components[i].id] = i; } return; @@ -219,6 +233,18 @@ internal unsafe struct Archetype : IIdentifierType, IDisposable // Need to allocate a new chunk var newChunk = new Chunk(Chunk.CHUNK_SIZE, _entityCapacity); + // Set all enable to true by default for enableable components + for (var i = 0; i < _layouts.Count; i++) + { + var layout = _layouts[i]; + if (layout.enableBitsOffset != -1) + { + var pChunk = newChunk.GetUnsafePtr(); + var pBits = pChunk + layout.enableBitsOffset; + MemoryUtility.MemSet(pBits, 0xFF, (nuint)((_entityCapacity + Chunk.BIT_ALIGNMENT_MINUS_ONE) / Chunk.BIT_ALIGNMENT)); + } + } + rowIndex = 0; newChunk.Count++; chunkIndex = _chunks.Count; @@ -237,9 +263,15 @@ internal unsafe struct Archetype : IIdentifierType, IDisposable } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly void SetComponentData(int chunkIndex, int rowIndex, Identifier componentID, void* pComponent) + public readonly ResultStatus SetComponentData(int chunkIndex, int rowIndex, Identifier componentID, void* pComponent) { - var offset = _componentIDToOffset[componentID]; + var r = GetLayout(componentID); + if (r.Status != ResultStatus.Success) + { + return r.Status; + } + + var offset = r.Value.offset; var chunk = _chunks[chunkIndex]; var chunkBase = chunk.GetUnsafePtr(); @@ -247,19 +279,27 @@ internal unsafe struct Archetype : IIdentifierType, IDisposable var dst = chunkBase + offset + (size * rowIndex); MemoryUtility.MemCpy(pComponent, dst, (nuint)size); + + return ResultStatus.Success; } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly void* GetComponentDataPtr(int chunkIndex, int rowIndex, Identifier componentID) + public readonly ResultStatus GetComponentDataPtr(int chunkIndex, int rowIndex, Identifier componentID, void** ppv) { - var offset = _componentIDToOffset[componentID]; + var r = GetLayout(componentID); + if (r.Status != ResultStatus.Success) + { + return r.Status; + } + + var offset = r.Value.offset; var chunk = _chunks[chunkIndex]; var chunkBase = chunk.GetUnsafePtr(); var size = ComponentRegister.GetComponentInfo(componentID).size; - var dst = chunkBase + offset + (size * rowIndex); + *ppv = chunkBase + offset + (size * rowIndex); - return dst; + return ResultStatus.Success; } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -269,14 +309,20 @@ internal unsafe struct Archetype : IIdentifierType, IDisposable } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly int GetOffset(int componentId) + public readonly Result GetLayout(int componentID) { - if (componentId >= _componentIDToOffset.Count) + if (componentID >= _componentIDToLayoutIndex.Count) { - return -1; + return Result.Create(default(ComponentMemoryLayout), ResultStatus.InvalidArgument); } - return _componentIDToOffset[componentId]; + var layoutIndex = _componentIDToLayoutIndex[componentID]; + if (layoutIndex == -1) + { + return Result.Create(default(ComponentMemoryLayout), ResultStatus.NotFound); + } + + return Result.Create(_layouts[layoutIndex], ResultStatus.Success); } public ResultStatus RemoveEntity(int chunkIndex, int rowIndex) @@ -382,26 +428,6 @@ internal unsafe struct Archetype : IIdentifierType, IDisposable return Identifier.Invalid; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly Span GetComponentArray(int chunkIndex) - where T : unmanaged, IComponent - { - var id = ComponentTypeID.value; - if (id >= _componentIDToOffset.Count) - { - return default; - } - - var offset = _componentIDToOffset[id]; - if (offset == -1) - { - return default; - } - - var chunk = _chunks[chunkIndex]; - return new Span((T*)((byte*)chunk.GetUnsafePtr() + offset), chunk.Count); - } - public override readonly int GetHashCode() { return _hash; @@ -419,7 +445,7 @@ internal unsafe struct Archetype : IIdentifierType, IDisposable _signature.Dispose(); _chunks.Dispose(); - _componentIDToOffset.Dispose(); + _componentIDToLayoutIndex.Dispose(); _layouts.Dispose(); _edgesAdd.Dispose(); _edgesRemove.Dispose(); diff --git a/Ghost.Entities/Component.cs b/Ghost.Entities/Component.cs index 9b6a72c..1945ce2 100644 --- a/Ghost.Entities/Component.cs +++ b/Ghost.Entities/Component.cs @@ -8,12 +8,17 @@ public interface IComponent : IIdentifierType { } +public interface IEnableableComponent : IComponent +{ +} + public struct ComponentInfo { // public FixedText64 stableName; // Do we actually need this? + public Identifier id; public int size; public int alignment; - public Identifier id; + public bool isEnableable; } public static class ComponentTypeID @@ -25,12 +30,12 @@ public static class ComponentTypeID internal static class ComponentRegister { private static int s_nextComponentTypeID = 0; - private static Dictionary> s_typeHandleToID = new(); + private static readonly Dictionary> s_typeHandleToID = new(); - private static List s_registeredComponents = new(); - private static Dictionary> s_nameToRuntimeID = new(); + private static readonly List s_registeredComponents = new(); + private static readonly Dictionary> s_nameToRuntimeID = new(); - public unsafe static Identifier GetOrRegisterComponent() + public static unsafe Identifier GetOrRegisterComponent() where T : unmanaged, IComponent { var typeHandle = typeof(T).TypeHandle.Value; @@ -50,9 +55,10 @@ internal static class ComponentRegister var info = new ComponentInfo { // stableName = new FixedText64(stableName), + id = newID, size = sizeof(T), alignment = (int)MemoryUtility.AlignOf(), - id = newID, + isEnableable = typeof(IEnableableComponent).IsAssignableFrom(typeof(T)) }; while (s_registeredComponents.Count <= newID.value) s_registeredComponents.Add(default); diff --git a/Ghost.Entities/EntityManager.cs b/Ghost.Entities/EntityManager.cs index f550f4c..26f4d9f 100644 --- a/Ghost.Entities/EntityManager.cs +++ b/Ghost.Entities/EntityManager.cs @@ -2,6 +2,7 @@ using Ghost.Core; using Misaki.HighPerformance.LowLevel.Buffer; using Misaki.HighPerformance.LowLevel.Collections; using Misaki.HighPerformance.LowLevel.Utilities; +using System; using System.Diagnostics; namespace Ghost.Entities; @@ -121,8 +122,14 @@ public unsafe class EntityManager : IDisposable var layout = oldArch._layouts[i]; var src = oldArch._chunks[oldChunk].GetUnsafePtr() + layout.offset + (layout.size * oldRow); - var newOffset = newArch.GetOffset(layout.componentID); // O(1) Looku - var dst = newArch._chunks[newChunk].GetUnsafePtr() + newOffset + (layout.size * newRow); + var layoutResult = newArch.GetLayout(layout.componentID); + Debug.Assert(layoutResult.Status == ResultStatus.Success); // This should always be true if the system is consistent. + if (layoutResult.Status != ResultStatus.Success) + { + continue; + } + + var dst = newArch._chunks[newChunk].GetUnsafePtr() + layoutResult.Value.offset + (layout.size * newRow); MemoryUtility.MemCpy(src, dst, (nuint)layout.size); } @@ -210,10 +217,10 @@ public unsafe class EntityManager : IDisposable 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; - // } + if (r != ResultStatus.Success) + { + return r; + } // Update location location.archetypeID = newArcID; @@ -265,6 +272,43 @@ public unsafe class EntityManager : IDisposable return HasComponent(entity, ComponentTypeID.value); } + public ResultStatus SetEnabled(Entity entity, bool enabled) + where T : unmanaged, IEnableableComponent + { + if (!_entityLocations.TryGetElementAt(entity.ID, entity.Generation, out var location)) + { + return ResultStatus.NotFound; + } + + ref var archetype = ref _world.GetArchetypeReference(location.archetypeID); + var chunkIndex = location.chunkIndex; + var rowIndex = location.rowIndex; + + var layoutResult = archetype.GetLayout(ComponentTypeID.value); + if (layoutResult.Status != ResultStatus.Success) + { + return layoutResult.Status; + } + + 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 ResultStatus.Success; + } + public void Dispose() { if (_disposed) diff --git a/Ghost.Entities/EntityQuery.cs b/Ghost.Entities/EntityQuery.cs deleted file mode 100644 index 41f7a82..0000000 --- a/Ghost.Entities/EntityQuery.cs +++ /dev/null @@ -1,60 +0,0 @@ -namespace Ghost.Entities; - -public unsafe class EntityQueryy - where T1 : unmanaged, IComponent - where T2 : unmanaged, IComponent -{ - // The Cache Struct - struct ArchetypeCache - { - public Archetype Archetype; - public int Offset1; // Offset for T0 - public int Offset2; // Offset for T2 - } - - private List _cache = new(); - - internal void AddMatchingArchetype(Archetype archetype) - { - // We look up the offsets ONCE when the archetype is registered - int off1 = archetype.GetOffset(ComponentTypeID.value); - int off2 = archetype.GetOffset(ComponentTypeID.value); - - _cache.Add(new ArchetypeCache - { - Archetype = archetype, - Offset1 = off1, - Offset2 = off2 - }); - } - - // The Optimized Iteration Loop - public void ForEach(delegate* action) - { - foreach (var cache in _cache) - { - var archetype = cache.Archetype; - var offset1 = cache.Offset1; - var offset2 = cache.Offset2; - - // Iterate Chunks - for (int i = 0; i < archetype.ChunkCount; i++) - { - var chunk = archetype.GetChunkReference(i); - var chunkPtr = chunk.GetUnsafePtr(); - var count = chunk.Count; - - // POINTER MATH ONLY - NO LOOKUPS - // We use the pre-calculated integer offsets - T1* ptr1 = (T1*)(chunkPtr + offset1); - T2* ptr2 = (T2*)(chunkPtr + offset2); - - // The hot loop - for (int k = 0; k < count; k++) - { - action(ref ptr1[k], ref ptr2[k]); - } - } - } - } -} diff --git a/Ghost.Entities/Exceptions.cs b/Ghost.Entities/Exceptions.cs new file mode 100644 index 0000000..b33d616 --- /dev/null +++ b/Ghost.Entities/Exceptions.cs @@ -0,0 +1,5 @@ +namespace Ghost.Entities; + +public class EntityNotFoundException : Exception +{ +} \ No newline at end of file diff --git a/Ghost.Entities/Ghost.Entities.csproj b/Ghost.Entities/Ghost.Entities.csproj index 0f6ca75..ca01b7d 100644 --- a/Ghost.Entities/Ghost.Entities.csproj +++ b/Ghost.Entities/Ghost.Entities.csproj @@ -7,6 +7,16 @@ True + + True + True + + + + True + True + + @@ -22,6 +32,11 @@ True True + + True + True + QueryBuilder.With.tt + @@ -37,6 +52,10 @@ ForEach.gen.cs TextTemplatingFileGenerator + + TextTemplatingFileGenerator + QueryBuilder.With.gen.cs + @@ -54,6 +73,11 @@ True ForEach.tt + + True + True + QueryBuilder.With.tt + diff --git a/Ghost.Entities/Query.cs b/Ghost.Entities/Query.cs index 8a4b9e2..e053778 100644 --- a/Ghost.Entities/Query.cs +++ b/Ghost.Entities/Query.cs @@ -5,42 +5,86 @@ using System.Runtime.CompilerServices; namespace Ghost.Entities; -public struct EntityQueryMask : IDisposable +public struct EntityQueryMask : IDisposable, IEquatable { - public UnsafeBitSet all; - public UnsafeBitSet any; - public UnsafeBitSet absent; + public UnsafeBitSet structuralAll; + public UnsafeBitSet structuralAny; + public UnsafeBitSet structuralAbsent; + + public UnsafeBitSet requireEnabled; + public UnsafeBitSet requireDisabled; + public UnsafeBitSet rejectIfEnabled; [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly bool Matches(UnsafeBitSet archetypeSignature) + public readonly bool Matches(ref readonly UnsafeBitSet archetypeSignature) { - return (!all.IsCreated || all.All(archetypeSignature)) - && (!absent.IsCreated || absent.None(archetypeSignature)) - && (!any.IsCreated || any.Count == 0 || any.Any(archetypeSignature)); + return (!structuralAll.IsCreated || structuralAll.All(archetypeSignature)) + && (!structuralAbsent.IsCreated || structuralAbsent.None(archetypeSignature)) + && (!structuralAny.IsCreated || structuralAny.Count == 0 || structuralAny.Any(archetypeSignature)); } - public readonly override int GetHashCode() + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override readonly int GetHashCode() { var hash = 17; - if (all.IsCreated) hash = hash * 23 + all.GetHashCode(); - if (absent.IsCreated) hash = hash * 23 + absent.GetHashCode(); - if (any.IsCreated) hash = hash * 23 + any.GetHashCode(); + if (structuralAll.IsCreated) hash = hash * 23 + structuralAll.GetHashCode(); + if (structuralAbsent.IsCreated) hash = hash * 23 + structuralAbsent.GetHashCode(); + if (structuralAny.IsCreated) hash = hash * 23 + structuralAny.GetHashCode(); + if (requireEnabled.IsCreated) hash = hash * 23 + requireEnabled.GetHashCode(); + if (requireDisabled.IsCreated) hash = hash * 23 + requireDisabled.GetHashCode(); + if (rejectIfEnabled.IsCreated) hash = hash * 23 + rejectIfEnabled.GetHashCode(); return hash; } public void Dispose() { - all.Dispose(); - any.Dispose(); - absent.Dispose(); + structuralAll.Dispose(); + structuralAny.Dispose(); + structuralAbsent.Dispose(); + + requireEnabled.Dispose(); + requireDisabled.Dispose(); + rejectIfEnabled.Dispose(); + } + + public readonly bool Equals(EntityQueryMask other) + { + return structuralAll.Equals(other.structuralAll) + && structuralAny.Equals(other.structuralAny) + && structuralAbsent.Equals(other.structuralAbsent) + && requireEnabled.Equals(other.requireEnabled) + && requireDisabled.Equals(other.requireDisabled) + && rejectIfEnabled.Equals(other.rejectIfEnabled); + } + + public override readonly bool Equals(object? obj) + { + return obj is EntityQueryMask mask && Equals(mask); + } + + public static bool operator ==(EntityQueryMask left, EntityQueryMask right) + { + return left.Equals(right); + } + + public static bool operator !=(EntityQueryMask left, EntityQueryMask right) + { + return !(left == right); } } public unsafe partial struct EntityQuery : IIdentifierType, IDisposable { + /// + /// Provides an enumerator for iterating over chunks of entities and their component data that match a set of archetypes within a world. + /// public readonly ref struct ChunkIterator { + /// + /// Provides a read-only view over a chunk of entities and their component data within an archetype. + /// + /// This does not filter disabled/enabled components. You must handle that manually. public readonly ref struct ChunkView { private readonly ref Archetype _archetype; @@ -54,6 +98,10 @@ public unsafe partial struct EntityQuery : IIdentifierType, IDisposable _chunk = ref archetype.GetChunkReference(chunkIndex); } + /// + /// Returns a read-only span containing structuralAll entities stored in the current chunk. + /// + /// A read-only span of values representing the entities in the chunk. public readonly ReadOnlySpan GetEntities() { var ptr = _chunk.GetUnsafePtr(); @@ -61,31 +109,70 @@ public unsafe partial struct EntityQuery : IIdentifierType, IDisposable return new ReadOnlySpan(pEntity, _chunk.Count); } + /// + /// Gets a span providing direct access to the component data of type T0 for structuralAll entities in the chunk. + /// + /// The type of component to access. Must be an unmanaged type that implements . + /// A span of type containing the component data for each entity in the chunk. + /// Thrown if the specified component type is not present in the archetype. public readonly Span GetComponentData() where T : unmanaged, IComponent { - var offset = _archetype.GetOffset(ComponentTypeID.value); - if (offset < 0) + var layout = _archetype.GetLayout(ComponentTypeID.value).GetValueOrThrow(ResultStatus.Success); + var ptr = _chunk.GetUnsafePtr() + layout.offset; + return new Span(ptr, _chunk.Count); + } + + /// + /// Gets a bit set representing the enabled state of each instance of the specified enableable component + /// type within the current chunk. + /// + /// The component type for which to retrieve enablement bits. Must be unmanaged and implement . + /// A that provides access to the enablement bits for all instances of the specified component type in the chunk. + /// Thrown if the specified component type does not support enablement. + public SpanBitSet GetEnableBits() + where T : unmanaged, IEnableableComponent + { + var layout = _archetype.GetLayout(ComponentTypeID.value).GetValueOrThrow(ResultStatus.Success); + if (layout.enableBitsOffset == -1) { - throw new InvalidOperationException($"Archetype does not contain component of type {typeof(T)}"); + throw new InvalidOperationException($"Component {typeof(T).FullName} is not enableable."); } - var ptr = (byte*)_chunk.GetUnsafePtr() + offset; - return new Span(ptr, _chunk.Count); + var maskBase = _chunk.GetUnsafePtr() + layout.enableBitsOffset; + return new SpanBitSet(new Span(maskBase, (_chunk.Count + 31) / 32)); + } + + /// + /// Determines whether the specified component of type at the given index is currently enabled. + /// + /// The type of the component to check. Must be an unmanaged type that implements . + /// The zero-based index of the component instance to check within the chunk. + /// true if the component at the specified index is enabled; otherwise, false. + /// Thrown if the specified component type does not support enable/disable functionality. + public readonly bool IsComponentEnabled(int index) + where T : unmanaged, IEnableableComponent + { + var layout = _archetype.GetLayout(ComponentTypeID.value).GetValueOrThrow(ResultStatus.Success); + if (layout.enableBitsOffset == -1) + { + throw new InvalidOperationException($"Component {typeof(T).FullName} is not enableable."); + } + + var maskBase = _chunk.GetUnsafePtr() + layout.enableBitsOffset; + return CheckBit(maskBase, index); } } public ref struct Enumerator { - private readonly ReadOnlyUnsafeCollection> _matchingArchetypes; - private readonly World _world; + private readonly ChunkIterator _iterator; private int _archetypeIndex; private int _chunkIndex; - internal Enumerator(ReadOnlyUnsafeCollection> matchingArchetypes, World world) + internal Enumerator(ChunkIterator iterator) { - _matchingArchetypes = matchingArchetypes; - _world = world; + _iterator = iterator; _archetypeIndex = 0; _chunkIndex = -1; } @@ -94,7 +181,7 @@ public unsafe partial struct EntityQuery : IIdentifierType, IDisposable { get { - ref var archetype = ref _world.GetArchetypeReference(_matchingArchetypes[_archetypeIndex]); + ref var archetype = ref _iterator._world.GetArchetypeReference(_iterator._matchingArchetypes[_archetypeIndex]); return new ChunkView(ref archetype, _chunkIndex); } } @@ -103,9 +190,9 @@ public unsafe partial struct EntityQuery : IIdentifierType, IDisposable { _chunkIndex++; - while (_archetypeIndex < _matchingArchetypes.Count) + while (_archetypeIndex < _iterator._matchingArchetypes.Count) { - ref var archetype = ref _world.GetArchetypeReference(_matchingArchetypes[_archetypeIndex]); + ref var archetype = ref _iterator._world.GetArchetypeReference(_iterator._matchingArchetypes[_archetypeIndex]); if (_chunkIndex < archetype.ChunkCount) { return true; @@ -140,14 +227,15 @@ public unsafe partial struct EntityQuery : IIdentifierType, IDisposable public readonly Enumerator GetEnumerator() { - return new Enumerator(_matchingArchetypes, _world); + return new Enumerator(this); } } private readonly Identifier _worldID; - private EntityQueryMask _mask; private UnsafeList> _matchingArchetypes; + internal EntityQueryMask _mask; + internal EntityQuery(Identifier worldID, EntityQueryMask mask) { _worldID = worldID; @@ -155,14 +243,96 @@ public unsafe partial struct EntityQuery : IIdentifierType, IDisposable _matchingArchetypes = new UnsafeList>(8, Allocator.Persistent); } - internal void AddArchetypeIfMatch(Archetype archetype) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool IsEntityValid(byte* chunkBase, int entityIndex, ref readonly Archetype archetype, ref readonly EntityQueryMask mask) { - if (_mask.Matches(archetype._signature)) + // 1. Check "Require Enabled" (WithAll) + // We iterate over the bits set in 'requireEnabled' + var it = mask.requireEnabled.GetIterator(); + + while (it.Next(out var id)) + { + // Get the EnableBitmask for this component in this chunk + var layout = archetype.GetLayout(id).Value; + if (layout.enableBitsOffset == -1) + { + // Not enableable, always true + continue; + } + + // Check bit + if (!CheckBit(chunkBase + layout.enableBitsOffset, entityIndex)) + { + return false; + } + } + + // 2. Check "Require Disabled" (WithDisabled) + it = mask.requireDisabled.GetIterator(); + while (it.Next(out var id)) + { + var layout = archetype.GetLayout(id).Value; + + // If component is not enableable, it is technically "Always Enabled", + // so it cannot satisfy "WithDisabled". + if (layout.enableBitsOffset == -1) + { + return false; + } + + // Check bit (Must be 0) + if (CheckBit(chunkBase + layout.enableBitsOffset, entityIndex)) + { + return false; + } + } + + // 3. Check "Reject if Enabled" (The "Soft WithNone") + it = mask.rejectIfEnabled.GetIterator(); + while (it.Next(out var id)) + { + var layoutResult = archetype.GetLayout(id); + if (layoutResult.Status != ResultStatus.Success) + { + // Component is absent, so it is not enabled. + continue; + } + + // If component is not enableable, it is technically "Always Enabled", + // so it cannot satisfy "Reject if Enabled". + if (layoutResult.Value.enableBitsOffset == -1) + { + return false; + } + + // Check bit (Must be 0) + if (CheckBit(chunkBase + layoutResult.Value.enableBitsOffset, entityIndex)) + { + return false; + } + } + + return true; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool CheckBit(byte* maskBase, int index) + { + var byteIndex = index >> Chunk.BIT_SHIFT; + var bitIndex = index & Chunk.BIT_ALIGNMENT_MINUS_ONE; + return (maskBase[byteIndex] & (1 << bitIndex)) != 0; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal void AddArchetypeIfMatch(ref readonly Archetype archetype) + { + if (_mask.Matches(in archetype._signature)) { _matchingArchetypes.Add(archetype.ID); } } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public readonly ChunkIterator GetChunkIterator() { var world = World.GetWorld(_worldID).Value; @@ -176,13 +346,16 @@ public unsafe partial struct EntityQuery : IIdentifierType, IDisposable } } -public ref struct QueryBuilder +public ref partial struct QueryBuilder { private readonly Stack.Scope _scope; private UnsafeList> _all; private UnsafeList> _any; private UnsafeList> _absent; + private UnsafeList> _none; + private UnsafeList> _disabled; + private UnsafeList> _present; public QueryBuilder() { @@ -191,68 +364,12 @@ public ref struct QueryBuilder _all = new UnsafeList>(4, Allocator.Stack); _any = new UnsafeList>(4, Allocator.Stack); _absent = new UnsafeList>(4, Allocator.Stack); + _none = new UnsafeList>(4, Allocator.Stack); + _disabled = new UnsafeList>(4, Allocator.Stack); + _present = new UnsafeList>(4, Allocator.Stack); } - public QueryBuilder WithAll() - where T : unmanaged, IComponent - { - _all.Add(ComponentTypeID.value); - return this; - } - - public QueryBuilder WithAny() - where T : unmanaged, IComponent - { - _any.Add(ComponentTypeID.value); - return this; - } - - public QueryBuilder WithNone() - where T : unmanaged, IComponent - { - _absent.Add(ComponentTypeID.value); - return this; - } - - public Identifier Build(World world) - { - // 1. Calculate max component ID to size the BitSets - int maxID = 0; - FindMax(_all, ref maxID); - FindMax(_any, ref maxID); - FindMax(_absent, ref maxID); - - // 2. Create the Mask - using var mask = new EntityQueryMask - { - all = new UnsafeBitSet(maxID + 1, Allocator.Stack), - any = new UnsafeBitSet(maxID + 1, Allocator.Stack), - absent = new UnsafeBitSet(maxID + 1, Allocator.Stack) - }; - - // 3. Fill BitSets - foreach (var id in _all) mask.all.SetBit(id); - foreach (var id in _any) mask.any.SetBit(id); - foreach (var id in _absent) mask.absent.SetBit(id); - - // 4. Ask World for the Query (Cached) - var queryID = world.GetEntityQueryIDByMaskHash(mask.GetHashCode()); - if (queryID.IsNotValid) - { - queryID = world.CreateEntityQuery(mask); - ref var query = ref world.GetEntityQueryReference(queryID); - for (var i = 0; i < world.ArchetypeCount; i++) - { - ref var archetype = ref world.GetArchetypeReference(i); - query.AddArchetypeIfMatch(archetype); - } - } - - Dispose(); - - return queryID; - } - + [MethodImpl(MethodImplOptions.AggressiveInlining)] private static void FindMax(UnsafeList> list, ref int max) { foreach (var id in list) @@ -261,11 +378,98 @@ public ref struct QueryBuilder } } + public Identifier Build(World world) + { + // 1. Calculate max component ID to size the BitSets + var maxID = 0; + FindMax(_all, ref maxID); + FindMax(_any, ref maxID); + FindMax(_absent, ref maxID); + FindMax(_none, ref maxID); + FindMax(_disabled, ref maxID); + FindMax(_present, ref maxID); + + // 2. Create the Mask + var mask = new EntityQueryMask + { + structuralAll = new UnsafeBitSet(maxID + 1, Allocator.Persistent, AllocationOption.Clear), + structuralAny = new UnsafeBitSet(maxID + 1, Allocator.Persistent, AllocationOption.Clear), + structuralAbsent = new UnsafeBitSet(maxID + 1, Allocator.Persistent, AllocationOption.Clear), + requireEnabled = new UnsafeBitSet(maxID + 1, Allocator.Persistent, AllocationOption.Clear), + requireDisabled = new UnsafeBitSet(maxID + 1, Allocator.Persistent, AllocationOption.Clear), + rejectIfEnabled = new UnsafeBitSet(maxID + 1, Allocator.Persistent, AllocationOption.Clear), + }; + + // 3. Fill BitSets + foreach (var id in _all) + { + mask.structuralAll.SetBit(id); // Structure: Must Exist + mask.requireEnabled.SetBit(id); // Filter: Must be Enabled + } + + foreach (var id in _disabled) + { + mask.structuralAll.SetBit(id); // Structure: Must Exist + mask.requireDisabled.SetBit(id); // Filter: Must be Disabled + } + + foreach (var id in _none) + { + if (ComponentRegister.GetComponentInfo(id).isEnableable) + { + mask.rejectIfEnabled.SetBit(id); // Filter: Must Not be Enabled (Can be Absent or Disabled) + } + else + { + mask.structuralAbsent.SetBit(id); // Structure: Must Not Exist + } + } + + foreach (var id in _present) + { + mask.structuralAll.SetBit(id); + } + + foreach (var id in _absent) + { + mask.structuralAbsent.SetBit(id); + } + + foreach (var id in _any) + { + mask.structuralAny.SetBit(id); + } + + // 4. Ask World for the Query (Cached) + var maskHash = mask.GetHashCode(); + var queryID = world.GetEntityQueryIDByMaskHash(maskHash); + if (queryID.IsValid) + { + // Check if the masks are actually equal (Hash collision?). + // Really worth it? It's unlikely to have collisions here. + if (world.GetEntityQueryReference(queryID)._mask.Equals(mask)) + { + mask.Dispose(); + goto Return; + } + } + + // NOTE: We do not dispose the mask here, as it is now owned by the EntityQuery. + queryID = world.CreateEntityQuery(mask, maskHash); + + Return: + Dispose(); + return queryID; + } + private void Dispose() { _all.Dispose(); _any.Dispose(); _absent.Dispose(); + _none.Dispose(); + _disabled.Dispose(); + _present.Dispose(); _scope.Dispose(); } diff --git a/Ghost.Entities/Templates/EntityQuery.ForEach.gen.cs b/Ghost.Entities/Templates/EntityQuery.ForEach.gen.cs index 6345423..9b3af5b 100644 --- a/Ghost.Entities/Templates/EntityQuery.ForEach.gen.cs +++ b/Ghost.Entities/Templates/EntityQuery.ForEach.gen.cs @@ -14,18 +14,20 @@ public unsafe partial struct EntityQuery var offsets = stackalloc int[1]; var basePtrs = stackalloc byte*[1]; - foreach (var archetypeID in _matchingArchetypes) + for (var i = 0; i < _matchingArchetypes.Count; i++) { - ref var archetype = ref world.GetArchetypeReference(archetypeID); + ref var archetype = ref world.GetArchetypeReference(_matchingArchetypes[i]); var hasAllComponents = true; for (var index = 0; index < 1; index++) { - offsets[index] = archetype.GetOffset(compTypeIDs[index]); - if (offsets[index] == -1) + var layoutResult = archetype.GetLayout(compTypeIDs[index]); + if (layoutResult.Status != ResultStatus.Success) { hasAllComponents = false; break; } + + offsets[index] = layoutResult.Value.offset; } if (!hasAllComponents) @@ -36,14 +38,20 @@ public unsafe partial struct EntityQuery for (var chunkIndex = 0; chunkIndex < archetype.ChunkCount; chunkIndex++) { ref var chunk = ref archetype.GetChunkReference(chunkIndex); - var count = chunk.Count; + var pChunkData = chunk.GetUnsafePtr(); + for (var index = 0; index < 1; index++) { - basePtrs[index] = chunk.GetUnsafePtr() + offsets[index]; + basePtrs[index] = pChunkData + offsets[index]; } - for (var entityIndex = 0; entityIndex < count; entityIndex++) + for (var entityIndex = 0; entityIndex < chunk.Count; entityIndex++) { + if (!IsEntityValid(pChunkData, entityIndex, in archetype, in _mask)) + { + continue; + } + var pComp0 = (T0*)(basePtrs[0] + (sizeof(T0) * entityIndex)); action(ref *pComp0); @@ -62,18 +70,20 @@ public unsafe partial struct EntityQuery var offsets = stackalloc int[2]; var basePtrs = stackalloc byte*[2]; - foreach (var archetypeID in _matchingArchetypes) + for (var i = 0; i < _matchingArchetypes.Count; i++) { - ref var archetype = ref world.GetArchetypeReference(archetypeID); + ref var archetype = ref world.GetArchetypeReference(_matchingArchetypes[i]); var hasAllComponents = true; for (var index = 0; index < 2; index++) { - offsets[index] = archetype.GetOffset(compTypeIDs[index]); - if (offsets[index] == -1) + var layoutResult = archetype.GetLayout(compTypeIDs[index]); + if (layoutResult.Status != ResultStatus.Success) { hasAllComponents = false; break; } + + offsets[index] = layoutResult.Value.offset; } if (!hasAllComponents) @@ -84,14 +94,20 @@ public unsafe partial struct EntityQuery for (var chunkIndex = 0; chunkIndex < archetype.ChunkCount; chunkIndex++) { ref var chunk = ref archetype.GetChunkReference(chunkIndex); - var count = chunk.Count; + var pChunkData = chunk.GetUnsafePtr(); + for (var index = 0; index < 2; index++) { - basePtrs[index] = chunk.GetUnsafePtr() + offsets[index]; + basePtrs[index] = pChunkData + offsets[index]; } - for (var entityIndex = 0; entityIndex < count; entityIndex++) + for (var entityIndex = 0; entityIndex < chunk.Count; entityIndex++) { + if (!IsEntityValid(pChunkData, entityIndex, in archetype, in _mask)) + { + continue; + } + var pComp0 = (T0*)(basePtrs[0] + (sizeof(T0) * entityIndex)); var pComp1 = (T1*)(basePtrs[1] + (sizeof(T1) * entityIndex)); @@ -112,18 +128,20 @@ public unsafe partial struct EntityQuery var offsets = stackalloc int[3]; var basePtrs = stackalloc byte*[3]; - foreach (var archetypeID in _matchingArchetypes) + for (var i = 0; i < _matchingArchetypes.Count; i++) { - ref var archetype = ref world.GetArchetypeReference(archetypeID); + ref var archetype = ref world.GetArchetypeReference(_matchingArchetypes[i]); var hasAllComponents = true; for (var index = 0; index < 3; index++) { - offsets[index] = archetype.GetOffset(compTypeIDs[index]); - if (offsets[index] == -1) + var layoutResult = archetype.GetLayout(compTypeIDs[index]); + if (layoutResult.Status != ResultStatus.Success) { hasAllComponents = false; break; } + + offsets[index] = layoutResult.Value.offset; } if (!hasAllComponents) @@ -134,14 +152,20 @@ public unsafe partial struct EntityQuery for (var chunkIndex = 0; chunkIndex < archetype.ChunkCount; chunkIndex++) { ref var chunk = ref archetype.GetChunkReference(chunkIndex); - var count = chunk.Count; + var pChunkData = chunk.GetUnsafePtr(); + for (var index = 0; index < 3; index++) { - basePtrs[index] = chunk.GetUnsafePtr() + offsets[index]; + basePtrs[index] = pChunkData + offsets[index]; } - for (var entityIndex = 0; entityIndex < count; entityIndex++) + for (var entityIndex = 0; entityIndex < chunk.Count; entityIndex++) { + if (!IsEntityValid(pChunkData, entityIndex, in archetype, in _mask)) + { + continue; + } + var pComp0 = (T0*)(basePtrs[0] + (sizeof(T0) * entityIndex)); var pComp1 = (T1*)(basePtrs[1] + (sizeof(T1) * entityIndex)); var pComp2 = (T2*)(basePtrs[2] + (sizeof(T2) * entityIndex)); @@ -164,18 +188,20 @@ public unsafe partial struct EntityQuery var offsets = stackalloc int[4]; var basePtrs = stackalloc byte*[4]; - foreach (var archetypeID in _matchingArchetypes) + for (var i = 0; i < _matchingArchetypes.Count; i++) { - ref var archetype = ref world.GetArchetypeReference(archetypeID); + ref var archetype = ref world.GetArchetypeReference(_matchingArchetypes[i]); var hasAllComponents = true; for (var index = 0; index < 4; index++) { - offsets[index] = archetype.GetOffset(compTypeIDs[index]); - if (offsets[index] == -1) + var layoutResult = archetype.GetLayout(compTypeIDs[index]); + if (layoutResult.Status != ResultStatus.Success) { hasAllComponents = false; break; } + + offsets[index] = layoutResult.Value.offset; } if (!hasAllComponents) @@ -186,14 +212,20 @@ public unsafe partial struct EntityQuery for (var chunkIndex = 0; chunkIndex < archetype.ChunkCount; chunkIndex++) { ref var chunk = ref archetype.GetChunkReference(chunkIndex); - var count = chunk.Count; + var pChunkData = chunk.GetUnsafePtr(); + for (var index = 0; index < 4; index++) { - basePtrs[index] = chunk.GetUnsafePtr() + offsets[index]; + basePtrs[index] = pChunkData + offsets[index]; } - for (var entityIndex = 0; entityIndex < count; entityIndex++) + for (var entityIndex = 0; entityIndex < chunk.Count; entityIndex++) { + if (!IsEntityValid(pChunkData, entityIndex, in archetype, in _mask)) + { + continue; + } + var pComp0 = (T0*)(basePtrs[0] + (sizeof(T0) * entityIndex)); var pComp1 = (T1*)(basePtrs[1] + (sizeof(T1) * entityIndex)); var pComp2 = (T2*)(basePtrs[2] + (sizeof(T2) * entityIndex)); @@ -218,18 +250,20 @@ public unsafe partial struct EntityQuery var offsets = stackalloc int[5]; var basePtrs = stackalloc byte*[5]; - foreach (var archetypeID in _matchingArchetypes) + for (var i = 0; i < _matchingArchetypes.Count; i++) { - ref var archetype = ref world.GetArchetypeReference(archetypeID); + ref var archetype = ref world.GetArchetypeReference(_matchingArchetypes[i]); var hasAllComponents = true; for (var index = 0; index < 5; index++) { - offsets[index] = archetype.GetOffset(compTypeIDs[index]); - if (offsets[index] == -1) + var layoutResult = archetype.GetLayout(compTypeIDs[index]); + if (layoutResult.Status != ResultStatus.Success) { hasAllComponents = false; break; } + + offsets[index] = layoutResult.Value.offset; } if (!hasAllComponents) @@ -240,14 +274,20 @@ public unsafe partial struct EntityQuery for (var chunkIndex = 0; chunkIndex < archetype.ChunkCount; chunkIndex++) { ref var chunk = ref archetype.GetChunkReference(chunkIndex); - var count = chunk.Count; + var pChunkData = chunk.GetUnsafePtr(); + for (var index = 0; index < 5; index++) { - basePtrs[index] = chunk.GetUnsafePtr() + offsets[index]; + basePtrs[index] = pChunkData + offsets[index]; } - for (var entityIndex = 0; entityIndex < count; entityIndex++) + for (var entityIndex = 0; entityIndex < chunk.Count; entityIndex++) { + if (!IsEntityValid(pChunkData, entityIndex, in archetype, in _mask)) + { + continue; + } + var pComp0 = (T0*)(basePtrs[0] + (sizeof(T0) * entityIndex)); var pComp1 = (T1*)(basePtrs[1] + (sizeof(T1) * entityIndex)); var pComp2 = (T2*)(basePtrs[2] + (sizeof(T2) * entityIndex)); @@ -274,18 +314,20 @@ public unsafe partial struct EntityQuery var offsets = stackalloc int[6]; var basePtrs = stackalloc byte*[6]; - foreach (var archetypeID in _matchingArchetypes) + for (var i = 0; i < _matchingArchetypes.Count; i++) { - ref var archetype = ref world.GetArchetypeReference(archetypeID); + ref var archetype = ref world.GetArchetypeReference(_matchingArchetypes[i]); var hasAllComponents = true; for (var index = 0; index < 6; index++) { - offsets[index] = archetype.GetOffset(compTypeIDs[index]); - if (offsets[index] == -1) + var layoutResult = archetype.GetLayout(compTypeIDs[index]); + if (layoutResult.Status != ResultStatus.Success) { hasAllComponents = false; break; } + + offsets[index] = layoutResult.Value.offset; } if (!hasAllComponents) @@ -296,14 +338,20 @@ public unsafe partial struct EntityQuery for (var chunkIndex = 0; chunkIndex < archetype.ChunkCount; chunkIndex++) { ref var chunk = ref archetype.GetChunkReference(chunkIndex); - var count = chunk.Count; + var pChunkData = chunk.GetUnsafePtr(); + for (var index = 0; index < 6; index++) { - basePtrs[index] = chunk.GetUnsafePtr() + offsets[index]; + basePtrs[index] = pChunkData + offsets[index]; } - for (var entityIndex = 0; entityIndex < count; entityIndex++) + for (var entityIndex = 0; entityIndex < chunk.Count; entityIndex++) { + if (!IsEntityValid(pChunkData, entityIndex, in archetype, in _mask)) + { + continue; + } + var pComp0 = (T0*)(basePtrs[0] + (sizeof(T0) * entityIndex)); var pComp1 = (T1*)(basePtrs[1] + (sizeof(T1) * entityIndex)); var pComp2 = (T2*)(basePtrs[2] + (sizeof(T2) * entityIndex)); @@ -332,18 +380,20 @@ public unsafe partial struct EntityQuery var offsets = stackalloc int[7]; var basePtrs = stackalloc byte*[7]; - foreach (var archetypeID in _matchingArchetypes) + for (var i = 0; i < _matchingArchetypes.Count; i++) { - ref var archetype = ref world.GetArchetypeReference(archetypeID); + ref var archetype = ref world.GetArchetypeReference(_matchingArchetypes[i]); var hasAllComponents = true; for (var index = 0; index < 7; index++) { - offsets[index] = archetype.GetOffset(compTypeIDs[index]); - if (offsets[index] == -1) + var layoutResult = archetype.GetLayout(compTypeIDs[index]); + if (layoutResult.Status != ResultStatus.Success) { hasAllComponents = false; break; } + + offsets[index] = layoutResult.Value.offset; } if (!hasAllComponents) @@ -354,14 +404,20 @@ public unsafe partial struct EntityQuery for (var chunkIndex = 0; chunkIndex < archetype.ChunkCount; chunkIndex++) { ref var chunk = ref archetype.GetChunkReference(chunkIndex); - var count = chunk.Count; + var pChunkData = chunk.GetUnsafePtr(); + for (var index = 0; index < 7; index++) { - basePtrs[index] = chunk.GetUnsafePtr() + offsets[index]; + basePtrs[index] = pChunkData + offsets[index]; } - for (var entityIndex = 0; entityIndex < count; entityIndex++) + for (var entityIndex = 0; entityIndex < chunk.Count; entityIndex++) { + if (!IsEntityValid(pChunkData, entityIndex, in archetype, in _mask)) + { + continue; + } + var pComp0 = (T0*)(basePtrs[0] + (sizeof(T0) * entityIndex)); var pComp1 = (T1*)(basePtrs[1] + (sizeof(T1) * entityIndex)); var pComp2 = (T2*)(basePtrs[2] + (sizeof(T2) * entityIndex)); @@ -392,18 +448,20 @@ public unsafe partial struct EntityQuery var offsets = stackalloc int[8]; var basePtrs = stackalloc byte*[8]; - foreach (var archetypeID in _matchingArchetypes) + for (var i = 0; i < _matchingArchetypes.Count; i++) { - ref var archetype = ref world.GetArchetypeReference(archetypeID); + ref var archetype = ref world.GetArchetypeReference(_matchingArchetypes[i]); var hasAllComponents = true; for (var index = 0; index < 8; index++) { - offsets[index] = archetype.GetOffset(compTypeIDs[index]); - if (offsets[index] == -1) + var layoutResult = archetype.GetLayout(compTypeIDs[index]); + if (layoutResult.Status != ResultStatus.Success) { hasAllComponents = false; break; } + + offsets[index] = layoutResult.Value.offset; } if (!hasAllComponents) @@ -414,14 +472,20 @@ public unsafe partial struct EntityQuery for (var chunkIndex = 0; chunkIndex < archetype.ChunkCount; chunkIndex++) { ref var chunk = ref archetype.GetChunkReference(chunkIndex); - var count = chunk.Count; + var pChunkData = chunk.GetUnsafePtr(); + for (var index = 0; index < 8; index++) { - basePtrs[index] = chunk.GetUnsafePtr() + offsets[index]; + basePtrs[index] = pChunkData + offsets[index]; } - for (var entityIndex = 0; entityIndex < count; entityIndex++) + for (var entityIndex = 0; entityIndex < chunk.Count; entityIndex++) { + if (!IsEntityValid(pChunkData, entityIndex, in archetype, in _mask)) + { + continue; + } + var pComp0 = (T0*)(basePtrs[0] + (sizeof(T0) * entityIndex)); var pComp1 = (T1*)(basePtrs[1] + (sizeof(T1) * entityIndex)); var pComp2 = (T2*)(basePtrs[2] + (sizeof(T2) * entityIndex)); @@ -437,7 +501,6 @@ public unsafe partial struct EntityQuery } } - public readonly void ForEach(ForEachWithEntity action) where T0 : unmanaged, IComponent { @@ -447,18 +510,20 @@ public unsafe partial struct EntityQuery var offsets = stackalloc int[1]; var basePtrs = stackalloc byte*[1]; - foreach (var archetypeID in _matchingArchetypes) + for (var i = 0; i < _matchingArchetypes.Count; i++) { - ref var archetype = ref world.GetArchetypeReference(archetypeID); + ref var archetype = ref world.GetArchetypeReference(_matchingArchetypes[i]); var hasAllComponents = true; for (var index = 0; index < 1; index++) { - offsets[index] = archetype.GetOffset(compTypeIDs[index]); - if (offsets[index] == -1) + var layoutResult = archetype.GetLayout(compTypeIDs[index]); + if (layoutResult.Status != ResultStatus.Success) { hasAllComponents = false; break; } + + offsets[index] = layoutResult.Value.offset; } if (!hasAllComponents) @@ -469,17 +534,23 @@ public unsafe partial struct EntityQuery for (var chunkIndex = 0; chunkIndex < archetype.ChunkCount; chunkIndex++) { ref var chunk = ref archetype.GetChunkReference(chunkIndex); - var count = chunk.Count; + var pChunkData = chunk.GetUnsafePtr(); + for (var index = 0; index < 1; index++) { - basePtrs[index] = chunk.GetUnsafePtr() + offsets[index]; + basePtrs[index] = pChunkData + offsets[index]; } - for (var entityIndex = 0; entityIndex < count; entityIndex++) + for (var entityIndex = 0; entityIndex < chunk.Count; entityIndex++) { - var pEntity = (Entity*)(chunk.GetUnsafePtr() + archetype.EntityIDsOffset + (sizeof(Entity) * entityIndex)); + if (!IsEntityValid(pChunkData, entityIndex, in archetype, in _mask)) + { + continue; + } + var pComp0 = (T0*)(basePtrs[0] + (sizeof(T0) * entityIndex)); + var pEntity = (Entity*)(pChunkData + archetype.EntityIDsOffset + (sizeof(Entity) * entityIndex)); action(*pEntity, ref *pComp0); } } @@ -496,18 +567,20 @@ public unsafe partial struct EntityQuery var offsets = stackalloc int[2]; var basePtrs = stackalloc byte*[2]; - foreach (var archetypeID in _matchingArchetypes) + for (var i = 0; i < _matchingArchetypes.Count; i++) { - ref var archetype = ref world.GetArchetypeReference(archetypeID); + ref var archetype = ref world.GetArchetypeReference(_matchingArchetypes[i]); var hasAllComponents = true; for (var index = 0; index < 2; index++) { - offsets[index] = archetype.GetOffset(compTypeIDs[index]); - if (offsets[index] == -1) + var layoutResult = archetype.GetLayout(compTypeIDs[index]); + if (layoutResult.Status != ResultStatus.Success) { hasAllComponents = false; break; } + + offsets[index] = layoutResult.Value.offset; } if (!hasAllComponents) @@ -518,18 +591,24 @@ public unsafe partial struct EntityQuery for (var chunkIndex = 0; chunkIndex < archetype.ChunkCount; chunkIndex++) { ref var chunk = ref archetype.GetChunkReference(chunkIndex); - var count = chunk.Count; + var pChunkData = chunk.GetUnsafePtr(); + for (var index = 0; index < 2; index++) { - basePtrs[index] = chunk.GetUnsafePtr() + offsets[index]; + basePtrs[index] = pChunkData + offsets[index]; } - for (var entityIndex = 0; entityIndex < count; entityIndex++) + for (var entityIndex = 0; entityIndex < chunk.Count; entityIndex++) { - var pEntity = (Entity*)(chunk.GetUnsafePtr() + archetype.EntityIDsOffset + (sizeof(Entity) * entityIndex)); + if (!IsEntityValid(pChunkData, entityIndex, in archetype, in _mask)) + { + continue; + } + var pComp0 = (T0*)(basePtrs[0] + (sizeof(T0) * entityIndex)); var pComp1 = (T1*)(basePtrs[1] + (sizeof(T1) * entityIndex)); + var pEntity = (Entity*)(pChunkData + archetype.EntityIDsOffset + (sizeof(Entity) * entityIndex)); action(*pEntity, ref *pComp0,ref *pComp1); } } @@ -547,18 +626,20 @@ public unsafe partial struct EntityQuery var offsets = stackalloc int[3]; var basePtrs = stackalloc byte*[3]; - foreach (var archetypeID in _matchingArchetypes) + for (var i = 0; i < _matchingArchetypes.Count; i++) { - ref var archetype = ref world.GetArchetypeReference(archetypeID); + ref var archetype = ref world.GetArchetypeReference(_matchingArchetypes[i]); var hasAllComponents = true; for (var index = 0; index < 3; index++) { - offsets[index] = archetype.GetOffset(compTypeIDs[index]); - if (offsets[index] == -1) + var layoutResult = archetype.GetLayout(compTypeIDs[index]); + if (layoutResult.Status != ResultStatus.Success) { hasAllComponents = false; break; } + + offsets[index] = layoutResult.Value.offset; } if (!hasAllComponents) @@ -569,19 +650,25 @@ public unsafe partial struct EntityQuery for (var chunkIndex = 0; chunkIndex < archetype.ChunkCount; chunkIndex++) { ref var chunk = ref archetype.GetChunkReference(chunkIndex); - var count = chunk.Count; + var pChunkData = chunk.GetUnsafePtr(); + for (var index = 0; index < 3; index++) { - basePtrs[index] = chunk.GetUnsafePtr() + offsets[index]; + basePtrs[index] = pChunkData + offsets[index]; } - for (var entityIndex = 0; entityIndex < count; entityIndex++) + for (var entityIndex = 0; entityIndex < chunk.Count; entityIndex++) { - var pEntity = (Entity*)(chunk.GetUnsafePtr() + archetype.EntityIDsOffset + (sizeof(Entity) * entityIndex)); + if (!IsEntityValid(pChunkData, entityIndex, in archetype, in _mask)) + { + continue; + } + var pComp0 = (T0*)(basePtrs[0] + (sizeof(T0) * entityIndex)); var pComp1 = (T1*)(basePtrs[1] + (sizeof(T1) * entityIndex)); var pComp2 = (T2*)(basePtrs[2] + (sizeof(T2) * entityIndex)); + var pEntity = (Entity*)(pChunkData + archetype.EntityIDsOffset + (sizeof(Entity) * entityIndex)); action(*pEntity, ref *pComp0,ref *pComp1,ref *pComp2); } } @@ -600,18 +687,20 @@ public unsafe partial struct EntityQuery var offsets = stackalloc int[4]; var basePtrs = stackalloc byte*[4]; - foreach (var archetypeID in _matchingArchetypes) + for (var i = 0; i < _matchingArchetypes.Count; i++) { - ref var archetype = ref world.GetArchetypeReference(archetypeID); + ref var archetype = ref world.GetArchetypeReference(_matchingArchetypes[i]); var hasAllComponents = true; for (var index = 0; index < 4; index++) { - offsets[index] = archetype.GetOffset(compTypeIDs[index]); - if (offsets[index] == -1) + var layoutResult = archetype.GetLayout(compTypeIDs[index]); + if (layoutResult.Status != ResultStatus.Success) { hasAllComponents = false; break; } + + offsets[index] = layoutResult.Value.offset; } if (!hasAllComponents) @@ -622,20 +711,26 @@ public unsafe partial struct EntityQuery for (var chunkIndex = 0; chunkIndex < archetype.ChunkCount; chunkIndex++) { ref var chunk = ref archetype.GetChunkReference(chunkIndex); - var count = chunk.Count; + var pChunkData = chunk.GetUnsafePtr(); + for (var index = 0; index < 4; index++) { - basePtrs[index] = chunk.GetUnsafePtr() + offsets[index]; + basePtrs[index] = pChunkData + offsets[index]; } - for (var entityIndex = 0; entityIndex < count; entityIndex++) + for (var entityIndex = 0; entityIndex < chunk.Count; entityIndex++) { - var pEntity = (Entity*)(chunk.GetUnsafePtr() + archetype.EntityIDsOffset + (sizeof(Entity) * entityIndex)); + if (!IsEntityValid(pChunkData, entityIndex, in archetype, in _mask)) + { + continue; + } + var pComp0 = (T0*)(basePtrs[0] + (sizeof(T0) * entityIndex)); var pComp1 = (T1*)(basePtrs[1] + (sizeof(T1) * entityIndex)); var pComp2 = (T2*)(basePtrs[2] + (sizeof(T2) * entityIndex)); var pComp3 = (T3*)(basePtrs[3] + (sizeof(T3) * entityIndex)); + var pEntity = (Entity*)(pChunkData + archetype.EntityIDsOffset + (sizeof(Entity) * entityIndex)); action(*pEntity, ref *pComp0,ref *pComp1,ref *pComp2,ref *pComp3); } } @@ -655,18 +750,20 @@ public unsafe partial struct EntityQuery var offsets = stackalloc int[5]; var basePtrs = stackalloc byte*[5]; - foreach (var archetypeID in _matchingArchetypes) + for (var i = 0; i < _matchingArchetypes.Count; i++) { - ref var archetype = ref world.GetArchetypeReference(archetypeID); + ref var archetype = ref world.GetArchetypeReference(_matchingArchetypes[i]); var hasAllComponents = true; for (var index = 0; index < 5; index++) { - offsets[index] = archetype.GetOffset(compTypeIDs[index]); - if (offsets[index] == -1) + var layoutResult = archetype.GetLayout(compTypeIDs[index]); + if (layoutResult.Status != ResultStatus.Success) { hasAllComponents = false; break; } + + offsets[index] = layoutResult.Value.offset; } if (!hasAllComponents) @@ -677,21 +774,27 @@ public unsafe partial struct EntityQuery for (var chunkIndex = 0; chunkIndex < archetype.ChunkCount; chunkIndex++) { ref var chunk = ref archetype.GetChunkReference(chunkIndex); - var count = chunk.Count; + var pChunkData = chunk.GetUnsafePtr(); + for (var index = 0; index < 5; index++) { - basePtrs[index] = chunk.GetUnsafePtr() + offsets[index]; + basePtrs[index] = pChunkData + offsets[index]; } - for (var entityIndex = 0; entityIndex < count; entityIndex++) + for (var entityIndex = 0; entityIndex < chunk.Count; entityIndex++) { - var pEntity = (Entity*)(chunk.GetUnsafePtr() + archetype.EntityIDsOffset + (sizeof(Entity) * entityIndex)); + if (!IsEntityValid(pChunkData, entityIndex, in archetype, in _mask)) + { + continue; + } + var pComp0 = (T0*)(basePtrs[0] + (sizeof(T0) * entityIndex)); var pComp1 = (T1*)(basePtrs[1] + (sizeof(T1) * entityIndex)); var pComp2 = (T2*)(basePtrs[2] + (sizeof(T2) * entityIndex)); var pComp3 = (T3*)(basePtrs[3] + (sizeof(T3) * entityIndex)); var pComp4 = (T4*)(basePtrs[4] + (sizeof(T4) * entityIndex)); + var pEntity = (Entity*)(pChunkData + archetype.EntityIDsOffset + (sizeof(Entity) * entityIndex)); action(*pEntity, ref *pComp0,ref *pComp1,ref *pComp2,ref *pComp3,ref *pComp4); } } @@ -712,18 +815,20 @@ public unsafe partial struct EntityQuery var offsets = stackalloc int[6]; var basePtrs = stackalloc byte*[6]; - foreach (var archetypeID in _matchingArchetypes) + for (var i = 0; i < _matchingArchetypes.Count; i++) { - ref var archetype = ref world.GetArchetypeReference(archetypeID); + ref var archetype = ref world.GetArchetypeReference(_matchingArchetypes[i]); var hasAllComponents = true; for (var index = 0; index < 6; index++) { - offsets[index] = archetype.GetOffset(compTypeIDs[index]); - if (offsets[index] == -1) + var layoutResult = archetype.GetLayout(compTypeIDs[index]); + if (layoutResult.Status != ResultStatus.Success) { hasAllComponents = false; break; } + + offsets[index] = layoutResult.Value.offset; } if (!hasAllComponents) @@ -734,15 +839,20 @@ public unsafe partial struct EntityQuery for (var chunkIndex = 0; chunkIndex < archetype.ChunkCount; chunkIndex++) { ref var chunk = ref archetype.GetChunkReference(chunkIndex); - var count = chunk.Count; + var pChunkData = chunk.GetUnsafePtr(); + for (var index = 0; index < 6; index++) { - basePtrs[index] = chunk.GetUnsafePtr() + offsets[index]; + basePtrs[index] = pChunkData + offsets[index]; } - for (var entityIndex = 0; entityIndex < count; entityIndex++) + for (var entityIndex = 0; entityIndex < chunk.Count; entityIndex++) { - var pEntity = (Entity*)(chunk.GetUnsafePtr() + archetype.EntityIDsOffset + (sizeof(Entity) * entityIndex)); + if (!IsEntityValid(pChunkData, entityIndex, in archetype, in _mask)) + { + continue; + } + var pComp0 = (T0*)(basePtrs[0] + (sizeof(T0) * entityIndex)); var pComp1 = (T1*)(basePtrs[1] + (sizeof(T1) * entityIndex)); var pComp2 = (T2*)(basePtrs[2] + (sizeof(T2) * entityIndex)); @@ -750,6 +860,7 @@ public unsafe partial struct EntityQuery var pComp4 = (T4*)(basePtrs[4] + (sizeof(T4) * entityIndex)); var pComp5 = (T5*)(basePtrs[5] + (sizeof(T5) * entityIndex)); + var pEntity = (Entity*)(pChunkData + archetype.EntityIDsOffset + (sizeof(Entity) * entityIndex)); action(*pEntity, ref *pComp0,ref *pComp1,ref *pComp2,ref *pComp3,ref *pComp4,ref *pComp5); } } @@ -771,18 +882,20 @@ public unsafe partial struct EntityQuery var offsets = stackalloc int[7]; var basePtrs = stackalloc byte*[7]; - foreach (var archetypeID in _matchingArchetypes) + for (var i = 0; i < _matchingArchetypes.Count; i++) { - ref var archetype = ref world.GetArchetypeReference(archetypeID); + ref var archetype = ref world.GetArchetypeReference(_matchingArchetypes[i]); var hasAllComponents = true; for (var index = 0; index < 7; index++) { - offsets[index] = archetype.GetOffset(compTypeIDs[index]); - if (offsets[index] == -1) + var layoutResult = archetype.GetLayout(compTypeIDs[index]); + if (layoutResult.Status != ResultStatus.Success) { hasAllComponents = false; break; } + + offsets[index] = layoutResult.Value.offset; } if (!hasAllComponents) @@ -793,15 +906,20 @@ public unsafe partial struct EntityQuery for (var chunkIndex = 0; chunkIndex < archetype.ChunkCount; chunkIndex++) { ref var chunk = ref archetype.GetChunkReference(chunkIndex); - var count = chunk.Count; + var pChunkData = chunk.GetUnsafePtr(); + for (var index = 0; index < 7; index++) { - basePtrs[index] = chunk.GetUnsafePtr() + offsets[index]; + basePtrs[index] = pChunkData + offsets[index]; } - for (var entityIndex = 0; entityIndex < count; entityIndex++) + for (var entityIndex = 0; entityIndex < chunk.Count; entityIndex++) { - var pEntity = (Entity*)(chunk.GetUnsafePtr() + archetype.EntityIDsOffset + (sizeof(Entity) * entityIndex)); + if (!IsEntityValid(pChunkData, entityIndex, in archetype, in _mask)) + { + continue; + } + var pComp0 = (T0*)(basePtrs[0] + (sizeof(T0) * entityIndex)); var pComp1 = (T1*)(basePtrs[1] + (sizeof(T1) * entityIndex)); var pComp2 = (T2*)(basePtrs[2] + (sizeof(T2) * entityIndex)); @@ -810,6 +928,7 @@ public unsafe partial struct EntityQuery var pComp5 = (T5*)(basePtrs[5] + (sizeof(T5) * entityIndex)); var pComp6 = (T6*)(basePtrs[6] + (sizeof(T6) * entityIndex)); + var pEntity = (Entity*)(pChunkData + archetype.EntityIDsOffset + (sizeof(Entity) * entityIndex)); action(*pEntity, ref *pComp0,ref *pComp1,ref *pComp2,ref *pComp3,ref *pComp4,ref *pComp5,ref *pComp6); } } @@ -832,18 +951,20 @@ public unsafe partial struct EntityQuery var offsets = stackalloc int[8]; var basePtrs = stackalloc byte*[8]; - foreach (var archetypeID in _matchingArchetypes) + for (var i = 0; i < _matchingArchetypes.Count; i++) { - ref var archetype = ref world.GetArchetypeReference(archetypeID); + ref var archetype = ref world.GetArchetypeReference(_matchingArchetypes[i]); var hasAllComponents = true; for (var index = 0; index < 8; index++) { - offsets[index] = archetype.GetOffset(compTypeIDs[index]); - if (offsets[index] == -1) + var layoutResult = archetype.GetLayout(compTypeIDs[index]); + if (layoutResult.Status != ResultStatus.Success) { hasAllComponents = false; break; } + + offsets[index] = layoutResult.Value.offset; } if (!hasAllComponents) @@ -854,15 +975,20 @@ public unsafe partial struct EntityQuery for (var chunkIndex = 0; chunkIndex < archetype.ChunkCount; chunkIndex++) { ref var chunk = ref archetype.GetChunkReference(chunkIndex); - var count = chunk.Count; + var pChunkData = chunk.GetUnsafePtr(); + for (var index = 0; index < 8; index++) { - basePtrs[index] = chunk.GetUnsafePtr() + offsets[index]; + basePtrs[index] = pChunkData + offsets[index]; } - for (var entityIndex = 0; entityIndex < count; entityIndex++) + for (var entityIndex = 0; entityIndex < chunk.Count; entityIndex++) { - var pEntity = (Entity*)(chunk.GetUnsafePtr() + archetype.EntityIDsOffset + (sizeof(Entity) * entityIndex)); + if (!IsEntityValid(pChunkData, entityIndex, in archetype, in _mask)) + { + continue; + } + var pComp0 = (T0*)(basePtrs[0] + (sizeof(T0) * entityIndex)); var pComp1 = (T1*)(basePtrs[1] + (sizeof(T1) * entityIndex)); var pComp2 = (T2*)(basePtrs[2] + (sizeof(T2) * entityIndex)); @@ -872,6 +998,7 @@ public unsafe partial struct EntityQuery var pComp6 = (T6*)(basePtrs[6] + (sizeof(T6) * entityIndex)); var pComp7 = (T7*)(basePtrs[7] + (sizeof(T7) * entityIndex)); + var pEntity = (Entity*)(pChunkData + archetype.EntityIDsOffset + (sizeof(Entity) * entityIndex)); action(*pEntity, ref *pComp0,ref *pComp1,ref *pComp2,ref *pComp3,ref *pComp4,ref *pComp5,ref *pComp6,ref *pComp7); } } diff --git a/Ghost.Entities/Templates/EntityQuery.ForEach.tt b/Ghost.Entities/Templates/EntityQuery.ForEach.tt index 5294b35..88ed12a 100644 --- a/Ghost.Entities/Templates/EntityQuery.ForEach.tt +++ b/Ghost.Entities/Templates/EntityQuery.ForEach.tt @@ -10,13 +10,19 @@ namespace Ghost.Entities; public unsafe partial struct EntityQuery { +<# for (var f = 0; f < 2; f++) +{ + var isForEachWithEntity = f != 0; +#> <# for (var i = 1; i <= Amount; i++) { var generics = AppendGenerics(i); var compGenerics = AppendGenericRefParameters(i); var restrictions = AppendGenericRestrictionsMultiline(i, "unmanaged, IComponent", 2); + + var delegateTupe = isForEachWithEntity ? "ForEachWithEntity" : "ForEach"; #> - public readonly void ForEach<<#= generics #>>(ForEach<<#= generics #>> action) + public readonly void ForEach<<#= generics #>>(<#= delegateTupe #><<#= generics #>> action) <#= restrictions #> { var world = World.GetWorld(_worldID).GetValueOrThrow(ResultStatus.Success); @@ -25,18 +31,20 @@ public unsafe partial struct EntityQuery var offsets = stackalloc int[<#= i #>]; var basePtrs = stackalloc byte*[<#= i #>]; - foreach (var archetypeID in _matchingArchetypes) + for (var i = 0; i < _matchingArchetypes.Count; i++) { - ref var archetype = ref world.GetArchetypeReference(archetypeID); + ref var archetype = ref world.GetArchetypeReference(_matchingArchetypes[i]); var hasAllComponents = true; for (var index = 0; index < <#= i #>; index++) { - offsets[index] = archetype.GetOffset(compTypeIDs[index]); - if (offsets[index] == -1) + var layoutResult = archetype.GetLayout(compTypeIDs[index]); + if (layoutResult.Status != ResultStatus.Success) { hasAllComponents = false; break; } + + offsets[index] = layoutResult.Value.offset; } if (!hasAllComponents) @@ -47,81 +55,35 @@ public unsafe partial struct EntityQuery for (var chunkIndex = 0; chunkIndex < archetype.ChunkCount; chunkIndex++) { ref var chunk = ref archetype.GetChunkReference(chunkIndex); - var count = chunk.Count; + var pChunkData = chunk.GetUnsafePtr(); + for (var index = 0; index < <#= i #>; index++) { - basePtrs[index] = chunk.GetUnsafePtr() + offsets[index]; + basePtrs[index] = pChunkData + offsets[index]; } - for (var entityIndex = 0; entityIndex < count; entityIndex++) + for (var entityIndex = 0; entityIndex < chunk.Count; entityIndex++) { + if (!IsEntityValid(pChunkData, entityIndex, in archetype, in _mask)) + { + continue; + } + <# for (var localIndex = 0; localIndex < i; localIndex++) { #> var pComp<#= localIndex #> = (T<#= localIndex #>*)(basePtrs[<#= localIndex #>] + (sizeof(T<#= localIndex #>) * entityIndex)); <# } #> - action(<#= AppendRefParameters(i, "*pComp{0}") #>); - } - } - } - } - -<# } #> - -<# for (var i = 1; i <= Amount; i++) -{ - var generics = AppendGenerics(i); - var compGenerics = AppendGenericRefParameters(i); - var restrictions = AppendGenericRestrictionsMultiline(i, "unmanaged, IComponent", 2); -#> - public readonly void ForEach<<#= generics #>>(ForEachWithEntity<<#= generics #>> action) -<#= restrictions #> - { - var world = World.GetWorld(_worldID).GetValueOrThrow(ResultStatus.Success); - - var compTypeIDs = stackalloc int[] { <#= AppendGenerics(i, "ComponentTypeID.value") #> }; - var offsets = stackalloc int[<#= i #>]; - var basePtrs = stackalloc byte*[<#= i #>]; - - foreach (var archetypeID in _matchingArchetypes) - { - ref var archetype = ref world.GetArchetypeReference(archetypeID); - var hasAllComponents = true; - for (var index = 0; index < <#= i #>; index++) - { - offsets[index] = archetype.GetOffset(compTypeIDs[index]); - if (offsets[index] == -1) - { - hasAllComponents = false; - break; - } - } - - if (!hasAllComponents) - { - continue; - } - - for (var chunkIndex = 0; chunkIndex < archetype.ChunkCount; chunkIndex++) - { - ref var chunk = ref archetype.GetChunkReference(chunkIndex); - var count = chunk.Count; - for (var index = 0; index < <#= i #>; index++) - { - basePtrs[index] = chunk.GetUnsafePtr() + offsets[index]; - } - - for (var entityIndex = 0; entityIndex < count; entityIndex++) - { - var pEntity = (Entity*)(chunk.GetUnsafePtr() + archetype.EntityIDsOffset + (sizeof(Entity) * entityIndex)); -<# for (var localIndex = 0; localIndex < i; localIndex++) { #> - var pComp<#= localIndex #> = (T<#= localIndex #>*)(basePtrs[<#= localIndex #>] + (sizeof(T<#= localIndex #>) * entityIndex)); -<# } #> - +<# if (isForEachWithEntity) { #> + var pEntity = (Entity*)(pChunkData + archetype.EntityIDsOffset + (sizeof(Entity) * entityIndex)); action(*pEntity, <#= AppendRefParameters(i, "*pComp{0}") #>); +<# } else { #> + action(<#= AppendRefParameters(i, "*pComp{0}") #>); +<# } #> } } } } +<# } #> <# } #> } \ No newline at end of file diff --git a/Ghost.Entities/Templates/QueryBuilder.With.gen.cs b/Ghost.Entities/Templates/QueryBuilder.With.gen.cs new file mode 100644 index 0000000..2c79ab8 --- /dev/null +++ b/Ghost.Entities/Templates/QueryBuilder.With.gen.cs @@ -0,0 +1,278 @@ + +using System.Runtime.CompilerServices; + +namespace Ghost.Entities; + +public ref partial struct QueryBuilder +{ + /// + /// Adds the specified component type(s) to the 'All' filter of the query. + /// Targets entities that have all of the specified component types and those component(s) must be enabled. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public QueryBuilder WithAll() + where T0 : unmanaged, IComponent + { + _all.Add(ComponentTypeID.value); + + return this; + } + + /// + /// Adds the specified component type(s) to the 'Any' filter of the query. + /// Targets entities that have at least one of the specified component types and those component(s) must be enabled. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public QueryBuilder WithAny() + where T0 : unmanaged, IComponent + { + _any.Add(ComponentTypeID.value); + + return this; + } + + /// + /// Adds the specified component type(s) to the 'Absent' filter of the query. + /// Targets entities that do not have any of the specified component types. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public QueryBuilder WithAbsent() + where T0 : unmanaged, IComponent + { + _absent.Add(ComponentTypeID.value); + + return this; + } + + /// + /// Adds the specified component type(s) to the 'None' filter of the query. + /// Targets entities that do not have any of the specified component types, or those component(s) are disabled. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public QueryBuilder WithNone() + where T0 : unmanaged, IComponent + { + _none.Add(ComponentTypeID.value); + + return this; + } + + /// + /// Adds the specified component type(s) to the 'Disabled' filter of the query. + /// Targets entities that have all of the specified component types and those component(s) are disabled. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public QueryBuilder WithDisabled() + where T0 : unmanaged, IEnableableComponent + { + _disabled.Add(ComponentTypeID.value); + + return this; + } + + /// + /// Adds the specified component type(s) to the 'Present' filter of the query. + /// Targets entities that have all of the specified component types, regardless of whether those component(s) are enabled or disabled. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public QueryBuilder WithPresent() + where T0 : unmanaged, IComponent + { + _present.Add(ComponentTypeID.value); + + return this; + } + + /// + /// Adds the specified component type(s) to the 'All' filter of the query. + /// Targets entities that have all of the specified component types and those component(s) must be enabled. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public QueryBuilder WithAll() + where T0 : unmanaged, IComponent + where T1 : unmanaged, IComponent + { + _all.Add(ComponentTypeID.value); + _all.Add(ComponentTypeID.value); + + return this; + } + + /// + /// Adds the specified component type(s) to the 'Any' filter of the query. + /// Targets entities that have at least one of the specified component types and those component(s) must be enabled. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public QueryBuilder WithAny() + where T0 : unmanaged, IComponent + where T1 : unmanaged, IComponent + { + _any.Add(ComponentTypeID.value); + _any.Add(ComponentTypeID.value); + + return this; + } + + /// + /// Adds the specified component type(s) to the 'Absent' filter of the query. + /// Targets entities that do not have any of the specified component types. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public QueryBuilder WithAbsent() + where T0 : unmanaged, IComponent + where T1 : unmanaged, IComponent + { + _absent.Add(ComponentTypeID.value); + _absent.Add(ComponentTypeID.value); + + return this; + } + + /// + /// Adds the specified component type(s) to the 'None' filter of the query. + /// Targets entities that do not have any of the specified component types, or those component(s) are disabled. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public QueryBuilder WithNone() + where T0 : unmanaged, IComponent + where T1 : unmanaged, IComponent + { + _none.Add(ComponentTypeID.value); + _none.Add(ComponentTypeID.value); + + return this; + } + + /// + /// Adds the specified component type(s) to the 'Disabled' filter of the query. + /// Targets entities that have all of the specified component types and those component(s) are disabled. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public QueryBuilder WithDisabled() + where T0 : unmanaged, IEnableableComponent + where T1 : unmanaged, IEnableableComponent + { + _disabled.Add(ComponentTypeID.value); + _disabled.Add(ComponentTypeID.value); + + return this; + } + + /// + /// Adds the specified component type(s) to the 'Present' filter of the query. + /// Targets entities that have all of the specified component types, regardless of whether those component(s) are enabled or disabled. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public QueryBuilder WithPresent() + where T0 : unmanaged, IComponent + where T1 : unmanaged, IComponent + { + _present.Add(ComponentTypeID.value); + _present.Add(ComponentTypeID.value); + + return this; + } + + /// + /// Adds the specified component type(s) to the 'All' filter of the query. + /// Targets entities that have all of the specified component types and those component(s) must be enabled. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public QueryBuilder WithAll() + where T0 : unmanaged, IComponent + where T1 : unmanaged, IComponent + where T2 : unmanaged, IComponent + { + _all.Add(ComponentTypeID.value); + _all.Add(ComponentTypeID.value); + _all.Add(ComponentTypeID.value); + + return this; + } + + /// + /// Adds the specified component type(s) to the 'Any' filter of the query. + /// Targets entities that have at least one of the specified component types and those component(s) must be enabled. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public QueryBuilder WithAny() + where T0 : unmanaged, IComponent + where T1 : unmanaged, IComponent + where T2 : unmanaged, IComponent + { + _any.Add(ComponentTypeID.value); + _any.Add(ComponentTypeID.value); + _any.Add(ComponentTypeID.value); + + return this; + } + + /// + /// Adds the specified component type(s) to the 'Absent' filter of the query. + /// Targets entities that do not have any of the specified component types. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public QueryBuilder WithAbsent() + where T0 : unmanaged, IComponent + where T1 : unmanaged, IComponent + where T2 : unmanaged, IComponent + { + _absent.Add(ComponentTypeID.value); + _absent.Add(ComponentTypeID.value); + _absent.Add(ComponentTypeID.value); + + return this; + } + + /// + /// Adds the specified component type(s) to the 'None' filter of the query. + /// Targets entities that do not have any of the specified component types, or those component(s) are disabled. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public QueryBuilder WithNone() + where T0 : unmanaged, IComponent + where T1 : unmanaged, IComponent + where T2 : unmanaged, IComponent + { + _none.Add(ComponentTypeID.value); + _none.Add(ComponentTypeID.value); + _none.Add(ComponentTypeID.value); + + return this; + } + + /// + /// Adds the specified component type(s) to the 'Disabled' filter of the query. + /// Targets entities that have all of the specified component types and those component(s) are disabled. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public QueryBuilder WithDisabled() + where T0 : unmanaged, IEnableableComponent + where T1 : unmanaged, IEnableableComponent + where T2 : unmanaged, IEnableableComponent + { + _disabled.Add(ComponentTypeID.value); + _disabled.Add(ComponentTypeID.value); + _disabled.Add(ComponentTypeID.value); + + return this; + } + + /// + /// Adds the specified component type(s) to the 'Present' filter of the query. + /// Targets entities that have all of the specified component types, regardless of whether those component(s) are enabled or disabled. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public QueryBuilder WithPresent() + where T0 : unmanaged, IComponent + where T1 : unmanaged, IComponent + where T2 : unmanaged, IComponent + { + _present.Add(ComponentTypeID.value); + _present.Add(ComponentTypeID.value); + _present.Add(ComponentTypeID.value); + + return this; + } + +} \ No newline at end of file diff --git a/Ghost.Entities/Templates/QueryBuilder.With.tt b/Ghost.Entities/Templates/QueryBuilder.With.tt new file mode 100644 index 0000000..dbed074 --- /dev/null +++ b/Ghost.Entities/Templates/QueryBuilder.With.tt @@ -0,0 +1,110 @@ +<#@ template language="C#" #> +<#@ output extension="gen.cs" #> +<#@ assembly name="System.Core" #> +<#@ import namespace="System.Linq" #> +<#@ import namespace="System.Text" #> +<#@ include file="Helpers.ttinclude" #> +using System.Runtime.CompilerServices; + +namespace Ghost.Entities; + +public ref partial struct QueryBuilder +{ +<# for (var i = 1; i <= 3; i++) +{ + var generics = AppendGenerics(i); + var restrictions = AppendGenericRestrictionsMultiline(i, "unmanaged, IComponent", 2); + var enableRestrictions = AppendGenericRestrictionsMultiline(i, "unmanaged, IEnableableComponent", 2); +#> + /// + /// Adds the specified component type(s) to the 'All' filter of the query. + /// Targets entities that have all of the specified component types and those component(s) must be enabled. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public QueryBuilder WithAll<<#= generics #>>() +<#= restrictions #> + { +<# for (var j = 0; j < i; j++) { #> + _all.Add(ComponentTypeID>.value); +<# } #> + + return this; + } + + /// + /// Adds the specified component type(s) to the 'Any' filter of the query. + /// Targets entities that have at least one of the specified component types and those component(s) must be enabled. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public QueryBuilder WithAny<<#= generics #>>() +<#= restrictions #> + { +<# for (var j = 0; j < i; j++) { #> + _any.Add(ComponentTypeID>.value); +<# } #> + + return this; + } + + /// + /// Adds the specified component type(s) to the 'Absent' filter of the query. + /// Targets entities that do not have any of the specified component types. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public QueryBuilder WithAbsent<<#= generics #>>() +<#= restrictions #> + { +<# for (var j = 0; j < i; j++) { #> + _absent.Add(ComponentTypeID>.value); +<# } #> + + return this; + } + + /// + /// Adds the specified component type(s) to the 'None' filter of the query. + /// Targets entities that do not have any of the specified component types, or those component(s) are disabled. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public QueryBuilder WithNone<<#= generics #>>() +<#= restrictions #> + { +<# for (var j = 0; j < i; j++) { #> + _none.Add(ComponentTypeID>.value); +<# } #> + + return this; + } + + /// + /// Adds the specified component type(s) to the 'Disabled' filter of the query. + /// Targets entities that have all of the specified component types and those component(s) are disabled. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public QueryBuilder WithDisabled<<#= generics #>>() +<#= enableRestrictions #> + { +<# for (var j = 0; j < i; j++) { #> + _disabled.Add(ComponentTypeID>.value); +<# } #> + + return this; + } + + /// + /// Adds the specified component type(s) to the 'Present' filter of the query. + /// Targets entities that have all of the specified component types, regardless of whether those component(s) are enabled or disabled. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public QueryBuilder WithPresent<<#= generics #>>() +<#= restrictions #> + { +<# for (var j = 0; j < i; j++) { #> + _present.Add(ComponentTypeID>.value); +<# } #> + + return this; + } + +<# } #> +} \ No newline at end of file diff --git a/Ghost.Entities/World.cs b/Ghost.Entities/World.cs index a3ffa16..e62d3da 100644 --- a/Ghost.Entities/World.cs +++ b/Ghost.Entities/World.cs @@ -2,6 +2,7 @@ using Ghost.Core; using Misaki.HighPerformance.LowLevel.Buffer; using Misaki.HighPerformance.LowLevel.Collections; using System.Runtime.CompilerServices; +using TerraFX.Interop.Windows; namespace Ghost.Entities; @@ -110,6 +111,7 @@ public partial class World : IIdentifierType, IDisposable, IEquatable Dispose(); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] internal Identifier CreateArchetype(ReadOnlySpan> componentTypeIDs, int signatureHash) { var arcID = new Identifier(_archetypes.Count); @@ -119,12 +121,13 @@ public partial class World : IIdentifierType, IDisposable, IEquatable for (int i = 0; i < _entityQueries.Count; i++) { ref var query = ref _entityQueries[i]; - query.AddArchetypeIfMatch(_archetypes[arcID.value]); + query.AddArchetypeIfMatch(in _archetypes[arcID.value]); } return arcID; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] internal Identifier GetArchetypeIDBySignatureHash(int signatureHash) { if (_archetypeLookup.TryGetValue(signatureHash, out var arcID)) @@ -135,20 +138,29 @@ public partial class World : IIdentifierType, IDisposable, IEquatable return Identifier.Invalid; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] internal ref Archetype GetArchetypeReference(Identifier id) { return ref _archetypes[id.value]; } - internal Identifier CreateEntityQuery(EntityQueryMask mask) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal Identifier CreateEntityQuery(EntityQueryMask mask, int maskHash) { var queryID = new Identifier(_entityQueries.Count); _entityQueries.Add(new EntityQuery(_id, mask)); - _querieLookup.Add(mask.GetHashCode(), queryID); + _querieLookup.Add(maskHash, queryID); + + ref var query = ref _entityQueries[queryID.value]; + for (var i = 0; i < _archetypes.Count; i++) + { + query.AddArchetypeIfMatch(in _archetypes[i]); + } return queryID; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] internal Identifier GetEntityQueryIDByMaskHash(int maskHash) { if (_querieLookup.TryGetValue(maskHash, out var queryID)) @@ -159,6 +171,7 @@ public partial class World : IIdentifierType, IDisposable, IEquatable return Identifier.Invalid; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public ref EntityQuery GetEntityQueryReference(Identifier id) { return ref _entityQueries[id.value];