diff --git a/src/Arch.SourceGen/Queries/AddWithQueryDescription.cs b/src/Arch.SourceGen/Queries/AddWithQueryDescription.cs index f3cbbdc6..31f86d5d 100644 --- a/src/Arch.SourceGen/Queries/AddWithQueryDescription.cs +++ b/src/Arch.SourceGen/Queries/AddWithQueryDescription.cs @@ -79,12 +79,17 @@ public static void AppendAddWithQueryDescription(this StringBuilder sb, int amou 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}}); - {{addEvents}} archetype.Clear(); + + Capacity += newArchetype.EntityCapacity - oldCapacity; + {{addEvents}} } + + EntityInfo.EnsureCapacity(Capacity); } """; diff --git a/src/Arch.SourceGen/Queries/RemoveWithQueryDescription.cs b/src/Arch.SourceGen/Queries/RemoveWithQueryDescription.cs index 781d78ca..3d872b57 100644 --- a/src/Arch.SourceGen/Queries/RemoveWithQueryDescription.cs +++ b/src/Arch.SourceGen/Queries/RemoveWithQueryDescription.cs @@ -77,9 +77,14 @@ public static void AppendRemoveWithQueryDescription(this StringBuilder sb, int a 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); } """; diff --git a/src/Arch.Tests/WorldTest.cs b/src/Arch.Tests/WorldTest.cs index d0626369..a5b95201 100644 --- a/src/Arch.Tests/WorldTest.cs +++ b/src/Arch.Tests/WorldTest.cs @@ -515,6 +515,39 @@ public void RemoveByQueryDescription() That(world.CountEntities(in withAIQueryDesc), Is.EqualTo(0)); That(world.CountEntities(in withoutAIQueryDesc), Is.EqualTo(1000)); } + + /// + /// Checks if the world fills an empty archetype with left capacity correctly + /// + [Test] + public void FillEmptyArchetypeWithCapacityLeft() + { + // Create entities with a single archetype + for (var i = 0; i < 100; i++) + { + _world.Create(new Transform()); + } + + // Create entities with a duplex archetype + var archetype = _world.Archetypes[0]; + var entityCapacity = archetype.EntityCapacity; + for (var i = 0; i <= entityCapacity; i++) + { + _world.Create(new Transform(), new Rotation()); + } + + // Try move entities from duplex archetype to single archetype + _world.Remove(new QueryDescription().WithAll()); + + // Just a big value to ensure it goes beyond the original capacity + // Capacity should grow properly + entityCapacity = archetype.EntityCapacity; + for (var i = 0; i <= entityCapacity; i++) + { + // Create entities with a duplex archetype + DoesNotThrow(() => _world.Create(new Transform(), new Rotation()), "Overflow at {0}", i); + } + } } // Get, Set, Has, Remove, Add diff --git a/src/Arch/Arch.csproj b/src/Arch/Arch.csproj index 031a6d02..087f85b3 100644 --- a/src/Arch/Arch.csproj +++ b/src/Arch/Arch.csproj @@ -91,7 +91,7 @@ Refactored adding of entities to be faster. - + diff --git a/src/Arch/Buffer/CommandBuffer.cs b/src/Arch/Buffer/CommandBuffer.cs index 30b6abfc..d9626083 100644 --- a/src/Arch/Buffer/CommandBuffer.cs +++ b/src/Arch/Buffer/CommandBuffer.cs @@ -1,3 +1,7 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Runtime.CompilerServices; using Arch.Core; using Arch.Core.Extensions; using Arch.Core.Extensions.Internal; diff --git a/src/Arch/Core/Archetype.cs b/src/Arch/Core/Archetype.cs index d7036baa..51e356e6 100644 --- a/src/Arch/Core/Archetype.cs +++ b/src/Arch/Core/Archetype.cs @@ -560,24 +560,16 @@ private void EnsureChunkCapacity(int newCapacity) ChunkCapacity = newCapacity; } - /// TODO : Currently this only ensures additional entity capacity, instead it should take the whole capacity in count. /// - /// Ensures the capacity of the array. - /// Increases the . + /// Ensures the capacity of the array for a certain amount of s. + /// Increases the to fit all entities within it. /// - /// The amount of 's required, in total. + /// The amount of 's required, in total. [MethodImpl(MethodImplOptions.AggressiveInlining)] internal void EnsureEntityCapacity(int newCapacity) { - // TODO: LastChunk updated sich nicht wenn von einem archetype weniger entities in einen anderen kopier werden als vorher drin waren. - // TODO: Dadurch bleibt z.B. ein Chunk am ende des Archetypes frei, wodurch beim entfernen eines entities wieder nen index -1 auftritt und ne exception - // TODO: LastChunk MUSS sich irgendwie updaten bei so nem Kopier quatsch? Glaube in dieser Methode machts keinen Sinn? Oder vllt doch? - // Calculate amount of required chunks. - //var freeSpots = EntityCapacity - EntityCount; - //var neededSpots = newCapacity - freeSpots; var neededChunks = (int)Math.Ceiling((float)newCapacity / EntitiesPerChunk); - if (ChunkCapacity-ChunkCount > neededChunks) { return; @@ -592,12 +584,6 @@ internal void EnsureEntityCapacity(int newCapacity) var newChunk = new Chunk(EntitiesPerChunk, _componentIdToArrayIndex, Types); Chunks[previousCapacity + index] = newChunk; } - - // If last chunk was full, add. - /*if (freeSpots == 0) - { - ChunkCount++; - }*/ } /// diff --git a/src/Arch/Core/EntityInfo.cs b/src/Arch/Core/EntityInfo.cs index 76b8adf7..2108a1a8 100644 --- a/src/Arch/Core/EntityInfo.cs +++ b/src/Arch/Core/EntityInfo.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Runtime.CompilerServices; using System.Text; using System.Threading.Tasks; using Arch.Core; @@ -229,7 +230,6 @@ public void Move(int id, Archetype archetype, Slot slot) EntitySlots[id] = new EntitySlot(archetype,slot); } - /// TODO : Find a cleaner way to break? One that does NOT require a branching? /// /// Updates the and all entities that moved/shifted between the archetypes. /// Use and modify with caution, one small logical issue and the whole framework stops working. @@ -242,7 +242,6 @@ public void Move(int id, Archetype archetype, Slot slot) public void Shift(Archetype archetype, Slot archetypeSlot, Archetype newArchetype, Slot newArchetypeSlot) { // Update the entityInfo of all copied entities. - //for (var chunkIndex = archetypeSlot.ChunkIndex; chunkIndex >= 0; --chunkIndex) for (var chunkIndex = 0; chunkIndex <= archetypeSlot.ChunkIndex; chunkIndex++) { // Get data @@ -253,7 +252,6 @@ public void Shift(Archetype archetype, Slot archetypeSlot, Archetype newArchetyp var isStart = chunkIndex == archetypeSlot.ChunkIndex; var upper = isStart ? archetypeSlot.Index : chunk.Size-1; - //for (var index = upper; index >= 0; --index) for(var index = 0; index <= upper; index++) { var entity = Unsafe.Add(ref entityFirstElement, index); diff --git a/src/Arch/Core/Extensions/Internal/MathExtensions.cs b/src/Arch/Core/Extensions/Internal/MathExtensions.cs index eb75a147..5560e779 100644 --- a/src/Arch/Core/Extensions/Internal/MathExtensions.cs +++ b/src/Arch/Core/Extensions/Internal/MathExtensions.cs @@ -7,25 +7,14 @@ internal static class MathExtensions { /// - /// This method will round down to the nearest power of 2 number. If the supplied number is a power of 2 it will return it. + /// Returns the max of two ints by bit operation without branching. /// - /// - /// + /// The first int. + /// The second int. + /// The highest of both ints. [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static int RoundToPowerOfTwo(int num) + public static int Max(int a, int b) { - // If num is a power of 2, return it - if (num > 0 && (num & (num - 1)) == 0) - { - return num; - } - - // Find the exponent of the nearest power of 2 (rounded down) - int exponent = (int)Math.Floor(Math.Log(num) / Math.Log(2)); - - // Calculate the nearest power of 2 - int result = (int)Math.Pow(2, exponent); - - return result; + return a - ((a - b) & ((a - b) >> 31)); } } diff --git a/src/Arch/Core/World.cs b/src/Arch/Core/World.cs index b55ab099..75db9f7d 100644 --- a/src/Arch/Core/World.cs +++ b/src/Arch/Core/World.cs @@ -268,6 +268,7 @@ public Entity Create(params ComponentType[] types) return Create(types.AsSpan()); } + // TODO: Find cleaner way to resize the EntityInfo? Let archetype.Create return an amount which is added to Capacity or whatever? /// /// Creates a new using its given component structure/. /// Might resize its target and allocate new space if its full. @@ -299,7 +300,7 @@ public Entity Create(Span types) EntityInfo.EnsureCapacity(Capacity); } - // Map + // Add entity to info storage EntityInfo.Add(entity.Id, recycled.Version, archetype, slot); Size++; OnEntityCreated(entity); @@ -895,14 +896,19 @@ public void Add(in QueryDescription queryDescription, in T? component = defau Slot.Shift(ref newArchetypeLastSlot, newArchetype.EntitiesPerChunk); EntityInfo.Shift(archetype, archetypeSlot, newArchetype, newArchetypeLastSlot); - // Copy, set and clear + // Copy, Set and clear + var oldCapacity = newArchetype.EntityCapacity; Archetype.Copy(archetype, newArchetype); var lastSlot = newArchetype.LastSlot; newArchetype.SetRange(in lastSlot, in newArchetypeLastSlot, in component); archetype.Clear(); + // Adjust capacity since the new archetype may have changed in size + Capacity += newArchetype.EntityCapacity - oldCapacity; OnComponentAdded(newArchetype); } + + EntityInfo.EnsureCapacity(Capacity); } /// @@ -949,9 +955,16 @@ public void Remove(in QueryDescription queryDescription) Slot.Shift(ref newArchetypeLastSlot, newArchetype.EntitiesPerChunk); EntityInfo.Shift(archetype, archetypeSlot, newArchetype, newArchetypeLastSlot); + // Copy and track capacity difference + var oldCapacity = newArchetype.EntityCapacity; Archetype.Copy(archetype, newArchetype); archetype.Clear(); + + // Adjust capacity since the new archetype may have changed in size + Capacity += newArchetype.EntityCapacity - oldCapacity; } + + EntityInfo.EnsureCapacity(Capacity); } }