Added new method to remove entities efficently.
This commit is contained in:
@@ -111,6 +111,8 @@ internal unsafe struct Chunk : IDisposable
|
|||||||
private UnsafeArray<byte> _data;
|
private UnsafeArray<byte> _data;
|
||||||
private UnsafeArray<int> _versions;
|
private UnsafeArray<int> _versions;
|
||||||
|
|
||||||
|
// TODO: Add structual change versioning, similar to DidOrderChange in unity ecs.
|
||||||
|
|
||||||
internal int _count;
|
internal int _count;
|
||||||
internal readonly int _capacity;
|
internal readonly int _capacity;
|
||||||
|
|
||||||
@@ -145,9 +147,7 @@ internal unsafe struct Chunk : IDisposable
|
|||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
_data.Dispose();
|
_data.Dispose();
|
||||||
Console.WriteLine($"Disposing chunk data");
|
|
||||||
_versions.Dispose();
|
_versions.Dispose();
|
||||||
Console.WriteLine($"Disposing chunk versions");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -487,8 +487,8 @@ internal unsafe struct Archetype : IIdentifierType, IDisposable
|
|||||||
var pLastEntity = chunkBase + _entityIdsOffset + (sizeof(Entity) * lastIndex);
|
var pLastEntity = chunkBase + _entityIdsOffset + (sizeof(Entity) * lastIndex);
|
||||||
var pRowEntity = chunkBase + _entityIdsOffset + (sizeof(Entity) * rowIndex);
|
var pRowEntity = chunkBase + _entityIdsOffset + (sizeof(Entity) * rowIndex);
|
||||||
|
|
||||||
var wrold = World.GetWorldUncheck(_worldID);
|
var world = World.GetWorldUncheck(_worldID);
|
||||||
var result = wrold.EntityManager.UpdateEntityLocation(*(Entity*)pLastEntity, _id, chunkIndex, rowIndex);
|
var result = world.EntityManager.UpdateEntityLocation(*(Entity*)pLastEntity, _id, chunkIndex, rowIndex);
|
||||||
if (result != ErrorStatus.None)
|
if (result != ErrorStatus.None)
|
||||||
{
|
{
|
||||||
return result;
|
return result;
|
||||||
@@ -512,6 +512,111 @@ internal unsafe struct Archetype : IIdentifierType, IDisposable
|
|||||||
return ErrorStatus.None;
|
return ErrorStatus.None;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ErrorStatus RemoveEntities(int chunkIndex, ReadOnlySpan<int> sortedIndicesToRemove)
|
||||||
|
{
|
||||||
|
if (chunkIndex < 0 || chunkIndex >= _chunks.Count)
|
||||||
|
{
|
||||||
|
return ErrorStatus.InvalidArgument;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sortedIndicesToRemove.Length == 0)
|
||||||
|
{
|
||||||
|
return ErrorStatus.None;
|
||||||
|
}
|
||||||
|
|
||||||
|
ref var chunk = ref _chunks[chunkIndex];
|
||||||
|
|
||||||
|
int oldCount = chunk._count;
|
||||||
|
int removeCount = sortedIndicesToRemove.Length;
|
||||||
|
int newCount = oldCount - removeCount; // The boundary between "Keep" and "Drop"
|
||||||
|
|
||||||
|
var chunkBase = chunk.GetUnsafePtr();
|
||||||
|
var world = World.GetWorldUncheck(_worldID); // Typo fixed from 'wrold'
|
||||||
|
|
||||||
|
// Pointers for the swap logic
|
||||||
|
// 1. 'holePtr' tracks which index in the sorted list we are processing
|
||||||
|
int holePtr = 0;
|
||||||
|
|
||||||
|
// 2. 'candidateIndex' starts at the end of the OLD array and moves backward
|
||||||
|
int candidateIndex = oldCount - 1;
|
||||||
|
|
||||||
|
// 3. 'removalTailPtr' tracks removals at the end of the array to skip them
|
||||||
|
int removalTailPtr = sortedIndicesToRemove.Length - 1;
|
||||||
|
|
||||||
|
// Iterate through the holes that are strictly INSIDE the new valid range
|
||||||
|
while (holePtr < removeCount)
|
||||||
|
{
|
||||||
|
int holeIndex = sortedIndicesToRemove[holePtr];
|
||||||
|
|
||||||
|
// If the current hole is beyond the new count, it's in the "Drop Zone".
|
||||||
|
// Since the list is sorted, all subsequent holes are also in the drop zone.
|
||||||
|
// We are done filling holes.
|
||||||
|
if (holeIndex >= newCount)
|
||||||
|
break;
|
||||||
|
|
||||||
|
// --- Find a Valid Filler ---
|
||||||
|
// We look for an entity at the end of the array that IS NOT scheduled for removal.
|
||||||
|
while (candidateIndex >= newCount)
|
||||||
|
{
|
||||||
|
// Check if the current candidate is actually marked for removal
|
||||||
|
bool isCandidateRemoved = false;
|
||||||
|
|
||||||
|
// Because sortedIndices is sorted, we check the end of the list
|
||||||
|
// to see if the candidateIndex matches a removal request.
|
||||||
|
if (removalTailPtr >= 0 && sortedIndicesToRemove[removalTailPtr] == candidateIndex)
|
||||||
|
{
|
||||||
|
isCandidateRemoved = true;
|
||||||
|
removalTailPtr--; // Consume this removal
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isCandidateRemoved)
|
||||||
|
{
|
||||||
|
// Found a valid filler!
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This candidate was also removed, so skip it and keep looking left
|
||||||
|
candidateIndex--;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Perform The Swap ---
|
||||||
|
// Move 'candidateIndex' (Filler) into 'holeIndex' (Hole)
|
||||||
|
|
||||||
|
var pFillerEntity = chunkBase + _entityIdsOffset + (sizeof(Entity) * candidateIndex);
|
||||||
|
var pHoleEntity = chunkBase + _entityIdsOffset + (sizeof(Entity) * holeIndex);
|
||||||
|
|
||||||
|
// 1. Update the Map (Critical Step)
|
||||||
|
// We tell the world: "The entity that WAS at 'candidateIndex' is now at 'holeIndex'"
|
||||||
|
var result = world.EntityManager.UpdateEntityLocation(*(Entity*)pFillerEntity, _id, chunkIndex, holeIndex);
|
||||||
|
if (result != ErrorStatus.None)
|
||||||
|
{
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Overwrite Entity ID
|
||||||
|
MemoryUtility.MemCpy(pHoleEntity, pFillerEntity, (nuint)sizeof(Entity));
|
||||||
|
|
||||||
|
// 3. Overwrite Components
|
||||||
|
for (var i = 0; i < _layouts.Count; i++)
|
||||||
|
{
|
||||||
|
var layout = _layouts[i];
|
||||||
|
var pRow = chunkBase + layout.offset + (layout.size * holeIndex);
|
||||||
|
var pLast = chunkBase + layout.offset + (layout.size * candidateIndex);
|
||||||
|
MemoryUtility.MemCpy(pRow, pLast, (nuint)layout.size);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare for next hole
|
||||||
|
holePtr++;
|
||||||
|
candidateIndex--;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finally, simply truncate the count
|
||||||
|
chunk._count = newCount;
|
||||||
|
|
||||||
|
// (Optional) If you have Versioning, mark the components as changed here.
|
||||||
|
return ErrorStatus.None;
|
||||||
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public readonly bool HasComponent(Identifier<IComponent> componentID)
|
public readonly bool HasComponent(Identifier<IComponent> componentID)
|
||||||
{
|
{
|
||||||
@@ -577,12 +682,9 @@ internal unsafe struct Archetype : IIdentifierType, IDisposable
|
|||||||
{
|
{
|
||||||
if (_chunks.IsCreated)
|
if (_chunks.IsCreated)
|
||||||
{
|
{
|
||||||
var i= 0;
|
|
||||||
foreach (ref var chunk in _chunks)
|
foreach (ref var chunk in _chunks)
|
||||||
{
|
{
|
||||||
Console.WriteLine($"Disposing chunk {i} of archetype {_id}");
|
|
||||||
chunk.Dispose();
|
chunk.Dispose();
|
||||||
i++;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,11 +16,28 @@ namespace Ghost.Entities;
|
|||||||
/// </remarks>
|
/// </remarks>
|
||||||
public unsafe partial class EntityManager : IDisposable
|
public unsafe partial class EntityManager : IDisposable
|
||||||
{
|
{
|
||||||
private struct EntityLocation
|
private struct EntityLocation : IComparable<EntityLocation>
|
||||||
{
|
{
|
||||||
public int archetypeID;
|
public int archetypeID;
|
||||||
public int chunkIndex;
|
public int chunkIndex;
|
||||||
public int rowIndex;
|
public int rowIndex;
|
||||||
|
|
||||||
|
public readonly int CompareTo(EntityLocation other)
|
||||||
|
{
|
||||||
|
var archComp = chunkIndex.CompareTo(other.chunkIndex);
|
||||||
|
if (archComp != 0)
|
||||||
|
{
|
||||||
|
return archComp;
|
||||||
|
}
|
||||||
|
|
||||||
|
var chunkComp = chunkIndex.CompareTo(other.chunkIndex);
|
||||||
|
if (chunkComp != 0)
|
||||||
|
{
|
||||||
|
return chunkComp;
|
||||||
|
}
|
||||||
|
|
||||||
|
return rowIndex.CompareTo(other.rowIndex);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly World _world;
|
private readonly World _world;
|
||||||
@@ -234,9 +251,79 @@ public unsafe partial class EntityManager : IDisposable
|
|||||||
/// <param name="entities">The entities to destroy.</param>
|
/// <param name="entities">The entities to destroy.</param>
|
||||||
public void DestroyEntities(ReadOnlySpan<Entity> entities)
|
public void DestroyEntities(ReadOnlySpan<Entity> entities)
|
||||||
{
|
{
|
||||||
for (var i = 0; i < entities.Length; i++)
|
if (entities.Length == 0)
|
||||||
{
|
{
|
||||||
DestroyEntity(entities[i]);
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
using var scope = AllocationManager.CreateStackScope();
|
||||||
|
var batchDestroy = new UnsafeList<EntityLocation>(entities.Length, scope.AllocationHandle);
|
||||||
|
var rowIndicesCache = new UnsafeList<int>(32, scope.AllocationHandle);
|
||||||
|
|
||||||
|
// 1. GATHER
|
||||||
|
// Resolve all entities to their locations
|
||||||
|
for (int i = 0; i < entities.Length; i++)
|
||||||
|
{
|
||||||
|
var entity = entities[i];
|
||||||
|
if (_entityLocations.TryGetElementAt(entity.ID, entity.Generation, out var location))
|
||||||
|
{
|
||||||
|
batchDestroy.Add(location);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (batchDestroy.Count == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. SORT
|
||||||
|
// Sorting groups them by chunk automatically
|
||||||
|
batchDestroy.AsSpan().Sort();
|
||||||
|
|
||||||
|
// 3. SWEEP
|
||||||
|
// Iterate through the sorted list and batch process each chunk
|
||||||
|
var firstLoc = batchDestroy[0];
|
||||||
|
var prevArchetypeID = firstLoc.archetypeID;
|
||||||
|
var prevChunkIndex = firstLoc.chunkIndex;
|
||||||
|
|
||||||
|
for (int i = 0; i < batchDestroy.Count; i++)
|
||||||
|
{
|
||||||
|
var loc = batchDestroy[i];
|
||||||
|
|
||||||
|
// Check if we have crossed a boundary (Different Chunk OR Different Archetype)
|
||||||
|
bool isNewBatch = (loc.chunkIndex != prevChunkIndex) || (loc.archetypeID != prevArchetypeID);
|
||||||
|
|
||||||
|
if (isNewBatch)
|
||||||
|
{
|
||||||
|
// FLUSH PREVIOUS BATCH
|
||||||
|
// We must retrieve the Archetype of the *Previous* batch, not the current 'loc'
|
||||||
|
ref var prevArchetype = ref _world.GetArchetypeReference(prevArchetypeID);
|
||||||
|
|
||||||
|
// Execute the hole-filling/swap logic
|
||||||
|
prevArchetype.RemoveEntities(prevChunkIndex, rowIndicesCache.AsSpan());
|
||||||
|
|
||||||
|
// RESET
|
||||||
|
rowIndicesCache.Clear();
|
||||||
|
prevArchetypeID = loc.archetypeID;
|
||||||
|
prevChunkIndex = loc.chunkIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
rowIndicesCache.Add(loc.rowIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. FINAL FLUSH
|
||||||
|
// Process the stragglers remaining in the cache
|
||||||
|
if (rowIndicesCache.Count > 0)
|
||||||
|
{
|
||||||
|
ref var lastArchetype = ref _world.GetArchetypeReference(prevArchetypeID);
|
||||||
|
lastArchetype.RemoveEntities(prevChunkIndex, rowIndicesCache.AsSpan());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. Remove from Entity Locations
|
||||||
|
for (int i = 0; i < entities.Length; i++)
|
||||||
|
{
|
||||||
|
var entity = entities[i];
|
||||||
|
_entityLocations.Remove(entity.ID, entity.Generation);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ public unsafe partial struct EntityQuery
|
|||||||
private readonly ReadOnlyUnsafeCollection<Identifier<Archetype>> _matchingArchetypes;
|
private readonly ReadOnlyUnsafeCollection<Identifier<Archetype>> _matchingArchetypes;
|
||||||
private readonly EntityQueryMask _mask;
|
private readonly EntityQueryMask _mask;
|
||||||
private readonly World _world;
|
private readonly World _world;
|
||||||
private readonly int _currentVersion;
|
|
||||||
|
|
||||||
private readonly Stack.Scope _scope;
|
private readonly Stack.Scope _scope;
|
||||||
private UnsafeList<int> _changedComponentIDs;
|
private UnsafeList<int> _changedComponentIDs;
|
||||||
@@ -210,7 +209,6 @@ public unsafe partial struct EntityQuery
|
|||||||
private readonly ReadOnlyUnsafeCollection<Identifier<Archetype>> _matchingArchetypes;
|
private readonly ReadOnlyUnsafeCollection<Identifier<Archetype>> _matchingArchetypes;
|
||||||
private readonly EntityQueryMask _mask;
|
private readonly EntityQueryMask _mask;
|
||||||
private readonly World _world;
|
private readonly World _world;
|
||||||
private readonly int _currentVersion;
|
|
||||||
|
|
||||||
private readonly Stack.Scope _scope;
|
private readonly Stack.Scope _scope;
|
||||||
private UnsafeList<int> _changedComponentIDs;
|
private UnsafeList<int> _changedComponentIDs;
|
||||||
@@ -412,7 +410,6 @@ public unsafe partial struct EntityQuery
|
|||||||
private readonly ReadOnlyUnsafeCollection<Identifier<Archetype>> _matchingArchetypes;
|
private readonly ReadOnlyUnsafeCollection<Identifier<Archetype>> _matchingArchetypes;
|
||||||
private readonly EntityQueryMask _mask;
|
private readonly EntityQueryMask _mask;
|
||||||
private readonly World _world;
|
private readonly World _world;
|
||||||
private readonly int _currentVersion;
|
|
||||||
|
|
||||||
private readonly Stack.Scope _scope;
|
private readonly Stack.Scope _scope;
|
||||||
private UnsafeList<int> _changedComponentIDs;
|
private UnsafeList<int> _changedComponentIDs;
|
||||||
@@ -624,7 +621,6 @@ public unsafe partial struct EntityQuery
|
|||||||
private readonly ReadOnlyUnsafeCollection<Identifier<Archetype>> _matchingArchetypes;
|
private readonly ReadOnlyUnsafeCollection<Identifier<Archetype>> _matchingArchetypes;
|
||||||
private readonly EntityQueryMask _mask;
|
private readonly EntityQueryMask _mask;
|
||||||
private readonly World _world;
|
private readonly World _world;
|
||||||
private readonly int _currentVersion;
|
|
||||||
|
|
||||||
private readonly Stack.Scope _scope;
|
private readonly Stack.Scope _scope;
|
||||||
private UnsafeList<int> _changedComponentIDs;
|
private UnsafeList<int> _changedComponentIDs;
|
||||||
@@ -846,7 +842,6 @@ public unsafe partial struct EntityQuery
|
|||||||
private readonly ReadOnlyUnsafeCollection<Identifier<Archetype>> _matchingArchetypes;
|
private readonly ReadOnlyUnsafeCollection<Identifier<Archetype>> _matchingArchetypes;
|
||||||
private readonly EntityQueryMask _mask;
|
private readonly EntityQueryMask _mask;
|
||||||
private readonly World _world;
|
private readonly World _world;
|
||||||
private readonly int _currentVersion;
|
|
||||||
|
|
||||||
private readonly Stack.Scope _scope;
|
private readonly Stack.Scope _scope;
|
||||||
private UnsafeList<int> _changedComponentIDs;
|
private UnsafeList<int> _changedComponentIDs;
|
||||||
@@ -1078,7 +1073,6 @@ public unsafe partial struct EntityQuery
|
|||||||
private readonly ReadOnlyUnsafeCollection<Identifier<Archetype>> _matchingArchetypes;
|
private readonly ReadOnlyUnsafeCollection<Identifier<Archetype>> _matchingArchetypes;
|
||||||
private readonly EntityQueryMask _mask;
|
private readonly EntityQueryMask _mask;
|
||||||
private readonly World _world;
|
private readonly World _world;
|
||||||
private readonly int _currentVersion;
|
|
||||||
|
|
||||||
private readonly Stack.Scope _scope;
|
private readonly Stack.Scope _scope;
|
||||||
private UnsafeList<int> _changedComponentIDs;
|
private UnsafeList<int> _changedComponentIDs;
|
||||||
@@ -1320,7 +1314,6 @@ public unsafe partial struct EntityQuery
|
|||||||
private readonly ReadOnlyUnsafeCollection<Identifier<Archetype>> _matchingArchetypes;
|
private readonly ReadOnlyUnsafeCollection<Identifier<Archetype>> _matchingArchetypes;
|
||||||
private readonly EntityQueryMask _mask;
|
private readonly EntityQueryMask _mask;
|
||||||
private readonly World _world;
|
private readonly World _world;
|
||||||
private readonly int _currentVersion;
|
|
||||||
|
|
||||||
private readonly Stack.Scope _scope;
|
private readonly Stack.Scope _scope;
|
||||||
private UnsafeList<int> _changedComponentIDs;
|
private UnsafeList<int> _changedComponentIDs;
|
||||||
@@ -1572,7 +1565,6 @@ public unsafe partial struct EntityQuery
|
|||||||
private readonly ReadOnlyUnsafeCollection<Identifier<Archetype>> _matchingArchetypes;
|
private readonly ReadOnlyUnsafeCollection<Identifier<Archetype>> _matchingArchetypes;
|
||||||
private readonly EntityQueryMask _mask;
|
private readonly EntityQueryMask _mask;
|
||||||
private readonly World _world;
|
private readonly World _world;
|
||||||
private readonly int _currentVersion;
|
|
||||||
|
|
||||||
private readonly Stack.Scope _scope;
|
private readonly Stack.Scope _scope;
|
||||||
private UnsafeList<int> _changedComponentIDs;
|
private UnsafeList<int> _changedComponentIDs;
|
||||||
|
|||||||
@@ -55,7 +55,6 @@ public unsafe partial struct EntityQuery
|
|||||||
private readonly ReadOnlyUnsafeCollection<Identifier<Archetype>> _matchingArchetypes;
|
private readonly ReadOnlyUnsafeCollection<Identifier<Archetype>> _matchingArchetypes;
|
||||||
private readonly EntityQueryMask _mask;
|
private readonly EntityQueryMask _mask;
|
||||||
private readonly World _world;
|
private readonly World _world;
|
||||||
private readonly int _currentVersion;
|
|
||||||
|
|
||||||
private readonly Stack.Scope _scope;
|
private readonly Stack.Scope _scope;
|
||||||
private UnsafeList<int> _changedComponentIDs;
|
private UnsafeList<int> _changedComponentIDs;
|
||||||
|
|||||||
Reference in New Issue
Block a user