From 2179bb1ae81ac77aeffcbf96f2a4e559fa1cb612 Mon Sep 17 00:00:00 2001 From: Sebastiano Mandala Date: Sun, 17 Sep 2023 21:35:21 +0100 Subject: [PATCH] update svelto to 3.5.0 --- com.sebaslab.svelto.ecs/CHANGELOG.md | 36 ++- .../Core/CheckEntityUtilities.cs | 211 ++++++++++--- .../Core/ComponentBuilder.CheckFields.cs | 7 +- .../Core/ComponentBuilder.cs | 2 +- com.sebaslab.svelto.ecs/Core/ComponentID.cs | 7 +- .../Core/ComponentTypeID.cs | 6 +- com.sebaslab.svelto.ecs/Core/EGID.cs | 4 +- .../Core/EnginesGroup/IStepEngine.cs | 14 +- .../Core/EnginesGroup/SortedEnginesGroup.cs | 52 +++- .../Core/EnginesGroup/UnsortedEnginesGroup.cs | 48 ++- .../Core/EnginesRoot.Engines.cs | 24 +- .../Core/EnginesRoot.Entities.cs | 57 +++- .../Core/EnginesRoot.GenericEntityFactory.cs | 2 +- .../EnginesRoot.GenericEntityFunctions.cs | 9 +- .../Core/EnginesRoot.Submission.cs | 194 +++++------- com.sebaslab.svelto.ecs/Core/EntitiesDB.cs | 193 +++++++----- .../Core/EntitiesOperations.cs | 187 ++++++++---- .../Core/EntityCollection.cs | 8 +- .../Core/EntityInitializer.cs | 19 +- .../EntityReference/EnginesRoot.LocatorMap.cs | 16 +- .../EntityReference/EntitiesDB.References.cs | 1 + .../Core/EntityReference/EntityReference.cs | 9 +- .../Core/Filters/CombinedFilterID.cs | 5 + .../Core/Filters/EnginesRoot.Filters.cs | 85 ++---- .../Core/Filters/EntitiesDB.Filters.cs | 2 +- .../Core/Filters/EntityFilterCollection.cs | 84 +++++- .../Core/Filters/EntityFilterIndices.cs | 8 +- .../Core/Filters/EntityFilterIterator.cs | 23 +- .../Core/Groups/EntityDescriptorsWarmup.cs | 2 +- .../Core/Groups/ExclusiveBuildGroup.cs | 27 +- .../Core/Groups/ExclusiveGroup.cs | 12 +- .../Core/Groups/ExclusiveGroupStruct.cs | 13 +- .../Core/Groups/GroupCompound.cs | 285 ++++++++++++------ .../Core/Groups/GroupHashMap.cs | 121 ++++---- .../Core/Groups/GroupNamesMap.cs | 3 +- .../Core/Groups/QueryGroups.cs | 1 - .../Core/IDisposingEngine.cs | 2 +- com.sebaslab.svelto.ecs/Core/IEngine.cs | 21 +- .../Core/IEntityFunctions.cs | 37 ++- .../EntityIDs/ManagedEntityIDs.cs | 20 +- .../EntityIDs/NativeEntityIDs.cs | 20 +- .../DataStructures/ITypeSafeDictionary.cs | 13 +- .../ManagedTypeSafeDictionary.cs | 34 +-- .../TypeSafeDictionaryException.cs | 5 + .../TypeSafeDictionaryMethods.cs | 164 +++++----- .../UnmanagedTypeSafeDictionary.cs | 31 +- .../Native/EnginesRoot.NativeOperation.cs | 4 +- .../Native/EntityNativeDBExtensions.cs | 6 +- .../Native/NativeEGIDMultiMapper.cs | 11 +- .../Extensions/Svelto/EGIDMultiMapper.cs | 168 ++++------- .../Svelto/EntityCollectionExtension.cs | 156 +++++----- .../Svelto/EntityManagedDBExtensions.cs | 6 +- .../Extensions/Svelto/GroupsEnumerable.cs | 8 +- .../Extensions/Svelto/IEGIDMultiMapper.cs | 9 + .../Svelto/Legacy/FilterGroupExtensions.cs | 2 +- .../DOTS/UECS/SveltoOnDOTSEnginesGroup.cs | 16 + .../EnginesRoot.GenericEntitySerialization.cs | 3 +- .../EnginesRoot.SerializableEntityHeader.cs | 2 + .../Serialization/ISerializationData.cs | 2 +- .../SerializableEntityComponent.cs | 2 +- .../Serialization/SerializingEnginesRoot.cs | 2 +- .../Serialization/SimpleSerializationData.cs | 2 +- com.sebaslab.svelto.ecs/Svelto.ECS.csproj | 4 +- com.sebaslab.svelto.ecs/package.json | 4 +- com.sebaslab.svelto.ecs/version.json | 2 +- 65 files changed, 1538 insertions(+), 995 deletions(-) create mode 100644 com.sebaslab.svelto.ecs/Extensions/Svelto/IEGIDMultiMapper.cs diff --git a/com.sebaslab.svelto.ecs/CHANGELOG.md b/com.sebaslab.svelto.ecs/CHANGELOG.md index 2500424..8bc51d5 100644 --- a/com.sebaslab.svelto.ecs/CHANGELOG.md +++ b/com.sebaslab.svelto.ecs/CHANGELOG.md @@ -1,11 +1,29 @@ # Svelto.ECS Changelog All notable changes to this project will be documented in this file. Changes are listed in random order of importance. +## [3.5.0] - 09-2023 + +* Introduced Serialization namespace for the serialization code +* Unity: dropped 2020 support, minimum requirement is no 2021.3 +* Unity DOTS: added CreateDOTSToSveltoSyncEngine method in SveltoOnDOTSEnginesGroup +* Refactor: split NB/MB struct from their internal logic that must be used only by the framework. Eventually NB and MB structs must be ref, as they are not supposed to be held (they may become invalid over the time). However due to the current DOTS patterns this is not possible. In future a sentinel pattern will allow to lease these buffers with the assumption that they can't be modified while held (and if a modification happens an exception will throw) + * Improved managed EGIDMultiMapper. A MultiMapper can improve components fetching performance + * Renamed IDisposingEngine interface to IDisposableEngine + * added EntityReference Exists method to validate it against a given entity database + * BUG FIXED: IReactOnDisposeEx callbacks were not correctly called + * BUG FIXED: fixed serious bug that would pass wrong entities indices to the moveTO callback under specific conditions + * Added Group Range functionality to GroupCompound + * Added Offset to GroupCompound to know the index of a given group compared to the starting group ID of the compound range (check MiniExample 9, Groupsonly for example) + * range and bitmask can be now set only in GroupTag and be inherited by GroupCompounds. GroupCompound bitmasks will be the OR of the group tags bitmasks, while the range will be the larger of the group tags ranges. + * entity filters enumerator do not iterate anymore filters belonging to disabled groups + * remove operations can now be executed in the same frame of a swap. a Remove will always supersed as Swap operation + * engines added to a GreoupEngine are automatically added to the enginesgroup + + ## [3.4.6] - 05-2023 * SveltoOnDOTS bug fixes/improvements * Comments and code cleanup -* update Svelto.Common to 3.4.3 ## [3.4.4] - 04-2023 @@ -17,7 +35,21 @@ All notable changes to this project will be documented in this file. Changes are ## [3.4.2] - 03-2023 -* update Svelto.Common to 3.4.0 +* removed static caches used in performance critical paths as they were causing unexpected performance issues (the fetching of static data is slower than i imagined) +* add Native prefix in front of the native memory utilities method names +* largely improved the console logger system +* minor improvements to the platform profiler structs +* improvements to the ThreadSafeObjectPool class (some refactoring too) +* added several datastructures previously belonging to Svelto.ECS +* all the FastClear methods are gone. The standard clear method now is aware of the type used and will clear it in the fastest way possible +* MemClear is added in case memory needs to be cleared explicitly +* added new SveltoStream, Unmanaged and Managed stream classes, their use case will be documented one day +* renamed the Svelto.Common.DataStructures namespace to Svelto.DataStructures +* added FixedTypedArray* methods. Fixed size arrays embedded in structs are now possible +* FasterList extension to convert to Span and ByteSpan +* Fix reported bugs +* Minor Svelto Dictionary improvements +* Added ValueContainer, a simple int, Tvalue dictionary based on sparse set. It has very specific use cases at the moment. Mainly to be used for the new ECS OOP Abstraction resoruce manager * Added IReactOnSubmissionStarted interface ### SveltoOnDOTS changes diff --git a/com.sebaslab.svelto.ecs/Core/CheckEntityUtilities.cs b/com.sebaslab.svelto.ecs/Core/CheckEntityUtilities.cs index f32abf4..b4431e8 100644 --- a/com.sebaslab.svelto.ecs/Core/CheckEntityUtilities.cs +++ b/com.sebaslab.svelto.ecs/Core/CheckEntityUtilities.cs @@ -1,77 +1,135 @@ -#if !DEBUG || PROFILE_SVELTO +//#define VERBOSE +#if !DEBUG || PROFILE_SVELTO #define DONT_USE using System.Diagnostics; #endif using System; using System.Collections.Generic; +using Svelto.DataStructures; namespace Svelto.ECS { /// - /// Note: this check doesn't catch the case when an add and remove is done on the same entity before the nextI am + /// Note: this check doesn't catch the case when an add and remove is done on the same entity before the next /// submission. Two operations on the same entity are not allowed between submissions. /// public partial class EnginesRoot { -#if DONT_USE + enum OperationType + { + Add, + Remove, + SwapFrom, + SwapTo + } + +#if DONT_USE [Conditional("MEANINGLESS")] #endif - void CheckRemoveEntityID(EGID egid, Type entityDescriptorType, string caller) + void InitDebugChecks() + { + _multipleOperationOnSameEGIDChecker = new FasterDictionary(); + _idChecker = new FasterDictionary>(); + } + +#if DONT_USE + [Conditional("MEANINGLESS")] +#endif + void CheckSwapEntityID(EGID fromEgid, EGID toEgid, Type entityDescriptorType, string caller) { - if (_multipleOperationOnSameEGIDChecker.ContainsKey(egid) == true) + if (_multipleOperationOnSameEGIDChecker.TryGetValue(fromEgid, out var fromOperationType) == true) + { + var operationName = OperationName(fromOperationType); throw new ECSException( - "Executing multiple structural changes (remove) 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(" previous operation was: ") - .FastConcat(_multipleOperationOnSameEGIDChecker[egid] == 1 ? "add" : "remove")); - - if (_idChecker.TryGetValue(egid.groupID, out var hash)) + "Executing multiple structural changes (swapFrom) on the same entity is not supported " + .FastConcat(" caller: ", caller, " ").FastConcat(fromEgid.entityID).FastConcat(" groupid: ") + .FastConcat(fromEgid.groupID.ToName()).FastConcat(" type: ") + .FastConcat(entityDescriptorType != null ? entityDescriptorType.Name : "not available") + .FastConcat(" previous operation was: ") + .FastConcat(operationName)); + } + + if (_multipleOperationOnSameEGIDChecker.TryGetValue(toEgid, out var toOperationType) == true) { - if (hash.Contains(egid.entityID) == false) - throw new ECSException("Trying to remove an Entity not present in the database " - .FastConcat(" caller: ", caller, " entityID ").FastConcat(egid.entityID).FastConcat(" groupid: ") - .FastConcat(egid.groupID.ToName()).FastConcat(" type: ") - .FastConcat(entityDescriptorType != null ? entityDescriptorType.Name : "not available")); + var operationName = OperationName(toOperationType); + throw new ECSException( + "Executing multiple structural changes (swapTo) on the same entity is not supported " + .FastConcat(" caller: ", caller, " ").FastConcat(toEgid.entityID).FastConcat(" groupid: ") + .FastConcat(toEgid.groupID.ToName()).FastConcat(" type: ") + .FastConcat(entityDescriptorType != null ? entityDescriptorType.Name : "not available") + .FastConcat(" previous operation was: ") + .FastConcat(operationName)); } - else + + HashRemove(fromEgid, entityDescriptorType, false, caller); + HashAdd(toEgid, entityDescriptorType, caller); + + _multipleOperationOnSameEGIDChecker.Add(fromEgid, OperationType.SwapFrom); + _multipleOperationOnSameEGIDChecker.Add(toEgid, OperationType.SwapTo); + } + +#if DONT_USE + [Conditional("MEANINGLESS")] +#endif + void CheckRemoveEntityID(EGID egid, Type entityDescriptorType, string caller) + { + bool isAllowed = false; + if (_multipleOperationOnSameEGIDChecker.TryGetValue(egid, out var operationType) == true) { - throw new ECSException("Trying to remove an Entity with a group never used so far " - .FastConcat(" caller: ", caller, " entityID ").FastConcat(egid.entityID).FastConcat(" groupid: ") - .FastConcat(egid.groupID.ToName()).FastConcat(" type: ") - .FastConcat(entityDescriptorType != null ? entityDescriptorType.Name : "not available")); + isAllowed = operationType == OperationType.Remove || operationType == OperationType.SwapFrom; + + if (isAllowed) + { +#if VERBOSE + var operationName = OperationName(operationType); + Console.LogDebugWarning( + "Executing multiple structural changes (remove) in one submission on the same entity. Remove supersedes swap and remove operations " + .FastConcat(" caller: ", caller, " ").FastConcat(egid.entityID).FastConcat(" groupid: ") + .FastConcat(egid.groupID.ToName()).FastConcat(" type: ") + .FastConcat(entityDescriptorType != null ? entityDescriptorType.Name : "not available") + .FastConcat(" previous operation was: ") + .FastConcat(operationName)); +#endif + } + else + throw new ECSException( + "Executing multiple structural changes (remove) 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(" previous operation was: ") + .FastConcat("add")); } - hash.Remove(egid.entityID); + HashRemove(egid, entityDescriptorType, isAllowed, caller); - _multipleOperationOnSameEGIDChecker.Add(egid, 0); + if (isAllowed == false) + _multipleOperationOnSameEGIDChecker.Add(egid, OperationType.Remove); + else + _multipleOperationOnSameEGIDChecker[egid] = OperationType.Remove; } + #if DONT_USE [Conditional("MEANINGLESS")] #endif void CheckAddEntityID(EGID egid, Type entityDescriptorType, string caller) { - if (_multipleOperationOnSameEGIDChecker.ContainsKey(egid) == true) + if (_multipleOperationOnSameEGIDChecker.TryGetValue(egid, out var operationType) == true) + { + var operationName = OperationName(operationType); + throw new ECSException( "Executing multiple structural changes (build) 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(" previous operation was: ") - .FastConcat(_multipleOperationOnSameEGIDChecker[egid] == 1 ? "add" : "remove")); + .FastConcat(" caller: ", caller, " ").FastConcat(egid.entityID).FastConcat(" groupid: ") + .FastConcat(egid.groupID.ToName()).FastConcat(" type: ") + .FastConcat(entityDescriptorType != null ? entityDescriptorType.Name : "not available") + .FastConcat(" previous operation was: ") + .FastConcat(operationName)); + } - var hash = _idChecker.GetOrAdd(egid.groupID, () => new HashSet()); - if (hash.Contains(egid.entityID) == true) - throw new ECSException("Trying to add an Entity already present in the database " - .FastConcat(" caller: ", caller, " entityID ").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); + HashAdd(egid, entityDescriptorType, caller); + + _multipleOperationOnSameEGIDChecker.Add(egid, OperationType.Add); } #if DONT_USE @@ -85,9 +143,72 @@ namespace Svelto.ECS #if DONT_USE [Conditional("MEANINGLESS")] #endif - void ClearChecksForMultipleOperationsOnTheSameEgid() { _multipleOperationOnSameEGIDChecker.Clear(); } + void ClearChecksForMultipleOperationsOnTheSameEgid() + { + _multipleOperationOnSameEGIDChecker.Clear(); + } + + void HashRemove(EGID egid, Type entityDescriptorType, bool isAllowed, string caller) + { + if (_idChecker.TryGetValue(egid.groupID, out HashSet hash)) + { + if (hash.Contains(egid.entityID) == false && isAllowed == false) + throw new ECSException( + "Trying to remove an Entity not present in the database " + .FastConcat(" caller: ", caller, " entityID ").FastConcat(egid.entityID).FastConcat(" groupid: ") + .FastConcat(egid.groupID.ToName()).FastConcat(" type: ") + .FastConcat(entityDescriptorType != null ? entityDescriptorType.Name : "not available")); + } + else + { + throw new ECSException( + "Trying to remove an Entity with a group never used so far " + .FastConcat(" caller: ", caller, " entityID ").FastConcat(egid.entityID).FastConcat(" groupid: ") + .FastConcat(egid.groupID.ToName()).FastConcat(" type: ") + .FastConcat(entityDescriptorType != null ? entityDescriptorType.Name : "not available")); + } + + hash.Remove(egid.entityID); + } + + void HashAdd(EGID egid, Type entityDescriptorType, string caller) + { + var hash = _idChecker.GetOrAdd(egid.groupID, () => new HashSet()); + if (hash.Contains(egid.entityID) == true) + throw new ECSException( + "Trying to add an Entity already present in the database " + .FastConcat(" caller: ", caller, " entityID ").FastConcat(egid.entityID) + .FastConcat(" groupid: ").FastConcat(egid.groupID.ToName()).FastConcat(" type: ") + .FastConcat( + entityDescriptorType != null + ? entityDescriptorType.Name + : "not available")); + hash.Add(egid.entityID); + } + + string OperationName(OperationType operationType) + { + string operationName; + switch (operationType) + { + case OperationType.Remove: + operationName = "remove"; + break; + case OperationType.Add: + operationName = "add"; + break; + case OperationType.SwapFrom: + operationName = "swapFrom"; + break; + default: + operationName = "swapTo"; + break; + } + + return operationName; + } - readonly DataStructures.FasterDictionary _multipleOperationOnSameEGIDChecker; - readonly DataStructures.FasterDictionary> _idChecker; + DataStructures.FasterDictionary _multipleOperationOnSameEGIDChecker; + DataStructures.FasterDictionary> _idChecker; } } \ No newline at end of file diff --git a/com.sebaslab.svelto.ecs/Core/ComponentBuilder.CheckFields.cs b/com.sebaslab.svelto.ecs/Core/ComponentBuilder.CheckFields.cs index 9e76063..de51676 100644 --- a/com.sebaslab.svelto.ecs/Core/ComponentBuilder.CheckFields.cs +++ b/com.sebaslab.svelto.ecs/Core/ComponentBuilder.CheckFields.cs @@ -6,6 +6,7 @@ using System; using System.Reflection; using System.Runtime.CompilerServices; using Svelto.ECS.Hybrid; +using Svelto.ECS.Serialization; namespace Svelto.ECS { @@ -154,13 +155,11 @@ namespace Svelto.ECS static readonly Type STRINGBUILDERTYPE = typeof(System.Text.StringBuilder); internal static readonly Type ENTITY_INFO_COMPONENT = typeof(EntityInfoComponent); + public static ComponentID ENTITY_INFO_COMPONENT_ID { [MethodImpl(MethodImplOptions.AggressiveInlining)] - get - { - return ComponentTypeID.id; - } + get => ComponentTypeID.id; } } } \ No newline at end of file diff --git a/com.sebaslab.svelto.ecs/Core/ComponentBuilder.cs b/com.sebaslab.svelto.ecs/Core/ComponentBuilder.cs index c9a53e3..3487c41 100644 --- a/com.sebaslab.svelto.ecs/Core/ComponentBuilder.cs +++ b/com.sebaslab.svelto.ecs/Core/ComponentBuilder.cs @@ -52,7 +52,7 @@ namespace Svelto.ECS if (IS_UNMANAGED) EntityComponentIDMap.Register(new Filler()); #endif - + ComponentTypeID.Init(); ComponentBuilderUtilities.CheckFields(ENTITY_COMPONENT_TYPE, IS_ENTITY_VIEW_COMPONENT); if (IS_ENTITY_VIEW_COMPONENT) diff --git a/com.sebaslab.svelto.ecs/Core/ComponentID.cs b/com.sebaslab.svelto.ecs/Core/ComponentID.cs index 7d0d046..fdacf0c 100644 --- a/com.sebaslab.svelto.ecs/Core/ComponentID.cs +++ b/com.sebaslab.svelto.ecs/Core/ComponentID.cs @@ -17,7 +17,7 @@ namespace Svelto.ECS [DebuggerTypeProxy(typeof(ComponentIDDebugProxy))] - public struct ComponentID: IEquatable + public struct ComponentID: IEquatable, IComparable { public static implicit operator int(ComponentID id) { @@ -46,6 +46,11 @@ namespace Svelto.ECS { return _id; } + + public int CompareTo(ComponentID other) + { + return _id.CompareTo(other._id); + } int _id; } diff --git a/com.sebaslab.svelto.ecs/Core/ComponentTypeID.cs b/com.sebaslab.svelto.ecs/Core/ComponentTypeID.cs index 6ecf992..0ad42cb 100644 --- a/com.sebaslab.svelto.ecs/Core/ComponentTypeID.cs +++ b/com.sebaslab.svelto.ecs/Core/ComponentTypeID.cs @@ -5,7 +5,7 @@ using Svelto.ECS.Internal; namespace Svelto.ECS { - public static class BurstCompatibleCounter + static class BurstCompatibleCounter { public static int counter; } @@ -34,8 +34,10 @@ namespace Svelto.ECS [Unity.Burst.BurstDiscard] //SharedStatic values must be initialized from not burstified code #endif - static void Init() + internal static void Init() { + if (_id.Data != 0) + return; _id.Data = Interlocked.Increment(ref BurstCompatibleCounter.counter); ComponentTypeMap.Add(typeof(T), id); } diff --git a/com.sebaslab.svelto.ecs/Core/EGID.cs b/com.sebaslab.svelto.ecs/Core/EGID.cs index a3c2fd9..75263e0 100644 --- a/com.sebaslab.svelto.ecs/Core/EGID.cs +++ b/com.sebaslab.svelto.ecs/Core/EGID.cs @@ -6,8 +6,8 @@ using System.Runtime.InteropServices; namespace Svelto.ECS { - [Serialization.DoNotSerialize] - [Serializable] + [Serialization.DoNotSerialize] //EGID cannot be serialised with Svelto serialization code + [Serializable] //I do not remember why we marked this a Serializable though [StructLayout(LayoutKind.Explicit)] public struct EGID : IEquatable, IComparable { diff --git a/com.sebaslab.svelto.ecs/Core/EnginesGroup/IStepEngine.cs b/com.sebaslab.svelto.ecs/Core/EnginesGroup/IStepEngine.cs index 12e7b50..0055b2b 100644 --- a/com.sebaslab.svelto.ecs/Core/EnginesGroup/IStepEngine.cs +++ b/com.sebaslab.svelto.ecs/Core/EnginesGroup/IStepEngine.cs @@ -1,3 +1,6 @@ +using System.Collections.Generic; +using Svelto.DataStructures; + namespace Svelto.ECS { public interface IStepEngine : IEngine @@ -9,17 +12,22 @@ namespace Svelto.ECS public interface IStepEngine : IEngine { - void Step(in T _param); + void Step(in T param); string name { get; } } + + public interface IGroupEngine + { + public IEnumerable engines { get; } + } //this must stay IStepEngine as it may be part of a group itself - public interface IStepGroupEngine : IStepEngine + public interface IStepGroupEngine : IStepEngine, IGroupEngine { } - public interface IStepGroupEngine : IStepEngine + public interface IStepGroupEngine : IStepEngine, IGroupEngine { } } \ No newline at end of file diff --git a/com.sebaslab.svelto.ecs/Core/EnginesGroup/SortedEnginesGroup.cs b/com.sebaslab.svelto.ecs/Core/EnginesGroup/SortedEnginesGroup.cs index f9fee97..0e37231 100644 --- a/com.sebaslab.svelto.ecs/Core/EnginesGroup/SortedEnginesGroup.cs +++ b/com.sebaslab.svelto.ecs/Core/EnginesGroup/SortedEnginesGroup.cs @@ -1,12 +1,13 @@ +using System.Collections.Generic; using Svelto.DataStructures; using Svelto.Common; namespace Svelto.ECS { /// - /// SortedEnginesGroup is a practical way to group engines that can be ticked together. The class requires a - /// SequenceOrder struct that define the order of execution. The pattern to use is the following: - /// First define as many enums as you want with the ID of the engines to use. E.G.: + /// SortedEnginesGroup is a practical way to group engines that need to be ticked in order. The class requires a + /// SequenceOrder struct that defines the order of execution. The pattern to use is the following: + /// First define as many enums as you want with the IDs of the engines to use. E.G.: /// public enum WiresCompositionEngineNames ///{ /// WiresTimeRunningGroup, @@ -17,11 +18,11 @@ namespace Svelto.ECS /// then link these ID to the actual engines, using the attribute Sequenced: /// /// [Sequenced(nameof(WiresCompositionEngineNames.WiresTimeRunningGroup))] - /// class WiresTimeRunningGroup : UnsortedDeterministicEnginesGroupg {} + /// class WiresTimeRunningGroup : UnsortedDeterministicEnginesGroup {} /// - /// Note that the engine can be another group itself (like in this example). + /// Note that the engine can be another engines group itself. /// - /// then define the ISequenceOrder struct. E.G.: + /// then define the ISequenceOrder struct to define the order of execution. E.G.: /// public struct DeterministicTimeRunningEnginesOrder: ISequenceOrder /// { /// private static readonly string[] order = @@ -35,7 +36,18 @@ namespace Svelto.ECS /// } /// /// Now you can use the Type you just created (i.e.: DeterministicTimeRunningEnginesOrder) as generic parameter - /// of the SortedEnginesGroup. + /// of the SortedEnginesGroup. like this: + /// + /// public class SortedDoofusesEnginesExecutionGroup : SortedEnginesGroup + /// { + /// public SortedDoofusesEnginesExecutionGroup(FasterList engines) : base(engines) + /// { + /// } + /// } + /// + /// This will then Tick the engines passed by constructor with the order defined in the DeterministicTimeRunningEnginesOrder + /// calling _enginesGroup.Step(); + /// /// While the system may look convoluted, is an effective way to keep the engines assemblies decoupled from /// each other /// The class is abstract and it requires a user defined interface to push the user to use recognisable names meaningful @@ -43,7 +55,7 @@ namespace Svelto.ECS /// /// user defined interface that implements IStepEngine public abstract class SortedEnginesGroup : IStepGroupEngine - where SequenceOrder : struct, ISequenceOrder where Interface : IStepEngine + where SequenceOrder : struct, ISequenceOrder where Interface : class, IStepEngine { protected SortedEnginesGroup(FasterList engines) { @@ -65,7 +77,15 @@ namespace Svelto.ECS } public string name => _name; - + public IEnumerable engines + { + get + { + for (int i = 0; i < _instancedSequence.items.count; i++) + yield return _instancedSequence.items[i]; + } + } + readonly string _name; readonly Sequence _instancedSequence; } @@ -76,7 +96,7 @@ namespace Svelto.ECS /// /// Specialised Parameter that can be passed to all the engines in the group public abstract class SortedEnginesGroup: IStepGroupEngine - where SequenceOrder : struct, ISequenceOrder where Interface : IStepEngine + where SequenceOrder : struct, ISequenceOrder where Interface : class, IStepEngine { protected SortedEnginesGroup(FasterList engines) { @@ -84,7 +104,7 @@ namespace Svelto.ECS _instancedSequence = new Sequence(engines); } - public void Step(in Parameter param) + public void Step(in Parameter time) { var sequenceItems = _instancedSequence.items; using (var profiler = new PlatformProfiler(_name)) @@ -92,12 +112,20 @@ namespace Svelto.ECS for (var index = 0; index < sequenceItems.count; index++) { var engine = sequenceItems[index]; - using (profiler.Sample(engine.name)) engine.Step(param); + using (profiler.Sample(engine.name)) engine.Step(time); } } } public string name => _name; + public IEnumerable engines + { + get + { + for (int i = 0; i < _instancedSequence.items.count; i++) + yield return _instancedSequence.items[i]; + } + } readonly string _name; readonly Sequence _instancedSequence; diff --git a/com.sebaslab.svelto.ecs/Core/EnginesGroup/UnsortedEnginesGroup.cs b/com.sebaslab.svelto.ecs/Core/EnginesGroup/UnsortedEnginesGroup.cs index f1e9bfc..090b9b1 100644 --- a/com.sebaslab.svelto.ecs/Core/EnginesGroup/UnsortedEnginesGroup.cs +++ b/com.sebaslab.svelto.ecs/Core/EnginesGroup/UnsortedEnginesGroup.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using Svelto.Common; using Svelto.DataStructures; @@ -6,12 +7,16 @@ namespace Svelto.ECS /// /// UnsortedEnginesGroup is a practical way to group engines that can be ticked together. As the name suggest /// there is no way to defines an order, although the engines will run in the same order they are added. - /// It is abstract and it requires a user defined interface to push the user to use recognisable names meaningful - /// to the context where they are used. + /// It is abstract and it requires a user defined class to push the user to use recognisable names meaningful + /// to the context where they are used. like this: + /// public class SirensSequentialEngines: UnsortedEnginesGroup + /// { + /// + /// } /// /// user defined interface that implements IStepEngine public abstract class UnsortedEnginesGroup : IStepGroupEngine - where Interface : IStepEngine + where Interface : class, IStepEngine { protected UnsortedEnginesGroup() { @@ -27,12 +32,12 @@ namespace Svelto.ECS public void Step() { - var sequenceItems = _instancedSequence; using (var profiler = new PlatformProfiler(_name)) { - for (var index = 0; index < sequenceItems.count; index++) + var instancedSequenceCount = _instancedSequence.count; + for (var index = 0; index < instancedSequenceCount; index++) { - var engine = sequenceItems[index]; + var engine = _instancedSequence[index]; using (profiler.Sample(engine.name)) engine.Step(); } } @@ -45,8 +50,18 @@ namespace Svelto.ECS public string name => _name; + public IEnumerable engines + { + get + { + for (int i = 0; i < _instancedSequence.count; i++) + yield return _instancedSequence[i]; + } + } + readonly string _name; readonly FasterList _instancedSequence; + } /// @@ -55,7 +70,7 @@ namespace Svelto.ECS /// /// Specialised Parameter that can be passed to all the engines in the group public abstract class UnsortedEnginesGroup : IStepGroupEngine - where Interface : IStepEngine + where Interface : class, IStepEngine { protected UnsortedEnginesGroup() { @@ -69,15 +84,15 @@ namespace Svelto.ECS _instancedSequence = engines; } - public void Step(in Parameter param) + public void Step(in Parameter time) { - var sequenceItems = _instancedSequence; using (var profiler = new PlatformProfiler(_name)) { - for (var index = 0; index < sequenceItems.count; index++) + var instancedSequenceCount = _instancedSequence.count; + for (var index = 0; index < instancedSequenceCount; index++) { - var engine = sequenceItems[index]; - using (profiler.Sample(engine.name)) engine.Step(param); + var engine = _instancedSequence[index]; + using (profiler.Sample(engine.name)) engine.Step(time); } } } @@ -86,6 +101,15 @@ namespace Svelto.ECS { _instancedSequence.Add(engine); } + + public IEnumerable engines + { + get + { + for (int i = 0; i < _instancedSequence.count; i++) + yield return _instancedSequence[i]; + } + } public string name => _name; diff --git a/com.sebaslab.svelto.ecs/Core/EnginesRoot.Engines.cs b/com.sebaslab.svelto.ecs/Core/EnginesRoot.Engines.cs index 8892fe3..410ee83 100644 --- a/com.sebaslab.svelto.ecs/Core/EnginesRoot.Engines.cs +++ b/com.sebaslab.svelto.ecs/Core/EnginesRoot.Engines.cs @@ -18,7 +18,6 @@ namespace Svelto.ECS { EntityDescriptorsWarmup.WarmUp(); GroupHashMap.WarmUp(); - //SharedDictonary.Init(); SerializationDescriptorMap.Init(); _swapEntities = SwapEntities; @@ -38,13 +37,11 @@ namespace Svelto.ECS public EnginesRoot(EntitiesSubmissionScheduler entitiesComponentScheduler) { _entitiesOperations = new EntitiesOperations(); - _idChecker = new FasterDictionary>(); _cachedRangeOfSubmittedIndices = new FasterList<(uint, uint)>(); - _transientEntityIDsLeftAndAffectedByRemoval = new FasterList(); - _transientEntityIDsLeftWithoutDuplicates = new FasterDictionary(); - - _multipleOperationOnSameEGIDChecker = new FasterDictionary(); + _transientEntityIDsAffectedByRemoveAtSwapBack = new FasterDictionary(); + + InitDebugChecks(); #if UNITY_NATIVE //because of the thread count, ATM this is only for unity _nativeSwapOperationQueue = new AtomicNativeBags(Allocator.Persistent); _nativeRemoveOperationQueue = new AtomicNativeBags(Allocator.Persistent); @@ -152,7 +149,9 @@ namespace Svelto.ECS if (engine is IReactOnDispose viewEngineDispose) CheckReactEngineComponents( +#pragma warning disable CS0618 typeof(IReactOnDispose<>), viewEngineDispose, _reactiveEnginesDispose, type.Name); +#pragma warning restore CS0618 if (engine is IReactOnDisposeEx viewEngineDisposeEx) CheckReactEngineComponents( @@ -160,7 +159,9 @@ namespace Svelto.ECS if (engine is IReactOnSwap viewEngineSwap) #pragma warning disable CS0612 +#pragma warning disable CS0618 CheckReactEngineComponents(typeof(IReactOnSwap<>), viewEngineSwap, _reactiveEnginesSwap, type.Name); +#pragma warning restore CS0618 #pragma warning restore CS0612 if (engine is IReactOnSwapEx viewEngineSwapEx) @@ -172,7 +173,11 @@ namespace Svelto.ECS if (engine is IReactOnSubmissionStarted submissionEngineStarted) _reactiveEnginesSubmissionStarted.Add(submissionEngineStarted); - + + if (engine is IGroupEngine stepGroupEngine) + foreach (var stepEngine in stepGroupEngine.engines) + AddEngine(stepEngine); + _enginesTypeSet.Add(refWrapper); _enginesSet.Add(engine); @@ -243,6 +248,9 @@ namespace Svelto.ECS void Dispose(bool disposing) { + if (_isDisposed) + return; + using (var profiler = new PlatformProfiler("Final Dispose")) { //Note: The engines are disposed before the the remove callback to give the chance to behave @@ -252,7 +260,7 @@ namespace Svelto.ECS foreach (var engine in _disposableEngines) try { - if (engine is IDisposingEngine dengine) + if (engine is IDisposableEngine dengine) dengine.isDisposing = true; engine.Dispose(); diff --git a/com.sebaslab.svelto.ecs/Core/EnginesRoot.Entities.cs b/com.sebaslab.svelto.ecs/Core/EnginesRoot.Entities.cs index 0c25bc8..0c680d6 100644 --- a/com.sebaslab.svelto.ecs/Core/EnginesRoot.Entities.cs +++ b/com.sebaslab.svelto.ecs/Core/EnginesRoot.Entities.cs @@ -8,7 +8,26 @@ using Svelto.ECS.Internal; namespace Svelto.ECS { - public partial class EnginesRoot : IDisposable, IUnitTestingInterface + public static class EGIDMultiMapperNBExtension + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static EGIDMultiMapper QueryMappedEntities(this EntitiesDB entitiesDb, LocalFasterReadOnlyList groups) + where T : struct, _IInternalEntityComponent + { + var dictionary = new FasterDictionary>((uint) groups.count); + + foreach (var group in groups) + { + entitiesDb.QueryOrCreateEntityDictionary(group, out var typeSafeDictionary); + //if (typeSafeDictionary.count > 0) avoiding this allows these egidmappers to be precreated and stored + dictionary.Add(group, typeSafeDictionary as ITypeSafeDictionary); + } + + return new EGIDMultiMapper(dictionary); + } + } + + public partial class EnginesRoot: IDisposable, IUnitTestingInterface { ///-------------------------------------------- /// @@ -34,13 +53,15 @@ namespace Svelto.ECS { CheckAddEntityID(entityID, descriptorType, caller); - DBC.ECS.Check.Require(entityID.groupID.isInvalid == false, + DBC.ECS.Check.Require( + entityID.groupID.isInvalid == false, "invalid group detected, are you using new ExclusiveGroupStruct() instead of new ExclusiveGroup()?"); var reference = _entityLocator.ClaimReference(); _entityLocator.SetReference(reference, entityID); - var dic = EntityFactory.BuildGroupedEntities(entityID, _groupedEntityToAdd, componentsToBuild, implementors + var dic = EntityFactory.BuildGroupedEntities( + entityID, _groupedEntityToAdd, componentsToBuild, implementors #if DEBUG && !PROFILE_SVELTO , descriptorType #endif @@ -52,7 +73,7 @@ namespace Svelto.ECS /// /// Preallocate memory to avoid the impact to resize arrays when many entities are submitted at once /// - void Preallocate(ExclusiveGroupStruct groupID, uint size, IComponentBuilder[] entityComponentsToBuild) + internal void Preallocate(ExclusiveGroupStruct groupID, uint size, IComponentBuilder[] entityComponentsToBuild) { void PreallocateEntitiesToAdd() { @@ -67,14 +88,14 @@ namespace Svelto.ECS for (var index = 0; index < numberOfEntityComponents; index++) { var entityComponentBuilder = entityComponentsToBuild[index]; - var entityComponentType = entityComponentBuilder.getComponentID; + var entityComponentType = entityComponentBuilder.getComponentID; - var dbList = group.GetOrAdd(entityComponentType, () => entityComponentBuilder.CreateDictionary(size)); + var dbList = group.GetOrAdd(entityComponentType, () => entityComponentBuilder.CreateDictionary(size)); entityComponentBuilder.Preallocate(dbList, size); if (_groupsPerEntity.TryGetValue(entityComponentType, out var groupedGroup) == false) groupedGroup = _groupsPerEntity[entityComponentType] = - new FasterDictionary(); + new FasterDictionary(); groupedGroup[groupID] = dbList; } @@ -88,7 +109,8 @@ namespace Svelto.ECS [MethodImpl(MethodImplOptions.AggressiveInlining)] FasterDictionary GetDBGroup(ExclusiveGroupStruct fromIdGroupId) { - if (_groupEntityComponentsDB.TryGetValue(fromIdGroupId, + if (_groupEntityComponentsDB.TryGetValue( + fromIdGroupId, out FasterDictionary fromGroup) == false) throw new ECSException("Group doesn't exist: ".FastConcat(fromIdGroupId.ToName())); @@ -98,7 +120,8 @@ namespace Svelto.ECS [MethodImpl(MethodImplOptions.AggressiveInlining)] FasterDictionary GetOrAddDBGroup(ExclusiveGroupStruct toGroupId) { - return _groupEntityComponentsDB.GetOrAdd(toGroupId, + return _groupEntityComponentsDB.GetOrAdd( + toGroupId, () => new FasterDictionary()); } @@ -106,9 +129,11 @@ namespace Svelto.ECS { var fromGroup = GetDBGroup(fromEntityGID.groupID); - if (fromGroup.TryGetValue(ComponentBuilderUtilities.ENTITY_INFO_COMPONENT_ID, + if (fromGroup.TryGetValue( + ComponentBuilderUtilities.ENTITY_INFO_COMPONENT_ID, out var entityInfoDic) // - && ((ITypeSafeDictionary)entityInfoDic).TryGetValue(fromEntityGID.entityID, + && ((ITypeSafeDictionary)entityInfoDic).TryGetValue( + fromEntityGID.entityID, out var entityInfo)) //there could be multiple entity descriptors registered in the same group, so it's necessary to check if the entity registered in the group has entityInfoComponent { #if PARANOID_CHECK @@ -135,9 +160,11 @@ namespace Svelto.ECS { var fromGroup = GetDBGroup(fromEntityGID.groupID); - if (fromGroup.TryGetValue(ComponentBuilderUtilities.ENTITY_INFO_COMPONENT_ID, + if (fromGroup.TryGetValue( + ComponentBuilderUtilities.ENTITY_INFO_COMPONENT_ID, out var entityInfoDic) // - && ((ITypeSafeDictionary)entityInfoDic).TryGetValue(fromEntityGID.entityID, + && ((ITypeSafeDictionary)entityInfoDic).TryGetValue( + fromEntityGID.entityID, out var entityInfo)) //there could be multiple entity descriptors registered in the same group, so it's necessary to check if the entity registered in the group has entityInfoComponent { #if PARANOID_CHECK @@ -168,14 +195,14 @@ namespace Svelto.ECS //for each group id, save a dictionary indexed by entity type of entities indexed by id // group EntityComponentType entityID, EntityComponent internal readonly FasterDictionary> - _groupEntityComponentsDB; + _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 // >> internal readonly FasterDictionary> - _groupsPerEntity; + _groupsPerEntity; #if SVELTO_LEGACY_FILTERS //The filters stored for each component and group internal readonly FasterDictionary> diff --git a/com.sebaslab.svelto.ecs/Core/EnginesRoot.GenericEntityFactory.cs b/com.sebaslab.svelto.ecs/Core/EnginesRoot.GenericEntityFactory.cs index 00f068e..d24b96c 100644 --- a/com.sebaslab.svelto.ecs/Core/EnginesRoot.GenericEntityFactory.cs +++ b/com.sebaslab.svelto.ecs/Core/EnginesRoot.GenericEntityFactory.cs @@ -77,7 +77,7 @@ namespace Svelto.ECS } #endif - //enginesRoot is a weakreference because GenericEntityStreamConsumerFactory can be injected inside + //NOTE: enginesRoot is a weakreference ONLY because GenericEntityStreamConsumerFactory can be injected inside //engines of other enginesRoot readonly Svelto.DataStructures.WeakReference _enginesRoot; } diff --git a/com.sebaslab.svelto.ecs/Core/EnginesRoot.GenericEntityFunctions.cs b/com.sebaslab.svelto.ecs/Core/EnginesRoot.GenericEntityFunctions.cs index 2c03ed1..3403604 100644 --- a/com.sebaslab.svelto.ecs/Core/EnginesRoot.GenericEntityFunctions.cs +++ b/com.sebaslab.svelto.ecs/Core/EnginesRoot.GenericEntityFunctions.cs @@ -55,8 +55,7 @@ namespace Svelto.ECS dictionary.KeysEvaluator((key) => { - _enginesRoot.Target.CheckRemoveEntityID(new EGID(key, fromGroupID), null, caller); - _enginesRoot.Target.CheckAddEntityID(new EGID(key, toGroupID), null, caller); + _enginesRoot.Target.CheckSwapEntityID(new EGID(key, fromGroupID), new EGID(key, toGroupID), null, caller); }); #endif _enginesRoot.Target.QueueSwapGroupOperation(fromGroupID, toGroupID, caller); @@ -100,8 +99,7 @@ namespace Svelto.ECS var enginesRootTarget = _enginesRoot.Target; - enginesRootTarget.CheckRemoveEntityID(fromEGID, TypeCache.type, caller); - enginesRootTarget.CheckAddEntityID(toEGID, TypeCache.type, caller); + enginesRootTarget.CheckSwapEntityID(fromEGID, toEGID, TypeCache.type, caller); enginesRootTarget.QueueSwapEntityOperation(fromEGID, toEGID , _enginesRoot.Target.FindRealComponents(fromEGID) @@ -137,8 +135,7 @@ namespace Svelto.ECS } [MethodImpl(MethodImplOptions.AggressiveInlining)] - void QueueSwapEntityOperation - (EGID fromID, EGID toID, IComponentBuilder[] componentBuilders, string caller) + void QueueSwapEntityOperation(EGID fromID, EGID toID, IComponentBuilder[] componentBuilders, string caller) { _entitiesOperations.QueueSwapOperation(fromID, toID, componentBuilders, caller); } diff --git a/com.sebaslab.svelto.ecs/Core/EnginesRoot.Submission.cs b/com.sebaslab.svelto.ecs/Core/EnginesRoot.Submission.cs index d267c48..914118a 100644 --- a/com.sebaslab.svelto.ecs/Core/EnginesRoot.Submission.cs +++ b/com.sebaslab.svelto.ecs/Core/EnginesRoot.Submission.cs @@ -2,6 +2,7 @@ using System.Runtime.CompilerServices; using Svelto.Common; using Svelto.DataStructures; +using Svelto.DataStructures.Native; using Svelto.ECS.Internal; namespace Svelto.ECS @@ -13,7 +14,7 @@ namespace Svelto.ECS { //clear the data checks before the submission. We want to allow structural changes inside the callbacks ClearChecksForMultipleOperationsOnTheSameEgid(); - + _entitiesOperations.ExecuteRemoveAndSwappingOperations( _swapEntities, _removeEntities, @@ -22,7 +23,7 @@ namespace Svelto.ECS this); AddEntities(profiler); - + //clear the data checks after the submission, so if structural changes happened inside the callback, the debug structure is reset for the next frame operations ClearChecksForMultipleOperationsOnTheSameEgid(); } @@ -45,8 +46,7 @@ namespace Svelto.ECS } } - static void RemoveEntities( - FasterDictionary>> + static void RemoveEntities(FasterDictionary>> removeOperations, FasterList entitiesRemoved, EnginesRoot enginesRoot) { using (var sampler = new PlatformProfiler("remove Entities")) @@ -99,11 +99,11 @@ namespace Svelto.ECS FasterList<(uint, string)> entityIDsToRemove = groupedEntitiesToRemove.value; - enginesRoot._transientEntityIDsLeftAndAffectedByRemoval.Clear(); + enginesRoot._transientEntityIDsAffectedByRemoveAtSwapBack.Clear(); fromComponentsDictionary.RemoveEntitiesFromDictionary( entityIDsToRemove, - enginesRoot._transientEntityIDsLeftAndAffectedByRemoval); + enginesRoot._transientEntityIDsAffectedByRemoveAtSwapBack); //important: remove from the filter must happen after remove from the dictionary //as we need to read the new indices linked to entities after the removal @@ -111,8 +111,7 @@ namespace Svelto.ECS entityIDsToRemove, fromGroup, componentType, - fromComponentsDictionary, - enginesRoot._transientEntityIDsLeftAndAffectedByRemoval); + enginesRoot._transientEntityIDsAffectedByRemoveAtSwapBack); //store new database count after the entities are removed from the datatabase, plus the number of entities removed enginesRoot._cachedRangeOfSubmittedIndices.Add( @@ -162,89 +161,96 @@ namespace Svelto.ECS } static void SwapEntities(FasterDictionary>>> swapEntitiesOperations, - FasterList<(EGID, EGID)> entitiesIDSwaps, EnginesRoot enginesRoot) + FasterDictionary>>> swapEntitiesOperations, + FasterDictionary entitiesIDSwaps, EnginesRoot enginesRoot) { using (var sampler = new PlatformProfiler("Swap entities between groups")) { using (sampler.Sample("Update Entity References")) { var count = entitiesIDSwaps.count; + var entitiesIDSwapsValues = entitiesIDSwaps.unsafeValues; for (int i = 0; i < count; i++) { - var (fromEntityGid, toEntityGid) = entitiesIDSwaps[i]; + var (fromEntityGid, toEntityGid) = entitiesIDSwapsValues[i]; enginesRoot._entityLocator.UpdateEntityReference(fromEntityGid, toEntityGid); } } - + using (sampler.Sample("Swap Entities")) { - enginesRoot._cachedRangeOfSubmittedIndices.Clear(); - //Entities to swap are organised in order to minimise the amount of dictionary lookups. //swapEntitiesOperations iterations happen in the following order: //for each fromGroup, get all the entities to swap for each component type. //then get the list of IDs for each ToGroup. //now swap the set of FromGroup -> ToGroup entities per ID. - foreach (var entitiesToSwap in swapEntitiesOperations) + foreach (var entitiesToSwap in swapEntitiesOperations) //each operation is a component to swap { + enginesRoot._cachedRangeOfSubmittedIndices.Clear(); ExclusiveGroupStruct fromGroup = entitiesToSwap.key; - var fromGroupDictionary = enginesRoot.GetDBGroup(fromGroup); + FasterDictionary fromGroupDictionary = enginesRoot.GetDBGroup(fromGroup); //iterate all the fromgroups foreach (var groupedEntitiesToSwap in entitiesToSwap.value) { - var componentType = groupedEntitiesToSwap.key; + ComponentID componentType = groupedEntitiesToSwap.key; ITypeSafeDictionary fromComponentsDictionaryDB = fromGroupDictionary[componentType]; - //get the subset of togroups that come from from group + //for each fromgroup get the subset of togroups (entities that move from fromgroup to any togroup) foreach (var entitiesInfoToSwap in groupedEntitiesToSwap.value) { ExclusiveGroupStruct toGroup = entitiesInfoToSwap.key; ITypeSafeDictionary toComponentsDictionaryDB = enginesRoot.GetOrAddTypeSafeDictionary( - toGroup, - enginesRoot.GetOrAddDBGroup(toGroup), - componentType, - fromComponentsDictionaryDB); + toGroup, enginesRoot.GetOrAddDBGroup(toGroup), componentType, fromComponentsDictionaryDB); - DBC.ECS.Check.Assert( - toComponentsDictionaryDB != null, - "something went wrong with the creation of dictionaries"); - - //this list represents the set of entities that come from fromGroup and need - //to be swapped to toGroup. Most of the times will be 1 of few units. - FasterList<(uint, uint, string)> fromEntityToEntityIDs = entitiesInfoToSwap.value; + DBC.ECS.Check.Assert(toComponentsDictionaryDB != null, "something went wrong with the creation of dictionaries"); + //entities moving from fromgroup to this togroup + FasterDictionary fromEntityToEntityIDs = entitiesInfoToSwap.value; + DBC.ECS.Check.Assert(fromEntityToEntityIDs.count > 0, "something went wrong, no entities to swap"); //ensure that to dictionary has enough room to store the new entities - toComponentsDictionaryDB.EnsureCapacity( - (uint)(toComponentsDictionaryDB.count + (uint)fromEntityToEntityIDs.count)); - - //fortunately swap means that entities are added at the end of each destination - //dictionary list, so we can just iterate the list using the indices ranges added in the - //_cachedIndices - enginesRoot._cachedRangeOfSubmittedIndices.Add( - ((uint, uint))(toComponentsDictionaryDB.count, - toComponentsDictionaryDB.count + fromEntityToEntityIDs.count)); + var newBufferSize = (uint)(toComponentsDictionaryDB.count + fromEntityToEntityIDs.count); + toComponentsDictionaryDB.EnsureCapacity(newBufferSize); - enginesRoot._transientEntityIDsLeftAndAffectedByRemoval.Clear(); + //fortunately swap adds the swapped entities at the end of the buffer + //so we can just iterate the list using the indices ranges added in the cachedIndices + enginesRoot._cachedRangeOfSubmittedIndices.Add(((uint, uint))(toComponentsDictionaryDB.count, newBufferSize)); + enginesRoot._transientEntityIDsAffectedByRemoveAtSwapBack.Clear(); fromComponentsDictionaryDB.SwapEntitiesBetweenDictionaries( - fromEntityToEntityIDs, - fromGroup, - toGroup, - toComponentsDictionaryDB, - enginesRoot._transientEntityIDsLeftAndAffectedByRemoval); + fromEntityToEntityIDs, fromGroup, toGroup, toComponentsDictionaryDB, + enginesRoot._transientEntityIDsAffectedByRemoveAtSwapBack); //important: this must happen after the entities are swapped in the database enginesRoot.SwapEntityBetweenPersistentFilters( - fromEntityToEntityIDs, - fromComponentsDictionaryDB, - toComponentsDictionaryDB, - fromGroup, - toGroup, - componentType, - enginesRoot._transientEntityIDsLeftAndAffectedByRemoval); + fromEntityToEntityIDs, fromGroup, toGroup, componentType, + enginesRoot._transientEntityIDsAffectedByRemoveAtSwapBack); + } + } + + var rangeEnumerator = enginesRoot._cachedRangeOfSubmittedIndices.GetEnumerator(); + using (sampler.Sample("Execute Swap Callbacks Fast")) + { + foreach (var groupedEntitiesToSwap in entitiesToSwap.value) + { + var componentType = groupedEntitiesToSwap.key; + + foreach (var entitiesInfoToSwap in groupedEntitiesToSwap.value) + { + rangeEnumerator.MoveNext(); + + //get all the engines linked to TValue + if (!enginesRoot._reactiveEnginesSwapEx.TryGetValue(componentType, out var entityComponentsEngines)) + continue; + + ExclusiveGroupStruct toGroup = entitiesInfoToSwap.key; + ITypeSafeDictionary toComponentsDictionary = GetTypeSafeDictionary( + toGroup, enginesRoot.GetDBGroup(toGroup), componentType); + + toComponentsDictionary.ExecuteEnginesSwapCallbacksFast( + entityComponentsEngines, fromGroup, toGroup, rangeEnumerator.Current, in sampler); + } } } } @@ -261,64 +267,20 @@ namespace Svelto.ECS var componentType = groupedEntitiesToSwap.key; //get all the engines linked to TValue - if (!enginesRoot._reactiveEnginesSwap.TryGetValue( - componentType, - out var entityComponentsEngines)) + if (enginesRoot._reactiveEnginesSwap.TryGetValue(componentType, out var entityComponentsEngines) == false) continue; foreach (var entitiesInfoToSwap in groupedEntitiesToSwap.value) { ExclusiveGroupStruct toGroup = entitiesInfoToSwap.key; ITypeSafeDictionary toComponentsDictionary = GetTypeSafeDictionary( - toGroup, - enginesRoot.GetDBGroup(toGroup), - componentType); + toGroup, enginesRoot.GetDBGroup(toGroup), componentType); var infosToProcess = entitiesInfoToSwap.value; toComponentsDictionary.ExecuteEnginesSwapCallbacks( - infosToProcess, - entityComponentsEngines, - fromGroup, - toGroup, - in sampler); - } - } - } - } - - var rangeEnumerator = enginesRoot._cachedRangeOfSubmittedIndices.GetEnumerator(); - using (sampler.Sample("Execute Swap Callbacks Fast")) - { - foreach (var entitiesToSwap in swapEntitiesOperations) - { - ExclusiveGroupStruct fromGroup = entitiesToSwap.key; - - foreach (var groupedEntitiesToSwap in entitiesToSwap.value) - { - var componentType = groupedEntitiesToSwap.key; - - foreach (var entitiesInfoToSwap in groupedEntitiesToSwap.value) - { - rangeEnumerator.MoveNext(); - - //get all the engines linked to TValue - if (!enginesRoot._reactiveEnginesSwapEx.TryGetValue( - componentType, out var entityComponentsEngines)) - continue; - - ExclusiveGroupStruct toGroup = entitiesInfoToSwap.key; - ITypeSafeDictionary toComponentsDictionary = GetTypeSafeDictionary( - toGroup, - enginesRoot.GetDBGroup(toGroup), - componentType); - - toComponentsDictionary.ExecuteEnginesSwapCallbacksFast( - entityComponentsEngines, - fromGroup, - toGroup, - rangeEnumerator.Current, - in sampler); + infosToProcess, entityComponentsEngines, + fromGroup, toGroup, in sampler); } } } @@ -353,17 +315,17 @@ namespace Svelto.ECS { var type = entityComponentsToSubmit.key; var fromDictionary = entityComponentsToSubmit.value; - + var toDictionary = GetOrAddTypeSafeDictionary(groupID, groupDB, type, fromDictionary); //all the new entities are added at the end of each dictionary list, so we can //just iterate the list using the indices ranges added in the _cachedIndices - _cachedRangeOfSubmittedIndices.Add( - ((uint, uint))(toDictionary.count, toDictionary.count + fromDictionary.count)); + _cachedRangeOfSubmittedIndices.Add(((uint, uint))(toDictionary.count, toDictionary.count + fromDictionary.count)); //Fill the DB with the entity components generated this frame. - fromDictionary.AddEntitiesToDictionary(toDictionary, groupID + fromDictionary.AddEntitiesToDictionary( + toDictionary, groupID #if SLOW_SVELTO_SUBMISSION - , entityLocator + , entityLocator #endif ); } @@ -383,7 +345,7 @@ namespace Svelto.ECS foreach (var entityComponentsToSubmit in groupToSubmit.components) { var type = entityComponentsToSubmit.key; - + var toDictionary = GetTypeSafeDictionary(groupID, groupDB, type); enumerator.MoveNext(); toDictionary.ExecuteEnginesAddEntityCallbacksFast(_reactiveEnginesAddEx, groupID, enumerator.Current, in sampler); @@ -413,8 +375,7 @@ namespace Svelto.ECS //this contains the total number of components ever submitted in the DB ITypeSafeDictionary toDictionary = GetTypeSafeDictionary(groupID, groupDB, type); - fromDictionary.ExecuteEnginesAddCallbacks( - _reactiveEnginesAdd, toDictionary, groupID, in sampler); + fromDictionary.ExecuteEnginesAddCallbacks(_reactiveEnginesAdd, toDictionary, groupID, in sampler); } } } @@ -475,9 +436,10 @@ namespace Svelto.ECS ITypeSafeDictionary fromDictionary = dictionaryOfEntities.value; ITypeSafeDictionary toDictionary = GetOrAddTypeSafeDictionary(toGroupId, toGroup, refWrapperType, fromDictionary); - fromDictionary.AddEntitiesToDictionary(toDictionary, toGroupId + fromDictionary.AddEntitiesToDictionary( + toDictionary, toGroupId #if SLOW_SVELTO_SUBMISSION - , entityLocator + , entityLocator #endif ); } @@ -491,7 +453,8 @@ namespace Svelto.ECS ITypeSafeDictionary toDictionary = GetTypeSafeDictionary(toGroupId, toGroup, refWrapperType); //SwapEX happens inside - fromDictionary.ExecuteEnginesSwapCallbacks_Group(_reactiveEnginesSwap, _reactiveEnginesSwapEx, toDictionary, + fromDictionary.ExecuteEnginesSwapCallbacks_Group( + _reactiveEnginesSwap, _reactiveEnginesSwapEx, toDictionary, fromGroupId, toGroupId, platformProfiler); } @@ -520,7 +483,7 @@ namespace Svelto.ECS //update GroupsPerEntity if (_groupsPerEntity.TryGetValue(typeID, out var groupedGroup) == false) groupedGroup = _groupsPerEntity[typeID] = - new FasterDictionary(); + new FasterDictionary(); groupedGroup[groupId] = toEntitiesDictionary; } @@ -545,14 +508,13 @@ namespace Svelto.ECS //transient caches>>>>>>>>>>>>>>>>>>>>> readonly FasterList<(uint, uint)> _cachedRangeOfSubmittedIndices; - readonly FasterDictionary _transientEntityIDsLeftWithoutDuplicates; - readonly FasterList _transientEntityIDsLeftAndAffectedByRemoval; + readonly FasterDictionary _transientEntityIDsAffectedByRemoveAtSwapBack; //<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< static readonly - Action>>>, FasterList<(EGID, EGID)> - , EnginesRoot> _swapEntities; + Action>>>, FasterDictionary, + EnginesRoot> _swapEntities; static readonly Action< FasterDictionary>>, diff --git a/com.sebaslab.svelto.ecs/Core/EntitiesDB.cs b/com.sebaslab.svelto.ecs/Core/EntitiesDB.cs index e271f1d..133fe64 100644 --- a/com.sebaslab.svelto.ecs/Core/EntitiesDB.cs +++ b/com.sebaslab.svelto.ecs/Core/EntitiesDB.cs @@ -2,9 +2,7 @@ #define ENABLE_DEBUG_FUNC #endif -using System; using System.Runtime.CompilerServices; -using Svelto.Common; using Svelto.DataStructures; using Svelto.ECS.Internal; @@ -14,27 +12,14 @@ namespace Svelto.ECS { internal EntitiesDB(EnginesRoot enginesRoot, EnginesRoot.EntityReferenceMap entityReferencesMap) { - _enginesRoot = enginesRoot; + _enginesRoot = enginesRoot; _entityReferencesMap = entityReferencesMap; } - EntityCollection InternalQueryEntities - (FasterDictionary entitiesInGroupPerType) - where T : struct, _IInternalEntityComponent + public void PreallocateEntitySpace(ExclusiveGroupStruct groupStructId, uint numberOfEntities) + where T : IEntityDescriptor, new() { - uint count = 0; - IBuffer buffer; - IEntityIDs ids = default; - if (SafeQueryEntityDictionary(out var typeSafeDictionary, entitiesInGroupPerType) == false) - buffer = default; - else - { - ITypeSafeDictionary safeDictionary = (typeSafeDictionary as ITypeSafeDictionary); - buffer = safeDictionary.GetValues(out count); - ids = safeDictionary.entityIDs; - } - - return new EntityCollection(buffer, ids, count); + _enginesRoot.Preallocate(groupStructId, numberOfEntities, EntityDescriptorTemplate.realDescriptor.componentsToBuild); } /// @@ -46,7 +31,7 @@ namespace Svelto.ECS /// /// public EntityCollection QueryEntities(ExclusiveGroupStruct groupStructId) - where T : struct, _IInternalEntityComponent + where T : struct, _IInternalEntityComponent { if (groupEntityComponentsDB.TryGetValue(groupStructId, out var entitiesInGroupPerType) == false) { @@ -57,7 +42,7 @@ namespace Svelto.ECS } public EntityCollection QueryEntities(ExclusiveGroupStruct groupStruct) - where T1 : struct, _IInternalEntityComponent where T2 : struct, _IInternalEntityComponent + where T1 : struct, _IInternalEntityComponent where T2 : struct, _IInternalEntityComponent { if (groupEntityComponentsDB.TryGetValue(groupStruct, out var entitiesInGroupPerType) == false) { @@ -70,21 +55,23 @@ namespace Svelto.ECS 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())).FastConcat(" this means that you are mixing descriptors in the same group that do not share the components that you are querying")); + 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())).FastConcat( + " this means that you are mixing descriptors in the same group that do not share the components that you are querying")); #endif return new EntityCollection(T1entities, T2entities); } public EntityCollection QueryEntities(ExclusiveGroupStruct groupStruct) - where T1 : struct, _IInternalEntityComponent - where T2 : struct, _IInternalEntityComponent - where T3 : struct, _IInternalEntityComponent + where T1 : struct, _IInternalEntityComponent + where T2 : struct, _IInternalEntityComponent + where T3 : struct, _IInternalEntityComponent { if (groupEntityComponentsDB.TryGetValue(groupStruct, out var entitiesInGroupPerType) == false) { @@ -99,23 +86,25 @@ namespace Svelto.ECS 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)).FastConcat(" this means that you are mixing descriptors in the same group that do not share the components that you are querying")); + 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)).FastConcat( + " this means that you are mixing descriptors in the same group that do not share the components that you are querying")); #endif return new EntityCollection(T1entities, T2entities, T3entities); } public EntityCollection QueryEntities(ExclusiveGroupStruct groupStruct) - where T1 : struct, _IInternalEntityComponent - where T2 : struct, _IInternalEntityComponent - where T3 : struct, _IInternalEntityComponent - where T4 : struct, _IInternalEntityComponent + where T1 : struct, _IInternalEntityComponent + where T2 : struct, _IInternalEntityComponent + where T3 : struct, _IInternalEntityComponent + where T4 : struct, _IInternalEntityComponent { if (groupEntityComponentsDB.TryGetValue(groupStruct, out var entitiesInGroupPerType) == false) { @@ -132,23 +121,25 @@ namespace Svelto.ECS var T4entities = InternalQueryEntities(entitiesInGroupPerType); #if DEBUG && !PROFILE_SVELTO if (T1entities.count != T2entities.count || T2entities.count != T3entities.count - || T3entities.count != T4entities.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) - .FastConcat(" Entity 4: ".FastConcat(typeof(T4).ToString())) - .FastConcat(" count: ").FastConcat(T4entities.count)).FastConcat(" this means that you are mixing descriptors in the same group that do not share the components that you are querying")); + || T3entities.count != T4entities.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) + .FastConcat(" Entity 4: ".FastConcat(typeof(T4).ToString())) + .FastConcat(" count: ").FastConcat(T4entities.count)).FastConcat( + " this means that you are mixing descriptors in the same group that do not share the components that you are querying")); #endif return new EntityCollection(T1entities, T2entities, T3entities, T4entities); } - public GroupsEnumerable QueryEntities - (in LocalFasterReadOnlyList groups) where T : struct, _IInternalEntityComponent + public GroupsEnumerable QueryEntities(in LocalFasterReadOnlyList groups) + where T : struct, _IInternalEntityComponent { return new GroupsEnumerable(this, groups); } @@ -159,31 +150,31 @@ namespace Svelto.ECS /// /// public GroupsEnumerable QueryEntities(in LocalFasterReadOnlyList groups) - where T1 : struct, _IInternalEntityComponent where T2 : struct, _IInternalEntityComponent + where T1 : struct, _IInternalEntityComponent where T2 : struct, _IInternalEntityComponent { return new GroupsEnumerable(this, groups); } - public GroupsEnumerable QueryEntities - (in LocalFasterReadOnlyList groups) where T1 : struct, _IInternalEntityComponent - where T2 : struct, _IInternalEntityComponent - where T3 : struct, _IInternalEntityComponent + public GroupsEnumerable QueryEntities(in LocalFasterReadOnlyList groups) + where T1 : struct, _IInternalEntityComponent + where T2 : struct, _IInternalEntityComponent + where T3 : struct, _IInternalEntityComponent { return new GroupsEnumerable(this, groups); } - public GroupsEnumerable QueryEntities - (in LocalFasterReadOnlyList groups) where T1 : struct, _IInternalEntityComponent - where T2 : struct, _IInternalEntityComponent - where T3 : struct, _IInternalEntityComponent - where T4 : struct, _IInternalEntityComponent + public GroupsEnumerable QueryEntities(in LocalFasterReadOnlyList groups) + where T1 : struct, _IInternalEntityComponent + where T2 : struct, _IInternalEntityComponent + where T3 : struct, _IInternalEntityComponent + where T4 : struct, _IInternalEntityComponent { return new GroupsEnumerable(this, groups); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public EGIDMapper QueryMappedEntities(ExclusiveGroupStruct groupStructId) - where T : struct, _IInternalEntityComponent + where T : struct, _IInternalEntityComponent { if (SafeQueryEntityDictionary(groupStructId, out var typeSafeDictionary) == false) throw new EntityGroupNotFoundException(typeof(T), groupStructId.ToName()); @@ -192,8 +183,8 @@ namespace Svelto.ECS } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool TryQueryMappedEntities - (ExclusiveGroupStruct groupStructId, out EGIDMapper mapper) where T : struct, _IInternalEntityComponent + public bool TryQueryMappedEntities(ExclusiveGroupStruct groupStructId, out EGIDMapper mapper) + where T : struct, _IInternalEntityComponent { mapper = default; if (SafeQueryEntityDictionary(groupStructId, out var typeSafeDictionary) == false @@ -235,8 +226,7 @@ namespace Svelto.ECS [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool ExistsAndIsNotEmpty(ExclusiveGroupStruct gid) { - if (groupEntityComponentsDB.TryGetValue( - gid, out FasterDictionary group) == true) + if (groupEntityComponentsDB.TryGetValue(gid, out FasterDictionary group) == true) { return group.count > 0; } @@ -271,9 +261,8 @@ namespace Svelto.ECS } [MethodImpl(MethodImplOptions.AggressiveInlining)] - bool SafeQueryEntityDictionary - (out ITypeSafeDictionary typeSafeDictionary - , FasterDictionary entitiesInGroupPerType) where T : struct, _IInternalEntityComponent + bool SafeQueryEntityDictionary(FasterDictionary entitiesInGroupPerType, + out ITypeSafeDictionary typeSafeDictionary) where T : struct, _IInternalEntityComponent { if (entitiesInGroupPerType.TryGetValue(ComponentTypeID.id, out var safeDictionary) == false) @@ -289,10 +278,24 @@ namespace Svelto.ECS } [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal bool SafeQueryEntityDictionary - (ExclusiveGroupStruct group, out ITypeSafeDictionary typeSafeDictionary) where T : struct, _IInternalEntityComponent + internal bool SafeQueryEntityDictionary(ExclusiveGroupStruct group, out ITypeSafeDictionary typeSafeDictionary) + where T : struct, _IInternalEntityComponent { - if (UnsafeQueryEntityDictionary(group, ComponentTypeID.id, out var safeDictionary) == false) + ITypeSafeDictionary safeDictionary; + bool ret; + //search for the group + if (groupEntityComponentsDB.TryGetValue(group, out FasterDictionary entitiesInGroupPerType) == false) + { + safeDictionary = null; + ret = false; + } + else + { + ret = entitiesInGroupPerType.TryGetValue(ComponentTypeID.id, out safeDictionary); + } + + //search for the indexed entities in the group + if (ret == false) { typeSafeDictionary = default; return false; @@ -305,11 +308,21 @@ namespace Svelto.ECS } [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal bool UnsafeQueryEntityDictionary - (ExclusiveGroupStruct group, ComponentID id, out ITypeSafeDictionary typeSafeDictionary) + internal void QueryOrCreateEntityDictionary(ExclusiveGroupStruct group, out ITypeSafeDictionary typeSafeDictionary) + where T : struct, _IInternalEntityComponent { //search for the group - if (groupEntityComponentsDB.TryGetValue(group, out var entitiesInGroupPerType) == false) + FasterDictionary entitiesInGroupPerType = + groupEntityComponentsDB.GetOrAdd(group, () => new FasterDictionary()); + + typeSafeDictionary = entitiesInGroupPerType.GetOrAdd(ComponentTypeID.id, () => TypeSafeDictionaryFactory.Create(0)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal bool UnsafeQueryEntityDictionary(ExclusiveGroupStruct groupID, ComponentID id, out ITypeSafeDictionary typeSafeDictionary) + { + //search for the group + if (groupEntityComponentsDB.TryGetValue(groupID, out FasterDictionary entitiesInGroupPerType) == false) { typeSafeDictionary = null; return false; @@ -319,8 +332,28 @@ namespace Svelto.ECS return entitiesInGroupPerType.TryGetValue(id, out typeSafeDictionary); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + EntityCollection InternalQueryEntities(FasterDictionary entitiesInGroupPerType) + where T : struct, _IInternalEntityComponent + { + uint count = 0; + IBuffer buffer; + IEntityIDs ids = default; + + if (SafeQueryEntityDictionary(entitiesInGroupPerType, out var typeSafeDictionary) == false) + buffer = default; + else + { + ITypeSafeDictionary safeDictionary = (typeSafeDictionary as ITypeSafeDictionary); + buffer = safeDictionary.GetValues(out count); + ids = safeDictionary.entityIDs; + } + + return new EntityCollection(buffer, ids, count); + } + static readonly FasterDictionary _emptyDictionary = - new FasterDictionary(); + new FasterDictionary(); readonly EnginesRoot _enginesRoot; @@ -331,14 +364,14 @@ namespace Svelto.ECS //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. FasterDictionary> - groupEntityComponentsDB => _enginesRoot._groupEntityComponentsDB; + 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> groupsPerComponent => - _enginesRoot._groupsPerEntity; + _enginesRoot._groupsPerEntity; EnginesRoot.EntityReferenceMap _entityReferencesMap; } diff --git a/com.sebaslab.svelto.ecs/Core/EntitiesOperations.cs b/com.sebaslab.svelto.ecs/Core/EntitiesOperations.cs index f6364cf..a00beaa 100644 --- a/com.sebaslab.svelto.ecs/Core/EntitiesOperations.cs +++ b/com.sebaslab.svelto.ecs/Core/EntitiesOperations.cs @@ -31,21 +31,40 @@ namespace Svelto.ECS _thisSubmissionInfo._groupsToRemove.Add((groupID, caller)); } - public void QueueRemoveOperation(EGID entityEgid, IComponentBuilder[] componentBuilders, string caller) + public void QueueRemoveOperation(EGID fromEgid, IComponentBuilder[] componentBuilders, string caller) { - _thisSubmissionInfo._entitiesRemoved.Add(entityEgid); - + _thisSubmissionInfo._entitiesRemoved.Add(fromEgid); + RevertSwapOperation(fromEgid); + //todo: limit the number of dictionaries that can be cached //recycle or create dictionaries of components per group var removedComponentsPerType = _thisSubmissionInfo._currentRemoveEntitiesOperations.RecycleOrAdd( - entityEgid.groupID, _newGroupsDictionary, _recycleDictionary); + fromEgid.groupID, _newGroupsDictionary, _recycleDictionary); foreach (var operation in componentBuilders) { removedComponentsPerType //recycle or create dictionaries per component type .RecycleOrAdd(operation.getComponentID, _newList, _clearList) //add entity to remove - .Add((entityEgid.entityID, caller)); + .Add((fromEgid.entityID, caller)); + } + + void RevertSwapOperation(EGID fromEgid) + { + if (_thisSubmissionInfo._entitiesSwapped.Remove(fromEgid, out (EGID fromEgid, EGID toEgid) val)) //Remove supersedes swap, check comment in IEntityFunctions.cs + { + var swappedComponentsPerType = _thisSubmissionInfo._currentSwapEntitiesOperations[fromEgid.groupID]; + + var componentBuildersLength = componentBuilders.Length - 1; + + for (var index = componentBuildersLength; index >= 0; index--) + { + var operation = componentBuilders[index]; + + //todo: maybe the order of swappedComponentsPerType should be fromID, toGroupID, componentID + swappedComponentsPerType[operation.getComponentID][val.toEgid.groupID].Remove(fromEgid.entityID); + } + } } } @@ -54,42 +73,42 @@ namespace Svelto.ECS _thisSubmissionInfo._groupsToSwap.Add((fromGroupID, toGroupID, caller)); } - public void QueueSwapOperation(EGID fromID, EGID toID, IComponentBuilder[] componentBuilders, string caller) + public void QueueSwapOperation(EGID fromEGID, EGID toEGID, IComponentBuilder[] componentBuilders, string caller) { - _thisSubmissionInfo._entitiesSwapped.Add((fromID, toID)); + _thisSubmissionInfo._entitiesSwapped.Add(fromEGID, (fromEGID, toEGID)); //todo: limit the number of dictionaries that can be cached - //recycle or create dictionaries of components per group - - //Get the dictionary that holds the entities that are swapping from fromID + + //Get (or create) the dictionary that holds the entities that are swapping from fromEGID group var swappedComponentsPerType = _thisSubmissionInfo._currentSwapEntitiesOperations.RecycleOrAdd( - fromID.groupID, _newGroupsDictionaryWithCaller, _recycleGroupDictionaryWithCaller); + fromEGID.groupID, _newGroupsDictionaryWithCaller, _recycleGroupDictionaryWithCaller); var componentBuildersLength = componentBuilders.Length - 1; + //for each component of the entity that is swapping for (var index = componentBuildersLength; index >= 0; index--) { - var operation = componentBuilders[index]; - - //Get the dictionary for each component that holds the list of entities to swap - swappedComponentsPerType //recycle or create dictionaries per component type + var operation = componentBuilders[index]; + + //Get the dictionary for each component that holds the list of entities to swap + swappedComponentsPerType + //recycle or create dictionaries per component type .RecycleOrAdd(operation.getComponentID, _newGroupDictionary, _recycleDicitionaryWithCaller) //recycle or create list of entities to swap - .RecycleOrAdd(toID.groupID, _newListWithCaller, _clearListWithCaller) + .RecycleOrAdd(toEGID.groupID, _newListWithCaller, _clearListWithCaller) //add entity to swap - .Add((fromID.entityID, toID.entityID, caller)); + .Add(fromEGID.entityID, new SwapInfo(fromEGID.entityID, toEGID.entityID, caller)); } } - + [MethodImpl(MethodImplOptions.Synchronized)] public bool AnyOperationQueued() { return _thisSubmissionInfo.AnyOperationQueued(); } - public void ExecuteRemoveAndSwappingOperations(Action>>>, FasterList<(EGID, EGID)>, - EnginesRoot> swapEntities, Action>>, - FasterList, EnginesRoot> removeEntities, Action removeGroup, + public void ExecuteRemoveAndSwappingOperations( + Action>>>, FasterDictionary, EnginesRoot> swapEntities, Action>>, + FasterList, EnginesRoot> removeEntities, Action removeGroup, Action swapGroup, EnginesRoot enginesRoot) { (_thisSubmissionInfo, _lastSubmittedInfo) = (_lastSubmittedInfo, _thisSubmissionInfo); @@ -104,7 +123,7 @@ namespace Svelto.ECS catch { var str = "Crash while removing a whole group on ".FastConcat(group.ToString()) - .FastConcat(" from : ", caller); + .FastConcat(" from : ", caller); Console.LogError(str); @@ -119,7 +138,7 @@ namespace Svelto.ECS catch { var str = "Crash while swapping a whole group on " - .FastConcat(fromGroup.ToString(), " ", toGroup.ToString()).FastConcat(" from : ", caller); + .FastConcat(fromGroup.ToString(), " ", toGroup.ToString()).FastConcat(" from : ", caller); Console.LogError(str); @@ -127,88 +146,86 @@ namespace Svelto.ECS } if (_lastSubmittedInfo._entitiesSwapped.count > 0) - swapEntities(_lastSubmittedInfo._currentSwapEntitiesOperations, _lastSubmittedInfo._entitiesSwapped - , enginesRoot); + swapEntities(_lastSubmittedInfo._currentSwapEntitiesOperations, _lastSubmittedInfo._entitiesSwapped, enginesRoot); if (_lastSubmittedInfo._entitiesRemoved.count > 0) - removeEntities(_lastSubmittedInfo._currentRemoveEntitiesOperations, _lastSubmittedInfo._entitiesRemoved - , enginesRoot); + removeEntities( + _lastSubmittedInfo._currentRemoveEntitiesOperations, _lastSubmittedInfo._entitiesRemoved + , enginesRoot); _lastSubmittedInfo.Clear(); } - - FasterDictionary> NewGroupsDictionary() + + static FasterDictionary> NewGroupsDictionary() { return new FasterDictionary>(); } - - void RecycleDictionary(ref FasterDictionary> recycled) + + static void RecycleDictionary(ref FasterDictionary> recycled) { recycled.Recycle(); } - FasterList<(uint, string)> NewList() + static FasterList<(uint, string)> NewList() { return new FasterList<(uint, string)>(); } - void ClearList(ref FasterList<(uint, string)> target) + static void ClearList(ref FasterList<(uint, string)> target) { target.Clear(); } - void RecycleDicitionaryWithCaller(ref FasterDictionary> target) + static void RecycleDicitionaryWithCaller(ref FasterDictionary> target) { target.Recycle(); } - void ClearListWithCaller(ref FasterList<(uint, uint, string)> target) + static void ClearListWithCaller(ref FasterDictionary target) { target.Clear(); } - FasterList<(uint, uint, string)> NewListWithCaller() + static FasterDictionary NewListWithCaller() { - return new FasterList<(uint, uint, string)>(); + return new FasterDictionary(); } - FasterDictionary>> NewGroupsDictionaryWithCaller() + static FasterDictionary>> + NewGroupsDictionaryWithCaller() { - return new FasterDictionary>>(); + return new FasterDictionary>>(); } - void RecycleGroupDictionaryWithCaller(ref FasterDictionary>> recycled) + static void RecycleGroupDictionaryWithCaller( + ref FasterDictionary>> recycled) { recycled.Recycle(); } - FasterDictionary> NewGroupDictionary() + static FasterDictionary> NewGroupDictionary() { - return new FasterDictionary>(); + return new FasterDictionary>(); } struct Info { - //from group //actual component type - internal FasterDictionary>>> - _currentSwapEntitiesOperations; + //from group //actual component type + internal FasterDictionary>>> _currentSwapEntitiesOperations; internal FasterDictionary>> _currentRemoveEntitiesOperations; - internal FasterList<(EGID, EGID)> _entitiesSwapped; - internal FasterList _entitiesRemoved; - public FasterList<(ExclusiveBuildGroup, ExclusiveBuildGroup, string)> _groupsToSwap; - public FasterList<(ExclusiveBuildGroup, string)> _groupsToRemove; + internal FasterDictionary _entitiesSwapped; + internal FasterList _entitiesRemoved; + public FasterList<(ExclusiveBuildGroup, ExclusiveBuildGroup, string)> _groupsToSwap; + public FasterList<(ExclusiveBuildGroup, string)> _groupsToRemove; [MethodImpl(MethodImplOptions.AggressiveInlining)] internal bool AnyOperationQueued() { return _entitiesSwapped.count > 0 || _entitiesRemoved.count > 0 || _groupsToSwap.count > 0 - || _groupsToRemove.count > 0; + || _groupsToRemove.count > 0; } internal void Clear() @@ -223,32 +240,68 @@ namespace Svelto.ECS internal void Init() { - _entitiesSwapped = new FasterList<(EGID, EGID)>(); + _entitiesSwapped = new FasterDictionary(); _entitiesRemoved = new FasterList(); - _groupsToRemove = new FasterList<(ExclusiveBuildGroup, string)>(); - _groupsToSwap = new FasterList<(ExclusiveBuildGroup, ExclusiveBuildGroup, string)>(); + _groupsToRemove = new FasterList<(ExclusiveBuildGroup, string)>(); + _groupsToSwap = new FasterList<(ExclusiveBuildGroup, ExclusiveBuildGroup, string)>(); _currentSwapEntitiesOperations = - new FasterDictionary>>>(); + new FasterDictionary>>>(); _currentRemoveEntitiesOperations = - new FasterDictionary>>(); + new FasterDictionary>>(); } } Info _lastSubmittedInfo; Info _thisSubmissionInfo; - readonly Func>> _newGroupDictionary; + readonly Func>> _newGroupDictionary; readonly Func>> _newGroupsDictionary; readonly ActionRef>> _recycleDictionary; readonly Func> _newList; readonly ActionRef> _clearList; - readonly Func>>> _newGroupsDictionaryWithCaller; - readonly ActionRef>>> _recycleGroupDictionaryWithCaller; - readonly ActionRef>> _recycleDicitionaryWithCaller; - readonly Func> _newListWithCaller; - readonly ActionRef> _clearListWithCaller; + + readonly Func>>> + _newGroupsDictionaryWithCaller; + + readonly ActionRef>>> + _recycleGroupDictionaryWithCaller; + + readonly ActionRef>> _recycleDicitionaryWithCaller; + readonly Func> _newListWithCaller; + readonly ActionRef> _clearListWithCaller; + } + + public struct SwapInfo + { + public uint fromID; //to do this information should be redundant, try to remove it + public uint toID; + public uint toIndex; + public string trace; + + public SwapInfo(uint fromEgidEntityId, uint toEgidEntityId, string s) + { + fromID = fromEgidEntityId; + toID = toEgidEntityId; + toIndex = 0; + trace = s; + } + + public void Deconstruct(out uint fromID, out uint toID, out uint toIndex, out string caller) + { + fromID = this.fromID; + toID = this.toID; + toIndex = this.toIndex; + caller = this.trace; + } + + public void Deconstruct(out uint fromID, out uint toID, out string caller) + { + fromID = this.fromID; + toID = this.toID; + caller = this.trace; + } } } \ No newline at end of file diff --git a/com.sebaslab.svelto.ecs/Core/EntityCollection.cs b/com.sebaslab.svelto.ecs/Core/EntityCollection.cs index 1875918..0a37e22 100644 --- a/com.sebaslab.svelto.ecs/Core/EntityCollection.cs +++ b/com.sebaslab.svelto.ecs/Core/EntityCollection.cs @@ -10,15 +10,15 @@ namespace Svelto.ECS { DBC.ECS.Check.Require(count == 0 || buffer.isValid, "Buffer is found in impossible state"); - _buffer = buffer; - _entityIDs = entityIDs; + this.buffer = buffer; + this.entityIDs = entityIDs; this.count = count; } public uint count { get; } - public readonly IBufferBase _buffer; - public readonly IEntityIDs _entityIDs; + public readonly IBufferBase buffer; + public readonly IEntityIDs entityIDs; } public readonly ref struct EntityCollection where T1 : struct, _IInternalEntityComponent diff --git a/com.sebaslab.svelto.ecs/Core/EntityInitializer.cs b/com.sebaslab.svelto.ecs/Core/EntityInitializer.cs index f50c49d..0865588 100644 --- a/com.sebaslab.svelto.ecs/Core/EntityInitializer.cs +++ b/com.sebaslab.svelto.ecs/Core/EntityInitializer.cs @@ -19,9 +19,7 @@ namespace Svelto.ECS public void Init(T initializer) where T : struct, _IInternalEntityComponent { - if (_group.TryGetValue( - ComponentTypeID.id, - out var typeSafeDictionary) == false) + if (_group.TryGetValue(ComponentTypeID.id, out var typeSafeDictionary) == false) return; var dictionary = (ITypeSafeDictionary)typeSafeDictionary; @@ -29,16 +27,14 @@ namespace Svelto.ECS if (ComponentBuilder.HAS_EGID) SetEGIDWithoutBoxing.SetIDWithoutBoxing(ref initializer, _ID); #endif - if (dictionary.TryFindIndex(_ID.entityID, out var findElementIndex)) dictionary.GetDirectValueByRef(findElementIndex) = initializer; } - internal ref T GetOrAdd() where T : struct, _IInternalEntityComponent + internal ref T GetOrAdd() where T : unmanaged, _IInternalEntityComponent { - ref var entityDictionary = ref _group.GetOrAdd( - ComponentTypeID.id, - () => new UnmanagedTypeSafeDictionary(1)); + ref var entityDictionary = ref _group.GetOrAdd(ComponentTypeID.id, () => new UnmanagedTypeSafeDictionary(1)); + var dictionary = (ITypeSafeDictionary)entityDictionary; return ref dictionary.GetOrAdd(_ID.entityID); @@ -46,15 +42,12 @@ namespace Svelto.ECS public ref T Get() where T : struct, _IInternalEntityComponent { - return ref (_group[ComponentTypeID.id] as ITypeSafeDictionary) - .GetValueByRef(_ID.entityID); + return ref (_group[ComponentTypeID.id] as ITypeSafeDictionary).GetValueByRef(_ID.entityID); } public bool Has() where T : struct, _IInternalEntityComponent { - if (_group.TryGetValue( - ComponentTypeID.id, - out var typeSafeDictionary)) + if (_group.TryGetValue(ComponentTypeID.id, out var typeSafeDictionary)) { var dictionary = (ITypeSafeDictionary)typeSafeDictionary; diff --git a/com.sebaslab.svelto.ecs/Core/EntityReference/EnginesRoot.LocatorMap.cs b/com.sebaslab.svelto.ecs/Core/EntityReference/EnginesRoot.LocatorMap.cs index b8c2966..afcd1f0 100644 --- a/com.sebaslab.svelto.ecs/Core/EntityReference/EnginesRoot.LocatorMap.cs +++ b/com.sebaslab.svelto.ecs/Core/EntityReference/EnginesRoot.LocatorMap.cs @@ -146,6 +146,7 @@ namespace Svelto.ECS _egidToReferenceMap.Remove(fromGroupId); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public EntityReference GetEntityReference(EGID egid) { if (_egidToReferenceMap.TryGetValue(egid.groupID, out var groupMap)) @@ -153,15 +154,16 @@ namespace Svelto.ECS if (groupMap.TryGetValue(egid.entityID, out var locator)) return locator; #if DEBUG && !PROFILE_SVELTO - else - throw new ECSException( - $"Entity {egid} does not exist. Are you creating it? Try getting it from initializer.reference."); + throw new ECSException( + $"Entity {egid} does not exist. If you just created it, get it from initializer.reference."); #endif } - return EntityReference.Invalid; + throw new ECSException( + $"Entity {egid} does not exist. If you just created it, get it from initializer.reference."); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public SharedSveltoDictionaryNative GetEntityReferenceMap(ExclusiveGroupStruct groupID) { if (_egidToReferenceMap.TryGetValue(groupID, out var groupMap) == false) @@ -170,6 +172,7 @@ namespace Svelto.ECS return groupMap; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool TryGetEGID(EntityReference reference, out EGID egid) { egid = default; @@ -189,6 +192,7 @@ namespace Svelto.ECS return false; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public EGID GetEGID(EntityReference reference) { #if DEBUG && !PROFILE_SVELTO @@ -202,7 +206,6 @@ namespace Svelto.ECS if (entityReferenceMapElement.version != reference.version) throw new ECSException("outdated Reference"); #endif - return entityReferenceMapElement.egid; } @@ -244,8 +247,7 @@ namespace Svelto.ECS //alternatively since the groups are guaranteed to be sequential an array should be used instead //than a dictionary for groups. It could be a good case to implement a 4k chunk based sparseset - SharedSveltoDictionaryNative> - _egidToReferenceMap; + SharedSveltoDictionaryNative> _egidToReferenceMap; } EntityReferenceMap entityLocator => _entityLocator; diff --git a/com.sebaslab.svelto.ecs/Core/EntityReference/EntitiesDB.References.cs b/com.sebaslab.svelto.ecs/Core/EntityReference/EntitiesDB.References.cs index a47fea6..b4fbb04 100644 --- a/com.sebaslab.svelto.ecs/Core/EntityReference/EntitiesDB.References.cs +++ b/com.sebaslab.svelto.ecs/Core/EntityReference/EntitiesDB.References.cs @@ -1,4 +1,5 @@ using System.Runtime.CompilerServices; +using Svelto.DataStructures; using Svelto.DataStructures.Native; namespace Svelto.ECS diff --git a/com.sebaslab.svelto.ecs/Core/EntityReference/EntityReference.cs b/com.sebaslab.svelto.ecs/Core/EntityReference/EntityReference.cs index 6b5aa99..3d85283 100644 --- a/com.sebaslab.svelto.ecs/Core/EntityReference/EntityReference.cs +++ b/com.sebaslab.svelto.ecs/Core/EntityReference/EntityReference.cs @@ -67,11 +67,16 @@ namespace Svelto.ECS [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool ToEGID(EntitiesDB entitiesDB, out EGID egid) { - DBC.ECS.Check.Require(this != Invalid, "Invalid Reference Used"); - return entitiesDB.TryGetEGID(this, out egid); } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Exists(EntitiesDB entitiesDB) + { + return this != Invalid && entitiesDB.TryGetEGID(this, out _); + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public ulong ToULong() { return _GID; diff --git a/com.sebaslab.svelto.ecs/Core/Filters/CombinedFilterID.cs b/com.sebaslab.svelto.ecs/Core/Filters/CombinedFilterID.cs index 2196c31..68fdbcc 100644 --- a/com.sebaslab.svelto.ecs/Core/Filters/CombinedFilterID.cs +++ b/com.sebaslab.svelto.ecs/Core/Filters/CombinedFilterID.cs @@ -26,6 +26,11 @@ namespace Svelto.ECS { id = (long)filterID << 32 | (long)contextID.id << 16; } + + public CombinedFilterID(uint filterID, FilterContextID contextID) + { + id = (long)filterID << 32 | (long)contextID.id << 16; + } public static implicit operator CombinedFilterID((int filterID, FilterContextID contextID) data) { diff --git a/com.sebaslab.svelto.ecs/Core/Filters/EnginesRoot.Filters.cs b/com.sebaslab.svelto.ecs/Core/Filters/EnginesRoot.Filters.cs index 305f329..6b4c102 100644 --- a/com.sebaslab.svelto.ecs/Core/Filters/EnginesRoot.Filters.cs +++ b/com.sebaslab.svelto.ecs/Core/Filters/EnginesRoot.Filters.cs @@ -1,6 +1,5 @@ using Svelto.DataStructures; using Svelto.DataStructures.Native; -using Svelto.ECS.Internal; namespace Svelto.ECS { @@ -59,11 +58,9 @@ namespace Svelto.ECS /// /// /// - /// /// void RemoveEntitiesFromPersistentFilters - (FasterList<(uint entityID, string)> entityIDsRemoved, ExclusiveGroupStruct fromGroup, ComponentID refWrapperType - , ITypeSafeDictionary fromDic, FasterList entityIDsLeftAndAffectedByRemoval) + (FasterList<(uint entityID, string)> entityIDsRemoved, ExclusiveGroupStruct fromGroup, ComponentID refWrapperType, FasterDictionary entityIDsAffectedByRemoveAtSwapBack) { //is there any filter used by this component? if (_indicesOfPersistentFiltersUsedByThisComponent.TryGetValue( @@ -72,14 +69,6 @@ namespace Svelto.ECS var numberOfFilters = listOfFilters.count; var filters = _persistentEntityFilters.unsafeValues; - //remove duplicates - _transientEntityIDsLeftWithoutDuplicates.Clear(); - var entityAffectedCount = entityIDsLeftAndAffectedByRemoval.count; - for (int i = 0; i < entityAffectedCount; i++) - { - _transientEntityIDsLeftWithoutDuplicates[entityIDsLeftAndAffectedByRemoval[i]] = -1; - } - for (int filterIndex = 0; filterIndex < numberOfFilters; ++filterIndex) { //foreach filter linked to this component @@ -105,16 +94,11 @@ namespace Svelto.ECS //of the deleted component //entityIDsAffectedByRemoval tracks all the entitiesID of the components that need to be updated //in the filters because their indices in the array changed. - foreach (var entity in _transientEntityIDsLeftWithoutDuplicates) + foreach (var entity in entityIDsAffectedByRemoveAtSwapBack) { - var entityId = entity.key; - if (fromGroupFilter.Exists(entityId)) //does the entityID that has been swapped exist in the filter? - { - if (entity.value == -1) - entity.value = (int)fromDic.GetIndex(entityId); //let's find the index of the entityID in the dictionary only once - - fromGroupFilter._entityIDToDenseIndex[entityId] = (uint) entity.value; //update the index in the filter of the component that has been swapped - } + var resultEntityIdToDenseIndex = fromGroupFilter._entityIDToDenseIndex; + if (resultEntityIdToDenseIndex.TryFindIndex(entity.key, out var index)) //does the entityID that has been swapped exist in the filter? + resultEntityIdToDenseIndex.unsafeValues[index] = entity.value; //update the index in the filter of the component that has been swapped } } } @@ -123,66 +107,49 @@ namespace Svelto.ECS //this method is called by the framework only if listOfFilters.count > 0 void SwapEntityBetweenPersistentFilters - (FasterList<(uint, uint, string)> fromEntityToEntityIDs, ITypeSafeDictionary fromDic - , ITypeSafeDictionary toDic, ExclusiveGroupStruct fromGroup, ExclusiveGroupStruct toGroup - , ComponentID refWrapperType, FasterList entityIDsLeftAndAffectedByRemoval) + (FasterDictionary fromEntityToEntityIDs, ExclusiveGroupStruct fromGroup, ExclusiveGroupStruct toGroup + , ComponentID refWrapperType, FasterDictionary entityIDsAffectedByRemoveAtSwapBack) { //is there any filter used by this component? - if (_indicesOfPersistentFiltersUsedByThisComponent.TryGetValue( - refWrapperType, out NativeDynamicArrayCast listOfFilters) == true) + if (_indicesOfPersistentFiltersUsedByThisComponent.TryGetValue(refWrapperType, out NativeDynamicArrayCast listOfFilters) == true) { DBC.ECS.Check.Require(listOfFilters.count > 0, "why are you calling this with an empty list?"); var numberOfFilters = listOfFilters.count; - //remove duplicates - _transientEntityIDsLeftWithoutDuplicates.Clear(); - var entityAffectedCount = entityIDsLeftAndAffectedByRemoval.count; - for (int i = 0; i < entityAffectedCount; i++) - { - _transientEntityIDsLeftWithoutDuplicates[entityIDsLeftAndAffectedByRemoval[i]] = -1; - } - - /// fromEntityToEntityIDs are the IDs of the entities to swap from the from group to the to group. - /// for this component type. for each component type, there is only one set of fromEntityToEntityIDs - /// per from/to group. + //foreach filter linked to this component for (int filterIndex = 0; filterIndex < numberOfFilters; ++filterIndex) { - //if the group has a filter linked: - EntityFilterCollection persistentFilter = - _persistentEntityFilters.unsafeValues[listOfFilters[filterIndex]]; + EntityFilterCollection persistentFilter = _persistentEntityFilters.unsafeValues[listOfFilters[filterIndex]]; + //if the group has a filter linked: if (persistentFilter._filtersPerGroup.TryGetValue(fromGroup, out var fromGroupFilter)) { EntityFilterCollection.GroupFilters groupFilterTo = default; - foreach (var (fromEntityID, toEntityID, _) in fromEntityToEntityIDs) + /// fromEntityToEntityIDs are the IDs of the entities to swap from the fromgroup to the togroup. + /// for this component type. for each component type, there is only one set of fromEntityToEntityIDs + /// per from/to group. + var countOfEntitiesToSwap = fromEntityToEntityIDs.count; + SwapInfo[] idOfEntitiesToSwap = fromEntityToEntityIDs.unsafeValues; + + for (var index = 0; index < countOfEntitiesToSwap; index++) { - var toIndex = toDic.GetIndex(toEntityID); //todo: optimize this should be calculated only once and not once per filter + (uint fromEntityID, uint toEntityID, uint toIndex, _) = idOfEntitiesToSwap[index]; //if there is an entity, it must be moved to the to filter - if (fromGroupFilter.Exists(fromEntityID) == true) + if (fromGroupFilter.Remove(fromEntityID) == true) { if (groupFilterTo.isValid == false) - groupFilterTo = persistentFilter.GetOrCreateGroupFilter(toGroup); + groupFilterTo = persistentFilter.GetOrCreateGroupFilter(toGroup); //should I call this outside and avoid the if? Do I want to create a group if there is no entity to swap? - groupFilterTo.Add(toEntityID, toIndex); + groupFilterTo.Add(toEntityID, toIndex); //add the entity with the index in the current buffer } } - foreach (var (fromEntityID, _, _) in fromEntityToEntityIDs) + foreach (var entity in entityIDsAffectedByRemoveAtSwapBack) { - fromGroupFilter.Remove(fromEntityID); //Remove works even if the ID is not found (just returns false) - } - - foreach (var entity in _transientEntityIDsLeftWithoutDuplicates) - { - var entityId = entity.key; - if (fromGroupFilter.Exists(entityId)) - { - if (entity.value == -1) - entity.value = (int)fromDic.GetIndex(entityId); - - fromGroupFilter._entityIDToDenseIndex[entityId] = (uint) entity.value; - } + var resultEntityIdToDenseIndex = fromGroupFilter._entityIDToDenseIndex; + if (resultEntityIdToDenseIndex.TryFindIndex(entity.key, out var index)) + resultEntityIdToDenseIndex.unsafeValues[index] = (uint)entity.value; } } } diff --git a/com.sebaslab.svelto.ecs/Core/Filters/EntitiesDB.Filters.cs b/com.sebaslab.svelto.ecs/Core/Filters/EntitiesDB.Filters.cs index 4a81f21..a66b5c4 100644 --- a/com.sebaslab.svelto.ecs/Core/Filters/EntitiesDB.Filters.cs +++ b/com.sebaslab.svelto.ecs/Core/Filters/EntitiesDB.Filters.cs @@ -99,10 +99,10 @@ namespace Svelto.ECS /// Create a persistent filter. Persistent filters are not deleted after each submission, /// however they have a maintenance cost that must be taken into account and will affect /// entities submission performance. + /// Persistent filters keep track of the entities group swaps and they are automatically updated accordingly. /// /// /// - #if UNITY_BURST && UNITY_COLLECTIONS [Unity.Burst.BurstDiscard] //not burst compatible because of ComponentTypeID.id and GetOrAdd callback; #endif diff --git a/com.sebaslab.svelto.ecs/Core/Filters/EntityFilterCollection.cs b/com.sebaslab.svelto.ecs/Core/Filters/EntityFilterCollection.cs index 808c61d..1d959a8 100644 --- a/com.sebaslab.svelto.ecs/Core/Filters/EntityFilterCollection.cs +++ b/com.sebaslab.svelto.ecs/Core/Filters/EntityFilterCollection.cs @@ -7,6 +7,13 @@ using Svelto.ECS.Native; namespace Svelto.ECS { + /// + /// note: the whole Svelto ECS framework is based on the assumption that the NB and MB structures are ref + /// since we are holding them inside jobs, we decided to not mark them as ref but the pointers inside must be constant! + /// This works because NB and MB data wrapped can be changed only inside a submission which is not jobifiable + /// however filters can be modified inside jobs which means that data can change asynchronously. For this reason filters should be + /// always queries inside jobs when these are used. + /// public readonly struct EntityFilterCollection { internal EntityFilterCollection(CombinedFilterID combinedFilterId, @@ -22,25 +29,72 @@ namespace Svelto.ECS public EntityFilterIterator GetEnumerator() => new EntityFilterIterator(this); + /// + /// Add a new entity to this filter + /// This method assumes that the entity has already been submitted in the database, so it can be found through the EGIDMapper + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool Add(EGID egid, NativeEGIDMapper mmap) where T : unmanaged, _IInternalEntityComponent + public void Add(EGID egid, NativeEGIDMapper mmap) where T : unmanaged, _IInternalEntityComponent { DBC.ECS.Check.Require(mmap.groupID == egid.groupID, "not compatible NativeEgidMapper used"); - return Add(egid, mmap.GetIndex(egid.entityID)); + Add(egid, mmap.GetIndex(egid.entityID)); } - public bool Add(EGID egid, NativeEGIDMultiMapper mmap) where T : unmanaged, _IInternalEntityComponent + /// + /// Add a new entity to this filter + /// This method assumes that the entity has already been submitted in the database, so it can be found through the EGIDMapper + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Add(EGID egid, NativeEGIDMultiMapper mmap) where T : unmanaged, _IInternalEntityComponent + { + Add(egid, mmap.GetIndex(egid)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Add(EGID egid, EGIDMapper mmap) where T : unmanaged, _IInternalEntityComponent { - return Add(egid, mmap.GetIndex(egid)); + DBC.ECS.Check.Require(mmap.groupID == egid.groupID, "not compatible NativeEgidMapper used"); + + Add(egid, mmap.GetIndex(egid.entityID)); } + /// + /// Add a new entity to this filter + /// This method assumes that the entity has already been submitted in the database, so it can be found through the EGIDMapper + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool Add(EGID egid, uint indexInComponentArray) + public void Add(EGID egid, EGIDMultiMapper mmap) where T : unmanaged, _IInternalEntityComponent { - return GetOrCreateGroupFilter(egid.groupID).Add(egid.entityID, indexInComponentArray); + Add(egid, mmap.GetIndex(egid)); } - + + /// + /// Add a new entity to this filter + /// If the user knows at which position the entity is stored in the database, it can be passed directly to the filter + /// This position can be assumed if the user knows how Svelto stores components internally, but it's only guaranteed when passed back from the + /// Svelto callbacks, like the IReactOnAddEx.Add callbacks. Exploting IReactOnAddEx is the only reccomended pattern to use together with this + /// method. If the wrong index is passed, the filter will have undefined behaviour + /// + /// the entity EGID + /// the position in the Svelto database array where the component is stored + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Add(EGID egid, uint indexInComponentArray) + { + GetOrCreateGroupFilter(egid.groupID).Add(egid.entityID, indexInComponentArray); + } + + /// + /// Add a new entity to this filter + /// If the user knows at which position the entity is stored in the database, it can be passed directly to the filter + /// This position can be assumed if the user knows how Svelto stores components internally, but it's only guaranteed when passed back from the + /// Svelto callbacks, like the IReactOnAddEx.Add callbacks. Exploting IReactOnAddEx is the only reccomended pattern to use together with this + /// method. If the wrong index is passed, the filter will have undefined behaviour + /// + /// the entity EGID + /// the position in the Svelto database array where the component is stored + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Add(uint entityID, ExclusiveGroupStruct groupId, uint indexInComponentArray) { @@ -122,14 +176,16 @@ namespace Svelto.ECS internal int groupCount => _filtersPerGroup.count; - public void ComputeFinalCount(out int count) + public int ComputeFinalCount() { - count = 0; + int count = 0; for (int i = 0; i < _filtersPerGroup.count; i++) { count += (int)GetGroup(i).count; } + + return count; } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -161,21 +217,19 @@ namespace Svelto.ECS } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool Add(uint entityId, uint indexInComponentArray) + public void Add(uint entityId, uint indexInComponentArray) { + //TODO: check what happens (unit test) if adding the same entityID twice //TODO: when sentinels are finished, we need to add AsWriter here //cannot write in parallel - return _entityIDToDenseIndex.TryAdd(entityId, indexInComponentArray, out _); + _entityIDToDenseIndex.Add(entityId, indexInComponentArray); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool Exists(uint entityId) => _entityIDToDenseIndex.ContainsKey(entityId); [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Remove(uint entityId) - { - _entityIDToDenseIndex.Remove(entityId); - } + public bool Remove(uint entityId) => _entityIDToDenseIndex.Remove(entityId); public EntityFilterIndices indices { diff --git a/com.sebaslab.svelto.ecs/Core/Filters/EntityFilterIndices.cs b/com.sebaslab.svelto.ecs/Core/Filters/EntityFilterIndices.cs index 7762a8c..f5c4e03 100644 --- a/com.sebaslab.svelto.ecs/Core/Filters/EntityFilterIndices.cs +++ b/com.sebaslab.svelto.ecs/Core/Filters/EntityFilterIndices.cs @@ -4,6 +4,8 @@ using Svelto.DataStructures; namespace Svelto.ECS { + //these are not meant to be held or passed by parameter. + //however cannot be ref struct because of DOTS lambda design for entity queries Entities.ForEach public struct EntityFilterIndices { public EntityFilterIndices(NB indices, uint count) @@ -13,10 +15,10 @@ namespace Svelto.ECS _index = 0; } - public uint count + public int count { [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => _count; + get => (int)_count; } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -40,7 +42,7 @@ namespace Svelto.ECS return _indices[Interlocked.Increment(ref _index) - 1]; } - readonly NB _indices; + readonly NBInternal _indices; readonly uint _count; int _index; } diff --git a/com.sebaslab.svelto.ecs/Core/Filters/EntityFilterIterator.cs b/com.sebaslab.svelto.ecs/Core/Filters/EntityFilterIterator.cs index 146c281..63736c6 100644 --- a/com.sebaslab.svelto.ecs/Core/Filters/EntityFilterIterator.cs +++ b/com.sebaslab.svelto.ecs/Core/Filters/EntityFilterIterator.cs @@ -2,11 +2,11 @@ { public ref struct EntityFilterIterator { - internal EntityFilterIterator(EntityFilterCollection filter) + internal EntityFilterIterator(EntityFilterCollection filter) { - _filter = filter; + _filter = filter; _indexGroup = -1; - _current = default; + _current = default; } public bool MoveNext() @@ -15,7 +15,8 @@ { _current = _filter.GetGroup(_indexGroup); - if (_current.count > 0) break; + if (_current.count > 0 && _current.group.IsEnabled() == true) + break; } return _indexGroup < _filter.groupCount; @@ -28,8 +29,8 @@ public RefCurrent Current => new RefCurrent(_current); - int _indexGroup; - readonly EntityFilterCollection _filter; + int _indexGroup; + readonly EntityFilterCollection _filter; EntityFilterCollection.GroupFilters _current; public readonly ref struct RefCurrent @@ -42,7 +43,15 @@ public void Deconstruct(out EntityFilterIndices indices, out ExclusiveGroupStruct group) { indices = _filter.indices; - group = _filter.group; + group = _filter.group; + } + + public void Deconstruct(out EntityFilterIndices indices, out ExclusiveGroupStruct group, + out EntityFilterCollection.GroupFilters groupFilter) + { + indices = _filter.indices; + group = _filter.group; + groupFilter = _filter; } readonly EntityFilterCollection.GroupFilters _filter; diff --git a/com.sebaslab.svelto.ecs/Core/Groups/EntityDescriptorsWarmup.cs b/com.sebaslab.svelto.ecs/Core/Groups/EntityDescriptorsWarmup.cs index 9e60153..a022a08 100644 --- a/com.sebaslab.svelto.ecs/Core/Groups/EntityDescriptorsWarmup.cs +++ b/com.sebaslab.svelto.ecs/Core/Groups/EntityDescriptorsWarmup.cs @@ -20,7 +20,7 @@ namespace Svelto.ECS foreach (Type type in AssemblyUtility.GetTypesSafe(assembly)) { - if (type.IsInterface == false && typeOfEntityDescriptors.IsAssignableFrom(type)) //IsClass and IsSealed and IsAbstract means only static classes + if (type.IsInterface == false && type.IsAbstract == false && type.IsGenericType == false && typeOfEntityDescriptors.IsAssignableFrom(type)) { try { diff --git a/com.sebaslab.svelto.ecs/Core/Groups/ExclusiveBuildGroup.cs b/com.sebaslab.svelto.ecs/Core/Groups/ExclusiveBuildGroup.cs index bbacbc6..054108c 100644 --- a/com.sebaslab.svelto.ecs/Core/Groups/ExclusiveBuildGroup.cs +++ b/com.sebaslab.svelto.ecs/Core/Groups/ExclusiveBuildGroup.cs @@ -1,20 +1,23 @@ +using System.Runtime.CompilerServices; + namespace Svelto.ECS { public readonly struct ExclusiveBuildGroup { - internal ExclusiveBuildGroup(ExclusiveGroupStruct group) + internal ExclusiveBuildGroup(ExclusiveGroupStruct group, ushort range) { + _range = range; this.group = group; } public static implicit operator ExclusiveBuildGroup(ExclusiveGroupStruct group) { - return new ExclusiveBuildGroup(group); + return new ExclusiveBuildGroup(group, 0); } public static implicit operator ExclusiveBuildGroup(ExclusiveGroup group) { - return new ExclusiveBuildGroup(group); + return new ExclusiveBuildGroup(group, 0); } public static implicit operator ExclusiveGroupStruct(ExclusiveBuildGroup group) @@ -22,12 +25,30 @@ namespace Svelto.ECS return group.group; } + public static ExclusiveGroupStruct operator +(ExclusiveBuildGroup c1, uint c2) + { + DBC.ECS.Check.Require(c2 < c1._range, $"group out of range, {c2} max range is {c1._range}"); + + return c1.group + c2; + } + public override string ToString() { return group.ToName(); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal uint Offset(ExclusiveGroupStruct exclusiveGroupStruct) + { + DBC.ECS.Check.Require((exclusiveGroupStruct.id - group.id) < _range, "group out of range"); + var offset = (uint)exclusiveGroupStruct.id - (uint)group.id; + return offset; + } + internal ExclusiveGroupStruct @group { get; } + + readonly ushort _range; + public bool isInvalid => group == ExclusiveGroupStruct.Invalid; } } \ No newline at end of file diff --git a/com.sebaslab.svelto.ecs/Core/Groups/ExclusiveGroup.cs b/com.sebaslab.svelto.ecs/Core/Groups/ExclusiveGroup.cs index a5287a0..bb3527f 100644 --- a/com.sebaslab.svelto.ecs/Core/Groups/ExclusiveGroup.cs +++ b/com.sebaslab.svelto.ecs/Core/Groups/ExclusiveGroup.cs @@ -46,6 +46,14 @@ namespace Svelto.ECS _range = range; #endif } + + public ExclusiveGroup(ushort range, ExclusiveGroupBitmask bitmask) + { + _group = ExclusiveGroupStruct.GenerateWithRange(range, (byte)bitmask); +#if DEBUG && !PROFILE_SVELTO + _range = range; +#endif + } public static implicit operator ExclusiveGroupStruct(ExclusiveGroup group) { @@ -62,10 +70,10 @@ namespace Svelto.ECS #endif return group._group + b; } - + public uint id => _group.id; - //todo document the use case for this method + //todo document the use case for this method. I may honestly set this as a deprecated as it's original scenario is probably not valid anymore public static ExclusiveGroupStruct Search(string holderGroupName) { if (_knownGroups.ContainsKey(holderGroupName) == false) diff --git a/com.sebaslab.svelto.ecs/Core/Groups/ExclusiveGroupStruct.cs b/com.sebaslab.svelto.ecs/Core/Groups/ExclusiveGroupStruct.cs index 5ed895f..0e82393 100644 --- a/com.sebaslab.svelto.ecs/Core/Groups/ExclusiveGroupStruct.cs +++ b/com.sebaslab.svelto.ecs/Core/Groups/ExclusiveGroupStruct.cs @@ -121,7 +121,18 @@ namespace Svelto.ECS { var newValue = Interlocked.Add(ref _staticGlobalID, (int)range); - ExclusiveGroupStruct groupStruct = new ExclusiveGroupStruct((uint) newValue - (uint)range); + ExclusiveGroupStruct groupStruct = new ExclusiveGroupStruct((uint)newValue - (uint)range); + + DBC.ECS.Check.Require(_globalId < ExclusiveGroup.MaxNumberOfExclusiveGroups, "too many exclusive groups created"); + + return groupStruct; + } + + public static ExclusiveGroupStruct GenerateWithRange(ushort range, byte bitmask) + { + var newValue = Interlocked.Add(ref _staticGlobalID, (int)range); + + ExclusiveGroupStruct groupStruct = new ExclusiveGroupStruct((uint)newValue - (uint)range, bitmask); DBC.ECS.Check.Require(_globalId < ExclusiveGroup.MaxNumberOfExclusiveGroups, "too many exclusive groups created"); diff --git a/com.sebaslab.svelto.ecs/Core/Groups/GroupCompound.cs b/com.sebaslab.svelto.ecs/Core/Groups/GroupCompound.cs index 9946316..ee355aa 100644 --- a/com.sebaslab.svelto.ecs/Core/Groups/GroupCompound.cs +++ b/com.sebaslab.svelto.ecs/Core/Groups/GroupCompound.cs @@ -1,4 +1,6 @@ +using System; using System.Collections.Generic; +using System.Runtime.CompilerServices; using System.Threading; using Svelto.DataStructures; @@ -9,6 +11,9 @@ namespace Svelto.ECS /// point to the same group ID. /// A group compound can generate several permutation of tags, so that the order of the tag doesn't matter, /// but for this to work, each permutation must be identified by the same ID (generated by the unique combination) + /// each permutation of the same groups of tag is actually a different class with a different static constructor and we + /// don't want to call it more than once for the same set of tags + /// it's thread local because since it's not linked to a specific group, it must work synchronously /// static class GroupCompoundInitializer { @@ -27,22 +32,16 @@ namespace Svelto.ECS { static GroupCompound() { - //avoid race conditions if compounds are using on multiple thread. This shouldn't be necessary though since c# static constructors are guaranteed to be thread safe! - /// c# Static constructors are guaranteed to be thread safe - /// The runtime guarantees that a static constructor is only called once. So even if a type is called by multiple threads at the same time, - /// the static constructor is always executed one time. To get a better understanding how this works, it helps to know what purpose it serves. - if (Interlocked.CompareExchange(ref isInitializing, 1, 0) == 0 && - GroupCompoundInitializer.skipStaticCompoundConstructorsWith4Tags.Value == false) + /// c# Static constructors are guaranteed to be thread safe and not called more than once + if (Interlocked.CompareExchange(ref isInitialised, 1, 0) != 0) + throw new Exception("GroupCompound static constructor called twice - impossible"); + + if (GroupCompoundInitializer.skipStaticCompoundConstructorsWith4Tags.Value == false) { - System.Runtime.CompilerServices.RuntimeHelpers.RunClassConstructor( - typeof(GroupCompound).TypeHandle); - var group = new ExclusiveGroup( - GroupTag.bitmask | GroupTag.bitmask | GroupTag.bitmask | GroupTag.bitmask - | bitmask); + var group = new ExclusiveGroup(GroupTag.bitmask | GroupTag.bitmask | GroupTag.bitmask | GroupTag.bitmask); _Groups = new FasterList(1); _Groups.Add(group); - #if DEBUG var name = $"Compound: {typeof(G1).Name}-{typeof(G2).Name}-{typeof(G3).Name}-{typeof(G4).Name} ID {(uint)group.id}"; @@ -54,7 +53,13 @@ namespace Svelto.ECS //ToArrayFast is theoretically not correct, but since multiple 0s are ignored and we don't care if we //add one, we avoid an allocation - _GroupsHashSet = new HashSet(_Groups.ToArrayFast(out _)); + var exclusiveGroupStructs = _Groups.ToArrayFast(out var count); + _GroupsHashSet = new HashSet(count); + for (var index = 0; index < count; ++index) + { + var exclusiveGroupStruct = exclusiveGroupStructs[index]; + _GroupsHashSet.Add(exclusiveGroupStruct); + } GroupCompoundInitializer.skipStaticCompoundConstructorsWith4Tags.Value = true; @@ -84,7 +89,7 @@ namespace Svelto.ECS GroupCompound._Groups = _Groups; GroupCompound._Groups = _Groups; - //all the permutations are warmed up now + //all the constructor have been called now GroupCompoundInitializer.skipStaticCompoundConstructorsWith4Tags.Value = false; GroupCompound._GroupsHashSet = _GroupsHashSet; @@ -110,7 +115,7 @@ namespace Svelto.ECS GroupCompound._GroupsHashSet = _GroupsHashSet; GroupCompound._GroupsHashSet = _GroupsHashSet; GroupCompound._GroupsHashSet = _GroupsHashSet; - + GroupCompound.Add(group); GroupCompound.Add(group); GroupCompound.Add(group); @@ -132,11 +137,19 @@ namespace Svelto.ECS } } - public static FasterReadOnlyList Groups => - new FasterReadOnlyList(_Groups); + public static FasterReadOnlyList Groups + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => new(_Groups); + } - public static ExclusiveBuildGroup BuildGroup => new ExclusiveBuildGroup(_Groups[0]); + public static ExclusiveBuildGroup BuildGroup + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => new(_Groups[0], 1); + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool Includes(ExclusiveGroupStruct group) { DBC.ECS.Check.Require(group != ExclusiveGroupStruct.Invalid, "invalid group passed"); @@ -160,8 +173,7 @@ namespace Svelto.ECS static readonly HashSet _GroupsHashSet; //we are changing this with Interlocked, so it cannot be readonly - static int isInitializing; - protected internal static ExclusiveGroupBitmask bitmask; + static int isInitialised; } public abstract class GroupCompound: ITouchedByReflection @@ -171,14 +183,13 @@ namespace Svelto.ECS { static GroupCompound() { - if (Interlocked.CompareExchange(ref isInitializing, 1, 0) == 0 && - GroupCompoundInitializer.skipStaticCompoundConstructorsWith3Tags.Value == false) + /// c# Static constructors are guaranteed to be thread safe and not called more than once + if (Interlocked.CompareExchange(ref isInitializing, 1, 0) != 0) + throw new Exception("GroupCompound static constructor called twice - impossible"); + + if (GroupCompoundInitializer.skipStaticCompoundConstructorsWith3Tags.Value == false) { - System.Runtime.CompilerServices.RuntimeHelpers.RunClassConstructor( - typeof(GroupCompound).TypeHandle); - - var group = new ExclusiveGroup( - GroupTag.bitmask | GroupTag.bitmask | GroupTag.bitmask | bitmask); + var group = new ExclusiveGroup(GroupTag.bitmask | GroupTag.bitmask | GroupTag.bitmask); _Groups = new FasterList(1); _Groups.Add(group); @@ -191,7 +202,13 @@ namespace Svelto.ECS //guarantees the hash to be the same across different machines GroupHashMap.RegisterGroup(group, typeof(GroupCompound).FullName); - _GroupsHashSet = new HashSet(_Groups.ToArrayFast(out _)); + var exclusiveGroupStructs = _Groups.ToArrayFast(out var count); + _GroupsHashSet = new HashSet(count); + for (var index = 0; index < count; ++index) + { + var exclusiveGroupStruct = exclusiveGroupStructs[index]; + _GroupsHashSet.Add(exclusiveGroupStruct); + } GroupCompoundInitializer.skipStaticCompoundConstructorsWith3Tags.Value = true; @@ -210,7 +227,7 @@ namespace Svelto.ECS GroupCompound._GroupsHashSet = _GroupsHashSet; GroupCompound._GroupsHashSet = _GroupsHashSet; GroupCompound._GroupsHashSet = _GroupsHashSet; - + GroupCompound.Add(group); // and must share the same array GroupCompound.Add(group); GroupCompound.Add(group); @@ -223,11 +240,19 @@ namespace Svelto.ECS } } - public static FasterReadOnlyList Groups => - new FasterReadOnlyList(_Groups); + public static FasterReadOnlyList Groups + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => new(_Groups); + } - public static ExclusiveBuildGroup BuildGroup => new ExclusiveBuildGroup(_Groups[0]); + public static ExclusiveBuildGroup BuildGroup + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => new(_Groups[0], 1); + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool Includes(ExclusiveGroupStruct group) { DBC.ECS.Check.Require(group != ExclusiveGroupStruct.Invalid, "invalid group passed"); @@ -252,7 +277,6 @@ namespace Svelto.ECS //we are changing this with Interlocked, so it cannot be readonly static int isInitializing; - protected internal static ExclusiveGroupBitmask bitmask; } public abstract class GroupCompound: ITouchedByReflection @@ -261,44 +285,75 @@ namespace Svelto.ECS { static GroupCompound() { - if (Interlocked.CompareExchange(ref isInitializing, 1, 0) == 0 && - GroupCompoundInitializer.skipStaticCompoundConstructorsWith2Tags.Value == false) + /// c# Static constructors are guaranteed to be thread safe and not called more than once + if (Interlocked.CompareExchange(ref isInitializing, 1, 0) != 0) + throw new Exception($"{typeof(GroupCompound).FullName} GroupCompound static constructor called twice - impossible"); + + if (GroupCompoundInitializer.skipStaticCompoundConstructorsWith2Tags.Value == false) { - System.Runtime.CompilerServices.RuntimeHelpers.RunClassConstructor( - typeof(GroupCompound).TypeHandle); - - var group = new ExclusiveGroup(GroupTag.bitmask | GroupTag.bitmask | bitmask); + range = GroupTag.range > GroupTag.range ? GroupTag.range : GroupTag.range; - _Groups = new FasterList(1); - _Groups.Add(group); + var initialSize = range; + _Groups = new FasterList(initialSize); + var group = new ExclusiveGroup(initialSize, GroupTag.bitmask | GroupTag.bitmask); + #if DEBUG - GroupNamesMap.idToName[group] = $"Compound: {typeof(G1).Name}-{typeof(G2).Name} ID {group.id}"; + for (uint i = 0; i < initialSize; ++i) + { + var groupID = group.id + i; + var name = $"Compound: {typeof(G1).Name}-{typeof(G2).Name} ID {groupID}"; + + GroupNamesMap.idToName[group + i] = name; + } #endif - //The hashname is independent from the actual group ID. this is fundamental because it is want - //guarantees the hash to be the same across different machines - GroupHashMap.RegisterGroup(group, typeof(GroupCompound).FullName); - _GroupsHashSet = new HashSet(_Groups.ToArrayFast(out _)); + for (uint i = 0; i < initialSize; ++i) + { + var exclusiveGroupStruct = group + i; + + //The hashname is independent from the actual group ID. this is fundamental because it is want + //guarantees the hash to be the same across different machines + GroupHashMap.RegisterGroup(exclusiveGroupStruct, typeof(GroupCompound).FullName + i); + + _Groups.Add(exclusiveGroupStruct); + //every abstract group preemptively adds this group, it may or may not be empty in future + GroupTag.Add(exclusiveGroupStruct); + GroupTag.Add(exclusiveGroupStruct); + } + + var exclusiveGroupStructs = _Groups.ToArrayFast(out var count); + _GroupsHashSet = new HashSet(count); + for (var index = 0; index < count; index++) + { + var exclusiveGroupStruct = exclusiveGroupStructs[index]; + _GroupsHashSet.Add(exclusiveGroupStruct); + } GroupCompoundInitializer.skipStaticCompoundConstructorsWith2Tags.Value = true; GroupCompound._Groups = _Groups; + //all the constructor have been called now GroupCompoundInitializer.skipStaticCompoundConstructorsWith2Tags.Value = false; - + + GroupCompound.range = initialSize; GroupCompound._GroupsHashSet = _GroupsHashSet; - - //every abstract group preemptively adds this group, it may or may not be empty in future - GroupTag.Add(group); - GroupTag.Add(group); } } - public static FasterReadOnlyList Groups => - new FasterReadOnlyList(_Groups); + public static FasterReadOnlyList Groups + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => new(_Groups); + } - public static ExclusiveBuildGroup BuildGroup => new ExclusiveBuildGroup(_Groups[0]); + public static ExclusiveBuildGroup BuildGroup + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => new(_Groups[0], range); + } //TODO there is an overlap between this method and ExclusiveGroupExtensions FoundIn + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool Includes(ExclusiveGroupStruct group) { DBC.ECS.Check.Require(group != ExclusiveGroupStruct.Invalid, "invalid group passed"); @@ -318,65 +373,92 @@ namespace Svelto.ECS _GroupsHashSet.Add(group); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static uint Offset(ExclusiveGroupStruct group) + { + return BuildGroup.Offset(group); + } + static readonly FasterList _Groups; static readonly HashSet _GroupsHashSet; + static ushort range; + static int isInitializing; - protected internal static ExclusiveGroupBitmask bitmask; } /// /// 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. + /// can use the same adjective together with other ones. Albeit since I need to be able to iterate over all the + /// groups with the same adjective, a group tag needs to hold all the group compounds using it. /// /// - public abstract class GroupTag: ITouchedByReflection - where T : GroupTag + public abstract class GroupTag: ITouchedByReflection where T : GroupTag { static GroupTag() { - if (Interlocked.CompareExchange(ref isInitializing, 1, 0) == 0) + if (Interlocked.CompareExchange(ref isInitializing, 1, 0) != 0) + throw new Exception("GroupTag static constructor called twice - impossible"); + + //GroupTag can set values for ranges and bitmasks in their static constructors so they must be called first + //there is no other way around this, as the base static constructor will be called once base fields are touched + //RuntimeHelpers.RunClassConstructor(typeof(T).TypeHandle); + typeof(T).TypeInitializer?.Invoke(null, null); //must use this because if a specialised GroupTag is called first will call the this constructor before having the chance to initialise the protected values. This will force to initialise the values no matter what (and won't call the base constructor again because already executing) + + var initialRange = range; //range may be overriden by the constructor previously called + + if (initialRange == 0) //means never initialised by a inherited static constructor { - //Allow to call GroupTag static constructors like -// public class Dead: GroupTag -// { -// static Dead() -// { -// bitmask = ExclusiveGroupBitmask.DISABLED_BIT; -// } -// }; - System.Runtime.CompilerServices.RuntimeHelpers.RunClassConstructor(typeof(T).TypeHandle); - - var group = new ExclusiveGroup(bitmask); - _Groups.Add(group); + initialRange = 1; + range = 1; + } + var group = new ExclusiveGroup(initialRange, bitmask); + for (uint i = 0; i < initialRange; ++i) + { + _Groups.Add(group + i); + //The hashname is independent from the actual group ID. this is fundamental because it is want + //guarantees the hash to be the same across different machines + GroupHashMap.RegisterGroup(group + i, typeof(GroupTag).FullName + i); + } #if DEBUG - var typeInfo = typeof(T); - var name = $"Compound: {typeInfo.Name} ID {(uint)group.id}"; -#if !PROFILE_SVELTO + var typeInfo = typeof(T); + for (uint i = 0; i < initialRange; ++i) + { + var groupID = group.id + i; + var name = $"Compound: {typeInfo.Name} ID {groupID}"; + var typeInfoBaseType = typeInfo.BaseType; - if (typeInfoBaseType.GenericTypeArguments[0] != - typeInfo) //todo: this should shield from using a pattern different than public class GROUP_NAME : GroupTag {} however I am not sure it's working + //todo: this should shield from using a pattern different than public class GROUP_NAME : GroupTag {} however I am not sure it's working + if (typeInfoBaseType.GenericTypeArguments[0] != typeInfo) throw new ECSException("Invalid Group Tag declared"); -#endif - GroupNamesMap.idToName[group] = name; + GroupNamesMap.idToName[group + i] = name; + } #endif - //The hashname is independent from the actual group ID. this is fundamental because it is want - //guarantees the hash to be the same across different machines - GroupHashMap.RegisterGroup(group, typeof(GroupTag).FullName); - - _GroupsHashSet = new HashSet(_Groups.ToArrayFast(out _)); + var exclusiveGroupStructs = _Groups.ToArrayFast(out var count); + _GroupsHashSet = new HashSet(count); + for (var index = 0; index < count; index++) + { + var exclusiveGroupStruct = exclusiveGroupStructs[index]; + _GroupsHashSet.Add(exclusiveGroupStruct); } } - public static FasterReadOnlyList Groups => - new FasterReadOnlyList(_Groups); + public static FasterReadOnlyList Groups + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => new(_Groups); + } - public static ExclusiveBuildGroup BuildGroup => new ExclusiveBuildGroup(_Groups[0]); + public static ExclusiveBuildGroup BuildGroup + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => new(_Groups[0], range); + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool Includes(ExclusiveGroupStruct group) { DBC.ECS.Check.Require(group != ExclusiveGroupStruct.Invalid, "invalid group passed"); @@ -397,11 +479,40 @@ namespace Svelto.ECS _GroupsHashSet.Add(group); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static uint Offset(ExclusiveGroupStruct group) + { + return BuildGroup.Offset(group); + } + static readonly FasterList _Groups = new FasterList(1); static readonly HashSet _GroupsHashSet; //we are changing this with Interlocked, so it cannot be readonly static int isInitializing; + + //special group attributes, at the moment of writing this comment, only the disabled group has a special attribute + + //Allow to call GroupTag static constructors like +// public class Dead: GroupTag +// { +// static Dead() +// { +// bitmask = ExclusiveGroupBitmask.DISABLED_BIT; +// } +// }; + protected internal static ExclusiveGroupBitmask bitmask; + //set a number different than 0 to create a range of groups instead of a single group + //example of usage: +// public class VehicleGroup:GroupTag +// { +// static VehicleGroup() +// { +// range = (ushort)Data.MaxTeamCount; +// } +// } + + protected internal static ushort range; } } \ No newline at end of file diff --git a/com.sebaslab.svelto.ecs/Core/Groups/GroupHashMap.cs b/com.sebaslab.svelto.ecs/Core/Groups/GroupHashMap.cs index bac7438..3bd4896 100644 --- a/com.sebaslab.svelto.ecs/Core/Groups/GroupHashMap.cs +++ b/com.sebaslab.svelto.ecs/Core/Groups/GroupHashMap.cs @@ -1,6 +1,8 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Reflection; +using System.Runtime.CompilerServices; using System.Text; using Svelto.ECS.Serialization; @@ -20,78 +22,69 @@ namespace Svelto.ECS List assemblies = AssemblyUtility.GetCompatibleAssemblies(); foreach (Assembly assembly in assemblies) { - try + var typeOfExclusiveGroup = typeof(ExclusiveGroup); + var typeOfExclusiveGroupStruct = typeof(ExclusiveGroupStruct); + var typeOfExclusiveBuildGroup = typeof(ExclusiveBuildGroup); + + var typesSafe = AssemblyUtility.GetTypesSafe(assembly); + foreach (Type type in typesSafe) { - var typeOfExclusiveGroup = typeof(ExclusiveGroup); - var typeOfExclusiveGroupStruct = typeof(ExclusiveGroupStruct); - var typeOfExclusiveBuildGroup = typeof(ExclusiveBuildGroup); + CheckForGroupCompounds(type); - foreach (Type type in AssemblyUtility.GetTypesSafe(assembly)) + //Search inside static types + if (type != null && type.IsClass && type.IsSealed + && type.IsAbstract) //IsClass and IsSealed and IsAbstract means only static classes { - CheckForGroupCompounds(type); + var subClasses = type.GetNestedTypes(); - //Search inside static types - if (type != null && type.IsClass && type.IsSealed && type.IsAbstract) //IsClass and IsSealed and IsAbstract means only static classes + foreach (var subclass in subClasses) { - var subClasses = type.GetNestedTypes(); + CheckForGroupCompounds(subclass); + } + + var fields = type.GetFields(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); - foreach (var subclass in subClasses) + foreach (var field in fields) + { + if ((typeOfExclusiveGroup.IsAssignableFrom(field.FieldType) + || typeOfExclusiveGroupStruct.IsAssignableFrom(field.FieldType) + || typeOfExclusiveBuildGroup.IsAssignableFrom(field.FieldType))) { - CheckForGroupCompounds(subclass); - } + uint groupIDAndBitMask; - var fields = type.GetFields(); + if (typeOfExclusiveGroup.IsAssignableFrom(field.FieldType)) + { + var group = (ExclusiveGroup)field.GetValue(null); + groupIDAndBitMask = ((ExclusiveGroupStruct)@group).ToIDAndBitmask(); + } + else if (typeOfExclusiveGroupStruct.IsAssignableFrom(field.FieldType)) + { + var group = (ExclusiveGroupStruct)field.GetValue(null); + groupIDAndBitMask = @group.ToIDAndBitmask(); + } + else + { + var group = (ExclusiveBuildGroup)field.GetValue(null); + groupIDAndBitMask = ((ExclusiveGroupStruct)@group).ToIDAndBitmask(); + } - foreach (var field in fields) - { - if (field.IsStatic - && (typeOfExclusiveGroup.IsAssignableFrom(field.FieldType) - || typeOfExclusiveGroupStruct.IsAssignableFrom(field.FieldType) - || typeOfExclusiveBuildGroup.IsAssignableFrom(field.FieldType))) { - uint groupIDAndBitMask; - - if (typeOfExclusiveGroup.IsAssignableFrom(field.FieldType)) - { - var group = (ExclusiveGroup)field.GetValue(null); - groupIDAndBitMask = ((ExclusiveGroupStruct)@group).ToIDAndBitmask(); - } - else - if (typeOfExclusiveGroupStruct.IsAssignableFrom(field.FieldType)) - { - var group = (ExclusiveGroupStruct)field.GetValue(null); - groupIDAndBitMask = @group.ToIDAndBitmask(); - } - else - { - var group = (ExclusiveBuildGroup)field.GetValue(null); - groupIDAndBitMask = ((ExclusiveGroupStruct)@group).ToIDAndBitmask(); - } - - { - var bitMask = (byte)(groupIDAndBitMask >> 24); - var groupID = groupIDAndBitMask & 0xFFFFFF; - ExclusiveGroupStruct group = new ExclusiveGroupStruct(groupID, bitMask); + var bitMask = (byte)(groupIDAndBitMask >> 24); + var groupID = groupIDAndBitMask & 0xFFFFFF; + ExclusiveGroupStruct group = new ExclusiveGroupStruct(groupID, bitMask); #if DEBUG && !PROFILE_SVELTO if (GroupNamesMap.idToName.ContainsKey(@group) == false) GroupNamesMap.idToName[@group] = $"{type.FullName}.{field.Name} {@group.id})"; #endif - //The hashname is independent from the actual group ID. this is fundamental because it is want - //guarantees the hash to be the same across different machines - RegisterGroup(@group, $"{type.FullName}.{field.Name}"); - } + //The hashname is independent from the actual group ID. this is fundamental because it is want + //guarantees the hash to be the same across different machines + RegisterGroup(@group, $"{type.FullName}.{field.Name}"); } } } } } - catch - { - Console.LogDebugWarning( - "something went wrong while gathering group names on the assembly: ".FastConcat( - assembly.FullName)); - } } } @@ -101,7 +94,29 @@ namespace Svelto.ECS { //this calls the static constructor, but only once. Static constructors won't be called //more than once with this - System.Runtime.CompilerServices.RuntimeHelpers.RunClassConstructor(type.BaseType.TypeHandle); + CallStaticConstructorsRecursively(type); + } + + static void CallStaticConstructorsRecursively(Type type) + { + // Check if the current type has a static constructor +// type.TypeInitializer.Invoke(null, null); //calling Invoke will force the static constructor to be called even if already called, this is a problem because GroupTag and Compound throw an exception if called multiple times + RuntimeHelpers.RunClassConstructor(type.TypeHandle); //this will call the static constructor only once + +#if DEBUG && !PROFILE_SVELTO + if (type.GetInterfaces().Contains(type) == false) + { + if (type.IsSealed == false) + Svelto.Console.LogWarning( + $"Group compound/tag {type} is not sealed. GroupCompounds and Tags cannot be inherited, consider marking it sealed"); + } +#endif + // Recursively check the base types + Type baseType = type.BaseType; + if (baseType != null && baseType != typeof(object)) //second if means we got the the end of the hierarchy + { + CallStaticConstructorsRecursively(baseType); + } } } diff --git a/com.sebaslab.svelto.ecs/Core/Groups/GroupNamesMap.cs b/com.sebaslab.svelto.ecs/Core/Groups/GroupNamesMap.cs index 5df712f..842f74b 100644 --- a/com.sebaslab.svelto.ecs/Core/Groups/GroupNamesMap.cs +++ b/com.sebaslab.svelto.ecs/Core/Groups/GroupNamesMap.cs @@ -7,8 +7,7 @@ static class GroupNamesMap static GroupNamesMap() { idToName = new Dictionary(); } internal static readonly Dictionary idToName; -#endif -#if DEBUG + public static string ToName(this in ExclusiveGroupStruct group) { Dictionary idToName = GroupNamesMap.idToName; diff --git a/com.sebaslab.svelto.ecs/Core/Groups/QueryGroups.cs b/com.sebaslab.svelto.ecs/Core/Groups/QueryGroups.cs index 3ceef7c..755cd4b 100644 --- a/com.sebaslab.svelto.ecs/Core/Groups/QueryGroups.cs +++ b/com.sebaslab.svelto.ecs/Core/Groups/QueryGroups.cs @@ -61,7 +61,6 @@ namespace Svelto.ECS.Experimental HashSet _sets; } - //I am not 100% sure why I made this thread-safe since it cannot be used inside jobs. public ref struct QueryGroups { static readonly ThreadLocal groups; diff --git a/com.sebaslab.svelto.ecs/Core/IDisposingEngine.cs b/com.sebaslab.svelto.ecs/Core/IDisposingEngine.cs index 49045f9..d2ab1c5 100644 --- a/com.sebaslab.svelto.ecs/Core/IDisposingEngine.cs +++ b/com.sebaslab.svelto.ecs/Core/IDisposingEngine.cs @@ -2,7 +2,7 @@ using System; namespace Svelto.ECS { - public interface IDisposingEngine: IDisposable + public interface IDisposableEngine: IDisposable { bool isDisposing { set; } } diff --git a/com.sebaslab.svelto.ecs/Core/IEngine.cs b/com.sebaslab.svelto.ecs/Core/IEngine.cs index c622fc1..d76b666 100644 --- a/com.sebaslab.svelto.ecs/Core/IEngine.cs +++ b/com.sebaslab.svelto.ecs/Core/IEngine.cs @@ -1,12 +1,12 @@ using System; using Svelto.ECS.Internal; -namespace Svelto.ECS.Internal +namespace Svelto.ECS { public interface IReactEngine : IEngine { } -#region legacy interfaces + /// /// This is now considered legacy and it will be deprecated in future /// @@ -27,7 +27,13 @@ namespace Svelto.ECS.Internal public interface IReactOnSwap : IReactEngine { } -#endregion + + /// + /// This is now considered legacy and it will be deprecated in future + /// + public interface IReactOnDispose : IReactEngine + { + } public interface IReactOnAddEx : IReactEngine { @@ -44,10 +50,6 @@ namespace Svelto.ECS.Internal public interface IReactOnDisposeEx : IReactEngine { } - - public interface IReactOnDispose : IReactEngine - { - } } namespace Svelto.ECS @@ -102,7 +104,7 @@ namespace Svelto.ECS ExclusiveGroupStruct groupID); } - [Obsolete] + [Obsolete("Use IReactOnAddEx and IReactOnRemoveEx or IReactOnAddAndRemoveEx instead")] public interface IReactOnAddAndRemove : IReactOnAdd, IReactOnRemove where T : _IInternalEntityComponent { } @@ -112,6 +114,7 @@ namespace Svelto.ECS /// It can work together with IReactOnRemove which normally is not called on enginesroot disposed /// /// + [Obsolete("Use IReactOnDisposeEx instead")] public interface IReactOnDispose : IReactOnDispose where T : _IInternalEntityComponent { void Remove(ref T entityComponent, EGID egid); @@ -121,7 +124,7 @@ namespace Svelto.ECS /// Interface to mark an Engine as reacting to entities swapping group /// /// - [Obsolete] + [Obsolete("Use IReactOnSwapEx instead")] public interface IReactOnSwap : IReactOnSwap where T : _IInternalEntityComponent { void MovedTo(ref T entityComponent, ExclusiveGroupStruct previousGroup, EGID egid); diff --git a/com.sebaslab.svelto.ecs/Core/IEntityFunctions.cs b/com.sebaslab.svelto.ecs/Core/IEntityFunctions.cs index f762fda..ba08c40 100644 --- a/com.sebaslab.svelto.ecs/Core/IEntityFunctions.cs +++ b/com.sebaslab.svelto.ecs/Core/IEntityFunctions.cs @@ -4,22 +4,33 @@ namespace Svelto.ECS { public interface IEntityFunctions { - //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, ExclusiveBuildGroup groupID , [CallerMemberName] string caller = null) where T : IEntityDescriptor, new(); - void RemoveEntity(EGID entityegid , [CallerMemberName] string caller = null) where T : IEntityDescriptor, new(); + /// + /// Remove an entity from the database. Since Svelto.ECS 3.5 Removal operation behaves like this: + /// * Remove supersedes a previous Remove operation on the same submission frame + /// * Remove supersedes a previous Swap operation on the same submission frame if the egid is used a origin (fromEGID) - similar to the remove case + /// * Remove throws an exception if a Build operation with the same egid is done on the same submission frame + /// * Remove throws an exception if called on an EGID used as destination (toEGID) for a swap - similare to the build case + /// + void RemoveEntity(uint entityID, ExclusiveBuildGroup groupID, [CallerMemberName] string caller = null) where T : IEntityDescriptor, new(); + void RemoveEntity(EGID entityegid, [CallerMemberName] string caller = null) where T : IEntityDescriptor, new(); + + /// + /// Swap an entity between groups (subset of entities). Only one structural operation per submission frame is allowed. + /// + void SwapEntityGroup(uint entityID, ExclusiveBuildGroup fromGroupID, ExclusiveBuildGroup toGroupID, + [CallerMemberName] string caller = null) where T : IEntityDescriptor, new(); + void SwapEntityGroup(EGID fromEGID, ExclusiveBuildGroup toGroupID, [CallerMemberName] string caller = null) + where T : IEntityDescriptor, new(); + void SwapEntityGroup(EGID fromEGID, EGID toEGID, [CallerMemberName] string caller = null) where T : IEntityDescriptor, new(); + void SwapEntityGroup(EGID fromEGID, EGID toEGID, ExclusiveBuildGroup mustBeFromGroup, [CallerMemberName] string caller = null) + where T : IEntityDescriptor, new(); - void RemoveEntitiesFromGroup(ExclusiveBuildGroup groupID , [CallerMemberName] string caller = null); + void RemoveEntitiesFromGroup(ExclusiveBuildGroup groupID, [CallerMemberName] string caller = null); void SwapEntitiesInGroup(ExclusiveBuildGroup fromGroupID, ExclusiveBuildGroup toGroupID, [CallerMemberName] string caller = null); - void SwapEntityGroup(uint entityID, ExclusiveBuildGroup fromGroupID, ExclusiveBuildGroup toGroupID, [CallerMemberName] string caller = null) where T : IEntityDescriptor, new(); - void SwapEntityGroup(EGID fromEGID, ExclusiveBuildGroup toGroupID, [CallerMemberName] string caller = null) where T : IEntityDescriptor, new(); - void SwapEntityGroup(EGID fromEGID, EGID toEGID, [CallerMemberName] string caller = null)where T : IEntityDescriptor, new(); - void SwapEntityGroup(EGID fromEGID, EGID toEGID, ExclusiveBuildGroup mustBeFromGroup, [CallerMemberName] string caller = null) where T : IEntityDescriptor, new(); #if UNITY_NATIVE - Svelto.ECS.Native.NativeEntityRemove ToNativeRemove(string memberName) where T : IEntityDescriptor, new(); - Svelto.ECS.Native.NativeEntitySwap ToNativeSwap(string memberName) where T : IEntityDescriptor, new(); -#endif + Svelto.ECS.Native.NativeEntityRemove ToNativeRemove(string memberName) where T : IEntityDescriptor, new(); + Svelto.ECS.Native.NativeEntitySwap ToNativeSwap(string memberName) where T : IEntityDescriptor, new(); +#endif } } \ No newline at end of file diff --git a/com.sebaslab.svelto.ecs/DataStructures/EntityIDs/ManagedEntityIDs.cs b/com.sebaslab.svelto.ecs/DataStructures/EntityIDs/ManagedEntityIDs.cs index d8acf71..f0a9f38 100644 --- a/com.sebaslab.svelto.ecs/DataStructures/EntityIDs/ManagedEntityIDs.cs +++ b/com.sebaslab.svelto.ecs/DataStructures/EntityIDs/ManagedEntityIDs.cs @@ -1,22 +1,32 @@ +using System.Runtime.CompilerServices; using Svelto.DataStructures; namespace Svelto.ECS.Internal { public struct ManagedEntityIDs: IEntityIDs { - public ManagedEntityIDs(MB> managed) + internal ManagedEntityIDs(MB> managed) { _managed = managed; } - public void Update(MB> managed) + internal void Update(MB> managed) { _managed = managed; } - public uint this[uint index] => _managed[index].key; - public uint this[int index] => _managed[index].key; + public uint this[uint index] + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _managed[index].key; + } + + public uint this[int index] + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _managed[index].key; + } - MB> _managed; + MBInternal> _managed; } } \ No newline at end of file diff --git a/com.sebaslab.svelto.ecs/DataStructures/EntityIDs/NativeEntityIDs.cs b/com.sebaslab.svelto.ecs/DataStructures/EntityIDs/NativeEntityIDs.cs index be68fcd..ad1fd64 100644 --- a/com.sebaslab.svelto.ecs/DataStructures/EntityIDs/NativeEntityIDs.cs +++ b/com.sebaslab.svelto.ecs/DataStructures/EntityIDs/NativeEntityIDs.cs @@ -1,22 +1,32 @@ +using System.Runtime.CompilerServices; using Svelto.DataStructures; namespace Svelto.ECS.Internal { public struct NativeEntityIDs: IEntityIDs { - public NativeEntityIDs(NB> native) + internal NativeEntityIDs(NB> native) { _native = native; } - + public void Update(in NB> unsafeKeys) { _native = unsafeKeys; } - public uint this[uint index] => _native[index].key; - public uint this[int index] => _native[index].key; + public uint this[uint index] + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _native[index].key; + } + + public uint this[int index] + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _native[index].key; + } - NB> _native; + NBInternal> _native; } } \ No newline at end of file diff --git a/com.sebaslab.svelto.ecs/DataStructures/ITypeSafeDictionary.cs b/com.sebaslab.svelto.ecs/DataStructures/ITypeSafeDictionary.cs index 5ad6fb5..1866846 100644 --- a/com.sebaslab.svelto.ecs/DataStructures/ITypeSafeDictionary.cs +++ b/com.sebaslab.svelto.ecs/DataStructures/ITypeSafeDictionary.cs @@ -6,7 +6,7 @@ namespace Svelto.ECS.Internal { public interface ITypeSafeDictionary : ITypeSafeDictionary where TValue : _IInternalEntityComponent { - void Add(uint egidEntityId, in TValue entityComponent); + uint Add(uint egidEntityId, in TValue entityComponent); bool TryGetValue(uint entityId, out TValue item); ref TValue GetOrAdd(uint idEntityId); @@ -29,11 +29,10 @@ namespace Svelto.ECS.Internal , in EnginesRoot.EntityReferenceMap entityLocator #endif ); - void RemoveEntitiesFromDictionary - (FasterList<(uint, string)> infosToProcess, FasterList entityIDsAffectedByRemoval); - void SwapEntitiesBetweenDictionaries - (FasterList<(uint, uint, string)> infosToProcess, ExclusiveGroupStruct fromGroup, ExclusiveGroupStruct toGroup - , ITypeSafeDictionary toComponentsDictionary, FasterList entityIDsAffectedByRemoval); + void RemoveEntitiesFromDictionary(FasterList<(uint, string)> infosToProcess, FasterDictionary entityIDsAffectedByRemoveAtSwapBack); + void SwapEntitiesBetweenDictionaries(in FasterDictionary infosToProcess, ExclusiveGroupStruct fromGroup, + ExclusiveGroupStruct toGroup + , ITypeSafeDictionary toComponentsDictionary, FasterDictionary entityIDsAffectedByRemoveAtSwapBack); //------------ @@ -49,7 +48,7 @@ namespace Svelto.ECS.Internal //------------ //This is now obsolete, but I cannot mark it as such because it's heavily used by legacy projects - void ExecuteEnginesSwapCallbacks(FasterList<(uint, uint, string)> infosToProcess, + void ExecuteEnginesSwapCallbacks(FasterDictionary infosToProcess, FasterList> reactiveEnginesSwap, ExclusiveGroupStruct fromGroup, ExclusiveGroupStruct toGroup, in PlatformProfiler sampler); //Version to use diff --git a/com.sebaslab.svelto.ecs/DataStructures/ManagedTypeSafeDictionary.cs b/com.sebaslab.svelto.ecs/DataStructures/ManagedTypeSafeDictionary.cs index 1581585..b7c891c 100644 --- a/com.sebaslab.svelto.ecs/DataStructures/ManagedTypeSafeDictionary.cs +++ b/com.sebaslab.svelto.ecs/DataStructures/ManagedTypeSafeDictionary.cs @@ -127,9 +127,12 @@ namespace Svelto.ECS.Internal } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Add(uint egidEntityId, in TValue entityComponent) + public uint Add(uint egidEntityId, in TValue entityComponent) { - implMgd.Add(egidEntityId, entityComponent); + if (implMgd.TryAdd(egidEntityId, entityComponent, out uint index) == false) + throw new TypeSafeDictionaryException("Key already present"); + + return index; } public void Dispose() @@ -158,22 +161,20 @@ namespace Svelto.ECS.Internal } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void RemoveEntitiesFromDictionary - (FasterList<(uint, string)> infosToProcess, FasterList entityIDsAffectedByRemoval) + public void RemoveEntitiesFromDictionary(FasterList<(uint, string)> infosToProcess, FasterDictionary entityIDsAffectedByRemoveAtSwapBack) { - TypeSafeDictionaryMethods.RemoveEntitiesFromDictionary(infosToProcess, ref implMgd, entityIDsAffectedByRemoval); + TypeSafeDictionaryMethods.RemoveEntitiesFromDictionary(infosToProcess, ref implMgd, entityIDsAffectedByRemoveAtSwapBack); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void SwapEntitiesBetweenDictionaries - (FasterList<(uint, uint, string)> infosToProcess, ExclusiveGroupStruct fromGroup - , ExclusiveGroupStruct toGroup, ITypeSafeDictionary toComponentsDictionary - , FasterList entityIDsAffectedByRemoval) + public void SwapEntitiesBetweenDictionaries(in FasterDictionary infosToProcess, + ExclusiveGroupStruct fromGroup + , ExclusiveGroupStruct toGroup, ITypeSafeDictionary toComponentsDictionary + , FasterDictionary entityIDsAffectedByRemoveAtSwapBack) { TypeSafeDictionaryMethods.SwapEntitiesBetweenDictionaries(infosToProcess, ref implMgd - , toComponentsDictionary as - ITypeSafeDictionary, fromGroup - , toGroup, entityIDsAffectedByRemoval); + , toComponentsDictionary as ITypeSafeDictionary, fromGroup + , toGroup, entityIDsAffectedByRemoveAtSwapBack); } /// @@ -192,10 +193,9 @@ namespace Svelto.ECS.Internal /// Execute all the engine IReactOnSwap callbacks linked to components swapped this submit /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void ExecuteEnginesSwapCallbacks - (FasterList<(uint, uint, string)> infosToProcess - , FasterList> reactiveEnginesSwap, ExclusiveGroupStruct fromGroup - , ExclusiveGroupStruct toGroup, in PlatformProfiler profiler) + public void ExecuteEnginesSwapCallbacks(FasterDictionary infosToProcess + , FasterList> reactiveEnginesSwap, ExclusiveGroupStruct fromGroup + , ExclusiveGroupStruct toGroup, in PlatformProfiler profiler) { TypeSafeDictionaryMethods.ExecuteEnginesSwapCallbacks(infosToProcess, ref implMgd, reactiveEnginesSwap , toGroup, fromGroup, in profiler); @@ -295,7 +295,7 @@ namespace Svelto.ECS.Internal ref implMgd, reactiveEnginesDispose, reactiveEnginesDisposeEx, entityIDs, group, in profiler); } - SveltoDictionary>, ManagedStrategy, + internal SveltoDictionary>, ManagedStrategy, ManagedStrategy> implMgd; } } \ No newline at end of file diff --git a/com.sebaslab.svelto.ecs/DataStructures/TypeSafeDictionaryException.cs b/com.sebaslab.svelto.ecs/DataStructures/TypeSafeDictionaryException.cs index 6f0a2c9..81f5878 100644 --- a/com.sebaslab.svelto.ecs/DataStructures/TypeSafeDictionaryException.cs +++ b/com.sebaslab.svelto.ecs/DataStructures/TypeSafeDictionaryException.cs @@ -8,5 +8,10 @@ namespace Svelto.ECS base(message, exception) { } + + public TypeSafeDictionaryException(string message) : + base(message) + { + } } } \ No newline at end of file diff --git a/com.sebaslab.svelto.ecs/DataStructures/TypeSafeDictionaryMethods.cs b/com.sebaslab.svelto.ecs/DataStructures/TypeSafeDictionaryMethods.cs index b58844c..50436d5 100644 --- a/com.sebaslab.svelto.ecs/DataStructures/TypeSafeDictionaryMethods.cs +++ b/com.sebaslab.svelto.ecs/DataStructures/TypeSafeDictionaryMethods.cs @@ -34,9 +34,12 @@ namespace Svelto.ECS.Internal ref tuple.value, entityLocator.GetEntityReference(egid)); #endif +#if DEBUG && !PROFILE_SVELTO try { +#endif toDic.Add(tuple.key, tuple.value); +#if DEBUG && !PROFILE_SVELTO } catch (Exception e) { @@ -47,6 +50,7 @@ namespace Svelto.ECS.Internal throw; } +#endif #if PARANOID_CHECK && SLOW_SVELTO_SUBMISSION DBC.ECS.Check.Ensure(_hasEgid == false || ((INeedEGID)fromDictionary[egid.entityID]).ID == egid, "impossible situation happened during swap"); #endif @@ -109,58 +113,59 @@ namespace Svelto.ECS.Internal where Strategy3 : struct, IBufferStrategy where TValue : struct, _IInternalEntityComponent { - if (reactiveEnginesDispose.TryGetValue(ComponentTypeID.id, out var entityComponentsEngines) - == false) - return; - - for (var i = 0; i < entityComponentsEngines.count; i++) - try - { - using (sampler.Sample(entityComponentsEngines[i].name)) + if (reactiveEnginesDispose.TryGetValue(ComponentTypeID.id, out var entityComponentsEngines) == true) + { + var resultCount = entityComponentsEngines.count; + for (var i = 0; i < resultCount; i++) + try { - foreach (var value in fromDictionary) + using (sampler.Sample(entityComponentsEngines[i].name)) { - ref var entity = ref value.value; - var egid = new EGID(value.key, group); - var reactOnRemove = (IReactOnDispose)entityComponentsEngines[i].engine; - reactOnRemove.Remove(ref entity, egid); + foreach (var value in fromDictionary) + { + ref var entity = ref value.value; + var egid = new EGID(value.key, group); +#pragma warning disable CS0618 + var reactOnRemove = (IReactOnDispose)entityComponentsEngines[i].engine; +#pragma warning restore CS0618 + reactOnRemove.Remove(ref entity, egid); + } } } - } - catch - { - Console.LogError("Code crashed inside Remove callback ".FastConcat(entityComponentsEngines[i].name)); + catch + { + Console.LogError("Code crashed inside Remove callback ".FastConcat(entityComponentsEngines[i].name)); - throw; - } - - - var count = fromDictionary.count; - - if (reactiveEnginesDisposeEx.TryGetValue( - ComponentTypeID.id - , out var reactiveEnginesDisposeExPerType)) + throw; + } + } + + if (reactiveEnginesDisposeEx.TryGetValue(ComponentTypeID.id, out var reactiveEnginesDisposeExPerType)) { + var count = fromDictionary.count; var enginesCount = reactiveEnginesDisposeExPerType.count; - for (var i = 0; i < enginesCount; i++) - try + if (count > 0) + { + for (var i = 0; i < enginesCount; i++) { - using (sampler.Sample(reactiveEnginesDisposeExPerType[i].name)) + try { - ((IReactOnDisposeEx)reactiveEnginesDisposeExPerType[i].engine).Remove( - (0, (uint)count) - , new EntityCollection( - fromDictionary.UnsafeGetValues(out _), entityids - , (uint)count), group); + using (sampler.Sample(reactiveEnginesDisposeExPerType[i].name)) + { + ((IReactOnDisposeEx)reactiveEnginesDisposeExPerType[i].engine).Remove( + (0, (uint)count) + , new EntityCollection(fromDictionary.UnsafeGetValues(out _), entityids, (uint)count), group); + } } - } - catch - { - Console.LogError("Code crashed inside Remove callback ".FastConcat(reactiveEnginesDisposeExPerType[i].name)); + catch + { + Console.LogError("Code crashed inside Remove callback ".FastConcat(reactiveEnginesDisposeExPerType[i].name)); - throw; + throw; + } } + } } } @@ -283,7 +288,7 @@ namespace Svelto.ECS.Internal } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void ExecuteEnginesSwapCallbacks(FasterList<(uint, uint, string)> infostoprocess + public static void ExecuteEnginesSwapCallbacks(FasterDictionary infostoprocess , ref SveltoDictionary fromDictionary , FasterList> reactiveenginesswap, ExclusiveGroupStruct togroup , ExclusiveGroupStruct fromgroup, in PlatformProfiler sampler) @@ -297,19 +302,23 @@ namespace Svelto.ECS.Internal var iterations = infostoprocess.count; + var infostoprocessUnsafeValues = infostoprocess.unsafeValues; + for (var i = 0; i < iterations; i++) { - var (fromEntityID, toEntityID, trace) = infostoprocess[i]; + var (fromEntityID, toEntityID, trace) = infostoprocessUnsafeValues[i]; try { - ref var entityComponent = ref fromDictionary.GetValueByRef(fromEntityID); + ref var entityComponent = ref fromDictionary.GetValueByRef(toEntityID); var newEgid = new EGID(toEntityID, togroup); for (var j = 0; j < reactiveenginesswap.count; j++) using (sampler.Sample(reactiveenginesswap[j].name)) { #pragma warning disable CS0612 +#pragma warning disable CS0618 ((IReactOnSwap)reactiveenginesswap[j].engine).MovedTo( +#pragma warning restore CS0618 #pragma warning restore CS0612 ref entityComponent, fromgroup, newEgid); } @@ -357,7 +366,9 @@ namespace Svelto.ECS.Internal using (sampler.Sample(reactiveEnginesSwapPerType[i].name)) { #pragma warning disable CS0612 +#pragma warning disable CS0618 ((IReactOnSwap)reactiveEnginesSwapPerType[i].engine).MovedTo( +#pragma warning restore CS0618 #pragma warning restore CS0612 ref entityComponent, fromgroup, newEgid); } @@ -401,7 +412,7 @@ namespace Svelto.ECS.Internal [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void RemoveEntitiesFromDictionary(FasterList<(uint, string)> infostoprocess , ref SveltoDictionary fromDictionary - , FasterList entityIDsAffectedByRemoval) + , FasterDictionary entityIDsAffectedByRemoveAtSwapBack) where Strategy1 : struct, IBufferStrategy> where Strategy2 : struct, IBufferStrategy where Strategy3 : struct, IBufferStrategy @@ -412,25 +423,31 @@ namespace Svelto.ECS.Internal for (var i = 0; i < iterations; i++) { var (id, trace) = infostoprocess[i]; - +#if DEBUG && !PROFILE_SVELTO try { +#endif if (fromDictionary.Remove(id, out var index, out var value)) { //Note I am doing this to be able to use a range of values even with the //remove Ex callbacks. Basically I am copying back the deleted value //at the end of the array, so I can use as range count, count + number of deleted entities //I need to swap the keys too to have matching EntityIDs - fromDictionary.unsafeValues[(uint)fromDictionary.count] = value; - fromDictionary.unsafeKeys[(uint)fromDictionary.count] = new SveltoDictionaryNode(ref id, 0); + if (index != fromDictionary.count) + { + fromDictionary.unsafeValues[(uint)fromDictionary.count] = value; + fromDictionary.unsafeKeys[(uint)fromDictionary.count] = new SveltoDictionaryNode(id, 0); + } + //when a component is removed from a component array, a remove swap back happens. This means //that not only we have to remove the index of the component of the entity deleted from the array //but we need also to update the index of the component that has been swapped in the cell //of the deleted component //entityIDsAffectedByRemoval tracks all the entitiesID of the components that need to be updated //in the filters because their indices in the array changed. - entityIDsAffectedByRemoval.Add(fromDictionary.unsafeKeys[index].key); + entityIDsAffectedByRemoveAtSwapBack[fromDictionary.unsafeKeys[index].key] = index; } +#if DEBUG && !PROFILE_SVELTO } catch { @@ -441,34 +458,39 @@ namespace Svelto.ECS.Internal throw; } +#endif } } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void SwapEntitiesBetweenDictionaries(FasterList<(uint, uint, string)> infostoprocess + public static void SwapEntitiesBetweenDictionaries(in FasterDictionary entitiesIDsToSwap , ref SveltoDictionary fromDictionary , ITypeSafeDictionary toDictionary, ExclusiveGroupStruct fromgroup, ExclusiveGroupStruct togroup - , FasterList entityIDsAffectedByRemoval) + , FasterDictionary entityIDsAffectedByRemoveAtSwapBack) where Strategy1 : struct, IBufferStrategy> where Strategy2 : struct, IBufferStrategy where Strategy3 : struct, IBufferStrategy where TValue : struct, _IInternalEntityComponent { - var iterations = infostoprocess.count; + var iterations = entitiesIDsToSwap.count; + var entitiesToSwapInfo = entitiesIDsToSwap.unsafeValues; + var fromDictionaryUnsafeKeys = fromDictionary.unsafeKeys; for (var i = 0; i < iterations; i++) { - var (fromID, toID, trace) = infostoprocess[i]; + ref SwapInfo swapInfo = ref entitiesToSwapInfo[i]; +#if DEBUG && !PROFILE_SVELTO try { - var fromEntityGid = new EGID(fromID, fromgroup); - var toEntityEgid = new EGID(toID, togroup); +#endif + var fromEntityGid = new EGID(swapInfo.fromID, fromgroup); + var toEntityEgid = new EGID(swapInfo.toID, togroup); Check.Require(togroup.isInvalid == false, "Invalid To Group"); if (fromDictionary.Remove(fromEntityGid.entityID, out var index, out var value)) - entityIDsAffectedByRemoval.Add(fromDictionary.unsafeKeys[index].key); + entityIDsAffectedByRemoveAtSwapBack[fromDictionaryUnsafeKeys[index].key] = index; //after the removal, the entity ad index is the entity that was at the end of the buffer (swapped back). else Check.Assert(false, "Swapping an entity that doesn't exist"); @@ -477,21 +499,23 @@ namespace Svelto.ECS.Internal SetEGIDWithoutBoxing.SetIDWithoutBoxing(ref value, toEntityEgid); #endif - toDictionary.Add(toEntityEgid.entityID, value); + swapInfo.toIndex = toDictionary.Add(toEntityEgid.entityID, value); #if PARANOID_CHECK - DBC.ECS.Check.Ensure(_hasEgid == false || ((INeedEGID)toGroupCasted[toEntityEGID.entityID]).ID == toEntityEGID, "impossible situation happened during swap"); + DBC.ECS.Check.Ensure(_hasEgid == false || ((INeedEGID)toGroupCasted[toEntityEGID.entityID]).ID == toEntityEGID, "impossible situation happened during swap"); #endif +#if DEBUG && !PROFILE_SVELTO } catch { var str = "Crash while executing Swap Entity operation on ".FastConcat(TypeCache.name) - .FastConcat(" from : ", trace); + .FastConcat(" from : ", swapInfo.trace); Console.LogError(str); throw; } +#endif } } @@ -503,9 +527,7 @@ namespace Svelto.ECS.Internal where TValue : struct, _IInternalEntityComponent { //get all the engines linked to TValue - if (!fasterDictionary.TryGetValue( - ComponentTypeID.id - , out var entityComponentsEngines)) + if (!fasterDictionary.TryGetValue(ComponentTypeID.id, out var entityComponentsEngines)) return; for (var i = 0; i < entityComponentsEngines.count; i++) @@ -528,26 +550,26 @@ namespace Svelto.ECS.Internal } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void ExecuteEnginesSwapCallbacksFast(FasterList> fasterList, - ExclusiveGroupStruct fromGroup - , ExclusiveGroupStruct toGroup, IEntityIDs entityids, ITypeSafeDictionary typeSafeDictionary - , (uint, uint) rangeofsubmittedentitiesindicies, PlatformProfiler sampler) + public static void ExecuteEnginesSwapCallbacksFast( + FasterList> callbackEngines, + ExclusiveGroupStruct fromGroup, ExclusiveGroupStruct toGroup, IEntityIDs entityids, ITypeSafeDictionary typeSafeDictionary + , (uint, uint) rangeOfSubmittedEntitiesIndicies, PlatformProfiler sampler) where TValue : struct, _IInternalEntityComponent { - for (var i = 0; i < fasterList.count; i++) + for (var i = 0; i < callbackEngines.count; i++) try { - using (sampler.Sample(fasterList[i].name)) + using (sampler.Sample(callbackEngines[i].name)) { - ((IReactOnSwapEx)fasterList[i].engine).MovedTo( - rangeofsubmittedentitiesindicies - , new EntityCollection(typeSafeDictionary.GetValues(out var count), entityids, count) - , fromGroup, toGroup); + var values = typeSafeDictionary.GetValues(out var count); + + ((IReactOnSwapEx)callbackEngines[i].engine).MovedTo( + rangeOfSubmittedEntitiesIndicies, new EntityCollection(values, entityids, count), fromGroup, toGroup); } } catch (Exception e) { - Console.LogException(e, "Code crashed inside Add callback ".FastConcat(fasterList[i].name)); + Console.LogException(e, "Code crashed inside Add callback ".FastConcat(callbackEngines[i].name)); throw; } diff --git a/com.sebaslab.svelto.ecs/DataStructures/UnmanagedTypeSafeDictionary.cs b/com.sebaslab.svelto.ecs/DataStructures/UnmanagedTypeSafeDictionary.cs index 2a2ae60..47b1253 100644 --- a/com.sebaslab.svelto.ecs/DataStructures/UnmanagedTypeSafeDictionary.cs +++ b/com.sebaslab.svelto.ecs/DataStructures/UnmanagedTypeSafeDictionary.cs @@ -139,9 +139,12 @@ namespace Svelto.ECS.Internal } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Add(uint egidEntityId, in TValue entityComponent) + public uint Add(uint egidEntityId, in TValue entityComponent) { - implUnmgd.dictionary.Add(egidEntityId, entityComponent); + if (implUnmgd.dictionary.TryAdd(egidEntityId, entityComponent, out var index) == false) + throw new TypeSafeDictionaryException("Key already present"); + + return index; } public void Dispose() @@ -172,20 +175,19 @@ namespace Svelto.ECS.Internal } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void RemoveEntitiesFromDictionary - (FasterList<(uint, string)> infosToProcess, FasterList entityIDsAffectedByRemoval) + public void RemoveEntitiesFromDictionary(FasterList<(uint, string)> infosToProcess, FasterDictionary entityIDsAffectedByRemoveAtSwapBack) { - TypeSafeDictionaryMethods.RemoveEntitiesFromDictionary(infosToProcess, ref implUnmgd.dictionary - , entityIDsAffectedByRemoval); + TypeSafeDictionaryMethods.RemoveEntitiesFromDictionary(infosToProcess, ref implUnmgd.dictionary, entityIDsAffectedByRemoveAtSwapBack); } - public void SwapEntitiesBetweenDictionaries - (FasterList<(uint, uint, string)> infosToProcess, ExclusiveGroupStruct fromGroup - , ExclusiveGroupStruct toGroup, ITypeSafeDictionary toComponentsDictionary - , FasterList entityIDsAffectedByRemoval) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void SwapEntitiesBetweenDictionaries(in FasterDictionary infosToProcess, + ExclusiveGroupStruct fromGroup + , ExclusiveGroupStruct toGroup, ITypeSafeDictionary toComponentsDictionary + , FasterDictionary entityIDsAffectedByRemoveAtSwapBack) { TypeSafeDictionaryMethods.SwapEntitiesBetweenDictionaries(infosToProcess, ref implUnmgd.dictionary - ,toComponentsDictionary as ITypeSafeDictionary, fromGroup, toGroup, entityIDsAffectedByRemoval); + ,toComponentsDictionary as ITypeSafeDictionary, fromGroup, toGroup, entityIDsAffectedByRemoveAtSwapBack); } /// @@ -204,10 +206,9 @@ namespace Svelto.ECS.Internal /// Execute all the engine IReactOnSwap callbacks linked to components swapped this submit /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void ExecuteEnginesSwapCallbacks - (FasterList<(uint, uint, string)> infosToProcess - , FasterList> reactiveEnginesSwap, ExclusiveGroupStruct fromGroup - , ExclusiveGroupStruct toGroup, in PlatformProfiler profiler) + public void ExecuteEnginesSwapCallbacks(FasterDictionary infosToProcess + , FasterList> reactiveEnginesSwap, ExclusiveGroupStruct fromGroup + , ExclusiveGroupStruct toGroup, in PlatformProfiler profiler) { TypeSafeDictionaryMethods.ExecuteEnginesSwapCallbacks(infosToProcess, ref implUnmgd.dictionary , reactiveEnginesSwap, toGroup, fromGroup, in profiler); diff --git a/com.sebaslab.svelto.ecs/Extensions/Native/EnginesRoot.NativeOperation.cs b/com.sebaslab.svelto.ecs/Extensions/Native/EnginesRoot.NativeOperation.cs index a838428..c52050f 100644 --- a/com.sebaslab.svelto.ecs/Extensions/Native/EnginesRoot.NativeOperation.cs +++ b/com.sebaslab.svelto.ecs/Extensions/Native/EnginesRoot.NativeOperation.cs @@ -88,10 +88,8 @@ namespace Svelto.ECS ref var nativeSwapOperation = ref _nativeSwapOperations[componentsIndex]; - CheckRemoveEntityID(entityEGID.@from, nativeSwapOperation.entityDescriptorType + CheckSwapEntityID(entityEGID.@from, entityEGID.to, nativeSwapOperation.entityDescriptorType , nativeSwapOperation.caller); - CheckAddEntityID(entityEGID.to, nativeSwapOperation.entityDescriptorType - , nativeSwapOperation.caller); QueueSwapEntityOperation(entityEGID.@from, entityEGID.to , FindRealComponents(entityEGID.@from, nativeSwapOperation.components) diff --git a/com.sebaslab.svelto.ecs/Extensions/Native/EntityNativeDBExtensions.cs b/com.sebaslab.svelto.ecs/Extensions/Native/EntityNativeDBExtensions.cs index 208cf00..c9a80a6 100644 --- a/com.sebaslab.svelto.ecs/Extensions/Native/EntityNativeDBExtensions.cs +++ b/com.sebaslab.svelto.ecs/Extensions/Native/EntityNativeDBExtensions.cs @@ -83,7 +83,7 @@ namespace Svelto.ECS if (safeDictionary.TryFindIndex(entityGID.entityID, out index) == false) return false; - buffer = (NB) (safeDictionary as ITypeSafeDictionary).GetValues(out _); + buffer = (NBInternal) (safeDictionary as ITypeSafeDictionary).GetValues(out _); return true; } @@ -129,7 +129,7 @@ namespace Svelto.ECS { if (mapper._map.TryFindIndex(entityID, out index)) { - return (NB) mapper._map.GetValues(out _); + return (NBInternal) mapper._map.GetValues(out _); } throw new ECSException("Entity not found"); @@ -143,7 +143,7 @@ namespace Svelto.ECS index = default; if (mapper._map != null && mapper._map.TryFindIndex(entityID, out index)) { - array = (NB) mapper._map.GetValues(out _); + array = (NBInternal) mapper._map.GetValues(out _); return true; } diff --git a/com.sebaslab.svelto.ecs/Extensions/Native/NativeEGIDMultiMapper.cs b/com.sebaslab.svelto.ecs/Extensions/Native/NativeEGIDMultiMapper.cs index 2b5fe39..3f4cc47 100644 --- a/com.sebaslab.svelto.ecs/Extensions/Native/NativeEGIDMultiMapper.cs +++ b/com.sebaslab.svelto.ecs/Extensions/Native/NativeEGIDMultiMapper.cs @@ -1,4 +1,5 @@ using System; +using Svelto.Common; using Svelto.DataStructures.Native; using Svelto.ECS.Internal; @@ -11,11 +12,15 @@ namespace Svelto.ECS.Native /// is disposed right after the use. /// ///WARNING: REMEMBER THIS MUST BE DISPOSED OF, AS IT USES NATIVE MEMORY. IT WILL LEAK MEMORY OTHERWISE + /// + /// to retrieve a NativeEGIDMultiMapper use entitiesDB.QueryNativeMappedEntities(groups, Svelto.Common.Allocator.TempJob); + /// + /// TODO: this could be extended to support all the query interfaces so that NB can become ref and this used to query entities inside jobs /// /// - public struct NativeEGIDMultiMapper : IDisposable where T : unmanaged, _IInternalEntityComponent + public struct NativeEGIDMultiMapper : IEGIDMultiMapper, IDisposable where T : unmanaged, _IInternalEntityComponent { - public NativeEGIDMultiMapper(in SveltoDictionaryNative> dictionary) + internal NativeEGIDMultiMapper(in SveltoDictionaryNative> dictionary) { _dic = dictionary; } @@ -61,5 +66,7 @@ namespace Svelto.ECS.Native } SveltoDictionaryNative> _dic; + + public Type entityType => TypeCache.type; } } \ No newline at end of file diff --git a/com.sebaslab.svelto.ecs/Extensions/Svelto/EGIDMultiMapper.cs b/com.sebaslab.svelto.ecs/Extensions/Svelto/EGIDMultiMapper.cs index 4eac41c..c016fe8 100644 --- a/com.sebaslab.svelto.ecs/Extensions/Svelto/EGIDMultiMapper.cs +++ b/com.sebaslab.svelto.ecs/Extensions/Svelto/EGIDMultiMapper.cs @@ -1,111 +1,18 @@ -using System; +using System; using System.Runtime.CompilerServices; using Svelto.Common; using Svelto.DataStructures; -using Svelto.DataStructures.Native; -using Svelto.ECS.Hybrid; using Svelto.ECS.Internal; namespace Svelto.ECS { - namespace Native + /// + /// to retrieve an EGIDMultiMapper use entitiesDB.QueryMappedEntities(groups); + /// + /// + public readonly struct EGIDMultiMapper: IEGIDMultiMapper where T : struct, _IInternalEntityComponent { - public struct EGIDMultiMapper where T : unmanaged, _IInternalEntityComponent - { - public EGIDMultiMapper - (SveltoDictionary>, NativeStrategy, NativeStrategy>, - ManagedStrategy>, - ManagedStrategy>, NativeStrategy - , NativeStrategy>>, NativeStrategy> dictionary) - { - _dic = dictionary; - } - - public int count - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => _dic.count; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ref T Entity(EGID entity) - { -#if DEBUG && !PROFILE_SVELTO - if (Exists(entity) == false) - throw new Exception("NativeEGIDMultiMapper: Entity not found"); -#endif - ref var sveltoDictionary = ref _dic.GetValueByRef(entity.groupID); - return ref sveltoDictionary.GetValueByRef(entity.entityID); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool Exists(EGID entity) - { - return _dic.TryFindIndex(entity.groupID, out var index) - && _dic.GetDirectValueByRef(index).ContainsKey(entity.entityID); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool TryGetEntity(EGID entity, out T component) - { - component = default; - return _dic.TryFindIndex(entity.groupID, out var index) - && _dic.GetDirectValueByRef(index).TryGetValue(entity.entityID, out component); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool FindIndex(ExclusiveGroupStruct group, uint entityID, out uint index) - { - index = 0; - return _dic.TryFindIndex(group, out var groupIndex) && - _dic.GetDirectValueByRef(groupIndex).TryFindIndex(entityID, out index); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public uint GetIndex(ExclusiveGroupStruct group, uint entityID) - { - uint groupIndex = _dic.GetIndex(group); - return _dic.GetDirectValueByRef(groupIndex).GetIndex(entityID); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool Exists(ExclusiveGroupStruct group, uint entityID) - { - return _dic.TryFindIndex(group, out var groupIndex) && - _dic.GetDirectValueByRef(groupIndex).ContainsKey(entityID); - } - - public Type entityType => TypeCache.type; - - SveltoDictionary>, NativeStrategy, - NativeStrategy>, ManagedStrategy>, - ManagedStrategy>, NativeStrategy, - NativeStrategy>>, NativeStrategy> _dic; - } - - public interface IEGIDMultiMapper - { - bool FindIndex(ExclusiveGroupStruct group, uint entityID, out uint index); - - uint GetIndex(ExclusiveGroupStruct group, uint entityID); - - bool Exists(ExclusiveGroupStruct group, uint entityID); - - Type entityType { get; } - } - } - - public struct EGIDMultiMapper where T : struct, IEntityViewComponent - { - public EGIDMultiMapper - (SveltoDictionary>, ManagedStrategy, - ManagedStrategy>, ManagedStrategy>, - ManagedStrategy>, ManagedStrategy, - ManagedStrategy>>, ManagedStrategy> dictionary) + internal EGIDMultiMapper(FasterDictionary> dictionary) { _dic = dictionary; } @@ -121,17 +28,34 @@ namespace Svelto.ECS { #if DEBUG && !PROFILE_SVELTO if (Exists(entity) == false) - throw new Exception("NativeEGIDMultiMapper: Entity not found"); + throw new Exception("EGIDMultiMapper: Entity not found"); #endif ref var sveltoDictionary = ref _dic.GetValueByRef(entity.groupID); return ref sveltoDictionary.GetValueByRef(entity.entityID); } + + public EntityCollection Entities(ExclusiveGroupStruct targetEgidGroupID) + { + uint count = 0; + IBuffer buffer; + IEntityIDs ids = default; + + if (_dic.TryGetValue(targetEgidGroupID, out var typeSafeDictionary) == false) + buffer = default; + else + { + ITypeSafeDictionary safeDictionary = typeSafeDictionary; + buffer = safeDictionary.GetValues(out count); + ids = safeDictionary.entityIDs; + } + + return new EntityCollection(buffer, ids, count); + } [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool Exists(EGID entity) { - return _dic.TryFindIndex(entity.groupID, out var index) - && _dic.GetDirectValueByRef(index).ContainsKey(entity.entityID); + return _dic.TryFindIndex(entity.groupID, out var index) && _dic.GetDirectValueByRef(index).ContainsKey(entity.entityID); } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -139,13 +63,39 @@ namespace Svelto.ECS { component = default; return _dic.TryFindIndex(entity.groupID, out var index) - && _dic.GetDirectValueByRef(index).TryGetValue(entity.entityID, out component); + && _dic.GetDirectValueByRef(index).TryGetValue(entity.entityID, out component); } - SveltoDictionary>, ManagedStrategy, - ManagedStrategy>, ManagedStrategy>, - ManagedStrategy>, ManagedStrategy, - ManagedStrategy>>, ManagedStrategy> _dic; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool FindIndex(ExclusiveGroupStruct group, uint entityID, out uint index) + { + index = 0; + return _dic.TryFindIndex(group, out var groupIndex) && + _dic.GetDirectValueByRef(groupIndex).TryFindIndex(entityID, out index); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public uint GetIndex(ExclusiveGroupStruct group, uint entityID) + { + uint groupIndex = _dic.GetIndex(group); + return _dic.GetDirectValueByRef(groupIndex).GetIndex(entityID); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public uint GetIndex(EGID egid) + { + return GetIndex(egid.groupID, egid.entityID); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Exists(ExclusiveGroupStruct group, uint entityID) + { + return _dic.TryFindIndex(group, out var groupIndex) && + _dic.GetDirectValueByRef(groupIndex).ContainsKey(entityID); + } + + public Type entityType => TypeCache.type; + + readonly FasterDictionary> _dic; } } \ No newline at end of file diff --git a/com.sebaslab.svelto.ecs/Extensions/Svelto/EntityCollectionExtension.cs b/com.sebaslab.svelto.ecs/Extensions/Svelto/EntityCollectionExtension.cs index b4edc01..6ec090a 100644 --- a/com.sebaslab.svelto.ecs/Extensions/Svelto/EntityCollectionExtension.cs +++ b/com.sebaslab.svelto.ecs/Extensions/Svelto/EntityCollectionExtension.cs @@ -11,14 +11,14 @@ namespace Svelto.ECS public static void Deconstruct(in this EntityCollection ec, out NB buffer, out int count) where T1 : unmanaged, IEntityComponent { - if (ec._buffer == null) //I cannot test 0, as buffer can be valid and processed by removeEx with count 0 + if (ec.buffer == null) //I cannot test 0, as buffer can be valid and processed by removeEx with count 0 { buffer = default; count = 0; return; } - buffer = (NB)ec._buffer; + buffer = (NBInternal)ec.buffer; count = (int)ec.count; } @@ -27,7 +27,7 @@ namespace Svelto.ECS out NativeEntityIDs entityIDs, out int count) where T1 : unmanaged, IEntityComponent { - if (ec._buffer == null) //I cannot test 0, as buffer can be valid and processed by removeEx with count 0 + if (ec.buffer == null) //I cannot test 0, as buffer can be valid and processed by removeEx with count 0 { buffer = default; count = 0; @@ -35,9 +35,9 @@ namespace Svelto.ECS return; } - buffer = (NB)ec._buffer; + buffer = (NBInternal)ec.buffer; count = (int)ec.count; - entityIDs = (NativeEntityIDs)ec._entityIDs; + entityIDs = (NativeEntityIDs)ec.entityIDs; } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -46,7 +46,7 @@ namespace Svelto.ECS where T1 : unmanaged, IEntityComponent where T2 : unmanaged, IEntityComponent { - if (ec.buffer1._buffer + if (ec.buffer1.buffer == null) //I cannot test 0, as buffer can be valid and processed by removeEx with count 0 { buffer1 = default; @@ -56,10 +56,10 @@ namespace Svelto.ECS return; } - buffer1 = (NB)ec.buffer1._buffer; - buffer2 = (NB)ec.buffer2._buffer; + buffer1 = (NBInternal)ec.buffer1.buffer; + buffer2 = (NBInternal)ec.buffer2.buffer; count = ec.count; - entityIDs = (NativeEntityIDs)ec.buffer1._entityIDs; + entityIDs = (NativeEntityIDs)ec.buffer1.entityIDs; } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -68,7 +68,7 @@ namespace Svelto.ECS where T1 : unmanaged, IEntityComponent where T2 : unmanaged, IEntityComponent { - if (ec.buffer1._buffer + if (ec.buffer1.buffer == null) //I cannot test 0, as buffer can be valid and processed by removeEx with count 0 { buffer1 = default; @@ -77,8 +77,8 @@ namespace Svelto.ECS return; } - buffer1 = (NB)ec.buffer1._buffer; - buffer2 = (NB)ec.buffer2._buffer; + buffer1 = (NBInternal)ec.buffer1.buffer; + buffer2 = (NBInternal)ec.buffer2.buffer; count = (int)ec.count; } @@ -89,7 +89,7 @@ namespace Svelto.ECS where T2 : unmanaged, IEntityComponent where T3 : unmanaged, IEntityComponent { - if (ec.buffer1._buffer + if (ec.buffer1.buffer == null) //I cannot test 0, as buffer can be valid and processed by removeEx with count 0 { buffer1 = default; @@ -99,9 +99,9 @@ namespace Svelto.ECS return; } - buffer1 = (NB)ec.buffer1._buffer; - buffer2 = (NB)ec.buffer2._buffer; - buffer3 = (NB)ec.buffer3._buffer; + buffer1 = (NBInternal)ec.buffer1.buffer; + buffer2 = (NBInternal)ec.buffer2.buffer; + buffer3 = (NBInternal)ec.buffer3.buffer; count = (int)ec.count; } @@ -112,7 +112,7 @@ namespace Svelto.ECS where T2 : unmanaged, IEntityComponent where T3 : unmanaged, IEntityComponent { - if (ec.buffer1._buffer + if (ec.buffer1.buffer == null) //I cannot test 0, as buffer can be valid and processed by removeEx with count 0 { buffer1 = default; @@ -123,11 +123,11 @@ namespace Svelto.ECS return; } - buffer1 = (NB)ec.buffer1._buffer; - buffer2 = (NB)ec.buffer2._buffer; - buffer3 = (NB)ec.buffer3._buffer; + buffer1 = (NBInternal)ec.buffer1.buffer; + buffer2 = (NBInternal)ec.buffer2.buffer; + buffer3 = (NBInternal)ec.buffer3.buffer; count = (int)ec.count; - entityIDs = (NativeEntityIDs)ec.buffer1._entityIDs; + entityIDs = (NativeEntityIDs)ec.buffer1.entityIDs; } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -138,7 +138,7 @@ namespace Svelto.ECS where T3 : unmanaged, IEntityComponent where T4 : unmanaged, IEntityComponent { - if (ec.buffer1._buffer + if (ec.buffer1.buffer == null) //I cannot test 0, as buffer can be valid and processed by removeEx with count 0 { buffer1 = default; @@ -149,10 +149,10 @@ namespace Svelto.ECS return; } - buffer1 = (NB)ec.buffer1._buffer; - buffer2 = (NB)ec.buffer2._buffer; - buffer3 = (NB)ec.buffer3._buffer; - buffer4 = (NB)ec.buffer4._buffer; + buffer1 = (NBInternal)ec.buffer1.buffer; + buffer2 = (NBInternal)ec.buffer2.buffer; + buffer3 = (NBInternal)ec.buffer3.buffer; + buffer4 = (NBInternal)ec.buffer4.buffer; count = (int)ec.count; } @@ -164,7 +164,7 @@ namespace Svelto.ECS where T3 : unmanaged, IEntityComponent where T4 : unmanaged, IEntityComponent { - if (ec.buffer1._buffer + if (ec.buffer1.buffer == null) //I cannot test 0, as buffer can be valid and processed by removeEx with count 0 { buffer1 = default; @@ -176,11 +176,11 @@ namespace Svelto.ECS return; } - buffer1 = (NB)ec.buffer1._buffer; - buffer2 = (NB)ec.buffer2._buffer; - buffer3 = (NB)ec.buffer3._buffer; - buffer4 = (NB)ec.buffer4._buffer; - entityIDs = (NativeEntityIDs)ec.buffer1._entityIDs; + buffer1 = (NBInternal)ec.buffer1.buffer; + buffer2 = (NBInternal)ec.buffer2.buffer; + buffer3 = (NBInternal)ec.buffer3.buffer; + buffer4 = (NBInternal)ec.buffer4.buffer; + entityIDs = (NativeEntityIDs)ec.buffer1.entityIDs; count = (int)ec.count; } } @@ -191,14 +191,14 @@ namespace Svelto.ECS public static void Deconstruct(in this EntityCollection ec, out MB buffer, out int count) where T1 : struct, IEntityViewComponent { - if (ec._buffer == null) //I cannot test 0, as buffer can be valid and processed by removeEx with count 0 + if (ec.buffer == null) //I cannot test 0, as buffer can be valid and processed by removeEx with count 0 { buffer = default; count = 0; return; } - buffer = (MB)ec._buffer; + buffer = (MBInternal)ec.buffer; count = (int)ec.count; } @@ -207,7 +207,7 @@ namespace Svelto.ECS out ManagedEntityIDs entityIDs, out int count) where T1 : struct, IEntityViewComponent { - if (ec._buffer == null) //I cannot test 0, as buffer can be valid and processed by removeEx with count 0 + if (ec.buffer == null) //I cannot test 0, as buffer can be valid and processed by removeEx with count 0 { buffer = default; count = 0; @@ -215,9 +215,9 @@ namespace Svelto.ECS return; } - buffer = (MB)ec._buffer; + buffer = (MBInternal)ec.buffer; count = (int)ec.count; - entityIDs = (ManagedEntityIDs)ec._entityIDs; + entityIDs = (ManagedEntityIDs)ec.entityIDs; } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -226,7 +226,7 @@ namespace Svelto.ECS where T1 : struct, IEntityViewComponent where T2 : struct, IEntityViewComponent { - if (ec.buffer1._buffer + if (ec.buffer1.buffer == null) //I cannot test 0, as buffer can be valid and processed by removeEx with count 0 { buffer1 = default; @@ -235,8 +235,8 @@ namespace Svelto.ECS return; } - buffer1 = (MB)ec.buffer1._buffer; - buffer2 = (MB)ec.buffer2._buffer; + buffer1 = (MBInternal)ec.buffer1.buffer; + buffer2 = (MBInternal)ec.buffer2.buffer; count = (int)ec.count; } @@ -246,7 +246,7 @@ namespace Svelto.ECS where T1 : struct, IEntityViewComponent where T2 : struct, IEntityViewComponent { - if (ec.buffer1._buffer + if (ec.buffer1.buffer == null) //I cannot test 0, as buffer can be valid and processed by removeEx with count 0 { buffer1 = default; @@ -256,10 +256,10 @@ namespace Svelto.ECS return; } - buffer1 = (MB)ec.buffer1._buffer; - buffer2 = (MB)ec.buffer2._buffer; + buffer1 = (MBInternal)ec.buffer1.buffer; + buffer2 = (MBInternal)ec.buffer2.buffer; count = (int)ec.count; - entityIDs = (ManagedEntityIDs)ec.buffer1._entityIDs; + entityIDs = (ManagedEntityIDs)ec.buffer1.entityIDs; } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -269,7 +269,7 @@ namespace Svelto.ECS where T2 : struct, IEntityViewComponent where T3 : struct, IEntityViewComponent { - if (ec.buffer1._buffer + if (ec.buffer1.buffer == null) //I cannot test 0, as buffer can be valid and processed by removeEx with count 0 { buffer1 = default; @@ -279,9 +279,9 @@ namespace Svelto.ECS return; } - buffer1 = (MB)ec.buffer1._buffer; - buffer2 = (MB)ec.buffer2._buffer; - buffer3 = (MB)ec.buffer3._buffer; + buffer1 = (MBInternal)ec.buffer1.buffer; + buffer2 = (MBInternal)ec.buffer2.buffer; + buffer3 = (MBInternal)ec.buffer3.buffer; count = (int)ec.count; } @@ -292,7 +292,7 @@ namespace Svelto.ECS where T2 : struct, IEntityViewComponent where T3 : struct, IEntityViewComponent { - if (ec.buffer1._buffer + if (ec.buffer1.buffer == null) //I cannot test 0, as buffer can be valid and processed by removeEx with count 0 { buffer1 = default; @@ -303,11 +303,11 @@ namespace Svelto.ECS return; } - buffer1 = (MB)ec.buffer1._buffer; - buffer2 = (MB)ec.buffer2._buffer; - buffer3 = (MB)ec.buffer3._buffer; + buffer1 = (MBInternal)ec.buffer1.buffer; + buffer2 = (MBInternal)ec.buffer2.buffer; + buffer3 = (MBInternal)ec.buffer3.buffer; count = (int)ec.count; - entityIDs = (ManagedEntityIDs)ec.buffer1._entityIDs; + entityIDs = (ManagedEntityIDs)ec.buffer1.entityIDs; } } @@ -319,7 +319,7 @@ namespace Svelto.ECS where T1 : unmanaged, IEntityComponent where T2 : struct, IEntityViewComponent { - if (ec.buffer1._buffer + if (ec.buffer1.buffer == null) //I cannot test 0, as buffer can be valid and processed by removeEx with count 0 { buffer1 = default; @@ -328,10 +328,10 @@ namespace Svelto.ECS return; } - buffer1 = (NB)ec.buffer1._buffer; - buffer2 = (MB)ec.buffer2._buffer; + buffer1 = (NBInternal)ec.buffer1.buffer; + buffer2 = (MBInternal)ec.buffer2.buffer; count = (int)ec.count; - entityIDs = (ManagedEntityIDs)ec.buffer2._entityIDs; + entityIDs = (ManagedEntityIDs)ec.buffer2.entityIDs; } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -340,7 +340,7 @@ namespace Svelto.ECS where T1 : unmanaged, IEntityComponent where T2 : struct, IEntityViewComponent { - if (ec.buffer1._buffer + if (ec.buffer1.buffer == null) //I cannot test 0, as buffer can be valid and processed by removeEx with count 0 { buffer1 = default; @@ -349,8 +349,8 @@ namespace Svelto.ECS return; } - buffer1 = (NB)ec.buffer1._buffer; - buffer2 = (MB)ec.buffer2._buffer; + buffer1 = (NBInternal)ec.buffer1.buffer; + buffer2 = (MBInternal)ec.buffer2.buffer; count = (int)ec.count; } @@ -361,7 +361,7 @@ namespace Svelto.ECS where T2 : struct, IEntityViewComponent where T3 : struct, IEntityViewComponent { - if (ec.buffer1._buffer + if (ec.buffer1.buffer == null) //I cannot test 0, as buffer can be valid and processed by removeEx with count 0 { buffer1 = default; @@ -371,9 +371,9 @@ namespace Svelto.ECS return; } - buffer1 = (NB)ec.buffer1._buffer; - buffer2 = (MB)ec.buffer2._buffer; - buffer3 = (MB)ec.buffer3._buffer; + buffer1 = (NBInternal)ec.buffer1.buffer; + buffer2 = (MBInternal)ec.buffer2.buffer; + buffer3 = (MBInternal)ec.buffer3.buffer; count = (int)ec.count; } @@ -385,7 +385,7 @@ namespace Svelto.ECS where T3 : unmanaged, IEntityComponent where T4 : struct, IEntityViewComponent { - if (ec.buffer1._buffer + if (ec.buffer1.buffer == null) //I cannot test 0, as buffer can be valid and processed by removeEx with count 0 { buffer1 = default; @@ -396,10 +396,10 @@ namespace Svelto.ECS return; } - buffer1 = (NB)ec.buffer1._buffer; - buffer2 = (NB)ec.buffer2._buffer; - buffer3 = (NB)ec.buffer3._buffer; - buffer4 = (MB)ec.buffer4._buffer; + buffer1 = (NBInternal)ec.buffer1.buffer; + buffer2 = (NBInternal)ec.buffer2.buffer; + buffer3 = (NBInternal)ec.buffer3.buffer; + buffer4 = (MBInternal)ec.buffer4.buffer; count = (int)ec.count; } } @@ -413,7 +413,7 @@ namespace Svelto.ECS where T2 : unmanaged, IEntityComponent where T3 : struct, IEntityViewComponent { - if (ec.buffer1._buffer + if (ec.buffer1.buffer == null) //I cannot test 0, as buffer can be valid and processed by removeEx with count 0 { buffer1 = default; @@ -423,9 +423,9 @@ namespace Svelto.ECS return; } - buffer1 = (NB)ec.buffer1._buffer; - buffer2 = (NB)ec.buffer2._buffer; - buffer3 = (MB)ec.buffer3._buffer; + buffer1 = (NBInternal)ec.buffer1.buffer; + buffer2 = (NBInternal)ec.buffer2.buffer; + buffer3 = (MBInternal)ec.buffer3.buffer; count = (int)ec.count; } @@ -437,7 +437,7 @@ namespace Svelto.ECS where T3 : struct, IEntityViewComponent where T4 : struct, IEntityViewComponent { - if (ec.buffer1._buffer + if (ec.buffer1.buffer == null) //I cannot test 0, as buffer can be valid and processed by removeEx with count 0 { buffer1 = default; @@ -448,10 +448,10 @@ namespace Svelto.ECS return; } - buffer1 = (NB)ec.buffer1._buffer; - buffer2 = (NB)ec.buffer2._buffer; - buffer3 = (MB)ec.buffer3._buffer; - buffer4 = (MB)ec.buffer4._buffer; + buffer1 = (NBInternal)ec.buffer1.buffer; + buffer2 = (NBInternal)ec.buffer2.buffer; + buffer3 = (MBInternal)ec.buffer3.buffer; + buffer4 = (MBInternal)ec.buffer4.buffer; count = (int)ec.count; } } diff --git a/com.sebaslab.svelto.ecs/Extensions/Svelto/EntityManagedDBExtensions.cs b/com.sebaslab.svelto.ecs/Extensions/Svelto/EntityManagedDBExtensions.cs index 3262c98..881d301 100644 --- a/com.sebaslab.svelto.ecs/Extensions/Svelto/EntityManagedDBExtensions.cs +++ b/com.sebaslab.svelto.ecs/Extensions/Svelto/EntityManagedDBExtensions.cs @@ -52,7 +52,7 @@ namespace Svelto.ECS if (safeDictionary.TryFindIndex(entityGID.entityID, out index) == false) return false; - buffer = (MB) (safeDictionary as ITypeSafeDictionary).GetValues(out _); + buffer = (MBInternal) (safeDictionary as ITypeSafeDictionary).GetValues(out _); return true; } @@ -98,7 +98,7 @@ namespace Svelto.ECS { if (mapper._map.TryFindIndex(entityID, out index)) { - return (MB) mapper._map.GetValues(out _); + return (MBInternal) mapper._map.GetValues(out _); } throw new ECSException("Entity not found"); @@ -112,7 +112,7 @@ namespace Svelto.ECS index = default; if (mapper._map != null && mapper._map.TryFindIndex(entityID, out index)) { - array = (MB) mapper._map.GetValues(out _); + array = (MBInternal) mapper._map.GetValues(out _); return true; } diff --git a/com.sebaslab.svelto.ecs/Extensions/Svelto/GroupsEnumerable.cs b/com.sebaslab.svelto.ecs/Extensions/Svelto/GroupsEnumerable.cs index b102c86..bac6f0e 100644 --- a/com.sebaslab.svelto.ecs/Extensions/Svelto/GroupsEnumerable.cs +++ b/com.sebaslab.svelto.ecs/Extensions/Svelto/GroupsEnumerable.cs @@ -37,7 +37,7 @@ namespace Svelto.ECS while (++_indexGroup < _groups.count) { var exclusiveGroupStruct = _groups[_indexGroup]; - if (!exclusiveGroupStruct.IsEnabled()) + if (exclusiveGroupStruct.IsEnabled() == false) continue; var entityCollection = _entitiesDB.QueryEntities(exclusiveGroupStruct); @@ -117,7 +117,7 @@ namespace Svelto.ECS while (++_indexGroup < _groups.count) { var exclusiveGroupStruct = _groups[_indexGroup]; - if (!exclusiveGroupStruct.IsEnabled()) + if (exclusiveGroupStruct.IsEnabled() == false) continue; EntityCollection entityCollection = _entitiesDB.QueryEntities(exclusiveGroupStruct); @@ -198,7 +198,7 @@ namespace Svelto.ECS while (++_indexGroup < _groups.count) { var exclusiveGroupStruct = _groups[_indexGroup]; - if (!exclusiveGroupStruct.IsEnabled()) + if (exclusiveGroupStruct.IsEnabled() == false) continue; var entityCollection = _db.QueryEntities(exclusiveGroupStruct); @@ -277,7 +277,7 @@ namespace Svelto.ECS { var exclusiveGroupStruct = _groups[_indexGroup]; - if (!exclusiveGroupStruct.IsEnabled()) + if (exclusiveGroupStruct.IsEnabled() == false) continue; var entityCollection = _db.QueryEntities(exclusiveGroupStruct); diff --git a/com.sebaslab.svelto.ecs/Extensions/Svelto/IEGIDMultiMapper.cs b/com.sebaslab.svelto.ecs/Extensions/Svelto/IEGIDMultiMapper.cs new file mode 100644 index 0000000..e3a7c33 --- /dev/null +++ b/com.sebaslab.svelto.ecs/Extensions/Svelto/IEGIDMultiMapper.cs @@ -0,0 +1,9 @@ +using System; + +namespace Svelto.ECS +{ + public interface IEGIDMultiMapper + { + uint GetIndex(EGID entity); + } +} \ No newline at end of file diff --git a/com.sebaslab.svelto.ecs/Extensions/Svelto/Legacy/FilterGroupExtensions.cs b/com.sebaslab.svelto.ecs/Extensions/Svelto/Legacy/FilterGroupExtensions.cs index a2db2e2..b50b148 100644 --- a/com.sebaslab.svelto.ecs/Extensions/Svelto/Legacy/FilterGroupExtensions.cs +++ b/com.sebaslab.svelto.ecs/Extensions/Svelto/Legacy/FilterGroupExtensions.cs @@ -13,7 +13,7 @@ namespace Svelto.ECS $"trying adding an entity {entityID} to filter {mapper.entityType} - {legacyFilter._ID} with group {legacyFilter._exclusiveGroupStruct}, but entity is not found! "); #endif - return legacyFilter.InternalAdd(entityID, mapper.GetIndex(legacyFilter._exclusiveGroupStruct, entityID)); + return legacyFilter.InternalAdd(entityID, mapper.GetIndex(new EGID(entityID, legacyFilter._exclusiveGroupStruct))); } } diff --git a/com.sebaslab.svelto.ecs/Extensions/Unity/DOTS/UECS/SveltoOnDOTSEnginesGroup.cs b/com.sebaslab.svelto.ecs/Extensions/Unity/DOTS/UECS/SveltoOnDOTSEnginesGroup.cs index 7e8d09f..132b39d 100644 --- a/com.sebaslab.svelto.ecs/Extensions/Unity/DOTS/UECS/SveltoOnDOTSEnginesGroup.cs +++ b/com.sebaslab.svelto.ecs/Extensions/Unity/DOTS/UECS/SveltoOnDOTSEnginesGroup.cs @@ -1,4 +1,5 @@ #if UNITY_ECS +using System; using Svelto.Common; using Svelto.ECS.Schedulers; using Unity.Entities; @@ -78,6 +79,21 @@ namespace Svelto.ECS.SveltoOnDOTS _syncSveltoToDotsGroup.Add(engine); } + + public void CreateDOTSToSveltoSyncEngine() where T:SyncSveltoToDOTSEngine, new() + { + //it's a Svelto Engine/DOTS ECS SystemBase so it must be added in the DOTS ECS world AND svelto enginesRoot +#if UNITY_ECS_100 + T engine = world.GetOrCreateSystemManaged(); + + _enginesRoot.AddEngine(engine); + + _syncSveltoToDotsGroup.Add(engine); +#else + throw new NotImplementedException(); +#endif + } + public void AddDOTSToSveltoSyncEngine(SyncDOTSToSveltoEngine engine) { diff --git a/com.sebaslab.svelto.ecs/Serialization/EnginesRoot.GenericEntitySerialization.cs b/com.sebaslab.svelto.ecs/Serialization/EnginesRoot.GenericEntitySerialization.cs index cb47353..db705ff 100644 --- a/com.sebaslab.svelto.ecs/Serialization/EnginesRoot.GenericEntitySerialization.cs +++ b/com.sebaslab.svelto.ecs/Serialization/EnginesRoot.GenericEntitySerialization.cs @@ -119,8 +119,7 @@ namespace Svelto.ECS uint descriptorHash = serializableEntityComponent.descriptorHash; var entityDescriptor = serializationDescriptorMap.GetDescriptorFromHash(descriptorHash); - _enginesRoot.CheckRemoveEntityID(fromEGID, entityDescriptor.realType, caller); - _enginesRoot.CheckAddEntityID(toEGID, entityDescriptor.realType, caller); + _enginesRoot.CheckSwapEntityID(fromEGID, toEGID, entityDescriptor.realType, caller); /// Serializable Entity Descriptors can be extended so we need to use FindRealComponents _enginesRoot.QueueSwapEntityOperation(fromEGID, toEGID, diff --git a/com.sebaslab.svelto.ecs/Serialization/EnginesRoot.SerializableEntityHeader.cs b/com.sebaslab.svelto.ecs/Serialization/EnginesRoot.SerializableEntityHeader.cs index efa9517..0bc4c24 100644 --- a/com.sebaslab.svelto.ecs/Serialization/EnginesRoot.SerializableEntityHeader.cs +++ b/com.sebaslab.svelto.ecs/Serialization/EnginesRoot.SerializableEntityHeader.cs @@ -1,3 +1,5 @@ +using Svelto.ECS.Serialization; + namespace Svelto.ECS { public partial class EnginesRoot diff --git a/com.sebaslab.svelto.ecs/Serialization/ISerializationData.cs b/com.sebaslab.svelto.ecs/Serialization/ISerializationData.cs index 1556d6d..1d53e25 100644 --- a/com.sebaslab.svelto.ecs/Serialization/ISerializationData.cs +++ b/com.sebaslab.svelto.ecs/Serialization/ISerializationData.cs @@ -1,6 +1,6 @@ using Svelto.DataStructures; -namespace Svelto.ECS +namespace Svelto.ECS.Serialization { public interface ISerializationData { diff --git a/com.sebaslab.svelto.ecs/Serialization/SerializableEntityComponent.cs b/com.sebaslab.svelto.ecs/Serialization/SerializableEntityComponent.cs index ed45a27..faae306 100644 --- a/com.sebaslab.svelto.ecs/Serialization/SerializableEntityComponent.cs +++ b/com.sebaslab.svelto.ecs/Serialization/SerializableEntityComponent.cs @@ -1,4 +1,4 @@ -namespace Svelto.ECS +namespace Svelto.ECS.Serialization { struct SerializableEntityComponent : IEntityComponent { diff --git a/com.sebaslab.svelto.ecs/Serialization/SerializingEnginesRoot.cs b/com.sebaslab.svelto.ecs/Serialization/SerializingEnginesRoot.cs index 465c848..7d82c6c 100644 --- a/com.sebaslab.svelto.ecs/Serialization/SerializingEnginesRoot.cs +++ b/com.sebaslab.svelto.ecs/Serialization/SerializingEnginesRoot.cs @@ -1,6 +1,6 @@ using Svelto.ECS.Schedulers; -namespace Svelto.ECS +namespace Svelto.ECS.Serialization { public class SerializingEnginesRoot : EnginesRoot { diff --git a/com.sebaslab.svelto.ecs/Serialization/SimpleSerializationData.cs b/com.sebaslab.svelto.ecs/Serialization/SimpleSerializationData.cs index e1563f9..68e166c 100644 --- a/com.sebaslab.svelto.ecs/Serialization/SimpleSerializationData.cs +++ b/com.sebaslab.svelto.ecs/Serialization/SimpleSerializationData.cs @@ -1,6 +1,6 @@ using Svelto.DataStructures; -namespace Svelto.ECS +namespace Svelto.ECS.Serialization { public class SimpleSerializationData : ISerializationData { diff --git a/com.sebaslab.svelto.ecs/Svelto.ECS.csproj b/com.sebaslab.svelto.ecs/Svelto.ECS.csproj index 9a0b63e..21afa3e 100644 --- a/com.sebaslab.svelto.ecs/Svelto.ECS.csproj +++ b/com.sebaslab.svelto.ecs/Svelto.ECS.csproj @@ -1,7 +1,7 @@ - + Svelto.ECS - 9 + 10 netstandard2.1 Svelto 3.4 diff --git a/com.sebaslab.svelto.ecs/package.json b/com.sebaslab.svelto.ecs/package.json index f91dfa6..98d0215 100644 --- a/com.sebaslab.svelto.ecs/package.json +++ b/com.sebaslab.svelto.ecs/package.json @@ -11,7 +11,7 @@ "url": "https://github.com/sebas77/Svelto.ECS.git" }, "dependencies": { - "com.sebaslab.svelto.common": "3.4.3" + "com.sebaslab.svelto.common": "3.5.0" }, "keywords": [ "svelto", @@ -19,7 +19,7 @@ "svelto.ecs" ], "name": "com.sebaslab.svelto.ecs", - "version": "3.4.6", + "version": "3.5.0", "type": "library", "unity": "2020.3" } diff --git a/com.sebaslab.svelto.ecs/version.json b/com.sebaslab.svelto.ecs/version.json index 289f3c1..4fdc669 100644 --- a/com.sebaslab.svelto.ecs/version.json +++ b/com.sebaslab.svelto.ecs/version.json @@ -1,3 +1,3 @@ { - "version": "3.4.6" + "version": "3.5.0-pre" }