33 * PROJECT: MemoryArenaPrototype.Lane
44 * FILE: FastLane.cs
55 * PURPOSE: Memory store for short lived data, smaller one preferable but that is up to the user.
6- * Ids for Allocations is always positive here.
6+ * Ids for Allocations is always positive here.
77 * PROGRAMMER: Peter Geinitz (Wayfarer)
88 */
99
@@ -64,9 +64,14 @@ public sealed class FastLane : IFastLane, IDisposable
6464 private FreeBlock [ ] _freeBlocks = new FreeBlock [ 128 ] ;
6565
6666 /// <summary>
67- /// The versions
67+ /// Pointer to the flat versions array. Index matches handle ID.
6868 /// </summary>
69- private readonly UnmanagedMap < uint > _versions ;
69+ private unsafe uint * _versions ;
70+
71+ /// <summary>
72+ /// Current capacity of the versions array.
73+ /// </summary>
74+ private int _versionsCapacity ;
7075
7176 /// <summary>
7277 /// The free block count
@@ -79,7 +84,7 @@ public sealed class FastLane : IFastLane, IDisposable
7984 /// <param name="size">The size.</param>
8085 /// <param name="slowLane">The slow lane.</param>
8186 /// <param name="maxEntries">The maximum entries.</param>
82- public FastLane ( int size , SlowLane slowLane , int maxEntries = 1024 )
87+ public unsafe FastLane ( int size , SlowLane slowLane , int maxEntries = 1024 )
8388 {
8489 _slowLane = slowLane ;
8590 Capacity = size ;
@@ -88,7 +93,9 @@ public FastLane(int size, SlowLane slowLane, int maxEntries = 1024)
8893 // PRE-ALLOCATE everything based on maxEntries
8994 _entries = new AllocationEntry [ maxEntries ] ;
9095 _freeBlocks = new FreeBlock [ maxEntries ] ; // Free blocks can theoretically equal maxEntries
91- _versions = new UnmanagedMap < uint > ( maxEntries ) ;
96+ // INITIALIZATION: Allocate flat tracking for versions
97+ _versionsCapacity = maxEntries + 1 ;
98+ _versions = ( uint * ) NativeMemory . AllocZeroed ( ( nuint ) _versionsCapacity , sizeof ( uint ) ) ;
9299
93100 // INITIALIZATION: The entire lane starts as one giant free block
94101 _freeBlocks [ 0 ] = new FreeBlock { Offset = 0 , Size = Capacity } ;
@@ -106,7 +113,6 @@ public FastLane(int size, SlowLane slowLane, int maxEntries = 1024)
106113 /// <inheritdoc />
107114 public int EntryCount { get ; private set ; }
108115
109-
110116 /// <inheritdoc />
111117 public OneWayLane ? OneWayLane { get ; set ; }
112118
@@ -122,10 +128,17 @@ public FastLane(int size, SlowLane slowLane, int maxEntries = 1024)
122128 /// <summary>
123129 /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
124130 /// </summary>
125- public void Dispose ( )
131+ public unsafe void Dispose ( )
126132 {
127133 Marshal . FreeHGlobal ( Buffer ) ;
128134 _handleIndex . Clear ( ) ;
135+
136+ if ( _versions != null )
137+ {
138+ NativeMemory . Free ( _versions ) ;
139+ _versions = null ;
140+ }
141+
129142 _entries = null ;
130143 GC . SuppressFinalize ( this ) ;
131144 }
@@ -155,7 +168,7 @@ public bool CanAllocate(int size)
155168 /// <returns>Handler to the reserved memory.</returns>
156169 /// <exception cref="T:System.InvalidOperationException">FastLane: Memory not reserved</exception>
157170 /// <exception cref="T:System.OutOfMemoryException">FastLane: Not enough memory</exception>
158- public MemoryHandle Allocate (
171+ public unsafe MemoryHandle Allocate (
159172 int size ,
160173 AllocationPriority priority = AllocationPriority . Normal ,
161174 AllocationHints hints = AllocationHints . None ,
@@ -167,19 +180,22 @@ public MemoryHandle Allocate(
167180 var offset = MemoryLaneUtils . FindFreeSpot ( size , ref _freeBlocks , ref _freeBlockCount ) ;
168181
169182 if ( offset == - 1 )
170- throw new OutOfMemoryException ( "SlowLane : Cannot allocate - No contiguous block large enough." ) ;
183+ throw new OutOfMemoryException ( "FastLane : Cannot allocate - No contiguous block large enough." ) ;
171184
172185 EnsureEntryCapacity ( EntryCount ) ;
173186
174187 //So we reuse freed handles here
188+ // So we reuse freed handles here
175189 var id = MemoryLaneUtils . GetNextId ( _freeIds , ref _nextHandleId ) ;
176190
177- // Increment the version for this ID slot ---
178- // This ensures that any old handles still pointing to this ID will be rejected.
179- _versions . TryGetValue ( id , out var version ) ;
180- version ++ ;
191+ // Ensure our flat array is large enough for this ID
192+ if ( id >= _versionsCapacity )
193+ {
194+ GrowVersions ( id + 1 ) ;
195+ }
181196
182- _versions [ id ] = version ;
197+ // True O(1) Version Bump
198+ uint version = ++ _versions [ id ] ;
183199
184200#if DEBUG
185201 if ( ! string . IsNullOrEmpty ( debugName ) )
@@ -261,7 +277,7 @@ public void Free(MemoryHandle handle)
261277 {
262278 // 1. Remove from index first
263279 if ( ! _handleIndex . TryRemove ( handle . Id , out var index ) )
264- throw new InvalidOperationException ( $ "SlowLane : Invalid handle { handle . Id } ") ;
280+ throw new InvalidOperationException ( $ "FastLane : Invalid handle { handle . Id } ") ;
265281
266282#if DEBUG
267283 _debugNames . Remove ( handle . Id ) ;
@@ -273,9 +289,8 @@ public void Free(MemoryHandle handle)
273289
274290 if ( entry . IsStub && entry . RedirectToId != 0 )
275291 {
276- var slowHandle = new MemoryHandle ( entry . RedirectToId , entry . Version , _slowLane ) ;
292+ var slowHandle = new MemoryHandle ( entry . RedirectToId , entry . RedirectVersion , _slowLane ) ;
277293 _slowLane . Free ( slowHandle ) ;
278- // Redirects.Remove(handle.Id); // We are going to delete this dictionary entirely in a second!
279294 }
280295
281296 // 3. Swap-with-tail removal
@@ -296,7 +311,6 @@ public void Free(MemoryHandle handle)
296311 _freeIds . Push ( handle . Id ) ;
297312 }
298313
299-
300314 /// <summary>
301315 /// Compacts the memory lane by consolidating free space and possibly rearranging allocations.
302316 /// Useful to reduce fragmentation and improve allocation efficiency.
@@ -472,7 +486,13 @@ private void EnsureEntryCapacity(int requiredSlotIndex)
472486 {
473487 var oldSize = _entries . Length ;
474488 var newSize = MemoryLaneUtils . EnsureEntryCapacity ( ref _entries , requiredSlotIndex ) ;
475- // Allocation Entriesmust be extended
489+
490+ // Defensive Safeguard: Ensure free blocks grow alongside entries
491+ if ( _freeBlocks . Length < newSize )
492+ {
493+ Array . Resize ( ref _freeBlocks , newSize ) ;
494+ }
495+
476496 OnAllocationExtension ? . Invoke ( nameof ( FastLane ) , oldSize , newSize ) ;
477497 }
478498
@@ -563,8 +583,8 @@ public string DebugRedirections()
563583 // Pass the debug names dictionary in Debug mode
564584 return MemoryLaneUtils . DebugRedirections ( _entries , EntryCount , _debugNames ) ;
565585#else
566- // Pass null in Release mode since the dictionary doesn't exist
567- return MemoryLaneUtils . DebugRedirections ( _entries , EntryCount , null ) ;
586+ // Pass null in Release mode since the dictionary doesn't exist
587+ return MemoryLaneUtils . DebugRedirections ( _entries , EntryCount , null ) ;
568588#endif
569589 }
570590
@@ -580,6 +600,29 @@ public void LogDump()
580600 Trace . WriteLine ( $ "--- { GetType ( ) . Name } Dump End ---") ;
581601 }
582602
603+ /// <summary>
604+ /// Grows the versions.
605+ /// </summary>
606+ /// <param name="minCapacity">The minimum capacity.</param>
607+ [ MethodImpl ( MethodImplOptions . NoInlining ) ]
608+ private unsafe void GrowVersions ( int minCapacity )
609+ {
610+ var newCapacity = _versionsCapacity * 2 ;
611+ if ( newCapacity < minCapacity ) newCapacity = minCapacity ;
612+
613+ var newVersions = ( uint * ) NativeMemory . AllocZeroed ( ( nuint ) newCapacity , sizeof ( uint ) ) ;
614+
615+ if ( _versions != null )
616+ {
617+ // Copy old versions to the new block
618+ Unsafe . CopyBlock ( newVersions , _versions , ( uint ) ( _versionsCapacity * sizeof ( uint ) ) ) ;
619+ NativeMemory . Free ( _versions ) ;
620+ }
621+
622+ _versions = newVersions ;
623+ _versionsCapacity = newCapacity ;
624+ }
625+
583626 /// <summary>
584627 /// Evaluates an allocation against current policies to see if it should be evicted.
585628 /// </summary>
0 commit comments