Refactor unsafe collections and improve memory handling
Some checks failed
Publish NuGet Packages / publish (pull_request) Has been cancelled

Refactored enumerators across multiple unsafe collections to use
`ref` returns for `Current`, improving performance and reducing
memory usage. Enhanced memory management with `AllocationOption`
support and optimized resizing logic for collections like
`UnsafeBitSet`, `UnsafeSlotMap`, and `UnsafeSparseSet`.

Updated `publish-nuget.yaml` to support manual workflow dispatch
and trigger on `push`/`pull_request` events. Incremented project
version to `1.1.2` and ensured NuGet package generation on build.
This commit is contained in:
2025-11-11 21:20:33 +09:00
parent bc8b2c0aaa
commit bf4dd5670e
12 changed files with 128 additions and 240 deletions

View File

@@ -20,44 +20,36 @@ public unsafe struct UnsafeSlotMap<T> : IUnsafeCollection<T>
private readonly UnsafeSlotMap<T>* _collection;
private int _currentIndex;
public readonly ref T Current => ref _collection->_data[_currentIndex];
readonly T IEnumerator<T>.Current => Current;
readonly object? IEnumerator.Current => Current;
public Enumerator(UnsafeSlotMap<T>* collection)
{
_collection = collection;
_currentIndex = -1;
}
public readonly T Current => _collection->_data[_currentIndex].value;
readonly object? IEnumerator.Current => Current;
public bool MoveNext()
{
while (++_currentIndex < _collection->_capacity)
{
if (_collection->_data[_currentIndex].isValid)
{
return true;
}
}
return false;
_currentIndex = _collection->_validBits.NextSetBit(_currentIndex + 1);
return _currentIndex != -1;
}
public void Reset() => _currentIndex = -1;
public void Reset()
{
_currentIndex = -1;
}
public void Dispose()
{
}
}
private struct SlotData
{
public T value;
public int generation;
public bool isValid;
}
private UnsafeArray<SlotData> _data;
private UnsafeArray<T> _data;
private UnsafeArray<int> _generations;
private UnsafeQueue<int> _freeSlots;
private UnsafeBitSet _validBits;
private int _count;
private int _capacity;
@@ -94,8 +86,12 @@ public unsafe struct UnsafeSlotMap<T> : IUnsafeCollection<T>
throw new ArgumentOutOfRangeException(nameof(capacity), "Capacity must be greater than zero.");
}
_data = new UnsafeArray<SlotData>(capacity, ref handle, allocationOption);
_data = new UnsafeArray<T>(capacity, ref handle, allocationOption);
_generations = new UnsafeArray<int>(capacity, ref handle, allocationOption);
_freeSlots = new UnsafeQueue<int>(capacity, ref handle, allocationOption);
_validBits = new UnsafeBitSet(GetBitSetCapacity(capacity), ref handle, allocationOption);
_validBits.ClearAll();
_count = 0;
_capacity = capacity;
@@ -113,6 +109,12 @@ public unsafe struct UnsafeSlotMap<T> : IUnsafeCollection<T>
{
}
private static int GetBitSetCapacity(int capacity)
{
// Each uint32 can hold 32 bits.
return (capacity + 31) / 32;
}
/// <summary>
/// Adds the specified item to the collection and returns the index of the slot where it was stored.
/// </summary>
@@ -123,27 +125,26 @@ public unsafe struct UnsafeSlotMap<T> : IUnsafeCollection<T>
{
if (_count >= _capacity)
{
Resize(_capacity * 2);
Resize((int)(_capacity * 1.5f));
}
int slotIndex;
int index;
if (_freeSlots.Count == 0)
{
slotIndex = _count;
index = _count;
}
else
{
slotIndex = _freeSlots.Dequeue();
index = _freeSlots.Dequeue();
}
ref var slot = ref _data[slotIndex];
slot.value = item;
slot.isValid = true;
generation = slot.generation;
_data[index] = item;
_validBits.SetBit(index);
_count++;
return slotIndex;
generation = _generations[index];
return index;
}
/// <summary>
@@ -160,16 +161,16 @@ public unsafe struct UnsafeSlotMap<T> : IUnsafeCollection<T>
return false;
}
ref var slot = ref _data[slotIndex];
if (slot.generation != generation)
ref var gen = ref _generations[slotIndex];
if (gen != generation)
{
return false;
}
slot.generation++;
slot.isValid = false;
gen++;
_validBits.ClearBit(slotIndex);
_freeSlots.Enqueue(slotIndex);
_count--;
return true;
@@ -188,9 +189,7 @@ public unsafe struct UnsafeSlotMap<T> : IUnsafeCollection<T>
return false;
}
ref var slot = ref _data[slotIndex];
if (slot.isValid && slot.generation == generation)
if (_validBits.IsSet(slotIndex) && _generations[slotIndex] == generation)
{
return true;
}
@@ -215,14 +214,13 @@ public unsafe struct UnsafeSlotMap<T> : IUnsafeCollection<T>
return false;
}
ref var slot = ref _data[slotIndex];
if (slot.generation != generation)
if (_generations[slotIndex] != generation)
{
value = default;
return false;
}
value = slot.value;
value = _data[slotIndex];
return true;
}
@@ -241,13 +239,12 @@ public unsafe struct UnsafeSlotMap<T> : IUnsafeCollection<T>
throw new ArgumentOutOfRangeException(nameof(slotIndex), "Slot index is out of range.");
}
ref var slot = ref _data[slotIndex];
if (!slot.isValid || slot.generation != generation)
if (!_validBits.IsSet(slotIndex)|| _generations[slotIndex] != generation)
{
throw new InvalidOperationException($"Slot {slotIndex} is not occupied.");
}
return slot.value;
return _data[slotIndex];
}
/// <summary>
@@ -268,28 +265,31 @@ public unsafe struct UnsafeSlotMap<T> : IUnsafeCollection<T>
ref var slot = ref _data[slotIndex];
if (!slot.isValid || slot.generation != generation)
if (!_validBits.IsSet(slotIndex) || _generations[slotIndex] != generation)
{
exist = false;
return ref Unsafe.NullRef<T>();
}
exist = true;
return ref slot.value;
return ref _data[slotIndex];
}
public void Resize(int newSize, AllocationOption option = AllocationOption.None)
{
_data.Resize(newSize, option);
_generations.Resize(newSize, option);
_freeSlots.Resize(newSize, option);
_validBits.Resize(GetBitSetCapacity(newSize), option);
_capacity = newSize;
}
public void Clear()
{
_data.Clear();
_generations.Clear();
_freeSlots.Clear();
_validBits.ClearAll();
_count = 0;
}