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:
@@ -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
|
||||
{
|
||||
@@ -38,7 +40,7 @@ public unsafe interface IUnsafeCollection<T> : IUnsafeCollection, IEnumerable<T>
|
||||
/// </summary>
|
||||
/// <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>
|
||||
public void Resize(int newSize);
|
||||
public void Resize(int newSize, AllocationOption option);
|
||||
}
|
||||
|
||||
public unsafe interface IUnTypedCollection : IUnsafeCollection
|
||||
|
||||
@@ -79,14 +79,14 @@ public unsafe struct UnTypedArray : IUnTypedCollection
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Resize(uint newSize)
|
||||
public void Resize(uint newSize, AllocationOption option = AllocationOption.None)
|
||||
{
|
||||
if (newSize == _size)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_buffer = _handle->Realloc(_handle->Allocator, _buffer, newSize, _alignment);
|
||||
_buffer = _handle->Realloc(_handle->Allocator, _buffer, _size, newSize, _alignment, option);
|
||||
_size = newSize;
|
||||
}
|
||||
|
||||
|
||||
@@ -168,14 +168,15 @@ public unsafe struct UnsafeArray<T> : IUnsafeCollection<T>
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Resize(int newSize)
|
||||
public void Resize(int newSize, AllocationOption option = AllocationOption.None)
|
||||
{
|
||||
if (newSize == _count)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@@ -184,7 +184,7 @@ public unsafe struct UnsafeHashMap<TKey, TValue> : IUnsafeCollection<KeyValuePai
|
||||
/// </summary>
|
||||
public void TrimExcess() => _hashMap.TrimExcess();
|
||||
|
||||
public void Resize(int newSize)
|
||||
public void Resize(int newSize, AllocationOption option = AllocationOption.None)
|
||||
{
|
||||
_hashMap.Resize(newSize);
|
||||
}
|
||||
|
||||
@@ -114,7 +114,7 @@ public unsafe struct UnsafeHashSet<T> : IUnsafeCollection<T>, IEnumerable<T>
|
||||
return _hashMap.GetKeyArray(allocator);
|
||||
}
|
||||
|
||||
public void Resize(int newSize)
|
||||
public void Resize(int newSize, AllocationOption option = AllocationOption.None)
|
||||
{
|
||||
_hashMap.Resize(newSize);
|
||||
}
|
||||
|
||||
@@ -362,9 +362,9 @@ public unsafe struct UnsafeList<T> : IUnsafeCollection<T>
|
||||
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)
|
||||
{
|
||||
|
||||
@@ -170,9 +170,9 @@ public unsafe struct UnsafeQueue<T> : IUnsafeCollection<T>
|
||||
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)
|
||||
{
|
||||
|
||||
@@ -276,10 +276,10 @@ public unsafe struct UnsafeSlotMap<T> : IUnsafeCollection<T>
|
||||
return ref slot.value;
|
||||
}
|
||||
|
||||
public void Resize(int newSize)
|
||||
public void Resize(int newSize, AllocationOption option = AllocationOption.None)
|
||||
{
|
||||
_data.Resize(newSize);
|
||||
_freeSlots.Resize(newSize);
|
||||
_data.Resize(newSize, option);
|
||||
_freeSlots.Resize(newSize, option);
|
||||
|
||||
_capacity = newSize;
|
||||
}
|
||||
|
||||
@@ -17,12 +17,6 @@ namespace Misaki.HighPerformance.LowLevel.Collections;
|
||||
public unsafe struct UnsafeSparseSet<T> : IUnsafeCollection<T>
|
||||
where T : unmanaged
|
||||
{
|
||||
private struct SlotEntry
|
||||
{
|
||||
public T value;
|
||||
public int generation;
|
||||
}
|
||||
|
||||
public struct Enumerator : IEnumerator<T>
|
||||
{
|
||||
private readonly UnsafeSparseSet<T>* _collection;
|
||||
@@ -40,14 +34,10 @@ public unsafe struct UnsafeSparseSet<T> : IUnsafeCollection<T>
|
||||
public bool MoveNext()
|
||||
{
|
||||
_index++;
|
||||
if (_index < _collection->_sparse.Count)
|
||||
if (_index < _collection->_count)
|
||||
{
|
||||
var index = _collection->_sparse[_index];
|
||||
if (index >= 0 && index < _collection->_count)
|
||||
{
|
||||
_value = _collection->_dense[index].value;
|
||||
return true;
|
||||
}
|
||||
_value = _collection->_dense[_index];
|
||||
return true;
|
||||
}
|
||||
|
||||
_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> _reverse; // Maps dense index to sparse index
|
||||
private UnsafeArray<int> _freeList; // Stack of available sparse indices
|
||||
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 UnsafeStack<int> _freeSparse;
|
||||
|
||||
private int _count;
|
||||
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 Capacity => _dense.Count;
|
||||
public readonly bool IsCreated => _dense.IsCreated && _sparse.IsCreated && _reverse.IsCreated && _freeList.IsCreated;
|
||||
public readonly int Capacity => _capacity;
|
||||
public readonly bool IsCreated => _dense.IsCreated && _sparse.IsCreated && _reverse.IsCreated;
|
||||
|
||||
public IEnumerator<T> GetEnumerator() => new Enumerator((UnsafeSparseSet<T>*)UnsafeUtilities.AddressOf(ref this));
|
||||
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.");
|
||||
}
|
||||
|
||||
_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);
|
||||
_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;
|
||||
_nextId = 0;
|
||||
_freeCount = 0;
|
||||
_capacity = capacity;
|
||||
|
||||
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>
|
||||
/// Adds a value to the sparse set and returns a unique sparse index for the value.
|
||||
/// </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>
|
||||
public int Add(T value, out int generation)
|
||||
{
|
||||
int sparseIndex;
|
||||
|
||||
// Try to reuse a freed sparse index first
|
||||
if (_freeCount > 0)
|
||||
{
|
||||
_freeCount--;
|
||||
sparseIndex = _freeList[_freeCount];
|
||||
}
|
||||
else
|
||||
if (!_freeSparse.TryPop(out var sparseIndex))
|
||||
{
|
||||
// Use the next available ID
|
||||
sparseIndex = _nextId++;
|
||||
@@ -167,21 +159,18 @@ public unsafe struct UnsafeSparseSet<T> : IUnsafeCollection<T>
|
||||
// Resize dense arrays if necessary
|
||||
if (_count >= _dense.Count)
|
||||
{
|
||||
var newCapacity = _dense.Count + Math.Max(1, _dense.Count / 2);
|
||||
_dense.Resize(newCapacity);
|
||||
_reverse.Resize(newCapacity);
|
||||
var newCapacity = _dense.Count + (int)Math.Max(1, _dense.Count * 0.5f);
|
||||
Resize(newCapacity);
|
||||
}
|
||||
|
||||
// Add the value to the dense array and update mappings
|
||||
ref var entry = ref _dense[_count];
|
||||
entry.value = value;
|
||||
_dense[_count] = value;
|
||||
|
||||
_sparse[sparseIndex] = _count;
|
||||
_reverse[_count] = sparseIndex;
|
||||
_count++;
|
||||
|
||||
generation = entry.generation;
|
||||
|
||||
generation = _generations[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
|
||||
_sparse[sparseIndex] = -1;
|
||||
_dense[lastIndex].generation++; // 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++;
|
||||
_generations[sparseIndex]++; // Increment generation to invalidate old references
|
||||
|
||||
_freeSparse.Push(sparseIndex);
|
||||
_count--;
|
||||
|
||||
return true;
|
||||
@@ -247,7 +228,7 @@ public unsafe struct UnsafeSparseSet<T> : IUnsafeCollection<T>
|
||||
}
|
||||
|
||||
var denseIndex = _sparse[sparseIndex];
|
||||
return denseIndex >= 0 && denseIndex < _count && _dense[denseIndex].generation == generation;
|
||||
return denseIndex >= 0 && denseIndex < _count && _generations[denseIndex] == generation;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -261,10 +242,7 @@ public unsafe struct UnsafeSparseSet<T> : IUnsafeCollection<T>
|
||||
{
|
||||
if (Contains(sparseIndex, generation))
|
||||
{
|
||||
var denseIndex = _sparse[sparseIndex];
|
||||
ref var entry = ref _dense[denseIndex];
|
||||
|
||||
value = entry.value;
|
||||
value = GetDenseReferenceUnchecked(sparseIndex);
|
||||
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.");
|
||||
}
|
||||
|
||||
var denseIndex = _sparse[sparseIndex];
|
||||
ref var entry = ref _dense[denseIndex];
|
||||
|
||||
return entry.value;
|
||||
return GetDenseReferenceUnchecked(sparseIndex);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -308,12 +283,8 @@ public unsafe struct UnsafeSparseSet<T> : IUnsafeCollection<T>
|
||||
return ref Unsafe.NullRef<T>();
|
||||
}
|
||||
|
||||
var denseIndex = _sparse[sparseIndex];
|
||||
ref var entry = ref _dense[denseIndex];
|
||||
|
||||
exist = true;
|
||||
|
||||
return ref entry.value;
|
||||
return ref GetDenseReferenceUnchecked(sparseIndex);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -330,10 +301,7 @@ public unsafe struct UnsafeSparseSet<T> : IUnsafeCollection<T>
|
||||
return false;
|
||||
}
|
||||
|
||||
var denseIndex = _sparse[sparseIndex];
|
||||
ref var entry = ref _dense[denseIndex];
|
||||
entry.value = value;
|
||||
|
||||
GetDenseReferenceUnchecked(sparseIndex) = value;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -354,32 +322,32 @@ public unsafe struct UnsafeSparseSet<T> : IUnsafeCollection<T>
|
||||
}
|
||||
|
||||
_sparse.AsSpan().Fill(-1);
|
||||
_generations.AsSpan().Clear();
|
||||
|
||||
_count = 0;
|
||||
_nextId = 0;
|
||||
_freeCount = 0;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Resize(int newSize)
|
||||
public void Resize(int newSize, AllocationOption option = AllocationOption.None)
|
||||
{
|
||||
if (newSize <= 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(newSize), "New size must be greater than zero.");
|
||||
}
|
||||
|
||||
_dense.Resize(newSize);
|
||||
_reverse.Resize(newSize);
|
||||
_freeList.Resize(newSize);
|
||||
var oldSize = _capacity;
|
||||
|
||||
_dense.Resize(newSize, option);
|
||||
_generations.Resize(newSize, option | AllocationOption.Clear);
|
||||
_reverse.Resize(newSize, option);
|
||||
|
||||
if (newSize > _sparse.Count)
|
||||
{
|
||||
ResizeSparse(newSize);
|
||||
}
|
||||
|
||||
if (_count > newSize)
|
||||
{
|
||||
_count = newSize;
|
||||
}
|
||||
_capacity = newSize;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
@@ -403,11 +371,12 @@ public unsafe struct UnsafeSparseSet<T> : IUnsafeCollection<T>
|
||||
public void Dispose()
|
||||
{
|
||||
_dense.Dispose();
|
||||
_generations.Dispose();
|
||||
_sparse.Dispose();
|
||||
_reverse.Dispose();
|
||||
_freeList.Dispose();
|
||||
_freeSparse.Dispose();
|
||||
|
||||
_count = 0;
|
||||
_nextId = 0;
|
||||
_freeCount = 0;
|
||||
}
|
||||
}
|
||||
@@ -126,9 +126,9 @@ public unsafe struct UnsafeStack<T> : IUnsafeCollection<T>
|
||||
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)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user