This commit is contained in:
Mikhail 2024-02-13 21:00:01 +08:00
parent c7b8fd8d57
commit f1f23181f5
3 changed files with 223 additions and 197 deletions

View File

@ -24,7 +24,10 @@ namespace DCFApixels.DragonECS
#endregion #endregion
#region Methods #region Methods
public bool IsMatches(int entityID) => _source.IsMatchesMask(_mask, entityID); public bool IsMatches(int entityID)
{
return _source.IsMatchesMask(_mask, entityID);
}
#endregion #endregion
#region Builder #region Builder

View File

@ -44,7 +44,22 @@ namespace DCFApixels.DragonECS.Internal
} }
public static int NormalizeSizeToPowerOfTwo(int minSize) public static int NormalizeSizeToPowerOfTwo(int minSize)
{ {
return 1 << (GetHighBitNumber((uint)minSize - 1u) + 1); unchecked
{
return 1 << (GetHighBitNumber((uint)minSize - 1u) + 1);
}
}
public static int NormalizeSizeToPowerOfTwo_ClampOverflow(int minSize)
{
unchecked
{
int hibit = (GetHighBitNumber((uint)minSize - 1u) + 1);
if (hibit >= 32)
{
return int.MaxValue;
}
return 1 << hibit;
}
} }
public static void Fill<T>(T[] array, T value, int startIndex = 0, int length = -1) public static void Fill<T>(T[] array, T value, int startIndex = 0, int length = -1)
{ {

View File

@ -1,6 +1,4 @@
// Sparse Set based ID dispenser, with the ability to reserve IDs. using DCFApixels.DragonECS.Internal;
// Warning! Release version omits error exceptions, incorrect use may lead to unstable state.
using System; using System;
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
@ -13,144 +11,129 @@ namespace DCFApixels.DragonECS.Utils
[DebuggerTypeProxy(typeof(DebuggerProxy))] [DebuggerTypeProxy(typeof(DebuggerProxy))]
public class IdDispenser : IEnumerable<int>, IReadOnlyCollection<int> public class IdDispenser : IEnumerable<int>, IReadOnlyCollection<int>
{ {
private const int FREE_FLAG_BIT = ~INDEX_MASK;
private const int INDEX_MASK = 0x7FFF_FFFF;
private const int MIN_SIZE = 4; private const int MIN_SIZE = 4;
private int[] _dense = Array.Empty<int>(); private int[] _dense = Array.Empty<int>();
private int[] _sparse = Array.Empty<int>(); private int[] _sparse = Array.Empty<int>(); //hibit free flag
private IDState[] _sparseState = Array.Empty<IDState>();
private int _usedCount; //[ |uuuu| ] private int _usedCount; //[|uuuu| ]
private int _reservedCount; //[rrr| | ] private int _size; //[|uuuu|ffffff]
private int _size; //[rrr|uuuu|ffffff]
private int _nullID; private int _nullID;
#region Properties #region Properties
/// <summary> Used Count </summary> /// <summary> Used Count </summary>
public int Count => _usedCount; public int Count
public int ReservedCount => _reservedCount; {
public int Size => _size; get { return _usedCount; }
public int NullID => _nullID; }
public int Size
{
get { return _size; }
}
public int NullID
{
get { return _nullID; }
}
#endregion #endregion
public IdDispenser(int capacity, int nullID = 0) #region Constructors
public IdDispenser(int minCapacity = MIN_SIZE, int nullID = 0, ResizedHandler resizedHandler = null)
{ {
if (capacity % MIN_SIZE > 0) Resized += resizedHandler;
capacity += MIN_SIZE; if (minCapacity < MIN_SIZE)
Resize(capacity); {
minCapacity = MIN_SIZE;
}
Resize(minCapacity);
SetNullID(nullID); SetNullID(nullID);
Reserved = new ReservedSpan(this);
Used = new UsedSpan(this);
} }
#endregion
#region Use/Reserve/Release #region Use/Reserve/Realese
/// <summary>Marks as used and returns next free id.</summary> /// <summary>Marks as used and returns next free id.</summary>
public int UseFree() public int UseFree() //+
{ {
int count = _usedCount + _reservedCount; int ptr = _usedCount;
CheckOrResize(count + 1); CheckOrResize(ptr);
int id = _dense[count]; int id = _dense[ptr];
Add(id); Move_FromFree_ToUsed(id);
_sparseState[id] = IDState.Used; SetFlag_Used(id);
return id; return id;
} }
public void UseFreeRange(ref int[] array, int range)
{
if (array.Length < range)
Array.Resize(ref array, range);
for (int i = 0; i < range; i++)
array[i] = UseFree();
}
public void UseFreeRange(List<int> list, int range)
{
for (int i = 0; i < range; i++)
list.Add(UseFree());
}
/// <summary>Marks as used a free or reserved id, after this id cannot be retrieved via UseFree.</summary> /// <summary>Marks as used a free or reserved id, after this id cannot be retrieved via UseFree.</summary>
public void Use(int id) public void Use(int id) //+
{ {
CheckOrResize(id); CheckOrResize(id);
#if DEBUG #if DEBUG
if (IsUsed(id) || IsReserved(id)) if (IsUsed(id) || IsNullID(id))
{ {
if (IsUsed(id)) ThrowHalper.ThrowIsAlreadyInUse(id); if (IsNullID(id)) { ThrowHalper.ThrowIsNullID(id); }
else ThrowHalper.ThrowIsHasBeenReserved(id); else { ThrowHalper.ThrowIsAlreadyInUse(id); }
} }
#endif #endif
if (IsFree(id)) Move_FromFree_ToUsed(id);
Add(id); SetFlag_Used(id);
_sparseState[id] = IDState.Used;
} }
public void UseRange(IEnumerable<int> ids)
{ public void Release(int id) //+
foreach (var item in ids)
Use(item);
}
/// <summary>Marks as reserved and returns next free id, after this id cannot be retrieved via UseFree.</summary>
public int ReserveFree()
{
int count = _usedCount + _reservedCount;
CheckOrResize(count + 1);
int id = _dense[count];
_sparseState[id] = IDState.Reserved;
AddReserved(id);
return id;
}
/// <summary>Marks as reserved a free id, after this id cannot be retrieved via UseFree.</summary>
public void Reserve(int id)
{
CheckOrResize(id);
#if DEBUG
if (!IsFree(id)) ThrowHalper.ThrowIsNotAvailable(id);
#endif
_sparseState[id] = IDState.Reserved;
AddReserved(id);
}
public void ReserveRange(IEnumerable<int> ids)
{
foreach (var item in ids)
Reserve(item);
}
public void Release(int id)
{ {
CheckOrResize(id); CheckOrResize(id);
#if DEBUG #if DEBUG
if (IsFree(id) || IsNullID(id)) if (IsFree(id) || IsNullID(id))
{ {
if (IsFree(id)) ThrowHalper.ThrowIsNotUsed(id); if (IsFree(id)) { ThrowHalper.ThrowIsNotUsed(id); }
else ThrowHalper.ThrowIsNullID(id); else { ThrowHalper.ThrowIsNullID(id); }
} }
#endif #endif
if (_sparseState[id] == IDState.Used) Move_FromUsed_ToFree(id);
Remove(id); SetFlag_Free(id);
else }
RemoveReserved(id); #endregion
_sparseState[id] = IDState.Free;
#region Range Methods
public void UseFreeRange(ref int[] array, int range)
{
if (array.Length < range)
{
Array.Resize(ref array, range);
}
for (int i = 0; i < range; i++)
{
array[i] = UseFree();
}
}
public void UseFreeRange(List<int> list, int range)
{
for (int i = 0; i < range; i++)
{
list.Add(UseFree());
}
}
public void UseRange(IEnumerable<int> ids)
{
foreach (var item in ids)
{
Use(item);
}
} }
public void ReleaseRange(IEnumerable<int> ids) public void ReleaseRange(IEnumerable<int> ids)
{ {
foreach (var item in ids) foreach (var item in ids)
{
Release(item); Release(item);
}
} }
public void ReleaseAll() public void ReleaseAll()
{ {
_usedCount = 0; _usedCount = 0;
_reservedCount = 0; for (int i = 0; i < _size; i++)
for (int i = 0; i < _size;)
{ {
_sparse[i] = i; _sparse[i] = SetFlag_Free_For(i);
_sparseState[i] = IDState.Free; _dense[i] = i;
_dense[i] = i++;
_sparse[i] = i;
_sparseState[i] = IDState.Free;
_dense[i] = i++;
_sparse[i] = i;
_sparseState[i] = IDState.Free;
_dense[i] = i++;
_sparse[i] = i;
_sparseState[i] = IDState.Free;
_dense[i] = i++;
} }
SetNullID(_nullID); SetNullID(_nullID);
} }
@ -158,52 +141,83 @@ namespace DCFApixels.DragonECS.Utils
#region Checks #region Checks
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool IsFree(int id) => _sparseState[id] == IDState.Free; public bool IsFree(int id)
{
return _sparse[id] < 0;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool IsReserved(int id) => _sparseState[id] == IDState.Reserved; public bool IsUsed(int id)
{
return _sparse[id] >= 0;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool IsUsed(int id) => _sparseState[id] == IDState.Used; public bool IsNullID(int id)
[MethodImpl(MethodImplOptions.AggressiveInlining)] {
public bool IsNullID(int id) => id == _nullID; return id == _nullID;
}
#endregion #endregion
#region Sort #region Sort
/// <summary>O(n) Sort. n = Size. Allows the UseFree method to return denser ids.</summary> /// <summary>O(n) Sort. n = Size. Allows the UseFree method to return denser ids.</summary>
public void Sort() public void Sort()
{ {
int usedInc = _reservedCount; int usedIndex = 0;
int reservedInc = 0; int freeIndex = _usedCount;
int freeInc = _reservedCount + _usedCount;
for (int i = 0; i < _size; i++) for (int i = 0; i < _size; i++)
{ {
switch (_sparseState[i]) if(_sparse[i] > 0)
{ {
case IDState.Free: _sparse[i] = usedIndex;
_sparse[i] = freeInc; _dense[usedIndex++] = i;
_dense[freeInc++] = i; }
break; else
case IDState.Reserved: {
_sparse[i] = reservedInc; _sparse[i] = SetFlag_Free_For(freeIndex);
_dense[reservedInc++] = i; _dense[freeIndex++] = i;
break;
case IDState.Used:
_sparse[i] = usedInc;
_dense[usedInc++] = i;
break;
} }
} }
} }
#endregion #endregion
#region Other #region UpSize
[MethodImpl(MethodImplOptions.NoInlining)]
public void UpSize(int minSize)
{
if (minSize > _size)
{
UpSize_Internal(minSize);
}
}
#endregion
#region Internal
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private int SetFlag_Used_For(int value)
{
return value & INDEX_MASK;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private int SetFlag_Free_For(int value)
{
return value | FREE_FLAG_BIT;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void SetFlag_Used(int id)
{
_sparse[id] &= INDEX_MASK;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void SetFlag_Free(int id)
{
_sparse[id] |= FREE_FLAG_BIT;
}
private void SetNullID(int nullID) private void SetNullID(int nullID)
{ {
_nullID = nullID; _nullID = nullID;
if (nullID >= 0) if (nullID >= 0)
{ {
AddReserved(nullID); Swap(nullID, _usedCount++);
_sparseState[nullID] = IDState.Reserved;
} }
} }
private bool IsValid() private bool IsValid()
@ -211,77 +225,53 @@ namespace DCFApixels.DragonECS.Utils
for (int i = 0; i < _usedCount; i++) for (int i = 0; i < _usedCount; i++)
{ {
if (_sparse[_dense[i]] != i || _dense[_sparse[i]] != i) if (_sparse[_dense[i]] != i || _dense[_sparse[i]] != i)
{
return false; return false;
}
} }
return true; return true;
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void CheckOrResize(int id) private void CheckOrResize(int id)
{ {
if (id > _size) if (id >= _size)
{ {
int leftBit = 0; UpSize_Internal(id + 1);
while (id != 0)
{
id >>= 1;
id &= int.MaxValue;
leftBit++;
}
if (leftBit >= 32)
Resize(int.MaxValue);
else
Resize(1 << leftBit);
} }
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private void Add(int value) private void Move_FromFree_ToUsed(int id)
{ {
Swap(value, _reservedCount + _usedCount++); Swap(id, _usedCount++);
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private void Remove(int value) private void Move_FromUsed_ToFree(int id)
{ {
Swap(value, _reservedCount + --_usedCount); Swap(id, --_usedCount);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void AddReserved(int value)
{
Swap(value, _reservedCount + _usedCount);
Swap(value, _reservedCount++);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void RemoveReserved(int value)
{
Swap(value, --_reservedCount);
Swap(value, _reservedCount + _usedCount);
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private void Swap(int sparseIndex, int denseIndex) private void Swap(int sparseIndex, int denseIndex)
{ {
int _dense_denseIndex_ = _dense[denseIndex]; int _dense_denseIndex_ = _dense[denseIndex];
int _sparse_sparseIndex_ = _sparse[sparseIndex]; int _sparse_sparseIndex_ = SetFlag_Used_For(_sparse[sparseIndex]);
_dense[denseIndex] = _dense[_sparse_sparseIndex_]; _dense[denseIndex] = _dense[_sparse_sparseIndex_];
_dense[_sparse_sparseIndex_] = _dense_denseIndex_; _dense[_sparse_sparseIndex_] = _dense_denseIndex_;
_sparse[_dense_denseIndex_] = _sparse_sparseIndex_; _sparse[_dense_denseIndex_] = _sparse_sparseIndex_;
_sparse[sparseIndex] = denseIndex; _sparse[sparseIndex] = denseIndex;
} }
[MethodImpl(MethodImplOptions.NoInlining)]
private void UpSize_Internal(int minSize)
{
Resize(ArrayUtility.NormalizeSizeToPowerOfTwo_ClampOverflow(minSize));
}
private void Resize(int newSize) private void Resize(int newSize)
{ {
if (newSize < MIN_SIZE)
newSize = MIN_SIZE;
Array.Resize(ref _dense, newSize); Array.Resize(ref _dense, newSize);
Array.Resize(ref _sparse, newSize); Array.Resize(ref _sparse, newSize);
Array.Resize(ref _sparseState, newSize);
for (int i = _size; i < newSize;) for (int i = _size; i < newSize;)
{ {
_sparse[i] = i; _sparse[i] = SetFlag_Free_For(i);
_dense[i] = i++;
_sparse[i] = i;
_dense[i] = i++;
_sparse[i] = i;
_dense[i] = i++;
_sparse[i] = i;
_dense[i] = i++; _dense[i] = i++;
} }
_size = newSize; _size = newSize;
@ -289,22 +279,13 @@ namespace DCFApixels.DragonECS.Utils
} }
#endregion #endregion
public delegate void ResizedHandler(int newSize);
public event ResizedHandler Resized = delegate { };
internal enum IDState : byte
{
Free,
Reserved,
Used,
}
#region Enumerable #region Enumerable
public UsedSpan Used; public Enumerator GetEnumerator()
public ReservedSpan Reserved; {
public Enumerator GetEnumerator() => new Enumerator(_dense, _reservedCount, _usedCount); return new Enumerator(_dense, 0, _usedCount);
IEnumerator<int> IEnumerable<int>.GetEnumerator() => GetEnumerator(); }
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); IEnumerator<int> IEnumerable<int>.GetEnumerator() { return GetEnumerator(); }
IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); }
public struct Enumerator : IEnumerator<int> public struct Enumerator : IEnumerator<int>
{ {
private readonly int[] _dense; private readonly int[] _dense;
@ -322,27 +303,44 @@ namespace DCFApixels.DragonECS.Utils
public void Dispose() { } public void Dispose() { }
public void Reset() => _index = -1; public void Reset() => _index = -1;
} }
#endregion
#region UsedToEcsSpan
public EcsSpan UsedToEcsSpan(int worldID)
{
return new EcsSpan(worldID, _dense, 1, _usedCount - 1);
}
#endregion
#region Spans
public readonly struct UsedSpan : IEnumerable<int> public readonly struct UsedSpan : IEnumerable<int>
{ {
private readonly IdDispenser _instance; private readonly IdDispenser _instance;
public int Count => _instance._usedCount; public int Count => _instance._usedCount;
internal UsedSpan(IdDispenser instance) => _instance = instance; internal UsedSpan(IdDispenser instance) => _instance = instance;
public Enumerator GetEnumerator() => new Enumerator(_instance._dense, _instance._reservedCount, _instance._usedCount); public Enumerator GetEnumerator() => new Enumerator(_instance._dense, 0, _instance._usedCount);
IEnumerator<int> IEnumerable<int>.GetEnumerator() => GetEnumerator(); IEnumerator<int> IEnumerable<int>.GetEnumerator() => GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
} }
public readonly struct ReservedSpan : IEnumerable<int> public readonly struct FreeSpan : IEnumerable<int>
{ {
private readonly IdDispenser _instance; private readonly IdDispenser _instance;
public int Count => _instance._reservedCount; public int Count => _instance._size - _instance._usedCount;
internal ReservedSpan(IdDispenser instance) => _instance = instance; internal FreeSpan(IdDispenser instance) => _instance = instance;
public Enumerator GetEnumerator() => new Enumerator(_instance._dense, 0, _instance._reservedCount); public Enumerator GetEnumerator() => new Enumerator(_instance._dense, _instance._usedCount, Count);
IEnumerator<int> IEnumerable<int>.GetEnumerator() => GetEnumerator(); IEnumerator<int> IEnumerable<int>.GetEnumerator() => GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
} }
#endregion #endregion
#region Utils #region Utils
private enum IDState : byte
{
Free = 0,
Reserved = 1,
Used = 2,
}
private static class ThrowHalper private static class ThrowHalper
{ {
[MethodImpl(MethodImplOptions.NoInlining)] [MethodImpl(MethodImplOptions.NoInlining)]
@ -358,22 +356,27 @@ namespace DCFApixels.DragonECS.Utils
[MethodImpl(MethodImplOptions.NoInlining)] [MethodImpl(MethodImplOptions.NoInlining)]
public static void ThrowIsNullID(int id) => throw new ArgumentException($"Id {id} cannot be released because it is used as a null id."); public static void ThrowIsNullID(int id) => throw new ArgumentException($"Id {id} cannot be released because it is used as a null id.");
[MethodImpl(MethodImplOptions.NoInlining)]
public static void UndefinedException() { throw new Exception(); }
} }
internal class DebuggerProxy internal class DebuggerProxy
{ {
private IdDispenser _dispenser; private IdDispenser _target;
public DebuggerProxy(IdDispenser dispenser) => _dispenser = dispenser; public DebuggerProxy(IdDispenser dispenser)
{
_target = dispenser;
}
#if DEBUG #if DEBUG
public IEnumerable<int> Used => _dispenser.Used; public ReadOnlySpan<int> Used => new ReadOnlySpan<int>(_target._dense, 0, _target._usedCount);
public IEnumerable<int> Reserved => _dispenser.Reserved;
public Pair[] Pairs public Pair[] Pairs
{ {
get get
{ {
Pair[] result = new Pair[_dispenser.Size]; Pair[] result = new Pair[_target.Size];
for (int i = 0; i < result.Length; i++) for (int i = 0; i < result.Length; i++)
result[i] = new Pair(_dispenser._dense[i], _dispenser._sparse[i]); result[i] = new Pair(_target._dense[i], _target._sparse[i]);
return result; return result;
} }
} }
@ -381,19 +384,19 @@ namespace DCFApixels.DragonECS.Utils
{ {
get get
{ {
ID[] result = new ID[_dispenser.Size]; ID[] result = new ID[_target.Size];
for (int i = 0; i < result.Length; i++) for (int i = 0; i < result.Length; i++)
{ {
int id = _dispenser._dense[i]; int id = _target._dense[i];
result[i] = new ID(id, _dispenser._sparseState[id].ToString()); result[i] = new ID(id, _target.IsUsed(id) ? "Used" : "Free");
} }
return result; return result;
} }
} }
public bool IsValid => _dispenser.IsValid(); public bool IsValid => _target.IsValid();
public int Count => _dispenser.ReservedCount; public int Count => _target.Count;
public int Size => _dispenser.Size; public int Size => _target.Size;
public int NullID => _dispenser._nullID; public int NullID => _target._nullID;
internal readonly struct ID internal readonly struct ID
{ {
public readonly int id; public readonly int id;
@ -411,5 +414,10 @@ namespace DCFApixels.DragonECS.Utils
#endif #endif
} }
#endregion #endregion
#region Events
public delegate void ResizedHandler(int newSize);
public event ResizedHandler Resized = delegate { };
#endregion
} }
} }