From 24849ab61919baaf5399f31921abe0007e077762 Mon Sep 17 00:00:00 2001 From: Mikhail <99481254+DCFApixels@users.noreply.github.com> Date: Mon, 24 Apr 2023 16:48:18 +0800 Subject: [PATCH] update queries --- src/Builtin/BaseRunners.cs | 40 ++--- src/Builtin/InjectSystem.cs | 5 + src/Builtin/Queries.cs | 29 +++ src/EcsGroup.cs | 36 ++-- src/EcsJoinQuery.cs | 320 +++++++++++++++++++++++++--------- src/EcsQuery.cs | 64 ++++++- src/EcsWorld.cs | 101 +++++------ src/Entities/EcsEntity.cs | 8 +- src/Pools/EcsAttachPool.cs | 9 +- src/Pools/EcsReadonlyPool.cs | 143 +++++++++++++++ src/Pools/EcsRelationPool.cs | 2 +- src/Pools/EcsSinglePool.cs | 8 +- src/Utils/EntityLinkedList.cs | 6 +- 13 files changed, 583 insertions(+), 188 deletions(-) create mode 100644 src/Builtin/Queries.cs create mode 100644 src/Pools/EcsReadonlyPool.cs diff --git a/src/Builtin/BaseRunners.cs b/src/Builtin/BaseRunners.cs index 42f2a57..a92fc37 100644 --- a/src/Builtin/BaseRunners.cs +++ b/src/Builtin/BaseRunners.cs @@ -31,10 +31,10 @@ namespace DCFApixels.DragonECS public void PreInit(EcsPipeline pipeline) { #if DEBUG && !DISABLE_DEBUG - for (int i = 0; i < Targets.Length && Targets.Length <= _markers.Length; i++) + for (int i = 0; i < targets.Length && targets.Length <= _markers.Length; i++) { using (_markers[i].Auto()) - Targets[i].PreInit(pipeline); + targets[i].PreInit(pipeline); } #else foreach (var item in targets) item.PreInit(pipeline); @@ -44,10 +44,10 @@ namespace DCFApixels.DragonECS #if DEBUG && !DISABLE_DEBUG protected override void OnSetup() { - _markers = new EcsProfilerMarker[Targets.Length]; - for (int i = 0; i < Targets.Length; i++) + _markers = new EcsProfilerMarker[targets.Length]; + for (int i = 0; i < targets.Length; i++) { - _markers[i] = new EcsProfilerMarker(EcsDebug.RegisterMark($"EcsRunner.{Targets[i].GetType().Name}.{nameof(PreInit)}")); + _markers[i] = new EcsProfilerMarker(EcsDebug.RegisterMark($"EcsRunner.{targets[i].GetType().Name}.{nameof(PreInit)}")); } } #endif @@ -61,10 +61,10 @@ namespace DCFApixels.DragonECS public void Init(EcsPipeline pipeline) { #if DEBUG && !DISABLE_DEBUG - for (int i = 0; i < Targets.Length && Targets.Length <= _markers.Length; i++) + for (int i = 0; i < targets.Length && targets.Length <= _markers.Length; i++) { using (_markers[i].Auto()) - Targets[i].Init(pipeline); + targets[i].Init(pipeline); } #else foreach (var item in targets) item.Init(pipeline); @@ -74,10 +74,10 @@ namespace DCFApixels.DragonECS #if DEBUG && !DISABLE_DEBUG protected override void OnSetup() { - _markers = new EcsProfilerMarker[Targets.Length]; - for (int i = 0; i < Targets.Length; i++) + _markers = new EcsProfilerMarker[targets.Length]; + for (int i = 0; i < targets.Length; i++) { - _markers[i] = new EcsProfilerMarker(EcsDebug.RegisterMark($"EcsRunner.{Targets[i].GetType().Name}.{nameof(Init)}")); + _markers[i] = new EcsProfilerMarker(EcsDebug.RegisterMark($"EcsRunner.{targets[i].GetType().Name}.{nameof(Init)}")); } } #endif @@ -91,10 +91,10 @@ namespace DCFApixels.DragonECS public void Run(EcsPipeline pipeline) { #if DEBUG && !DISABLE_DEBUG - for (int i = 0; i < Targets.Length && Targets.Length <= _markers.Length; i++) + for (int i = 0; i < targets.Length && targets.Length <= _markers.Length; i++) { using (_markers[i].Auto()) - Targets[i].Run(pipeline); + targets[i].Run(pipeline); } #else @@ -105,10 +105,10 @@ namespace DCFApixels.DragonECS #if DEBUG && !DISABLE_DEBUG protected override void OnSetup() { - _markers = new EcsProfilerMarker[Targets.Length]; - for (int i = 0; i < Targets.Length; i++) + _markers = new EcsProfilerMarker[targets.Length]; + for (int i = 0; i < targets.Length; i++) { - _markers[i] = new EcsProfilerMarker(EcsDebug.RegisterMark($"EcsRunner.{Targets[i].GetType().Name}.{nameof(Run)}")); + _markers[i] = new EcsProfilerMarker(EcsDebug.RegisterMark($"EcsRunner.{targets[i].GetType().Name}.{nameof(Run)}")); } } #endif @@ -122,10 +122,10 @@ namespace DCFApixels.DragonECS public void Destroy(EcsPipeline pipeline) { #if DEBUG && !DISABLE_DEBUG - for (int i = 0; i < Targets.Length && Targets.Length <= _markers.Length; i++) + for (int i = 0; i < targets.Length && targets.Length <= _markers.Length; i++) { using (_markers[i].Auto()) - Targets[i].Destroy(pipeline); + targets[i].Destroy(pipeline); } #else foreach (var item in targets) item.Destroy(pipeline); @@ -135,10 +135,10 @@ namespace DCFApixels.DragonECS #if DEBUG && !DISABLE_DEBUG protected override void OnSetup() { - _markers = new EcsProfilerMarker[Targets.Length]; - for (int i = 0; i < Targets.Length; i++) + _markers = new EcsProfilerMarker[targets.Length]; + for (int i = 0; i < targets.Length; i++) { - _markers[i] = new EcsProfilerMarker(EcsDebug.RegisterMark($"EcsRunner.{Targets[i].GetType().Name}.{nameof(Destroy)}")); + _markers[i] = new EcsProfilerMarker(EcsDebug.RegisterMark($"EcsRunner.{targets[i].GetType().Name}.{nameof(Destroy)}")); } } #endif diff --git a/src/Builtin/InjectSystem.cs b/src/Builtin/InjectSystem.cs index 3ecd21b..eb1d4bc 100644 --- a/src/Builtin/InjectSystem.cs +++ b/src/Builtin/InjectSystem.cs @@ -4,6 +4,11 @@ using System.Linq; namespace DCFApixels.DragonECS { + //TODO развить идею инжектов + //1) добавить расширенный метод инжекта, с 2 джинерик-аргументами, первый базовый тип и второй инжектируемый тип. + //напримере это будет работать так Inject делает инжект объекта типа Foo для систем с IEcsInject или с IEcsInject + //2) добавить контейнер, который автоматически создается, собирает в себя все пре-инжекты и авто-инжектится во все системы. + //но это спорная идея namespace Internal { internal class PreInitInjectController diff --git a/src/Builtin/Queries.cs b/src/Builtin/Queries.cs new file mode 100644 index 0000000..1e42e07 --- /dev/null +++ b/src/Builtin/Queries.cs @@ -0,0 +1,29 @@ +namespace DCFApixels.DragonECS +{ + + public sealed class EmptyQuery : EcsQueryBase + { + private long _whereVersion; + + public override long WhereVersion => _whereVersion; + + public EmptyQuery(Builder b) { } + + public sealed override WhereResult Where() + { + groupFilter = source.Entities.GetGroupInternal(); + return new WhereResult(this, ++_whereVersion); + } + + protected sealed override void OnBuild(Builder b) { } + } + public static partial class EcsWorldExtensions + { + public static WhereResult WhereAll(this EcsWorld self) => self.Select().Where(); + } + + public sealed class HierarchyQuery : EcsJoinAttachQuery + { + public HierarchyQuery(Builder b) { } + } +} diff --git a/src/EcsGroup.cs b/src/EcsGroup.cs index 47dd3bc..ee91202 100644 --- a/src/EcsGroup.cs +++ b/src/EcsGroup.cs @@ -46,7 +46,7 @@ namespace DCFApixels.DragonECS #region Methods [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool Contains(int entityID) => _source.Contains(entityID); + public bool Contains(int entityID) => _source.Has(entityID); [MethodImpl(MethodImplOptions.AggressiveInlining)] public EcsGroup.Enumerator GetEnumerator() => _source.GetEnumerator(); [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -152,16 +152,17 @@ namespace DCFApixels.DragonECS //защита от криворукости //перед сборкой мусора снова создает сильную ссылку и возвращает в пул - //TODO переделат ьиил удалить, так как сборщик мусора просыпается только после 12к и более экземпляров, только тогда и вызывается финализатор, слишком жирно + //TODO переделат или удалить, так как сборщик мусора просыпается только после 12к и более экземпляров, только тогда и вызывается финализатор, слишком жирно ~EcsGroup() { Release(); } #endregion - #region Contains + #region Has + //TODO переименовать в Has [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool Contains(int entityID) + public bool Has(int entityID) { return _sparse[entityID] > 0; } @@ -179,7 +180,7 @@ namespace DCFApixels.DragonECS public void UncheckedAdd(int entityID) => AddInternal(entityID); public void Add(int entityID) { - if (Contains(entityID)) return; + if (Has(entityID)) return; AddInternal(entityID); } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -204,7 +205,7 @@ namespace DCFApixels.DragonECS public void UncheckedRemove(int entityID) => RemoveInternal(entityID); public void Remove(int entityID) { - if (!Contains(entityID)) return; + if (!Has(entityID)) return; RemoveInternal(entityID); } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -234,6 +235,15 @@ namespace DCFApixels.DragonECS } _delayedOps[_delayedOpsCount++] = entityID | isAddBitFlag; // delayedOp = entityID add isAddBitFlag } + + public void RemoveUnusedEntityIDs() + { + foreach (var e in this) + { + if (!_source.IsUsed(e)) + AggressiveRemove(e); + } + } #endregion #region Sort/Clear @@ -290,7 +300,7 @@ namespace DCFApixels.DragonECS if (_source != group.World) throw new ArgumentException("WorldIndex != groupFilter.WorldIndex"); #endif foreach (var item in group) - if (!Contains(item)) + if (!Has(item)) AggressiveAdd(item); } @@ -304,7 +314,7 @@ namespace DCFApixels.DragonECS if (_source != group.World) throw new ArgumentException("WorldIndex != groupFilter.WorldIndex"); #endif foreach (var item in this) - if (group.Contains(item)) + if (group.Has(item)) AggressiveRemove(item); } @@ -318,7 +328,7 @@ namespace DCFApixels.DragonECS if (World != group.World) throw new ArgumentException("WorldIndex != groupFilter.WorldIndex"); #endif foreach (var item in this) - if (!group.Contains(item)) + if (!group.Has(item)) AggressiveRemove(item); } @@ -332,7 +342,7 @@ namespace DCFApixels.DragonECS if (_source != group.World) throw new ArgumentException("WorldIndex != groupFilter.WorldIndex"); #endif foreach (var item in group) - if (Contains(item)) + if (Has(item)) AggressiveRemove(item); else AggressiveAdd(item); @@ -349,7 +359,7 @@ namespace DCFApixels.DragonECS #endif EcsGroup result = a._source.GetGroupFromPool(); foreach (var item in a) - if (!b.Contains(item)) + if (!b.Has(item)) result.AggressiveAdd(item); a._source.ReleaseGroup(a); return result; @@ -363,7 +373,7 @@ namespace DCFApixels.DragonECS #endif EcsGroup result = a._source.GetGroupFromPool(); foreach (var item in a) - if (b.Contains(item)) + if (b.Has(item)) result.AggressiveAdd(item); a._source.ReleaseGroup(a); return result; @@ -456,7 +466,7 @@ namespace DCFApixels.DragonECS if (other.Count != Count) return false; foreach (var item in other) - if (!Contains(item)) + if (!Has(item)) return false; return true; } diff --git a/src/EcsJoinQuery.cs b/src/EcsJoinQuery.cs index c84237a..068c8ca 100644 --- a/src/EcsJoinQuery.cs +++ b/src/EcsJoinQuery.cs @@ -1,13 +1,17 @@ -using Unity.Profiling; +using System; +using System.Xml.Schema; +using Unity.Profiling; +using UnityEditor.Search; +using UnityEngine; namespace DCFApixels.DragonECS { - public abstract class EcsJoinQueryBase : EcsQueryBase + public abstract class EcsJoinAttachQueryBase : EcsQueryBase { - public abstract void ExecuteJoin(); + public abstract void Join(WhereResult targetWorldWhereQuery); } - public abstract class EcsJoinAttachQuery : EcsJoinQueryBase - where TAttachComponent : struct, IEcsAttachComponent + public abstract class EcsJoinAttachQuery : EcsJoinAttachQueryBase + where TAttachComponent : struct, IEcsAttachComponent { private EcsWorld _targetWorld; private EcsAttachPool _targetPool; @@ -19,39 +23,49 @@ namespace DCFApixels.DragonECS private int[] _counts; private EntityLinkedList _linkedBasket; - private bool _isJoinExecuted = false; + private bool _isInitTargetWorld = false; - private bool _isInitTargetWorlds = false; + private long _executeWhereVersion = 0; + private long _executeJoinVersion = 0; - private ProfilerMarker _execute = new ProfilerMarker("Query.ExecuteJoin"); + private ProfilerMarker _executeWhere = new ProfilerMarker("JoinAttachQuery.Where"); + private ProfilerMarker _executeJoin = new ProfilerMarker("JoinAttachQuery.Join"); #region Properties public EcsWorld AttachWorld => _targetWorld; public EcsAttachPool Attach => _targetPool; - public bool IsJoinExecuted => _isJoinExecuted; + + public sealed override long WhereVersion => _executeWhereVersion; + public long JoinVersion => _executeJoinVersion; #endregion protected sealed override void OnBuild(Builder b) { _targetPool = b.Include(); } - public sealed override void ExecuteWhere() + public sealed override WhereResult Where() { - //ExecuteWhere(_targetPool.Entities, groupFilter); - ExecuteWhere(World.Entities, groupFilter); + using (_executeWhere.Auto()) + { + _executeWhereVersion++; + ExecuteWhere(_targetPool.Entities, groupFilter); + return new WhereResult(this, WhereVersion); + } } - public sealed override void ExecuteJoin() + public sealed override void Join(WhereResult targetWorldWhereQuery) { - _execute.Begin(); - - _isJoinExecuted = false; - if (_isInitTargetWorlds == false) - { - InitTargetWorlds(); - if (_isInitTargetWorlds == false) - return; - } + _executeJoin.Begin(); +#if (DEBUG && !DISABLE_DEBUG) || !DRAGONECS_NO_SANITIZE_CHECKS + if (targetWorldWhereQuery.IsNull) + throw new ArgumentNullException();//TODO составить текст исключения. +#endif + if (!_isInitTargetWorld) + InitTargetWorlds(targetWorldWhereQuery.World); +#if (DEBUG && !DISABLE_DEBUG) || !DRAGONECS_NO_SANITIZE_CHECKS + else + if (_targetWorld != targetWorldWhereQuery.World) throw new ArgumentException();//TODO составить текст исключения. это проверка на то что пользователь использует правильный мир +#endif //Подготовка массивов if (_targetWorldCapacity < _targetWorld.Capacity) @@ -73,19 +87,137 @@ namespace DCFApixels.DragonECS _linkedBasket.Clear(); //Конец подготовки массивов - ExecuteWhere(); + Where(); foreach (var attachID in groupFilter) { EcsEntity attachTarget = _targetPool.Read(attachID).Target; - if (!attachTarget.IsAlive)//TODO пофиксить IsAlive + if (!attachTarget.IsAlive) { //_targetPool.Del(attachID); continue; } int attachTargetID = attachTarget.id; - ref int nodeIndex = ref _mapping[attachTargetID]; - if(nodeIndex <= 0) + ref int nodeIndex = ref _mapping[attachTargetID]; + if (nodeIndex <= 0) + nodeIndex = _linkedBasket.Add(attachID); + else + _linkedBasket.Insert(nodeIndex, attachID); + _counts[attachTargetID]++; + } + + _executeJoinVersion++; + _executeJoin.End(); + } + private void InitTargetWorlds(EcsWorld targetWorld) + { + _targetWorld = targetWorld; + + _targetWorldCapacity = _targetWorld.Capacity; + _mapping = new int[_targetWorldCapacity]; + _counts = new int[_targetWorldCapacity]; + + _targetPoolCapacity = _targetPool.Capacity; + _linkedBasket = new EntityLinkedList(_targetPoolCapacity); + _isInitTargetWorld = true; + } + + public bool Has(int attachedEnttiyID) => groupFilter.Has(attachedEnttiyID); + public EntityLinkedList.EnumerableSpan GetNodes(int entityID) => _linkedBasket.Span(_mapping[entityID], _counts[entityID]); + public int GetNode(int entityID) => _counts[entityID] > 0 ? _linkedBasket.Get(_mapping[entityID]) : 0; + public int GetNodesCount(int entityID) => _counts[entityID]; + public bool HasNode(int entityID, int attachedEntityID) => groupFilter.Has(attachedEntityID) && _targetPool.Read(attachedEntityID).Target.id == entityID; + } + /* + public abstract class EcsJoinRelationQuery : EcsQueryBase + where TRelationComponent : struct, IEcsRelationComponent + { + private EcsWorld _firstWorld; + private EcsWorld _secondWorld; + private EcsRelationPool _targetPool; + + internal int _targetPoolCapacity = -1; + + private bool _isInitTargetWorlds = false; + private bool _isJoinExecuted = false; + + private long _executeWhereVersion = 0; + private long _executeJoinVersion = 0; + + public readonly Orientation ToSecond = new Orientation(); + public readonly Orientation ToFirst = new Orientation(); + + private ProfilerMarker _executeWhere = new ProfilerMarker("JoinRelationQuery.Where"); + private ProfilerMarker _executeJoin = new ProfilerMarker("JoinRelationQuery.Join"); + + #region Properties + public EcsWorld RelationFirstWorld => _firstWorld; + public EcsWorld RelationSecondWorld => _secondWorld; + public EcsRelationPool Relation => _targetPool; + public bool IsMonoWorldRelation => _firstWorld == _secondWorld; + + public sealed override long WhereVersion => _executeWhereVersion; + public long JoinVersion => _executeJoinVersion; + #endregion + + protected sealed override void OnBuild(Builder b) + { + _targetPool = b.Include(); + } + public sealed override WhereResult Where() + { + using (_executeWhere.Auto()) + { + _executeWhereVersion++; + ExecuteWhere(_targetPool.Entities, groupFilter); + return new WhereResult(this, WhereVersion); + } + } + + public void Join(WhereResult firstWorldWhereQuery, WhereResult secondWorldWhereQuery) + { + _executeJoin.Begin(); + _isJoinExecuted = false; + if (_isInitTargetWorlds == false) + { + InitTargetWorlds(); + if (_isInitTargetWorlds == false) + return; + }; + + // //Подготовка массивов + // if (_targetWorldCapacity < _targetWorld.Capacity) + // { + // _targetWorldCapacity = _targetWorld.Capacity; + // _mapping = new int[_targetWorldCapacity]; + // _counts = new int[_targetWorldCapacity]; + // } + // else + // { + // ArrayUtility.Fill(_counts, 0); + // ArrayUtility.Fill(_mapping, 0); + // } + // if (_targetPoolCapacity < _targetPool.Capacity) + // { + // _targetPoolCapacity = _targetPool.Capacity; + // _linkedBasket.Resize(_targetPoolCapacity); + // } + // _linkedBasket.Clear(); + // //Конец подготовки массивов + + Where(); + foreach (var attachID in groupFilter) + { + EcsEntity attachTarget = _targetPool.Read(attachID).First; + if (!attachTarget.IsAlive) + { + //_targetPool.Del(attachID); + continue; + } + int attachTargetID = attachTarget.id; + + ref int nodeIndex = ref _mapping[attachTargetID]; + if (nodeIndex <= 0) nodeIndex = _linkedBasket.Add(attachID); else _linkedBasket.Insert(nodeIndex, attachID); @@ -93,66 +225,12 @@ namespace DCFApixels.DragonECS } _isJoinExecuted = true; - _execute.End(); - } - private void InitTargetWorlds() - { - foreach (var e in _targetPool.Entities) - { - ref readonly var rel = ref _targetPool.Read(e); - //if (rel.Target.IsNotNull) - _targetWorld = EcsWorld.Worlds[rel.Target.world]; + _executeJoinVersion++; + _executeJoin.End(); - if (_targetWorld != null) - { - _isInitTargetWorlds = true; - break; - } - } - - if (_isInitTargetWorlds) - { - _targetWorldCapacity = _targetWorld.Capacity; - _mapping = new int[_targetWorldCapacity]; - _counts = new int[_targetWorldCapacity]; - - _targetPoolCapacity = _targetPool.Capacity; - //_entites = new int[_targetPoolCapacity]; - _linkedBasket = new EntityLinkedList(_targetPoolCapacity); - } - } - public EcsGroup.Enumerator GetEnumerator() - { - return groupFilter.GetEnumerator(); - } - public EntityLinkedList.EnumerableSpan GetNodes(int entityID) => _linkedBasket.Span(_mapping[entityID], _counts[entityID]); - } - public abstract class EcsJoinRelationQuery : EcsJoinQueryBase - where TRelationComponent : struct, IEcsRelationComponent - { - private EcsWorld _firstWorld; - private EcsWorld _secondWorld; - private EcsRelationPool _targetPool; - private bool _isInitTargetWorlds = false; - - #region Properties - public EcsWorld RelationFirstWorld => _firstWorld; - public EcsWorld RelationSecondWorld => _secondWorld; - public EcsRelationPool Relation => _targetPool; - public bool IsMonoWorldRelation => _firstWorld == _secondWorld; - #endregion - - protected sealed override void OnBuild(Builder b) - { - _targetPool = b.Include(); - } - public sealed override void ExecuteWhere() - { - ExecuteWhere(_targetPool.Entites, groupFilter); - } - public sealed override void ExecuteJoin() - { - if (_isInitTargetWorlds == false) InitTargetWorlds(); + _executeJoinVersion++; + _isJoinExecuted = true; + _executeJoin.End(); } private void InitTargetWorlds() @@ -170,10 +248,78 @@ namespace DCFApixels.DragonECS break; } } + + if (_isInitTargetWorlds) + { + _targetWorldCapacity = _targetWorld.Capacity; + _mapping = new int[_targetWorldCapacity]; + _counts = new int[_targetWorldCapacity]; + + _targetPoolCapacity = _targetPool.Capacity; + _linkedBasket = new EntityLinkedList(_targetPoolCapacity); + } } - public EcsGroup.Enumerator GetEnumerator() + + + public bool Has(int relationEntityID) => groupFilter.Has(relationEntityID); + + + public class Orientation { - return groupFilter.GetEnumerator(); + internal int targetWorldCapacity = -1; + + internal int[] mapping; + internal int[] counts; + internal EntityLinkedList linkedBasket; + + public bool HasRelation(int fromEntityID, int toEntityID) + { + throw new NotImplementedException(); + } + public bool HasNode(int fromEntityID, int toEntityID) + { + throw new NotImplementedException(); + } + public EntityLinkedList.EnumerableSpan GetRelations(int fromEntityID) => linkedBasket.Span(mapping[fromEntityID], counts[fromEntityID]); + public int GetRelation(int fromEntityID) => counts[fromEntityID] > 0 ? linkedBasket.Get(mapping[fromEntityID]) : 0; + public int GetRelationsCount(int fromEntityID) => counts[fromEntityID]; } + }*/ + + #region Extensions + public static class EcsJoinQueryBaseExtensions + { + public static void Join(this EcsJoinAttachQueryBase self, EcsWorld targetWorld) + { + self.Join(targetWorld.Where()); + } + + public static TQuery Join(this EcsWorld self, EcsWorld targetWorld, out TQuery query) where TQuery : EcsJoinAttachQueryBase + { + return self.Join(targetWorld.WhereAll(), out query); + } + public static TQuery Join(this EcsWorld self, EcsWorld targetWorld) where TQuery : EcsJoinAttachQueryBase + { + return self.Join(targetWorld.WhereAll()); + } + /* public static class EcsJoinRelationQueryExtensions + { + public static void Join(this EcsJoinRelationQuery self, EcsWorld firstWorld, EcsWorld secondWorld) + where TRelationComponent : struct, IEcsRelationComponent + { + self.Join(firstWorld.Where(), secondWorld.Where()); + } + public static void Join(this EcsJoinRelationQuery self, EcsWorld firstWorld, WhereResult secondWorldWhereQuery) + where TRelationComponent : struct, IEcsRelationComponent + { + self.Join(firstWorld.Where(), secondWorldWhereQuery); + } + public static void Join(this EcsJoinRelationQuery self, WhereResult firstWorldWhereQuery, EcsWorld secondWorld) + where TRelationComponent : struct, IEcsRelationComponent + { + self.Join(firstWorldWhereQuery, secondWorld.Where()); + } + }*/ } + #endregion } diff --git a/src/EcsQuery.cs b/src/EcsQuery.cs index 21c0c25..5fef1bd 100644 --- a/src/EcsQuery.cs +++ b/src/EcsQuery.cs @@ -1,6 +1,9 @@ using System; using System.Collections.Generic; +using System.Drawing; using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using Unity.Profiling; namespace DCFApixels.DragonECS @@ -11,15 +14,19 @@ namespace DCFApixels.DragonECS internal EcsGroup groupFilter; internal EcsQueryMask mask; + private bool _isInit; + #region Properties - internal EcsQueryMask Mask => mask; + public EcsQueryMask Mask => mask; public EcsWorld World => source; + public bool IsInit => _isInit; + + public abstract long WhereVersion { get; } #endregion #region Builder protected virtual void Init(Builder b) { } protected abstract void OnBuild(Builder b); - public abstract void ExecuteWhere(); public sealed class Builder : EcsQueryBuilderBase { private EcsWorld _world; @@ -53,6 +60,7 @@ namespace DCFApixels.DragonECS newQuery.source = world; newQuery.OnBuild(builder); builder.End(out newQuery.mask); + newQuery._isInit = true; return (TQuery)(object)newQuery; } @@ -82,10 +90,11 @@ namespace DCFApixels.DragonECS } } #endregion + public abstract WhereResult Where(); protected void ExecuteWhere(EcsReadonlyGroup group, EcsGroup result) { - var pools = World.Pools; + var pools = World.pools; result.Clear(); foreach (var e in group) { @@ -103,6 +112,7 @@ namespace DCFApixels.DragonECS next: continue; } } + //protected void IsMaskCompatible protected void ExecuteWhereAndSort(EcsReadonlyGroup group, EcsGroup result) { ExecuteWhere(group, result); @@ -110,20 +120,38 @@ namespace DCFApixels.DragonECS } } + //TODO есть идея проверки того что запросбылвыполнен путем создания ref struct которыйсодержит результат выполнения заапроса и существует только в стеке. + //таким образом для каждой системы он будет выполняться единожды, без холостых перезапусков, скорее всего эта система будет работать лучше чем "Версии запросов" public abstract class EcsQuery : EcsQueryBase { - private ProfilerMarker _execute = new ProfilerMarker("EcsQuery.ExecuteWhere"); + private ProfilerMarker _execute = new ProfilerMarker("EcsQuery.Where"); + + private long _executeWhereVersion = 0; + + #region Properties + //на данный момент бесполное свойство, основная идея, реализовать все методы запроса с аргументом (..., ref long version), + //далее в теле метододов сравнивать с текущей версией, и если они отличаются, выполнять запрос и перезаписывать значение version + //таким образом можно добиться сокращение "холостых" выполнений запроса, тоесть в рамках одной системы запрос всегда будет выполняться один раз + //даже при повторном вызове. + //Но нужно добавить метод для принудительного повторения запроса без сравнения вресий. + //TODO реализовать описанное выше поведение + //TODO проверить что лучше подходит int или long. long делает этот механизм очень надежным, но возможно его использование будет существенно влиять на производительность. + public sealed override long WhereVersion => _executeWhereVersion; + #endregion + protected sealed override void OnBuild(Builder b) { } - public sealed override void ExecuteWhere() + public sealed override WhereResult Where() { using (_execute.Auto()) { ExecuteWhereAndSort(World.Entities, groupFilter); + return new WhereResult(this, ++_executeWhereVersion); } } - public EcsGroup.Enumerator GetEnumerator() + + public bool Has(int entityID) { - return groupFilter.GetEnumerator(); + return groupFilter.Has(entityID); } } @@ -142,4 +170,26 @@ namespace DCFApixels.DragonECS public abstract TPool Exclude() where TComponent : struct where TPool : EcsPoolBase, new(); public abstract TPool Optional() where TComponent : struct where TPool : EcsPoolBase, new(); } + + [StructLayout(LayoutKind.Sequential, Pack = 8, Size = 16)] + public readonly ref struct WhereResult + { + public readonly EcsQueryBase query; //ref = 8 byte + public readonly long version; //long = 8 byte + + #region Properties + public bool IsNull => query == null; + public EcsWorld World => query.World; + public bool IsActual => query.WhereVersion == version; + #endregion + + public WhereResult(EcsQueryBase query, long version) + { + this.query = query; + this.version = version; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public EcsGroup.Enumerator GetEnumerator() => query.groupFilter.GetEnumerator(); + } } diff --git a/src/EcsWorld.cs b/src/EcsWorld.cs index a26559d..d940912 100644 --- a/src/EcsWorld.cs +++ b/src/EcsWorld.cs @@ -9,12 +9,12 @@ namespace DCFApixels.DragonECS { private const short GEN_BITS = 0x7fff; private const short DEATH_GEN_BIT = short.MinValue; + private const int DEL_ENT_BUFFER_SIZE_OFFSET = 2; public static EcsWorld[] Worlds = new EcsWorld[8]; private static IntDispenser _worldIdDispenser = new IntDispenser(0); public readonly short uniqueID; - private const int DEL_ENT_BUFFER_SIZE_OFFSET = 2; private int _worldArchetypeID; private IntDispenser _entityDispenser; @@ -30,7 +30,7 @@ namespace DCFApixels.DragonECS private int[] _delEntBuffer; private int _delEntBufferCount; - private EcsPoolBase[] _pools; + internal EcsPoolBase[] pools; private EcsNullPool _nullPool; private EcsQueryBase[] _queries; @@ -43,12 +43,6 @@ namespace DCFApixels.DragonECS private IEcsEntityCreate _entityCreate; private IEcsEntityDestroy _entityDestry; - #region GetterMethods - public ReadOnlySpan GetAllPools() => new ReadOnlySpan(_pools); - public int GetComponentID() => WorldMetaStorage.GetComponentId(_worldArchetypeID);////ComponentType.uniqueID; - - #endregion - #region Properties public abstract Type Archetype { get; } public int UniqueID => uniqueID; @@ -56,14 +50,7 @@ namespace DCFApixels.DragonECS public int Capacity => _entitesCapacity; //_denseEntities.Length; public EcsPipeline Pipeline => _pipeline; public EcsReadonlyGroup Entities => _allEntites.Readonly; - #endregion - - #region Internal Properties - internal EcsPoolBase[] Pools - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => _pools; - } + public ReadOnlySpan AllPools => pools; #endregion #region Constructors/Destroy @@ -82,8 +69,8 @@ namespace DCFApixels.DragonECS if (!_pipeline.IsInit) pipline.Init(); _entityDispenser = new IntDispenser(0); _nullPool = EcsNullPool.instance; - _pools = new EcsPoolBase[512]; - ArrayUtility.Fill(_pools, _nullPool); + pools = new EcsPoolBase[512]; + ArrayUtility.Fill(pools, _nullPool); _gens = new short[_entitesCapacity]; ArrayUtility.Fill(_gens, DEATH_GEN_BIT); @@ -105,7 +92,7 @@ namespace DCFApixels.DragonECS _entityDispenser = null; //_denseEntities = null; _gens = null; - _pools = null; + pools = null; _nullPool = null; _queries = null; @@ -119,38 +106,37 @@ namespace DCFApixels.DragonECS } #endregion + #region GetComponentID + public int GetComponentID() => WorldMetaStorage.GetComponentId(_worldArchetypeID);////ComponentType.uniqueID; + + #endregion + #region GetPool public TPool GetPool() where TComponent : struct where TPool : EcsPoolBase, new() { int uniqueID = WorldMetaStorage.GetComponentId(_worldArchetypeID); - if (uniqueID >= _pools.Length) + if (uniqueID >= pools.Length) { - int oldCapacity = _pools.Length; - Array.Resize(ref _pools, _pools.Length << 1); - ArrayUtility.Fill(_pools, _nullPool, oldCapacity, oldCapacity - _pools.Length); + int oldCapacity = pools.Length; + Array.Resize(ref pools, pools.Length << 1); + ArrayUtility.Fill(pools, _nullPool, oldCapacity, oldCapacity - pools.Length); } - if (_pools[uniqueID] == _nullPool) + if (pools[uniqueID] == _nullPool) { var pool = new TPool(); - _pools[uniqueID] = pool; + pools[uniqueID] = pool; pool.InvokeInit(this); //EcsDebug.Print(pool.GetType().FullName); } - return (TPool)_pools[uniqueID]; + return (TPool)pools[uniqueID]; } #endregion #region Queries - public TQuery Where(out TQuery query) where TQuery : EcsQuery - { - query = Select(); - query.ExecuteWhere(); - return query; - } public TQuery Select() where TQuery : EcsQueryBase { int uniqueID = WorldMetaStorage.GetQueryId(_worldArchetypeID); @@ -160,6 +146,28 @@ namespace DCFApixels.DragonECS _queries[uniqueID] = EcsQueryBase.Builder.Build(this); return (TQuery)_queries[uniqueID]; } + public WhereResult Where(out TQuery query) where TQuery : EcsQueryBase + { + query = Select(); + return query.Where(); + } + public WhereResult Where() where TQuery : EcsQueryBase + { + return Select().Where(); + } + + public TQuery Join(WhereResult targetWorldWhereQuery, out TQuery query) where TQuery : EcsJoinAttachQueryBase + { + query = Select(); + query.Join(targetWorldWhereQuery); + return query; + } + public TQuery Join(WhereResult targetWorldWhereQuery) where TQuery : EcsJoinAttachQueryBase + { + TQuery query = Select(); + query.Join(targetWorldWhereQuery); + return query; + } #endregion #region IsMaskCompatible @@ -171,12 +179,12 @@ namespace DCFApixels.DragonECS #endif for (int i = 0, iMax = mask.Inc.Length; i < iMax; i++) { - if (!_pools[mask.Inc[i]].Has(entityID)) + if (!pools[mask.Inc[i]].Has(entityID)) return false; } for (int i = 0, iMax = mask.Exc.Length; i < iMax; i++) { - if (_pools[mask.Exc[i]].Has(entityID)) + if (pools[mask.Exc[i]].Has(entityID)) return false; } return true; @@ -208,12 +216,11 @@ namespace DCFApixels.DragonECS _groups.RemoveAt(last); } } - foreach (var item in _pools) + foreach (var item in pools) item.InvokeOnWorldResize(_gens.Length); } _gens[entityID] &= GEN_BITS; EcsEntity entity = new EcsEntity(entityID, ++_gens[entityID], uniqueID); - // UnityEngine.Debug.Log($"{entityID} {_gens[entityID]} {uniqueID}"); _entityCreate.OnEntityCreate(entity); _allEntites.Add(entityID); return entity; @@ -227,26 +234,20 @@ namespace DCFApixels.DragonECS _entityDestry.OnEntityDestroy(entity); if (_delEntBufferCount >= _delEntBuffer.Length) - ReleaseDelEntBuffer(); + ReleaseDelEntityBuffer(); } - - private void ReleaseDelEntBuffer()//TODO проверить что буфер удаления работает нормально + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public EcsEntity GetEcsEntity(int entityID) => new EcsEntity(entityID, _gens[entityID], uniqueID); //TODO придумать получше имя метода + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool IsAlive(int entityID, short gen) => _gens[entityID] == gen; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool IsUsed(int entityID) => (_gens[entityID] | DEATH_GEN_BIT) == 0; + public void ReleaseDelEntityBuffer() { for (int i = 0; i < _delEntBufferCount; i++) _entityDispenser.Release(_delEntBuffer[i]); _delEntBufferCount = 0; } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public EcsEntity GetEcsEntity(int entityID) - { - return new EcsEntity(entityID, _gens[entityID], uniqueID); - } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool EntityIsAlive(int entityID, short gen) //TODO пофиксить EntityIsAlive - { - return _gens[entityID] == gen; - } #endregion #region Groups diff --git a/src/Entities/EcsEntity.cs b/src/Entities/EcsEntity.cs index 4e939a1..7ee470d 100644 --- a/src/Entities/EcsEntity.cs +++ b/src/Entities/EcsEntity.cs @@ -8,7 +8,7 @@ namespace DCFApixels.DragonECS // gen - 16 bits // world - 16 bits /// Strong identifier/Permanent entity identifier - [StructLayout(LayoutKind.Explicit, Pack =2, Size = 8)] + [StructLayout(LayoutKind.Explicit, Pack = 2, Size = 8)] public readonly partial struct EcsEntity : IEquatable, IEquatable { public static readonly EcsEntity NULL = default; @@ -22,9 +22,9 @@ namespace DCFApixels.DragonECS public readonly short world; //[MethodImpl(MethodImplOptions.AggressiveInlining)] - //public ent ToEnt() => EcsWorld.Worlds[world].EntityIsAlive(id, gen) ? new ent(id) : default; + //public ent ToEnt() => EcsWorld.Worlds[world].IsAlive(id, gen) ? new ent(id) : default; - public bool IsAlive => EcsWorld.Worlds[world].EntityIsAlive(id, gen); + public bool IsAlive => EcsWorld.Worlds[world].IsAlive(id, gen); #region Constructors [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -104,7 +104,7 @@ namespace DCFApixels.DragonECS // { // //using (_IsAliveMarker.Auto()) // //{ - // bool result = EcsWorld.Worlds[self.world].EntityIsAlive(self.id, self.gen); + // bool result = EcsWorld.Worlds[self.world].IsAlive(self.id, self.gen); // if (!result) self = EcsEntity.NULL; // return result; // //} diff --git a/src/Pools/EcsAttachPool.cs b/src/Pools/EcsAttachPool.cs index 3bd6479..05229cc 100644 --- a/src/Pools/EcsAttachPool.cs +++ b/src/Pools/EcsAttachPool.cs @@ -15,7 +15,14 @@ namespace DCFApixels.DragonECS private PoolRunners _poolRunners; private EcsGroup _entities; - public EcsReadonlyGroup Entities => _entities.Readonly; + public EcsReadonlyGroup Entities + { + get + { + _entities.RemoveUnusedEntityIDs(); + return _entities.Readonly; + } + } #if (DEBUG && !DISABLE_DEBUG) || !DRAGONECS_NO_SANITIZE_CHECKS private short _sanitizeTargetWorld = -1; diff --git a/src/Pools/EcsReadonlyPool.cs b/src/Pools/EcsReadonlyPool.cs new file mode 100644 index 0000000..cd5d86f --- /dev/null +++ b/src/Pools/EcsReadonlyPool.cs @@ -0,0 +1,143 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using Unity.Profiling; + +namespace DCFApixels.DragonECS +{ + public sealed class EcsReadonlyPool : EcsPoolBase + where T : struct, IEcsReadonlyComponent + { + public static string name = typeof(T).Name; + + private EcsWorld _source; + + 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; + private PoolRunners _poolRunners; + + #region Properites + public int Count => _itemsCount; + public int Capacity => _items.Length; + public sealed override EcsWorld World => _source; + #endregion + + #region Init + protected override void Init(EcsWorld world) + { + const int capacity = 512; + _source = world; + + _mapping = new int[world.Capacity]; + _recycledItems = new int[128]; + _recycledItemsCount = 0; + _items = new T[capacity]; + _itemsCount = 0; + + _componentResetHandler = EcsComponentResetHandler.instance; + _poolRunners = new PoolRunners(world.Pipeline); + } + #endregion + + #region Write/Read/Has/Del + private ProfilerMarker _addMark = new ProfilerMarker("EcsPoo.Add"); + private ProfilerMarker _writeMark = new ProfilerMarker("EcsPoo.Write"); + private ProfilerMarker _readMark = new ProfilerMarker("EcsPoo.Read"); + private ProfilerMarker _hasMark = new ProfilerMarker("EcsPoo.Has"); + private ProfilerMarker _delMark = new ProfilerMarker("EcsPoo.Del"); + public ref T Add(int entityID) + { + // using (_addMark.Auto()) + // { + ref int itemIndex = ref _mapping[entityID]; + if (itemIndex <= 0) + { + if (_recycledItemsCount > 0) + { + itemIndex = _recycledItems[--_recycledItemsCount]; + _itemsCount++; + } + else + { + itemIndex = ++_itemsCount; + if (itemIndex >= _items.Length) + Array.Resize(ref _items, _items.Length << 1); + } + + //_mapping[entityID] = itemIndex; TODO проверить что это лишнее дейсвие + _poolRunners.add.OnComponentAdd(entityID); + } + else + { + _componentResetHandler.Reset(ref _items[itemIndex]); + } + _poolRunners.write.OnComponentWrite(entityID); + return ref _items[itemIndex]; + // } + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ref readonly T Read(int entityID) + { + // using (_readMark.Auto()) + return ref _items[_mapping[entityID]]; + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public sealed override bool Has(int entityID) + { + // using (_hasMark.Auto()) + return _mapping[entityID] > 0; + } + public void Del(int entityID) + { + // using (_delMark.Auto()) + // { + ref int itemIndex = ref _mapping[entityID]; + _componentResetHandler.Reset(ref _items[itemIndex]); + if (_recycledItemsCount >= _recycledItems.Length) + Array.Resize(ref _recycledItems, _recycledItems.Length << 1); + _recycledItems[_recycledItemsCount++] = itemIndex; + itemIndex = 0; + _itemsCount--; + _poolRunners.del.OnComponentDel(entityID); + // } + } + #endregion + + #region WorldCallbacks + protected override void OnWorldResize(int newSize) + { + Array.Resize(ref _mapping, newSize); + } + protected override void OnDestroy() { } + + #endregion + } + + public interface IEcsReadonlyComponent { } + public static class EcsReadonlyPoolExt + { + public static EcsReadonlyPool GetPool(this EcsWorld self) where TReadolnyComponent : struct, IEcsReadonlyComponent + { + return self.GetPool>(); + } + + public static EcsReadonlyPool Include(this EcsQueryBuilderBase self) where TReadolnyComponent : struct, IEcsReadonlyComponent + { + return self.Include>(); + } + public static EcsReadonlyPool Exclude(this EcsQueryBuilderBase self) where TReadolnyComponent : struct, IEcsReadonlyComponent + { + return self.Exclude>(); + } + public static EcsReadonlyPool Optional(this EcsQueryBuilderBase self) where TReadolnyComponent : struct, IEcsReadonlyComponent + { + return self.Optional>(); + } + } +} diff --git a/src/Pools/EcsRelationPool.cs b/src/Pools/EcsRelationPool.cs index c5a5c9c..a59966c 100644 --- a/src/Pools/EcsRelationPool.cs +++ b/src/Pools/EcsRelationPool.cs @@ -15,7 +15,7 @@ namespace DCFApixels.DragonECS private PoolRunners _poolRunners; private EcsGroup _entities; - public EcsReadonlyGroup Entites => _entities.Readonly; + public EcsReadonlyGroup Entities => _entities.Readonly; #if (DEBUG && !DISABLE_DEBUG) || !DRAGONECS_NO_SANITIZE_CHECKS private short _sanitizeFirstWorld = -1; diff --git a/src/Pools/EcsSinglePool.cs b/src/Pools/EcsSinglePool.cs index c70a7b1..0122f05 100644 --- a/src/Pools/EcsSinglePool.cs +++ b/src/Pools/EcsSinglePool.cs @@ -21,6 +21,11 @@ namespace DCFApixels.DragonECS private PoolRunners _poolRunners; #region Properites + public ref T Instance + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => ref _component; + } public int Count => _count; public sealed override EcsWorld World => _source; #endregion @@ -53,6 +58,7 @@ namespace DCFApixels.DragonECS return ref _component; // } } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public ref T Write(int entityID) { @@ -91,7 +97,7 @@ namespace DCFApixels.DragonECS protected override void OnDestroy() { } #endregion } - + /// Singleton component public interface IEcsSingleComponent { } public static class EcsSinglePoolExt { diff --git a/src/Utils/EntityLinkedList.cs b/src/Utils/EntityLinkedList.cs index af41450..f801865 100644 --- a/src/Utils/EntityLinkedList.cs +++ b/src/Utils/EntityLinkedList.cs @@ -39,10 +39,8 @@ namespace DCFApixels.DragonECS _count = 0; } - public void Set(int nodeIndex, int entityID) - { - _nodes[nodeIndex].entityID = entityID; - } + public void Set(int nodeIndex, int entityID) => _nodes[nodeIndex].entityID = entityID; + public int Get(int nodeIndex) => _nodes[nodeIndex].entityID; /// Insert after /// new node index