DragonECS/src/Internal/IdDispenser.cs

388 lines
12 KiB
C#
Raw Normal View History

2024-02-14 03:04:05 +08:00
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.CompilerServices;
2024-02-14 03:04:05 +08:00
namespace DCFApixels.DragonECS.Internal
{
[Serializable]
[DebuggerTypeProxy(typeof(DebuggerProxy))]
public class IdDispenser : IEnumerable<int>, IReadOnlyCollection<int>
{
private const int MIN_SIZE = 4;
private int[] _dense = Array.Empty<int>();
2024-02-13 21:00:01 +08:00
private int[] _sparse = Array.Empty<int>(); //hibit free flag
private int _usedCount; //[uuuu| ]
private int _size; //[uuuu|ffffff]
private int _nullID;
#region Properties
/// <summary> Used Count </summary>
2024-02-13 21:00:01 +08:00
public int Count
{
get { return _usedCount; }
}
public int Size
{
get { return _size; }
}
public int NullID
{
get { return _nullID; }
}
#endregion
2024-02-13 21:00:01 +08:00
#region Constructors
public IdDispenser(int minCapacity = MIN_SIZE, int nullID = 0, ResizedHandler resizedHandler = null)
{
2024-02-13 21:00:01 +08:00
Resized += resizedHandler;
if (minCapacity < MIN_SIZE)
{
minCapacity = MIN_SIZE;
}
Resize(minCapacity);
SetNullID(nullID);
}
2024-02-13 21:00:01 +08:00
#endregion
2024-02-13 21:00:01 +08:00
#region Use/Reserve/Realese
/// <summary>Marks as used and returns next free id.</summary>
2024-02-15 20:28:38 +08:00
public int UseFree()
{
2024-02-13 21:00:01 +08:00
int ptr = _usedCount;
2024-02-15 20:28:38 +08:00
CheckIDOrUpsize(ptr);
2024-02-13 21:00:01 +08:00
int id = _dense[ptr];
Move_FromFree_ToUsed(id);
return id;
}
/// <summary>Marks as used a free or reserved id, after this id cannot be retrieved via UseFree.</summary>
2024-02-15 20:28:38 +08:00
public void Use(int id)
{
2024-02-15 20:28:38 +08:00
CheckIDOrUpsize(id);
#if DEBUG
2024-02-13 21:00:01 +08:00
if (IsUsed(id) || IsNullID(id))
{
2024-02-13 21:00:01 +08:00
if (IsNullID(id)) { ThrowHalper.ThrowIsNullID(id); }
else { ThrowHalper.ThrowIsAlreadyInUse(id); }
}
#endif
2024-02-13 21:00:01 +08:00
Move_FromFree_ToUsed(id);
}
2024-02-13 21:00:01 +08:00
2024-02-15 20:28:38 +08:00
public void Release(int id)
{
2024-02-15 20:28:38 +08:00
CheckIDOrUpsize(id);
#if DEBUG
2024-02-13 21:00:01 +08:00
if (IsFree(id) || IsNullID(id))
{
if (IsFree(id)) { ThrowHalper.ThrowIsNotUsed(id); }
else { ThrowHalper.ThrowIsNullID(id); }
}
#endif
2024-02-13 21:00:01 +08:00
Move_FromUsed_ToFree(id);
}
2024-02-13 21:00:01 +08:00
#endregion
#region Range Methods
public void UseFreeRange(ref int[] array, int range)
{
2024-02-13 21:00:01 +08:00
if (array.Length < range)
{
Array.Resize(ref array, range);
}
for (int i = 0; i < range; i++)
{
array[i] = UseFree();
}
}
2024-02-13 21:00:01 +08:00
public void UseFreeRange(List<int> list, int range)
{
2024-02-13 21:00:01 +08:00
for (int i = 0; i < range; i++)
{
2024-02-13 21:00:01 +08:00
list.Add(UseFree());
}
}
public void UseRange(IEnumerable<int> ids)
{
foreach (var item in ids)
{
Use(item);
}
}
public void ReleaseRange(IEnumerable<int> ids)
{
foreach (var item in ids)
2024-02-13 21:00:01 +08:00
{
Release(item);
2024-02-13 21:00:01 +08:00
}
}
public void ReleaseAll()
{
_usedCount = 0;
2024-02-13 21:00:01 +08:00
for (int i = 0; i < _size; i++)
{
2024-02-14 15:00:07 +08:00
_sparse[i] = i;
2024-02-13 21:00:01 +08:00
_dense[i] = i;
}
SetNullID(_nullID);
}
#endregion
#region Checks
[MethodImpl(MethodImplOptions.AggressiveInlining)]
2024-02-13 21:00:01 +08:00
public bool IsFree(int id)
{
2024-02-14 15:00:07 +08:00
return _sparse[id] >= _usedCount;
2024-02-13 21:00:01 +08:00
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
2024-02-13 21:00:01 +08:00
public bool IsUsed(int id)
{
2024-02-14 15:00:07 +08:00
return _sparse[id] < _usedCount;
2024-02-13 21:00:01 +08:00
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
2024-02-13 21:00:01 +08:00
public bool IsNullID(int id)
{
return id == _nullID;
}
#endregion
#region Sort
/// <summary>O(n) Sort. n = Size. Allows the UseFree method to return denser ids.</summary>
public void Sort()
{
2024-02-13 21:00:01 +08:00
int usedIndex = 0;
int freeIndex = _usedCount;
2024-02-14 17:05:41 +08:00
for (int i = 0; i < _size; i++)
{
2024-02-14 15:00:07 +08:00
if (_sparse[i] < _usedCount)
2024-02-13 21:00:01 +08:00
{
_sparse[i] = usedIndex;
_dense[usedIndex++] = i;
}
else
{
2024-02-14 15:00:07 +08:00
_sparse[i] = freeIndex;
2024-02-13 21:00:01 +08:00
_dense[freeIndex++] = i;
}
}
}
#endregion
2024-02-15 20:28:38 +08:00
#region Upsize
2024-02-13 21:00:01 +08:00
[MethodImpl(MethodImplOptions.NoInlining)]
2024-02-15 20:28:38 +08:00
public void Upsize(int minSize)
2024-02-13 21:00:01 +08:00
{
if (minSize > _size)
{
2024-02-15 20:28:38 +08:00
Upsize_Internal(minSize);
2024-02-13 21:00:01 +08:00
}
}
#endregion
#region Internal
private void SetNullID(int nullID)
{
_nullID = nullID;
if (nullID >= 0)
{
2024-02-15 20:28:38 +08:00
CheckIDOrUpsize(nullID);
2024-02-13 21:00:01 +08:00
Swap(nullID, _usedCount++);
}
}
2024-02-14 17:05:41 +08:00
internal bool IsValid()
{
for (int i = 0; i < _usedCount; i++)
{
if (_sparse[_dense[i]] != i || _dense[_sparse[i]] != i)
2024-02-13 21:00:01 +08:00
{
return false;
2024-02-13 21:00:01 +08:00
}
}
return true;
}
2024-02-13 21:00:01 +08:00
[MethodImpl(MethodImplOptions.AggressiveInlining)]
2024-02-15 20:28:38 +08:00
private void CheckIDOrUpsize(int id)
{
2024-02-13 21:00:01 +08:00
if (id >= _size)
{
2024-02-15 20:28:38 +08:00
Upsize_Internal(id + 1);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
2024-02-13 21:00:01 +08:00
private void Move_FromFree_ToUsed(int id)
{
2024-02-13 21:00:01 +08:00
Swap(id, _usedCount++);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
2024-02-13 21:00:01 +08:00
private void Move_FromUsed_ToFree(int id)
{
2024-02-13 21:00:01 +08:00
Swap(id, --_usedCount);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void Swap(int sparseIndex, int denseIndex)
{
int _dense_denseIndex_ = _dense[denseIndex];
2024-02-14 15:00:07 +08:00
int _sparse_sparseIndex_ = _sparse[sparseIndex];
_dense[denseIndex] = _dense[_sparse_sparseIndex_];
_dense[_sparse_sparseIndex_] = _dense_denseIndex_;
_sparse[_dense_denseIndex_] = _sparse_sparseIndex_;
_sparse[sparseIndex] = denseIndex;
}
2024-02-13 21:00:01 +08:00
[MethodImpl(MethodImplOptions.NoInlining)]
2024-02-15 20:28:38 +08:00
private void Upsize_Internal(int minSize)
2024-02-13 21:00:01 +08:00
{
Resize(ArrayUtility.NormalizeSizeToPowerOfTwo_ClampOverflow(minSize));
}
private void Resize(int newSize)
{
Array.Resize(ref _dense, newSize);
Array.Resize(ref _sparse, newSize);
for (int i = _size; i < newSize;)
{
2024-02-14 15:00:07 +08:00
_sparse[i] = i;
_dense[i] = i++;
}
_size = newSize;
Resized(newSize);
}
#endregion
2024-02-13 21:00:01 +08:00
#region Enumerable
public Enumerator GetEnumerator()
{
2024-02-13 21:00:01 +08:00
return new Enumerator(_dense, 0, _usedCount);
}
2024-02-13 21:00:01 +08:00
IEnumerator<int> IEnumerable<int>.GetEnumerator() { return GetEnumerator(); }
IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); }
public struct Enumerator : IEnumerator<int>
{
private readonly int[] _dense;
private readonly int _count;
private int _index;
public int Current => _dense[_index];
object IEnumerator.Current => Current;
public Enumerator(int[] dense, int startIndex, int count)
{
_dense = dense;
_count = startIndex + count;
_index = startIndex - 1;
}
public bool MoveNext() => ++_index < _count;
public void Dispose() { }
public void Reset() => _index = -1;
}
2024-02-13 21:00:01 +08:00
#endregion
#region UsedToEcsSpan
public EcsSpan UsedToEcsSpan(int worldID)
{
return new EcsSpan(worldID, _dense, 1, _usedCount - 1);
}
#endregion
#region Utils
2024-02-13 21:00:01 +08:00
private enum IDState : byte
{
Free = 0,
Reserved = 1,
Used = 2,
}
private static class ThrowHalper
{
[MethodImpl(MethodImplOptions.NoInlining)]
public static void ThrowIsAlreadyInUse(int id) => throw new ArgumentException($"Id {id} is already in use.");
[MethodImpl(MethodImplOptions.NoInlining)]
public static void ThrowIsHasBeenReserved(int id) => throw new ArgumentException($"Id {id} has been reserved.");
[MethodImpl(MethodImplOptions.NoInlining)]
public static void ThrowIsNotUsed(int id) => throw new ArgumentException($"Id {id} is not used.");
[MethodImpl(MethodImplOptions.NoInlining)]
public static void ThrowIsNotAvailable(int id) => throw new ArgumentException($"Id {id} is not available.");
[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.");
2024-02-13 21:00:01 +08:00
[MethodImpl(MethodImplOptions.NoInlining)]
public static void UndefinedException() { throw new Exception(); }
}
private class DebuggerProxy
{
2024-02-13 21:00:01 +08:00
private IdDispenser _target;
public DebuggerProxy(IdDispenser dispenser)
{
_target = dispenser;
}
#if DEBUG
2024-02-13 21:00:01 +08:00
public ReadOnlySpan<int> Used => new ReadOnlySpan<int>(_target._dense, 0, _target._usedCount);
public ReadOnlySpan<int> Free => new ReadOnlySpan<int>(_target._dense, _target._usedCount, _target._size - _target._usedCount);
public Pair[] Pairs
{
get
{
2024-02-13 21:00:01 +08:00
Pair[] result = new Pair[_target.Size];
for (int i = 0; i < result.Length; i++)
2024-02-14 17:05:41 +08:00
{
result[i] = new Pair(
_target._dense[i],
_target._sparse[i],
i < _target.Count);
}
return result;
}
}
public ID[] All
{
get
{
2024-02-13 21:00:01 +08:00
ID[] result = new ID[_target.Size];
for (int i = 0; i < result.Length; i++)
{
2024-02-13 21:00:01 +08:00
int id = _target._dense[i];
result[i] = new ID(id, _target.IsUsed(id) ? "Used" : "Free");
}
return result;
}
}
2024-02-13 21:00:01 +08:00
public bool IsValid => _target.IsValid();
public int Count => _target.Count;
public int Size => _target.Size;
public int NullID => _target._nullID;
internal readonly struct ID
{
public readonly int id;
public readonly string state;
public ID(int id, string state) { this.id = id; this.state = state; }
public override string ToString() => $"{id} - {state}";
}
2024-02-14 17:05:41 +08:00
[DebuggerDisplay("{Separator} -> {sparse} - {dense}")]
internal readonly struct Pair
{
public readonly int sparse;
2024-02-14 17:05:41 +08:00
public readonly int dense;
public readonly bool isSeparator;
public int Separator => isSeparator ? 1 : 0;
public Pair(int dense, int sparse, bool isSeparator)
{
this.dense = dense;
this.sparse = sparse;
this.isSeparator = isSeparator;
}
//public override string ToString() => $"{sparse} - {dense} { (isSeparator ? '>' : ' ') } ";
}
#endif
}
#endregion
2024-02-13 21:00:01 +08:00
#region Events
public delegate void ResizedHandler(int newSize);
public event ResizedHandler Resized = delegate { };
#endregion
}
}