From 8cf706d64e9cff9209ddf493c760dcc51a7978b6 Mon Sep 17 00:00:00 2001 From: Lars Date: Mon, 4 Nov 2024 23:28:37 +0100 Subject: [PATCH] Refactored code, Added `Signature.Add` and `Signature.Remove`. --- .../Fundamentals/StructuralChanges.cs | 109 ----------------- .../Queries/AddWithQueryDescription.cs | 98 --------------- .../Queries/RemoveWithQueryDescription.cs | 94 --------------- src/Arch.SourceGen/QueryGenerator.cs | 4 - src/Arch.Tests/ArchetypeTest.cs | 4 +- src/Arch/Arch.csproj | 36 ++++++ src/Arch/Buffer/CommandBuffer.cs | 10 +- src/Arch/Core/Archetype.cs | 89 +++++++------- src/Arch/Core/Chunk.cs | 5 + src/Arch/Core/Edges/World.Edges.cs | 8 +- .../Dangerous/DangerousWorldExtensions.cs | 2 +- src/Arch/Core/Query.cs | 112 +++++++++++++++--- src/Arch/Core/World.cs | 39 +++--- src/Arch/Templates/World.Add.tt | 61 ++++++++++ .../World.AddWithQueryDescription.tt | 80 +++++++++++++ src/Arch/Templates/World.Remove.tt | 54 +++++++++ .../World.RemoveWithQueryDescription.tt | 77 ++++++++++++ 17 files changed, 483 insertions(+), 399 deletions(-) delete mode 100644 src/Arch.SourceGen/Queries/AddWithQueryDescription.cs delete mode 100644 src/Arch.SourceGen/Queries/RemoveWithQueryDescription.cs create mode 100644 src/Arch/Templates/World.Add.tt create mode 100644 src/Arch/Templates/World.AddWithQueryDescription.tt create mode 100644 src/Arch/Templates/World.Remove.tt create mode 100644 src/Arch/Templates/World.RemoveWithQueryDescription.tt diff --git a/src/Arch.SourceGen/Fundamentals/StructuralChanges.cs b/src/Arch.SourceGen/Fundamentals/StructuralChanges.cs index 5d82d69..34a806f 100644 --- a/src/Arch.SourceGen/Fundamentals/StructuralChanges.cs +++ b/src/Arch.SourceGen/Fundamentals/StructuralChanges.cs @@ -2,115 +2,6 @@ namespace Arch.SourceGen; public static class StructuralChangesExtensions { - public static StringBuilder AppendWorldAdds(this StringBuilder sb, int amount) - { - for (var index = 1; index < amount; index++) - { - sb.AppendWorldAdd(index); - } - - return sb; - } - - public static StringBuilder AppendWorldAdd(this StringBuilder sb, int amount) - { - var generics = new StringBuilder().GenericWithoutBrackets(amount); - var parameters = new StringBuilder().GenericInDefaultParams(amount); - var inParameters = new StringBuilder().InsertGenericInParams(amount); - var types = new StringBuilder().GenericTypeParams(amount); - - var getIds = new StringBuilder(); - var setIds = new StringBuilder(); - var addEvents = new StringBuilder(); - for (var index = 0; index <= amount; index++) - { - getIds.AppendLine($"var id{index} = Component.ComponentType.Id;"); - setIds.AppendLine($"spanBitSet.SetBit(id{index});"); - addEvents.AppendLine($"OnComponentAdded(entity);"); - } - - var template = - $$""" - [SkipLocalsInit] - - [StructuralChange] - public void Add<{{generics}}>(Entity entity, {{parameters}}) - { - var oldArchetype = EntityInfo.GetArchetype(entity.Id); - - // Get all the ids here just in case we are adding a new component as this will grow the ComponentRegistry.Size - {{getIds}} - - // BitSet to stack/span bitset, size big enough to contain ALL registered components. - Span stack = stackalloc uint[BitSet.RequiredLength(ComponentRegistry.Size)]; - oldArchetype.BitSet.AsSpan(stack); - - // Create a span bitset, doing it local saves us headache and gargabe - var spanBitSet = new SpanBitSet(stack); - {{setIds}} - - if (!TryGetArchetype(spanBitSet.GetHashCode(), out var newArchetype)) - newArchetype = GetOrCreate(oldArchetype.Types.Add({{types}})); - - Move(entity, oldArchetype, newArchetype, out var newSlot); - newArchetype.Set<{{generics}}>(ref newSlot, {{inParameters}}); - {{addEvents}} - } - """; - - return sb.AppendLine(template); - } - - public static StringBuilder AppendWorldRemoves(this StringBuilder sb, int amount) - { - for (var index = 1; index < amount; index++) - { - sb.AppendWorldRemove(index); - } - - return sb; - } - - public static StringBuilder AppendWorldRemove(this StringBuilder sb, int amount) - { - var generics = new StringBuilder().GenericWithoutBrackets(amount); - var types = new StringBuilder().GenericTypeParams(amount); - - var removes = new StringBuilder(); - var events = new StringBuilder(); - for (var index = 0; index <= amount; index++) - { - removes.AppendLine($"spanBitSet.ClearBit(Component.ComponentType.Id);"); - events.AppendLine($"OnComponentRemoved(entity);"); - } - - var template = - $$""" - [SkipLocalsInit] - - [StructuralChange] - public void Remove<{{generics}}>(Entity entity) - { - var oldArchetype = EntityInfo.GetArchetype(entity.Id); - - // BitSet to stack/span bitset, size big enough to contain ALL registered components. - Span stack = stackalloc uint[oldArchetype.BitSet.Length]; - oldArchetype.BitSet.AsSpan(stack); - - // Create a span bitset, doing it local saves us headache and gargabe - var spanBitSet = new SpanBitSet(stack); - {{removes}} - - if (!TryGetArchetype(spanBitSet.GetHashCode(), out var newArchetype)) - newArchetype = GetOrCreate(oldArchetype.Types.Remove({{types}})); - - {{events}} - Move(entity, oldArchetype, newArchetype, out _); - } - """; - - return sb.AppendLine(template); - } public static StringBuilder AppendEntityAdds(this StringBuilder sb, int amount) { diff --git a/src/Arch.SourceGen/Queries/AddWithQueryDescription.cs b/src/Arch.SourceGen/Queries/AddWithQueryDescription.cs deleted file mode 100644 index d3a7531..0000000 --- a/src/Arch.SourceGen/Queries/AddWithQueryDescription.cs +++ /dev/null @@ -1,98 +0,0 @@ -namespace Arch.SourceGen; - -/// -/// Adds extension methods for generating `World.Add(in query, T0...TN);` methods. -/// -public static class AddWithQueryDescription -{ - /// - /// Appends `World.Add(in query, T0...TN)` methods. - /// - /// The instance. - /// The amount. - /// - public static StringBuilder AppendAddWithQueryDescriptions(this StringBuilder sb, int amount) - { - for (var index = 1; index < amount; index++) - { - sb.AppendAddWithQueryDescription(index); - } - - return sb; - } - - /// - /// Appends a `World.Add(in query, T0...TN)` method. - /// - /// The instance. - /// The amount of generic parameters. - public static void AppendAddWithQueryDescription(this StringBuilder sb, int amount) - { - var generics = new StringBuilder().GenericWithoutBrackets(amount); - var parameters = new StringBuilder().GenericInDefaultParams(amount); - var inParameters = new StringBuilder().InsertGenericInParams(amount); - var types = new StringBuilder().GenericTypeParams(amount); - - var setIds = new StringBuilder(); - var addEvents = new StringBuilder(); - var setEvents = new StringBuilder(); - for (var index = 0; index <= amount; index++) - { - setIds.AppendLine($"spanBitSet.SetBit(Component.ComponentType.Id);"); - addEvents.AppendLine($"OnComponentAdded(archetype);"); - } - - var template = - $$""" - [SkipLocalsInit] - - [StructuralChange] - public void Add<{{generics}}>(in QueryDescription queryDescription, {{parameters}}) - { - // BitSet to stack/span bitset, size big enough to contain ALL registered components. - Span stack = stackalloc uint[BitSet.RequiredLength(ComponentRegistry.Size + {{amount + 1}})]; - - var query = Query(in queryDescription); - foreach (var archetype in query.GetArchetypeIterator()) - { - // Archetype with T shouldnt be skipped to prevent undefined behaviour. - if(archetype.EntityCount == 0 || archetype.Has<{{generics}}>()) - { - continue; - } - - // Create local bitset on the stack and set bits to get a new fitting bitset of the new archetype. - archetype.BitSet.AsSpan(stack); - var spanBitSet = new SpanBitSet(stack); - {{setIds}} - - // Get or create new archetype. - if (!TryGetArchetype(spanBitSet.GetHashCode(), out var newArchetype)) - { - newArchetype = GetOrCreate(archetype.Types.Add({{types}})); - } - - // Get last slots before copy, for updating entityinfo later - var archetypeSlot = archetype.LastSlot; - var newArchetypeLastSlot = newArchetype.LastSlot; - Slot.Shift(ref newArchetypeLastSlot, newArchetype.EntitiesPerChunk); - EntityInfo.Shift(archetype, archetypeSlot, newArchetype, newArchetypeLastSlot); - - // Copy, set and clear - var oldCapacity = newArchetype.EntityCapacity; - Archetype.Copy(archetype, newArchetype); - var lastSlot = newArchetype.LastSlot; - newArchetype.SetRange(in lastSlot, in newArchetypeLastSlot, {{inParameters}}); - archetype.Clear(); - - Capacity += newArchetype.EntityCapacity - oldCapacity; - {{addEvents}} - } - - EntityInfo.EnsureCapacity(Capacity); - } - """; - - sb.AppendLine(template); - } -} diff --git a/src/Arch.SourceGen/Queries/RemoveWithQueryDescription.cs b/src/Arch.SourceGen/Queries/RemoveWithQueryDescription.cs deleted file mode 100644 index 5199546..0000000 --- a/src/Arch.SourceGen/Queries/RemoveWithQueryDescription.cs +++ /dev/null @@ -1,94 +0,0 @@ -namespace Arch.SourceGen; - -/// -/// Adds extension methods for generating `World.Remove(in query);` methods. -/// -public static class RemoveWithQueryDesription -{ - /// - /// Appends `World.Remove(in query)` methods. - /// - /// The instance. - /// The amount. - /// - public static StringBuilder AppendRemoveWithQueryDescriptions(this StringBuilder sb, int amount) - { - for (var index = 1; index < amount; index++) - { - sb.AppendRemoveWithQueryDescription(index); - } - - return sb; - } - - /// - /// Appends a `World.Remove(in query)` method. - /// - /// The instance. - /// The amount of generic parameters. - public static void AppendRemoveWithQueryDescription(this StringBuilder sb, int amount) - { - var generics = new StringBuilder().GenericWithoutBrackets(amount); - var types = new StringBuilder().GenericTypeParams(amount); - - var clearIds = new StringBuilder(); - var removeEvents = new StringBuilder(); - for (var index = 0; index <= amount; index++) - { - clearIds.AppendLine($"spanBitSet.ClearBit(Component.ComponentType.Id);"); - removeEvents.AppendLine($"OnComponentRemoved(archetype);"); - } - - var template = - $$""" - [SkipLocalsInit] - - [StructuralChange] - public void Remove<{{generics}}>(in QueryDescription queryDescription) - { - // BitSet to stack/span bitset, size big enough to contain ALL registered components. - Span stack = stackalloc uint[BitSet.RequiredLength(ComponentRegistry.Size)]; - - var query = Query(in queryDescription); - foreach (var archetype in query.GetArchetypeIterator()) - { - // Archetype without T shouldnt be skipped to prevent undefined behaviour. - if(archetype.EntityCount <= 0 || !archetype.Has<{{generics}}>()) - { - continue; - } - - // Create local bitset on the stack and set bits to get a new fitting bitset of the new archetype. - var bitSet = archetype.BitSet; - var spanBitSet = new SpanBitSet(bitSet.AsSpan(stack)); - {{clearIds}} - - // Get or create new archetype. - if (!TryGetArchetype(spanBitSet.GetHashCode(), out var newArchetype)) - { - newArchetype = GetOrCreate(archetype.Types.Remove({{types}})); - } - - {{removeEvents}} - - // Get last slots before copy, for updating entityinfo later - var archetypeSlot = archetype.LastSlot; - var newArchetypeLastSlot = newArchetype.LastSlot; - Slot.Shift(ref newArchetypeLastSlot, newArchetype.EntitiesPerChunk); - EntityInfo.Shift(archetype, archetypeSlot, newArchetype, newArchetypeLastSlot); - - var oldCapacity = newArchetype.EntityCapacity; - Archetype.Copy(archetype, newArchetype); - archetype.Clear(); - - Capacity += newArchetype.EntityCapacity - oldCapacity; - } - - EntityInfo.EnsureCapacity(Capacity); - } - """; - - sb.AppendLine(template); - } -} - diff --git a/src/Arch.SourceGen/QueryGenerator.cs b/src/Arch.SourceGen/QueryGenerator.cs index 1a32dfa..8ea2f11 100644 --- a/src/Arch.SourceGen/QueryGenerator.cs +++ b/src/Arch.SourceGen/QueryGenerator.cs @@ -97,8 +97,6 @@ public partial class World {{new StringBuilder().AppendWorldHases(25)}} {{new StringBuilder().AppendWorldGets(25)}} {{new StringBuilder().AppendWorldSets(25)}} - {{new StringBuilder().AppendWorldAdds(25)}} - {{new StringBuilder().AppendWorldRemoves(25)}} {{new StringBuilder().AppendQueryMethods(25)}} {{new StringBuilder().AppendEntityQueryMethods(25)}} @@ -111,8 +109,6 @@ public partial class World {{new StringBuilder().AppendHpeParallelQuerys(25)}} {{new StringBuilder().AppendSetWithQueryDescriptions(25)}} - {{new StringBuilder().AppendAddWithQueryDescriptions(25)}} - {{new StringBuilder().AppendRemoveWithQueryDescriptions(25)}} } public partial struct QueryDescription diff --git a/src/Arch.Tests/ArchetypeTest.cs b/src/Arch.Tests/ArchetypeTest.cs index 1951e69..9eb89e7 100644 --- a/src/Arch.Tests/ArchetypeTest.cs +++ b/src/Arch.Tests/ArchetypeTest.cs @@ -345,8 +345,8 @@ public void CopyToShift([Values(1111,2222,3333)] int sourceAmount, [Values(1111, } // Calculate their slots and position of copied entity. - var sourceSlot = source.LastSlot; - var destinationSlot = destination.LastSlot; + var sourceSlot = source.CurrentSlot; + var destinationSlot = destination.CurrentSlot; destinationSlot++; var resultSlot = Slot.Shift(sourceSlot, source.EntitiesPerChunk, destinationSlot, destination.EntitiesPerChunk); diff --git a/src/Arch/Arch.csproj b/src/Arch/Arch.csproj index 4023517..209cd7b 100644 --- a/src/Arch/Arch.csproj +++ b/src/Arch/Arch.csproj @@ -139,6 +139,22 @@ Merged arrays in EntityInfo for increased performance when creating and destroyi TextTemplatingFileGenerator World.CreateBulk.cs + + TextTemplatingFileGenerator + World.RemoveWithQueryDescription.cs + + + TextTemplatingFileGenerator + World.AddWithQueryDescription.cs + + + TextTemplatingFileGenerator + World.Add.cs + + + TextTemplatingFileGenerator + World.Remove.cs + @@ -156,6 +172,26 @@ Merged arrays in EntityInfo for increased performance when creating and destroyi True World.CreateBulk.tt + + True + True + World.Add.tt + + + True + True + World.RemoveWithQueryDescription.tt + + + True + True + World.AddWithQueryDescription.tt + + + True + True + World.Remove.tt + diff --git a/src/Arch/Buffer/CommandBuffer.cs b/src/Arch/Buffer/CommandBuffer.cs index 19fbd77..48cd047 100644 --- a/src/Arch/Buffer/CommandBuffer.cs +++ b/src/Arch/Buffer/CommandBuffer.cs @@ -315,7 +315,7 @@ public void Playback(World world, bool dispose = true) var entity = Resolve(wrappedEntity.Entity); Debug.Assert(world.IsAlive(entity), $"CommandBuffer can not to add components to the dead {wrappedEntity.Entity}"); - AddRange(world, entity, _addTypes); + AddRange(world, entity, _addTypes.Span); _addTypes.Clear(); } @@ -442,8 +442,7 @@ public sealed partial class CommandBuffer /// The . /// A of 's, those are added to the . [SkipLocalsInit] - - internal static void AddRange(World world, Entity entity, IList components) + internal static void AddRange(World world, Entity entity, Span components) { var oldArchetype = world.EntityInfo.GetArchetype(entity.Id); @@ -454,7 +453,7 @@ internal static void AddRange(World world, Entity entity, IList c // Create a span bitset, doing it local saves us headache and gargabe var spanBitSet = new SpanBitSet(stack); - for (var index = 0; index < components.Count; index++) + for (var index = 0; index < components.Length; index++) { var type = components[index]; spanBitSet.SetBit(type.Id); @@ -462,7 +461,8 @@ internal static void AddRange(World world, Entity entity, IList c if (!world.TryGetArchetype(spanBitSet.GetHashCode(), out var newArchetype)) { - newArchetype = world.GetOrCreate(oldArchetype.Types.Add(components)); + var newSignature = Signature.Add(oldArchetype.Signature, components); + newArchetype = world.GetOrCreate(newSignature); } world.Move(entity, oldArchetype, newArchetype, out _); diff --git a/src/Arch/Core/Archetype.cs b/src/Arch/Core/Archetype.cs index 95f4523..9ec7234 100644 --- a/src/Arch/Core/Archetype.cs +++ b/src/Arch/Core/Archetype.cs @@ -280,7 +280,7 @@ public sealed partial class Archetype /// The component structure of the 's that can be stored in this . internal Archetype(Signature signature) { - Types = signature; + Signature = signature; // Calculations ChunkSizeInBytes = GetChunkSizeInBytesFor(MinimumAmountOfEntitiesPerChunk, signature); @@ -299,39 +299,39 @@ internal Archetype(Signature signature) } /// - /// The component types that the 's stored here have. + /// The minimum number of 's that should fit into a within this . + /// On the basis of this, the is increased. /// - public ComponentType[] Types { get; } + public int MinimumAmountOfEntitiesPerChunk { get; } = 100; /// - /// A bitset representation of the array for fast lookups and queries. + /// The size of a within the in KB. + /// Necessary because the adjusts the size of a . /// - public BitSet BitSet { get; } + public int ChunkSizeInBytes { get; } = BaseSize; /// - /// The lookup array used by this , is being passed to all its to save memory. + /// The number of entities that are stored per . /// - internal int[] LookupArray - { - get => _componentIdToArrayIndex; - } + public int EntitiesPerChunk { get; } /// - /// The number of entities that are stored per . + /// The component types that the 's stored here have. /// - public int EntitiesPerChunk { get; } + public Signature Signature { get; } /// - /// The size of a within the in KB. - /// Necessary because the adjusts the size of a . + /// A bitset representation of the array for fast lookups and queries. /// - public int ChunkSizeInBytes { get; } = BaseSize; + public BitSet BitSet { get; } /// - /// The minimum number of 's that should fit into a within this . - /// On the basis of this, the is increased. + /// The lookup array used by this , is being passed to all its to save memory. /// - public int MinimumAmountOfEntitiesPerChunk { get; } = 100; + internal int[] LookupArray + { + get => _componentIdToArrayIndex; + } /// /// An array which stores the 's. @@ -366,19 +366,18 @@ public int ChunkCapacity { public int Count { get; internal set; } /// - /// Points to the last that is not yet full. + /// Points to the current in use with remaining capacity. /// - internal ref Chunk LastChunk { get => ref Chunks[Count]; } + internal ref Chunk CurrentChunk { get => ref Chunks[Count]; } /// /// Points to the last . /// - internal Slot LastSlot + internal Slot CurrentSlot { get { - var lastRow = LastChunk.Count - 1; - //lastRow = lastRow > 0 ? lastRow : 0; // Make sure no negative slot is returned when chunk is empty. + var lastRow = CurrentChunk.Count - 1; return new(lastRow, Count); } } @@ -410,7 +409,7 @@ public ref Chunk AddChunk() // Insert chunk var count = Chunks.Count; - Chunks.Add(new Chunk(EntitiesPerChunk, _componentIdToArrayIndex, Types)); + Chunks.Add(new Chunk(EntitiesPerChunk, _componentIdToArrayIndex, Signature)); return ref Chunks[count]; } @@ -434,13 +433,14 @@ public ref Chunk GetChunk(int index) /// True if a new was allocated, otherwise false. internal bool Add(Entity entity, out Chunk chunk, out Slot slot) // TODO: Store chunk reference in slot? { - // Storing stack variables to prevent multiple times accessing those fields. EntityCount++; + + // Storing stack variables to prevent multiple times accessing those fields. var count = Count; ref var currentChunk = ref GetChunk(count); // Fill chunk - if (currentChunk.Count < currentChunk.Capacity) + if (currentChunk.IsEmpty) { slot = new Slot(currentChunk.Add(entity), count); chunk = currentChunk; @@ -506,7 +506,7 @@ internal void Remove(Slot slot, out int movedEntityId) { // Move the last entity from the last chunk into the chunk to replace the removed entity directly ref var chunk = ref GetChunk(slot.ChunkIndex); - ref var lastChunk = ref LastChunk; + ref var lastChunk = ref CurrentChunk; movedEntityId = chunk.Transfer(slot.Index, ref lastChunk); EntityCount--; @@ -520,6 +520,17 @@ internal void Remove(Slot slot, out int movedEntityId) Count--; } + /// + /// Returns a reference of the at a given . + /// + /// The . + /// A reference to the . + internal ref Entity Entity(scoped ref Slot slot) + { + ref var chunk = ref GetChunk(slot.ChunkIndex); + return ref chunk.Entity(slot.Index); + } + /// /// Adds an to the and offloads it to a . /// Uses the last that is not full, once it is full and the capacity is exhausted, a new is allocated. @@ -570,16 +581,6 @@ internal ref T Get(scoped ref Slot slot) return ref chunk.Get(slot.Index); } - /// - /// Returns a reference of the at a given . - /// - /// The . - /// A reference to the . - internal ref Entity Entity(scoped ref Slot slot) - { - ref var chunk = ref GetChunk(slot.ChunkIndex); - return ref chunk.Entity(slot.Index); - } /// /// Sets a component value for all entities within an in a certain range of s @@ -609,7 +610,6 @@ internal void SetRange(in Slot from, in Slot to, in T? component = default) /// Creates an which iterates over all in this . /// /// An . - public Enumerator GetEnumerator() { return new Enumerator(Chunks.AsSpan()[..ChunkCount]); @@ -619,7 +619,6 @@ public Enumerator GetEnumerator() /// Creates an which iterates over all within a range backwards. /// /// A . - internal ChunkRangeIterator GetRangeIterator(int from, int to) { return new ChunkRangeIterator(this, from, to); @@ -629,17 +628,15 @@ internal ChunkRangeIterator GetRangeIterator(int from, int to) /// Creates an which iterates from the last valid chunk to another within a range backwards. /// /// A . - internal ChunkRangeIterator GetRangeIterator(int to) { - return new ChunkRangeIterator(this, LastSlot.ChunkIndex, to); + return new ChunkRangeIterator(this, CurrentSlot.ChunkIndex, to); } /// /// Cleares this , an efficient method to delete all s. /// Does not dispose any resources nor modifies its . /// - public void Clear() { Count = 0; @@ -655,11 +652,9 @@ public void Clear() /// Converts this to a human readable string. /// /// A string. - public override string ToString() { - var types = string.Join(",", Types.Select(p => p.Type.Name).ToArray()); - return $"Archetype {{ {nameof(Types)} = {{ {types} }}, {nameof(BitSet)} = {{ {BitSet} }}, {nameof(EntitiesPerChunk)} = {EntitiesPerChunk}, {nameof(ChunkSizeInBytes)} = {ChunkSizeInBytes}, {nameof(ChunkCapacity)} = {ChunkCapacity}, {nameof(ChunkCount)} = {ChunkCount}, {nameof(EntityCapacity)} = {EntityCapacity}, {nameof(EntityCount)} = {EntityCount} }}}}"; + return $"Archetype {{ {nameof(Signature)} = {{ {Signature} }}, {nameof(BitSet)} = {{ {BitSet} }}, {nameof(EntitiesPerChunk)} = {EntitiesPerChunk}, {nameof(ChunkSizeInBytes)} = {ChunkSizeInBytes}, {nameof(ChunkCapacity)} = {ChunkCapacity}, {nameof(ChunkCount)} = {ChunkCount}, {nameof(EntityCapacity)} = {EntityCapacity}, {nameof(EntityCount)} = {EntityCount} }}}}"; } } @@ -738,7 +733,7 @@ internal void EnsureEntityCapacity(int newCapacity) for (var index = previousCapacity; index < neededChunks; index++) { - Chunks.Add(new Chunk(EntitiesPerChunk, _componentIdToArrayIndex, Types)); + Chunks.Add(new Chunk(EntitiesPerChunk, _componentIdToArrayIndex, Signature)); } } @@ -865,7 +860,7 @@ internal static void Copy(Archetype source, Archetype destination) // Set new chunk count and if the lastchunk was set to 0 by the copy algorithm, reduce it by one to point to a valid chunk destination.Count = destinationChunkIndex; - if (destination.LastChunk.Count == 0) + if (destination.CurrentChunk.Count == 0) { destination.Count--; } diff --git a/src/Arch/Core/Chunk.cs b/src/Arch/Core/Chunk.cs index 4ddc006..d473def 100644 --- a/src/Arch/Core/Chunk.cs +++ b/src/Arch/Core/Chunk.cs @@ -172,6 +172,11 @@ internal Chunk(int capacity, int[] componentIdToArrayIndex, Span /// public readonly bool IsFull { [Pure] get => Count >= Capacity; } + /// + /// Checks whether this instance is full or not. + /// + public readonly bool IsEmpty { [Pure] get => Count < Capacity; } + /// /// Inserts an entity into the . /// This won't fire an event for . diff --git a/src/Arch/Core/Edges/World.Edges.cs b/src/Arch/Core/Edges/World.Edges.cs index ebf19c6..b783984 100644 --- a/src/Arch/Core/Edges/World.Edges.cs +++ b/src/Arch/Core/Edges/World.Edges.cs @@ -13,7 +13,6 @@ public partial class World /// The new that additionally forms a new with the old components of the old archetype. /// The old . /// The cached or newly created with that additional component. - private Archetype GetOrCreateArchetypeByAddEdge(in ComponentType type, Archetype oldArchetype) { Archetype archetype; @@ -21,7 +20,8 @@ private Archetype GetOrCreateArchetypeByAddEdge(in ComponentType type, Archetype if (!oldArchetype.HasAddEdge(edgeIndex)) { - archetype = GetOrCreate(oldArchetype.Types.Add(type)); + var newSignature = Signature.Add(oldArchetype.Signature, type); + archetype = GetOrCreate(newSignature); oldArchetype.AddAddEdge(edgeIndex, archetype); } else @@ -39,7 +39,6 @@ private Archetype GetOrCreateArchetypeByAddEdge(in ComponentType type, Archetype /// The new that additionally forms a new with the old components of the old archetype. /// The old . /// The cached or newly created with that additional component. - private Archetype GetOrCreateArchetypeByRemoveEdge(in ComponentType type, Archetype oldArchetype) { Archetype archetype; @@ -47,7 +46,8 @@ private Archetype GetOrCreateArchetypeByRemoveEdge(in ComponentType type, Archet if (!oldArchetype.HasRemoveEdge(edgeIndex)) { - archetype = GetOrCreate(oldArchetype.Types.Remove(type)); + var newSignature = Signature.Remove(oldArchetype.Signature, type); + archetype = GetOrCreate(newSignature); oldArchetype.AddRemoveEdge(edgeIndex, archetype); } else diff --git a/src/Arch/Core/Extensions/Dangerous/DangerousWorldExtensions.cs b/src/Arch/Core/Extensions/Dangerous/DangerousWorldExtensions.cs index b495f45..8f25a4b 100644 --- a/src/Arch/Core/Extensions/Dangerous/DangerousWorldExtensions.cs +++ b/src/Arch/Core/Extensions/Dangerous/DangerousWorldExtensions.cs @@ -82,7 +82,7 @@ public static void SetArchetypes(this World world, List archetypes) foreach (var archetype in archetypes) { - var hash = Component.GetHashCode(archetype.Types); + var hash = Component.GetHashCode(archetype.Signature); world.GroupToArchetype[hash] = archetype; world.Size += archetype.EntityCount; diff --git a/src/Arch/Core/Query.cs b/src/Arch/Core/Query.cs index badd1d8..70f36ec 100644 --- a/src/Arch/Core/Query.cs +++ b/src/Arch/Core/Query.cs @@ -5,7 +5,6 @@ using CommunityToolkit.HighPerformance; namespace Arch.Core; -using Arch.Core; /// /// The struct @@ -34,6 +33,17 @@ public Signature() _hashCode = -1; } + /// + /// Initializes a new instance of the struct. + /// + /// An array of s. + public Signature(Span components) + { + ComponentsArray = components.ToArray(); + _hashCode = -1; + _hashCode = GetHashCode(); + } + /// /// Initializes a new instance of the struct. /// @@ -50,7 +60,6 @@ public Signature(params ComponentType[] components) /// internal ComponentType[] ComponentsArray { - get; set; } = Array.Empty(); @@ -60,7 +69,6 @@ internal ComponentType[] ComponentsArray /// public Span Components { - get => MemoryMarshal.CreateSpan(ref ComponentsArray.DangerousGetReferenceAt(0), Count); } @@ -69,16 +77,15 @@ public Span Components /// public int Count { - get => ComponentsArray.Length; } + /// /// Checks for indifference, if the internal arrays have equal elements true is returned. Otherwise false. /// /// The other to compare with. /// True if elements of the arrays are equal, otherwhise false. - public bool Equals(Signature other) { return GetHashCode() == other.GetHashCode(); @@ -89,7 +96,6 @@ public bool Equals(Signature other) /// /// The other to compare with. /// True if elements of the arrays are equal, otherwhise false. - public override bool Equals(object? obj) { return obj is Signature other && Equals(other); @@ -99,7 +105,6 @@ public override bool Equals(object? obj) /// Calculates the hash. /// /// The hash. - public override int GetHashCode() { // Cache hashcode since the calculation is expensive. @@ -121,19 +126,53 @@ public override int GetHashCode() /// Creates an which iterates over all in this . /// /// An . - public Enumerator GetEnumerator() { return new Enumerator(Components); } + // TODO: Add method that accepts single ComponentType to prevent allocation of signature/array? + /// + /// Put the two together and return them as a new one. Removes duplicates. + /// + /// The first . + /// The second . + /// A new . + public static Signature Add(Signature first, Signature second) + { + // Copy signatures into new array + using var set = new PooledSet(first.Count + second.Count); + set.UnionWith(first); + set.UnionWith(second); + + var result = new Signature(set.ToArray()); + return result; + } + + // TODO: Add method that accepts single ComponentType to prevent allocation of signature/array? + /// + /// Put the two together and return them as a new one. Removes duplicates. + /// + /// The first . + /// The second . + /// A new . + public static Signature Remove(Signature first, Signature second) + { + // Copy signatures into new array + using var set = new PooledSet(first.Count + second.Count); + set.UnionWith(first); + set.ExceptWith(second); + + var result = new Signature(set.ToArray()); + return result; + } + /// /// Checks for indifference, if the internal arrays have equal elements true is returned. Otherwise false. /// /// The left . /// The right . /// True if their internal arrays are equal, otherwhise false. - public static bool operator ==(Signature left, Signature right) { return left.Equals(right); @@ -145,29 +184,69 @@ public Enumerator GetEnumerator() /// The left . /// The right . /// True if their internal arrays are unequal, otherwhise false. - public static bool operator !=(Signature left, Signature right) { return !left.Equals(right); } + // TODO: Use + & - everywhere instead of add/remove? + /// + /// Adds both s and creates a new . + /// + /// The first . + /// The second . + /// A new combining both. + public static Signature operator +(Signature a, Signature b) + { + return Add(a, b); + } + + /// + /// Subtracts the s from the . + /// + /// The first . + /// The second . + /// A new combining both. + public static Signature operator -(Signature a, Signature b) + { + return Remove(a, b); + } + + /// + /// Converts a into a . + /// + /// The passed . + /// A new . + public static implicit operator Signature(ComponentType component) + { + return new Signature(component); + } + /// /// Converts a array into a . /// /// The passed s. /// A new . - public static implicit operator Signature(ComponentType[] components) { return new Signature(components); } + /// + /// Converts a array into a . + /// + /// The passed s. + /// A new . + public static implicit operator Signature(Span components) + { + return new Signature(components); + } + /// /// Converts a into a s array. /// /// The passed . /// The s array. - public static implicit operator ComponentType[](Signature signature) { return signature.ComponentsArray; @@ -178,7 +257,6 @@ public static implicit operator ComponentType[](Signature signature) /// /// The passed . /// The s array. - public static implicit operator Span(Signature signature) { return signature.Components; @@ -189,7 +267,6 @@ public static implicit operator Span(Signature signature) /// /// The passed . /// A new s. - public static implicit operator BitSet(Signature signature) { if (signature.Count == 0) @@ -202,8 +279,13 @@ public static implicit operator BitSet(Signature signature) return bitSet; } -} + public override string ToString() + { + var types = string.Join(",", ComponentsArray.Select(p => p.Type.Name).ToArray()); + return $"Signature {{ {nameof(ComponentsArray)} = {{ {types} }}, {nameof(Count)} = {Count}, {nameof(_hashCode)} = {_hashCode} }}"; + } +} /// /// The struct diff --git a/src/Arch/Core/World.cs b/src/Arch/Core/World.cs index f2593c8..958c4ca 100644 --- a/src/Arch/Core/World.cs +++ b/src/Arch/Core/World.cs @@ -621,7 +621,7 @@ public bool TryGetArchetype(in Signature signature, [MaybeNullWhen(false)] out A /// The to destroy. internal void DestroyArchetype(Archetype archetype) { - var hash = Component.GetHashCode(archetype.Types); + var hash = Component.GetHashCode(archetype.Signature); Archetypes.Remove(archetype); GroupToArchetype.Remove(hash); @@ -766,7 +766,6 @@ public void Destroy(in QueryDescription queryDescription) /// /// The which specifies which s will be targeted. /// The value of the component to set. - public void Set(in QueryDescription queryDescription, in T? value = default) { var query = Query(in queryDescription); @@ -796,7 +795,6 @@ public void Set(in QueryDescription queryDescription, in T? value = default) /// The value of the component to add. [SkipLocalsInit] [StructuralChange] - public void Add(in QueryDescription queryDescription, in T? component = default) { // BitSet to stack/span bitset, size big enough to contain ALL registered components. @@ -819,19 +817,20 @@ public void Add(in QueryDescription queryDescription, in T? component = defau // Get or create new archetype. if (!TryGetArchetype(spanBitSet.GetHashCode(), out var newArchetype)) { - newArchetype = GetOrCreate(archetype.Types.Add(typeof(T))); + var newSignature = Signature.Add(archetype.Signature, Component.Signature); + newArchetype = GetOrCreate(newSignature); } // Get last slots before copy, for updating entityinfo later - var archetypeSlot = archetype.LastSlot; - var newArchetypeLastSlot = newArchetype.LastSlot; + var archetypeSlot = archetype.CurrentSlot; + var newArchetypeLastSlot = newArchetype.CurrentSlot; Slot.Shift(ref newArchetypeLastSlot, newArchetype.EntitiesPerChunk); EntityInfo.Shift(archetype, archetypeSlot, newArchetype, newArchetypeLastSlot); // Copy, Set and clear var oldCapacity = newArchetype.EntityCapacity; Archetype.Copy(archetype, newArchetype); - var lastSlot = newArchetype.LastSlot; + var lastSlot = newArchetype.CurrentSlot; newArchetype.SetRange(in lastSlot, in newArchetypeLastSlot, in component); archetype.Clear(); @@ -853,7 +852,6 @@ public void Add(in QueryDescription queryDescription, in T? component = defau /// The which specifies which s will be targeted. [SkipLocalsInit] [StructuralChange] - public void Remove(in QueryDescription queryDescription) { // BitSet to stack/span bitset, size big enough to contain ALL registered components. @@ -876,14 +874,15 @@ public void Remove(in QueryDescription queryDescription) // Get or create new archetype. if (!TryGetArchetype(spanBitSet.GetHashCode(), out var newArchetype)) { - newArchetype = GetOrCreate(archetype.Types.Remove(typeof(T))); + var newSignature = Signature.Remove(archetype.Signature, Component.Signature); + newArchetype = GetOrCreate(newSignature); } OnComponentRemoved(archetype); // Get last slots before copy, for updating entityinfo later - var archetypeSlot = archetype.LastSlot; - var newArchetypeLastSlot = newArchetype.LastSlot; + var archetypeSlot = archetype.CurrentSlot; + var newArchetypeLastSlot = newArchetype.CurrentSlot; Slot.Shift(ref newArchetypeLastSlot, newArchetype.EntitiesPerChunk); EntityInfo.Shift(archetype, archetypeSlot, newArchetype, newArchetypeLastSlot); @@ -1426,7 +1425,6 @@ public void Add(Entity entity, in object cmp) /// The . /// The of components. [SkipLocalsInit] - [StructuralChange] public void AddRange(Entity entity, Span components) { @@ -1453,7 +1451,8 @@ public void AddRange(Entity entity, Span components) newComponents[index] = (ComponentType)components[index].GetType(); } - newArchetype = GetOrCreate(oldArchetype.Types.Add(newComponents)); + var newSignature = Signature.Add(oldArchetype.Signature, newComponents); + newArchetype = GetOrCreate(newSignature); } // Move and fire events @@ -1475,7 +1474,6 @@ public void AddRange(Entity entity, Span components) /// The . /// A of 's, those are added to the . [SkipLocalsInit] - [StructuralChange] public void AddRange(Entity entity, Span components) { @@ -1496,7 +1494,8 @@ public void AddRange(Entity entity, Span components) if (!TryGetArchetype(spanBitSet.GetHashCode(), out var newArchetype)) { - newArchetype = GetOrCreate(oldArchetype.Types.Add(components.ToArray())); + var newSignature = Signature.Add(oldArchetype.Signature, components.ToArray()); + newArchetype = GetOrCreate(newSignature); } Move(entity, oldArchetype, newArchetype, out _); @@ -1517,7 +1516,6 @@ public void AddRange(Entity entity, Span components) /// /// The . /// The to remove from the . - [StructuralChange] public void Remove(Entity entity, ComponentType type) { @@ -1533,7 +1531,8 @@ public void Remove(Entity entity, ComponentType type) if (!TryGetArchetype(spanBitSet.GetHashCode(), out var newArchetype)) { - newArchetype = GetOrCreate(oldArchetype.Types.Remove(type)); + var newSignature = Signature.Remove(oldArchetype.Signature,type); + newArchetype = GetOrCreate(newSignature); } OnComponentRemoved(entity, type); @@ -1549,7 +1548,6 @@ public void Remove(Entity entity, ComponentType type) /// The . /// A of s, that are removed from the . [SkipLocalsInit] - [StructuralChange] public void RemoveRange(Entity entity, Span types) { @@ -1570,7 +1568,8 @@ public void RemoveRange(Entity entity, Span types) // Get or Create new archetype if (!TryGetArchetype(spanBitSet.GetHashCode(), out var newArchetype)) { - newArchetype = GetOrCreate(oldArchetype.Types.Remove(types.ToArray())); + var newSignature = Signature.Remove(oldArchetype.Signature, types); + newArchetype = GetOrCreate(newSignature); } // Fire events and move @@ -1681,7 +1680,7 @@ public ref readonly Chunk GetChunk(Entity entity) public ComponentType[] GetComponentTypes(Entity entity) { var archetype = EntityInfo.GetArchetype(entity.Id); - return archetype.Types; + return archetype.Signature; } /// diff --git a/src/Arch/Templates/World.Add.tt b/src/Arch/Templates/World.Add.tt new file mode 100644 index 0000000..d6c4fc2 --- /dev/null +++ b/src/Arch/Templates/World.Add.tt @@ -0,0 +1,61 @@ +<#@ template language="C#" #> +<#@ output extension=".cs" #> +<#@ import namespace="System.Text" #> +<#@ include file="Helpers.ttinclude" #> + +using System; +using System.Runtime.CompilerServices; +using CommunityToolkit.HighPerformance; +using Arch.Core.Utils; + +namespace Arch.Core; +public partial class World +{ + <# + for (var index = 2; index <= Amount; index++) + { + var generics = AppendGenerics(index); + var parameters = AppendGenericInDefaultParams(index); + var inParameters = InsertGenericInParams(index); + + var getIds = new StringBuilder(); + var setIds = new StringBuilder(); + var addEvents = new StringBuilder(); + for (var i = 0; i < index; i++) + { + getIds.AppendLine($" var id{i} = Component.ComponentType.Id;"); + setIds.AppendLine($" spanBitSet.SetBit(id{i});"); + addEvents.AppendLine($" OnComponentAdded(entity);"); + } + #> + + [SkipLocalsInit] + [StructuralChange] + public void Add<<#= generics #>>(Entity entity, <#= parameters #>) + { + var oldArchetype = EntityInfo.GetArchetype(entity.Id); + + // Get all the ids here just in case we are adding a new component as this will grow the ComponentRegistry.Size +<#= getIds.ToString() #> + + // BitSet to stack/span bitset, size big enough to contain ALL registered components. + Span stack = stackalloc uint[BitSet.RequiredLength(ComponentRegistry.Size)]; + oldArchetype.BitSet.AsSpan(stack); + + // Create a span bitset, doing it local saves us headache and gargabe + var spanBitSet = new SpanBitSet(stack); +<#= setIds.ToString() #> + + if (!TryGetArchetype(spanBitSet.GetHashCode(), out var newArchetype)){ + var newSignature = Signature.Add(oldArchetype.Signature, Component<<#= generics #>>.Signature); + newArchetype = GetOrCreate(newSignature); + } + + Move(entity, oldArchetype, newArchetype, out var newSlot); + newArchetype.Set<<#= generics #>>(ref newSlot, <#= inParameters #>); +<#= addEvents.ToString() #> + } + <# + } + #> +} diff --git a/src/Arch/Templates/World.AddWithQueryDescription.tt b/src/Arch/Templates/World.AddWithQueryDescription.tt new file mode 100644 index 0000000..ce00c7f --- /dev/null +++ b/src/Arch/Templates/World.AddWithQueryDescription.tt @@ -0,0 +1,80 @@ +<#@ template language="C#" #> +<#@ output extension=".cs" #> +<#@ import namespace="System.Text" #> +<#@ include file="Helpers.ttinclude" #> + +using System; +using System.Runtime.CompilerServices; +using CommunityToolkit.HighPerformance; +using Arch.Core.Utils; + +namespace Arch.Core; +public partial class World +{ + <# + for (var index = 2; index <= Amount; index++) + { + var generics = AppendGenerics(index); + var parameters = AppendGenericInDefaultParams(index); + var inParameters = InsertGenericInParams(index); + + var setIds = new StringBuilder(); + var addEvents = new StringBuilder(); + for (var i = 0; i < index; i++) + { + setIds.AppendLine($" spanBitSet.SetBit(Component.ComponentType.Id);"); + addEvents.AppendLine($" OnComponentAdded(archetype);"); + } + #> + + [SkipLocalsInit] + [StructuralChange] + public void Add<<#= generics #>>(in QueryDescription queryDescription, <#= parameters #>) + { + // BitSet to stack/span bitset, size big enough to contain ALL registered components. + Span stack = stackalloc uint[BitSet.RequiredLength(ComponentRegistry.Size + <#= index + 1 #>)]; + + var query = Query(in queryDescription); + foreach (var archetype in query.GetArchetypeIterator()) + { + // Archetype with T shouldnt be skipped to prevent undefined behaviour. + if(archetype.EntityCount == 0 || archetype.Has<<#= generics #>>()) + { + continue; + } + + // Create local bitset on the stack and set bits to get a new fitting bitset of the new archetype. + archetype.BitSet.AsSpan(stack); + var spanBitSet = new SpanBitSet(stack); +<#= setIds.ToString() #> + + // Get or create new archetype. + if (!TryGetArchetype(spanBitSet.GetHashCode(), out var newArchetype)) + { + var newSignature = Signature.Add(archetype.Signature, Component<<#= generics #>>.Signature); + newArchetype = GetOrCreate(newSignature); + } + + // Get last slots before copy, for updating entityinfo later + var archetypeSlot = archetype.CurrentSlot; + var newArchetypeLastSlot = newArchetype.CurrentSlot; + Slot.Shift(ref newArchetypeLastSlot, newArchetype.EntitiesPerChunk); + EntityInfo.Shift(archetype, archetypeSlot, newArchetype, newArchetypeLastSlot); + + // Copy, set and clear + var oldCapacity = newArchetype.EntityCapacity; + Archetype.Copy(archetype, newArchetype); + var lastSlot = newArchetype.CurrentSlot; + newArchetype.SetRange(in lastSlot, in newArchetypeLastSlot, <#= inParameters #>); + archetype.Clear(); + + Capacity += newArchetype.EntityCapacity - oldCapacity; +<#= addEvents.ToString() #> + } + + EntityInfo.EnsureCapacity(Capacity); + } + <# + } + #> +} diff --git a/src/Arch/Templates/World.Remove.tt b/src/Arch/Templates/World.Remove.tt new file mode 100644 index 0000000..468df59 --- /dev/null +++ b/src/Arch/Templates/World.Remove.tt @@ -0,0 +1,54 @@ +<#@ template language="C#" #> +<#@ output extension=".cs" #> +<#@ import namespace="System.Text" #> +<#@ include file="Helpers.ttinclude" #> + +using System; +using System.Runtime.CompilerServices; +using CommunityToolkit.HighPerformance; +using Arch.Core.Utils; + +namespace Arch.Core; +public partial class World +{ + <# + for (var index = 2; index <= Amount; index++) + { + var generics = AppendGenerics(index); + var types = AppendTypes(index); + + var removes = new StringBuilder(); + var events = new StringBuilder(); + for (var i = 0; i < index; i++) + { + removes.AppendLine($" spanBitSet.ClearBit(Component.ComponentType.Id);"); + events.AppendLine($" OnComponentRemoved(entity);"); + } + #> + + [SkipLocalsInit] + [StructuralChange] + public void Remove<<#= generics #>>(Entity entity) + { + var oldArchetype = EntityInfo.GetArchetype(entity.Id); + + // BitSet to stack/span bitset, size big enough to contain ALL registered components. + Span stack = stackalloc uint[oldArchetype.BitSet.Length]; + oldArchetype.BitSet.AsSpan(stack); + + // Create a span bitset, doing it local saves us headache and gargabe + var spanBitSet = new SpanBitSet(stack); +<#= removes.ToString() #> + + if (!TryGetArchetype(spanBitSet.GetHashCode(), out var newArchetype)){ + var newSignature = Signature.Remove(oldArchetype.Signature, Component<<#= generics #>>.Signature); + newArchetype = GetOrCreate(newSignature); + } + +<#= events.ToString() #> + Move(entity, oldArchetype, newArchetype, out _); + } + <# + } + #> +} diff --git a/src/Arch/Templates/World.RemoveWithQueryDescription.tt b/src/Arch/Templates/World.RemoveWithQueryDescription.tt new file mode 100644 index 0000000..58cf1c5 --- /dev/null +++ b/src/Arch/Templates/World.RemoveWithQueryDescription.tt @@ -0,0 +1,77 @@ +<#@ template language="C#" #> +<#@ import namespace="System.Text" #> +<#@ import namespace="System.Collections.Generic" #> +<#@ include file="Helpers.ttinclude" #> + +using System; +using System.Runtime.CompilerServices; +using CommunityToolkit.HighPerformance; +using Arch.Core.Utils; + +namespace Arch.Core; +public partial class World +{ + <# + for (var index = 2; index <= Amount; index++) + { + var generics = AppendGenerics(index); + var types = AppendTypes(index); + + var clearIds = new StringBuilder(); + var removeEvents = new StringBuilder(); + for (var i = 0; i < index; i++) + { + clearIds.AppendLine($"spanBitSet.ClearBit(Component.ComponentType.Id);"); + removeEvents.AppendLine($"OnComponentRemoved(archetype);"); + } + #> + + [SkipLocalsInit] + [StructuralChange] + public void Remove<<#= generics #>>(in QueryDescription queryDescription) + { + // BitSet to stack/span bitset, size big enough to contain ALL registered components. + Span stack = stackalloc uint[BitSet.RequiredLength(ComponentRegistry.Size)]; + + var query = Query(in queryDescription); + foreach (var archetype in query.GetArchetypeIterator()) + { + // Archetype without T shouldnt be skipped to prevent undefined behaviour. + if(archetype.EntityCount <= 0 || !archetype.Has<<#= generics #>>()) + { + continue; + } + + // Create local bitset on the stack and set bits to get a new fitting bitset of the new archetype. + var bitSet = archetype.BitSet; + var spanBitSet = new SpanBitSet(bitSet.AsSpan(stack)); + <#= clearIds #> + + // Get or create new archetype. + if (!TryGetArchetype(spanBitSet.GetHashCode(), out var newArchetype)) + { + var newSignature = Signature.Remove(archetype.Signature, Component<<#= generics #>>.Signature); + newArchetype = GetOrCreate(newSignature); + } + + <#= removeEvents #> + + // Get last slots before copy, for updating entityinfo later + var archetypeSlot = archetype.CurrentSlot; + var newArchetypeLastSlot = newArchetype.CurrentSlot; + Slot.Shift(ref newArchetypeLastSlot, newArchetype.EntitiesPerChunk); + EntityInfo.Shift(archetype, archetypeSlot, newArchetype, newArchetypeLastSlot); + + var oldCapacity = newArchetype.EntityCapacity; + Archetype.Copy(archetype, newArchetype); + archetype.Clear(); + + Capacity += newArchetype.EntityCapacity - oldCapacity; + } + + EntityInfo.EnsureCapacity(Capacity); + } + <# + } + #> +}