diff --git a/DragonECS.asmdef b/DragonECS.asmdef new file mode 100644 index 0000000..9685236 --- /dev/null +++ b/DragonECS.asmdef @@ -0,0 +1,14 @@ +{ + "name": "DCFApixels.DragonECS", + "rootNamespace": "DCFApixels", + "references": [], + "includePlatforms": [], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [], + "versionDefines": [], + "noEngineReferences": false +} \ No newline at end of file diff --git a/DragonECS.asmdef.meta b/DragonECS.asmdef.meta new file mode 100644 index 0000000..ff30c2e --- /dev/null +++ b/DragonECS.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: abb125fa67fff1e45914d0825236f608 +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/src.meta b/src.meta new file mode 100644 index 0000000..f5ba3be --- /dev/null +++ b/src.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 0192fd952d3f4a24d8057af65f35d8e1 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/src/EcsEntityTable.cs b/src/EcsEntityTable.cs new file mode 100644 index 0000000..9fd6791 --- /dev/null +++ b/src/EcsEntityTable.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace DCFApixels.DragonECS +{ + public interface IEcsEntityTable + { + + } +} diff --git a/src/EcsEntityTable.cs.meta b/src/EcsEntityTable.cs.meta new file mode 100644 index 0000000..c3d7eaa --- /dev/null +++ b/src/EcsEntityTable.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 1d395d007b9c7c24c9f1f4c09f16e04c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/src/EcsEntityTableManager.cs b/src/EcsEntityTableManager.cs new file mode 100644 index 0000000..f223177 --- /dev/null +++ b/src/EcsEntityTableManager.cs @@ -0,0 +1,84 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace DCFApixels.DragonECS +{ + public class EcsEntityTableManager + { + private int _count; + private IEcsFieldPool[] _fieldPools; + + private int _idIncrement; + private Dictionary _ids; + + public EcsEntityTableManager(int capacity) + { + _fieldPools = new IEcsFieldPool[capacity]; + _ids = new Dictionary(capacity); + _count = 0; + } + + public EcsFieldPool GetFieldPool(int id) + { + if(id < _count) + return (EcsFieldPool)_fieldPools[id]; + + _count++; + if(_fieldPools.Length < _count) + { + Array.Resize(ref _fieldPools, _fieldPools.Length << 1); + } + EcsFieldPool newPool = new EcsFieldPool(7); + _fieldPools[id] = newPool; + return newPool; + } + + public void ResizeFieldPool(int id) + { + } + + + public int GetFieldID(string name, int index) + { + IDKey key = new IDKey(name, index); + if (_ids.TryGetValue(key, out int id)) + return id; + + id = _idIncrement++; + _ids.Add(key, id); + return id; + } + + private struct IDKey + { + public string name; + public int index; + + public IDKey(string name, int index) + { + this.name = name; + this.index = index; + } + + public override bool Equals(object obj) + { + return obj is IDKey key && + name == key.name && + index == key.index; + } + + public override int GetHashCode() + { + return HashCode.Combine(name, index); + } + + public override string ToString() + { + return name + "_" + index; + } + } + } +} diff --git a/src/EcsEntityTableManager.cs.meta b/src/EcsEntityTableManager.cs.meta new file mode 100644 index 0000000..5215d19 --- /dev/null +++ b/src/EcsEntityTableManager.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f568bc9f583414c4188526dfade20358 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/src/EcsField.cs b/src/EcsField.cs new file mode 100644 index 0000000..99b4423 --- /dev/null +++ b/src/EcsField.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace DCFApixels.DragonECS +{ + public struct EcsField + { + private EcsFieldPool _pool; + public ref T this[int index] + { + get => ref _pool[index]; + } + } +} diff --git a/src/EcsField.cs.meta b/src/EcsField.cs.meta new file mode 100644 index 0000000..b2b648f --- /dev/null +++ b/src/EcsField.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f2fa97ad86c494a40939307a2cfaca36 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/src/EcsFieldPool.cs b/src/EcsFieldPool.cs new file mode 100644 index 0000000..8cda7a8 --- /dev/null +++ b/src/EcsFieldPool.cs @@ -0,0 +1,51 @@ +using System.Collections; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using UnityEngine; + +namespace DCFApixels.DragonECS +{ + public interface IEcsFieldPool + { + public bool Has(int index); + public void Add(int index); + } + public class EcsFieldPool : IEcsFieldPool + { + private SparseSet _sparseSet; + private T[] _denseItems; + + public EcsFieldPool(int capacity) + { + _denseItems = new T[capacity]; + _sparseSet = new SparseSet(capacity); + } + + public ref T this[int index] + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => ref _denseItems[_sparseSet[index]]; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ref T Add(int index) + { + _sparseSet.Add(index); + _sparseSet.Normalize(ref _denseItems); + return ref _denseItems[_sparseSet.IndexOf(index)]; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Has(int index) + { + return _sparseSet.Contains(index); + } + + #region IEcsFieldPool + void IEcsFieldPool.Add(int index) + { + Add(index); + } + #endregion + } +} diff --git a/src/EcsFieldPool.cs.meta b/src/EcsFieldPool.cs.meta new file mode 100644 index 0000000..5bdc538 --- /dev/null +++ b/src/EcsFieldPool.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2b523e51b8d5f4c4c969e0ffec1b8b6f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/src/EcsSession.cs b/src/EcsSession.cs new file mode 100644 index 0000000..84cd3e7 --- /dev/null +++ b/src/EcsSession.cs @@ -0,0 +1,119 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace DCFApixels.DragonECS +{ + + public class EcsSession + { + private List _allSystems; + private ReadOnlyCollection _ecsSystemsSealed; + + private bool _isInit = false; + private bool _isDestoryed = false; + + private int _worldIdIncrement; + private Dictionary _worldsDict = new Dictionary(); + private List _worlds = new List(); + + private Dictionary _runners; + private Dictionary _messengers; + private EcsSystemsRunner<_Run> _runRunnerCache; + + #region Properties + public ReadOnlyCollection AllSystems => _ecsSystemsSealed; + + #endregion + + #region React Runners/Messengers + public EcsSystemsRunner GetRunner() + where TDoTag : IEcsDoTag + { + Type type = typeof(TDoTag); + if (_runners.TryGetValue(type, out IEcsSystemsRunner result)) + { + return (EcsSystemsRunner)result; + } + result = new EcsSystemsRunner(this); + _runners.Add(type, result); + return (EcsSystemsRunner)result; + } + + public EcsSystemsMessenger GetMessenger() + where TMessege : IEcsMessage + { + Type type = typeof(TMessege); + if (_messengers.TryGetValue(type, out IEcsSystemsMessenger result)) + { + return (EcsSystemsMessenger)result; + } + result = new EcsSystemsMessenger(this); + _messengers.Add(type, result); + return (EcsSystemsMessenger)result; + } + #endregion + + #region Configuration + public EcsSession Add(IEcsSystem system) + { + CheckInitForMethod(nameof(AddWorld)); + + _allSystems.Add(system); + return this; + } + public EcsSession AddWorld(string name) + { + CheckInitForMethod(nameof(AddWorld)); + + //_worlds.Add(new EcsWorld(_worldIdIncrement++)); + return this; + } + + #endregion + + #region LifeCycle + public void Init() + { + CheckInitForMethod(nameof(Init)); + _ecsSystemsSealed = _allSystems.AsReadOnly(); + _isInit = true; + + GetRunner<_PreInit>().Run(); + GetRunner<_Init>().Run(); + + _runRunnerCache = GetRunner<_Run>(); + } + public void Run() + { + CheckDestroyForMethod(nameof(Run)); + + + _runRunnerCache.Run(); + } + public void Destroy() + { + CheckDestroyForMethod(nameof(Run)); + _isDestoryed = true; + GetRunner<_Destroy>().Run(); + GetRunner<_PostDestroy>().Run(); + } + #endregion + + #region StateChecks + private void CheckInitForMethod(string methodName) + { + if (_isInit) + throw new MethodAccessException($"Запрещено вызывать метод {methodName}, после инициализации {nameof(EcsSession)}"); + } + private void CheckDestroyForMethod(string methodName) + { + if (_isInit) + throw new MethodAccessException($"Запрещено вызывать метод {methodName}, после уничтожения {nameof(EcsSession)}"); + } + #endregion + } +} diff --git a/src/EcsSession.cs.meta b/src/EcsSession.cs.meta new file mode 100644 index 0000000..3bf7273 --- /dev/null +++ b/src/EcsSession.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2ae31658c78bbf04cb755ac72be367dd +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/src/EcsWorld.cs b/src/EcsWorld.cs new file mode 100644 index 0000000..9329d4b --- /dev/null +++ b/src/EcsWorld.cs @@ -0,0 +1,83 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace DCFApixels.DragonECS +{ + public class EcsWorld + { + public const int MAX_WORLDS = byte.MaxValue; //Номер последнего мира 254 + public const int DEAD_WORLD_ID = byte.MaxValue; //Зарезервированный номер мира для мертвых сущьностей + + private byte _id = DEAD_WORLD_ID; + + private Dictionary _pools; + private SparseSet _entities = new SparseSet(); + private short[] _gens; + private byte[] _components; + + //private Dictionary _tables; + + #region Properties + public int ID => _id; + public bool IsAlive => _id != DEAD_WORLD_ID; + #endregion + + #region Constructors + public EcsWorld() + { + _pools = new Dictionary(); + _entities = new SparseSet(); + } + #endregion + + #region ID + internal void SetId(byte id) + { + _id = id; + } + #endregion + + #region GetPool + public EcsFieldPool GetPool() + { + Type type = typeof(T); + if (_pools.TryGetValue(type, out IEcsFieldPool pool)) + { + return (EcsFieldPool)pool; + } + + //pool = new EcsPool(); + _pools.Add(type, pool); + return (EcsFieldPool)pool; + } + #endregion + + #region NewEntity + public ent NewEntity() + { + int entityID = _entities.GetFree(); + _entities.Normalize(ref _gens); + _entities.Normalize(ref _components); + _gens[entityID]++; + + + return new ent(entityID, _gens[entityID], _id, _components[entityID]); + } + #endregion + + #region Destroy + public void Destroy() + { + _id = Consts.DEAD_WORLD_ID; + } + #endregion + + private void Resize() + { + + } + } +} diff --git a/src/EcsWorld.cs.meta b/src/EcsWorld.cs.meta new file mode 100644 index 0000000..19f3fbd --- /dev/null +++ b/src/EcsWorld.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b045d6a8b5bcf654f9c2be8012a526f6 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/src/INFO.txt b/src/INFO.txt new file mode 100644 index 0000000..67f0469 --- /dev/null +++ b/src/INFO.txt @@ -0,0 +1,2 @@ +Мервтый мир - значение byte 255, зарезервированный адишник мира, все что ссылается на мертвый мир считается так же мертвым. + diff --git a/src/INFO.txt.meta b/src/INFO.txt.meta new file mode 100644 index 0000000..6c8fd67 --- /dev/null +++ b/src/INFO.txt.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 39030d83d78d46048982adceaa08bfbd +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/src/Interfaces.meta b/src/Interfaces.meta new file mode 100644 index 0000000..a036cc1 --- /dev/null +++ b/src/Interfaces.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 4ed6f2439b576f34eaefee5b9d0978bf +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/src/Interfaces/IEcsSystem.cs b/src/Interfaces/IEcsSystem.cs new file mode 100644 index 0000000..1ed138e --- /dev/null +++ b/src/Interfaces/IEcsSystem.cs @@ -0,0 +1,29 @@ +namespace DCFApixels.DragonECS +{ + public interface IEcsSystem { } + + public interface IEcsDoTag { } + public struct _PreInit : IEcsDoTag { } + public struct _Init : IEcsDoTag { } + public struct _Run : IEcsDoTag { } + public struct _Destroy : IEcsDoTag { } + public struct _PostDestroy : IEcsDoTag { } + public interface IEcsDo : IEcsSystem + where TTag : IEcsDoTag + { + public void Do(EcsSession engine); + } + + public interface IEcsMessage { } + public interface IEcsDoMessege : IEcsSystem + where TMessage : IEcsMessage + { + public void Do(EcsSession engine, in TMessage message); + } + + public interface IEcsSimpleCycleSystem : + IEcsDo<_Init>, + IEcsDo<_Run>, + IEcsDo<_Destroy> + { } +} diff --git a/src/Interfaces/IEcsSystem.cs.meta b/src/Interfaces/IEcsSystem.cs.meta new file mode 100644 index 0000000..3d46598 --- /dev/null +++ b/src/Interfaces/IEcsSystem.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c440e40c9802ce54fb7a84fda930b22d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/src/React.meta b/src/React.meta new file mode 100644 index 0000000..c549f70 --- /dev/null +++ b/src/React.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: c0c409d38416ea840b358d614c3e62a4 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/src/React/EcsSystemsMessenger.cs b/src/React/EcsSystemsMessenger.cs new file mode 100644 index 0000000..f76b988 --- /dev/null +++ b/src/React/EcsSystemsMessenger.cs @@ -0,0 +1,41 @@ +using System.Collections.Generic; + +namespace DCFApixels.DragonECS +{ + public interface IEcsSystemsMessenger + { + public EcsSession Source { get; } + } + public class EcsSystemsMessenger : IEcsSystemsMessenger + where TMessage : IEcsMessage + { + private EcsSession _source; + private IEcsDoMessege[] _systems; + + public EcsSession Source => _source; + public IReadOnlyList> Systems => _systems; + + internal EcsSystemsMessenger(EcsSession source) + { + _source = source; + List> list = new List>(); + + foreach (var item in _source.AllSystems) + { + if (item is IEcsDoMessege targetItem) + { + list.Add(targetItem); + } + } + _systems = list.ToArray(); + } + + public void Send(in TMessage message) + { + foreach (var item in _systems) + { + item.Do(_source, message); + } + } + } +} diff --git a/src/React/EcsSystemsMessenger.cs.meta b/src/React/EcsSystemsMessenger.cs.meta new file mode 100644 index 0000000..0d8200d --- /dev/null +++ b/src/React/EcsSystemsMessenger.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: dc514cbb93c3f3049adf666ad237c6ee +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/src/React/EcsSystemsRunner.cs b/src/React/EcsSystemsRunner.cs new file mode 100644 index 0000000..dcdcfc2 --- /dev/null +++ b/src/React/EcsSystemsRunner.cs @@ -0,0 +1,42 @@ +using System.Collections.Generic; + +namespace DCFApixels.DragonECS +{ + public interface IEcsSystemsRunner + { + public EcsSession Source { get; } + public void Run(); + } + public class EcsSystemsRunner : IEcsSystemsRunner + where TDoTag : IEcsDoTag + { + private EcsSession _source; + private IEcsDo[] _systems; + + public EcsSession Source => _source; + public IReadOnlyList> Systems => _systems; + + internal EcsSystemsRunner(EcsSession source) + { + _source = source; + List> list = new List>(); + + foreach (var item in _source.AllSystems) + { + if (item is IEcsDo targetItem) + { + list.Add(targetItem); + } + } + _systems = list.ToArray(); + } + + public void Run() + { + foreach (var item in _systems) + { + item.Do(_source); + } + } + } +} diff --git a/src/React/EcsSystemsRunner.cs.meta b/src/React/EcsSystemsRunner.cs.meta new file mode 100644 index 0000000..0f1370c --- /dev/null +++ b/src/React/EcsSystemsRunner.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 37fabd05090af0843a67e6c8046ad374 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/src/Utils.meta b/src/Utils.meta new file mode 100644 index 0000000..78cad81 --- /dev/null +++ b/src/Utils.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 16b79c30e1b668e47bd35de518796871 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/src/Utils/ComponentTypeID.cs b/src/Utils/ComponentTypeID.cs new file mode 100644 index 0000000..dacdb0b --- /dev/null +++ b/src/Utils/ComponentTypeID.cs @@ -0,0 +1,12 @@ +namespace DCFApixels.DragonECS +{ + public class ComponentTypeID + { + protected static int _incerement = 0; + } + public class TypeID : ComponentTypeID + where T : struct + { + public static readonly int id = _incerement++; + } +} \ No newline at end of file diff --git a/src/Utils/ComponentTypeID.cs.meta b/src/Utils/ComponentTypeID.cs.meta new file mode 100644 index 0000000..c35772f --- /dev/null +++ b/src/Utils/ComponentTypeID.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b5af477721d789f498a0de809e753321 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/src/Utils/GrowingSparseCollection.cs b/src/Utils/GrowingSparseCollection.cs new file mode 100644 index 0000000..be42c80 --- /dev/null +++ b/src/Utils/GrowingSparseCollection.cs @@ -0,0 +1,264 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.Contracts; +using System.Runtime.CompilerServices; +using System.Runtime.ConstrainedExecution; + +namespace DCFApixels +{ + public class GrowingSparseCollection + { + private const int EMPTY = -1; + + private int[] _buckets = Array.Empty(); + private Entry[] _entries = Array.Empty(); + + private int _capacity; + private int _count; + + #region Properties + public TValue this[int key] + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _entries[FindEntry(key)].value; + } + #endregion + + #region Add + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Add(int key, TValue value) + { +#if DEBUG + if (Contains(key)) + throw new ArgumentException("Contains(key) is true"); +#endif + Insert(key, value); + } + #endregion + + #region Getter + public bool TryGetValue(int key, out TValue value) + { + int index = IndexOfKey(key); + if (index < 0) + { + value = default; + return false; + } + value = _entries[index].value; + return true; + } + #endregion + + #region Constructors + public GrowingSparseCollection(int capacity) + { + Initialize(capacity); + } + #endregion + + #region Initialize + private void Initialize(int capacity) + { + _capacity = HashHelpers.GetPrime(capacity); + _buckets = new int[_capacity]; + + for (int i = 0; i < _capacity; i++) + _buckets[i] = EMPTY; + + _entries = new Entry[_capacity]; + } + #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 Contains + public bool Contains(int key) + { + return IndexOfKey(key) >= 0; + } + #endregion + + #region IndexOfKey/Find/Insert + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int IndexOfKey(int key) + { + key &= ~int.MinValue; + for (int i = _buckets[key % _capacity]; i >= 0; i = _entries[i].next) + { + if (_entries[i].key == key) + return i; + } + return -1; + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private int FindEntry(int key) + { + key &= ~int.MinValue; + for (int i = _buckets[key % _capacity]; i >= 0; i = _entries[i].next) + { + if (_entries[i].key == key) + return i; + } + throw new KeyNotFoundException(); + } + + private void Insert(int key, TValue value) + { + key &= ~int.MinValue; + int targetBucket = key % _capacity; + + for (int i = _buckets[targetBucket]; i >= 0; i = _entries[i].next) + { + if (_entries[i].key == key) + { + _entries[i].value = value; + return; + } + } + + if (_count >= _entries.Length) + { + Resize(); + targetBucket = key % _capacity; + } + int index = _count; + _count++; + + _entries[index].next = _buckets[targetBucket]; + _entries[index].key = key; + _entries[index].value = value; + _buckets[targetBucket] = index; + } + #endregion + + #region Resize + private void Resize() + { + Resize(HashHelpers.ExpandPrime(_count), false); + } + private void Resize(int newSize, bool forceNewHashCodes) + { + _capacity = newSize; + 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); + if (forceNewHashCodes) + { + for (int i = 0; i < _count; i++) + { + if (newEntries[i].key != -1) + { + newEntries[i].key = newEntries[i].key; + } + } + } + for (int i = 0; i < _count; i++) + { + if (newEntries[i].key >= 0) + { + int bucket = newEntries[i].key % newSize; + newEntries[i].next = newBuckets[bucket]; + newBuckets[bucket] = i; + } + } + _buckets = newBuckets; + _entries = newEntries; + } + #endregion + + #region Utils + private struct Entry + { + public int next; // Index of next entry, -1 if last + public int key; // key & hash + public TValue value; + } + #endregion + } + + #region HashHelpers + internal static class HashHelpers + { + public const int MaxPrimeArrayLength = 0x7FEFFFFD; + public static readonly int[] primes = { + 3, 7, 11, 17, 23, 29, 37, 47, 59, 71, 89, 107, 131, 163, 197, 239, 293, 353, 431, 521, 631, 761, 919, + 1103, 1327, 1597, 1931, 2333, 2801, 3371, 4049, 4861, 5839, 7013, 8419, 10103, 12143, 14591, + 17519, 21023, 25229, 30293, 36353, 43627, 52361, 62851, 75431, 90523, 108631, 130363, 156437, + 187751, 225307, 270371, 324449, 389357, 467237, 560689, 672827, 807403, 968897, 1162687, 1395263, + 1674319, 2009191, 2411033, 2893249, 3471899, 4166287, 4999559, 5999471, 7199369}; + + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] + public static bool IsPrime(int candidate) + { + if ((candidate & 1) != 0) + { + int limit = (int)Math.Sqrt(candidate); + for (int divisor = 3; divisor <= limit; divisor += 2) + { + if ((candidate % divisor) == 0) + return false; + } + return true; + } + return (candidate == 2); + } + + public static int ExpandPrime(int oldSize) + { + int newSize = 2 * oldSize; + + if ((uint)newSize > MaxPrimeArrayLength && MaxPrimeArrayLength > oldSize) + { + Contract.Assert(MaxPrimeArrayLength == GetPrime(MaxPrimeArrayLength), "Invalid MaxPrimeArrayLength"); + return MaxPrimeArrayLength; + } + + return GetPrime(newSize); + } + internal const int HashtableHashPrime = 101; + + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] + public static int GetPrime(int min) + { + if (min < 0) + { + throw new ArgumentException("min < 0"); //TODO + } + Contract.EndContractBlock(); + + for (int i = 0; i < primes.Length; i++) + { + int prime = primes[i]; + if (prime >= min) + return prime; + } + + for (int i = (min | 1); i < int.MaxValue; i += 2) + { + if (IsPrime(i) && ((i - 1) % HashtableHashPrime != 0)) + return i; + } + return min; + } + } + #endregion +} diff --git a/src/Utils/GrowingSparseCollection.cs.meta b/src/Utils/GrowingSparseCollection.cs.meta new file mode 100644 index 0000000..55c00d5 --- /dev/null +++ b/src/Utils/GrowingSparseCollection.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0862221477782744a98863ae86648660 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/src/Utils/IntSet.cs b/src/Utils/IntSet.cs new file mode 100644 index 0000000..c1c4852 --- /dev/null +++ b/src/Utils/IntSet.cs @@ -0,0 +1,1036 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Security; + +namespace Kibnet +{ + [Serializable] + public class IntSet : ISet, IReadOnlyCollection + { + #region IntSet + + public IntSet() : this(false, false) { } + + public IntSet(IEnumerable items) : this(false, false) + { + UnionWith(items); + } + + public IntSet(bool isFastest) : this(isFastest, false) { } + + public IntSet(bool isFastest, bool isFull) + { + _isFastest = isFastest; + if (isFull) + { + _count = UInt32.MaxValue; + root.Full = true; + root.Cards = null; + } + } + + protected bool _isFastest; + + protected long _count; + + public long LongCount => _count; + + public bool IsFastest + { + get => _isFastest; + set => _isFastest = value; + } + + public Card root = new Card(0); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected void IntersectWithIntSet(ISet other) + { + foreach (var item in this) + { + if (!other.Contains(item)) + { + InternalRemove(item, true); + } + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected void IntersectWithEnumerable(IEnumerable other) + { + var result = new IntSet(); + foreach (var item in other) + { + if (Contains(item)) + { + result.Add(item); + } + } + + root = result.root; + _count = result._count; + } + + /// + /// Checks if this contains of other's elements. Iterates over other's elements and + /// returns false as soon as it finds an element in other that's not in this. + /// Used by SupersetOf, ProperSupersetOf, and SetEquals. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected bool ContainsAllElements(IEnumerable other) + { + foreach (var element in other) + { + if (!Contains(element)) + { + return false; + } + } + + return true; + } + + /// + /// Implementation Notes: + /// If other is a intset and is using same equality comparer, then checking subset is + /// faster. Simply check that each element in this is in other. + /// + /// Note: if other doesn't use same equality comparer, then Contains check is invalid, + /// which is why callers must take are of this. + /// + /// If callers are concerned about whether this is a proper subset, they take care of that. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected bool IsSubsetOfHashSetWithSameComparer(IntSet other) + { + foreach (var item in this) + { + if (!other.Contains(item)) + { + return false; + } + } + + return true; + } + + /// + /// Determines counts that can be used to determine equality, subset, and superset. This + /// is only used when other is an IEnumerable and not a HashSet. If other is a HashSet + /// these properties can be checked faster without use of marking because we can assume + /// other has no duplicates. + /// + /// The following count checks are performed by callers: + /// 1. Equals: checks if unfoundCount = 0 and uniqueFoundCount = _count; i.e. everything + /// in other is in this and everything in this is in other + /// 2. Subset: checks if unfoundCount >= 0 and uniqueFoundCount = _count; i.e. other may + /// have elements not in this and everything in this is in other + /// 3. Proper subset: checks if unfoundCount > 0 and uniqueFoundCount = _count; i.e + /// other must have at least one element not in this and everything in this is in other + /// 4. Proper superset: checks if unfound count = 0 and uniqueFoundCount strictly less + /// than _count; i.e. everything in other was in this and this had at least one element + /// not contained in other. + /// + /// An earlier implementation used delegates to perform these checks rather than returning + /// an ElementCount struct; however this was changed due to the perf overhead of delegates. + /// + /// + /// Allows us to finish faster for equals and proper superset + /// because unfoundCount must be 0. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected (int UniqueCount, int UnfoundCount) CheckUniqueAndUnfoundElements(IEnumerable other, bool returnIfUnfound) + { + // Need special case in case this has no elements. + if (_count == 0) + { + int numElementsInOther = 0; + foreach (int item in other) + { + numElementsInOther++; + break; // break right away, all we want to know is whether other has 0 or 1 elements + } + + return (UniqueCount: 0, UnfoundCount: numElementsInOther); + } + + Debug.Assert((root.Cards != null) && (_count > 0), "root.Cards was null but count greater than 0"); + + int unfoundCount = 0; // count of items in other not found in this + int uniqueFoundCount = 0; // count of unique items in other found in this + + + var otherAsSet = new IntSet(); + + foreach (int item in other) + { + if (Contains(item)) + { + if (!otherAsSet.Contains(item)) + { + // Item hasn't been seen yet. + otherAsSet.Add(item); + uniqueFoundCount++; + } + } + else + { + unfoundCount++; + if (returnIfUnfound) + { + break; + } + } + } + + return (uniqueFoundCount, unfoundCount); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected void SymmetricExceptWithUniqueHashSet(ISet other) + { + foreach (int item in other) + { + if (!Remove(item)) + { + Add(item); + } + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected void SymmetricExceptWithEnumerable(IEnumerable other) + { + var itemsToRemove = new IntSet(); + + var itemsAddedFromOther = new IntSet(); + + foreach (int item in other) + { + if (Add(item)) + { + // wasn't already present in collection; flag it as something not to remove + // *NOTE* if location is out of range, we should ignore. BitHelper will + // detect that it's out of bounds and not try to mark it. But it's + // expected that location could be out of bounds because adding the item + // will increase _lastIndex as soon as all the free spots are filled. + itemsAddedFromOther.Add(item); + } + else + { + // already there...if not added from other, mark for remove. + // *NOTE* Even though BitHelper will check that location is in range, we want + // to check here. There's no point in checking items beyond originalCount + // because they could not have been in the original collection + if (!itemsAddedFromOther.Contains(item)) + { + itemsToRemove.Add(item); + } + } + } + + // if anything marked, remove it + foreach (var item in itemsToRemove) + { + Remove(item); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected bool InternalRemove(int item, bool isFastest) + { + var card = root; + for (int i = 0; i < 5; i++) + { + if (card.Full) + { + //TODO Split card + card.Init(i, true); + card.Full = false; + } + + var index = Card.GetIndex(item, i); + if (i < 4) + { + if (card.Cards[index] == null) + { + return false; + } + + card = card.Cards[index]; + } + else + { + var bindex = index >> 3; + var mask = (byte) (1 << (index & 7)); + if ((card.Bytes[bindex] & mask) == 0) + { + return false; + } + + card.Bytes[bindex] ^= mask; + _count--; + if (isFastest == false && card.Bytes[bindex] == byte.MinValue) + { + var isEmpty = card.CheckEmpty(); + if (isEmpty) + { + var parentCard0 = root.Cards[Card.GetIndex(item, 0)]; + var parentCard1 = parentCard0.Cards[Card.GetIndex(item, 1)]; + var parentCard2 = parentCard1.Cards[Card.GetIndex(item, 2)]; + var parentCard3 = parentCard2.Cards[Card.GetIndex(item, 3)]; + //var parentCard4 = parentCard3.Cards[indexes[4]]; + //if (parentCard4.CheckFull()) + if (parentCard3.CheckEmpty()) + if (parentCard2.CheckEmpty()) + if (parentCard1.CheckEmpty()) + if (parentCard0.CheckEmpty()) + ; + } + } + + return true; + } + } + + return false; + } + + #endregion + + #region Card + + [Serializable] + public class Card : IEnumerable, IEnumerable + { + public Card() { } + + public Card(int level) + { + Init(level); + } + + public Card Init(int level, bool isFull = false) + { + if (level < 4) + { + Cards = new Card[64]; + if (isFull) + { + for (int i = 0; i < 64; i++) + { + Cards[i] = new Card { Full = true }; + } + } + } + else + { + Bytes = new byte[32]; + if (isFull) + { + for (int i = 0; i < 32; i++) + { + Bytes[i] = byte.MaxValue; + } + } + } + return this; + } + + public Card[] Cards; + public byte[] Bytes; + public bool Full; + + [SecuritySafeCritical] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe int GetIndex(int value, int level) + { + var temp = (uint)(uint*)(value); + var index = level switch + { + 0 => (temp) >> 26, + 1 => (temp << 6) >> 26, + 2 => (temp << 12) >> 26, + 3 => (temp << 18) >> 26, + 4 => (temp << 24) >> 24, + }; + //TODO переписать на получение указателей + return (int)(int*)index; + } + + IEnumerator IEnumerable.GetEnumerator() + { + if (Cards != null) + { + for (int i = 0; i < 64; i++) + { + if (Cards[i] != null) + { + yield return i; + } + } + } + else if (Full) + { + for (int i = 0; i < 64; i++) + { + yield return i; + } + } + } + + IEnumerator IEnumerable.GetEnumerator() + { + if (Bytes != null) + { + for (int i = 0; i < 32; i++) + { + yield return Bytes[i]; + } + } + else if (Full) + { + for (int i = 0; i < 32; i++) + { + yield return byte.MaxValue; + } + } + } + + public IEnumerator GetEnumerator() + { + if (Bytes != null) + { + return ((IEnumerable)this).GetEnumerator(); + } + else + { + return ((IEnumerable)this).GetEnumerator(); + } + } + + public override string ToString() + { + if (Bytes != null) + { + return String.Create(256, this, GetMask); + } + else + { + return String.Create(64, this, GetMask); + } + } + + public bool CheckFull() + { + if (Bytes != null) + { + Full = Bytes.All(b => b == byte.MaxValue); + if (Full == true) Bytes = null; + } + if (Cards != null) + { + Full = Cards.All(c => c != null && c.Full); + if (Full == true) Cards = null; + } + return Full; + } + + public bool CheckEmpty() + { + if (Bytes != null) + { + if (Bytes.All(b => b == byte.MinValue)) + { + Full = false; + Bytes = null; + return true; + } + return false; + } + if (Cards != null) + { + if (Cards.All(c => c == null || c.CheckEmpty())) + { + Full = false; + Cards = null; + return true; + } + return false; + } + return !Full; + } + + public static void GetMask(Span span, Card arg) + { + if (arg.Full == true) + { + span.Fill('1'); + } + else + { + if (arg.Cards != null) + { + var count = arg.Cards.Length; + for (int i = 0; i < count; i++) + { + span[i] = arg.Cards[i] != null ? '1' : '0'; + } + return; + } + if (arg.Bytes != null) + { + var count = arg.Bytes.Length; + var bytesize = 8; + var p = 0; + for (int i = 0; i < count; i++) + { + var b = arg.Bytes[i]; + for (int j = 0; j < bytesize; j++) + { + span[p++] = ((b & 1) == 1) ? '1' : '0'; + b >>= 1; + } + } + } + } + } + } + + #endregion + + #region IEnumerable + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + public IEnumerator GetEnumerator() + { + foreach (var i0 in (IEnumerable)root) + { + var cards1 = root.Full ? root : root.Cards[i0]; + if (cards1 != null) + { + foreach (var i1 in (IEnumerable)cards1) + { + var cards2 = cards1.Full ? cards1 : cards1.Cards[i1]; + if (cards2 != null) + { + foreach (var i2 in (IEnumerable)cards2) + { + var cards3 = cards2.Full ? cards2 : cards2.Cards[i2]; + if (cards3 != null) + { + foreach (var i3 in (IEnumerable)cards3) + { + var bytes = cards3.Full ? cards3 : cards3.Cards[i3]; + if (bytes != null) + { + var bytecount = 0; + foreach (var i4 in (IEnumerable)bytes) + { + for (int j = 0; j < 8; j++) + { + var tail = i4 & (1 << j); + if (tail != 0) + { + var value = i0 << 26; + value |= i1 << 20; + value |= i2 << 14; + value |= i3 << 8; + value |= bytecount << 3; + value |= j; + yield return value; + } + } + + bytecount++; + } + } + } + } + } + } + } + } + } + } + + #endregion + + #region ICollection + + public int Count => (int)_count; + + public bool IsReadOnly { get; } + + void ICollection.Add(int item) => Add(item); + + public void Clear() + { + root = new Card(0); + _count = 0; + } + + bool ICollection.Contains(int item) => Contains(item); + + public bool Contains(int item) + { + var card = root; + for (int i = 0; i < 5; i++) + { + if (card.Full) + { + return true; + } + + var index = Card.GetIndex(item, i); + + if (i < 4) + { + if (card.Cards?[index] == null) + return false; + card = card.Cards[index]; + } + else + { + if (card.Bytes == null) + { + return false; + } + var bindex = index >> 3; + var mask = (byte)(1 << (index & 7)); + return (card.Bytes[bindex] & mask) != 0; + } + } + return false; + } + + public void CopyTo(int[] array) => CopyTo(array, 0, Count); + + public void CopyTo(int[] array, int arrayIndex) => CopyTo(array, arrayIndex, Count); + + public void CopyTo(int[] array, int arrayIndex, int count) + { + if (array == null) + { + throw new ArgumentNullException(nameof(array)); + } + + // Check array index valid index into array. + if (arrayIndex < 0) + { + throw new ArgumentOutOfRangeException(nameof(arrayIndex), arrayIndex, "Non-negative number required."); + } + + // Also throw if count less than 0. + if (count < 0) + { + throw new ArgumentOutOfRangeException(nameof(count), count, "Non-negative number required."); + } + + // Will the array, starting at arrayIndex, be able to hold elements? Note: not + // checking arrayIndex >= array.Length (consistency with list of allowing + // count of 0; subsequent check takes care of the rest) + if (arrayIndex > array.Length || count > array.Length - arrayIndex) + { + throw new ArgumentException("Destination array is not long enough to copy all the items in the collection. Check array index and length."); + } + + var enumerator = GetEnumerator(); + for (int i = 0; i < _count && count != 0; i++) + { + if (enumerator.MoveNext()) + { + array[arrayIndex++] = enumerator.Current; + count--; + } + } + } + + public bool Remove(int item) => InternalRemove(item, _isFastest); + + #endregion + + #region ISet + + public bool Add(int item) + { + var card = root; + for (int i = 0; i < 5; i++) + { + if (card.Full) + { + return false; + } + + var index = Card.GetIndex(item, i); + if (i < 4) + { + if (card.Cards == null) + { + card.Init(i); + } + if (card.Cards[index] == null) + { + var newcard = new Card(i + 1); + card.Cards[index] = newcard; + card = newcard; + } + else + { + card = card.Cards[index]; + } + } + else + { + if (card.Bytes == null) + { + card.Init(i); + } + var bindex = index >> 3; + var mask = (byte)(1 << (index & 7)); + if ((card.Bytes[bindex] & mask) == 0) + { + card.Bytes[bindex] |= mask; + _count++; + if (_isFastest == false && card.Bytes[bindex] == byte.MaxValue) + { + var full = card.CheckFull(); + if (full) + { + var parentCard0 = root.Cards[Card.GetIndex(item, 0)]; + var parentCard1 = parentCard0.Cards[Card.GetIndex(item, 1)]; + var parentCard2 = parentCard1.Cards[Card.GetIndex(item, 2)]; + var parentCard3 = parentCard2.Cards[Card.GetIndex(item, 3)]; + //var parentCard4 = parentCard3.Cards[indexes[4]]; + //if (parentCard4.CheckFull()) + if (parentCard3.CheckFull()) + if (parentCard2.CheckFull()) + if (parentCard1.CheckFull()) + if (parentCard0.CheckFull()) ; + } + } + return true; + } + } + } + return false; + } + + public void UnionWith(IEnumerable other) + { + if (other == null) + { + throw new ArgumentNullException(nameof(other)); + } + + foreach (var item in other) + { + Add(item); + } + } + + public void IntersectWith(IEnumerable other) + { + if (other == null) + { + throw new ArgumentNullException(nameof(other)); + } + + // Intersection of anything with empty set is empty set, so return if count is 0. + // Same if the set intersecting with itself is the same set. + if (Count == 0 || ReferenceEquals(other, this)) + { + return; + } + + // If other is known to be empty, intersection is empty set; remove all elements, and we're done. + if (other is ICollection otherAsCollection) + { + if (otherAsCollection.Count == 0) + { + Clear(); + return; + } + + if (other is ISet otherAsSet) + { + IntersectWithIntSet(otherAsSet); + return; + } + } + + IntersectWithEnumerable(other); + } + + public void ExceptWith(IEnumerable other) + { + foreach (var item in other) + { + Remove(item); + } + } + + public void SymmetricExceptWith(IEnumerable other) + { + if (other == null) + { + throw new ArgumentNullException(nameof(other)); + } + + // If set is empty, then symmetric difference is other. + if (Count == 0) + { + UnionWith(other); + return; + } + + // Special-case this; the symmetric difference of a set with itself is the empty set. + if (other == this) + { + Clear(); + return; + } + + // If other is a HashSet, it has unique elements according to its equality comparer, + // but if they're using different equality comparers, then assumption of uniqueness + // will fail. So first check if other is a hashset using the same equality comparer; + // symmetric except is a lot faster and avoids bit array allocations if we can assume + // uniqueness. + if (other is ISet otherAsSet) + { + SymmetricExceptWithUniqueHashSet(otherAsSet); + } + else + { + SymmetricExceptWithEnumerable(other); + } + } + + public bool IsSubsetOf(IEnumerable other) + { + if (other == null) + { + throw new ArgumentNullException(nameof(other)); + } + + // The empty set is a subset of any set, and a set is a subset of itself. + // Set is always a subset of itself + if (Count == 0 || other == this) + { + return true; + } + + // Faster if other has unique elements according to this equality comparer; so check + // that other is a hashset using the same equality comparer. + if (other is IntSet otherAsSet) + { + // if this has more elements then it can't be a subset + if (Count > otherAsSet.Count) + { + return false; + } + + // already checked that we're using same equality comparer. simply check that + // each element in this is contained in other. + return IsSubsetOfHashSetWithSameComparer(otherAsSet); + } + + (int uniqueCount, int unfoundCount) = CheckUniqueAndUnfoundElements(other, returnIfUnfound: false); + return uniqueCount == Count && unfoundCount >= 0; + } + + public bool IsSupersetOf(IEnumerable other) + { + if (other == null) + { + throw new ArgumentNullException(nameof(other)); + } + + // A set is always a superset of itself. + if (other == this) + { + return true; + } + + // Try to fall out early based on counts. + if (other is ICollection otherAsCollection) + { + // If other is the empty set then this is a superset. + if (otherAsCollection.Count == 0) + { + return true; + } + + // Try to compare based on counts alone if other is a hashset with same equality comparer. + if (other is IntSet otherAsSet && otherAsSet.Count > Count) + { + return false; + } + } + + return ContainsAllElements(other); + } + + public bool IsProperSupersetOf(IEnumerable other) + { + if (other == null) + { + throw new ArgumentNullException(nameof(other)); + } + + // The empty set isn't a proper superset of any set, and a set is never a strict superset of itself. + if (Count == 0 || other == this) + { + return false; + } + + if (other is ICollection otherAsCollection) + { + // If other is the empty set then this is a superset. + if (otherAsCollection.Count == 0) + { + // Note that this has at least one element, based on above check. + return true; + } + + // Faster if other is a hashset with the same equality comparer + if (other is IntSet otherAsSet) + { + if (otherAsSet.Count >= Count) + { + return false; + } + + // Now perform element check. + return ContainsAllElements(otherAsSet); + } + } + + // Couldn't fall out in the above cases; do it the long way + (int uniqueCount, int unfoundCount) = CheckUniqueAndUnfoundElements(other, returnIfUnfound: true); + return uniqueCount < Count && unfoundCount == 0; + } + + public bool IsProperSubsetOf(IEnumerable other) + { + if (other == null) + { + throw new ArgumentNullException(nameof(other)); + } + + // No set is a proper subset of itself. + if (other == this) + { + return false; + } + + if (other is ICollection otherAsCollection) + { + // No set is a proper subset of an empty set. + if (otherAsCollection.Count == 0) + { + return false; + } + + // The empty set is a proper subset of anything but the empty set. + if (Count == 0) + { + return otherAsCollection.Count > 0; + } + + // Faster if other is a hashset (and we're using same equality comparer). + if (other is IntSet otherAsSet) + { + if (Count >= otherAsSet.Count) + { + return false; + } + + // This has strictly less than number of items in other, so the following + // check suffices for proper subset. + return IsSubsetOfHashSetWithSameComparer(otherAsSet); + } + } + + (int uniqueCount, int unfoundCount) = CheckUniqueAndUnfoundElements(other, returnIfUnfound: false); + return uniqueCount == Count && unfoundCount > 0; + } + + public bool Overlaps(IEnumerable other) + { + if (other == null) + { + throw new ArgumentNullException(nameof(other)); + } + + if (Count == 0) + { + return false; + } + + // Set overlaps itself + if (other == this) + { + return true; + } + + foreach (var element in other) + { + if (Contains(element)) + { + return true; + } + } + + return false; + } + + public bool SetEquals(IEnumerable other) + { + if (other == null) + { + throw new ArgumentNullException(nameof(other)); + } + + // A set is equal to itself. + if (other == this) + { + return true; + } + + // Faster if other is a hashset and we're using same equality comparer. + if (other is IntSet otherAsSet) + { + // Attempt to return early: since both contain unique elements, if they have + // different counts, then they can't be equal. + if (Count != otherAsSet.Count) + { + return false; + } + + // Already confirmed that the sets have the same number of distinct elements, so if + // one is a superset of the other then they must be equal. + return ContainsAllElements(otherAsSet); + } + else + { + // If this count is 0 but other contains at least one element, they can't be equal. + if (Count == 0 && + other is ICollection otherAsCollection && + otherAsCollection.Count > 0) + { + return false; + } + + (int uniqueCount, int unfoundCount) = CheckUniqueAndUnfoundElements(other, returnIfUnfound: true); + return uniqueCount == Count && unfoundCount == 0; + } + } + + #endregion + } +} \ No newline at end of file diff --git a/src/Utils/IntSet.cs.meta b/src/Utils/IntSet.cs.meta new file mode 100644 index 0000000..d56fb85 --- /dev/null +++ b/src/Utils/IntSet.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 9cad30d5b37df1d48bc2abe5d1743649 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/src/Utils/SparseSet.cs b/src/Utils/SparseSet.cs new file mode 100644 index 0000000..08c1fd7 --- /dev/null +++ b/src/Utils/SparseSet.cs @@ -0,0 +1,489 @@ +// _sparse[value] == index +// _dense[index] == value +// +// int[] _dense => |2|4|1|_|_| +// int[] _sparse => |_|2|0|_|1| +// +// indexator => [0]2, [1]4, [2]1 +// +// can use foreach +// implements IEnumerable + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using System.Text; + +namespace DCFApixels.DragonECS +{ + public class SparseSet : IEnumerable, ICollection, IReadOnlyCollection + { + public const int DEFAULT_CAPACITY = 16; + + private int[] _dense; + private int[] _sparse; + + private int _count; + + #region Properties + public int Count => _count; + public int Capacity => _dense.Length; + + public int this[int index] + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] +#if DEBUG + get + { + ThrowHalper.CheckOutOfRange(this, index); + return _dense[index]; + } +#else + get => _dense[index]; +#endif + } + + public IndexesCollection Indexes => new IndexesCollection(_sparse); + #endregion + + #region Constructors + public SparseSet() : this(DEFAULT_CAPACITY) { } + public SparseSet(int capacity) + { +#if DEBUG + ThrowHalper.CheckCapacity(capacity); +#endif + _dense = new int[capacity]; + _sparse = new int[capacity]; + for (int i = 0; i < _sparse.Length; i++) + { + _dense[i] = i; + _sparse[i] = i; + } + _count = 0; + } + #endregion + + #region Add/AddRange/GetFree + public void Add(int value, ref T[] normalizedArray) + { + Add(value); + Normalize(ref normalizedArray); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Add(int value) + { +#if DEBUG + ThrowHalper.CheckValueIsPositive(value); + ThrowHalper.CheckValueNotContained(this, value); +#endif + + int neadedSpace = _dense.Length; + while (value >= neadedSpace) + neadedSpace <<= 1; + + if (neadedSpace != _dense.Length) + Resize(neadedSpace); + + if (Contains(value)) + { + return; + } + + Swap(value, _count++); + } + + public bool TryAdd(int value, ref T[] normalizedArray) + { + if (Contains(value)) + return false; + + Add(value, ref normalizedArray); + return true; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool TryAdd(int value) + { + if (Contains(value)) + return false; + + Add(value); + return true; + } + + public void AddRange(IEnumerable range, ref T[] normalizedArray) + { + foreach (var item in range) + { + if (Contains(item)) + continue; + + Add(item); + } + Normalize(ref normalizedArray); + } + + public void AddRange(IEnumerable range) + { + foreach (var item in range) + { + if (Contains(item)) + continue; + + Add(item); + } + } + /// + /// Adds a value between 0 and Capacity to the array and returns it. + /// + /// Value between 0 and Capacity + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int GetFree(ref T[] normalizedArray) + { + int result = GetFree(); + Normalize(ref normalizedArray); + return result; + } + /// + /// Adds a value between 0 and Capacity to the array and returns it. + /// + /// Value between 0 and Capacity + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int GetFree() + { + if (++_count >= _dense.Length) + AddSpaces(); + + return _dense[_count - 1]; + } + #endregion + + #region Contains + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Contains(int value) + { + return value >= 0 && value < Capacity && _sparse[value] < _count; + } + #endregion + + #region Remove + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Remove(int value) + { +#if DEBUG + ThrowHalper.CheckValueContained(this, value); +#endif + Swap(_sparse[value], --_count); + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool TryRemove(int value) + { + if (!Contains(value)) + return false; + + Remove(value); + return true; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void RemoveAt(int index) + { +#if DEBUG + ThrowHalper.CheckOutOfRange(this, index); +#endif + Remove(_dense[index]); + } + #endregion + + #region Other + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Normalize(ref T[] array) + { + if (array.Length != _dense.Length) + Array.Resize(ref array, _dense.Length); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int IndexOf(int value) + { + if (value < 0 || !Contains(value)) + return -1; + + return _sparse[value]; + } + + public void Sort() + { + int increment = 0; + for (int i = 0; i < Capacity; i++) + { + if (_sparse[i] < _count) + { + _sparse[i] = increment; + _dense[increment++] = i; + } + } + } + + public void HardSort() + { + int inc = 0; + int inc2 = _count; + for (int i = 0; i < Capacity; i++) + { + if (_sparse[i] < _count) + { + _sparse[i] = inc; + _dense[inc++] = i; + } + else + { + _sparse[i] = inc2; + _dense[inc2++] = i; + } + } + } + + public void CopyTo(SparseSet other) + { + other._count = _count; + if (Capacity != other.Capacity) + { + other.Resize(Capacity); + } + _dense.CopyTo(other._dense, 0); + _sparse.CopyTo(other._sparse, 0); + } + + public void CopyTo(int[] array, int arrayIndex) + { +#if DEBUG + if (arrayIndex < 0) + throw new ArgumentException("arrayIndex is less than 0"); + if (arrayIndex + _count >= array.Length) + throw new ArgumentException("The number of elements in the source List is greater than the available space from arrayIndex to the end of the destination array."); +#endif + for (int i = 0; i < _count; i++, arrayIndex++) + { + array[arrayIndex] = this[i]; + } + } + #endregion + + #region Clear/Reset + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Clear() + { + _count = 0; + } + + public void Reset() + { + Clear(); + for (int i = 0; i < _dense.Length; i++) + { + _dense[i] = i; + _sparse[i] = i; + } + } + public void Reset(int newCapacity) + { +#if DEBUG + ThrowHalper.CheckCapacity(newCapacity); +#endif + Reset(); + Resize(newCapacity); + } + #endregion + + #region AddSpace/Resize + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void AddSpaces() => Resize(_count << 1); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void Resize(int newSpace) + { + int oldspace = _dense.Length; + Array.Resize(ref _dense, newSpace); + Array.Resize(ref _sparse, newSpace); + + for (int i = oldspace; i < newSpace; i++) + { + _dense[i] = i; + _sparse[i] = i; + } + } + #endregion + + #region Swap + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void Swap(int fromIndex, int toIndex) + { + int value = _dense[toIndex]; + int oldValue = _dense[fromIndex]; + + _dense[toIndex] = oldValue; + _dense[fromIndex] = value; + _sparse[_dense[fromIndex]] = fromIndex; + _sparse[_dense[toIndex]] = toIndex; + } + #endregion + + #region Enumerator + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public RefEnumerator GetEnumerator() => new RefEnumerator(_dense, _count); + + public ref struct RefEnumerator + { + private readonly int[] _dense; + private readonly int _count; + private int _index; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public RefEnumerator(int[] values, int count) + { + _dense = values; + _count = count; + _index = -1; + } + + public int Current + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _dense[_index]; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Dispose() { } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool MoveNext() => ++_index < _count; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Reset() => _index = -1; + } + + IEnumerator IEnumerable.GetEnumerator() => new Enumerator(_dense, _count); + IEnumerator IEnumerable.GetEnumerator() => new Enumerator(_dense, _count); + public struct Enumerator : IEnumerator //to implement the IEnumerable interface and use the ref structure, 2 Enumerators were created. + { + private readonly int[] _dense; + private readonly int _count; + private int _index; + public Enumerator(int[] values, int count) + { + _dense = values; + _count = count; + _index = -1; + } + public int Current => _dense[_index]; + object IEnumerator.Current => _dense[_index]; + public void Dispose() { } + public bool MoveNext() => ++_index < _count; + public void Reset() => _index = -1; + } + #endregion + + #region Utils + public ref struct IndexesCollection + { + private readonly int[] _indexes; + + public IndexesCollection(int[] indexes) + { + _indexes = indexes; + } + + public int this[int value] + { + get => _indexes[value]; + } + } + #endregion + + #region ICollection + bool ICollection.IsReadOnly => false; + + bool ICollection.Remove(int value) => TryRemove(value); + #endregion + + #region Debug + public string Log() + { + StringBuilder logbuild = new StringBuilder(); + for (int i = 0; i < Capacity; i++) + { + logbuild.Append(_dense[i] + ", "); + } + logbuild.Append("\n\r"); + for (int i = 0; i < Capacity; i++) + { + logbuild.Append(_sparse[i] + ", "); + } + logbuild.Append("\n\r --------------------------"); + logbuild.Append("\n\r"); + for (int i = 0; i < Capacity; i++) + { + logbuild.Append((i < _count ? _dense[i].ToString() : "_") + ", "); + } + logbuild.Append("\n\r"); + for (int i = 0; i < Capacity; i++) + { + logbuild.Append((_sparse[i] < _count ? _sparse[i].ToString() : "_") + ", "); + } + logbuild.Append("\n\r Count: " + _count); + logbuild.Append("\n\r Capacity: " + Capacity); + logbuild.Append("\n\r IsValide: " + IsValide_Debug()); + + logbuild.Append("\n\r"); + return logbuild.ToString(); + } + + public bool IsValide_Debug() + { + bool isPass = true; + for (int index = 0; index < Capacity; index++) + { + int value = _dense[index]; + isPass = isPass && _sparse[value] == index; + } + return isPass; + } + + +#if DEBUG + private static class ThrowHalper + { + public static void CheckCapacity(int capacity) + { + if (capacity < 0) + throw new ArgumentException("Capacity cannot be a negative number"); + } + public static void CheckValueIsPositive(int value) + { + if (value < 0) + throw new ArgumentException("The SparseSet can only contain positive numbers"); + } + public static void CheckValueContained(SparseSet source, int value) + { + if (!source.Contains(value)) + throw new ArgumentException($"Value {value} is not contained"); + } + public static void CheckValueNotContained(SparseSet source, int value) + { + if (source.Contains(value)) + throw new ArgumentException($"Value {value} is already contained"); + } + public static void CheckOutOfRange(SparseSet source, int index) + { + if (index < 0 || index >= source.Count) + throw new ArgumentOutOfRangeException($"Index {index} was out of range. Must be non-negative and less than the size of the collection."); + } + } +#endif + #endregion + } +} \ No newline at end of file diff --git a/src/Utils/SparseSet.cs.meta b/src/Utils/SparseSet.cs.meta new file mode 100644 index 0000000..a4f1f78 --- /dev/null +++ b/src/Utils/SparseSet.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d46dabbefb72a224bbf8e74eb12fc455 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/src/ent.cs b/src/ent.cs new file mode 100644 index 0000000..2aea147 --- /dev/null +++ b/src/ent.cs @@ -0,0 +1,113 @@ +using System; +using System.ComponentModel; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace DCFApixels.DragonECS +{ + + [StructLayout(LayoutKind.Sequential, Pack = 0, Size = 8)] + public readonly struct ent : IEquatable, IEquatable + { + //private const int ID_BITS = 32; + //private const int GEN_BITS = 16; + //private const int WORLD_BITS = 8; + //private const int COM_BITS = 8; + + public readonly long _full; + + [EditorBrowsable(EditorBrowsableState.Never)] + public int id + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => (int)(_full >> 32); + } + [EditorBrowsable(EditorBrowsableState.Never)] + public short gen + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => (short)((_full << 32) >> 48); + + } + [EditorBrowsable(EditorBrowsableState.Never)] + public byte world + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => (byte)((_full << 48) >> 56); + + } + [EditorBrowsable(EditorBrowsableState.Never)] + public byte com + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => (byte)((_full << 56) >> 56); + + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public ent(int id, short gen, byte world, byte com) + { + _full = ((long)id) << 32; + _full += ((long)gen) << 16; + _full += ((long)world) << 8; + _full += com; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ent(long value) + { + _full = value; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public TypeCode GetTypeCode() + { + return TypeCode.Int64; + } + + #region GetHashCode + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override int GetHashCode() + { + return unchecked((int)(_full)) ^ (int)(_full >> 32); + } + #endregion + + #region Equals + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Equals(in ent other) + { + return _full == other._full; + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override bool Equals(object obj) + { + return obj is ent other && Equals(in other); + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Equals(ent other) + { + return _full == other._full; + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Equals(long other) + { + return _full == other; + } + #endregion + + #region operators + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator ==(in ent left, in ent right) => left.Equals(in right); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator !=(in ent left, in ent right) => !left.Equals(in right); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator long(in ent eent) => eent._full; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator ent(in long value) => new ent(value); + #endregion + } +} diff --git a/src/ent.cs.meta b/src/ent.cs.meta new file mode 100644 index 0000000..165404b --- /dev/null +++ b/src/ent.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 24f0d55c3815f3c429a749511f5d3837 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/test.meta b/test.meta new file mode 100644 index 0000000..e25bdca --- /dev/null +++ b/test.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 3776f76149b634c48a0bf7a7f98ee0c5 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/test/TestSystem.cs b/test/TestSystem.cs new file mode 100644 index 0000000..1beecbe --- /dev/null +++ b/test/TestSystem.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace DCFApixels.DragonECS +{ + public class TestSystem : IEcsDo<_Init>, IEcsDo<_Run>, IEcsDo<_Destroy> + { + void IEcsDo<_Init>.Do(EcsSession engine) + { + } + + void IEcsDo<_Run>.Do(EcsSession engine) + { + } + + void IEcsDo<_Destroy>.Do(EcsSession engine) + { + } + } +} diff --git a/test/TestSystem.cs.meta b/test/TestSystem.cs.meta new file mode 100644 index 0000000..db63935 --- /dev/null +++ b/test/TestSystem.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: affa952a9e445864ebfe2ca2388e76de +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/test/TransformTable.cs b/test/TransformTable.cs new file mode 100644 index 0000000..358a7b7 --- /dev/null +++ b/test/TransformTable.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace DCFApixels.DragonECS +{ + public struct TransformTable + { + } +} diff --git a/test/TransformTable.cs.meta b/test/TransformTable.cs.meta new file mode 100644 index 0000000..2a12223 --- /dev/null +++ b/test/TransformTable.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d6c6320184f942444b499e3f027a72a7 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: