11using System ;
2+ using System . Collections . Generic ;
23using System . Runtime . CompilerServices ;
34using EasyEcs . Core . Components ;
45
@@ -8,6 +9,7 @@ namespace EasyEcs.Core;
89/// Archetype stores entities with the same component configuration.
910/// Uses tombstone pattern (-1) for safe iteration during modifications.
1011/// Uses free list for O(1) tombstone slot reuse.
12+ /// Uses reverse lookup dictionary for O(1) removal.
1113/// </summary>
1214internal class Archetype
1315{
@@ -20,13 +22,17 @@ internal class Archetype
2022 private int [ ] _freeSlots ;
2123 private int _freeCount ;
2224
25+ // Reverse lookup: entityId -> index in EntityIds array (O(1) removal)
26+ private readonly Dictionary < int , int > _entityToIndex ;
27+
2328 private const int Tombstone = - 1 ;
2429
2530 public Archetype ( in Tag componentMask , int initialCapacity = 1024 )
2631 {
2732 ComponentMask = componentMask ;
2833 EntityIds = new int [ initialCapacity ] ;
29- _freeSlots = new int [ initialCapacity / 4 ] ; // Start smaller, grow as needed
34+ _freeSlots = new int [ initialCapacity ] ; // Match capacity to avoid early resizing
35+ _entityToIndex = new Dictionary < int , int > ( initialCapacity ) ;
3036 Count = 0 ;
3137 AliveCount = 0 ;
3238 _freeCount = 0 ;
@@ -39,10 +45,12 @@ public Archetype(in Tag componentMask, int initialCapacity = 1024)
3945 [ MethodImpl ( MethodImplOptions . AggressiveInlining | MethodImplOptions . AggressiveOptimization ) ]
4046 public void Add ( int entityId )
4147 {
48+ int slot ;
49+
4250 // Fast path: Reuse free slot from free list (O(1))
4351 if ( _freeCount > 0 )
4452 {
45- int slot = _freeSlots [ -- _freeCount ] ;
53+ slot = _freeSlots [ -- _freeCount ] ;
4654 EntityIds [ slot ] = entityId ;
4755 AliveCount ++ ;
4856 }
@@ -55,55 +63,67 @@ public void Add(int entityId)
5563 Array . Resize ( ref EntityIds , EntityIds . Length * 2 ) ;
5664 }
5765
58- EntityIds [ Count ++ ] = entityId ;
66+ slot = Count ++ ;
67+ EntityIds [ slot ] = entityId ;
5968 AliveCount ++ ;
6069 }
70+
71+ // Update reverse lookup for O(1) removal
72+ _entityToIndex [ entityId ] = slot ;
6173 }
6274
6375 /// <summary>
6476 /// Remove an entity from this archetype.
77+ /// O(1) removal using reverse lookup dictionary.
6578 /// Marks the slot as tombstone (-1) and adds to free list for O(1) reuse.
6679 /// </summary>
6780 [ MethodImpl ( MethodImplOptions . AggressiveInlining | MethodImplOptions . AggressiveOptimization ) ]
6881 public void Remove ( int entityId )
6982 {
70- // Find and tombstone the entity
71- var span = EntityIds . AsSpan ( 0 , Count ) ;
72- int idx = span . IndexOf ( entityId ) ;
83+ // O(1) lookup using reverse index
84+ if ( ! _entityToIndex . TryGetValue ( entityId , out int idx ) )
85+ return ;
7386
74- if ( idx >= 0 )
75- {
76- EntityIds [ idx ] = Tombstone ;
77- AliveCount -- ;
87+ // Tombstone the slot
88+ EntityIds [ idx ] = Tombstone ;
89+ AliveCount -- ;
7890
79- // Add to free list for O(1) reuse
80- if ( _freeCount >= _freeSlots . Length )
81- {
82- Array . Resize ( ref _freeSlots , _freeSlots . Length * 2 ) ;
83- }
84- _freeSlots [ _freeCount ++ ] = idx ;
91+ // Remove from reverse lookup
92+ _entityToIndex . Remove ( entityId ) ;
8593
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.
94+ // Add to free list for O(1) reuse
95+ if ( _freeCount >= _freeSlots . Length )
96+ {
97+ Array . Resize ( ref _freeSlots , _freeSlots . Length * 2 ) ;
8998 }
99+ _freeSlots [ _freeCount ++ ] = idx ;
100+
101+ // Note: Auto-compaction is disabled to avoid unpredictable performance spikes mid-frame.
102+ // Call Context.CompactArchetypes() manually during loading screens or maintenance windows
103+ // to remove tombstones and reduce fragmentation.
90104 }
91105
92106 /// <summary>
93107 /// Compact the array by removing all tombstones.
94- /// Clears the free list since all gaps are removed.
108+ /// Clears the free list and rebuilds reverse lookup since all gaps are removed.
95109 /// </summary>
96110 [ MethodImpl ( MethodImplOptions . AggressiveOptimization ) ]
97111 public void Compact ( )
98112 {
99113 int writeIdx = 0 ;
100114 var span = EntityIds . AsSpan ( 0 , Count ) ;
101115
116+ // Clear and rebuild reverse lookup
117+ _entityToIndex . Clear ( ) ;
118+
102119 for ( int readIdx = 0 ; readIdx < Count ; readIdx ++ )
103120 {
104- if ( span [ readIdx ] != Tombstone )
121+ int entityId = span [ readIdx ] ;
122+ if ( entityId != Tombstone )
105123 {
106- EntityIds [ writeIdx ++ ] = EntityIds [ readIdx ] ;
124+ EntityIds [ writeIdx ] = entityId ;
125+ _entityToIndex [ entityId ] = writeIdx ; // Rebuild reverse lookup
126+ writeIdx ++ ;
107127 }
108128 }
109129
0 commit comments