From 7a5aa6477d22c6a311b6cbd9e8e1061b7f2230ec Mon Sep 17 00:00:00 2001 From: Mikhail <99481254+DCFApixels@users.noreply.github.com> Date: Thu, 29 Jun 2023 00:56:26 +0800 Subject: [PATCH] update entity copying/EcsTypeCode Api/add IdDispenser/import module with inject --- src/Builtin/InjectSystem.cs | 5 +- src/EcsWorld.cache.cs | 3 +- src/EcsWorld.cs | 23 +- .../{EcsTypeCodeCache.cs => EcsTypeCode.cs} | 8 +- ...eCodeCache.cs.meta => EcsTypeCode.cs.meta} | 0 src/Utils/IdDispenser.cs | 415 ++++++++++++++++++ src/Utils/IdDispenser.cs.meta | 11 + 7 files changed, 456 insertions(+), 9 deletions(-) rename src/Utils/{EcsTypeCodeCache.cs => EcsTypeCode.cs} (81%) rename src/Utils/{EcsTypeCodeCache.cs.meta => EcsTypeCode.cs.meta} (100%) create mode 100644 src/Utils/IdDispenser.cs create mode 100644 src/Utils/IdDispenser.cs.meta diff --git a/src/Builtin/InjectSystem.cs b/src/Builtin/InjectSystem.cs index fdac12e..47b8b50 100644 --- a/src/Builtin/InjectSystem.cs +++ b/src/Builtin/InjectSystem.cs @@ -147,7 +147,10 @@ namespace DCFApixels.DragonECS public static EcsPipeline.Builder Inject(this EcsPipeline.Builder self, T data) { if (data == null) Throw.ArgumentNull(); - return self.Add(new InjectSystem(data)); + self.Add(new InjectSystem(data)); + if (data is IEcsModule module) + self.AddModule(module); + return self; } public static EcsPipeline.Builder Inject(this EcsPipeline.Builder self, A a, B b) { diff --git a/src/EcsWorld.cache.cs b/src/EcsWorld.cache.cs index 6862d0b..2ba0f22 100644 --- a/src/EcsWorld.cache.cs +++ b/src/EcsWorld.cache.cs @@ -1,10 +1,9 @@ using DCFApixels.DragonECS.Utils; using System; -using System.Reflection; namespace DCFApixels.DragonECS { - public partial class EcsWorld + public abstract partial class EcsWorld { internal readonly struct PoolCache : IEcsWorldComponent> where T : IEcsPoolImplementation, new() diff --git a/src/EcsWorld.cs b/src/EcsWorld.cs index 797abdb..f64521a 100644 --- a/src/EcsWorld.cs +++ b/src/EcsWorld.cs @@ -3,7 +3,6 @@ using DCFApixels.DragonECS.Utils; using System; using System.Collections.Generic; using System.Runtime.CompilerServices; -using typecode = System.Int32; namespace DCFApixels.DragonECS { @@ -14,6 +13,8 @@ namespace DCFApixels.DragonECS private Type _worldType; private int _worldTypeID; + private bool _isDestroyed; + private IntDispenser _entityDispenser; private int _entitiesCount; private int _entitesCapacity; @@ -34,6 +35,7 @@ namespace DCFApixels.DragonECS private List _entityListeners = new List(); #region Properties + public bool IsDestroyed => _isDestroyed; public int WorldTypeID => _worldTypeID; public int Count => _entitiesCount; public int Capacity => _entitesCapacity; //_denseEntities.Length; @@ -80,6 +82,7 @@ namespace DCFApixels.DragonECS Worlds[id] = null; ReleaseData(id); _worldIdDispenser.Release(id); + _isDestroyed = true; } #endregion @@ -242,7 +245,16 @@ namespace DCFApixels.DragonECS { foreach (var pool in _pools) { - if (pool.Has(fromEntityID)) pool.Copy(fromEntityID, toEntityID); + if (pool.Has(fromEntityID)) + pool.Copy(fromEntityID, toEntityID); + } + } + public void CopyEntity(int fromEntityID, EcsWorld toWorld, int toEntityID) + { + foreach (var pool in _pools) + { + if (pool.Has(fromEntityID)) + pool.Copy(fromEntityID, toWorld, toEntityID); } } public int CloneEntity(int fromEntityID) @@ -251,6 +263,12 @@ namespace DCFApixels.DragonECS CopyEntity(fromEntityID, newEntity); return newEntity; } + public int CloneEntity(int fromEntityID, EcsWorld toWorld) + { + int newEntity = NewEmptyEntity(); + CopyEntity(fromEntityID, toWorld, newEntity); + return newEntity; + } public void CloneEntity(int fromEntityID, int toEntityID) { CopyEntity(fromEntityID, toEntityID); @@ -260,6 +278,7 @@ namespace DCFApixels.DragonECS pool.Del(toEntityID); } } + //public void CloneEntity(int fromEntityID, EcsWorld toWorld, int toEntityID) #endregion #region Components Increment diff --git a/src/Utils/EcsTypeCodeCache.cs b/src/Utils/EcsTypeCode.cs similarity index 81% rename from src/Utils/EcsTypeCodeCache.cs rename to src/Utils/EcsTypeCode.cs index c60c241..77d26f2 100644 --- a/src/Utils/EcsTypeCodeCache.cs +++ b/src/Utils/EcsTypeCode.cs @@ -19,10 +19,10 @@ namespace DCFApixels.DragonECS return code; } public static int Count => _codes.Count; - } - internal static class EcsTypeCodeCache - { - public static readonly int code = EcsTypeCode.GetCode(typeof(T)); + internal static class Cache + { + public static readonly int code = EcsTypeCode.GetCode(typeof(T)); + } } } } diff --git a/src/Utils/EcsTypeCodeCache.cs.meta b/src/Utils/EcsTypeCode.cs.meta similarity index 100% rename from src/Utils/EcsTypeCodeCache.cs.meta rename to src/Utils/EcsTypeCode.cs.meta diff --git a/src/Utils/IdDispenser.cs b/src/Utils/IdDispenser.cs new file mode 100644 index 0000000..beaf822 --- /dev/null +++ b/src/Utils/IdDispenser.cs @@ -0,0 +1,415 @@ +// 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 System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using System.Runtime.CompilerServices; + +namespace DCFApixels +{ + [Serializable] + [DebuggerTypeProxy(typeof(DebuggerProxy))] + public class IdDispenser : IEnumerable, IReadOnlyCollection + { + private const int MIN_SIZE = 4; + + private int[] _dense = Array.Empty(); + private int[] _sparse = Array.Empty(); + private IDState[] _sparseState = Array.Empty(); + + private int _usedCount; //[ |uuuu| ] + private int _reservedCount; //[rrr| | ] + private int _size; //[rrr|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; + #endregion + + public IdDispenser(int capacity, int nullID = 0) + { + if (capacity % MIN_SIZE > 0) + capacity += MIN_SIZE; + Resize(capacity); + SetNullID(nullID); + + Reserved = new ReservedSpan(this); + Used = new UsedSpan(this); + } + + #region Use/Reserve/Release + /// Marks as used and returns next free id. + public int UseFree() + { + int count = _usedCount + _reservedCount; + CheckOrResize(count + 1); + int id = _dense[count]; + Add(id); + _sparseState[id] = IDState.Used; + 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) + { + CheckOrResize(id); +#if DEBUG + if (IsUsed(id) || IsReserved(id)) + { + if (IsUsed(id)) ThrowHalper.ThrowIsAlreadyInUse(id); + else ThrowHalper.ThrowIsHasBeenReserved(id); + } +#endif + if (IsFree(id)) + Add(id); + _sparseState[id] = IDState.Used; + } + 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) + { + CheckOrResize(id); +#if DEBUG + if (IsFree(id) || IsNullID(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; + } + 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;) + { + _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++; + } + SetNullID(_nullID); + } + #endregion + + #region Checks + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool IsFree(int id) => _sparseState[id] == IDState.Free; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool IsReserved(int id) => _sparseState[id] == IDState.Reserved; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool IsUsed(int id) => _sparseState[id] == IDState.Used; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool IsNullID(int id) => 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; + for (int i = 0; i < _size; i++) + { + switch (_sparseState[i]) + { + 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; + } + } + } + #endregion + + #region Other + private void SetNullID(int nullID) + { + _nullID = nullID; + if (nullID >= 0) + { + AddReserved(nullID); + _sparseState[nullID] = IDState.Reserved; + } + } + private bool IsValid() + { + for (int i = 0; i < _usedCount; i++) + { + if (_sparse[_dense[i]] != i || _dense[_sparse[i]] != i) + return false; + } + return true; + } + private void CheckOrResize(int id) + { + 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); + } + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void Add(int value) + { + Swap(value, _reservedCount + _usedCount++); + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void Remove(int value) + { + 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); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void Swap(int sparseIndex, int denseIndex) + { + int _dense_denseIndex_ = _dense[denseIndex]; + int _sparse_sparseIndex_ = _sparse[sparseIndex]; + _dense[denseIndex] = _dense[_sparse_sparseIndex_]; + _dense[_sparse_sparseIndex_] = _dense_denseIndex_; + _sparse[_dense_denseIndex_] = _sparse_sparseIndex_; + _sparse[sparseIndex] = denseIndex; + } + 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; + _dense[i] = i++; + } + _size = newSize; + Resized(newSize); + } + #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 struct Enumerator : IEnumerator + { + 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; + } + 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); + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + } + public readonly struct ReservedSpan : 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); + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + } + #endregion + + #region Utils + 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."); + } + + internal class DebuggerProxy + { + private IdDispenser _dispenser; + public DebuggerProxy(IdDispenser dispenser) => _dispenser = dispenser; +#if DEBUG + public IEnumerable Used => _dispenser.Used; + public IEnumerable Reserved => _dispenser.Reserved; + public Pair[] Pairs + { + get + { + Pair[] result = new Pair[_dispenser.Size]; + for (int i = 0; i < result.Length; i++) + result[i] = new Pair(_dispenser._dense[i], _dispenser._sparse[i]); + return result; + } + } + public ID[] All + { + get + { + ID[] result = new ID[_dispenser.Size]; + for (int i = 0; i < result.Length; i++) + { + int id = _dispenser._dense[i]; + result[i] = new ID(id, _dispenser._sparseState[id].ToString()); + } + return result; + } + } + public bool IsValid => _dispenser.IsValid(); + public int Count => _dispenser.ReservedCount; + public int Size => _dispenser.Size; + public int NullID => _dispenser._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}"; + } + internal readonly struct Pair + { + public readonly int dense; + public readonly int sparse; + public Pair(int dense, int sparse) { this.dense = dense; this.sparse = sparse; } + public override string ToString() => $"{dense} - {sparse}"; + } +#endif + } + #endregion + } +} \ No newline at end of file diff --git a/src/Utils/IdDispenser.cs.meta b/src/Utils/IdDispenser.cs.meta new file mode 100644 index 0000000..eda80df --- /dev/null +++ b/src/Utils/IdDispenser.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 54c009cfa7ae0fd49938525468703c8b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: