Skip to content

Commit 35ffc95

Browse files
release v3.0.0
- [opt] immediate entity creation/destruction and component addition/removal - [opt] improve groupof performance - [opt] improve tag performance - [opt] improve spatial and temporal locality - [feat] introduce archetype - [feat] introduce batch add/remove components see `migration_guide.md`
1 parent 6c81f6a commit 35ffc95

9 files changed

Lines changed: 1870 additions & 453 deletions

File tree

EasyEcs.Core/Archetype.cs

Lines changed: 26 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ namespace EasyEcs.Core;
77
/// <summary>
88
/// Archetype stores entities with the same component configuration.
99
/// Uses tombstone pattern (-1) for safe iteration during modifications.
10+
/// Uses free list for O(1) tombstone slot reuse.
1011
/// </summary>
1112
internal class Archetype
1213
{
@@ -15,36 +16,39 @@ internal class Archetype
1516
public int Count; // Total slots (including tombstones)
1617
public int AliveCount; // Living entities only
1718

19+
// Free list for O(1) tombstone reuse (stack-based)
20+
private int[] _freeSlots;
21+
private int _freeCount;
22+
1823
private const int Tombstone = -1;
1924

2025
public Archetype(in Tag componentMask, int initialCapacity = 1024)
2126
{
2227
ComponentMask = componentMask;
2328
EntityIds = new int[initialCapacity];
29+
_freeSlots = new int[initialCapacity / 4]; // Start smaller, grow as needed
2430
Count = 0;
2531
AliveCount = 0;
32+
_freeCount = 0;
2633
}
2734

2835
/// <summary>
2936
/// Add an entity to this archetype.
30-
/// Reuses tombstone slots first, then appends.
37+
/// Reuses tombstone slots (O(1) via free list), then appends.
3138
/// </summary>
3239
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
3340
public void Add(int entityId)
3441
{
35-
// Try to find a tombstone slot for reuse
36-
var span = EntityIds.AsSpan(0, Count);
37-
int emptySlot = span.IndexOf(Tombstone);
38-
39-
if (emptySlot >= 0)
42+
// Fast path: Reuse free slot from free list (O(1))
43+
if (_freeCount > 0)
4044
{
41-
// Reuse tombstone slot
42-
EntityIds[emptySlot] = entityId;
45+
int slot = _freeSlots[--_freeCount];
46+
EntityIds[slot] = entityId;
4347
AliveCount++;
4448
}
4549
else
4650
{
47-
// No tombstones available, append to end
51+
// No free slots, append to end
4852
if (Count >= EntityIds.Length)
4953
{
5054
// Grow array (doubling strategy)
@@ -58,7 +62,7 @@ public void Add(int entityId)
5862

5963
/// <summary>
6064
/// Remove an entity from this archetype.
61-
/// Marks the slot as tombstone (-1) for safe iteration.
65+
/// Marks the slot as tombstone (-1) and adds to free list for O(1) reuse.
6266
/// </summary>
6367
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
6468
public void Remove(int entityId)
@@ -72,18 +76,22 @@ public void Remove(int entityId)
7276
EntityIds[idx] = Tombstone;
7377
AliveCount--;
7478

75-
// Optional: Compact if heavily fragmented (>50% tombstones)
76-
// This prevents memory waste while keeping iteration safe
77-
if (AliveCount > 0 && AliveCount < Count / 2)
79+
// Add to free list for O(1) reuse
80+
if (_freeCount >= _freeSlots.Length)
7881
{
79-
Compact();
82+
Array.Resize(ref _freeSlots, _freeSlots.Length * 2);
8083
}
84+
_freeSlots[_freeCount++] = idx;
85+
86+
// Note: Auto-compaction is disabled to avoid unpredictable performance spikes mid-frame.
87+
// Call Context.CompactArchetypes() manually during loading screens or maintenance windows
88+
// to remove tombstones and reduce fragmentation.
8189
}
8290
}
8391

8492
/// <summary>
8593
/// Compact the array by removing all tombstones.
86-
/// Only called when heavily fragmented (>50% dead slots).
94+
/// Clears the free list since all gaps are removed.
8795
/// </summary>
8896
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
8997
public void Compact()
@@ -101,6 +109,9 @@ public void Compact()
101109

102110
Count = writeIdx;
103111
// AliveCount should already match Count after compaction
112+
113+
// Clear free list since there are no more tombstones
114+
_freeCount = 0;
104115
}
105116

106117
/// <summary>

EasyEcs.Core/Components/SimdOps.cs

Lines changed: 0 additions & 75 deletions
This file was deleted.

0 commit comments

Comments
 (0)