diff --git a/src/EcsAspect.cs b/src/EcsAspect.cs index a65d555..dc386e0 100644 --- a/src/EcsAspect.cs +++ b/src/EcsAspect.cs @@ -257,7 +257,7 @@ namespace DCFApixels.DragonECS } internal class DebuggerProxy { - public readonly Type worldType; + public readonly EcsWorld world; public readonly int worldID; public readonly int[] included; public readonly int[] excluded; @@ -265,11 +265,11 @@ namespace DCFApixels.DragonECS public readonly Type[] excludedTypes; public DebuggerProxy(EcsMask mask) { - worldType = WorldMetaStorage.GetWorldType(mask.worldID); + world = EcsWorld.GetWorld(mask.worldID); worldID = mask.worldID; included = mask.inc; excluded = mask.exc; - Type converter(int o) => WorldMetaStorage.GetComponentType(worldID, o); + Type converter(int o) => world.GetComponentType(o); includedTypes = included.Select(converter).ToArray(); excludedTypes = excluded.Select(converter).ToArray(); } diff --git a/src/EcsWorld.cache.cs b/src/EcsWorld.cache.cs index 2ba0f22..487bc86 100644 --- a/src/EcsWorld.cache.cs +++ b/src/EcsWorld.cache.cs @@ -19,23 +19,6 @@ namespace DCFApixels.DragonECS component = default; } } - private TPool CreatePool() where TPool : IEcsPoolImplementation, new() - { - int index = WorldMetaStorage.GetPoolID(_worldTypeID); - if (index >= _pools.Length) - { - int oldCapacity = _pools.Length; - Array.Resize(ref _pools, _pools.Length << 1); - ArrayUtility.Fill(_pools, _nullPool, oldCapacity, oldCapacity - _pools.Length); - } - if (_pools[index] == _nullPool) - { - var pool = new TPool(); - _pools[index] = pool; - pool.OnInit(this, index); - } - return (TPool)_pools[index]; - } internal readonly struct AspectCache : IEcsWorldComponent> where T : EcsAspect { diff --git a/src/EcsWorld.cs b/src/EcsWorld.cs index f36f4b1..c2bc05d 100644 --- a/src/EcsWorld.cs +++ b/src/EcsWorld.cs @@ -10,9 +10,6 @@ namespace DCFApixels.DragonECS { public readonly short id; - private Type _worldType; - private int _worldTypeID; - private bool _isDestroyed; private IntDispenser _entityDispenser; @@ -25,9 +22,6 @@ namespace DCFApixels.DragonECS private int[] _delEntBuffer; private int _delEntBufferCount; - internal IEcsPoolImplementation[] _pools; - private EcsNullPool _nullPool = EcsNullPool.instance; - private List> _groups = new List>(); private Stack _groupsPool = new Stack(64); @@ -36,7 +30,6 @@ namespace DCFApixels.DragonECS #region Properties public bool IsDestroyed => _isDestroyed; - public int WorldTypeID => _worldTypeID; public int Count => _entitiesCount; public int Capacity => _entitesCapacity; //_denseEntities.Length; public EcsReadonlyGroup Entities => _allEntites.Readonly; @@ -51,15 +44,12 @@ namespace DCFApixels.DragonECS if (isIndexable) { - id = (short)_worldIdDispenser.GetFree(); + id = (short)_worldIdDispenser.UseFree(); if (id >= Worlds.Length) Array.Resize(ref Worlds, Worlds.Length << 1); Worlds[id] = this; } - _worldType = this.GetType(); - _worldTypeID = WorldMetaStorage.GetWorldID(_worldType); - _entityDispenser = new IntDispenser(0); _pools = new IEcsPoolImplementation[512]; ArrayUtility.Fill(_pools, _nullPool); @@ -83,42 +73,15 @@ namespace DCFApixels.DragonECS ReleaseData(id); _worldIdDispenser.Release(id); _isDestroyed = true; + _poolIds = null; + _componentIds = null; } #endregion - #region ComponentInfo - public int GetComponentID() => WorldMetaStorage.GetComponentID(_worldTypeID); - public int GetComponentID(Type type) => WorldMetaStorage.GetComponentID(type, _worldTypeID); - public Type GetComponentType(int componentID) => WorldMetaStorage.GetComponentType(_worldTypeID, componentID); - public bool IsComponentTypeDeclared() => IsComponentTypeDeclared(typeof(T)); - public bool IsComponentTypeDeclared(Type type) => WorldMetaStorage.IsComponentTypeDeclared(_worldTypeID, type); - #endregion - #region Getters #if UNITY_2020_3_OR_NEWER [UnityEngine.Scripting.Preserve] #endif - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public TPool GetPool() where TPool : IEcsPoolImplementation, new() - { - return Get>().instance; - } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public TPool UncheckedGetPool() where TPool : IEcsPoolImplementation, new() - { - return UncheckedGet>().instance; - } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPool GetPool(int worldID) where TPool : IEcsPoolImplementation, new() - { - return Get>(worldID).instance; - } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPool UncheckedGetPool(int worldID) where TPool : IEcsPoolImplementation, new() - { - return UncheckedGet>(worldID).instance; - } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public TAspect GetAspect() where TAspect : EcsAspect { diff --git a/src/EcsWorld.pools.cs b/src/EcsWorld.pools.cs new file mode 100644 index 0000000..1c83dff --- /dev/null +++ b/src/EcsWorld.pools.cs @@ -0,0 +1,107 @@ +using DCFApixels.DragonECS.Internal; +using DCFApixels.DragonECS.Utils; +using System; +using System.Linq; +using System.Runtime.CompilerServices; + +namespace DCFApixels.DragonECS +{ + public abstract partial class EcsWorld + { + private SparseArray _poolIds = new SparseArray(); + private SparseArray _componentIds = new SparseArray(); + private int _poolsCount; + internal IEcsPoolImplementation[] _pools; + private EcsNullPool _nullPool = EcsNullPool.instance; + + #region ComponentInfo + public int GetComponentID() => DeclareComponentType(EcsTypeCode.Get()); + public int GetComponentID(Type type) => DeclareComponentType(EcsTypeCode.Get(type)); + public bool IsComponentTypeDeclared() => _componentIds.Contains(EcsTypeCode.Get()); + public bool IsComponentTypeDeclared(Type type) => _componentIds.Contains(EcsTypeCode.Get(type)); + public Type GetComponentType(int componentID) => _pools[componentID].ComponentType; + #endregion + + #region Getters + +#if UNITY_2020_3_OR_NEWER + [UnityEngine.Scripting.Preserve] +#endif + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public TPool GetPool() where TPool : IEcsPoolImplementation, new() + { + return Get>().instance; + } +#if UNITY_2020_3_OR_NEWER + [UnityEngine.Scripting.Preserve] +#endif + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public TPool UncheckedGetPool() where TPool : IEcsPoolImplementation, new() + { + return UncheckedGet>().instance; + } +#if UNITY_2020_3_OR_NEWER + [UnityEngine.Scripting.Preserve] +#endif + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPool GetPool(int worldID) where TPool : IEcsPoolImplementation, new() + { + return Get>(worldID).instance; + } +#if UNITY_2020_3_OR_NEWER + [UnityEngine.Scripting.Preserve] +#endif + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPool UncheckedGetPool(int worldID) where TPool : IEcsPoolImplementation, new() + { + return UncheckedGet>(worldID).instance; + } + #endregion + + #region Declare/Create + private int DeclareComponentType(int typeCode) + { + if (!_componentIds.TryGetValue(typeCode, out int componentId)) + { + componentId = _poolsCount++; + _componentIds.Add(typeCode, componentId); + } + return componentId; + } + private TPool CreatePool() where TPool : IEcsPoolImplementation, new() + { + if (_poolIds.Contains(EcsTypeCode.Get())) + throw new EcsFrameworkException("The pool has already been created."); + + Type componentType = typeof(TPool).GetInterfaces().First(o => o.IsGenericType && o.GetGenericTypeDefinition() == typeof(IEcsPoolImplementation<>)); + int componentTypeCode = EcsTypeCode.Get(componentType); + + if (_componentIds.TryGetValue(componentTypeCode, out int componentID)) + { + _poolIds[componentTypeCode] = componentID; + } + else + { + componentID = _poolsCount++; + _poolIds[componentTypeCode] = componentID; + _componentIds[componentTypeCode] = componentID; + } + + if (_poolsCount >= _pools.Length) + { + int oldCapacity = _pools.Length; + Array.Resize(ref _pools, _pools.Length << 1); + ArrayUtility.Fill(_pools, _nullPool, oldCapacity, oldCapacity - _pools.Length); + } + + if (_pools[componentID] == _nullPool) + { + var pool = new TPool(); + _pools[componentID] = pool; + pool.OnInit(this, componentID); + } + return (TPool)_pools[componentID]; + } + #endregion + } +} diff --git a/src/EcsWorld.static.cs b/src/EcsWorld.static.cs index 8237000..4b11c25 100644 --- a/src/EcsWorld.static.cs +++ b/src/EcsWorld.static.cs @@ -21,7 +21,7 @@ namespace DCFApixels.DragonECS private const int DEL_ENT_BUFFER_SIZE_OFFSET = 2; private static EcsWorld[] Worlds = new EcsWorld[4]; - private static IntDispenser _worldIdDispenser = new IntDispenser(0); + private static IdDispenser _worldIdDispenser = new IdDispenser(0); private static List _dataReleaseres = new List(); @@ -101,5 +101,8 @@ namespace DCFApixels.DragonECS } } } - internal sealed class EcsNullWorld : EcsWorld { } + internal sealed class EcsNullWorld : EcsWorld + { + internal EcsNullWorld() : base(false) { } + } } diff --git a/src/Utils/EcsTypeCode.cs b/src/Utils/EcsTypeCode.cs index b18b4af..f90ac67 100644 --- a/src/Utils/EcsTypeCode.cs +++ b/src/Utils/EcsTypeCode.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Runtime.CompilerServices; namespace DCFApixels.DragonECS { @@ -9,7 +10,8 @@ namespace DCFApixels.DragonECS { private static readonly Dictionary _codes = new Dictionary(); private static int _incremetn = 1; - public static int GetCode(Type type) + public static int Count => _codes.Count; + public static int Get(Type type) { if (!_codes.TryGetValue(type, out int code)) { @@ -18,10 +20,11 @@ namespace DCFApixels.DragonECS } return code; } - public static int Count => _codes.Count; - internal static class Cache + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int Get() => Cache.code; + private static class Cache { - public static readonly int code = GetCode(typeof(T)); + public static readonly int code = Get(typeof(T)); } } } diff --git a/src/Utils/IdDispenser.cs b/src/Utils/IdDispenser.cs index beaf822..433edbb 100644 --- a/src/Utils/IdDispenser.cs +++ b/src/Utils/IdDispenser.cs @@ -7,7 +7,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.Runtime.CompilerServices; -namespace DCFApixels +namespace DCFApixels.DragonECS.Utils { [Serializable] [DebuggerTypeProxy(typeof(DebuggerProxy))] diff --git a/src/Utils/SparseArray.cs b/src/Utils/SparseArray.cs new file mode 100644 index 0000000..9d83ae1 --- /dev/null +++ b/src/Utils/SparseArray.cs @@ -0,0 +1,228 @@ +//SparseArray. Analogous to Dictionary, but faster. +//Benchmark result of indexer.get speed test with 300 elements: +//[Dictinary: 5.786us] [SparseArray: 2.047us]. +using System; +using System.Diagnostics.Contracts; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace DCFApixels.DragonECS.Utils +{ + public class SparseArray + { + public const int MIN_CAPACITY_BITS_OFFSET = 4; + public const int MIN_CAPACITY = 1 << MIN_CAPACITY_BITS_OFFSET; + private const int EMPTY = -1; + + private int[] _buckets = Array.Empty(); + private Entry[] _entries = Array.Empty(); + + private int _count; + + private int _freeList; + private int _freeCount; + + private int _modBitMask; + + #region Properties + public TValue this[int keyX, int keyY] + { + get => _entries[FindEntry((keyX << 32) | keyY)].value; + set => Insert(keyX + (keyY << 32), value); + } + public TValue this[int key] + { + get => _entries[FindEntry(key)].value; + set => Insert(key, value); + } + public int Count => _count; + #endregion + + #region Constructors + public SparseArray(int minCapacity = MIN_CAPACITY) + { + minCapacity = NormalizeCapacity(minCapacity); + _buckets = new int[minCapacity]; + for (int i = 0; i < minCapacity; i++) + _buckets[i] = EMPTY; + _entries = new Entry[minCapacity]; + _modBitMask = (minCapacity - 1) & 0x7FFFFFFF; + } + #endregion + + #region Add/Contains/Remove + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Add(int keyX, int keyY, TValue value) => Add((keyX << 32) | keyY, value); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Add(int key, TValue value) + { +#if DEBUG + if (Contains(key)) + throw new ArgumentException("Contains(hashKey) is true"); +#endif + Insert(key, value); + } + + public bool Contains(int keyX, int keyY) => FindEntry((keyX << 32) | keyY) >= 0; + public bool Contains(int key) => FindEntry(key) >= 0; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Remove(int keyX, int keyY) => Remove((keyX << 32) | keyY); + public bool Remove(int key) + { + int bucket = key & _modBitMask; + int last = -1; + for (int i = _buckets[bucket]; i >= 0; last = i, i = _entries[i].next) + { + if (_entries[i].hashKey == key) + { + if (last < 0) + { + _buckets[bucket] = _entries[i].next; + } + else + { + _entries[last].next = _entries[i].next; + } + _entries[i].next = _freeList; + _entries[i].hashKey = -1; + _entries[i].value = default; + _freeList = i; + _freeCount++; + return true; + } + } + return false; + } + #endregion + + #region Find/Insert + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private int FindEntry(int key) + { + for (int i = _buckets[key & _modBitMask]; i >= 0; i = _entries[i].next) + if (_entries[i].hashKey == key) return i; + return -1; + } + private void Insert(int key, TValue value) + { + int targetBucket = key & _modBitMask; + + for (int i = _buckets[targetBucket]; i >= 0; i = _entries[i].next) + { + if (_entries[i].hashKey == key) + { + _entries[i].value = value; + return; + } + } + + int index; + if (_freeCount > 0) + { + index = _freeList; + _freeList = _entries[index].next; + _freeCount--; + } + else + { + if (_count == _entries.Length) + { + Resize(); + targetBucket = key & _modBitMask; + } + index = _count++; + } + + _entries[index].next = _buckets[targetBucket]; + _entries[index].hashKey = key; + _entries[index].value = value; + _buckets[targetBucket] = index; + } + #endregion + + #region TryGetValue + public bool TryGetValue(int keyX, int keyY, out TValue value) + { + int index = FindEntry((keyX << 32) | keyY); + if (index < 0) + { + value = default; + return false; + } + value = _entries[index].value; + return true; + } + public bool TryGetValue(int key, out TValue value) + { + int index = FindEntry(key); + if (index < 0) + { + value = default; + return false; + } + value = _entries[index].value; + return true; + } + #endregion + + #region Clear + public void Clear() + { + if (_count > 0) + { + for (int i = 0; i < _buckets.Length; i++) + { + _buckets[i] = -1; + } + Array.Clear(_entries, 0, _count); + _count = 0; + } + } + #endregion + + #region Resize + private void Resize() + { + int newSize = _buckets.Length << 1; + _modBitMask = (newSize - 1) & 0x7FFFFFFF; + + Contract.Assert(newSize >= _entries.Length); + int[] newBuckets = new int[newSize]; + for (int i = 0; i < newBuckets.Length; i++) + newBuckets[i] = EMPTY; + + Entry[] newEntries = new Entry[newSize]; + Array.Copy(_entries, 0, newEntries, 0, _count); + for (int i = 0; i < _count; i++) + { + if (newEntries[i].hashKey >= 0) + { + int bucket = newEntries[i].hashKey % newSize; + newEntries[i].next = newBuckets[bucket]; + newBuckets[bucket] = i; + } + } + _buckets = newBuckets; + _entries = newEntries; + } + + private int NormalizeCapacity(int capacity) + { + int result = MIN_CAPACITY; + while (result < capacity) result <<= 1; + return result; + } + #endregion + + #region Utils + [StructLayout(LayoutKind.Sequential, Pack = 4)] + private struct Entry + { + public int next; // Index of next entry, -1 if last + public int hashKey; + public TValue value; + } + #endregion + } +} \ No newline at end of file diff --git a/src/Utils/WorldMetaStorage.cs b/src/Utils/WorldMetaStorage.cs deleted file mode 100644 index 85dd1a2..0000000 --- a/src/Utils/WorldMetaStorage.cs +++ /dev/null @@ -1,184 +0,0 @@ -using DCFApixels.DragonECS.Utils; -using System; -using System.Collections.Generic; -using System.Runtime.CompilerServices; - -namespace DCFApixels.DragonECS -{ - //TODO этот класс требует переработки, изначально такая конструкция имела хорошую производительность, но сейчас он слишком раздулся - internal static class WorldMetaStorage - { - private static int _tokenCount = 0; - private static List _resizers = new List(); - private static WorldTypeMeta[] _metas = new WorldTypeMeta[0]; - private static Dictionary _worldIds = new Dictionary(); - private static class WorldIndex - { - public static int id = GetWorldID(typeof(TWorldArchetype)); - } - private static int GetToken(Type worldType) - { - WorldTypeMeta meta = new WorldTypeMeta(worldType); - meta.id = _tokenCount; - Array.Resize(ref _metas, ++_tokenCount); - _metas[_tokenCount - 1] = meta; - - foreach (var item in _resizers) - item.Resize(_tokenCount); - return _tokenCount - 1; - } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int GetWorldID(Type worldType) - { - if (!_worldIds.TryGetValue(worldType, out int id)) - { - id = GetToken(worldType); - _worldIds.Add(worldType, id); - } - return id; - } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Type GetWorldType(int worldTypeID) => _metas[worldTypeID].worldType; - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int GetWorldID() => WorldIndex.id; - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int GetComponentID(int worldID) => Component.Get(worldID); - public static int GetComponentID(Type type, int worldID) => _metas[worldID].GetComponentID(type); - public static bool IsComponentTypeDeclared(int worldID, Type type) => _metas[worldID].IsDeclaredComponentType(type); - public static Type GetComponentType(int worldID, int componentID) => _metas[worldID].GetComponentType(componentID); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int GetPoolID(int worldID) => Pool.Get(worldID); - - private abstract class ResizerBase - { - public abstract Type Type { get; } - public abstract int[] IDS { get; } - public abstract void Resize(int size); - } - - #region Containers - public static class PoolComponentIdArrays - { - private static Dictionary _componentTypeArrayPairs = new Dictionary(); - - public static int[] GetIdsArray(Type type) - { - int targetSize = _tokenCount; - if (!_componentTypeArrayPairs.TryGetValue(type, out int[] result)) - { - result = new int[targetSize]; - for (int i = 0; i < result.Length; i++) - result[i] = -1; - _componentTypeArrayPairs.Add(type, result); - } - else - { - if (result.Length < targetSize) - { - int oldSize = result.Length; - Array.Resize(ref result, targetSize); - ArrayUtility.Fill(result, -1, oldSize, targetSize); - _componentTypeArrayPairs[type] = result; - } - } - - return result; - } - - public static int GetComponentID(Type type, int token) - { - GetIdsArray(type); - ref int id = ref _componentTypeArrayPairs[type][token]; - if (id < 0) - id = _metas[token].DeclareComponentType(type); - return id; - } - } - private static class Pool - { - public static int[] ids; - private static Type componentType = typeof(T).GetGenericArguments()[0]; - static Pool() - { - ids = PoolComponentIdArrays.GetIdsArray(componentType); - _resizers.Add(new Resizer()); - } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int Get(int token) - { - ref int id = ref ids[token]; - if (id < 0) - { - id = PoolComponentIdArrays.GetComponentID(componentType, token); - } - return id; - } - private sealed class Resizer : ResizerBase - { - public override Type Type => typeof(T); - public override int[] IDS => ids; - public override void Resize(int size) - { - ids = PoolComponentIdArrays.GetIdsArray(componentType); - } - } - } - private static class Component - { - public static int[] ids; - static Component() - { - ids = PoolComponentIdArrays.GetIdsArray(typeof(T)); - _resizers.Add(new Resizer()); - } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int Get(int token) - { - ref int id = ref ids[token]; - if (id < 0) - { - id = PoolComponentIdArrays.GetComponentID(typeof(T), token); - } - return id; - } - private sealed class Resizer : ResizerBase - { - public override Type Type => typeof(T); - public override int[] IDS => ids; - public override void Resize(int size) - { - ids = PoolComponentIdArrays.GetIdsArray(typeof(T)); - } - } - } - #endregion - - private class WorldTypeMeta - { - public readonly Type worldType; - public int id; - public int componentCount; - public int worldComponentCount; - private Type[] _types = new Type[10]; - private Dictionary _declaredComponentTypes = new Dictionary(); - - public WorldTypeMeta(Type worldType) - { - this.worldType = worldType; - } - - public int DeclareComponentType(Type type) - { - int id = componentCount++; - if (_types.Length <= id) - Array.Resize(ref _types, id + 10); - _types[id] = type; - _declaredComponentTypes.Add(type, id); - return id; - } - public bool IsDeclaredComponentType(Type type) => _declaredComponentTypes.ContainsKey(type); - public Type GetComponentType(int componentID) => _types[componentID]; - public int GetComponentID(Type type) => PoolComponentIdArrays.GetComponentID(type, id); - } - } -} diff --git a/src/Utils/WorldMetaStorage.cs.meta b/src/Utils/WorldMetaStorage.cs.meta deleted file mode 100644 index ac629af..0000000 --- a/src/Utils/WorldMetaStorage.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 5e06cf4352ab9414293b145eb27daba7 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: