From f1f23181f55d879e61d8d501ffd4c41bef3f6c95 Mon Sep 17 00:00:00 2001 From: Mikhail <99481254+DCFApixels@users.noreply.github.com> Date: Tue, 13 Feb 2024 21:00:01 +0800 Subject: [PATCH] update --- src/EcsAspect.cs | 5 +- src/Utils/ArrayUtility.cs | 17 +- src/Utils/IdDispenser.cs | 398 +++++++++++++++++++------------------- 3 files changed, 223 insertions(+), 197 deletions(-) diff --git a/src/EcsAspect.cs b/src/EcsAspect.cs index 42632bc..9aa669d 100644 --- a/src/EcsAspect.cs +++ b/src/EcsAspect.cs @@ -24,7 +24,10 @@ namespace DCFApixels.DragonECS #endregion #region Methods - public bool IsMatches(int entityID) => _source.IsMatchesMask(_mask, entityID); + public bool IsMatches(int entityID) + { + return _source.IsMatchesMask(_mask, entityID); + } #endregion #region Builder diff --git a/src/Utils/ArrayUtility.cs b/src/Utils/ArrayUtility.cs index 52c7a70..9087337 100644 --- a/src/Utils/ArrayUtility.cs +++ b/src/Utils/ArrayUtility.cs @@ -44,7 +44,22 @@ namespace DCFApixels.DragonECS.Internal } 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[] array, T value, int startIndex = 0, int length = -1) { diff --git a/src/Utils/IdDispenser.cs b/src/Utils/IdDispenser.cs index 433edbb..5e0b476 100644 --- a/src/Utils/IdDispenser.cs +++ b/src/Utils/IdDispenser.cs @@ -1,6 +1,4 @@ -// Sparse Set based ID dispenser, with the ability to reserve IDs. -// Warning! Release version omits error exceptions, incorrect use may lead to unstable state. - +using DCFApixels.DragonECS.Internal; using System; using System.Collections; using System.Collections.Generic; @@ -13,144 +11,129 @@ namespace DCFApixels.DragonECS.Utils [DebuggerTypeProxy(typeof(DebuggerProxy))] public class IdDispenser : IEnumerable, IReadOnlyCollection { + private const int FREE_FLAG_BIT = ~INDEX_MASK; + private const int INDEX_MASK = 0x7FFF_FFFF; + private const int MIN_SIZE = 4; private int[] _dense = Array.Empty(); - private int[] _sparse = Array.Empty(); - private IDState[] _sparseState = Array.Empty(); + private int[] _sparse = Array.Empty(); //hibit free flag - private int _usedCount; //[ |uuuu| ] - private int _reservedCount; //[rrr| | ] - private int _size; //[rrr|uuuu|ffffff] + private int _usedCount; //[|uuuu| ] + private int _size; //[|uuuu|ffffff] private int _nullID; #region Properties /// Used Count - public int Count => _usedCount; - public int ReservedCount => _reservedCount; - public int Size => _size; - public int NullID => _nullID; + public int Count + { + get { return _usedCount; } + } + public int Size + { + get { return _size; } + } + public int NullID + { + get { return _nullID; } + } #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) - capacity += MIN_SIZE; - Resize(capacity); + Resized += resizedHandler; + if (minCapacity < MIN_SIZE) + { + minCapacity = MIN_SIZE; + } + Resize(minCapacity); SetNullID(nullID); - - Reserved = new ReservedSpan(this); - Used = new UsedSpan(this); } + #endregion - #region Use/Reserve/Release + #region Use/Reserve/Realese /// Marks as used and returns next free id. - public int UseFree() + public int UseFree() //+ { - int count = _usedCount + _reservedCount; - CheckOrResize(count + 1); - int id = _dense[count]; - Add(id); - _sparseState[id] = IDState.Used; + int ptr = _usedCount; + CheckOrResize(ptr); + int id = _dense[ptr]; + Move_FromFree_ToUsed(id); + SetFlag_Used(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 list, int range) - { - for (int i = 0; i < range; i++) - list.Add(UseFree()); - } /// Marks as used a free or reserved id, after this id cannot be retrieved via UseFree. - public void Use(int id) + public void Use(int id) //+ { CheckOrResize(id); #if DEBUG - if (IsUsed(id) || IsReserved(id)) + if (IsUsed(id) || IsNullID(id)) { - if (IsUsed(id)) ThrowHalper.ThrowIsAlreadyInUse(id); - else ThrowHalper.ThrowIsHasBeenReserved(id); + if (IsNullID(id)) { ThrowHalper.ThrowIsNullID(id); } + else { ThrowHalper.ThrowIsAlreadyInUse(id); } } #endif - if (IsFree(id)) - Add(id); - _sparseState[id] = IDState.Used; + Move_FromFree_ToUsed(id); + SetFlag_Used(id); } - public void UseRange(IEnumerable ids) - { - foreach (var item in ids) - Use(item); - } - /// Marks as reserved and returns next free id, after this id cannot be retrieved via UseFree. - public int ReserveFree() - { - int count = _usedCount + _reservedCount; - CheckOrResize(count + 1); - int id = _dense[count]; - _sparseState[id] = IDState.Reserved; - AddReserved(id); - return id; - } - /// Marks as reserved a free id, after this id cannot be retrieved via UseFree. - 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 ids) - { - foreach (var item in ids) - Reserve(item); - } - public void Release(int id) + + public void Release(int id) //+ { CheckOrResize(id); #if DEBUG if (IsFree(id) || IsNullID(id)) { - if (IsFree(id)) ThrowHalper.ThrowIsNotUsed(id); - else ThrowHalper.ThrowIsNullID(id); + if (IsFree(id)) { ThrowHalper.ThrowIsNotUsed(id); } + else { ThrowHalper.ThrowIsNullID(id); } } #endif - if (_sparseState[id] == IDState.Used) - Remove(id); - else - RemoveReserved(id); - _sparseState[id] = IDState.Free; + Move_FromUsed_ToFree(id); + SetFlag_Free(id); + } + #endregion + + #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 list, int range) + { + for (int i = 0; i < range; i++) + { + list.Add(UseFree()); + } + } + public void UseRange(IEnumerable ids) + { + foreach (var item in ids) + { + Use(item); + } } public void ReleaseRange(IEnumerable ids) { foreach (var item in ids) + { Release(item); + } } public void ReleaseAll() { _usedCount = 0; - _reservedCount = 0; - for (int i = 0; i < _size;) + for (int i = 0; i < _size; 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++; - _sparse[i] = i; - _sparseState[i] = IDState.Free; - _dense[i] = i++; + _sparse[i] = SetFlag_Free_For(i); + _dense[i] = i; } SetNullID(_nullID); } @@ -158,52 +141,83 @@ namespace DCFApixels.DragonECS.Utils #region Checks [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool IsFree(int id) => _sparseState[id] == IDState.Free; + public bool IsFree(int id) + { + return _sparse[id] < 0; + } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool IsReserved(int id) => _sparseState[id] == IDState.Reserved; + public bool IsUsed(int id) + { + return _sparse[id] >= 0; + } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool IsUsed(int id) => _sparseState[id] == IDState.Used; - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool IsNullID(int id) => id == _nullID; - + public bool IsNullID(int id) + { + return id == _nullID; + } #endregion #region Sort /// O(n) Sort. n = Size. Allows the UseFree method to return denser ids. public void Sort() { - int usedInc = _reservedCount; - int reservedInc = 0; - int freeInc = _reservedCount + _usedCount; + int usedIndex = 0; + int freeIndex = _usedCount; for (int i = 0; i < _size; i++) { - switch (_sparseState[i]) + if(_sparse[i] > 0) { - case IDState.Free: - _sparse[i] = freeInc; - _dense[freeInc++] = i; - break; - case IDState.Reserved: - _sparse[i] = reservedInc; - _dense[reservedInc++] = i; - break; - case IDState.Used: - _sparse[i] = usedInc; - _dense[usedInc++] = i; - break; + _sparse[i] = usedIndex; + _dense[usedIndex++] = i; + } + else + { + _sparse[i] = SetFlag_Free_For(freeIndex); + _dense[freeIndex++] = i; } } } #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) { _nullID = nullID; if (nullID >= 0) { - AddReserved(nullID); - _sparseState[nullID] = IDState.Reserved; + Swap(nullID, _usedCount++); } } private bool IsValid() @@ -211,77 +225,53 @@ namespace DCFApixels.DragonECS.Utils for (int i = 0; i < _usedCount; i++) { if (_sparse[_dense[i]] != i || _dense[_sparse[i]] != i) + { return false; + } } return true; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] private void CheckOrResize(int id) { - if (id > _size) + if (id >= _size) { - int leftBit = 0; - while (id != 0) - { - id >>= 1; - id &= int.MaxValue; - leftBit++; - } - if (leftBit >= 32) - Resize(int.MaxValue); - else - Resize(1 << leftBit); + UpSize_Internal(id + 1); } } [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)] - private void Remove(int value) + private void Move_FromUsed_ToFree(int id) { - Swap(value, _reservedCount + --_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); + Swap(id, --_usedCount); } [MethodImpl(MethodImplOptions.AggressiveInlining)] private void Swap(int sparseIndex, int 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[_sparse_sparseIndex_] = _dense_denseIndex_; _sparse[_dense_denseIndex_] = _sparse_sparseIndex_; _sparse[sparseIndex] = denseIndex; } + [MethodImpl(MethodImplOptions.NoInlining)] + private void UpSize_Internal(int minSize) + { + Resize(ArrayUtility.NormalizeSizeToPowerOfTwo_ClampOverflow(minSize)); + } private void Resize(int newSize) { - if (newSize < MIN_SIZE) - newSize = MIN_SIZE; Array.Resize(ref _dense, newSize); Array.Resize(ref _sparse, newSize); - Array.Resize(ref _sparseState, newSize); for (int i = _size; i < newSize;) { - _sparse[i] = i; - _dense[i] = i++; - _sparse[i] = i; - _dense[i] = i++; - _sparse[i] = i; - _dense[i] = i++; - _sparse[i] = i; + _sparse[i] = SetFlag_Free_For(i); _dense[i] = i++; } _size = newSize; @@ -289,22 +279,13 @@ namespace DCFApixels.DragonECS.Utils } #endregion - public delegate void ResizedHandler(int newSize); - public event ResizedHandler Resized = delegate { }; - - internal enum IDState : byte - { - Free, - Reserved, - Used, - } - #region Enumerable - public UsedSpan Used; - public ReservedSpan Reserved; - public Enumerator GetEnumerator() => new Enumerator(_dense, _reservedCount, _usedCount); - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + public Enumerator GetEnumerator() + { + return new Enumerator(_dense, 0, _usedCount); + } + IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } + IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } public struct Enumerator : IEnumerator { private readonly int[] _dense; @@ -322,27 +303,44 @@ namespace DCFApixels.DragonECS.Utils public void Dispose() { } 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 { private readonly IdDispenser _instance; public int Count => _instance._usedCount; 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 IEnumerable.GetEnumerator() => GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); } - public readonly struct ReservedSpan : IEnumerable + public readonly struct FreeSpan : IEnumerable { private readonly IdDispenser _instance; - public int Count => _instance._reservedCount; - internal ReservedSpan(IdDispenser instance) => _instance = instance; - public Enumerator GetEnumerator() => new Enumerator(_instance._dense, 0, _instance._reservedCount); + public int Count => _instance._size - _instance._usedCount; + internal FreeSpan(IdDispenser instance) => _instance = instance; + public Enumerator GetEnumerator() => new Enumerator(_instance._dense, _instance._usedCount, Count); IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); } #endregion #region Utils + private enum IDState : byte + { + Free = 0, + Reserved = 1, + Used = 2, + } + private static class ThrowHalper { [MethodImpl(MethodImplOptions.NoInlining)] @@ -358,22 +356,27 @@ namespace DCFApixels.DragonECS.Utils [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."); + + [MethodImpl(MethodImplOptions.NoInlining)] + public static void UndefinedException() { throw new Exception(); } } internal class DebuggerProxy { - private IdDispenser _dispenser; - public DebuggerProxy(IdDispenser dispenser) => _dispenser = dispenser; + private IdDispenser _target; + public DebuggerProxy(IdDispenser dispenser) + { + _target = dispenser; + } #if DEBUG - public IEnumerable Used => _dispenser.Used; - public IEnumerable Reserved => _dispenser.Reserved; + public ReadOnlySpan Used => new ReadOnlySpan(_target._dense, 0, _target._usedCount); public Pair[] Pairs { get { - Pair[] result = new Pair[_dispenser.Size]; + Pair[] result = new Pair[_target.Size]; 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; } } @@ -381,19 +384,19 @@ namespace DCFApixels.DragonECS.Utils { get { - ID[] result = new ID[_dispenser.Size]; + ID[] result = new ID[_target.Size]; for (int i = 0; i < result.Length; i++) { - int id = _dispenser._dense[i]; - result[i] = new ID(id, _dispenser._sparseState[id].ToString()); + int id = _target._dense[i]; + result[i] = new ID(id, _target.IsUsed(id) ? "Used" : "Free"); } return result; } } - public bool IsValid => _dispenser.IsValid(); - public int Count => _dispenser.ReservedCount; - public int Size => _dispenser.Size; - public int NullID => _dispenser._nullID; + 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; @@ -411,5 +414,10 @@ namespace DCFApixels.DragonECS.Utils #endif } #endregion + + #region Events + public delegate void ResizedHandler(int newSize); + public event ResizedHandler Resized = delegate { }; + #endregion } } \ No newline at end of file