@@ -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>
1112internal 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>
0 commit comments