diff --git a/src/Debug/Attributes/DebugColorAttribute.cs b/src/Debug/Attributes/DebugColorAttribute.cs index 5ec68dd..fda3aa5 100644 --- a/src/Debug/Attributes/DebugColorAttribute.cs +++ b/src/Debug/Attributes/DebugColorAttribute.cs @@ -61,7 +61,6 @@ namespace DCFApixels.DragonECS /// color code Black. RGB is (0, 0, 0) public const int Black = 0; - [FieldOffset(0)] public readonly int colorCode; [FieldOffset(3)] public readonly byte r; [FieldOffset(2)] public readonly byte g; @@ -99,6 +98,5 @@ namespace DCFApixels.DragonECS { return new DebugColor((byte)(a.r / b), (byte)(a.g / b), (byte)(a.b / b)); } - //public static explicit operator DebugColor(int colorCode) => new DebugColor(colorCode); } } \ No newline at end of file diff --git a/src/Pools/EcsHybridPool.cs b/src/Pools/EcsHybridPool.cs new file mode 100644 index 0000000..2653295 --- /dev/null +++ b/src/Pools/EcsHybridPool.cs @@ -0,0 +1,223 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Runtime.CompilerServices; + +namespace DCFApixels.DragonECS +{ + /// Pool for IEcsComponent components + public sealed class EcsHybridPool : IEcsPoolImplementation, IEcsHybridPool, IEnumerable //IEnumerable - IntelliSense hack + where T : IEcsHybridComponent + { + private EcsWorld _source; + private int _componentID; + + private int[] _mapping;// index = entityID / value = itemIndex;/ value = 0 = no entityID + private T[] _items; //dense + private int _itemsCount; + private int[] _recycledItems; + private int _recycledItemsCount; + + private IEcsComponentReset _componentResetHandler = EcsComponentResetHandler.instance; + private IEcsComponentCopy _componentCopyHandler = EcsComponentCopyHandler.instance; + + private List _listeners = new List(); + + #region Properites + public int Count => _itemsCount; + public int Capacity => _items.Length; + public int ComponentID => _componentID; + public Type ComponentType => typeof(T); + public EcsWorld World => _source; + #endregion + + #region Init + void IEcsPoolImplementation.OnInit(EcsWorld world, int componentID) + { + _source = world; + _componentID = componentID; + + const int capacity = 512; + + _mapping = new int[world.Capacity]; + _recycledItems = new int[128]; + _recycledItemsCount = 0; + _items = new T[capacity]; + _itemsCount = 0; + } + #endregion + + #region Methods + public void Add(int entityID, T component) + { + ref int itemIndex = ref _mapping[entityID]; +#if (DEBUG && !DISABLE_DEBUG) || ENABLE_DRAGONECS_ASSERT_CHEKS + if (itemIndex > 0) EcsPoolThrowHalper.ThrowAlreadyHasComponent(entityID); +#endif + if (_recycledItemsCount > 0) + { + itemIndex = _recycledItems[--_recycledItemsCount]; + _itemsCount++; + } + else + { + itemIndex = ++_itemsCount; + if (itemIndex >= _items.Length) + Array.Resize(ref _items, _items.Length << 1); + } + this.IncrementEntityComponentCount(entityID); + _listeners.InvokeOnAdd(entityID); + _items[itemIndex] = component; + } + public void Set(int entityID, T component) + { + ref int itemIndex = ref _mapping[entityID]; + if(itemIndex <= 0) + {//null + if (_recycledItemsCount > 0) + { + itemIndex = _recycledItems[--_recycledItemsCount]; + _itemsCount++; + } + else + { + itemIndex = ++_itemsCount; + if (itemIndex >= _items.Length) + Array.Resize(ref _items, _items.Length << 1); + } + this.IncrementEntityComponentCount(entityID); + } + else + {//not null + _listeners.InvokeOnDel(entityID); + if (_items[itemIndex] is IDisposable disposable) + disposable.Dispose(); + } + _listeners.InvokeOnAdd(entityID); + _items[itemIndex] = component; + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public T Get(int entityID) + { +#if (DEBUG && !DISABLE_DEBUG) || ENABLE_DRAGONECS_ASSERT_CHEKS + if (!Has(entityID)) EcsPoolThrowHalper.ThrowNotHaveComponent(entityID); +#endif + _listeners.InvokeOnGet(entityID); + return _items[_mapping[entityID]]; + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ref readonly T Read(int entityID) + { +#if (DEBUG && !DISABLE_DEBUG) || ENABLE_DRAGONECS_ASSERT_CHEKS + if (!Has(entityID)) EcsPoolThrowHalper.ThrowNotHaveComponent(entityID); +#endif + return ref _items[_mapping[entityID]]; + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Has(int entityID) + { + return _mapping[entityID] > 0; + } + public void Del(int entityID) + { +#if (DEBUG && !DISABLE_DEBUG) || ENABLE_DRAGONECS_ASSERT_CHEKS + if (!Has(entityID)) EcsPoolThrowHalper.ThrowNotHaveComponent(entityID); +#endif + ref int itemIndex = ref _mapping[entityID]; + T component = _items[itemIndex]; + if (component is IDisposable disposable) + disposable.Dispose(); + if (_recycledItemsCount >= _recycledItems.Length) + Array.Resize(ref _recycledItems, _recycledItems.Length << 1); + _recycledItems[_recycledItemsCount++] = itemIndex; + _mapping[entityID] = 0; + _itemsCount--; + this.DecrementEntityComponentCount(entityID); + _listeners.InvokeOnDel(entityID); + } + public void TryDel(int entityID) + { + if (Has(entityID)) Del(entityID); + } + public void Copy(int fromEntityID, int toEntityID) + { +#if (DEBUG && !DISABLE_DEBUG) || ENABLE_DRAGONECS_ASSERT_CHEKS + if (!Has(fromEntityID)) EcsPoolThrowHalper.ThrowNotHaveComponent(fromEntityID); +#endif + Set(toEntityID, Get(fromEntityID)); + } + public void Copy(int fromEntityID, EcsWorld toWorld, int toEntityID) + { +#if (DEBUG && !DISABLE_DEBUG) || ENABLE_DRAGONECS_ASSERT_CHEKS + if (!Has(fromEntityID)) EcsPoolThrowHalper.ThrowNotHaveComponent(fromEntityID); +#endif + toWorld.GetPool().Set(toEntityID, Get(fromEntityID)); + } + #endregion + + #region Callbacks + void IEcsPoolImplementation.OnWorldResize(int newSize) + { + Array.Resize(ref _mapping, newSize); + } + void IEcsPoolImplementation.OnWorldDestroy() { } + void IEcsPoolImplementation.OnReleaseDelEntityBuffer(ReadOnlySpan buffer) + { + foreach (var entityID in buffer) + TryDel(entityID); + } + #endregion + + #region Other + void IEcsPool.AddRaw(int entityID, object dataRaw) => Add(entityID, (T)dataRaw); + object IEcsPool.GetRaw(int entityID) => Read(entityID); + void IEcsPool.SetRaw(int entityID, object dataRaw) => Set(entityID, (T)dataRaw); + #endregion + + #region Listeners + public void AddListener(IEcsPoolEventListener listener) + { + if (listener == null) { throw new ArgumentNullException("listener is null"); } + _listeners.Add(listener); + } + public void RemoveListener(IEcsPoolEventListener listener) + { + if (listener == null) { throw new ArgumentNullException("listener is null"); } + _listeners.Remove(listener); + } + #endregion + + #region IEnumerator - IntelliSense hack + IEnumerator IEnumerable.GetEnumerator() => throw new NotImplementedException(); + IEnumerator IEnumerable.GetEnumerator() => throw new NotImplementedException(); + #endregion + } + /// Hybrid component + public interface IEcsHybridComponent + { + bool IsAlive { get; } + entlong Entity { set; } + void OnAddToPool(); + void OnDelFromPool(); + } + public static class EcsHybridPoolExt + { + public static EcsHybridPool GetPool(this EcsWorld self) where T : IEcsHybridComponent + { + return self.GetPool>(); + } + + public static EcsHybridPool Include(this EcsAspectBuilderBase self) where T : IEcsHybridComponent + { + return self.Include>(); + } + public static EcsHybridPool Exclude(this EcsAspectBuilderBase self) where T : IEcsHybridComponent + { + return self.Exclude>(); + } + public static EcsHybridPool Optional(this EcsAspectBuilderBase self) where T : IEcsHybridComponent + { + return self.Optional>(); + } + } +} diff --git a/src/Pools/EcsPoolBase.cs b/src/Pools/EcsPoolBase.cs index 459c01f..bcd8b90 100644 --- a/src/Pools/EcsPoolBase.cs +++ b/src/Pools/EcsPoolBase.cs @@ -41,6 +41,11 @@ namespace DCFApixels.DragonECS T Add(int entityID); T Get(int entityID); } + public interface IEcsHybridPool : IEcsPool + { + void Add(int entityID, T component); + T Get(int entityID); + } /// Only used to implement a custom pool. In other contexts use IEcsPool or IEcsPool. public interface IEcsPoolImplementation : IEcsPool {