Refactor memory management and improve allocation APIs
Updated `ReallocFunc` to support `oldSize`, `newSize`, and `AllocationOption`, enabling more granular control over memory reallocation. Simplified `AllocationInfo` by removing the `FreeHandler` property. Enhanced `Reallocate` and `Allocate` methods in `AllocationManager` to handle memory clearing and tracking more effectively. Refactored `UnsafeSparseSet` to use `UnsafeArray<T>` directly, added a `generations` array, and replaced the `freeList` with `UnsafeStack<int>` for better performance and simplicity. Updated `Resize`, `Add`, and `Remove` methods to improve memory handling and code clarity. Introduced `AllocationOption` support in `Resize` methods across `IUnsafeCollection` implementations for flexible resizing behavior. Added `GetUnsafePtr` extension methods in `UnsafeUtilities` for direct access to span data. Added new tests for `UnsafeSparseSet` to validate resizing, clearing, enumeration, and memory compaction. These changes improve memory management, enhance performance, and ensure correctness.
This commit is contained in:
@@ -2,5 +2,5 @@
|
|||||||
|
|
||||||
|
|
||||||
global using unsafe AllocFunc = delegate*<void*, nuint, nuint, Misaki.HighPerformance.LowLevel.Buffer.AllocationOption, void*>;
|
global using unsafe AllocFunc = delegate*<void*, nuint, nuint, Misaki.HighPerformance.LowLevel.Buffer.AllocationOption, void*>;
|
||||||
|
global using unsafe ReallocFunc = delegate*<void*, void*, nuint, nuint, nuint, Misaki.HighPerformance.LowLevel.Buffer.AllocationOption, void*>;
|
||||||
global using unsafe FreeFunc = delegate*<void*, void*, void>;
|
global using unsafe FreeFunc = delegate*<void*, void*, void>;
|
||||||
global using unsafe ReallocFunc = delegate*<void*, void*, nuint, nuint, void*>;
|
|
||||||
@@ -28,14 +28,6 @@ public readonly unsafe struct AllocationInfo
|
|||||||
get; init;
|
get; init;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Get the function pointer used to free the allocated memory.
|
|
||||||
/// </summary>
|
|
||||||
public FreeFunc FreeHandler
|
|
||||||
{
|
|
||||||
get; init;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Get the stack trace at the time of allocation for debugging purposes.
|
/// Get the stack trace at the time of allocation for debugging purposes.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -72,11 +64,19 @@ public static unsafe class AllocationManager
|
|||||||
return ptr;
|
return ptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void* Reallocate(void* instance, void* ptr, nuint size, nuint alignment)
|
private static void* Reallocate(void* instance, void* ptr, nuint oldSize, nuint newSize, nuint alignment, AllocationOption allocationOption)
|
||||||
{
|
{
|
||||||
var selfPtr = (ArenaAllocator*)instance;
|
var selfPtr = (ArenaAllocator*)instance;
|
||||||
var newPtr = selfPtr->_arena.Allocate(size, alignment, AllocationOption.None);
|
var newPtr = selfPtr->_arena.Allocate(newSize, alignment, allocationOption);
|
||||||
MemCpy(newPtr, ptr, size);
|
MemCpy(newPtr, ptr, newSize);
|
||||||
|
|
||||||
|
if (allocationOption.HasFlag(AllocationOption.Clear))
|
||||||
|
{
|
||||||
|
if (newSize > oldSize)
|
||||||
|
{
|
||||||
|
MemClear((byte*)newPtr + oldSize, newSize - oldSize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// We do not free the old pointer here, as it is managed by the arena.
|
// We do not free the old pointer here, as it is managed by the arena.
|
||||||
return newPtr;
|
return newPtr;
|
||||||
@@ -113,23 +113,39 @@ public static unsafe class AllocationManager
|
|||||||
{
|
{
|
||||||
var ptr = AlignedAlloc(size, alignment);
|
var ptr = AlignedAlloc(size, alignment);
|
||||||
|
|
||||||
if (!allocationOption.HasFlag(AllocationOption.UnTracked))
|
|
||||||
{
|
|
||||||
TrackAllocation(ptr, size, instance, &FreeBlock);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (allocationOption.HasFlag(AllocationOption.Clear))
|
if (allocationOption.HasFlag(AllocationOption.Clear))
|
||||||
{
|
{
|
||||||
MemClear(ptr, size);
|
MemClear(ptr, size);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!allocationOption.HasFlag(AllocationOption.UnTracked))
|
||||||
|
{
|
||||||
|
TrackAllocation(ptr, size, instance);
|
||||||
|
}
|
||||||
|
|
||||||
return ptr;
|
return ptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void* Reallocate(void* instance, void* ptr, nuint size, nuint alignment)
|
private static void* Reallocate(void* instance, void* ptr, nuint oldSize, nuint newSize, nuint alignment, AllocationOption allocationOption)
|
||||||
{
|
{
|
||||||
var newPtr = AlignedRealloc(ptr, size, alignment);
|
var newPtr = AlignedRealloc(ptr, newSize, alignment);
|
||||||
UpdateAllocation(ptr, newPtr, size, instance, &FreeBlock);
|
|
||||||
|
if (allocationOption.HasFlag(AllocationOption.Clear))
|
||||||
|
{
|
||||||
|
if (newSize > oldSize)
|
||||||
|
{
|
||||||
|
MemClear((byte*)newPtr + oldSize, newSize - oldSize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!allocationOption.HasFlag(AllocationOption.UnTracked))
|
||||||
|
{
|
||||||
|
UpdateAllocation(ptr, newPtr, newSize, instance);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
UntrackAllocation(ptr);
|
||||||
|
}
|
||||||
|
|
||||||
return newPtr;
|
return newPtr;
|
||||||
}
|
}
|
||||||
@@ -161,10 +177,19 @@ public static unsafe class AllocationManager
|
|||||||
return ptr;
|
return ptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void* Reallocate(void* instance, void* ptr, nuint size, nuint alignment)
|
private static void* Reallocate(void* instance, void* ptr, nuint oldSize, nuint newSize, nuint alignment, AllocationOption allocationOption)
|
||||||
{
|
{
|
||||||
var newPtr = s_stack.Allocate(size, alignment, AllocationOption.None);
|
var newPtr = s_stack.Allocate(newSize, alignment, AllocationOption.None);
|
||||||
MemCpy(newPtr, ptr, size);
|
MemCpy(newPtr, ptr, newSize);
|
||||||
|
|
||||||
|
if (allocationOption.HasFlag(AllocationOption.Clear))
|
||||||
|
{
|
||||||
|
if (newSize > oldSize)
|
||||||
|
{
|
||||||
|
MemClear((byte*)newPtr + oldSize, newSize - oldSize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// We do not free the old pointer here, as it is managed by the stack.
|
// We do not free the old pointer here, as it is managed by the stack.
|
||||||
return newPtr;
|
return newPtr;
|
||||||
}
|
}
|
||||||
@@ -240,7 +265,7 @@ public static unsafe class AllocationManager
|
|||||||
/// <param name="allocator">The allocator used for the allocation.</param>
|
/// <param name="allocator">The allocator used for the allocation.</param>
|
||||||
/// <param name="freeFunc">The function pointer used to free the allocated memory.</param>
|
/// <param name="freeFunc">The function pointer used to free the allocated memory.</param>
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public static void TrackAllocation(void* ptr, nuint allocationSize, void* allocator, FreeFunc freeFunc)
|
public static void TrackAllocation(void* ptr, nuint allocationSize, void* allocator)
|
||||||
{
|
{
|
||||||
if (!s_debugLayer || s_allocated == null || ptr == null)
|
if (!s_debugLayer || s_allocated == null || ptr == null)
|
||||||
{
|
{
|
||||||
@@ -251,7 +276,6 @@ public static unsafe class AllocationManager
|
|||||||
{
|
{
|
||||||
Size = allocationSize,
|
Size = allocationSize,
|
||||||
Allocator = allocator,
|
Allocator = allocator,
|
||||||
FreeHandler = freeFunc,
|
|
||||||
StackTrace = new StackTrace(true)
|
StackTrace = new StackTrace(true)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -265,7 +289,7 @@ public static unsafe class AllocationManager
|
|||||||
/// <param name="allocator">A pointer to the allocator responsible for the new allocation.</param>
|
/// <param name="allocator">A pointer to the allocator responsible for the new allocation.</param>
|
||||||
/// <param name="freeFunc">A delegate or function pointer used to free the memory associated with the allocation.</param>
|
/// <param name="freeFunc">A delegate or function pointer used to free the memory associated with the allocation.</param>
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public static void UpdateAllocation(void* oldPtr, void* newPtr, nuint allocationSize, void* allocator, FreeFunc freeFunc)
|
public static void UpdateAllocation(void* oldPtr, void* newPtr, nuint allocationSize, void* allocator)
|
||||||
{
|
{
|
||||||
if (!s_debugLayer || s_allocated == null || oldPtr == null || newPtr == null)
|
if (!s_debugLayer || s_allocated == null || oldPtr == null || newPtr == null)
|
||||||
{
|
{
|
||||||
@@ -279,7 +303,6 @@ public static unsafe class AllocationManager
|
|||||||
{
|
{
|
||||||
Size = allocationSize,
|
Size = allocationSize,
|
||||||
Allocator = allocator,
|
Allocator = allocator,
|
||||||
FreeHandler = freeFunc,
|
|
||||||
StackTrace = info.StackTrace
|
StackTrace = info.StackTrace
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -330,7 +353,6 @@ public static unsafe class AllocationManager
|
|||||||
foreach (var pair in s_allocated)
|
foreach (var pair in s_allocated)
|
||||||
{
|
{
|
||||||
unfreeBytes += pair.Value.Size;
|
unfreeBytes += pair.Value.Size;
|
||||||
pair.Value.FreeHandler(pair.Value.Allocator, (void*)pair.Key);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (unfreeBytes > 0u)
|
if (unfreeBytes > 0u)
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
namespace Misaki.HighPerformance.LowLevel.Collections.Contracts;
|
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||||
|
|
||||||
|
namespace Misaki.HighPerformance.LowLevel.Collections.Contracts;
|
||||||
|
|
||||||
public unsafe interface IUnsafeCollection : IDisposable
|
public unsafe interface IUnsafeCollection : IDisposable
|
||||||
{
|
{
|
||||||
@@ -38,7 +40,7 @@ public unsafe interface IUnsafeCollection<T> : IUnsafeCollection, IEnumerable<T>
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>This is to adjust the element count of the collection, not the size of the underlying buffer in memory.</remarks>
|
/// <remarks>This is to adjust the element count of the collection, not the size of the underlying buffer in memory.</remarks>
|
||||||
/// <param name="newSize">Specifies the new size to which the collection should be adjusted.</param>
|
/// <param name="newSize">Specifies the new size to which the collection should be adjusted.</param>
|
||||||
public void Resize(int newSize);
|
public void Resize(int newSize, AllocationOption option);
|
||||||
}
|
}
|
||||||
|
|
||||||
public unsafe interface IUnTypedCollection : IUnsafeCollection
|
public unsafe interface IUnTypedCollection : IUnsafeCollection
|
||||||
|
|||||||
@@ -79,14 +79,14 @@ public unsafe struct UnTypedArray : IUnTypedCollection
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public void Resize(uint newSize)
|
public void Resize(uint newSize, AllocationOption option = AllocationOption.None)
|
||||||
{
|
{
|
||||||
if (newSize == _size)
|
if (newSize == _size)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_buffer = _handle->Realloc(_handle->Allocator, _buffer, newSize, _alignment);
|
_buffer = _handle->Realloc(_handle->Allocator, _buffer, _size, newSize, _alignment, option);
|
||||||
_size = newSize;
|
_size = newSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -168,14 +168,15 @@ public unsafe struct UnsafeArray<T> : IUnsafeCollection<T>
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public void Resize(int newSize)
|
public void Resize(int newSize, AllocationOption option = AllocationOption.None)
|
||||||
{
|
{
|
||||||
if (newSize == _count)
|
if (newSize == _count)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_buffer = (T*)_handle->Realloc(_handle->Allocator, _buffer, (nuint)newSize * SizeOf<T>(), AlignOf<T>());
|
var elemSize = SizeOf<T>();
|
||||||
|
_buffer = (T*)_handle->Realloc(_handle->Allocator, _buffer, (nuint)_count * elemSize, (nuint)newSize * elemSize, AlignOf<T>(), option);
|
||||||
_count = newSize;
|
_count = newSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -184,7 +184,7 @@ public unsafe struct UnsafeHashMap<TKey, TValue> : IUnsafeCollection<KeyValuePai
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public void TrimExcess() => _hashMap.TrimExcess();
|
public void TrimExcess() => _hashMap.TrimExcess();
|
||||||
|
|
||||||
public void Resize(int newSize)
|
public void Resize(int newSize, AllocationOption option = AllocationOption.None)
|
||||||
{
|
{
|
||||||
_hashMap.Resize(newSize);
|
_hashMap.Resize(newSize);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -114,7 +114,7 @@ public unsafe struct UnsafeHashSet<T> : IUnsafeCollection<T>, IEnumerable<T>
|
|||||||
return _hashMap.GetKeyArray(allocator);
|
return _hashMap.GetKeyArray(allocator);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Resize(int newSize)
|
public void Resize(int newSize, AllocationOption option = AllocationOption.None)
|
||||||
{
|
{
|
||||||
_hashMap.Resize(newSize);
|
_hashMap.Resize(newSize);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -362,9 +362,9 @@ public unsafe struct UnsafeList<T> : IUnsafeCollection<T>
|
|||||||
RemoveRangeSwapBack(index, 1);
|
RemoveRangeSwapBack(index, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Resize(int newSize)
|
public void Resize(int newSize, AllocationOption option = AllocationOption.None)
|
||||||
{
|
{
|
||||||
_array.Resize(newSize);
|
_array.Resize(newSize, option);
|
||||||
|
|
||||||
if (_count > newSize)
|
if (_count > newSize)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -170,9 +170,9 @@ public unsafe struct UnsafeQueue<T> : IUnsafeCollection<T>
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Resize(int newSize)
|
public void Resize(int newSize, AllocationOption option = AllocationOption.None)
|
||||||
{
|
{
|
||||||
_array.Resize(newSize);
|
_array.Resize(newSize, option);
|
||||||
|
|
||||||
if (_count > newSize)
|
if (_count > newSize)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -276,10 +276,10 @@ public unsafe struct UnsafeSlotMap<T> : IUnsafeCollection<T>
|
|||||||
return ref slot.value;
|
return ref slot.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Resize(int newSize)
|
public void Resize(int newSize, AllocationOption option = AllocationOption.None)
|
||||||
{
|
{
|
||||||
_data.Resize(newSize);
|
_data.Resize(newSize, option);
|
||||||
_freeSlots.Resize(newSize);
|
_freeSlots.Resize(newSize, option);
|
||||||
|
|
||||||
_capacity = newSize;
|
_capacity = newSize;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,12 +17,6 @@ namespace Misaki.HighPerformance.LowLevel.Collections;
|
|||||||
public unsafe struct UnsafeSparseSet<T> : IUnsafeCollection<T>
|
public unsafe struct UnsafeSparseSet<T> : IUnsafeCollection<T>
|
||||||
where T : unmanaged
|
where T : unmanaged
|
||||||
{
|
{
|
||||||
private struct SlotEntry
|
|
||||||
{
|
|
||||||
public T value;
|
|
||||||
public int generation;
|
|
||||||
}
|
|
||||||
|
|
||||||
public struct Enumerator : IEnumerator<T>
|
public struct Enumerator : IEnumerator<T>
|
||||||
{
|
{
|
||||||
private readonly UnsafeSparseSet<T>* _collection;
|
private readonly UnsafeSparseSet<T>* _collection;
|
||||||
@@ -40,14 +34,10 @@ public unsafe struct UnsafeSparseSet<T> : IUnsafeCollection<T>
|
|||||||
public bool MoveNext()
|
public bool MoveNext()
|
||||||
{
|
{
|
||||||
_index++;
|
_index++;
|
||||||
if (_index < _collection->_sparse.Count)
|
if (_index < _collection->_count)
|
||||||
{
|
{
|
||||||
var index = _collection->_sparse[_index];
|
_value = _collection->_dense[_index];
|
||||||
if (index >= 0 && index < _collection->_count)
|
return true;
|
||||||
{
|
|
||||||
_value = _collection->_dense[index].value;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_value = default;
|
_value = default;
|
||||||
@@ -76,17 +66,19 @@ public unsafe struct UnsafeSparseSet<T> : IUnsafeCollection<T>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private UnsafeArray<SlotEntry> _dense;
|
private UnsafeArray<T> _dense;
|
||||||
|
private UnsafeArray<int> _generations;
|
||||||
private UnsafeArray<int> _sparse;
|
private UnsafeArray<int> _sparse;
|
||||||
private UnsafeArray<int> _reverse; // Maps dense index to sparse index
|
private UnsafeArray<int> _reverse; // Maps dense index to sparse index. Since this is a general purpose sparse set, we have to include reverse array. In real world ecs, this should be replaced with entity id array.
|
||||||
private UnsafeArray<int> _freeList; // Stack of available sparse indices
|
private UnsafeStack<int> _freeSparse;
|
||||||
|
|
||||||
private int _count;
|
private int _count;
|
||||||
private int _nextId; // Next available sparse index
|
private int _nextId; // Next available sparse index
|
||||||
private int _freeCount; // Number of items in the free list
|
private int _capacity;
|
||||||
|
|
||||||
public readonly int Count => _count;
|
public readonly int Count => _count;
|
||||||
public readonly int Capacity => _dense.Count;
|
public readonly int Capacity => _capacity;
|
||||||
public readonly bool IsCreated => _dense.IsCreated && _sparse.IsCreated && _reverse.IsCreated && _freeList.IsCreated;
|
public readonly bool IsCreated => _dense.IsCreated && _sparse.IsCreated && _reverse.IsCreated;
|
||||||
|
|
||||||
public IEnumerator<T> GetEnumerator() => new Enumerator((UnsafeSparseSet<T>*)UnsafeUtilities.AddressOf(ref this));
|
public IEnumerator<T> GetEnumerator() => new Enumerator((UnsafeSparseSet<T>*)UnsafeUtilities.AddressOf(ref this));
|
||||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||||
@@ -113,13 +105,15 @@ public unsafe struct UnsafeSparseSet<T> : IUnsafeCollection<T>
|
|||||||
throw new ArgumentOutOfRangeException(nameof(capacity), "Capacity must be greater than zero.");
|
throw new ArgumentOutOfRangeException(nameof(capacity), "Capacity must be greater than zero.");
|
||||||
}
|
}
|
||||||
|
|
||||||
_dense = new UnsafeArray<SlotEntry>(capacity, ref handle, allocationOption);
|
_dense = new UnsafeArray<T>(capacity, ref handle, allocationOption);
|
||||||
|
_generations = new UnsafeArray<int>(capacity, ref handle, allocationOption);
|
||||||
_sparse = new UnsafeArray<int>(capacity, ref handle, allocationOption);
|
_sparse = new UnsafeArray<int>(capacity, ref handle, allocationOption);
|
||||||
_reverse = new UnsafeArray<int>(capacity, ref handle, allocationOption);
|
_reverse = new UnsafeArray<int>(capacity, ref handle, allocationOption);
|
||||||
_freeList = new UnsafeArray<int>(capacity, ref handle, allocationOption);
|
_freeSparse = new UnsafeStack<int>(capacity, ref handle, allocationOption);
|
||||||
|
|
||||||
_count = 0;
|
_count = 0;
|
||||||
_nextId = 0;
|
_nextId = 0;
|
||||||
_freeCount = 0;
|
_capacity = capacity;
|
||||||
|
|
||||||
Clear();
|
Clear();
|
||||||
}
|
}
|
||||||
@@ -136,6 +130,12 @@ public unsafe struct UnsafeSparseSet<T> : IUnsafeCollection<T>
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private readonly ref T GetDenseReferenceUnchecked(int sparseIndex)
|
||||||
|
{
|
||||||
|
return ref _dense[_sparse[sparseIndex]];
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Adds a value to the sparse set and returns a unique sparse index for the value.
|
/// Adds a value to the sparse set and returns a unique sparse index for the value.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -144,15 +144,7 @@ public unsafe struct UnsafeSparseSet<T> : IUnsafeCollection<T>
|
|||||||
/// <returns>A unique sparse index that can be used to reference this value.</returns>
|
/// <returns>A unique sparse index that can be used to reference this value.</returns>
|
||||||
public int Add(T value, out int generation)
|
public int Add(T value, out int generation)
|
||||||
{
|
{
|
||||||
int sparseIndex;
|
if (!_freeSparse.TryPop(out var sparseIndex))
|
||||||
|
|
||||||
// Try to reuse a freed sparse index first
|
|
||||||
if (_freeCount > 0)
|
|
||||||
{
|
|
||||||
_freeCount--;
|
|
||||||
sparseIndex = _freeList[_freeCount];
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
{
|
||||||
// Use the next available ID
|
// Use the next available ID
|
||||||
sparseIndex = _nextId++;
|
sparseIndex = _nextId++;
|
||||||
@@ -167,21 +159,18 @@ public unsafe struct UnsafeSparseSet<T> : IUnsafeCollection<T>
|
|||||||
// Resize dense arrays if necessary
|
// Resize dense arrays if necessary
|
||||||
if (_count >= _dense.Count)
|
if (_count >= _dense.Count)
|
||||||
{
|
{
|
||||||
var newCapacity = _dense.Count + Math.Max(1, _dense.Count / 2);
|
var newCapacity = _dense.Count + (int)Math.Max(1, _dense.Count * 0.5f);
|
||||||
_dense.Resize(newCapacity);
|
Resize(newCapacity);
|
||||||
_reverse.Resize(newCapacity);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add the value to the dense array and update mappings
|
// Add the value to the dense array and update mappings
|
||||||
ref var entry = ref _dense[_count];
|
_dense[_count] = value;
|
||||||
entry.value = value;
|
|
||||||
|
|
||||||
_sparse[sparseIndex] = _count;
|
_sparse[sparseIndex] = _count;
|
||||||
_reverse[_count] = sparseIndex;
|
_reverse[_count] = sparseIndex;
|
||||||
_count++;
|
_count++;
|
||||||
|
|
||||||
generation = entry.generation;
|
generation = _generations[sparseIndex];
|
||||||
|
|
||||||
return sparseIndex;
|
return sparseIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -216,17 +205,9 @@ public unsafe struct UnsafeSparseSet<T> : IUnsafeCollection<T>
|
|||||||
|
|
||||||
// Mark the sparse index as unused and add to free list
|
// Mark the sparse index as unused and add to free list
|
||||||
_sparse[sparseIndex] = -1;
|
_sparse[sparseIndex] = -1;
|
||||||
_dense[lastIndex].generation++; // Increment generation to invalidate old references
|
_generations[sparseIndex]++; // Increment generation to invalidate old references
|
||||||
|
|
||||||
// Add the freed sparse index to the free list for reuse
|
|
||||||
if (_freeCount >= _freeList.Count)
|
|
||||||
{
|
|
||||||
_freeList.Resize(_freeList.Count + Math.Max(1, _freeList.Count / 2));
|
|
||||||
}
|
|
||||||
|
|
||||||
_freeList[_freeCount] = sparseIndex;
|
|
||||||
_freeCount++;
|
|
||||||
|
|
||||||
|
_freeSparse.Push(sparseIndex);
|
||||||
_count--;
|
_count--;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -247,7 +228,7 @@ public unsafe struct UnsafeSparseSet<T> : IUnsafeCollection<T>
|
|||||||
}
|
}
|
||||||
|
|
||||||
var denseIndex = _sparse[sparseIndex];
|
var denseIndex = _sparse[sparseIndex];
|
||||||
return denseIndex >= 0 && denseIndex < _count && _dense[denseIndex].generation == generation;
|
return denseIndex >= 0 && denseIndex < _count && _generations[denseIndex] == generation;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -261,10 +242,7 @@ public unsafe struct UnsafeSparseSet<T> : IUnsafeCollection<T>
|
|||||||
{
|
{
|
||||||
if (Contains(sparseIndex, generation))
|
if (Contains(sparseIndex, generation))
|
||||||
{
|
{
|
||||||
var denseIndex = _sparse[sparseIndex];
|
value = GetDenseReferenceUnchecked(sparseIndex);
|
||||||
ref var entry = ref _dense[denseIndex];
|
|
||||||
|
|
||||||
value = entry.value;
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -286,10 +264,7 @@ public unsafe struct UnsafeSparseSet<T> : IUnsafeCollection<T>
|
|||||||
throw new ArgumentOutOfRangeException(nameof(sparseIndex), "Sparse index and feneration not found in the set.");
|
throw new ArgumentOutOfRangeException(nameof(sparseIndex), "Sparse index and feneration not found in the set.");
|
||||||
}
|
}
|
||||||
|
|
||||||
var denseIndex = _sparse[sparseIndex];
|
return GetDenseReferenceUnchecked(sparseIndex);
|
||||||
ref var entry = ref _dense[denseIndex];
|
|
||||||
|
|
||||||
return entry.value;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -308,12 +283,8 @@ public unsafe struct UnsafeSparseSet<T> : IUnsafeCollection<T>
|
|||||||
return ref Unsafe.NullRef<T>();
|
return ref Unsafe.NullRef<T>();
|
||||||
}
|
}
|
||||||
|
|
||||||
var denseIndex = _sparse[sparseIndex];
|
|
||||||
ref var entry = ref _dense[denseIndex];
|
|
||||||
|
|
||||||
exist = true;
|
exist = true;
|
||||||
|
return ref GetDenseReferenceUnchecked(sparseIndex);
|
||||||
return ref entry.value;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -330,10 +301,7 @@ public unsafe struct UnsafeSparseSet<T> : IUnsafeCollection<T>
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
var denseIndex = _sparse[sparseIndex];
|
GetDenseReferenceUnchecked(sparseIndex) = value;
|
||||||
ref var entry = ref _dense[denseIndex];
|
|
||||||
entry.value = value;
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -354,32 +322,32 @@ public unsafe struct UnsafeSparseSet<T> : IUnsafeCollection<T>
|
|||||||
}
|
}
|
||||||
|
|
||||||
_sparse.AsSpan().Fill(-1);
|
_sparse.AsSpan().Fill(-1);
|
||||||
|
_generations.AsSpan().Clear();
|
||||||
|
|
||||||
_count = 0;
|
_count = 0;
|
||||||
_nextId = 0;
|
_nextId = 0;
|
||||||
_freeCount = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public void Resize(int newSize)
|
public void Resize(int newSize, AllocationOption option = AllocationOption.None)
|
||||||
{
|
{
|
||||||
if (newSize <= 0)
|
if (newSize <= 0)
|
||||||
{
|
{
|
||||||
throw new ArgumentOutOfRangeException(nameof(newSize), "New size must be greater than zero.");
|
throw new ArgumentOutOfRangeException(nameof(newSize), "New size must be greater than zero.");
|
||||||
}
|
}
|
||||||
|
|
||||||
_dense.Resize(newSize);
|
var oldSize = _capacity;
|
||||||
_reverse.Resize(newSize);
|
|
||||||
_freeList.Resize(newSize);
|
_dense.Resize(newSize, option);
|
||||||
|
_generations.Resize(newSize, option | AllocationOption.Clear);
|
||||||
|
_reverse.Resize(newSize, option);
|
||||||
|
|
||||||
if (newSize > _sparse.Count)
|
if (newSize > _sparse.Count)
|
||||||
{
|
{
|
||||||
ResizeSparse(newSize);
|
ResizeSparse(newSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_count > newSize)
|
_capacity = newSize;
|
||||||
{
|
|
||||||
_count = newSize;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
@@ -403,11 +371,12 @@ public unsafe struct UnsafeSparseSet<T> : IUnsafeCollection<T>
|
|||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
_dense.Dispose();
|
_dense.Dispose();
|
||||||
|
_generations.Dispose();
|
||||||
_sparse.Dispose();
|
_sparse.Dispose();
|
||||||
_reverse.Dispose();
|
_reverse.Dispose();
|
||||||
_freeList.Dispose();
|
_freeSparse.Dispose();
|
||||||
|
|
||||||
_count = 0;
|
_count = 0;
|
||||||
_nextId = 0;
|
_nextId = 0;
|
||||||
_freeCount = 0;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -126,9 +126,9 @@ public unsafe struct UnsafeStack<T> : IUnsafeCollection<T>
|
|||||||
return _array[_count - 1];
|
return _array[_count - 1];
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Resize(int newSize)
|
public void Resize(int newSize, AllocationOption option = AllocationOption.None)
|
||||||
{
|
{
|
||||||
_array.Resize(newSize);
|
_array.Resize(newSize, option);
|
||||||
|
|
||||||
if (_count > newSize)
|
if (_count > newSize)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -156,4 +156,34 @@ public static unsafe class UnsafeUtilities
|
|||||||
{
|
{
|
||||||
return new UnsafeArray<TOut>((TOut*)array.GetUnsafePtr(), array.Count * sizeof(TIn) / sizeof(TOut));
|
return new UnsafeArray<TOut>((TOut*)array.GetUnsafePtr(), array.Count * sizeof(TIn) / sizeof(TOut));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns a pointer to the first element of the specified span. This method enables direct, unsafe access to the underlying data of the span.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">The type of elements in the span. Must be an unmanaged type.</typeparam>
|
||||||
|
/// <param name="span">The span whose underlying data pointer is to be obtained.</param>
|
||||||
|
/// <returns>A pointer to the first element of the span. If the span is empty, the returned pointer is undefined and must not be dereferenced.</returns>
|
||||||
|
public static T* GetUnsafePtr<T>(this Span<T> span)
|
||||||
|
where T : unmanaged
|
||||||
|
{
|
||||||
|
fixed (T* ptr = span)
|
||||||
|
{
|
||||||
|
return ptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns a pointer to the first element of the specified span. This method enables direct, unsafe access to the underlying data of the span.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">The type of elements in the span. Must be an unmanaged type.</typeparam>
|
||||||
|
/// <param name="span">The span whose underlying data pointer is to be obtained.</param>
|
||||||
|
/// <returns>A pointer to the first element of the span. If the span is empty, the returned pointer is undefined and must not be dereferenced.</returns>
|
||||||
|
public static T* GetUnsafePtr<T>(this ReadOnlySpan<T> span)
|
||||||
|
where T : unmanaged
|
||||||
|
{
|
||||||
|
fixed (T* ptr = span)
|
||||||
|
{
|
||||||
|
return ptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -50,4 +50,93 @@ public class TestUnsafeSparseSet
|
|||||||
Assert.AreEqual(id, newId);
|
Assert.AreEqual(id, newId);
|
||||||
Assert.AreNotEqual(gen, newGen);
|
Assert.AreNotEqual(gen, newGen);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public unsafe void Resize()
|
||||||
|
{
|
||||||
|
const int count = 20;
|
||||||
|
|
||||||
|
var indices = stackalloc int[count];
|
||||||
|
var generations = stackalloc int[count];
|
||||||
|
|
||||||
|
for (var i = 0; i < count; i++)
|
||||||
|
{
|
||||||
|
indices[i] = _sparseSet.Add(i, out generations[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
Assert.AreEqual(count, _sparseSet.Count);
|
||||||
|
for (var i = 0; i < count; i++)
|
||||||
|
{
|
||||||
|
Assert.IsTrue(_sparseSet.Contains(indices[i], generations[i]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void Clear()
|
||||||
|
{
|
||||||
|
var id1 = _sparseSet.Add(10, out var gen1);
|
||||||
|
var id2 = _sparseSet.Add(20, out var gen2);
|
||||||
|
|
||||||
|
Assert.AreEqual(2, _sparseSet.Count);
|
||||||
|
|
||||||
|
_sparseSet.Clear();
|
||||||
|
|
||||||
|
Assert.AreEqual(0, _sparseSet.Count);
|
||||||
|
Assert.IsFalse(_sparseSet.Contains(id1, gen1));
|
||||||
|
Assert.IsFalse(_sparseSet.Contains(id2, gen2));
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public unsafe void Enumerate()
|
||||||
|
{
|
||||||
|
const int count = 3;
|
||||||
|
|
||||||
|
var values = stackalloc int[count] { 10, 20, 30 };
|
||||||
|
var ids = stackalloc int[count];
|
||||||
|
var gens = stackalloc int[count];
|
||||||
|
|
||||||
|
for (var i = 0; i < count; i++)
|
||||||
|
{
|
||||||
|
ids[i] = _sparseSet.Add(values[i], out gens[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
var index = 0;
|
||||||
|
foreach (var value in _sparseSet)
|
||||||
|
{
|
||||||
|
Assert.AreEqual(values[index], value);
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
|
||||||
|
Assert.AreEqual(count, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public unsafe void MemoryCompact()
|
||||||
|
{
|
||||||
|
const int count = 3;
|
||||||
|
|
||||||
|
var values = stackalloc int[count] { 10, 20, 30 };
|
||||||
|
var ids = stackalloc int[count];
|
||||||
|
var gens = stackalloc int[count];
|
||||||
|
|
||||||
|
for (var i = 0; i < count; i++)
|
||||||
|
{
|
||||||
|
ids[i] = _sparseSet.Add(values[i], out gens[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
_sparseSet.Remove(ids[1], gens[1]); // Remove the second element (20)
|
||||||
|
|
||||||
|
var ptr = (int*)_sparseSet.GetUnsafePtr();
|
||||||
|
|
||||||
|
Assert.AreEqual(2, _sparseSet.Count);
|
||||||
|
|
||||||
|
var index = 0;
|
||||||
|
foreach (var value in _sparseSet)
|
||||||
|
{
|
||||||
|
Assert.AreEqual(ptr[index], value);
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
|
||||||
|
Assert.AreEqual(2, index);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user