diff --git a/Svelto.Common b/Svelto.Common index 6053063..49d3591 160000 --- a/Svelto.Common +++ b/Svelto.Common @@ -1 +1 @@ -Subproject commit 6053063786f5c0972cfd3bc9067a50e02e3bd1de +Subproject commit 49d3591924c8c45e0ec32655f992480f9419e327 diff --git a/Svelto.ECS/CheckEntityUtilities.cs b/Svelto.ECS/CheckEntityUtilities.cs index a002893..020b4b2 100644 --- a/Svelto.ECS/CheckEntityUtilities.cs +++ b/Svelto.ECS/CheckEntityUtilities.cs @@ -1,11 +1,10 @@ -#if DEBUG && !PROFILE_SVELTO +#if !DEBUG || PROFILE_SVELTO +#define DONT_USE +#endif using System; using System.Collections.Generic; -using Svelto.DataStructures; -#else -using System; using System.Diagnostics; -#endif +using Svelto.DataStructures; namespace Svelto.ECS { @@ -15,65 +14,72 @@ namespace Svelto.ECS /// public partial class EnginesRoot { -#if DEBUG && !PROFILE_SVELTO - void CheckRemoveEntityID(EGID egid, Type entityComponent, string caller = "") +#if DONT_USE + [Conditional("CHECK_ALL")] +#endif + void CheckRemoveEntityID(EGID egid, Type entityDescriptorType, string caller = "") { - if (_idCheckers.TryGetValue(egid.groupID, out var hash)) - { - if (hash.Contains(egid.entityID) == false) - throw new ECSException("Entity with not found ID is about to be removed: id: " - .FastConcat(" caller: ", caller, " ") - .FastConcat(egid.entityID).FastConcat(" groupid: ").FastConcat(egid.groupID.ToName()) - .FastConcat(" type: ").FastConcat(entityComponent != null ? entityComponent.Name : "not available")); + if (_multipleOperationOnSameEGIDChecker.ContainsKey(egid) == true) + throw new ECSException( + "Executing multiple structural changes in one submission on the same entity is not supported " + .FastConcat(" caller: ", caller, " ").FastConcat(egid.entityID).FastConcat(" groupid: ") + .FastConcat(egid.groupID.ToName()).FastConcat(" type: ") + .FastConcat(entityDescriptorType != null ? entityDescriptorType.Name : "not available") + .FastConcat(" operation was: ") + .FastConcat(_multipleOperationOnSameEGIDChecker[egid] == 1 ? "add" : "remove")); - hash.Remove(egid.entityID); + if (_idChecker.TryGetValue(egid.groupID, out var hash)) + if (hash.Contains(egid.entityID) == false) + throw new ECSException("Trying to remove an Entity never submitted in the database " + .FastConcat(" caller: ", caller, " ").FastConcat(egid.entityID) + .FastConcat(" groupid: ").FastConcat(egid.groupID.ToName()) + .FastConcat(" type: ") + .FastConcat(entityDescriptorType != null + ? entityDescriptorType.Name + : "not available")); + else + hash.Remove(egid.entityID); - if (hash.Count == 0) - _idCheckers.Remove(egid.groupID); - } - else - { - throw new ECSException("Entity with not found ID is about to be removed: id: " - .FastConcat(" caller: ", caller, " ") - .FastConcat(egid.entityID).FastConcat(" groupid: ").FastConcat(egid.groupID.ToName()) - .FastConcat(" type: ").FastConcat(entityComponent != null ? entityComponent.Name : "not available")); - } + _multipleOperationOnSameEGIDChecker.Add(egid, 0); } - - void CheckAddEntityID(EGID egid, Type entityComponent, string caller = "") +#if DONT_USE + [Conditional("CHECK_ALL")] +#endif + void CheckAddEntityID(EGID egid, Type entityDescriptorType, string caller = "") { -// Console.LogError("added ".FastConcat(egid.ToString())); - - if (_idCheckers.TryGetValue(egid.groupID, out var hash) == false) - hash = _idCheckers[egid.groupID] = new HashSet(); - else - if (hash.Contains(egid.entityID)) - throw new ECSException("Entity with used ID is about to be built: '" - .FastConcat("' id: '").FastConcat(egid.entityID).FastConcat("' groupid: '") - .FastConcat(egid.groupID.ToName()).FastConcat(" ", entityComponent != null ? entityComponent.Name : "not available") - .FastConcat("'")); + if (_multipleOperationOnSameEGIDChecker.ContainsKey(egid) == true) + throw new ECSException( + "Executing multiple structural changes in one submission on the same entity is not supported " + .FastConcat(" caller: ", caller, " ").FastConcat(egid.entityID).FastConcat(" groupid: ") + .FastConcat(egid.groupID.ToName()).FastConcat(" type: ") + .FastConcat(entityDescriptorType != null ? entityDescriptorType.Name : "not available") + .FastConcat(" operation was: ") + .FastConcat(_multipleOperationOnSameEGIDChecker[egid] == 1 ? "add" : "remove")); + var hash = _idChecker.GetOrCreate(egid.groupID, () => new HashSet()); + if (hash.Contains(egid.entityID) == true) + throw new ECSException("Trying to add an Entity already submitted to the database " + .FastConcat(" caller: ", caller, " ").FastConcat(egid.entityID) + .FastConcat(" groupid: ").FastConcat(egid.groupID.ToName()).FastConcat(" type: ") + .FastConcat(entityDescriptorType != null + ? entityDescriptorType.Name + : "not available")); hash.Add(egid.entityID); + _multipleOperationOnSameEGIDChecker.Add(egid, 1); + } - void RemoveGroupID(ExclusiveGroupStruct groupID) { _idCheckers.Remove(groupID); } - - readonly FasterDictionary> _idCheckers = new FasterDictionary>(); -#else - [Conditional("_CHECKS_DISABLED")] - void CheckRemoveEntityID(EGID egid, Type entityComponent, string caller = "") - { - } +#if DONT_USE + [Conditional("CHECK_ALL")] +#endif + void RemoveGroupID(BuildGroup groupID) { _idChecker.Remove(groupID); } - [Conditional("_CHECKS_DISABLED")] - void CheckAddEntityID(EGID egid, Type entityComponen, string caller = "") - { - } - - [Conditional("_CHECKS_DISABLED")] - void RemoveGroupID(ExclusiveGroupStruct groupID) - { - } +#if DONT_USE + [Conditional("CHECK_ALL")] #endif + void ClearChecks() { _multipleOperationOnSameEGIDChecker.FastClear(); } + + readonly FasterDictionary _multipleOperationOnSameEGIDChecker = new FasterDictionary(); + readonly FasterDictionary> _idChecker = new FasterDictionary>(); } } \ No newline at end of file diff --git a/Svelto.ECS/ComponentBuilder.CheckFields.cs b/Svelto.ECS/ComponentBuilder.CheckFields.cs index e09f38d..e933d36 100644 --- a/Svelto.ECS/ComponentBuilder.CheckFields.cs +++ b/Svelto.ECS/ComponentBuilder.CheckFields.cs @@ -4,12 +4,11 @@ using System.Diagnostics; #endif using System; using System.Reflection; -using System.Text; using Svelto.Common; namespace Svelto.ECS { - internal static class ComponentBuilderUtilities + static class ComponentBuilderUtilities { const string MSG = "Entity Components and Entity View Components fields cannot hold managed fields outside the Svelto rules."; @@ -18,7 +17,7 @@ namespace Svelto.ECS #endif public static void CheckFields(Type entityComponentType, bool needsReflection, bool isStringAllowed = false) { - if (entityComponentType == ENTITY_STRUCT_INFO_VIEW || entityComponentType == EGIDType || + if (entityComponentType == ENTITY_INFO_COMPONENT || entityComponentType == EGIDType || entityComponentType == EXCLUSIVEGROUPSTRUCTTYPE || entityComponentType == SERIALIZABLE_ENTITY_STRUCT) { return; @@ -80,11 +79,7 @@ namespace Svelto.ECS } } else - if (fieldInfo.FieldType.IsUnmanagedEx() == true) - { - SubCheckFields(fieldInfo.FieldType, entityComponentType, isStringAllowed); - } - else + if (fieldInfo.FieldType.IsUnmanagedEx() == false) { ProcessError("Entity View Components must hold only public interfaces, strings or unmanaged type fields.", entityComponentType); @@ -110,10 +105,10 @@ namespace Svelto.ECS { //pass if it's Primitive or C# 8 unmanaged, or it's a string and string are allowed //this check must allow pointers are they are unmanaged types - if ((isStringAllowed == true && IsString(fieldType) == true) || fieldType.IsUnmanagedEx() == true) + if ((isStringAllowed == true && IsString(fieldType) == true) || fieldType.IsValueTypeEx() == true) { //if it's a struct we have to check the fields recursively - if (IsString(fieldType) == false && !fieldType.IsEnum && fieldType.IsPrimitive == false) + if (IsString(fieldType) == false) { CheckFields(fieldType, false, isStringAllowed); } @@ -142,6 +137,6 @@ namespace Svelto.ECS static readonly Type STRINGTYPE = typeof(string); static readonly Type STRINGBUILDERTYPE = typeof(System.Text.StringBuilder); - internal static readonly Type ENTITY_STRUCT_INFO_VIEW = typeof(EntityInfoViewComponent); + internal static readonly Type ENTITY_INFO_COMPONENT = typeof(EntityInfoComponent); } } \ No newline at end of file diff --git a/Svelto.ECS/ComponentBuilder.cs b/Svelto.ECS/ComponentBuilder.cs index 7089576..a948d2d 100644 --- a/Svelto.ECS/ComponentBuilder.cs +++ b/Svelto.ECS/ComponentBuilder.cs @@ -30,6 +30,7 @@ namespace Svelto.ECS var castedDic = dictionary as ITypeSafeDictionary; T entityComponent = default; + if (IS_ENTITY_VIEW_COMPONENT) { DBC.ECS.Check.Require(castedDic.ContainsKey(egid.entityID) == false, @@ -89,7 +90,7 @@ namespace Svelto.ECS EntityViewComponentCache.InitCache(); else { - if (ENTITY_COMPONENT_TYPE != ComponentBuilderUtilities.ENTITY_STRUCT_INFO_VIEW && ENTITY_COMPONENT_TYPE.IsUnmanagedEx() == false) + if (ENTITY_COMPONENT_TYPE != ComponentBuilderUtilities.ENTITY_INFO_COMPONENT && ENTITY_COMPONENT_TYPE.IsUnmanagedEx() == false) throw new Exception($"Entity Component check failed, unexpected struct type (must be unmanaged) {ENTITY_COMPONENT_TYPE}"); } } @@ -104,7 +105,12 @@ namespace Svelto.ECS static readonly T DEFAULT_IT; static readonly string ENTITY_COMPONENT_NAME; - + /// + /// Note: this static class will hold forever the references of the entities implementors. These references + /// are not even cleared when the engines root is destroyed, as they are static references. + /// It must be seen as an application-wide cache system. Honestly, I am not sure if this may cause leaking + /// issues in some kind of applications. To remember. + /// static class EntityViewComponentCache { internal static readonly FasterList>> cachedFields; diff --git a/Svelto.ECS/EGIDComponent.cs b/Svelto.ECS/Components/EGIDComponent.cs similarity index 100% rename from Svelto.ECS/EGIDComponent.cs rename to Svelto.ECS/Components/EGIDComponent.cs diff --git a/Svelto.ECS/EntityHierarchyComponent.cs b/Svelto.ECS/Components/EntityHierarchyComponent.cs similarity index 100% rename from Svelto.ECS/EntityHierarchyComponent.cs rename to Svelto.ECS/Components/EntityHierarchyComponent.cs diff --git a/Svelto.ECS/Components/LinkedEntityComponent.cs b/Svelto.ECS/Components/LinkedEntityComponent.cs new file mode 100644 index 0000000..03583d4 --- /dev/null +++ b/Svelto.ECS/Components/LinkedEntityComponent.cs @@ -0,0 +1,7 @@ +namespace Svelto.ECS +{ + public struct LinkedEntityComponent : IEntityComponent + { + public EGID linkedEntity; + } +} \ No newline at end of file diff --git a/Svelto.ECS/DataStructures/FastTypeSafeDictionary.cs b/Svelto.ECS/DataStructures/FastTypeSafeDictionary.cs index dc55efc..15aa8ef 100644 --- a/Svelto.ECS/DataStructures/FastTypeSafeDictionary.cs +++ b/Svelto.ECS/DataStructures/FastTypeSafeDictionary.cs @@ -61,7 +61,7 @@ namespace Svelto.ECS.Internal } } - public void AddEntitiesToEngines(FasterDictionary, FasterList> entityComponentEnginesDB, + public void AddEntitiesToEngines(FasterDictionary> entityComponentEnginesDB, ITypeSafeDictionary realDic, ExclusiveGroupStruct @group, in PlatformProfiler profiler) @@ -75,7 +75,7 @@ namespace Svelto.ECS.Internal } public void RemoveEntitiesFromEngines( - FasterDictionary, FasterList> entityComponentEnginesDB, in PlatformProfiler profiler, + FasterDictionary> entityComponentEnginesDB, in PlatformProfiler profiler, ExclusiveGroupStruct @group) { foreach (var value in _implementation) @@ -105,7 +105,7 @@ namespace Svelto.ECS.Internal public void Clear() { _implementation.Clear(); } public void MoveEntityFromEngines(EGID fromEntityGid, EGID? toEntityID, ITypeSafeDictionary toGroup, - FasterDictionary, FasterList> engines, + FasterDictionary> engines, in PlatformProfiler profiler) { var valueIndex = _implementation.GetIndex(fromEntityGid.entityID); @@ -139,14 +139,14 @@ namespace Svelto.ECS.Internal [MethodImpl(MethodImplOptions.AggressiveInlining)] public ITypeSafeDictionary Create() { return new FastTypeSafeDictionary(); } - void AddEntityComponentToEngines(FasterDictionary, FasterList> entityComponentEnginesDB, + void AddEntityComponentToEngines(FasterDictionary> entityComponentEnginesDB, ref TValue entity, ExclusiveGroupStruct? previousGroup, in PlatformProfiler profiler, EGID egid) { //get all the engines linked to TValue - if (!entityComponentEnginesDB.TryGetValue(new RefWrapper(_type), out var entityComponentsEngines)) return; + if (!entityComponentEnginesDB.TryGetValue(new RefWrapperType(_type), out var entityComponentsEngines)) return; if (previousGroup == null) { @@ -184,13 +184,13 @@ namespace Svelto.ECS.Internal } } - static void RemoveEntityComponentFromEngines(FasterDictionary, FasterList> @group, + static void RemoveEntityComponentFromEngines(FasterDictionary> @group, ref TValue entity, ExclusiveGroupStruct? previousGroup, in PlatformProfiler profiler, EGID egid) { - if (!@group.TryGetValue(new RefWrapper(_type), out var entityComponentsEngines)) return; + if (!@group.TryGetValue(new RefWrapperType(_type), out var entityComponentsEngines)) return; if (previousGroup == null) { diff --git a/Svelto.ECS/DataStructures/ITypeSafeDictionary.cs b/Svelto.ECS/DataStructures/ITypeSafeDictionary.cs index f111a12..77d24e1 100644 --- a/Svelto.ECS/DataStructures/ITypeSafeDictionary.cs +++ b/Svelto.ECS/DataStructures/ITypeSafeDictionary.cs @@ -17,16 +17,19 @@ namespace Svelto.ECS.Internal public interface ITypeSafeDictionary:IDisposable { - uint count { get; } + uint count { get; } ITypeSafeDictionary Create(); - void AddEntitiesToEngines(FasterDictionary, FasterList> entityComponentEnginesDb, - ITypeSafeDictionary realDic, ExclusiveGroupStruct @group, in PlatformProfiler profiler); - void RemoveEntitiesFromEngines(FasterDictionary, FasterList> entityComponentEnginesDB, + //todo: there is something wrong in the design of the execute callback methods. Something to cleanup + void ExecuteEnginesAddOrSwapCallbacks(FasterDictionary> entityComponentEnginesDb, + ITypeSafeDictionary realDic, ExclusiveGroupStruct? fromGroup, ExclusiveGroupStruct toGroup, in PlatformProfiler profiler); + void ExecuteEnginesSwapOrRemoveCallbacks(EGID fromEntityGid, EGID? toEntityID, ITypeSafeDictionary toGroup, + FasterDictionary> engines, in PlatformProfiler profiler); + void ExecuteEnginesRemoveCallbacks(FasterDictionary> entityComponentEnginesDB, in PlatformProfiler profiler, ExclusiveGroupStruct @group); + void AddEntitiesFromDictionary(ITypeSafeDictionary entitiesToSubmit, uint groupId); - void MoveEntityFromEngines(EGID fromEntityGid, EGID? toEntityID, ITypeSafeDictionary toGroup, - FasterDictionary, FasterList> engines, in PlatformProfiler profiler); + void AddEntityToDictionary(EGID fromEntityGid, EGID toEntityID, ITypeSafeDictionary toGroup); void RemoveEntityFromDictionary(EGID fromEntityGid); @@ -38,5 +41,7 @@ namespace Svelto.ECS.Internal bool ContainsKey(uint egidEntityId); uint GetIndex(uint valueEntityId); bool TryFindIndex(uint entityGidEntityId, out uint index); + + void KeysEvaluator(System.Action action); } } \ No newline at end of file diff --git a/Svelto.ECS/DataStructures/ThreadSafeNativeBagTest.cs b/Svelto.ECS/DataStructures/ThreadSafeNativeBagTest.cs new file mode 100644 index 0000000..b001c65 --- /dev/null +++ b/Svelto.ECS/DataStructures/ThreadSafeNativeBagTest.cs @@ -0,0 +1,74 @@ +// using System.Threading.Tasks; +// using NUnit.Framework; +// using Svelto.Common; +// using Svelto.ECS.DataStructures; +// +// namespace Svelto.ECS.Tests.Common.DataStructures +// { +// [TestFixture] +// public class ThreadSafeNativeBagTest +// { +// [Test] +// public void TestByteReallocWorks() +// { +// var threadNativeBag = new ThreadSafeNativeBag(Allocator.Persistent); +// +// Parallel.Invoke(() => +// { +// for (int i = 0; i < 100; i++) +// { +// threadNativeBag.Enqueue((int)1); +// } +// } +// , // close first Action +// () => +// { +// for (int i = 0; i < 100; i++) +// { +// threadNativeBag.Enqueue((int)2); +// } +// } +// , //close second Action +// +// () => +// { +// for (int i = 0; i < 100; i++) +// { +// threadNativeBag.Enqueue(3); +// } +// } //close third Action +// ); //close parallel.invoke +// +// // for (int i = 0; i < 100; i++) +// // { +// // threadNativeBag.Enqueue(1); +// // } +// +// int oneCount = 0, twoCount = 0, threeCount = 0; +// +// while (threadNativeBag.count > 0) +// { +// var value = threadNativeBag.Dequeue(); +// +// switch (value) +// { +// case 1: +// oneCount++; +// break; +// case 2: +// twoCount++; +// break; +// case 3: +// threeCount++; +// break; +// } +// } +// +// Assert.That(oneCount, Is.EqualTo(100)); +// Assert.That(twoCount, Is.EqualTo(100)); +// Assert.That(threeCount, Is.EqualTo(100)); +// +// threadNativeBag.Dispose(); +// } +// } +// } \ No newline at end of file diff --git a/Svelto.ECS/DataStructures/TypeSafeDictionary.cs b/Svelto.ECS/DataStructures/TypeSafeDictionary.cs index 089fde8..c74f124 100644 --- a/Svelto.ECS/DataStructures/TypeSafeDictionary.cs +++ b/Svelto.ECS/DataStructures/TypeSafeDictionary.cs @@ -8,30 +8,36 @@ namespace Svelto.ECS.Internal { sealed class TypeSafeDictionary : ITypeSafeDictionary where TValue : struct, IEntityComponent { - static readonly Type _type = typeof(TValue); - static readonly string _typeName = _type.Name; - static readonly bool _hasEgid = typeof(INeedEGID).IsAssignableFrom(_type); + static readonly Type _type = typeof(TValue); + static readonly string _typeName = _type.Name; + static readonly bool _hasEgid = typeof(INeedEGID).IsAssignableFrom(_type); - internal static readonly bool _isUmanaged = + internal static readonly bool IsUnmanaged = _type.IsUnmanagedEx() && (typeof(IEntityViewComponent).IsAssignableFrom(_type) == false); - internal SveltoDictionary>, ManagedStrategy> implMgd; - internal SveltoDictionary>, NativeStrategy> implUnmgd; + SveltoDictionary>, ManagedStrategy, + ManagedStrategy> implMgd; + + //used directly by native methods + internal SveltoDictionary>, NativeStrategy, + NativeStrategy> implUnmgd; public TypeSafeDictionary(uint size) { - if (_isUmanaged) - implUnmgd = new SveltoDictionary>, NativeStrategy>(size); + if (IsUnmanaged) + implUnmgd = new SveltoDictionary>, + NativeStrategy, NativeStrategy>(size); else { - implMgd = new SveltoDictionary>, ManagedStrategy>(size); + implMgd = new SveltoDictionary>, + ManagedStrategy, ManagedStrategy>(size); } } [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Add(uint egidEntityId, in TValue entityComponent) { - if (_isUmanaged) + if (IsUnmanaged) implUnmgd.Add(egidEntityId, entityComponent); else implMgd.Add(egidEntityId, entityComponent); @@ -45,7 +51,7 @@ namespace Svelto.ECS.Internal /// public void AddEntitiesFromDictionary(ITypeSafeDictionary entitiesToSubmit, uint groupId) { - if (_isUmanaged) + if (IsUnmanaged) { var typeSafeDictionary = (entitiesToSubmit as TypeSafeDictionary).implUnmgd; @@ -89,18 +95,19 @@ namespace Svelto.ECS.Internal } } - public void AddEntitiesToEngines - (FasterDictionary, FasterList> entityComponentEnginesDB - , ITypeSafeDictionary realDic, ExclusiveGroupStruct group, in PlatformProfiler profiler) + public void ExecuteEnginesAddOrSwapCallbacks + (FasterDictionary> entityComponentEnginesDB + , ITypeSafeDictionary realDic, ExclusiveGroupStruct? fromGroup, ExclusiveGroupStruct toGroup + , in PlatformProfiler profiler) { - if (_isUmanaged) + if (IsUnmanaged) { var typeSafeDictionary = realDic as ITypeSafeDictionary; //this can be optimized, should pass all the entities and not restart the process for each one foreach (var value in implUnmgd) - AddEntityComponentToEngines(entityComponentEnginesDB, ref typeSafeDictionary[value.Key] - , null, in profiler, new EGID(value.Key, group)); + ExecuteEnginesAddOrSwapCallbacksOnSingleEntity(entityComponentEnginesDB, ref typeSafeDictionary[value.Key] + , fromGroup, in profiler, new EGID(value.Key, toGroup)); } else { @@ -108,14 +115,14 @@ namespace Svelto.ECS.Internal //this can be optimized, should pass all the entities and not restart the process for each one foreach (var value in implMgd) - AddEntityComponentToEngines(entityComponentEnginesDB, ref typeSafeDictionary[value.Key] - , null, in profiler, new EGID(value.Key, group)); + ExecuteEnginesAddOrSwapCallbacksOnSingleEntity(entityComponentEnginesDB, ref typeSafeDictionary[value.Key] + , fromGroup, in profiler, new EGID(value.Key, toGroup)); } } public void AddEntityToDictionary(EGID fromEntityGid, EGID toEntityID, ITypeSafeDictionary toGroup) { - if (_isUmanaged) + if (IsUnmanaged) { var valueIndex = implUnmgd.GetIndex(fromEntityGid.entityID); @@ -152,7 +159,7 @@ namespace Svelto.ECS.Internal [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Clear() { - if (_isUmanaged) + if (IsUnmanaged) { implUnmgd.Clear(); } @@ -165,7 +172,7 @@ namespace Svelto.ECS.Internal [MethodImpl(MethodImplOptions.AggressiveInlining)] public void FastClear() { - if (_isUmanaged) + if (IsUnmanaged) { implUnmgd.FastClear(); } @@ -178,7 +185,7 @@ namespace Svelto.ECS.Internal [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool ContainsKey(uint egidEntityId) { - if (_isUmanaged) + if (IsUnmanaged) { return implUnmgd.ContainsKey(egidEntityId); } @@ -189,15 +196,12 @@ namespace Svelto.ECS.Internal } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ITypeSafeDictionary Create() - { - return TypeSafeDictionaryFactory.Create(1); - } + public ITypeSafeDictionary Create() { return TypeSafeDictionaryFactory.Create(1); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public uint GetIndex(uint valueEntityId) { - if (_isUmanaged) + if (IsUnmanaged) { return this.implUnmgd.GetIndex(valueEntityId); } @@ -210,7 +214,7 @@ namespace Svelto.ECS.Internal [MethodImpl(MethodImplOptions.AggressiveInlining)] public ref TValue GetOrCreate(uint idEntityId) { - if (_isUmanaged) + if (IsUnmanaged) { return ref this.implUnmgd.GetOrCreate(idEntityId); } @@ -223,7 +227,7 @@ namespace Svelto.ECS.Internal [MethodImpl(MethodImplOptions.AggressiveInlining)] public IBuffer GetValues(out uint count) { - if (_isUmanaged) + if (IsUnmanaged) { return this.implUnmgd.GetValues(out count); } @@ -236,7 +240,7 @@ namespace Svelto.ECS.Internal [MethodImpl(MethodImplOptions.AggressiveInlining)] public ref TValue GetDirectValueByRef(uint key) { - if (_isUmanaged) + if (IsUnmanaged) { return ref this.implUnmgd.GetDirectValueByRef(key); } @@ -249,7 +253,7 @@ namespace Svelto.ECS.Internal [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool Has(uint key) { - if (_isUmanaged) + if (IsUnmanaged) { return this.implUnmgd.ContainsKey(key); } @@ -259,21 +263,19 @@ namespace Svelto.ECS.Internal } } - public void MoveEntityFromEngines + public void ExecuteEnginesSwapOrRemoveCallbacks (EGID fromEntityGid, EGID? toEntityID, ITypeSafeDictionary toGroup - , FasterDictionary, FasterList> engines, in PlatformProfiler profiler) + , FasterDictionary> engines, in PlatformProfiler profiler) { - if (_isUmanaged) + if (IsUnmanaged) { var valueIndex = this.implUnmgd.GetIndex(fromEntityGid.entityID); ref var entity = ref this.implUnmgd.GetDirectValueByRef(valueIndex); + //move if (toGroup != null) { - RemoveEntityComponentFromEngines(engines, ref entity, fromEntityGid.groupID, in profiler - , fromEntityGid); - var toGroupCasted = toGroup as ITypeSafeDictionary; var previousGroup = fromEntityGid.groupID; @@ -282,12 +284,13 @@ namespace Svelto.ECS.Internal var index = toGroupCasted.GetIndex(toEntityID.Value.entityID); - AddEntityComponentToEngines(engines, ref toGroupCasted.GetDirectValueByRef(index), previousGroup - , in profiler, toEntityID.Value); + ExecuteEnginesAddOrSwapCallbacksOnSingleEntity(engines, ref toGroupCasted.GetDirectValueByRef(index) + , previousGroup, in profiler, toEntityID.Value); } + //remove else { - RemoveEntityComponentFromEngines(engines, ref entity, null, in profiler, fromEntityGid); + ExecuteEnginesRemoveCallbackOnSingleEntity(engines, ref entity, in profiler, fromEntityGid); } } else @@ -298,9 +301,6 @@ namespace Svelto.ECS.Internal if (toGroup != null) { - RemoveEntityComponentFromEngines(engines, ref entity, fromEntityGid.groupID, in profiler - , fromEntityGid); - var toGroupCasted = toGroup as ITypeSafeDictionary; var previousGroup = fromEntityGid.groupID; @@ -309,37 +309,38 @@ namespace Svelto.ECS.Internal var index = toGroupCasted.GetIndex(toEntityID.Value.entityID); - AddEntityComponentToEngines(engines, ref toGroupCasted.GetDirectValueByRef(index), previousGroup - , in profiler, toEntityID.Value); + ExecuteEnginesAddOrSwapCallbacksOnSingleEntity(engines, ref toGroupCasted.GetDirectValueByRef(index) + , previousGroup, in profiler, toEntityID.Value); } else { - RemoveEntityComponentFromEngines(engines, ref entity, null, in profiler, fromEntityGid); + ExecuteEnginesRemoveCallbackOnSingleEntity(engines, ref entity, in profiler, fromEntityGid); } } } - public void RemoveEntitiesFromEngines(FasterDictionary, FasterList> engines - , in PlatformProfiler profiler, ExclusiveGroupStruct group) + public void ExecuteEnginesRemoveCallbacks + (FasterDictionary> engines, in PlatformProfiler profiler + , ExclusiveGroupStruct group) { - if (_isUmanaged) + if (IsUnmanaged) { foreach (var value in implUnmgd) - RemoveEntityComponentFromEngines(engines, ref implUnmgd.GetValueByRef(value.Key), null - , in profiler, new EGID(value.Key, group)); + ExecuteEnginesRemoveCallbackOnSingleEntity(engines, ref implUnmgd.GetValueByRef(value.Key) + , in profiler, new EGID(value.Key, group)); } else { foreach (var value in implMgd) - RemoveEntityComponentFromEngines(engines, ref implMgd.GetValueByRef(value.Key), null - , in profiler, new EGID(value.Key, group)); + ExecuteEnginesRemoveCallbackOnSingleEntity(engines, ref implMgd.GetValueByRef(value.Key) + , in profiler, new EGID(value.Key, group)); } } [MethodImpl(MethodImplOptions.AggressiveInlining)] public void RemoveEntityFromDictionary(EGID fromEntityGid) { - if (_isUmanaged) + if (IsUnmanaged) { this.implUnmgd.Remove(fromEntityGid.entityID); } @@ -352,7 +353,7 @@ namespace Svelto.ECS.Internal [MethodImpl(MethodImplOptions.AggressiveInlining)] public void SetCapacity(uint size) { - if (_isUmanaged) + if (IsUnmanaged) { this.implUnmgd.SetCapacity(size); } @@ -365,7 +366,7 @@ namespace Svelto.ECS.Internal [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Trim() { - if (_isUmanaged) + if (IsUnmanaged) { this.implUnmgd.Trim(); } @@ -378,7 +379,7 @@ namespace Svelto.ECS.Internal [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool TryFindIndex(uint entityId, out uint index) { - if (_isUmanaged) + if (IsUnmanaged) { return implUnmgd.TryFindIndex(entityId, out index); } @@ -388,10 +389,28 @@ namespace Svelto.ECS.Internal } } + public void KeysEvaluator(Action action) + { + if (IsUnmanaged) + { + foreach (var key in implUnmgd.keys) + { + action(key); + } + } + else + { + foreach (var key in implMgd.keys) + { + action(key); + } + } + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool TryGetValue(uint entityId, out TValue item) { - if (_isUmanaged) + if (IsUnmanaged) { return this.implUnmgd.TryGetValue(entityId, out item); } @@ -406,13 +425,13 @@ namespace Svelto.ECS.Internal [MethodImpl(MethodImplOptions.AggressiveInlining)] get { - if (_isUmanaged) + if (IsUnmanaged) { - return this.implUnmgd.count; + return (uint) this.implUnmgd.count; } else { - return this.implMgd.count; + return (uint) this.implMgd.count; } } } @@ -422,7 +441,7 @@ namespace Svelto.ECS.Internal [MethodImpl(MethodImplOptions.AggressiveInlining)] get { - if (_isUmanaged) + if (IsUnmanaged) { return ref this.implUnmgd.GetValueByRef(idEntityId); } @@ -433,36 +452,36 @@ namespace Svelto.ECS.Internal } } - static void RemoveEntityComponentFromEngines - (FasterDictionary, FasterList> engines, ref TValue entity, uint? previousGroup + static void ExecuteEnginesRemoveCallbackOnSingleEntity + (FasterDictionary> engines, ref TValue entity , in PlatformProfiler profiler, EGID egid) { - if (!engines.TryGetValue(new RefWrapper(_type), out var entityComponentsEngines)) + if (!engines.TryGetValue(new RefWrapperType(_type), out var entityComponentsEngines)) return; - if (previousGroup == null) - for (var i = 0; i < entityComponentsEngines.count; i++) - try + for (var i = 0; i < entityComponentsEngines.count; i++) + try + { + using (profiler.Sample(entityComponentsEngines[i], _typeName)) { - using (profiler.Sample(entityComponentsEngines[i], _typeName)) - { - (entityComponentsEngines[i] as IReactOnAddAndRemove).Remove(ref entity, egid); - } + (entityComponentsEngines[i] as IReactOnAddAndRemove).Remove(ref entity, egid); } - catch - { - Svelto.Console.LogError("Code crashed inside Remove callback ".FastConcat(typeof(TValue).ToString())); + } + catch + { + Svelto.Console.LogError( + "Code crashed inside Remove callback ".FastConcat(typeof(TValue).ToString())); - throw; - } + throw; + } } - void AddEntityComponentToEngines - (FasterDictionary, FasterList> engines, ref TValue entity + void ExecuteEnginesAddOrSwapCallbacksOnSingleEntity + (FasterDictionary> engines, ref TValue entity , ExclusiveGroupStruct? previousGroup, in PlatformProfiler profiler, EGID egid) { //get all the engines linked to TValue - if (!engines.TryGetValue(new RefWrapper(_type), out var entityComponentsEngines)) + if (!engines.TryGetValue(new RefWrapperType(_type), out var entityComponentsEngines)) return; if (previousGroup == null) @@ -477,7 +496,8 @@ namespace Svelto.ECS.Internal } catch { - Svelto.Console.LogError("Code crashed inside Add callback ".FastConcat(typeof(TValue).ToString())); + Svelto.Console.LogError( + "Code crashed inside Add callback ".FastConcat(typeof(TValue).ToString())); throw; } @@ -495,7 +515,8 @@ namespace Svelto.ECS.Internal } catch (Exception) { - Svelto.Console.LogError("Code crashed inside MoveTo callback ".FastConcat(typeof(TValue).ToString())); + Svelto.Console.LogError( + "Code crashed inside MoveTo callback ".FastConcat(typeof(TValue).ToString())); throw; } @@ -504,11 +525,11 @@ namespace Svelto.ECS.Internal public void Dispose() { - if (_isUmanaged) + if (IsUnmanaged) implUnmgd.Dispose(); else implMgd.Dispose(); - + GC.SuppressFinalize(this); } } diff --git a/Svelto.ECS/DataStructures/AtomicNativeBags.cs b/Svelto.ECS/DataStructures/Unmanaged/AtomicNativeBags.cs similarity index 65% rename from Svelto.ECS/DataStructures/AtomicNativeBags.cs rename to Svelto.ECS/DataStructures/Unmanaged/AtomicNativeBags.cs index c858617..3d9208e 100644 --- a/Svelto.ECS/DataStructures/AtomicNativeBags.cs +++ b/Svelto.ECS/DataStructures/Unmanaged/AtomicNativeBags.cs @@ -1,40 +1,33 @@ +#if UNITY_NATIVE //because of the thread count, ATM this is only for unity using System; using System.Runtime.CompilerServices; using Svelto.Common; +using Unity.Jobs.LowLevel.Unsafe; using Allocator = Svelto.Common.Allocator; -namespace Svelto.ECS.DataStructures.Unity +namespace Svelto.ECS.DataStructures { - /// - /// A collection of intended to allow one buffer per thread. - /// from: https://github.com/jeffvella/UnityEcsEvents/blob/develop/Runtime/MultiAppendBuffer.cs - /// public unsafe struct AtomicNativeBags:IDisposable { - public const int DefaultThreadIndex = -1; - const int MinThreadIndex = DefaultThreadIndex; - -#if UNITY_COLLECTIONS [global::Unity.Collections.LowLevel.Unsafe.NativeDisableUnsafePtrRestriction] -#endif - readonly NativeBag* _data; + + NativeBag* _data; readonly Allocator _allocator; readonly uint _threadsCount; - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool IsInvalidThreadIndex(int index) => index < MinThreadIndex || index > _threadsCount; + public uint count => _threadsCount; - public AtomicNativeBags(Common.Allocator allocator, uint threadsCount) + public AtomicNativeBags(Allocator allocator) { - _allocator = allocator; - _threadsCount = threadsCount; + _allocator = allocator; + _threadsCount = JobsUtility.MaxJobThreadCount + 1; var bufferSize = MemoryUtilities.SizeOf(); var bufferCount = _threadsCount; var allocationSize = bufferSize * bufferCount; var ptr = (byte*)MemoryUtilities.Alloc((uint) allocationSize, allocator); - MemoryUtilities.MemClear((IntPtr) ptr, (uint) allocationSize); + // MemoryUtilities.MemClear((IntPtr) ptr, (uint) allocationSize); for (int i = 0; i < bufferCount; i++) { @@ -49,22 +42,30 @@ namespace Svelto.ECS.DataStructures.Unity [MethodImpl(MethodImplOptions.AggressiveInlining)] public ref NativeBag GetBuffer(int index) { + if (_data == null) + throw new Exception("using invalid AtomicNativeBags"); + return ref MemoryUtilities.ArrayElementAsRef((IntPtr) _data, index); } - public uint count => _threadsCount; - public void Dispose() { + if (_data == null) + throw new Exception("using invalid AtomicNativeBags"); + for (int i = 0; i < _threadsCount; i++) { GetBuffer(i).Dispose(); } MemoryUtilities.Free((IntPtr) _data, _allocator); + _data = null; } public void Clear() { + if (_data == null) + throw new Exception("using invalid AtomicNativeBags"); + for (int i = 0; i < _threadsCount; i++) { GetBuffer(i).Clear(); @@ -72,3 +73,4 @@ namespace Svelto.ECS.DataStructures.Unity } } } +#endif \ No newline at end of file diff --git a/Svelto.ECS/DataStructures/Unmanaged/NativeBag.cs b/Svelto.ECS/DataStructures/Unmanaged/NativeBag.cs new file mode 100644 index 0000000..96e89c6 --- /dev/null +++ b/Svelto.ECS/DataStructures/Unmanaged/NativeBag.cs @@ -0,0 +1,281 @@ +#if DEBUG && !PROFILE_SVELTO +#define ENABLE_DEBUG_CHECKS +#endif + +#if DEBUG && !PROFILE_SVELTO +//#define ENABLE_THREAD_SAFE_CHECKS +#endif + +using System; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Threading; +using Svelto.Common; + +namespace Svelto.ECS.DataStructures +{ + /// + /// Burst friendly RingBuffer on steroid: + /// it can: Enqueue/Dequeue, it wraps if there is enough space after dequeuing + /// It resizes if there isn't enough space left. + /// It's a "bag", you can queue and dequeue any T. Just be sure that you dequeue what you queue! No check on type + /// is done. + /// You can reserve a position in the queue to update it later. + /// The datastructure is a struct and it's "copyable" + /// I eventually decided to call it NativeBag and not NativeBag because it can also be used as + /// a preallocated memory pool where any kind of T can be stored as long as T is unmanaged + /// + public struct NativeBag : IDisposable + { + public uint count + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + unsafe + { + BasicTests(); +#if ENABLE_THREAD_SAFE_CHECKS + try + { +#endif + return _queue->size; +#if ENABLE_THREAD_SAFE_CHECKS + } + finally + { + Volatile.Write(ref _threadSentinel, 0); + } +#endif + } + } + } + + public uint capacity + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + unsafe + { + BasicTests(); +#if ENABLE_THREAD_SAFE_CHECKS + try + { +#endif + return _queue->capacity; +#if ENABLE_THREAD_SAFE_CHECKS + } + finally + { + Volatile.Write(ref _threadSentinel, 0); + } +#endif + } + } + } + + public NativeBag(Allocator allocator) + { + unsafe + { + var sizeOf = MemoryUtilities.SizeOf(); + var listData = (UnsafeBlob*) MemoryUtilities.Alloc((uint) sizeOf, allocator); + + //clear to nullify the pointers + //MemoryUtilities.MemClear((IntPtr) listData, (uint) sizeOf); + listData->allocator = allocator; + _queue = listData; +#if ENABLE_THREAD_SAFE_CHECKS + _threadSentinel = 0; +#endif + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool IsEmpty() + { + unsafe + { + BasicTests(); +#if ENABLE_THREAD_SAFE_CHECKS + try + { +#endif + if (_queue == null || _queue->ptr == null) + return true; +#if ENABLE_THREAD_SAFE_CHECKS + } + finally + { + Volatile.Write(ref _threadSentinel, 0); + } +#endif + } + + return count == 0; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public unsafe void Dispose() + { + if (_queue != null) + { +#if ENABLE_THREAD_SAFE_CHECKS + //todo: this must be unit tested + if (Interlocked.CompareExchange(ref _threadSentinel, 1, 0) != 0) + throw new Exception("NativeBag is not thread safe, reading and writing operations can happen" + + "on different threads, but not simultaneously"); + + try + { +#endif + _queue->Dispose(); + MemoryUtilities.Free((IntPtr) _queue, _queue->allocator); + _queue = null; +#if ENABLE_THREAD_SAFE_CHECKS + } + finally + { + Volatile.Write(ref _threadSentinel, 0); + } +#endif + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ref T ReserveEnqueue(out UnsafeArrayIndex index) where T : struct + { + unsafe + { + BasicTests(); + + var sizeOf = MemoryUtilities.SizeOf(); + if (_queue->space - sizeOf < 0) + _queue->Realloc((uint) ((_queue->capacity + MemoryUtilities.Align4((uint) sizeOf)) * 2.0f)); + +#if ENABLE_THREAD_SAFE_CHECKS + try + { +#endif + + return ref _queue->Reserve(out index); +#if ENABLE_THREAD_SAFE_CHECKS + } + finally + { + Volatile.Write(ref _threadSentinel, 0); + } +#endif + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Enqueue(in T item) where T : struct + { + unsafe + { + BasicTests(); + +#if ENABLE_THREAD_SAFE_CHECKS + try + { +#endif + var sizeOf = MemoryUtilities.SizeOf(); + if (_queue->space - sizeOf < 0) + _queue->Realloc((uint) ((_queue->capacity + MemoryUtilities.Align4((uint) sizeOf)) * 2.0f)); + + _queue->Write(item); +#if ENABLE_THREAD_SAFE_CHECKS + } + finally + { + Volatile.Write(ref _threadSentinel, 0); + } +#endif + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Clear() + { + unsafe + { + BasicTests(); +#if ENABLE_THREAD_SAFE_CHECKS + try + { +#endif + _queue->Clear(); +#if ENABLE_THREAD_SAFE_CHECKS + } + finally + { + Volatile.Write(ref _threadSentinel, 0); + } +#endif + } + } + + public T Dequeue() where T : struct + { + unsafe + { + BasicTests(); +#if ENABLE_THREAD_SAFE_CHECKS + try + { +#endif + return _queue->Read(); +#if ENABLE_THREAD_SAFE_CHECKS + } + finally + { + Volatile.Write(ref _threadSentinel, 0); + } +#endif + } + } + + internal ref T AccessReserved(UnsafeArrayIndex reserverIndex) where T : struct + { + unsafe + { + BasicTests(); +#if ENABLE_THREAD_SAFE_CHECKS + try + { +#endif + return ref _queue->AccessReserved(reserverIndex); +#if ENABLE_THREAD_SAFE_CHECKS + } + finally + { + Volatile.Write(ref _threadSentinel, 0); + } +#endif + } + } + + [Conditional("ENABLE_DEBUG_CHECKS")] + unsafe void BasicTests() + { + if (_queue == null) + throw new Exception("SimpleNativeArray: null-access"); +#if ENABLE_THREAD_SAFE_CHECKS + todo: this must be unit tested + if (Interlocked.CompareExchange(ref _threadSentinel, 1, 0) != 0) + throw new Exception("NativeBag is not thread safe, reading and writing operations can happen" + + "on different threads, but not simultaneously"); +#endif + } + +#if ENABLE_THREAD_SAFE_CHECKS + int _threadSentinel; +#endif +#if UNITY_NATIVE + [global::Unity.Collections.LowLevel.Unsafe.NativeDisableUnsafePtrRestriction] +#endif + unsafe UnsafeBlob* _queue; + } +} \ No newline at end of file diff --git a/Svelto.ECS/DataStructures/NativeDynamicArray.cs b/Svelto.ECS/DataStructures/Unmanaged/NativeDynamicArray.cs similarity index 76% rename from Svelto.ECS/DataStructures/NativeDynamicArray.cs rename to Svelto.ECS/DataStructures/Unmanaged/NativeDynamicArray.cs index 83efdd2..86771e8 100644 --- a/Svelto.ECS/DataStructures/NativeDynamicArray.cs +++ b/Svelto.ECS/DataStructures/Unmanaged/NativeDynamicArray.cs @@ -7,13 +7,17 @@ namespace Svelto.ECS.DataStructures { public struct NativeDynamicArray : IDisposable { -#if UNITY_COLLECTIONS - [global::Unity.Burst.NoAlias] [global::Unity.Collections.LowLevel.Unsafe.NativeDisableUnsafePtrRestriction] -#endif - unsafe UnsafeArray* _list; -#if DEBUG && !PROFILE_SVELTO - int hashType; -#endif + public bool isValid + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + unsafe + { + return _list != null; + } + } + } [MethodImpl(MethodImplOptions.AggressiveInlining)] public int Count() where T : struct @@ -23,8 +27,8 @@ namespace Svelto.ECS.DataStructures #if DEBUG && !PROFILE_SVELTO if (_list == null) throw new Exception("NativeDynamicArray: null-access"); - if (hashType != TypeHash.hash) - throw new Exception("NativeDynamicArray: not excepted type used"); + if (_hashType != TypeHash.hash) + throw new Exception($"NativeDynamicArray: not expected type used"); #endif return (_list->count / MemoryUtilities.SizeOf()); @@ -39,8 +43,8 @@ namespace Svelto.ECS.DataStructures #if DEBUG && !PROFILE_SVELTO if (_list == null) throw new Exception("NativeDynamicArray: null-access"); - if (hashType != TypeHash.hash) - throw new Exception("NativeDynamicArray: not excepted type used"); + if (_hashType != TypeHash.hash) + throw new Exception("NativeDynamicArray: not expected type used"); #endif return (_list->capacity / MemoryUtilities.SizeOf()); @@ -51,20 +55,21 @@ namespace Svelto.ECS.DataStructures { unsafe { - var rtnStruc = new NativeDynamicArray(); #if DEBUG && !PROFILE_SVELTO - rtnStruc.hashType = TypeHash.hash; + var rtnStruc = new NativeDynamicArray {_hashType = TypeHash.hash}; +#else + NativeDynamicArray rtnStruc = default; #endif var sizeOf = MemoryUtilities.SizeOf(); - uint pointerSize = (uint) MemoryUtilities.SizeOf(); - UnsafeArray* listData = (UnsafeArray*) MemoryUtilities.Alloc(pointerSize, allocator); + uint structSize = (uint) MemoryUtilities.SizeOf(); + UnsafeArray* listData = (UnsafeArray*) MemoryUtilities.Alloc(structSize, allocator); //clear to nullify the pointers - MemoryUtilities.MemClear((IntPtr) listData, pointerSize); + //MemoryUtilities.MemClear((IntPtr) listData, structSize); - listData->allocator = allocator; - listData->Realloc((uint) (newLength * sizeOf)); + rtnStruc._allocator = allocator; + listData->Realloc((uint) (newLength * sizeOf), allocator); rtnStruc._list = listData; @@ -80,8 +85,8 @@ namespace Svelto.ECS.DataStructures #if DEBUG && !PROFILE_SVELTO if (_list == null) throw new Exception("NativeDynamicArray: null-access"); - if (hashType != TypeHash.hash) - throw new Exception("NativeDynamicArray: not excepted type used"); + if (_hashType != TypeHash.hash) + throw new Exception("NativeDynamicArray: not expected type used"); if (index >= Count()) throw new Exception($"NativeDynamicArray: out of bound access, index {index} count {Count()}"); #endif @@ -97,8 +102,8 @@ namespace Svelto.ECS.DataStructures #if DEBUG && !PROFILE_SVELTO if (_list == null) throw new Exception("NativeDynamicArray: null-access"); - if (hashType != TypeHash.hash) - throw new Exception("NativeDynamicArray: not excepted type used"); + if (_hashType != TypeHash.hash) + throw new Exception("NativeDynamicArray: not expected type used"); if (index >= Capacity()) throw new Exception($"NativeDynamicArray: out of bound access, index {index} capacity {Capacity()}"); #endif @@ -112,7 +117,7 @@ namespace Svelto.ECS.DataStructures if (_list == null) throw new Exception("NativeDynamicArray: null-access"); #endif - _list->Dispose(); + _list->Dispose(_allocator); _list = null; } @@ -124,18 +129,42 @@ namespace Svelto.ECS.DataStructures #if DEBUG && !PROFILE_SVELTO if (_list == null) throw new Exception("NativeDynamicArray: null-access"); - if (hashType != TypeHash.hash) - throw new Exception("NativeDynamicArray: not excepted type used"); + if (_hashType != TypeHash.hash) + throw new Exception("NativeDynamicArray: not expected type used"); #endif var structSize = (uint) MemoryUtilities.SizeOf(); if (_list->space - (int) structSize < 0) - _list->Realloc((uint) (((uint) ((Count() + 1) * 1.5f) * (float) structSize))); + _list->Realloc((uint) (((uint) ((Count() + 1) * 1.5f) * (float) structSize)), _allocator); _list->Add(item); } } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ref T AddAt(uint index) where T : struct + { + unsafe + { +#if DEBUG && !PROFILE_SVELTO + if (_list == null) + throw new Exception("NativeDynamicArray: null-access"); + if (_hashType != TypeHash.hash) + throw new Exception("NativeDynamicArray: not expected type used"); +#endif + var structSize = (uint) MemoryUtilities.SizeOf(); + if (index >= Capacity()) + _list->Realloc((uint) (((index + 1) * 1.5f) * structSize), _allocator); + + var writeIndex = (index + 1) * structSize; + if (_list->count < writeIndex) + _list->SetCountTo(writeIndex); + + return ref _list->Get(index); + } + } + public void Grow(uint newCapacity) where T : struct { unsafe @@ -143,15 +172,15 @@ namespace Svelto.ECS.DataStructures #if DEBUG && !PROFILE_SVELTO if (_list == null) throw new Exception("NativeDynamicArray: null-access"); - if (hashType != TypeHash.hash) - throw new Exception("NativeDynamicArray: not excepted type used"); + if (_hashType != TypeHash.hash) + throw new Exception("NativeDynamicArray: not expected type used"); if (newCapacity <= Capacity()) throw new Exception("New capacity must be greater than current one"); #endif uint structSize = (uint) MemoryUtilities.SizeOf(); uint size = (uint) (newCapacity * structSize); - _list->Realloc((uint) size); + _list->Realloc((uint) size, _allocator); } } @@ -162,8 +191,8 @@ namespace Svelto.ECS.DataStructures #if DEBUG && !PROFILE_SVELTO if (_list == null) throw new Exception("NativeDynamicArray: null-access"); - if (hashType != TypeHash.hash) - throw new Exception("NativeDynamicArray: not excepted type used"); + if (_hashType != TypeHash.hash) + throw new Exception("NativeDynamicArray: not expected type used"); #endif uint structSize = (uint) MemoryUtilities.SizeOf(); uint size = (uint) (count * structSize); @@ -180,8 +209,8 @@ namespace Svelto.ECS.DataStructures #if DEBUG && !PROFILE_SVELTO if (_list == null) throw new Exception("NativeDynamicArray: null-access"); - if (hashType != TypeHash.hash) - throw new Exception("NativeDynamicArray: not excepted type used"); + if (_hashType != TypeHash.hash) + throw new Exception("NativeDynamicArray: not expected type used"); var structSize = (uint) MemoryUtilities.SizeOf(); @@ -200,15 +229,15 @@ namespace Svelto.ECS.DataStructures #if DEBUG && !PROFILE_SVELTO if (_list == null) throw new Exception("NativeDynamicArray: null-access"); - if (hashType != TypeHash.hash) - throw new Exception("NativeDynamicArray: not excepted type used"); + if (_hashType != TypeHash.hash) + throw new Exception("NativeDynamicArray: not expected type used"); if (Count() == 0) throw new Exception("NativeDynamicArray: empty array invalid operation"); #endif - var count = Count() - 1; - if (index < count) + var indexToMove = Count() - 1; + if (index < indexToMove) { - Set(index, Get((uint) count)); + Set(index, Get((uint) indexToMove)); } _list->Pop(); @@ -216,7 +245,7 @@ namespace Svelto.ECS.DataStructures } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Clear() + public void FastClear() { unsafe { @@ -233,8 +262,8 @@ namespace Svelto.ECS.DataStructures #if DEBUG && !PROFILE_SVELTO if (_list == null) throw new Exception("NativeDynamicArray: null-access"); - if (hashType != TypeHash.hash) - throw new Exception("NativeDynamicArray: not excepted type used"); + if (_hashType != TypeHash.hash) + throw new Exception("NativeDynamicArray: not expected type used"); #endif return (T*) _list->ptr; @@ -247,8 +276,8 @@ namespace Svelto.ECS.DataStructures #if DEBUG && !PROFILE_SVELTO if (_list == null) throw new Exception("NativeDynamicArray: null-access"); - if (hashType != TypeHash.hash) - throw new Exception("NativeDynamicArray: not excepted type used"); + if (_hashType != TypeHash.hash) + throw new Exception("NativeDynamicArray: not expected type used"); #endif return (IntPtr) _list->ptr; @@ -262,8 +291,8 @@ namespace Svelto.ECS.DataStructures #if DEBUG && !PROFILE_SVELTO if (_list == null) throw new Exception("NativeDynamicArray: null-access"); - if (hashType != TypeHash.hash) - throw new Exception("NativeDynamicArray: not excepted type used"); + if (_hashType != TypeHash.hash) + throw new Exception("NativeDynamicArray: not expected type used"); #endif var count = Count(); @@ -286,8 +315,8 @@ namespace Svelto.ECS.DataStructures #if DEBUG && !PROFILE_SVELTO if (_list == null) throw new Exception("NativeDynamicArray: null-access"); - if (hashType != TypeHash.hash) - throw new Exception("NativeDynamicArray: not excepted type used"); + if (_hashType != TypeHash.hash) + throw new Exception("NativeDynamicArray: not expected type used"); #endif var capacity = Capacity(); var lengthToCopyInBytes = capacity * MemoryUtilities.SizeOf(); @@ -309,8 +338,8 @@ namespace Svelto.ECS.DataStructures #if DEBUG && !PROFILE_SVELTO if (_list == null) throw new Exception("NativeDynamicArray: null-access"); - if (hashType != TypeHash.hash) - throw new Exception("NativeDynamicArray: not excepted type used"); + if (_hashType != TypeHash.hash) + throw new Exception("NativeDynamicArray: not expected type used"); #endif var sizeOf = MemoryUtilities.SizeOf(); @@ -328,42 +357,14 @@ namespace Svelto.ECS.DataStructures MemoryUtilities.MemClear((IntPtr) _list->ptr, (uint) _list->capacity); } } - } - - public ref struct NativeDynamicArrayCast where T : struct - { - NativeDynamicArray _array; - - public NativeDynamicArrayCast(NativeDynamicArray array) : this() { _array = array; } - - public int count - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => _array.Count(); - } - - public ref T this[int index] - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => ref _array.Get((uint) index); - } - - public ref T this[uint index] - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => ref _array.Get(index); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Add(in T id) { _array.Add(id); } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void UnorderedRemoveAt(uint index) { _array.UnorderedRemoveAt(index); } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void RemoveAt(uint index) { _array.RemoveAt(index); } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Clear() { _array.Clear(); } + +#if UNITY_NATIVE + [global::Unity.Burst.NoAlias] [global::Unity.Collections.LowLevel.Unsafe.NativeDisableUnsafePtrRestriction] +#endif + unsafe UnsafeArray* _list; +#if DEBUG && !PROFILE_SVELTO + int _hashType; +#endif + Allocator _allocator; } } \ No newline at end of file diff --git a/Svelto.ECS/DataStructures/Unmanaged/NativeDynamicArrayCast.cs b/Svelto.ECS/DataStructures/Unmanaged/NativeDynamicArrayCast.cs new file mode 100644 index 0000000..a34078b --- /dev/null +++ b/Svelto.ECS/DataStructures/Unmanaged/NativeDynamicArrayCast.cs @@ -0,0 +1,48 @@ +using System.Runtime.CompilerServices; + +namespace Svelto.ECS.DataStructures +{ + public struct NativeDynamicArrayCast where T : struct + { + public NativeDynamicArrayCast(NativeDynamicArray array) : this() { _array = array; } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int Count() => _array.Count(); + + public int count => _array.Count(); + + public ref T this[int index] + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => ref _array.Get((uint) index); + } + + public ref T this[uint index] + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => ref _array.Get(index); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Add(in T id) { _array.Add(id); } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void UnorderedRemoveAt(uint index) { _array.UnorderedRemoveAt(index); } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void RemoveAt(uint index) { _array.RemoveAt(index); } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Clear() { _array.FastClear(); } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Dispose() { _array.Dispose(); } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ref T AddAt(uint lastIndex) { return ref _array.AddAt(lastIndex); } + + public bool isValid => _array.isValid; + + NativeDynamicArray _array; + } +} \ No newline at end of file diff --git a/Svelto.ECS/DataStructures/NativeDynamicArrayUnityExtension.cs b/Svelto.ECS/DataStructures/Unmanaged/NativeDynamicArrayUnityExtension.cs similarity index 96% rename from Svelto.ECS/DataStructures/NativeDynamicArrayUnityExtension.cs rename to Svelto.ECS/DataStructures/Unmanaged/NativeDynamicArrayUnityExtension.cs index 3688940..3a0dd90 100644 --- a/Svelto.ECS/DataStructures/NativeDynamicArrayUnityExtension.cs +++ b/Svelto.ECS/DataStructures/Unmanaged/NativeDynamicArrayUnityExtension.cs @@ -1,4 +1,4 @@ -#if UNITY_COLLECTIONS +#if UNITY_NATIVE using Unity.Collections; using Unity.Collections.LowLevel.Unsafe; diff --git a/Svelto.ECS/DataStructures/SharedNativeInt.cs b/Svelto.ECS/DataStructures/Unmanaged/SharedNativeInt.cs similarity index 56% rename from Svelto.ECS/DataStructures/SharedNativeInt.cs rename to Svelto.ECS/DataStructures/Unmanaged/SharedNativeInt.cs index b5269e6..e180084 100644 --- a/Svelto.ECS/DataStructures/SharedNativeInt.cs +++ b/Svelto.ECS/DataStructures/Unmanaged/SharedNativeInt.cs @@ -1,22 +1,35 @@ using System; using System.Runtime.InteropServices; using System.Threading; +using Svelto.Common; namespace Svelto.ECS.DataStructures { public struct SharedNativeInt: IDisposable { -#if UNITY_COLLECTIONS +#if UNITY_NATIVE [global::Unity.Collections.LowLevel.Unsafe.NativeDisableUnsafePtrRestriction] #endif unsafe int* data; - public static SharedNativeInt Create(int t) + Allocator _allocator; + + public SharedNativeInt(Allocator allocator) + { + unsafe + { + _allocator = allocator; + data = (int*) MemoryUtilities.Alloc(sizeof(int), allocator); + } + } + + public static SharedNativeInt Create(int t, Allocator allocator) { unsafe { var current = new SharedNativeInt(); - current.data = (int*) Marshal.AllocHGlobal(sizeof(int)); + current._allocator = allocator; + current.data = (int*) MemoryUtilities.Alloc(sizeof(int), allocator); *current.data = t; return current; @@ -31,7 +44,6 @@ namespace Svelto.ECS.DataStructures if (t.data == null) throw new Exception("using disposed SharedInt"); #endif - return *t.data; } } @@ -40,12 +52,11 @@ namespace Svelto.ECS.DataStructures { unsafe { -#if DEBUG && !PROFILE_SVELTO - if (data == null) - throw new Exception("disposing already disposed data"); -#endif - Marshal.FreeHGlobal((IntPtr) data); - data = null; + if (data != null) + { + MemoryUtilities.Free((IntPtr) data, _allocator); + data = null; + } } } @@ -74,5 +85,31 @@ namespace Svelto.ECS.DataStructures return Interlocked.Increment(ref *data); } } + + public int Add(int val) + { + unsafe + { +#if DEBUG && !PROFILE_SVELTO + if (data == null) + throw new Exception("null-access"); +#endif + + return Interlocked.Add(ref *data, val); + } + } + + public void Set(int val) + { + unsafe + { +#if DEBUG && !PROFILE_SVELTO + if (data == null) + throw new Exception("null-access"); +#endif + + Volatile.Write(ref *data, val); + } + } } } \ No newline at end of file diff --git a/Svelto.ECS/DataStructures/NativeBag.cs b/Svelto.ECS/DataStructures/Unmanaged/ThreadSafeNativeBag.cs similarity index 55% rename from Svelto.ECS/DataStructures/NativeBag.cs rename to Svelto.ECS/DataStructures/Unmanaged/ThreadSafeNativeBag.cs index e8a5ab9..d0517ee 100644 --- a/Svelto.ECS/DataStructures/NativeBag.cs +++ b/Svelto.ECS/DataStructures/Unmanaged/ThreadSafeNativeBag.cs @@ -1,11 +1,14 @@ +#if later using System; using System.Runtime.CompilerServices; +using System.Threading; using Svelto.Common; +using Svelto.Utilities; namespace Svelto.ECS.DataStructures { /// - /// Burst friendly RingBuffer on steroid: + /// Burst friendly Ring Buffer on steroid: /// it can: Enqueue/Dequeue, it wraps if there is enough space after dequeuing /// It resizes if there isn't enough space left. /// It's a "bag", you can queue and dequeue any T. Just be sure that you dequeue what you queue! No check on type @@ -15,25 +18,8 @@ namespace Svelto.ECS.DataStructures /// I eventually decided to call it NativeBag and not NativeBag because it can also be used as /// a preallocated memory pool where any kind of T can be stored as long as T is unmanaged /// - public struct NativeBag : IDisposable + public struct ThreadSafeNativeBag : IDisposable { -#if UNITY_COLLECTIONS - [global::Unity.Collections.LowLevel.Unsafe.NativeDisableUnsafePtrRestriction] -#endif - unsafe UnsafeBlob* _queue; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool IsEmpty() - { - unsafe - { - if (_queue == null || _queue->ptr == null) - return true; - } - - return count == 0; - } - public uint count { [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -68,7 +54,7 @@ namespace Svelto.ECS.DataStructures } } - public NativeBag(Allocator allocator) + public ThreadSafeNativeBag(Allocator allocator) { unsafe { @@ -76,36 +62,51 @@ namespace Svelto.ECS.DataStructures var listData = (UnsafeBlob*) MemoryUtilities.Alloc((uint) sizeOf, allocator); //clear to nullify the pointers - MemoryUtilities.MemClear((IntPtr) listData, (uint) sizeOf); + //MemoryUtilities.MemClear((IntPtr) listData, (uint) sizeOf); listData->allocator = allocator; _queue = listData; } - } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public unsafe void Dispose() + _writingGuard = 0; + } + + public ThreadSafeNativeBag(Allocator allocator, uint capacity) { - if (_queue != null) + unsafe { - _queue->Dispose(); - _queue = null; + var sizeOf = MemoryUtilities.SizeOf(); + var listData = (UnsafeBlob*) MemoryUtilities.Alloc((uint) sizeOf, allocator); + + //clear to nullify the pointers + //MemoryUtilities.MemClear((IntPtr) listData, (uint) sizeOf); + listData->allocator = allocator; + _queue = listData; + _queue->Realloc(capacity); } + + _writingGuard = 0; } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ref T ReserveEnqueue(out UnsafeArrayIndex index) where T : struct + public bool IsEmpty() { unsafe { -#if DEBUG && !PROFILE_SVELTO - if (_queue == null) - throw new Exception("SimpleNativeArray: null-access"); -#endif - var sizeOf = MemoryUtilities.SizeOf(); - if (_queue->space - sizeOf < 0) - _queue->Realloc((uint) ((_queue->capacity + sizeOf) * 2.0f)); + if (_queue == null || _queue->ptr == null) + return true; + } - return ref _queue->Reserve(out index); + return count == 0; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public unsafe void Dispose() + { + if (_queue != null) + { + _queue->Dispose(); + MemoryUtilities.Free((IntPtr) _queue, _queue->allocator); + _queue = null; } } @@ -118,11 +119,46 @@ namespace Svelto.ECS.DataStructures if (_queue == null) throw new Exception("SimpleNativeArray: null-access"); #endif - var sizeOf = MemoryUtilities.SizeOf(); - if (_queue->space - sizeOf < 0) - _queue->Realloc((uint) ((_queue->capacity + MemoryUtilities.Align4((uint) sizeOf)) * 2.0f)); + var sizeOf = MemoryUtilities.SizeOf(); + var alignedSize = (uint) MemoryUtilities.SizeOfAligned(); + + Interlocked.MemoryBarrier(); + Reset: + var oldCapacity = _queue->capacity; + var spaceleft = oldCapacity - (_queue->_writeIndex - _queue->_readIndex) - sizeOf; + + while (spaceleft < 0) + { + //if _writingGuard is not equal to 0, it means that another thread increased the + //value so it's possible the reallocing is already happening OR it means that + //writing are still in progress and we must be sure that are all flushed first + if (Interlocked.CompareExchange(ref _writingGuard, 1, 0) != 0) + { + ThreadUtility.Yield(); + goto Reset; + } + + var newCapacity = (uint) ((oldCapacity + alignedSize) * 2.0f); + Svelto.Console.Log($"realloc {newCapacity}"); + _queue->Realloc(newCapacity); + + Volatile.Write(ref _writingGuard, 0); + } + + int writeIndex; + + //look for the first available slot to write in + writeIndex = _queue->_writeIndex; + if (Interlocked.CompareExchange(ref _queue->_writeIndex, (int) (writeIndex + alignedSize) + , writeIndex) != writeIndex) + { + ThreadUtility.Yield(); + goto Reset; + } - _queue->Write(item); + Interlocked.Increment(ref _writingGuard); + _queue->Write(item, (uint) writeIndex); + Interlocked.Decrement(ref _writingGuard); } } @@ -146,17 +182,13 @@ namespace Svelto.ECS.DataStructures return _queue->Read(); } } - - public ref T AccessReserved(UnsafeArrayIndex reserverIndex) where T : struct - { - unsafe - { -#if DEBUG && !PROFILE_SVELTO - if (_queue == null) - throw new Exception("SimpleNativeArray: null-access"); + +#if UNITY_NATIVE + [global::Unity.Collections.LowLevel.Unsafe.NativeDisableUnsafePtrRestriction] #endif - return ref _queue->AccessReserved(reserverIndex); - } - } + unsafe UnsafeBlob* _queue; + + int _writingGuard; } -} \ No newline at end of file +} +#endif \ No newline at end of file diff --git a/Svelto.ECS/DataStructures/UnsafeArray.cs b/Svelto.ECS/DataStructures/Unmanaged/UnsafeArray.cs similarity index 76% rename from Svelto.ECS/DataStructures/UnsafeArray.cs rename to Svelto.ECS/DataStructures/Unmanaged/UnsafeArray.cs index a995dc6..a70c112 100644 --- a/Svelto.ECS/DataStructures/UnsafeArray.cs +++ b/Svelto.ECS/DataStructures/Unmanaged/UnsafeArray.cs @@ -4,7 +4,7 @@ using Svelto.Common; namespace Svelto.ECS.DataStructures { - struct UnsafeArray : IDisposable + struct UnsafeArray { internal unsafe byte* ptr => _ptr; @@ -17,9 +17,6 @@ namespace Svelto.ECS.DataStructures //expressed in bytes internal int space => capacity - count; - /// - /// - internal Allocator allocator; #if DEBUG && !PROFILE_SVELTO #pragma warning disable 649 internal uint id; @@ -31,6 +28,11 @@ namespace Svelto.ECS.DataStructures { unsafe { +#if DEBUG && !PROFILE_SVELTO + uint sizeOf = (uint) MemoryUtilities.SizeOf(); + if (index + sizeOf > _writeIndex) + throw new Exception("no reading authorized"); +#endif return ref Unsafe.AsRef(Unsafe.Add(ptr, (int) index)); } } @@ -73,40 +75,34 @@ namespace Svelto.ECS.DataStructures } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Pop() where T : struct + public ref T Pop() where T : struct { - var structSize = MemoryUtilities.SizeOf(); + unsafe + { + var structSize = MemoryUtilities.SizeOf(); - _writeIndex -= (uint)structSize; + _writeIndex -= (uint)structSize; + + return ref Unsafe.AsRef(ptr + _writeIndex); + } } [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal void Realloc(uint newCapacity) + internal void Realloc(uint newCapacity, Allocator allocator) { unsafe { - byte* newPointer = null; -#if DEBUG && !PROFILE_SVELTO - if (_capacity > 0 && newCapacity <= _capacity) - throw new Exception("new capacity must be bigger than current"); -#endif - if (newCapacity >= 0) - { - newPointer = (byte*) MemoryUtilities.Alloc(newCapacity, allocator); - if (count > 0) - Unsafe.CopyBlock(newPointer, ptr, (uint) count); - } - - if (ptr != null) - MemoryUtilities.Free((IntPtr) ptr, allocator); + if (_ptr == null) + _ptr = (byte*) MemoryUtilities.Alloc(newCapacity, allocator); + else + _ptr = (byte*) MemoryUtilities.Realloc((IntPtr) _ptr, (uint) count, newCapacity, allocator); - _ptr = newPointer; _capacity = newCapacity; } } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Dispose() + public void Dispose(Allocator allocator) { unsafe { @@ -131,7 +127,7 @@ namespace Svelto.ECS.DataStructures _writeIndex = count; } -#if UNITY_COLLECTIONS +#if UNITY_NATIVE [global::Unity.Collections.LowLevel.Unsafe.NativeDisableUnsafePtrRestriction] #endif unsafe byte* _ptr; diff --git a/Svelto.ECS/DataStructures/UnsafeBlob.cs b/Svelto.ECS/DataStructures/Unmanaged/UnsafeBlob.cs similarity index 84% rename from Svelto.ECS/DataStructures/UnsafeBlob.cs rename to Svelto.ECS/DataStructures/Unmanaged/UnsafeBlob.cs index a658f02..591d5c4 100644 --- a/Svelto.ECS/DataStructures/UnsafeBlob.cs +++ b/Svelto.ECS/DataStructures/Unmanaged/UnsafeBlob.cs @@ -23,7 +23,7 @@ namespace Svelto.ECS.DataStructures internal uint capacity { get; private set; } //expressed in bytes - internal uint size => _writeIndex - _readIndex; + internal uint size => (uint)_writeIndex - _readIndex; //expressed in bytes internal uint space => capacity - size; @@ -31,7 +31,7 @@ namespace Svelto.ECS.DataStructures /// /// internal Allocator allocator; - + [MethodImpl(MethodImplOptions.AggressiveInlining)] internal void Write(in T item) where T : struct { @@ -57,22 +57,53 @@ namespace Svelto.ECS.DataStructures var localCopyToAvoidGcIssues = item; //read and copy the first portion of Item until the end of the stream - Unsafe.CopyBlock(ptr + writeHead, Unsafe.AsPointer(ref localCopyToAvoidGcIssues), byteCountToEnd); + Unsafe.CopyBlock(ptr + writeHead, Unsafe.AsPointer(ref localCopyToAvoidGcIssues), (uint)byteCountToEnd); var restCount = structSize - byteCountToEnd; //read and copy the remainder Unsafe.CopyBlock(ptr, (byte*) Unsafe.AsPointer(ref localCopyToAvoidGcIssues) + byteCountToEnd - , restCount); + , (uint)restCount); } //this is may seems a waste if you are going to use an unsafeBlob just for bytes, but it's necessary for mixed types. //it's still possible to use WriteUnaligned though - var paddedStructSize = MemoryUtilities.Align4(structSize); + int paddedStructSize = (int) MemoryUtilities.Align4(structSize); _writeIndex += paddedStructSize; } } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal void Write(in T item, uint writeIndex) where T : struct + { + unsafe + { + var structSize = (uint) MemoryUtilities.SizeOf(); + + //the idea is, considering the wrap, a read pointer must always be behind a writer pointer + var writeHead = writeIndex % capacity; + + if (writeHead + structSize <= capacity) + { + Unsafe.Write(ptr + writeHead, item); + } + else //copy with wrap, will start to copy and wrap for the reminder + { + var byteCountToEnd = capacity - writeHead; + + var localCopyToAvoidGcIssues = item; + //read and copy the first portion of Item until the end of the stream + Unsafe.CopyBlock(ptr + writeHead, Unsafe.AsPointer(ref localCopyToAvoidGcIssues), byteCountToEnd); + + var restCount = structSize - byteCountToEnd; + + //read and copy the remainder + Unsafe.CopyBlock(ptr, (byte*) Unsafe.AsPointer(ref localCopyToAvoidGcIssues) + byteCountToEnd + , restCount); + } + } + } // [MethodImpl(MethodImplOptions.AggressiveInlining)] // //ToDo: remove this and create an UnsafeBlobUnaligned, used on NativeRingBuffer where T cannot change @@ -169,10 +200,10 @@ namespace Svelto.ECS.DataStructures index = new UnsafeArrayIndex { capacity = capacity - , index = _writeIndex + , index = (uint)_writeIndex }; - var align4 = MemoryUtilities.Align4(sizeOf); + int align4 = (int) MemoryUtilities.Align4(sizeOf); _writeIndex += align4; return ref buffer; @@ -218,7 +249,7 @@ namespace Svelto.ECS.DataStructures { //copy to the new pointer, from th reader position var currentSize = _writeIndex - _readIndex; - Unsafe.CopyBlock(newPointer, ptr + readerHead, currentSize); + Unsafe.CopyBlock(newPointer, ptr + readerHead, (uint)currentSize); } //the assumption is that if size > 0 (so readerPointer and writerPointer are not the same) //writerHead wrapped and reached readerHead. so I have to copy from readerHead to the end @@ -228,7 +259,7 @@ namespace Svelto.ECS.DataStructures var byteCountToEnd = capacity - readerHead; Unsafe.CopyBlock(newPointer, ptr + readerHead, byteCountToEnd); - Unsafe.CopyBlock(newPointer + byteCountToEnd, ptr, writerHead); + Unsafe.CopyBlock(newPointer + byteCountToEnd, ptr, (uint)writerHead); } } } @@ -236,11 +267,11 @@ namespace Svelto.ECS.DataStructures if (ptr != null) MemoryUtilities.Free((IntPtr) ptr, allocator); - _writeIndex = size; - _readIndex = 0; - ptr = newPointer; capacity = newCapacity; + + _readIndex = 0; + _writeIndex = (int)size; } } @@ -265,6 +296,7 @@ namespace Svelto.ECS.DataStructures _readIndex = 0; } - uint _writeIndex, _readIndex; + internal int _writeIndex; + internal uint _readIndex; } } \ No newline at end of file diff --git a/Svelto.ECS/Debugger/ExclusiveGroupDebugger.cs b/Svelto.ECS/Debugger/ExclusiveGroupDebugger.cs index b38e2dc..3ebf160 100644 --- a/Svelto.ECS/Debugger/ExclusiveGroupDebugger.cs +++ b/Svelto.ECS/Debugger/ExclusiveGroupDebugger.cs @@ -23,15 +23,16 @@ public static class ExclusiveGroupDebugger { if (field.IsStatic && typeof(ExclusiveGroup).IsAssignableFrom(field.FieldType)) { - string name = $"{type.FullName}.{field.Name}"; - var group = (ExclusiveGroup) field.GetValue(null); + var group = (ExclusiveGroup) field.GetValue(null); + string name = $"{type.FullName}.{field.Name} ({(uint)group})"; GroupMap.idToName[(ExclusiveGroupStruct) group] = name; } if (field.IsStatic && typeof(ExclusiveGroupStruct).IsAssignableFrom(field.FieldType)) { - string name = $"{type.FullName}.{field.Name}"; - var group = (ExclusiveGroupStruct) field.GetValue(null); + var group = (ExclusiveGroupStruct) field.GetValue(null); + + string name = $"{type.FullName}.{field.Name} ({(uint)group})"; GroupMap.idToName[@group] = name; } } diff --git a/Svelto.ECS/DynamicEntityDescriptor.cs b/Svelto.ECS/DynamicEntityDescriptor.cs index 05aa65d..d44a193 100644 --- a/Svelto.ECS/DynamicEntityDescriptor.cs +++ b/Svelto.ECS/DynamicEntityDescriptor.cs @@ -9,7 +9,7 @@ namespace Svelto.ECS /// This method allocates, so it shouldn't be abused /// /// - public struct DynamicEntityDescriptor : IEntityDescriptor where TType : IEntityDescriptor, new() + public struct DynamicEntityDescriptor : IDynamicEntityDescriptor where TType : IEntityDescriptor, new() { internal DynamicEntityDescriptor(bool isExtendible) : this() { @@ -22,9 +22,9 @@ namespace Svelto.ECS //assign it after otherwise the previous copy will overwrite the value in case the item //is already present - ComponentsToBuild[length] = new ComponentBuilder + ComponentsToBuild[length] = new ComponentBuilder ( - new EntityInfoViewComponent + new EntityInfoComponent { componentsToBuild = ComponentsToBuild } @@ -72,17 +72,16 @@ namespace Svelto.ECS } var defaultEntities = startingEntities; - var length = defaultEntities.Length; - - var index = SetupSpecialEntityComponent(defaultEntities, out localEntitiesToBuild, extraEntitiesLength); + + var index = SetupEntityInfoComponent(defaultEntities, out localEntitiesToBuild, extraEntitiesLength); - Array.Copy(extraEntities, 0, localEntitiesToBuild, length, extraEntitiesLength); + Array.Copy(extraEntities, 0, localEntitiesToBuild, defaultEntities.Length, extraEntitiesLength); //assign it after otherwise the previous copy will overwrite the value in case the item //is already present - localEntitiesToBuild[index] = new ComponentBuilder + localEntitiesToBuild[index] = new ComponentBuilder ( - new EntityInfoViewComponent + new EntityInfoComponent { componentsToBuild = localEntitiesToBuild } @@ -91,7 +90,7 @@ namespace Svelto.ECS return localEntitiesToBuild; } - static int SetupSpecialEntityComponent(IComponentBuilder[] defaultEntities, out IComponentBuilder[] componentsToBuild, + static int SetupEntityInfoComponent(IComponentBuilder[] defaultEntities, out IComponentBuilder[] componentsToBuild, int extraLenght) { int length = defaultEntities.Length; @@ -100,7 +99,7 @@ namespace Svelto.ECS for (var i = 0; i < length; i++) { //the special entity already exists - if (defaultEntities[i].GetEntityComponentType() == ComponentBuilderUtilities.ENTITY_STRUCT_INFO_VIEW) + if (defaultEntities[i].GetEntityComponentType() == ComponentBuilderUtilities.ENTITY_INFO_COMPONENT) { index = i; break; @@ -120,7 +119,6 @@ namespace Svelto.ECS return index; } - public IComponentBuilder[] componentsToBuild => ComponentsToBuild; IComponentBuilder[] ComponentsToBuild; diff --git a/Svelto.ECS/ECSResources/ECSString.cs b/Svelto.ECS/ECSResources/ECSString.cs index 2717aaa..50fef70 100644 --- a/Svelto.ECS/ECSResources/ECSString.cs +++ b/Svelto.ECS/ECSResources/ECSString.cs @@ -8,6 +8,10 @@ namespace Svelto.ECS.Experimental /// /// Note: I should extend this to reuse unused id /// + + //todo ResourcesECSDB must be used only inside entity components. Same for ECSString. + //what I could do is that if the component is removed from the database, a reference counter to the object + //will be modified. If 0 is reached, the ID should be recycled. public struct ECSString:IEquatable { [FieldOffset(0)] uint _id; diff --git a/Svelto.ECS/EGID.cs b/Svelto.ECS/EGID.cs index 4945031..e2a0f77 100644 --- a/Svelto.ECS/EGID.cs +++ b/Svelto.ECS/EGID.cs @@ -13,6 +13,8 @@ namespace Svelto.ECS [FieldOffset(0)] public readonly uint entityID; [FieldOffset(4)] public readonly ExclusiveGroupStruct groupID; [FieldOffset(0)] readonly ulong _GID; + + public static readonly EGID Empty = new EGID(); public static bool operator ==(EGID obj1, EGID obj2) { @@ -28,6 +30,11 @@ namespace Svelto.ECS { _GID = MAKE_GLOBAL_ID(entityID, groupID); } + + public EGID(uint entityID, BuildGroup groupID) : this() + { + _GID = MAKE_GLOBAL_ID(entityID, groupID.group); + } static ulong MAKE_GLOBAL_ID(uint entityId, uint groupId) { @@ -74,7 +81,8 @@ namespace Svelto.ECS public override string ToString() { - return "id ".FastConcat(entityID).FastConcat(" group ").FastConcat(groupID.ToName()); + var value = groupID.ToName(); + return "id ".FastConcat(entityID).FastConcat(" group ").FastConcat(value); } } } diff --git a/Svelto.ECS/EGIDMapper.cs b/Svelto.ECS/EGIDMapper.cs index e4a23d0..3596a9d 100644 --- a/Svelto.ECS/EGIDMapper.cs +++ b/Svelto.ECS/EGIDMapper.cs @@ -1,15 +1,23 @@ +using System; using System.Runtime.CompilerServices; +using Svelto.Common; using Svelto.DataStructures; using Svelto.ECS.Internal; namespace Svelto.ECS { - public readonly struct EGIDMapper where T : struct, IEntityComponent + /// + /// Note: does mono devirtualize sealed classes? If so it could be worth to use TypeSafeDictionary instead of + /// the interface + /// + /// + public readonly struct EGIDMapper: IEGIDMapper where T : struct, IEntityComponent { - public uint length => _map.count; - public ExclusiveGroupStruct groupID { get; } + public uint length => _map.count; + public ExclusiveGroupStruct groupID { get; } + public Type entityType => TypeCache.type; - public EGIDMapper(ExclusiveGroupStruct groupStructId, ITypeSafeDictionary dic) : this() + internal EGIDMapper(ExclusiveGroupStruct groupStructId, ITypeSafeDictionary dic) : this() { groupID = groupStructId; _map = dic; @@ -19,6 +27,8 @@ namespace Svelto.ECS public ref T Entity(uint entityID) { #if DEBUG && !PROFILE_SVELTO + if (_map == null) + throw new System.Exception("Not initialized EGIDMapper in this group ".FastConcat(typeof(T).ToString())); if (_map.TryFindIndex(entityID, out var findIndex) == false) throw new System.Exception("Entity not found in this group ".FastConcat(typeof(T).ToString())); #else @@ -39,29 +49,34 @@ namespace Svelto.ECS return false; } - public IBuffer GetArrayAndEntityIndex(uint entityID, out uint index) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Exists(uint idEntityId) { - if (_map.TryFindIndex(entityID, out index)) - { - return _map.GetValues(out _); - } - - throw new ECSException("Entity not found"); + return _map.count > 0 && _map.TryFindIndex(idEntityId, out _); } - public bool TryGetArrayAndEntityIndex(uint entityID, out uint index, out IBuffer array) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public uint GetIndex(uint entityID) { - index = default; - if (_map != null && _map.TryFindIndex(entityID, out index)) - { - array = _map.GetValues(out _); - return true; - } + return _map.GetIndex(entityID); + } - array = default; - return false; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool FindIndex(uint valueKey, out uint index) + { + return _map.TryFindIndex(valueKey, out index); } + + internal readonly ITypeSafeDictionary _map; + } + + public interface IEGIDMapper + { + bool FindIndex(uint valueKey, out uint index); + uint GetIndex(uint entityID); + bool Exists(uint idEntityId); - readonly ITypeSafeDictionary _map; + ExclusiveGroupStruct groupID { get; } + Type entityType { get; } } } \ No newline at end of file diff --git a/Svelto.ECS/EnginesRoot.DoubleBufferedEntitiesToAdd.cs b/Svelto.ECS/EnginesRoot.DoubleBufferedEntitiesToAdd.cs index 4e1eae0..86322d4 100644 --- a/Svelto.ECS/EnginesRoot.DoubleBufferedEntitiesToAdd.cs +++ b/Svelto.ECS/EnginesRoot.DoubleBufferedEntitiesToAdd.cs @@ -29,7 +29,7 @@ namespace Svelto.ECS var otherCount = other.count; if (otherCount > MaximumNumberOfItemsPerFrameBeforeToClear) { - FasterDictionary, ITypeSafeDictionary>[] otherValuesArray = other.unsafeValues; + FasterDictionary[] otherValuesArray = other.unsafeValues; for (int i = 0; i < otherCount; ++i) { var safeDictionariesCount = otherValuesArray[i].count; @@ -49,7 +49,7 @@ namespace Svelto.ECS } { - FasterDictionary, ITypeSafeDictionary>[] otherValuesArray = other.unsafeValues; + FasterDictionary[] otherValuesArray = other.unsafeValues; for (int i = 0; i < otherCount; ++i) { var safeDictionariesCount = otherValuesArray[i].count; @@ -83,25 +83,25 @@ namespace Svelto.ECS /// To avoid extra allocation, I don't clear the dictionaries, so I need an extra data structure /// to keep count of the number of entities submitted this frame /// - internal FasterDictionary currentEntitiesCreatedPerGroup; - internal FasterDictionary otherEntitiesCreatedPerGroup; + internal FasterDictionary currentEntitiesCreatedPerGroup; + internal FasterDictionary otherEntitiesCreatedPerGroup; //Before I tried for the third time to use a SparseSet instead of FasterDictionary, remember that //while group indices are sequential, they may not be used in a sequential order. Sparaset needs //entities to be created sequentially (the index cannot be managed externally) - internal FasterDictionary, ITypeSafeDictionary>> current; - internal FasterDictionary, ITypeSafeDictionary>> other; + internal FasterDictionary> current; + internal FasterDictionary> other; - readonly FasterDictionary, ITypeSafeDictionary>> + readonly FasterDictionary> _entityComponentsToAddBufferA = - new FasterDictionary, ITypeSafeDictionary>>(); + new FasterDictionary>(); - readonly FasterDictionary, ITypeSafeDictionary>> + readonly FasterDictionary> _entityComponentsToAddBufferB = - new FasterDictionary, ITypeSafeDictionary>>(); + new FasterDictionary>(); - readonly FasterDictionary _entitiesCreatedPerGroupA = new FasterDictionary(); - readonly FasterDictionary _entitiesCreatedPerGroupB = new FasterDictionary(); + readonly FasterDictionary _entitiesCreatedPerGroupA = new FasterDictionary(); + readonly FasterDictionary _entitiesCreatedPerGroupB = new FasterDictionary(); public DoubleBufferedEntitiesToAdd() { diff --git a/Svelto.ECS/EnginesRoot.Engines.cs b/Svelto.ECS/EnginesRoot.Engines.cs index 70c8266..7b3b388 100644 --- a/Svelto.ECS/EnginesRoot.Engines.cs +++ b/Svelto.ECS/EnginesRoot.Engines.cs @@ -9,13 +9,15 @@ namespace Svelto.ECS { public sealed partial class EnginesRoot { - public struct EntitiesSubmitter + public readonly struct EntitiesSubmitter { public EntitiesSubmitter(EnginesRoot enginesRoot) { _weakReference = new Svelto.DataStructures.WeakReference(enginesRoot); } + public bool IsUnused => _weakReference.IsValid == false; + public void Invoke() { if (_weakReference.IsValid) @@ -25,7 +27,8 @@ namespace Svelto.ECS readonly Svelto.DataStructures.WeakReference _weakReference; } - public IEntitiesSubmissionScheduler scheduler { get; } + readonly EntitiesSubmissionScheduler _scheduler; + public IEntitiesSubmissionScheduler scheduler => _scheduler; /// /// Engines root contextualize your engines and entities. You don't need to limit yourself to one EngineRoot @@ -35,34 +38,38 @@ namespace Svelto.ECS /// The EntitySubmissionScheduler cannot hold an EnginesRoot reference, that's why /// it must receive a weak reference of the EnginesRoot callback. /// - public EnginesRoot(IEntitiesSubmissionScheduler entitiesComponentScheduler) + public EnginesRoot(EntitiesSubmissionScheduler entitiesComponentScheduler) { _entitiesOperations = new ThreadSafeDictionary(); serializationDescriptorMap = new SerializationDescriptorMap(); - _reactiveEnginesAddRemove = new FasterDictionary, FasterList>(); - _reactiveEnginesSwap = new FasterDictionary, FasterList>(); + _reactiveEnginesAddRemove = new FasterDictionary>(); + _reactiveEnginesSwap = new FasterDictionary>(); + _reactiveEnginesSubmission = new FasterList(); _enginesSet = new FasterList(); _enginesTypeSet = new HashSet(); _disposableEngines = new FasterList(); _transientEntitiesOperations = new FasterList(); _groupEntityComponentsDB = - new FasterDictionary, ITypeSafeDictionary>>(); - _groupsPerEntity = new FasterDictionary, FasterDictionary>(); + new FasterDictionary>(); + _groupsPerEntity = + new FasterDictionary>(); _groupedEntityToAdd = new DoubleBufferedEntitiesToAdd(); - _entitiesStream = new EntitiesStream(); - _entitiesDB = new EntitiesDB(_groupEntityComponentsDB, _groupsPerEntity, _entitiesStream); + _entityStreams = EntitiesStreams.Create(); + _groupFilters = + new FasterDictionary>(); + _entitiesDB = new EntitiesDB(this); - scheduler = entitiesComponentScheduler; - scheduler.onTick = new EntitiesSubmitter(this); -#if UNITY_BURST + _scheduler = entitiesComponentScheduler; + _scheduler.onTick = new EntitiesSubmitter(this); +#if UNITY_NATIVE AllocateNativeOperations(); #endif } public EnginesRoot - (IEntitiesSubmissionScheduler entitiesComponentScheduler, bool isDeserializationOnly) : + (EntitiesSubmissionScheduler entitiesComponentScheduler, bool isDeserializationOnly) : this(entitiesComponentScheduler) { _isDeserializationOnly = isDeserializationOnly; @@ -75,12 +82,20 @@ namespace Svelto.ECS /// public void Dispose() { + _isDisposing = true; + using (var profiler = new PlatformProfiler("Final Dispose")) { + //Note: The engines are disposed before the the remove callback to give the chance to behave + //differently if a remove happens as a consequence of a dispose + //The pattern is to implement the IDisposable interface and set a flag in the engine. The + //remove callback will then behave differently according the flag. foreach (var engine in _disposableEngines) { try { + if (engine is IDisposingEngine dengine) + dengine.isDisposing = true; engine.Dispose(); } catch (Exception e) @@ -88,15 +103,15 @@ namespace Svelto.ECS Svelto.Console.LogException(e); } } - - foreach (FasterDictionary, ITypeSafeDictionary>>. + + foreach (FasterDictionary>. KeyValuePairFast groups in _groupEntityComponentsDB) { - foreach (FasterDictionary, ITypeSafeDictionary>.KeyValuePairFast entityList in - groups.Value) + foreach (FasterDictionary.KeyValuePairFast entityList in groups + .Value) try { - entityList.Value.RemoveEntitiesFromEngines(_reactiveEnginesAddRemove, profiler + entityList.Value.ExecuteEnginesRemoveCallbacks(_reactiveEnginesAddRemove, profiler , new ExclusiveGroupStruct(groups.Key)); } catch (Exception e) @@ -104,15 +119,27 @@ namespace Svelto.ECS Svelto.Console.LogException(e); } } - - foreach (FasterDictionary, ITypeSafeDictionary>>. + + foreach (FasterDictionary>. KeyValuePairFast groups in _groupEntityComponentsDB) { - foreach (FasterDictionary, ITypeSafeDictionary>.KeyValuePairFast entityList in - groups.Value) + foreach (FasterDictionary.KeyValuePairFast entityList in groups + .Value) entityList.Value.Dispose(); } + foreach (FasterDictionary>. + KeyValuePairFast type in _groupFilters) + foreach (FasterDictionary.KeyValuePairFast group in type.Value) + group.Value.Dispose(); + + _groupFilters.Clear(); + +#if UNITY_NATIVE + _addOperationQueue.Dispose(); + _removeOperationQueue.Dispose(); + _swapOperationQueue.Dispose(); +#endif _groupEntityComponentsDB.Clear(); _groupsPerEntity.Clear(); @@ -121,15 +148,13 @@ namespace Svelto.ECS _enginesTypeSet.Clear(); _reactiveEnginesSwap.Clear(); _reactiveEnginesAddRemove.Clear(); + _reactiveEnginesSubmission.Clear(); _entitiesOperations.Clear(); _transientEntitiesOperations.Clear(); -#if DEBUG && !PROFILE_SVELTO - _idCheckers.Clear(); -#endif + _groupedEntityToAdd.Dispose(); - _entitiesStream.Dispose(); - + _entityStreams.Dispose(); scheduler.Dispose(); } @@ -146,7 +171,7 @@ namespace Svelto.ECS public void AddEngine(IEngine engine) { var type = engine.GetType(); - var refWrapper = new RefWrapper(type); + var refWrapper = new RefWrapperType(type); DBC.ECS.Check.Require(engine != null, "Engine to add is invalid or null"); DBC.ECS.Check.Require( _enginesTypeSet.Contains(refWrapper) == false @@ -156,10 +181,13 @@ namespace Svelto.ECS try { if (engine is IReactOnAddAndRemove viewEngine) - CheckEntityComponentsEngine(viewEngine, _reactiveEnginesAddRemove); + CheckReactEngineComponents(viewEngine, _reactiveEnginesAddRemove); if (engine is IReactOnSwap viewEngineSwap) - CheckEntityComponentsEngine(viewEngineSwap, _reactiveEnginesSwap); + CheckReactEngineComponents(viewEngineSwap, _reactiveEnginesSwap); + + if (engine is IReactOnSubmission submissionEngine) + _reactiveEnginesSubmission.Add(submissionEngine); _enginesTypeSet.Add(refWrapper); _enginesSet.Add(engine); @@ -180,8 +208,8 @@ namespace Svelto.ECS } } - void CheckEntityComponentsEngine(T engine, FasterDictionary, FasterList> engines) - where T : class, IEngine + void CheckReactEngineComponents(T engine, FasterDictionary> engines) + where T : class, IReactEngine { var interfaces = engine.GetType().GetInterfaces(); @@ -197,8 +225,8 @@ namespace Svelto.ECS } static void AddEngine - (T engine, Type[] entityComponentTypes, FasterDictionary, FasterList> engines) - where T : class, IEngine + (T engine, Type[] entityComponentTypes, FasterDictionary> engines) + where T : class, IReactEngine { for (var i = 0; i < entityComponentTypes.Length; i++) { @@ -208,23 +236,25 @@ namespace Svelto.ECS } } - static void AddEngine(T engine, FasterDictionary, FasterList> engines, Type type) - where T : class, IEngine + static void AddEngine(T engine, FasterDictionary> engines, Type type) + where T : class, IReactEngine { - if (engines.TryGetValue(new RefWrapper(type), out var list) == false) + if (engines.TryGetValue(new RefWrapperType(type), out var list) == false) { - list = new FasterList(); + list = new FasterList(); - engines.Add(new RefWrapper(type), list); + engines.Add(new RefWrapperType(type), list); } list.Add(engine); } - readonly FasterDictionary, FasterList> _reactiveEnginesAddRemove; - readonly FasterDictionary, FasterList> _reactiveEnginesSwap; - readonly FasterList _disposableEngines; - readonly FasterList _enginesSet; - readonly HashSet _enginesTypeSet; + readonly FasterDictionary> _reactiveEnginesAddRemove; + readonly FasterDictionary> _reactiveEnginesSwap; + readonly FasterList _reactiveEnginesSubmission; + readonly FasterList _disposableEngines; + readonly FasterList _enginesSet; + readonly HashSet _enginesTypeSet; + internal bool _isDisposing; } } \ No newline at end of file diff --git a/Svelto.ECS/EnginesRoot.Entities.cs b/Svelto.ECS/EnginesRoot.Entities.cs index f25e6a3..fadbabb 100644 --- a/Svelto.ECS/EnginesRoot.Entities.cs +++ b/Svelto.ECS/EnginesRoot.Entities.cs @@ -16,19 +16,28 @@ namespace Svelto.ECS { return new GenericEntityStreamConsumerFactory(this); } - public IEntityFactory GenerateEntityFactory() { return new GenericEntityFactory(this); } - public IEntityFunctions GenerateEntityFunctions() { return new GenericEntityFunctions(this); } + + public IEntityFactory GenerateEntityFactory() + { + return new GenericEntityFactory(this); + } + + public IEntityFunctions GenerateEntityFunctions() + { + return new GenericEntityFunctions(this); + } ///-------------------------------------------- [MethodImpl(MethodImplOptions.AggressiveInlining)] EntityComponentInitializer BuildEntity - (EGID entityID, IComponentBuilder[] componentsToBuild, Type implementorType, IEnumerable implementors = null) + (EGID entityID, IComponentBuilder[] componentsToBuild, Type descriptorType, + IEnumerable implementors = null) { - CheckAddEntityID(entityID, implementorType); - Check.Require(entityID.groupID != 0, "invalid group detected"); + CheckAddEntityID(entityID, descriptorType); + Check.Require(entityID.groupID != 0, "invalid group detected, are you using new ExclusiveGroupStruct() instead of new ExclusiveGroup()?"); var dic = EntityFactory.BuildGroupedEntities(entityID, _groupedEntityToAdd, componentsToBuild - , implementors); + , implementors, descriptorType); return new EntityComponentInitializer(entityID, dic); } @@ -41,21 +50,21 @@ namespace Svelto.ECS var entityComponentsToBuild = EntityDescriptorTemplate.descriptor.componentsToBuild; var numberOfEntityComponents = entityComponentsToBuild.Length; - FasterDictionary, ITypeSafeDictionary> group = GetOrCreateGroup(groupID, profiler); + FasterDictionary group = GetOrCreateGroup(groupID, profiler); for (var index = 0; index < numberOfEntityComponents; index++) { var entityComponentBuilder = entityComponentsToBuild[index]; var entityComponentType = entityComponentBuilder.GetEntityComponentType(); - var refWrapper = new RefWrapper(entityComponentType); + var refWrapper = new RefWrapperType(entityComponentType); if (group.TryGetValue(refWrapper, out var dbList) == false) group[refWrapper] = entityComponentBuilder.Preallocate(ref dbList, size); else dbList.SetCapacity(size); if (_groupsPerEntity.TryGetValue(refWrapper, out var groupedGroup) == false) - groupedGroup = _groupsPerEntity[refWrapper] = new FasterDictionary(); + groupedGroup = _groupsPerEntity[refWrapper] = new FasterDictionary(); groupedGroup[groupID] = dbList; } @@ -70,28 +79,30 @@ namespace Svelto.ECS { var fromGroup = GetGroup(fromEntityGID.groupID); - //Check if there is an EntityInfoView linked to this entity, if so it's a DynamicEntityDescriptor! - if (fromGroup.TryGetValue(new RefWrapper(ComponentBuilderUtilities.ENTITY_STRUCT_INFO_VIEW) - , out var entityInfoViewDic) - && (entityInfoViewDic as ITypeSafeDictionary).TryGetValue( - fromEntityGID.entityID, out var entityInfoView)) - MoveEntityComponents(fromEntityGID, toEntityGID, entityInfoView.componentsToBuild, fromGroup - , sampler); + //Check if there is an EntityInfo linked to this entity, if so it's a DynamicEntityDescriptor! + if (fromGroup.TryGetValue(new RefWrapperType(ComponentBuilderUtilities.ENTITY_INFO_COMPONENT) + , out var entityInfoDic) + && (entityInfoDic as ITypeSafeDictionary).TryGetValue( + fromEntityGID.entityID, out var entityInfo)) + SwapOrRemoveEntityComponents(fromEntityGID, toEntityGID, entityInfo.componentsToBuild, fromGroup + , sampler); //otherwise it's a normal static entity descriptor else - MoveEntityComponents(fromEntityGID, toEntityGID, componentBuilders, fromGroup, sampler); + SwapOrRemoveEntityComponents(fromEntityGID, toEntityGID, componentBuilders, fromGroup, sampler); } } - void MoveEntityComponents(EGID fromEntityGID, EGID? toEntityGID, IComponentBuilder[] entitiesToMove - , FasterDictionary, ITypeSafeDictionary> fromGroup, in PlatformProfiler sampler) + void SwapOrRemoveEntityComponents(EGID fromEntityGID, EGID? toEntityGID, IComponentBuilder[] entitiesToMove + , FasterDictionary fromGroup, in PlatformProfiler sampler) { using (sampler.Sample("MoveEntityComponents")) { var length = entitiesToMove.Length; - FasterDictionary, ITypeSafeDictionary> toGroup = null; + FasterDictionary toGroup = null; + //Swap is not like adding a new entity. While adding new entities happen at the end of submission + //Adding an entity to a group due to a swap of groups happens now. if (toEntityGID != null) { var toGroupID = toEntityGID.Value.groupID; @@ -101,36 +112,38 @@ namespace Svelto.ECS //Add all the entities to the dictionary for (var i = 0; i < length; i++) CopyEntityToDictionary(fromEntityGID, toEntityGID.Value, fromGroup, toGroup - , entitiesToMove[i].GetEntityComponentType(), sampler); + , entitiesToMove[i].GetEntityComponentType(), sampler); } //call all the callbacks for (var i = 0; i < length; i++) - MoveEntityComponentFromAndToEngines(fromEntityGID, toEntityGID, fromGroup, toGroup - , entitiesToMove[i].GetEntityComponentType(), sampler); + ExecuteEnginesSwapOrRemoveCallbacks(fromEntityGID, toEntityGID, fromGroup, toGroup + , entitiesToMove[i].GetEntityComponentType(), sampler); //then remove all the entities from the dictionary for (var i = 0; i < length; i++) - RemoveEntityFromDictionary(fromEntityGID, fromGroup, entitiesToMove[i].GetEntityComponentType(), sampler); + RemoveEntityFromDictionary(fromEntityGID, fromGroup, entitiesToMove[i].GetEntityComponentType(), + sampler); } } void CopyEntityToDictionary - (EGID entityGID, EGID toEntityGID, FasterDictionary, ITypeSafeDictionary> fromGroup - , FasterDictionary, ITypeSafeDictionary> toGroup, Type entityComponentType, in PlatformProfiler sampler) + (EGID entityGID, EGID toEntityGID, FasterDictionary fromGroup + , FasterDictionary toGroup, Type entityComponentType, + in PlatformProfiler sampler) { using (sampler.Sample("CopyEntityToDictionary")) { - var wrapper = new RefWrapper(entityComponentType); + var wrapper = new RefWrapperType(entityComponentType); ITypeSafeDictionary fromTypeSafeDictionary = GetTypeSafeDictionary(entityGID.groupID, fromGroup, wrapper); #if DEBUG && !PROFILE_SVELTO - if (fromTypeSafeDictionary.Has(entityGID.entityID) == false) - { - throw new EntityNotFoundException(entityGID, entityComponentType); - } + if (fromTypeSafeDictionary.Has(entityGID.entityID) == false) + { + throw new EntityNotFoundException(entityGID, entityComponentType); + } #endif ITypeSafeDictionary toEntitiesDictionary = GetOrCreateTypeSafeDictionary(toEntityGID.groupID, toGroup, wrapper, fromTypeSafeDictionary); @@ -139,15 +152,15 @@ namespace Svelto.ECS } } - void MoveEntityComponentFromAndToEngines - (EGID entityGID, EGID? toEntityGID, FasterDictionary, ITypeSafeDictionary> fromGroup - , FasterDictionary, ITypeSafeDictionary> toGroup, Type entityComponentType - , in PlatformProfiler profiler) + void ExecuteEnginesSwapOrRemoveCallbacks + (EGID entityGID, EGID? toEntityGID, FasterDictionary fromGroup + , FasterDictionary toGroup, Type entityComponentType + , in PlatformProfiler profiler) { using (profiler.Sample("MoveEntityComponentFromAndToEngines")) { //add all the entities - var refWrapper = new RefWrapper(entityComponentType); + var refWrapper = new RefWrapperType(entityComponentType); var fromTypeSafeDictionary = GetTypeSafeDictionary(entityGID.groupID, fromGroup, refWrapper); ITypeSafeDictionary toEntitiesDictionary = null; @@ -155,24 +168,22 @@ namespace Svelto.ECS toEntitiesDictionary = toGroup[refWrapper]; //this is guaranteed to exist by AddEntityToDictionary #if DEBUG && !PROFILE_SVELTO - if (fromTypeSafeDictionary.Has(entityGID.entityID) == false) - throw new EntityNotFoundException(entityGID, entityComponentType); + if (fromTypeSafeDictionary.Has(entityGID.entityID) == false) + throw new EntityNotFoundException(entityGID, entityComponentType); #endif - fromTypeSafeDictionary.MoveEntityFromEngines(entityGID, toEntityGID, toEntitiesDictionary - , toEntityGID == null - ? _reactiveEnginesAddRemove - : _reactiveEnginesSwap, in profiler); + fromTypeSafeDictionary.ExecuteEnginesSwapOrRemoveCallbacks(entityGID, toEntityGID, toEntitiesDictionary + , toEntityGID == null ? _reactiveEnginesAddRemove : _reactiveEnginesSwap, in profiler); } } [MethodImpl(MethodImplOptions.AggressiveInlining)] void RemoveEntityFromDictionary - (EGID entityGID, FasterDictionary, ITypeSafeDictionary> fromGroup, Type entityComponentType - , in PlatformProfiler sampler) + (EGID entityGID, FasterDictionary fromGroup, Type entityComponentType + , in PlatformProfiler sampler) { using (sampler.Sample("RemoveEntityFromDictionary")) { - var refWrapper = new RefWrapper(entityComponentType); + var refWrapper = new RefWrapperType(entityComponentType); var fromTypeSafeDictionary = GetTypeSafeDictionary(entityGID.groupID, fromGroup, refWrapper); fromTypeSafeDictionary.RemoveEntityFromDictionary(entityGID); @@ -181,65 +192,67 @@ namespace Svelto.ECS /// /// Swap all the entities from one group to another + /// + /// TODO: write unit test that also tests that this calls MoveTo callbacks and not Add or Remove. + /// also that the passing EGID is the same of a component with EGID /// /// /// /// - void SwapEntitiesBetweenGroups(uint fromIdGroupId, uint toGroupId, in PlatformProfiler profiler) + void SwapEntitiesBetweenGroups(ExclusiveGroupStruct fromIdGroupId, ExclusiveGroupStruct toGroupId, in PlatformProfiler profiler) { using (profiler.Sample("SwapEntitiesBetweenGroups")) { - FasterDictionary, ITypeSafeDictionary> fromGroup = GetGroup(fromIdGroupId); - FasterDictionary, ITypeSafeDictionary> toGroup = GetOrCreateGroup(toGroupId, profiler); + FasterDictionary fromGroup = GetGroup(fromIdGroupId); + FasterDictionary toGroup = GetOrCreateGroup(toGroupId, profiler); - foreach (FasterDictionary, ITypeSafeDictionary>.KeyValuePairFast dictionaryOfEntities - in fromGroup) + foreach (var dictionaryOfEntities in fromGroup) { - //call all the MoveTo callbacks - dictionaryOfEntities.Value.AddEntitiesToEngines(_reactiveEnginesAddRemove - , dictionaryOfEntities.Value - , new ExclusiveGroupStruct(toGroupId), profiler); - ITypeSafeDictionary toEntitiesDictionary = GetOrCreateTypeSafeDictionary(toGroupId, toGroup, dictionaryOfEntities.Key - , dictionaryOfEntities.Value); + , dictionaryOfEntities.Value); - FasterDictionary groupsOfEntityType = - _groupsPerEntity[dictionaryOfEntities.Key]; + var groupsOfEntityType = _groupsPerEntity[dictionaryOfEntities.Key]; - ITypeSafeDictionary typeSafeDictionary = groupsOfEntityType[fromIdGroupId]; - toEntitiesDictionary.AddEntitiesFromDictionary(typeSafeDictionary, toGroupId); + var groupOfEntitiesToCopyAndClear = groupsOfEntityType[fromIdGroupId]; + toEntitiesDictionary.AddEntitiesFromDictionary(groupOfEntitiesToCopyAndClear, toGroupId); + + //call all the MoveTo callbacks + dictionaryOfEntities.Value.ExecuteEnginesAddOrSwapCallbacks(_reactiveEnginesSwap + , dictionaryOfEntities.Value, new ExclusiveGroupStruct(fromIdGroupId), new ExclusiveGroupStruct(toGroupId), profiler); - typeSafeDictionary.FastClear(); + //todo: if it's unmanaged, I can use fastclear + groupOfEntitiesToCopyAndClear.Clear(); } } } - FasterDictionary, ITypeSafeDictionary> GetGroup(uint fromIdGroupId) + FasterDictionary GetGroup(ExclusiveGroupStruct fromIdGroupId) { if (_groupEntityComponentsDB.TryGetValue(fromIdGroupId - , out FasterDictionary, ITypeSafeDictionary> - fromGroup) == false) + , out FasterDictionary + fromGroup) == false) throw new ECSException("Group doesn't exist: ".FastConcat(fromIdGroupId)); return fromGroup; } - FasterDictionary, ITypeSafeDictionary> GetOrCreateGroup(uint toGroupId, in PlatformProfiler profiler) + FasterDictionary GetOrCreateGroup(ExclusiveGroupStruct toGroupId, + in PlatformProfiler profiler) { using (profiler.Sample("GetOrCreateGroup")) { if (_groupEntityComponentsDB.TryGetValue( - toGroupId, out FasterDictionary, ITypeSafeDictionary> toGroup) == false) + toGroupId, out FasterDictionary toGroup) == false) toGroup = _groupEntityComponentsDB[toGroupId] = - new FasterDictionary, ITypeSafeDictionary>(); + new FasterDictionary(); return toGroup; } } ITypeSafeDictionary GetOrCreateTypeSafeDictionary - (uint groupId, FasterDictionary, ITypeSafeDictionary> toGroup, RefWrapper type + (ExclusiveGroupStruct groupId, FasterDictionary toGroup, RefWrapperType type , ITypeSafeDictionary fromTypeSafeDictionary) { //be sure that the TypeSafeDictionary for the entity Type exists @@ -251,14 +264,14 @@ namespace Svelto.ECS //update GroupsPerEntity if (_groupsPerEntity.TryGetValue(type, out var groupedGroup) == false) - groupedGroup = _groupsPerEntity[type] = new FasterDictionary(); + groupedGroup = _groupsPerEntity[type] = new FasterDictionary(); groupedGroup[groupId] = toEntitiesDictionary; return toEntitiesDictionary; } static ITypeSafeDictionary GetTypeSafeDictionary - (uint groupID, FasterDictionary, ITypeSafeDictionary> @group, RefWrapper refWrapper) + (uint groupID, FasterDictionary @group, RefWrapperType refWrapper) { if (@group.TryGetValue(refWrapper, out ITypeSafeDictionary fromTypeSafeDictionary) == false) { @@ -268,34 +281,24 @@ namespace Svelto.ECS return fromTypeSafeDictionary; } - void RemoveGroupAndEntities(uint groupID, in PlatformProfiler profiler) + void RemoveEntitiesFromGroup(ExclusiveGroupStruct groupID, in PlatformProfiler profiler) { - FasterDictionary, ITypeSafeDictionary> dictionariesOfEntities = - _groupEntityComponentsDB[groupID]; - - foreach (FasterDictionary, ITypeSafeDictionary>.KeyValuePairFast dictionaryOfEntities in dictionariesOfEntities) + if (_groupEntityComponentsDB.TryGetValue(groupID, out var dictionariesOfEntities)) { - dictionaryOfEntities.Value.RemoveEntitiesFromEngines(_reactiveEnginesAddRemove, profiler - , new ExclusiveGroupStruct(groupID)); - dictionaryOfEntities.Value.FastClear(); + foreach (FasterDictionary.KeyValuePairFast dictionaryOfEntities + in dictionariesOfEntities) + { + dictionaryOfEntities.Value.ExecuteEnginesRemoveCallbacks(_reactiveEnginesAddRemove, profiler + , new ExclusiveGroupStruct(groupID)); + dictionaryOfEntities.Value.FastClear(); - FasterDictionary groupsOfEntityType = - _groupsPerEntity[dictionaryOfEntities.Key]; - groupsOfEntityType[groupID].FastClear(); + var groupsOfEntityType = + _groupsPerEntity[dictionaryOfEntities.Key]; + groupsOfEntityType[groupID].FastClear(); + } } } - internal Consumer GenerateConsumer(string name, uint capacity) where T : unmanaged, IEntityComponent - { - return _entitiesStream.GenerateConsumer(name, capacity); - } - - internal Consumer GenerateConsumer(ExclusiveGroupStruct group, string name, uint capacity) - where T : unmanaged, IEntityComponent - { - return _entitiesStream.GenerateConsumer(group, name, capacity); - } - //one datastructure rule them all: //split by group //split by type per group. It's possible to get all the entities of a give type T per group thanks @@ -303,17 +306,21 @@ namespace Svelto.ECS //ID. This ID doesn't need to be the EGID, it can be just the entityID //for each group id, save a dictionary indexed by entity type of entities indexed by id // group EntityComponentType entityID, EntityComponent - readonly FasterDictionary, ITypeSafeDictionary>> + internal readonly FasterDictionary> _groupEntityComponentsDB; //for each entity view type, return the groups (dictionary of entities indexed by entity id) where they are //found indexed by group id. TypeSafeDictionary are never created, they instead point to the ones hold //by _groupEntityComponentsDB - // EntityComponentType groupID entityID, EntityComponent - readonly FasterDictionary, FasterDictionary> _groupsPerEntity; + // >> + internal readonly FasterDictionary> + _groupsPerEntity; + + //The filters stored for each component and group + internal readonly FasterDictionary> + _groupFilters; - readonly EntitiesDB _entitiesDB; - readonly EntitiesStream _entitiesStream; + readonly EntitiesDB _entitiesDB; EntitiesDB IUnitTestingInterface.entitiesForTesting => _entitiesDB; } diff --git a/Svelto.ECS/EnginesRoot.GenericEntityFactory.cs b/Svelto.ECS/EnginesRoot.GenericEntityFactory.cs index 684d889..e13432b 100644 --- a/Svelto.ECS/EnginesRoot.GenericEntityFactory.cs +++ b/Svelto.ECS/EnginesRoot.GenericEntityFactory.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using Svelto.Common; @@ -14,7 +14,7 @@ namespace Svelto.ECS } public EntityComponentInitializer BuildEntity - (uint entityID, ExclusiveGroupStruct groupStructId, IEnumerable implementors = null) + (uint entityID, BuildGroup groupStructId, IEnumerable implementors = null) where T : IEntityDescriptor, new() { return _enginesRoot.Target.BuildEntity(new EGID(entityID, groupStructId) @@ -34,14 +34,14 @@ namespace Svelto.ECS { return _enginesRoot.Target.BuildEntity(egid, entityDescriptor.componentsToBuild, TypeCache.type, implementors); } -#if UNITY_BURST +#if UNITY_NATIVE public NativeEntityFactory ToNative(string memberName) where T : IEntityDescriptor, new() { return _enginesRoot.Target.ProvideNativeEntityFactoryQueue(memberName); } #endif public EntityComponentInitializer BuildEntity - (uint entityID, ExclusiveGroupStruct groupStructId, T descriptorEntity, IEnumerable implementors) + (uint entityID, BuildGroup groupStructId, T descriptorEntity, IEnumerable implementors) where T : IEntityDescriptor { return _enginesRoot.Target.BuildEntity(new EGID(entityID, groupStructId) diff --git a/Svelto.ECS/EnginesRoot.GenericEntityFunctions.cs b/Svelto.ECS/EnginesRoot.GenericEntityFunctions.cs index 580f8fa..8b9a537 100644 --- a/Svelto.ECS/EnginesRoot.GenericEntityFunctions.cs +++ b/Svelto.ECS/EnginesRoot.GenericEntityFunctions.cs @@ -1,6 +1,8 @@ using System; - using System.Runtime.CompilerServices; +using System.Runtime.CompilerServices; using Svelto.Common; +using Svelto.DataStructures; +using Svelto.ECS.Internal; namespace Svelto.ECS { @@ -8,7 +10,7 @@ namespace Svelto.ECS { /// /// todo: EnginesRoot was a weakreference to give the change to inject - /// entityfunctions from other engines root. It probably should be reverted + /// entity functions from other engines root. It probably should be reverted /// class GenericEntityFunctions : IEntityFunctions { @@ -18,7 +20,7 @@ namespace Svelto.ECS } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void RemoveEntity(uint entityID, ExclusiveGroupStruct groupID) where T : + public void RemoveEntity(uint entityID, BuildGroup groupID) where T : IEntityDescriptor, new() { RemoveEntity(new EGID(entityID, groupID)); @@ -28,64 +30,91 @@ namespace Svelto.ECS public void RemoveEntity(EGID entityEGID) where T : IEntityDescriptor, new() { DBC.ECS.Check.Require(entityEGID.groupID != 0, "invalid group detected"); + var descriptorComponentsToBuild = EntityDescriptorTemplate.descriptor.componentsToBuild; _enginesRoot.Target.CheckRemoveEntityID(entityEGID, TypeCache.type); _enginesRoot.Target.QueueEntitySubmitOperation( new EntitySubmitOperation(EntitySubmitOperationType.Remove, entityEGID, entityEGID, - EntityDescriptorTemplate.descriptor.componentsToBuild)); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void RemoveAllEntities(ExclusiveGroupStruct group) where T : IEntityDescriptor, new() - { - throw new NotImplementedException(); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void RemoveAllEntities() where T : IEntityDescriptor, new() - { - throw new NotImplementedException(); + descriptorComponentsToBuild)); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void RemoveGroupAndEntities(ExclusiveGroupStruct groupID) + public void RemoveEntitiesFromGroup(BuildGroup groupID) { - _enginesRoot.Target.RemoveGroupID(groupID); DBC.ECS.Check.Require(groupID != 0, "invalid group detected"); + _enginesRoot.Target.RemoveGroupID(groupID); _enginesRoot.Target.QueueEntitySubmitOperation( new EntitySubmitOperation(EntitySubmitOperationType.RemoveGroup, new EGID(0, groupID), new EGID())); } + // [MethodImpl(MethodImplOptions.AggressiveInlining)] + // void RemoveAllEntities(ExclusiveGroup group) + // where D : IEntityDescriptor, new() where S : unmanaged, IEntityComponent + // { + // var targetEntitiesDB = _enginesRoot.Target._entitiesDB; + // var (buffer, count) = targetEntitiesDB.QueryEntities(@group); + // for (uint i = 0; i < count; ++i) + // { + // RemoveEntity(new EGID(i, group)); + // } + // } + // + // [MethodImpl(MethodImplOptions.AggressiveInlining)] + // void RemoveAllEntities() + // where D : IEntityDescriptor, new() where S : unmanaged, IEntityComponent + // { + // var targetEntitiesDB = _enginesRoot.Target._entitiesDB; + // foreach (var ((buffer, count), exclusiveGroupStruct) in targetEntitiesDB.QueryEntities()) + // for (uint i = 0; i < count; ++i) + // { + // RemoveEntity(new EGID(i, exclusiveGroupStruct)); + // } + // } + [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void SwapEntitiesInGroup(ExclusiveGroupStruct fromGroupID, ExclusiveGroupStruct toGroupID) + public void SwapEntitiesInGroup(BuildGroup fromGroupID, BuildGroup toGroupID) + where T : IEntityDescriptor, new() { - throw new NotImplementedException("can't run this until I add the checks!"); - -#pragma warning disable 162 - _enginesRoot.Target.QueueEntitySubmitOperation( - new EntitySubmitOperation(EntitySubmitOperationType.SwapGroup, new EGID(0, fromGroupID), - new EGID(0, toGroupID))); -#pragma warning restore 162 + if (_enginesRoot.Target._groupEntityComponentsDB.TryGetValue( + fromGroupID.group, out FasterDictionary entitiesInGroupPerType) + == true) + { +#if DEBUG && !PROFILE_SVELTO + IComponentBuilder[] components = EntityDescriptorTemplate.descriptor.componentsToBuild; + var dictionary = entitiesInGroupPerType[new RefWrapperType(components[0].GetEntityComponentType())]; + + dictionary.KeysEvaluator((key) => + { + _enginesRoot.Target.CheckRemoveEntityID(new EGID(key, fromGroupID), TypeCache.type); + _enginesRoot.Target.CheckAddEntityID(new EGID(key, toGroupID), TypeCache.type); + }); + +#endif + _enginesRoot.Target.QueueEntitySubmitOperation( + new EntitySubmitOperation(EntitySubmitOperationType.SwapGroup, new EGID(0, fromGroupID) + , new EGID(0, toGroupID))); + } } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void SwapEntityGroup(uint entityID, ExclusiveGroupStruct fromGroupID, ExclusiveGroupStruct toGroupID) + public void SwapEntityGroup(uint entityID, BuildGroup fromGroupID, + BuildGroup toGroupID) where T : IEntityDescriptor, new() { SwapEntityGroup(new EGID(entityID, fromGroupID), toGroupID); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void SwapEntityGroup(EGID fromID, ExclusiveGroupStruct toGroupID) + public void SwapEntityGroup(EGID fromID, BuildGroup toGroupID) where T : IEntityDescriptor, new() { SwapEntityGroup(fromID, new EGID(fromID.entityID, (uint) toGroupID)); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void SwapEntityGroup(EGID fromID, ExclusiveGroupStruct toGroupID - , ExclusiveGroupStruct mustBeFromGroup) + public void SwapEntityGroup(EGID fromID, BuildGroup toGroupID + , BuildGroup mustBeFromGroup) where T : IEntityDescriptor, new() { if (fromID.groupID != mustBeFromGroup) @@ -96,7 +125,7 @@ namespace Svelto.ECS [MethodImpl(MethodImplOptions.AggressiveInlining)] public void SwapEntityGroup(EGID fromID, EGID toID - , ExclusiveGroupStruct mustBeFromGroup) + , BuildGroup mustBeFromGroup) where T : IEntityDescriptor, new() { if (fromID.groupID != mustBeFromGroup) @@ -105,17 +134,17 @@ namespace Svelto.ECS SwapEntityGroup(fromID, toID); } -#if UNITY_BURST +#if UNITY_NATIVE public NativeEntityRemove ToNativeRemove(string memberName) where T : IEntityDescriptor, new() { return _enginesRoot.Target.ProvideNativeEntityRemoveQueue(memberName); } - + public NativeEntitySwap ToNativeSwap(string memberName) where T : IEntityDescriptor, new() { return _enginesRoot.Target.ProvideNativeEntitySwapQueue(memberName); } -#endif +#endif [MethodImpl(MethodImplOptions.AggressiveInlining)] public void SwapEntityGroup(EGID fromID, EGID toID) @@ -123,17 +152,20 @@ namespace Svelto.ECS { DBC.ECS.Check.Require(fromID.groupID != 0, "invalid group detected"); DBC.ECS.Check.Require(toID.groupID != 0, "invalid group detected"); + + var enginesRootTarget = _enginesRoot.Target; + var descriptorComponentsToBuild = EntityDescriptorTemplate.descriptor.componentsToBuild; - _enginesRoot.Target.CheckRemoveEntityID(fromID, TypeCache.type); - _enginesRoot.Target.CheckAddEntityID(toID, TypeCache.type); + enginesRootTarget.CheckRemoveEntityID(fromID, TypeCache.type); + enginesRootTarget.CheckAddEntityID(toID, TypeCache.type); - _enginesRoot.Target.QueueEntitySubmitOperation( + enginesRootTarget.QueueEntitySubmitOperation( new EntitySubmitOperation(EntitySubmitOperationType.Swap, - fromID, toID, EntityDescriptorTemplate.descriptor.componentsToBuild)); + fromID, toID, descriptorComponentsToBuild)); } //enginesRoot is a weakreference because GenericEntityStreamConsumerFactory can be injected inside -//engines of other enginesRoot + //engines of other enginesRoot readonly Svelto.DataStructures.WeakReference _enginesRoot; } @@ -154,13 +186,13 @@ namespace Svelto.ECS { if (entitySubmitedOperation != entitySubmitOperation) throw new ECSException("Only one entity operation per submission is allowed" - .FastConcat(" entityComponentType: ") - .FastConcat(typeof(T).Name) - .FastConcat(" submission type ", entitySubmitOperation.type.ToString(), + .FastConcat(" entityComponentType: ") + .FastConcat(typeof(T).Name) + .FastConcat(" submission type ", entitySubmitOperation.type.ToString(), " from ID: ", entitySubmitOperation.fromID.entityID.ToString()) - .FastConcat(" previous operation type: ", + .FastConcat(" previous operation type: ", _entitiesOperations[(ulong) entitySubmitOperation.fromID].type - .ToString())); + .ToString())); } else #endif diff --git a/Svelto.ECS/EnginesRoot.Submission.cs b/Svelto.ECS/EnginesRoot.Submission.cs index dfc049c..559c800 100644 --- a/Svelto.ECS/EnginesRoot.Submission.cs +++ b/Svelto.ECS/EnginesRoot.Submission.cs @@ -27,11 +27,20 @@ namespace Svelto.ECS } } + /// + /// Todo: it would be probably better to split even further the logic between submission and callbacks + /// Something to do when I will optimize the callbacks + /// + /// void SingleSubmission(in PlatformProfiler profiler) { -#if UNITY_BURST +#if UNITY_NATIVE NativeOperationSubmission(profiler); #endif + ClearChecks(); + + bool entitiesAreSubmitted = false; + if (_entitiesOperations.Count > 0) { using (profiler.Sample("Remove and Swap operations")) @@ -56,7 +65,7 @@ namespace Svelto.ECS entitiesOperations[i].fromID, null); break; case EntitySubmitOperationType.RemoveGroup: - RemoveGroupAndEntities( + RemoveEntitiesFromGroup( entitiesOperations[i].fromID.groupID, profiler); break; case EntitySubmitOperationType.SwapGroup: @@ -81,6 +90,8 @@ namespace Svelto.ECS } } } + + entitiesAreSubmitted = true; } _groupedEntityToAdd.Swap(); @@ -102,6 +113,15 @@ namespace Svelto.ECS } } } + + entitiesAreSubmitted = true; + } + + if (entitiesAreSubmitted) + { + var enginesCount = _reactiveEnginesSubmission.count; + for (int i = 0; i < enginesCount; i++) + _reactiveEnginesSubmission[i].EntitiesSubmitted(); } } @@ -114,14 +134,14 @@ namespace Svelto.ECS { var groupID = groupOfEntitiesToSubmit.Key; - FasterDictionary, ITypeSafeDictionary> groupDB = GetOrCreateGroup(groupID, profiler); + var groupDB = GetOrCreateGroup(groupID, profiler); //add the entityComponents in the group foreach (var entityComponentsToSubmit in _groupedEntityToAdd.other[groupID]) { var type = entityComponentsToSubmit.Key; var targetTypeSafeDictionary = entityComponentsToSubmit.Value; - var wrapper = new RefWrapper(type); + var wrapper = new RefWrapperType(type); ITypeSafeDictionary dbDic = GetOrCreateTypeSafeDictionary(groupID, groupDB, wrapper, targetTypeSafeDictionary); @@ -143,16 +163,16 @@ namespace Svelto.ECS foreach (var entityComponentsToSubmit in _groupedEntityToAdd.other[groupID]) { - var realDic = groupDB[new RefWrapper(entityComponentsToSubmit.Key)]; + var realDic = groupDB[new RefWrapperType(entityComponentsToSubmit.Key)]; - entityComponentsToSubmit.Value.AddEntitiesToEngines(_reactiveEnginesAddRemove, realDic, - new ExclusiveGroupStruct(groupToSubmit.Key), in profiler); + entityComponentsToSubmit.Value.ExecuteEnginesAddOrSwapCallbacks(_reactiveEnginesAddRemove, realDic, + null, new ExclusiveGroupStruct(groupToSubmit.Key), in profiler); } } } } - DoubleBufferedEntitiesToAdd _groupedEntityToAdd; + readonly DoubleBufferedEntitiesToAdd _groupedEntityToAdd; readonly ThreadSafeDictionary _entitiesOperations; } } \ No newline at end of file diff --git a/Svelto.ECS/EntitiesDB.FindGroups.cs b/Svelto.ECS/EntitiesDB.FindGroups.cs index 63d8391..0c7df8f 100644 --- a/Svelto.ECS/EntitiesDB.FindGroups.cs +++ b/Svelto.ECS/EntitiesDB.FindGroups.cs @@ -7,20 +7,12 @@ namespace Svelto.ECS { public partial class EntitiesDB { - internal FasterDictionary FindGroups_INTERNAL() where T1 : IEntityComponent - { - if (_groupsPerEntity.ContainsKey(TypeRefWrapper.wrapper) == false) - return _emptyDictionary; - - return _groupsPerEntity[TypeRefWrapper.wrapper]; - } - public LocalFasterReadOnlyList FindGroups() where T1 : IEntityComponent { FasterList result = groups.Value; result.FastClear(); - if (_groupsPerEntity.TryGetValue(TypeRefWrapper.wrapper - , out FasterDictionary result1) == false) + if (groupsPerEntity.TryGetValue(TypeRefWrapper.wrapper + , out FasterDictionary result1) == false) return result; var result1Count = result1.count; @@ -38,11 +30,11 @@ namespace Svelto.ECS { FasterList result = groups.Value; result.FastClear(); - if (_groupsPerEntity.TryGetValue(TypeRefWrapper.wrapper - , out FasterDictionary result1) == false) + if (groupsPerEntity.TryGetValue(TypeRefWrapper.wrapper + , out FasterDictionary result1) == false) return result; - if (_groupsPerEntity.TryGetValue(TypeRefWrapper.wrapper - , out FasterDictionary result2) == false) + if (groupsPerEntity.TryGetValue(TypeRefWrapper.wrapper + , out FasterDictionary result2) == false) return result; var result1Count = result1.count; @@ -52,12 +44,13 @@ namespace Svelto.ECS for (int i = 0; i < result1Count; i++) { + var groupID = fasterDictionaryNodes1[i].key; for (int j = 0; j < result2Count; j++) { //if the same group is found used with both T1 and T2 - if (fasterDictionaryNodes1[i].key == fasterDictionaryNodes2[j].key) + if (groupID == fasterDictionaryNodes2[j].key) { - result.Add(new ExclusiveGroupStruct(fasterDictionaryNodes1[i].key)); + result.Add(new ExclusiveGroupStruct(groupID)); break; } } @@ -77,33 +70,68 @@ namespace Svelto.ECS public LocalFasterReadOnlyList FindGroups() where T1 : IEntityComponent where T2 : IEntityComponent where T3 : IEntityComponent { - FindGroups(); - FasterList result = groups.Value; - - if (result.count == 0) + result.FastClear(); + if (groupsPerEntity.TryGetValue(TypeRefWrapper.wrapper + , out FasterDictionary groupOfEntities1) == false) return result; - - if (_groupsPerEntity.TryGetValue(TypeRefWrapper.wrapper - , out FasterDictionary result3) == false) + if (groupsPerEntity.TryGetValue(TypeRefWrapper.wrapper + , out FasterDictionary groupOfEntities2) == false) return result; - - var result3Count = result3.count; - var fasterDictionaryNodes3 = result3.unsafeKeys; - - for (int j = 0; j < result3Count; j++) - for (int i = (int) 0; i < result.count; i++) + if (groupsPerEntity.TryGetValue(TypeRefWrapper.wrapper + , out FasterDictionary groupOfEntities3) == false) + return result; + + var result1Count = groupOfEntities1.count; + var result2Count = groupOfEntities2.count; + var result3Count = groupOfEntities3.count; + var fasterDictionaryNodes1 = groupOfEntities1.unsafeKeys; + var fasterDictionaryNodes2 = groupOfEntities2.unsafeKeys; + var fasterDictionaryNodes3 = groupOfEntities3.unsafeKeys; + + // + //TODO: I have to find once for ever a solution to be sure that the entities in the groups match + //Currently this returns group where the entities are found, but the entities may not match in these + //groups. + //Checking the size of the entities is an early check, needed, but not sufficient, as entities components may + //coincidentally match in number but not from which entities they are generated + + //foreach group where T1 is found + for (int i = 0; i < result1Count; i++) { - if (fasterDictionaryNodes3[j].key == result[i]) - break; - - result.UnorderedRemoveAt(i); - i--; + var groupT1 = fasterDictionaryNodes1[i].key; + + //foreach group where T2 is found + for (int j = 0; j < result2Count; ++j) + { + if (groupT1 == fasterDictionaryNodes2[j].key) + { + //foreach group where T3 is found + for (int k = 0; k < result3Count; ++k) + { + if (groupT1 == fasterDictionaryNodes3[k].key) + { + result.Add(new ExclusiveGroupStruct(groupT1)); + break; + } + } + + break; + } + } } return result; } - + + internal FasterDictionary FindGroups_INTERNAL(Type type) + { + if (groupsPerEntity.ContainsKey(new RefWrapperType(type)) == false) + return _emptyDictionary; + + return groupsPerEntity[new RefWrapperType(type)]; + } + struct GroupsList { static GroupsList() diff --git a/Svelto.ECS/EntitiesDB.cs b/Svelto.ECS/EntitiesDB.cs index 781f4dd..d65fa37 100644 --- a/Svelto.ECS/EntitiesDB.cs +++ b/Svelto.ECS/EntitiesDB.cs @@ -6,36 +6,31 @@ using System; using System.Runtime.CompilerServices; using Svelto.Common; using Svelto.DataStructures; -using Svelto.ECS.Hybrid; using Svelto.ECS.Internal; namespace Svelto.ECS { public partial class EntitiesDB { - internal EntitiesDB - (FasterDictionary, ITypeSafeDictionary>> groupEntityComponentsDB - , FasterDictionary, FasterDictionary> groupsPerEntity - , EntitiesStream entityStream) + internal EntitiesDB(EnginesRoot enginesRoot) { - _groupEntityComponentsDB = groupEntityComponentsDB; - _groupsPerEntity = groupsPerEntity; - _entityStream = entityStream; + _enginesRoot = enginesRoot; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ref T QueryUniqueEntity(ExclusiveGroupStruct group) where T : struct, IEntityComponent + EntityCollection InternalQueryEntities(FasterDictionary entitiesInGroupPerType) + where T : struct, IEntityComponent { - var entities = QueryEntities(group); + uint count = 0; + IBuffer buffer; + if (SafeQueryEntityDictionary(out var typeSafeDictionary, entitiesInGroupPerType) == false) + buffer = RetrieveEmptyEntityComponentArray(); + else + { + var safeDictionary = (typeSafeDictionary as ITypeSafeDictionary); + buffer = safeDictionary.GetValues(out count); + } -#if DEBUG && !PROFILE_SVELTO - if (entities.count == 0) - throw new ECSException("Unique entity not found '".FastConcat(typeof(T).ToString()).FastConcat("'")); - if (entities.count != 1) - throw new ECSException("Unique entities must be unique! '".FastConcat(typeof(T).ToString()) - .FastConcat("'")); -#endif - return ref entities[0]; + return new EntityCollection(buffer, count); } /// @@ -49,33 +44,34 @@ namespace Svelto.ECS public EntityCollection QueryEntities(ExclusiveGroupStruct groupStructId) where T : struct, IEntityComponent { - IBuffer buffer; - uint count = 0; - - if (SafeQueryEntityDictionary(groupStructId, out var typeSafeDictionary) == false) - buffer = RetrieveEmptyEntityComponentArray(); - else + if (groupEntityComponentsDB.TryGetValue(groupStructId, out var entitiesInGroupPerType) == false) { - var safeDictionary = (typeSafeDictionary as ITypeSafeDictionary); - buffer = safeDictionary.GetValues(out count); + var buffer = RetrieveEmptyEntityComponentArray(); + return new EntityCollection(buffer, 0); } - return new EntityCollection(buffer, count); + return InternalQueryEntities(entitiesInGroupPerType); } public EntityCollection QueryEntities(ExclusiveGroupStruct groupStruct) where T1 : struct, IEntityComponent where T2 : struct, IEntityComponent { - var T1entities = QueryEntities(groupStruct); - var T2entities = QueryEntities(groupStruct); + if (groupEntityComponentsDB.TryGetValue(groupStruct, out var entitiesInGroupPerType) == false) + { + return new EntityCollection(new EntityCollection(RetrieveEmptyEntityComponentArray(), 0), + new EntityCollection(RetrieveEmptyEntityComponentArray(), 0)); + } + + var T1entities = InternalQueryEntities(entitiesInGroupPerType); + var T2entities = InternalQueryEntities(entitiesInGroupPerType); #if DEBUG && !PROFILE_SVELTO if (T1entities.count != T2entities.count) throw new ECSException("Entity components count do not match in group. Entity 1: ' count: " - .FastConcat(T1entities.count).FastConcat(" ", typeof(T1).ToString()) - .FastConcat("'. Entity 2: ' count: ".FastConcat(T2entities.count) - .FastConcat(" ", typeof(T2).ToString()) - .FastConcat("' group: ", groupStruct.ToName()))); -#endif + .FastConcat(T1entities.count).FastConcat(" ", typeof(T1).ToString()) + .FastConcat("'. Entity 2: ' count: ".FastConcat(T2entities.count) + .FastConcat(" ", typeof(T2).ToString()) + .FastConcat("' group: ", groupStruct.ToName()))); +#endif return new EntityCollection(T1entities, T2entities); } @@ -83,66 +79,87 @@ namespace Svelto.ECS public EntityCollection QueryEntities(ExclusiveGroupStruct groupStruct) where T1 : struct, IEntityComponent where T2 : struct, IEntityComponent where T3 : struct, IEntityComponent { - var T1entities = QueryEntities(groupStruct); - var T2entities = QueryEntities(groupStruct); - var T3entities = QueryEntities(groupStruct); + if (groupEntityComponentsDB.TryGetValue(groupStruct, out var entitiesInGroupPerType) == false) + { + return new EntityCollection( + new EntityCollection(RetrieveEmptyEntityComponentArray(), 0), + new EntityCollection(RetrieveEmptyEntityComponentArray(), 0), + new EntityCollection(RetrieveEmptyEntityComponentArray(), 0)); + } + + var T1entities = InternalQueryEntities(entitiesInGroupPerType); + var T2entities = InternalQueryEntities(entitiesInGroupPerType); + var T3entities = InternalQueryEntities(entitiesInGroupPerType); #if DEBUG && !PROFILE_SVELTO if (T1entities.count != T2entities.count || T2entities.count != T3entities.count) throw new ECSException("Entity components count do not match in group. Entity 1: " - .FastConcat(typeof(T1).ToString()).FastConcat(" count: ") - .FastConcat(T1entities.count).FastConcat( - " Entity 2: " - .FastConcat(typeof(T2).ToString()).FastConcat(" count: ") - .FastConcat(T2entities.count) - .FastConcat(" Entity 3: ".FastConcat(typeof(T3).ToString())) - .FastConcat(" count: ").FastConcat(T3entities.count))); -#endif + .FastConcat(typeof(T1).ToString()).FastConcat(" count: ") + .FastConcat(T1entities.count).FastConcat( + " Entity 2: " + .FastConcat(typeof(T2).ToString()).FastConcat(" count: ") + .FastConcat(T2entities.count) + .FastConcat(" Entity 3: ".FastConcat(typeof(T3).ToString())) + .FastConcat(" count: ").FastConcat(T3entities.count))); +#endif return new EntityCollection(T1entities, T2entities, T3entities); } - public int IterateOverGroupsAndCount - (in LocalFasterReadOnlyList groups) where T : struct, IEntityComponent + public EntityCollection QueryEntities(ExclusiveGroupStruct groupStruct) + where T1 : struct, IEntityComponent where T2 : struct, IEntityComponent where T3 : struct, IEntityComponent where T4 : struct, IEntityComponent { - int count = 0; - - for (int i = 0; i < groups.count; i++) + if (groupEntityComponentsDB.TryGetValue(groupStruct, out var entitiesInGroupPerType) == false) { - count += Count(groups[i]); + return new EntityCollection( + new EntityCollection(RetrieveEmptyEntityComponentArray(), 0), + new EntityCollection(RetrieveEmptyEntityComponentArray(), 0), + new EntityCollection(RetrieveEmptyEntityComponentArray(), 0), + new EntityCollection(RetrieveEmptyEntityComponentArray(), 0)); } + + var T1entities = InternalQueryEntities(entitiesInGroupPerType); + var T2entities = InternalQueryEntities(entitiesInGroupPerType); + var T3entities = InternalQueryEntities(entitiesInGroupPerType); + var T4entities = InternalQueryEntities(entitiesInGroupPerType); +#if DEBUG && !PROFILE_SVELTO + if (T1entities.count != T2entities.count || T2entities.count != T3entities.count) + throw new ECSException("Entity components count do not match in group. Entity 1: " + .FastConcat(typeof(T1).ToString()).FastConcat(" count: ") + .FastConcat(T1entities.count).FastConcat( + " Entity 2: " + .FastConcat(typeof(T2).ToString()).FastConcat(" count: ") + .FastConcat(T2entities.count) + .FastConcat(" Entity 3: ".FastConcat(typeof(T3).ToString())) + .FastConcat(" count: ").FastConcat(T3entities.count))); +#endif - return 0; + return new EntityCollection(T1entities, T2entities, T3entities, T4entities); } - public TupleRef QueryEntities + public GroupsEnumerable QueryEntities (in LocalFasterReadOnlyList groups) where T : struct, IEntityComponent { - return new TupleRef(new EntityCollections(this, groups), new GroupsEnumerable(this, groups)); + return new GroupsEnumerable(this, groups); } - public TupleRef QueryEntities(in LocalFasterReadOnlyList groups) + public GroupsEnumerable QueryEntities(in LocalFasterReadOnlyList groups) where T1 : struct, IEntityComponent where T2 : struct, IEntityComponent { - return new TupleRef(new EntityCollections(this, groups) - , new GroupsEnumerable(this, groups)); + return new GroupsEnumerable(this, groups); } - public TupleRef QueryEntities(in LocalFasterReadOnlyList groups) + public GroupsEnumerable QueryEntities(in LocalFasterReadOnlyList groups) where T1 : struct, IEntityComponent where T2 : struct, IEntityComponent where T3 : struct, IEntityComponent { - return new TupleRef(new EntityCollections(this, groups) - , new GroupsEnumerable(this, groups)); + return new GroupsEnumerable(this, groups); } - public TupleRef QueryEntities + public GroupsEnumerable QueryEntities (in LocalFasterReadOnlyList groups) - where T1 : struct, IEntityComponent - where T2 : struct, IEntityComponent - where T3 : struct, IEntityComponent - where T4 : struct, IEntityComponent + where T1 : struct, IEntityComponent where T2 : struct, IEntityComponent + where T3 : struct, IEntityComponent where T4 : struct, IEntityComponent { - return new TupleRef(new EntityCollections(this, groups) - , new GroupsEnumerable(this, groups)); + return new GroupsEnumerable(this, groups); } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -150,11 +167,11 @@ namespace Svelto.ECS where T : struct, IEntityComponent { if (SafeQueryEntityDictionary(groupStructId, out var typeSafeDictionary) == false) - throw new EntityGroupNotFoundException(typeof(T)); + throw new EntityGroupNotFoundException(typeof(T) , groupStructId.ToName()); return (typeSafeDictionary as ITypeSafeDictionary).ToEGIDMapper(groupStructId); } - + [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool TryQueryMappedEntities (ExclusiveGroupStruct groupStructId, out EGIDMapper mapper) where T : struct, IEntityComponent @@ -190,8 +207,8 @@ namespace Svelto.ECS [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool ExistsAndIsNotEmpty(ExclusiveGroupStruct gid) { - if (_groupEntityComponentsDB.TryGetValue( - gid, out FasterDictionary, ITypeSafeDictionary> group) == true) + if (groupEntityComponentsDB.TryGetValue( + gid, out FasterDictionary group) == true) { return group.count > 0; } @@ -210,50 +227,36 @@ namespace Svelto.ECS { if (SafeQueryEntityDictionary(groupStruct, out var typeSafeDictionary) == false) return 0; - + return (int) typeSafeDictionary.count; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void PublishEntityChange(EGID egid) where T : unmanaged, IEntityComponent + public bool FoundInGroups() where T1 : IEntityComponent { - _entityStream.PublishEntity(ref this.QueryEntity(egid), egid); + return groupsPerEntity.ContainsKey(TypeRefWrapper.wrapper); } - [Obsolete("This Method will be removed soon. please use QueryEntities instead")] - public void ExecuteOnAllEntities(ExecuteOnAllEntitiesAction action) where T : struct, IEntityComponent - { - if (_groupsPerEntity.TryGetValue(TypeRefWrapper.wrapper, out var dictionary)) - foreach (var pair in dictionary) - { - IBuffer entities = (pair.Value as ITypeSafeDictionary).GetValues(out var count); - - if (count > 0) - action(entities, new ExclusiveGroupStruct(pair.Key), count, this); - } - } + public bool IsDisposing => _enginesRoot._isDisposing; - [Obsolete("This Method will be removed soon. please use QueryEntities instead")] - public void ExecuteOnAllEntities(ref W value, ExecuteOnAllEntitiesAction action) - where T : struct, IEntityComponent + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal bool SafeQueryEntityDictionary(out ITypeSafeDictionary typeSafeDictionary, + FasterDictionary entitiesInGroupPerType) + where T : IEntityComponent { - if (_groupsPerEntity.TryGetValue(TypeRefWrapper.wrapper, out var dic)) - foreach (var pair in dic) - { - IBuffer entities = (pair.Value as ITypeSafeDictionary).GetValues(out var innerCount); + if (entitiesInGroupPerType.TryGetValue(new RefWrapperType(TypeCache.type), out var safeDictionary) == false) + { + typeSafeDictionary = default; + return false; + } - if (innerCount > 0) - action(entities, new ExclusiveGroupStruct(pair.Key), innerCount, this, ref value); - } - } + //return the indexes entities if they exist + typeSafeDictionary = safeDictionary; - public bool FoundInGroups() where T1 : IEntityComponent - { - return _groupsPerEntity.ContainsKey(TypeRefWrapper.wrapper); + return true; } - + [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal bool SafeQueryEntityDictionary(uint group, out ITypeSafeDictionary typeSafeDictionary) + internal bool SafeQueryEntityDictionary(ExclusiveGroupStruct group, out ITypeSafeDictionary typeSafeDictionary) where T : IEntityComponent { if (UnsafeQueryEntityDictionary(group, TypeCache.type, out var safeDictionary) == false) @@ -269,17 +272,47 @@ namespace Svelto.ECS } [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal bool UnsafeQueryEntityDictionary(uint group, Type type, out ITypeSafeDictionary typeSafeDictionary) + internal bool UnsafeQueryEntityDictionary(ExclusiveGroupStruct group, Type type, out ITypeSafeDictionary typeSafeDictionary) { //search for the group - if (_groupEntityComponentsDB.TryGetValue(group, out var entitiesInGroupPerType) == false) + if (groupEntityComponentsDB.TryGetValue(group, out var entitiesInGroupPerType) == false) { typeSafeDictionary = null; return false; } //search for the indexed entities in the group - return entitiesInGroupPerType.TryGetValue(new RefWrapper(type), out typeSafeDictionary); + return entitiesInGroupPerType.TryGetValue(new RefWrapperType(type), out typeSafeDictionary); + } + + internal bool FindIndex(uint entityID, ExclusiveGroupStruct @group, Type type, out uint index) + { + EGID entityGID = new EGID(entityID, @group); + + index = default; + + if (UnsafeQueryEntityDictionary(@group, type, out var safeDictionary) == false) + return false; + + if (safeDictionary.TryFindIndex(entityGID.entityID, out index) == false) + return false; + + return true; + } + + internal uint GetIndex(uint entityID, ExclusiveGroupStruct @group, Type type) + { + EGID entityGID = new EGID(entityID, @group); + + if (UnsafeQueryEntityDictionary(@group, type, out var safeDictionary) == false) + { + throw new EntityNotFoundException(entityGID, type); + } + + if (safeDictionary.TryFindIndex(entityGID.entityID, out var index) == false) + throw new EntityNotFoundException(entityGID, type); + + return index; } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -309,20 +342,25 @@ namespace Svelto.ECS } } - readonly FasterDictionary _emptyDictionary = - new FasterDictionary(); + static readonly FasterDictionary _emptyDictionary = + new FasterDictionary(); - readonly EntitiesStream _entityStream; + readonly EnginesRoot _enginesRoot; + + EntitiesStreams _entityStream => _enginesRoot._entityStreams; //grouped set of entity components, this is the standard way to handle entity components are grouped per //group, then indexable per type, then indexable per EGID. however the TypeSafeDictionary can return an array of //values directly, that can be iterated over, so that is possible to iterate over all the entity components of //a specific type inside a specific group. - readonly FasterDictionary, ITypeSafeDictionary>> - _groupEntityComponentsDB; - - //needed to be able to track in which groups a specific entity type can be found. - //may change in future as it could be expanded to support queries - readonly FasterDictionary, FasterDictionary> _groupsPerEntity; +FasterDictionary> + groupEntityComponentsDB => _enginesRoot._groupEntityComponentsDB; + +//for each entity view type, return the groups (dictionary of entities indexed by entity id) where they are +//found indexed by group id. TypeSafeDictionary are never created, they instead point to the ones hold +//by _groupEntityComponentsDB +// >> + FasterDictionary> groupsPerEntity => + _enginesRoot._groupsPerEntity; } } \ No newline at end of file diff --git a/Svelto.ECS/EntityCollection.cs b/Svelto.ECS/EntityCollection.cs index 7211113..c39ed69 100644 --- a/Svelto.ECS/EntityCollection.cs +++ b/Svelto.ECS/EntityCollection.cs @@ -1,5 +1,4 @@ using System.Runtime.CompilerServices; -using Svelto.Common; using Svelto.DataStructures; using Svelto.ECS.Internal; @@ -7,7 +6,7 @@ namespace Svelto.ECS { public readonly ref struct EntityCollection where T : struct, IEntityComponent { - static readonly bool IsUnmanaged = TypeSafeDictionary._isUmanaged; + static readonly bool IsUnmanaged = TypeSafeDictionary.IsUnmanaged; public EntityCollection(IBuffer buffer, uint count):this() { @@ -17,74 +16,19 @@ namespace Svelto.ECS _managedBuffer = (MB) buffer; _count = count; - _buffer = buffer; } public uint count => _count; internal readonly MB _managedBuffer; internal readonly NB _nativedBuffer; - readonly uint _count; - //todo very likely remove this - public ref T this[uint i] - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get - { - if (IsUnmanaged) - return ref _nativedBuffer[i]; - else - return ref _managedBuffer[i]; - } - } - - public ref T this[int i] - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get - { - if (IsUnmanaged) - return ref _nativedBuffer[i]; - else - return ref _managedBuffer[i]; - } - } - - //TODO SOON: ALL THIS STUFF BELOW MUST DISAPPEAR - readonly IBuffer _buffer; - - //todo to remove - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public EntityIterator GetEnumerator() { return new EntityIterator(_buffer, _count); } -//todo to remove - public ref struct EntityIterator - { - public EntityIterator(IBuffer array, uint count) : this() - { - _array = array; - _count = count; - _index = -1; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool MoveNext() { return ++_index < _count; } - - public ref T Current - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => ref _array[_index]; - } - - readonly IBuffer _array; - readonly uint _count; - int _index; - } + readonly uint _count; } public readonly ref struct EntityCollection where T1 : struct, IEntityComponent where T2 : struct, IEntityComponent { - public EntityCollection(in EntityCollection array1, in EntityCollection array2) + internal EntityCollection(in EntityCollection array1, in EntityCollection array2) { _array1 = array1; _array2 = array2; @@ -92,14 +36,13 @@ namespace Svelto.ECS public uint count => _array1.count; - //todo to remove - public EntityCollection Item2 + internal EntityCollection buffer2 { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => _array2; } -//todo to remove - public EntityCollection Item1 + + internal EntityCollection buffer1 { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => _array1; @@ -107,81 +50,52 @@ namespace Svelto.ECS readonly EntityCollection _array1; readonly EntityCollection _array2; -//todo to remove - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public EntityIterator GetEnumerator() { return new EntityIterator(this); } -//todo to remove - public ref struct EntityIterator - { - public EntityIterator(in EntityCollection array1) : this() - { - _array1 = array1; - _count = array1.count; - _index = -1; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool MoveNext() { return ++_index < _count; } - - public void Reset() { _index = -1; } - - public ValueRef Current - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => new ValueRef(_array1, (uint) _index); - } - - readonly EntityCollection _array1; - readonly uint _count; - int _index; - } - } public readonly ref struct EntityCollection where T3 : struct, IEntityComponent - where T2 : struct, IEntityComponent - where T1 : struct, IEntityComponent + where T2 : struct, IEntityComponent + where T1 : struct, IEntityComponent { - public EntityCollection + internal EntityCollection (in EntityCollection array1, in EntityCollection array2, in EntityCollection array3) { _array1 = array1; _array2 = array2; _array3 = array3; } -//todo to remove - public EntityCollection Item1 + + internal EntityCollection buffer1 { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => _array1; } -//todo to remove - public EntityCollection Item2 + + internal EntityCollection buffer2 { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => _array2; } -//todo to remove - public EntityCollection Item3 + + internal EntityCollection buffer3 { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => _array3; } - public uint count => Item1.count; + internal uint count => buffer1.count; readonly EntityCollection _array1; readonly EntityCollection _array2; readonly EntityCollection _array3; } - + public readonly ref struct EntityCollection where T1 : struct, IEntityComponent where T2 : struct, IEntityComponent where T3 : struct, IEntityComponent where T4 : struct, IEntityComponent { - public EntityCollection + internal EntityCollection (in EntityCollection array1, in EntityCollection array2, in EntityCollection array3, in EntityCollection array4) { _array1 = array1; @@ -190,35 +104,31 @@ namespace Svelto.ECS _array4 = array4; } - //todo to remove - public EntityCollection Item1 + internal EntityCollection Item1 { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => _array1; } - //todo to remove - public EntityCollection Item2 + internal EntityCollection Item2 { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => _array2; } - //todo to remove - public EntityCollection Item3 + internal EntityCollection Item3 { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => _array3; } - //todo to remove - public EntityCollection Item4 + internal EntityCollection Item4 { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => _array4; } - public uint count => _array1.count; + internal uint count => _array1.count; readonly EntityCollection _array1; readonly EntityCollection _array2; @@ -308,62 +218,4 @@ namespace Svelto.ECS count = this.count; } } - - public readonly ref struct ValueRef where T2 : struct, IEntityComponent where T1 : struct, IEntityComponent - { - readonly EntityCollection array1; - - readonly uint index; - - public ValueRef(in EntityCollection entity2, uint i) - { - array1 = entity2; - index = i; - } - - public ref T1 entityComponentA - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => ref array1.Item1[index]; - } - - public ref T2 entityComponentB - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => ref array1.Item2[index]; - } - } - - public readonly ref struct ValueRef where T2 : struct, IEntityComponent - where T1 : struct, IEntityComponent - where T3 : struct, IEntityComponent - { - readonly EntityCollection array1; - - readonly uint index; - - public ValueRef(in EntityCollection entity, uint i) - { - array1 = entity; - index = i; - } - - public ref T1 entityComponentA - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => ref array1.Item1[index]; - } - - public ref T2 entityComponentB - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => ref array1.Item2[index]; - } - - public ref T3 entityComponentC - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => ref array1.Item3[index]; - } - } } diff --git a/Svelto.ECS/EntityCollections.cs b/Svelto.ECS/EntityCollections.cs deleted file mode 100644 index 28e7161..0000000 --- a/Svelto.ECS/EntityCollections.cs +++ /dev/null @@ -1,266 +0,0 @@ -using System; -using System.Runtime.CompilerServices; -using Svelto.DataStructures; - -namespace Svelto.ECS -{ - public readonly ref struct EntityCollections where T1 : struct, IEntityComponent - where T2 : struct, IEntityComponent - where T3 : struct, IEntityComponent - where T4 : struct, IEntityComponent - { - public EntityCollections(EntitiesDB db, in LocalFasterReadOnlyList groups) : this() - { - _db = db; - _groups = groups; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public EntityGroupsIterator GetEnumerator() - { - throw new NotImplementedException("tell seb to finish this one"); -#pragma warning disable 162 - return new EntityGroupsIterator(_db, _groups); -#pragma warning restore 162 - } - - readonly EntitiesDB _db; - readonly LocalFasterReadOnlyList _groups; - - public ref struct EntityGroupsIterator - { - public EntityGroupsIterator(EntitiesDB db, in LocalFasterReadOnlyList groups) : this() - { - _db = db; - _groups = groups; - _indexGroup = -1; - _index = -1; - } - - public bool MoveNext() - { - //attention, the while is necessary to skip empty groups - while (_index + 1 >= _count && ++_indexGroup < _groups.count) - { - _index = -1; - _array1 = _db.QueryEntities(_groups[_indexGroup]); - _count = _array1.count; - } - - return ++_index < _count; - } - - public void Reset() - { - _index = -1; - _indexGroup = -1; - - _array1 = _db.QueryEntities(_groups[0]); - _count = _array1.count; - } - - public ValueRef Current - { - get - { - var valueRef = new ValueRef(_array1, (uint) _index); - return valueRef; - } - } - - readonly EntitiesDB _db; - readonly LocalFasterReadOnlyList _groups; - uint _count; - int _index; - int _indexGroup; - - EntityCollection _array1; - } - } - - public readonly ref struct EntityCollections where T1 : struct, IEntityComponent - where T2 : struct, IEntityComponent - where T3 : struct, IEntityComponent - { - public EntityCollections(EntitiesDB db, in LocalFasterReadOnlyList groups) : this() - { - _db = db; - _groups = groups; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public EntityGroupsIterator GetEnumerator() { return new EntityGroupsIterator(_db, _groups); } - - readonly EntitiesDB _db; - readonly LocalFasterReadOnlyList _groups; - - public ref struct EntityGroupsIterator - { - public EntityGroupsIterator(EntitiesDB db, in LocalFasterReadOnlyList groups) : this() - { - _db = db; - _groups = groups; - _indexGroup = -1; - _index = -1; - } - - public bool MoveNext() - { - //attention, the while is necessary to skip empty groups - while (_index + 1 >= _count && ++_indexGroup < _groups.count) - { - _index = -1; - _array1 = _db.QueryEntities(_groups[_indexGroup]); - _count = _array1.count; - } - - return ++_index < _count; - } - - public void Reset() - { - _index = -1; - _indexGroup = -1; - - _array1 = _db.QueryEntities(_groups[0]); - _count = _array1.count; - } - - public ValueRef Current - { - get - { - var valueRef = new ValueRef(_array1, (uint) _index); - return valueRef; - } - } - - readonly EntitiesDB _db; - readonly LocalFasterReadOnlyList _groups; - uint _count; - int _index; - int _indexGroup; - - EntityCollection _array1; - } - } - - public readonly ref struct EntityCollections - where T1 : struct, IEntityComponent where T2 : struct, IEntityComponent - { - public EntityCollections(EntitiesDB db, in LocalFasterReadOnlyList groups) : this() - { - _db = db; - _groups = groups; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public EntityGroupsIterator GetEnumerator() { return new EntityGroupsIterator(_db, _groups); } - - readonly EntitiesDB _db; - readonly LocalFasterReadOnlyList _groups; - - public ref struct EntityGroupsIterator - { - public EntityGroupsIterator(EntitiesDB db, in LocalFasterReadOnlyList groups) : this() - { - _db = db; - _groups = groups; - _indexGroup = -1; - _index = -1; - } - - public bool MoveNext() - { - //attention, the while is necessary to skip empty groups - while (_index + 1 >= _array1.count && ++_indexGroup < _groups.count) - { - _index = -1; - _array1 = _db.QueryEntities(_groups[_indexGroup]); - } - - return ++_index < _array1.count; - } - - public void Reset() - { - _index = -1; - _indexGroup = -1; - - _array1 = _db.QueryEntities(_groups[0]); - } - - public ValueRef Current - { - get - { - var valueRef = new ValueRef(_array1, (uint) _index); - return valueRef; - } - } - - readonly EntitiesDB _db; - readonly LocalFasterReadOnlyList _groups; - int _index; - int _indexGroup; - - EntityCollection _array1; - } - } - - public readonly ref struct EntityCollections where T : struct, IEntityComponent - { - public EntityCollections(EntitiesDB db, in LocalFasterReadOnlyList groups) : this() - { - _db = db; - _groups = groups; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public EntityGroupsIterator GetEnumerator() { return new EntityGroupsIterator(_db, _groups); } - - readonly EntitiesDB _db; - readonly LocalFasterReadOnlyList _groups; - - public ref struct EntityGroupsIterator - { - public EntityGroupsIterator(EntitiesDB db, in LocalFasterReadOnlyList groups) : this() - { - _db = db; - _groups = groups; - _indexGroup = -1; - _index = -1; - } - - public bool MoveNext() - { - //attention, the while is necessary to skip empty groups - while (_index + 1 >= _count && ++_indexGroup < _groups.count) - { - _index = -1; - _array = _db.QueryEntities(_groups[_indexGroup]); - _count = _array.count; - } - - return ++_index < _count; - } - - public void Reset() - { - _index = -1; - _indexGroup = -1; - _count = 0; - } - - public ref T Current => ref _array[(uint) _index]; - - readonly EntitiesDB _db; - readonly LocalFasterReadOnlyList _groups; - - EntityCollection _array; - uint _count; - int _index; - int _indexGroup; - } - } -} \ No newline at end of file diff --git a/Svelto.ECS/EntityComponentInitializer.cs b/Svelto.ECS/EntityComponentInitializer.cs index 9b7caa6..72cbad2 100644 --- a/Svelto.ECS/EntityComponentInitializer.cs +++ b/Svelto.ECS/EntityComponentInitializer.cs @@ -6,7 +6,7 @@ namespace Svelto.ECS { public readonly ref struct EntityComponentInitializer { - public EntityComponentInitializer(EGID id, FasterDictionary, ITypeSafeDictionary> group) + public EntityComponentInitializer(EGID id, FasterDictionary group) { _group = group; _ID = id; @@ -16,7 +16,7 @@ namespace Svelto.ECS public void Init(T initializer) where T : struct, IEntityComponent { - if (_group.TryGetValue(new RefWrapper(ComponentBuilder.ENTITY_COMPONENT_TYPE), + if (_group.TryGetValue(new RefWrapperType(ComponentBuilder.ENTITY_COMPONENT_TYPE), out var typeSafeDictionary) == false) return; var dictionary = (ITypeSafeDictionary) typeSafeDictionary; @@ -30,7 +30,7 @@ namespace Svelto.ECS public ref T GetOrCreate() where T : struct, IEntityComponent { - ref var entityDictionary = ref _group.GetOrCreate(new RefWrapper(ComponentBuilder.ENTITY_COMPONENT_TYPE) + ref var entityDictionary = ref _group.GetOrCreate(new RefWrapperType(ComponentBuilder.ENTITY_COMPONENT_TYPE) , TypeSafeDictionaryFactory.Create); var dictionary = (ITypeSafeDictionary) entityDictionary; @@ -39,13 +39,13 @@ namespace Svelto.ECS public ref T Get() where T : struct, IEntityComponent { - return ref (_group[new RefWrapper(ComponentBuilder.ENTITY_COMPONENT_TYPE)] as ITypeSafeDictionary)[ + return ref (_group[new RefWrapperType(ComponentBuilder.ENTITY_COMPONENT_TYPE)] as ITypeSafeDictionary)[ _ID.entityID]; } public bool Has() where T : struct, IEntityComponent { - if (_group.TryGetValue(new RefWrapper(ComponentBuilder.ENTITY_COMPONENT_TYPE), + if (_group.TryGetValue(new RefWrapperType(ComponentBuilder.ENTITY_COMPONENT_TYPE), out var typeSafeDictionary)) { var dictionary = (ITypeSafeDictionary) typeSafeDictionary; @@ -58,6 +58,6 @@ namespace Svelto.ECS } readonly EGID _ID; - readonly FasterDictionary, ITypeSafeDictionary> _group; + readonly FasterDictionary _group; } } \ No newline at end of file diff --git a/Svelto.ECS/EntityDescriptorTemplate.cs b/Svelto.ECS/EntityDescriptorTemplate.cs index 7ce178c..d32b16b 100644 --- a/Svelto.ECS/EntityDescriptorTemplate.cs +++ b/Svelto.ECS/EntityDescriptorTemplate.cs @@ -1,17 +1,26 @@ +using System; + namespace Svelto.ECS { public interface IEntityDescriptor { IComponentBuilder[] componentsToBuild { get; } } + + public interface IDynamicEntityDescriptor: IEntityDescriptor + { + } static class EntityDescriptorTemplate where TType : IEntityDescriptor, new() { static EntityDescriptorTemplate() { - descriptor = new TType(); + realDescriptor = new TType(); + descriptor = realDescriptor; } - public static IEntityDescriptor descriptor { get; } + public static TType realDescriptor { get; } + public static Type type => typeof(TType); + public static IEntityDescriptor descriptor { get; } } } diff --git a/Svelto.ECS/EntityFactory.cs b/Svelto.ECS/EntityFactory.cs index 088f743..ef1760a 100644 --- a/Svelto.ECS/EntityFactory.cs +++ b/Svelto.ECS/EntityFactory.cs @@ -6,23 +6,23 @@ namespace Svelto.ECS.Internal { static class EntityFactory { - public static FasterDictionary, ITypeSafeDictionary> BuildGroupedEntities(EGID egid, - EnginesRoot.DoubleBufferedEntitiesToAdd groupEntitiesToAdd, IComponentBuilder[] componentsToBuild, - IEnumerable implementors) + public static FasterDictionary BuildGroupedEntities + (EGID egid, EnginesRoot.DoubleBufferedEntitiesToAdd groupEntitiesToAdd + , IComponentBuilder[] componentsToBuild, IEnumerable implementors, Type implementorType) { var group = FetchEntityGroup(egid.groupID, groupEntitiesToAdd); - BuildEntitiesAndAddToGroup(egid, group, componentsToBuild, implementors); + BuildEntitiesAndAddToGroup(egid, group, componentsToBuild, implementors, implementorType); return group; } - static FasterDictionary, ITypeSafeDictionary> FetchEntityGroup(uint groupID, + static FasterDictionary FetchEntityGroup(ExclusiveGroupStruct groupID, EnginesRoot.DoubleBufferedEntitiesToAdd groupEntityComponentsByType) { if (groupEntityComponentsByType.current.TryGetValue(groupID, out var group) == false) { - group = new FasterDictionary, ITypeSafeDictionary>(); + group = new FasterDictionary(); groupEntityComponentsByType.current.Add(groupID, group); } @@ -35,9 +35,9 @@ namespace Svelto.ECS.Internal return group; } - static void BuildEntitiesAndAddToGroup(EGID entityID, - FasterDictionary, ITypeSafeDictionary> group, - IComponentBuilder[] componentBuilders, IEnumerable implementors) + static void BuildEntitiesAndAddToGroup + (EGID entityID, FasterDictionary @group + , IComponentBuilder[] componentBuilders, IEnumerable implementors, Type implementorType) { var count = componentBuilders.Length; @@ -49,7 +49,7 @@ namespace Svelto.ECS.Internal var entityComponentType = componentBuilders[index].GetEntityComponentType(); if (types.Contains(entityComponentType)) { - throw new ECSException("EntityBuilders must be unique inside an EntityDescriptor"); + throw new ECSException($"EntityBuilders must be unique inside an EntityDescriptor. Descriptor Type {implementorType} Component Type: {entityComponentType}"); } types.Add(entityComponentType); @@ -58,17 +58,17 @@ namespace Svelto.ECS.Internal for (var index = 0; index < count; ++index) { var entityComponentBuilder = componentBuilders[index]; - var entityComponentType = entityComponentBuilder.GetEntityComponentType(); + var entityComponentType = entityComponentBuilder.GetEntityComponentType(); BuildEntity(entityID, @group, entityComponentType, entityComponentBuilder, implementors); } } - static void BuildEntity(EGID entityID, FasterDictionary, ITypeSafeDictionary> group, + static void BuildEntity(EGID entityID, FasterDictionary group, Type entityComponentType, IComponentBuilder componentBuilder, IEnumerable implementors) { var entityComponentsPoolWillBeCreated = - group.TryGetValue(new RefWrapper(entityComponentType), out var safeDictionary) == false; + group.TryGetValue(new RefWrapperType(entityComponentType), out var safeDictionary) == false; //passing the undefined entityComponentsByType inside the entityComponentBuilder will allow it to be created with the //correct type and casted back to the undefined list. that's how the list will be eventually of the target @@ -76,7 +76,7 @@ namespace Svelto.ECS.Internal componentBuilder.BuildEntityAndAddToList(ref safeDictionary, entityID, implementors); if (entityComponentsPoolWillBeCreated) - group.Add(new RefWrapper(entityComponentType), safeDictionary); + group.Add(new RefWrapperType(entityComponentType), safeDictionary); } } } \ No newline at end of file diff --git a/Svelto.ECS/EntityGroupNotFoundException.cs b/Svelto.ECS/EntityGroupNotFoundException.cs index fbc49dd..be306f0 100644 --- a/Svelto.ECS/EntityGroupNotFoundException.cs +++ b/Svelto.ECS/EntityGroupNotFoundException.cs @@ -4,8 +4,8 @@ namespace Svelto.ECS.Internal { class EntityGroupNotFoundException : Exception { - public EntityGroupNotFoundException(Type type) - : base("entity group not found ".FastConcat(type.ToString())) + public EntityGroupNotFoundException(Type type, string toName) + : base($"entity group {toName} not used for component type ".FastConcat(type.ToString())) { } } diff --git a/Svelto.ECS/EntityHierarchyStruct.cs b/Svelto.ECS/EntityHierarchyStruct.cs deleted file mode 100644 index 5ef8637..0000000 --- a/Svelto.ECS/EntityHierarchyStruct.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace Svelto.ECS -{ - public struct EntityHierarchyStruct: IEntityComponent, INeedEGID - { - public readonly ExclusiveGroupStruct parentGroup; - - public EntityHierarchyStruct(ExclusiveGroup @group): this() { parentGroup = group; } - - public EGID ID { get; set; } - } -} \ No newline at end of file diff --git a/Svelto.ECS/EntityInfoView.cs b/Svelto.ECS/EntityInfoView.cs index 40eed5c..edbb968 100644 --- a/Svelto.ECS/EntityInfoView.cs +++ b/Svelto.ECS/EntityInfoView.cs @@ -1,6 +1,6 @@ namespace Svelto.ECS { - struct EntityInfoViewComponent: IEntityComponent + struct EntityInfoComponent: IEntityComponent { public IComponentBuilder[] componentsToBuild; } diff --git a/Svelto.ECS/EntityStream.cs b/Svelto.ECS/EntityStream.cs deleted file mode 100644 index aa9181d..0000000 --- a/Svelto.ECS/EntityStream.cs +++ /dev/null @@ -1,205 +0,0 @@ -using System; -using System.Runtime.InteropServices; -using Svelto.DataStructures; - -namespace Svelto.ECS -{ - /// - /// I eventually realised that, with the ECS design, no form of communication other than polling entity components can exist. - /// Using groups, you can have always an optimal set of entity components to poll. However EntityStreams - /// can be useful if: - /// - you need to react on seldom entity changes, usually due to user events - /// - you want engines to be able to track entity changes - /// - you want a thread-safe way to read entity states, which includes all the state changes and not the last - /// one only - /// - you want to communicate between EnginesRoots - /// - class EntitiesStream : IDisposable - { - internal Consumer GenerateConsumer(string name, uint capacity) - where T : unmanaged, IEntityComponent - { - if (_streams.ContainsKey(TypeRefWrapper.wrapper) == false) - _streams[TypeRefWrapper.wrapper] = new EntityStream(); - - return (_streams[TypeRefWrapper.wrapper] as EntityStream).GenerateConsumer(name, capacity); - } - - public Consumer GenerateConsumer(ExclusiveGroupStruct group, string name, uint capacity) - where T : unmanaged, IEntityComponent - { - if (_streams.ContainsKey(TypeRefWrapper.wrapper) == false) - _streams[TypeRefWrapper.wrapper] = new EntityStream(); - - EntityStream typeSafeStream = (EntityStream) _streams[TypeRefWrapper.wrapper]; - return typeSafeStream.GenerateConsumer(group, name, capacity); - } - - internal void PublishEntity(ref T entity, EGID egid) where T : unmanaged, IEntityComponent - { - if (_streams.TryGetValue(TypeRefWrapper.wrapper, out var typeSafeStream)) - (typeSafeStream as EntityStream).PublishEntity(ref entity, egid); - else - Console.LogDebug("No Consumers are waiting for this entity to change ", typeof(T)); - } - - readonly ThreadSafeDictionary, ITypeSafeStream> _streams = - new ThreadSafeDictionary, ITypeSafeStream>(); - - public void Dispose() - { - _streams.Clear(); - } - } - - interface ITypeSafeStream - { } - - public class EntityStream : ITypeSafeStream where T : unmanaged, IEntityComponent - { - ~EntityStream() - { - for (int i = 0; i < _consumers.Count; i++) - _consumers[i].Free(); - } - - internal EntityStream() - { - _consumers = new ThreadSafeFasterList>(); - } - - internal void PublishEntity(ref T entity, EGID egid) - { - for (int i = 0; i < _consumers.Count; i++) - { - unsafe - { - if (*(bool *)_consumers[i].mustBeDisposed) - { - _consumers[i].Free(); - _consumers.UnorderedRemoveAt(i); - --i; - continue; - } - - if (_consumers[i].hasGroup) - { - if (egid.groupID == _consumers[i].@group) - { - _consumers[i].Enqueue(entity, egid); - } - } - else - { - _consumers[i].Enqueue(entity, egid); - } - } - } - } - - internal Consumer GenerateConsumer(string name, uint capacity) - { - var consumer = new Consumer(name, capacity); - - _consumers.Add(consumer); - - return consumer; - } - - internal Consumer GenerateConsumer(ExclusiveGroupStruct group, string name, uint capacity) - { - var consumer = new Consumer(group, name, capacity); - - _consumers.Add(consumer); - - return consumer; - } - - readonly ThreadSafeFasterList> _consumers; - } - - public struct Consumer :IDisposable where T : unmanaged, IEntityComponent - { - internal Consumer(string name, uint capacity) : this() - { - unsafe - { -#if DEBUG && !PROFILE_SVELTO - _name = name; -#endif - _ringBuffer = new RingBuffer>((int) capacity, -#if DEBUG && !PROFILE_SVELTO - _name -#else - string.Empty -#endif - ); - mustBeDisposed = Marshal.AllocHGlobal(sizeof(bool)); - *((bool*) mustBeDisposed) = false; - } - } - - internal Consumer(ExclusiveGroupStruct group, string name, uint capacity) : this(name, capacity) - { - this.@group = @group; - hasGroup = true; - } - - internal void Enqueue(in T entity, in EGID egid) - { - _ringBuffer.Enqueue((entity, egid)); - } - - public bool TryDequeue(out T entity) - { - var tryDequeue = _ringBuffer.TryDequeue(out var values); - - entity = values.Item1; - - return tryDequeue; - } - - public bool TryDequeue(out T entity, out EGID id) - { - var tryDequeue = _ringBuffer.TryDequeue(out var values); - - entity = values.Item1; - id = values.Item2; - - return tryDequeue; - } - - public void Flush() - { - _ringBuffer.Reset(); - } - - public void Dispose() - { - unsafe - { - *(bool *)mustBeDisposed = true; - } - } - - public uint Count() - { - return (uint) _ringBuffer.Count; - } - - public void Free() - { - Marshal.FreeHGlobal(mustBeDisposed); - } - - readonly RingBuffer> _ringBuffer; - - internal readonly ExclusiveGroupStruct @group; - internal readonly bool hasGroup; - internal IntPtr mustBeDisposed; - -#if DEBUG && !PROFILE_SVELTO - readonly string _name; -#endif - } -} \ No newline at end of file diff --git a/Svelto.ECS/EntitySubmissionScheduler.cs b/Svelto.ECS/EntitySubmissionScheduler.cs index 098cbab..d5af356 100644 --- a/Svelto.ECS/EntitySubmissionScheduler.cs +++ b/Svelto.ECS/EntitySubmissionScheduler.cs @@ -4,8 +4,18 @@ namespace Svelto.ECS.Schedulers { public interface IEntitiesSubmissionScheduler: IDisposable { - EnginesRoot.EntitiesSubmitter onTick { set; } - bool paused { get; set; } } + + public abstract class EntitiesSubmissionScheduler: IEntitiesSubmissionScheduler + { + protected internal abstract EnginesRoot.EntitiesSubmitter onTick { set; } + public abstract void Dispose(); + public abstract bool paused { get; set; } + } + + public abstract class ISimpleEntitiesSubmissionScheduler: EntitiesSubmissionScheduler + { + public abstract void SubmitEntities(); + } } \ No newline at end of file diff --git a/Svelto.ECS/EntitySubmitOperation.cs b/Svelto.ECS/EntitySubmitOperation.cs index 8f02ff7..3f6ecf5 100644 --- a/Svelto.ECS/EntitySubmitOperation.cs +++ b/Svelto.ECS/EntitySubmitOperation.cs @@ -8,7 +8,7 @@ namespace Svelto.ECS : IEquatable { public readonly EntitySubmitOperationType type; - public readonly IComponentBuilder[] builders; + public readonly IComponentBuilder[] builders; public readonly EGID fromID; public readonly EGID toID; #if DEBUG && !PROFILE_SVELTO @@ -26,7 +26,19 @@ namespace Svelto.ECS trace = default; #endif } - + + public EntitySubmitOperation + (EntitySubmitOperationType operation, ExclusiveGroupStruct @group + , IComponentBuilder[] descriptorComponentsToBuild):this() + { + type = operation; + this.builders = descriptorComponentsToBuild; + fromID = new EGID(0, group); +#if DEBUG && !PROFILE_SVELTO + trace = default; +#endif + } + public static bool operator ==(EntitySubmitOperation obj1, EntitySubmitOperation obj2) { return obj1.Equals(obj2); diff --git a/Svelto.ECS/EntityViewUtility.cs b/Svelto.ECS/EntityViewUtility.cs index 057c43d..6f964d9 100644 --- a/Svelto.ECS/EntityViewUtility.cs +++ b/Svelto.ECS/EntityViewUtility.cs @@ -46,7 +46,7 @@ namespace Svelto.ECS //efficient way to collect the fields of every EntityComponentType var setters = FasterList>>.NoVirt.ToArrayFast( entityComponentBlazingFastReflection, out var count); - + //todo this should happen once per T, not once per Build if (implementors != null) { diff --git a/Svelto.ECS/ExclusiveGroup.cs b/Svelto.ECS/ExclusiveGroup.cs index 862ad28..b4fdac3 100644 --- a/Svelto.ECS/ExclusiveGroup.cs +++ b/Svelto.ECS/ExclusiveGroup.cs @@ -65,6 +65,7 @@ namespace Svelto.ECS return a._group + b; } + //todo document the use case for this method public static ExclusiveGroupStruct Search(string holderGroupName) { if (_knownGroups.ContainsKey(holderGroupName) == false) diff --git a/Svelto.ECS/ExclusiveGroupStruct.cs b/Svelto.ECS/ExclusiveGroupStruct.cs index c87d0f2..8bb0a9c 100644 --- a/Svelto.ECS/ExclusiveGroupStruct.cs +++ b/Svelto.ECS/ExclusiveGroupStruct.cs @@ -1,10 +1,34 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.Runtime.InteropServices; namespace Svelto.ECS { + public readonly struct BuildGroup + { + internal BuildGroup(ExclusiveGroupStruct group) + { + this.group = group; + } + + public static implicit operator BuildGroup(ExclusiveGroupStruct group) + { + return new BuildGroup(group); + } + + public static implicit operator BuildGroup(ExclusiveGroup group) + { + return new BuildGroup(group); + } + + public static implicit operator uint(BuildGroup groupStruct) + { + return groupStruct.group; + } + + internal ExclusiveGroupStruct @group { get; } + } + [StructLayout(LayoutKind.Explicit, Size = 4)] public struct ExclusiveGroupStruct : IEquatable, IComparable, IEqualityComparer @@ -65,7 +89,7 @@ namespace Svelto.ECS return groupStruct; } - + internal ExclusiveGroupStruct(ExclusiveGroupStruct @group):this() { this = group; } /// @@ -107,10 +131,9 @@ namespace Svelto.ECS return @group; } - [FieldOffset(0)] uint _id; + [FieldOffset(0)] uint _id; [FieldOffset(3)] byte _bytemask; - - static uint _globalId = 1; //it starts from 1 because default EGID is considered not initalized value - + + static uint _globalId = 1; //it starts from 1 because default EGID is considered not initalized value } } \ No newline at end of file diff --git a/Svelto.ECS/ExtendibleEntityDescriptor.cs b/Svelto.ECS/ExtendibleEntityDescriptor.cs index 71dc7de..384823c 100644 --- a/Svelto.ECS/ExtendibleEntityDescriptor.cs +++ b/Svelto.ECS/ExtendibleEntityDescriptor.cs @@ -8,7 +8,7 @@ namespace Svelto.ECS /// to swap and remove specialized entities from abstract engines /// /// - public class ExtendibleEntityDescriptor : IEntityDescriptor where TType : IEntityDescriptor, new() + public class ExtendibleEntityDescriptor : IDynamicEntityDescriptor where TType : IEntityDescriptor, new() { static ExtendibleEntityDescriptor() { diff --git a/Svelto.ECS/Extensions/ProcessorCount.cs b/Svelto.ECS/Extensions/ProcessorCount.cs index 56f7ca8..4df4450 100644 --- a/Svelto.ECS/Extensions/ProcessorCount.cs +++ b/Svelto.ECS/Extensions/ProcessorCount.cs @@ -4,8 +4,8 @@ namespace Svelto.ECS { internal static class ProcessorCount { - static readonly int processorCount = Environment.ProcessorCount; - + public static readonly int processorCount = Environment.ProcessorCount; + public static int BatchSize(uint totalIterations) { var iterationsPerBatch = totalIterations / processorCount; diff --git a/Svelto.ECS/Extensions/Svelto/AllGroupsEnumerable.cs b/Svelto.ECS/Extensions/Svelto/AllGroupsEnumerable.cs index 24e90fa..eb746df 100644 --- a/Svelto.ECS/Extensions/Svelto/AllGroupsEnumerable.cs +++ b/Svelto.ECS/Extensions/Svelto/AllGroupsEnumerable.cs @@ -1,3 +1,4 @@ +using Svelto.Common; using Svelto.DataStructures; using Svelto.ECS.Internal; @@ -31,7 +32,7 @@ namespace Svelto.ECS { public GroupsIterator(EntitiesDB db) : this() { - _db = db.FindGroups_INTERNAL().GetEnumerator(); + _db = db.FindGroups_INTERNAL(TypeCache.type).GetEnumerator(); } public bool MoveNext() @@ -39,7 +40,7 @@ namespace Svelto.ECS //attention, the while is necessary to skip empty groups while (_db.MoveNext() == true) { - FasterDictionary.KeyValuePairFast group = _db.Current; + FasterDictionary.KeyValuePairFast group = _db.Current; ITypeSafeDictionary typeSafeDictionary = @group.Value as ITypeSafeDictionary; if (typeSafeDictionary.count == 0) continue; @@ -55,7 +56,7 @@ namespace Svelto.ECS public GroupCollection Current => _array; - FasterDictionary.FasterDictionaryKeyValueEnumerator _db; + FasterDictionary.FasterDictionaryKeyValueEnumerator _db; GroupCollection _array; } @@ -66,63 +67,4 @@ namespace Svelto.ECS readonly EntitiesDB _db; } -#if TO_BE_FINISHED - public struct NativeAllGroupsEnumerable - where T1 : unmanaged, IEntityComponent where T2 : unmanaged, IEntityComponent - { - public NativeAllGroupsEnumerable(EntitiesDB db) - { - _db = db; - } - - public struct NativeGroupsIterator - { - public NativeGroupsIterator(EntitiesDB db) : this() - { - _db = db.FindGroups().GetEnumerator(); - } - - public bool MoveNext() - { - //attention, the while is necessary to skip empty groups - while (_db.MoveNext() == true) - { - FasterDictionary.KeyValuePairFast group = _db.Current; - - ITypeSafeDictionary typeSafeDictionary1 = @group.Value as ITypeSafeDictionary; - ITypeSafeDictionary typeSafeDictionary2 = @group.Value as ITypeSafeDictionary; - - DBC.ECS.Check.Require(typeSafeDictionary1.Count != typeSafeDictionary2.Count - , "entities count do not match"); - - if (typeSafeDictionary1.Count == 0) continue; - - _array = new BT, NB>()(new EntityCollection(typeSafeDictionary1.GetValuesArray(out var count), count) - .ToBuffer(); - - return true; - } - - return false; - } - - public void Reset() - { - } - - public BT, NB> Current => _array; - - FasterDictionary.FasterDictionaryKeyValueEnumerator _db; - - BT, NB> _array; - } - - public NativeGroupsIterator GetEnumerator() - { - return new NativeGroupsIterator(_db); - } - - readonly EntitiesDB _db; - } -#endif } diff --git a/Svelto.ECS/Extensions/Svelto/EntityCollectionExtension.cs b/Svelto.ECS/Extensions/Svelto/EntityCollectionExtension.cs index 9b8eaf7..b9b4153 100644 --- a/Svelto.ECS/Extensions/Svelto/EntityCollectionExtension.cs +++ b/Svelto.ECS/Extensions/Svelto/EntityCollectionExtension.cs @@ -8,32 +8,51 @@ namespace Svelto.ECS public static class EntityCollectionExtension { [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Deconstruct(in this EntityCollection ec, out NB buffer, out int count) where T1 : unmanaged, IEntityComponent + public static void Deconstruct + (in this EntityCollection ec, out NB buffer, out int count) where T1 : unmanaged, IEntityComponent { buffer = ec._nativedBuffer; - count = (int) ec.count; + count = (int) ec.count; } - + [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Deconstruct(in this EntityCollection ec, out NB buffer1, out NB buffer2, out int count) where T1 : unmanaged, IEntityComponent - where T2 : unmanaged, IEntityComponent + public static void Deconstruct + (in this EntityCollection ec, out NB buffer1, out NB buffer2, out int count) + where T1 : unmanaged, IEntityComponent where T2 : unmanaged, IEntityComponent { - buffer1 = ec.Item1._nativedBuffer; - buffer2 = ec.Item2._nativedBuffer; - count = (int) ec.count; + buffer1 = ec.buffer1._nativedBuffer; + buffer2 = ec.buffer2._nativedBuffer; + count = (int) ec.count; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Deconstruct + (in this EntityCollection ec, out NB buffer1, out NB buffer2, out NB buffer3 + , out int count) where T1 : unmanaged, IEntityComponent + where T2 : unmanaged, IEntityComponent + where T3 : unmanaged, IEntityComponent + { + buffer1 = ec.buffer1._nativedBuffer; + buffer2 = ec.buffer2._nativedBuffer; + buffer3 = ec.buffer3._nativedBuffer; + count = (int) ec.count; } - + [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Deconstruct(in this EntityCollection ec, out NB buffer1, out NB buffer2, out NB buffer3, out int count) where T1 : unmanaged, IEntityComponent - where T2 : unmanaged, IEntityComponent - where T3 : unmanaged, IEntityComponent + public static void Deconstruct + (in this EntityCollection ec, out NB buffer1, out NB buffer2, out NB buffer3 + , out NB buffer4, out int count) where T1 : unmanaged, IEntityComponent + where T2 : unmanaged, IEntityComponent + where T3 : unmanaged, IEntityComponent + where T4 : unmanaged, IEntityComponent { buffer1 = ec.Item1._nativedBuffer; buffer2 = ec.Item2._nativedBuffer; buffer3 = ec.Item3._nativedBuffer; + buffer4 = ec.Item4._nativedBuffer; count = (int) ec.count; } - + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static BT> ToBuffer(in this EntityCollection ec) where T1 : unmanaged, IEntityComponent { @@ -45,7 +64,7 @@ namespace Svelto.ECS (in this EntityCollection ec) where T2 : unmanaged, IEntityComponent where T1 : unmanaged, IEntityComponent { - return new BT, NB>(ec.Item1._nativedBuffer, ec.Item2._nativedBuffer, ec.count); + return new BT, NB>(ec.buffer1._nativedBuffer, ec.buffer2._nativedBuffer, ec.count); } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -55,15 +74,16 @@ namespace Svelto.ECS where T1 : unmanaged, IEntityComponent where T3 : unmanaged, IEntityComponent { - return new BT, NB, NB>(ec.Item1._nativedBuffer, ec.Item2._nativedBuffer - , ec.Item3._nativedBuffer, ec.count); + return new BT, NB, NB>(ec.buffer1._nativedBuffer, ec.buffer2._nativedBuffer + , ec.buffer3._nativedBuffer, ec.count); } } public static class EntityCollectionExtensionB { [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Deconstruct(in this EntityCollection ec, out MB buffer, out int count) where T1 : struct, IEntityViewComponent + public static void Deconstruct + (in this EntityCollection ec, out MB buffer, out int count) where T1 : struct, IEntityViewComponent { buffer = ec._managedBuffer; count = (int) ec.count; @@ -76,12 +96,13 @@ namespace Svelto.ECS } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Deconstruct(in this EntityCollection ec, out MB buffer1, out MB buffer2, out int count) where T1 : struct, IEntityViewComponent - where T2 : struct, IEntityViewComponent + public static void Deconstruct + (in this EntityCollection ec, out MB buffer1, out MB buffer2, out int count) + where T1 : struct, IEntityViewComponent where T2 : struct, IEntityViewComponent { - buffer1 = ec.Item1._managedBuffer; - buffer2 = ec.Item2._managedBuffer; - count = (int) ec.count; + buffer1 = ec.buffer1._managedBuffer; + buffer2 = ec.buffer2._managedBuffer; + count = (int) ec.count; } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -89,18 +110,20 @@ namespace Svelto.ECS (in this EntityCollection ec) where T2 : struct, IEntityViewComponent where T1 : struct, IEntityViewComponent { - return (ec.Item1._managedBuffer, ec.Item2._managedBuffer, ec.count); + return (ec.buffer1._managedBuffer, ec.buffer2._managedBuffer, ec.count); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Deconstruct(in this EntityCollection ec, out MB buffer1, out MB buffer2, out MB buffer3, out int count) where T1 : struct, IEntityViewComponent - where T2 : struct, IEntityViewComponent - where T3 : struct, IEntityViewComponent + public static void Deconstruct + (in this EntityCollection ec, out MB buffer1, out MB buffer2, out MB buffer3 + , out int count) where T1 : struct, IEntityViewComponent + where T2 : struct, IEntityViewComponent + where T3 : struct, IEntityViewComponent { - buffer1 = ec.Item1._managedBuffer; - buffer2 = ec.Item2._managedBuffer; - buffer3 = ec.Item3._managedBuffer; - count = (int) ec.count; + buffer1 = ec.buffer1._managedBuffer; + buffer2 = ec.buffer2._managedBuffer; + buffer3 = ec.buffer3._managedBuffer; + count = (int) ec.count; } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -110,7 +133,7 @@ namespace Svelto.ECS where T1 : struct, IEntityViewComponent where T3 : struct, IEntityViewComponent { - return (ec.Item1._managedBuffer, ec.Item2._managedBuffer, ec.Item3._managedBuffer, ec.count); + return (ec.buffer1._managedBuffer, ec.buffer2._managedBuffer, ec.buffer3._managedBuffer, ec.count); } } @@ -121,42 +144,73 @@ namespace Svelto.ECS (in this EntityCollection ec) where T1 : unmanaged, IEntityComponent where T2 : struct, IEntityViewComponent { - return (ec.Item1._nativedBuffer, ec.Item2._managedBuffer, ec.count); + return (ec.buffer1._nativedBuffer, ec.buffer2._managedBuffer, ec.count); } - + [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Deconstruct(in this EntityCollection ec, out NB buffer1, out MB buffer2, out int count) where T1 : unmanaged, IEntityComponent - where T2 : struct, IEntityViewComponent + public static (NB buffer1, MB buffer2, MB buffer3, uint count) ToBuffers + (in this EntityCollection ec) + where T1 : unmanaged, IEntityComponent + where T2 : struct, IEntityViewComponent + where T3 : struct, IEntityViewComponent { - buffer1 = ec.Item1._nativedBuffer; - buffer2 = ec.Item2._managedBuffer; + return (ec.buffer1._nativedBuffer, ec.buffer2._managedBuffer, ec.buffer3._managedBuffer, ec.count); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Deconstruct + (in this EntityCollection ec, out NB buffer1, out MB buffer2, out int count) + where T1 : unmanaged, IEntityComponent where T2 : struct, IEntityViewComponent + { + buffer1 = ec.buffer1._nativedBuffer; + buffer2 = ec.buffer2._managedBuffer; count = (int) ec.count; } - + [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static (NB buffer1, MB buffer2, MB buffer3, uint count) ToBuffers - (in this EntityCollection ec) + public static void Deconstruct + (in this EntityCollection ec, out NB buffer1, out MB buffer2, out MB buffer3, out int count) where T1 : unmanaged, IEntityComponent where T2 : struct, IEntityViewComponent where T3 : struct, IEntityViewComponent { - return (ec.Item1._nativedBuffer, ec.Item2._managedBuffer, ec.Item3._managedBuffer, ec.count); + buffer1 = ec.buffer1._nativedBuffer; + buffer2 = ec.buffer2._managedBuffer; + buffer3 = ec.buffer3._managedBuffer; + count = (int) ec.count; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Deconstruct + (in this EntityCollection ec, out NB buffer1, out NB buffer2, out NB buffer3 + , out MB buffer4, out int count) where T1 : unmanaged, IEntityComponent + where T2 : unmanaged, IEntityComponent + where T3 : unmanaged, IEntityComponent + where T4 : struct, IEntityViewComponent + { + buffer1 = ec.Item1._nativedBuffer; + buffer2 = ec.Item2._nativedBuffer; + buffer3 = ec.Item3._nativedBuffer; + buffer4 = ec.Item4._managedBuffer; + count = (int) ec.count; } } - + public static class EntityCollectionExtensionD { [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Deconstruct(in this EntityCollection ec, out NB buffer1, out NB buffer2, out MB buffer3, out int count) where T1 : unmanaged, IEntityComponent - where T2 : unmanaged, IEntityComponent - where T3 : struct, IEntityViewComponent + public static void Deconstruct + (in this EntityCollection ec, out NB buffer1, out NB buffer2, out MB buffer3 + , out int count) where T1 : unmanaged, IEntityComponent + where T2 : unmanaged, IEntityComponent + where T3 : struct, IEntityViewComponent { - buffer1 = ec.Item1._nativedBuffer; - buffer2 = ec.Item2._nativedBuffer; - buffer3 = ec.Item3._managedBuffer; + buffer1 = ec.buffer1._nativedBuffer; + buffer2 = ec.buffer2._nativedBuffer; + buffer3 = ec.buffer3._managedBuffer; count = (int) ec.count; } - + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static (NB buffer1, NB buffer2, MB buffer3, uint count) ToBuffers (in this EntityCollection ec) @@ -164,18 +218,34 @@ namespace Svelto.ECS where T2 : unmanaged, IEntityComponent where T3 : struct, IEntityViewComponent { - return (ec.Item1._nativedBuffer, ec.Item2._nativedBuffer, ec.Item3._managedBuffer, ec.count); + return (ec.buffer1._nativedBuffer, ec.buffer2._nativedBuffer, ec.buffer3._managedBuffer, ec.count); } - + [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static BT, NB, NB, NB > ToBuffers + public static BT, NB, NB, NB> ToBuffers (in this EntityCollection ec) where T2 : unmanaged, IEntityComponent where T1 : unmanaged, IEntityComponent where T3 : unmanaged, IEntityComponent where T4 : unmanaged, IEntityComponent { - return new BT, NB, NB, NB>(ec.Item1._nativedBuffer, ec.Item2._nativedBuffer, ec.Item3._nativedBuffer, ec.Item4._nativedBuffer, ec.count); + return new BT, NB, NB, NB>(ec.Item1._nativedBuffer, ec.Item2._nativedBuffer + , ec.Item3._nativedBuffer, ec.Item4._nativedBuffer, ec.count); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Deconstruct + (in this EntityCollection ec, out NB buffer1, out NB buffer2, out MB buffer3 + , out MB buffer4, out int count) where T1 : unmanaged, IEntityComponent + where T2 : unmanaged, IEntityComponent + where T3 : struct, IEntityViewComponent + where T4 : struct, IEntityViewComponent + { + buffer1 = ec.Item1._nativedBuffer; + buffer2 = ec.Item2._nativedBuffer; + buffer3 = ec.Item3._managedBuffer; + buffer4 = ec.Item4._managedBuffer; + count = (int) ec.count; } } } \ No newline at end of file diff --git a/Svelto.ECS/Extensions/Svelto/EntityDBExtensionsB.cs b/Svelto.ECS/Extensions/Svelto/EntityManagedDBExtensions.cs similarity index 60% rename from Svelto.ECS/Extensions/Svelto/EntityDBExtensionsB.cs rename to Svelto.ECS/Extensions/Svelto/EntityManagedDBExtensions.cs index 54cfdd4..ceafe28 100644 --- a/Svelto.ECS/Extensions/Svelto/EntityDBExtensionsB.cs +++ b/Svelto.ECS/Extensions/Svelto/EntityManagedDBExtensions.cs @@ -5,7 +5,7 @@ using Svelto.ECS.Internal; namespace Svelto.ECS { - public static class EntityDBExtensionsB + public static class EntityManagedDBExtensions { [MethodImpl(MethodImplOptions.AggressiveInlining)] public static MB QueryEntitiesAndIndex(this EntitiesDB entitiesDb, EGID entityGID, out uint index) where T : struct, IEntityViewComponent @@ -65,5 +65,45 @@ namespace Svelto.ECS { return ref entitiesDb.QueryEntity(new EGID(id, group)); } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ref T QueryUniqueEntity(this EntitiesDB entitiesDb, ExclusiveGroupStruct group) where T : struct, IEntityViewComponent + { + var (entities, entitiescount) = entitiesDb.QueryEntities(@group); + +#if DEBUG && !PROFILE_SVELTO + if (entitiescount == 0) + throw new ECSException("Unique entity not found '".FastConcat(typeof(T).ToString()).FastConcat("'")); + if (entitiescount != 1) + throw new ECSException("Unique entities must be unique! '".FastConcat(typeof(T).ToString()) + .FastConcat("'")); +#endif + return ref entities[0]; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static MB GetArrayAndEntityIndex(this EGIDMapper mapper, uint entityID, out uint index) where T : struct, IEntityViewComponent + { + if (mapper._map.TryFindIndex(entityID, out index)) + { + return (MB) mapper._map.GetValues(out _); + } + + throw new ECSException("Entity not found"); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool TryGetArrayAndEntityIndex(this EGIDMapper mapper, uint entityID, out uint index, out MB array) where T : struct, IEntityViewComponent + { + index = default; + if (mapper._map != null && mapper._map.TryFindIndex(entityID, out index)) + { + array = (MB) mapper._map.GetValues(out _); + return true; + } + + array = default; + return false; + } } } \ No newline at end of file diff --git a/Svelto.ECS/Extensions/Svelto/EntityDBExtensions.cs b/Svelto.ECS/Extensions/Svelto/EntityNativeDBExtensions.cs similarity index 58% rename from Svelto.ECS/Extensions/Svelto/EntityDBExtensions.cs rename to Svelto.ECS/Extensions/Svelto/EntityNativeDBExtensions.cs index e0ba53f..bec4e41 100644 --- a/Svelto.ECS/Extensions/Svelto/EntityDBExtensions.cs +++ b/Svelto.ECS/Extensions/Svelto/EntityNativeDBExtensions.cs @@ -4,7 +4,7 @@ using Svelto.ECS.Internal; namespace Svelto.ECS { - public static class EntityDBExtensions + public static class EntityNativeDBExtensions { [MethodImpl(MethodImplOptions.AggressiveInlining)] public static AllGroupsEnumerable QueryEntities(this EntitiesDB db) @@ -16,7 +16,7 @@ namespace Svelto.ECS [MethodImpl(MethodImplOptions.AggressiveInlining)] public static NB QueryEntitiesAndIndex(this EntitiesDB entitiesDb, EGID entityGID, out uint index) where T : unmanaged, IEntityComponent { - if (entitiesDb.QueryEntitiesAndIndexInternal(entityGID, out index, out NB array) == true) + if (entitiesDb.QueryEntitiesAndIndexInternal(entityGID, out index, out NB array) == true) return array; throw new EntityNotFoundException(entityGID, typeof(T)); @@ -26,7 +26,7 @@ namespace Svelto.ECS public static NB QueryEntitiesAndIndex(this EntitiesDB entitiesDb, uint id, ExclusiveGroupStruct group, out uint index) where T : unmanaged, IEntityComponent { EGID entityGID = new EGID(id, group); - if (entitiesDb.QueryEntitiesAndIndexInternal(entityGID, out index, out NB array) == true) + if (entitiesDb.QueryEntitiesAndIndexInternal(entityGID, out index, out NB array) == true) return array; throw new EntityNotFoundException(entityGID, typeof(T)); @@ -36,7 +36,7 @@ namespace Svelto.ECS public static bool TryQueryEntitiesAndIndex(this EntitiesDB entitiesDb, EGID entityGID, out uint index, out NB array) where T : unmanaged, IEntityComponent { - if (entitiesDb.QueryEntitiesAndIndexInternal(entityGID, out index, out array) == true) + if (entitiesDb.QueryEntitiesAndIndexInternal(entityGID, out index, out array) == true) return true; return false; @@ -46,7 +46,7 @@ namespace Svelto.ECS public static bool TryQueryEntitiesAndIndex(this EntitiesDB entitiesDb, uint id, ExclusiveGroupStruct group, out uint index, out NB array) where T : unmanaged, IEntityComponent { - if (entitiesDb.QueryEntitiesAndIndexInternal(new EGID(id, group), out index, out array) == true) + if (entitiesDb.QueryEntitiesAndIndexInternal(new EGID(id, group), out index, out array) == true) return true; return false; @@ -81,5 +81,45 @@ namespace Svelto.ECS { return ref entitiesDb.QueryEntity(new EGID(id, group)); } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ref T QueryUniqueEntity(this EntitiesDB entitiesDb, ExclusiveGroupStruct group) where T : unmanaged, IEntityComponent + { + var (entities, entitiescount) = entitiesDb.QueryEntities(@group); + +#if DEBUG && !PROFILE_SVELTO + if (entitiescount == 0) + throw new ECSException("Unique entity not found '".FastConcat(typeof(T).ToString()).FastConcat("'")); + if (entitiescount != 1) + throw new ECSException("Unique entities must be unique! '".FastConcat(typeof(T).ToString()) + .FastConcat("'")); +#endif + return ref entities[0]; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static NB GetArrayAndEntityIndex(this EGIDMapper mapper, uint entityID, out uint index) where T : unmanaged, IEntityComponent + { + if (mapper._map.TryFindIndex(entityID, out index)) + { + return (NB) mapper._map.GetValues(out _); + } + + throw new ECSException("Entity not found"); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool TryGetArrayAndEntityIndex(this EGIDMapper mapper, uint entityID, out uint index, out NB array) where T : unmanaged, IEntityComponent + { + index = default; + if (mapper._map != null && mapper._map.TryFindIndex(entityID, out index)) + { + array = (NB) mapper._map.GetValues(out _); + return true; + } + + array = default; + return false; + } } } \ No newline at end of file diff --git a/Svelto.ECS/Extensions/Svelto/ExclusiveGroupExtensions.cs b/Svelto.ECS/Extensions/Svelto/ExclusiveGroupExtensions.cs new file mode 100644 index 0000000..eb9b61e --- /dev/null +++ b/Svelto.ECS/Extensions/Svelto/ExclusiveGroupExtensions.cs @@ -0,0 +1,38 @@ +using System.Runtime.CompilerServices; +using Svelto.DataStructures; + +namespace Svelto.ECS +{ + public static class ExclusiveGroupExtensions + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool FoundIn(this in ExclusiveGroupStruct group, ExclusiveGroupStruct[] groups) + { + for (int i = 0; i < groups.Length; ++i) + if (groups[i] == group) + return true; + + return false; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool FoundIn(this in ExclusiveGroupStruct group, FasterList groups) + { + for (int i = 0; i < groups.count; ++i) + if (groups[i] == group) + return true; + + return false; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool FoundIn(this in ExclusiveGroupStruct group, LocalFasterReadOnlyList groups) + { + for (int i = 0; i < groups.count; ++i) + if (groups[i] == group) + return true; + + return false; + } + } +} \ No newline at end of file diff --git a/Svelto.ECS/Extensions/Svelto/GroupsEnumerable.cs b/Svelto.ECS/Extensions/Svelto/GroupsEnumerable.cs index 854ee57..465ed8a 100644 --- a/Svelto.ECS/Extensions/Svelto/GroupsEnumerable.cs +++ b/Svelto.ECS/Extensions/Svelto/GroupsEnumerable.cs @@ -4,20 +4,20 @@ using Svelto.DataStructures; namespace Svelto.ECS { /// - /// NOTE THESE ENUMERABLES EXIST TO AVOID BOILERPLATE CODE AS THEY SKIP 0 SIZED GROUPS - /// However if the normal pattern with the double foreach is used, this is not necessary - /// Note: atm cannot be ref structs because they are returned in a valuetuple + /// NOTE THESE ENUMERABLES EXIST TO AVOID BOILERPLATE CODE AS THEY SKIP 0 SIZED GROUPS + /// However if the normal pattern with the double foreach is used, this is not necessary + /// Note: atm cannot be ref structs because they are returned in a valuetuple /// /// /// /// /// public readonly ref struct GroupsEnumerable where T1 : struct, IEntityComponent - where T2 : struct, IEntityComponent - where T3 : struct, IEntityComponent - where T4 : struct, IEntityComponent + where T2 : struct, IEntityComponent + where T3 : struct, IEntityComponent + where T4 : struct, IEntityComponent { - readonly EntitiesDB _db; + readonly EntitiesDB _db; readonly LocalFasterReadOnlyList _groups; public GroupsEnumerable(EntitiesDB db, in LocalFasterReadOnlyList groups) @@ -50,15 +50,14 @@ namespace Svelto.ECS Check.Assert(entityCollection1.count == entityCollection2.count , "congratulation, you found a bug in Svelto, please report it"); - EntityCollection array = entityCollection1; + var array = entityCollection1; var array2 = entityCollection2; - _buffers = new EntityCollection( - array.Item1, array.Item2, array.Item3, array2); + _buffers = new EntityCollection(array.buffer1, array.buffer2, array.buffer3, array2); break; } - + var moveNext = _indexGroup < _groups.count; - + if (moveNext == false) Reset(); @@ -67,7 +66,7 @@ namespace Svelto.ECS public void Reset() { _indexGroup = -1; } - public EntityCollection Current => _buffers; + public RefCurrent Current => new RefCurrent(_buffers, _groups[_indexGroup]); readonly LocalFasterReadOnlyList _groups; @@ -79,17 +78,38 @@ namespace Svelto.ECS public GroupsIterator GetEnumerator() { return new GroupsIterator(_db, _groups); } } + public ref struct RefCurrent where T1 : struct, IEntityComponent + where T2 : struct, IEntityComponent + where T3 : struct, IEntityComponent + where T4 : struct, IEntityComponent + { + public RefCurrent(in EntityCollection buffers, ExclusiveGroupStruct group) + { + _buffers = buffers; + _group = group; + } + + public void Deconstruct(out EntityCollection buffers, out ExclusiveGroupStruct group) + { + buffers = _buffers; + group = _group; + } + + public readonly EntityCollection _buffers; + public readonly ExclusiveGroupStruct _group; + } + /// - /// ToDo source gen could return the implementation of IBuffer directly, but cannot be done manually + /// ToDo source gen could return the implementation of IBuffer directly, but cannot be done manually /// /// /// /// public readonly ref struct GroupsEnumerable where T1 : struct, IEntityComponent - where T2 : struct, IEntityComponent - where T3 : struct, IEntityComponent + where T2 : struct, IEntityComponent + where T3 : struct, IEntityComponent { - readonly EntitiesDB _db; + readonly EntitiesDB _db; readonly LocalFasterReadOnlyList _groups; public GroupsEnumerable(EntitiesDB db, in LocalFasterReadOnlyList groups) @@ -112,16 +132,16 @@ namespace Svelto.ECS //attention, the while is necessary to skip empty groups while (++_indexGroup < _groups.count) { - EntityCollection entityCollection = _entitiesDB.QueryEntities(_groups[_indexGroup]); + var entityCollection = _entitiesDB.QueryEntities(_groups[_indexGroup]); if (entityCollection.count == 0) continue; _buffers = entityCollection; break; } - + var moveNext = _indexGroup < _groups.count; - + if (moveNext == false) Reset(); @@ -130,19 +150,40 @@ namespace Svelto.ECS public void Reset() { _indexGroup = -1; } - public EntityCollection Current => _buffers; + public RefCurrent Current => new RefCurrent(_buffers, _groups[_indexGroup]); readonly LocalFasterReadOnlyList _groups; - int _indexGroup; + int _indexGroup; EntityCollection _buffers; - readonly EntitiesDB _entitiesDB; + readonly EntitiesDB _entitiesDB; } public GroupsIterator GetEnumerator() { return new GroupsIterator(_db, _groups); } } - public readonly ref struct GroupsEnumerable where T1 : struct, IEntityComponent where T2 : struct, IEntityComponent + public ref struct RefCurrent where T1 : struct, IEntityComponent + where T2 : struct, IEntityComponent + where T3 : struct, IEntityComponent + { + public RefCurrent(in EntityCollection buffers, ExclusiveGroupStruct group) + { + _buffers = buffers; + _group = group; + } + + public void Deconstruct(out EntityCollection buffers, out ExclusiveGroupStruct group) + { + buffers = _buffers; + group = _group; + } + + public readonly EntityCollection _buffers; + public readonly ExclusiveGroupStruct _group; + } + + public readonly ref struct GroupsEnumerable + where T1 : struct, IEntityComponent where T2 : struct, IEntityComponent { public GroupsEnumerable(EntitiesDB db, in LocalFasterReadOnlyList groups) { @@ -173,30 +214,48 @@ namespace Svelto.ECS } var moveNext = _indexGroup < _groups.count; - + if (moveNext == false) Reset(); - + return moveNext; } public void Reset() { _indexGroup = -1; } - public EntityCollection Current => _buffers; + public RefCurrent Current => new RefCurrent(_buffers, _groups[_indexGroup]); - readonly EntitiesDB _db; + readonly EntitiesDB _db; readonly LocalFasterReadOnlyList _groups; - int _indexGroup; + int _indexGroup; EntityCollection _buffers; } public GroupsIterator GetEnumerator() { return new GroupsIterator(_db, _groups); } - readonly EntitiesDB _db; + readonly EntitiesDB _db; readonly LocalFasterReadOnlyList _groups; } + public ref struct RefCurrent where T1 : struct, IEntityComponent where T2 : struct, IEntityComponent + { + public RefCurrent(in EntityCollection buffers, ExclusiveGroupStruct group) + { + _buffers = buffers; + _group = group; + } + + public void Deconstruct(out EntityCollection buffers, out ExclusiveGroupStruct group) + { + buffers = _buffers; + group = _group; + } + + public readonly EntityCollection _buffers; + public readonly ExclusiveGroupStruct _group; + } + public readonly ref struct GroupsEnumerable where T1 : struct, IEntityComponent { public GroupsEnumerable(EntitiesDB db, in LocalFasterReadOnlyList groups) @@ -232,18 +291,36 @@ namespace Svelto.ECS public void Reset() { _indexGroup = -1; } - public EntityCollection Current => _buffer; + public RefCurrent Current => new RefCurrent(_buffer, _groups[_indexGroup]); - readonly EntitiesDB _db; + readonly EntitiesDB _db; readonly LocalFasterReadOnlyList _groups; - int _indexGroup; + int _indexGroup; EntityCollection _buffer; } public GroupsIterator GetEnumerator() { return new GroupsIterator(_db, _groups); } - readonly EntitiesDB _db; + readonly EntitiesDB _db; readonly LocalFasterReadOnlyList _groups; } + + public ref struct RefCurrent where T1 : struct, IEntityComponent + { + public RefCurrent(in EntityCollection buffers, ExclusiveGroupStruct group) + { + _buffers = buffers; + _group = group; + } + + public void Deconstruct(out EntityCollection buffers, out ExclusiveGroupStruct group) + { + buffers = _buffers; + group = _group; + } + + public readonly EntityCollection _buffers; + public readonly ExclusiveGroupStruct _group; + } } \ No newline at end of file diff --git a/Svelto.ECS/Extensions/Unity/DOTS/DisposeJob.cs b/Svelto.ECS/Extensions/Unity/DOTS/Jobs/DisposeJob.cs similarity index 100% rename from Svelto.ECS/Extensions/Unity/DOTS/DisposeJob.cs rename to Svelto.ECS/Extensions/Unity/DOTS/Jobs/DisposeJob.cs diff --git a/Svelto.ECS/Extensions/Unity/DOTS/JobifedEnginesGroup.cs b/Svelto.ECS/Extensions/Unity/DOTS/Jobs/JobifiedEnginesGroup.cs similarity index 65% rename from Svelto.ECS/Extensions/Unity/DOTS/JobifedEnginesGroup.cs rename to Svelto.ECS/Extensions/Unity/DOTS/Jobs/JobifiedEnginesGroup.cs index 093a4a0..2a5eb10 100644 --- a/Svelto.ECS/Extensions/Unity/DOTS/JobifedEnginesGroup.cs +++ b/Svelto.ECS/Extensions/Unity/DOTS/Jobs/JobifiedEnginesGroup.cs @@ -7,35 +7,37 @@ namespace Svelto.ECS.Extensions.Unity { public interface IJobifiedEngine : IEngine { - JobHandle Execute(JobHandle _jobHandle); + JobHandle Execute(JobHandle inputDeps); string name { get; } } - public interface IJobifiedGroupEngine : IJobifiedEngine - { } - public interface IJobifiedEngine : IEngine { - JobHandle Execute(JobHandle _jobHandle, ref T _param); + JobHandle Execute(JobHandle inputDeps, ref T _param); string name { get; } } public interface IJobifiedGroupEngine : IJobifiedEngine - { - } + { } /// /// Note unsorted jobs run in parallel /// /// - public abstract class JobifedEnginesGroup : IJobifiedGroupEngine where Interface : class, IJobifiedEngine + public abstract class JobifiedEnginesGroup:IJobifiedEngine where Interface : class, IJobifiedEngine { - protected JobifedEnginesGroup(FasterList engines) + protected JobifiedEnginesGroup(FasterList engines) { _name = "JobifiedEnginesGroup - "+this.GetType().Name; _engines = engines; } + + protected JobifiedEnginesGroup() + { + _name = "JobifiedEnginesGroup - "+this.GetType().Name; + _engines = new FasterList(); + } public JobHandle Execute(JobHandle inputHandles) { @@ -48,7 +50,7 @@ namespace Svelto.ECS.Extensions.Unity ref var engine = ref engines[index]; using (profiler.Sample(engine.name)) { - combinedHandles = JobHandle.CombineDependencies(combinedHandles, engine.Execute(inputHandles)); + combinedHandles = engine.Execute(inputHandles); } } } @@ -56,16 +58,20 @@ namespace Svelto.ECS.Extensions.Unity return combinedHandles; } + protected internal void Add(Interface engine) + { + _engines.Add(engine); + } + public string name => _name; - readonly FasterReadOnlyList _engines; - readonly bool _completeEachJob; - readonly string _name; + protected readonly FasterList _engines; + readonly string _name; } - public abstract class JobifedEnginesGroup: IJobifiedGroupEngine where Interface : class, IJobifiedEngine + public abstract class JobifiedEnginesGroup: IJobifiedGroupEngine where Interface : class, IJobifiedEngine { - protected JobifedEnginesGroup(FasterList engines) + protected JobifiedEnginesGroup(FasterList engines) { _name = "JobifiedEnginesGroup - "+this.GetType().Name; _engines = engines; @@ -79,8 +85,7 @@ namespace Svelto.ECS.Extensions.Unity for (var index = 0; index < engines.count; index++) { var engine = engines[index]; - using (profiler.Sample(engine.name)) combinedHandles = - JobHandle.CombineDependencies(combinedHandles, engine.Execute(combinedHandles, ref _param)); + using (profiler.Sample(engine.name)) combinedHandles = engine.Execute(combinedHandles, ref _param); } } diff --git a/Svelto.ECS/Extensions/Unity/DOTS/SortedJobifedEnginesGroup.cs b/Svelto.ECS/Extensions/Unity/DOTS/Jobs/SortedJobifiedEnginesGroup.cs similarity index 73% rename from Svelto.ECS/Extensions/Unity/DOTS/SortedJobifedEnginesGroup.cs rename to Svelto.ECS/Extensions/Unity/DOTS/Jobs/SortedJobifiedEnginesGroup.cs index c51e5e2..f0c7546 100644 --- a/Svelto.ECS/Extensions/Unity/DOTS/SortedJobifedEnginesGroup.cs +++ b/Svelto.ECS/Extensions/Unity/DOTS/Jobs/SortedJobifiedEnginesGroup.cs @@ -7,15 +7,15 @@ namespace Svelto.ECS.Extensions.Unity { /// /// Note sorted jobs run in serial - /// + /// /// /// - public abstract class SortedJobifedEnginesGroup : IJobifiedGroupEngine + public abstract class SortedJobifiedEnginesGroup where SequenceOrder : struct, ISequenceOrder where Interface : class, IJobifiedEngine { - protected SortedJobifedEnginesGroup(FasterList engines) + protected SortedJobifiedEnginesGroup(FasterList engines) { - _name = "SortedJobifedEnginesGroup - "+this.GetType().Name; + _name = "SortedJobifiedEnginesGroup - "+this.GetType().Name; _instancedSequence = new Sequence(engines); } @@ -28,7 +28,7 @@ namespace Svelto.ECS.Extensions.Unity for (var index = 0; index < sequenceItems.count; index++) { var engine = sequenceItems[index]; - using (profiler.Sample(engine.name)) combinedHandles = JobHandle.CombineDependencies(combinedHandles, engine.Execute(combinedHandles)); + using (profiler.Sample(engine.name)) combinedHandles = engine.Execute(combinedHandles); } } @@ -41,12 +41,12 @@ namespace Svelto.ECS.Extensions.Unity readonly Sequence _instancedSequence; } - public abstract class SortedJobifedEnginesGroup: IJobifiedGroupEngine + public abstract class SortedJobifiedEnginesGroup: IJobifiedGroupEngine where SequenceOrder : struct, ISequenceOrder where Interface : class, IJobifiedEngine { - protected SortedJobifedEnginesGroup(FasterList engines) + protected SortedJobifiedEnginesGroup(FasterList engines) { - _name = "SortedJobifedEnginesGroup - "+this.GetType().Name; + _name = "SortedJobifiedEnginesGroup - "+this.GetType().Name; _instancedSequence = new Sequence(engines); } @@ -58,8 +58,7 @@ namespace Svelto.ECS.Extensions.Unity for (var index = 0; index < sequenceItems.count; index++) { var engine = sequenceItems[index]; - using (profiler.Sample(engine.name)) combinedHandles = - JobHandle.CombineDependencies(combinedHandles, engine.Execute(combinedHandles, ref param)); + using (profiler.Sample(engine.name)) combinedHandles = engine.Execute(combinedHandles, ref param); } } diff --git a/Svelto.ECS/Extensions/Unity/DOTS/Jobs/UnityJobExtensions.cs b/Svelto.ECS/Extensions/Unity/DOTS/Jobs/UnityJobExtensions.cs new file mode 100644 index 0000000..7d0df74 --- /dev/null +++ b/Svelto.ECS/Extensions/Unity/DOTS/Jobs/UnityJobExtensions.cs @@ -0,0 +1,68 @@ +#if UNITY_NATIVE +using System; +using Svelto.ECS.Extensions.Unity; +using Unity.Jobs; + +namespace Svelto.ECS +{ +#if UNITY_JOBS + public static class UnityJobExtensions + { + public static JobHandle ScheduleParallel + (this JOB job, uint iterations, JobHandle inputDeps) where JOB: struct, IJobParallelForBatch + { + if (iterations == 0) + return inputDeps; + + var innerloopBatchCount = ProcessorCount.BatchSize((uint) iterations); + return job.ScheduleBatch((int)iterations, innerloopBatchCount, inputDeps); + } + + public static JobHandle ScheduleParallel + (this JOB job, int iterations, JobHandle inputDeps) where JOB: struct, IJobParallelForBatch + { + if (iterations <= 0) + return inputDeps; + + var innerloopBatchCount = ProcessorCount.BatchSize((uint) iterations); + return job.ScheduleBatch((int)iterations, innerloopBatchCount, inputDeps); + } + } +#endif + public static class UnityJobExtensions2 + { + public static JobHandle ScheduleDispose + (this T1 disposable, JobHandle inputDeps) where T1 : struct, IDisposable + { + return new DisposeJob(disposable).Schedule(inputDeps); + } + + public static JobHandle ScheduleDispose + (this T1 disposable1, T2 disposable2, JobHandle inputDeps) + where T1 : struct, IDisposable where T2 : struct, IDisposable + { + return new DisposeJob(disposable1, disposable2).Schedule(inputDeps); + } + + public static JobHandle ScheduleParallel + (this JOB job, int iterations, JobHandle inputDeps) where JOB: struct, IJobParallelFor + { + if (iterations <= 0) + return inputDeps; + + var innerloopBatchCount = ProcessorCount.BatchSize((uint) iterations); + return job.Schedule((int)iterations, innerloopBatchCount, inputDeps); + } + + public static JobHandle ScheduleParallel + (this JOB job, uint iterations, JobHandle inputDeps) where JOB: struct, IJobParallelFor + { + if (iterations == 0) + return inputDeps; + + var innerloopBatchCount = ProcessorCount.BatchSize(iterations); + return job.Schedule((int)iterations, innerloopBatchCount, inputDeps); + } + } +} +#endif \ No newline at end of file diff --git a/Svelto.ECS/Extensions/Unity/DOTS/EnginesRoot.NativeOperation.cs b/Svelto.ECS/Extensions/Unity/DOTS/Native/EnginesRoot.NativeOperation.cs similarity index 77% rename from Svelto.ECS/Extensions/Unity/DOTS/EnginesRoot.NativeOperation.cs rename to Svelto.ECS/Extensions/Unity/DOTS/Native/EnginesRoot.NativeOperation.cs index 54dd0de..0302254 100644 --- a/Svelto.ECS/Extensions/Unity/DOTS/EnginesRoot.NativeOperation.cs +++ b/Svelto.ECS/Extensions/Unity/DOTS/Native/EnginesRoot.NativeOperation.cs @@ -1,9 +1,8 @@ -#if UNITY_BURST +#if UNITY_NATIVE using System; using Svelto.Common; using Svelto.DataStructures; -using Svelto.ECS.DataStructures.Unity; -using Unity.Jobs.LowLevel.Unsafe; +using Svelto.ECS.DataStructures; namespace Svelto.ECS { @@ -11,13 +10,13 @@ namespace Svelto.ECS { //todo: I very likely don't need to create one for each native entity factory, the same can be reused readonly AtomicNativeBags _addOperationQueue = - new AtomicNativeBags(Common.Allocator.Persistent, JobsUtility.MaxJobThreadCount + 1); + new AtomicNativeBags(Common.Allocator.Persistent); readonly AtomicNativeBags _removeOperationQueue = - new AtomicNativeBags(Common.Allocator.Persistent, JobsUtility.MaxJobThreadCount + 1); + new AtomicNativeBags(Common.Allocator.Persistent); readonly AtomicNativeBags _swapOperationQueue = - new AtomicNativeBags(Common.Allocator.Persistent, JobsUtility.MaxJobThreadCount + 1); + new AtomicNativeBags(Common.Allocator.Persistent); NativeEntityRemove ProvideNativeEntityRemoveQueue(string memberName) where T : IEntityDescriptor, new() { @@ -57,12 +56,13 @@ namespace Svelto.ECS while (buffer.IsEmpty() == false) { - var componentsIndex = buffer.Dequeue(); - var entityEGID = buffer.Dequeue(); - CheckRemoveEntityID(entityEGID, _nativeRemoveOperations[componentsIndex].type); + var componentsIndex = buffer.Dequeue(); + var entityEGID = buffer.Dequeue(); + var nativeRemoveOperation = _nativeRemoveOperations[componentsIndex]; + CheckRemoveEntityID(entityEGID, nativeRemoveOperation.entityDescriptorType); QueueEntitySubmitOperation(new EntitySubmitOperation( EntitySubmitOperationType.Remove, entityEGID, entityEGID - , _nativeRemoveOperations[componentsIndex].components)); + , nativeRemoveOperation.components)); } } @@ -75,12 +75,14 @@ namespace Svelto.ECS var componentsIndex = buffer.Dequeue(); var entityEGID = buffer.Dequeue(); - CheckRemoveEntityID(entityEGID.@from, _nativeSwapOperations[componentsIndex].type, _nativeSwapOperations[componentsIndex].caller ); - CheckAddEntityID(entityEGID.to, _nativeSwapOperations[componentsIndex].type, _nativeSwapOperations[componentsIndex].caller); - + var componentBuilders = _nativeSwapOperations[componentsIndex].components; + + CheckRemoveEntityID(entityEGID.@from, _nativeSwapOperations[componentsIndex].entityDescriptorType, _nativeSwapOperations[componentsIndex].caller ); + CheckAddEntityID(entityEGID.to, _nativeSwapOperations[componentsIndex].entityDescriptorType, _nativeSwapOperations[componentsIndex].caller); + QueueEntitySubmitOperation(new EntitySubmitOperation( EntitySubmitOperationType.Swap, entityEGID.@from, entityEGID.to - , _nativeSwapOperations[componentsIndex].components)); + , componentBuilders)); } } } @@ -98,7 +100,7 @@ namespace Svelto.ECS var componentCounts = buffer.Dequeue(); EntityComponentInitializer init = - BuildEntity(egid, _nativeAddOperations[componentsIndex].components, _nativeAddOperations[componentsIndex].type); + BuildEntity(egid, _nativeAddOperations[componentsIndex].components, _nativeAddOperations[componentsIndex].entityDescriptorType); //only called if Init is called on the initialized (there is something to init) while (componentCounts > 0) @@ -144,11 +146,11 @@ namespace Svelto.ECS readonly struct NativeOperationBuild { internal readonly IComponentBuilder[] components; - internal readonly Type type; + internal readonly Type entityDescriptorType; - public NativeOperationBuild(IComponentBuilder[] descriptorComponentsToBuild, Type entityType) + public NativeOperationBuild(IComponentBuilder[] descriptorComponentsToBuild, Type entityDescriptorType) { - type = entityType; + this.entityDescriptorType = entityDescriptorType; components = descriptorComponentsToBuild; } } @@ -156,28 +158,28 @@ namespace Svelto.ECS readonly struct NativeOperationRemove { internal readonly IComponentBuilder[] components; - internal readonly Type type; + internal readonly Type entityDescriptorType; internal readonly string caller; - public NativeOperationRemove(IComponentBuilder[] descriptorComponentsToRemove, Type entityType, string caller) + public NativeOperationRemove(IComponentBuilder[] descriptorComponentsToRemove, Type entityDescriptorType, string caller) { - this.caller = caller; - components = descriptorComponentsToRemove; - type = entityType; + this.caller = caller; + components = descriptorComponentsToRemove; + this.entityDescriptorType = entityDescriptorType; } } readonly struct NativeOperationSwap { internal readonly IComponentBuilder[] components; - internal readonly Type type; + internal readonly Type entityDescriptorType; internal readonly string caller; - public NativeOperationSwap(IComponentBuilder[] descriptorComponentsToSwap, Type entityType, string caller) + public NativeOperationSwap(IComponentBuilder[] descriptorComponentsToSwap, Type entityDescriptorType, string caller) { - this.caller = caller; - components = descriptorComponentsToSwap; - type = entityType; + this.caller = caller; + components = descriptorComponentsToSwap; + this.entityDescriptorType = entityDescriptorType; } } } diff --git a/Svelto.ECS/Extensions/Unity/DOTS/Native/NativeEGIDMapper.cs b/Svelto.ECS/Extensions/Unity/DOTS/Native/NativeEGIDMapper.cs new file mode 100644 index 0000000..3bf1e21 --- /dev/null +++ b/Svelto.ECS/Extensions/Unity/DOTS/Native/NativeEGIDMapper.cs @@ -0,0 +1,97 @@ +#if UNITY_NATIVE +using System; +using System.Runtime.CompilerServices; +using Svelto.Common; +using Svelto.DataStructures; + +namespace Svelto.ECS +{ + public readonly struct NativeEGIDMapper:IEGIDMapper where T : unmanaged, IEntityComponent + { + public NativeEGIDMapper + (ExclusiveGroupStruct groupStructId, SveltoDictionaryNative toNative) : this() + { + groupID = groupStructId; + _map = toNative; + } + + public int count => _map.count; + public Type entityType => TypeCache.type; + public ExclusiveGroupStruct groupID { get; } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ref T Entity(uint entityID) + { +#if DEBUG && !PROFILE_SVELTO + if (_map.TryFindIndex(entityID, out var findIndex) == false) + throw new Exception("Entity not found in this group ".FastConcat(typeof(T).ToString())); +#else + _map.TryFindIndex(entityID, out var findIndex); +#endif + return ref _map.GetDirectValueByRef(findIndex); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool TryGetEntity(uint entityID, out T value) + { + if (_map.count > 0 && _map.TryFindIndex(entityID, out var index)) + { + unsafe + { + value = Unsafe.AsRef(Unsafe.Add((void*) _map.GetValues(out _).ToNativeArray(out _) + , (int) index)); + return true; + } + } + + value = default; + return false; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public NB GetArrayAndEntityIndex(uint entityID, out uint index) + { + if (_map.TryFindIndex(entityID, out index)) + { + return new NB(_map.GetValues(out var count).ToNativeArray(out _), count); + } + + throw new ECSException("Entity not found"); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool TryGetArrayAndEntityIndex(uint entityID, out uint index, out NB array) + { + index = 0; + if (_map.count > 0 && _map.TryFindIndex(entityID, out index)) + { + array = new NB(_map.GetValues(out var count).ToNativeArray(out _), count); + return true; + } + + array = default; + return false; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Exists(uint idEntityId) + { + return _map.count > 0 && _map.TryFindIndex(idEntityId, out _); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public uint GetIndex(uint entityID) + { + return _map.GetIndex(entityID); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool FindIndex(uint valueKey, out uint index) + { + return _map.TryFindIndex(valueKey, out index); + } + + readonly ReadonlySveltoDictionaryNative _map; + } +} +#endif \ No newline at end of file diff --git a/Svelto.ECS/Extensions/Unity/DOTS/Native/NativeEGIDMultiMapper.cs b/Svelto.ECS/Extensions/Unity/DOTS/Native/NativeEGIDMultiMapper.cs new file mode 100644 index 0000000..cac1afd --- /dev/null +++ b/Svelto.ECS/Extensions/Unity/DOTS/Native/NativeEGIDMultiMapper.cs @@ -0,0 +1,55 @@ +#if UNITY_NATIVE +using System; +using Svelto.DataStructures; + +namespace Svelto.ECS +{ + public struct NativeEGIDMultiMapper:IDisposable where T : unmanaged, IEntityComponent + { + SveltoDictionary>, + NativeStrategy, + NativeStrategy>, + NativeStrategy>, + NativeStrategy>, + NativeStrategy, + NativeStrategy>>, + NativeStrategy> _dic; + + public NativeEGIDMultiMapper + (SveltoDictionary>, + NativeStrategy, + NativeStrategy>, + NativeStrategy>, + NativeStrategy>, + NativeStrategy, + NativeStrategy>>, + NativeStrategy> dictionary) + { + _dic = dictionary; + } + + public int count => (int) _dic.count; + + public void Dispose() + { + _dic.Dispose(); + } + + public ref T Entity(EGID entity) + { + ref var sveltoDictionary = ref _dic.GetValueByRef(entity.groupID); + return ref sveltoDictionary.GetValueByRef(entity.entityID); + } + + public bool Exists(EGID entity) + { + return _dic.TryFindIndex(entity.groupID, out var index) + && _dic.GetDirectValueByRef(index).ContainsKey(entity.entityID); + } + } +} +#endif \ No newline at end of file diff --git a/Svelto.ECS/Extensions/Unity/DOTS/NativeEntityComponentInitializer.cs b/Svelto.ECS/Extensions/Unity/DOTS/Native/NativeEntityComponentInitializer.cs similarity index 100% rename from Svelto.ECS/Extensions/Unity/DOTS/NativeEntityComponentInitializer.cs rename to Svelto.ECS/Extensions/Unity/DOTS/Native/NativeEntityComponentInitializer.cs diff --git a/Svelto.ECS/Extensions/Unity/DOTS/NativeEntityFactory.cs b/Svelto.ECS/Extensions/Unity/DOTS/Native/NativeEntityFactory.cs similarity index 81% rename from Svelto.ECS/Extensions/Unity/DOTS/NativeEntityFactory.cs rename to Svelto.ECS/Extensions/Unity/DOTS/Native/NativeEntityFactory.cs index 9888883..7351b16 100644 --- a/Svelto.ECS/Extensions/Unity/DOTS/NativeEntityFactory.cs +++ b/Svelto.ECS/Extensions/Unity/DOTS/Native/NativeEntityFactory.cs @@ -1,27 +1,26 @@ -#if UNITY_BURST +#if UNITY_NATIVE using Svelto.ECS.DataStructures; -using Svelto.ECS.DataStructures.Unity; namespace Svelto.ECS { public readonly struct NativeEntityFactory { readonly AtomicNativeBags _addOperationQueue; - readonly uint _index; + readonly int _index; - internal NativeEntityFactory(AtomicNativeBags addOperationQueue, uint index) + internal NativeEntityFactory(AtomicNativeBags addOperationQueue, int index) { _index = index; _addOperationQueue = addOperationQueue; } public NativeEntityComponentInitializer BuildEntity - (uint eindex, ExclusiveGroupStruct buildGroup, int threadIndex) + (uint eindex, BuildGroup BuildGroup, int threadIndex) { NativeBag unsafeBuffer = _addOperationQueue.GetBuffer(threadIndex + 1); unsafeBuffer.Enqueue(_index); - unsafeBuffer.Enqueue(new EGID(eindex, buildGroup)); + unsafeBuffer.Enqueue(new EGID(eindex, BuildGroup)); unsafeBuffer.ReserveEnqueue(out var index) = 0; return new NativeEntityComponentInitializer(unsafeBuffer, index); diff --git a/Svelto.ECS/Extensions/Unity/DOTS/NativeEntityRemove.cs b/Svelto.ECS/Extensions/Unity/DOTS/Native/NativeEntityRemove.cs similarity index 81% rename from Svelto.ECS/Extensions/Unity/DOTS/NativeEntityRemove.cs rename to Svelto.ECS/Extensions/Unity/DOTS/Native/NativeEntityRemove.cs index 345a2f0..c2365dd 100644 --- a/Svelto.ECS/Extensions/Unity/DOTS/NativeEntityRemove.cs +++ b/Svelto.ECS/Extensions/Unity/DOTS/Native/NativeEntityRemove.cs @@ -1,14 +1,14 @@ -#if UNITY_BURST -using Svelto.ECS.DataStructures.Unity; +#if UNITY_NATIVE +using Svelto.ECS.DataStructures; namespace Svelto.ECS { public readonly struct NativeEntityRemove { readonly AtomicNativeBags _removeQueue; - readonly uint _indexRemove; + readonly int _indexRemove; - internal NativeEntityRemove(AtomicNativeBags EGIDsToRemove, uint indexRemove) + internal NativeEntityRemove(AtomicNativeBags EGIDsToRemove, int indexRemove) { _removeQueue = EGIDsToRemove; _indexRemove = indexRemove; diff --git a/Svelto.ECS/Extensions/Unity/DOTS/NativeEntitySwap.cs b/Svelto.ECS/Extensions/Unity/DOTS/Native/NativeEntitySwap.cs similarity index 79% rename from Svelto.ECS/Extensions/Unity/DOTS/NativeEntitySwap.cs rename to Svelto.ECS/Extensions/Unity/DOTS/Native/NativeEntitySwap.cs index 52633a9..53de378 100644 --- a/Svelto.ECS/Extensions/Unity/DOTS/NativeEntitySwap.cs +++ b/Svelto.ECS/Extensions/Unity/DOTS/Native/NativeEntitySwap.cs @@ -1,14 +1,14 @@ -#if UNITY_BURST -using Svelto.ECS.DataStructures.Unity; +#if UNITY_NATIVE +using Svelto.ECS.DataStructures; namespace Svelto.ECS { public readonly struct NativeEntitySwap { readonly AtomicNativeBags _swapQueue; - readonly uint _indexSwap; + readonly int _indexSwap; - internal NativeEntitySwap(AtomicNativeBags EGIDsToSwap, uint indexSwap) + internal NativeEntitySwap(AtomicNativeBags EGIDsToSwap, int indexSwap) { _swapQueue = EGIDsToSwap; _indexSwap = indexSwap; @@ -22,7 +22,7 @@ namespace Svelto.ECS } - public void SwapEntity(EGID from, ExclusiveGroupStruct to, int threadIndex) + public void SwapEntity(EGID from, BuildGroup to, int threadIndex) { var simpleNativeBag = _swapQueue.GetBuffer(threadIndex); simpleNativeBag.Enqueue(_indexSwap); diff --git a/Svelto.ECS/Extensions/Unity/DOTS/Native/UnityEntityDBExtensions.cs b/Svelto.ECS/Extensions/Unity/DOTS/Native/UnityEntityDBExtensions.cs new file mode 100644 index 0000000..4917887 --- /dev/null +++ b/Svelto.ECS/Extensions/Unity/DOTS/Native/UnityEntityDBExtensions.cs @@ -0,0 +1,73 @@ +#if UNITY_NATIVE +using System.Runtime.CompilerServices; +using Svelto.Common; +using Svelto.DataStructures; +using Svelto.ECS.Internal; + +namespace Svelto.ECS +{ + public static class UnityEntityDBExtensions + { + internal static NativeEGIDMapper ToNativeEGIDMapper(this TypeSafeDictionary dic, + ExclusiveGroupStruct groupStructId) where T : unmanaged, IEntityComponent + { + var mapper = new NativeEGIDMapper(groupStructId, dic.implUnmgd); + + return mapper; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static NativeEGIDMapper QueryNativeMappedEntities(this EntitiesDB entitiesDb, ExclusiveGroupStruct groupStructId) + where T : unmanaged, IEntityComponent + { + if (entitiesDb.SafeQueryEntityDictionary(groupStructId, out var typeSafeDictionary) == false) + throw new EntityGroupNotFoundException(typeof(T), groupStructId.ToName()); + + return (typeSafeDictionary as TypeSafeDictionary).ToNativeEGIDMapper(groupStructId); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool TryQueryNativeMappedEntities(this EntitiesDB entitiesDb, ExclusiveGroupStruct groupStructId, + out NativeEGIDMapper mapper) + where T : unmanaged, IEntityComponent + { + mapper = default; + if (entitiesDb.SafeQueryEntityDictionary(groupStructId, out var typeSafeDictionary) == false || + typeSafeDictionary.count == 0) + return false; + + mapper = (typeSafeDictionary as TypeSafeDictionary).ToNativeEGIDMapper(groupStructId); + + return true; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static NativeEGIDMultiMapper QueryNativeMappedEntities(this EntitiesDB entitiesDb, + LocalFasterReadOnlyList groups) + where T : unmanaged, IEntityComponent + { + var dictionary = + new SveltoDictionary>, + NativeStrategy, + NativeStrategy>, + NativeStrategy>, + NativeStrategy>, + NativeStrategy, + NativeStrategy>>, + NativeStrategy> + ((uint) groups.count, Allocator.TempJob); + + foreach (var group in groups) + { + if (entitiesDb.SafeQueryEntityDictionary(group, out var typeSafeDictionary) == true) + if (typeSafeDictionary.count > 0) + dictionary.Add(group, ((TypeSafeDictionary)typeSafeDictionary).implUnmgd); + } + + return new NativeEGIDMultiMapper(dictionary); + } + } +} +#endif \ No newline at end of file diff --git a/Svelto.ECS/Extensions/Unity/DOTS/NativeEGIDMapper.cs b/Svelto.ECS/Extensions/Unity/DOTS/NativeEGIDMapper.cs deleted file mode 100644 index 34d91b6..0000000 --- a/Svelto.ECS/Extensions/Unity/DOTS/NativeEGIDMapper.cs +++ /dev/null @@ -1,83 +0,0 @@ -#if UNITY_BURST -using System; -using System.Runtime.CompilerServices; -using Svelto.DataStructures; - -namespace Svelto.ECS -{ - public readonly struct NativeEGIDMapper where T : unmanaged, IEntityComponent - { - readonly SveltoDictionaryNative map; - public ExclusiveGroupStruct groupID { get; } - - public NativeEGIDMapper - (ExclusiveGroupStruct groupStructId - , SveltoDictionary>, NativeStrategy> toNative) : this() - { - groupID = groupStructId; - map = toNative; - } - - public uint Count => map.count; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ref T Entity(uint entityID) - { -#if DEBUG && !PROFILE_SVELTO - if (map.TryFindIndex(entityID, out var findIndex) == false) - throw new Exception("Entity not found in this group ".FastConcat(typeof(T).ToString())); -#else - map.TryFindIndex(entityID, out var findIndex); -#endif - return ref map.GetDirectValueByRef(findIndex); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool TryGetEntity(uint entityID, out T value) - { - if (map.count > 0 && map.TryFindIndex(entityID, out var index)) - { - unsafe - { - value = Unsafe.AsRef(Unsafe.Add((void*) map.GetValues(out _).ToNativeArray(out _) - , (int) index)); - return true; - } - } - - value = default; - return false; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public NB GetArrayAndEntityIndex(uint entityID, out uint index) - { - if (map.TryFindIndex(entityID, out index)) - { - return new NB((IntPtr) map.GetValues(out var count).ToNativeArray(out _), count); - } - - throw new ECSException("Entity not found"); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool TryGetArrayAndEntityIndex(uint entityID, out uint index, out NB array) - { - index = 0; - if (map.count > 0 && map.TryFindIndex(entityID, out index)) - { - array = new NB((IntPtr) map.GetValues(out var count).ToNativeArray(out _), count); - return true; - } - - array = default; - return false; - } - - public bool Exists(uint idEntityId) - { - return map.count > 0 && map.TryFindIndex(idEntityId, out _); - } - } -} -#endif \ No newline at end of file diff --git a/Svelto.ECS/Extensions/Unity/DOTS/NativeEGIDMultiMapper.cs b/Svelto.ECS/Extensions/Unity/DOTS/NativeEGIDMultiMapper.cs deleted file mode 100644 index da67a17..0000000 --- a/Svelto.ECS/Extensions/Unity/DOTS/NativeEGIDMultiMapper.cs +++ /dev/null @@ -1,43 +0,0 @@ -#if UNITY_BURST -using System; -using Svelto.DataStructures; - -namespace Svelto.ECS -{ - public struct NativeEGIDMultiMapper:IDisposable where T : unmanaged, IEntityComponent - { - SveltoDictionary>, NativeStrategy>, - NativeStrategy>, NativeStrategy< - SveltoDictionary>, NativeStrategy>>> _dic; - - public NativeEGIDMultiMapper - (SveltoDictionary>, NativeStrategy>, - NativeStrategy>, NativeStrategy< - SveltoDictionary>, NativeStrategy>>> dictionary) - { - _dic = dictionary; - } - - public int count => (int) _dic.count; - - public void Dispose() - { - _dic.Dispose(); - } - - public ref T Entity(EGID entity) - { - ref var sveltoDictionary = ref _dic.GetValueByRef(entity.groupID); - return ref sveltoDictionary.GetValueByRef(entity.entityID); - } - - public bool Exists(EGID entity) - { - return _dic.TryFindIndex(entity.groupID, out var index) - && _dic.GetDirectValueByRef(index).ContainsKey(entity.entityID); - } - } -} -#endif \ No newline at end of file diff --git a/Svelto.ECS/Extensions/Unity/DOTS/PureUECSSystemsGroup.cs b/Svelto.ECS/Extensions/Unity/DOTS/PureUECSSystemsGroup.cs deleted file mode 100644 index 889c9f6..0000000 --- a/Svelto.ECS/Extensions/Unity/DOTS/PureUECSSystemsGroup.cs +++ /dev/null @@ -1,29 +0,0 @@ -#if UNITY_ECS -using Svelto.Common; -using Unity.Entities; -using Unity.Jobs; - -namespace Svelto.ECS.Extensions.Unity -{ - [Sequenced(nameof(JobifiedSveltoEngines.PureUECSSystemsGroup))] - [DisableAutoCreation] - public class PureUECSSystemsGroup : IJobifiedEngine - { - public PureUECSSystemsGroup(World world) - { - _world = world; - } - - public JobHandle Execute(JobHandle _jobHandle) - { - _world.Update(); - - return _jobHandle; - } - - public string name => nameof(PureUECSSystemsGroup); - - readonly World _world; - } -} -#endif \ No newline at end of file diff --git a/Svelto.ECS/Extensions/Unity/DOTS/SyncSveltoToUECSGroup.cs b/Svelto.ECS/Extensions/Unity/DOTS/SyncSveltoToUECSGroup.cs deleted file mode 100644 index 10e2eba..0000000 --- a/Svelto.ECS/Extensions/Unity/DOTS/SyncSveltoToUECSGroup.cs +++ /dev/null @@ -1,39 +0,0 @@ -#if UNITY_ECS -using Svelto.Common; -using Unity.Entities; -using Unity.Jobs; - -namespace Svelto.ECS.Extensions.Unity -{ - [Sequenced(nameof(JobifiedSveltoEngines.CopySveltoToUECSEnginesGroup))] - [DisableAutoCreation] - public class SyncSveltoToUECSGroup : ComponentSystemGroup, IJobifiedEngine - { - public JobHandle Execute(JobHandle _jobHandle) - { - foreach (var engine in Systems) - (engine as SyncSveltoToUECSEngine).externalHandle = _jobHandle; - - Update(); - - return _jobHandle; - } - - public string name => nameof(SyncSveltoToUECSGroup); - - readonly SimulationSystemGroup _simulationSystemGroup; - } - - public abstract class SyncSveltoToUECSEngine : SystemBase, IEngine - { - internal JobHandle externalHandle; - protected abstract void Execute(); - - protected sealed override void OnUpdate() - { - Dependency = JobHandle.CombineDependencies(Dependency, externalHandle); - Execute(); - } - } -} -#endif \ No newline at end of file diff --git a/Svelto.ECS/Extensions/Unity/DOTS/UECS/IUECSSubmissionEngine.cs b/Svelto.ECS/Extensions/Unity/DOTS/UECS/IUECSSubmissionEngine.cs new file mode 100644 index 0000000..b86f9db --- /dev/null +++ b/Svelto.ECS/Extensions/Unity/DOTS/UECS/IUECSSubmissionEngine.cs @@ -0,0 +1,12 @@ +#if UNITY_ECS +using Unity.Entities; + +namespace Svelto.ECS.Extensions.Unity +{ + public interface IUECSSubmissionEngine : IJobifiedEngine + { + EntityCommandBuffer ECB { get; set;} + EntityManager EM { get; set;} + } +} +#endif \ No newline at end of file diff --git a/Svelto.ECS/Extensions/Unity/DOTS/JobifiedSveltoEngines.cs b/Svelto.ECS/Extensions/Unity/DOTS/UECS/JobifiedSveltoEngines.cs similarity index 50% rename from Svelto.ECS/Extensions/Unity/DOTS/JobifiedSveltoEngines.cs rename to Svelto.ECS/Extensions/Unity/DOTS/UECS/JobifiedSveltoEngines.cs index 3f67e89..be86b38 100644 --- a/Svelto.ECS/Extensions/Unity/DOTS/JobifiedSveltoEngines.cs +++ b/Svelto.ECS/Extensions/Unity/DOTS/UECS/JobifiedSveltoEngines.cs @@ -1,10 +1,9 @@ -#if UNITY_BURST +#if UNITY_ECS namespace Svelto.ECS.Extensions.Unity { public enum JobifiedSveltoEngines { - CopySveltoToUECSEnginesGroup, - PureUECSSystemsGroup + SveltoOverUECS } } #endif \ No newline at end of file diff --git a/Svelto.ECS/Extensions/Unity/DOTS/UECS/SveltoOverUECSEnginesGroup.cs b/Svelto.ECS/Extensions/Unity/DOTS/UECS/SveltoOverUECSEnginesGroup.cs new file mode 100644 index 0000000..309fc74 --- /dev/null +++ b/Svelto.ECS/Extensions/Unity/DOTS/UECS/SveltoOverUECSEnginesGroup.cs @@ -0,0 +1,100 @@ +#if UNITY_ECS +using Svelto.Common; +using Svelto.ECS.Schedulers; +using Unity.Entities; +using Unity.Jobs; + +namespace Svelto.ECS.Extensions.Unity +{ + [Sequenced(nameof(JobifiedSveltoEngines.SveltoOverUECS))] + public class SveltoOverUECSEnginesGroup: IJobifiedEngine + { + public SveltoOverUECSEnginesGroup(EnginesRoot enginesRoot) + { + DBC.ECS.Check.Require(enginesRoot.scheduler is ISimpleEntitiesSubmissionScheduler, "The Engines root must use a EntitiesSubmissionScheduler scheduler implementation"); + + CreateUnityECSWorldForSvelto(enginesRoot.scheduler as ISimpleEntitiesSubmissionScheduler, enginesRoot); + } + + public World world { get; private set; } + + void CreateUnityECSWorldForSvelto(ISimpleEntitiesSubmissionScheduler scheduler, EnginesRoot enginesRoot) + { + world = new World("Svelto<>UECS world"); + + var systems = DefaultWorldInitialization.GetAllSystems(WorldSystemFilterFlags.Default); + DefaultWorldInitialization.AddSystemsToRootLevelSystemGroups(world, systems); + World.DefaultGameObjectInjectionWorld = world; + + //This is the UECS group that takes care of all the UECS systems that creates entities + //it also submits Svelto entities + _sveltoUecsEntitiesSubmissionGroup = new SveltoUECSEntitiesSubmissionGroup(scheduler, world); + enginesRoot.AddEngine(_sveltoUecsEntitiesSubmissionGroup); + //This is the group that handles the UECS sync systems that copy the svelto entities values to UECS entities + _syncSveltoToUecsGroup = new SyncSveltoToUECSGroup(); + enginesRoot.AddEngine(_syncSveltoToUecsGroup); + _syncUecsToSveltoGroup = new SyncUECSToSveltoGroup(); + enginesRoot.AddEngine(_syncUecsToSveltoGroup); + //This is the group that handles the UECS sync systems that copy the UECS entities values to svelto entities + //enginesRoot.AddEngine(new SveltoUECSEntitiesSubmissionGroup(scheduler, world)); + enginesRoot.AddEngine(this); + + _enginesRoot = enginesRoot; + } + + public JobHandle Execute(JobHandle inputDeps) + { + //this is a sync point, there won't be pending jobs after this + _sveltoUecsEntitiesSubmissionGroup.Execute(inputDeps); + + //Mixed explicit job dependency and internal automatic ECS dependency system + //Write in to UECS entities so the UECS dependencies react on the components touched + var handle = _syncSveltoToUecsGroup.Execute(default); + + //As long as pure UECS systems do not use external containers (like native arrays and so) the Unity + //automatic dependencies system will guarantee that there won't be race conditions + world.Update(); + + //this svelto group of UECS SystemBase systems + return _syncUecsToSveltoGroup.Execute(handle); + } + + public void AddUECSSubmissionEngine(IUECSSubmissionEngine spawnUnityEntityOnSveltoEntityEngine) + { + _sveltoUecsEntitiesSubmissionGroup.Add(spawnUnityEntityOnSveltoEntityEngine); + _enginesRoot.AddEngine(spawnUnityEntityOnSveltoEntityEngine); + } + + public void AddSveltoToUECSEngine(SyncSveltoToUECSEngine engine) + { + //it's a Svelto Engine/UECS SystemBase so it must be added in the UECS world AND svelto enginesRoot + world.AddSystem(engine); + _enginesRoot.AddEngine(engine); + + _syncSveltoToUecsGroup.Add(engine); + } + + public void AddUECSToSveltoEngine(SyncUECSToSveltoEngine engine) + { + //it's a Svelto Engine/UECS SystemBase so it must be added in the UECS world AND svelto enginesRoot + world.AddSystem(engine); + _enginesRoot.AddEngine(engine); + + _syncUecsToSveltoGroup.Add(engine); + } + + public void Dispose() + { + world.Dispose(); + } + + public string name => nameof(SveltoOverUECSEnginesGroup); + + SveltoUECSEntitiesSubmissionGroup _sveltoUecsEntitiesSubmissionGroup; + SyncSveltoToUECSGroup _syncSveltoToUecsGroup; + SyncUECSToSveltoGroup _syncUecsToSveltoGroup; + EnginesRoot _enginesRoot; + + } +} +#endif \ No newline at end of file diff --git a/Svelto.ECS/Extensions/Unity/DOTS/UECS/SveltoUECSEntitiesSubmissionGroup.cs b/Svelto.ECS/Extensions/Unity/DOTS/UECS/SveltoUECSEntitiesSubmissionGroup.cs new file mode 100644 index 0000000..ca835d6 --- /dev/null +++ b/Svelto.ECS/Extensions/Unity/DOTS/UECS/SveltoUECSEntitiesSubmissionGroup.cs @@ -0,0 +1,61 @@ +#if UNITY_ECS +using Svelto.ECS.Schedulers; +using Unity.Entities; +using Unity.Jobs; + +namespace Svelto.ECS.Extensions.Unity +{ + /// + /// Group of UECS/Svelto SystemBase engines that creates UECS entities. + /// Svelto entities are submitted + /// Svelto Add and remove callback are called + /// OnUpdate of the systems are called + /// finally the UECS command buffer is flushed + /// Note: I cannot use Unity ComponentSystemGroups nor I can rely on the SystemBase Dependency field to + /// solve external dependencies. External dependencies are tracked, but only linked to the UECS components operations + /// With Dependency I cannot guarantee that an external container is used before previous jobs working on it are completed + /// + public class SveltoUECSEntitiesSubmissionGroup : JobifiedEnginesGroup + { + public SveltoUECSEntitiesSubmissionGroup + (ISimpleEntitiesSubmissionScheduler submissionScheduler, World UECSWorld) + { + _submissionScheduler = submissionScheduler; + _ECBSystem = UECSWorld.CreateSystem(); + } + + public new void Execute(JobHandle jobHandle) + { + //Sync Point as we must be sure that jobs that create/swap/remove entities are done + jobHandle.Complete(); + + if (_submissionScheduler.paused) + return; + + //prepare the entity command buffer to be used by the registered engines + var entityCommandBuffer = _ECBSystem.CreateCommandBuffer(); + + foreach (var system in _engines) + { + system.ECB = entityCommandBuffer; + system.EM = _ECBSystem.EntityManager; + } + + //Submit Svelto Entities, calls Add/Remove/MoveTo that can be used by the IUECSSubmissionEngines + _submissionScheduler.SubmitEntities(); + + //execute submission engines and complete jobs + base.Execute(default).Complete(); + + //flush command buffer + _ECBSystem.Update(); + } + + readonly ISimpleEntitiesSubmissionScheduler _submissionScheduler; + readonly SubmissionEntitiesCommandBufferSystem _ECBSystem; + + [DisableAutoCreation] + class SubmissionEntitiesCommandBufferSystem : EntityCommandBufferSystem { } + } +} +#endif \ No newline at end of file diff --git a/Svelto.ECS/Extensions/Unity/DOTS/UECS/SyncSveltoToUECSGroup.cs b/Svelto.ECS/Extensions/Unity/DOTS/UECS/SyncSveltoToUECSGroup.cs new file mode 100644 index 0000000..33e8443 --- /dev/null +++ b/Svelto.ECS/Extensions/Unity/DOTS/UECS/SyncSveltoToUECSGroup.cs @@ -0,0 +1,25 @@ +#if UNITY_ECS +using Unity.Entities; +using Unity.Jobs; + +namespace Svelto.ECS.Extensions.Unity +{ + public class SyncSveltoToUECSGroup : JobifiedEnginesGroup + { + } + + public abstract class SyncSveltoToUECSEngine : SystemBase, IJobifiedEngine + { + public JobHandle Execute(JobHandle inputDeps) + { + Dependency = JobHandle.CombineDependencies(Dependency, inputDeps); + + Update(); + + return Dependency; + } + + public abstract string name { get; } + } +} +#endif \ No newline at end of file diff --git a/Svelto.ECS/Extensions/Unity/DOTS/UECS/SyncUECSToSveltoGroup.cs b/Svelto.ECS/Extensions/Unity/DOTS/UECS/SyncUECSToSveltoGroup.cs new file mode 100644 index 0000000..6bccbe0 --- /dev/null +++ b/Svelto.ECS/Extensions/Unity/DOTS/UECS/SyncUECSToSveltoGroup.cs @@ -0,0 +1,26 @@ +#if UNITY_ECS +using Unity.Entities; +using Unity.Jobs; + +namespace Svelto.ECS.Extensions.Unity +{ + public class SyncUECSToSveltoGroup : JobifiedEnginesGroup + { + + } + + public abstract class SyncUECSToSveltoEngine : SystemBase, IJobifiedEngine + { + public JobHandle Execute(JobHandle inputDeps) + { + Dependency = JobHandle.CombineDependencies(Dependency, inputDeps); + + Update(); + + return Dependency; + } + + public abstract string name { get; } + } +} +#endif \ No newline at end of file diff --git a/Svelto.ECS/Extensions/Unity/DOTS/UECSSveltoEGID.cs b/Svelto.ECS/Extensions/Unity/DOTS/UECS/UECSSveltoEGID.cs similarity index 100% rename from Svelto.ECS/Extensions/Unity/DOTS/UECSSveltoEGID.cs rename to Svelto.ECS/Extensions/Unity/DOTS/UECS/UECSSveltoEGID.cs diff --git a/Svelto.ECS/Extensions/Unity/DOTS/UnityEntityDBExtensions.cs b/Svelto.ECS/Extensions/Unity/DOTS/UnityEntityDBExtensions.cs deleted file mode 100644 index feeba5d..0000000 --- a/Svelto.ECS/Extensions/Unity/DOTS/UnityEntityDBExtensions.cs +++ /dev/null @@ -1,119 +0,0 @@ -#if UNITY_JOBS -using System; -using System.Runtime.CompilerServices; -using Svelto.Common; -using Svelto.DataStructures; -using Svelto.ECS.Extensions.Unity; -using Svelto.ECS.Internal; -using Unity.Jobs; - -namespace Svelto.ECS -{ - public static class UnityEntityDBExtensions2 - { - public static JobHandle ScheduleParallel - (this JOB job, uint iterations, JobHandle inputDeps) where JOB: struct, IJobParallelForBatch - { - if (iterations == 0) - return inputDeps; - var innerloopBatchCount = ProcessorCount.BatchSize(iterations); - return job.ScheduleBatch((int)iterations, innerloopBatchCount, inputDeps); - } - - public static JobHandle ScheduleParallel - (this JOB job, int iterations, JobHandle inputDeps) where JOB: struct, IJobParallelForBatch - { - if (iterations <= 0) - return inputDeps; - var innerloopBatchCount = ProcessorCount.BatchSize((uint) iterations); - return job.ScheduleBatch((int)iterations, innerloopBatchCount, inputDeps); - } - } - - public static class UnityEntityDBExtensions - { - internal static NativeEGIDMapper ToNativeEGIDMapper(this TypeSafeDictionary dic, - ExclusiveGroupStruct groupStructId) where T : unmanaged, IEntityComponent - { - var mapper = new NativeEGIDMapper(groupStructId, dic.implUnmgd); - - return mapper; - } - - public static JobHandle ScheduleDispose - (this T1 disposable, JobHandle inputDeps) where T1 : struct, IDisposable - { - return new DisposeJob(disposable).Schedule(inputDeps); - } - - public static JobHandle ScheduleDispose - (this T1 disposable1, T2 disposable2, JobHandle inputDeps) - where T1 : struct, IDisposable where T2 : struct, IDisposable - { - return new DisposeJob(disposable1, disposable2).Schedule(inputDeps); - } - - public static JobHandle ScheduleParallel - (this JOB job, uint iterations, JobHandle inputDeps) where JOB: struct, IJobParallelFor - { - if (iterations == 0) - return inputDeps; - var innerloopBatchCount = ProcessorCount.BatchSize(iterations); - return job.Schedule((int)iterations, innerloopBatchCount, inputDeps); - } - - public static JobHandle ScheduleParallel - (this JOB job, int iterations, JobHandle inputDeps) where JOB: struct, IJobParallelFor - { - if (iterations <= 0) - return inputDeps; - var innerloopBatchCount = ProcessorCount.BatchSize((uint) iterations); - return job.Schedule((int)iterations, innerloopBatchCount, inputDeps); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static NativeEGIDMapper QueryNativeMappedEntities(this EntitiesDB entitiesDb, ExclusiveGroupStruct groupStructId) - where T : unmanaged, IEntityComponent - { - if (entitiesDb.SafeQueryEntityDictionary(groupStructId, out var typeSafeDictionary) == false) - throw new EntityGroupNotFoundException(typeof(T)); - - return (typeSafeDictionary as TypeSafeDictionary).ToNativeEGIDMapper(groupStructId); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool TryQueryNativeMappedEntities(this EntitiesDB entitiesDb, ExclusiveGroupStruct groupStructId, - out NativeEGIDMapper mapper) - where T : unmanaged, IEntityComponent - { - mapper = default; - if (entitiesDb.SafeQueryEntityDictionary(groupStructId, out var typeSafeDictionary) == false || - typeSafeDictionary.count == 0) - return false; - - mapper = (typeSafeDictionary as TypeSafeDictionary).ToNativeEGIDMapper(groupStructId); - - return true; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static NativeEGIDMultiMapper QueryNativeMappedEntities(this EntitiesDB entitiesDb, LocalFasterReadOnlyList groups) - where T : unmanaged, IEntityComponent - { - var dictionary = - new SveltoDictionary>, NativeStrategy>, - NativeStrategy>, NativeStrategy>, NativeStrategy>>> - (groups.count, Allocator.TempJob); - - foreach (var group in groups) - { - if (entitiesDb.SafeQueryEntityDictionary(group, out var typeSafeDictionary) == true) - if (typeSafeDictionary.count > 0) - dictionary.Add(group, ((TypeSafeDictionary)typeSafeDictionary).implUnmgd); - } - - return new NativeEGIDMultiMapper(dictionary); - } - } -} -#endif \ No newline at end of file diff --git a/Svelto.ECS/Extensions/Unity/EntityDescriptorHolderHelper.cs b/Svelto.ECS/Extensions/Unity/EntityDescriptorHolderHelper.cs new file mode 100644 index 0000000..609c845 --- /dev/null +++ b/Svelto.ECS/Extensions/Unity/EntityDescriptorHolderHelper.cs @@ -0,0 +1,30 @@ +#if UNITY_ECS +using Svelto.ECS.Hybrid; +using UnityEngine; + +namespace Svelto.ECS.Extensions.Unity +{ + public static class EntityDescriptorHolderHelper + { + public static EntityComponentInitializer CreateEntity(this Transform contextHolder, EGID ID, + IEntityFactory factory, out T holder) + where T : MonoBehaviour, IEntityDescriptorHolder + { + holder = contextHolder.GetComponentInChildren(true); + var implementors = holder.GetComponents(); + + return factory.BuildEntity(ID, holder.GetDescriptor(), implementors); + } + + public static EntityComponentInitializer Create(this Transform contextHolder, EGID ID, + IEntityFactory factory) + where T : MonoBehaviour, IEntityDescriptorHolder + { + var holder = contextHolder.GetComponentInChildren(true); + var implementors = holder.GetComponents(); + + return factory.BuildEntity(ID, holder.GetDescriptor(), implementors); + } + } +} +#endif \ No newline at end of file diff --git a/Svelto.ECS/Extensions/Unity/GenericEntityDescriptorHolder.cs b/Svelto.ECS/Extensions/Unity/GenericEntityDescriptorHolder.cs index aeaa210..42b21e5 100644 --- a/Svelto.ECS/Extensions/Unity/GenericEntityDescriptorHolder.cs +++ b/Svelto.ECS/Extensions/Unity/GenericEntityDescriptorHolder.cs @@ -3,8 +3,7 @@ using UnityEngine; namespace Svelto.ECS.Extensions.Unity { - public abstract class GenericEntityDescriptorHolder: - MonoBehaviour , IEntityDescriptorHolder + public abstract class GenericEntityDescriptorHolder: MonoBehaviour , IEntityDescriptorHolder where T: IEntityDescriptor, new() { public IEntityDescriptor GetDescriptor() @@ -12,6 +11,11 @@ namespace Svelto.ECS.Extensions.Unity return EntityDescriptorTemplate.descriptor; } + public T GetRealDescriptor() + { + return EntityDescriptorTemplate.realDescriptor; + } + public string groupName => _groupName; public ushort id => _id; diff --git a/Svelto.ECS/Extensions/Unity/SveltoGUIHelper.cs b/Svelto.ECS/Extensions/Unity/SveltoGUIHelper.cs index 5ed7d42..e24303b 100644 --- a/Svelto.ECS/Extensions/Unity/SveltoGUIHelper.cs +++ b/Svelto.ECS/Extensions/Unity/SveltoGUIHelper.cs @@ -1,34 +1,23 @@ #if UNITY_5 || UNITY_5_3_OR_NEWER +using System; using Svelto.ECS.Hybrid; using UnityEngine; namespace Svelto.ECS.Extensions.Unity { - public static class EntityDescriptorHolderHelper - { - public static EntityComponentInitializer CreateEntity(this Transform contextHolder, EGID ID, - IEntityFactory factory, out T holder) - where T : MonoBehaviour, IEntityDescriptorHolder - { - holder = contextHolder.GetComponentInChildren(true); - var implementors = holder.GetComponents(); - - return factory.BuildEntity(ID, holder.GetDescriptor(), implementors); - } - - public static EntityComponentInitializer Create(this Transform contextHolder, EGID ID, - IEntityFactory factory) - where T : MonoBehaviour, IEntityDescriptorHolder - { - var holder = contextHolder.GetComponentInChildren(true); - var implementors = holder.GetComponents(); - - return factory.BuildEntity(ID, holder.GetDescriptor(), implementors); - } - } - public static class SveltoGUIHelper { + /// + /// This is the suggested way to create GUIs from prefabs now. + /// + /// + /// + /// + /// + /// + /// + /// + /// public static T CreateFromPrefab(ref uint startIndex, Transform contextHolder, IEntityFactory factory, ExclusiveGroup group, bool searchImplementorsInChildren = false, string groupNamePostfix = null) where T : MonoBehaviour, IEntityDescriptorHolder { @@ -37,64 +26,96 @@ namespace Svelto.ECS.Extensions.Unity foreach (var child in children) { - IImplementor[] childImplementors; if (child.GetType() != typeof(T)) { - var monoBehaviour = child as MonoBehaviour; + var monoBehaviour = child as MonoBehaviour; + IImplementor[] childImplementors; if (searchImplementorsInChildren == false) childImplementors = monoBehaviour.GetComponents(); else childImplementors = monoBehaviour.GetComponentsInChildren(true); - startIndex = InternalBuildAll( - startIndex, - child, - factory, - group, - childImplementors, - groupNamePostfix); + + startIndex = InternalBuildAll(startIndex, child, factory, group, childImplementors, + groupNamePostfix); } } return holder; } - public static EntityComponentInitializer Create(EGID ID, Transform contextHolder, - IEntityFactory factory, out T holder, bool searchImplementorsInChildren = false) - where T : MonoBehaviour, IEntityDescriptorHolder + /// + /// Creates all the entities in a hierarchy. This was commonly used to create entities from gameobjects + /// already present in the scene + /// + /// + /// + /// + /// + /// + /// + /// + public static uint CreateAll(uint startIndex, ExclusiveGroup group, + Transform contextHolder, IEntityFactory factory, string groupNamePostfix = null) where T : MonoBehaviour, IEntityDescriptorHolder { - holder = contextHolder.GetComponentInChildren(true); - var implementors = searchImplementorsInChildren == false ? holder.GetComponents() : holder.GetComponentsInChildren(true) ; + var holders = contextHolder.GetComponentsInChildren(true); - return factory.BuildEntity(ID, holder.GetDescriptor(), implementors); + foreach (var holder in holders) + { + var implementors = holder.GetComponents(); + try + { + startIndex = InternalBuildAll(startIndex, holder, factory, group, implementors, groupNamePostfix); + } + catch (Exception ex) + { + throw new Exception($"When building entity from game object {Path(holder.transform)}", ex); + } + } + + return startIndex; + } + + static string Path(Transform go) + { + string s = go.name; + while (go.parent != null) + { + go = go.parent; + s = go.name + "/" + s; + } + return s; } - public static EntityComponentInitializer Create(EGID ID, Transform contextHolder, - IEntityFactory factory, bool searchImplementorsInChildren = false) + public static EntityComponentInitializer Create(EGID ID, Transform contextHolder, IEntityFactory factory, + out T holder, bool searchImplementorsInChildren = false) where T : MonoBehaviour, IEntityDescriptorHolder { - var holder = contextHolder.GetComponentInChildren(true); + holder = contextHolder.GetComponentInChildren(true); + if (holder == null) + { + throw new Exception($"Could not find holder {typeof(T).Name} in {contextHolder.name}"); + } var implementors = searchImplementorsInChildren == false ? holder.GetComponents() : holder.GetComponentsInChildren(true) ; return factory.BuildEntity(ID, holder.GetDescriptor(), implementors); } - public static uint CreateAll(uint startIndex, ExclusiveGroup group, - Transform contextHolder, IEntityFactory factory, string groupNamePostfix = null) where T : MonoBehaviour, IEntityDescriptorHolder + public static EntityComponentInitializer Create(EGID ID, Transform contextHolder, + IEntityFactory factory, bool searchImplementorsInChildren = false) + where T : MonoBehaviour, IEntityDescriptorHolder { - var holders = contextHolder.GetComponentsInChildren(true); - - foreach (var holder in holders) + var holder = contextHolder.GetComponentInChildren(true); + if (holder == null) { - var implementors = holder.GetComponents(); - - startIndex = InternalBuildAll(startIndex, holder, factory, group, implementors, groupNamePostfix); + throw new Exception($"Could not find holder {typeof(T).Name} in {contextHolder.name}"); } + var implementors = searchImplementorsInChildren == false ? holder.GetComponents() : holder.GetComponentsInChildren(true) ; - return startIndex; + return factory.BuildEntity(ID, holder.GetDescriptor(), implementors); } static uint InternalBuildAll(uint startIndex, IEntityDescriptorHolder descriptorHolder, - IEntityFactory factory, ExclusiveGroup group, IImplementor[] implementors, string groupNamePostfix) + IEntityFactory factory, ExclusiveGroup group, IImplementor[] implementors, string groupNamePostfix) { ExclusiveGroupStruct realGroup = group; @@ -121,6 +142,8 @@ namespace Svelto.ECS.Extensions.Unity /// /// Works like CreateAll but only builds entities with holders that have the same group specified + /// This is a very specific case, basically used only by ControlsScreenRowFactory. Not sure what the real + /// use case is. /// /// /// The group to match diff --git a/Svelto.ECS/Extensions/Unity/UnityEntitySubmissionScheduler.cs b/Svelto.ECS/Extensions/Unity/UnityEntitiesSubmissionScheduler.cs similarity index 83% rename from Svelto.ECS/Extensions/Unity/UnityEntitySubmissionScheduler.cs rename to Svelto.ECS/Extensions/Unity/UnityEntitiesSubmissionScheduler.cs index 1f40879..f58a5b3 100644 --- a/Svelto.ECS/Extensions/Unity/UnityEntitySubmissionScheduler.cs +++ b/Svelto.ECS/Extensions/Unity/UnityEntitiesSubmissionScheduler.cs @@ -7,7 +7,7 @@ namespace Svelto.ECS.Schedulers.Unity { //The EntitySubmissionScheduler has been introduced to make the entity components submission logic platform independent //You can customize the scheduler if you wish - public class UnityEntitiesSubmissionScheduler : IEntitiesSubmissionScheduler + public class UnityEntitiesSubmissionScheduler : EntitiesSubmissionScheduler { class Scheduler : MonoBehaviour { @@ -37,14 +37,14 @@ namespace Svelto.ECS.Schedulers.Unity public System.Action onTick; } - public UnityEntitiesSubmissionScheduler(string name = "ECSScheduler") + public UnityEntitiesSubmissionScheduler(string name) { _scheduler = new GameObject(name).AddComponent(); GameObject.DontDestroyOnLoad(_scheduler.gameObject); _scheduler.onTick = SubmitEntities; } - public void Dispose() + public override void Dispose() { if (_scheduler != null && _scheduler.gameObject != null) { @@ -52,18 +52,18 @@ namespace Svelto.ECS.Schedulers.Unity } } + public override bool paused { get; set; } + void SubmitEntities() { if (paused == false) _onTick.Invoke(); } - - EnginesRoot.EntitiesSubmitter IEntitiesSubmissionScheduler.onTick + + protected internal override EnginesRoot.EntitiesSubmitter onTick { set => _onTick = value; } - - public bool paused { get; set; } readonly Scheduler _scheduler; EnginesRoot.EntitiesSubmitter _onTick; diff --git a/Svelto.ECS/FastGroup.cs b/Svelto.ECS/FastGroup.cs deleted file mode 100644 index 836bf35..0000000 --- a/Svelto.ECS/FastGroup.cs +++ /dev/null @@ -1,27 +0,0 @@ -#if later -namespace Svelto.ECS -{ - public class FastGroup - { - internal static uint entitiesCount; - - public FastGroup() - { - _group = ExclusiveGroupStruct.Generate(1); - } - - public static implicit operator ExclusiveGroupStruct(FastGroup group) - { - return group._group; - } - - public static explicit operator uint(FastGroup group) - { - return group._group; - } - - readonly ExclusiveGroupStruct _group; - public uint value => _group; - } -} -#endif \ No newline at end of file diff --git a/Svelto.ECS/Filters/EntitiesDB.GroupFilters.cs b/Svelto.ECS/Filters/EntitiesDB.GroupFilters.cs new file mode 100644 index 0000000..77e5684 --- /dev/null +++ b/Svelto.ECS/Filters/EntitiesDB.GroupFilters.cs @@ -0,0 +1,213 @@ +using System; +using Svelto.DataStructures; + +namespace Svelto.ECS +{ + /// + /// This feature must be eventually tied to the new ExclusiveGroup that won't allow the use of custom EntitiesID + /// The filters could be updated when entities buffer changes during the submission, while now this process + /// is completely manual. + /// Making this working properly is not in my priorities right now, as I will need to add the new group type + /// AND optimize the submission process to be able to add more overhead + /// + public partial class EntitiesDB + { + public readonly struct Filters + { + public Filters + (FasterDictionary> filters) + { + _filters = filters; + } + + public ref FilterGroup CreateOrGetFilterForGroup(int filterID, ExclusiveGroupStruct groupID) + where T : struct, IEntityComponent + { + var refWrapper = TypeRefWrapper.wrapper; + + return ref CreateOrGetFilterForGroup(filterID, groupID, refWrapper); + } + + ref FilterGroup CreateOrGetFilterForGroup(int filterID, ExclusiveGroupStruct groupID, RefWrapperType refWrapper) + { + var fasterDictionary = + _filters.GetOrCreate(refWrapper, () => new FasterDictionary()); + + GroupFilters filters = + fasterDictionary.GetOrCreate( + groupID, () => new GroupFilters(new SharedSveltoDictionaryNative(0), groupID)); + + return ref filters.CreateOrGetFilter(filterID); + } + + public bool HasFiltersForGroup(ExclusiveGroupStruct groupID) where T : struct, IEntityComponent + { + if (_filters.TryGetValue(TypeRefWrapper.wrapper, out var fasterDictionary) == false) + return false; + + return fasterDictionary.ContainsKey(groupID); + } + + public bool HasFilterForGroup(int filterID, ExclusiveGroupStruct groupID) + where T : struct, IEntityComponent + { + if (_filters.TryGetValue(TypeRefWrapper.wrapper, out var fasterDictionary) == false) + return false; + + if (fasterDictionary.TryGetValue(groupID, out var result)) + return result.HasFilter(filterID); + + return false; + } + + public ref GroupFilters CreateOrGetFiltersForGroup(ExclusiveGroupStruct groupID) + where T : struct, IEntityComponent + { + var fasterDictionary = + _filters.GetOrCreate(TypeRefWrapper.wrapper, () => new FasterDictionary()); + + return ref + fasterDictionary.GetOrCreate( + groupID, () => new GroupFilters(new SharedSveltoDictionaryNative(0), groupID)); + } + + public ref GroupFilters GetFiltersForGroup(ExclusiveGroupStruct groupID) + where T : struct, IEntityComponent + { +#if DEBUG && !PROFILE_SVELTO + if (_filters.ContainsKey(TypeRefWrapper.wrapper) == false) + throw new ECSException( + $"trying to fetch not existing filters, type {typeof(T)}"); + if (_filters[TypeRefWrapper.wrapper].ContainsKey(groupID) == false) + throw new ECSException( + $"trying to fetch not existing filters, type {typeof(T)} group {groupID.ToName()}"); +#endif + + return ref _filters[TypeRefWrapper.wrapper].GetValueByRef(groupID); + } + + public ref FilterGroup GetFilterForGroup(int filterId, ExclusiveGroupStruct groupID) + where T : struct, IEntityComponent + { +#if DEBUG && !PROFILE_SVELTO + if (_filters.ContainsKey(TypeRefWrapper.wrapper) == false) + throw new ECSException( + $"trying to fetch not existing filters, type {typeof(T)}"); + if (_filters[TypeRefWrapper.wrapper].ContainsKey(groupID) == false) + throw new ECSException( + $"trying to fetch not existing filters, type {typeof(T)} group {groupID.ToName()}"); +#endif + return ref _filters[TypeRefWrapper.wrapper][groupID].GetFilter(filterId); + } + + public bool TryGetFilterForGroup(int filterId, ExclusiveGroupStruct groupID, out FilterGroup groupFilter) + where T : struct, IEntityComponent + { + groupFilter = default; + + if (_filters.TryGetValue(TypeRefWrapper.wrapper, out var fasterDictionary) == false) + return false; + + if (fasterDictionary.TryGetValue(groupID, out var groupFilters) == false) + return false; + + if (groupFilters.TryGetFilter(filterId, out groupFilter) == false) + return false; + + return true; + } + + public bool TryGetFiltersForGroup(ExclusiveGroupStruct groupID, out GroupFilters groupFilters) + where T : struct, IEntityComponent + { + groupFilters = default; + + if (_filters.TryGetValue(TypeRefWrapper.wrapper, out var fasterDictionary) == false) + return false; + + return fasterDictionary.TryGetValue(groupID, out groupFilters); + } + + public void ClearFilter(int filterID, ExclusiveGroupStruct exclusiveGroupStruct) + { + if (_filters.TryGetValue(TypeRefWrapper.wrapper, out var fasterDictionary) == true) + { + DBC.ECS.Check.Require(fasterDictionary.ContainsKey(exclusiveGroupStruct), $"trying to clear filter not present in group {exclusiveGroupStruct}"); + + fasterDictionary[exclusiveGroupStruct].ClearFilter(filterID); + } + } + + public void ClearFilters(int filterID) + { + if (_filters.TryGetValue(TypeRefWrapper.wrapper, out var fasterDictionary) == true) + { + foreach (var filtersPerGroup in fasterDictionary) + filtersPerGroup.Value.ClearFilter(filterID); + } + } + + public void DisposeFilters(ExclusiveGroupStruct exclusiveGroupStruct) + { + if (_filters.TryGetValue(TypeRefWrapper.wrapper, out var fasterDictionary) == true) + { + fasterDictionary[exclusiveGroupStruct].DisposeFilters(); + fasterDictionary.Remove(exclusiveGroupStruct); + } + } + + public void DisposeFilters() + { + if (_filters.TryGetValue(TypeRefWrapper.wrapper, out var fasterDictionary) == true) + { + foreach (var filtersPerGroup in fasterDictionary) + filtersPerGroup.Value.DisposeFilters(); + } + + _filters.Remove(TypeRefWrapper.wrapper); + } + + public void DisposeFilterForGroup(int resetFilterID, ExclusiveGroupStruct @group) + { + if (_filters.TryGetValue(TypeRefWrapper.wrapper, out var fasterDictionary) == true) + { + fasterDictionary[group].DisposeFilter(resetFilterID); + } + } + + public bool TryRemoveEntityFromFilter(int filtersID, EGID egid) where T : unmanaged, IEntityComponent + { + if (TryGetFilterForGroup(filtersID, egid.groupID, out var filter)) + { + return filter.TryRemove(egid.entityID); + } + + return false; + } + + public void RemoveEntityFromFilter(int filtersID, EGID egid) where T : unmanaged, IEntityComponent + { + ref var filter = ref GetFilterForGroup(filtersID, egid.groupID); + + filter.Remove(egid.entityID); + } + + public void AddEntityToFilter(int filtersID, EGID egid, N mapper) where N:IEGIDMapper + { + ref var filter = ref CreateOrGetFilterForGroup(filtersID, egid.groupID, new RefWrapperType(mapper.entityType)); + + filter.Add(egid.entityID, mapper); + } + + readonly FasterDictionary> _filters; + } + + public Filters GetFilters() + { + return new Filters(_filters); + } + + FasterDictionary> _filters + => _enginesRoot._groupFilters; + } +} \ No newline at end of file diff --git a/Svelto.ECS/Filters/FilterGroup.cs b/Svelto.ECS/Filters/FilterGroup.cs new file mode 100644 index 0000000..99091dc --- /dev/null +++ b/Svelto.ECS/Filters/FilterGroup.cs @@ -0,0 +1,194 @@ +using Svelto.Common; +using Svelto.DataStructures; +using Svelto.ECS.DataStructures; + +namespace Svelto.ECS +{ + /// + /// In order to complete this feature, I need to be able to detect if the entity pointed by the filter + /// is still present. + /// This feature should work only with groups where entityID cannot be chosen by the user, so that + /// a real sparse set can be used like explained at: https://skypjack.github.io/2020-08-02-ecs-baf-part-9/ + /// For a sparse set to work, the index in the sparse list must coincide with the ID of the entity + /// so that from the dense list (that holds unordered entity index), I can get to the sparse list index + /// sparse[0] = position in the dense list of the entity 0 + /// dense[index] = entity ID but also index in the sparse list of the same entity ID + /// + public struct FilterGroup + { + internal FilterGroup(ExclusiveGroupStruct exclusiveGroupStruct, int ID) + { + _denseListOfIndicesToEntityComponentArray = + new NativeDynamicArrayCast(NativeDynamicArray.Alloc(Allocator.Persistent)); + //from the index, find the entityID + _reverseEIDs = new NativeDynamicArrayCast(NativeDynamicArray.Alloc(Allocator.Persistent)); + //from the entityID, find the index + _indexOfEntityInDenseList = new SharedSveltoDictionaryNative(0, Allocator.Persistent); + _exclusiveGroupStruct = exclusiveGroupStruct; + _ID = ID; + } + + /// + /// Todo: how to detect if the indices are still pointing to valid entities? + /// + public FilteredIndices filteredIndices => new FilteredIndices(_denseListOfIndicesToEntityComponentArray); + + public void Add(uint entityID, N mapper) where N:IEGIDMapper + { +#if DEBUG && !PROFILE_SVELTO + if (_denseListOfIndicesToEntityComponentArray.isValid == false) + throw new ECSException($"using an invalid filter"); + if (_indexOfEntityInDenseList.ContainsKey(entityID) == true) + throw new ECSException( + $"trying to add an existing entity {entityID} to filter {mapper.entityType} - {_ID} with group {mapper.groupID}"); + if (mapper.Exists(entityID) == false) + throw new ECSException( + $"trying adding an entity {entityID} to filter {mapper.entityType} - {_ID} with group {mapper.groupID}, but entity is not found! "); +#endif + //Get the index of the Entity in the component array + var indexOfEntityInBufferComponent = mapper.GetIndex(entityID); + + //add the index in the list of filtered indices + _denseListOfIndicesToEntityComponentArray.Add(indexOfEntityInBufferComponent); + + //inverse map: need to get the entityID from the index. This wouldn't be needed with a real sparseset + var lastIndex = (uint) (_denseListOfIndicesToEntityComponentArray.Count() - 1); + _reverseEIDs.AddAt(lastIndex) = entityID; + + //remember the entities indices. This is needed to remove entities from the filter + _indexOfEntityInDenseList.Add(entityID, lastIndex); + } + + public void Remove(uint entityID) + { +#if DEBUG && !PROFILE_SVELTO + if (_denseListOfIndicesToEntityComponentArray.isValid == false) + throw new ECSException($"invalid Filter"); + if (_indexOfEntityInDenseList.ContainsKey(entityID) == false) + throw new ECSException( + $"trying to remove a not existing entity {new EGID(entityID, _exclusiveGroupStruct)} from filter"); +#endif + InternalRemove(entityID); + } + + public bool TryRemove(uint entityID) + { +#if DEBUG && !PROFILE_SVELTO + if (_denseListOfIndicesToEntityComponentArray.isValid == false) + throw new ECSException($"invalid Filter"); +#endif + if (_indexOfEntityInDenseList.ContainsKey(entityID) == false) + return false; + + InternalRemove(entityID); + + return true; + } + + /// + /// Filters were initially designed to be used for tagging operations within submissions of entities. + /// They were designed as a fast tagging mechanism to be used within the submission frame. However I then + /// extended it, but the extension includes a drawback: + ///If filters are not in sync with the operations of remove and swap, filters may end up pointing to + ///invalid indices. I need to put in place a way to be able to recognised an invalid filter. + ///This is currently a disadvantage of the filters. The filters are not updated by the framework + ///but they must be updated by the user. + ///When to use this method: Add and Removed should be used to add and remove entities in the filters. This is + /// valid as long as no structural changes happen in the group of entities involved. + /// IF structural changes happen, the indices stored in the filters won't be valid anymore as they will possibly + /// point to entities that were not the original ones. On structural changes + /// (specifically entities swapped or removed) + /// the filters must then be rebuilt. It would be too slow to add this in the standard flow of Svelto in + /// the current state, so calling this method is a user responsibility. + /// + public void RebuildIndicesOnStructuralChange(N mapper) where N:IEGIDMapper + { +#if DEBUG && !PROFILE_SVELTO + if (_denseListOfIndicesToEntityComponentArray.isValid == false) + throw new ECSException($"invalid Filter"); +#endif + _denseListOfIndicesToEntityComponentArray.Clear(); + _reverseEIDs.Clear(); + + foreach (var value in _indexOfEntityInDenseList) + if (mapper.FindIndex(value.Key, out var indexOfEntityInBufferComponent) == true) + { + _denseListOfIndicesToEntityComponentArray.Add(indexOfEntityInBufferComponent); + var lastIndex = (uint) (_denseListOfIndicesToEntityComponentArray.Count() - 1); + _reverseEIDs.AddAt(lastIndex) = value.Key; + } + + _indexOfEntityInDenseList.Clear(); + + for (uint i = 0; i < _reverseEIDs.Count(); i++) + _indexOfEntityInDenseList[_reverseEIDs[i]] = i; + } + + public void Clear() + { +#if DEBUG && !PROFILE_SVELTO + if (_denseListOfIndicesToEntityComponentArray.isValid == false) + throw new ECSException($"invalid Filter"); +#endif + _indexOfEntityInDenseList.FastClear(); + _reverseEIDs.Clear(); + _denseListOfIndicesToEntityComponentArray.Clear(); + } + + internal void Dispose() + { +#if DEBUG && !PROFILE_SVELTO + if (_denseListOfIndicesToEntityComponentArray.isValid == false) + throw new ECSException($"invalid Filter"); +#endif + _denseListOfIndicesToEntityComponentArray.Dispose(); + _indexOfEntityInDenseList.Dispose(); + _reverseEIDs.Dispose(); + } + + void InternalRemove(uint entityID) + { + var count = (uint) _denseListOfIndicesToEntityComponentArray.Count(); + if (count > 0) + { + if (count > 1) + { + //get the index in the filter array of the entity to delete + var indexInDenseListFromEGID = _indexOfEntityInDenseList[entityID]; + //get the entityID of the last entity in the filter array + uint entityIDToMove = _reverseEIDs[count - 1]; + + //the last index of the last entity is updated to the slot of the deleted entity + if (entityIDToMove != entityID) + { + _indexOfEntityInDenseList[entityIDToMove] = indexInDenseListFromEGID; + //the reverseEGID is updated accordingly + _reverseEIDs[indexInDenseListFromEGID] = entityIDToMove; + } + + // + _reverseEIDs.UnorderedRemoveAt(count - 1); + + //finally remove the deleted entity from the filters array + _denseListOfIndicesToEntityComponentArray.UnorderedRemoveAt(indexInDenseListFromEGID); + + //remove the entity to delete from the tracked Entity + _indexOfEntityInDenseList.Remove(entityID); + } + else + { + _indexOfEntityInDenseList.FastClear(); + _reverseEIDs.Clear(); + _denseListOfIndicesToEntityComponentArray.Clear(); + } + } + } + + NativeDynamicArrayCast _denseListOfIndicesToEntityComponentArray; + NativeDynamicArrayCast _reverseEIDs; //forced to use this because it's not a real sparse set + SharedSveltoDictionaryNative _indexOfEntityInDenseList; + + readonly ExclusiveGroupStruct _exclusiveGroupStruct; + readonly int _ID; + } +} \ No newline at end of file diff --git a/Svelto.ECS/Filters/FilteredIndices.cs b/Svelto.ECS/Filters/FilteredIndices.cs new file mode 100644 index 0000000..47697cd --- /dev/null +++ b/Svelto.ECS/Filters/FilteredIndices.cs @@ -0,0 +1,21 @@ +using System.Runtime.CompilerServices; +using Svelto.ECS.DataStructures; + +namespace Svelto.ECS +{ + public readonly struct FilteredIndices + { + public FilteredIndices(NativeDynamicArrayCast denseListOfIndicesToEntityComponentArray) + { + _denseListOfIndicesToEntityComponentArray = denseListOfIndicesToEntityComponentArray; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int Count() => _denseListOfIndicesToEntityComponentArray.Count(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public uint Get(uint index) => _denseListOfIndicesToEntityComponentArray[index]; + + readonly NativeDynamicArrayCast _denseListOfIndicesToEntityComponentArray; + } +} \ No newline at end of file diff --git a/Svelto.ECS/Filters/GroupFilters.cs b/Svelto.ECS/Filters/GroupFilters.cs new file mode 100644 index 0000000..4226a23 --- /dev/null +++ b/Svelto.ECS/Filters/GroupFilters.cs @@ -0,0 +1,103 @@ +using Svelto.DataStructures; + +namespace Svelto.ECS +{ + public struct GroupFilters + { + internal GroupFilters(SharedSveltoDictionaryNative filters, ExclusiveGroupStruct group) + { + this.filters = filters; + _group = @group; + } + + public ref FilterGroup GetFilter(int filterIndex) + { +#if DEBUG && !PROFILE_SVELTO + if (filters.isValid == false) + throw new ECSException($"trying to fetch not existing filters {filterIndex} group {_group.ToName()}"); + if (filters.ContainsKey(filterIndex) == false) + throw new ECSException($"trying to fetch not existing filters {filterIndex} group {_group.ToName()}"); +#endif + return ref filters.GetValueByRef(filterIndex); + } + + public bool HasFilter(int filterIndex) { return filters.ContainsKey(filterIndex); } + + public void ClearFilter(int filterIndex) + { + if (filters.TryFindIndex(filterIndex, out var index)) + filters.GetValues(out _)[index].Clear(); + } + + public void ClearFilters() + { + foreach (var filter in filters) + filter.Value.Clear(); + } + + public bool TryGetFilter(int filterIndex, out FilterGroup filter) + { + return filters.TryGetValue(filterIndex, out filter); + } + + public SveltoDictionary>, NativeStrategy + , NativeStrategy>.SveltoDictionaryKeyValueEnumerator GetEnumerator() + { + return filters.GetEnumerator(); + } + + //Note the following methods are internal because I was pondering the idea to be able to return + //the list of GroupFilters linked to a specific filter ID. However this would mean to be able to + //maintain a revers map which at this moment seems too much and also would need the following + //method to be for ever internal (at this point in time I am not sure it's a good idea) + internal void DisposeFilter(int filterIndex) + { + if (filters.TryFindIndex(filterIndex, out var index)) + { + ref var filterGroup = ref filters.GetValues(out _)[index]; + + filterGroup.Dispose(); + + filters.Remove(filterIndex); + } + } + + internal void DisposeFilters() + { + //must release the native buffers! + foreach (var filter in filters) + filter.Value.Dispose(); + + filters.FastClear(); + } + + internal ref FilterGroup CreateOrGetFilter(int filterID) + { + if (filters.TryFindIndex(filterID, out var index) == false) + { + var orGetFilterForGroup = new FilterGroup(_group, filterID); + + filters[filterID] = orGetFilterForGroup; + + return ref filters.GetValueByRef(filterID); + } + + return ref filters.GetValues(out _)[index]; + } + + internal void Dispose() + { + foreach (var filter in filters) + { + filter.Value.Dispose(); + } + + filters.Dispose(); + } + + readonly ExclusiveGroupStruct _group; + + //filterID, filter + SharedSveltoDictionaryNative filters; + } +} \ No newline at end of file diff --git a/Svelto.ECS/GlobalTypeID.cs b/Svelto.ECS/GlobalTypeID.cs index b19f813..78a12c6 100644 --- a/Svelto.ECS/GlobalTypeID.cs +++ b/Svelto.ECS/GlobalTypeID.cs @@ -23,7 +23,7 @@ namespace Svelto.ECS { static Filler() { - DBC.ECS.Check.Require(UnmanagedTypeExtensions.IsUnmanagedEx() == true, "invalid type used"); + DBC.ECS.Check.Require(TypeCache.IsUnmanaged == true, "invalid type used"); } //it's an internal interface @@ -37,7 +37,7 @@ namespace Svelto.ECS static class EntityComponentID { -#if UNITY_BURST +#if UNITY_NATIVE internal static readonly Unity.Burst.SharedStatic ID = Unity.Burst.SharedStatic.GetOrCreate(); #else diff --git a/Svelto.ECS/GroupCompound.cs b/Svelto.ECS/GroupCompound.cs index 6de2cf0..53badc1 100644 --- a/Svelto.ECS/GroupCompound.cs +++ b/Svelto.ECS/GroupCompound.cs @@ -1,126 +1,225 @@ using System; +using System.Threading; using Svelto.DataStructures; namespace Svelto.ECS { - public abstract class GroupCompound - where G1 : GroupTag where G2 : GroupTag where G3 : GroupTag + /// + /// Very naive fail safe, but at least it's simple to understand and safe + /// + static class GroupCompoundInitializer { - static readonly FasterList _Groups; - - public static FasterReadOnlyList Groups => new FasterReadOnlyList(_Groups); - - static GroupCompound() - { - if ((_Groups = GroupCompound._Groups) == null) - if ((_Groups = GroupCompound._Groups) == null) - if ((_Groups = GroupCompound._Groups) == null) - if ((_Groups = GroupCompound._Groups) == null) - if ((_Groups = GroupCompound._Groups) == null) - { - _Groups = new FasterList(1); - - var Group = new ExclusiveGroup(); - _Groups.Add(Group); - - GroupCompound.Add(Group); // and must share the same array - GroupCompound.Add(Group); - GroupCompound.Add(Group); - - //This is done here to be sure that the group is added once per group tag - //(if done inside the previous group compound it would be added multiple times) - GroupTag.Add(Group); - GroupTag.Add(Group); - GroupTag.Add(Group); - -#if DEBUG - GroupMap.idToName[(uint) Group] = $"Compound: {typeof(G1).Name}-{typeof(G2).Name}-{typeof(G3).Name}"; -#endif - } - } - - public static void Add(ExclusiveGroupStruct @group) - { - for (int i = 0; i < _Groups.count; ++i) - if (_Groups[i] == group) - throw new Exception("temporary must be transformed in unit test"); - - _Groups.Add(group); - - // GroupCompound._Groups = _Groups; - } - - public static ExclusiveGroupStruct BuildGroup => new ExclusiveGroupStruct(_Groups[0]); - } - - public abstract class GroupCompound where G1 : GroupTag where G2 : GroupTag - { - static FasterList _Groups; - public static FasterReadOnlyList Groups => new FasterReadOnlyList(_Groups); - - static GroupCompound() - { - _Groups = GroupCompound._Groups; - - if (_Groups == null) - { - _Groups = new FasterList(1); - var Group = new ExclusiveGroup(); - _Groups.Add(Group); - - //every abstract group preemptively adds this group, it may or may not be empty in future - GroupTag.Add(Group); - GroupTag.Add(Group); - -#if DEBUG - GroupMap.idToName[(uint) Group] = $"Compound: {typeof(G1).Name}-{typeof(G2).Name}"; -#endif - } - } - - public static ExclusiveGroupStruct BuildGroup => new ExclusiveGroupStruct(_Groups[0]); - - public static void Add(ExclusiveGroupStruct @group) - { - for (int i = 0; i < _Groups.count; ++i) - if (_Groups[i] == group) - throw new Exception("temporary must be transformed in unit test"); - - _Groups.Add(group); - - //unit test this to check if it's necessary - // GroupCompound._Groups = _Groups; - } - } - - //A Group Tag holds initially just a group, itself. However the number of groups can grow with the number of - //combinations of GroupTags including this one. This because a GroupTag is an adjective and different entities - //can use the same adjective together with other ones. However since I need to be able to iterate over all the - //groups with the same adjective, a group tag needs to hold all the groups sharing it. - public abstract class GroupTag where T : GroupTag - { - static FasterList _Groups = new FasterList(1); - - public static FasterReadOnlyList Groups => new FasterReadOnlyList(_Groups); - - static GroupTag() - { - _Groups.Add(new ExclusiveGroup()); - } - - //Each time a new combination of group tags is found a new group is added. - internal static void Add(ExclusiveGroupStruct @group) - { - for (int i = 0; i < _Groups.count; ++i) - if (_Groups[i] == group) - throw new Exception("temporary must be transformed in unit test"); - - _Groups.Add(group); -#if DEBUG - GroupMap.idToName[(uint) group] = $"Compound: {typeof(T).Name}"; -#endif - } - - public static ExclusiveGroupStruct BuildGroup => new ExclusiveGroupStruct(_Groups[0]); - } -} + internal static readonly ThreadLocal isInitializing4 = new ThreadLocal(); + internal static readonly ThreadLocal isInitializing3 = new ThreadLocal(); + internal static readonly ThreadLocal isInitializing2 = new ThreadLocal(); + } + + public abstract class GroupCompound + where G1 : GroupTag where G2 : GroupTag where G3 : GroupTag where G4 : GroupTag + { + static readonly FasterList _Groups; + + public static FasterReadOnlyList Groups => new FasterReadOnlyList(_Groups); + public static BuildGroup BuildGroup => new BuildGroup(_Groups[0]); + + static GroupCompound() + { + if (GroupCompoundInitializer.isInitializing4.Value == false) + { + _Groups = new FasterList(1); + + var Group = new ExclusiveGroup(); + _Groups.Add(Group); + + GroupCompound.Add(Group); + GroupCompound.Add(Group); + GroupCompound.Add(Group); + GroupCompound.Add(Group); + + GroupCompound.Add(Group); // and must share the same array + GroupCompound.Add(Group); + GroupCompound.Add(Group); + GroupCompound.Add(Group); + GroupCompound.Add(Group); + GroupCompound.Add(Group); + + //This is done here to be sure that the group is added once per group tag + //(if done inside the previous group compound it would be added multiple times) + GroupTag.Add(Group); + GroupTag.Add(Group); + GroupTag.Add(Group); + GroupTag.Add(Group); + + #if DEBUG + GroupMap.idToName[(uint) Group] = $"Compound: {typeof(G1).Name}-{typeof(G2).Name}-{typeof(G3).Name}-{typeof(G4).Name} ID {(uint)Group}"; + #endif + GroupCompoundInitializer.isInitializing4.Value = true; + //all the combinations must share the same group + GroupCompound._Groups = _Groups; + GroupCompound._Groups = _Groups; + GroupCompound._Groups = _Groups; + GroupCompound._Groups = _Groups; + GroupCompound._Groups = _Groups; + GroupCompound._Groups = _Groups; + GroupCompound._Groups = _Groups; + GroupCompound._Groups = _Groups; + GroupCompound._Groups = _Groups; + GroupCompound._Groups = _Groups; + GroupCompound._Groups = _Groups; + GroupCompound._Groups = _Groups; + GroupCompound._Groups = _Groups; + GroupCompound._Groups = _Groups; + GroupCompound._Groups = _Groups; + GroupCompound._Groups = _Groups; + GroupCompound._Groups = _Groups; + GroupCompound._Groups = _Groups; + GroupCompound._Groups = _Groups; + GroupCompound._Groups = _Groups; + GroupCompound._Groups = _Groups; + GroupCompound._Groups = _Groups; + GroupCompound._Groups = _Groups; + GroupCompoundInitializer.isInitializing4.Value = false; + } + } + + public static void Add(ExclusiveGroupStruct @group) + { + for (int i = 0; i < _Groups.count; ++i) + if (_Groups[i] == group) + throw new Exception("temporary must be transformed in unit test"); + + _Groups.Add(group); + } + } + + public abstract class GroupCompound + where G1 : GroupTag where G2 : GroupTag where G3 : GroupTag + { + static readonly FasterList _Groups; + + public static FasterReadOnlyList Groups => + new FasterReadOnlyList(_Groups); + + public static BuildGroup BuildGroup => new BuildGroup(_Groups[0]); + + public static void Add(ExclusiveGroupStruct group) + { + for (var i = 0; i < _Groups.count; ++i) + if (_Groups[i] == group) + throw new Exception("temporary must be transformed in unit test"); + + _Groups.Add(group); + } + + static GroupCompound() + { + if (GroupCompoundInitializer.isInitializing3.Value == false) + { + _Groups = new FasterList(1); + + var Group = new ExclusiveGroup(); + _Groups.Add(Group); + + GroupCompound.Add(Group); // and must share the same array + GroupCompound.Add(Group); + GroupCompound.Add(Group); + + //This is done here to be sure that the group is added once per group tag + //(if done inside the previous group compound it would be added multiple times) + GroupTag.Add(Group); + GroupTag.Add(Group); + GroupTag.Add(Group); + + #if DEBUG + GroupMap.idToName[(uint) Group] = $"Compound: {typeof(G1).Name}-{typeof(G2).Name}-{typeof(G3).Name} ID {(uint)Group}"; + #endif + //all the combinations must share the same group + GroupCompoundInitializer.isInitializing3.Value = true; + GroupCompound._Groups = _Groups; + GroupCompound._Groups = _Groups; + GroupCompound._Groups = _Groups; + GroupCompound._Groups = _Groups; + GroupCompound._Groups = _Groups; + GroupCompoundInitializer.isInitializing3.Value = false; + } + } + } + + public abstract class GroupCompound where G1 : GroupTag where G2 : GroupTag + { + static readonly FasterList _Groups; + + public static FasterReadOnlyList Groups => + new FasterReadOnlyList(_Groups); + + public static BuildGroup BuildGroup => new BuildGroup(_Groups[0]); + + public static void Add(ExclusiveGroupStruct group) + { + for (var i = 0; i < _Groups.count; ++i) + if (_Groups[i] == group) + throw new Exception("temporary must be transformed in unit test"); + + _Groups.Add(group); + } + + static GroupCompound() + { + if (GroupCompoundInitializer.isInitializing2.Value == false) + { + var Group = new ExclusiveGroup(); + + _Groups = new FasterList(1); + _Groups.Add(Group); + + //every abstract group preemptively adds this group, it may or may not be empty in future + GroupTag.Add(Group); + GroupTag.Add(Group); + + #if DEBUG + GroupMap.idToName[(uint) Group] = $"Compound: {typeof(G1).Name}-{typeof(G2).Name} ID {(uint)Group}"; + #endif + GroupCompoundInitializer.isInitializing2.Value = true; + GroupCompound._Groups = _Groups; + GroupCompoundInitializer.isInitializing2.Value = false; + } + } + } + + /// + ///A Group Tag holds initially just a group, itself. However the number of groups can grow with the number of + ///combinations of GroupTags including this one. This because a GroupTag is an adjective and different entities + ///can use the same adjective together with other ones. However since I need to be able to iterate over all the + ///groups with the same adjective, a group tag needs to hold all the groups sharing it. + /// + /// + public abstract class GroupTag where T : GroupTag + { + static readonly FasterList _Groups = new FasterList(1); + + public static FasterReadOnlyList Groups => + new FasterReadOnlyList(_Groups); + + public static BuildGroup BuildGroup => new BuildGroup(_Groups[0]); + + static GroupTag() + { + var group = new ExclusiveGroup(); + _Groups.Add(group); + + #if DEBUG + GroupMap.idToName[(uint) group] = $"Compound: {typeof(T).Name} ID {(uint)group}"; + #endif + } + + //Each time a new combination of group tags is found a new group is added. + internal static void Add(ExclusiveGroupStruct group) + { + for (var i = 0; i < _Groups.count; ++i) + if (_Groups[i] == group) + throw new Exception("temporary must be transformed in unit test"); + + _Groups.Add(group); + } + } +} \ No newline at end of file diff --git a/Svelto.ECS/Hybrid/IEntityViewComponent.cs b/Svelto.ECS/Hybrid/IEntityViewComponent.cs index 179e9fd..7bee311 100644 --- a/Svelto.ECS/Hybrid/IEntityViewComponent.cs +++ b/Svelto.ECS/Hybrid/IEntityViewComponent.cs @@ -1,6 +1,9 @@ namespace Svelto.ECS.Hybrid { - public interface IEntityViewComponent:IEntityComponent, INeedEGID + public interface IManagedComponent:IEntityComponent + {} + + public interface IEntityViewComponent:IManagedComponent, INeedEGID {} } diff --git a/Svelto.ECS/IDisposingEngine.cs b/Svelto.ECS/IDisposingEngine.cs new file mode 100644 index 0000000..49045f9 --- /dev/null +++ b/Svelto.ECS/IDisposingEngine.cs @@ -0,0 +1,9 @@ +using System; + +namespace Svelto.ECS +{ + public interface IDisposingEngine: IDisposable + { + bool isDisposing { set; } + } +} \ No newline at end of file diff --git a/Svelto.ECS/IEngine.cs b/Svelto.ECS/IEngine.cs index b671fd9..5c5c8e9 100644 --- a/Svelto.ECS/IEngine.cs +++ b/Svelto.ECS/IEngine.cs @@ -1,3 +1,5 @@ +using Svelto.ECS.Internal; + namespace Svelto.ECS.Internal { public interface IReactEngine: IEngine @@ -14,4 +16,20 @@ namespace Svelto.ECS { public interface IEngine {} + + public interface IReactOnAddAndRemove : IReactOnAddAndRemove where T : IEntityComponent + { + void Add(ref T entityComponent, EGID egid); + void Remove(ref T entityComponent, EGID egid); + } + + public interface IReactOnSwap : IReactOnSwap where T : IEntityComponent + { + void MovedTo(ref T entityComponent, ExclusiveGroupStruct previousGroup, EGID egid); + } + + public interface IReactOnSubmission:IReactEngine + { + void EntitiesSubmitted(); + } } \ No newline at end of file diff --git a/Svelto.ECS/IEntitiesDB.cs b/Svelto.ECS/IEntitiesDB.cs deleted file mode 100644 index e1a2822..0000000 --- a/Svelto.ECS/IEntitiesDB.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Svelto.DataStructures; - -namespace Svelto.ECS -{ - public delegate void ExecuteOnAllEntitiesAction(IBuffer prefabStruct, ExclusiveGroupStruct group, - uint count, EntitiesDB db, ref W instances); - public delegate void ExecuteOnAllEntitiesAction(IBuffer entities, ExclusiveGroupStruct group, - uint count, EntitiesDB db); -} diff --git a/Svelto.ECS/IEntityComponent.cs b/Svelto.ECS/IEntityComponent.cs index 1b3bd40..d5269f7 100644 --- a/Svelto.ECS/IEntityComponent.cs +++ b/Svelto.ECS/IEntityComponent.cs @@ -1,12 +1,12 @@ namespace Svelto.ECS { - ///EntityComponent MUST implement IEntityComponent + ///Entity Components MUST implement IEntityComponent public interface IEntityComponent { } /// - /// use INeedEGID on an IEntityComponent only if you need the EGID + /// use INeedEGID on an IEntityComponent only if you need the EGID. consider using EGIDComponent instead /// public interface INeedEGID { diff --git a/Svelto.ECS/IEntityFactory.cs b/Svelto.ECS/IEntityFactory.cs index 814babc..6220c2b 100644 --- a/Svelto.ECS/IEntityFactory.cs +++ b/Svelto.ECS/IEntityFactory.cs @@ -43,22 +43,14 @@ namespace Svelto.ECS /// /// /// - EntityComponentInitializer BuildEntity(uint entityID, ExclusiveGroupStruct groupStructId, + EntityComponentInitializer BuildEntity(uint entityID, BuildGroup groupStructId, IEnumerable implementors = null) where T : IEntityDescriptor, new(); EntityComponentInitializer BuildEntity(EGID egid, IEnumerable implementors = null) where T : IEntityDescriptor, new(); - /// - /// When the type of the entity is not known (this is a special case!) an EntityDescriptorInfo - /// can be built in place of the generic parameter T. - /// - /// - /// - /// - /// - EntityComponentInitializer BuildEntity(uint entityID, ExclusiveGroupStruct groupStructId, + EntityComponentInitializer BuildEntity(uint entityID, BuildGroup groupStructId, T descriptorEntity, IEnumerable implementors = null) where T : IEntityDescriptor; @@ -68,7 +60,7 @@ namespace Svelto.ECS EntityComponentInitializer BuildEntity (EGID egid, IComponentBuilder[] componentsToBuild, Type type, IEnumerable implementors = null); -#if UNITY_BURST +#if UNITY_NATIVE NativeEntityFactory ToNative(string memberName) where T : IEntityDescriptor, new(); #endif } diff --git a/Svelto.ECS/IEntityFunctions.cs b/Svelto.ECS/IEntityFunctions.cs index e42d410..cee25a4 100644 --- a/Svelto.ECS/IEntityFunctions.cs +++ b/Svelto.ECS/IEntityFunctions.cs @@ -5,29 +5,26 @@ namespace Svelto.ECS //being entity ID globally not unique, the group must be specified when //an entity is removed. Not specifying the group will attempt to remove //the entity from the special standard group. - void RemoveEntity(uint entityID, ExclusiveGroupStruct groupID) where T : IEntityDescriptor, new(); + void RemoveEntity(uint entityID, BuildGroup groupID) where T : IEntityDescriptor, new(); void RemoveEntity(EGID entityegid) where T : IEntityDescriptor, new(); - void RemoveAllEntities(ExclusiveGroupStruct group) where T : IEntityDescriptor, new(); - void RemoveAllEntities() where T : IEntityDescriptor, new(); + void RemoveEntitiesFromGroup(BuildGroup groupID); - void RemoveGroupAndEntities(ExclusiveGroupStruct groupID); + void SwapEntitiesInGroup(BuildGroup fromGroupID, BuildGroup toGroupID) where T : IEntityDescriptor, new(); - void SwapEntitiesInGroup(ExclusiveGroupStruct fromGroupID, ExclusiveGroupStruct toGroupID); - - void SwapEntityGroup(uint entityID, ExclusiveGroupStruct fromGroupID, ExclusiveGroupStruct toGroupID) + void SwapEntityGroup(uint entityID, BuildGroup fromGroupID, BuildGroup toGroupID) where T : IEntityDescriptor, new(); - void SwapEntityGroup(EGID fromID, ExclusiveGroupStruct toGroupID) where T : IEntityDescriptor, new(); + void SwapEntityGroup(EGID fromID, BuildGroup toGroupID) where T : IEntityDescriptor, new(); - void SwapEntityGroup(EGID fromID, ExclusiveGroupStruct toGroupID, ExclusiveGroupStruct mustBeFromGroup) + void SwapEntityGroup(EGID fromID, BuildGroup toGroupID, BuildGroup mustBeFromGroup) where T : IEntityDescriptor, new(); void SwapEntityGroup(EGID fromID, EGID toId) where T : IEntityDescriptor, new(); - void SwapEntityGroup(EGID fromID, EGID toId, ExclusiveGroupStruct mustBeFromGroup) + void SwapEntityGroup(EGID fromID, EGID toId, BuildGroup mustBeFromGroup) where T : IEntityDescriptor, new(); -#if UNITY_BURST +#if UNITY_NATIVE NativeEntityRemove ToNativeRemove(string memberName) where T : IEntityDescriptor, new(); NativeEntitySwap ToNativeSwap(string memberName) where T : IEntityDescriptor, new(); #endif diff --git a/Svelto.ECS/IReactOnAddAndRemove.cs b/Svelto.ECS/IReactOnAddAndRemove.cs deleted file mode 100644 index f3e5375..0000000 --- a/Svelto.ECS/IReactOnAddAndRemove.cs +++ /dev/null @@ -1,10 +0,0 @@ -using Svelto.ECS.Internal; - -namespace Svelto.ECS -{ - public interface IReactOnAddAndRemove : IReactOnAddAndRemove where T : IEntityComponent - { - void Add(ref T entityComponent, EGID egid); - void Remove(ref T entityComponent, EGID egid); - } - } \ No newline at end of file diff --git a/Svelto.ECS/IReactOnSwap.cs b/Svelto.ECS/IReactOnSwap.cs deleted file mode 100644 index e8e1e32..0000000 --- a/Svelto.ECS/IReactOnSwap.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Svelto.ECS.Internal; - -namespace Svelto.ECS -{ - public interface IReactOnSwap : IReactOnSwap where T : IEntityComponent - { - void MovedTo(ref T entityComponent, ExclusiveGroupStruct previousGroup, EGID egid); - } -} \ No newline at end of file diff --git a/Svelto.ECS/NamedExclusiveGroup.cs b/Svelto.ECS/NamedExclusiveGroup.cs index 61dc15e..a35d06b 100644 --- a/Svelto.ECS/NamedExclusiveGroup.cs +++ b/Svelto.ECS/NamedExclusiveGroup.cs @@ -14,7 +14,7 @@ namespace Svelto.ECS static NamedExclusiveGroup() { #if DEBUG - GroupMap.idToName[(uint) Group] = name; + GroupMap.idToName[(uint) Group] = $"{name} ID {(uint)Group}"; #endif } // protected NamedExclusiveGroup(string recognizeAs) : base(recognizeAs) {} diff --git a/Svelto.ECS/QueryGroups.cs b/Svelto.ECS/QueryGroups.cs index a22f4b5..42b9b20 100644 --- a/Svelto.ECS/QueryGroups.cs +++ b/Svelto.ECS/QueryGroups.cs @@ -1,3 +1,4 @@ +using System.Runtime.CompilerServices; using System.Threading; using Svelto.DataStructures; @@ -26,9 +27,25 @@ namespace Svelto.ECS.Experimental group.FastClear(); for (int i = 0; i < findGroups.count; i++) - { group.Add(findGroups[i]); - } + } + + public QueryGroups(ExclusiveGroupStruct findGroups) + { + var groupsValue = groups.Value; + var group = groupsValue.reference; + + group.FastClear(); + group.Add(findGroups); + } + + public QueryGroups(uint preparecount) + { + var groupsValue = groups.Value; + var group = groupsValue.reference; + + group.FastClear(); + group.EnsureCapacity(preparecount); } public QueryResult Except(ExclusiveGroupStruct[] groupsToIgnore) @@ -37,8 +54,8 @@ namespace Svelto.ECS.Experimental var groupsCount = group.count; for (int i = 0; i < groupsToIgnore.Length; i++) + for (int j = 0; j < groupsCount; j++) { - for (int j = 0; j < groupsCount; j++) if (groupsToIgnore[i] == group[j]) { group.UnorderedRemoveAt(j); @@ -50,6 +67,25 @@ namespace Svelto.ECS.Experimental return new QueryResult(group); } + public QueryResult Except(FasterList groupsToIgnore) + { + var group = groups.Value.reference; + var groupsCount = group.count; + + for (int i = 0; i < groupsToIgnore.count; i++) + for (int j = 0; j < groupsCount; j++) + { + if (groupsToIgnore[i] == group[j]) + { + group.UnorderedRemoveAt(j); + j--; + groupsCount--; + } + } + + return new QueryResult(group); + } + public QueryResult Except(ExclusiveGroupStruct groupsToIgnore) { var group = groups.Value.reference; @@ -65,7 +101,47 @@ namespace Svelto.ECS.Experimental return new QueryResult(group); } - } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int Count + (EntitiesDB entitiesDB, in LocalFasterReadOnlyList groups) where T : struct, IEntityComponent + { + int count = 0; + + var groupsCount = groups.count; + for (int i = 0; i < groupsCount; ++i) + { + count += entitiesDB.Count(groups[i]); + } + + return count; + } + + public QueryResult WithAny(EntitiesDB entitiesDB) + where T : struct, IEntityComponent + { + var group = groups.Value.reference; + var groupsCount = group.count; + + for (var i = 0; i < groupsCount; i++) + { + if (entitiesDB.Count(group[i]) == 0) + { + group.UnorderedRemoveAt(i); + i--; + groupsCount--; + } + } + + return new QueryResult(group); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Add(ExclusiveGroupStruct group) + { + groups.Value.reference.Add(group); + } + } public readonly ref struct QueryResult { diff --git a/Svelto.ECS/Serialization/EnginesRoot.GenericEntitySerialization.cs b/Svelto.ECS/Serialization/EnginesRoot.GenericEntitySerialization.cs index c333630..98d856f 100644 --- a/Svelto.ECS/Serialization/EnginesRoot.GenericEntitySerialization.cs +++ b/Svelto.ECS/Serialization/EnginesRoot.GenericEntitySerialization.cs @@ -1,22 +1,20 @@ using System; +using Svelto.DataStructures; using Svelto.ECS.Serialization; namespace Svelto.ECS { public partial class EnginesRoot { - readonly bool _isDeserializationOnly; - sealed class EntitySerialization : IEntitySerialization { - public void SerializeEntity(EGID egid, ISerializationData serializationData, - int serializationType) + public void SerializeEntity(EGID egid, ISerializationData serializationData, int serializationType) { var entitiesDb = _enginesRoot._entitiesDB; //needs to retrieve the meta data associated with the entity ref var serializableEntityComponent = ref entitiesDb.QueryEntity(egid); - uint descriptorHash = serializableEntityComponent.descriptorHash; + uint descriptorHash = serializableEntityComponent.descriptorHash; SerializationDescriptorMap serializationDescriptorMap = _enginesRoot.serializationDescriptorMap; var entityDescriptor = serializationDescriptorMap.GetDescriptorFromHash(descriptorHash); @@ -35,33 +33,20 @@ namespace Svelto.ECS } } - public EntityComponentInitializer DeserializeNewEntity(EGID egid, ISerializationData serializationData, - int serializationType) + public EntityComponentInitializer DeserializeNewEntity + (EGID egid, ISerializationData serializationData, int serializationType) { //todo: SerializableEntityHeader may be needed to be customizable var serializableEntityHeader = new SerializableEntityHeader(serializationData); uint descriptorHash = serializableEntityHeader.descriptorHash; SerializationDescriptorMap serializationDescriptorMap = _enginesRoot.serializationDescriptorMap; - IDeserializationFactory factory = serializationDescriptorMap.GetSerializationFactory(descriptorHash); var entityDescriptor = serializationDescriptorMap.GetDescriptorFromHash(descriptorHash); + IDeserializationFactory factory = serializationDescriptorMap.GetSerializationFactory(descriptorHash); - // //default factory - // //todo: we have a default factory, why don't we always register that instead? - // if (factory == null) - // { - // var initializer = _enginesRoot.BuildEntity(egid, - // _enginesRoot._isDeserializationOnly ? entityDescriptor.entitiesToSerialize - // : entityDescriptor.componentsToBuild, entityDescriptor.realType); - // - // DeserializeEntityComponents(serializationData, entityDescriptor, ref initializer, serializationType); - // - // return initializer; - // } - - //custom factory - return factory.BuildDeserializedEntity(egid, serializationData, entityDescriptor, serializationType, - this, this._enginesRoot.GenerateEntityFactory(), _enginesRoot._isDeserializationOnly); + return factory.BuildDeserializedEntity(egid, serializationData, entityDescriptor, serializationType + , this, this._enginesRoot.GenerateEntityFactory() + , _enginesRoot._isDeserializationOnly); } public void DeserializeEntity(ISerializationData serializationData, int serializationType) @@ -73,17 +58,16 @@ namespace Svelto.ECS DeserializeEntityInternal(serializationData, egid, serializableEntityHeader, serializationType); } - public void DeserializeEntity(EGID egid, ISerializationData serializationData, - int serializationType) + public void DeserializeEntity(EGID egid, ISerializationData serializationData, int serializationType) { var serializableEntityHeader = new SerializableEntityHeader(serializationData); DeserializeEntityInternal(serializationData, egid, serializableEntityHeader, serializationType); } - public void DeserializeEntityComponents(ISerializationData serializationData, - ISerializableEntityDescriptor entityDescriptor, - ref EntityComponentInitializer initializer, int serializationType) + public void DeserializeEntityComponents + (ISerializationData serializationData, ISerializableEntityDescriptor entityDescriptor + , ref EntityComponentInitializer initializer, int serializationType) { foreach (var serializableEntityBuilder in entityDescriptor.entitiesToSerialize) { @@ -92,18 +76,19 @@ namespace Svelto.ECS } } - public T DeserializeEntityComponent(ISerializationData serializationData, - ISerializableEntityDescriptor entityDescriptor, int serializationType) - where T : unmanaged, IEntityComponent + public T DeserializeEntityComponent + (ISerializationData serializationData, ISerializableEntityDescriptor entityDescriptor + , int serializationType) where T : unmanaged, IEntityComponent { - var readPos = serializationData.dataPos; - T entityComponent = default; + var readPos = serializationData.dataPos; + T entityComponent = default; foreach (var serializableEntityBuilder in entityDescriptor.entitiesToSerialize) { if (serializableEntityBuilder is SerializableComponentBuilder entityBuilder) { entityBuilder.Deserialize(serializationData, ref entityComponent, serializationType); } + break; } @@ -114,14 +99,16 @@ namespace Svelto.ECS public void DeserializeEntityToSwap(EGID localEgid, EGID toEgid) { EntitiesDB entitiesDb = _enginesRoot._entitiesDB; - ref var serializableEntityComponent = ref entitiesDb.QueryEntity(localEgid); + ref var serializableEntityComponent = + ref entitiesDb.QueryEntity(localEgid); SerializationDescriptorMap serializationDescriptorMap = _enginesRoot.serializationDescriptorMap; uint descriptorHash = serializableEntityComponent.descriptorHash; var entityDescriptor = serializationDescriptorMap.GetDescriptorFromHash(descriptorHash); - var entitySubmitOperation = new EntitySubmitOperation(EntitySubmitOperationType.Swap, - localEgid, toEgid, entityDescriptor.componentsToBuild); + var entitySubmitOperation = + new EntitySubmitOperation(EntitySubmitOperationType.Swap, localEgid, toEgid + , entityDescriptor.componentsToBuild); _enginesRoot.CheckRemoveEntityID(localEgid, entityDescriptor.realType); _enginesRoot.CheckAddEntityID(toEgid, entityDescriptor.realType); @@ -131,20 +118,18 @@ namespace Svelto.ECS public void DeserializeEntityToDelete(EGID egid) { - EntitiesDB entitiesDB = _enginesRoot._entitiesDB; - ref var serializableEntityComponent = ref entitiesDB.QueryEntity(egid); - uint descriptorHash = serializableEntityComponent.descriptorHash; + EntitiesDB entitiesDB = _enginesRoot._entitiesDB; + ref var serializableEntityComponent = ref entitiesDB.QueryEntity(egid); + uint descriptorHash = serializableEntityComponent.descriptorHash; SerializationDescriptorMap serializationDescriptorMap = _enginesRoot.serializationDescriptorMap; var entityDescriptor = serializationDescriptorMap.GetDescriptorFromHash(descriptorHash); _enginesRoot.CheckRemoveEntityID(egid, entityDescriptor.realType); - var entitySubmitOperation = new EntitySubmitOperation( - EntitySubmitOperationType.Remove, - egid, - egid, - entityDescriptor.componentsToBuild); + var entitySubmitOperation = + new EntitySubmitOperation(EntitySubmitOperationType.Remove, egid, egid + , entityDescriptor.componentsToBuild); _enginesRoot.QueueEntitySubmitOperation(entitySubmitOperation); } @@ -156,18 +141,15 @@ namespace Svelto.ECS serializationDescriptorMap.RegisterSerializationFactory(deserializationFactory); } - internal EntitySerialization(EnginesRoot enginesRoot) - { - _enginesRoot = enginesRoot; - } + internal EntitySerialization(EnginesRoot enginesRoot) { _enginesRoot = enginesRoot; } - void SerializeEntityComponent(EGID entityGID, ISerializableComponentBuilder componentBuilder, - ISerializationData serializationData, int serializationType) + void SerializeEntityComponent + (EGID entityGID, ISerializableComponentBuilder componentBuilder, ISerializationData serializationData + , int serializationType) { - uint groupId = entityGID.groupID; - Type entityType = componentBuilder.GetEntityComponentType(); - if (!_enginesRoot._entitiesDB.UnsafeQueryEntityDictionary(groupId, entityType, - out var safeDictionary)) + ExclusiveGroupStruct groupId = entityGID.groupID; + Type entityType = componentBuilder.GetEntityComponentType(); + if (!_enginesRoot._entitiesDB.UnsafeQueryEntityDictionary(groupId, entityType, out var safeDictionary)) { throw new Exception("Entity Serialization failed"); } @@ -175,29 +157,33 @@ namespace Svelto.ECS componentBuilder.Serialize(entityGID.entityID, safeDictionary, serializationData, serializationType); } - void DeserializeEntityInternal(ISerializationData serializationData, EGID egid, - SerializableEntityHeader serializableEntityHeader, int serializationType) + void DeserializeEntityInternal + (ISerializationData serializationData, EGID egid, SerializableEntityHeader serializableEntityHeader + , int serializationType) { SerializationDescriptorMap descriptorMap = _enginesRoot.serializationDescriptorMap; var entityDescriptor = descriptorMap.GetDescriptorFromHash(serializableEntityHeader.descriptorHash); + if (_enginesRoot._groupEntityComponentsDB.TryGetValue(egid.groupID, out var entitiesInGroupPerType) + == false) + throw new Exception("Entity Serialization failed"); + foreach (var serializableEntityBuilder in entityDescriptor.entitiesToSerialize) { - _enginesRoot._entitiesDB.UnsafeQueryEntityDictionary(egid.groupID, - serializableEntityBuilder.GetEntityComponentType(), out var safeDictionary); + entitiesInGroupPerType.TryGetValue( + new RefWrapperType(serializableEntityBuilder.GetEntityComponentType()), out var safeDictionary); serializationData.BeginNextEntityComponent(); - serializableEntityBuilder.Deserialize(egid.entityID, safeDictionary, serializationData, - serializationType); + serializableEntityBuilder.Deserialize(egid.entityID, safeDictionary, serializationData + , serializationType); } } readonly EnginesRoot _enginesRoot; } - public IEntitySerialization GenerateEntitySerializer() - { - return new EntitySerialization(this); - } + public IEntitySerialization GenerateEntitySerializer() { return new EntitySerialization(this); } + + readonly bool _isDeserializationOnly; } } \ No newline at end of file diff --git a/Svelto.ECS/Serialization/EnginesRoot.SerializableEntityHeader.cs b/Svelto.ECS/Serialization/EnginesRoot.SerializableEntityHeader.cs index c5f5232..92cf959 100644 --- a/Svelto.ECS/Serialization/EnginesRoot.SerializableEntityHeader.cs +++ b/Svelto.ECS/Serialization/EnginesRoot.SerializableEntityHeader.cs @@ -2,7 +2,7 @@ namespace Svelto.ECS { public partial class EnginesRoot { - struct SerializableEntityHeader + readonly struct SerializableEntityHeader { public readonly uint descriptorHash; public readonly byte entityComponentsCount; diff --git a/Svelto.ECS/Serialization/EntitiesDB.DescriptorMap.cs b/Svelto.ECS/Serialization/EntitiesDB.SerializationDescriptorMap.cs similarity index 73% rename from Svelto.ECS/Serialization/EntitiesDB.DescriptorMap.cs rename to Svelto.ECS/Serialization/EntitiesDB.SerializationDescriptorMap.cs index 0cb062b..e301625 100644 --- a/Svelto.ECS/Serialization/EntitiesDB.DescriptorMap.cs +++ b/Svelto.ECS/Serialization/EntitiesDB.SerializationDescriptorMap.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Reflection; using Svelto.Common; using Svelto.ECS.Serialization; @@ -24,23 +25,19 @@ namespace Svelto.ECS using (new StandardProfiler("Assemblies Scan")) { Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); - // Assembly executingAssembly = Assembly.GetExecutingAssembly(); Type d1 = typeof(DefaultVersioningFactory<>); foreach (Assembly assembly in assemblies) { - // if (assembly.GetReferencedAssemblies().Contains(executingAssembly.GetName())) + foreach (Type type in GetTypesSafe(assembly)) { - foreach (Type type in GetTypesSafe(assembly)) + if (type != null && type.IsClass && type.IsAbstract == false && type.BaseType != null + && type.BaseType.IsGenericType && type.BaseType.GetGenericTypeDefinition() + == typeof(SerializableEntityDescriptor<>)) { - if (type != null && type.IsClass && type.IsAbstract == false && type.BaseType != null - && type.BaseType.IsGenericType && type.BaseType.GetGenericTypeDefinition() - == typeof(SerializableEntityDescriptor<>)) - { - var descriptor = Activator.CreateInstance(type) as ISerializableEntityDescriptor; + var descriptor = Activator.CreateInstance(type) as ISerializableEntityDescriptor; - RegisterEntityDescriptor(descriptor, type, d1); - } + RegisterEntityDescriptor(descriptor, type, d1); } } } @@ -64,9 +61,7 @@ namespace Svelto.ECS void RegisterEntityDescriptor(ISerializableEntityDescriptor descriptor, Type type, Type d1) { if (descriptor == null) - { return; - } uint descriptorHash = descriptor.hash; @@ -77,7 +72,6 @@ namespace Svelto.ECS $"'{_descriptors[descriptorHash]} ::: {descriptorHash}'"); } #endif - _descriptors[descriptorHash] = descriptor; Type[] typeArgs = {type}; var makeGenericType = d1.MakeGenericType(typeArgs); @@ -85,19 +79,25 @@ namespace Svelto.ECS _factories.Add(descriptorHash, instance as IDeserializationFactory); } - public ISerializableEntityDescriptor GetDescriptorFromHash(uint descriptorID) + public ISerializableEntityDescriptor GetDescriptorFromHash(uint descriptorHash) { #if DEBUG && !PROFILE_SVELTO - DBC.ECS.Check.Require(_descriptors.ContainsKey(descriptorID), - $"Could not find descriptor with ID '{descriptorID}'!"); + DBC.ECS.Check.Require(_descriptors.ContainsKey(descriptorHash), + $"Could not find descriptor linked to hash, wrong deserialization size? '{ descriptorHash}'!"); #endif - return _descriptors[descriptorID]; + return _descriptors[descriptorHash]; } - public IDeserializationFactory GetSerializationFactory(uint descriptorID) + public IDeserializationFactory GetSerializationFactory(uint descriptorHash) { - return _factories[descriptorID]; +#if DEBUG && !PROFILE_SVELTO + DBC.ECS.Check.Require(_descriptors.ContainsKey(descriptorHash), + $"Could not find descriptor linked to descriptor hash, wrong deserialization size? '{ descriptorHash}'!"); + DBC.ECS.Check.Require(_factories.ContainsKey(descriptorHash), + $"Could not find factory linked to hash '{ _descriptors[descriptorHash]}'!"); +#endif + return _factories[descriptorHash]; } public void RegisterSerializationFactory(IDeserializationFactory deserializationFactory) @@ -106,7 +106,6 @@ namespace Svelto.ECS _factories[SerializationEntityDescriptorTemplate.hash] = deserializationFactory; } - readonly Dictionary _descriptors; readonly Dictionary _factories; } diff --git a/Svelto.ECS/Serialization/ISerializableEntityDescriptor.cs b/Svelto.ECS/Serialization/ISerializableEntityDescriptor.cs index 67726cd..d755ab7 100644 --- a/Svelto.ECS/Serialization/ISerializableEntityDescriptor.cs +++ b/Svelto.ECS/Serialization/ISerializableEntityDescriptor.cs @@ -2,10 +2,10 @@ using System; namespace Svelto.ECS.Serialization { - public interface ISerializableEntityDescriptor : IEntityDescriptor + public interface ISerializableEntityDescriptor : IDynamicEntityDescriptor { - uint hash { get; } + uint hash { get; } ISerializableComponentBuilder[] entitiesToSerialize { get; } - Type realType { get; } + Type realType { get; } } } \ No newline at end of file diff --git a/Svelto.ECS/Serialization/SerializableEntityDescriptor.cs b/Svelto.ECS/Serialization/SerializableEntityDescriptor.cs index d59d571..dd676dd 100644 --- a/Svelto.ECS/Serialization/SerializableEntityDescriptor.cs +++ b/Svelto.ECS/Serialization/SerializableEntityDescriptor.cs @@ -42,7 +42,7 @@ namespace Svelto.ECS.Serialization // If the current serializable is an ExtendibleDescriptor, I have to update it. if (dynamicIndex != -1) { - ComponentsToBuild[dynamicIndex] = new ComponentBuilder(new EntityInfoViewComponent + ComponentsToBuild[dynamicIndex] = new ComponentBuilder(new EntityInfoComponent { componentsToBuild = ComponentsToBuild }); @@ -50,13 +50,13 @@ namespace Svelto.ECS.Serialization ///// var entitiesToSerialize = new FasterList(); - EntityComponentsToSerializeMap = new FasterDictionary, ISerializableComponentBuilder>(); + EntityComponentsToSerializeMap = new FasterDictionary(); foreach (IComponentBuilder e in defaultEntities) { if (e is ISerializableComponentBuilder serializableEntityBuilder) { var entityType = serializableEntityBuilder.GetEntityComponentType(); - EntityComponentsToSerializeMap[new RefWrapper(entityType)] = serializableEntityBuilder; + EntityComponentsToSerializeMap[new RefWrapperType(entityType)] = serializableEntityBuilder; entitiesToSerialize.Add(serializableEntityBuilder); } } @@ -81,7 +81,7 @@ namespace Svelto.ECS.Serialization --newLenght; } - if (defaultEntities[i].GetEntityComponentType() == ComponentBuilderUtilities.ENTITY_STRUCT_INFO_VIEW) + if (defaultEntities[i].GetEntityComponentType() == ComponentBuilderUtilities.ENTITY_INFO_COMPONENT) { indexDynamic = i; } @@ -96,11 +96,11 @@ namespace Svelto.ECS.Serialization public IComponentBuilder[] componentsToBuild => ComponentsToBuild; public uint hash => Hash; - public Type realType => Type; + public Type realType => Type; public ISerializableComponentBuilder[] entitiesToSerialize => EntitiesToSerialize; static readonly IComponentBuilder[] ComponentsToBuild; - static readonly FasterDictionary, ISerializableComponentBuilder> EntityComponentsToSerializeMap; + static readonly FasterDictionary EntityComponentsToSerializeMap; static readonly ISerializableComponentBuilder[] EntitiesToSerialize; static readonly uint Hash; diff --git a/Svelto.ECS/Serialization/SerializerExt.cs b/Svelto.ECS/Serialization/SerializerExt.cs index 45f705c..c7d455d 100644 --- a/Svelto.ECS/Serialization/SerializerExt.cs +++ b/Svelto.ECS/Serialization/SerializerExt.cs @@ -1,4 +1,5 @@ -namespace Svelto.ECS.Serialization { +namespace Svelto.ECS.Serialization +{ public static class SerializerExt { public static bool SerializeSafe diff --git a/Svelto.ECS/SimpleEntitiesSubmissionScheduler.cs b/Svelto.ECS/SimpleEntitiesSubmissionScheduler.cs index 9d543c1..512a49f 100644 --- a/Svelto.ECS/SimpleEntitiesSubmissionScheduler.cs +++ b/Svelto.ECS/SimpleEntitiesSubmissionScheduler.cs @@ -1,24 +1,29 @@ using Svelto.ECS.Schedulers; -namespace Svelto.ECS +namespace Svelto.ECS.Schedulers { //This scheduler shouldn't be used in production and it's meant to be used for Unit Tests only - public class SimpleEntitiesSubmissionScheduler : IEntitiesSubmissionScheduler + public sealed class SimpleEntitiesSubmissionScheduler : ISimpleEntitiesSubmissionScheduler { - public void SubmitEntities() + public override void SubmitEntities() { if (paused == false) _onTick.Invoke(); } - - EnginesRoot.EntitiesSubmitter IEntitiesSubmissionScheduler.onTick + + protected internal override EnginesRoot.EntitiesSubmitter onTick { - set => _onTick = value; + set + { + DBC.ECS.Check.Require(_onTick.IsUnused , "a scheduler can be exclusively used by one enginesRoot only"); + + _onTick = value; + } } - - public bool paused { get; set; } - public void Dispose() { } + public override bool paused { get; set; } + + public override void Dispose() { } EnginesRoot.EntitiesSubmitter _onTick; } diff --git a/Svelto.ECS/Extensions/Svelto/SortedEnginesGroup.cs b/Svelto.ECS/SortedEnginesGroup.cs similarity index 87% rename from Svelto.ECS/Extensions/Svelto/SortedEnginesGroup.cs rename to Svelto.ECS/SortedEnginesGroup.cs index 8e6cc35..dc4d7ec 100644 --- a/Svelto.ECS/Extensions/Svelto/SortedEnginesGroup.cs +++ b/Svelto.ECS/SortedEnginesGroup.cs @@ -1,7 +1,7 @@ using Svelto.DataStructures; using Svelto.Common; -namespace Svelto.ECS.Extensions +namespace Svelto.ECS { public interface IStepEngine : IEngine { @@ -10,9 +10,6 @@ namespace Svelto.ECS.Extensions string name { get; } } - public interface IGroupEngine : IStepEngine - { } - public interface IStepEngine : IEngine { void Step(ref T _param); @@ -20,16 +17,16 @@ namespace Svelto.ECS.Extensions string name { get; } } + public interface IStepGroupEngine : IStepEngine + { + } + public interface IStepGroupEngine : IStepEngine { } - /// - /// Note sorted jobs run in serial - /// - /// - /// - public abstract class SortedEnginesGroup : IGroupEngine - where SequenceOrder : struct, ISequenceOrder where Interface : class, IStepEngine + + public abstract class SortedEnginesGroup : IStepGroupEngine + where SequenceOrder : struct, ISequenceOrder where Interface : IStepEngine { protected SortedEnginesGroup(FasterList engines) { @@ -57,7 +54,7 @@ namespace Svelto.ECS.Extensions } public abstract class SortedEnginesGroup: IStepGroupEngine - where SequenceOrder : struct, ISequenceOrder where Interface : class, IStepEngine + where SequenceOrder : struct, ISequenceOrder where Interface : IStepEngine { protected SortedEnginesGroup(FasterList engines) { diff --git a/Svelto.ECS/Streams/Consumer.cs b/Svelto.ECS/Streams/Consumer.cs new file mode 100644 index 0000000..ffdc88b --- /dev/null +++ b/Svelto.ECS/Streams/Consumer.cs @@ -0,0 +1,91 @@ +using System; +using Svelto.Common; +using Svelto.DataStructures; + +namespace Svelto.ECS +{ + public struct Consumer : IDisposable where T : unmanaged, IEntityComponent + { + internal Consumer(string name, uint capacity) : this() + { + unsafe + { +#if DEBUG && !PROFILE_SVELTO + _name = name; +#endif + _ringBuffer = new RingBuffer>((int) capacity, +#if DEBUG && !PROFILE_SVELTO + _name +#else + string.Empty +#endif + ); + mustBeDisposed = MemoryUtilities.Alloc(sizeof(bool), Allocator.Persistent); + *(bool*) mustBeDisposed = false; + } + } + + internal Consumer(ExclusiveGroupStruct group, string name, uint capacity) : this(name, capacity) + { + this.group = group; + hasGroup = true; + } + + internal void Enqueue(in T entity, in EGID egid) + { + _ringBuffer.Enqueue((entity, egid)); + } + + public bool TryDequeue(out T entity) + { + var tryDequeue = _ringBuffer.TryDequeue(out var values); + + entity = values.Item1; + + return tryDequeue; + } + + public bool TryDequeue(out T entity, out EGID id) + { + var tryDequeue = _ringBuffer.TryDequeue(out var values); + + entity = values.Item1; + id = values.Item2; + + return tryDequeue; + } + + public void Flush() + { + _ringBuffer.Reset(); + } + + public void Dispose() + { + unsafe + { + *(bool*) mustBeDisposed = true; + } + } + + public uint Count() + { + return (uint) _ringBuffer.Count; + } + + public void Free() + { + MemoryUtilities.Free(mustBeDisposed, Allocator.Persistent); + } + + readonly RingBuffer> _ringBuffer; + + internal readonly ExclusiveGroupStruct group; + internal readonly bool hasGroup; + internal IntPtr mustBeDisposed; + +#if DEBUG && !PROFILE_SVELTO + readonly string _name; +#endif + } +} \ No newline at end of file diff --git a/Svelto.ECS/Streams/EnginesRoot.Streams.cs b/Svelto.ECS/Streams/EnginesRoot.Streams.cs new file mode 100644 index 0000000..3c2e29c --- /dev/null +++ b/Svelto.ECS/Streams/EnginesRoot.Streams.cs @@ -0,0 +1,18 @@ +namespace Svelto.ECS +{ + public partial class EnginesRoot + { + internal Consumer GenerateConsumer(string name, uint capacity) where T : unmanaged, IEntityComponent + { + return _entityStreams.GenerateConsumer(name, capacity); + } + + internal Consumer GenerateConsumer(ExclusiveGroupStruct group, string name, uint capacity) + where T : unmanaged, IEntityComponent + { + return _entityStreams.GenerateConsumer(@group, name, capacity); + } + + internal readonly EntitiesStreams _entityStreams; + } +} \ No newline at end of file diff --git a/Svelto.ECS/Streams/EntitiesDB.Streams.cs b/Svelto.ECS/Streams/EntitiesDB.Streams.cs new file mode 100644 index 0000000..6b2f4c1 --- /dev/null +++ b/Svelto.ECS/Streams/EntitiesDB.Streams.cs @@ -0,0 +1,20 @@ +using System; +using System.Runtime.CompilerServices; + +namespace Svelto.ECS +{ + public partial class EntitiesDB + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void PublishEntityChange(EGID egid) where T : unmanaged, IEntityComponent + { + _entityStream.PublishEntity(ref this.QueryEntity(egid), egid); + } +#if later + public ThreadSafeNativeEntityStream GenerateThreadSafePublisher() where T: unmanaged, IEntityComponent + { + return _entityStream.GenerateThreadSafePublisher(this); + } +#endif + } +} \ No newline at end of file diff --git a/Svelto.ECS/Streams/EntitiesStreams.cs b/Svelto.ECS/Streams/EntitiesStreams.cs new file mode 100644 index 0000000..6d12992 --- /dev/null +++ b/Svelto.ECS/Streams/EntitiesStreams.cs @@ -0,0 +1,72 @@ +using System; +using Svelto.DataStructures; + +namespace Svelto.ECS +{ + /// + /// I eventually realised that, with the ECS design, no form of communication other than polling entity components can + /// exist. + /// Using groups, you can have always an optimal set of entity components to poll. However EntityStreams + /// can be useful if: + /// - you need to react on seldom entity changes, usually due to user events + /// - you want engines to be able to track entity changes + /// - you want a thread-safe way to read entity states, which includes all the state changes and not the last + /// one only + /// - you want to communicate between EnginesRoots + /// + internal struct EntitiesStreams : IDisposable + { + internal Consumer GenerateConsumer(string name, uint capacity) + where T : unmanaged, IEntityComponent + { + if (_streams.ContainsKey(TypeRefWrapper.wrapper) == false) + _streams[TypeRefWrapper.wrapper] = new EntityStream(); + + return (_streams[TypeRefWrapper.wrapper] as EntityStream).GenerateConsumer(name, capacity); + } + + public Consumer GenerateConsumer(ExclusiveGroupStruct group, string name, uint capacity) + where T : unmanaged, IEntityComponent + { + if (_streams.ContainsKey(TypeRefWrapper.wrapper) == false) + _streams[TypeRefWrapper.wrapper] = new EntityStream(); + + var typeSafeStream = (EntityStream) _streams[TypeRefWrapper.wrapper]; + return typeSafeStream.GenerateConsumer(group, name, capacity); + } +#if later + public ThreadSafeNativeEntityStream GenerateThreadSafePublisher(EntitiesDB entitiesDB) where T: unmanaged, IEntityComponent + { + var threadSafeNativeEntityStream = new ThreadSafeNativeEntityStream(entitiesDB); + + _streams[TypeRefWrapper.wrapper] = threadSafeNativeEntityStream; + + return threadSafeNativeEntityStream; + } +#endif + + internal void PublishEntity(ref T entity, EGID egid) where T : unmanaged, IEntityComponent + { + if (_streams.TryGetValue(TypeRefWrapper.wrapper, out var typeSafeStream)) + (typeSafeStream as EntityStream).PublishEntity(ref entity, egid); + else + Console.LogDebug("No Consumers are waiting for this entity to change ", typeof(T)); + } + + public void Dispose() + { + foreach (var stream in _streams) + stream.Value.Dispose(); + } + + public static EntitiesStreams Create() + { + var stream = new EntitiesStreams(); + stream._streams = ManagedSveltoDictionary.Create(); + + return stream; + } + + ManagedSveltoDictionary _streams; + } +} \ No newline at end of file diff --git a/Svelto.ECS/Streams/EntityStream.cs b/Svelto.ECS/Streams/EntityStream.cs new file mode 100644 index 0000000..1825013 --- /dev/null +++ b/Svelto.ECS/Streams/EntityStream.cs @@ -0,0 +1,71 @@ +using Svelto.DataStructures; + +namespace Svelto.ECS +{ + internal interface ITypeSafeStream + { + void Dispose(); + } + + public class EntityStream : ITypeSafeStream where T : unmanaged, IEntityComponent + { + readonly ThreadSafeFasterList> _consumers; + + internal EntityStream() + { + _consumers = new ThreadSafeFasterList>(); + } + + ~EntityStream() + { + for (var i = 0; i < _consumers.count; i++) + _consumers[i].Free(); + } + + public void Dispose() + { } + + internal void PublishEntity(ref T entity, EGID egid) + { + for (var i = 0; i < _consumers.count; i++) + unsafe + { + if (*(bool*) _consumers[i].mustBeDisposed) + { + _consumers[i].Free(); + _consumers.UnorderedRemoveAt(i); + --i; + continue; + } + + if (_consumers[i].hasGroup) + { + if (egid.groupID == _consumers[i].@group) + _consumers[i].Enqueue(entity, egid); + } + else + { + _consumers[i].Enqueue(entity, egid); + } + } + } + + internal Consumer GenerateConsumer(string name, uint capacity) + { + var consumer = new Consumer(name, capacity); + + _consumers.Add(consumer); + + return consumer; + } + + internal Consumer GenerateConsumer(ExclusiveGroupStruct group, string name, uint capacity) + { + var consumer = new Consumer(group, name, capacity); + + _consumers.Add(consumer); + + return consumer; + } + } +} \ No newline at end of file diff --git a/Svelto.ECS/GenericentityStreamConsumerFactory.cs b/Svelto.ECS/Streams/GenericentityStreamConsumerFactory.cs similarity index 100% rename from Svelto.ECS/GenericentityStreamConsumerFactory.cs rename to Svelto.ECS/Streams/GenericentityStreamConsumerFactory.cs diff --git a/Svelto.ECS/Streams/ThreadSafeNativeEntityStream.cs b/Svelto.ECS/Streams/ThreadSafeNativeEntityStream.cs new file mode 100644 index 0000000..a648384 --- /dev/null +++ b/Svelto.ECS/Streams/ThreadSafeNativeEntityStream.cs @@ -0,0 +1,31 @@ +namespace Svelto.ECS +{ + /// + /// This EntityStream can be used in parallel jobs, but does NOT guarantee order. + /// + /// + public struct ThreadSafeNativeEntityStream : ITypeSafeStream + { + public ThreadSafeNativeEntityStream(EntitiesDB entitiesDB) + { + } + + public void Dispose() + { + + } + + /// + /// I am thinking to pass the component and do the queryEntity only as a validation + /// + /// + /// + public void PublishEntityChange(in T entityComponent, EGID id) + { +#if DEBUG && !PROFILE_SVELTO + +#endif + + } + } +} \ No newline at end of file diff --git a/Svelto.ECS/Svelto.ECS.asmdef b/Svelto.ECS/Svelto.ECS.asmdef index 591e514..6ccb718 100644 --- a/Svelto.ECS/Svelto.ECS.asmdef +++ b/Svelto.ECS/Svelto.ECS.asmdef @@ -5,7 +5,7 @@ "Unity.Collections", "Unity.Burst", "Unity.Jobs", - "Svelto.Common_3" + "Svelto.Common" ], "includePlatforms": [], "excludePlatforms": [], @@ -32,6 +32,21 @@ "expression": "", "define": "UNITY_COLLECTIONS" }, + { + "name": "com.unity.entities", + "expression": "", + "define": "UNITY_NATIVE" + }, + { + "name": "com.unity.burst", + "expression": "", + "define": "UNITY_NATIVE" + }, + { + "name": "com.unity.collections", + "expression": "", + "define": "UNITY_NATIVE" + }, { "name": "com.unity.jobs", "expression": "", diff --git a/Svelto.ECS/Svelto.ECS.csproj b/Svelto.ECS/Svelto.ECS.csproj index da96d32..1e77aca 100644 --- a/Svelto.ECS/Svelto.ECS.csproj +++ b/Svelto.ECS/Svelto.ECS.csproj @@ -15,7 +15,8 @@ true - + + diff --git a/Svelto.ECS/TupleRef.cs b/Svelto.ECS/TupleRef.cs deleted file mode 100644 index 80ea9ee..0000000 --- a/Svelto.ECS/TupleRef.cs +++ /dev/null @@ -1,58 +0,0 @@ -namespace Svelto.ECS -{ - public readonly ref struct TupleRef where T1 : struct, IEntityComponent - { - public readonly EntityCollections entities; - public readonly GroupsEnumerable groups; - - public TupleRef(in EntityCollections entityCollections, in GroupsEnumerable groupsEnumerable) - { - this.entities = entityCollections; - groups = groupsEnumerable; - } - } - - public readonly ref struct TupleRef where T1 : struct, IEntityComponent where T2 : struct, IEntityComponent - { - public readonly EntityCollections entities; - public readonly GroupsEnumerable groups; - - public TupleRef(in EntityCollections entityCollections, in GroupsEnumerable groupsEnumerable) - { - this.entities = entityCollections; - groups = groupsEnumerable; - } - } - - public readonly ref struct TupleRef where T1 : struct, IEntityComponent - where T2 : struct, IEntityComponent - where T3 : struct, IEntityComponent - { - public readonly EntityCollections entities; - public readonly GroupsEnumerable groups; - - public TupleRef - (in EntityCollections entityCollections, in GroupsEnumerable groupsEnumerable) - { - this.entities = entityCollections; - groups = groupsEnumerable; - } - } - - public readonly ref struct TupleRef where T1 : struct, IEntityComponent - where T2 : struct, IEntityComponent - where T3 : struct, IEntityComponent - where T4 : struct, IEntityComponent - { - public readonly EntityCollections entities; - public readonly GroupsEnumerable groups; - - public TupleRef - (in EntityCollections entityCollections - , in GroupsEnumerable groupsEnumerable) - { - this.entities = entityCollections; - groups = groupsEnumerable; - } - } -} \ No newline at end of file diff --git a/Svelto.ECS/UnsortedEnginesGroup.cs b/Svelto.ECS/UnsortedEnginesGroup.cs new file mode 100644 index 0000000..2362f80 --- /dev/null +++ b/Svelto.ECS/UnsortedEnginesGroup.cs @@ -0,0 +1,33 @@ +using Svelto.Common; +using Svelto.DataStructures; + +namespace Svelto.ECS +{ + public abstract class UnsortedEnginesGroup : IStepGroupEngine + where Interface : IStepEngine + { + protected UnsortedEnginesGroup(FasterList engines) + { + _name = "UnsortedEnginesGroup - "+this.GetType().Name; + _instancedSequence = engines; + } + + public void Step() + { + var sequenceItems = _instancedSequence; + using (var profiler = new PlatformProfiler(_name)) + { + for (var index = 0; index < sequenceItems.count; index++) + { + var engine = sequenceItems[index]; + using (profiler.Sample(engine.name)) engine.Step(); + } + } + } + + public string name => _name; + + readonly string _name; + readonly FasterList _instancedSequence; + } +} \ No newline at end of file diff --git a/Svelto.ECS/package.json b/Svelto.ECS/package.json index 120416d..7a7be4c 100644 --- a/Svelto.ECS/package.json +++ b/Svelto.ECS/package.json @@ -11,5 +11,5 @@ "name": "com.sebaslab.svelto.ecs", "unity": "2019.2", "version": "3.0.0", - "type": "library" + "type": "framework" }