22 * COPYRIGHT: See COPYING in the top level directory
33 * PROJECT: MemoryArenaPrototype.Lane
44 * FILE: SlowLane.cs
5- * PURPOSE: Memory store for long lived data and stuff we could not hold into he slow lane.
5+ * PURPOSE: Memory store for long lived data and stuff we could not hold into the fast lane.
66 * Ids for Allocations is always negative here.
77 * PROGRAMMER: Peter Geinitz (Wayfarer)
88 */
99
1010// ReSharper disable EventNeverSubscribedTo.Global
11+ #nullable enable
1112
1213using ExtendedSystemObjects ;
1314using MemoryManager . Core ;
@@ -19,7 +20,7 @@ namespace MemoryManager.Lanes
1920{
2021 /// <inheritdoc cref="IMemoryLane" />
2122 /// <summary>
22- /// The SlowLane for all the sorted out stuff and bigger longer resting data.
23+ /// The SlowLane for all the sorted out stuff and bigger longer resting data.
2324 /// </summary>
2425 /// <seealso cref="T:Core.IMemoryLane" />
2526 /// <seealso cref="T:System.IDisposable" />
@@ -33,32 +34,32 @@ public sealed class SlowLane : IMemoryLane, IDisposable
3334#endif
3435
3536 /// <summary>
36- /// The safety margin
37+ /// The safety margin
3738 /// </summary>
3839 private const double SafetyMargin = 0.10 ; // 10% free space reserved
3940
4041 /// <summary>
41- /// The free ids
42+ /// The free ids
4243 /// </summary>
4344 private readonly UnmanagedIntList _freeIds = new ( 128 ) ;
4445
4546 /// <summary>
46- /// The free slots, we reuse freed slots
47+ /// The free slots, we reuse freed slots
4748 /// </summary>
4849 private readonly UnmanagedIntList _freeSlots = new ( 128 ) ;
4950
5051 /// <summary>
51- /// The handle index
52+ /// The handle index
5253 /// </summary>
5354 private readonly UnmanagedMap < int > _handleIndex = new ( 7 ) ; // handleId -> entries array index
5455
5556 /// <summary>
56- /// The allocated entries
57+ /// The allocated entries
5758 /// </summary>
5859 private AllocationEntry [ ] _entries ;
5960
6061 /// <summary>
61- /// The next handle identifier
62+ /// The next handle identifier
6263 /// </summary>
6364 private int _nextHandleId = - 1 ;
6465
@@ -73,20 +74,25 @@ public sealed class SlowLane : IMemoryLane, IDisposable
7374 private int _freeBlockCount ;
7475
7576 /// <summary>
76- /// The threshold. Anything smaller than this goes to the BlobManager.
77- /// e.g., 256 bytes
77+ /// The threshold. Anything smaller than this goes to the BlobManager.
78+ /// e.g., 256 bytes
7879 /// </summary>
7980 private readonly int _blobThreshold = 256 ;
8081
8182 /// <summary>
82- /// The specialized manager for tiny/unpredictable allocations.
83+ /// The specialized manager for tiny/unpredictable allocations.
8384 /// </summary>
8485 private readonly BlobManager ? _blobManager ;
8586
8687 /// <summary>
87- /// The versions
88+ /// Pointer to the flat versions array. Index matches absolute handle ID (-id).
8889 /// </summary>
89- private readonly UnmanagedMap < uint > _versions ;
90+ private unsafe uint * _versions ;
91+
92+ /// <summary>
93+ /// Current capacity of the versions array.
94+ /// </summary>
95+ private int _versionsCapacity ;
9096
9197 /// <summary>
9298 /// Initializes a new instance of the <see cref="SlowLane" /> class.
@@ -95,15 +101,18 @@ public sealed class SlowLane : IMemoryLane, IDisposable
95101 /// <param name="blobCapacityFraction">The BLOB capacity fraction.</param>
96102 /// <param name="blobThreshold">The BLOB threshold.</param>
97103 /// <param name="maxEntries">The maximum entries.</param>
98- public SlowLane ( int capacity , double blobCapacityFraction = 0.20 , int blobThreshold = 256 ,
104+ public unsafe SlowLane ( int capacity , double blobCapacityFraction = 0.20 , int blobThreshold = 256 ,
99105 int maxEntries = 1024 )
100106 {
101107 Capacity = capacity ;
102108 _blobThreshold = blobThreshold ;
103109
104110 Buffer = Marshal . AllocHGlobal ( capacity ) ;
105111 _entries = new AllocationEntry [ maxEntries ] ;
106- _versions = new UnmanagedMap < uint > ( maxEntries ) ;
112+
113+ // INITIALIZATION: Allocate flat tracking for versions (+1 to accommodate 1-based indexing of absolute IDs)
114+ _versionsCapacity = maxEntries + 1 ;
115+ _versions = ( uint * ) NativeMemory . AllocZeroed ( ( nuint ) _versionsCapacity , sizeof ( uint ) ) ;
107116
108117 // Use the fraction provided by the config/constructor
109118 var blobCapacity = ( int ) ( capacity * blobCapacityFraction ) ;
@@ -115,43 +124,49 @@ public SlowLane(int capacity, double blobCapacityFraction = 0.20, int blobThresh
115124 }
116125
117126 /// <summary>
118- /// Gets or sets the buffer.
127+ /// Gets or sets the buffer.
119128 /// </summary>
120129 /// <value>
121- /// The buffer.
130+ /// The buffer.
122131 /// </value>
123132 public nint Buffer { get ; private set ; }
124133
125134 /// <summary>
126- /// Gets the capacity.
135+ /// Gets the capacity.
127136 /// </summary>
128137 /// <value>
129- /// The capacity.
138+ /// The capacity.
130139 /// </value>
131140 public int Capacity { get ; }
132141
133-
134142 /// <inheritdoc />
135143 public int EntryCount { get ; private set ; }
136144
137145 /// <inheritdoc />
138146 /// <summary>
139- /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
147+ /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
140148 /// </summary>
141- public void Dispose ( )
149+ public unsafe void Dispose ( )
142150 {
143151 Marshal . FreeHGlobal ( Buffer ) ;
144152 EntryCount = 0 ;
145153 _handleIndex . Clear ( ) ;
146154 _freeSlots . Clear ( ) ;
147155 _freeIds . Clear ( ) ;
148156
157+ // clean up unmanaged versions array to prevent memory leaks
158+ if ( _versions != null )
159+ {
160+ NativeMemory . Free ( _versions ) ;
161+ _versions = null ;
162+ }
163+
149164 _blobManager ? . Compact ( ) ;
150165 }
151166
152167 /// <inheritdoc />
153168 /// <summary>
154- /// Allocates the specified size.
169+ /// Allocates the specified size.
155170 /// </summary>
156171 /// <param name="size">The size.</param>
157172 /// <param name="priority">The priority.</param>
@@ -160,7 +175,7 @@ public void Dispose()
160175 /// <param name="currentFrame">The current frame.</param>
161176 /// <returns>Allocated memory and a reference.</returns>
162177 /// <exception cref="T:System.OutOfMemoryException">SlowLane: Cannot allocate</exception>
163- public MemoryHandle Allocate (
178+ public unsafe MemoryHandle Allocate (
164179 int size ,
165180 AllocationPriority priority = AllocationPriority . Normal ,
166181 AllocationHints hints = AllocationHints . None ,
@@ -184,7 +199,7 @@ public MemoryHandle Allocate(
184199 var slotIndex = _freeSlots . Length > 0 ? _freeSlots . Pop ( ) : EntryCount ++ ;
185200 EnsureEntryCapacity ( slotIndex ) ;
186201
187- //So we reuse freed handles here
202+ // So we reuse freed handles here (Yields sequential negative IDs, e.g., -1, -2, -3...)
188203 var id = MemoryLaneUtils . GetNextId ( _freeIds , ref _nextHandleId ) ;
189204
190205#if DEBUG
@@ -193,11 +208,15 @@ public MemoryHandle Allocate(
193208 _debugNames [ id ] = debugName ;
194209 }
195210#endif
196- var currentVersion = _versions . TryGetValue ( id , out var version )
197- ? ( ushort ) ( version + 1 )
198- : ( ushort ) 1 ;
211+ // Map sequential negative ID directly to positive array coordinates
212+ int versionIdx = - id ;
213+ if ( versionIdx >= _versionsCapacity )
214+ {
215+ GrowVersions ( versionIdx + 1 ) ;
216+ }
199217
200- _versions [ id ] = currentVersion ;
218+ // True O(1) Version Bump
219+ uint currentVersion = ++ _versions [ versionIdx ] ;
201220
202221 _entries [ slotIndex ] = new AllocationEntry
203222 {
@@ -242,14 +261,14 @@ public bool CanAllocate(int size)
242261
243262 /// <inheritdoc />
244263 /// <summary>
245- /// Resolves the specified handle.
264+ /// Resolves the specified handle.
246265 /// </summary>
247266 /// <param name="handle">The handle.</param>
248267 /// <returns>Pointer to the stored data.</returns>
249268 /// <exception cref="T:System.InvalidOperationException">
250- /// SlowLane: Invalid handle
251- /// or
252- /// SlowLane: Cannot resolve stub entry without redirection
269+ /// SlowLane: Invalid handle
270+ /// or
271+ /// SlowLane: Cannot resolve stub entry without redirection
253272 /// </exception>
254273 public nint Resolve ( MemoryHandle handle )
255274 {
@@ -276,7 +295,7 @@ public nint Resolve(MemoryHandle handle)
276295
277296 /// <inheritdoc />
278297 /// <summary>
279- /// Frees the specified handle.
298+ /// Frees the specified handle.
280299 /// </summary>
281300 /// <param name="handle">The handle.</param>
282301 /// <exception cref="T:System.InvalidOperationException">SlowLane: Invalid handle</exception>
@@ -292,6 +311,11 @@ public void Free(MemoryHandle handle)
292311 if ( ! _handleIndex . TryRemove ( handle . Id , out var index ) )
293312 throw new InvalidOperationException ( $ "SlowLane: Invalid handle { handle . Id } ") ;
294313
314+ var entry = _entries [ index ] ;
315+
316+ // Reclaim the physical block space inside the main arena tracker!
317+ MemoryLaneUtils . ReturnFreeSpace ( entry . Offset , entry . Size , ref _freeBlocks , ref _freeBlockCount ) ;
318+
295319 _entries [ index ] = default ;
296320 _freeSlots . Push ( index ) ;
297321 _freeIds . Push ( handle . Id ) ;
@@ -306,12 +330,13 @@ public void Free(MemoryHandle handle)
306330 /// </summary>
307331 /// <param name="handles">The handles.</param>
308332 /// <exception cref="InvalidOperationException">SlowLane: Invalid handle {handle.Id}</exception>
309- public unsafe void FreeMany ( MemoryHandle [ ] handles ) // Or ReadOnlySpan<MemoryHandle>
333+ public unsafe void FreeMany ( MemoryHandle [ ] handles )
310334 {
311335 var span = handles . AsSpan ( ) ;
312336 var count = span . Length ;
313337
314- // We'll collect the IDs and Indices in temporary buffers to batch-push
338+ if ( count == 0 ) return ;
339+
315340 // Using stackalloc for small-to-medium batches avoids GC pressure
316341 var ids = stackalloc int [ count ] ;
317342 var indices = stackalloc int [ count ] ;
@@ -320,9 +345,21 @@ public unsafe void FreeMany(MemoryHandle[] handles) // Or ReadOnlySpan<MemoryHan
320345 {
321346 var id = span [ i ] . Id ;
322347
348+ // Route tiny blobs safely away from structural array paths
349+ if ( id <= BlobManager . StartingId && _blobManager != null )
350+ {
351+ _blobManager . Free ( span [ i ] ) ;
352+ continue ;
353+ }
354+
323355 if ( ! _handleIndex . TryRemove ( id , out var index ) )
324356 throw new InvalidOperationException ( $ "SlowLane: Invalid handle { id } ") ;
325357
358+ var entry = _entries [ index ] ;
359+
360+ // Return the space to the physical free list for EVERY element in the batch
361+ MemoryLaneUtils . ReturnFreeSpace ( entry . Offset , entry . Size , ref _freeBlocks , ref _freeBlockCount ) ;
362+
326363 // Clear entry data
327364 _entries [ index ] = default ;
328365
@@ -333,8 +370,6 @@ public unsafe void FreeMany(MemoryHandle[] handles) // Or ReadOnlySpan<MemoryHan
333370 // Batch add to our unmanaged lists
334371 _freeIds . PushRange ( new ReadOnlySpan < int > ( ids , count ) ) ;
335372 _freeSlots . PushRange ( new ReadOnlySpan < int > ( indices , count ) ) ;
336-
337- EntryCount += count ;
338373 }
339374
340375 /// <inheritdoc />
@@ -402,7 +437,7 @@ public unsafe void Compact()
402437 EntryCount = writeIndex ;
403438 _freeSlots . Clear ( ) ; // No more holes in the array!
404439
405- // 6. THE MISSING FIX: Reset the Free-List!
440+ // 6. Reset the Free-List!
406441 _freeBlocks [ 0 ] = new FreeBlock
407442 {
408443 Offset = offset ,
@@ -481,9 +516,8 @@ public IEnumerable<MemoryHandle> GetHandles()
481516 }
482517
483518 /// <summary>
484- /// Gets the used.
519+ /// Gets the used memory .
485520 /// </summary>
486- /// <returns>Get used Id.</returns>
487521 [ MethodImpl ( MethodImplOptions . AggressiveInlining ) ]
488522 private int GetUsed ( )
489523 {
@@ -499,24 +533,49 @@ private int GetUsed()
499533 public int FreeSpace ( )
500534 {
501535 var mainFreeSpace = MemoryLaneUtils . CalculateFreeSpace ( _entries , EntryCount , Capacity ) ;
502-
503536 var blobFreeSpace = _blobManager ? . FreeSpace ( ) ?? 0 ;
504537
505538 return mainFreeSpace + blobFreeSpace ;
506539 }
507540
508541 /// <summary>
509- /// Ensures the entry capacity.
542+ /// Ensures the entry capacity.
510543 /// </summary>
511- /// <param name="requiredSlotIndex">Index of the required slot.</param>
512544 private void EnsureEntryCapacity ( int requiredSlotIndex )
513545 {
514546 var oldSize = _entries . Length ;
515547 var newSize = MemoryLaneUtils . EnsureEntryCapacity ( ref _entries , requiredSlotIndex ) ;
516- // Allocation Entries must be extended
548+
549+ // Defensive Safeguard: Grow free blocks buffer concurrently
550+ if ( _freeBlocks . Length < newSize )
551+ {
552+ Array . Resize ( ref _freeBlocks , newSize ) ;
553+ }
554+
517555 OnAllocationExtension ? . Invoke ( nameof ( SlowLane ) , oldSize , newSize ) ;
518556 }
519557
558+ /// <summary>
559+ /// Resizes the unmanaged versions buffer.
560+ /// </summary>
561+ [ MethodImpl ( MethodImplOptions . NoInlining ) ]
562+ private unsafe void GrowVersions ( int minCapacity )
563+ {
564+ var newCapacity = _versionsCapacity * 2 ;
565+ if ( newCapacity < minCapacity ) newCapacity = minCapacity ;
566+
567+ var newVersions = ( uint * ) NativeMemory . AllocZeroed ( ( nuint ) newCapacity , sizeof ( uint ) ) ;
568+
569+ if ( _versions != null )
570+ {
571+ Unsafe . CopyBlock ( newVersions , _versions , ( uint ) ( _versionsCapacity * sizeof ( uint ) ) ) ;
572+ NativeMemory . Free ( _versions ) ;
573+ }
574+
575+ _versions = newVersions ;
576+ _versionsCapacity = newCapacity ;
577+ }
578+
520579 /// <inheritdoc />
521580 public int StubCount ( )
522581 {
@@ -545,19 +604,17 @@ public string DebugVisualMap()
545604 /// <inheritdoc />
546605 public string DebugRedirections ( )
547606 {
548- if ( _entries == null ) throw new InvalidOperationException ( "FastLane : Invalid memory." ) ;
607+ if ( _entries == null ) throw new InvalidOperationException ( "SlowLane : Invalid memory." ) ;
549608
550609#if DEBUG
551- // Pass the debug names dictionary in Debug mode
552610 return MemoryLaneUtils . DebugRedirections ( _entries , EntryCount , _debugNames ) ;
553611#else
554- // Pass null in Release mode since the dictionary doesn't exist
555- return MemoryLaneUtils . DebugRedirections ( _entries , EntryCount , null ) ;
612+ return MemoryLaneUtils . DebugRedirections ( _entries , EntryCount , null ) ;
556613#endif
557614 }
558615
559616 /// <summary>
560- /// Dump all Debug Infos.
617+ /// Dump all Debug Infos.
561618 /// </summary>
562619 public void LogDump ( )
563620 {
0 commit comments