From 856fa4f07da10420744e2b989bdfe7abaecd110c Mon Sep 17 00:00:00 2001 From: Misaki Date: Wed, 10 Dec 2025 19:01:25 +0900 Subject: [PATCH] Per-component versioning and change tracking for ECS Introduce per-component versioning in chunks and world for efficient change detection. - Add version arrays to chunks and global version to world. - Update queries and ForEach to mark written components as changed. - Extend QueryBuilder with WithAllRW/WithPresentRW for write access. - Expose change tracking API in ChunkView. - Improve thread safety and debug code. - Update tests and examples to demonstrate new features. --- Ghost.Entities.Test/EntityTest.cs | 40 +- Ghost.Entities/Archetype.cs | 48 +- Ghost.Entities/Component.cs | 20 +- Ghost.Entities/Query.cs | 102 ++- .../Templates/EntityQuery.ForEach.gen.cs | 657 +++++++++++++++++- .../Templates/EntityQuery.ForEach.tt | 38 +- .../Templates/QueryBuilder.With.gen.cs | 102 +++ Ghost.Entities/Templates/QueryBuilder.With.tt | 32 + Ghost.Entities/World.cs | 19 + Ghost.Graphics/Utilities/MeshBuilder.cs | 2 +- GhostEngine.slnx | 1 - 11 files changed, 968 insertions(+), 93 deletions(-) diff --git a/Ghost.Entities.Test/EntityTest.cs b/Ghost.Entities.Test/EntityTest.cs index a8815ed..cb37d87 100644 --- a/Ghost.Entities.Test/EntityTest.cs +++ b/Ghost.Entities.Test/EntityTest.cs @@ -39,7 +39,7 @@ public partial class EntityTest : ITest var entity2 = _world.EntityManager.CreateEntity(ComponentTypeID.value); _world.EntityManager.SetComponent(entity2, new Transform { position = new float3(1, 2, 3) }); - var queryID = new QueryBuilder().WithAll().Build(_world); + var queryID = new QueryBuilder().WithAllRW().WithAbsent().Build(_world); ref var query = ref _world.GetEntityQueryReference(queryID); // var testJob = new TestEntityQueryJob(); @@ -49,28 +49,32 @@ public partial class EntityTest : ITest _world.EntityManager.AddScriptComponent(entity1); _world.EntityManager.RemoveComponent(entity1); // This should destory the managed entity and call OnDestroy + _world.AdvanceVersion(); + query.ForEach((e, ref t) => { Console.WriteLine($"Entity {e} Has Position: {t.position}"); }); - // foreach (var (entity, transform) in query.GetEntityComponentIterator()) - // { - // Console.WriteLine($"Entity {entity} Updated Position: {transform.Get().position}"); - // } - // - // foreach (var chunk in query.GetChunkIterator()) - // { - // var transforms = chunk.GetComponentData(); - // var entities = chunk.GetEntities(); - // var bits = chunk.GetEnableBits(); - // - // var it = bits.GetIterator(); - // while (it.Next(out var index) && index < chunk.Count) - // { - // Console.WriteLine($"Entity {entities[index]} Updated Position: {transforms[index].position}"); - // } - // } + //foreach (var (entity, transform) in query.GetEntityComponentIterator()) + //{ + // Console.WriteLine($"Entity {entity} Updated Position: {transform.Get().position}"); + //} + + foreach (var chunk in query.GetChunkIterator()) + { + var transforms = chunk.GetComponentData(); + var entities = chunk.GetEntities(); + var bits = chunk.GetEnableBits(); + + var changed = chunk.HasChanged(0); + + var it = bits.GetIterator(); + while (it.Next(out var index) && index < chunk.Count) + { + Console.WriteLine($"Entity {entities[index]} Updated Position: {transforms[index].position}"); + } + } _world.EntityManager.DestroyEntity(entity1); _world.EntityManager.DestroyEntity(entity2); diff --git a/Ghost.Entities/Archetype.cs b/Ghost.Entities/Archetype.cs index 18fdcb6..58c9f2a 100644 --- a/Ghost.Entities/Archetype.cs +++ b/Ghost.Entities/Archetype.cs @@ -48,7 +48,7 @@ internal unsafe sealed class ChunkDebugView { get { -#if DEBUG || GHOST_EDITOR +#if !(DEBUG || GHOST_EDITOR) #else if (count == 0) #endif @@ -56,9 +56,7 @@ internal unsafe sealed class ChunkDebugView return []; } -#pragma warning disable CS0162 // Unreachable code detected var views = new List(); -#pragma warning restore CS0162 // Unreachable code detected ref var archetype = ref World.GetWorld(worldID).GetValueOrThrow() .GetArchetypeReference(archetypeID); @@ -108,8 +106,8 @@ internal unsafe struct Chunk : IDisposable public const int BIT_ALIGNMENT_MINUS_ONE = BIT_ALIGNMENT - 1; private UnsafeArray _data; + private UnsafeArray _versions; - internal int _version; internal int _count; internal readonly int _capacity; @@ -119,11 +117,26 @@ internal unsafe struct Chunk : IDisposable internal int _archetypeID; #endif - public Chunk(int bufferSize, int capacity) + public Chunk(int bufferSize, int capacity, int componentCount, int globalVersion) { _data = new UnsafeArray(bufferSize, Allocator.Persistent, AllocationOption.Clear); + _versions = new UnsafeArray(componentCount, Allocator.Persistent); _capacity = capacity; _count = 0; + + _versions.AsSpan().Fill(globalVersion); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void MarkChanged(int componentTypeId, int globalVersion) + { + _versions[componentTypeId] = globalVersion; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly int GetVersion(int componentTypeId) + { + return _versions[componentTypeId]; } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -132,9 +145,16 @@ internal unsafe struct Chunk : IDisposable return (byte*)_data.GetUnsafePtr(); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly int* GetVersionUnsafePtr() + { + return (int*)_versions.GetUnsafePtr(); + } + public void Dispose() { _data.Dispose(); + _versions.Dispose(); } } @@ -324,8 +344,10 @@ internal unsafe struct Archetype : IIdentifierType, IDisposable } } + var world = World.GetWorldUncheck(_worldID); + // Need to allocate a new chunk - var newChunk = new Chunk(Chunk.CHUNK_BUFFER_SIZE, _entityCapacity); + var newChunk = new Chunk(Chunk.CHUNK_BUFFER_SIZE, _entityCapacity, _layouts.Count, world.Version); #if DEBUG || GHOST_EDITOR newChunk._worldID = _worldID; newChunk._archetypeID = _id; @@ -370,7 +392,7 @@ internal unsafe struct Archetype : IIdentifierType, IDisposable } var offset = r.Value.offset; - var chunk = _chunks[chunkIndex]; + ref var chunk = ref _chunks[chunkIndex]; var chunkBase = chunk.GetUnsafePtr(); var size = ComponentRegister.GetComponentInfo(componentID).size; @@ -378,6 +400,9 @@ internal unsafe struct Archetype : IIdentifierType, IDisposable MemoryUtility.MemCpy(dst, pComponent, (nuint)size); + var world = World.GetWorldUncheck(_worldID); + chunk.MarkChanged(componentID, world.Version); + return ErrorStatus.None; } @@ -438,13 +463,8 @@ internal unsafe struct Archetype : IIdentifierType, IDisposable var pLastEntity = chunkBase + _entityIdsOffset + (sizeof(Entity) * lastIndex); var pRowEntity = chunkBase + _entityIdsOffset + (sizeof(Entity) * rowIndex); - var wroldResult = World.GetWorld(_worldID); - if (wroldResult.Error != ErrorStatus.None) - { - return wroldResult.Error; - } - - var result = wroldResult.Value.EntityManager.UpdateEntityLocation(*(Entity*)pLastEntity, _id, chunkIndex, rowIndex); + var wrold = World.GetWorldUncheck(_worldID); + var result = wrold.EntityManager.UpdateEntityLocation(*(Entity*)pLastEntity, _id, chunkIndex, rowIndex); if (result != ErrorStatus.None) { return result; diff --git a/Ghost.Entities/Component.cs b/Ghost.Entities/Component.cs index c589e28..3128cab 100644 --- a/Ghost.Entities/Component.cs +++ b/Ghost.Entities/Component.cs @@ -1,6 +1,7 @@ using Ghost.Core; using Misaki.HighPerformance.LowLevel.Collections; using Misaki.HighPerformance.LowLevel.Utilities; +using System.Runtime.CompilerServices; namespace Ghost.Entities; @@ -18,6 +19,7 @@ public struct ComponentInfo public Identifier id; public int size; public int alignment; + public int lastWriteVersion; public bool isEnableable; } @@ -77,6 +79,7 @@ internal static class ComponentRegister } } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Identifier GetComponentID(Type type) { var typeHandle = type.TypeHandle.Value; @@ -91,9 +94,24 @@ internal static class ComponentRegister throw new KeyNotFoundException($"Component type {type} is not registered."); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static ComponentInfo GetComponentInfo(Identifier typeId) { - return s_registeredComponents[typeId]; + lock (s_registeredComponents) + { + return s_registeredComponents[typeId]; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void SetComponentLastWrite(Identifier typeId, int version) + { + lock (s_registeredComponents) + { + var info = s_registeredComponents[typeId]; + info.lastWriteVersion = version; + s_registeredComponents[typeId] = info; + } } public static int GetHashCode(params ReadOnlySpan> componentTypeIDs) diff --git a/Ghost.Entities/Query.cs b/Ghost.Entities/Query.cs index 6fad2bd..5eb979c 100644 --- a/Ghost.Entities/Query.cs +++ b/Ghost.Entities/Query.cs @@ -5,7 +5,7 @@ using System.Runtime.CompilerServices; namespace Ghost.Entities; -public struct EntityQueryMask : IDisposable, IEquatable +internal struct EntityQueryMask : IDisposable, IEquatable { public UnsafeBitSet structuralAll; public UnsafeBitSet structuralAny; @@ -15,6 +15,8 @@ public struct EntityQueryMask : IDisposable, IEquatable public UnsafeBitSet requireDisabled; public UnsafeBitSet rejectIfEnabled; + public UnsafeBitSet writeAccess; + [MethodImpl(MethodImplOptions.AggressiveInlining)] public readonly bool Matches(ref readonly UnsafeBitSet archetypeSignature) { @@ -33,6 +35,7 @@ public struct EntityQueryMask : IDisposable, IEquatable if (requireEnabled.IsCreated) hash = hash * 23 + requireEnabled.GetHashCode(); if (requireDisabled.IsCreated) hash = hash * 23 + requireDisabled.GetHashCode(); if (rejectIfEnabled.IsCreated) hash = hash * 23 + rejectIfEnabled.GetHashCode(); + if (writeAccess.IsCreated) hash = hash * 23 + writeAccess.GetHashCode(); return hash; } @@ -44,7 +47,8 @@ public struct EntityQueryMask : IDisposable, IEquatable && structuralAbsent.Equals(other.structuralAbsent) && requireEnabled.Equals(other.requireEnabled) && requireDisabled.Equals(other.requireDisabled) - && rejectIfEnabled.Equals(other.rejectIfEnabled); + && rejectIfEnabled.Equals(other.rejectIfEnabled) + && writeAccess.Equals(other.writeAccess); } public override readonly bool Equals(object? obj) @@ -71,6 +75,8 @@ public struct EntityQueryMask : IDisposable, IEquatable requireEnabled.Dispose(); requireDisabled.Dispose(); rejectIfEnabled.Dispose(); + + writeAccess.Dispose(); } } @@ -82,21 +88,11 @@ public readonly unsafe ref struct ChunkView { private readonly ReadOnlyUnsafeCollection _layouts; private readonly byte* _pChunkData; + private readonly int* _pVersion; private readonly int _entityOffset; private readonly int _entityCount; - private readonly int _version; public readonly int Count => _entityCount; - public readonly int Version => _version; - - internal ChunkView(ReadOnlyUnsafeCollection layouts, byte* pChunkData, int entityOffset, int entityCount, int version) - { - _layouts = layouts; - _pChunkData = pChunkData; - _entityOffset = entityOffset; - _entityCount = entityCount; - _version = version; - } internal ChunkView(ref readonly Archetype archetype, ref readonly Chunk chunk) { @@ -104,19 +100,63 @@ public readonly unsafe ref struct ChunkView _pChunkData = chunk.GetUnsafePtr(); _entityOffset = archetype.EntityIDsOffset; _entityCount = chunk._count; - _version = chunk._version; + _pVersion = chunk.GetVersionUnsafePtr(); } - // TODO: We do not have a proper versioning system yet. - public bool HasChanged(int version) + /// + /// Determines whether the specified component has changed since the given version. + /// + /// The identifier of the component to check for changes. + /// The version number to compare against the component's current version. Must be greater than or equal to zero. + /// true if the component's current version is less than or equal to the specified version; otherwise, false. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool HasChanged(Identifier id, int version) { - return _version != version; + return version < _pVersion[id]; + } + + /// + /// Determines whether the specified version indicates that the component of type has + /// changed since the last recorded version. + /// + /// The type of component to check for changes. Must be an unmanaged type that implements . + /// The version number to compare against the current version of the component. + /// true if the component of type T has changed since the specified version; otherwise, false. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly bool HasChanged(int version) + where T : unmanaged, IComponent + { + return version < _pVersion[ComponentTypeID.value]; + } + + /// + /// Gets the current version number associated with the specified component identifier. + /// + /// The identifier of the component for which to retrieve the version number. Must reference a valid component. + /// The version number of the specified component. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly int GetComponentVersion(Identifier id) + { + return _pVersion[id]; + } + + /// + /// Gets the current version number associated with the specified component type. + /// + /// The component type for which to retrieve the version. Must be an unmanaged type that implements . + /// The version number of the component type . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly int GetComponentVersion() + where T : unmanaged, IComponent + { + return _pVersion[ComponentTypeID.value]; } /// /// 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. + [MethodImpl(MethodImplOptions.AggressiveInlining)] public ReadOnlySpan GetEntities() { var pEntity = (Entity*)(_pChunkData + _entityOffset); @@ -129,6 +169,7 @@ public readonly unsafe ref struct ChunkView /// 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. + [MethodImpl(MethodImplOptions.AggressiveInlining)] public Span GetComponentData() where T : unmanaged, IComponent { @@ -144,6 +185,7 @@ public readonly unsafe ref struct ChunkView /// 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. + [MethodImpl(MethodImplOptions.AggressiveInlining)] public SpanBitSet GetEnableBits() where T : unmanaged, IEnableableComponent { @@ -164,6 +206,7 @@ public readonly unsafe ref struct ChunkView /// 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. + [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool IsComponentEnabled(int index) where T : unmanaged, IEnableableComponent { @@ -373,6 +416,8 @@ public ref partial struct QueryBuilder private UnsafeList> _disabled; private UnsafeList> _present; + private UnsafeList> _rw; + public QueryBuilder() { _scope = AllocationManager.CreateStackScope(); @@ -383,6 +428,8 @@ public ref partial struct QueryBuilder _none = new UnsafeList>(4, _scope.AllocationHandle); _disabled = new UnsafeList>(4, _scope.AllocationHandle); _present = new UnsafeList>(4, _scope.AllocationHandle); + + _rw = new UnsafeList>(4, _scope.AllocationHandle); } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -394,7 +441,7 @@ public ref partial struct QueryBuilder } } - public Identifier Build(World world) + public Identifier Build(World world, Allocator allocator = Allocator.Persistent) { // 1. Calculate max component ID to size the BitSets var maxID = 0; @@ -408,12 +455,14 @@ public ref partial struct QueryBuilder // 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), + structuralAll = new UnsafeBitSet(maxID + 1, allocator, AllocationOption.Clear), + structuralAny = new UnsafeBitSet(maxID + 1, allocator, AllocationOption.Clear), + structuralAbsent = new UnsafeBitSet(maxID + 1, allocator, AllocationOption.Clear), + requireEnabled = new UnsafeBitSet(maxID + 1, allocator, AllocationOption.Clear), + requireDisabled = new UnsafeBitSet(maxID + 1, allocator, AllocationOption.Clear), + rejectIfEnabled = new UnsafeBitSet(maxID + 1, allocator, AllocationOption.Clear), + + writeAccess = new UnsafeBitSet(maxID + 1, allocator, AllocationOption.Clear), }; // 3. Fill BitSets @@ -456,6 +505,11 @@ public ref partial struct QueryBuilder mask.structuralAny.SetBit(id); } + foreach (var id in _rw) + { + mask.writeAccess.SetBit(id); + } + // 4. Ask World for the Query (Cached) var maskHash = mask.GetHashCode(); var queryID = world.GetEntityQueryIDByMaskHash(maskHash); diff --git a/Ghost.Entities/Templates/EntityQuery.ForEach.gen.cs b/Ghost.Entities/Templates/EntityQuery.ForEach.gen.cs index d4e5437..f6a38cc 100644 --- a/Ghost.Entities/Templates/EntityQuery.ForEach.gen.cs +++ b/Ghost.Entities/Templates/EntityQuery.ForEach.gen.cs @@ -1,3 +1,4 @@ + using Ghost.Core; namespace Ghost.Entities; @@ -7,12 +8,37 @@ public unsafe partial struct EntityQuery public readonly void ForEach(ForEach action) where T0 : unmanaged, IComponent { - var world = World.GetWorld(_worldID).GetValueOrThrow(); + var world = World.GetWorldUncheck(_worldID); + var globalVersion = world.Version; - var compTypeIDs = stackalloc int[] { ComponentTypeID.value }; + var comp0TypeID = ComponentTypeID.value; + + var compTypeIDs = stackalloc int[] + { + comp0TypeID.value, + }; + + var changedCompIDs = stackalloc int[1]; var offsets = stackalloc int[1]; var basePtrs = stackalloc byte*[1]; + var changedCompCount = 0; + + var it = _mask.writeAccess.GetIterator(); + while (it.Next(out var id)) + { + for (var i =0; i < 1; i++) + { + if (id == compTypeIDs[i]) + { + ComponentRegister.SetComponentLastWrite(id, globalVersion); + changedCompIDs[changedCompCount] = id; + changedCompCount++; + break; + } + } + } + for (var i = 0; i < _matchingArchetypes.Count; i++) { ref var archetype = ref world.GetArchetypeReference(_matchingArchetypes[i]); @@ -39,6 +65,11 @@ public unsafe partial struct EntityQuery ref var chunk = ref archetype.GetChunkReference(chunkIndex); var pChunkData = chunk.GetUnsafePtr(); + for (var j = 0; j < changedCompCount; j++) + { + chunk.MarkChanged(changedCompIDs[i], globalVersion); + } + for (var index = 0; index < 1; index++) { basePtrs[index] = pChunkData + offsets[index]; @@ -63,12 +94,39 @@ public unsafe partial struct EntityQuery where T0 : unmanaged, IComponent where T1 : unmanaged, IComponent { - var world = World.GetWorld(_worldID).GetValueOrThrow(); + var world = World.GetWorldUncheck(_worldID); + var globalVersion = world.Version; - var compTypeIDs = stackalloc int[] { ComponentTypeID.value, ComponentTypeID.value }; + var comp0TypeID = ComponentTypeID.value; + var comp1TypeID = ComponentTypeID.value; + + var compTypeIDs = stackalloc int[] + { + comp0TypeID.value, + comp1TypeID.value, + }; + + var changedCompIDs = stackalloc int[2]; var offsets = stackalloc int[2]; var basePtrs = stackalloc byte*[2]; + var changedCompCount = 0; + + var it = _mask.writeAccess.GetIterator(); + while (it.Next(out var id)) + { + for (var i =0; i < 2; i++) + { + if (id == compTypeIDs[i]) + { + ComponentRegister.SetComponentLastWrite(id, globalVersion); + changedCompIDs[changedCompCount] = id; + changedCompCount++; + break; + } + } + } + for (var i = 0; i < _matchingArchetypes.Count; i++) { ref var archetype = ref world.GetArchetypeReference(_matchingArchetypes[i]); @@ -95,6 +153,11 @@ public unsafe partial struct EntityQuery ref var chunk = ref archetype.GetChunkReference(chunkIndex); var pChunkData = chunk.GetUnsafePtr(); + for (var j = 0; j < changedCompCount; j++) + { + chunk.MarkChanged(changedCompIDs[i], globalVersion); + } + for (var index = 0; index < 2; index++) { basePtrs[index] = pChunkData + offsets[index]; @@ -121,12 +184,41 @@ public unsafe partial struct EntityQuery where T1 : unmanaged, IComponent where T2 : unmanaged, IComponent { - var world = World.GetWorld(_worldID).GetValueOrThrow(); + var world = World.GetWorldUncheck(_worldID); + var globalVersion = world.Version; - var compTypeIDs = stackalloc int[] { ComponentTypeID.value, ComponentTypeID.value, ComponentTypeID.value }; + var comp0TypeID = ComponentTypeID.value; + var comp1TypeID = ComponentTypeID.value; + var comp2TypeID = ComponentTypeID.value; + + var compTypeIDs = stackalloc int[] + { + comp0TypeID.value, + comp1TypeID.value, + comp2TypeID.value, + }; + + var changedCompIDs = stackalloc int[3]; var offsets = stackalloc int[3]; var basePtrs = stackalloc byte*[3]; + var changedCompCount = 0; + + var it = _mask.writeAccess.GetIterator(); + while (it.Next(out var id)) + { + for (var i =0; i < 3; i++) + { + if (id == compTypeIDs[i]) + { + ComponentRegister.SetComponentLastWrite(id, globalVersion); + changedCompIDs[changedCompCount] = id; + changedCompCount++; + break; + } + } + } + for (var i = 0; i < _matchingArchetypes.Count; i++) { ref var archetype = ref world.GetArchetypeReference(_matchingArchetypes[i]); @@ -153,6 +245,11 @@ public unsafe partial struct EntityQuery ref var chunk = ref archetype.GetChunkReference(chunkIndex); var pChunkData = chunk.GetUnsafePtr(); + for (var j = 0; j < changedCompCount; j++) + { + chunk.MarkChanged(changedCompIDs[i], globalVersion); + } + for (var index = 0; index < 3; index++) { basePtrs[index] = pChunkData + offsets[index]; @@ -181,12 +278,43 @@ public unsafe partial struct EntityQuery where T2 : unmanaged, IComponent where T3 : unmanaged, IComponent { - var world = World.GetWorld(_worldID).GetValueOrThrow(); + var world = World.GetWorldUncheck(_worldID); + var globalVersion = world.Version; - var compTypeIDs = stackalloc int[] { ComponentTypeID.value, ComponentTypeID.value, ComponentTypeID.value, ComponentTypeID.value }; + var comp0TypeID = ComponentTypeID.value; + var comp1TypeID = ComponentTypeID.value; + var comp2TypeID = ComponentTypeID.value; + var comp3TypeID = ComponentTypeID.value; + + var compTypeIDs = stackalloc int[] + { + comp0TypeID.value, + comp1TypeID.value, + comp2TypeID.value, + comp3TypeID.value, + }; + + var changedCompIDs = stackalloc int[4]; var offsets = stackalloc int[4]; var basePtrs = stackalloc byte*[4]; + var changedCompCount = 0; + + var it = _mask.writeAccess.GetIterator(); + while (it.Next(out var id)) + { + for (var i =0; i < 4; i++) + { + if (id == compTypeIDs[i]) + { + ComponentRegister.SetComponentLastWrite(id, globalVersion); + changedCompIDs[changedCompCount] = id; + changedCompCount++; + break; + } + } + } + for (var i = 0; i < _matchingArchetypes.Count; i++) { ref var archetype = ref world.GetArchetypeReference(_matchingArchetypes[i]); @@ -213,6 +341,11 @@ public unsafe partial struct EntityQuery ref var chunk = ref archetype.GetChunkReference(chunkIndex); var pChunkData = chunk.GetUnsafePtr(); + for (var j = 0; j < changedCompCount; j++) + { + chunk.MarkChanged(changedCompIDs[i], globalVersion); + } + for (var index = 0; index < 4; index++) { basePtrs[index] = pChunkData + offsets[index]; @@ -243,12 +376,45 @@ public unsafe partial struct EntityQuery where T3 : unmanaged, IComponent where T4 : unmanaged, IComponent { - var world = World.GetWorld(_worldID).GetValueOrThrow(); + var world = World.GetWorldUncheck(_worldID); + var globalVersion = world.Version; - var compTypeIDs = stackalloc int[] { ComponentTypeID.value, ComponentTypeID.value, ComponentTypeID.value, ComponentTypeID.value, ComponentTypeID.value }; + var comp0TypeID = ComponentTypeID.value; + var comp1TypeID = ComponentTypeID.value; + var comp2TypeID = ComponentTypeID.value; + var comp3TypeID = ComponentTypeID.value; + var comp4TypeID = ComponentTypeID.value; + + var compTypeIDs = stackalloc int[] + { + comp0TypeID.value, + comp1TypeID.value, + comp2TypeID.value, + comp3TypeID.value, + comp4TypeID.value, + }; + + var changedCompIDs = stackalloc int[5]; var offsets = stackalloc int[5]; var basePtrs = stackalloc byte*[5]; + var changedCompCount = 0; + + var it = _mask.writeAccess.GetIterator(); + while (it.Next(out var id)) + { + for (var i =0; i < 5; i++) + { + if (id == compTypeIDs[i]) + { + ComponentRegister.SetComponentLastWrite(id, globalVersion); + changedCompIDs[changedCompCount] = id; + changedCompCount++; + break; + } + } + } + for (var i = 0; i < _matchingArchetypes.Count; i++) { ref var archetype = ref world.GetArchetypeReference(_matchingArchetypes[i]); @@ -275,6 +441,11 @@ public unsafe partial struct EntityQuery ref var chunk = ref archetype.GetChunkReference(chunkIndex); var pChunkData = chunk.GetUnsafePtr(); + for (var j = 0; j < changedCompCount; j++) + { + chunk.MarkChanged(changedCompIDs[i], globalVersion); + } + for (var index = 0; index < 5; index++) { basePtrs[index] = pChunkData + offsets[index]; @@ -307,12 +478,47 @@ public unsafe partial struct EntityQuery where T4 : unmanaged, IComponent where T5 : unmanaged, IComponent { - var world = World.GetWorld(_worldID).GetValueOrThrow(); + var world = World.GetWorldUncheck(_worldID); + var globalVersion = world.Version; - var compTypeIDs = stackalloc int[] { ComponentTypeID.value, ComponentTypeID.value, ComponentTypeID.value, ComponentTypeID.value, ComponentTypeID.value, ComponentTypeID.value }; + var comp0TypeID = ComponentTypeID.value; + var comp1TypeID = ComponentTypeID.value; + var comp2TypeID = ComponentTypeID.value; + var comp3TypeID = ComponentTypeID.value; + var comp4TypeID = ComponentTypeID.value; + var comp5TypeID = ComponentTypeID.value; + + var compTypeIDs = stackalloc int[] + { + comp0TypeID.value, + comp1TypeID.value, + comp2TypeID.value, + comp3TypeID.value, + comp4TypeID.value, + comp5TypeID.value, + }; + + var changedCompIDs = stackalloc int[6]; var offsets = stackalloc int[6]; var basePtrs = stackalloc byte*[6]; + var changedCompCount = 0; + + var it = _mask.writeAccess.GetIterator(); + while (it.Next(out var id)) + { + for (var i =0; i < 6; i++) + { + if (id == compTypeIDs[i]) + { + ComponentRegister.SetComponentLastWrite(id, globalVersion); + changedCompIDs[changedCompCount] = id; + changedCompCount++; + break; + } + } + } + for (var i = 0; i < _matchingArchetypes.Count; i++) { ref var archetype = ref world.GetArchetypeReference(_matchingArchetypes[i]); @@ -339,6 +545,11 @@ public unsafe partial struct EntityQuery ref var chunk = ref archetype.GetChunkReference(chunkIndex); var pChunkData = chunk.GetUnsafePtr(); + for (var j = 0; j < changedCompCount; j++) + { + chunk.MarkChanged(changedCompIDs[i], globalVersion); + } + for (var index = 0; index < 6; index++) { basePtrs[index] = pChunkData + offsets[index]; @@ -373,12 +584,49 @@ public unsafe partial struct EntityQuery where T5 : unmanaged, IComponent where T6 : unmanaged, IComponent { - var world = World.GetWorld(_worldID).GetValueOrThrow(); + var world = World.GetWorldUncheck(_worldID); + var globalVersion = world.Version; - var compTypeIDs = stackalloc int[] { ComponentTypeID.value, ComponentTypeID.value, ComponentTypeID.value, ComponentTypeID.value, ComponentTypeID.value, ComponentTypeID.value, ComponentTypeID.value }; + var comp0TypeID = ComponentTypeID.value; + var comp1TypeID = ComponentTypeID.value; + var comp2TypeID = ComponentTypeID.value; + var comp3TypeID = ComponentTypeID.value; + var comp4TypeID = ComponentTypeID.value; + var comp5TypeID = ComponentTypeID.value; + var comp6TypeID = ComponentTypeID.value; + + var compTypeIDs = stackalloc int[] + { + comp0TypeID.value, + comp1TypeID.value, + comp2TypeID.value, + comp3TypeID.value, + comp4TypeID.value, + comp5TypeID.value, + comp6TypeID.value, + }; + + var changedCompIDs = stackalloc int[7]; var offsets = stackalloc int[7]; var basePtrs = stackalloc byte*[7]; + var changedCompCount = 0; + + var it = _mask.writeAccess.GetIterator(); + while (it.Next(out var id)) + { + for (var i =0; i < 7; i++) + { + if (id == compTypeIDs[i]) + { + ComponentRegister.SetComponentLastWrite(id, globalVersion); + changedCompIDs[changedCompCount] = id; + changedCompCount++; + break; + } + } + } + for (var i = 0; i < _matchingArchetypes.Count; i++) { ref var archetype = ref world.GetArchetypeReference(_matchingArchetypes[i]); @@ -405,6 +653,11 @@ public unsafe partial struct EntityQuery ref var chunk = ref archetype.GetChunkReference(chunkIndex); var pChunkData = chunk.GetUnsafePtr(); + for (var j = 0; j < changedCompCount; j++) + { + chunk.MarkChanged(changedCompIDs[i], globalVersion); + } + for (var index = 0; index < 7; index++) { basePtrs[index] = pChunkData + offsets[index]; @@ -441,12 +694,51 @@ public unsafe partial struct EntityQuery where T6 : unmanaged, IComponent where T7 : unmanaged, IComponent { - var world = World.GetWorld(_worldID).GetValueOrThrow(); + var world = World.GetWorldUncheck(_worldID); + var globalVersion = world.Version; - var compTypeIDs = stackalloc int[] { ComponentTypeID.value, ComponentTypeID.value, ComponentTypeID.value, ComponentTypeID.value, ComponentTypeID.value, ComponentTypeID.value, ComponentTypeID.value, ComponentTypeID.value }; + var comp0TypeID = ComponentTypeID.value; + var comp1TypeID = ComponentTypeID.value; + var comp2TypeID = ComponentTypeID.value; + var comp3TypeID = ComponentTypeID.value; + var comp4TypeID = ComponentTypeID.value; + var comp5TypeID = ComponentTypeID.value; + var comp6TypeID = ComponentTypeID.value; + var comp7TypeID = ComponentTypeID.value; + + var compTypeIDs = stackalloc int[] + { + comp0TypeID.value, + comp1TypeID.value, + comp2TypeID.value, + comp3TypeID.value, + comp4TypeID.value, + comp5TypeID.value, + comp6TypeID.value, + comp7TypeID.value, + }; + + var changedCompIDs = stackalloc int[8]; var offsets = stackalloc int[8]; var basePtrs = stackalloc byte*[8]; + var changedCompCount = 0; + + var it = _mask.writeAccess.GetIterator(); + while (it.Next(out var id)) + { + for (var i =0; i < 8; i++) + { + if (id == compTypeIDs[i]) + { + ComponentRegister.SetComponentLastWrite(id, globalVersion); + changedCompIDs[changedCompCount] = id; + changedCompCount++; + break; + } + } + } + for (var i = 0; i < _matchingArchetypes.Count; i++) { ref var archetype = ref world.GetArchetypeReference(_matchingArchetypes[i]); @@ -473,6 +765,11 @@ public unsafe partial struct EntityQuery ref var chunk = ref archetype.GetChunkReference(chunkIndex); var pChunkData = chunk.GetUnsafePtr(); + for (var j = 0; j < changedCompCount; j++) + { + chunk.MarkChanged(changedCompIDs[i], globalVersion); + } + for (var index = 0; index < 8; index++) { basePtrs[index] = pChunkData + offsets[index]; @@ -503,12 +800,37 @@ public unsafe partial struct EntityQuery public readonly void ForEach(ForEachWithEntity action) where T0 : unmanaged, IComponent { - var world = World.GetWorld(_worldID).GetValueOrThrow(); + var world = World.GetWorldUncheck(_worldID); + var globalVersion = world.Version; - var compTypeIDs = stackalloc int[] { ComponentTypeID.value }; + var comp0TypeID = ComponentTypeID.value; + + var compTypeIDs = stackalloc int[] + { + comp0TypeID.value, + }; + + var changedCompIDs = stackalloc int[1]; var offsets = stackalloc int[1]; var basePtrs = stackalloc byte*[1]; + var changedCompCount = 0; + + var it = _mask.writeAccess.GetIterator(); + while (it.Next(out var id)) + { + for (var i =0; i < 1; i++) + { + if (id == compTypeIDs[i]) + { + ComponentRegister.SetComponentLastWrite(id, globalVersion); + changedCompIDs[changedCompCount] = id; + changedCompCount++; + break; + } + } + } + for (var i = 0; i < _matchingArchetypes.Count; i++) { ref var archetype = ref world.GetArchetypeReference(_matchingArchetypes[i]); @@ -535,6 +857,11 @@ public unsafe partial struct EntityQuery ref var chunk = ref archetype.GetChunkReference(chunkIndex); var pChunkData = chunk.GetUnsafePtr(); + for (var j = 0; j < changedCompCount; j++) + { + chunk.MarkChanged(changedCompIDs[i], globalVersion); + } + for (var index = 0; index < 1; index++) { basePtrs[index] = pChunkData + offsets[index]; @@ -560,12 +887,39 @@ public unsafe partial struct EntityQuery where T0 : unmanaged, IComponent where T1 : unmanaged, IComponent { - var world = World.GetWorld(_worldID).GetValueOrThrow(); + var world = World.GetWorldUncheck(_worldID); + var globalVersion = world.Version; - var compTypeIDs = stackalloc int[] { ComponentTypeID.value, ComponentTypeID.value }; + var comp0TypeID = ComponentTypeID.value; + var comp1TypeID = ComponentTypeID.value; + + var compTypeIDs = stackalloc int[] + { + comp0TypeID.value, + comp1TypeID.value, + }; + + var changedCompIDs = stackalloc int[2]; var offsets = stackalloc int[2]; var basePtrs = stackalloc byte*[2]; + var changedCompCount = 0; + + var it = _mask.writeAccess.GetIterator(); + while (it.Next(out var id)) + { + for (var i =0; i < 2; i++) + { + if (id == compTypeIDs[i]) + { + ComponentRegister.SetComponentLastWrite(id, globalVersion); + changedCompIDs[changedCompCount] = id; + changedCompCount++; + break; + } + } + } + for (var i = 0; i < _matchingArchetypes.Count; i++) { ref var archetype = ref world.GetArchetypeReference(_matchingArchetypes[i]); @@ -592,6 +946,11 @@ public unsafe partial struct EntityQuery ref var chunk = ref archetype.GetChunkReference(chunkIndex); var pChunkData = chunk.GetUnsafePtr(); + for (var j = 0; j < changedCompCount; j++) + { + chunk.MarkChanged(changedCompIDs[i], globalVersion); + } + for (var index = 0; index < 2; index++) { basePtrs[index] = pChunkData + offsets[index]; @@ -619,12 +978,41 @@ public unsafe partial struct EntityQuery where T1 : unmanaged, IComponent where T2 : unmanaged, IComponent { - var world = World.GetWorld(_worldID).GetValueOrThrow(); + var world = World.GetWorldUncheck(_worldID); + var globalVersion = world.Version; - var compTypeIDs = stackalloc int[] { ComponentTypeID.value, ComponentTypeID.value, ComponentTypeID.value }; + var comp0TypeID = ComponentTypeID.value; + var comp1TypeID = ComponentTypeID.value; + var comp2TypeID = ComponentTypeID.value; + + var compTypeIDs = stackalloc int[] + { + comp0TypeID.value, + comp1TypeID.value, + comp2TypeID.value, + }; + + var changedCompIDs = stackalloc int[3]; var offsets = stackalloc int[3]; var basePtrs = stackalloc byte*[3]; + var changedCompCount = 0; + + var it = _mask.writeAccess.GetIterator(); + while (it.Next(out var id)) + { + for (var i =0; i < 3; i++) + { + if (id == compTypeIDs[i]) + { + ComponentRegister.SetComponentLastWrite(id, globalVersion); + changedCompIDs[changedCompCount] = id; + changedCompCount++; + break; + } + } + } + for (var i = 0; i < _matchingArchetypes.Count; i++) { ref var archetype = ref world.GetArchetypeReference(_matchingArchetypes[i]); @@ -651,6 +1039,11 @@ public unsafe partial struct EntityQuery ref var chunk = ref archetype.GetChunkReference(chunkIndex); var pChunkData = chunk.GetUnsafePtr(); + for (var j = 0; j < changedCompCount; j++) + { + chunk.MarkChanged(changedCompIDs[i], globalVersion); + } + for (var index = 0; index < 3; index++) { basePtrs[index] = pChunkData + offsets[index]; @@ -680,12 +1073,43 @@ public unsafe partial struct EntityQuery where T2 : unmanaged, IComponent where T3 : unmanaged, IComponent { - var world = World.GetWorld(_worldID).GetValueOrThrow(); + var world = World.GetWorldUncheck(_worldID); + var globalVersion = world.Version; - var compTypeIDs = stackalloc int[] { ComponentTypeID.value, ComponentTypeID.value, ComponentTypeID.value, ComponentTypeID.value }; + var comp0TypeID = ComponentTypeID.value; + var comp1TypeID = ComponentTypeID.value; + var comp2TypeID = ComponentTypeID.value; + var comp3TypeID = ComponentTypeID.value; + + var compTypeIDs = stackalloc int[] + { + comp0TypeID.value, + comp1TypeID.value, + comp2TypeID.value, + comp3TypeID.value, + }; + + var changedCompIDs = stackalloc int[4]; var offsets = stackalloc int[4]; var basePtrs = stackalloc byte*[4]; + var changedCompCount = 0; + + var it = _mask.writeAccess.GetIterator(); + while (it.Next(out var id)) + { + for (var i =0; i < 4; i++) + { + if (id == compTypeIDs[i]) + { + ComponentRegister.SetComponentLastWrite(id, globalVersion); + changedCompIDs[changedCompCount] = id; + changedCompCount++; + break; + } + } + } + for (var i = 0; i < _matchingArchetypes.Count; i++) { ref var archetype = ref world.GetArchetypeReference(_matchingArchetypes[i]); @@ -712,6 +1136,11 @@ public unsafe partial struct EntityQuery ref var chunk = ref archetype.GetChunkReference(chunkIndex); var pChunkData = chunk.GetUnsafePtr(); + for (var j = 0; j < changedCompCount; j++) + { + chunk.MarkChanged(changedCompIDs[i], globalVersion); + } + for (var index = 0; index < 4; index++) { basePtrs[index] = pChunkData + offsets[index]; @@ -743,12 +1172,45 @@ public unsafe partial struct EntityQuery where T3 : unmanaged, IComponent where T4 : unmanaged, IComponent { - var world = World.GetWorld(_worldID).GetValueOrThrow(); + var world = World.GetWorldUncheck(_worldID); + var globalVersion = world.Version; - var compTypeIDs = stackalloc int[] { ComponentTypeID.value, ComponentTypeID.value, ComponentTypeID.value, ComponentTypeID.value, ComponentTypeID.value }; + var comp0TypeID = ComponentTypeID.value; + var comp1TypeID = ComponentTypeID.value; + var comp2TypeID = ComponentTypeID.value; + var comp3TypeID = ComponentTypeID.value; + var comp4TypeID = ComponentTypeID.value; + + var compTypeIDs = stackalloc int[] + { + comp0TypeID.value, + comp1TypeID.value, + comp2TypeID.value, + comp3TypeID.value, + comp4TypeID.value, + }; + + var changedCompIDs = stackalloc int[5]; var offsets = stackalloc int[5]; var basePtrs = stackalloc byte*[5]; + var changedCompCount = 0; + + var it = _mask.writeAccess.GetIterator(); + while (it.Next(out var id)) + { + for (var i =0; i < 5; i++) + { + if (id == compTypeIDs[i]) + { + ComponentRegister.SetComponentLastWrite(id, globalVersion); + changedCompIDs[changedCompCount] = id; + changedCompCount++; + break; + } + } + } + for (var i = 0; i < _matchingArchetypes.Count; i++) { ref var archetype = ref world.GetArchetypeReference(_matchingArchetypes[i]); @@ -775,6 +1237,11 @@ public unsafe partial struct EntityQuery ref var chunk = ref archetype.GetChunkReference(chunkIndex); var pChunkData = chunk.GetUnsafePtr(); + for (var j = 0; j < changedCompCount; j++) + { + chunk.MarkChanged(changedCompIDs[i], globalVersion); + } + for (var index = 0; index < 5; index++) { basePtrs[index] = pChunkData + offsets[index]; @@ -808,12 +1275,47 @@ public unsafe partial struct EntityQuery where T4 : unmanaged, IComponent where T5 : unmanaged, IComponent { - var world = World.GetWorld(_worldID).GetValueOrThrow(); + var world = World.GetWorldUncheck(_worldID); + var globalVersion = world.Version; - var compTypeIDs = stackalloc int[] { ComponentTypeID.value, ComponentTypeID.value, ComponentTypeID.value, ComponentTypeID.value, ComponentTypeID.value, ComponentTypeID.value }; + var comp0TypeID = ComponentTypeID.value; + var comp1TypeID = ComponentTypeID.value; + var comp2TypeID = ComponentTypeID.value; + var comp3TypeID = ComponentTypeID.value; + var comp4TypeID = ComponentTypeID.value; + var comp5TypeID = ComponentTypeID.value; + + var compTypeIDs = stackalloc int[] + { + comp0TypeID.value, + comp1TypeID.value, + comp2TypeID.value, + comp3TypeID.value, + comp4TypeID.value, + comp5TypeID.value, + }; + + var changedCompIDs = stackalloc int[6]; var offsets = stackalloc int[6]; var basePtrs = stackalloc byte*[6]; + var changedCompCount = 0; + + var it = _mask.writeAccess.GetIterator(); + while (it.Next(out var id)) + { + for (var i =0; i < 6; i++) + { + if (id == compTypeIDs[i]) + { + ComponentRegister.SetComponentLastWrite(id, globalVersion); + changedCompIDs[changedCompCount] = id; + changedCompCount++; + break; + } + } + } + for (var i = 0; i < _matchingArchetypes.Count; i++) { ref var archetype = ref world.GetArchetypeReference(_matchingArchetypes[i]); @@ -840,6 +1342,11 @@ public unsafe partial struct EntityQuery ref var chunk = ref archetype.GetChunkReference(chunkIndex); var pChunkData = chunk.GetUnsafePtr(); + for (var j = 0; j < changedCompCount; j++) + { + chunk.MarkChanged(changedCompIDs[i], globalVersion); + } + for (var index = 0; index < 6; index++) { basePtrs[index] = pChunkData + offsets[index]; @@ -875,12 +1382,49 @@ public unsafe partial struct EntityQuery where T5 : unmanaged, IComponent where T6 : unmanaged, IComponent { - var world = World.GetWorld(_worldID).GetValueOrThrow(); + var world = World.GetWorldUncheck(_worldID); + var globalVersion = world.Version; - var compTypeIDs = stackalloc int[] { ComponentTypeID.value, ComponentTypeID.value, ComponentTypeID.value, ComponentTypeID.value, ComponentTypeID.value, ComponentTypeID.value, ComponentTypeID.value }; + var comp0TypeID = ComponentTypeID.value; + var comp1TypeID = ComponentTypeID.value; + var comp2TypeID = ComponentTypeID.value; + var comp3TypeID = ComponentTypeID.value; + var comp4TypeID = ComponentTypeID.value; + var comp5TypeID = ComponentTypeID.value; + var comp6TypeID = ComponentTypeID.value; + + var compTypeIDs = stackalloc int[] + { + comp0TypeID.value, + comp1TypeID.value, + comp2TypeID.value, + comp3TypeID.value, + comp4TypeID.value, + comp5TypeID.value, + comp6TypeID.value, + }; + + var changedCompIDs = stackalloc int[7]; var offsets = stackalloc int[7]; var basePtrs = stackalloc byte*[7]; + var changedCompCount = 0; + + var it = _mask.writeAccess.GetIterator(); + while (it.Next(out var id)) + { + for (var i =0; i < 7; i++) + { + if (id == compTypeIDs[i]) + { + ComponentRegister.SetComponentLastWrite(id, globalVersion); + changedCompIDs[changedCompCount] = id; + changedCompCount++; + break; + } + } + } + for (var i = 0; i < _matchingArchetypes.Count; i++) { ref var archetype = ref world.GetArchetypeReference(_matchingArchetypes[i]); @@ -907,6 +1451,11 @@ public unsafe partial struct EntityQuery ref var chunk = ref archetype.GetChunkReference(chunkIndex); var pChunkData = chunk.GetUnsafePtr(); + for (var j = 0; j < changedCompCount; j++) + { + chunk.MarkChanged(changedCompIDs[i], globalVersion); + } + for (var index = 0; index < 7; index++) { basePtrs[index] = pChunkData + offsets[index]; @@ -944,12 +1493,51 @@ public unsafe partial struct EntityQuery where T6 : unmanaged, IComponent where T7 : unmanaged, IComponent { - var world = World.GetWorld(_worldID).GetValueOrThrow(); + var world = World.GetWorldUncheck(_worldID); + var globalVersion = world.Version; - var compTypeIDs = stackalloc int[] { ComponentTypeID.value, ComponentTypeID.value, ComponentTypeID.value, ComponentTypeID.value, ComponentTypeID.value, ComponentTypeID.value, ComponentTypeID.value, ComponentTypeID.value }; + var comp0TypeID = ComponentTypeID.value; + var comp1TypeID = ComponentTypeID.value; + var comp2TypeID = ComponentTypeID.value; + var comp3TypeID = ComponentTypeID.value; + var comp4TypeID = ComponentTypeID.value; + var comp5TypeID = ComponentTypeID.value; + var comp6TypeID = ComponentTypeID.value; + var comp7TypeID = ComponentTypeID.value; + + var compTypeIDs = stackalloc int[] + { + comp0TypeID.value, + comp1TypeID.value, + comp2TypeID.value, + comp3TypeID.value, + comp4TypeID.value, + comp5TypeID.value, + comp6TypeID.value, + comp7TypeID.value, + }; + + var changedCompIDs = stackalloc int[8]; var offsets = stackalloc int[8]; var basePtrs = stackalloc byte*[8]; + var changedCompCount = 0; + + var it = _mask.writeAccess.GetIterator(); + while (it.Next(out var id)) + { + for (var i =0; i < 8; i++) + { + if (id == compTypeIDs[i]) + { + ComponentRegister.SetComponentLastWrite(id, globalVersion); + changedCompIDs[changedCompCount] = id; + changedCompCount++; + break; + } + } + } + for (var i = 0; i < _matchingArchetypes.Count; i++) { ref var archetype = ref world.GetArchetypeReference(_matchingArchetypes[i]); @@ -976,6 +1564,11 @@ public unsafe partial struct EntityQuery ref var chunk = ref archetype.GetChunkReference(chunkIndex); var pChunkData = chunk.GetUnsafePtr(); + for (var j = 0; j < changedCompCount; j++) + { + chunk.MarkChanged(changedCompIDs[i], globalVersion); + } + for (var index = 0; index < 8; index++) { basePtrs[index] = pChunkData + offsets[index]; diff --git a/Ghost.Entities/Templates/EntityQuery.ForEach.tt b/Ghost.Entities/Templates/EntityQuery.ForEach.tt index c2527aa..ff3516c 100644 --- a/Ghost.Entities/Templates/EntityQuery.ForEach.tt +++ b/Ghost.Entities/Templates/EntityQuery.ForEach.tt @@ -24,12 +24,41 @@ public unsafe partial struct EntityQuery public readonly void ForEach<<#= generics #>>(<#= delegateTupe #><<#= generics #>> action) <#= restrictions #> { - var world = World.GetWorld(_worldID).GetValueOrThrow(); + var world = World.GetWorldUncheck(_worldID); + var globalVersion = world.Version; - var compTypeIDs = stackalloc int[] { <#= AppendGenerics(i, "ComponentTypeID.value") #> }; +<# for (var localIndex = 0; localIndex < i; localIndex++) { #> + var comp<#= localIndex #>TypeID = ComponentTypeID>.value; +<# } #> + + var compTypeIDs = stackalloc int[] + { +<# for (var localIndex = 0; localIndex < i; localIndex++) { #> + comp<#= localIndex #>TypeID.value, +<# } #> + }; + + var changedCompIDs = stackalloc int[<#= i #>]; var offsets = stackalloc int[<#= i #>]; var basePtrs = stackalloc byte*[<#= i #>]; + var changedCompCount = 0; + + var it = _mask.writeAccess.GetIterator(); + while (it.Next(out var id)) + { + for (var i =0; i < <#= i #>; i++) + { + if (id == compTypeIDs[i]) + { + ComponentRegister.SetComponentLastWrite(id, globalVersion); + changedCompIDs[changedCompCount] = id; + changedCompCount++; + break; + } + } + } + for (var i = 0; i < _matchingArchetypes.Count; i++) { ref var archetype = ref world.GetArchetypeReference(_matchingArchetypes[i]); @@ -56,6 +85,11 @@ public unsafe partial struct EntityQuery ref var chunk = ref archetype.GetChunkReference(chunkIndex); var pChunkData = chunk.GetUnsafePtr(); + for (var j = 0; j < changedCompCount; j++) + { + chunk.MarkChanged(changedCompIDs[i], globalVersion); + } + for (var index = 0; index < <#= i #>; index++) { basePtrs[index] = pChunkData + offsets[index]; diff --git a/Ghost.Entities/Templates/QueryBuilder.With.gen.cs b/Ghost.Entities/Templates/QueryBuilder.With.gen.cs index 2c79ab8..6c5e6a9 100644 --- a/Ghost.Entities/Templates/QueryBuilder.With.gen.cs +++ b/Ghost.Entities/Templates/QueryBuilder.With.gen.cs @@ -18,6 +18,20 @@ public ref partial struct QueryBuilder return this; } + /// + /// Adds the specified component type(s) to the 'All' filter of the query and requires read-write access. + /// Targets entities that have all of the specified component types and those component(s) must be enabled. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public QueryBuilder WithAllRW() + where T0 : unmanaged, IComponent + { + _all.Add(ComponentTypeID.value); + _rw.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. @@ -83,6 +97,20 @@ public ref partial struct QueryBuilder return this; } + /// + /// Adds the specified component type(s) to the 'Present' filter of the query and requires read-write access. + /// 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 WithPresentRW() + where T0 : unmanaged, IComponent + { + _present.Add(ComponentTypeID.value); + _rw.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. @@ -98,6 +126,23 @@ public ref partial struct QueryBuilder return this; } + /// + /// Adds the specified component type(s) to the 'All' filter of the query and requires read-write access. + /// Targets entities that have all of the specified component types and those component(s) must be enabled. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public QueryBuilder WithAllRW() + where T0 : unmanaged, IComponent + where T1 : unmanaged, IComponent + { + _all.Add(ComponentTypeID.value); + _rw.Add(ComponentTypeID.value); + _all.Add(ComponentTypeID.value); + _rw.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. @@ -173,6 +218,23 @@ public ref partial struct QueryBuilder return this; } + /// + /// Adds the specified component type(s) to the 'Present' filter of the query and requires read-write access. + /// 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 WithPresentRW() + where T0 : unmanaged, IComponent + where T1 : unmanaged, IComponent + { + _present.Add(ComponentTypeID.value); + _rw.Add(ComponentTypeID.value); + _present.Add(ComponentTypeID.value); + _rw.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. @@ -190,6 +252,26 @@ public ref partial struct QueryBuilder return this; } + /// + /// Adds the specified component type(s) to the 'All' filter of the query and requires read-write access. + /// Targets entities that have all of the specified component types and those component(s) must be enabled. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public QueryBuilder WithAllRW() + where T0 : unmanaged, IComponent + where T1 : unmanaged, IComponent + where T2 : unmanaged, IComponent + { + _all.Add(ComponentTypeID.value); + _rw.Add(ComponentTypeID.value); + _all.Add(ComponentTypeID.value); + _rw.Add(ComponentTypeID.value); + _all.Add(ComponentTypeID.value); + _rw.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. @@ -275,4 +357,24 @@ public ref partial struct QueryBuilder return this; } + /// + /// Adds the specified component type(s) to the 'Present' filter of the query and requires read-write access. + /// 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 WithPresentRW() + where T0 : unmanaged, IComponent + where T1 : unmanaged, IComponent + where T2 : unmanaged, IComponent + { + _present.Add(ComponentTypeID.value); + _rw.Add(ComponentTypeID.value); + _present.Add(ComponentTypeID.value); + _rw.Add(ComponentTypeID.value); + _present.Add(ComponentTypeID.value); + _rw.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 index dbed074..fc576b3 100644 --- a/Ghost.Entities/Templates/QueryBuilder.With.tt +++ b/Ghost.Entities/Templates/QueryBuilder.With.tt @@ -31,6 +31,22 @@ public ref partial struct QueryBuilder return this; } + /// + /// Adds the specified component type(s) to the 'All' filter of the query and requires read-write access. + /// Targets entities that have all of the specified component types and those component(s) must be enabled. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public QueryBuilder WithAllRW<<#= generics #>>() +<#= restrictions #> + { +<# for (var j = 0; j < i; j++) { #> + _all.Add(ComponentTypeID>.value); + _rw.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. @@ -106,5 +122,21 @@ public ref partial struct QueryBuilder return this; } + /// + /// Adds the specified component type(s) to the 'Present' filter of the query and requires read-write access. + /// 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 WithPresentRW<<#= generics #>>() +<#= restrictions #> + { +<# for (var j = 0; j < i; j++) { #> + _present.Add(ComponentTypeID>.value); + _rw.Add(ComponentTypeID>.value); +<# } #> + + return this; + } + <# } #> } \ No newline at end of file diff --git a/Ghost.Entities/World.cs b/Ghost.Entities/World.cs index 9333a67..5e9166e 100644 --- a/Ghost.Entities/World.cs +++ b/Ghost.Entities/World.cs @@ -47,6 +47,12 @@ public partial class World } } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static World GetWorldUncheck(Identifier id) + { + return s_worlds[id.value]!; + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Result GetWorld(Identifier id) { @@ -80,6 +86,7 @@ public partial class World : IIdentifierType, IDisposable, IEquatable private UnsafeHashMap> _archetypeLookup; // Signature Hash to Archetype ID private UnsafeHashMap> _querieLookup; // Query Mask Hash to Query ID + private int _version; private bool _disposed = false; internal int ArchetypeCount => _archetypes.Count; @@ -99,6 +106,11 @@ public partial class World : IIdentifierType, IDisposable, IEquatable /// public EntityManager EntityManager => _entityManager; + /// + /// Gets the current version number of the world. + /// + public int Version => Interlocked.CompareExchange(ref _version, 0, 0); + /// /// Gets the main entity command buffer for this world. /// @@ -196,6 +208,7 @@ public partial class World : IIdentifierType, IDisposable, IEquatable return Identifier.Invalid; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] internal void PlaybackEntityCommandBuffers() { _entityCommandBuffer.Playback(); @@ -206,6 +219,12 @@ public partial class World : IIdentifierType, IDisposable, IEquatable } } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal int AdvanceVersion() + { + return Interlocked.Increment(ref _version); + } + /// /// Gets a reference to the entity query with the specified identifier. /// diff --git a/Ghost.Graphics/Utilities/MeshBuilder.cs b/Ghost.Graphics/Utilities/MeshBuilder.cs index 1c22a7f..eccaec9 100644 --- a/Ghost.Graphics/Utilities/MeshBuilder.cs +++ b/Ghost.Graphics/Utilities/MeshBuilder.cs @@ -231,7 +231,7 @@ public unsafe static class MeshBuilder public static void ComputeTangents(UnsafeList vertices, UnsafeList indices) { using var scope = AllocationManager.CreateStackScope(); - using var bitangents = new UnsafeArray(vertices.Count, Allocator.Stack); + var bitangents = new UnsafeArray(vertices.Count, scope.AllocationHandle); for (var i = 0; i < indices.Count; i += 3) { diff --git a/GhostEngine.slnx b/GhostEngine.slnx index 0044d31..ab31aa1 100644 --- a/GhostEngine.slnx +++ b/GhostEngine.slnx @@ -20,7 +20,6 @@ -