Skip to content

Commit ad8187c

Browse files
author
LoneWandererProductions
committed
improve Types
1 parent f41cf23 commit ad8187c

3 files changed

Lines changed: 269 additions & 26 deletions

File tree

MemoryManager.Types/ArenaList.cs

Lines changed: 42 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
* COPYRIGHT: See COPYING in the top level directory
33
* PROJECT: MemoryManager.Types
44
* FILE: ArenaList.cs
5-
* PURPOSE: A resizable, unmanaged list backed by a MemoryArena.
5+
* PURPOSE: A resizable, unmanaged list backed by an IMemoryAllocator interface contract.
66
* Automatically handles growth and memory reclamation via handles.
77
* PROGRAMMER: Peter Geinitz (Wayfarer)
88
*/
@@ -14,15 +14,15 @@ namespace MemoryManager.Types
1414
{
1515
/// <summary>
1616
/// A high-performance, resizable list for unmanaged types that allocates
17-
/// its internal buffer from a <see cref="MemoryArena"/>.
17+
/// its internal buffer from an <see cref="IMemoryAllocator"/>.
1818
/// </summary>
1919
/// <typeparam name="T">The unmanaged type to store.</typeparam>
2020
public sealed class ArenaList<T> : IDisposable where T : unmanaged
2121
{
2222
/// <summary>
2323
/// The arena
2424
/// </summary>
25-
private readonly MemoryArena _arena;
25+
private readonly IMemoryAllocator _arena;
2626

2727
/// <summary>
2828
/// The priority
@@ -55,10 +55,18 @@ public sealed class ArenaList<T> : IDisposable where T : unmanaged
5555
public int Capacity => _capacity;
5656

5757
/// <summary>
58-
/// Initializes a new instance of the <see cref="ArenaList{T}"/> class.
58+
/// Initializes a new instance of the <see cref="ArenaList{T}" /> class.
5959
/// </summary>
60+
<<<<<<< HEAD
61+
/// <param name="arena">The arena.</param>
62+
/// <param name="initialCapacity">The initial capacity.</param>
63+
/// <param name="priority">The priority.</param>
64+
/// <param name="hints">The hints.</param>
65+
public ArenaList(IMemoryAllocator arena, int initialCapacity = 8, AllocationPriority priority = AllocationPriority.Normal, AllocationHints hints = AllocationHints.None)
66+
=======
6067
public ArenaList(MemoryArena arena, int initialCapacity = 8,
6168
AllocationPriority priority = AllocationPriority.Normal, AllocationHints hints = AllocationHints.None)
69+
>>>>>>> f41cf23e1c66b3d2e4e393356f32f92443e0ab03
6270
{
6371
if (initialCapacity <= 0) initialCapacity = 8;
6472

@@ -67,56 +75,60 @@ public ArenaList(MemoryArena arena, int initialCapacity = 8,
6775
_priority = priority;
6876
_hints = hints;
6977

70-
// Allocate the initial contiguous block from the arena using explicit design parameters
7178
_handle = _arena.Allocate(Unsafe.SizeOf<T>() * _capacity, _priority, _hints);
7279
}
7380

7481
/// <summary>
75-
/// Gets or sets the element at the specified index via direct address reference tracking expressions.
82+
/// Gets the <see cref="T"/> at the specified index.
7683
/// </summary>
77-
/// <remarks>
78-
/// Warning: Avoid using this indexer heavily inside tight loops, as each invocation incurs a synchronization check.
79-
/// For performance-critical iteration paths, extract a transient block layout using <see cref="AsSpan"/> instead.
80-
/// </remarks>
84+
/// <value>
85+
/// The <see cref="T"/>.
86+
/// </value>
87+
/// <param name="index">The index.</param>
88+
/// <returns></returns>
8189
public ref T this[int index] => ref Get(index);
8290

8391
/// <summary>
84-
/// Adds an item to the list, automatically growing the internal buffer if necessary.
92+
/// Adds the specified item.
8593
/// </summary>
94+
/// <param name="item">The item.</param>
8695
public void Add(T item)
8796
{
8897
if (Count == _capacity) Grow();
8998

90-
// Resolve layout constraints safely and insert item at tracking high-water marks
9199
var span = _arena.GetSpan<T>(_handle, _capacity);
92100
span[Count++] = item;
93101
}
94102

95103
/// <summary>
96-
/// Returns a reference to the element at the specified index.
104+
/// Gets the specified index.
97105
/// </summary>
106+
/// <param name="index">The index.</param>
107+
/// <returns></returns>
108+
/// <exception cref="System.IndexOutOfRangeException">Index {index} is outside active boundaries.</exception>
98109
public ref T Get(int index)
99110
{
100111
if (index < 0 || index >= Count)
112+
<<<<<<< HEAD
113+
throw new IndexOutOfRangeException($"Index {index} is outside active boundaries.");
114+
=======
101115
throw new IndexOutOfRangeException(
102116
$"Index {index} is outside the active boundaries of the ArenaList (Count: {Count}).");
117+
>>>>>>> f41cf23e1c66b3d2e4e393356f32f92443e0ab03
103118

104119
var span = _arena.GetSpan<T>(_handle, _capacity);
105120
return ref span[index];
106121
}
107122

108123
/// <summary>
109-
/// Resets the count to zero. Note: Does not clear memory tracking until an arena cycle triggers maintenance.
124+
/// Clears this instance.
110125
/// </summary>
111-
public void Clear()
112-
{
113-
Count = 0;
114-
}
126+
public void Clear() => Count = 0;
115127

116128
/// <summary>
117-
/// Exposes a fast, direct raw hardware <see cref="Span{T}"/> over the active items list.
118-
/// This completely bypasses inner index loops lock overhead constraints.
129+
/// Ases the span.
119130
/// </summary>
131+
/// <returns></returns>
120132
[MethodImpl(MethodImplOptions.AggressiveInlining)]
121133
public Span<T> AsSpan()
122134
{
@@ -125,29 +137,33 @@ public Span<T> AsSpan()
125137
}
126138

127139
/// <summary>
128-
/// Doubles the capacity of the list by allocating a new handle and migrating data.
140+
/// Exposes a zero-allocation, struct-based enumerator.
141+
/// Allows clean 'foreach' loop compilation bypassing the managed Garbage Collector entirely.
142+
/// </summary>
143+
/// <returns>An enumerator for the list.</returns>
144+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
145+
public Span<T>.Enumerator GetEnumerator() => AsSpan().GetEnumerator();
146+
147+
/// <summary>
148+
/// Grows this instance.
129149
/// </summary>
130150
private void Grow()
131151
{
132152
var newCapacity = _capacity * 2;
133153
var newHandle = _arena.Allocate(Unsafe.SizeOf<T>() * newCapacity, _priority, _hints);
134154

135-
// Fetch structural views over original and destination arrays atomically
136155
var oldSpan = _arena.GetSpan<T>(_handle, _capacity);
137156
var newSpan = _arena.GetSpan<T>(newHandle, newCapacity);
138157

139-
// Execute blazing fast bitwise memory migration copy operations
140158
oldSpan.CopyTo(newSpan);
141-
142-
// Reclaim legacy positions within the parent lane array trackers instantly
143159
_arena.Free(_handle);
144160

145161
_handle = newHandle;
146162
_capacity = newCapacity;
147163
}
148164

149165
/// <summary>
150-
/// Releases the unmanaged memory handle pool block back to the parent arena coordinator framework.
166+
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
151167
/// </summary>
152168
public void Dispose()
153169
{

MemoryManager.Types/ArenaQueue.cs

Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
/*
2+
* COPYRIGHT: See COPYING in the top level directory
3+
* PROJECT: MemoryManager.Types
4+
* FILE: ArenaQueue.cs
5+
* PURPOSE: A high-performance, resizable unmanaged Ring Buffer (Circular Queue).
6+
* Manages wrap-around index alignment and unmanaged data unwrapping.
7+
* PROGRAMMER: Peter Geinitz (Wayfarer)
8+
*/
9+
10+
using MemoryManager.Core;
11+
using System.Runtime.CompilerServices;
12+
13+
namespace MemoryManager.Types
14+
{
15+
/// <summary>
16+
/// A high-performance, zero-allocation circular queue (Ring Buffer) for unmanaged types,
17+
/// backed by an <see cref="IMemoryAllocator"/>.
18+
/// </summary>
19+
/// <typeparam name="T">The unmanaged type to store.</typeparam>
20+
public sealed class ArenaQueue<T> : IDisposable where T : unmanaged
21+
{
22+
/// <summary>
23+
/// The arena
24+
/// </summary>
25+
private readonly IMemoryAllocator _arena;
26+
27+
/// <summary>
28+
/// The priority
29+
/// </summary>
30+
private readonly AllocationPriority _priority;
31+
32+
/// <summary>
33+
/// The hints
34+
/// </summary>
35+
private readonly AllocationHints _hints;
36+
37+
/// <summary>
38+
/// The handle
39+
/// </summary>
40+
private MemoryHandle _handle;
41+
42+
/// <summary>
43+
/// The capacity
44+
/// </summary>
45+
private int _capacity;
46+
47+
/// <summary>
48+
/// The head
49+
/// </summary>
50+
private int _head;
51+
52+
/// <summary>
53+
/// The tail
54+
/// </summary>
55+
private int _tail;
56+
57+
/// <summary>
58+
/// The count
59+
/// </summary>
60+
private int _count;
61+
62+
/// <summary>
63+
/// Gets the number of elements currently waiting in the queue.
64+
/// </summary>
65+
public int Count => _count;
66+
67+
/// <summary>
68+
/// Gets the total size of the internal native ring loop before a resize triggers.
69+
/// </summary>
70+
public int Capacity => _capacity;
71+
72+
/// <summary>
73+
/// Initializes a new instance of the <see cref="ArenaQueue{T}" /> class.
74+
/// </summary>
75+
/// <param name="arena">The arena.</param>
76+
/// <param name="initialCapacity">The initial capacity.</param>
77+
/// <param name="priority">The priority.</param>
78+
/// <param name="hints">The hints.</param>
79+
public ArenaQueue(IMemoryAllocator arena, int initialCapacity = 8, AllocationPriority priority = AllocationPriority.Normal, AllocationHints hints = AllocationHints.None)
80+
{
81+
if (initialCapacity <= 0) initialCapacity = 8;
82+
83+
_arena = arena;
84+
_capacity = initialCapacity;
85+
_priority = priority;
86+
_hints = hints;
87+
88+
// Allocate the circular block segment tracking structural alignment properties
89+
_handle = _arena.Allocate(Unsafe.SizeOf<T>() * _capacity, _priority, _hints);
90+
_head = 0;
91+
_tail = 0;
92+
_count = 0;
93+
}
94+
95+
/// <summary>
96+
/// Pushes an item to the back of the circular queue, automatically growing the array if full.
97+
/// </summary>
98+
/// <param name="item">The item.</param>
99+
public void Enqueue(T item)
100+
{
101+
if (_count == _capacity) Grow();
102+
103+
var span = _arena.GetSpan<T>(_handle, _capacity);
104+
span[_tail] = item;
105+
106+
// The Clockwork Magic: Wrap index back around to 0 if it overshoots the hardware boundary
107+
_tail = (_tail + 1) % _capacity;
108+
_count++;
109+
}
110+
111+
/// <summary>
112+
/// Pops and returns the oldest item from the front of the circular queue.
113+
/// </summary>
114+
/// <returns></returns>
115+
/// <exception cref="System.InvalidOperationException">The ArenaQueue is empty.</exception>
116+
public T Dequeue()
117+
{
118+
if (_count == 0)
119+
throw new InvalidOperationException("The ArenaQueue is empty.");
120+
121+
var span = _arena.GetSpan<T>(_handle, _capacity);
122+
T item = span[_head];
123+
124+
// Advance the front coordinate tracking pointer around the ring loop
125+
_head = (_head + 1) % _capacity;
126+
_count--;
127+
128+
return item;
129+
}
130+
131+
/// <summary>
132+
/// Evaluates the front-most item without popping it from the data lane buffer.
133+
/// </summary>
134+
/// <returns></returns>
135+
/// <exception cref="System.InvalidOperationException">The ArenaQueue is empty.</exception>
136+
public ref T Peek()
137+
{
138+
if (_count == 0)
139+
throw new InvalidOperationException("The ArenaQueue is empty.");
140+
141+
var span = _arena.GetSpan<T>(_handle, _capacity);
142+
return ref span[_head];
143+
}
144+
145+
/// <summary>
146+
/// Resets structural coordinates to zero. Memory retains previous properties until reclamation sweeps.
147+
/// </summary>
148+
public void Clear()
149+
{
150+
_head = 0;
151+
_tail = 0;
152+
_count = 0;
153+
}
154+
155+
/// <summary>
156+
/// The Unwrapping Engine: Allocates a double-sized ring loop and cleanly flattens
157+
/// segmented wrap-around data fragments into a unified linear coordinate system.
158+
/// </summary>
159+
private void Grow()
160+
{
161+
var newCapacity = _capacity * 2;
162+
var newHandle = _arena.Allocate(Unsafe.SizeOf<T>() * newCapacity, _priority, _hints);
163+
164+
var oldSpan = _arena.GetSpan<T>(_handle, _capacity);
165+
var newSpan = _arena.GetSpan<T>(newHandle, newCapacity);
166+
167+
if (_count > 0)
168+
{
169+
// Case 1: Contiguous layout block (Head is positioned behind Tail)
170+
if (_head < _tail)
171+
{
172+
oldSpan.Slice(_head, _count).CopyTo(newSpan);
173+
}
174+
// Case 2: Fractured layout block (Tail wrapped around and is positioned ahead of Head)
175+
else
176+
{
177+
// Step A: Copy from Head to the physical end of the old unmanaged buffer array block
178+
var headSegmentSize = _capacity - _head;
179+
oldSpan.Slice(_head, headSegmentSize).CopyTo(newSpan);
180+
181+
// Step B: Copy remaining wrapped trailing elements from index 0 up to the Tail marker position
182+
oldSpan.Slice(0, _tail).CopyTo(newSpan.Slice(headSegmentSize));
183+
}
184+
}
185+
186+
// Recycle old positions and update spatial properties
187+
_arena.Free(_handle);
188+
_handle = newHandle;
189+
_head = 0;
190+
_tail = _count; // Tail is now cleanly positioned right after our linear block data cluster
191+
_capacity = newCapacity;
192+
}
193+
194+
/// <summary>
195+
/// Cleans up unmanaged boundaries back to native runtime engines safely.
196+
/// </summary>
197+
public void Dispose()
198+
{
199+
if (!_handle.IsInvalid)
200+
{
201+
_arena.Free(_handle);
202+
_handle = default;
203+
}
204+
}
205+
}
206+
}

MemoryManager/MemoryArenaExtensions.cs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,5 +131,26 @@ public static void BulkSet<T>(this IMemoryAllocator arena, MemoryHandle handle,
131131
// Bypasses instance type inference by passing T explicitly
132132
arena.BulkSet<T>(handle, source);
133133
}
134+
135+
/// <summary>
136+
/// Resolves a memory handle and creates a direct, high-performance <see cref="Span{T}"/> view over the native memory block.
137+
/// </summary>
138+
/// <typeparam name="T">The unmanaged value type.</typeparam>
139+
/// <param name="allocator">The allocation engine.</param>
140+
/// <param name="handle">The tracking handle tracking the allocation site.</param>
141+
/// <param name="count">The number of elements of type T to include in the span view.</param>
142+
/// <returns>A span tracking the underlying raw native block coordinates.</returns>
143+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
144+
public static unsafe Span<T> GetSpan<T>(this IMemoryAllocator allocator, MemoryHandle handle, int count) where T : unmanaged
145+
{
146+
if (handle.IsInvalid || count <= 0) return Span<T>.Empty;
147+
148+
// Resolve handles thread-isolation tracks or global locks automatically
149+
nint pointer = allocator.Resolve(handle);
150+
if (pointer == nint.Zero) return Span<T>.Empty;
151+
152+
// Pivot straight into raw unmanaged address spaces
153+
return new Span<T>((void*)pointer, count);
154+
}
134155
}
135156
}

0 commit comments

Comments
 (0)