From b37ec4f076b1f6e0a73c8e0421e335c9793175ea Mon Sep 17 00:00:00 2001 From: "Seb\\smandala" Date: Mon, 11 Apr 2022 12:11:42 +0100 Subject: [PATCH] Update Svelto.ECS to 3.3 --- com.sebaslab.svelto.common | 2 +- com.sebaslab.svelto.ecs/CHANGELOG.md | 13 + .../Components/EGIDComponent.cs | 4 +- .../Components/EntityHierarchyComponent.cs | 11 - .../Components/EntityReferenceComponent.cs | 11 - .../Components/LinkedEntityComponent.cs | 7 - .../Core/CheckEntityUtilities.cs | 51 +- .../Core/ComponentBuilder.CheckFields.cs | 37 +- .../Core/ComponentBuilder.cs | 113 +- com.sebaslab.svelto.ecs/Core/DBC.cs | 249 ++--- com.sebaslab.svelto.ecs/Core/EGIDMapper.cs | 23 +- .../Core/EnginesGroup/SortedEnginesGroup.cs | 1 - ...EnginesRoot.DoubleBufferedEntitiesToAdd.cs | 245 +++-- .../Core/EnginesRoot.Engines.cs | 449 ++++---- .../Core/EnginesRoot.Entities.cs | 301 ++---- .../Core/EnginesRoot.GenericEntityFactory.cs | 52 +- .../EnginesRoot.GenericEntityFunctions.cs | 177 ++-- .../Core/EnginesRoot.Submission.cs | 574 ++++++++--- .../Core/EntitiesDB.FindGroups.cs | 9 +- com.sebaslab.svelto.ecs/Core/EntitiesDB.cs | 13 +- .../Core/EntitiesOperations.cs | 191 ++++ .../Core/EntityCollection.cs | 171 ++-- .../DynamicEntityDescriptor.cs | 2 +- .../EntityDescriptorExtension.cs | 4 +- .../ExtendibleEntityDescriptor.cs | 12 +- .../GenericEntityDescriptor.cs | 6 +- com.sebaslab.svelto.ecs/Core/EntityFactory.cs | 27 +- .../Core/EntityInitializer.cs | 32 +- .../EntityReference/EnginesRoot.LocatorMap.cs | 26 +- .../EntityReference/EntitiesDB.References.cs | 3 +- .../Core/EntityReference/EntityReference.cs | 4 +- .../Core/EntitySubmissionScheduler.cs | 6 +- .../Core/EntitySubmitOperation.cs | 61 +- .../Core/Filters/EnginesRoot.Filters.cs | 160 +++ .../Core/Filters/EntitiesDB.Filters.cs | 349 +++++++ ...Filters.cs => EntitiesDB.LegacyFilters.cs} | 138 ++- .../Core/Filters/EntityFilterCollection.cs | 198 ++++ .../Core/Filters/EntityFilterID.cs | 32 + .../Core/Filters/EntityFilterIndices.cs | 47 + .../Core/Filters/EntityFilterIterator.cs | 51 + .../Core/Filters/GroupFilters.cs | 104 -- .../{FilterGroup.cs => LegacyFilterGroup.cs} | 21 +- ...redIndices.cs => LegacyFilteredIndices.cs} | 11 +- .../Core/Filters/LegacyGroupFilters.cs | 104 ++ .../Filters/NativeEntityFilterCollection.cs | 148 +++ .../Filters/NativeEntityFilterIterator.cs | 63 ++ com.sebaslab.svelto.ecs/Core/GlobalTypeID.cs | 18 +- com.sebaslab.svelto.ecs/Core/GroupHashMap.cs | 85 +- com.sebaslab.svelto.ecs/Core/GroupNamesMap.cs | 4 +- .../Core/Groups/ExclusiveBuildGroup.cs | 2 +- .../Core/Groups/ExclusiveGroup.cs | 7 +- .../Core/Groups/ExclusiveGroupStruct.cs | 2 + .../Core/Groups/GroupCompound.cs | 95 +- .../Core/Groups/NamedExclusiveGroup.cs | 2 +- .../Core/Hybrid/IEntityViewComponent.cs | 5 +- .../Core/Hybrid/ValueReference.cs | 30 +- com.sebaslab.svelto.ecs/Core/IEngine.cs | 115 ++- .../Core/IEntityFactory.cs | 28 +- .../Core/IEntityFunctions.cs | 31 +- com.sebaslab.svelto.ecs/Core/INeedEGID.cs | 5 +- .../Core/INeedEntityReference.cs | 7 +- .../Core/IQueryingEntitiesEngine.cs | 9 - com.sebaslab.svelto.ecs/Core/QueryGroups.cs | 117 ++- .../Core/ReactEngineContainer.cs | 6 +- .../Core/SetEGIDWithoutBoxing.cs | 6 +- .../Core/SimpleEntitiesSubmissionScheduler.cs | 59 +- .../WaitForSubmissionEnumerator.cs | 4 +- .../Core/Streams/EntitiesStreams.cs | 18 +- .../Streams/ThreadSafeNativeEntityStream.cs | 31 - .../DataStructures/ITypeSafeDictionary.cs | 81 +- .../DataStructures/TypeSafeDictionary.cs | 967 +++++++++++------- .../Unmanaged/AtomicNativeBags.cs | 2 +- .../DataStructures/Unmanaged/NativeBag.cs | 184 +--- .../Unmanaged/NativeDynamicArray.cs | 286 ++++-- .../Unmanaged/NativeDynamicArrayCast.cs | 17 +- .../Unmanaged/SharedNativeInt.cs | 44 +- .../DataStructures/Unmanaged/UnsafeBlob.cs | 57 +- .../Dispatcher/ReactiveValue.cs | 2 +- .../Native/EnginesRoot.NativeOperation.cs | 72 +- .../EntityNativeDBExtensions.cs | 6 +- .../Extensions/Native/NativeEGIDMapper.cs | 117 +++ .../Native/NativeEGIDMultiMapper.cs | 78 ++ .../DOTS => }/Native/NativeEntityFactory.cs | 17 +- .../Native/NativeEntityInitializer.cs | 8 +- .../DOTS => }/Native/NativeEntityRemove.cs | 0 .../DOTS => }/Native/NativeEntitySwap.cs | 0 .../Native/UnityNativeEntityDBExtensions.cs | 25 +- .../Extensions/Svelto/AllGroupsEnumerable.cs | 11 +- .../Svelto/EntitiesDBFiltersExtension.cs | 4 +- .../Svelto/EntityCollectionExtension.cs | 237 +++-- .../Svelto/EntityManagedDBExtensions.cs | 1 - .../Svelto/FilterGroupExtensions.cs | 8 +- .../Extensions/Svelto/GroupsEnumerable.cs | 10 +- .../Extensions/Unity/DOTS/Jobs/DisposeJob.cs | 2 +- .../Unity/DOTS/Jobs/IJobifiedEngine.cs | 2 +- .../DOTS/Jobs/SortedJobifiedEnginesGroup.cs | 31 +- .../Unity/DOTS/Jobs/UnityJobExtensions.cs | 4 +- .../DOTS/Jobs/UnsortedJobifiedEnginesGroup.cs | 33 +- .../Unity/DOTS/Native/NativeEGIDMapper.cs | 108 -- .../DOTS/Native/NativeEGIDMultiMapper.cs | 63 -- .../Unity/DOTS/UECS/DOTSSveltoEGID.cs | 55 + .../DOTS/UECS/EntityCommandBufferForSvelto.cs | 215 ++++ .../DOTS/UECS/ISveltoOnDOTSSubmission.cs | 17 + .../Unity/DOTS/UECS/JobifiedSveltoEngines.cs | 4 +- .../Unity/DOTS/UECS/SubmissionEngine.cs | 29 - .../DOTS/UECS/SveltoOnDOTSEnginesGroup.cs | 122 +++ .../SveltoOnDOTSEntitiesSubmissionGroup.cs | 196 ++++ .../UECS/SveltoOnDOTSHandleCreationEngine.cs | 60 ++ .../UECS/SveltoOnDOTSHandleLifeTimeEngine.cs | 44 + .../DOTS/UECS/SveltoOverUECSEnginesGroup.cs | 118 --- .../UECS/SveltoUECSEntitiesSubmissionGroup.cs | 241 ----- .../Unity/DOTS/UECS/SyncDOTSToSveltoGroup.cs | 81 ++ ...oUECSGroup.cs => SyncSveltoToDOTSGroup.cs} | 24 +- .../Unity/DOTS/UECS/SyncUECSToSveltoGroup.cs | 26 - .../Unity/DOTS/UECS/UECSSveltoEGID.cs | 40 - .../GameObjects/Implementors/IECSManager.cs | 6 + .../IUseMultipleResourceManagerImplementor.cs | 9 + .../IUseResourceManagerImplementor.cs | 9 + .../Unity/GameObjects/SveltoGUIHelper.cs | 161 --- .../Unity/UnityEntitiesSubmissionScheduler.cs | 18 +- .../ComposedComponentSerializer.cs | 6 +- .../Serialization/DefaultSerializer.cs | 3 +- .../Serialization/DefaultVersioningFactory.cs | 18 +- .../EnginesRoot.GenericEntitySerialization.cs | 127 ++- .../EnginesRoot.SerializableEntityHeader.cs | 2 +- .../Serialization/IEntitySerialization.cs | 20 +- .../Serialization/PartialSerializer.cs | 19 +- .../SerializableComponentBuilder.cs | 4 +- .../SerializableEntityComponent.cs | 4 +- .../Serialization/SerializingEnginesRoot.cs | 16 + com.sebaslab.svelto.ecs/Svelto.ECS.asmdef | 117 ++- com.sebaslab.svelto.ecs/Svelto.ECS.csproj | 19 +- com.sebaslab.svelto.ecs/package.json | 4 +- com.sebaslab.svelto.ecs/version.json | 2 +- 134 files changed, 5800 insertions(+), 3512 deletions(-) delete mode 100644 com.sebaslab.svelto.ecs/Components/EntityHierarchyComponent.cs delete mode 100644 com.sebaslab.svelto.ecs/Components/EntityReferenceComponent.cs delete mode 100644 com.sebaslab.svelto.ecs/Components/LinkedEntityComponent.cs create mode 100644 com.sebaslab.svelto.ecs/Core/EntitiesOperations.cs create mode 100644 com.sebaslab.svelto.ecs/Core/Filters/EnginesRoot.Filters.cs create mode 100644 com.sebaslab.svelto.ecs/Core/Filters/EntitiesDB.Filters.cs rename com.sebaslab.svelto.ecs/Core/Filters/{EntitiesDB.GroupFilters.cs => EntitiesDB.LegacyFilters.cs} (53%) create mode 100644 com.sebaslab.svelto.ecs/Core/Filters/EntityFilterCollection.cs create mode 100644 com.sebaslab.svelto.ecs/Core/Filters/EntityFilterID.cs create mode 100644 com.sebaslab.svelto.ecs/Core/Filters/EntityFilterIndices.cs create mode 100644 com.sebaslab.svelto.ecs/Core/Filters/EntityFilterIterator.cs delete mode 100644 com.sebaslab.svelto.ecs/Core/Filters/GroupFilters.cs rename com.sebaslab.svelto.ecs/Core/Filters/{FilterGroup.cs => LegacyFilterGroup.cs} (91%) rename com.sebaslab.svelto.ecs/Core/Filters/{FilteredIndices.cs => LegacyFilteredIndices.cs} (76%) create mode 100644 com.sebaslab.svelto.ecs/Core/Filters/LegacyGroupFilters.cs create mode 100644 com.sebaslab.svelto.ecs/Core/Filters/NativeEntityFilterCollection.cs create mode 100644 com.sebaslab.svelto.ecs/Core/Filters/NativeEntityFilterIterator.cs delete mode 100644 com.sebaslab.svelto.ecs/Core/IQueryingEntitiesEngine.cs delete mode 100644 com.sebaslab.svelto.ecs/Core/Streams/ThreadSafeNativeEntityStream.cs rename com.sebaslab.svelto.ecs/Extensions/{Unity/DOTS => }/Native/EnginesRoot.NativeOperation.cs (72%) rename com.sebaslab.svelto.ecs/Extensions/{Svelto => Native}/EntityNativeDBExtensions.cs (98%) create mode 100644 com.sebaslab.svelto.ecs/Extensions/Native/NativeEGIDMapper.cs create mode 100644 com.sebaslab.svelto.ecs/Extensions/Native/NativeEGIDMultiMapper.cs rename com.sebaslab.svelto.ecs/Extensions/{Unity/DOTS => }/Native/NativeEntityFactory.cs (59%) rename com.sebaslab.svelto.ecs/Extensions/{Unity/DOTS => }/Native/NativeEntityInitializer.cs (70%) rename com.sebaslab.svelto.ecs/Extensions/{Unity/DOTS => }/Native/NativeEntityRemove.cs (100%) rename com.sebaslab.svelto.ecs/Extensions/{Unity/DOTS => }/Native/NativeEntitySwap.cs (100%) rename com.sebaslab.svelto.ecs/Extensions/{Unity/DOTS => }/Native/UnityNativeEntityDBExtensions.cs (68%) delete mode 100644 com.sebaslab.svelto.ecs/Extensions/Unity/DOTS/Native/NativeEGIDMapper.cs delete mode 100644 com.sebaslab.svelto.ecs/Extensions/Unity/DOTS/Native/NativeEGIDMultiMapper.cs create mode 100644 com.sebaslab.svelto.ecs/Extensions/Unity/DOTS/UECS/DOTSSveltoEGID.cs create mode 100644 com.sebaslab.svelto.ecs/Extensions/Unity/DOTS/UECS/EntityCommandBufferForSvelto.cs create mode 100644 com.sebaslab.svelto.ecs/Extensions/Unity/DOTS/UECS/ISveltoOnDOTSSubmission.cs delete mode 100644 com.sebaslab.svelto.ecs/Extensions/Unity/DOTS/UECS/SubmissionEngine.cs create mode 100644 com.sebaslab.svelto.ecs/Extensions/Unity/DOTS/UECS/SveltoOnDOTSEnginesGroup.cs create mode 100644 com.sebaslab.svelto.ecs/Extensions/Unity/DOTS/UECS/SveltoOnDOTSEntitiesSubmissionGroup.cs create mode 100644 com.sebaslab.svelto.ecs/Extensions/Unity/DOTS/UECS/SveltoOnDOTSHandleCreationEngine.cs create mode 100644 com.sebaslab.svelto.ecs/Extensions/Unity/DOTS/UECS/SveltoOnDOTSHandleLifeTimeEngine.cs delete mode 100644 com.sebaslab.svelto.ecs/Extensions/Unity/DOTS/UECS/SveltoOverUECSEnginesGroup.cs delete mode 100644 com.sebaslab.svelto.ecs/Extensions/Unity/DOTS/UECS/SveltoUECSEntitiesSubmissionGroup.cs create mode 100644 com.sebaslab.svelto.ecs/Extensions/Unity/DOTS/UECS/SyncDOTSToSveltoGroup.cs rename com.sebaslab.svelto.ecs/Extensions/Unity/DOTS/UECS/{SyncSveltoToUECSGroup.cs => SyncSveltoToDOTSGroup.cs} (89%) delete mode 100644 com.sebaslab.svelto.ecs/Extensions/Unity/DOTS/UECS/SyncUECSToSveltoGroup.cs delete mode 100644 com.sebaslab.svelto.ecs/Extensions/Unity/DOTS/UECS/UECSSveltoEGID.cs create mode 100644 com.sebaslab.svelto.ecs/Extensions/Unity/GameObjects/Implementors/IECSManager.cs create mode 100644 com.sebaslab.svelto.ecs/Extensions/Unity/GameObjects/Implementors/IUseMultipleResourceManagerImplementor.cs create mode 100644 com.sebaslab.svelto.ecs/Extensions/Unity/GameObjects/Implementors/IUseResourceManagerImplementor.cs delete mode 100644 com.sebaslab.svelto.ecs/Extensions/Unity/GameObjects/SveltoGUIHelper.cs create mode 100644 com.sebaslab.svelto.ecs/Serialization/SerializingEnginesRoot.cs diff --git a/com.sebaslab.svelto.common b/com.sebaslab.svelto.common index 9ebffd8..db2e491 160000 --- a/com.sebaslab.svelto.common +++ b/com.sebaslab.svelto.common @@ -1 +1 @@ -Subproject commit 9ebffd823db96acb9c29095ab4b9e78fd1fdd56b +Subproject commit db2e49147d988c06f5fa2de93d38ad1a3be99b9d diff --git a/com.sebaslab.svelto.ecs/CHANGELOG.md b/com.sebaslab.svelto.ecs/CHANGELOG.md index 4db8188..82055ac 100644 --- a/com.sebaslab.svelto.ecs/CHANGELOG.md +++ b/com.sebaslab.svelto.ecs/CHANGELOG.md @@ -1,6 +1,19 @@ # Changelog All notable changes to this project will be documented in this file. Changes are listed in random order of importance. +## [3.3.0] - 04-2022 + +* INeedEGID and INeedEntityReference interfaces are not deprecated, but still available for backwards compatibility through the define SLOW_SVELTO_SUBMISSION +* There are some minor breaking changes, you may need to rename a bunch of methods calls +* Drastically improved Submission phase performance +* All the IReactOn interfaces are now replaced by much faster IReacOn*Ex interfaces. Use those~~~~ +* QueryEntities methods now optionally return also an array of Entity IDs that you can reference like a component (this supersedes INeedEGID) +* Completely reworked and way more powerful filter API. The old one has been renamed to Legacy and left for backward compatibility +* NativeEGIDMultiMapper doesn't need to be created every submission anymore. It can be created permanently and disposed when not used anymore (some caveats with it) +* Improved Serialization system +* Improved SveltoOnDots system +* Tons of other improvements and bug fixes +~~~~ ## [3.2.5] * refactor and improved NativeBag and UnsafeBlob. This fix a previously known crash with Unity IL2CPP diff --git a/com.sebaslab.svelto.ecs/Components/EGIDComponent.cs b/com.sebaslab.svelto.ecs/Components/EGIDComponent.cs index c818065..c7dcd31 100644 --- a/com.sebaslab.svelto.ecs/Components/EGIDComponent.cs +++ b/com.sebaslab.svelto.ecs/Components/EGIDComponent.cs @@ -1,7 +1,9 @@ +#if SLOW_SVELTO_SUBMISSION namespace Svelto.ECS { public struct EGIDComponent:IEntityComponent, INeedEGID { public EGID ID { get; set; } } -} \ No newline at end of file +} +#endif \ No newline at end of file diff --git a/com.sebaslab.svelto.ecs/Components/EntityHierarchyComponent.cs b/com.sebaslab.svelto.ecs/Components/EntityHierarchyComponent.cs deleted file mode 100644 index 22f3a2d..0000000 --- a/com.sebaslab.svelto.ecs/Components/EntityHierarchyComponent.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace Svelto.ECS -{ - public struct EntityHierarchyComponent: IEntityComponent, INeedEGID - { - public readonly ExclusiveGroupStruct parentGroup; - - public EntityHierarchyComponent(ExclusiveGroup group): this() { parentGroup = group; } - - public EGID ID { get; set; } - } -} \ No newline at end of file diff --git a/com.sebaslab.svelto.ecs/Components/EntityReferenceComponent.cs b/com.sebaslab.svelto.ecs/Components/EntityReferenceComponent.cs deleted file mode 100644 index 803ed1c..0000000 --- a/com.sebaslab.svelto.ecs/Components/EntityReferenceComponent.cs +++ /dev/null @@ -1,11 +0,0 @@ -using Svelto.ECS.Reference; - -namespace Svelto.ECS -{ - //To do: this should be removed and the only reason it exists is to solve some edge case scenarios with - //the publish/consumer pattern - public struct EntityReferenceComponent:IEntityComponent, INeedEntityReference - { - public EntityReference selfReference { get; set; } - } -} \ No newline at end of file diff --git a/com.sebaslab.svelto.ecs/Components/LinkedEntityComponent.cs b/com.sebaslab.svelto.ecs/Components/LinkedEntityComponent.cs deleted file mode 100644 index 03583d4..0000000 --- a/com.sebaslab.svelto.ecs/Components/LinkedEntityComponent.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Svelto.ECS -{ - public struct LinkedEntityComponent : IEntityComponent - { - public EGID linkedEntity; - } -} \ No newline at end of file diff --git a/com.sebaslab.svelto.ecs/Core/CheckEntityUtilities.cs b/com.sebaslab.svelto.ecs/Core/CheckEntityUtilities.cs index 43a6f38..7d82a4d 100644 --- a/com.sebaslab.svelto.ecs/Core/CheckEntityUtilities.cs +++ b/com.sebaslab.svelto.ecs/Core/CheckEntityUtilities.cs @@ -4,7 +4,6 @@ using System.Diagnostics; #endif using System; using System.Collections.Generic; -using System.Runtime.CompilerServices; using Svelto.DataStructures; namespace Svelto.ECS @@ -16,9 +15,9 @@ namespace Svelto.ECS public partial class EnginesRoot { #if DONT_USE - [Conditional("CHECK_ALL")] + [Conditional("MEANINGLESS")] #endif - void CheckRemoveEntityID(EGID egid, Type entityDescriptorType, [CallerMemberName] string caller = null) + void CheckRemoveEntityID(EGID egid, Type entityDescriptorType, string caller) { if (_multipleOperationOnSameEGIDChecker.ContainsKey(egid) == true) throw new ECSException( @@ -30,23 +29,29 @@ namespace Svelto.ECS .FastConcat(_multipleOperationOnSameEGIDChecker[egid] == 1 ? "add" : "remove")); if (_idChecker.TryGetValue(egid.groupID, out var hash)) + { if (hash.Contains(egid.entityID) == false) - throw new ECSException("Trying to remove an Entity never submitted in the database " - .FastConcat(" caller: ", caller, " ").FastConcat(egid.entityID) - .FastConcat(" groupid: ").FastConcat(egid.groupID.ToName()) - .FastConcat(" type: ") - .FastConcat(entityDescriptorType != null - ? entityDescriptorType.Name - : "not available")); - else - hash.Remove(egid.entityID); + 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); _multipleOperationOnSameEGIDChecker.Add(egid, 0); } #if DONT_USE - [Conditional("CHECK_ALL")] + [Conditional("MEANINGLESS")] #endif - void CheckAddEntityID(EGID egid, Type entityDescriptorType, [CallerMemberName] string caller = null) + void CheckAddEntityID(EGID egid, Type entityDescriptorType, string caller) { if (_multipleOperationOnSameEGIDChecker.ContainsKey(egid) == true) throw new ECSException( @@ -57,27 +62,31 @@ namespace Svelto.ECS .FastConcat(" previous operation was: ") .FastConcat(_multipleOperationOnSameEGIDChecker[egid] == 1 ? "add" : "remove")); - var hash = _idChecker.GetOrCreate(egid.groupID, () => new HashSet()); + var hash = _idChecker.GetOrAdd(egid.groupID, () => new HashSet()); if (hash.Contains(egid.entityID) == true) - throw new ECSException("Trying to add an Entity already submitted to the database " - .FastConcat(" caller: ", caller, " ").FastConcat(egid.entityID) + 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); } #if DONT_USE - [Conditional("CHECK_ALL")] + [Conditional("MEANINGLESS")] #endif - void RemoveGroupID(ExclusiveBuildGroup groupID) { _idChecker.Remove(groupID); } + void RemoveGroupID(ExclusiveBuildGroup groupID) + { + _idChecker.Remove(groupID); + } #if DONT_USE - [Conditional("CHECK_ALL")] + [Conditional("MEANINGLESS")] #endif - void ClearChecks() { _multipleOperationOnSameEGIDChecker.FastClear(); } + void ClearDebugChecks() { _multipleOperationOnSameEGIDChecker.FastClear(); } readonly FasterDictionary _multipleOperationOnSameEGIDChecker; readonly FasterDictionary> _idChecker; diff --git a/com.sebaslab.svelto.ecs/Core/ComponentBuilder.CheckFields.cs b/com.sebaslab.svelto.ecs/Core/ComponentBuilder.CheckFields.cs index e33b87e..cc3ed6a 100644 --- a/com.sebaslab.svelto.ecs/Core/ComponentBuilder.CheckFields.cs +++ b/com.sebaslab.svelto.ecs/Core/ComponentBuilder.CheckFields.cs @@ -4,10 +4,11 @@ using System.Diagnostics; #endif using System; using System.Reflection; +using Svelto.ECS.Hybrid; namespace Svelto.ECS { - static class ComponentBuilderUtilities + public static class ComponentBuilderUtilities { const string MSG = "Entity Components and Entity View Components fields cannot hold managed fields outside the Svelto rules."; @@ -63,16 +64,28 @@ namespace Svelto.ECS for (int j = properties.Length - 1; j >= 0; --j) { - if (properties[j].PropertyType.IsGenericType) + Type propertyType = properties[j].PropertyType; + if (propertyType.IsGenericType) { - Type genericTypeDefinition = properties[j].PropertyType.GetGenericTypeDefinition(); + Type genericTypeDefinition = propertyType.GetGenericTypeDefinition(); if (genericTypeDefinition == RECATIVEVALUETYPE) { continue; } } - Type propertyType = properties[j].PropertyType; + // Special rules for ValueReference + if (IsValueReference(propertyType)) + { + // Getters of ValueReference must be refs, which would cause a failure on the common check. + if (properties[j].CanRead == true && propertyType.IsByRef == false) + { + ProcessError(MSG, entityComponentType, propertyType); + } + + continue; + } + if (propertyType != STRINGTYPE) { //for EntityComponentStructs, component fields that are structs that hold strings @@ -89,6 +102,12 @@ namespace Svelto.ECS return type == STRINGTYPE || type == STRINGBUILDERTYPE; } + static bool IsValueReference(Type type) + { + var interfaces = type.GetInterfaces(); + return interfaces.Length == 1 && interfaces[0] == VALUE_REF_TYPE; + } + /// /// This method checks the fields if it's an IEntityComponent, but checks all the properties if it's /// IEntityViewComponent @@ -99,8 +118,9 @@ namespace Svelto.ECS static void SubCheckFields(Type fieldType, Type entityComponentType, bool isStringAllowed = false) { //pass if it's Primitive or C# 8 unmanaged, or it's a string and string are allowed - //this check must allow pointers are they are unmanaged types - if ((isStringAllowed == true && IsString(fieldType) == true) || fieldType.IsValueTypeEx() == true) + //this check must allow pointers as they are unmanaged types + if ((isStringAllowed == true && IsString(fieldType) == true) || + fieldType.IsValueTypeEx() == true) { //if it's a struct we have to check the fields recursively if (IsString(fieldType) == false) @@ -110,7 +130,7 @@ namespace Svelto.ECS return; } - + ProcessError(MSG, entityComponentType, fieldType); } @@ -124,7 +144,8 @@ namespace Svelto.ECS throw new ECSException(message, entityComponentType); } - static readonly Type RECATIVEVALUETYPE = typeof(ReactiveValue<>); + static readonly Type RECATIVEVALUETYPE = typeof(ReactiveValue<>); + static readonly Type VALUE_REF_TYPE = typeof(IValueReferenceInternal); static readonly Type EGIDType = typeof(EGID); static readonly Type EXCLUSIVEGROUPSTRUCTTYPE = typeof(ExclusiveGroupStruct); static readonly Type SERIALIZABLE_ENTITY_STRUCT = typeof(SerializableEntityComponent); diff --git a/com.sebaslab.svelto.ecs/Core/ComponentBuilder.cs b/com.sebaslab.svelto.ecs/Core/ComponentBuilder.cs index f9a7c8a..bf2eaf1 100644 --- a/com.sebaslab.svelto.ecs/Core/ComponentBuilder.cs +++ b/com.sebaslab.svelto.ecs/Core/ComponentBuilder.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Reflection; +using System.Threading; using DBC.ECS; using Svelto.Common; using Svelto.DataStructures; @@ -22,33 +23,59 @@ namespace Svelto.ECS return obj.GetEntityComponentType().GetHashCode(); } } + + public static class BurstCompatibleCounter + { + public static int counter; + } - public class ComponentBuilder : IComponentBuilder - where T : struct, IEntityComponent + public class ComponentID where T : struct, IEntityComponent + { + public static readonly SharedStaticWrapper> id; + +#if UNITY_BURST + [Unity.Burst.BurstDiscard] + //SharedStatic values must be initialized from not burstified code +#endif + public static void Init() + { + id.Data = Interlocked.Increment(ref BurstCompatibleCounter.counter); + + DBC.ECS.Check.Ensure(id.Data < ushort.MaxValue, "too many types registered, HOW :)"); + } + } + + public class ComponentBuilder : IComponentBuilder where T : struct, IEntityComponent { internal static readonly Type ENTITY_COMPONENT_TYPE; - public static readonly bool HAS_EGID; internal static readonly bool IS_ENTITY_VIEW_COMPONENT; static readonly T DEFAULT_IT; static readonly string ENTITY_COMPONENT_NAME; static readonly bool IS_UNMANAGED; - public static bool HAS_REFERENCE; +#if SLOW_SVELTO_SUBMISSION + public static readonly bool HAS_EGID; + public static readonly bool HAS_REFERENCE; +#endif static ComponentBuilder() { - ENTITY_COMPONENT_TYPE = typeof(T); - DEFAULT_IT = default; + ENTITY_COMPONENT_TYPE = typeof(T); + DEFAULT_IT = default; IS_ENTITY_VIEW_COMPONENT = typeof(IEntityViewComponent).IsAssignableFrom(ENTITY_COMPONENT_TYPE); - HAS_EGID = typeof(INeedEGID).IsAssignableFrom(ENTITY_COMPONENT_TYPE); - HAS_REFERENCE = typeof(INeedEntityReference).IsAssignableFrom(ENTITY_COMPONENT_TYPE); - ENTITY_COMPONENT_NAME = ENTITY_COMPONENT_TYPE.ToString(); - IS_UNMANAGED = ENTITY_COMPONENT_TYPE.IsUnmanagedEx(); - +#if SLOW_SVELTO_SUBMISSION + HAS_EGID = typeof(INeedEGID).IsAssignableFrom(ENTITY_COMPONENT_TYPE); + HAS_REFERENCE = typeof(INeedEntityReference).IsAssignableFrom(ENTITY_COMPONENT_TYPE); + + SetEGIDWithoutBoxing.Warmup(); +#endif + ComponentID.Init(); + ENTITY_COMPONENT_NAME = ENTITY_COMPONENT_TYPE.ToString(); + IS_UNMANAGED = TypeType.isUnmanaged(); //attention this is important as it serves as warm up for Type +#if UNITY_NATIVE if (IS_UNMANAGED) EntityComponentIDMap.Register(new Filler()); - - SetEGIDWithoutBoxing.Warmup(); +#endif ComponentBuilderUtilities.CheckFields(ENTITY_COMPONENT_TYPE, IS_ENTITY_VIEW_COMPONENT); @@ -58,16 +85,22 @@ namespace Svelto.ECS } else { - if (ENTITY_COMPONENT_TYPE != ComponentBuilderUtilities.ENTITY_INFO_COMPONENT - && ENTITY_COMPONENT_TYPE.IsUnmanagedEx() == false) + if (ENTITY_COMPONENT_TYPE != ComponentBuilderUtilities.ENTITY_INFO_COMPONENT && + ENTITY_COMPONENT_TYPE.IsUnmanagedEx() == false) throw new Exception( $"Entity Component check failed, unexpected struct type (must be unmanaged) {ENTITY_COMPONENT_TYPE}"); } } - public ComponentBuilder() { _initializer = DEFAULT_IT; } + public ComponentBuilder() + { + _initializer = DEFAULT_IT; + } - public ComponentBuilder(in T initializer) : this() { _initializer = initializer; } + public ComponentBuilder(in T initializer) : this() + { + _initializer = initializer; + } public bool isUnmanaged => IS_UNMANAGED; @@ -75,37 +108,51 @@ namespace Svelto.ECS { var castedDic = dictionary as ITypeSafeDictionary; - T entityComponent = default; - if (IS_ENTITY_VIEW_COMPONENT) { - Check.Require(castedDic.ContainsKey(egid.entityID) == false - , $"building an entity with already used entity id! id: '{(ulong) egid}', {ENTITY_COMPONENT_NAME}"); + T entityComponent = default; + + Check.Require(castedDic.ContainsKey(egid.entityID) == false, + $"building an entity with already used entity id! id: '{(ulong)egid}', {ENTITY_COMPONENT_NAME}"); - this.SetEntityViewComponentImplementors(ref entityComponent, EntityViewComponentCache.cachedFields - , implementors, EntityViewComponentCache.implementorsByType - , EntityViewComponentCache.cachedTypes); + this.SetEntityViewComponentImplementors(ref entityComponent, EntityViewComponentCache.cachedFields, + implementors, EntityViewComponentCache.implementorsByType, EntityViewComponentCache.cachedTypes); castedDic.Add(egid.entityID, entityComponent); } else { - Check.Require(!castedDic.ContainsKey(egid.entityID) - , $"building an entity with already used entity id! id: '{egid.entityID}'"); + Check.Require(!castedDic.ContainsKey(egid.entityID), + $"building an entity with already used entity id! id: '{egid.entityID}'"); castedDic.Add(egid.entityID, _initializer); } } - void IComponentBuilder.Preallocate(ITypeSafeDictionary dictionary, uint size) { Preallocate(dictionary, size); } + void IComponentBuilder.Preallocate(ITypeSafeDictionary dictionary, uint size) + { + Preallocate(dictionary, size); + } - public ITypeSafeDictionary CreateDictionary(uint size) { return TypeSafeDictionaryFactory.Create(size); } + public ITypeSafeDictionary CreateDictionary(uint size) + { + return TypeSafeDictionaryFactory.Create(size); + } - public Type GetEntityComponentType() { return ENTITY_COMPONENT_TYPE; } + public Type GetEntityComponentType() + { + return ENTITY_COMPONENT_TYPE; + } - public override int GetHashCode() { return _initializer.GetHashCode(); } + public override int GetHashCode() + { + return _initializer.GetHashCode(); + } - static void Preallocate(ITypeSafeDictionary dictionary, uint size) { dictionary.ResizeTo(size); } + static void Preallocate(ITypeSafeDictionary dictionary, uint size) + { + dictionary.EnsureCapacity(size); + } readonly T _initializer; @@ -156,7 +203,9 @@ namespace Svelto.ECS #endif } - internal static void InitCache() { } + internal static void InitCache() + { + } } } } \ No newline at end of file diff --git a/com.sebaslab.svelto.ecs/Core/DBC.cs b/com.sebaslab.svelto.ecs/Core/DBC.cs index 295fc45..97db65f 100644 --- a/com.sebaslab.svelto.ecs/Core/DBC.cs +++ b/com.sebaslab.svelto.ecs/Core/DBC.cs @@ -55,7 +55,7 @@ namespace DBC.ECS /// static class Check { - #region Interface +#region Interface /// /// Precondition check. @@ -65,15 +65,8 @@ namespace DBC.ECS #endif public static void Require(bool assertion, string message) { - if (UseExceptions) - { - if (!assertion) - throw new PreconditionException(message); - } - else - { - Trace.Assert(assertion, "Precondition: " + message); - } + if (!assertion) + throw new PreconditionException(message); } /// @@ -85,15 +78,8 @@ namespace DBC.ECS #endif public static void Require(bool assertion, string message, Exception inner) { - if (UseExceptions) - { - if (!assertion) - throw new PreconditionException(message, inner); - } - else - { - Trace.Assert(assertion, "Precondition: " + message); - } + if (!assertion) + throw new PreconditionException(message, inner); } /// @@ -105,15 +91,8 @@ namespace DBC.ECS #endif public static void Require(bool assertion) { - if (UseExceptions) - { - if (!assertion) - throw new PreconditionException("Precondition failed."); - } - else - { - Trace.Assert(assertion, "Precondition failed."); - } + if (!assertion) + throw new PreconditionException("Precondition failed."); } /// @@ -125,15 +104,8 @@ namespace DBC.ECS #endif public static void Ensure(bool assertion, string message) { - if (UseExceptions) - { - if (!assertion) - throw new PostconditionException(message); - } - else - { - Trace.Assert(assertion, "Postcondition: " + message); - } + if (!assertion) + throw new PostconditionException(message); } /// @@ -145,15 +117,8 @@ namespace DBC.ECS #endif public static void Ensure(bool assertion, string message, Exception inner) { - if (UseExceptions) - { - if (!assertion) - throw new PostconditionException(message, inner); - } - else - { - Trace.Assert(assertion, "Postcondition: " + message); - } + if (!assertion) + throw new PostconditionException(message, inner); } /// @@ -165,15 +130,8 @@ namespace DBC.ECS #endif public static void Ensure(bool assertion) { - if (UseExceptions) - { - if (!assertion) - throw new PostconditionException("Postcondition failed."); - } - else - { - Trace.Assert(assertion, "Postcondition failed."); - } + if (!assertion) + throw new PostconditionException("Postcondition failed."); } /// @@ -185,15 +143,8 @@ namespace DBC.ECS #endif public static void Invariant(bool assertion, string message) { - if (UseExceptions) - { - if (!assertion) - throw new InvariantException(message); - } - else - { - Trace.Assert(assertion, "Invariant: " + message); - } + if (!assertion) + throw new InvariantException(message); } /// @@ -205,15 +156,8 @@ namespace DBC.ECS #endif public static void Invariant(bool assertion, string message, Exception inner) { - if (UseExceptions) - { - if (!assertion) - throw new InvariantException(message, inner); - } - else - { - Trace.Assert(assertion, "Invariant: " + message); - } + if (!assertion) + throw new InvariantException(message, inner); } /// @@ -225,15 +169,8 @@ namespace DBC.ECS #endif public static void Invariant(bool assertion) { - if (UseExceptions) - { - if (!assertion) - throw new InvariantException("Invariant failed."); - } - else - { - Trace.Assert(assertion, "Invariant failed."); - } + if (!assertion) + throw new InvariantException("Invariant failed."); } /// @@ -244,15 +181,8 @@ namespace DBC.ECS #endif public static void Assert(bool assertion, string message) { - if (UseExceptions) - { - if (!assertion) - throw new AssertionException(message); - } - else - { - Trace.Assert(assertion, "Assertion: " + message); - } + if (!assertion) + throw new AssertionException(message); } /// @@ -264,15 +194,8 @@ namespace DBC.ECS #endif public static void Assert(bool assertion, string message, Exception inner) { - if (UseExceptions) - { - if (!assertion) - throw new AssertionException(message, inner); - } - else - { - Trace.Assert(assertion, "Assertion: " + message); - } + if (!assertion) + throw new AssertionException(message, inner); } /// @@ -284,59 +207,22 @@ namespace DBC.ECS #endif public static void Assert(bool assertion) { - if (UseExceptions) - { - if (!assertion) - throw new AssertionException("Assertion failed."); - } - else - { - Trace.Assert(assertion, "Assertion failed."); - } + if (!assertion) + throw new AssertionException("Assertion failed."); } - /// - /// Set this if you wish to use Trace Assert statements - /// instead of exception handling. - /// (The Check class uses exception handling by default.) - /// - public static bool UseAssertions - { +#endregion // Interface - get - { - return useAssertions; - } - set - { - useAssertions = value; - } - } - - #endregion // Interface - - #region Implementation +#region Implementation // No creation /// /// Is exception handling being used? /// - static bool UseExceptions - { - get - { - return !useAssertions; - } - } - - // Are trace assertion statements being used? - // Default is to use exception handling. - static bool useAssertions; - - #endregion // Implementation - } // End Check +#endregion // Implementation + } // End Check internal class Trace { @@ -350,7 +236,7 @@ namespace DBC.ECS } } - #region Exceptions +#region Exceptions /// /// Exception raised when a contract is broken. @@ -360,9 +246,17 @@ namespace DBC.ECS /// public class DesignByContractException : Exception { - protected DesignByContractException() {} - protected DesignByContractException(string message) : base(message) {} - protected DesignByContractException(string message, Exception inner) : base(message, inner) {} + protected DesignByContractException() + { + } + + protected DesignByContractException(string message) : base(message) + { + } + + protected DesignByContractException(string message, Exception inner) : base(message, inner) + { + } } /// @@ -373,15 +267,23 @@ namespace DBC.ECS /// /// Precondition Exception. /// - public PreconditionException() {} + public PreconditionException() + { + } + /// /// Precondition Exception. /// - public PreconditionException(string message) : base(message) {} + public PreconditionException(string message) : base(message) + { + } + /// /// Precondition Exception. /// - public PreconditionException(string message, Exception inner) : base(message, inner) {} + public PreconditionException(string message, Exception inner) : base(message, inner) + { + } } /// @@ -392,15 +294,23 @@ namespace DBC.ECS /// /// Postcondition Exception. /// - public PostconditionException() {} + public PostconditionException() + { + } + /// /// Postcondition Exception. /// - public PostconditionException(string message) : base(message) {} + public PostconditionException(string message) : base(message) + { + } + /// /// Postcondition Exception. /// - public PostconditionException(string message, Exception inner) : base(message, inner) {} + public PostconditionException(string message, Exception inner) : base(message, inner) + { + } } /// @@ -411,15 +321,23 @@ namespace DBC.ECS /// /// Invariant Exception. /// - public InvariantException() {} + public InvariantException() + { + } + /// /// Invariant Exception. /// - public InvariantException(string message) : base(message) {} + public InvariantException(string message) : base(message) + { + } + /// /// Invariant Exception. /// - public InvariantException(string message, Exception inner) : base(message, inner) {} + public InvariantException(string message, Exception inner) : base(message, inner) + { + } } /// @@ -430,17 +348,24 @@ namespace DBC.ECS /// /// Assertion Exception. /// - public AssertionException() {} + public AssertionException() + { + } + /// /// Assertion Exception. /// - public AssertionException(string message) : base(message) {} + public AssertionException(string message) : base(message) + { + } + /// /// Assertion Exception. /// - public AssertionException(string message, Exception inner) : base(message, inner) {} + public AssertionException(string message, Exception inner) : base(message, inner) + { + } } - #endregion // Exception classes - -} // End Design By Contract +#endregion // Exception classes +} // End Design By Contract \ No newline at end of file diff --git a/com.sebaslab.svelto.ecs/Core/EGIDMapper.cs b/com.sebaslab.svelto.ecs/Core/EGIDMapper.cs index 6966d1f..a1c7f2d 100644 --- a/com.sebaslab.svelto.ecs/Core/EGIDMapper.cs +++ b/com.sebaslab.svelto.ecs/Core/EGIDMapper.cs @@ -8,16 +8,16 @@ namespace Svelto.ECS /// /// /// - public readonly struct EGIDMapper: IEGIDMapper where T : struct, IEntityComponent + public readonly struct EGIDMapper : IEGIDMapper where T : struct, IEntityComponent { - public uint length => _map.count; - public ExclusiveGroupStruct groupID { get; } - public Type entityType => TypeCache.type; + public int count => _map.count; + public ExclusiveGroupStruct groupID { get; } + public Type entityType => TypeCache.type; internal EGIDMapper(ExclusiveGroupStruct groupStructId, ITypeSafeDictionary dic) : this() { groupID = groupStructId; - _map = dic; + _map = dic; } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -25,9 +25,10 @@ namespace Svelto.ECS { #if DEBUG && !PROFILE_SVELTO if (_map == null) - throw new System.Exception("Not initialized EGIDMapper in this group ".FastConcat(typeof(T).ToString())); - if (_map.TryFindIndex(entityID, out var findIndex) == false) - throw new System.Exception("Entity not found in this group ".FastConcat(typeof(T).ToString())); + throw new System.Exception( + "Not initialized EGIDMapper in this group ".FastConcat(typeof(T).ToString())); + if (_map.TryFindIndex(entityID, out var findIndex) == false) + throw new System.Exception("Entity not found in this group ".FastConcat(typeof(T).ToString())); #else _map.TryFindIndex(entityID, out var findIndex); #endif @@ -72,8 +73,8 @@ namespace Svelto.ECS bool FindIndex(uint valueKey, out uint index); uint GetIndex(uint entityID); bool Exists(uint idEntityId); - - ExclusiveGroupStruct groupID { get; } - Type entityType { get; } + + ExclusiveGroupStruct groupID { get; } + Type entityType { get; } } } \ 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 acfa0f2..6389d8f 100644 --- a/com.sebaslab.svelto.ecs/Core/EnginesGroup/SortedEnginesGroup.cs +++ b/com.sebaslab.svelto.ecs/Core/EnginesGroup/SortedEnginesGroup.cs @@ -11,7 +11,6 @@ namespace Svelto.ECS ///{ /// WiresTimeRunningGroup, /// WiresPreInitTimeRunningGroup, - /// WiresInitTimeStoppedGroup, /// WiresInitTimeRunningGroup ///} /// diff --git a/com.sebaslab.svelto.ecs/Core/EnginesRoot.DoubleBufferedEntitiesToAdd.cs b/com.sebaslab.svelto.ecs/Core/EnginesRoot.DoubleBufferedEntitiesToAdd.cs index e407d8b..a9ebeb9 100644 --- a/com.sebaslab.svelto.ecs/Core/EnginesRoot.DoubleBufferedEntitiesToAdd.cs +++ b/com.sebaslab.svelto.ecs/Core/EnginesRoot.DoubleBufferedEntitiesToAdd.cs @@ -7,77 +7,98 @@ namespace Svelto.ECS { internal class DoubleBufferedEntitiesToAdd { - const int MAX_NUMBER_OF_ITEMS_PER_FRAME_BEFORE_TO_CLEAR = 100; + //while caching is good to avoid over creating dictionaries that may be reused, the side effect + //is that I have to iterate every time up to 100 dictionaries during the flushing of the build entities + //even if there are 0 entities inside. + const int MAX_NUMBER_OF_GROUPS_TO_CACHE = 100; + const int MAX_NUMBER_OF_TYPES_PER_GROUP_TO_CACHE = 100; public DoubleBufferedEntitiesToAdd() { - _currentEntitiesCreatedPerGroup = _entitiesCreatedPerGroupA; - _otherEntitiesCreatedPerGroup = _entitiesCreatedPerGroupB; + var entitiesCreatedPerGroupA = new FasterDictionary(); + var entitiesCreatedPerGroupB = new FasterDictionary(); + var entityComponentsToAddBufferA = + new FasterDictionary>(); + var entityComponentsToAddBufferB = + new FasterDictionary>(); - current = _entityComponentsToAddBufferA; - other = _entityComponentsToAddBufferB; + _currentNumberEntitiesCreatedPerGroup = entitiesCreatedPerGroupA; + _lastNumberEntitiesCreatedPerGroup = entitiesCreatedPerGroupB; + + currentComponentsToAddPerGroup = entityComponentsToAddBufferA; + lastComponentsToAddPerGroup = entityComponentsToAddBufferB; } - public void ClearOther() + public void ClearLastAddOperations() { - //do not clear the groups created so far, they will be reused, unless they are too many! - var otherCount = other.count; - if (otherCount > MAX_NUMBER_OF_ITEMS_PER_FRAME_BEFORE_TO_CLEAR) + var numberOfGroupsAddedSoFar = lastComponentsToAddPerGroup.count; + var componentDictionariesPerType = lastComponentsToAddPerGroup.unsafeValues; + + //TODO: rewrite the caching logic with the new RecycleOrAdd dictionary functionality + //I still do not want to cache too many groups + + //If we didn't create too many groups, we keep them alive, so we avoid the cost of creating new dictionaries + //during future submissions, otherwise we clean up everything + if (numberOfGroupsAddedSoFar > MAX_NUMBER_OF_GROUPS_TO_CACHE) { - var otherValuesArray = other.unsafeValues; - for (var i = 0; i < otherCount; ++i) + for (var i = 0; i < numberOfGroupsAddedSoFar; ++i) { - var safeDictionariesCount = otherValuesArray[i].count; - var safeDictionaries = otherValuesArray[i].unsafeValues; + var componentTypesCount = componentDictionariesPerType[i].count; + var componentTypesDictionary = componentDictionariesPerType[i].unsafeValues; { - for (var j = 0; j < safeDictionariesCount; ++j) - //clear the dictionary of entities create do far (it won't allocate though) - safeDictionaries[j].Dispose(); + for (var j = 0; j < componentTypesCount; ++j) + //dictionaries of components may be native so they need to be disposed + //before the references are GCed + componentTypesDictionary[j].Dispose(); } } - + //reset the number of entities created so far - _otherEntitiesCreatedPerGroup.FastClear(); - other.FastClear(); + _lastNumberEntitiesCreatedPerGroup.FastClear(); + lastComponentsToAddPerGroup.FastClear(); + return; } - + + for (var i = 0; i < numberOfGroupsAddedSoFar; ++i) { - var otherValuesArray = other.unsafeValues; - for (var i = 0; i < otherCount; ++i) + var componentTypesCount = componentDictionariesPerType[i].count; + ITypeSafeDictionary[] componentTypesDictionary = componentDictionariesPerType[i].unsafeValues; + for (var j = 0; j < componentTypesCount; ++j) + //clear the dictionary of entities created so far (it won't allocate though) + componentTypesDictionary[j].Clear(); + + //if we didn't create too many component for this group, I reuse the component arrays + if (componentTypesCount <= MAX_NUMBER_OF_TYPES_PER_GROUP_TO_CACHE) { - var safeDictionariesCount = otherValuesArray[i].count; - var safeDictionaries = otherValuesArray[i].unsafeValues; - //do not remove the dictionaries of entities per type created so far, they will be reused - if (safeDictionariesCount <= MAX_NUMBER_OF_ITEMS_PER_FRAME_BEFORE_TO_CLEAR) - { - for (var j = 0; j < safeDictionariesCount; ++j) - //clear the dictionary of entities create do far (it won't allocate though) - safeDictionaries[j].FastClear(); - } - else - { - for (var j = 0; j < safeDictionariesCount; ++j) - //clear the dictionary of entities create do far (it won't allocate though) - safeDictionaries[j].Dispose(); - - otherValuesArray[i].FastClear(); - } + for (var j = 0; j < componentTypesCount; ++j) + componentTypesDictionary[j].Clear(); + } + else + { + //here I have to dispose, because I am actually clearing the reference of the dictionary + //with the next line. + for (var j = 0; j < componentTypesCount; ++j) + componentTypesDictionary[j].Dispose(); + + componentDictionariesPerType[i].FastClear(); } - - //reset the number of entities created so far - _otherEntitiesCreatedPerGroup.FastClear(); } + + //reset the number of entities created so far + _lastNumberEntitiesCreatedPerGroup.FastClear(); + + // _totalEntitiesToAdd = 0; } public void Dispose() { { - var otherValuesArray = other.unsafeValues; - for (var i = 0; i < other.count; ++i) + var otherValuesArray = lastComponentsToAddPerGroup.unsafeValues; + for (var i = 0; i < lastComponentsToAddPerGroup.count; ++i) { - var safeDictionariesCount = otherValuesArray[i].count; - var safeDictionaries = otherValuesArray[i].unsafeValues; + int safeDictionariesCount = otherValuesArray[i].count; + ITypeSafeDictionary[] safeDictionaries = otherValuesArray[i].unsafeValues; //do not remove the dictionaries of entities per type created so far, they will be reused for (var j = 0; j < safeDictionariesCount; ++j) //clear the dictionary of entities create do far (it won't allocate though) @@ -85,98 +106,158 @@ namespace Svelto.ECS } } { - var currentValuesArray = current.unsafeValues; - for (var i = 0; i < current.count; ++i) + var currentValuesArray = currentComponentsToAddPerGroup.unsafeValues; + for (var i = 0; i < currentComponentsToAddPerGroup.count; ++i) { - var safeDictionariesCount = currentValuesArray[i].count; - var safeDictionaries = currentValuesArray[i].unsafeValues; + int safeDictionariesCount = currentValuesArray[i].count; + ITypeSafeDictionary[] safeDictionaries = currentValuesArray[i].unsafeValues; //do not remove the dictionaries of entities per type created so far, they will be reused for (var j = 0; j < safeDictionariesCount; ++j) //clear the dictionary of entities create do far (it won't allocate though) safeDictionaries[j].Dispose(); } } + + _currentNumberEntitiesCreatedPerGroup = null; + _lastNumberEntitiesCreatedPerGroup = null; + lastComponentsToAddPerGroup = null; + currentComponentsToAddPerGroup = null; } internal bool AnyEntityCreated() { - return _currentEntitiesCreatedPerGroup.count > 0; + return _currentNumberEntitiesCreatedPerGroup.count > 0; } - internal bool AnyOtherEntityCreated() + internal bool AnyPreviousEntityCreated() { - return _otherEntitiesCreatedPerGroup.count > 0; + return _lastNumberEntitiesCreatedPerGroup.count > 0; } internal void IncrementEntityCount(ExclusiveGroupStruct groupID) { - _currentEntitiesCreatedPerGroup.GetOrCreate(groupID)++; + _currentNumberEntitiesCreatedPerGroup.GetOrAdd(groupID)++; + // _totalEntitiesToAdd++; } + // public uint NumberOfEntitiesToAdd() + // { + // return _totalEntitiesToAdd; + // } + internal void Preallocate (ExclusiveGroupStruct groupID, uint numberOfEntities, IComponentBuilder[] entityComponentsToBuild) { void PreallocateDictionaries (FasterDictionary> dic) { - var group = dic.GetOrCreate(groupID, () => new FasterDictionary()); + var group = dic.GetOrAdd( + groupID, () => new FasterDictionary()); foreach (var componentBuilder in entityComponentsToBuild) { var entityComponentType = componentBuilder.GetEntityComponentType(); - var safeDictionary = group.GetOrCreate(new RefWrapperType(entityComponentType) + var safeDictionary = group.GetOrAdd(new RefWrapperType(entityComponentType) , () => componentBuilder .CreateDictionary(numberOfEntities)); componentBuilder.Preallocate(safeDictionary, numberOfEntities); } } - PreallocateDictionaries(current); - PreallocateDictionaries(other); + PreallocateDictionaries(currentComponentsToAddPerGroup); + PreallocateDictionaries(lastComponentsToAddPerGroup); - _currentEntitiesCreatedPerGroup.GetOrCreate(groupID); - _otherEntitiesCreatedPerGroup.GetOrCreate(groupID); + _currentNumberEntitiesCreatedPerGroup.GetOrAdd(groupID); + _lastNumberEntitiesCreatedPerGroup.GetOrAdd(groupID); } internal void Swap() { - Swap(ref current, ref other); - Swap(ref _currentEntitiesCreatedPerGroup, ref _otherEntitiesCreatedPerGroup); + Swap(ref currentComponentsToAddPerGroup, ref lastComponentsToAddPerGroup); + Swap(ref _currentNumberEntitiesCreatedPerGroup, ref _lastNumberEntitiesCreatedPerGroup); } - void Swap(ref T item1, ref T item2) + static void Swap(ref T item1, ref T item2) { - var toSwap = item2; - item2 = item1; - item1 = toSwap; + (item2, item1) = (item1, item2); + } + + public OtherComponentsToAddPerGroupEnumerator GetEnumerator() + { + return new OtherComponentsToAddPerGroupEnumerator(lastComponentsToAddPerGroup + , _lastNumberEntitiesCreatedPerGroup); } //Before I tried for the third time to use a SparseSet instead of FasterDictionary, remember that //while group indices are sequential, they may not be used in a sequential order. Sparseset needs //entities to be created sequentially (the index cannot be managed externally) - internal FasterDictionary> current; - internal FasterDictionary> other; - - readonly FasterDictionary _entitiesCreatedPerGroupA = - new FasterDictionary(); - - readonly FasterDictionary _entitiesCreatedPerGroupB = - new FasterDictionary(); - - readonly FasterDictionary> _entityComponentsToAddBufferA = - new FasterDictionary>(); + internal FasterDictionary> + currentComponentsToAddPerGroup; - readonly FasterDictionary> _entityComponentsToAddBufferB = - new FasterDictionary>(); + FasterDictionary> + lastComponentsToAddPerGroup; /// /// To avoid extra allocation, I don't clear the groups, so I need an extra data structure /// to keep count of the number of entities built this frame. At the moment the actual number /// of entities built is not used /// - FasterDictionary _currentEntitiesCreatedPerGroup; - FasterDictionary _otherEntitiesCreatedPerGroup; + FasterDictionary _currentNumberEntitiesCreatedPerGroup; + FasterDictionary _lastNumberEntitiesCreatedPerGroup; + + //uint _totalEntitiesToAdd; } } + + struct OtherComponentsToAddPerGroupEnumerator + { + public OtherComponentsToAddPerGroupEnumerator + (FasterDictionary> + lastComponentsToAddPerGroup + , FasterDictionary otherNumberEntitiesCreatedPerGroup) + { + _lastComponentsToAddPerGroup = lastComponentsToAddPerGroup; + _lastNumberEntitiesCreatedPerGroup = otherNumberEntitiesCreatedPerGroup.GetEnumerator(); + Current = default; + } + + public bool MoveNext() + { + while (_lastNumberEntitiesCreatedPerGroup.MoveNext()) + { + var current = _lastNumberEntitiesCreatedPerGroup.Current; + + if (current.value > 0) //there are entities in this group + { + var value = _lastComponentsToAddPerGroup[current.key]; + Current = new GroupInfo() + { + group = current.key + , components = value + }; + + return true; + } + } + + return false; + } + + public GroupInfo Current { get; private set; } + + //cannot be read only as they will be modified by MoveNext + readonly FasterDictionary> + _lastComponentsToAddPerGroup; + + SveltoDictionaryKeyValueEnumerator>, ManagedStrategy, + ManagedStrategy> + _lastNumberEntitiesCreatedPerGroup; + } + + struct GroupInfo + { + public ExclusiveGroupStruct group; + public FasterDictionary components; + } } \ No newline at end of file diff --git a/com.sebaslab.svelto.ecs/Core/EnginesRoot.Engines.cs b/com.sebaslab.svelto.ecs/Core/EnginesRoot.Engines.cs index 908c438..c48f2af 100644 --- a/com.sebaslab.svelto.ecs/Core/EnginesRoot.Engines.cs +++ b/com.sebaslab.svelto.ecs/Core/EnginesRoot.Engines.cs @@ -1,19 +1,33 @@ +#if PROFILE_SVELTO && DEBUG +#warning the global define PROFILE_SVELTO should be used only when it's necessary to profile in order to reduce the overhead of debug code. Normally remove this define to get insights when errors happen +#endif + using System; using System.Collections.Generic; +using DBC.ECS; using Svelto.Common; +using Svelto.Common.DataStructures; using Svelto.DataStructures; using Svelto.ECS.Internal; using Svelto.ECS.Schedulers; namespace Svelto.ECS { - public sealed partial class EnginesRoot + public partial class EnginesRoot { static EnginesRoot() { GroupHashMap.Init(); + SharedDictonary.Init(); SerializationDescriptorMap.Init(); + + + _swapEntities = SwapEntities; + _removeEntities = RemoveEntities; + _removeGroup = RemoveGroup; + _swapGroup = SwapGroup; } + /// /// Engines root contextualize your engines and entities. You don't need to limit yourself to one EngineRoot /// as multiple engines root could promote separation of scopes. The EntitySubmissionScheduler checks @@ -24,36 +38,51 @@ namespace Svelto.ECS /// public EnginesRoot(EntitiesSubmissionScheduler entitiesComponentScheduler) { - _entitiesOperations = new FasterDictionary(); - _idChecker = new FasterDictionary>(); - _multipleOperationOnSameEGIDChecker = new FasterDictionary(); -#if UNITY_NATIVE //because of the thread count, ATM this is only for unity - _nativeSwapOperationQueue = new DataStructures.AtomicNativeBags(Allocator.Persistent); - _nativeRemoveOperationQueue = new DataStructures.AtomicNativeBags(Allocator.Persistent); - _nativeAddOperationQueue = new DataStructures.AtomicNativeBags(Allocator.Persistent); -#endif - _serializationDescriptorMap = new SerializationDescriptorMap(); - _maxNumberOfOperationsPerFrame = uint.MaxValue; - _reactiveEnginesAddRemove = new FasterDictionary>(); - _reactiveEnginesAddRemoveOnDispose = - new FasterDictionary>(); - _reactiveEnginesSwap = new FasterDictionary>(); - _reactiveEnginesSubmission = new FasterList(); - _enginesSet = new FasterList(); - _enginesTypeSet = new HashSet(); - _disposableEngines = new FasterList(); - _transientEntitiesOperations = new FasterList(); + _entitiesOperations = new EntitiesOperations(); + _idChecker = new FasterDictionary>(); + + _cachedRangeOfSubmittedIndices = new FasterList<(uint, uint)>(); + _cachedIndicesToSwapBeforeSubmissionForFilters = new FasterDictionary(); + + _multipleOperationOnSameEGIDChecker = new FasterDictionary(); +#if UNITY_NATIVE //because of the thread count, ATM this is only for unity + _nativeSwapOperationQueue = new Svelto.ECS.DataStructures.AtomicNativeBags(Allocator.Persistent); + _nativeRemoveOperationQueue = new Svelto.ECS.DataStructures.AtomicNativeBags(Allocator.Persistent); + _nativeAddOperationQueue = new Svelto.ECS.DataStructures.AtomicNativeBags(Allocator.Persistent); +#endif + _serializationDescriptorMap = new SerializationDescriptorMap(); + _reactiveEnginesAdd = new FasterDictionary>>(); + _reactiveEnginesAddEx = + new FasterDictionary>>(); + _reactiveEnginesRemove = + new FasterDictionary>>(); + _reactiveEnginesRemoveEx = + new FasterDictionary>>(); + _reactiveEnginesSwap = + new FasterDictionary>>(); + _reactiveEnginesSwapEx = + new FasterDictionary>>(); + + _reactiveEnginesDispose = + new FasterDictionary>>(); + + _reactiveEnginesSubmission = new FasterList(); + _enginesSet = new FasterList(); + _enginesTypeSet = new HashSet(); + _disposableEngines = new FasterList(); _groupEntityComponentsDB = new FasterDictionary>(); _groupsPerEntity = new FasterDictionary>(); _groupedEntityToAdd = new DoubleBufferedEntitiesToAdd(); - _entityStreams = EntitiesStreams.Create(); + _entityStreams = EntitiesStreams.Create(); _groupFilters = - new FasterDictionary>(); + new FasterDictionary>(); _entityLocator.InitEntityReferenceMap(); - _entitiesDB = new EntitiesDB(this,_entityLocator); + _entitiesDB = new EntitiesDB(this, _entityLocator); + + InitFilters(); scheduler = entitiesComponentScheduler; scheduler.onTick = new EntitiesSubmitter(this); @@ -62,11 +91,10 @@ namespace Svelto.ECS #endif } - public EnginesRoot - (EntitiesSubmissionScheduler entitiesComponentScheduler, bool isDeserializationOnly) : - this(entitiesComponentScheduler) + protected EnginesRoot(EntitiesSubmissionScheduler entitiesComponentScheduler, + EnginesReadyOption enginesWaitForReady) : this(entitiesComponentScheduler) { - _isDeserializationOnly = isDeserializationOnly; + _enginesWaitForReady = enginesWaitForReady; } public EntitiesSubmissionScheduler scheduler { get; } @@ -78,7 +106,117 @@ namespace Svelto.ECS /// public void Dispose() { - _isDisposing = true; + Dispose(true); + GC.SuppressFinalize(this); + } + + public void AddEngine(IEngine engine) + { + var type = engine.GetType(); + var refWrapper = new RefWrapperType(type); + Check.Require(engine != null, "Engine to add is invalid or null"); + Check.Require( + _enginesTypeSet.Contains(refWrapper) == false || + type.ContainsCustomAttribute(typeof(AllowMultipleAttribute)), + "The same engine has been added more than once, if intentional, use [AllowMultiple] class attribute " + .FastConcat(engine.ToString())); + try + { + if (engine is IReactOnAdd viewEngineAdd) + CheckReactEngineComponents(typeof(IReactOnAdd<>), viewEngineAdd, _reactiveEnginesAdd, type.Name); + + if (engine is IReactOnAddEx viewEngineAddEx) + CheckReactEngineComponents(typeof(IReactOnAddEx<>), viewEngineAddEx, _reactiveEnginesAddEx, type.Name); + + if (engine is IReactOnRemove viewEngineRemove) + CheckReactEngineComponents(typeof(IReactOnRemove<>), viewEngineRemove, _reactiveEnginesRemove, type.Name); + + if (engine is IReactOnRemoveEx viewEngineRemoveEx) + CheckReactEngineComponents(typeof(IReactOnRemoveEx<>), viewEngineRemoveEx, _reactiveEnginesRemoveEx, type.Name); + + if (engine is IReactOnDispose viewEngineDispose) + CheckReactEngineComponents(typeof(IReactOnDispose<>), viewEngineDispose, _reactiveEnginesDispose, type.Name); + + if (engine is IReactOnSwap viewEngineSwap) + CheckReactEngineComponents(typeof(IReactOnSwap<>), viewEngineSwap, _reactiveEnginesSwap, type.Name); + + if (engine is IReactOnSwapEx viewEngineSwapEx) + CheckReactEngineComponents(typeof(IReactOnSwapEx<>), viewEngineSwapEx, _reactiveEnginesSwapEx, type.Name); + + if (engine is IReactOnSubmission submissionEngine) + _reactiveEnginesSubmission.Add(submissionEngine); + + _enginesTypeSet.Add(refWrapper); + _enginesSet.Add(engine); + + if (engine is IDisposable) + _disposableEngines.Add(engine as IDisposable); + + if (engine is IQueryingEntitiesEngine queryableEntityComponentEngine) + queryableEntityComponentEngine.entitiesDB = _entitiesDB; + + if (_enginesWaitForReady == EnginesReadyOption.ReadyAsAdded && engine is IGetReadyEngine getReadyEngine) + getReadyEngine.Ready(); + } + catch (Exception e) + { + throw new ECSException("Code crashed while adding engine ".FastConcat(engine.GetType().ToString(), " "), + e); + } + } + + public void Ready() + { + Check.Require(_enginesWaitForReady == EnginesReadyOption.WaitForReady, + "The engine has not been initialise to wait for an external ready trigger"); + + foreach (var engine in _enginesSet) + if (engine is IGetReadyEngine getReadyEngine) + getReadyEngine.Ready(); + } + + static void AddEngineToList(T engine, Type[] entityComponentTypes, + FasterDictionary>> engines, string typeName) + where T : class, IReactEngine + { + for (var i = 0; i < entityComponentTypes.Length; i++) + { + var type = entityComponentTypes[i]; + + if (engines.TryGetValue(new RefWrapperType(type), out var list) == false) + { + list = new FasterList>(); + + engines.Add(new RefWrapperType(type), list); + } + + list.Add(new ReactEngineContainer(engine, typeName)); + } + } + + void CheckReactEngineComponents(Type genericDefinition, T engine, + FasterDictionary>> engines, string typeName) + where T : class, IReactEngine + { + var interfaces = engine.GetType().GetInterfaces(); + + foreach (var interf in interfaces) + { + if (interf.IsGenericTypeEx() && interf.GetGenericTypeDefinition() == genericDefinition) + { + var genericArguments = interf.GetGenericArgumentsEx(); + + AddEngineToList(engine, genericArguments, engines, typeName); + } + } + } + + void Dispose(bool disposing) + { + _isDisposing = disposing; + + if (disposing == false) + return; using (var profiler = new PlatformProfiler("Final Dispose")) { @@ -99,11 +237,13 @@ namespace Svelto.ECS } foreach (var groups in _groupEntityComponentsDB) - foreach (var entityList in groups.Value) + foreach (var entityList in groups.value) try { - entityList.Value.ExecuteEnginesRemoveCallbacks(_reactiveEnginesAddRemoveOnDispose, profiler - , groups.Key); + ITypeSafeDictionary typeSafeDictionary = entityList.value; + + typeSafeDictionary.ExecuteEnginesDisposeCallbacks_Group(_reactiveEnginesDispose, groups.key, + profiler); } catch (Exception e) { @@ -111,15 +251,17 @@ namespace Svelto.ECS } foreach (var groups in _groupEntityComponentsDB) - foreach (var entityList in groups.Value) - entityList.Value.Dispose(); + foreach (var entityList in groups.value) + entityList.value.Dispose(); foreach (var type in _groupFilters) - foreach (var group in type.Value) - group.Value.Dispose(); + foreach (var group in type.value) + group.value.Dispose(); _groupFilters.Clear(); + DisposeFilters(); + #if UNITY_NATIVE _nativeAddOperationQueue.Dispose(); _nativeRemoveOperationQueue.Dispose(); @@ -132,207 +274,128 @@ namespace Svelto.ECS _enginesSet.Clear(); _enginesTypeSet.Clear(); _reactiveEnginesSwap.Clear(); - _reactiveEnginesAddRemove.Clear(); - _reactiveEnginesAddRemoveOnDispose.Clear(); + _reactiveEnginesAdd.Clear(); + _reactiveEnginesRemove.Clear(); + _reactiveEnginesDispose.Clear(); _reactiveEnginesSubmission.Clear(); - _entitiesOperations.Clear(); - _transientEntitiesOperations.Clear(); - _groupedEntityToAdd.Dispose(); _entityLocator.DisposeEntityReferenceMap(); - + _entityStreams.Dispose(); scheduler.Dispose(); } + } - GC.SuppressFinalize(this); + void NotifyReactiveEnginesOnSubmission() + { + var enginesCount = _reactiveEnginesSubmission.count; + for (var i = 0; i < enginesCount; i++) + _reactiveEnginesSubmission[i].EntitiesSubmitted(); } - public void AddEngine(IEngine engine) + public readonly struct EntitiesSubmitter { - var type = engine.GetType(); - var refWrapper = new RefWrapperType(type); - DBC.ECS.Check.Require(engine != null, "Engine to add is invalid or null"); - DBC.ECS.Check.Require( - _enginesTypeSet.Contains(refWrapper) == false - || type.ContainsCustomAttribute(typeof(AllowMultipleAttribute)) == true - , "The same engine has been added more than once, if intentional, use [AllowMultiple] class attribute " - .FastConcat(engine.ToString())); - try + public EntitiesSubmitter(EnginesRoot enginesRoot) : this() { - if (engine is IReactOnAddAndRemove viewEngine) - CheckReactEngineComponents(viewEngine, _reactiveEnginesAddRemove, type.Name); + _enginesRoot = new Svelto.DataStructures.WeakReference(enginesRoot); + } - if (engine is IReactOnDispose viewEngineDispose) - CheckReactEngineComponents(viewEngineDispose, _reactiveEnginesAddRemoveOnDispose, type.Name); + internal void SubmitEntities() + { + Check.Require(_enginesRoot.IsValid, "ticking an GCed engines root?"); - if (engine is IReactOnSwap viewEngineSwap) - CheckReactEngineComponents(viewEngineSwap, _reactiveEnginesSwap, type.Name); + var enginesRootTarget = _enginesRoot.Target; + var entitiesSubmissionScheduler = enginesRootTarget.scheduler; - if (engine is IReactOnSubmission submissionEngine) - _reactiveEnginesSubmission.Add(submissionEngine); + if (entitiesSubmissionScheduler.paused == false) + { + Check.Require(entitiesSubmissionScheduler.isRunning == false, + "A submission started while the previous one was still flushing"); + entitiesSubmissionScheduler.isRunning = true; - _enginesTypeSet.Add(refWrapper); - _enginesSet.Add(engine); + using (var profiler = new PlatformProfiler("Svelto.ECS - Entities Submission")) + { + var iterations = 0; + var hasEverSubmitted = false; - if (engine is IDisposable) - _disposableEngines.Add(engine as IDisposable); + // We need to clear transient filters before processing callbacks since the callbacks may add + // new entities to these filters. + enginesRootTarget.ClearTransientFilters(); - if (engine is IQueryingEntitiesEngine queryableEntityComponentEngine) - { - queryableEntityComponentEngine.entitiesDB = _entitiesDB; - queryableEntityComponentEngine.Ready(); +#if UNITY_NATIVE + enginesRootTarget.FlushNativeOperations(profiler); +#endif + //todo: proper unit test structural changes made as result of add/remove callbacks + while (enginesRootTarget.HasMadeNewStructuralChangesInThisIteration() + && iterations++ < MAX_SUBMISSION_ITERATIONS) + { + hasEverSubmitted = true; + + _enginesRoot.Target.SingleSubmission(profiler); +#if UNITY_NATIVE + if (enginesRootTarget.HasMadeNewStructuralChangesInThisIteration()) + enginesRootTarget.FlushNativeOperations(profiler); +#endif + } + +#if DEBUG && !PROFILE_SVELTO + if (iterations == MAX_SUBMISSION_ITERATIONS) + throw new ECSException("possible circular submission detected"); +#endif + if (hasEverSubmitted) + enginesRootTarget.NotifyReactiveEnginesOnSubmission(); + } + + entitiesSubmissionScheduler.isRunning = false; + ++entitiesSubmissionScheduler.iteration; } } - catch (Exception e) - { - throw new ECSException("Code crashed while adding engine ".FastConcat(engine.GetType().ToString(), " ") - , e); - } - } - void NotifyReactiveEnginesOnSubmission() - { - var enginesCount = _reactiveEnginesSubmission.count; - for (var i = 0; i < enginesCount; i++) - _reactiveEnginesSubmission[i].EntitiesSubmitted(); + readonly Svelto.DataStructures.WeakReference _enginesRoot; } ~EnginesRoot() { Console.LogWarning("Engines Root has been garbage collected, don't forget to call Dispose()!"); - Dispose(); + Dispose(false); } - void CheckReactEngineComponents - (T engine, FasterDictionary> engines, string typeName) - where T : class, IReactEngine - { - var interfaces = engine.GetType().GetInterfaces(); - - foreach (var interf in interfaces) - if (interf.IsGenericTypeEx() && typeof(T).IsAssignableFrom(interf)) - { - var genericArguments = interf.GetGenericArgumentsEx(); - - AddEngineToList(engine, genericArguments, engines, typeName); - } - } - - static void AddEngineToList - (T engine, Type[] entityComponentTypes - , FasterDictionary> engines, string typeName) - where T : class, IReactEngine - { - for (var i = 0; i < entityComponentTypes.Length; i++) - { - var type = entityComponentTypes[i]; - - if (engines.TryGetValue(new RefWrapperType(type), out var list) == false) - { - list = new FasterList(); - - engines.Add(new RefWrapperType(type), list); - } - - list.Add(new ReactEngineContainer(engine, typeName)); - } - } + const int MAX_SUBMISSION_ITERATIONS = 10; internal bool _isDisposing; readonly FasterList _disposableEngines; readonly FasterList _enginesSet; readonly HashSet _enginesTypeSet; + readonly EnginesReadyOption _enginesWaitForReady; - readonly FasterDictionary> _reactiveEnginesAddRemove; - readonly FasterDictionary> _reactiveEnginesAddRemoveOnDispose; - readonly FasterList _reactiveEnginesSubmission; - readonly FasterDictionary> _reactiveEnginesSwap; - - public struct EntitiesSubmitter - { - public EntitiesSubmitter(EnginesRoot enginesRoot) : this() - { - _enginesRoot = new Svelto.DataStructures.WeakReference(enginesRoot); - _privateSubmitEntities = - _enginesRoot.Target.SingleSubmission(new PlatformProfiler()); - submitEntities = Invoke(); //this must be last to capture all the variables - } - - IEnumerator Invoke() - { - while (true) - { - DBC.ECS.Check.Require(_enginesRoot.IsValid, "ticking an GCed engines root?"); + readonly FasterDictionary>> _reactiveEnginesAdd; - var enginesRootTarget = _enginesRoot.Target; - var entitiesSubmissionScheduler = enginesRootTarget.scheduler; + readonly FasterDictionary>> + _reactiveEnginesAddEx; - if (entitiesSubmissionScheduler.paused == false) - { - DBC.ECS.Check.Require(entitiesSubmissionScheduler.isRunning == false - , "A submission started while the previous one was still flushing"); - entitiesSubmissionScheduler.isRunning = true; + readonly FasterDictionary>> + _reactiveEnginesRemove; - using (var profiler = new PlatformProfiler("Svelto.ECS - Entities Submission")) - { - var iterations = 0; - var hasEverSubmitted = false; -#if UNITY_NATIVE - enginesRootTarget.FlushNativeOperations(profiler); -#endif - //todo: proper unit test structural changes made as result of add/remove callbacks - while (enginesRootTarget.HasMadeNewStructuralChangesInThisIteration() && iterations++ < 5) - { - hasEverSubmitted = true; - - while (true) - { - _privateSubmitEntities.MoveNext(); - if (_privateSubmitEntities.Current == true) - { - using (profiler.Yield()) - { - yield return true; - } - } - else - break; - } -#if UNITY_NATIVE - if (enginesRootTarget.HasMadeNewStructuralChangesInThisIteration()) - enginesRootTarget.FlushNativeOperations(profiler); -#endif - } + readonly FasterDictionary>> + _reactiveEnginesRemoveEx; -#if DEBUG && !PROFILE_SVELTO - if (iterations == 5) - throw new ECSException("possible circular submission detected"); -#endif - if (hasEverSubmitted) - enginesRootTarget.NotifyReactiveEnginesOnSubmission(); - } + readonly FasterDictionary>> _reactiveEnginesSwap; - entitiesSubmissionScheduler.isRunning = false; - ++entitiesSubmissionScheduler.iteration; - } + readonly FasterDictionary>> + _reactiveEnginesSwapEx; - yield return false; - } - } + readonly FasterDictionary>> + _reactiveEnginesDispose; - public uint maxNumberOfOperationsPerFrame - { - set => _enginesRoot.Target._maxNumberOfOperationsPerFrame = value; - } - - readonly Svelto.DataStructures.WeakReference _enginesRoot; + readonly FasterList _reactiveEnginesSubmission; + } - internal readonly IEnumerator submitEntities; - readonly IEnumerator _privateSubmitEntities; - } + public enum EnginesReadyOption + { + ReadyAsAdded, + WaitForReady } } \ No newline at end of file diff --git a/com.sebaslab.svelto.ecs/Core/EnginesRoot.Entities.cs b/com.sebaslab.svelto.ecs/Core/EnginesRoot.Entities.cs index f429ec9..82f883f 100644 --- a/com.sebaslab.svelto.ecs/Core/EnginesRoot.Entities.cs +++ b/com.sebaslab.svelto.ecs/Core/EnginesRoot.Entities.cs @@ -1,7 +1,8 @@ -using System; +//#define PARANOID_CHECK + +using System; using System.Collections.Generic; using System.Runtime.CompilerServices; -using Svelto.Common; using Svelto.DataStructures; using Svelto.ECS.Internal; @@ -16,27 +17,34 @@ namespace Svelto.ECS return new GenericEntityStreamConsumerFactory(this); } - public IEntityFactory GenerateEntityFactory() { return new GenericEntityFactory(this); } - public IEntityFunctions GenerateEntityFunctions() { return new GenericEntityFunctions(this); } + public IEntityFactory GenerateEntityFactory() + { + return new GenericEntityFactory(this); + } + + public IEntityFunctions GenerateEntityFunctions() + { + return new GenericEntityFunctions(this); + } ///-------------------------------------------- [MethodImpl(MethodImplOptions.AggressiveInlining)] - EntityInitializer BuildEntity(EGID entityID, IComponentBuilder[] componentsToBuild, Type descriptorType - , IEnumerable implementors = null) + EntityInitializer BuildEntity(EGID entityID, IComponentBuilder[] componentsToBuild, Type descriptorType, + IEnumerable implementors, string caller) { - CheckAddEntityID(entityID, descriptorType); + CheckAddEntityID(entityID, descriptorType, caller); - DBC.ECS.Check.Require(entityID.groupID.isInvalid == false - , "invalid group detected, are you using new ExclusiveGroupStruct() instead of new ExclusiveGroup()?"); + 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 #if DEBUG && !PROFILE_SVELTO - , descriptorType + , descriptorType #endif - ); + ); return new EntityInitializer(entityID, dic, reference); } @@ -44,17 +52,17 @@ namespace Svelto.ECS /// /// Preallocate memory to avoid the impact to resize arrays when many entities are submitted at once /// - void Preallocate(ExclusiveGroupStruct groupID, uint numberOfEntities, IComponentBuilder[] entityComponentsToBuild) + void Preallocate(ExclusiveGroupStruct groupID, uint size, IComponentBuilder[] entityComponentsToBuild) { void PreallocateEntitiesToAdd() { - _groupedEntityToAdd.Preallocate(groupID, numberOfEntities, entityComponentsToBuild); + _groupedEntityToAdd.Preallocate(groupID, size, entityComponentsToBuild); } void PreallocateDBGroup() { var numberOfEntityComponents = entityComponentsToBuild.Length; - FasterDictionary group = GetOrCreateDBGroup(groupID); + FasterDictionary group = GetOrAddDBGroup(groupID); for (var index = 0; index < numberOfEntityComponents; index++) { @@ -62,8 +70,8 @@ namespace Svelto.ECS var entityComponentType = entityComponentBuilder.GetEntityComponentType(); var refWrapper = new RefWrapperType(entityComponentType); - var dbList = group.GetOrCreate(refWrapper, ()=>entityComponentBuilder.CreateDictionary(numberOfEntities)); - entityComponentBuilder.Preallocate(dbList, numberOfEntities); + var dbList = group.GetOrAdd(refWrapper, () => entityComponentBuilder.CreateDictionary(size)); + entityComponentBuilder.Preallocate(dbList, size); if (_groupsPerEntity.TryGetValue(refWrapper, out var groupedGroup) == false) groupedGroup = _groupsPerEntity[refWrapper] = @@ -75,245 +83,82 @@ namespace Svelto.ECS PreallocateDBGroup(); PreallocateEntitiesToAdd(); - _entityLocator.PreallocateReferenceMaps(groupID, numberOfEntities); - } - - ///-------------------------------------------- - /// - void MoveEntityFromAndToEngines(IComponentBuilder[] componentBuilders, EGID fromEntityGID, EGID? toEntityGID) - { - using (var sampler = new PlatformProfiler("Move Entity From Engines")) - { - var fromGroup = GetDBGroup(fromEntityGID.groupID); - - //Check if there is an EntityInfo linked to this entity, if so it's a DynamicEntityDescriptor! - if (fromGroup.TryGetValue(new RefWrapperType(ComponentBuilderUtilities.ENTITY_INFO_COMPONENT) - , out var entityInfoDic) - && ((ITypeSafeDictionary) entityInfoDic).TryGetValue( - fromEntityGID.entityID, out var entityInfo)) - SwapOrRemoveEntityComponents(fromEntityGID, toEntityGID, entityInfo.componentsToBuild, fromGroup - , sampler); - //otherwise it's a normal static entity descriptor - else - SwapOrRemoveEntityComponents(fromEntityGID, toEntityGID, componentBuilders, fromGroup, sampler); - } - } - - void SwapOrRemoveEntityComponents - (EGID fromEntityGID, EGID? toEntityGID, IComponentBuilder[] entitiesToMove - , FasterDictionary fromGroup, in PlatformProfiler sampler) - { - using (sampler.Sample("MoveEntityComponents")) - { - var length = entitiesToMove.Length; - - FasterDictionary toGroup = null; - - //Swap is not like adding a new entity. While adding new entities happen at the end of submission - //Adding an entity to a group due to a swap of groups happens now. - if (toEntityGID.HasValue) - { - var entityGid = toEntityGID.Value; - _entityLocator.UpdateEntityReference(fromEntityGID, entityGid); - - var toGroupID = entityGid.groupID; - - toGroup = GetOrCreateDBGroup(toGroupID); - - //Add all the entities to the dictionary - for (var i = 0; i < length; i++) - CopyEntityToDictionary(fromEntityGID, entityGid, fromGroup, toGroup - , entitiesToMove[i].GetEntityComponentType(), sampler); - } - else - { - _entityLocator.RemoveEntityReference(fromEntityGID); - } - - //call all the callbacks - for (var i = 0; i < length; i++) - ExecuteEnginesSwapOrRemoveCallbacks(fromEntityGID, toEntityGID, fromGroup, toGroup - , entitiesToMove[i].GetEntityComponentType(), sampler); - - //then remove all the entities from the dictionary - for (var i = 0; i < length; i++) - RemoveEntityFromDictionary(fromEntityGID, fromGroup, entitiesToMove[i].GetEntityComponentType() - , sampler); - } - } - - void CopyEntityToDictionary - (EGID entityGID, EGID toEntityGID, FasterDictionary fromGroup - , FasterDictionary toGroup, Type entityComponentType - , in PlatformProfiler sampler) - { - using (sampler.Sample("CopyEntityToDictionary")) - { - var wrapper = new RefWrapperType(entityComponentType); - - ITypeSafeDictionary fromTypeSafeDictionary = - GetTypeSafeDictionary(entityGID.groupID, fromGroup, wrapper); - -#if DEBUG && !PROFILE_SVELTO - if (fromTypeSafeDictionary.Has(entityGID.entityID) == false) - { - throw new EntityNotFoundException(entityGID, entityComponentType); - } -#endif - ITypeSafeDictionary toEntitiesDictionary = - GetOrCreateTypeSafeDictionary(toEntityGID.groupID, toGroup, wrapper, fromTypeSafeDictionary); - - fromTypeSafeDictionary.AddEntityToDictionary(entityGID, toEntityGID, toEntitiesDictionary); - } - } - - void ExecuteEnginesSwapOrRemoveCallbacks - (EGID entityGID, EGID? toEntityGID, FasterDictionary fromGroup - , FasterDictionary toGroup, Type entityComponentType - , in PlatformProfiler profiler) - { - using (profiler.Sample("MoveEntityComponentFromAndToEngines")) - { - //add all the entities - var refWrapper = new RefWrapperType(entityComponentType); - var fromTypeSafeDictionary = GetTypeSafeDictionary(entityGID.groupID, fromGroup, refWrapper); - - ITypeSafeDictionary toEntitiesDictionary = null; - if (toGroup != null) - toEntitiesDictionary = toGroup[refWrapper]; //this is guaranteed to exist by AddEntityToDictionary - -#if DEBUG && !PROFILE_SVELTO - if (fromTypeSafeDictionary.Has(entityGID.entityID) == false) - throw new EntityNotFoundException(entityGID, entityComponentType); -#endif - fromTypeSafeDictionary.ExecuteEnginesSwapOrRemoveCallbacks(entityGID, toEntityGID, toEntitiesDictionary - , toEntityGID == null - ? _reactiveEnginesAddRemove - : _reactiveEnginesSwap, in profiler); - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - void RemoveEntityFromDictionary - (EGID entityGID, FasterDictionary fromGroup, Type entityComponentType - , in PlatformProfiler sampler) - { - using (sampler.Sample("RemoveEntityFromDictionary")) - { - var refWrapper = new RefWrapperType(entityComponentType); - var fromTypeSafeDictionary = GetTypeSafeDictionary(entityGID.groupID, fromGroup, refWrapper); - - fromTypeSafeDictionary.RemoveEntityFromDictionary(entityGID); - } - } - - /// - /// Swap all the entities from one group to another - /// - /// TODO: write unit test that also tests that this calls MoveTo callbacks and not Add or Remove. - /// also that the passing EGID is the same of a component with EGID - /// - /// - /// - /// - void SwapEntitiesBetweenGroups - (ExclusiveGroupStruct fromIdGroupId, ExclusiveGroupStruct toGroupId, in PlatformProfiler profiler) - { - using (profiler.Sample("SwapEntitiesBetweenGroups")) - { - FasterDictionary fromGroup = GetDBGroup(fromIdGroupId); - FasterDictionary toGroup = GetOrCreateDBGroup(toGroupId); - - _entityLocator.UpdateAllGroupReferenceLocators(fromIdGroupId, toGroupId); - - foreach (var dictionaryOfEntities in fromGroup) - { - ITypeSafeDictionary toEntitiesDictionary = - GetOrCreateTypeSafeDictionary(toGroupId, toGroup, dictionaryOfEntities.Key - , dictionaryOfEntities.Value); - - var groupsOfEntityType = _groupsPerEntity[dictionaryOfEntities.Key]; - var groupOfEntitiesToCopyAndClear = groupsOfEntityType[fromIdGroupId]; - - toEntitiesDictionary.AddEntitiesFromDictionary(groupOfEntitiesToCopyAndClear, toGroupId, this); - - //call all the MoveTo callbacks - dictionaryOfEntities.Value.ExecuteEnginesAddOrSwapCallbacks(_reactiveEnginesSwap - , dictionaryOfEntities.Value, fromIdGroupId, toGroupId, profiler); - - //todo: if it's unmanaged, I can use fastclear - groupOfEntitiesToCopyAndClear.Clear(); - } - } + _entityLocator.PreallocateReferenceMaps(groupID, size); } [MethodImpl(MethodImplOptions.AggressiveInlining)] FasterDictionary GetDBGroup(ExclusiveGroupStruct fromIdGroupId) { - if (_groupEntityComponentsDB.TryGetValue(fromIdGroupId - , out FasterDictionary - fromGroup) == false) + if (_groupEntityComponentsDB.TryGetValue(fromIdGroupId, + out FasterDictionary fromGroup) == false) throw new ECSException("Group doesn't exist: ".FastConcat(fromIdGroupId.ToName())); return fromGroup; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - FasterDictionary GetOrCreateDBGroup - (ExclusiveGroupStruct toGroupId) + FasterDictionary GetOrAddDBGroup(ExclusiveGroupStruct toGroupId) { - return _groupEntityComponentsDB.GetOrCreate( - toGroupId, () => new FasterDictionary()); + return _groupEntityComponentsDB.GetOrAdd(toGroupId, + () => new FasterDictionary()); } - ITypeSafeDictionary GetOrCreateTypeSafeDictionary - (ExclusiveGroupStruct groupId, FasterDictionary toGroup - , RefWrapperType type, ITypeSafeDictionary fromTypeSafeDictionary) + IComponentBuilder[] FindRealComponents(EGID fromEntityGID) where T : IEntityDescriptor, new() { - //be sure that the TypeSafeDictionary for the entity Type exists - if (toGroup.TryGetValue(type, out ITypeSafeDictionary toEntitiesDictionary) == false) - { - toEntitiesDictionary = fromTypeSafeDictionary.Create(); - toGroup.Add(type, toEntitiesDictionary); - } + var fromGroup = GetDBGroup(fromEntityGID.groupID); - //update GroupsPerEntity - if (_groupsPerEntity.TryGetValue(type, out var groupedGroup) == false) - groupedGroup = _groupsPerEntity[type] = - new FasterDictionary(); + if (fromGroup.TryGetValue(new RefWrapperType(ComponentBuilderUtilities.ENTITY_INFO_COMPONENT), + out var entityInfoDic) // + && ((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 + var hash = new HashSet(entityInfo.componentsToBuild, + default(ComponentBuilderComparer)); - groupedGroup[groupId] = toEntitiesDictionary; - return toEntitiesDictionary; - } + foreach (var component in EntityDescriptorTemplate.descriptor.componentsToBuild) + { + if (hash.Contains(component) == false) + throw new Exception( + $"entityInfo.componentsToBuild must contain all the base components {fromEntityGID}," + + $" missing component {component}"); - static ITypeSafeDictionary GetTypeSafeDictionary - (ExclusiveGroupStruct groupID, FasterDictionary @group, RefWrapperType refWrapper) - { - if (@group.TryGetValue(refWrapper, out ITypeSafeDictionary fromTypeSafeDictionary) == false) - { - throw new ECSException("no group found: ".FastConcat(groupID.ToName())); + hash.Remove(component); + } +#endif + return entityInfo.componentsToBuild; } - return fromTypeSafeDictionary; + return EntityDescriptorTemplate.descriptor.componentsToBuild; } - void RemoveEntitiesFromGroup(ExclusiveGroupStruct groupID, in PlatformProfiler profiler) + IComponentBuilder[] FindRealComponents(EGID fromEntityGID, IComponentBuilder[] baseComponents) { - _entityLocator.RemoveAllGroupReferenceLocators(groupID); + var fromGroup = GetDBGroup(fromEntityGID.groupID); - if (_groupEntityComponentsDB.TryGetValue(groupID, out var dictionariesOfEntities)) + if (fromGroup.TryGetValue(new RefWrapperType(ComponentBuilderUtilities.ENTITY_INFO_COMPONENT), + out var entityInfoDic) // + && ((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 { - foreach (var dictionaryOfEntities in dictionariesOfEntities) +#if PARANOID_CHECK + var hash = new HashSet(entityInfo.componentsToBuild, + default(ComponentBuilderComparer)); + + foreach (var component in baseComponents) { - dictionaryOfEntities.Value.ExecuteEnginesRemoveCallbacks(_reactiveEnginesAddRemove, profiler - , groupID); - dictionaryOfEntities.Value.FastClear(); + if (hash.Contains(component) == false) + throw new Exception( + $"entityInfo.componentsToBuild must contain all the base components {fromEntityGID}," + + $" missing component {component}"); - var groupsOfEntityType = _groupsPerEntity[dictionaryOfEntities.Key]; - groupsOfEntityType[groupID].FastClear(); + hash.Remove(component); } +#endif + return entityInfo.componentsToBuild; } + + return baseComponents; } //one datastructure rule them all: @@ -322,7 +167,7 @@ namespace Svelto.ECS //to the FasterDictionary capabilities OR it's possible to get a specific entityComponent indexed by //ID. This ID doesn't need to be the EGID, it can be just the entityID //for each group id, save a dictionary indexed by entity type of entities indexed by id - // group EntityComponentType entityID, EntityComponent + // group EntityComponentType entityID, EntityComponent internal readonly FasterDictionary> _groupEntityComponentsDB; @@ -334,7 +179,7 @@ namespace Svelto.ECS _groupsPerEntity; //The filters stored for each component and group - internal readonly FasterDictionary> + internal readonly FasterDictionary> _groupFilters; readonly EntitiesDB _entitiesDB; diff --git a/com.sebaslab.svelto.ecs/Core/EnginesRoot.GenericEntityFactory.cs b/com.sebaslab.svelto.ecs/Core/EnginesRoot.GenericEntityFactory.cs index 9e1f53d..a68eafa 100644 --- a/com.sebaslab.svelto.ecs/Core/EnginesRoot.GenericEntityFactory.cs +++ b/com.sebaslab.svelto.ecs/Core/EnginesRoot.GenericEntityFactory.cs @@ -1,8 +1,9 @@ using System; using System.Collections.Generic; +using System.Runtime.CompilerServices; using Svelto.Common; - namespace Svelto.ECS +namespace Svelto.ECS { public partial class EnginesRoot { @@ -14,52 +15,61 @@ using Svelto.Common; } public EntityInitializer BuildEntity - (uint entityID, ExclusiveBuildGroup groupStructId, IEnumerable implementors = null) - where T : IEntityDescriptor, new() + (uint entityID, ExclusiveBuildGroup groupStructId, IEnumerable implementors = null + , [CallerMemberName] string caller = null) where T : IEntityDescriptor, new() { return _enginesRoot.Target.BuildEntity(new EGID(entityID, groupStructId) , EntityDescriptorTemplate.descriptor.componentsToBuild - , TypeCache.type, implementors); + , TypeCache.type, implementors, caller); } - public EntityInitializer BuildEntity(EGID egid, IEnumerable implementors = null) - where T : IEntityDescriptor, new() + public EntityInitializer BuildEntity + (EGID egid, IEnumerable implementors = null + , [CallerMemberName] string caller = null) where T : IEntityDescriptor, new() { - return _enginesRoot.Target.BuildEntity( - egid, EntityDescriptorTemplate.descriptor.componentsToBuild, TypeCache.type, implementors); + return _enginesRoot.Target.BuildEntity(egid, EntityDescriptorTemplate.descriptor.componentsToBuild + , TypeCache.type, implementors, caller); } public EntityInitializer BuildEntity - (EGID egid, T entityDescriptor, IEnumerable implementors) where T : IEntityDescriptor + (EGID egid, T entityDescriptor, IEnumerable implementors + , [CallerMemberName] string caller = null) where T : IEntityDescriptor { - return _enginesRoot.Target.BuildEntity(egid, entityDescriptor.componentsToBuild, TypeCache.type, implementors); + return _enginesRoot.Target.BuildEntity(egid, entityDescriptor.componentsToBuild, TypeCache.type + , implementors, caller); } public EntityInitializer BuildEntity - (uint entityID, ExclusiveBuildGroup groupStructId, T descriptorEntity, IEnumerable implementors) - where T : IEntityDescriptor + (uint entityID, ExclusiveBuildGroup groupStructId, T descriptorEntity, IEnumerable implementors + , [CallerMemberName] string caller = null) where T : IEntityDescriptor { return _enginesRoot.Target.BuildEntity(new EGID(entityID, groupStructId) - , descriptorEntity.componentsToBuild, TypeCache.type, implementors); + , descriptorEntity.componentsToBuild, TypeCache.type + , implementors, caller); } public void PreallocateEntitySpace(ExclusiveGroupStruct groupStructId, uint numberOfEntities) where T : IEntityDescriptor, new() { - _enginesRoot.Target.Preallocate(groupStructId, numberOfEntities, EntityDescriptorTemplate.descriptor.componentsToBuild); + _enginesRoot.Target.Preallocate(groupStructId, numberOfEntities + , EntityDescriptorTemplate.descriptor.componentsToBuild); } - - public EntityInitializer BuildEntity(EGID egid, IComponentBuilder[] componentsToBuild, Type type, IEnumerable implementors = null) + + public EntityInitializer BuildEntity + (EGID egid, IComponentBuilder[] componentsToBuild, Type type, IEnumerable implementors = null + , [CallerMemberName] string caller = null) { - return _enginesRoot.Target.BuildEntity(egid, componentsToBuild, type, implementors); + return _enginesRoot.Target.BuildEntity(egid, componentsToBuild, type, implementors, caller); } - + #if UNITY_NATIVE - public Svelto.ECS.Native.NativeEntityFactory ToNative(string callerName) where T : IEntityDescriptor, new() + public Native.NativeEntityFactory ToNative + ([CallerMemberName] string caller = null) + where T : IEntityDescriptor, new() { - return _enginesRoot.Target.ProvideNativeEntityFactoryQueue(callerName); + return _enginesRoot.Target.ProvideNativeEntityFactoryQueue(caller); } -#endif +#endif //enginesRoot is a weakreference because GenericEntityStreamConsumerFactory can be injected inside //engines of other enginesRoot diff --git a/com.sebaslab.svelto.ecs/Core/EnginesRoot.GenericEntityFunctions.cs b/com.sebaslab.svelto.ecs/Core/EnginesRoot.GenericEntityFunctions.cs index 2aa097d..d5ac6d1 100644 --- a/com.sebaslab.svelto.ecs/Core/EnginesRoot.GenericEntityFunctions.cs +++ b/com.sebaslab.svelto.ecs/Core/EnginesRoot.GenericEntityFunctions.cs @@ -7,193 +7,146 @@ namespace Svelto.ECS { public partial class EnginesRoot { - /// - /// todo: EnginesRoot was a weakreference to give the change to inject - /// entity functions from other engines root. It probably should be reverted - /// class GenericEntityFunctions : IEntityFunctions { internal GenericEntityFunctions(EnginesRoot weakReference) { - _enginesRoot = new Svelto.DataStructures.WeakReference(weakReference); + _enginesRoot = new WeakReference(weakReference); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void RemoveEntity(uint entityID, ExclusiveBuildGroup groupID) where T : - IEntityDescriptor, new() + public void RemoveEntity + (uint entityID, ExclusiveBuildGroup groupID, [CallerMemberName] string caller = null) + where T : IEntityDescriptor, new() { - RemoveEntity(new EGID(entityID, groupID)); + RemoveEntity(new EGID(entityID, groupID), caller); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void RemoveEntity(EGID entityEGID) where T : IEntityDescriptor, new() + public void RemoveEntity(EGID entityEGID, [CallerMemberName] string caller = null) + where T : IEntityDescriptor, new() { DBC.ECS.Check.Require(entityEGID.groupID.isInvalid == false, "invalid group detected"); - var descriptorComponentsToBuild = EntityDescriptorTemplate.descriptor.componentsToBuild; - _enginesRoot.Target.CheckRemoveEntityID(entityEGID, TypeCache.type); + _enginesRoot.Target.CheckRemoveEntityID(entityEGID, TypeCache.type, caller); - _enginesRoot.Target.QueueEntitySubmitOperation( - new EntitySubmitOperation(EntitySubmitOperationType.Remove, entityEGID, entityEGID, - descriptorComponentsToBuild)); + _enginesRoot.Target.QueueRemoveEntityOperation( + entityEGID, _enginesRoot.Target.FindRealComponents(entityEGID), caller); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void RemoveEntitiesFromGroup(ExclusiveBuildGroup groupID) + public void RemoveEntitiesFromGroup(ExclusiveBuildGroup groupID, [CallerMemberName] string caller = null) { DBC.ECS.Check.Require(groupID.isInvalid == false, "invalid group detected"); _enginesRoot.Target.RemoveGroupID(groupID); - _enginesRoot.Target.QueueEntitySubmitOperation( - new EntitySubmitOperation(EntitySubmitOperationType.RemoveGroup, new EGID(0, groupID), new EGID())); + _enginesRoot.Target.QueueRemoveGroupOperation(groupID, caller); } - // [MethodImpl(MethodImplOptions.AggressiveInlining)] - // void RemoveAllEntities(ExclusiveGroup group) - // where D : IEntityDescriptor, new() where S : unmanaged, IEntityComponent - // { - // var targetEntitiesDB = _enginesRoot.Target._entitiesDB; - // var (buffer, count) = targetEntitiesDB.QueryEntities(@group); - // for (uint i = 0; i < count; ++i) - // { - // RemoveEntity(new EGID(i, group)); - // } - // } - // - // [MethodImpl(MethodImplOptions.AggressiveInlining)] - // void RemoveAllEntities() - // where D : IEntityDescriptor, new() where S : unmanaged, IEntityComponent - // { - // var targetEntitiesDB = _enginesRoot.Target._entitiesDB; - // foreach (var ((buffer, count), exclusiveGroupStruct) in targetEntitiesDB.QueryEntities()) - // for (uint i = 0; i < count; ++i) - // { - // RemoveEntity(new EGID(i, exclusiveGroupStruct)); - // } - // } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void SwapEntitiesInGroup(ExclusiveBuildGroup fromGroupID, ExclusiveBuildGroup toGroupID) - where T : IEntityDescriptor, new() + public void SwapEntitiesInGroup + (ExclusiveBuildGroup fromGroupID, ExclusiveBuildGroup toGroupID, [CallerMemberName] string caller = null) { if (_enginesRoot.Target._groupEntityComponentsDB.TryGetValue( - fromGroupID.group, out FasterDictionary entitiesInGroupPerType) - == true) + fromGroupID.group + , out FasterDictionary entitiesInGroupPerType) == true) { #if DEBUG && !PROFILE_SVELTO - IComponentBuilder[] components = EntityDescriptorTemplate.descriptor.componentsToBuild; - var dictionary = entitiesInGroupPerType[new RefWrapperType(components[0].GetEntityComponentType())]; + ITypeSafeDictionary dictionary = entitiesInGroupPerType.unsafeValues[0]; dictionary.KeysEvaluator((key) => { - _enginesRoot.Target.CheckRemoveEntityID(new EGID(key, fromGroupID), TypeCache.type); - _enginesRoot.Target.CheckAddEntityID(new EGID(key, toGroupID), TypeCache.type); + _enginesRoot.Target.CheckRemoveEntityID(new EGID(key, fromGroupID), null, caller); + _enginesRoot.Target.CheckAddEntityID(new EGID(key, toGroupID), null, caller); }); - #endif - _enginesRoot.Target.QueueEntitySubmitOperation( - new EntitySubmitOperation(EntitySubmitOperationType.SwapGroup, new EGID(0, fromGroupID) - , new EGID(0, toGroupID))); + _enginesRoot.Target.QueueSwapGroupOperation(fromGroupID, toGroupID, caller); } } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void SwapEntityGroup(uint entityID, ExclusiveBuildGroup fromGroupID, - ExclusiveBuildGroup toGroupID) - where T : IEntityDescriptor, new() + public void SwapEntityGroup + (uint entityID, ExclusiveBuildGroup fromGroupID, ExclusiveBuildGroup toGroupID + , [CallerMemberName] string caller = null) where T : IEntityDescriptor, new() { - SwapEntityGroup(new EGID(entityID, fromGroupID), toGroupID); + SwapEntityGroup(new EGID(entityID, fromGroupID), toGroupID, caller); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void SwapEntityGroup(EGID fromID, ExclusiveBuildGroup toGroupID) + public void SwapEntityGroup + (EGID fromEGID, ExclusiveBuildGroup toGroupID, [CallerMemberName] string caller = null) where T : IEntityDescriptor, new() { - SwapEntityGroup(fromID, new EGID(fromID.entityID, toGroupID)); + SwapEntityGroup(fromEGID, new EGID(fromEGID.entityID, toGroupID), caller); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void SwapEntityGroup(EGID fromID, ExclusiveBuildGroup mustBeFromGroup, ExclusiveBuildGroup toGroupID) + public void SwapEntityGroup + (EGID fromEGID, EGID toEGID, ExclusiveBuildGroup mustBeFromGroup, [CallerMemberName] string caller = null) where T : IEntityDescriptor, new() { - if (fromID.groupID != mustBeFromGroup) - throw new ECSException($"Entity is not coming from the expected group. Expected {mustBeFromGroup} is {fromID.groupID}"); + if (fromEGID.groupID != mustBeFromGroup) + throw new ECSException( + $"Entity is not coming from the expected group Expected {mustBeFromGroup} is {fromEGID.groupID}"); - SwapEntityGroup(fromID, toGroupID); + SwapEntityGroup(fromEGID, toEGID, caller); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void SwapEntityGroup(EGID fromID, EGID toID, ExclusiveBuildGroup mustBeFromGroup) + public void SwapEntityGroup(EGID fromEGID, EGID toEGID, [CallerMemberName] string caller = null) where T : IEntityDescriptor, new() { - if (fromID.groupID != mustBeFromGroup) - throw new ECSException($"Entity is not coming from the expected group Expected {mustBeFromGroup} is {fromID.groupID}"); + DBC.ECS.Check.Require(fromEGID.groupID.isInvalid == false, "invalid group detected"); + DBC.ECS.Check.Require(toEGID.groupID.isInvalid == false, "invalid group detected"); + + var enginesRootTarget = _enginesRoot.Target; - SwapEntityGroup(fromID, toID); + enginesRootTarget.CheckRemoveEntityID(fromEGID, TypeCache.type, caller); + enginesRootTarget.CheckAddEntityID(toEGID, TypeCache.type, caller); + + enginesRootTarget.QueueSwapEntityOperation(fromEGID, toEGID + , this._enginesRoot.Target.FindRealComponents(fromEGID) + , caller); } #if UNITY_NATIVE - public Svelto.ECS.Native.NativeEntityRemove ToNativeRemove(string memberName) where T : IEntityDescriptor, new() + public Native.NativeEntityRemove ToNativeRemove(string memberName) where T : IEntityDescriptor, new() { return _enginesRoot.Target.ProvideNativeEntityRemoveQueue(memberName); } - public Svelto.ECS.Native.NativeEntitySwap ToNativeSwap(string memberName) where T : IEntityDescriptor, new() + public Native.NativeEntitySwap ToNativeSwap(string memberName) where T : IEntityDescriptor, new() { return _enginesRoot.Target.ProvideNativeEntitySwapQueue(memberName); } #endif - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void SwapEntityGroup(EGID fromID, EGID toID) - where T : IEntityDescriptor, new() - { - DBC.ECS.Check.Require(fromID.groupID.isInvalid == false, "invalid group detected"); - DBC.ECS.Check.Require(toID.groupID.isInvalid == false, "invalid group detected"); - - var enginesRootTarget = _enginesRoot.Target; - var descriptorComponentsToBuild = EntityDescriptorTemplate.descriptor.componentsToBuild; - - enginesRootTarget.CheckRemoveEntityID(fromID, TypeCache.type); - enginesRootTarget.CheckAddEntityID(toID, TypeCache.type); - - enginesRootTarget.QueueEntitySubmitOperation( - new EntitySubmitOperation(EntitySubmitOperationType.Swap, - fromID, toID, descriptorComponentsToBuild)); - } - //enginesRoot is a weakreference because GenericEntityStreamConsumerFactory can be injected inside //engines of other enginesRoot - readonly Svelto.DataStructures.WeakReference _enginesRoot; + readonly WeakReference _enginesRoot; } - void QueueEntitySubmitOperation(EntitySubmitOperation entitySubmitOperation) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + void QueueRemoveGroupOperation(ExclusiveBuildGroup groupID, string caller) { -#if DEBUG && !PROFILE_SVELTO - entitySubmitOperation.trace = new System.Diagnostics.StackFrame(1, true); -#endif - _entitiesOperations.Add((ulong) entitySubmitOperation.fromID, entitySubmitOperation); + _entitiesOperations.AddRemoveGroupOperation(groupID, caller); } - void QueueEntitySubmitOperation(EntitySubmitOperation entitySubmitOperation) where T : IEntityDescriptor + [MethodImpl(MethodImplOptions.AggressiveInlining)] + void QueueSwapGroupOperation(ExclusiveBuildGroup fromGroupID, ExclusiveBuildGroup toGroupID, string caller) { -#if DEBUG && !PROFILE_SVELTO - entitySubmitOperation.trace = new System.Diagnostics.StackFrame(1, true); + _entitiesOperations.AddSwapGroupOperation(fromGroupID, toGroupID, caller); + } - if (_entitiesOperations.TryGetValue((ulong) entitySubmitOperation.fromID, out var entitySubmitedOperation)) - { - if (entitySubmitedOperation != entitySubmitOperation) - throw new ECSException("Only one entity operation per submission is allowed" - .FastConcat(" entityComponentType: ") - .FastConcat(typeof(T).Name) - .FastConcat(" submission type ", entitySubmitOperation.type.ToString(), - " from ID: ", entitySubmitOperation.fromID.entityID.ToString()) - .FastConcat(" previous operation type: ", - _entitiesOperations[(ulong) entitySubmitOperation.fromID].type - .ToString())); - } - else -#endif - _entitiesOperations[(ulong) entitySubmitOperation.fromID] = entitySubmitOperation; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + void QueueSwapEntityOperation + (EGID fromID, EGID toID, IComponentBuilder[] componentBuilders, string caller) + { + _entitiesOperations.AddSwapOperation(fromID, toID, componentBuilders, caller); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + void QueueRemoveEntityOperation(EGID entityEGID, IComponentBuilder[] componentBuilders, string caller) + { + _entitiesOperations.AddRemoveOperation(entityEGID, componentBuilders, caller); } } } \ No newline at end of file diff --git a/com.sebaslab.svelto.ecs/Core/EnginesRoot.Submission.cs b/com.sebaslab.svelto.ecs/Core/EnginesRoot.Submission.cs index 9e7312a..55366f9 100644 --- a/com.sebaslab.svelto.ecs/Core/EnginesRoot.Submission.cs +++ b/com.sebaslab.svelto.ecs/Core/EnginesRoot.Submission.cs @@ -1,177 +1,515 @@ -using System.Collections.Generic; +using System; +using System.Runtime.CompilerServices; using Svelto.Common; using Svelto.DataStructures; +using Svelto.ECS.DataStructures; +using Svelto.ECS.Internal; namespace Svelto.ECS { public partial class EnginesRoot { - /// - /// Todo: it would be probably better to split even further the logic between submission and callbacks - /// Something to do when I will optimize the callbacks - /// - /// - /// - IEnumerator SingleSubmission(PlatformProfiler profiler) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + void SingleSubmission(PlatformProfiler profiler) { - while (true) + ClearDebugChecks(); //this must be done first as I need the carry the last states after the submission + + _entitiesOperations.ExecuteRemoveAndSwappingOperations(_swapEntities, _removeEntities, _removeGroup, + _swapGroup, this); + + AddEntities(profiler); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static void RemoveGroup(ExclusiveGroupStruct groupID, EnginesRoot enginesRoot) + { + using (var sampler = new PlatformProfiler("remove whole group")) + { + enginesRoot.RemoveEntitiesFromGroup(groupID, sampler); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static void SwapGroup(ExclusiveGroupStruct fromGroupID, ExclusiveGroupStruct toGroupID, EnginesRoot enginesRoot) + { + using (var sampler = new PlatformProfiler("swap whole group")) + { + enginesRoot.SwapEntitiesBetweenGroups(fromGroupID, toGroupID, sampler); + } + } + + static void RemoveEntities( + FasterDictionary>> + removeOperations, FasterList entitiesRemoved, EnginesRoot enginesRoot) + { + using (var sampler = new PlatformProfiler("remove Entities")) + { + using (sampler.Sample("Remove Entity References")) + { + var count = entitiesRemoved.count; + for (int i = 0; i < count; i++) + { + enginesRoot._entityLocator.RemoveEntityReference(entitiesRemoved[i]); + } + } + + using (sampler.Sample("Execute remove callbacks and remove entities")) + { + foreach (var entitiesToRemove in removeOperations) + { + ExclusiveGroupStruct group = entitiesToRemove.key; + var fromGroupDictionary = enginesRoot.GetDBGroup(group); + + foreach (var groupedEntitiesToRemove in entitiesToRemove.value) + { + var componentType = groupedEntitiesToRemove.key; + ITypeSafeDictionary fromComponentsDictionary = fromGroupDictionary[componentType]; + + FasterList<(uint, string)> infosToProcess = groupedEntitiesToRemove.value; + + fromComponentsDictionary.ExecuteEnginesRemoveCallbacks(infosToProcess, + enginesRoot._reactiveEnginesRemove, group, in sampler); + } + } + } + + using (sampler.Sample("Remove Entities")) + { + foreach (var entitiesToRemove in removeOperations) + { + ExclusiveGroupStruct fromGroup = entitiesToRemove.key; + var fromGroupDictionary = enginesRoot.GetDBGroup(fromGroup); + + foreach (var groupedEntitiesToRemove in entitiesToRemove.value) + { + RefWrapperType componentType = groupedEntitiesToRemove.key; + ITypeSafeDictionary fromComponentsDictionary = fromGroupDictionary[componentType]; + + FasterList<(uint, string)> entityIDsToRemove = groupedEntitiesToRemove.value; + + enginesRoot.RemoveEntityFromPersistentFilters(entityIDsToRemove, fromGroup, + componentType, fromComponentsDictionary); + + fromComponentsDictionary.RemoveEntitiesFromDictionary(entityIDsToRemove); + + //store new count after the entities are removed, plus the number of entities removed + enginesRoot._cachedRangeOfSubmittedIndices.Add(((uint, uint))( + fromComponentsDictionary.count, + fromComponentsDictionary.count + entityIDsToRemove.count)); + } + } + } + + var rangeEnumerator = enginesRoot._cachedRangeOfSubmittedIndices.GetEnumerator(); + using (sampler.Sample("Execute remove Callbacks Fast")) + { + foreach (var entitiesToRemove in removeOperations) + { + ExclusiveGroupStruct group = entitiesToRemove.key; + var fromGroupDictionary = enginesRoot.GetDBGroup(group); + + foreach (var groupedEntitiesToRemove in entitiesToRemove.value) + { + rangeEnumerator.MoveNext(); + + var componentType = groupedEntitiesToRemove.key; + ITypeSafeDictionary fromComponentsDictionary = fromGroupDictionary[componentType]; + + //get all the engines linked to TValue + if (!enginesRoot._reactiveEnginesRemoveEx.TryGetValue(new RefWrapperType(componentType), + out var entityComponentsEngines)) + continue; + + fromComponentsDictionary.ExecuteEnginesRemoveCallbacksFast(entityComponentsEngines, + group, rangeEnumerator.Current, in sampler); + } + } + } + } + } + + static void SwapEntities( + FasterDictionary>>> swapEntitiesOperations, + FasterList<(EGID, EGID)> entitiesIDSwaps, EnginesRoot enginesRoot) + { + using (var sampler = new PlatformProfiler("Swap entities between groups")) { - DBC.ECS.Check.Require(_maxNumberOfOperationsPerFrame > 0); - - ClearChecks(); + using (sampler.Sample("Update Entity References")) + { + var count = entitiesIDSwaps.count; + for (int i = 0; i < count; i++) + { + var (fromEntityGid, toEntityGid) = entitiesIDSwaps[i]; - uint numberOfOperations = 0; + enginesRoot._entityLocator.UpdateEntityReference(fromEntityGid, toEntityGid); + } + } - if (_entitiesOperations.count > 0) + using (sampler.Sample("Swap Entities")) { - using (var sample = profiler.Sample("Remove and Swap operations")) + enginesRoot._cachedRangeOfSubmittedIndices.FastClear(); + //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) { - _transientEntitiesOperations.FastClear(); - _entitiesOperations.CopyValuesTo(_transientEntitiesOperations); - _entitiesOperations.FastClear(); - - EntitySubmitOperation[] entitiesOperations = - _transientEntitiesOperations.ToArrayFast(out var count); - - for (var i = 0; i < count; i++) + ExclusiveGroupStruct fromGroup = entitiesToSwap.key; + var fromGroupDictionary = enginesRoot.GetDBGroup(fromGroup); + + //iterate all the fromgroups + foreach (var groupedEntitiesToSwap in entitiesToSwap.value) { - try + var componentType = groupedEntitiesToSwap.key; + ITypeSafeDictionary fromComponentsDictionary = fromGroupDictionary[componentType]; + + //get the subset of togroups that come from from group + foreach (var entitiesInfoToSwap in groupedEntitiesToSwap.value) { - switch (entitiesOperations[i].type) + ExclusiveGroupStruct toGroup = entitiesInfoToSwap.key; + ITypeSafeDictionary toComponentsDictionary = + enginesRoot.GetOrAddTypeSafeDictionary(toGroup, + enginesRoot.GetOrAddDBGroup(toGroup), componentType, fromComponentsDictionary); + + DBC.ECS.Check.Assert(toComponentsDictionary != 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; + + int fromDictionaryCountBeforeSubmission = -1; + + if (enginesRoot._indicesOfPersistentFiltersUsedByThisComponent.TryGetValue( + new NativeRefWrapperType(componentType), + out NativeDynamicArrayCast listOfFilters)) { - case EntitySubmitOperationType.Swap: - MoveEntityFromAndToEngines(entitiesOperations[i].builders - , entitiesOperations[i].fromID - , entitiesOperations[i].toID); - break; - case EntitySubmitOperationType.Remove: - MoveEntityFromAndToEngines(entitiesOperations[i].builders - , entitiesOperations[i].fromID, null); - break; - case EntitySubmitOperationType.RemoveGroup: - RemoveEntitiesFromGroup(entitiesOperations[i].fromID.groupID, profiler); - break; - case EntitySubmitOperationType.SwapGroup: - SwapEntitiesBetweenGroups(entitiesOperations[i].fromID.groupID - , entitiesOperations[i].toID.groupID, profiler); - break; + enginesRoot._cachedIndicesToSwapBeforeSubmissionForFilters.FastClear(); + + fromDictionaryCountBeforeSubmission = fromComponentsDictionary.count - 1; + + //add the index of the entities in the component array for each entityID + //BEFORE the submission, as after that the ID will be different + foreach (var (fromEntityID, _, _) in fromEntityToEntityIDs) + enginesRoot._cachedIndicesToSwapBeforeSubmissionForFilters.Add(fromEntityID, + fromComponentsDictionary.GetIndex(fromEntityID)); } + + //ensure that to dictionary has enough room to store the new entities` + toComponentsDictionary.EnsureCapacity((uint)(toComponentsDictionary.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))( + toComponentsDictionary.count, + toComponentsDictionary.count + fromEntityToEntityIDs.count)); + + fromComponentsDictionary.SwapEntitiesBetweenDictionaries(fromEntityToEntityIDs, + fromGroup, toGroup, toComponentsDictionary); + + if (fromDictionaryCountBeforeSubmission != -1) //this if skips the swap if there are no filters linked to the component + enginesRoot.SwapEntityBetweenPersistentFilters(fromEntityToEntityIDs, + enginesRoot._cachedIndicesToSwapBeforeSubmissionForFilters, + toComponentsDictionary, fromGroup, toGroup, + (uint)fromDictionaryCountBeforeSubmission, listOfFilters); } - catch + } + } + } + + using (sampler.Sample("Execute Swap Callbacks")) + { + foreach (var entitiesToSwap in swapEntitiesOperations) + { + ExclusiveGroupStruct fromGroup = entitiesToSwap.key; + + foreach (var groupedEntitiesToSwap in entitiesToSwap.value) + { + var componentType = groupedEntitiesToSwap.key; + + //get all the engines linked to TValue + if (!enginesRoot._reactiveEnginesSwap.TryGetValue(new RefWrapperType(componentType), + out var entityComponentsEngines)) + continue; + + foreach (var entitiesInfoToSwap in groupedEntitiesToSwap.value) { - var str = "Crash while executing Entity Operation ".FastConcat( - entitiesOperations[i].type.ToString()); + ExclusiveGroupStruct toGroup = entitiesInfoToSwap.key; + ITypeSafeDictionary toComponentsDictionary = GetTypeSafeDictionary(toGroup, + enginesRoot.GetDBGroup(toGroup), componentType); - Svelto.Console.LogError(str.FastConcat(" ") -#if DEBUG && !PROFILE_SVELTO - .FastConcat(entitiesOperations[i].trace.ToString()) -#endif - ); + var infosToProcess = entitiesInfoToSwap.value; - throw; + toComponentsDictionary.ExecuteEnginesSwapCallbacks(infosToProcess, + entityComponentsEngines, fromGroup, toGroup, in sampler); } + } + } + } - ++numberOfOperations; + 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; - if ((uint) numberOfOperations >= (uint) _maxNumberOfOperationsPerFrame) + foreach (var entitiesInfoToSwap in groupedEntitiesToSwap.value) { - using (sample.Yield()) - yield return true; + rangeEnumerator.MoveNext(); - numberOfOperations = 0; + //get all the engines linked to TValue + if (!enginesRoot._reactiveEnginesSwapEx.TryGetValue(new RefWrapperType(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); } } } } + } + } - _groupedEntityToAdd.Swap(); + void AddEntities(PlatformProfiler sampler) + { + //current buffer becomes other, and other becomes current + _groupedEntityToAdd.Swap(); - if (_groupedEntityToAdd.AnyOtherEntityCreated()) + //I need to iterate the previous current, which is now other + if (_groupedEntityToAdd.AnyPreviousEntityCreated()) + { + _cachedRangeOfSubmittedIndices.FastClear(); + using (sampler.Sample("Add operations")) { - using (var outerSampler = profiler.Sample("Add operations")) + try { - try + using (sampler.Sample("Add entities to database")) { - using (profiler.Sample("Add entities to database")) + //each group is indexed by entity view type. for each type there is a dictionary indexed + //by entityID + foreach (var groupToSubmit in _groupedEntityToAdd) { - //each group is indexed by entity view type. for each type there is a dictionary indexed by entityID - foreach (var groupToSubmit in _groupedEntityToAdd.other) + var groupID = groupToSubmit.@group; + var groupDB = GetOrAddDBGroup(groupID); + + //add the entityComponents in the group + foreach (var entityComponentsToSubmit in groupToSubmit.components) { - var groupID = groupToSubmit.Key; - var groupDB = GetOrCreateDBGroup(groupID); - - //add the entityComponents in the group - foreach (var entityComponentsToSubmit in groupToSubmit.Value) - { - var type = entityComponentsToSubmit.Key; - var targetTypeSafeDictionary = entityComponentsToSubmit.Value; - var wrapper = new RefWrapperType(type); - - var dbDic = GetOrCreateTypeSafeDictionary( - groupID, groupDB, wrapper, targetTypeSafeDictionary); - - //Fill the DB with the entity components generated this frame. - dbDic.AddEntitiesFromDictionary(targetTypeSafeDictionary, groupID, this); - } + var type = entityComponentsToSubmit.key; + var fromDictionary = entityComponentsToSubmit.value; + var wrapper = new RefWrapperType(type); + + var toDictionary = + GetOrAddTypeSafeDictionary(groupID, groupDB, wrapper, 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)); + //Fill the DB with the entity components generated this frame. + fromDictionary.AddEntitiesToDictionary(toDictionary, groupID, entityLocator); } } + } - //then submit everything in the engines, so that the DB is up to date with all the entity components - //created by the entity built - using (var sampler = profiler.Sample("Add entities to engines")) + //then submit everything in the engines, so that the DB is up to date with all the entity components + //created by the entity built + var enumerator = _cachedRangeOfSubmittedIndices.GetEnumerator(); + using (sampler.Sample("Add entities to engines fast")) + { + foreach (GroupInfo groupToSubmit in _groupedEntityToAdd) { - foreach (var groupToSubmit in _groupedEntityToAdd.other) + var groupID = groupToSubmit.@group; + var groupDB = GetDBGroup(groupID); + + foreach (var entityComponentsToSubmit in groupToSubmit.components) { - var groupID = groupToSubmit.Key; - var groupDB = GetDBGroup(groupID); -//entityComponentsToSubmit is the array of components found in the groupID per component type. -//if there are N entities to submit, and M components type to add for each entity, this foreach will run NxM times. - foreach (var entityComponentsToSubmit in groupToSubmit.Value) - { - var realDic = groupDB[new RefWrapperType(entityComponentsToSubmit.Key)]; - - entityComponentsToSubmit.Value.ExecuteEnginesAddOrSwapCallbacks( - _reactiveEnginesAddRemove, realDic, null, groupID, in profiler); - - numberOfOperations += entityComponentsToSubmit.Value.count; - - if (numberOfOperations >= _maxNumberOfOperationsPerFrame) - { - using (outerSampler.Yield()) - using (sampler.Yield()) - { - yield return true; - } - - numberOfOperations = 0; - } - } + var type = entityComponentsToSubmit.key; + var wrapper = new RefWrapperType(type); + + var toDictionary = GetTypeSafeDictionary(groupID, groupDB, wrapper); + enumerator.MoveNext(); + toDictionary.ExecuteEnginesAddEntityCallbacksFast(_reactiveEnginesAddEx, groupID, + enumerator.Current, in sampler); } } } - finally + + //then submit everything in the engines, so that the DB is up to date with all the entity components + //created by the entity built + using (sampler.Sample("Add entities to engines")) { - using (profiler.Sample("clear double buffering")) + foreach (GroupInfo groupToSubmit in _groupedEntityToAdd) { - //other can be cleared now, but let's avoid deleting the dictionary every time - _groupedEntityToAdd.ClearOther(); + var groupID = groupToSubmit.@group; + var groupDB = GetDBGroup(groupID); + + //This loop iterates again all the entity components that have been just submitted to call + //the Add Callbacks on them. Note that I am iterating the transient buffer of the just + //added components, but calling the callback on the entities just added in the real buffer + //Note: it's OK to add new entities while this happens because of the double buffer + //design of the transient buffer of added entities. + foreach (var entityComponentsToSubmit in groupToSubmit.components) + { + var type = entityComponentsToSubmit.key; + var fromDictionary = entityComponentsToSubmit.value; + + //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); + } } } } + finally + { + using (sampler.Sample("clear double buffering")) + { + //other can be cleared now, but let's avoid deleting the dictionary every time + _groupedEntityToAdd.ClearLastAddOperations(); + } + } } - - yield return false; } } - + bool HasMadeNewStructuralChangesInThisIteration() { - return _groupedEntityToAdd.AnyEntityCreated() || _entitiesOperations.count > 0; + return _groupedEntityToAdd.AnyEntityCreated() || _entitiesOperations.AnyOperationQueued(); } - readonly DoubleBufferedEntitiesToAdd _groupedEntityToAdd; - readonly FasterDictionary _entitiesOperations; - readonly FasterList _transientEntitiesOperations; - uint _maxNumberOfOperationsPerFrame; + void RemoveEntitiesFromGroup(ExclusiveGroupStruct groupID, in PlatformProfiler profiler) + { + _entityLocator.RemoveAllGroupReferenceLocators(groupID); + + if (_groupEntityComponentsDB.TryGetValue(groupID, out var dictionariesOfEntities)) + { + foreach (var dictionaryOfEntities in dictionariesOfEntities) + { + //RemoveEX happens inside + dictionaryOfEntities.value.ExecuteEnginesRemoveCallbacks_Group(_reactiveEnginesRemove, + _reactiveEnginesRemoveEx, groupID, profiler); + } + + foreach (var dictionaryOfEntities in dictionariesOfEntities) + { + dictionaryOfEntities.value.Clear(); + + _groupsPerEntity[dictionaryOfEntities.key][groupID].Clear(); + } + } + } + + void SwapEntitiesBetweenGroups(ExclusiveGroupStruct fromGroupId, ExclusiveGroupStruct toGroupId, + PlatformProfiler platformProfiler) + { + FasterDictionary fromGroup = GetDBGroup(fromGroupId); + FasterDictionary toGroup = GetOrAddDBGroup(toGroupId); + + _entityLocator.UpdateAllGroupReferenceLocators(fromGroupId, toGroupId); + + //remove entities from dictionaries + foreach (var dictionaryOfEntities in fromGroup) + { + RefWrapperType refWrapperType = dictionaryOfEntities.key; + + ITypeSafeDictionary fromDictionary = dictionaryOfEntities.value; + ITypeSafeDictionary toDictionary = + GetOrAddTypeSafeDictionary(toGroupId, toGroup, refWrapperType, fromDictionary); + + fromDictionary.AddEntitiesToDictionary(toDictionary, toGroupId, this.entityLocator); + } + + //Call all the callbacks + foreach (var dictionaryOfEntities in fromGroup) + { + RefWrapperType refWrapperType = dictionaryOfEntities.key; + + ITypeSafeDictionary fromDictionary = dictionaryOfEntities.value; + ITypeSafeDictionary toDictionary = GetTypeSafeDictionary(toGroupId, toGroup, refWrapperType); + + //SwapEX happens inside + fromDictionary.ExecuteEnginesSwapCallbacks_Group(_reactiveEnginesSwap, + _reactiveEnginesSwapEx, toDictionary, fromGroupId, toGroupId, platformProfiler); + } + + //remove entities from dictionaries + foreach (var dictionaryOfEntities in fromGroup) + { + dictionaryOfEntities.value.Clear(); + + _groupsPerEntity[dictionaryOfEntities.key][fromGroupId].Clear(); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + ITypeSafeDictionary GetOrAddTypeSafeDictionary(ExclusiveGroupStruct groupId, + FasterDictionary groupPerComponentType, RefWrapperType type, + ITypeSafeDictionary fromDictionary) + { + //be sure that the TypeSafeDictionary for the entity Type exists + if (groupPerComponentType.TryGetValue(type, out ITypeSafeDictionary toEntitiesDictionary) == false) + { + toEntitiesDictionary = fromDictionary.Create(); + groupPerComponentType.Add(type, toEntitiesDictionary); + } + + { + //update GroupsPerEntity + if (_groupsPerEntity.TryGetValue(type, out var groupedGroup) == false) + groupedGroup = _groupsPerEntity[type] = + new FasterDictionary(); + + groupedGroup[groupId] = toEntitiesDictionary; + } + + return toEntitiesDictionary; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static ITypeSafeDictionary GetTypeSafeDictionary(ExclusiveGroupStruct groupID, + FasterDictionary @group, RefWrapperType refWrapper) + { + if (@group.TryGetValue(refWrapper, out ITypeSafeDictionary fromTypeSafeDictionary) == false) + { + throw new ECSException("no group found: ".FastConcat(groupID.ToName())); + } + + return fromTypeSafeDictionary; + } + + readonly DoubleBufferedEntitiesToAdd _groupedEntityToAdd; + readonly EntitiesOperations _entitiesOperations; + readonly FasterList<(uint, uint)> _cachedRangeOfSubmittedIndices; + readonly FasterDictionary _cachedIndicesToSwapBeforeSubmissionForFilters; + + static readonly + Action>>>, FasterList<(EGID, EGID)> + , + EnginesRoot> _swapEntities; + + static readonly Action< + FasterDictionary>>, + FasterList, EnginesRoot> _removeEntities; + + static readonly Action _removeGroup; + static readonly Action _swapGroup; } } \ No newline at end of file diff --git a/com.sebaslab.svelto.ecs/Core/EntitiesDB.FindGroups.cs b/com.sebaslab.svelto.ecs/Core/EntitiesDB.FindGroups.cs index 817ef40..41186fd 100644 --- a/com.sebaslab.svelto.ecs/Core/EntitiesDB.FindGroups.cs +++ b/com.sebaslab.svelto.ecs/Core/EntitiesDB.FindGroups.cs @@ -1,5 +1,4 @@ using System; -using System.Runtime.CompilerServices; using System.Threading; using Svelto.DataStructures; using Svelto.ECS.Internal; @@ -115,9 +114,9 @@ namespace Svelto.ECS foreach (var value in localArray[startIndex]) { - if (value.Key.IsEnabled()) + if (value.key.IsEnabled()) { - localGroups.Add(value.Key, value.Key); + localGroups.Add(value.key, value.key); } } @@ -171,9 +170,9 @@ namespace Svelto.ECS foreach (var value in localArray[startIndex]) { - if (value.Key.IsEnabled()) + if (value.key.IsEnabled()) { - localGroups.Add(value.Key, value.Key); + localGroups.Add(value.key, value.key); } } diff --git a/com.sebaslab.svelto.ecs/Core/EntitiesDB.cs b/com.sebaslab.svelto.ecs/Core/EntitiesDB.cs index 07b3576..6958f2e 100644 --- a/com.sebaslab.svelto.ecs/Core/EntitiesDB.cs +++ b/com.sebaslab.svelto.ecs/Core/EntitiesDB.cs @@ -6,14 +6,13 @@ using System; using System.Runtime.CompilerServices; using Svelto.Common; using Svelto.DataStructures; -using Svelto.DataStructures.Native; using Svelto.ECS.Internal; namespace Svelto.ECS { public partial class EntitiesDB { - internal EntitiesDB(EnginesRoot enginesRoot, EnginesRoot.LocatorMap entityReferencesMap) + internal EntitiesDB(EnginesRoot enginesRoot, EnginesRoot.EntityReferenceMap entityReferencesMap) { _enginesRoot = enginesRoot; _entityReferencesMap = entityReferencesMap; @@ -25,15 +24,17 @@ namespace Svelto.ECS { uint count = 0; IBuffer buffer; + EntityIDs ids = default; if (SafeQueryEntityDictionary(out var typeSafeDictionary, entitiesInGroupPerType) == false) buffer = RetrieveEmptyEntityComponentArray(); else { - var safeDictionary = (typeSafeDictionary as ITypeSafeDictionary); + ITypeSafeDictionary safeDictionary = (typeSafeDictionary as ITypeSafeDictionary); buffer = safeDictionary.GetValues(out count); + ids = safeDictionary.entityIDs; } - return new EntityCollection(buffer, count); + return new EntityCollection(buffer, count, ids); } /// @@ -257,7 +258,7 @@ namespace Svelto.ECS public bool IsDisposing => _enginesRoot._isDisposing; [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal bool SafeQueryEntityDictionary + bool SafeQueryEntityDictionary (out ITypeSafeDictionary typeSafeDictionary , FasterDictionary entitiesInGroupPerType) where T : IEntityComponent { @@ -353,6 +354,6 @@ namespace Svelto.ECS FasterDictionary> groupsPerEntity => _enginesRoot._groupsPerEntity; - EnginesRoot.LocatorMap _entityReferencesMap; + EnginesRoot.EntityReferenceMap _entityReferencesMap; } } \ No newline at end of file diff --git a/com.sebaslab.svelto.ecs/Core/EntitiesOperations.cs b/com.sebaslab.svelto.ecs/Core/EntitiesOperations.cs new file mode 100644 index 0000000..ef37ec0 --- /dev/null +++ b/com.sebaslab.svelto.ecs/Core/EntitiesOperations.cs @@ -0,0 +1,191 @@ +using System; +using Svelto.DataStructures; + +namespace Svelto.ECS +{ + class EntitiesOperations + { + public EntitiesOperations() + { + _thisSubmissionInfo.Init(); + _lastSubmittedInfo.Init(); + } + + public void AddSwapOperation(EGID fromID, EGID toID, IComponentBuilder[] componentBuilders, string caller) + { + _thisSubmissionInfo._entitiesSwapped.Add((fromID, toID)); + + //todo: limit the number of dictionaries that can be cached + //recycle or create dictionaries of components per group + var swappedComponentsPerType = _thisSubmissionInfo._currentSwapEntitiesOperations.RecycleOrAdd( + fromID.groupID, + () => new FasterDictionary>>(), + (ref FasterDictionary>> recycled) => + recycled.FastClear()); + + foreach (IComponentBuilder operation in componentBuilders) + { + swappedComponentsPerType + //recycle or create dictionaries per component type + .RecycleOrAdd(new RefWrapperType(operation.GetEntityComponentType()), + () => new FasterDictionary>(), + (ref FasterDictionary> target) => + target.FastClear()) + //recycle or create list of entities to swap + .RecycleOrAdd(toID.groupID, () => new FasterList<(uint, uint, string)>(), + (ref FasterList<(uint, uint, string)> target) => target.FastClear()) + //add entity to swap + .Add((fromID.entityID, toID.entityID, caller)); + } + } + + public void AddRemoveOperation(EGID entityEgid, IComponentBuilder[] componentBuilders, string caller) + { + _thisSubmissionInfo._entitiesRemoved.Add(entityEgid); + //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, () => new FasterDictionary>(), + (ref FasterDictionary> recycled) => recycled.FastClear()); + + foreach (IComponentBuilder operation in componentBuilders) + { + removedComponentsPerType + //recycle or create dictionaries per component type + .RecycleOrAdd(new RefWrapperType(operation.GetEntityComponentType()), + () => new FasterList<(uint, string)>(), + (ref FasterList<(uint, string)> target) => target.FastClear()) + //add entity to swap + .Add((entityEgid.entityID, caller)); + } + } + + public void AddRemoveGroupOperation(ExclusiveBuildGroup groupID, string caller) + { + _thisSubmissionInfo._groupsToRemove.Add((groupID, caller)); + } + + public void AddSwapGroupOperation(ExclusiveBuildGroup fromGroupID, ExclusiveBuildGroup toGroupID, string caller) + { + _thisSubmissionInfo._groupsToSwap.Add((fromGroupID, toGroupID, caller)); + } + + public void ExecuteRemoveAndSwappingOperations( + Action>>>, FasterList<(EGID, EGID)> + , + EnginesRoot> swapEntities, + Action>>, + FasterList, EnginesRoot> removeEntities, Action removeGroup, + Action swapGroup, EnginesRoot enginesRoot) + { + (_thisSubmissionInfo, _lastSubmittedInfo) = (_lastSubmittedInfo, _thisSubmissionInfo); + + ///todo: we found a case where entities with reference to other entities were removed + /// in the same frame where the referenced entities are remove too. + /// the callback of the referencing entities were assuming that the reference at that point + /// would be invalid. However since the callbacks were called before the groups are removed + /// the reference were still valid, which was not expected. + /// If the referenced entities were removed one by one instead that with the group, by chance + /// it instead worked because the entities were removed before the callbacks were called. + /// this is why RemoveGroup is happeing before RemoveEntities, however the real fix + /// should be to update all the references before removing the entities from the dictionaries + /// and call the callbacks + foreach (var (group, caller) in _lastSubmittedInfo._groupsToRemove) + { + try + { + removeGroup(group, enginesRoot); + } + catch + { + var str = "Crash while removing a whole group on ".FastConcat(group.ToString()) + .FastConcat(" from : ", caller); + + Console.LogError(str); + + throw; + } + } + + foreach (var (fromGroup, toGroup, caller) in _lastSubmittedInfo._groupsToSwap) + { + try + { + swapGroup(fromGroup, toGroup, enginesRoot); + } + catch + { + var str = "Crash while swapping a whole group on " + .FastConcat(fromGroup.ToString(), " ", toGroup.ToString()).FastConcat(" from : ", caller); + + Console.LogError(str); + + throw; + } + } + + if (_lastSubmittedInfo._entitiesSwapped.count > 0) + swapEntities(_lastSubmittedInfo._currentSwapEntitiesOperations, _lastSubmittedInfo._entitiesSwapped, + enginesRoot); + + if (_lastSubmittedInfo._entitiesRemoved.count > 0) + removeEntities(_lastSubmittedInfo._currentRemoveEntitiesOperations, _lastSubmittedInfo._entitiesRemoved, + enginesRoot); + + _lastSubmittedInfo.Clear(); + } + + public bool AnyOperationQueued() => _thisSubmissionInfo.AnyOperationQueued(); + + struct Info + { + //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 bool AnyOperationQueued() => + _entitiesSwapped.count > 0 || _entitiesRemoved.count > 0 || _groupsToSwap.count > 0 || + _groupsToRemove.count > 0; + + internal void Clear() + { + _currentSwapEntitiesOperations.FastClear(); + _currentRemoveEntitiesOperations.FastClear(); + _entitiesSwapped.FastClear(); + _entitiesRemoved.FastClear(); + _groupsToRemove.FastClear(); + _groupsToSwap.FastClear(); + } + + internal void Init() + { + _entitiesSwapped = new FasterList<(EGID, EGID)>(); + _entitiesRemoved = new FasterList(); + _groupsToRemove = new FasterList<(ExclusiveBuildGroup, string)>(); + _groupsToSwap = new FasterList<(ExclusiveBuildGroup, ExclusiveBuildGroup, string)>(); + + _currentSwapEntitiesOperations = + new FasterDictionary>>>(); + _currentRemoveEntitiesOperations = + new FasterDictionary>>(); + } + } + + Info _thisSubmissionInfo; + Info _lastSubmittedInfo; + } +} \ 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 bfbe086..35c2a58 100644 --- a/com.sebaslab.svelto.ecs/Core/EntityCollection.cs +++ b/com.sebaslab.svelto.ecs/Core/EntityCollection.cs @@ -1,6 +1,5 @@ using System.Runtime.CompilerServices; using Svelto.DataStructures; -using Svelto.DataStructures.Native; using Svelto.ECS.Internal; namespace Svelto.ECS @@ -9,134 +8,139 @@ namespace Svelto.ECS { static readonly bool IsUnmanaged = TypeSafeDictionary.isUnmanaged; + public EntityCollection(IBuffer buffer, uint count, EntityIDs entityIDs) : this() + { + DBC.ECS.Check.Require(count == 0 || buffer.isValid, "Buffer is found in impossible state"); + if (IsUnmanaged) + { + _nativedBuffer = (NB)buffer; + _nativedIndices = entityIDs.nativeIDs; + } + else + { + _managedBuffer = (MB)buffer; + _managedIndices = entityIDs.managedIDs; + } + + this.count = count; + } + public EntityCollection(IBuffer buffer, uint count) : this() { DBC.ECS.Check.Require(count == 0 || buffer.isValid, "Buffer is found in impossible state"); if (IsUnmanaged) - _nativedBuffer = (NB) buffer; + _nativedBuffer = (NB)buffer; else - _managedBuffer = (MB) buffer; + _managedBuffer = (MB)buffer; - _count = count; + this.count = count; } - public uint count => _count; + public uint count { get; } internal readonly MB _managedBuffer; internal readonly NB _nativedBuffer; - - readonly uint _count; + + internal readonly NativeEntityIDs _nativedIndices; + internal readonly ManagedEntityIDs _managedIndices; } - public readonly ref struct EntityCollection - where T1 : struct, IEntityComponent where T2 : struct, IEntityComponent + public readonly ref struct EntityCollection where T1 : struct, IEntityComponent + where T2 : struct, IEntityComponent { internal EntityCollection(in EntityCollection array1, in EntityCollection array2) { - _array1 = array1; - _array2 = array2; + buffer1 = array1; + buffer2 = array2; } - public uint count => _array1.count; + public int count => (int)buffer1.count; internal EntityCollection buffer2 { [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => _array2; + get; } internal EntityCollection buffer1 { [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => _array1; + get; } - - readonly EntityCollection _array1; - readonly EntityCollection _array2; } public readonly ref struct EntityCollection where T3 : struct, IEntityComponent - where T2 : struct, IEntityComponent - where T1 : struct, IEntityComponent + where T2 : struct, IEntityComponent + where T1 : struct, IEntityComponent { - internal EntityCollection - (in EntityCollection array1, in EntityCollection array2, in EntityCollection array3) + internal EntityCollection(in EntityCollection array1, in EntityCollection array2, + in EntityCollection array3) { - _array1 = array1; - _array2 = array2; - _array3 = array3; + buffer1 = array1; + buffer2 = array2; + buffer3 = array3; } internal EntityCollection buffer1 { [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => _array1; + get; } internal EntityCollection buffer2 { [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => _array2; + get; } internal EntityCollection buffer3 { [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => _array3; + get; } - internal uint count => buffer1.count; - - readonly EntityCollection _array1; - readonly EntityCollection _array2; - readonly EntityCollection _array3; + internal int count => (int)buffer1.count; } public readonly ref struct EntityCollection where T1 : struct, IEntityComponent - where T2 : struct, IEntityComponent - where T3 : struct, IEntityComponent - where T4 : struct, IEntityComponent + where T2 : struct, IEntityComponent + where T3 : struct, IEntityComponent + where T4 : struct, IEntityComponent { - internal EntityCollection - (in EntityCollection array1, in EntityCollection array2, in EntityCollection array3 - , in EntityCollection array4) + internal EntityCollection(in EntityCollection array1, in EntityCollection array2, + in EntityCollection array3, in EntityCollection array4) { - _array1 = array1; - _array2 = array2; - _array3 = array3; - _array4 = array4; + buffer1 = array1; + buffer2 = array2; + buffer3 = array3; + buffer4 = array4; } internal EntityCollection buffer1 { [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => _array1; + get; } internal EntityCollection buffer2 { [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => _array2; + get; } internal EntityCollection buffer3 { [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => _array3; + get; } internal EntityCollection buffer4 { [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => _array4; + get; } - internal uint count => _array1.count; - - readonly EntityCollection _array1; - readonly EntityCollection _array2; - readonly EntityCollection _array3; - readonly EntityCollection _array4; + internal int count => (int)buffer1.count; } public readonly struct BT @@ -147,13 +151,20 @@ namespace Svelto.ECS public readonly BufferT4 buffer4; public readonly int count; - public BT(BufferT1 bufferT1, BufferT2 bufferT2, BufferT3 bufferT3, BufferT4 bufferT4, uint count) : this() + BT(in (BufferT1 bufferT1, BufferT2 bufferT2, BufferT3 bufferT3, BufferT4 bufferT4, int count) buffer) : + this() + { + buffer1 = buffer.bufferT1; + buffer2 = buffer.bufferT2; + buffer3 = buffer.bufferT3; + buffer4 = buffer.bufferT4; + count = buffer.count; + } + + public static implicit operator BT( + in (BufferT1 bufferT1, BufferT2 bufferT2, BufferT3 bufferT3, BufferT4 bufferT4, int count) buffer) { - this.buffer1 = bufferT1; - this.buffer2 = bufferT2; - this.buffer3 = bufferT3; - this.buffer4 = bufferT4; - this.count = (int) count; + return new BT(buffer); } } @@ -164,20 +175,18 @@ namespace Svelto.ECS public readonly BufferT3 buffer3; public readonly int count; - public BT(BufferT1 bufferT1, BufferT2 bufferT2, BufferT3 bufferT3, uint count) : this() + BT(in (BufferT1 bufferT1, BufferT2 bufferT2, BufferT3 bufferT3, int count) buffer) : this() { - this.buffer1 = bufferT1; - this.buffer2 = bufferT2; - this.buffer3 = bufferT3; - this.count = (int) count; + buffer1 = buffer.bufferT1; + buffer2 = buffer.bufferT2; + buffer3 = buffer.bufferT3; + count = buffer.count; } - public void Deconstruct(out BufferT1 bufferT1, out BufferT2 bufferT2, out BufferT3 bufferT3, out int count) + public static implicit operator BT( + in (BufferT1 bufferT1, BufferT2 bufferT2, BufferT3 bufferT3, int count) buffer) { - bufferT1 = buffer1; - bufferT2 = buffer2; - bufferT3 = buffer3; - count = this.count; + return new BT(buffer); } } @@ -186,16 +195,15 @@ namespace Svelto.ECS public readonly BufferT1 buffer; public readonly int count; - public BT(BufferT1 bufferT1, uint count) : this() + BT(in (BufferT1 bufferT1, int count) buffer) : this() { - this.buffer = bufferT1; - this.count = (int) count; + this.buffer = buffer.bufferT1; + count = buffer.count; } - public void Deconstruct(out BufferT1 bufferT1, out int count) + public static implicit operator BT(in (BufferT1 bufferT1, int count) buffer) { - bufferT1 = buffer; - count = this.count; + return new BT(buffer); } public static implicit operator BufferT1(BT t) => t.buffer; @@ -207,18 +215,17 @@ namespace Svelto.ECS public readonly BufferT2 buffer2; public readonly int count; - public BT(BufferT1 bufferT1, BufferT2 bufferT2, uint count) : this() + BT(in (BufferT1 bufferT1, BufferT2 bufferT2, int count) buffer) : this() { - this.buffer1 = bufferT1; - this.buffer2 = bufferT2; - this.count = (int) count; + buffer1 = buffer.bufferT1; + buffer2 = buffer.bufferT2; + count = buffer.count; } - public void Deconstruct(out BufferT1 bufferT1, out BufferT2 bufferT2, out int count) + public static implicit operator BT( + in (BufferT1 bufferT1, BufferT2 bufferT2, int count) buffer) { - bufferT1 = buffer1; - bufferT2 = buffer2; - count = this.count; + return new BT(buffer); } } } \ No newline at end of file diff --git a/com.sebaslab.svelto.ecs/Core/EntityDescriptor/DynamicEntityDescriptor.cs b/com.sebaslab.svelto.ecs/Core/EntityDescriptor/DynamicEntityDescriptor.cs index 7f94560..daa2c98 100644 --- a/com.sebaslab.svelto.ecs/Core/EntityDescriptor/DynamicEntityDescriptor.cs +++ b/com.sebaslab.svelto.ecs/Core/EntityDescriptor/DynamicEntityDescriptor.cs @@ -129,7 +129,7 @@ namespace Svelto.ECS uint index = 0; foreach (var couple in xtraComponents) - newComponents[index++] = couple.Key.value; + newComponents[index++] = couple.key.type; } IComponentBuilder[] componentBuilders = diff --git a/com.sebaslab.svelto.ecs/Core/EntityDescriptor/EntityDescriptorExtension.cs b/com.sebaslab.svelto.ecs/Core/EntityDescriptor/EntityDescriptorExtension.cs index 982e0ff..7704f6d 100644 --- a/com.sebaslab.svelto.ecs/Core/EntityDescriptor/EntityDescriptorExtension.cs +++ b/com.sebaslab.svelto.ecs/Core/EntityDescriptor/EntityDescriptorExtension.cs @@ -4,8 +4,8 @@ namespace Svelto.ECS { public static bool IsUnmanaged(this IEntityDescriptor descriptor) { - foreach (var component in descriptor.componentsToBuild) - if (component.isUnmanaged == false) + foreach (IComponentBuilder component in descriptor.componentsToBuild) + if (component.GetEntityComponentType() != typeof(EntityInfoComponent) && component.isUnmanaged == false) return false; return true; diff --git a/com.sebaslab.svelto.ecs/Core/EntityDescriptor/ExtendibleEntityDescriptor.cs b/com.sebaslab.svelto.ecs/Core/EntityDescriptor/ExtendibleEntityDescriptor.cs index 207120f..11e8633 100644 --- a/com.sebaslab.svelto.ecs/Core/EntityDescriptor/ExtendibleEntityDescriptor.cs +++ b/com.sebaslab.svelto.ecs/Core/EntityDescriptor/ExtendibleEntityDescriptor.cs @@ -1,6 +1,3 @@ -using System; -using Svelto.ECS.Serialization; - namespace Svelto.ECS { /// @@ -25,9 +22,11 @@ namespace Svelto.ECS { static ExtendibleEntityDescriptor() { - if (typeof(ISerializableEntityDescriptor).IsAssignableFrom(typeof(TType))) - throw new Exception( - $"SerializableEntityDescriptors cannot be used as base entity descriptor: {typeof(TType)}"); + //I am removing this check because in reality there is not a strong reason to forbid it and + //furthermore it's already possible to extend a SerializableEntityDescriptor through DynamicEntityDescriptor + // if (typeof(ISerializableEntityDescriptor).IsAssignableFrom(typeof(TType))) + // throw new Exception( + // $"SerializableEntityDescriptors cannot be used as base entity descriptor: {typeof(TType)}"); } protected ExtendibleEntityDescriptor(IComponentBuilder[] extraEntities) @@ -54,7 +53,6 @@ namespace Svelto.ECS return this; } - protected void Add() where T : struct, IEntityComponent { _dynamicDescriptor.Add(); diff --git a/com.sebaslab.svelto.ecs/Core/EntityDescriptor/GenericEntityDescriptor.cs b/com.sebaslab.svelto.ecs/Core/EntityDescriptor/GenericEntityDescriptor.cs index 0ef48bb..a10a149 100644 --- a/com.sebaslab.svelto.ecs/Core/EntityDescriptor/GenericEntityDescriptor.cs +++ b/com.sebaslab.svelto.ecs/Core/EntityDescriptor/GenericEntityDescriptor.cs @@ -2,7 +2,7 @@ { public abstract class GenericEntityDescriptor : IEntityDescriptor where T : struct, IEntityComponent { - internal static readonly IComponentBuilder[] _componentBuilders; + static readonly IComponentBuilder[] _componentBuilders; static GenericEntityDescriptor() { _componentBuilders = new IComponentBuilder[] {new ComponentBuilder()}; } public IComponentBuilder[] componentsToBuild => _componentBuilders; @@ -11,7 +11,7 @@ public abstract class GenericEntityDescriptor : IEntityDescriptor where T : struct, IEntityComponent where U : struct, IEntityComponent { - internal static readonly IComponentBuilder[] _componentBuilders; + static readonly IComponentBuilder[] _componentBuilders; static GenericEntityDescriptor() { @@ -24,7 +24,7 @@ public abstract class GenericEntityDescriptor : IEntityDescriptor where T : struct, IEntityComponent where U : struct, IEntityComponent where V : struct, IEntityComponent { - internal static readonly IComponentBuilder[] _componentBuilders; + static readonly IComponentBuilder[] _componentBuilders; static GenericEntityDescriptor() { diff --git a/com.sebaslab.svelto.ecs/Core/EntityFactory.cs b/com.sebaslab.svelto.ecs/Core/EntityFactory.cs index 652fa2e..ff86957 100644 --- a/com.sebaslab.svelto.ecs/Core/EntityFactory.cs +++ b/com.sebaslab.svelto.ecs/Core/EntityFactory.cs @@ -1,4 +1,3 @@ -using System; using System.Collections.Generic; using Svelto.DataStructures; @@ -10,11 +9,15 @@ namespace Svelto.ECS.Internal (EGID egid, EnginesRoot.DoubleBufferedEntitiesToAdd groupEntitiesToAdd, IComponentBuilder[] componentsToBuild , IEnumerable implementors #if DEBUG && !PROFILE_SVELTO - , Type descriptorType + , System.Type descriptorType #endif ) { - var group = FetchEntityGroup(egid.groupID, groupEntitiesToAdd); + var group = groupEntitiesToAdd.currentComponentsToAddPerGroup.GetOrAdd( + egid.groupID, () => new FasterDictionary()); + + //track the number of entities created so far in the group. + groupEntitiesToAdd.IncrementEntityCount(egid.groupID); BuildEntitiesAndAddToGroup(egid, group, componentsToBuild, implementors #if DEBUG && !PROFILE_SVELTO @@ -25,23 +28,11 @@ namespace Svelto.ECS.Internal return group; } - static FasterDictionary FetchEntityGroup - (ExclusiveGroupStruct groupID, EnginesRoot.DoubleBufferedEntitiesToAdd groupEntityComponentsByType) - { - var group = groupEntityComponentsByType.current.GetOrCreate( - groupID, () => new FasterDictionary()); - - //track the number of entities created so far in the group. - groupEntityComponentsByType.IncrementEntityCount(groupID); - - return group; - } - static void BuildEntitiesAndAddToGroup (EGID entityID, FasterDictionary @group , IComponentBuilder[] componentBuilders, IEnumerable implementors #if DEBUG && !PROFILE_SVELTO - , Type descriptorType + , System.Type descriptorType #endif ) { @@ -51,7 +42,7 @@ namespace Svelto.ECS.Internal var numberOfComponents = componentBuilders.Length; #if DEBUG && !PROFILE_SVELTO - HashSet types = new HashSet(); + var types = new HashSet(); for (var index = 0; index < numberOfComponents; ++index) { @@ -78,7 +69,7 @@ namespace Svelto.ECS.Internal , IComponentBuilder componentBuilder, IEnumerable implementors) { var entityComponentType = componentBuilder.GetEntityComponentType(); - var safeDictionary = group.GetOrCreate(new RefWrapperType(entityComponentType) + ITypeSafeDictionary safeDictionary = group.GetOrAdd(new RefWrapperType(entityComponentType) , (ref IComponentBuilder cb) => cb.CreateDictionary(1) , ref componentBuilder); diff --git a/com.sebaslab.svelto.ecs/Core/EntityInitializer.cs b/com.sebaslab.svelto.ecs/Core/EntityInitializer.cs index 6e800cc..3ce726f 100644 --- a/com.sebaslab.svelto.ecs/Core/EntityInitializer.cs +++ b/com.sebaslab.svelto.ecs/Core/EntityInitializer.cs @@ -1,58 +1,58 @@ using Svelto.DataStructures; using Svelto.ECS.Internal; -using Svelto.ECS.Reference; namespace Svelto.ECS { public readonly ref struct EntityInitializer { - public EntityInitializer - (EGID id, FasterDictionary group, in EntityReference reference) + public EntityInitializer(EGID id, FasterDictionary group, + in EntityReference reference) { _group = group; _ID = id; this.reference = reference; } - public EGID EGID => _ID; + public EGID EGID => _ID; public readonly EntityReference reference; public void Init(T initializer) where T : struct, IEntityComponent { - if (_group.TryGetValue(new RefWrapperType(ComponentBuilder.ENTITY_COMPONENT_TYPE) - , out var typeSafeDictionary) == false) + if (_group.TryGetValue(new RefWrapperType(ComponentBuilder.ENTITY_COMPONENT_TYPE), + out var typeSafeDictionary) == false) return; - var dictionary = (ITypeSafeDictionary) typeSafeDictionary; - + var dictionary = (ITypeSafeDictionary)typeSafeDictionary; +#if SLOW_SVELTO_SUBMISSION if (ComponentBuilder.HAS_EGID) SetEGIDWithoutBoxing.SetIDWithoutBoxing(ref initializer, _ID); +#endif if (dictionary.TryFindIndex(_ID.entityID, out var findElementIndex)) dictionary.GetDirectValueByRef(findElementIndex) = initializer; } - public ref T GetOrCreate() where T : struct, IEntityComponent + public ref T GetOrAdd() where T : struct, IEntityComponent { - ref var entityDictionary = ref _group.GetOrCreate( + ref var entityDictionary = ref _group.GetOrAdd( new RefWrapperType(ComponentBuilder.ENTITY_COMPONENT_TYPE), TypeSafeDictionaryFactory.Create); - var dictionary = (ITypeSafeDictionary) entityDictionary; + var dictionary = (ITypeSafeDictionary)entityDictionary; - return ref dictionary.GetOrCreate(_ID.entityID); + return ref dictionary.GetOrAdd(_ID.entityID); } public ref T Get() where T : struct, IEntityComponent { return ref (_group[new RefWrapperType(ComponentBuilder.ENTITY_COMPONENT_TYPE)] as ITypeSafeDictionary) - [_ID.entityID]; + .GetValueByRef(_ID.entityID); } public bool Has() where T : struct, IEntityComponent { - if (_group.TryGetValue(new RefWrapperType(ComponentBuilder.ENTITY_COMPONENT_TYPE) - , out var typeSafeDictionary)) + if (_group.TryGetValue(new RefWrapperType(ComponentBuilder.ENTITY_COMPONENT_TYPE), + out var typeSafeDictionary)) { - var dictionary = (ITypeSafeDictionary) typeSafeDictionary; + var dictionary = (ITypeSafeDictionary)typeSafeDictionary; if (dictionary.ContainsKey(_ID.entityID)) return true; diff --git a/com.sebaslab.svelto.ecs/Core/EntityReference/EnginesRoot.LocatorMap.cs b/com.sebaslab.svelto.ecs/Core/EntityReference/EnginesRoot.LocatorMap.cs index 3565a28..fed1eb8 100644 --- a/com.sebaslab.svelto.ecs/Core/EntityReference/EnginesRoot.LocatorMap.cs +++ b/com.sebaslab.svelto.ecs/Core/EntityReference/EnginesRoot.LocatorMap.cs @@ -1,6 +1,5 @@ using System.Runtime.CompilerServices; using Svelto.Common; -using Svelto.DataStructures; using Svelto.DataStructures.Native; using Svelto.ECS.DataStructures; using Svelto.ECS.Reference; @@ -12,7 +11,7 @@ namespace Svelto.ECS // find the last known EGID from last entity submission. public partial class EnginesRoot { - public struct LocatorMap + public struct EntityReferenceMap { internal EntityReference ClaimReference() { @@ -78,11 +77,12 @@ namespace Svelto.ECS // Update reverse map from egid to locator. var groupMap = - _egidToReferenceMap.GetOrCreate(egid.groupID + _egidToReferenceMap.GetOrAdd(egid.groupID , () => new SharedSveltoDictionaryNative(0)); groupMap[egid.entityID] = reference; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] internal void UpdateEntityReference(EGID from, EGID to) { var reference = FetchAndRemoveReference(@from); @@ -90,11 +90,12 @@ namespace Svelto.ECS _entityReferenceMap[reference.index].egid = to; var groupMap = - _egidToReferenceMap.GetOrCreate( + _egidToReferenceMap.GetOrAdd( to.groupID, () => new SharedSveltoDictionaryNative(0)); groupMap[to.entityID] = reference; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] internal void RemoveEntityReference(EGID egid) { var reference = FetchAndRemoveReference(@egid); @@ -108,6 +109,7 @@ namespace Svelto.ECS _nextFreeIndex.Set((int)reference.index); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] EntityReference FetchAndRemoveReference(EGID @from) { var egidToReference = _egidToReferenceMap[@from.groupID]; @@ -117,6 +119,7 @@ namespace Svelto.ECS return reference; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] internal void RemoveAllGroupReferenceLocators(ExclusiveGroupStruct groupId) { if (_egidToReferenceMap.TryGetValue(groupId, out var groupMap) == false) @@ -125,11 +128,12 @@ namespace Svelto.ECS // We need to traverse all entities in the group and remove the locator using the egid. // RemoveLocator would modify the enumerator so this is why we traverse the dictionary from last to first. foreach (var item in groupMap) - RemoveEntityReference(new EGID(item.Key, groupId)); + RemoveEntityReference(new EGID(item.key, groupId)); _egidToReferenceMap.Remove(groupId); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] internal void UpdateAllGroupReferenceLocators(ExclusiveGroupStruct fromGroupId, ExclusiveGroupStruct toGroupId) { if (_egidToReferenceMap.TryGetValue(fromGroupId, out var groupMap) == false) @@ -138,7 +142,7 @@ namespace Svelto.ECS // We need to traverse all entities in the group and update the locator using the egid. // UpdateLocator would modify the enumerator so this is why we traverse the dictionary from last to first. foreach (var item in groupMap) - UpdateEntityReference(new EGID(item.Key, fromGroupId), new EGID(item.Key, toGroupId)); + UpdateEntityReference(new EGID(item.key, fromGroupId), new EGID(item.key, toGroupId)); _egidToReferenceMap.Remove(fromGroupId); } @@ -188,8 +192,8 @@ namespace Svelto.ECS internal void PreallocateReferenceMaps(ExclusiveGroupStruct groupID, uint size) { _egidToReferenceMap - .GetOrCreate(groupID, () => new SharedSveltoDictionaryNative(size)) - .ResizeTo(size); + .GetOrAdd(groupID, () => new SharedSveltoDictionaryNative(size)) + .EnsureCapacity(size); _entityReferenceMap.Resize(size); } @@ -211,7 +215,7 @@ namespace Svelto.ECS _entityReferenceMap.Dispose(); foreach (var element in _egidToReferenceMap) - element.Value.Dispose(); + element.value.Dispose(); _egidToReferenceMap.Dispose(); } @@ -222,8 +226,8 @@ namespace Svelto.ECS _egidToReferenceMap; } - internal LocatorMap entityLocator => _entityLocator; + EntityReferenceMap entityLocator => _entityLocator; - LocatorMap _entityLocator; + EntityReferenceMap _entityLocator; } } \ No newline at end of file diff --git a/com.sebaslab.svelto.ecs/Core/EntityReference/EntitiesDB.References.cs b/com.sebaslab.svelto.ecs/Core/EntityReference/EntitiesDB.References.cs index ca72635..735c0e5 100644 --- a/com.sebaslab.svelto.ecs/Core/EntityReference/EntitiesDB.References.cs +++ b/com.sebaslab.svelto.ecs/Core/EntityReference/EntitiesDB.References.cs @@ -1,5 +1,4 @@ using System.Runtime.CompilerServices; -using Svelto.ECS.Reference; namespace Svelto.ECS { @@ -18,7 +17,7 @@ namespace Svelto.ECS } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public EnginesRoot.LocatorMap GetEntityLocator() + public EnginesRoot.EntityReferenceMap GetEntityReferenceMap() { return _entityReferencesMap; } diff --git a/com.sebaslab.svelto.ecs/Core/EntityReference/EntityReference.cs b/com.sebaslab.svelto.ecs/Core/EntityReference/EntityReference.cs index b737ad7..a109198 100644 --- a/com.sebaslab.svelto.ecs/Core/EntityReference/EntityReference.cs +++ b/com.sebaslab.svelto.ecs/Core/EntityReference/EntityReference.cs @@ -30,6 +30,8 @@ namespace Svelto.ECS return obj1._GID != obj2._GID; } + public override int GetHashCode() { return _GID.GetHashCode(); } + public EntityReference(uint uniqueId) : this(uniqueId, 0) {} public EntityReference(uint uniqueId, uint version) : this() @@ -59,7 +61,7 @@ namespace Svelto.ECS return entitiesDB.GetEGID(this); } - + [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool ToEGID(EntitiesDB entitiesDB, out EGID egid) { diff --git a/com.sebaslab.svelto.ecs/Core/EntitySubmissionScheduler.cs b/com.sebaslab.svelto.ecs/Core/EntitySubmissionScheduler.cs index f023054..a7d97eb 100644 --- a/com.sebaslab.svelto.ecs/Core/EntitySubmissionScheduler.cs +++ b/com.sebaslab.svelto.ecs/Core/EntitySubmissionScheduler.cs @@ -6,9 +6,9 @@ namespace Svelto.ECS.Schedulers public abstract void Dispose(); - public abstract bool paused { get; set; } - public uint iteration { get; protected internal set; } - + public bool paused { get; set; } + public uint iteration { get; protected internal set; } + internal bool isRunning; } } \ No newline at end of file diff --git a/com.sebaslab.svelto.ecs/Core/EntitySubmitOperation.cs b/com.sebaslab.svelto.ecs/Core/EntitySubmitOperation.cs index 3f6ecf5..93cb4e6 100644 --- a/com.sebaslab.svelto.ecs/Core/EntitySubmitOperation.cs +++ b/com.sebaslab.svelto.ecs/Core/EntitySubmitOperation.cs @@ -1,65 +1,10 @@ -using System; - -namespace Svelto.ECS +namespace Svelto.ECS { -#pragma warning disable 660,661 - struct EntitySubmitOperation -#pragma warning restore 660,661 - : IEquatable - { - public readonly EntitySubmitOperationType type; - public readonly IComponentBuilder[] builders; - public readonly EGID fromID; - public readonly EGID toID; -#if DEBUG && !PROFILE_SVELTO - public System.Diagnostics.StackFrame trace; -#endif - - public EntitySubmitOperation(EntitySubmitOperationType operation, EGID from, EGID to, - IComponentBuilder[] builders = null) - { - type = operation; - this.builders = builders; - fromID = from; - toID = to; -#if DEBUG && !PROFILE_SVELTO - trace = default; -#endif - } - - public EntitySubmitOperation - (EntitySubmitOperationType operation, ExclusiveGroupStruct @group - , IComponentBuilder[] descriptorComponentsToBuild):this() - { - type = operation; - this.builders = descriptorComponentsToBuild; - fromID = new EGID(0, group); -#if DEBUG && !PROFILE_SVELTO - trace = default; -#endif - } - - public static bool operator ==(EntitySubmitOperation obj1, EntitySubmitOperation obj2) - { - return obj1.Equals(obj2); - } - - public static bool operator !=(EntitySubmitOperation obj1, EntitySubmitOperation obj2) - { - return obj1.Equals(obj2) == false; - } - - public bool Equals(EntitySubmitOperation other) - { - return type == other.type && fromID == other.fromID && toID == other.toID; - } - } - - enum EntitySubmitOperationType + enum EntitySubmitOperationType { Swap, Remove, RemoveGroup, - SwapGroup + SwapGroup } } \ No newline at end of file diff --git a/com.sebaslab.svelto.ecs/Core/Filters/EnginesRoot.Filters.cs b/com.sebaslab.svelto.ecs/Core/Filters/EnginesRoot.Filters.cs new file mode 100644 index 0000000..979cfab --- /dev/null +++ b/com.sebaslab.svelto.ecs/Core/Filters/EnginesRoot.Filters.cs @@ -0,0 +1,160 @@ +using Svelto.DataStructures; +using Svelto.DataStructures.Native; +using Svelto.ECS.DataStructures; +using Svelto.ECS.Internal; + +namespace Svelto.ECS +{ + public partial class EnginesRoot + { + void InitFilters() + { + _transientEntityFilters = new SharedSveltoDictionaryNative(0); + _persistentEntityFilters = new SharedSveltoDictionaryNative(0); + _indicesOfPersistentFiltersUsedByThisComponent = + new SharedSveltoDictionaryNative>(0); + } + + void DisposeFilters() + { + foreach (var filter in _transientEntityFilters) + { + filter.value.Dispose(); + } + + foreach (var filter in _persistentEntityFilters) + { + filter.value.Dispose(); + } + + foreach (var filter in _indicesOfPersistentFiltersUsedByThisComponent) + { + filter.value.Dispose(); + } + + _transientEntityFilters.Dispose(); + _persistentEntityFilters.Dispose(); + _indicesOfPersistentFiltersUsedByThisComponent.Dispose(); + } + + void ClearTransientFilters() + { + foreach (var filter in _transientEntityFilters) + { + filter.value.Clear(); + } + } + + void RemoveEntityFromPersistentFilters(FasterList<(uint, string)> entityIDs, ExclusiveGroupStruct fromGroup, + RefWrapperType refWrapperType, ITypeSafeDictionary fromDic) + { + //is there any filter used by this component? + if (_indicesOfPersistentFiltersUsedByThisComponent.TryGetValue(new NativeRefWrapperType(refWrapperType), + out NativeDynamicArrayCast listOfFilters)) + { + var numberOfFilters = listOfFilters.count; + for (int filterIndex = 0; filterIndex < numberOfFilters; ++filterIndex) + { + //we are going to remove multiple entities, this means that the dictionary count would decrease + //for each entity removed from each filter + //we need to keep a copy to reset to the original count for each filter + var currentLastIndex = (uint)fromDic.count - 1; + var filters = _persistentEntityFilters.unsafeValues; + var persistentFilter = filters[listOfFilters[filterIndex]]._filtersPerGroup; + + if (persistentFilter.TryGetValue(fromGroup, out var groupFilter)) + { + var entitiesCount = entityIDs.count; + + for (int entityIndex = 0; entityIndex < entitiesCount; ++entityIndex) + { + uint fromentityID = entityIDs[entityIndex].Item1; + var fromIndex = fromDic.GetIndex(fromentityID); + + groupFilter.RemoveWithSwapBack(fromentityID, fromIndex, currentLastIndex--); + } + } + } + } + } + + //this method is called by the framework only if listOfFilters.count > 0 + void SwapEntityBetweenPersistentFilters(FasterList<(uint, uint, string)> fromEntityToEntityIDs, + FasterDictionary beforeSubmissionFromIDs, ITypeSafeDictionary toComponentsDictionary, + ExclusiveGroupStruct fromGroup, ExclusiveGroupStruct toGroup, uint fromDictionaryCount, + NativeDynamicArrayCast listOfFilters) + { + DBC.ECS.Check.Require(listOfFilters.count > 0, "why are you calling this with an empty list?"); + var numberOfFilters = listOfFilters.count; + + /// fromEntityToEntityIDs are the ID 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. + /// The complexity of this function is that the ToDictionary is already updated, so the toIndex + /// is actually correct and guaranteed to be valid. However the beforeSubmissionFromIDs are the + /// indices of the entities in the FromDictionary BEFORE the submission happens, so before the + /// entities are actually removed from the dictionary. + for (int filterIndex = 0; filterIndex < numberOfFilters; ++filterIndex) + { + //we are going to remove multiple entities, this means that the dictionary count would decrease + //for each entity removed from each filter + //we need to keep a copy to reset to the original count for each filter + var currentLastIndex = fromDictionaryCount; + + //if the group has a filter linked: + EntityFilterCollection persistentFilter = + _persistentEntityFilters.unsafeValues[listOfFilters[filterIndex]]; + if (persistentFilter._filtersPerGroup.TryGetValue(fromGroup, out var fromGroupFilter)) + { + EntityFilterCollection.GroupFilters groupFilterTo = default; + + foreach (var (fromEntityID, toEntityID, _) in fromEntityToEntityIDs) + { + //if there is an entity, it must be moved to the to filter + if (fromGroupFilter.Exists(fromEntityID) == true) + { + var toIndex = toComponentsDictionary.GetIndex(toEntityID); + + if (groupFilterTo.isValid == false) + groupFilterTo = persistentFilter.GetGroupFilter(toGroup); + + groupFilterTo.Add(toEntityID, toIndex); + } + } + + foreach (var (fromEntityID, _, _) in fromEntityToEntityIDs) + { + //fromIndex is the same of the index in the filter if the entity is in the filter, but + //we need to update the entity index of the last entity swapped from the dictionary even + //in the case when the fromEntity is not present in the filter. + + uint fromIndex; //index in the from dictionary + if (fromGroupFilter.Exists(fromEntityID)) + fromIndex = fromGroupFilter._entityIDToDenseIndex[fromEntityID]; + else + fromIndex = beforeSubmissionFromIDs[fromEntityID]; + + //Removing an entity from the dictionary may affect the index of the last entity in the + //values dictionary array, so we need to to update the indices of the affected entities. + //must be outside because from may not be present in the filter, but last index is + + //for each entity removed from the from group, I have to update it's index in the + //from filter. An entity removed from the DB is always swapped back, which means + //it's current position is taken by the last entity in the dictionary array. + + //this means that the index of the last entity will change to the index of the + //replaced entity + + fromGroupFilter.RemoveWithSwapBack(fromEntityID, fromIndex, currentLastIndex--); + } + } + } + } + + internal SharedSveltoDictionaryNative _transientEntityFilters; + internal SharedSveltoDictionaryNative _persistentEntityFilters; + + internal SharedSveltoDictionaryNative> + _indicesOfPersistentFiltersUsedByThisComponent; + } +} \ No newline at end of file diff --git a/com.sebaslab.svelto.ecs/Core/Filters/EntitiesDB.Filters.cs b/com.sebaslab.svelto.ecs/Core/Filters/EntitiesDB.Filters.cs new file mode 100644 index 0000000..161d36c --- /dev/null +++ b/com.sebaslab.svelto.ecs/Core/Filters/EntitiesDB.Filters.cs @@ -0,0 +1,349 @@ +using System; +using System.Threading; +using Svelto.Common; +using Svelto.DataStructures; +using Svelto.DataStructures.Native; +using Svelto.ECS.DataStructures; + + +namespace Svelto.ECS +{ + public struct FilterContextID + { + public readonly uint id; + + internal FilterContextID(uint id) + { + DBC.ECS.Check.Require(id < ushort.MaxValue, "too many types registered, HOW :)"); + + this.id = id; + } + } + + public readonly struct CombinedFilterID + { + internal readonly long id; + + public FilterContextID contextID => new FilterContextID((uint)((id & 0xFFFF0000) >> 16)); + public uint filterID => (uint)(id >> 32); + + public CombinedFilterID(int filterID, FilterContextID contextID) + { + id = (long)filterID << 32 | (uint)contextID.id << 16; + } + + public static implicit operator CombinedFilterID((int filterID, FilterContextID contextID) data) + { + return new CombinedFilterID(data.filterID, data.contextID); + } + } + + //this cannot be inside EntitiesDB otherwise it will cause hashing of reference in Burst + public class Internal_FilterHelper + { + //since the user can choose their own filterID, in order to avoid collisions between + //filters of the same type, the FilterContext is provided. The type is identified through + //TypeCounter + public static long CombineFilterIDs(CombinedFilterID combinedFilterID) where T: struct, IEntityComponent + { + var id = (uint)ComponentID.id.Data; + + var combineFilterIDs = (long)combinedFilterID.id | id; + + return combineFilterIDs; + } + } + + public partial class EntitiesDB + { + public SveltoFilters GetFilters() + { + return new SveltoFilters(_enginesRoot._persistentEntityFilters, + _enginesRoot._indicesOfPersistentFiltersUsedByThisComponent, _enginesRoot._transientEntityFilters); + } + + /// + /// this whole structure is usable inside DOTS JOBS and BURST + /// + public readonly struct SveltoFilters + { + static readonly SharedStaticWrapper uniqueContextID; + +#if UNITY_BURST + [Unity.Burst.BurstDiscard] + //SharedStatic values must be initialized from not burstified code +#endif + public static FilterContextID GetNewContextID() + { + return new FilterContextID((uint)Interlocked.Increment(ref uniqueContextID.Data)); + } + + public SveltoFilters(SharedSveltoDictionaryNative persistentEntityFilters, + SharedSveltoDictionaryNative> + indicesOfPersistentFiltersUsedByThisComponent, + SharedSveltoDictionaryNative transientEntityFilters) + { + _persistentEntityFilters = persistentEntityFilters; + _indicesOfPersistentFiltersUsedByThisComponent = indicesOfPersistentFiltersUsedByThisComponent; + _transientEntityFilters = transientEntityFilters; + } + +#if UNITY_BURST + public ref EntityFilterCollection GetOrCreatePersistentFilter(int filterID, + FilterContextID filterContextId, NativeRefWrapperType typeRef) where T : unmanaged, IEntityComponent + { + return ref GetOrCreatePersistentFilter(new CombinedFilterID(filterID, filterContextId), typeRef); + } + + public ref EntityFilterCollection GetOrCreatePersistentFilter(CombinedFilterID filterID, + NativeRefWrapperType typeRef) where T : unmanaged, IEntityComponent + { + long combineFilterIDs = Internal_FilterHelper.CombineFilterIDs(filterID); + + if (_persistentEntityFilters.TryFindIndex(combineFilterIDs, out var index) == true) + return ref _persistentEntityFilters.GetDirectValueByRef(index); + + _persistentEntityFilters.Add(combineFilterIDs, new EntityFilterCollection(filterID)); + + var lastIndex = _persistentEntityFilters.count - 1; + + if (_indicesOfPersistentFiltersUsedByThisComponent.TryFindIndex(typeRef, out var getIndex) == false) + { + var newArray = new NativeDynamicArrayCast(1, Allocator.Persistent); + newArray.Add(lastIndex); + _indicesOfPersistentFiltersUsedByThisComponent.Add(typeRef, newArray); + } + else + { + ref var array = ref _indicesOfPersistentFiltersUsedByThisComponent.GetDirectValueByRef(getIndex); + array.Add(lastIndex); + } + + return ref _persistentEntityFilters.GetDirectValueByRef((uint)lastIndex); + } +#endif + + /// + /// 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. + /// + /// + /// +#if UNITY_BURST && UNITY_COLLECTIONS + [Unity.Collections.NotBurstCompatible] +#endif + public ref EntityFilterCollection GetOrCreatePersistentFilter(int filterID, FilterContextID filterContextId) + where T : unmanaged, IEntityComponent + { + return ref GetOrCreatePersistentFilter(new CombinedFilterID(filterID, filterContextId)); + } +#if UNITY_BURST && UNITY_COLLECTIONS + [Unity.Collections.NotBurstCompatible] +#endif + public ref EntityFilterCollection GetOrCreatePersistentFilter(CombinedFilterID filterID) + where T : unmanaged, IEntityComponent + { + long combineFilterIDs = Internal_FilterHelper.CombineFilterIDs(filterID); + + if (_persistentEntityFilters.TryFindIndex(combineFilterIDs, out var index) == true) + return ref _persistentEntityFilters.GetDirectValueByRef(index); + + var typeRef = TypeRefWrapper.wrapper; + var filterCollection = new EntityFilterCollection(filterID); + + _persistentEntityFilters.Add(combineFilterIDs, filterCollection); + + var lastIndex = _persistentEntityFilters.count - 1; + + _indicesOfPersistentFiltersUsedByThisComponent.GetOrAdd(new NativeRefWrapperType(typeRef), + () => new NativeDynamicArrayCast(1, Svelto.Common.Allocator.Persistent)).Add(lastIndex); + + return ref _persistentEntityFilters.GetDirectValueByRef((uint)lastIndex); + } + + public ref EntityFilterCollection GetPersistentFilter(int filterID, FilterContextID filterContextId) + where T : unmanaged, IEntityComponent + { + return ref GetPersistentFilter(new CombinedFilterID(filterID, filterContextId)); + } + + public ref EntityFilterCollection GetPersistentFilter(CombinedFilterID filterID) + where T : unmanaged, IEntityComponent + { + long combineFilterIDs = Internal_FilterHelper.CombineFilterIDs(filterID); + + if (_persistentEntityFilters.TryFindIndex(combineFilterIDs, out var index) == true) + return ref _persistentEntityFilters.GetDirectValueByRef(index); + + throw new Exception("filter not found"); + } + + public bool TryGetPersistentFilter(CombinedFilterID combinedFilterID, out EntityFilterCollection entityCollection) + where T : unmanaged, IEntityComponent + { + long combineFilterIDs = Internal_FilterHelper.CombineFilterIDs(combinedFilterID); + + if (_persistentEntityFilters.TryFindIndex(combineFilterIDs, out var index) == true) + { + entityCollection = _persistentEntityFilters.GetDirectValueByRef(index); + return true; + } + + entityCollection = default; + return false; + } + + public EntityFilterCollectionsEnumerator GetPersistentFilters() where T : unmanaged, IEntityComponent + { + if (_indicesOfPersistentFiltersUsedByThisComponent.TryFindIndex( + new NativeRefWrapperType(new RefWrapperType(typeof(T))), out var index) == true) + return new EntityFilterCollectionsEnumerator( + _indicesOfPersistentFiltersUsedByThisComponent.GetDirectValueByRef(index), + _persistentEntityFilters); + + throw new Exception($"no filters associated with the type {TypeCache.name}"); + } + + public EntityFilterCollectionsWithContextEnumerator GetPersistentFilters(FilterContextID filterContextId) + { + if (_indicesOfPersistentFiltersUsedByThisComponent.TryFindIndex( + new NativeRefWrapperType(new RefWrapperType(typeof(T))), out var index) == true) + return new EntityFilterCollectionsWithContextEnumerator( + _indicesOfPersistentFiltersUsedByThisComponent.GetDirectValueByRef(index), + _persistentEntityFilters, filterContextId); + + throw new Exception($"no filters associated with the type {TypeCache.name}"); + } + + public bool TryGetPersistentFilters(FilterContextID filterContextId, out EntityFilterCollectionsWithContextEnumerator enumerator) + { + if (_indicesOfPersistentFiltersUsedByThisComponent.TryFindIndex( + new NativeRefWrapperType(new RefWrapperType(typeof(T))), out var index) == true) + { + enumerator = new EntityFilterCollectionsWithContextEnumerator( + _indicesOfPersistentFiltersUsedByThisComponent.GetDirectValueByRef(index), + _persistentEntityFilters, filterContextId); + + return true; + } + + enumerator = default; + return false; + } + + public struct EntityFilterCollectionsEnumerator + { + public EntityFilterCollectionsEnumerator(NativeDynamicArrayCast getDirectValueByRef, + SharedSveltoDictionaryNative sharedSveltoDictionaryNative) : this() + { + _getDirectValueByRef = getDirectValueByRef; + _sharedSveltoDictionaryNative = sharedSveltoDictionaryNative; + } + + public EntityFilterCollectionsEnumerator GetEnumerator() + { + return this; + } + + public bool MoveNext() + { + if (_currentIndex < _getDirectValueByRef.count) + { + _currentIndex++; + + return true; + } + + return false; + } + + public ref EntityFilterCollection Current => + ref _sharedSveltoDictionaryNative.GetDirectValueByRef((uint)_currentIndex - 1); + + readonly NativeDynamicArrayCast _getDirectValueByRef; + readonly SharedSveltoDictionaryNative _sharedSveltoDictionaryNative; + int _currentIndex; + } + + public struct EntityFilterCollectionsWithContextEnumerator + { + public EntityFilterCollectionsWithContextEnumerator(NativeDynamicArrayCast getDirectValueByRef, + SharedSveltoDictionaryNative sharedSveltoDictionaryNative, + FilterContextID filterContextId) : this() + { + _getDirectValueByRef = getDirectValueByRef; + _sharedSveltoDictionaryNative = sharedSveltoDictionaryNative; + _filterContextId = filterContextId; + } + + public EntityFilterCollectionsWithContextEnumerator GetEnumerator() + { + return this; + } + + public bool MoveNext() + { + while (_currentIndex++ < _getDirectValueByRef.count && + _sharedSveltoDictionaryNative.GetDirectValueByRef((uint)_currentIndex - 1).combinedFilterID + .contextID.id != _filterContextId.id) ; + + if (_currentIndex - 1 < _getDirectValueByRef.count) + return true; + + return false; + } + + public ref EntityFilterCollection Current => + ref _sharedSveltoDictionaryNative.GetDirectValueByRef((uint)_currentIndex - 1); + + readonly NativeDynamicArrayCast _getDirectValueByRef; + readonly SharedSveltoDictionaryNative _sharedSveltoDictionaryNative; + readonly FilterContextID _filterContextId; + int _currentIndex; + } + + /// + /// Creates a transient filter. Transient filters are deleted after each submission + /// + /// + /// + public ref EntityFilterCollection GetOrCreateTransientFilter(CombinedFilterID filterID) + where T : unmanaged, IEntityComponent + { + var combineFilterIDs = Internal_FilterHelper.CombineFilterIDs(filterID); + + if (_transientEntityFilters.TryFindIndex(combineFilterIDs, out var index)) + return ref _transientEntityFilters.GetDirectValueByRef(index); + + var filterCollection = new EntityFilterCollection(filterID); + + _transientEntityFilters.Add(combineFilterIDs, filterCollection); + + return ref _transientEntityFilters.GetDirectValueByRef((uint)(_transientEntityFilters.count - 1)); + } + + public bool TryGetTransientFilter(CombinedFilterID filterID, out EntityFilterCollection entityCollection) + where T : unmanaged, IEntityComponent + { + var combineFilterIDs = Internal_FilterHelper.CombineFilterIDs(filterID); + + if (_transientEntityFilters.TryFindIndex(combineFilterIDs, out var index)) + { + entityCollection = _transientEntityFilters.GetDirectValueByRef(index); + return true; + } + + entityCollection = default; + return false; + } + + readonly SharedSveltoDictionaryNative _persistentEntityFilters; + + readonly SharedSveltoDictionaryNative> + _indicesOfPersistentFiltersUsedByThisComponent; + + readonly SharedSveltoDictionaryNative _transientEntityFilters; + } + } +} \ No newline at end of file diff --git a/com.sebaslab.svelto.ecs/Core/Filters/EntitiesDB.GroupFilters.cs b/com.sebaslab.svelto.ecs/Core/Filters/EntitiesDB.LegacyFilters.cs similarity index 53% rename from com.sebaslab.svelto.ecs/Core/Filters/EntitiesDB.GroupFilters.cs rename to com.sebaslab.svelto.ecs/Core/Filters/EntitiesDB.LegacyFilters.cs index e0b963a..2cb913b 100644 --- a/com.sebaslab.svelto.ecs/Core/Filters/EntitiesDB.GroupFilters.cs +++ b/com.sebaslab.svelto.ecs/Core/Filters/EntitiesDB.LegacyFilters.cs @@ -1,10 +1,12 @@ +using DBC.ECS; using Svelto.DataStructures; using Svelto.DataStructures.Native; namespace Svelto.ECS { /// - /// This feature must be eventually tied to the new ExclusiveGroup that won't allow the use of custom EntitiesID + /// This feature must be eventually tied to the new ExclusiveGroup that won't allow the use of + /// custom EntitiesID /// The filters could be updated when entities buffer changes during the submission, while now this process /// is completely manual. /// Making this working properly is not in my priorities right now, as I will need to add the new group type @@ -12,15 +14,24 @@ namespace Svelto.ECS /// public partial class EntitiesDB { - public readonly struct Filters + FasterDictionary> _filters => + _enginesRoot._groupFilters; + + public LegacyFilters GetLegacyFilters() { - public Filters - (FasterDictionary> filters) + return new LegacyFilters(_filters); + } + + public readonly struct LegacyFilters + { + public LegacyFilters( + FasterDictionary> + filtersLegacy) { - _filters = filters; + _filtersLegacy = filtersLegacy; } - public ref FilterGroup CreateOrGetFilterForGroup(int filterID, ExclusiveGroupStruct groupID) + public ref LegacyFilterGroup CreateOrGetFilterForGroup(int filterID, ExclusiveGroupStruct groupID) where T : struct, IEntityComponent { var refWrapper = TypeRefWrapper.wrapper; @@ -28,21 +39,9 @@ namespace Svelto.ECS return ref CreateOrGetFilterForGroup(filterID, groupID, refWrapper); } - internal ref FilterGroup CreateOrGetFilterForGroup - (int filterID, ExclusiveGroupStruct groupID, RefWrapperType refWrapper) - { - var fasterDictionary = - _filters.GetOrCreate(refWrapper, () => new FasterDictionary()); - - GroupFilters filters = fasterDictionary.GetOrCreate( - groupID, () => new GroupFilters(new SharedSveltoDictionaryNative(0), groupID)); - - return ref filters.CreateOrGetFilter(filterID); - } - public bool HasFiltersForGroup(ExclusiveGroupStruct groupID) where T : struct, IEntityComponent { - if (_filters.TryGetValue(TypeRefWrapper.wrapper, out var fasterDictionary) == false) + if (_filtersLegacy.TryGetValue(TypeRefWrapper.wrapper, out var fasterDictionary) == false) return false; return fasterDictionary.ContainsKey(groupID); @@ -51,7 +50,7 @@ namespace Svelto.ECS public bool HasFilterForGroup(int filterID, ExclusiveGroupStruct groupID) where T : struct, IEntityComponent { - if (_filters.TryGetValue(TypeRefWrapper.wrapper, out var fasterDictionary) == false) + if (_filtersLegacy.TryGetValue(TypeRefWrapper.wrapper, out var fasterDictionary) == false) return false; if (fasterDictionary.TryGetValue(groupID, out var result)) @@ -60,79 +59,77 @@ namespace Svelto.ECS return false; } - public ref GroupFilters CreateOrGetFiltersForGroup(ExclusiveGroupStruct groupID) + public ref LegacyGroupFilters CreateOrGetFiltersForGroup(ExclusiveGroupStruct groupID) where T : struct, IEntityComponent { - var fasterDictionary = _filters.GetOrCreate(TypeRefWrapper.wrapper - , () => - new FasterDictionary()); + var fasterDictionary = _filtersLegacy.GetOrAdd(TypeRefWrapper.wrapper, + () => new FasterDictionary()); - return ref fasterDictionary.GetOrCreate( - groupID, () => new GroupFilters(new SharedSveltoDictionaryNative(0), groupID)); + return ref fasterDictionary.GetOrAdd(groupID, + () => new LegacyGroupFilters(new SharedSveltoDictionaryNative(0), groupID)); } - public ref GroupFilters GetFiltersForGroup(ExclusiveGroupStruct groupID) + public ref LegacyGroupFilters GetFiltersForGroup(ExclusiveGroupStruct groupID) where T : struct, IEntityComponent { #if DEBUG && !PROFILE_SVELTO - if (_filters.ContainsKey(TypeRefWrapper.wrapper) == false) + if (_filtersLegacy.ContainsKey(TypeRefWrapper.wrapper) == false) throw new ECSException($"trying to fetch not existing filters, type {typeof(T)}"); - if (_filters[TypeRefWrapper.wrapper].ContainsKey(groupID) == false) + if (_filtersLegacy[TypeRefWrapper.wrapper].ContainsKey(groupID) == false) throw new ECSException( $"trying to fetch not existing filters, type {typeof(T)} group {groupID.ToName()}"); #endif - return ref _filters[TypeRefWrapper.wrapper].GetValueByRef(groupID); + return ref _filtersLegacy[TypeRefWrapper.wrapper].GetValueByRef(groupID); } - public ref FilterGroup GetFilterForGroup(int filterId, ExclusiveGroupStruct groupID) + public ref LegacyFilterGroup GetFilterForGroup(int filterId, ExclusiveGroupStruct groupID) where T : struct, IEntityComponent { #if DEBUG && !PROFILE_SVELTO - if (_filters.ContainsKey(TypeRefWrapper.wrapper) == false) + if (_filtersLegacy.ContainsKey(TypeRefWrapper.wrapper) == false) throw new ECSException($"trying to fetch not existing filters, type {typeof(T)}"); - if (_filters[TypeRefWrapper.wrapper].ContainsKey(groupID) == false) + if (_filtersLegacy[TypeRefWrapper.wrapper].ContainsKey(groupID) == false) throw new ECSException( $"trying to fetch not existing filters, type {typeof(T)} group {groupID.ToName()}"); #endif - return ref _filters[TypeRefWrapper.wrapper][groupID].GetFilter(filterId); + return ref _filtersLegacy[TypeRefWrapper.wrapper][groupID].GetFilter(filterId); } - public bool TryGetFilterForGroup(int filterId, ExclusiveGroupStruct groupID, out FilterGroup groupFilter) - where T : struct, IEntityComponent + public bool TryGetFilterForGroup(int filterId, ExclusiveGroupStruct groupID, + out LegacyFilterGroup groupLegacyFilter) where T : struct, IEntityComponent { - groupFilter = default; + groupLegacyFilter = default; - if (_filters.TryGetValue(TypeRefWrapper.wrapper, out var fasterDictionary) == false) + if (_filtersLegacy.TryGetValue(TypeRefWrapper.wrapper, out var fasterDictionary) == false) return false; if (fasterDictionary.TryGetValue(groupID, out var groupFilters) == false) return false; - if (groupFilters.TryGetFilter(filterId, out groupFilter) == false) + if (groupFilters.TryGetFilter(filterId, out groupLegacyFilter) == false) return false; return true; } - public bool TryGetFiltersForGroup(ExclusiveGroupStruct groupID, out GroupFilters groupFilters) - where T : struct, IEntityComponent + public bool TryGetFiltersForGroup(ExclusiveGroupStruct groupID, + out LegacyGroupFilters legacyGroupFilters) where T : struct, IEntityComponent { - groupFilters = default; + legacyGroupFilters = default; - if (_filters.TryGetValue(TypeRefWrapper.wrapper, out var fasterDictionary) == false) + if (_filtersLegacy.TryGetValue(TypeRefWrapper.wrapper, out var fasterDictionary) == false) return false; - return fasterDictionary.TryGetValue(groupID, out groupFilters); + return fasterDictionary.TryGetValue(groupID, out legacyGroupFilters); } public void ClearFilter(int filterID, ExclusiveGroupStruct exclusiveGroupStruct) { - if (_filters.TryGetValue(TypeRefWrapper.wrapper, out var fasterDictionary) == true) + if (_filtersLegacy.TryGetValue(TypeRefWrapper.wrapper, out var fasterDictionary)) { - DBC.ECS.Check.Require(fasterDictionary.ContainsKey(exclusiveGroupStruct) - , $"trying to clear filter not present in group {exclusiveGroupStruct}"); + Check.Require(fasterDictionary.ContainsKey(exclusiveGroupStruct), + $"trying to clear filter not present in group {exclusiveGroupStruct}"); fasterDictionary[exclusiveGroupStruct].ClearFilter(filterID); } @@ -140,16 +137,14 @@ namespace Svelto.ECS public void ClearFilters(int filterID) { - if (_filters.TryGetValue(TypeRefWrapper.wrapper, out var fasterDictionary) == true) - { + if (_filtersLegacy.TryGetValue(TypeRefWrapper.wrapper, out var fasterDictionary)) foreach (var filtersPerGroup in fasterDictionary) - filtersPerGroup.Value.ClearFilter(filterID); - } + filtersPerGroup.value.ClearFilter(filterID); } public void DisposeFilters(ExclusiveGroupStruct exclusiveGroupStruct) { - if (_filters.TryGetValue(TypeRefWrapper.wrapper, out var fasterDictionary) == true) + if (_filtersLegacy.TryGetValue(TypeRefWrapper.wrapper, out var fasterDictionary)) { fasterDictionary[exclusiveGroupStruct].DisposeFilters(); fasterDictionary.Remove(exclusiveGroupStruct); @@ -158,29 +153,23 @@ namespace Svelto.ECS public void DisposeFilters() { - if (_filters.TryGetValue(TypeRefWrapper.wrapper, out var fasterDictionary) == true) - { + if (_filtersLegacy.TryGetValue(TypeRefWrapper.wrapper, out var fasterDictionary)) foreach (var filtersPerGroup in fasterDictionary) - filtersPerGroup.Value.DisposeFilters(); - } + filtersPerGroup.value.DisposeFilters(); - _filters.Remove(TypeRefWrapper.wrapper); + _filtersLegacy.Remove(TypeRefWrapper.wrapper); } - public void DisposeFilterForGroup(int resetFilterID, ExclusiveGroupStruct @group) + public void DisposeFilterForGroup(int resetFilterID, ExclusiveGroupStruct group) { - if (_filters.TryGetValue(TypeRefWrapper.wrapper, out var fasterDictionary) == true) - { - fasterDictionary[group].DisposeFilter(resetFilterID); - } + if (_filtersLegacy.TryGetValue(TypeRefWrapper.wrapper, out var fasterDictionary)) + fasterDictionary[@group].DisposeFilter(resetFilterID); } public bool TryRemoveEntityFromFilter(int filtersID, EGID egid) where T : struct, IEntityComponent { if (TryGetFilterForGroup(filtersID, egid.groupID, out var filter)) - { return filter.TryRemove(egid.entityID); - } return false; } @@ -200,13 +189,22 @@ namespace Svelto.ECS return filter.Add(egid.entityID, mapper); } - readonly FasterDictionary> _filters; - } + internal ref LegacyFilterGroup CreateOrGetFilterForGroup(int filterID, ExclusiveGroupStruct groupID, + RefWrapperType refWrapper) + { + var fasterDictionary = _filtersLegacy.GetOrAdd(refWrapper, + () => new FasterDictionary()); - public Filters GetFilters() { return new Filters(_filters); } + var filters = fasterDictionary.GetOrAdd(groupID, + (ref ExclusiveGroupStruct gid) => + new LegacyGroupFilters(new SharedSveltoDictionaryNative(0), gid), + ref groupID); + return ref filters.CreateOrGetFilter(filterID); + } - FasterDictionary> _filters => - _enginesRoot._groupFilters; + readonly FasterDictionary> + _filtersLegacy; + } } } \ No newline at end of file diff --git a/com.sebaslab.svelto.ecs/Core/Filters/EntityFilterCollection.cs b/com.sebaslab.svelto.ecs/Core/Filters/EntityFilterCollection.cs new file mode 100644 index 0000000..b20828b --- /dev/null +++ b/com.sebaslab.svelto.ecs/Core/Filters/EntityFilterCollection.cs @@ -0,0 +1,198 @@ +using System.Runtime.CompilerServices; +using Svelto.Common; +using Svelto.DataStructures.Native; +using Svelto.ECS.Native; + +namespace Svelto.ECS +{ + public readonly struct EntityFilterCollection + { + internal EntityFilterCollection(CombinedFilterID combinedFilterId, + Allocator allocatorStrategy = Allocator.Persistent) + { + _filtersPerGroup = + SharedSveltoDictionaryNative.Create(allocatorStrategy); + + combinedFilterID = combinedFilterId; + } + + public CombinedFilterID combinedFilterID { get; } + + public EntityFilterIterator GetEnumerator() => new EntityFilterIterator(this); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Add(EGID egid, NativeEGIDMapper mmap) where T : unmanaged, IEntityComponent + { + DBC.ECS.Check.Require(mmap.groupID == egid.groupID, "not compatible NativeEgidMapper used"); + + return Add(egid, mmap.GetIndex(egid.entityID)); + } + + public bool Add(EGID egid, NativeEGIDMultiMapper mmap) where T : unmanaged, IEntityComponent + { + return Add(egid, mmap.GetIndex(egid)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Add(EGID egid, uint toIndex) + { + return GetGroupFilter(egid.groupID).Add(egid.entityID, toIndex); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Add(uint entityID, ExclusiveGroupStruct groupId, uint index) + { + Add(new EGID(entityID, groupId), index); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Remove(EGID egid) + { + _filtersPerGroup[egid.groupID].Remove(egid.entityID); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Exists(EGID egid) + { + return GetGroupFilter(egid.groupID).Exists(egid.entityID); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public GroupFilters GetGroupFilter(ExclusiveGroupStruct group) + { + if (_filtersPerGroup.TryGetValue(group, out var groupFilter) == false) + { + groupFilter = new GroupFilters(group); + _filtersPerGroup.Add(group, groupFilter); + } + + return groupFilter; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Clear() + { + var filterSets = _filtersPerGroup.GetValues(out var count); + for (var i = 0; i < count; i++) + { + filterSets[i].Clear(); + } + } + + internal int groupCount => _filtersPerGroup.count; + + public void ComputeFinalCount(out int count) + { + count = 0; + + for (int i = 0; i < _filtersPerGroup.count; i++) + { + count += (int)GetGroup(i).count; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal GroupFilters GetGroup(int indexGroup) + { + DBC.ECS.Check.Require(indexGroup < _filtersPerGroup.count); + return _filtersPerGroup.GetValues(out _)[indexGroup]; + } + + public void Dispose() + { + var filterSets = _filtersPerGroup.GetValues(out var count); + for (var i = 0; i < count; i++) + { + filterSets[i].Dispose(); + } + + _filtersPerGroup.Dispose(); + } + + internal readonly SharedSveltoDictionaryNative _filtersPerGroup; + + public struct GroupFilters + { + internal GroupFilters(ExclusiveGroupStruct group) : this() + { + _entityIDToDenseIndex = new SharedSveltoDictionaryNative(1); + _indexToEntityId = new SharedSveltoDictionaryNative(1); + _group = group; + } + + public bool Add(uint entityId, uint entityIndex) + { + //TODO: when sentinels are finished, we need to add AsWriter here + if (_entityIDToDenseIndex.TryAdd(entityId, entityIndex, out _)) + { + _indexToEntityId[entityIndex] = entityId; + return true; + } + + return false; + } + + public bool Exists(uint entityId) => _entityIDToDenseIndex.ContainsKey(entityId); + + public void Remove(uint entityId) + { + _indexToEntityId.Remove(_entityIDToDenseIndex[entityId]); + _entityIDToDenseIndex.Remove(entityId); + } + + public EntityFilterIndices indices + { + get + { + var values = _entityIDToDenseIndex.GetValues(out var count); + return new EntityFilterIndices(values, count); + } + } + + public int count => _entityIDToDenseIndex.count; + public bool isValid => _entityIDToDenseIndex.isValid; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal void RemoveWithSwapBack(uint entityId, uint entityIndex, uint lastIndex) + { + // Check if the last index is part of the filter as an entity, in that case + //we need to update the filter + if (entityIndex != lastIndex && _indexToEntityId.TryGetValue(lastIndex, out var lastEntityID)) + { + _entityIDToDenseIndex[lastEntityID] = entityIndex; + _indexToEntityId[entityIndex] = lastEntityID; + + _indexToEntityId.Remove(lastIndex); + } + else + { + // We don't need to check if the entityIndex is a part of the dictionary. + // The Remove function will check for us. + _indexToEntityId.Remove(entityIndex); + } + + // We don't need to check if the entityID is part of the dictionary. + // The Remove function will check for us. + _entityIDToDenseIndex.Remove(entityId); + } + + internal void Clear() + { + _indexToEntityId.FastClear(); + _entityIDToDenseIndex.FastClear(); + } + + internal void Dispose() + { + _entityIDToDenseIndex.Dispose(); + _indexToEntityId.Dispose(); + } + + internal ExclusiveGroupStruct group => _group; + + SharedSveltoDictionaryNative _indexToEntityId; + internal SharedSveltoDictionaryNative _entityIDToDenseIndex; + readonly ExclusiveGroupStruct _group; + } + } +} \ No newline at end of file diff --git a/com.sebaslab.svelto.ecs/Core/Filters/EntityFilterID.cs b/com.sebaslab.svelto.ecs/Core/Filters/EntityFilterID.cs new file mode 100644 index 0000000..d7f97cf --- /dev/null +++ b/com.sebaslab.svelto.ecs/Core/Filters/EntityFilterID.cs @@ -0,0 +1,32 @@ +using System; +using Svelto.DataStructures; + +namespace Svelto.ECS +{ + // This is just an opaque struct to identify filter collections. + public struct EntityFilterID : IEquatable + { + internal EntityFilterID(uint filterID, RefWrapperType componentType) + { + _filterID = filterID; + _componentType = componentType; + _hashCode = (int)filterID + (int)filterID ^ componentType.GetHashCode(); + } + + public bool Equals(EntityFilterID other) + { + return _filterID == other._filterID && _componentType.Equals(other._componentType); + } + + public override bool Equals(object obj) + { + throw new NotImplementedException(); + } + + public override int GetHashCode() => _hashCode; + + readonly uint _filterID; + readonly RefWrapperType _componentType; + readonly int _hashCode; + } +} \ No newline at end of file diff --git a/com.sebaslab.svelto.ecs/Core/Filters/EntityFilterIndices.cs b/com.sebaslab.svelto.ecs/Core/Filters/EntityFilterIndices.cs new file mode 100644 index 0000000..7762a8c --- /dev/null +++ b/com.sebaslab.svelto.ecs/Core/Filters/EntityFilterIndices.cs @@ -0,0 +1,47 @@ +using System.Runtime.CompilerServices; +using System.Threading; +using Svelto.DataStructures; + +namespace Svelto.ECS +{ + public struct EntityFilterIndices + { + public EntityFilterIndices(NB indices, uint count) + { + _indices = indices; + _count = count; + _index = 0; + } + + public uint count + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _count; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public uint Get(uint index) => _indices[index]; + + public uint this[uint index] + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _indices[index]; + } + + public uint this[int index] + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _indices[index]; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public uint Next() + { + return _indices[Interlocked.Increment(ref _index) - 1]; + } + + readonly NB _indices; + readonly uint _count; + int _index; + } +} \ No newline at end of file diff --git a/com.sebaslab.svelto.ecs/Core/Filters/EntityFilterIterator.cs b/com.sebaslab.svelto.ecs/Core/Filters/EntityFilterIterator.cs new file mode 100644 index 0000000..146c281 --- /dev/null +++ b/com.sebaslab.svelto.ecs/Core/Filters/EntityFilterIterator.cs @@ -0,0 +1,51 @@ +namespace Svelto.ECS +{ + public ref struct EntityFilterIterator + { + internal EntityFilterIterator(EntityFilterCollection filter) + { + _filter = filter; + _indexGroup = -1; + _current = default; + } + + public bool MoveNext() + { + while (++_indexGroup < _filter.groupCount) + { + _current = _filter.GetGroup(_indexGroup); + + if (_current.count > 0) break; + } + + return _indexGroup < _filter.groupCount; + } + + public void Reset() + { + _indexGroup = -1; + } + + public RefCurrent Current => new RefCurrent(_current); + + int _indexGroup; + readonly EntityFilterCollection _filter; + EntityFilterCollection.GroupFilters _current; + + public readonly ref struct RefCurrent + { + internal RefCurrent(EntityFilterCollection.GroupFilters filter) + { + _filter = filter; + } + + public void Deconstruct(out EntityFilterIndices indices, out ExclusiveGroupStruct group) + { + indices = _filter.indices; + group = _filter.group; + } + + readonly EntityFilterCollection.GroupFilters _filter; + } + } +} \ No newline at end of file diff --git a/com.sebaslab.svelto.ecs/Core/Filters/GroupFilters.cs b/com.sebaslab.svelto.ecs/Core/Filters/GroupFilters.cs deleted file mode 100644 index f834121..0000000 --- a/com.sebaslab.svelto.ecs/Core/Filters/GroupFilters.cs +++ /dev/null @@ -1,104 +0,0 @@ -using Svelto.DataStructures; -using Svelto.DataStructures.Native; - -namespace Svelto.ECS -{ - public struct GroupFilters - { - internal GroupFilters(SharedSveltoDictionaryNative filters, ExclusiveGroupStruct group) - { - this.filters = filters; - _group = @group; - } - - public ref FilterGroup GetFilter(int filterIndex) - { -#if DEBUG && !PROFILE_SVELTO - if (filters.isValid == false) - throw new ECSException($"trying to fetch not existing filters {filterIndex} group {_group.ToName()}"); - if (filters.ContainsKey(filterIndex) == false) - throw new ECSException($"trying to fetch not existing filters {filterIndex} group {_group.ToName()}"); -#endif - return ref filters.GetValueByRef(filterIndex); - } - - public bool HasFilter(int filterIndex) { return filters.ContainsKey(filterIndex); } - - public void ClearFilter(int filterIndex) - { - if (filters.TryFindIndex(filterIndex, out var index)) - filters.GetValues(out _)[index].Clear(); - } - - public void ClearFilters() - { - foreach (var filter in filters) - filter.Value.Clear(); - } - - public bool TryGetFilter(int filterIndex, out FilterGroup filter) - { - return filters.TryGetValue(filterIndex, out filter); - } - - public SveltoDictionaryKeyValueEnumerator>, NativeStrategy - , NativeStrategy> GetEnumerator() - { - return filters.GetEnumerator(); - } - - //Note the following methods are internal because I was pondering the idea to be able to return - //the list of GroupFilters linked to a specific filter ID. However this would mean to be able to - //maintain a revers map which at this moment seems too much and also would need the following - //method to be for ever internal (at this point in time I am not sure it's a good idea) - internal void DisposeFilter(int filterIndex) - { - if (filters.TryFindIndex(filterIndex, out var index)) - { - ref var filterGroup = ref filters.GetValues(out _)[index]; - - filterGroup.Dispose(); - - filters.Remove(filterIndex); - } - } - - internal void DisposeFilters() - { - //must release the native buffers! - foreach (var filter in filters) - filter.Value.Dispose(); - - filters.FastClear(); - } - - internal ref FilterGroup CreateOrGetFilter(int filterID) - { - if (filters.TryFindIndex(filterID, out var index) == false) - { - var orGetFilterForGroup = new FilterGroup(_group, filterID); - - filters[filterID] = orGetFilterForGroup; - - return ref filters.GetValueByRef(filterID); - } - - return ref filters.GetValues(out _)[index]; - } - - internal void Dispose() - { - foreach (var filter in filters) - { - filter.Value.Dispose(); - } - - filters.Dispose(); - } - - readonly ExclusiveGroupStruct _group; - - //filterID, filter - SharedSveltoDictionaryNative filters; - } -} \ No newline at end of file diff --git a/com.sebaslab.svelto.ecs/Core/Filters/FilterGroup.cs b/com.sebaslab.svelto.ecs/Core/Filters/LegacyFilterGroup.cs similarity index 91% rename from com.sebaslab.svelto.ecs/Core/Filters/FilterGroup.cs rename to com.sebaslab.svelto.ecs/Core/Filters/LegacyFilterGroup.cs index 99f713b..b9d2e00 100644 --- a/com.sebaslab.svelto.ecs/Core/Filters/FilterGroup.cs +++ b/com.sebaslab.svelto.ecs/Core/Filters/LegacyFilterGroup.cs @@ -1,6 +1,5 @@ using System.Runtime.CompilerServices; using Svelto.Common; -using Svelto.DataStructures; using Svelto.DataStructures.Native; using Svelto.ECS.DataStructures; @@ -16,9 +15,9 @@ namespace Svelto.ECS /// sparse[0] = position in the dense list of the entity 0 /// dense[index] = entity ID but also index in the sparse list of the same entity ID /// - public struct FilterGroup + public struct LegacyFilterGroup { - internal FilterGroup(ExclusiveGroupStruct exclusiveGroupStruct, int ID) + internal LegacyFilterGroup(ExclusiveGroupStruct exclusiveGroupStruct, int ID) { _denseListOfIndicesToEntityComponentArray = new NativeDynamicArrayCast(NativeDynamicArray.Alloc(Allocator.Persistent)); @@ -33,7 +32,7 @@ namespace Svelto.ECS /// /// Todo: how to detect if the indices are still pointing to valid entities? /// - public FilteredIndices filteredIndices => new FilteredIndices(_denseListOfIndicesToEntityComponentArray); + public LegacyFilteredIndices filteredIndices => new LegacyFilteredIndices(_denseListOfIndicesToEntityComponentArray); public bool Add(uint entityID, N mapper) where N:IEGIDMapper { @@ -85,11 +84,11 @@ namespace Svelto.ECS /// Filters were initially designed to be used for tagging operations within submissions of entities. /// They were designed as a fast tagging mechanism to be used within the submission frame. However I then /// extended it, but the extension includes a drawback: - ///If filters are not in sync with the operations of remove and swap, filters may end up pointing to - ///invalid indices. I need to put in place a way to be able to recognised an invalid filter. - ///This is currently a disadvantage of the filters. The filters are not updated by the framework - ///but they must be updated by the user. - ///When to use this method: Add and Removed should be used to add and remove entities in the filters. This is + /// If filters are not in sync with the operations of remove and swap, filters may end up pointing to + /// invalid indices. I need to put in place a way to be able to recognised an invalid filter. + /// This is currently a disadvantage of the filters. The filters are not updated by the framework + /// but they must be updated by the user. + /// When to use this method: Add and Removed should be used to add and remove entities in the filters. This is /// valid as long as no structural changes happen in the group of entities involved. /// IF structural changes happen, the indices stored in the filters won't be valid anymore as they will possibly /// point to entities that were not the original ones. On structural changes @@ -107,11 +106,11 @@ namespace Svelto.ECS _reverseEIDs.Clear(); foreach (var value in _indexOfEntityInDenseList) - if (mapper.FindIndex(value.Key, out var indexOfEntityInBufferComponent) == true) + if (mapper.FindIndex(value.key, out var indexOfEntityInBufferComponent) == true) { _denseListOfIndicesToEntityComponentArray.Add(indexOfEntityInBufferComponent); var lastIndex = (uint) (_denseListOfIndicesToEntityComponentArray.Count() - 1); - _reverseEIDs.AddAt(lastIndex) = value.Key; + _reverseEIDs.AddAt(lastIndex) = value.key; } _indexOfEntityInDenseList.Clear(); diff --git a/com.sebaslab.svelto.ecs/Core/Filters/FilteredIndices.cs b/com.sebaslab.svelto.ecs/Core/Filters/LegacyFilteredIndices.cs similarity index 76% rename from com.sebaslab.svelto.ecs/Core/Filters/FilteredIndices.cs rename to com.sebaslab.svelto.ecs/Core/Filters/LegacyFilteredIndices.cs index 799a8f6..ce3dd62 100644 --- a/com.sebaslab.svelto.ecs/Core/Filters/FilteredIndices.cs +++ b/com.sebaslab.svelto.ecs/Core/Filters/LegacyFilteredIndices.cs @@ -3,16 +3,19 @@ using Svelto.ECS.DataStructures; namespace Svelto.ECS { - public readonly struct FilteredIndices + public readonly struct LegacyFilteredIndices { - public FilteredIndices(NativeDynamicArrayCast denseListOfIndicesToEntityComponentArray) + public LegacyFilteredIndices(NativeDynamicArrayCast denseListOfIndicesToEntityComponentArray) { _denseListOfIndicesToEntityComponentArray = denseListOfIndicesToEntityComponentArray; _count = _denseListOfIndicesToEntityComponentArray.count; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int Count() => _count; + public int count + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get { return _count; } + } [MethodImpl(MethodImplOptions.AggressiveInlining)] public uint Get(uint index) => _denseListOfIndicesToEntityComponentArray[index]; diff --git a/com.sebaslab.svelto.ecs/Core/Filters/LegacyGroupFilters.cs b/com.sebaslab.svelto.ecs/Core/Filters/LegacyGroupFilters.cs new file mode 100644 index 0000000..48dc864 --- /dev/null +++ b/com.sebaslab.svelto.ecs/Core/Filters/LegacyGroupFilters.cs @@ -0,0 +1,104 @@ +using Svelto.DataStructures; +using Svelto.DataStructures.Native; + +namespace Svelto.ECS +{ + public struct LegacyGroupFilters + { + internal LegacyGroupFilters(SharedSveltoDictionaryNative legacyFilters, ExclusiveGroupStruct group) + { + this._legacyFilters = legacyFilters; + _group = @group; + } + + public ref LegacyFilterGroup GetFilter(int filterIndex) + { +#if DEBUG && !PROFILE_SVELTO + if (_legacyFilters.isValid == false) + throw new ECSException($"trying to fetch not existing filters {filterIndex} group {_group.ToName()}"); + if (_legacyFilters.ContainsKey(filterIndex) == false) + throw new ECSException($"trying to fetch not existing filters {filterIndex} group {_group.ToName()}"); +#endif + return ref _legacyFilters.GetValueByRef(filterIndex); + } + + public bool HasFilter(int filterIndex) { return _legacyFilters.ContainsKey(filterIndex); } + + public void ClearFilter(int filterIndex) + { + if (_legacyFilters.TryFindIndex(filterIndex, out var index)) + _legacyFilters.GetValues(out _)[index].Clear(); + } + + public void ClearFilters() + { + foreach (var filter in _legacyFilters) + filter.value.Clear(); + } + + public bool TryGetFilter(int filterIndex, out LegacyFilterGroup legacyFilter) + { + return _legacyFilters.TryGetValue(filterIndex, out legacyFilter); + } + + public SveltoDictionaryKeyValueEnumerator>, NativeStrategy + , NativeStrategy> GetEnumerator() + { + return _legacyFilters.GetEnumerator(); + } + + //Note the following methods are internal because I was pondering the idea to be able to return + //the list of LegacyGroupFilters linked to a specific filter ID. However this would mean to be able to + //maintain a revers map which at this moment seems too much and also would need the following + //method to be for ever internal (at this point in time I am not sure it's a good idea) + internal void DisposeFilter(int filterIndex) + { + if (_legacyFilters.TryFindIndex(filterIndex, out var index)) + { + ref var filterGroup = ref _legacyFilters.GetValues(out _)[index]; + + filterGroup.Dispose(); + + _legacyFilters.Remove(filterIndex); + } + } + + internal void DisposeFilters() + { + //must release the native buffers! + foreach (var filter in _legacyFilters) + filter.value.Dispose(); + + _legacyFilters.FastClear(); + } + + internal ref LegacyFilterGroup CreateOrGetFilter(int filterID) + { + if (_legacyFilters.TryFindIndex(filterID, out var index) == false) + { + var orGetFilterForGroup = new LegacyFilterGroup(_group, filterID); + + _legacyFilters[filterID] = orGetFilterForGroup; + + return ref _legacyFilters.GetValueByRef(filterID); + } + + return ref _legacyFilters.GetValues(out _)[index]; + } + + internal void Dispose() + { + foreach (var filter in _legacyFilters) + { + filter.value.Dispose(); + } + + _legacyFilters.Dispose(); + } + + readonly ExclusiveGroupStruct _group; + + //filterID, filter + SharedSveltoDictionaryNative _legacyFilters; + } +} \ No newline at end of file diff --git a/com.sebaslab.svelto.ecs/Core/Filters/NativeEntityFilterCollection.cs b/com.sebaslab.svelto.ecs/Core/Filters/NativeEntityFilterCollection.cs new file mode 100644 index 0000000..b54ea0f --- /dev/null +++ b/com.sebaslab.svelto.ecs/Core/Filters/NativeEntityFilterCollection.cs @@ -0,0 +1,148 @@ +using System.Runtime.CompilerServices; +using Svelto.DataStructures.Native; +using Svelto.ECS.Native; + +namespace Svelto.ECS +{ + public struct NativeEntityFilterCollection where T : unmanaged, IEntityComponent + { + internal NativeEntityFilterCollection(NativeEGIDMultiMapper mmap) + { + _mmap = mmap; + _filtersPerGroup = new SharedSveltoDictionaryNative(); + } + + public NativeEntityFilterIterator iterator => new NativeEntityFilterIterator(this); + + public void AddEntity(EGID egid) + { + AddEntity(egid, _mmap.GetIndex(egid)); + } + + public void RemoveEntity(EGID egid) + { + _filtersPerGroup[egid.groupID].Remove(egid.entityID); + } + + public void Clear() + { + var filterSets = _filtersPerGroup.GetValues(out var count); + for (var i = 0; i < count; i++) + { + filterSets[i].Clear(); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + void AddEntity(EGID egid, uint toIndex) + { + if (_filtersPerGroup.TryGetValue(egid.groupID, out var groupFilter) == false) + { + groupFilter = new GroupFilters(32, egid.groupID); + _filtersPerGroup[egid.groupID] = groupFilter; + } + + groupFilter.Add(egid.entityID, toIndex); + } + + internal int groupCount => _filtersPerGroup.count; + + internal GroupFilters GetGroup(int indexGroup) + { + DBC.ECS.Check.Require(indexGroup < _filtersPerGroup.count); + return _filtersPerGroup.GetValues(out _)[indexGroup]; + } + + internal void Dispose() + { + var filterSets = _filtersPerGroup.GetValues(out var count); + for (var i = 0; i < count; i++) + { + filterSets[i].Dispose(); + } + } + + readonly NativeEGIDMultiMapper _mmap; + //double check if this needs to be shared + SharedSveltoDictionaryNative _filtersPerGroup; + + internal struct GroupFilters + { + internal GroupFilters(uint size, ExclusiveGroupStruct group) + { + _entityIDToDenseIndex = new SharedSveltoDictionaryNative(size); + _indexToEntityId = new SharedSveltoDictionaryNative(size); + _group = group; + } + + internal void Add(uint entityId, uint entityIndex) + { + _entityIDToDenseIndex.Add(entityId, entityIndex); + _indexToEntityId.Add(entityIndex, entityId); + } + + internal void Remove(uint entityId) + { + _indexToEntityId.Remove(_entityIDToDenseIndex[entityId]); + _entityIDToDenseIndex.Remove(entityId); + } + + internal void RemoveWithSwapBack(uint entityId, uint entityIndex, uint lastIndex) + { + // Check if the last index is part of the filter as an entity, in that case + //we need to update the filter + if (entityIndex != lastIndex && _indexToEntityId.ContainsKey(lastIndex)) + { + uint lastEntityID = _indexToEntityId[lastIndex]; + + _entityIDToDenseIndex[lastEntityID] = entityIndex; + _indexToEntityId[entityIndex] = lastEntityID; + + _indexToEntityId.Remove(lastIndex); + } + else + { + // We don't need to check if the entityIndex is a part of the dictionary. + // The Remove function will check for us. + _indexToEntityId.Remove(entityIndex); + } + + // We don't need to check if the entityID is part of the dictionary. + // The Remove function will check for us. + _entityIDToDenseIndex.Remove(entityId); + } + + internal void Clear() + { + _indexToEntityId.FastClear(); + _entityIDToDenseIndex.FastClear(); + } + + internal bool HasEntity(uint entityId) => _entityIDToDenseIndex.ContainsKey(entityId); + + internal void Dispose() + { + _entityIDToDenseIndex.Dispose(); + _indexToEntityId.Dispose(); + } + + internal EntityFilterIndices indices + { + get + { + var values = _entityIDToDenseIndex.GetValues(out var count); + return new EntityFilterIndices(values, count); + } + } + + internal uint count => (uint)_entityIDToDenseIndex.count; + + internal ExclusiveGroupStruct group => _group; + + //double check if these need to be shared + SharedSveltoDictionaryNative _indexToEntityId; + SharedSveltoDictionaryNative _entityIDToDenseIndex; + readonly ExclusiveGroupStruct _group; + } + } +} \ No newline at end of file diff --git a/com.sebaslab.svelto.ecs/Core/Filters/NativeEntityFilterIterator.cs b/com.sebaslab.svelto.ecs/Core/Filters/NativeEntityFilterIterator.cs new file mode 100644 index 0000000..db9d75a --- /dev/null +++ b/com.sebaslab.svelto.ecs/Core/Filters/NativeEntityFilterIterator.cs @@ -0,0 +1,63 @@ +namespace Svelto.ECS +{ + public readonly ref struct NativeEntityFilterIterator where T : unmanaged, IEntityComponent + { + internal NativeEntityFilterIterator(NativeEntityFilterCollection filter) + { + _filter = filter; + } + + public Iterator GetEnumerator() => new Iterator(_filter); + + readonly NativeEntityFilterCollection _filter; + + public ref struct Iterator + { + internal Iterator(NativeEntityFilterCollection filter) + { + _filter = filter; + _indexGroup = -1; + _current = default; + } + + public bool MoveNext() + { + while (++_indexGroup < _filter.groupCount) + { + _current = _filter.GetGroup(_indexGroup); + + if (_current.count > 0) break; + } + + return _indexGroup < _filter.groupCount; + } + + public void Reset() + { + _indexGroup = -1; + } + + public RefCurrent Current => new RefCurrent(_current); + + int _indexGroup; + readonly NativeEntityFilterCollection _filter; + NativeEntityFilterCollection.GroupFilters _current; + } + + public readonly ref struct RefCurrent + { + internal RefCurrent(NativeEntityFilterCollection.GroupFilters filter) + { + _filter = filter; + } + + public void Deconstruct(out EntityFilterIndices indices, out ExclusiveGroupStruct group) + { + indices = _filter.indices; + group = _filter.group; + } + + readonly NativeEntityFilterCollection.GroupFilters _filter; + } + } +} \ No newline at end of file diff --git a/com.sebaslab.svelto.ecs/Core/GlobalTypeID.cs b/com.sebaslab.svelto.ecs/Core/GlobalTypeID.cs index 426a286..0633a19 100644 --- a/com.sebaslab.svelto.ecs/Core/GlobalTypeID.cs +++ b/com.sebaslab.svelto.ecs/Core/GlobalTypeID.cs @@ -1,6 +1,5 @@ using System.Threading; using Svelto.Common; -using Svelto.DataStructures; using Svelto.ECS.DataStructures; namespace Svelto.ECS @@ -23,7 +22,7 @@ namespace Svelto.ECS { static Filler() { - DBC.ECS.Check.Require(TypeCache.isUnmanaged == true, "invalid type used"); + DBC.ECS.Check.Require(TypeType.isUnmanaged() == true, "invalid type used"); } //it's an internal interface @@ -35,28 +34,20 @@ namespace Svelto.ECS } } +#if UNITY_NATIVE //at the moment I am still considering NativeOperations useful only for Unity static class EntityComponentID { -#if UNITY_NATIVE internal static readonly Unity.Burst.SharedStatic ID = Unity.Burst.SharedStatic.GetOrCreate(); -#else - internal struct SharedStatic - { - public uint Data; - } - - internal static SharedStatic ID; -#endif } static class EntityComponentIDMap { - static readonly FasterList TYPE_IDS; + static readonly Svelto.DataStructures.FasterList TYPE_IDS; static EntityComponentIDMap() { - TYPE_IDS = new FasterList(); + TYPE_IDS = new Svelto.DataStructures.FasterList(); } internal static void Register(IFiller entityBuilder) where T : struct, IEntityComponent @@ -67,4 +58,5 @@ namespace Svelto.ECS internal static IFiller GetTypeFromID(uint typeId) { return TYPE_IDS[typeId]; } } +#endif } \ No newline at end of file diff --git a/com.sebaslab.svelto.ecs/Core/GroupHashMap.cs b/com.sebaslab.svelto.ecs/Core/GroupHashMap.cs index 9297545..a70023b 100644 --- a/com.sebaslab.svelto.ecs/Core/GroupHashMap.cs +++ b/com.sebaslab.svelto.ecs/Core/GroupHashMap.cs @@ -6,12 +6,12 @@ using Svelto.ECS.Serialization; namespace Svelto.ECS { - static class GroupHashMap + public static class GroupHashMap { /// /// c# Static constructors are guaranteed to be thread safe /// - public static void Init() + internal static void Init() { List assemblies = AssemblyUtility.GetCompatibleAssemblies(); foreach (Assembly assembly in assemblies) @@ -20,41 +20,61 @@ namespace Svelto.ECS { var typeOfExclusiveGroup = typeof(ExclusiveGroup); var typeOfExclusiveGroupStruct = typeof(ExclusiveGroupStruct); + var typeOfExclusiveBuildGroup = typeof(ExclusiveBuildGroup); foreach (Type type in AssemblyUtility.GetTypesSafe(assembly)) { - if (type != null && type.IsClass && type.IsSealed - && type.IsAbstract) //this means only static classes + CheckForGroupCompounds(type); + + if (type != null && type.IsClass && type.IsSealed && + type.IsAbstract) //IsClass and IsSealed and IsAbstract means only static classes { + var subClasses = type.GetNestedTypes(); + + foreach (var subclass in subClasses) + { + CheckForGroupCompounds(subclass); + } + var fields = type.GetFields(); foreach (var field in fields) { - if (field.IsStatic) + if (field.IsStatic + && (typeOfExclusiveGroup.IsAssignableFrom(field.FieldType) + || typeOfExclusiveGroupStruct.IsAssignableFrom(field.FieldType) + || typeOfExclusiveBuildGroup.IsAssignableFrom(field.FieldType))) { + uint groupID; + if (typeOfExclusiveGroup.IsAssignableFrom(field.FieldType)) { - var group = (ExclusiveGroup) field.GetValue(null); -#if DEBUG - 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}"); + var group = (ExclusiveGroup)field.GetValue(null); + groupID = ((ExclusiveGroupStruct)@group).id; + } + else + if (typeOfExclusiveGroupStruct.IsAssignableFrom(field.FieldType)) + { + var group = (ExclusiveGroupStruct)field.GetValue(null); + groupID = @group.id; } else - if (typeOfExclusiveGroupStruct.IsAssignableFrom(field.FieldType)) - { - var group = (ExclusiveGroupStruct) field.GetValue(null); -#if DEBUG + { + var group = (ExclusiveBuildGroup)field.GetValue(null); + groupID = ((ExclusiveGroupStruct)@group).id; + } + + { + ExclusiveGroupStruct group = new ExclusiveGroupStruct(groupID); +#if DEBUG && !PROFILE_SVELTO + if (GroupNamesMap.idToName.ContainsKey(@group) == false) GroupNamesMap.idToName[@group] = - $"{$"{type.FullName}.{field.Name}"} {group.id})"; + $"{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}"); + } } } } @@ -69,6 +89,16 @@ namespace Svelto.ECS } } + static void CheckForGroupCompounds(Type type) + { + if (typeof(ITouchedByReflection).IsAssignableFrom(type)) + { + //this wil call 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); + } + } + /// /// 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 @@ -78,7 +108,8 @@ namespace Svelto.ECS /// public static void RegisterGroup(ExclusiveGroupStruct exclusiveGroupStruct, string name) { - //Group already registered by another field referencing the same group + //Group already registered by another field referencing the same group, can happen because + //the group poked is a group compound which static constructor is already been called at this point if (_hashByGroups.ContainsKey(exclusiveGroupStruct)) return; @@ -93,9 +124,9 @@ namespace Svelto.ECS _hashByGroups.Add(exclusiveGroupStruct, nameHash); } - public static uint GetHashFromGroup(ExclusiveGroupStruct groupStruct) + internal static uint GetHashFromGroup(ExclusiveGroupStruct groupStruct) { -#if DEBUG +#if DEBUG && !PROFILE_SVELTO if (_hashByGroups.ContainsKey(groupStruct) == false) throw new ECSException($"Attempted to get hash from unregistered group {groupStruct}"); #endif @@ -103,9 +134,9 @@ namespace Svelto.ECS return _hashByGroups[groupStruct]; } - public static ExclusiveGroupStruct GetGroupFromHash(uint groupHash) + internal static ExclusiveGroupStruct GetGroupFromHash(uint groupHash) { -#if DEBUG +#if DEBUG && !PROFILE_SVELTO if (_groupsByHash.ContainsKey(groupHash) == false) throw new ECSException($"Attempted to get group from unregistered hash {groupHash}"); #endif diff --git a/com.sebaslab.svelto.ecs/Core/GroupNamesMap.cs b/com.sebaslab.svelto.ecs/Core/GroupNamesMap.cs index 5df712f..c0bc06c 100644 --- a/com.sebaslab.svelto.ecs/Core/GroupNamesMap.cs +++ b/com.sebaslab.svelto.ecs/Core/GroupNamesMap.cs @@ -3,12 +3,12 @@ using Svelto.ECS; static class GroupNamesMap { -#if DEBUG +#if DEBUG && !PROFILE_SVELTO static GroupNamesMap() { idToName = new Dictionary(); } internal static readonly Dictionary idToName; #endif -#if DEBUG +#if DEBUG && !PROFILE_SVELTO public static string ToName(this in ExclusiveGroupStruct group) { Dictionary idToName = GroupNamesMap.idToName; diff --git a/com.sebaslab.svelto.ecs/Core/Groups/ExclusiveBuildGroup.cs b/com.sebaslab.svelto.ecs/Core/Groups/ExclusiveBuildGroup.cs index e2e395d..bbda837 100644 --- a/com.sebaslab.svelto.ecs/Core/Groups/ExclusiveBuildGroup.cs +++ b/com.sebaslab.svelto.ecs/Core/Groups/ExclusiveBuildGroup.cs @@ -16,7 +16,7 @@ namespace Svelto.ECS { return new ExclusiveBuildGroup(group); } - + public static implicit operator ExclusiveGroupStruct(ExclusiveBuildGroup group) { return group.group; diff --git a/com.sebaslab.svelto.ecs/Core/Groups/ExclusiveGroup.cs b/com.sebaslab.svelto.ecs/Core/Groups/ExclusiveGroup.cs index 2bf6676..a5287a0 100644 --- a/com.sebaslab.svelto.ecs/Core/Groups/ExclusiveGroup.cs +++ b/com.sebaslab.svelto.ecs/Core/Groups/ExclusiveGroup.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Runtime.CompilerServices; #pragma warning disable 660,661 @@ -43,7 +42,7 @@ namespace Svelto.ECS public ExclusiveGroup(ushort range) { _group = ExclusiveGroupStruct.GenerateWithRange(range); -#if DEBUG +#if DEBUG && !PROFILE_SVELTO _range = range; #endif } @@ -55,7 +54,7 @@ namespace Svelto.ECS public static ExclusiveGroupStruct operator+(ExclusiveGroup @group, uint b) { -#if DEBUG +#if DEBUG && !PROFILE_SVELTO if (@group._range == 0) throw new ECSException($"Adding values to a not ranged ExclusiveGroup: {@group.id}"); if (b >= @group._range) @@ -83,7 +82,7 @@ namespace Svelto.ECS static readonly Dictionary _knownGroups = new Dictionary(); -#if DEBUG +#if DEBUG && !PROFILE_SVELTO readonly ushort _range; #endif readonly ExclusiveGroupStruct _group; diff --git a/com.sebaslab.svelto.ecs/Core/Groups/ExclusiveGroupStruct.cs b/com.sebaslab.svelto.ecs/Core/Groups/ExclusiveGroupStruct.cs index bee1781..c5ef40c 100644 --- a/com.sebaslab.svelto.ecs/Core/Groups/ExclusiveGroupStruct.cs +++ b/com.sebaslab.svelto.ecs/Core/Groups/ExclusiveGroupStruct.cs @@ -1,10 +1,12 @@ using System; +using System.Diagnostics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Threading; namespace Svelto.ECS { + [DebuggerDisplay("{ToString()}")] [StructLayout(LayoutKind.Explicit, Size = 4)] //the type doesn't implement IEqualityComparer, what implements it is a custom comparer public readonly struct ExclusiveGroupStruct : IEquatable, IComparable diff --git a/com.sebaslab.svelto.ecs/Core/Groups/GroupCompound.cs b/com.sebaslab.svelto.ecs/Core/Groups/GroupCompound.cs index 08940bf..7595819 100644 --- a/com.sebaslab.svelto.ecs/Core/Groups/GroupCompound.cs +++ b/com.sebaslab.svelto.ecs/Core/Groups/GroupCompound.cs @@ -1,4 +1,3 @@ -using System; using System.Collections.Generic; using System.Threading; using Svelto.DataStructures; @@ -18,25 +17,31 @@ namespace Svelto.ECS internal static readonly ThreadLocal skipStaticCompoundConstructorsWith2Tags = new ThreadLocal(); } - public abstract class GroupCompound where G1 : GroupTag - where G2 : GroupTag - where G3 : GroupTag - where G4 : GroupTag + interface ITouchedByReflection + { + } + + public abstract class GroupCompound : ITouchedByReflection where G1 : GroupTag + where G2 : GroupTag + where G3 : GroupTag + where G4 : GroupTag { static GroupCompound() { //avoid race conditions if compounds are using on multiple thread - if (Interlocked.CompareExchange(ref isInitializing, 1, 0) == 0 - && GroupCompoundInitializer.skipStaticCompoundConstructorsWith4Tags.Value == false) + if (Interlocked.CompareExchange(ref isInitializing, 1, 0) == 0 && + GroupCompoundInitializer.skipStaticCompoundConstructorsWith4Tags.Value == false) { - var group = new ExclusiveGroup(); //todo: it's a bit of a waste to create a class here even if this is a static constructor + var + group = + new ExclusiveGroup(); //todo: it's a bit of a waste to create a class here even if this is a static constructor _Groups = new FasterList(1); _Groups.Add(group); - -#if DEBUG + +#if DEBUG && !PROFILE_SVELTO var name = - $"Compound: {typeof(G1).Name}-{typeof(G2).Name}-{typeof(G3).Name}-{typeof(G4).Name} ID {(uint) group.id}"; + $"Compound: {typeof(G1).Name}-{typeof(G2).Name}-{typeof(G3).Name}-{typeof(G4).Name} ID {(uint)group.id}"; GroupNamesMap.idToName[group] = name; #endif //The hashname is independent from the actual group ID. this is fundamental because it is want @@ -133,16 +138,16 @@ namespace Svelto.ECS internal static void Add(ExclusiveGroupStruct group) { -#if DEBUG && !PROFILE_SVELTO +#if DEBUG && !PROFILE_SVELTO for (var i = 0; i < _Groups.count; ++i) if (_Groups[i] == group) - throw new Exception("this test must be transformed in unit test"); + throw new System.Exception("this test must be transformed in unit test"); #endif _Groups.Add(group); _GroupsHashSet.Add(group); } - + static readonly FasterList _Groups; static readonly HashSet _GroupsHashSet; @@ -150,21 +155,24 @@ namespace Svelto.ECS static int isInitializing; } - public abstract class GroupCompound - where G1 : GroupTag where G2 : GroupTag where G3 : GroupTag + public abstract class GroupCompound : ITouchedByReflection where G1 : GroupTag + where G2 : GroupTag + where G3 : GroupTag { static GroupCompound() { - if (Interlocked.CompareExchange(ref isInitializing, 1, 0) == 0 - && GroupCompoundInitializer.skipStaticCompoundConstructorsWith3Tags.Value == false) + if (Interlocked.CompareExchange(ref isInitializing, 1, 0) == 0 && + GroupCompoundInitializer.skipStaticCompoundConstructorsWith3Tags.Value == false) { - var group = new ExclusiveGroup(); //todo: it's a bit of a waste to create a class here even if this is a static constructor + var + group = + new ExclusiveGroup(); //todo: it's a bit of a waste to create a class here even if this is a static constructor _Groups = new FasterList(1); _Groups.Add(group); -#if DEBUG - var name = $"Compound: {typeof(G1).Name}-{typeof(G2).Name}-{typeof(G3).Name} ID {(uint) group.id}"; +#if DEBUG && !PROFILE_SVELTO + var name = $"Compound: {typeof(G1).Name}-{typeof(G2).Name}-{typeof(G3).Name} ID {(uint)group.id}"; GroupNamesMap.idToName[group] = name; #endif //The hashname is independent from the actual group ID. this is fundamental because it is want @@ -215,16 +223,16 @@ namespace Svelto.ECS internal static void Add(ExclusiveGroupStruct group) { -#if DEBUG && !PROFILE_SVELTO +#if DEBUG && !PROFILE_SVELTO for (var i = 0; i < _Groups.count; ++i) if (_Groups[i] == group) - throw new Exception("this test must be transformed in unit test"); + throw new System.Exception("this test must be transformed in unit test"); #endif _Groups.Add(group); _GroupsHashSet.Add(group); } - + static readonly FasterList _Groups; static readonly HashSet _GroupsHashSet; @@ -232,19 +240,21 @@ namespace Svelto.ECS static int isInitializing; } - public abstract class GroupCompound where G1 : GroupTag where G2 : GroupTag + public abstract class GroupCompound : ITouchedByReflection where G1 : GroupTag where G2 : GroupTag { static GroupCompound() { - if (Interlocked.CompareExchange(ref isInitializing, 1, 0) == 0 - && GroupCompoundInitializer.skipStaticCompoundConstructorsWith2Tags.Value == false) + if (Interlocked.CompareExchange(ref isInitializing, 1, 0) == 0 && + GroupCompoundInitializer.skipStaticCompoundConstructorsWith2Tags.Value == false) { - var group = new ExclusiveGroup(); //todo: it's a bit of a waste to create a class here even if this is a static constructor + var + group = + new ExclusiveGroup(); //todo: it's a bit of a waste to create a class here even if this is a static constructor _Groups = new FasterList(1); _Groups.Add(group); -#if DEBUG +#if DEBUG && !PROFILE_SVELTO GroupNamesMap.idToName[group] = $"Compound: {typeof(G1).Name}-{typeof(G2).Name} ID {group.id}"; #endif //The hashname is independent from the actual group ID. this is fundamental because it is want @@ -276,16 +286,16 @@ namespace Svelto.ECS internal static void Add(ExclusiveGroupStruct group) { -#if DEBUG && !PROFILE_SVELTO +#if DEBUG && !PROFILE_SVELTO for (var i = 0; i < _Groups.count; ++i) if (_Groups[i] == group) - throw new Exception("this test must be transformed in unit test"); -#endif + throw new System.Exception("this test must be transformed in unit test"); +#endif _Groups.Add(group); _GroupsHashSet.Add(group); } - + static readonly FasterList _Groups; static readonly HashSet _GroupsHashSet; @@ -299,7 +309,7 @@ namespace Svelto.ECS /// groups with the same adjective, a group tag needs to hold all the groups sharing it. /// /// - public abstract class GroupTag where T : GroupTag + public abstract class GroupTag : ITouchedByReflection where T : GroupTag { static GroupTag() { @@ -308,12 +318,13 @@ namespace Svelto.ECS var group = new ExclusiveGroup(); _Groups.Add(group); -#if DEBUG - var typeInfo = typeof(T); - var name = $"Compound: {typeInfo.Name} ID {(uint) group.id}"; - +#if DEBUG && !PROFILE_SVELTO + var typeInfo = typeof(T); + var name = $"Compound: {typeInfo.Name} ID {(uint)group.id}"; + 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 + 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 throw new ECSException("Invalid Group Tag declared"); GroupNamesMap.idToName[group] = name; @@ -339,16 +350,16 @@ namespace Svelto.ECS //Each time a new combination of group tags is found a new group is added. internal static void Add(ExclusiveGroupStruct group) { -#if DEBUG && !PROFILE_SVELTO +#if DEBUG && !PROFILE_SVELTO for (var i = 0; i < _Groups.count; ++i) if (_Groups[i] == group) - throw new Exception("this test must be transformed in unit test"); + throw new System.Exception("this test must be transformed in unit test"); #endif _Groups.Add(group); _GroupsHashSet.Add(group); } - + static readonly FasterList _Groups = new FasterList(1); static readonly HashSet _GroupsHashSet; diff --git a/com.sebaslab.svelto.ecs/Core/Groups/NamedExclusiveGroup.cs b/com.sebaslab.svelto.ecs/Core/Groups/NamedExclusiveGroup.cs index 9d7b0ad..7c31ce7 100644 --- a/com.sebaslab.svelto.ecs/Core/Groups/NamedExclusiveGroup.cs +++ b/com.sebaslab.svelto.ecs/Core/Groups/NamedExclusiveGroup.cs @@ -13,7 +13,7 @@ namespace Svelto.ECS static NamedExclusiveGroup() { -#if DEBUG +#if DEBUG && !PROFILE_SVELTO GroupNamesMap.idToName[Group] = $"{name} ID {Group.id}"; #endif //The hashname is independent from the actual group ID. this is fundamental because it is want diff --git a/com.sebaslab.svelto.ecs/Core/Hybrid/IEntityViewComponent.cs b/com.sebaslab.svelto.ecs/Core/Hybrid/IEntityViewComponent.cs index 7bee311..10c248b 100644 --- a/com.sebaslab.svelto.ecs/Core/Hybrid/IEntityViewComponent.cs +++ b/com.sebaslab.svelto.ecs/Core/Hybrid/IEntityViewComponent.cs @@ -3,7 +3,10 @@ namespace Svelto.ECS.Hybrid public interface IManagedComponent:IEntityComponent {} - public interface IEntityViewComponent:IManagedComponent, INeedEGID + public interface IEntityViewComponent:IManagedComponent +#if SLOW_SVELTO_SUBMISSION + ,INeedEGID +#endif {} } diff --git a/com.sebaslab.svelto.ecs/Core/Hybrid/ValueReference.cs b/com.sebaslab.svelto.ecs/Core/Hybrid/ValueReference.cs index c11990f..fcc3475 100644 --- a/com.sebaslab.svelto.ecs/Core/Hybrid/ValueReference.cs +++ b/com.sebaslab.svelto.ecs/Core/Hybrid/ValueReference.cs @@ -1,37 +1,29 @@ -using System; using System.Runtime.InteropServices; namespace Svelto.ECS.Hybrid { /// - /// an ECS dirty trick to hold the reference to an object. this is component can be used in an engine - /// managing an OOP abstraction layer. It's need is quite rare though! An example is found at - /// https://github.com/sebas77/Svelto.MiniExamples/blob/master/Example6-Unity%20Hybrid-OOP%20Abstraction/Assets/Code/WithEntityViewComponent/Descriptors/TransformImplementor.cs - /// All other uses must be considered an abuse - /// as the object can be casted back to it's real type only by an OOP Abstraction Layer Engine: - /// https://www.sebaslab.com/oop-abstraction-layer-in-a-ecs-centric-application/ + /// ValueReference is the only way to store a reference inside an Implementor. To stop any abuse + /// the reference must be an implementor and converted back to an implementor. + /// The OOP abstraction layer that knows about the implementor than can cast it to the real type /// /// - public struct ValueReference : IDisposable where T:class, IImplementor + public struct ValueReference : IValueReferenceInternal where T:class { - static ValueReference() - { - DBC.ECS.Check.Require(typeof(T).IsInterface == true, "ValueReference type can be only pure interface implementing IImplementor"); - } - public ValueReference(T obj) { _pointer = GCHandle.Alloc(obj, GCHandleType.Normal); } - public W Convert(W implementer) where W:T + public T ConvertAndDispose(W implementer) where W:IImplementor { var pointerTarget = _pointer.Target; - return (W)pointerTarget; - } - - public void Dispose() - { _pointer.Free(); + return (T)pointerTarget; } + + public bool isDefault => _pointer.IsAllocated == false; GCHandle _pointer; } + + // Used to validate the use of this struct on the component builder check fields. + internal interface IValueReferenceInternal {} } \ No newline at end of file diff --git a/com.sebaslab.svelto.ecs/Core/IEngine.cs b/com.sebaslab.svelto.ecs/Core/IEngine.cs index ec87289..ca28804 100644 --- a/com.sebaslab.svelto.ecs/Core/IEngine.cs +++ b/com.sebaslab.svelto.ecs/Core/IEngine.cs @@ -2,41 +2,128 @@ using Svelto.ECS.Internal; namespace Svelto.ECS.Internal { - public interface IReactEngine: IEngine - {} + public interface IReactEngine : IEngine + { + } +#region legacy interfaces + /// + /// This is now considered legacy and it will be deprecated in future + /// + public interface IReactOnAdd : IReactEngine + { + } - public interface IReactOnAddAndRemove : IReactEngine - {} + /// + /// This is now considered legacy and it will be deprecated in future + /// + public interface IReactOnRemove : IReactEngine + { + } - public interface IReactOnDispose : IReactEngine - {} - + /// + /// This is now considered legacy and it will be deprecated in future + /// public interface IReactOnSwap : IReactEngine - {} + { + } +#endregion + + public interface IReactOnAddEx : IReactEngine + { + } + + public interface IReactOnRemoveEx : IReactEngine + { + } + + public interface IReactOnSwapEx : IReactEngine + { + } + + public interface IReactOnDispose : IReactEngine + { + } } namespace Svelto.ECS { public interface IEngine - {} - - public interface IReactOnAddAndRemove : IReactOnAddAndRemove where T : IEntityComponent + { + } + + public interface IGetReadyEngine : IEngine + { + void Ready(); + } + + public interface IQueryingEntitiesEngine : IGetReadyEngine + { + EntitiesDB entitiesDB { set; } + } + + /// + /// Interface to mark an Engine as reacting on entities added + /// + /// + public interface IReactOnAdd : IReactOnAdd where T : IEntityComponent { void Add(ref T entityComponent, EGID egid); + } + + public interface IReactOnAddEx : IReactOnAddEx where T : struct, IEntityComponent + { + void Add((uint start, uint end) rangeOfEntities, in EntityCollection collection, + ExclusiveGroupStruct groupID); + } + + /// + /// Interface to mark an Engine as reacting on entities removed + /// + /// + public interface IReactOnRemove : IReactOnRemove where T : IEntityComponent + { void Remove(ref T entityComponent, EGID egid); } - + + public interface IReactOnRemoveEx : IReactOnRemoveEx where T : struct, IEntityComponent + { + void Remove((uint start, uint end) rangeOfEntities, in EntityCollection collection, + ExclusiveGroupStruct groupID); + } + + public interface IReactOnAddAndRemove : IReactOnAdd, IReactOnRemove where T : IEntityComponent + { + } + + /// + /// Interface to mark an Engine as reacting on engines root disposed. + /// It can work together with IReactOnRemove which normally is not called on enginesroot disposed + /// + /// public interface IReactOnDispose : IReactOnDispose where T : IEntityComponent { void Remove(ref T entityComponent, EGID egid); } - + + /// + /// Interface to mark an Engine as reacting to entities swapping group + /// + /// public interface IReactOnSwap : IReactOnSwap where T : IEntityComponent { void MovedTo(ref T entityComponent, ExclusiveGroupStruct previousGroup, EGID egid); } - public interface IReactOnSubmission:IReactEngine + public interface IReactOnSwapEx : IReactOnSwapEx where T : struct, IEntityComponent + { + void MovedTo((uint start, uint end) rangeOfEntities, in EntityCollection collection, + ExclusiveGroupStruct fromGroup, ExclusiveGroupStruct toGroup); + } + + /// + /// Interface to mark an Engine as reacting after each entities submission phase + /// + public interface IReactOnSubmission : IReactEngine { void EntitiesSubmitted(); } diff --git a/com.sebaslab.svelto.ecs/Core/IEntityFactory.cs b/com.sebaslab.svelto.ecs/Core/IEntityFactory.cs index d5ba023..276c707 100644 --- a/com.sebaslab.svelto.ecs/Core/IEntityFactory.cs +++ b/com.sebaslab.svelto.ecs/Core/IEntityFactory.cs @@ -39,25 +39,27 @@ namespace Svelto.ECS /// /// EntityInitializer BuildEntity(uint entityID, ExclusiveBuildGroup groupStructId, - IEnumerable implementors = null) - where T : IEntityDescriptor, new(); + IEnumerable implementors = null, + [System.Runtime.CompilerServices.CallerMemberName] string caller = null) where T : IEntityDescriptor, new(); - EntityInitializer BuildEntity(EGID egid, IEnumerable implementors = null) - where T : IEntityDescriptor, new(); + EntityInitializer BuildEntity(EGID egid, IEnumerable implementors = null, + [System.Runtime.CompilerServices.CallerMemberName] string caller = null) where T : IEntityDescriptor, new(); - EntityInitializer BuildEntity(uint entityID, ExclusiveBuildGroup groupStructId, - T descriptorEntity, IEnumerable implementors = null) - where T : IEntityDescriptor; + EntityInitializer BuildEntity(uint entityID, ExclusiveBuildGroup groupStructId, T descriptorEntity, + IEnumerable implementors = null, + [System.Runtime.CompilerServices.CallerMemberName] string caller = null) where T : IEntityDescriptor; - EntityInitializer BuildEntity(EGID egid, T entityDescriptor, IEnumerable implementors = null) - where T : IEntityDescriptor; + EntityInitializer BuildEntity(EGID egid, T entityDescriptor, IEnumerable implementors = null, + [System.Runtime.CompilerServices.CallerMemberName] string caller = null) where T : IEntityDescriptor; //Todo: analyze if this can be internal or just related to serialization - EntityInitializer BuildEntity - (EGID egid, IComponentBuilder[] componentsToBuild, Type descriptorType, IEnumerable implementors = null); + EntityInitializer BuildEntity(EGID egid, IComponentBuilder[] componentsToBuild, Type descriptorType, + IEnumerable implementors = null, + [System.Runtime.CompilerServices.CallerMemberName] string caller = null); #if UNITY_NATIVE - Svelto.ECS.Native.NativeEntityFactory ToNative(string callerName) where T : IEntityDescriptor, new(); -#endif + Svelto.ECS.Native.NativeEntityFactory ToNative([System.Runtime.CompilerServices.CallerMemberName] string callerName + = null) where T : IEntityDescriptor, new(); +#endif } } \ No newline at end of file diff --git a/com.sebaslab.svelto.ecs/Core/IEntityFunctions.cs b/com.sebaslab.svelto.ecs/Core/IEntityFunctions.cs index b3f6312..f762fda 100644 --- a/com.sebaslab.svelto.ecs/Core/IEntityFunctions.cs +++ b/com.sebaslab.svelto.ecs/Core/IEntityFunctions.cs @@ -1,3 +1,5 @@ +using System.Runtime.CompilerServices; + namespace Svelto.ECS { public interface IEntityFunctions @@ -5,28 +7,19 @@ namespace Svelto.ECS //being entity ID globally not unique, the group must be specified when //an entity is removed. Not specifying the group will attempt to remove //the entity from the special standard group. - void RemoveEntity(uint entityID, ExclusiveBuildGroup groupID) where T : IEntityDescriptor, new(); - void RemoveEntity(EGID entityegid) where T : IEntityDescriptor, new(); + 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(); - void RemoveEntitiesFromGroup(ExclusiveBuildGroup groupID); - - void SwapEntitiesInGroup(ExclusiveBuildGroup fromGroupID, ExclusiveBuildGroup toGroupID) where T : IEntityDescriptor, new(); - - void SwapEntityGroup(uint entityID, ExclusiveBuildGroup fromGroupID, ExclusiveBuildGroup toGroupID) - where T : IEntityDescriptor, new(); - - void SwapEntityGroup(EGID fromID, ExclusiveBuildGroup toGroupID) where T : IEntityDescriptor, new(); - - void SwapEntityGroup(EGID fromID, ExclusiveBuildGroup fromGroup, ExclusiveBuildGroup toGroupID) - where T : IEntityDescriptor, new(); - - void SwapEntityGroup(EGID fromID, EGID toId) where T : IEntityDescriptor, new(); + void RemoveEntitiesFromGroup(ExclusiveBuildGroup groupID , [CallerMemberName] string caller = null); + void SwapEntitiesInGroup(ExclusiveBuildGroup fromGroupID, ExclusiveBuildGroup toGroupID, [CallerMemberName] string caller = null); - void SwapEntityGroup(EGID fromID, EGID toId, ExclusiveBuildGroup mustBeFromGroup) - where T : IEntityDescriptor, new(); + 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(); + 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/Core/INeedEGID.cs b/com.sebaslab.svelto.ecs/Core/INeedEGID.cs index 9cd3176..bb668a4 100644 --- a/com.sebaslab.svelto.ecs/Core/INeedEGID.cs +++ b/com.sebaslab.svelto.ecs/Core/INeedEGID.cs @@ -1,13 +1,16 @@ +#if SLOW_SVELTO_SUBMISSION namespace Svelto.ECS { /// /// use INeedEGID on an IEntityComponent only if you need the EGID. consider using EGIDComponent instead /// INeedEGID and EGIDComponent will become probably obsolete once QueryEntities will be able to return /// also the EGIDs to iterate upon + /// This is set to become obsolete at a given point /// public interface INeedEGID { //The set is used only by the framework, but it must stay there EGID ID { get; set; } } -} \ No newline at end of file +} +#endif \ No newline at end of file diff --git a/com.sebaslab.svelto.ecs/Core/INeedEntityReference.cs b/com.sebaslab.svelto.ecs/Core/INeedEntityReference.cs index f672b8d..02af8e5 100644 --- a/com.sebaslab.svelto.ecs/Core/INeedEntityReference.cs +++ b/com.sebaslab.svelto.ecs/Core/INeedEntityReference.cs @@ -1,5 +1,4 @@ -using Svelto.ECS.Reference; - +#if SLOW_SVELTO_SUBMISSION namespace Svelto.ECS { /// @@ -7,9 +6,11 @@ namespace Svelto.ECS /// It currently exist because of the publisher/consumer behavior, but the publisher/consumer must not be /// considered an ECS pattern. /// Other uses are invalid. + /// It will become obsolete over the time /// public interface INeedEntityReference { EntityReference selfReference { get; set; } } -} \ No newline at end of file +} +#endif \ No newline at end of file diff --git a/com.sebaslab.svelto.ecs/Core/IQueryingEntitiesEngine.cs b/com.sebaslab.svelto.ecs/Core/IQueryingEntitiesEngine.cs deleted file mode 100644 index 326e0a0..0000000 --- a/com.sebaslab.svelto.ecs/Core/IQueryingEntitiesEngine.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Svelto.ECS -{ - public interface IQueryingEntitiesEngine : IEngine - { - EntitiesDB entitiesDB { set; } - - void Ready(); - } -} \ No newline at end of file diff --git a/com.sebaslab.svelto.ecs/Core/QueryGroups.cs b/com.sebaslab.svelto.ecs/Core/QueryGroups.cs index a40a12a..98654c6 100644 --- a/com.sebaslab.svelto.ecs/Core/QueryGroups.cs +++ b/com.sebaslab.svelto.ecs/Core/QueryGroups.cs @@ -5,72 +5,85 @@ using Svelto.DataStructures; namespace Svelto.ECS.Experimental { - struct GroupsList + internal struct GroupsList { - static GroupsList() + public static GroupsList Init() { - groups = new FasterList(); - sets = new HashSet(); - } + var group = new GroupsList(); + + group._groups = new FasterList(); + group._sets = new HashSet(); - static readonly FasterList groups; - static readonly HashSet sets; + return group; + } - public void Reset() { sets.Clear(); } + public void Reset() + { + _sets.Clear(); + } public void AddRange(ExclusiveGroupStruct[] groupsToAdd, int length) { - for (int i = 0; i < length; i++) - { - sets.Add(groupsToAdd[i]); - } + for (var i = 0; i < length; i++) _sets.Add(groupsToAdd[i]); } - public void Add(ExclusiveGroupStruct @group) { sets.Add(group); } + public void Add(ExclusiveGroupStruct group) + { + _sets.Add(group); + } public void Exclude(ExclusiveGroupStruct[] groupsToIgnore, int length) { - for (int i = 0; i < length; i++) - { - sets.Remove(groupsToIgnore[i]); - } + for (var i = 0; i < length; i++) _sets.Remove(groupsToIgnore[i]); } - public void Exclude(ExclusiveGroupStruct groupsToIgnore) { sets.Remove(groupsToIgnore); } + public void Exclude(ExclusiveGroupStruct groupsToIgnore) + { + _sets.Remove(groupsToIgnore); + } - public void EnsureCapacity(uint preparecount) { groups.EnsureCapacity(preparecount); } + public void Resize(uint preparecount) + { + _groups.Resize(preparecount); + } public FasterList Evaluate() { - groups.FastClear(); + _groups.FastClear(); - foreach (var item in sets) - { - groups.Add(item); - } + foreach (var item in _sets) _groups.Add(item); - return groups; + return _groups; } + + FasterList _groups; + 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 = new ThreadLocal(); + static readonly ThreadLocal groups; + + static QueryGroups() + { + groups = new ThreadLocal(GroupsList.Init); + } public QueryGroups(LocalFasterReadOnlyList groups) { var groupsValue = QueryGroups.groups.Value; groupsValue.Reset(); - groupsValue.AddRange(groups.ToArrayFast(out var count), count); + groupsValue.AddRange(groups.ToArrayFast(out var count), count); } - public QueryGroups(ExclusiveGroupStruct @group) + public QueryGroups(ExclusiveGroupStruct group) { var groupsValue = groups.Value; groupsValue.Reset(); - groupsValue.Add(@group); + groupsValue.Add(group); } public QueryGroups(uint preparecount) @@ -78,7 +91,7 @@ namespace Svelto.ECS.Experimental var groupsValue = groups.Value; groupsValue.Reset(); - groupsValue.EnsureCapacity(preparecount); + groupsValue.Resize(preparecount); } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -114,7 +127,7 @@ namespace Svelto.ECS.Experimental [MethodImpl(MethodImplOptions.AggressiveInlining)] public QueryGroups Except(ExclusiveGroupStruct[] groupsToIgnore) { - var groupsValue = QueryGroups.groups.Value; + var groupsValue = groups.Value; groupsValue.Exclude(groupsToIgnore, groupsToIgnore.Length); @@ -124,7 +137,7 @@ namespace Svelto.ECS.Experimental [MethodImpl(MethodImplOptions.AggressiveInlining)] public QueryGroups Except(LocalFasterReadOnlyList groupsToIgnore) { - var groupsValue = QueryGroups.groups.Value; + var groupsValue = groups.Value; groupsValue.Exclude(groupsToIgnore.ToArrayFast(out var count), count); @@ -134,7 +147,7 @@ namespace Svelto.ECS.Experimental [MethodImpl(MethodImplOptions.AggressiveInlining)] public QueryGroups Except(FasterList groupsToIgnore) { - var groupsValue = QueryGroups.groups.Value; + var groupsValue = groups.Value; groupsValue.Exclude(groupsToIgnore.ToArrayFast(out var count), count); @@ -144,7 +157,7 @@ namespace Svelto.ECS.Experimental [MethodImpl(MethodImplOptions.AggressiveInlining)] public QueryGroups Except(FasterReadOnlyList groupsToIgnore) { - var groupsValue = QueryGroups.groups.Value; + var groupsValue = groups.Value; groupsValue.Exclude(groupsToIgnore.ToArrayFast(out var count), count); @@ -176,29 +189,49 @@ namespace Svelto.ECS.Experimental return new QueryResult(groupsValue.Evaluate()); } + + public void Evaluate(FasterList group) + { + var groupsValue = groups.Value; + + groupsValue.Evaluate().CopyTo(group.ToArrayFast(out var count), count); + } } public readonly ref struct QueryResult { - public QueryResult(FasterList @group) { _group = @group; } + public QueryResult(FasterList group) + { + _group = group; + } public LocalFasterReadOnlyList result => _group; readonly FasterReadOnlyList _group; [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int Count(EntitiesDB entitiesDB) - where T : struct, IEntityComponent + public int Count(EntitiesDB entitiesDB) where T : struct, IEntityComponent + { + var count = 0; + + var groupsCount = result.count; + for (var i = 0; i < groupsCount; ++i) count += entitiesDB.Count(result[i]); + + return count; + } + + public int Max(EntitiesDB entitiesDB) where T : struct, IEntityComponent { - int count = 0; + var max = 0; - var groupsCount = result.count; - for (int i = 0; i < groupsCount; ++i) + var groupsCount = result.count; + for (var i = 0; i < groupsCount; ++i) { - count += entitiesDB.Count(result[i]); + var count = entitiesDB.Count(result[i]); + if (count > max) max = count; } - return count; + return max; } } } \ No newline at end of file diff --git a/com.sebaslab.svelto.ecs/Core/ReactEngineContainer.cs b/com.sebaslab.svelto.ecs/Core/ReactEngineContainer.cs index cf43856..2030e4f 100644 --- a/com.sebaslab.svelto.ecs/Core/ReactEngineContainer.cs +++ b/com.sebaslab.svelto.ecs/Core/ReactEngineContainer.cs @@ -2,12 +2,12 @@ using Svelto.ECS.Internal; namespace Svelto.ECS { - public readonly struct ReactEngineContainer + public readonly struct ReactEngineContainer where T:IReactEngine { public readonly string name; - public readonly IReactEngine engine; + public readonly T engine; - public ReactEngineContainer(IReactEngine engine, string name) + public ReactEngineContainer(T engine, string name) { this.name = name; this.engine = engine; diff --git a/com.sebaslab.svelto.ecs/Core/SetEGIDWithoutBoxing.cs b/com.sebaslab.svelto.ecs/Core/SetEGIDWithoutBoxing.cs index 87c8def..34e7607 100644 --- a/com.sebaslab.svelto.ecs/Core/SetEGIDWithoutBoxing.cs +++ b/com.sebaslab.svelto.ecs/Core/SetEGIDWithoutBoxing.cs @@ -1,5 +1,4 @@ -using Svelto.ECS.Reference; - +#if SLOW_SVELTO_SUBMISSION namespace Svelto.ECS.Internal { delegate void SetEGIDWithoutBoxingActionCast(ref T target, EGID egid) where T : struct, IEntityComponent; @@ -67,4 +66,5 @@ namespace Svelto.ECS.Internal } } } -} \ No newline at end of file +} +#endif \ No newline at end of file diff --git a/com.sebaslab.svelto.ecs/Core/SimpleEntitiesSubmissionScheduler.cs b/com.sebaslab.svelto.ecs/Core/SimpleEntitiesSubmissionScheduler.cs index 684136c..8388622 100644 --- a/com.sebaslab.svelto.ecs/Core/SimpleEntitiesSubmissionScheduler.cs +++ b/com.sebaslab.svelto.ecs/Core/SimpleEntitiesSubmissionScheduler.cs @@ -1,60 +1,33 @@ -using System; -using System.Collections.Generic; - -namespace Svelto.ECS.Schedulers +namespace Svelto.ECS.Schedulers { public sealed class SimpleEntitiesSubmissionScheduler : EntitiesSubmissionScheduler { - public SimpleEntitiesSubmissionScheduler(uint maxNumberOfOperationsPerFrame = UInt32.MaxValue) - { - _enumerator = SubmitEntitiesAsync(maxNumberOfOperationsPerFrame); - } - - public IEnumerator SubmitEntitiesAsync() - { - return _enumerator; - } - - public IEnumerator SubmitEntitiesAsync(uint maxNumberOfOperations) + protected internal override EnginesRoot.EntitiesSubmitter onTick { - EnginesRoot.EntitiesSubmitter entitiesSubmitter = _onTick.Value; - entitiesSubmitter.maxNumberOfOperationsPerFrame = maxNumberOfOperations; - - while (true) + set { - if (paused == false) - { - var entitiesSubmitterSubmitEntities = entitiesSubmitter.submitEntities; - - entitiesSubmitterSubmitEntities.MoveNext(); - - yield return entitiesSubmitterSubmitEntities.Current == true; - } - } - } + DBC.ECS.Check.Require(_entitiesSubmitter == null, "a scheduler can be exclusively used by one enginesRoot only"); - public void SubmitEntities() - { - do - { - _enumerator.MoveNext(); - } while (_enumerator.Current == true); + _entitiesSubmitter = value; + } } - public override bool paused { get; set; } public override void Dispose() { } - protected internal override EnginesRoot.EntitiesSubmitter onTick + public void SubmitEntities() { - set + try { - DBC.ECS.Check.Require(_onTick == null, "a scheduler can be exclusively used by one enginesRoot only"); - - _onTick = value; + _entitiesSubmitter.Value.SubmitEntities(); + } + catch + { + paused = true; + + throw; } } - EnginesRoot.EntitiesSubmitter? _onTick; - readonly IEnumerator _enumerator; + EnginesRoot.EntitiesSubmitter? _entitiesSubmitter; } } \ No newline at end of file diff --git a/com.sebaslab.svelto.ecs/Core/SpecialEnumerators/WaitForSubmissionEnumerator.cs b/com.sebaslab.svelto.ecs/Core/SpecialEnumerators/WaitForSubmissionEnumerator.cs index 6222a78..74c468a 100644 --- a/com.sebaslab.svelto.ecs/Core/SpecialEnumerators/WaitForSubmissionEnumerator.cs +++ b/com.sebaslab.svelto.ecs/Core/SpecialEnumerators/WaitForSubmissionEnumerator.cs @@ -7,9 +7,9 @@ namespace Svelto.ECS /// /// Enumerator that yields until the next Entities Submission /// - public struct WaitForSubmissionEnumerator : IEnumerator + public class WaitForSubmissionEnumerator : IEnumerator { - public WaitForSubmissionEnumerator(EntitiesSubmissionScheduler scheduler):this() + public WaitForSubmissionEnumerator(EntitiesSubmissionScheduler scheduler) { _scheduler = scheduler; } diff --git a/com.sebaslab.svelto.ecs/Core/Streams/EntitiesStreams.cs b/com.sebaslab.svelto.ecs/Core/Streams/EntitiesStreams.cs index c78de92..41b3ae2 100644 --- a/com.sebaslab.svelto.ecs/Core/Streams/EntitiesStreams.cs +++ b/com.sebaslab.svelto.ecs/Core/Streams/EntitiesStreams.cs @@ -4,15 +4,13 @@ using Svelto.DataStructures; namespace Svelto.ECS { /// - /// I eventually realised that, with the ECS design, no form of communication other than polling entity components can - /// exist. - /// Using groups, you can have always an optimal set of entity components to poll. However EntityStreams - /// can be useful if: - /// - you need to react on seldom entity changes, usually due to user events - /// - you want engines to be able to track entity changes - /// - you want a thread-safe way to read entity states, which includes all the state changes and not the last - /// one only - /// - you want to communicate between EnginesRoots + /// I eventually realised that, with the ECS design, no form of engines (systems) communication other + /// than polling entity components is effective. + /// The only purpose of this publisher/consumer model is to let two enginesroots communicate with each other + /// through a thread safe ring buffer. + /// The engines root A publishes entities. + /// The engines root B can consume those entities at any time, as they will be a copy of the original + /// entities and won't point directly to the database of the engines root A /// struct EntitiesStreams : IDisposable { @@ -46,7 +44,7 @@ namespace Svelto.ECS public void Dispose() { foreach (var stream in _streams) - stream.Value.Dispose(); + stream.value.Dispose(); } public static EntitiesStreams Create() diff --git a/com.sebaslab.svelto.ecs/Core/Streams/ThreadSafeNativeEntityStream.cs b/com.sebaslab.svelto.ecs/Core/Streams/ThreadSafeNativeEntityStream.cs deleted file mode 100644 index a648384..0000000 --- a/com.sebaslab.svelto.ecs/Core/Streams/ThreadSafeNativeEntityStream.cs +++ /dev/null @@ -1,31 +0,0 @@ -namespace Svelto.ECS -{ - /// - /// This EntityStream can be used in parallel jobs, but does NOT guarantee order. - /// - /// - public struct ThreadSafeNativeEntityStream : ITypeSafeStream - { - public ThreadSafeNativeEntityStream(EntitiesDB entitiesDB) - { - } - - public void Dispose() - { - - } - - /// - /// I am thinking to pass the component and do the queryEntity only as a validation - /// - /// - /// - public void PublishEntityChange(in T entityComponent, EGID id) - { -#if DEBUG && !PROFILE_SVELTO - -#endif - - } - } -} \ No newline at end of file diff --git a/com.sebaslab.svelto.ecs/DataStructures/ITypeSafeDictionary.cs b/com.sebaslab.svelto.ecs/DataStructures/ITypeSafeDictionary.cs index 7cb2cea..9c95c9f 100644 --- a/com.sebaslab.svelto.ecs/DataStructures/ITypeSafeDictionary.cs +++ b/com.sebaslab.svelto.ecs/DataStructures/ITypeSafeDictionary.cs @@ -7,37 +7,80 @@ namespace Svelto.ECS.Internal public interface ITypeSafeDictionary : ITypeSafeDictionary where TValue : IEntityComponent { void Add(uint egidEntityId, in TValue entityComponent); - ref TValue this[uint idEntityId] { get; } - bool TryGetValue(uint entityId, out TValue item); - ref TValue GetOrCreate(uint idEntityId); + + bool TryGetValue(uint entityId, out TValue item); + ref TValue GetOrAdd(uint idEntityId); IBuffer GetValues(out uint count); - ref TValue GetDirectValueByRef(uint key); + ref TValue GetDirectValueByRef(uint key); + ref TValue GetValueByRef(uint key); + EntityIDs entityIDs { get; } } - public interface ITypeSafeDictionary:IDisposable + public interface ITypeSafeDictionary : IDisposable { - uint count { get; } + int count { get; } + ITypeSafeDictionary Create(); - //todo: there is something wrong in the design of the execute callback methods. Something to cleanup - void ExecuteEnginesAddOrSwapCallbacks(FasterDictionary> entityComponentEnginesDb, - ITypeSafeDictionary realDic, ExclusiveGroupStruct? fromGroup, ExclusiveGroupStruct toGroup, in PlatformProfiler profiler); - void ExecuteEnginesSwapOrRemoveCallbacks(EGID fromEntityGid, EGID? toEntityID, ITypeSafeDictionary toGroup, - FasterDictionary> engines, in PlatformProfiler profiler); - void ExecuteEnginesRemoveCallbacks(FasterDictionary> entityComponentEnginesDB, - in PlatformProfiler profiler, ExclusiveGroupStruct @group); + void AddEntitiesToDictionary + (ITypeSafeDictionary toDictionary, ExclusiveGroupStruct groupId, in EnginesRoot.EntityReferenceMap entityLocator); + void RemoveEntitiesFromDictionary(FasterList<(uint, string)> infosToProcess); + void SwapEntitiesBetweenDictionaries(FasterList<(uint, uint, string)> infosToProcess, + ExclusiveGroupStruct fromGroup, ExclusiveGroupStruct toGroup, ITypeSafeDictionary toComponentsDictionary); + + //------------ + + //This is now obsolete, but I cannot mark it as such because it's heavily used by legacy projects + void ExecuteEnginesAddCallbacks + (FasterDictionary>> entityComponentEnginesDb + , ITypeSafeDictionary destinationDatabase, ExclusiveGroupStruct toGroup, in PlatformProfiler profiler); + //Version to use + void ExecuteEnginesAddEntityCallbacksFast( + FasterDictionary>> reactiveEnginesAdd, + ExclusiveGroupStruct groupID, (uint, uint) rangeOfSubmittedEntitiesIndicies, in PlatformProfiler profiler); + + //------------ + + //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, + FasterList> reactiveEnginesSwap, ExclusiveGroupStruct fromGroup, + ExclusiveGroupStruct toGroup, in PlatformProfiler sampler); + //Version to use + void ExecuteEnginesSwapCallbacksFast(FasterList> reactiveEnginesSwap, + ExclusiveGroupStruct fromGroup, ExclusiveGroupStruct toGroup, (uint, uint) rangeOfSubmittedEntitiesIndicies, + in PlatformProfiler sampler); - void AddEntitiesFromDictionary - (ITypeSafeDictionary entitiesToSubmit, ExclusiveGroupStruct groupId, EnginesRoot enginesRoot); + //------------ - void AddEntityToDictionary(EGID fromEntityGid, EGID toEntityID, ITypeSafeDictionary toGroup); - void RemoveEntityFromDictionary(EGID fromEntityGid); + //This is now obsolete, but I cannot mark it as such because it's heavily used by legacy projects + void ExecuteEnginesRemoveCallbacks(FasterList<(uint, string)> infosToProcess, + FasterDictionary>> reactiveEnginesRemove, + ExclusiveGroupStruct fromGroup, in PlatformProfiler sampler); + //Version to use + void ExecuteEnginesRemoveCallbacksFast(FasterList> reactiveEnginesRemoveEx, + ExclusiveGroupStruct fromGroup, (uint, uint) rangeOfSubmittedEntitiesIndicies, + in PlatformProfiler sampler); + + //------------ + + void ExecuteEnginesSwapCallbacks_Group( + FasterDictionary>> reactiveEnginesSwap, + FasterDictionary>> reactiveEnginesSwapEx, + ITypeSafeDictionary toEntitiesDictionary, ExclusiveGroupStruct fromGroupId, ExclusiveGroupStruct toGroupId, + in PlatformProfiler platformProfiler); + void ExecuteEnginesRemoveCallbacks_Group( + FasterDictionary>> engines, + FasterDictionary>> reactiveEnginesRemoveEx, + ExclusiveGroupStruct @group, in PlatformProfiler profiler); + void ExecuteEnginesDisposeCallbacks_Group + (FasterDictionary>> engines + , ExclusiveGroupStruct group, in PlatformProfiler profiler); - void ResizeTo(uint size); + void IncreaseCapacityBy(uint size); + void EnsureCapacity(uint size); void Trim(); void Clear(); - void FastClear(); bool Has(uint key); bool ContainsKey(uint egidEntityId); uint GetIndex(uint valueEntityId); diff --git a/com.sebaslab.svelto.ecs/DataStructures/TypeSafeDictionary.cs b/com.sebaslab.svelto.ecs/DataStructures/TypeSafeDictionary.cs index 2e60454..39a0fdc 100644 --- a/com.sebaslab.svelto.ecs/DataStructures/TypeSafeDictionary.cs +++ b/com.sebaslab.svelto.ecs/DataStructures/TypeSafeDictionary.cs @@ -1,204 +1,161 @@ -using System; +#if DEBUG && !PROFILE_SVELTO +//#define PARANOID_CHECK +#endif + +using System; using System.Runtime.CompilerServices; +using DBC.ECS; using Svelto.Common; using Svelto.DataStructures; using Svelto.DataStructures.Native; +using Svelto.ECS.DataStructures; using Svelto.ECS.Hybrid; namespace Svelto.ECS.Internal { - sealed class TypeSafeDictionary : ITypeSafeDictionary where TValue : struct, IEntityComponent + public readonly struct NativeEntityIDs + { + public NativeEntityIDs(NB> native) + { + _native = native; + } + + public uint this[uint index] => _native[index].key; + public uint this[int index] => _native[index].key; + + readonly NB> _native; + } + + public readonly struct ManagedEntityIDs + { + public ManagedEntityIDs(MB> managed) + { + _managed = managed; + } + + public uint this[uint index] => _managed[index].key; + public uint this[int index] => _managed[index].key; + + readonly MB> _managed; + } + + public readonly struct EntityIDs { - static readonly Type _type = typeof(TValue); + readonly NB> _native; + readonly MB> _managed; + + public EntityIDs(NativeStrategy> unmanagedKeys) : this() + { + _native = unmanagedKeys.ToRealBuffer(); + } + + public EntityIDs(ManagedStrategy> managedKeys) : this() + { + _managed = managedKeys.ToRealBuffer(); + } + + public NativeEntityIDs nativeIDs => new NativeEntityIDs(_native); + public ManagedEntityIDs managedIDs => new ManagedEntityIDs(_managed); + } + + public sealed class TypeSafeDictionary : ITypeSafeDictionary where TValue : struct, IEntityComponent + { + static readonly Type _type = typeof(TValue); +#if SLOW_SVELTO_SUBMISSION static readonly bool _hasEgid = typeof(INeedEGID).IsAssignableFrom(_type); static readonly bool _hasReference = typeof(INeedEntityReference).IsAssignableFrom(_type); - +#endif internal static readonly bool isUnmanaged = - _type.IsUnmanagedEx() && (typeof(IEntityViewComponent).IsAssignableFrom(_type) == false); + _type.IsUnmanagedEx() && typeof(IEntityViewComponent).IsAssignableFrom(_type) == false; public TypeSafeDictionary(uint size) { if (isUnmanaged) implUnmgd = - new SveltoDictionary>, - NativeStrategy, NativeStrategy>(size, Allocator.Persistent); + new SharedNative>, + NativeStrategy, NativeStrategy>>( + new SveltoDictionary>, + NativeStrategy, NativeStrategy>(size, Allocator.Persistent)); else - { implMgd = new SveltoDictionary>, ManagedStrategy, ManagedStrategy>(size, Allocator.Managed); - } } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Add(uint egidEntityId, in TValue entityComponent) + public EntityIDs entityIDs { - if (isUnmanaged) - implUnmgd.Add(egidEntityId, entityComponent); - else - implMgd.Add(egidEntityId, entityComponent); - } - - /// todo: Is this really needed, cannot I just use AddEntitiesFromDictionary? Needs to be checked - public void AddEntityToDictionary(EGID fromEntityGid, EGID toEntityID, ITypeSafeDictionary toGroup) - { - if (isUnmanaged) - { - var valueIndex = implUnmgd.GetIndex(fromEntityGid.entityID); - - DBC.ECS.Check.Require(toGroup != null - , "Invalid To Group"); //todo check this, if it's right merge GetIndex - { - var toGroupCasted = toGroup as ITypeSafeDictionary; - ref var entity = ref implUnmgd.GetDirectValueByRef(valueIndex); - - if (_hasEgid) - SetEGIDWithoutBoxing.SetIDWithoutBoxing(ref entity, toEntityID); - - toGroupCasted.Add(toEntityID.entityID, entity); - } - } - else + get { - var valueIndex = implMgd.GetIndex(fromEntityGid.entityID); - - DBC.ECS.Check.Require(toGroup != null - , "Invalid To Group"); //todo check this, if it's right merge GetIndex - { - var toGroupCasted = toGroup as ITypeSafeDictionary; - ref var entity = ref implMgd.GetDirectValueByRef(valueIndex); - - if (_hasEgid) - SetEGIDWithoutBoxing.SetIDWithoutBoxing(ref entity, toEntityID); + if (isUnmanaged) + return new EntityIDs(implUnmgd.value.unsafeKeys); - toGroupCasted.Add(toEntityID.entityID, entity); - } + return new EntityIDs(implMgd.unsafeKeys); } } - /// - /// Add entities from external typeSafeDictionary (todo: add use case) - /// - /// - /// - /// - /// - public void AddEntitiesFromDictionary - (ITypeSafeDictionary entitiesToSubmit, ExclusiveGroupStruct groupId, EnginesRoot enginesRoot) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool ContainsKey(uint egidEntityId) { - var safeDictionary = (entitiesToSubmit as TypeSafeDictionary); - if (isUnmanaged) - { - var typeSafeDictionary = safeDictionary.implUnmgd; - - foreach (var tuple in typeSafeDictionary) - try - { - var egid = new EGID(tuple.Key, groupId); - if (_hasEgid) - SetEGIDWithoutBoxing.SetIDWithoutBoxing(ref tuple.Value, egid); - - if (_hasReference) - SetEGIDWithoutBoxing.SetRefWithoutBoxing( - ref tuple.Value, enginesRoot.entityLocator.GetEntityReference(egid)); - - implUnmgd.Add(tuple.Key, tuple.Value); - } - catch (Exception e) - { - Console.LogException( - e, "trying to add an EntityComponent with the same ID more than once Entity: ".FastConcat(typeof(TValue).ToString()).FastConcat(", group ").FastConcat(groupId.ToName()).FastConcat(", id ").FastConcat(tuple.Key)); - - throw; - } - } - else - { - var typeSafeDictionary = safeDictionary.implMgd; - - foreach (var tuple in typeSafeDictionary) - try - { - if (_hasEgid) - SetEGIDWithoutBoxing.SetIDWithoutBoxing( - ref tuple.Value, new EGID(tuple.Key, groupId)); + return isUnmanaged ? implUnmgd.value.ContainsKey(egidEntityId) : implMgd.ContainsKey(egidEntityId); + } - implMgd.Add(tuple.Key, tuple.Value); - } - catch (Exception e) - { - Console.LogException( - e, "trying to add an EntityComponent with the same ID more than once Entity: ".FastConcat(typeof(TValue).ToString()).FastConcat(", group ").FastConcat(groupId.ToName()).FastConcat(", id ").FastConcat(tuple.Key)); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public uint GetIndex(uint valueEntityId) + { + return isUnmanaged ? implUnmgd.value.GetIndex(valueEntityId) : implMgd.GetIndex(valueEntityId); + } - throw; - } - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ref TValue GetOrAdd(uint idEntityId) + { + return ref isUnmanaged ? ref implUnmgd.value.GetOrAdd(idEntityId) : ref implMgd.GetOrAdd(idEntityId); } - public void ExecuteEnginesAddOrSwapCallbacks - (FasterDictionary> entityComponentEnginesDB - , ITypeSafeDictionary realDic, ExclusiveGroupStruct? fromGroup, ExclusiveGroupStruct toGroup - , in PlatformProfiler profiler) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public IBuffer GetValues(out uint count) { - if (isUnmanaged) - { - var typeSafeDictionary = realDic as ITypeSafeDictionary; + return isUnmanaged ? implUnmgd.value.UnsafeGetValues(out count) : implMgd.UnsafeGetValues(out count); + } - //this can be optimized, should pass all the entities and not restart the process for each one - foreach (var value in implUnmgd) - ExecuteEnginesAddOrSwapCallbacksOnSingleEntity(entityComponentEnginesDB - , ref typeSafeDictionary[value.Key], fromGroup - , in profiler, new EGID(value.Key, toGroup)); - } - else - { - var typeSafeDictionary = realDic as ITypeSafeDictionary; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ref TValue GetDirectValueByRef(uint key) + { + return ref isUnmanaged + ? ref implUnmgd.value.GetDirectValueByRef(key) + : ref implMgd.GetDirectValueByRef(key); + } - //this can be optimized, should pass all the entities and not restart the process for each one - foreach (var value in implMgd) - ExecuteEnginesAddOrSwapCallbacksOnSingleEntity(entityComponentEnginesDB - , ref typeSafeDictionary[value.Key], fromGroup - , in profiler, new EGID(value.Key, toGroup)); - } + public ref TValue GetValueByRef(uint key) + { + return ref isUnmanaged ? ref implUnmgd.value.GetValueByRef(key) : ref implMgd.GetValueByRef(key); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Clear() + public bool Has(uint key) { - if (isUnmanaged) - { - implUnmgd.Clear(); - } - else - { - implMgd.Clear(); - } + return isUnmanaged ? implUnmgd.value.ContainsKey(key) : implMgd.ContainsKey(key); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FastClear() + public bool TryFindIndex(uint entityId, out uint index) { - if (isUnmanaged) - { - implUnmgd.FastClear(); - } - else - { - implMgd.FastClear(); - } + return isUnmanaged + ? implUnmgd.value.TryFindIndex(entityId, out index) + : implMgd.TryFindIndex(entityId, out index); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool ContainsKey(uint egidEntityId) + public bool TryGetValue(uint entityId, out TValue item) { - if (isUnmanaged) - { - return implUnmgd.ContainsKey(egidEntityId); - } - else - { - return implMgd.ContainsKey(egidEntityId); - } + return isUnmanaged + ? implUnmgd.value.TryGetValue(entityId, out item) + : implMgd.TryGetValue(entityId, out item); + } + + public int count + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => isUnmanaged ? implUnmgd.value.count : implMgd.count; } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -208,344 +165,636 @@ namespace Svelto.ECS.Internal } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public uint GetIndex(uint valueEntityId) + public void Clear() { if (isUnmanaged) - { - return implUnmgd.GetIndex(valueEntityId); - } + implUnmgd.value.FastClear(); else - { - return implMgd.GetIndex(valueEntityId); - } + implMgd.Clear(); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ref TValue GetOrCreate(uint idEntityId) + public void EnsureCapacity(uint size) { if (isUnmanaged) - { - return ref implUnmgd.GetOrCreate(idEntityId); - } + implUnmgd.value.EnsureCapacity(size); else - { - return ref implMgd.GetOrCreate(idEntityId); - } + implMgd.EnsureCapacity(size); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public IBuffer GetValues(out uint count) + public void IncreaseCapacityBy(uint size) { if (isUnmanaged) - { - return implUnmgd.GetValues(out count); - } + implUnmgd.value.IncreaseCapacityBy(size); else - { - return implMgd.GetValues(out count); - } + implMgd.IncreaseCapacityBy(size); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ref TValue GetDirectValueByRef(uint key) + public void Trim() { if (isUnmanaged) - { - return ref implUnmgd.GetDirectValueByRef(key); - } + implUnmgd.value.Trim(); else - { - return ref implMgd.GetDirectValueByRef(key); - } + implMgd.Trim(); + } + + public void KeysEvaluator(Action action) + { + if (isUnmanaged) + foreach (var key in implUnmgd.value.keys) + action(key); + else + foreach (var key in implMgd.keys) + action(key); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool Has(uint key) + public void Add(uint egidEntityId, in TValue entityComponent) { if (isUnmanaged) - { - return implUnmgd.ContainsKey(key); - } + implUnmgd.value.Add(egidEntityId, entityComponent); else - { - return implMgd.ContainsKey(key); - } + implMgd.Add(egidEntityId, entityComponent); } - public void ExecuteEnginesSwapOrRemoveCallbacks - (EGID fromEntityGid, EGID? toEntityID, ITypeSafeDictionary toGroup - , FasterDictionary> engines, in PlatformProfiler profiler) + public void Dispose() { if (isUnmanaged) - { - var valueIndex = implUnmgd.GetIndex(fromEntityGid.entityID); + implUnmgd.Dispose(); + else + implMgd.Dispose(); - ref var entity = ref implUnmgd.GetDirectValueByRef(valueIndex); + GC.SuppressFinalize(this); + } - //move - if (toGroup != null) + public void AddEntitiesToDictionary(ITypeSafeDictionary toDictionary, ExclusiveGroupStruct groupId, + in EnginesRoot.EntityReferenceMap entityLocator) + { + void SharedAddEntitiesFromDictionary( + in SveltoDictionary fromDictionary, + ITypeSafeDictionary toDic, in EnginesRoot.EntityReferenceMap locator, + ExclusiveGroupStruct toGroupID) where Strategy1 : struct, IBufferStrategy> + where Strategy2 : struct, IBufferStrategy + where Strategy3 : struct, IBufferStrategy + { + foreach (var tuple in fromDictionary) { - var toGroupCasted = toGroup as ITypeSafeDictionary; - var previousGroup = fromEntityGid.groupID; +#if SLOW_SVELTO_SUBMISSION + var egid = new EGID(tuple.key, toGroupID); - //todo: why is setting the EGID if this code just execute callbacks? if (_hasEgid) - SetEGIDWithoutBoxing.SetIDWithoutBoxing(ref entity, toEntityID.Value); + SetEGIDWithoutBoxing.SetIDWithoutBoxing(ref tuple.value, egid); - var index = toGroupCasted.GetIndex(toEntityID.Value.entityID); + //todo: temporary code that will eventually be removed + if (_hasReference) + SetEGIDWithoutBoxing.SetRefWithoutBoxing(ref tuple.value, + locator.GetEntityReference(egid)); +#endif + try + { + toDic.Add(tuple.key, tuple.value); + } + catch (Exception e) + { + Console.LogException(e, + "trying to add an EntityComponent with the same ID more than once Entity: " + .FastConcat(typeof(TValue).ToString()).FastConcat(", group ") + .FastConcat(toGroupID.ToName()).FastConcat(", id ").FastConcat(tuple.key)); - ExecuteEnginesAddOrSwapCallbacksOnSingleEntity(engines, ref toGroupCasted.GetDirectValueByRef(index) - , previousGroup, in profiler, toEntityID.Value); - } - //remove - else - { - ExecuteEnginesRemoveCallbackOnSingleEntity(engines, ref entity, in profiler, fromEntityGid); + throw; + } +#if PARANOID_CHECK && SLOW_SVELTO_SUBMISSION + DBC.ECS.Check.Ensure(_hasEgid == false || ((INeedEGID)fromDictionary[egid.entityID]).ID == egid, "impossible situation happened during swap"); +#endif } } + + var destinationDictionary = toDictionary as ITypeSafeDictionary; + + if (isUnmanaged) + SharedAddEntitiesFromDictionary(implUnmgd.value, destinationDictionary, entityLocator, groupId); else - { - var valueIndex = implMgd.GetIndex(fromEntityGid.entityID); + SharedAddEntitiesFromDictionary(implMgd, destinationDictionary, entityLocator, groupId); + } - ref var entity = ref implMgd.GetDirectValueByRef(valueIndex); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void RemoveEntitiesFromDictionary(FasterList<(uint, string)> infosToProcess) + { + void AgnosticMethod( + ref SveltoDictionary fromDictionary) + where Strategy1 : struct, IBufferStrategy> + where Strategy2 : struct, IBufferStrategy + where Strategy3 : struct, IBufferStrategy + { + var iterations = infosToProcess.count; - if (toGroup != null) + for (var i = 0; i < iterations; i++) { - var toGroupCasted = toGroup as ITypeSafeDictionary; - var previousGroup = fromEntityGid.groupID; + var (id, trace) = infosToProcess[i]; - //todo: why is setting the EGID if this code just execute callbacks? - if (_hasEgid) - SetEGIDWithoutBoxing.SetIDWithoutBoxing(ref entity, toEntityID.Value); + try + { + if (fromDictionary.Remove(id, 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 + fromDictionary.GetDirectValueByRef((uint)fromDictionary.count) = value; + } + catch + { + var str = "Crash while executing Remove Entity operation on ".FastConcat(TypeCache.name) + .FastConcat(" from : ", trace); - var index = toGroupCasted.GetIndex(toEntityID.Value.entityID); + Console.LogError(str); - ExecuteEnginesAddOrSwapCallbacksOnSingleEntity(engines, ref toGroupCasted.GetDirectValueByRef(index) - , previousGroup, in profiler, toEntityID.Value); - } - else - { - ExecuteEnginesRemoveCallbackOnSingleEntity(engines, ref entity, in profiler, fromEntityGid); + throw; + } } } - } - public void ExecuteEnginesRemoveCallbacks - (FasterDictionary> engines, in PlatformProfiler profiler - , ExclusiveGroupStruct group) - { if (isUnmanaged) - { - foreach (var value in implUnmgd) - ExecuteEnginesRemoveCallbackOnSingleEntity(engines, ref implUnmgd.GetValueByRef(value.Key) - , in profiler, new EGID(value.Key, group)); - } + AgnosticMethod(ref implUnmgd.value); else - { - foreach (var value in implMgd) - ExecuteEnginesRemoveCallbackOnSingleEntity(engines, ref implMgd.GetValueByRef(value.Key) - , in profiler, new EGID(value.Key, group)); - } + AgnosticMethod(ref implMgd); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void RemoveEntityFromDictionary(EGID fromEntityGid) + public void SwapEntitiesBetweenDictionaries(FasterList<(uint, uint, string)> infosToProcess, + ExclusiveGroupStruct fromGroup, ExclusiveGroupStruct toGroup, ITypeSafeDictionary toComponentsDictionary) { - if (isUnmanaged) + void SharedSwapEntityInDictionary( + ref SveltoDictionary fromDictionary, + ITypeSafeDictionary toDictionary) + where Strategy1 : struct, IBufferStrategy> + where Strategy2 : struct, IBufferStrategy + where Strategy3 : struct, IBufferStrategy { - implUnmgd.Remove(fromEntityGid.entityID); - } - else - { - implMgd.Remove(fromEntityGid.entityID); + var iterations = infosToProcess.count; + + for (var i = 0; i < iterations; i++) + { + var (fromID, toID, trace) = infosToProcess[i]; + + try + { + var fromEntityGid = new EGID(fromID, fromGroup); + var toEntityEgid = new EGID(toID, toGroup); + + Check.Require(toGroup.isInvalid == false, "Invalid To Group"); + + var isFound = fromDictionary.Remove(fromEntityGid.entityID, out var entity); + Check.Assert(isFound, "Swapping an entity that doesn't exist"); +#if SLOW_SVELTO_SUBMISSION + if (_hasEgid) + SetEGIDWithoutBoxing.SetIDWithoutBoxing(ref entity, toEntityEgid); +#endif + + toDictionary.Add(toEntityEgid.entityID, entity); + +#if PARANOID_CHECK + DBC.ECS.Check.Ensure(_hasEgid == false || ((INeedEGID)toGroupCasted[toEntityEGID.entityID]).ID == toEntityEGID, "impossible situation happened during swap"); +#endif + } + catch + { + var str = "Crash while executing Swap Entity operation on ".FastConcat(TypeCache.name) + .FastConcat(" from : ", trace); + + Console.LogError(str); + + throw; + } + } } - } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void ResizeTo(uint size) - { + var toGroupCasted = toComponentsDictionary as ITypeSafeDictionary; + if (isUnmanaged) - { - implUnmgd.ResizeTo(size); - } + SharedSwapEntityInDictionary(ref implUnmgd.value, toGroupCasted); else - { - implMgd.ResizeTo(size); - } + SharedSwapEntityInDictionary(ref implMgd, toGroupCasted); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Trim() + public void ExecuteEnginesAddCallbacks( + FasterDictionary>> entityComponentEnginesDB, + ITypeSafeDictionary toDic, ExclusiveGroupStruct toGroup, in PlatformProfiler profiler) { - if (isUnmanaged) - { - implUnmgd.Trim(); - } - else - { - implMgd.Trim(); + void AgnosticMethod( + ref SveltoDictionary fromDictionary, + ITypeSafeDictionary todic, in PlatformProfiler sampler) + where Strategy1 : struct, IBufferStrategy> + where Strategy2 : struct, IBufferStrategy + where Strategy3 : struct, IBufferStrategy + { + if (entityComponentEnginesDB.TryGetValue(new RefWrapperType(_type), out var entityComponentsEngines)) + { + if (entityComponentsEngines.count == 0) return; + + var dictionaryKeyEnumerator = fromDictionary.unsafeKeys; + var count = fromDictionary.count; + + for (var i = 0; i < count; ++i) + try + { + var key = dictionaryKeyEnumerator[i].key; + ref var entity = ref todic.GetValueByRef(key); + var egid = new EGID(key, toGroup); + //get all the engines linked to TValue + for (var j = 0; j < entityComponentsEngines.count; j++) + using (sampler.Sample(entityComponentsEngines[j].name)) + { + ((IReactOnAdd)entityComponentsEngines[j].engine).Add(ref entity, egid); + } + } + catch (Exception e) + { + Console.LogException(e, + "Code crashed inside Add callback with Type ".FastConcat(TypeCache.name)); + + throw; + } + } } - } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool TryFindIndex(uint entityId, out uint index) - { + var toDictionary = (ITypeSafeDictionary)toDic; + if (isUnmanaged) - { - return implUnmgd.TryFindIndex(entityId, out index); - } + AgnosticMethod(ref implUnmgd.value, toDictionary, in profiler); else - { - return implMgd.TryFindIndex(entityId, out index); - } + AgnosticMethod(ref implMgd, toDictionary, in profiler); } - public void KeysEvaluator(Action action) + public void ExecuteEnginesSwapCallbacks(FasterList<(uint, uint, string)> infosToProcess, + FasterList> reactiveEnginesSwap, ExclusiveGroupStruct fromGroup, + ExclusiveGroupStruct toGroup, in PlatformProfiler profiler) { - if (isUnmanaged) + void AgnosticMethod( + ref SveltoDictionary fromDictionary, + in PlatformProfiler sampler) where Strategy1 : struct, IBufferStrategy> + where Strategy2 : struct, IBufferStrategy + where Strategy3 : struct, IBufferStrategy { - foreach (var key in implUnmgd.keys) + if (reactiveEnginesSwap.count == 0) return; + + var iterations = infosToProcess.count; + + for (var i = 0; i < iterations; i++) { - action(key); + var (fromEntityID, toEntityID, trace) = infosToProcess[i]; + + try + { + ref var entityComponent = ref fromDictionary.GetValueByRef(fromEntityID); + var newEgid = new EGID(toEntityID, toGroup); + for (var j = 0; j < reactiveEnginesSwap.count; j++) + using (sampler.Sample(reactiveEnginesSwap[j].name)) + { + ((IReactOnSwap)reactiveEnginesSwap[j].engine).MovedTo(ref entityComponent, + fromGroup, newEgid); + } + } + catch + { + var str = "Crash while executing Swap Entity callback on ".FastConcat(TypeCache.name) + .FastConcat(" from : ", trace); + + Console.LogError(str); + + throw; + } } } + + if (isUnmanaged) + AgnosticMethod(ref implUnmgd.value, in profiler); else + AgnosticMethod(ref implMgd, in profiler); + } + + public void ExecuteEnginesRemoveCallbacks(FasterList<(uint, string)> infosToProcess, + FasterDictionary>> reactiveEnginesRemove, + ExclusiveGroupStruct fromGroup, in PlatformProfiler sampler) + { + void AgnosticMethod( + ref SveltoDictionary fromDictionary, + in PlatformProfiler profiler) where Strategy1 : struct, IBufferStrategy> + where Strategy2 : struct, IBufferStrategy + where Strategy3 : struct, IBufferStrategy { - foreach (var key in implMgd.keys) + if (reactiveEnginesRemove.TryGetValue(new RefWrapperType(_type), out var entityComponentsEngines)) { - action(key); + if (entityComponentsEngines.count == 0) return; + + var iterations = infosToProcess.count; + + for (var i = 0; i < iterations; i++) + { + var (entityID, trace) = infosToProcess[i]; + try + { + ref var entity = ref fromDictionary.GetValueByRef(entityID); + var egid = new EGID(entityID, fromGroup); + + for (var j = 0; j < entityComponentsEngines.count; j++) + using (profiler.Sample(entityComponentsEngines[j].name)) + { + ((IReactOnRemove)entityComponentsEngines[j].engine).Remove(ref entity, + egid); + } + } + catch + { + var str = "Crash while executing Remove Entity callback on " + .FastConcat(TypeCache.name).FastConcat(" from : ", trace); + + Console.LogError(str); + + throw; + } + } } } - } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool TryGetValue(uint entityId, out TValue item) - { if (isUnmanaged) - { - return implUnmgd.TryGetValue(entityId, out item); - } + AgnosticMethod(ref implUnmgd.value, in sampler); else - { - return implMgd.TryGetValue(entityId, out item); - } + AgnosticMethod(ref implMgd, in sampler); } - public uint count + public void ExecuteEnginesAddEntityCallbacksFast( + FasterDictionary>> reactiveEnginesAdd, + ExclusiveGroupStruct groupID, (uint, uint) rangeOfSubmittedEntitiesIndicies, in PlatformProfiler profiler) { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get - { - if (isUnmanaged) + //get all the engines linked to TValue + if (!reactiveEnginesAdd.TryGetValue(new RefWrapperType(_type), out var entityComponentsEngines)) + return; + + for (var i = 0; i < entityComponentsEngines.count; i++) + try { - return (uint) implUnmgd.count; + using (profiler.Sample(entityComponentsEngines[i].name)) + { + ((IReactOnAddEx)entityComponentsEngines[i].engine).Add(rangeOfSubmittedEntitiesIndicies, + new EntityCollection(GetValues(out var count), count, entityIDs), groupID); + } } - else + catch (Exception e) { - return (uint) implMgd.count; + Console.LogException(e, + "Code crashed inside Add callback ".FastConcat(entityComponentsEngines[i].name)); + + throw; } - } } - public ref TValue this[uint idEntityId] + public void ExecuteEnginesSwapCallbacksFast( + FasterList> reactiveEnginesSwap, ExclusiveGroupStruct fromGroup, + ExclusiveGroupStruct toGroup, (uint, uint) rangeOfSubmittedEntitiesIndicies, in PlatformProfiler sampler) { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get - { - if (isUnmanaged) + for (var i = 0; i < reactiveEnginesSwap.count; i++) + try { - return ref implUnmgd.GetValueByRef(idEntityId); + using (sampler.Sample(reactiveEnginesSwap[i].name)) + { + ((IReactOnSwapEx)reactiveEnginesSwap[i].engine).MovedTo( + rangeOfSubmittedEntitiesIndicies, + new EntityCollection(GetValues(out var count), count, entityIDs), fromGroup, + toGroup); + } } - else + catch (Exception e) { - return ref implMgd.GetValueByRef(idEntityId); + Console.LogException(e, + "Code crashed inside Add callback ".FastConcat(reactiveEnginesSwap[i].name)); + + throw; } - } } - public void Dispose() + public void ExecuteEnginesRemoveCallbacksFast( + FasterList> reactiveEnginesRemoveEx, ExclusiveGroupStruct fromGroup, + (uint, uint) rangeOfSubmittedEntitiesIndicies, in PlatformProfiler sampler) { - if (isUnmanaged) - implUnmgd.Dispose(); - else - implMgd.Dispose(); + for (var i = 0; i < reactiveEnginesRemoveEx.count; i++) + try + { + using (sampler.Sample(reactiveEnginesRemoveEx[i].name)) + { + ((IReactOnRemoveEx)reactiveEnginesRemoveEx[i].engine).Remove( + rangeOfSubmittedEntitiesIndicies, + new EntityCollection(GetValues(out var count), count, entityIDs), fromGroup); + } + } + catch (Exception e) + { + Console.LogException(e, + "Code crashed inside Add callback ".FastConcat(reactiveEnginesRemoveEx[i].name)); - GC.SuppressFinalize(this); + throw; + } } - void ExecuteEnginesAddOrSwapCallbacksOnSingleEntity - (FasterDictionary> engines, ref TValue entity - , ExclusiveGroupStruct? previousGroup, in PlatformProfiler profiler, EGID egid) + public void ExecuteEnginesSwapCallbacks_Group( + FasterDictionary>> reactiveEnginesSwap, + FasterDictionary>> reactiveEnginesSwapEx, + ITypeSafeDictionary toDictionary, ExclusiveGroupStruct fromGroup, ExclusiveGroupStruct toGroup, + in PlatformProfiler profiler) { - //get all the engines linked to TValue - if (!engines.TryGetValue(new RefWrapperType(_type), out var entityComponentsEngines)) - return; - - if (previousGroup == null) + void AgnosticMethod( + ref SveltoDictionary fromDictionary, + ITypeSafeDictionary toDic, in PlatformProfiler sampler) + where Strategy1 : struct, IBufferStrategy> + where Strategy2 : struct, IBufferStrategy + where Strategy3 : struct, IBufferStrategy { - for (var i = 0; i < entityComponentsEngines.count; i++) + //get all the engines linked to TValue + if (!reactiveEnginesSwap.TryGetValue(new RefWrapperType(_type), out var reactiveEnginesSwapPerType)) + return; + + var componentsEnginesCount = reactiveEnginesSwapPerType.count; + + for (var i = 0; i < componentsEnginesCount; i++) try { - using (profiler.Sample(entityComponentsEngines[i].name)) + foreach (var value in fromDictionary) { - (entityComponentsEngines[i].engine as IReactOnAddAndRemove).Add(ref entity, egid); + ref var entityComponent = ref toDic.GetValueByRef(value.key); + var newEgid = new EGID(value.key, toGroup); + + + using (sampler.Sample(reactiveEnginesSwapPerType[i].name)) + { + ((IReactOnSwap)reactiveEnginesSwapPerType[i].engine).MovedTo( + ref entityComponent, fromGroup, newEgid); + } } } - catch + catch (Exception) { - Console.LogError("Code crashed inside Add callback ".FastConcat(typeof(TValue).ToString())); + Console.LogError( + "Code crashed inside MoveTo callback ".FastConcat(reactiveEnginesSwapPerType[i].name)); throw; } + + if (reactiveEnginesSwapEx.TryGetValue(new RefWrapperType(_type), + out var reactiveEnginesRemoveExPerType)) + { + var enginesCount = reactiveEnginesRemoveExPerType.count; + + for (var i = 0; i < enginesCount; i++) + try + { + using (sampler.Sample(reactiveEnginesRemoveExPerType[i].name)) + { + ((IReactOnSwapEx)reactiveEnginesRemoveExPerType[i].engine).MovedTo( + (0, (uint)count), + new EntityCollection(GetValues(out _), (uint)count, entityIDs), fromGroup, + toGroup); + } + } + catch + { + Console.LogError( + "Code crashed inside Remove callback ".FastConcat( + reactiveEnginesRemoveExPerType[i].name)); + + throw; + } + } + } + + var toEntitiesDictionary = (ITypeSafeDictionary)toDictionary; + + if (isUnmanaged) + AgnosticMethod(ref implUnmgd.value, toEntitiesDictionary, in profiler); + else + AgnosticMethod(ref implMgd, toEntitiesDictionary, in profiler); + } + + public void ExecuteEnginesRemoveCallbacks_Group( + FasterDictionary>> reactiveEnginesRemove, + FasterDictionary>> + reactiveEnginesRemoveEx, ExclusiveGroupStruct group, in PlatformProfiler profiler) + { + void AgnosticMethod( + ref SveltoDictionary fromDictionary, + in PlatformProfiler sampler) where Strategy1 : struct, IBufferStrategy> + where Strategy2 : struct, IBufferStrategy + where Strategy3 : struct, IBufferStrategy + { + if (reactiveEnginesRemove.TryGetValue(new RefWrapperType(_type), out var reactiveEnginesRemovePerType)) + { + var enginesCount = reactiveEnginesRemovePerType.count; + + for (var i = 0; i < enginesCount; i++) + try + { + foreach (var value in fromDictionary) + { + ref var entity = ref value.value; + var egid = new EGID(value.key, group); + + + using (sampler.Sample(reactiveEnginesRemovePerType[i].name)) + { + ((IReactOnRemove)reactiveEnginesRemovePerType[i].engine).Remove(ref entity, + egid); + } + } + } + catch + { + Console.LogError( + "Code crashed inside Remove callback ".FastConcat(reactiveEnginesRemovePerType[i] + .name)); + + throw; + } + } + + if (reactiveEnginesRemoveEx.TryGetValue(new RefWrapperType(_type), + out var reactiveEnginesRemoveExPerType)) + { + var enginesCount = reactiveEnginesRemoveExPerType.count; + + for (var i = 0; i < enginesCount; i++) + try + { + using (sampler.Sample(reactiveEnginesRemoveExPerType[i].name)) + { + ((IReactOnRemoveEx)reactiveEnginesRemoveExPerType[i].engine).Remove( + (0, (uint)count), + new EntityCollection(GetValues(out _), (uint)count, entityIDs), group); + } + } + catch + { + Console.LogError( + "Code crashed inside Remove callback ".FastConcat( + reactiveEnginesRemoveExPerType[i].name)); + + throw; + } + } } + + if (isUnmanaged) + AgnosticMethod(ref implUnmgd.value, in profiler); else + AgnosticMethod(ref implMgd, in profiler); + } + + public void ExecuteEnginesDisposeCallbacks_Group( + FasterDictionary>> engines, + ExclusiveGroupStruct group, in PlatformProfiler profiler) + { + void ExecuteEnginesDisposeEntityCallback( + ref SveltoDictionary fromDictionary, + FasterDictionary>> allEngines, + in PlatformProfiler sampler, ExclusiveGroupStruct inGroup) + where Strategy1 : struct, IBufferStrategy> + where Strategy2 : struct, IBufferStrategy + where Strategy3 : struct, IBufferStrategy { + if (allEngines.TryGetValue(new RefWrapperType(_type), out var entityComponentsEngines) == false) + return; + for (var i = 0; i < entityComponentsEngines.count; i++) try { - using (profiler.Sample(entityComponentsEngines[i].name)) + using (sampler.Sample(entityComponentsEngines[i].name)) { - (entityComponentsEngines[i].engine as IReactOnSwap).MovedTo( - ref entity, previousGroup.Value, egid); + foreach (var value in fromDictionary) + { + ref var entity = ref value.value; + var egid = new EGID(value.key, inGroup); + var reactOnRemove = ((IReactOnDispose)entityComponentsEngines[i].engine); + reactOnRemove.Remove(ref entity, egid); + } } } - catch (Exception) + catch { - Console.LogError("Code crashed inside MoveTo callback ".FastConcat(typeof(TValue).ToString())); + Console.LogError( + "Code crashed inside Remove callback ".FastConcat(entityComponentsEngines[i].name)); throw; } } - } - - static void ExecuteEnginesRemoveCallbackOnSingleEntity - (FasterDictionary> engines, ref TValue entity - , in PlatformProfiler profiler, EGID egid) - { - if (!engines.TryGetValue(new RefWrapperType(_type), out var entityComponentsEngines)) - return; - for (var i = 0; i < entityComponentsEngines.count; i++) - try - { - using (profiler.Sample(entityComponentsEngines[i].name)) - { - (entityComponentsEngines[i].engine as IReactOnAddAndRemove).Remove(ref entity, egid); - } - } - catch - { - Console.LogError("Code crashed inside Remove callback ".FastConcat(typeof(TValue).ToString())); - - throw; - } + if (isUnmanaged) + ExecuteEnginesDisposeEntityCallback(ref implUnmgd.value, engines, in profiler, @group); + else + ExecuteEnginesDisposeEntityCallback(ref implMgd, engines, in profiler, @group); } - internal SveltoDictionary>, ManagedStrategy, + SveltoDictionary>, ManagedStrategy, ManagedStrategy> implMgd; - //used directly by native methods - internal SveltoDictionary>, NativeStrategy, - NativeStrategy> implUnmgd; + internal SharedNative>, + NativeStrategy, NativeStrategy>> implUnmgd; } } \ No newline at end of file diff --git a/com.sebaslab.svelto.ecs/DataStructures/Unmanaged/AtomicNativeBags.cs b/com.sebaslab.svelto.ecs/DataStructures/Unmanaged/AtomicNativeBags.cs index bb57bc8..f614c3a 100644 --- a/com.sebaslab.svelto.ecs/DataStructures/Unmanaged/AtomicNativeBags.cs +++ b/com.sebaslab.svelto.ecs/DataStructures/Unmanaged/AtomicNativeBags.cs @@ -75,7 +75,7 @@ namespace Svelto.ECS.DataStructures readonly Allocator _allocator; readonly uint _threadsCount; -#if UNITY_COLLECTIONS || UNITY_JOBS || UNITY_BURST +#if UNITY_COLLECTIONS || UNITY_JOBS || UNITY_BURST #if UNITY_BURST [Unity.Burst.NoAlias] #endif diff --git a/com.sebaslab.svelto.ecs/DataStructures/Unmanaged/NativeBag.cs b/com.sebaslab.svelto.ecs/DataStructures/Unmanaged/NativeBag.cs index e68bb21..b0cab53 100644 --- a/com.sebaslab.svelto.ecs/DataStructures/Unmanaged/NativeBag.cs +++ b/com.sebaslab.svelto.ecs/DataStructures/Unmanaged/NativeBag.cs @@ -1,15 +1,11 @@ #if DEBUG && !PROFILE_SVELTO #define ENABLE_DEBUG_CHECKS #endif - -#if DEBUG && !PROFILE_SVELTO -//#define ENABLE_THREAD_SAFE_CHECKS -#endif - using System; using System.Diagnostics; using System.Runtime.CompilerServices; using Svelto.Common; +using Svelto.Common.DataStructures; namespace Svelto.ECS.DataStructures { @@ -34,18 +30,11 @@ namespace Svelto.ECS.DataStructures unsafe { BasicTests(); -#if ENABLE_THREAD_SAFE_CHECKS - try + + using (_threadSentinel.TestThreadSafety()) { -#endif - return _queue->size; -#if ENABLE_THREAD_SAFE_CHECKS - } - finally - { - Volatile.Write(ref _threadSentinel, 0); + return _queue->size; } -#endif } } } @@ -58,35 +47,23 @@ namespace Svelto.ECS.DataStructures unsafe { BasicTests(); -#if ENABLE_THREAD_SAFE_CHECKS - try + + using (_threadSentinel.TestThreadSafety()) { -#endif - return _queue->capacity; -#if ENABLE_THREAD_SAFE_CHECKS - } - finally - { - Volatile.Write(ref _threadSentinel, 0); + return _queue->capacity; } -#endif } } } - public NativeBag(Allocator allocator) + public NativeBag(Allocator allocator):this() { unsafe { - var listData = (UnsafeBlob*) MemoryUtilities.Alloc((uint) 1, allocator); + var listData = (UnsafeBlob*)MemoryUtilities.Alloc((uint)1, allocator); - //clear to nullify the pointers - //MemoryUtilities.MemClear((IntPtr) listData, (uint) sizeOf); listData->allocator = allocator; _queue = listData; -#if ENABLE_THREAD_SAFE_CHECKS - _threadSentinel = 0; -#endif } } @@ -96,19 +73,12 @@ namespace Svelto.ECS.DataStructures unsafe { BasicTests(); -#if ENABLE_THREAD_SAFE_CHECKS - try - { -#endif - if (_queue == null || _queue->ptr == null) - return true; -#if ENABLE_THREAD_SAFE_CHECKS - } - finally + + using (_threadSentinel.TestThreadSafety()) { - Volatile.Write(ref _threadSentinel, 0); + if (_queue == null || _queue->ptr == null) + return true; } -#endif } return count == 0; @@ -119,84 +89,58 @@ namespace Svelto.ECS.DataStructures { if (_queue != null) { -#if ENABLE_THREAD_SAFE_CHECKS - //todo: this must be unit tested - if (Interlocked.CompareExchange(ref _threadSentinel, 1, 0) != 0) - throw new Exception("NativeBag is not thread safe, reading and writing operations can happen" + - "on different threads, but not simultaneously"); + BasicTests(); - try + using (_threadSentinel.TestThreadSafety()) { -#endif - _queue->Dispose(); - MemoryUtilities.Free((IntPtr) _queue, _queue->allocator); - _queue = null; -#if ENABLE_THREAD_SAFE_CHECKS - } - finally - { - Volatile.Write(ref _threadSentinel, 0); + _queue->Dispose(); + MemoryUtilities.Free((IntPtr)_queue, _queue->allocator); + _queue = null; } -#endif } } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ref T ReserveEnqueue(out UnsafeArrayIndex index) where T : struct + public ref T ReserveEnqueue + (out UnsafeArrayIndex index) + where T : struct //should be unmanaged, but it's not due to Svelto.ECS constraints. { unsafe { BasicTests(); var sizeOf = MemoryUtilities.SizeOf(); - if (_queue->availableSpace - sizeOf < 0) + + using (_threadSentinel.TestThreadSafety()) { - _queue->Realloc((_queue->capacity + (uint)sizeOf) << 1); - } - -#if ENABLE_THREAD_SAFE_CHECKS - try - { -#endif + if (_queue->availableSpace - sizeOf < 0) + { + _queue->Grow(); + } - return ref _queue->Reserve(out index); -#if ENABLE_THREAD_SAFE_CHECKS + return ref _queue->Reserve(out index); } - finally - { - Volatile.Write(ref _threadSentinel, 0); - } -#endif } } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Enqueue(in T item) where T : struct + public void Enqueue + (in T item) where T : struct //should be unmanaged, but it's not due to Svelto.ECS constraints. { unsafe { BasicTests(); -#if ENABLE_THREAD_SAFE_CHECKS - try - { -#endif - var sizeOf = MemoryUtilities.SizeOf(); - if (_queue->availableSpace - sizeOf < 0) + using (_threadSentinel.TestThreadSafety()) { - var capacityInBytes = (_queue->capacity + (uint)sizeOf); - - _queue->Realloc(capacityInBytes << 1); - } + var sizeOf = MemoryUtilities.SizeOf(); + if (_queue->availableSpace - sizeOf < 0) + { + _queue->Grow(); + } - _queue->Enqueue(item); -#if ENABLE_THREAD_SAFE_CHECKS - } - finally - { - Volatile.Write(ref _threadSentinel, 0); + _queue->Enqueue(item); } -#endif } } @@ -206,58 +150,37 @@ namespace Svelto.ECS.DataStructures unsafe { BasicTests(); -#if ENABLE_THREAD_SAFE_CHECKS - try - { -#endif - _queue->Clear(); -#if ENABLE_THREAD_SAFE_CHECKS - } - finally + + using (_threadSentinel.TestThreadSafety()) { - Volatile.Write(ref _threadSentinel, 0); + _queue->Clear(); } -#endif } } - public T Dequeue() where T : struct + public T Dequeue() where T : struct //should be unmanaged, but it's not due to Svelto.ECS constraints. { unsafe { BasicTests(); -#if ENABLE_THREAD_SAFE_CHECKS - try - { -#endif - return _queue->Dequeue(); -#if ENABLE_THREAD_SAFE_CHECKS - } - finally + + using (_threadSentinel.TestThreadSafety()) { - Volatile.Write(ref _threadSentinel, 0); + return _queue->Dequeue(); } -#endif } } - public ref T AccessReserved(UnsafeArrayIndex reservedIndex) where T : struct + public ref T AccessReserved(UnsafeArrayIndex reservedIndex) where T : struct //should be unmanaged, but it's not due to Svelto.ECS constraints. { unsafe { BasicTests(); -#if ENABLE_THREAD_SAFE_CHECKS - try - { -#endif - return ref _queue->AccessReserved(reservedIndex); -#if ENABLE_THREAD_SAFE_CHECKS - } - finally + + using (_threadSentinel.TestThreadSafety()) { - Volatile.Write(ref _threadSentinel, 0); + return ref _queue->AccessReserved(reservedIndex); } -#endif } } @@ -266,17 +189,10 @@ namespace Svelto.ECS.DataStructures { if (_queue == null) throw new Exception("SimpleNativeArray: null-access"); -#if ENABLE_THREAD_SAFE_CHECKS - todo: this must be unit tested - if (Interlocked.CompareExchange(ref _threadSentinel, 1, 0) != 0) - throw new Exception("NativeBag is not thread safe, reading and writing operations can happen" - + "on different threads, but not simultaneously"); -#endif } + + readonly Sentinel _threadSentinel; -#if ENABLE_THREAD_SAFE_CHECKS - int _threadSentinel; -#endif #if UNITY_COLLECTIONS || UNITY_JOBS || UNITY_BURST #if UNITY_BURST [Unity.Burst.NoAlias] diff --git a/com.sebaslab.svelto.ecs/DataStructures/Unmanaged/NativeDynamicArray.cs b/com.sebaslab.svelto.ecs/DataStructures/Unmanaged/NativeDynamicArray.cs index dfc456e..d41adc1 100644 --- a/com.sebaslab.svelto.ecs/DataStructures/Unmanaged/NativeDynamicArray.cs +++ b/com.sebaslab.svelto.ecs/DataStructures/Unmanaged/NativeDynamicArray.cs @@ -1,6 +1,11 @@ +#if DEBUG && !PROFILE_SVELTO +#define ENABLE_DEBUG_CHECKS +#endif + using System; using System.Runtime.CompilerServices; using Svelto.Common; +using Svelto.Common.DataStructures; using Allocator = Svelto.Common.Allocator; namespace Svelto.ECS.DataStructures @@ -24,7 +29,7 @@ namespace Svelto.ECS.DataStructures { unsafe { -#if DEBUG && !PROFILE_SVELTO +#if ENABLE_DEBUG_CHECKS if (_list == null) throw new Exception("NativeDynamicArray: null-access"); if (_hashType != TypeHash.hash) @@ -34,13 +39,13 @@ namespace Svelto.ECS.DataStructures return (_list->count / MemoryUtilities.SizeOf()); } } - + [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int Size() + public int SizeInBytes() { unsafe { -#if DEBUG && !PROFILE_SVELTO +#if ENABLE_DEBUG_CHECKS if (_list == null) throw new Exception("NativeDynamicArray: null-access"); @@ -54,7 +59,7 @@ namespace Svelto.ECS.DataStructures { unsafe { -#if DEBUG && !PROFILE_SVELTO +#if ENABLE_DEBUG_CHECKS if (_list == null) throw new Exception("NativeDynamicArray: null-access"); if (_hashType != TypeHash.hash) @@ -74,16 +79,19 @@ namespace Svelto.ECS.DataStructures { unsafe { -#if DEBUG && !PROFILE_SVELTO - var rtnStruc = new NativeDynamicArray {_hashType = TypeHash.hash}; -#else +#if ENABLE_DEBUG_CHECKS + var rtnStruc = new NativeDynamicArray + { + _hashType = TypeHash.hash, + }; +#else NativeDynamicArray rtnStruc = default; #endif - UnsafeArray* listData = (UnsafeArray*) MemoryUtilities.Alloc(1, allocator); + UnsafeArray* listData = (UnsafeArray*)MemoryUtilities.Alloc(1, allocator); //clear to nullify the pointers //MemoryUtilities.MemClear((IntPtr) listData, structSize); - + rtnStruc._allocator = allocator; listData->Realloc(newLength, allocator); @@ -98,7 +106,7 @@ namespace Svelto.ECS.DataStructures { unsafe { -#if DEBUG && !PROFILE_SVELTO +#if ENABLE_DEBUG_CHECKS if (_list == null) throw new Exception("NativeDynamicArray: null-access"); if (_hashType != TypeHash.hash) @@ -106,14 +114,22 @@ namespace Svelto.ECS.DataStructures if (index >= Count()) throw new Exception($"NativeDynamicArray: out of bound access, index {index} count {Count()}"); #endif - return ref _list->Get(index); + +#if ENABLE_DEBUG_CHECKS + using (_threadSentinel.TestThreadSafety()) + { +#endif + return ref _list->Get(index); +#if ENABLE_DEBUG_CHECKS + } +#endif } } - + [MethodImpl(MethodImplOptions.AggressiveInlining)] public ref T Get(int index) where T : struct { - return ref Get((uint) index); + return ref Get((uint)index); } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -121,27 +137,45 @@ namespace Svelto.ECS.DataStructures { unsafe { -#if DEBUG && !PROFILE_SVELTO +#if ENABLE_DEBUG_CHECKS if (_list == null) throw new Exception("NativeDynamicArray: null-access"); if (_hashType != TypeHash.hash) throw new Exception("NativeDynamicArray: not expected type used"); if (index >= Capacity()) - throw new Exception($"NativeDynamicArray: out of bound access, index {index} capacity {Capacity()}"); + throw new Exception( + $"NativeDynamicArray: out of bound access, index {index} capacity {Capacity()}"); +#endif + +#if ENABLE_DEBUG_CHECKS + using (_threadSentinel.TestThreadSafety()) + { +#endif + _list->Set(index, value); + +#if ENABLE_DEBUG_CHECKS + } #endif - _list->Set(index, value); } } public unsafe void Dispose() { -#if DEBUG && !PROFILE_SVELTO +#if ENABLE_DEBUG_CHECKS if (_list == null) throw new Exception("NativeDynamicArray: null-access"); #endif - _list->Dispose(_allocator); - MemoryUtilities.Free((IntPtr) _list, _allocator); +#if ENABLE_DEBUG_CHECKS + using (_threadSentinel.TestThreadSafety()) + { +#endif + _list->Dispose(_allocator); + MemoryUtilities.Free((IntPtr)_list, _allocator); + +#if ENABLE_DEBUG_CHECKS + } +#endif _list = null; } @@ -150,56 +184,80 @@ namespace Svelto.ECS.DataStructures { unsafe { -#if DEBUG && !PROFILE_SVELTO +#if ENABLE_DEBUG_CHECKS if (_list == null) throw new Exception("NativeDynamicArray: null-access"); if (_hashType != TypeHash.hash) throw new Exception("NativeDynamicArray: not expected type used"); #endif - if (Count() == Capacity()) + +#if ENABLE_DEBUG_CHECKS + using (_threadSentinel.TestThreadSafety()) { - _list->Realloc((uint) ((Capacity() + 1) * 1.5f), _allocator); - } +#endif + if (Count() == Capacity()) + { + _list->Realloc((uint)((Capacity() + 1) * 1.5f), _allocator); + } - _list->Add(item); + _list->Add(item); +#if ENABLE_DEBUG_CHECKS + } +#endif } } - + [MethodImpl(MethodImplOptions.AggressiveInlining)] public ref T AddAt(uint index) where T : struct { unsafe { -#if DEBUG && !PROFILE_SVELTO +#if ENABLE_DEBUG_CHECKS if (_list == null) throw new Exception("NativeDynamicArray: null-access"); if (_hashType != TypeHash.hash) throw new Exception("NativeDynamicArray: not expected type used"); #endif - var structSize = (uint) MemoryUtilities.SizeOf(); + var structSize = (uint)MemoryUtilities.SizeOf(); - if (index >= Capacity()) - _list->Realloc((uint) ((index + 1) * 1.5f), _allocator); +#if ENABLE_DEBUG_CHECKS + using (_threadSentinel.TestThreadSafety()) + { +#endif + if (index >= Capacity()) + _list->Realloc((uint)((index + 1) * 1.5f), _allocator); - var writeIndex = (index + 1) * structSize; - if (_list->count < writeIndex) - _list->SetCountTo(writeIndex); + var writeIndex = (index + 1) * structSize; + if (_list->count < writeIndex) + _list->SetCountTo(writeIndex); - return ref _list->Get(index); + return ref _list->Get(index); +#if ENABLE_DEBUG_CHECKS + } +#endif } } - + public void Resize(uint newCapacity) where T : struct { unsafe { -#if DEBUG && !PROFILE_SVELTO +#if ENABLE_DEBUG_CHECKS if (_list == null) throw new Exception("NativeDynamicArray: null-access"); if (_hashType != TypeHash.hash) throw new Exception("NativeDynamicArray: not expected type used"); #endif - _list->Realloc((uint) newCapacity, _allocator); + +#if ENABLE_DEBUG_CHECKS + using (_threadSentinel.TestThreadSafety()) + { +#endif + _list->Realloc((uint)newCapacity, _allocator); + +#if ENABLE_DEBUG_CHECKS + } +#endif } } @@ -207,16 +265,24 @@ namespace Svelto.ECS.DataStructures { unsafe { -#if DEBUG && !PROFILE_SVELTO +#if ENABLE_DEBUG_CHECKS if (_list == null) throw new Exception("NativeDynamicArray: null-access"); if (_hashType != TypeHash.hash) throw new Exception("NativeDynamicArray: not expected type used"); #endif - uint structSize = (uint) MemoryUtilities.SizeOf(); - uint size = (uint) (count * structSize); + uint structSize = (uint)MemoryUtilities.SizeOf(); + uint size = (uint)(count * structSize); - _list->SetCountTo((uint) size); +#if ENABLE_DEBUG_CHECKS + using (_threadSentinel.TestThreadSafety()) + { +#endif + _list->SetCountTo((uint)size); + +#if ENABLE_DEBUG_CHECKS + } +#endif } } @@ -225,18 +291,27 @@ namespace Svelto.ECS.DataStructures { unsafe { -#if DEBUG && !PROFILE_SVELTO +#if ENABLE_DEBUG_CHECKS if (_list == null) throw new Exception("NativeDynamicArray: null-access"); if (_hashType != TypeHash.hash) throw new Exception("NativeDynamicArray: not expected type used"); - var structSize = (uint) MemoryUtilities.SizeOf(); - + var structSize = (uint)MemoryUtilities.SizeOf(); + if (_list->space - (int)structSize < 0) throw new Exception("NativeDynamicArray: no writing authorized"); #endif - _list->Add(item); + +#if ENABLE_DEBUG_CHECKS + using (_threadSentinel.TestThreadSafety()) + { +#endif + _list->Add(item); + +#if ENABLE_DEBUG_CHECKS + } +#endif } } @@ -245,7 +320,7 @@ namespace Svelto.ECS.DataStructures { unsafe { -#if DEBUG && !PROFILE_SVELTO +#if ENABLE_DEBUG_CHECKS if (_list == null) throw new Exception("NativeDynamicArray: null-access"); if (_hashType != TypeHash.hash) @@ -253,13 +328,22 @@ namespace Svelto.ECS.DataStructures if (Count() == 0) throw new Exception("NativeDynamicArray: empty array invalid operation"); #endif - var indexToMove = Count() - 1; - if (index < indexToMove) + +#if ENABLE_DEBUG_CHECKS + using (_threadSentinel.TestThreadSafety()) { - Set(index, Get((uint) indexToMove)); +#endif + var indexToMove = Count() - 1; + if (index < indexToMove) + { + Set(index, Get((uint)indexToMove)); + } + + _list->Pop(); + +#if ENABLE_DEBUG_CHECKS } - - _list->Pop(); +#endif } } @@ -268,38 +352,47 @@ namespace Svelto.ECS.DataStructures { unsafe { -#if DEBUG && !PROFILE_SVELTO +#if ENABLE_DEBUG_CHECKS if (_list == null) throw new Exception("NativeDynamicArray: null-access"); #endif - _list->Clear(); + +#if ENABLE_DEBUG_CHECKS + using (_threadSentinel.TestThreadSafety()) + { +#endif + _list->Clear(); + +#if ENABLE_DEBUG_CHECKS + } +#endif } } public unsafe T* ToPTR() where T : unmanaged { -#if DEBUG && !PROFILE_SVELTO +#if ENABLE_DEBUG_CHECKS if (_list == null) throw new Exception("NativeDynamicArray: null-access"); if (_hashType != TypeHash.hash) throw new Exception("NativeDynamicArray: not expected type used"); #endif - return (T*) _list->ptr; + return (T*)_list->ptr; } public IntPtr ToIntPTR() where T : struct { unsafe { -#if DEBUG && !PROFILE_SVELTO - if (_list == null) - throw new Exception("NativeDynamicArray: null-access"); - if (_hashType != TypeHash.hash) - throw new Exception("NativeDynamicArray: not expected type used"); +#if ENABLE_DEBUG_CHECKS + if (_list == null) + throw new Exception("NativeDynamicArray: null-access"); + if (_hashType != TypeHash.hash) + throw new Exception("NativeDynamicArray: not expected type used"); #endif - return (IntPtr) _list->ptr; + return (IntPtr)_list->ptr; } } @@ -307,7 +400,7 @@ namespace Svelto.ECS.DataStructures { unsafe { -#if DEBUG && !PROFILE_SVELTO +#if ENABLE_DEBUG_CHECKS if (_list == null) throw new Exception("NativeDynamicArray: null-access"); if (_hashType != TypeHash.hash) @@ -318,12 +411,20 @@ namespace Svelto.ECS.DataStructures var ret = new T[count]; var lengthToCopyInBytes = count * MemoryUtilities.SizeOf(); - fixed (void* handle = ret) +#if ENABLE_DEBUG_CHECKS + using (_threadSentinel.TestThreadSafety()) { - Unsafe.CopyBlock(handle, _list->ptr, (uint) lengthToCopyInBytes); +#endif + fixed (void* handle = ret) + { + Unsafe.CopyBlock(handle, _list->ptr, (uint)lengthToCopyInBytes); + } + + return ret; + +#if ENABLE_DEBUG_CHECKS } - - return ret; +#endif } } @@ -331,20 +432,27 @@ namespace Svelto.ECS.DataStructures { unsafe { -#if DEBUG && !PROFILE_SVELTO +#if ENABLE_DEBUG_CHECKS if (_list == null) throw new Exception("NativeDynamicArray: null-access"); if (_hashType != TypeHash.hash) throw new Exception("NativeDynamicArray: not expected type used"); #endif - var capacity = Capacity(); - var lengthToCopyInBytes = capacity * MemoryUtilities.SizeOf(); - var ret = new T[capacity]; + var capacity = Capacity(); + var ret = new T[capacity]; - fixed (void* handle = ret) +#if ENABLE_DEBUG_CHECKS + using (_threadSentinel.TestThreadSafety()) { - Unsafe.CopyBlock(handle, _list->ptr, (uint) lengthToCopyInBytes); +#endif + fixed (void* handle = ret) + { + MemoryUtilities.MemCpy((IntPtr)_list->ptr, 0, (IntPtr)handle, 0, (uint)capacity); + } + +#if ENABLE_DEBUG_CHECKS } +#endif return ret; } @@ -354,18 +462,24 @@ namespace Svelto.ECS.DataStructures { unsafe { -#if DEBUG && !PROFILE_SVELTO +#if ENABLE_DEBUG_CHECKS if (_list == null) throw new Exception("NativeDynamicArray: null-access"); if (_hashType != TypeHash.hash) throw new Exception("NativeDynamicArray: not expected type used"); #endif - var sizeOf = MemoryUtilities.SizeOf(); - //Unsafe.CopyBlock may not be memory overlapping safe (memcpy vs memmove) - Buffer.MemoryCopy(_list->ptr + (index + 1) * sizeOf, _list->ptr + index * sizeOf, _list->count - , (uint) ((Count() - (index + 1)) * sizeOf)); - _list->Pop(); +#if ENABLE_DEBUG_CHECKS + using (_threadSentinel.TestThreadSafety()) + { +#endif + MemoryUtilities.MemMove((IntPtr)_list->ptr, index + 1, index, (uint)(Count() - (index + 1))); + + _list->Pop(); + +#if ENABLE_DEBUG_CHECKS + } +#endif } } @@ -373,11 +487,18 @@ namespace Svelto.ECS.DataStructures { unsafe { - MemoryUtilities.MemClear((IntPtr) _list->ptr, (uint) _list->capacity); +#if ENABLE_DEBUG_CHECKS + using (_threadSentinel.TestThreadSafety()) + { +#endif + MemoryUtilities.MemClear((IntPtr)_list->ptr, (uint)_list->capacity); +#if ENABLE_DEBUG_CHECKS + } +#endif } } - -#if UNITY_COLLECTIONS || UNITY_JOBS || UNITY_BURST + +#if UNITY_COLLECTIONS || UNITY_JOBS || UNITY_BURST #if UNITY_BURST [Unity.Burst.NoAlias] #endif @@ -387,6 +508,9 @@ namespace Svelto.ECS.DataStructures #if DEBUG && !PROFILE_SVELTO int _hashType; #endif + + Sentinel _threadSentinel; + Allocator _allocator; } } \ No newline at end of file diff --git a/com.sebaslab.svelto.ecs/DataStructures/Unmanaged/NativeDynamicArrayCast.cs b/com.sebaslab.svelto.ecs/DataStructures/Unmanaged/NativeDynamicArrayCast.cs index 8eca9a2..4957926 100644 --- a/com.sebaslab.svelto.ecs/DataStructures/Unmanaged/NativeDynamicArrayCast.cs +++ b/com.sebaslab.svelto.ecs/DataStructures/Unmanaged/NativeDynamicArrayCast.cs @@ -1,9 +1,10 @@ -using System.Runtime.CompilerServices; +using System; +using System.Runtime.CompilerServices; using Svelto.Common; namespace Svelto.ECS.DataStructures { - public struct NativeDynamicArrayCast where T : struct + public struct NativeDynamicArrayCast:IDisposable where T : struct { public NativeDynamicArrayCast(uint size, Allocator allocator) { @@ -61,6 +62,18 @@ namespace Svelto.ECS.DataStructures [MethodImpl(MethodImplOptions.AggressiveInlining)] public NativeDynamicArray ToNativeArray() { return _array; } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Set(uint index, in T value) + { + _array.Set(index, value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Set(int index, in T value) + { + _array.Set((uint)index, value); + } public bool isValid => _array.isValid; diff --git a/com.sebaslab.svelto.ecs/DataStructures/Unmanaged/SharedNativeInt.cs b/com.sebaslab.svelto.ecs/DataStructures/Unmanaged/SharedNativeInt.cs index f5f9698..f7103cd 100644 --- a/com.sebaslab.svelto.ecs/DataStructures/Unmanaged/SharedNativeInt.cs +++ b/com.sebaslab.svelto.ecs/DataStructures/Unmanaged/SharedNativeInt.cs @@ -1,12 +1,54 @@ using System; +using System.Runtime.CompilerServices; using System.Threading; using Svelto.Common; namespace Svelto.ECS.DataStructures { + public struct SharedNative : IDisposable where T : struct, IDisposable + { +#if UNITY_COLLECTIONS || (UNITY_JOBS || UNITY_BURST) + [global::Unity.Collections.LowLevel.Unsafe.NativeDisableUnsafePtrRestriction] +#endif + unsafe IntPtr ptr; + + public SharedNative(in T value) + { + unsafe + { + ptr = MemoryUtilities.Alloc(1, Allocator.Persistent); + Unsafe.Write((void*)ptr, value); + } + } + + public void Dispose() + { + unsafe + { + Unsafe.AsRef((void*)ptr).Dispose(); + + MemoryUtilities.Free((IntPtr)ptr, Allocator.Persistent); + ptr = IntPtr.Zero; + } + } + + public ref T value + { + get + { + unsafe + { + DBC.ECS.Check.Require(ptr != null, "SharedNative has not been initialized"); + + return ref Unsafe.AsRef((void*)ptr); + } + } + } + } + public struct SharedNativeInt: IDisposable { -#if UNITY_COLLECTIONS || UNITY_JOBS || UNITY_BURST +#if UNITY_COLLECTIONS || (UNITY_JOBS || UNITY_BURST) [global::Unity.Collections.LowLevel.Unsafe.NativeDisableUnsafePtrRestriction] #endif unsafe int* data; diff --git a/com.sebaslab.svelto.ecs/DataStructures/Unmanaged/UnsafeBlob.cs b/com.sebaslab.svelto.ecs/DataStructures/Unmanaged/UnsafeBlob.cs index dd6b628..899021f 100644 --- a/com.sebaslab.svelto.ecs/DataStructures/Unmanaged/UnsafeBlob.cs +++ b/com.sebaslab.svelto.ecs/DataStructures/Unmanaged/UnsafeBlob.cs @@ -14,7 +14,7 @@ namespace Svelto.ECS.DataStructures /// Note: this must work inside burst, so it must follow burst restrictions /// It's a typeless native queue based on a ring-buffer model. This means that the writing head and the /// reading head always advance independently. If there is enough space left by dequeued elements, - /// the writing head will wrap around if it reaches the end of the array. The writing head cannot ever surpass the reading head. + /// the writing head will wrap around. The writing head cannot ever surpass the reading head. /// /// struct UnsafeBlob : IDisposable @@ -47,7 +47,7 @@ namespace Svelto.ECS.DataStructures internal Allocator allocator; [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal void Enqueue(in T item) where T : struct + internal void Enqueue(in T item) where T : struct //should be unmanaged, but it's not due to Svelto.ECS constraints. { unsafe { @@ -93,7 +93,7 @@ namespace Svelto.ECS.DataStructures [MethodImpl(MethodImplOptions.AggressiveInlining)] //The index returned is the index of the unwrapped ring. It must be wrapped again before to be used - internal ref T Reserve(out UnsafeArrayIndex index) where T : struct + internal ref T Reserve(out UnsafeArrayIndex index) where T : struct //should be unmanaged, but it's not due to Svelto.ECS constraints. { unsafe { @@ -119,7 +119,7 @@ namespace Svelto.ECS.DataStructures } [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal ref T AccessReserved(UnsafeArrayIndex index) where T : struct + internal ref T AccessReserved(UnsafeArrayIndex index) where T : struct //should be unmanaged, but it's not due to Svelto.ECS constraints. { unsafe { @@ -133,7 +133,7 @@ namespace Svelto.ECS.DataStructures } [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal T Dequeue() where T : struct + internal T Dequeue() where T : struct //should be unmanaged, but it's not due to Svelto.ECS constraints. { unsafe { @@ -176,36 +176,39 @@ namespace Svelto.ECS.DataStructures return item; } } - + /// - /// This version of Realloc unwraps a queue, but doesn't change the unwrapped index of existing elements. - /// In this way the previously indices will remain valid + /// This code unwraps the queue and resizes the array, but doesn't change the unwrapped index of existing elements. + /// In this way the previously reserved indices will remain valid /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal void Realloc(uint newCapacity) + internal void Grow() where T : struct //should be unmanaged, but it's not due to Svelto.ECS constraints. { unsafe { - //be sure it's multiple of 4. Assuming that what we write is aligned to 4, then we will always have aligned wrapped heads. + var sizeOf = MemoryUtilities.SizeOf(); + + var oldCapacity = capacity; + + uint newCapacity = (uint) ((oldCapacity + sizeOf) << 1); + //be sure it's multiple of 4. Assuming that what we write is aligned to 4, then we will always have aligned wrapped heads //the reading and writing head always increment in multiple of 4 newCapacity += MemoryUtilities.Pad4(newCapacity); byte* newPointer = null; -#if DEBUG && !PROFILE_SVELTO - if (newCapacity <= capacity) - throw new Exception("new capacity must be bigger than current"); -#endif newPointer = (byte*) MemoryUtilities.Alloc(newCapacity, allocator); //copy wrapped content if there is any var currentSize = _writeIndex - _readIndex; if (currentSize > 0) { - var oldReaderHead = _readIndex % capacity; - var writerHead = _writeIndex % capacity; + var oldReaderHead = _readIndex % oldCapacity; + var oldWriterHead = _writeIndex % oldCapacity; - //there was no wrapping - if (oldReaderHead < writerHead) + //Remembering that the unwrapped reader cannot ever surpass the unwrapped writer, if the reader is behind the writer + //it means that the writer didn't wrap. It's the natural position so the data can be copied with + //a single memcpy + if (oldReaderHead < oldWriterHead) { var newReaderHead = _readIndex % newCapacity; @@ -213,15 +216,21 @@ namespace Svelto.ECS.DataStructures } else { - var byteCountToEnd = capacity - oldReaderHead; - var newReaderHead = _readIndex % newCapacity; + //if the wrapped writer is behind the wrapped reader, it means the writer wrapped. Therefore + //I need to copy the data from the current wrapped reader to the end and then from the + //begin of the array to the current wrapped writer. + + var byteCountToEnd = oldCapacity - oldReaderHead; //bytes to copy from the reader to the end + var newReaderHead = _readIndex % newCapacity; #if DEBUG && !PROFILE_SVELTO - if (newReaderHead + byteCountToEnd + writerHead > newCapacity) + if (newReaderHead + byteCountToEnd + oldWriterHead > newCapacity) //basically the test is the old size must be less than the new capacity. throw new Exception("something is wrong with my previous assumptions"); #endif + //I am leaving on purpose gap at the begin of the new array if there is any, it will be + //anyway used once it's time to wrap. Unsafe.CopyBlock(newPointer + newReaderHead, ptr + oldReaderHead, byteCountToEnd); //from the old reader head to the end of the old array - Unsafe.CopyBlock(newPointer + newReaderHead + byteCountToEnd, ptr + 0, (uint) writerHead); //from the begin of the old array to the old writer head (rember the writerHead wrapped) + Unsafe.CopyBlock(newPointer + newReaderHead + byteCountToEnd, ptr + 0, (uint) oldWriterHead); //from the begin of the old array to the old writer head (rember the writerHead wrapped) } } @@ -231,7 +240,7 @@ namespace Svelto.ECS.DataStructures ptr = newPointer; capacity = newCapacity; - //_readIndex = 0; readIndex won't change to keep the previous reserved indices valid + //_readIndex = 0; the idea is that the old readIndex should remain unchanged. Remember this is the unwrapped index. _writeIndex = _readIndex + currentSize; } } @@ -260,4 +269,4 @@ namespace Svelto.ECS.DataStructures uint _writeIndex; uint _readIndex; } -} +} \ No newline at end of file diff --git a/com.sebaslab.svelto.ecs/Dispatcher/ReactiveValue.cs b/com.sebaslab.svelto.ecs/Dispatcher/ReactiveValue.cs index 122aabb..fab7018 100644 --- a/com.sebaslab.svelto.ecs/Dispatcher/ReactiveValue.cs +++ b/com.sebaslab.svelto.ecs/Dispatcher/ReactiveValue.cs @@ -21,7 +21,7 @@ namespace Svelto.ECS _subscriber = callback; if (notifyImmediately) - _subscriber(_senderID, initialValue); + _subscriber(senderID, initialValue); _senderID = senderID; _value = initialValue; diff --git a/com.sebaslab.svelto.ecs/Extensions/Unity/DOTS/Native/EnginesRoot.NativeOperation.cs b/com.sebaslab.svelto.ecs/Extensions/Native/EnginesRoot.NativeOperation.cs similarity index 72% rename from com.sebaslab.svelto.ecs/Extensions/Unity/DOTS/Native/EnginesRoot.NativeOperation.cs rename to com.sebaslab.svelto.ecs/Extensions/Native/EnginesRoot.NativeOperation.cs index 2ba76e4..62895df 100644 --- a/com.sebaslab.svelto.ecs/Extensions/Unity/DOTS/Native/EnginesRoot.NativeOperation.cs +++ b/com.sebaslab.svelto.ecs/Extensions/Native/EnginesRoot.NativeOperation.cs @@ -16,6 +16,7 @@ namespace Svelto.ECS //DBC.ECS.Check.Require(EntityDescriptorTemplate.descriptor.isUnmanaged(), "can't remove entities with not native types"); //todo: remove operation array and store entity descriptor hash in the return value //todo I maybe able to provide a _nativeSwap.SwapEntity + //todo make this work with base descriptors too _nativeRemoveOperations.Add(new NativeOperationRemove( EntityDescriptorTemplate.descriptor.componentsToBuild, TypeCache.type , memberName)); @@ -25,7 +26,7 @@ namespace Svelto.ECS NativeEntitySwap ProvideNativeEntitySwapQueue(string memberName) where T : IEntityDescriptor, new() { - // DBC.ECS.Check.Require(EntityDescriptorTemplate.descriptor.isUnmanaged(), "can't swap entities with not native types"); + // DBC.ECS.Check.Require(EntityDescriptorTemplate.descriptor.isUnmanaged(), "can't swap entities with not native types"); //todo: remove operation array and store entity descriptor hash in the return value _nativeSwapOperations.Add(new NativeOperationSwap(EntityDescriptorTemplate.descriptor.componentsToBuild , TypeCache.type, memberName)); @@ -33,19 +34,21 @@ namespace Svelto.ECS return new NativeEntitySwap(_nativeSwapOperationQueue, _nativeSwapOperations.count - 1); } - NativeEntityFactory ProvideNativeEntityFactoryQueue(string memberName) where T : IEntityDescriptor, new() + NativeEntityFactory ProvideNativeEntityFactoryQueue(string caller) where T : IEntityDescriptor, new() { - DBC.ECS.Check.Require(EntityDescriptorTemplate.descriptor.IsUnmanaged(), "can't build entities with not native types"); + DBC.ECS.Check.Require(EntityDescriptorTemplate.descriptor.IsUnmanaged() + , "can't build entities with not native types"); + DBC.ECS.Check.Require(string.IsNullOrEmpty(caller) == false, "an invalid caller has been provided"); //todo: remove operation array and store entity descriptor hash in the return value - _nativeAddOperations.Add( - new NativeOperationBuild(EntityDescriptorTemplate.descriptor.componentsToBuild, TypeCache.type, memberName)); + _nativeAddOperations.Add(new NativeOperationBuild(EntityDescriptorTemplate.descriptor.componentsToBuild + , TypeCache.type, caller)); return new NativeEntityFactory(_nativeAddOperationQueue, _nativeAddOperations.count - 1, _entityLocator); } void FlushNativeOperations(in PlatformProfiler profiler) { - using (profiler.Sample("Native Remove/Swap Operations")) + using (profiler.Sample("Native Remove Operations")) { var removeBuffersCount = _nativeRemoveOperationQueue.count; //todo, I don't like that this scans all the queues even if they are empty @@ -55,17 +58,23 @@ namespace Svelto.ECS while (buffer.IsEmpty() == false) { - var componentsIndex = buffer.Dequeue(); - var entityEGID = buffer.Dequeue(); - NativeOperationRemove nativeRemoveOperation = _nativeRemoveOperations[componentsIndex]; + var componentsIndex = buffer.Dequeue(); + var entityEGID = buffer.Dequeue(); + + ref NativeOperationRemove nativeRemoveOperation = ref _nativeRemoveOperations[componentsIndex]; + CheckRemoveEntityID(entityEGID, nativeRemoveOperation.entityDescriptorType , nativeRemoveOperation.caller); - QueueEntitySubmitOperation(new EntitySubmitOperation( - EntitySubmitOperationType.Remove, entityEGID, entityEGID - , nativeRemoveOperation.components)); + + QueueRemoveEntityOperation( + entityEGID, FindRealComponents(entityEGID, nativeRemoveOperation.components) + , nativeRemoveOperation.caller); } } + } + using (profiler.Sample("Native Swap Operations")) + { var swapBuffersCount = _nativeSwapOperationQueue.count; for (int i = 0; i < swapBuffersCount; i++) { @@ -76,28 +85,28 @@ namespace Svelto.ECS var componentsIndex = buffer.Dequeue(); var entityEGID = buffer.Dequeue(); - var componentBuilders = _nativeSwapOperations[componentsIndex].components; + ref var nativeSwapOperation = ref _nativeSwapOperations[componentsIndex]; - CheckRemoveEntityID(entityEGID.@from - , _nativeSwapOperations[componentsIndex].entityDescriptorType - , _nativeSwapOperations[componentsIndex].caller); - CheckAddEntityID(entityEGID.to, _nativeSwapOperations[componentsIndex].entityDescriptorType - , _nativeSwapOperations[componentsIndex].caller); + CheckRemoveEntityID(entityEGID.@from, nativeSwapOperation.entityDescriptorType + , nativeSwapOperation.caller); + CheckAddEntityID(entityEGID.to, nativeSwapOperation.entityDescriptorType + , nativeSwapOperation.caller); - QueueEntitySubmitOperation(new EntitySubmitOperation( - EntitySubmitOperationType.Swap, entityEGID.@from, entityEGID.to - , componentBuilders)); + QueueSwapEntityOperation(entityEGID.@from, entityEGID.to + , FindRealComponents(entityEGID.@from, nativeSwapOperation.components) + , nativeSwapOperation.caller); } } } + //todo: it feels weird that these builds in the transient entities database while it could build directly to the final one using (profiler.Sample("Native Add Operations")) { var addBuffersCount = _nativeAddOperationQueue.count; for (int i = 0; i < addBuffersCount; i++) { ref var buffer = ref _nativeAddOperationQueue.GetBuffer(i); - + //todo: I don't like to iterate a constant number of buffer and skip the empty ones while (buffer.IsEmpty() == false) { var componentsIndex = buffer.Dequeue(); @@ -105,21 +114,24 @@ namespace Svelto.ECS var reference = buffer.Dequeue(); var componentCounts = buffer.Dequeue(); - Check.Assert(egid.groupID.isInvalid == false, "invalid group detected, are you using new ExclusiveGroupStruct() instead of new ExclusiveGroup()?"); + Check.Assert(egid.groupID.isInvalid == false + , "invalid group detected, are you using new ExclusiveGroupStruct() instead of new ExclusiveGroup()?"); - var componentBuilders = _nativeAddOperations[componentsIndex].components; + ref var nativeOperation = ref _nativeAddOperations[componentsIndex]; #if DEBUG && !PROFILE_SVELTO - var entityDescriptorType = _nativeAddOperations[componentsIndex].entityDescriptorType; - CheckAddEntityID(egid, entityDescriptorType, _nativeAddOperations[componentsIndex].caller); + var entityDescriptorType = nativeOperation.entityDescriptorType; + CheckAddEntityID(egid, entityDescriptorType, nativeOperation.caller); #endif _entityLocator.SetReference(reference, egid); - var dic = EntityFactory.BuildGroupedEntities(egid, _groupedEntityToAdd, componentBuilders - , null + //todo: I reckon is not necessary to carry the components array in the native operation, it's enough to know the descriptor type + //however I guess this can work only if the type is hashed, which could be done with the burst type hash + var dic = EntityFactory.BuildGroupedEntities(egid, _groupedEntityToAdd + , nativeOperation.components, null #if DEBUG && !PROFILE_SVELTO , entityDescriptorType #endif - ); + ); var init = new EntityInitializer(egid, dic, reference); @@ -149,7 +161,7 @@ namespace Svelto.ECS FasterList _nativeRemoveOperations; FasterList _nativeSwapOperations; FasterList _nativeAddOperations; - + //todo: I very likely don't need to create one for each native entity factory, the same can be reused readonly AtomicNativeBags _nativeAddOperationQueue; readonly AtomicNativeBags _nativeRemoveOperationQueue; diff --git a/com.sebaslab.svelto.ecs/Extensions/Svelto/EntityNativeDBExtensions.cs b/com.sebaslab.svelto.ecs/Extensions/Native/EntityNativeDBExtensions.cs similarity index 98% rename from com.sebaslab.svelto.ecs/Extensions/Svelto/EntityNativeDBExtensions.cs rename to com.sebaslab.svelto.ecs/Extensions/Native/EntityNativeDBExtensions.cs index dc0f583..8b01184 100644 --- a/com.sebaslab.svelto.ecs/Extensions/Svelto/EntityNativeDBExtensions.cs +++ b/com.sebaslab.svelto.ecs/Extensions/Native/EntityNativeDBExtensions.cs @@ -1,7 +1,5 @@ using System.Runtime.CompilerServices; -using Svelto.Common; using Svelto.DataStructures; -using Svelto.DataStructures.Native; using Svelto.ECS.Internal; //todo: once using native memory for unmanaged struct will be optional, this will need to be moved under the Native namespace @@ -52,7 +50,6 @@ namespace Svelto.ECS return false; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool TryGetEntity(this EntitiesDB entitiesDb, uint entityID, ExclusiveGroupStruct @group, out T value) where T : unmanaged, IEntityComponent @@ -66,14 +63,13 @@ namespace Svelto.ECS value = default; return false; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool TryGetEntity(this EntitiesDB entitiesDb, EGID egid, out T value) where T : unmanaged, IEntityComponent { return TryGetEntity(entitiesDb, egid.entityID, egid.groupID, out value); } - + [MethodImpl(MethodImplOptions.AggressiveInlining)] static bool QueryEntitiesAndIndexInternal (this EntitiesDB entitiesDb, EGID entityGID, out uint index, out NB buffer) diff --git a/com.sebaslab.svelto.ecs/Extensions/Native/NativeEGIDMapper.cs b/com.sebaslab.svelto.ecs/Extensions/Native/NativeEGIDMapper.cs new file mode 100644 index 0000000..f8ccd7d --- /dev/null +++ b/com.sebaslab.svelto.ecs/Extensions/Native/NativeEGIDMapper.cs @@ -0,0 +1,117 @@ +using System; +using System.Runtime.CompilerServices; +using Svelto.Common; +using Svelto.DataStructures; +using Svelto.DataStructures.Native; +using Svelto.ECS.DataStructures; + +namespace Svelto.ECS.Native +{ + /// + /// Note: this class should really be ref struct by design. It holds the reference of a dictionary that can become + /// invalid. Unfortunately it can be a ref struct, because Jobs needs to hold if by paramater. So the deal is + /// that a job can use it as long as nothing else is modifying the entities database and the NativeEGIDMapper + /// is disposed right after the use. + /// + public readonly struct NativeEGIDMapper : IEGIDMapper where T : unmanaged, IEntityComponent + { + public static readonly NativeEGIDMapper empty = new NativeEGIDMapper + (default, new SharedNative>, + NativeStrategy, NativeStrategy>>( + new SveltoDictionary>, + NativeStrategy, NativeStrategy>(0, Allocator.Persistent))); + + public NativeEGIDMapper(ExclusiveGroupStruct groupStructId, + in SharedNative>, NativeStrategy, + NativeStrategy>> toNative) : this() + { + groupID = groupStructId; + _map = toNative; + } + + public int count => _map.value.count; + public Type entityType => TypeCache.type; + public ExclusiveGroupStruct groupID { get; } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ref T Entity(uint entityID) + { + var sveltoDictionary = _map.value; + +#if DEBUG && !PROFILE_SVELTO + if (sveltoDictionary.TryFindIndex(entityID, out var findIndex) == false) + throw new Exception($"Entity {entityID} not found in this group {groupID} - {typeof(T).Name}"); +#else + sveltoDictionary.TryFindIndex(entityID, out var findIndex); +#endif + return ref sveltoDictionary.GetDirectValueByRef(findIndex); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool TryGetEntity(uint entityID, out T value) + { + var sveltoDictionary = _map.value; + if (sveltoDictionary.count > 0 && sveltoDictionary.TryFindIndex(entityID, out var index)) + { + var values = sveltoDictionary.unsafeValues.ToRealBuffer(); + value = values[index]; + return true; + } + + value = default; + return false; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public NB GetArrayAndEntityIndex(uint entityID, out uint index) + { + var sveltoDictionary = _map.value; + if (sveltoDictionary.TryFindIndex(entityID, out index)) + return sveltoDictionary.unsafeValues.ToRealBuffer(); + +#if DEBUG && !PROFILE_SVELTO + throw new ECSException("Entity not found"); +#else + return default; +#endif + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool TryGetArrayAndEntityIndex(uint entityID, out uint index, out NB array) + { + index = 0; + var sveltoDictionary = _map.value; + + if (sveltoDictionary.count > 0 && sveltoDictionary.TryFindIndex(entityID, out index)) + { + array = sveltoDictionary.unsafeValues.ToRealBuffer(); + return true; + } + + array = default; + return false; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Exists(uint idEntityId) + { + var sveltoDictionary = _map.value; + return sveltoDictionary.count > 0 && sveltoDictionary.TryFindIndex(idEntityId, out _); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public uint GetIndex(uint entityID) + { + return _map.value.GetIndex(entityID); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool FindIndex(uint valueKey, out uint index) + { + return _map.value.TryFindIndex(valueKey, out index); + } + + readonly SharedNative>, NativeStrategy, + NativeStrategy>> _map; + } +} \ No newline at end of file diff --git a/com.sebaslab.svelto.ecs/Extensions/Native/NativeEGIDMultiMapper.cs b/com.sebaslab.svelto.ecs/Extensions/Native/NativeEGIDMultiMapper.cs new file mode 100644 index 0000000..4941076 --- /dev/null +++ b/com.sebaslab.svelto.ecs/Extensions/Native/NativeEGIDMultiMapper.cs @@ -0,0 +1,78 @@ +using System; +using Svelto.DataStructures; +using Svelto.DataStructures.Native; +using Svelto.ECS.DataStructures; + +namespace Svelto.ECS.Native +{ + /// + /// Note: this class should really be ref struct by design. It holds the reference of a dictionary that can become + /// invalid. Unfortunately it can be a ref struct, because Jobs needs to hold if by paramater. So the deal is + /// that a job can use it as long as nothing else is modifying the entities database and the NativeEGIDMultiMapper + /// is disposed right after the use. + /// + ///WARNING: REMEMBER THIS MUST BE DISPOSED OF, AS IT USES NATIVE MEMORY. IT WILL LEAK MEMORY OTHERWISE + /// + /// + public struct NativeEGIDMultiMapper : IDisposable where T : unmanaged, IEntityComponent + { + public NativeEGIDMultiMapper(in SveltoDictionary< + /*key */ExclusiveGroupStruct, + /*value*/ + SharedNative>, NativeStrategy, + NativeStrategy>>, + /*strategy to store the key*/ NativeStrategy>, + /*strategy to store the value*/ + NativeStrategy>, + NativeStrategy, NativeStrategy>>>, NativeStrategy> dictionary) + { + _dic = dictionary; + } + + public int count => (int)_dic.count; + + public void Dispose() + { + _dic.Dispose(); + } + + public ref T Entity(EGID entity) + { +#if DEBUG && !PROFILE_SVELTO + if (Exists(entity) == false) + throw new Exception($"NativeEGIDMultiMapper: Entity not found {entity}"); +#endif + ref var sveltoDictionary = ref _dic.GetValueByRef(entity.groupID); + return ref sveltoDictionary.value.GetValueByRef(entity.entityID); + } + + public uint GetIndex(EGID entity) + { +#if DEBUG && !PROFILE_SVELTO + if (Exists(entity) == false) + throw new Exception($"NativeEGIDMultiMapper: Entity not found {entity}"); +#endif + ref var sveltoDictionary = ref _dic.GetValueByRef(entity.groupID); + return sveltoDictionary.value.GetIndex(entity.entityID); + } + + public bool Exists(EGID entity) + { + return _dic.TryFindIndex(entity.groupID, out var index) && + _dic.GetDirectValueByRef(index).value.ContainsKey(entity.entityID); + } + + public bool TryGetEntity(EGID entity, out T component) + { + component = default; + return _dic.TryFindIndex(entity.groupID, out var index) && + _dic.GetDirectValueByRef(index).value.TryGetValue(entity.entityID, out component); + } + + SveltoDictionary>, NativeStrategy, + NativeStrategy>>, NativeStrategy>, NativeStrategy< + SharedNative>, NativeStrategy, + NativeStrategy>>>, NativeStrategy> _dic; + } +} \ No newline at end of file diff --git a/com.sebaslab.svelto.ecs/Extensions/Unity/DOTS/Native/NativeEntityFactory.cs b/com.sebaslab.svelto.ecs/Extensions/Native/NativeEntityFactory.cs similarity index 59% rename from com.sebaslab.svelto.ecs/Extensions/Unity/DOTS/Native/NativeEntityFactory.cs rename to com.sebaslab.svelto.ecs/Extensions/Native/NativeEntityFactory.cs index 87f9cf3..1a01731 100644 --- a/com.sebaslab.svelto.ecs/Extensions/Unity/DOTS/Native/NativeEntityFactory.cs +++ b/com.sebaslab.svelto.ecs/Extensions/Native/NativeEntityFactory.cs @@ -5,7 +5,7 @@ namespace Svelto.ECS.Native { public readonly struct NativeEntityFactory { - internal NativeEntityFactory(AtomicNativeBags addOperationQueue, int index, EnginesRoot.LocatorMap entityLocator) + internal NativeEntityFactory(AtomicNativeBags addOperationQueue, int index, EnginesRoot.EntityReferenceMap entityLocator) { _index = index; _addOperationQueue = addOperationQueue; @@ -16,17 +16,18 @@ namespace Svelto.ECS.Native (uint eindex, ExclusiveBuildGroup exclusiveBuildGroup, int threadIndex) { EntityReference reference = _entityLocator.ClaimReference(); - NativeBag unsafeBuffer = _addOperationQueue.GetBuffer(threadIndex + 1); + NativeBag bagPerEntityPerThread = _addOperationQueue.GetBuffer(threadIndex + 1); - unsafeBuffer.Enqueue(_index); //each native ECS native operation is stored in an array, each request to perform a native operation in a queue. _index is the index of the operation in the array that will be dequeued later - unsafeBuffer.Enqueue(new EGID(eindex, exclusiveBuildGroup)); - unsafeBuffer.Enqueue(reference); + bagPerEntityPerThread.Enqueue(_index); //each native ECS native operation is stored in an array, each request to perform a native operation in a queue. _index is the index of the operation in the array that will be dequeued later + bagPerEntityPerThread.Enqueue(new EGID(eindex, exclusiveBuildGroup)); + bagPerEntityPerThread.Enqueue(reference); //NativeEntityInitializer is quite a complex beast. It holds the starting values of the component set by the user. These components must be later dequeued and in order to know how many components //must be dequeued, a count must be used. The space to hold the count is then reserved in the queue and index will be used access the count later on through NativeEntityInitializer so it can increment it. - unsafeBuffer.ReserveEnqueue(out var index) = 0; + //index is not the number of components of the entity, it's just the number of components that the user decide to initialise + bagPerEntityPerThread.ReserveEnqueue(out var index) = 0; - return new NativeEntityInitializer(unsafeBuffer, index, reference); + return new NativeEntityInitializer(bagPerEntityPerThread, index, reference); } public NativeEntityInitializer BuildEntity(EGID egid, int threadIndex) @@ -34,7 +35,7 @@ namespace Svelto.ECS.Native return BuildEntity(egid.entityID, egid.groupID, threadIndex); } - readonly EnginesRoot.LocatorMap _entityLocator; + readonly EnginesRoot.EntityReferenceMap _entityLocator; readonly AtomicNativeBags _addOperationQueue; readonly int _index; } diff --git a/com.sebaslab.svelto.ecs/Extensions/Unity/DOTS/Native/NativeEntityInitializer.cs b/com.sebaslab.svelto.ecs/Extensions/Native/NativeEntityInitializer.cs similarity index 70% rename from com.sebaslab.svelto.ecs/Extensions/Unity/DOTS/Native/NativeEntityInitializer.cs rename to com.sebaslab.svelto.ecs/Extensions/Native/NativeEntityInitializer.cs index e067f6c..d4b1000 100644 --- a/com.sebaslab.svelto.ecs/Extensions/Unity/DOTS/Native/NativeEntityInitializer.cs +++ b/com.sebaslab.svelto.ecs/Extensions/Native/NativeEntityInitializer.cs @@ -1,3 +1,4 @@ +#if UNITY_NATIVE //at the moment I am still considering NativeOperations useful only for Unity using Svelto.ECS.DataStructures; namespace Svelto.ECS.Native @@ -19,12 +20,15 @@ namespace Svelto.ECS.Native { uint id = EntityComponentID.ID.Data; - _unsafeBuffer.AccessReserved(_index)++; + _unsafeBuffer.AccessReserved(_index)++; //number of components added so far + //Since NativeEntityInitializer is a ref struct, it guarantees that I am enqueueing components of the + //last entity built _unsafeBuffer.Enqueue(id); _unsafeBuffer.Enqueue(component); } public EntityReference reference => _reference; } -} \ No newline at end of file +} +#endif \ No newline at end of file diff --git a/com.sebaslab.svelto.ecs/Extensions/Unity/DOTS/Native/NativeEntityRemove.cs b/com.sebaslab.svelto.ecs/Extensions/Native/NativeEntityRemove.cs similarity index 100% rename from com.sebaslab.svelto.ecs/Extensions/Unity/DOTS/Native/NativeEntityRemove.cs rename to com.sebaslab.svelto.ecs/Extensions/Native/NativeEntityRemove.cs diff --git a/com.sebaslab.svelto.ecs/Extensions/Unity/DOTS/Native/NativeEntitySwap.cs b/com.sebaslab.svelto.ecs/Extensions/Native/NativeEntitySwap.cs similarity index 100% rename from com.sebaslab.svelto.ecs/Extensions/Unity/DOTS/Native/NativeEntitySwap.cs rename to com.sebaslab.svelto.ecs/Extensions/Native/NativeEntitySwap.cs diff --git a/com.sebaslab.svelto.ecs/Extensions/Unity/DOTS/Native/UnityNativeEntityDBExtensions.cs b/com.sebaslab.svelto.ecs/Extensions/Native/UnityNativeEntityDBExtensions.cs similarity index 68% rename from com.sebaslab.svelto.ecs/Extensions/Unity/DOTS/Native/UnityNativeEntityDBExtensions.cs rename to com.sebaslab.svelto.ecs/Extensions/Native/UnityNativeEntityDBExtensions.cs index 6ad7338..0254f4a 100644 --- a/com.sebaslab.svelto.ecs/Extensions/Unity/DOTS/Native/UnityNativeEntityDBExtensions.cs +++ b/com.sebaslab.svelto.ecs/Extensions/Native/UnityNativeEntityDBExtensions.cs @@ -1,15 +1,15 @@ -#if UNITY_NATIVE using System.Runtime.CompilerServices; using Svelto.Common; using Svelto.DataStructures; using Svelto.DataStructures.Native; +using Svelto.ECS.DataStructures; using Svelto.ECS.Internal; namespace Svelto.ECS.Native { public static class UnityNativeEntityDBExtensions { - internal static NativeEGIDMapper ToNativeEGIDMapper(this TypeSafeDictionary dic, + static NativeEGIDMapper ToNativeEGIDMapper(this TypeSafeDictionary dic, ExclusiveGroupStruct groupStructId) where T : unmanaged, IEntityComponent { var mapper = new NativeEGIDMapper(groupStructId, dic.implUnmgd); @@ -32,7 +32,7 @@ namespace Svelto.ECS.Native out NativeEGIDMapper mapper) where T : unmanaged, IEntityComponent { - mapper = default; + mapper = NativeEGIDMapper.empty; if (entitiesDb.SafeQueryEntityDictionary(groupStructId, out var typeSafeDictionary) == false || typeSafeDictionary.count == 0) return false; @@ -43,21 +43,25 @@ namespace Svelto.ECS.Native } [MethodImpl(MethodImplOptions.AggressiveInlining)] + ///Note: if I use a SharedNativeSveltoDictionary for implUnmg, I may be able to cache NativeEGIDMultiMapper + /// and reuse it public static NativeEGIDMultiMapper QueryNativeMappedEntities(this EntitiesDB entitiesDb, - LocalFasterReadOnlyList groups, Allocator allocator) + LocalFasterReadOnlyList groups, Allocator allocator) where T : unmanaged, IEntityComponent { - var dictionary = new SveltoDictionary>, NativeStrategy, NativeStrategy>, //value - NativeStrategy>, //strategy to store the key - NativeStrategy>, NativeStrategy, NativeStrategy>>, NativeStrategy> //strategy to store the value + var dictionary = new SveltoDictionary< + /*key */ExclusiveGroupStruct, + /*value*/SharedNative>, NativeStrategy, NativeStrategy>>, + /*strategy to store the key*/ NativeStrategy>, + /*strategy to store the value*/NativeStrategy< + SharedNative>, NativeStrategy, NativeStrategy>>> + , NativeStrategy> ((uint) groups.count, allocator); foreach (var group in groups) { if (entitiesDb.SafeQueryEntityDictionary(group, out var typeSafeDictionary) == true) - if (typeSafeDictionary.count > 0) + //if (typeSafeDictionary.count > 0) dictionary.Add(group, ((TypeSafeDictionary)typeSafeDictionary).implUnmgd); } @@ -65,4 +69,3 @@ namespace Svelto.ECS.Native } } } -#endif \ No newline at end of file diff --git a/com.sebaslab.svelto.ecs/Extensions/Svelto/AllGroupsEnumerable.cs b/com.sebaslab.svelto.ecs/Extensions/Svelto/AllGroupsEnumerable.cs index ca0f099..7961fc7 100644 --- a/com.sebaslab.svelto.ecs/Extensions/Svelto/AllGroupsEnumerable.cs +++ b/com.sebaslab.svelto.ecs/Extensions/Svelto/AllGroupsEnumerable.cs @@ -38,17 +38,16 @@ namespace Svelto.ECS while (_db.MoveNext() == true) { var group = _db.Current; - if (group.Key.IsEnabled() == false) + if (group.key.IsEnabled() == false) continue; - ITypeSafeDictionary typeSafeDictionary = @group.Value as ITypeSafeDictionary; + ITypeSafeDictionary typeSafeDictionary = @group.value as ITypeSafeDictionary; if (typeSafeDictionary.count == 0) continue; - - _array.collection = new EntityCollection(typeSafeDictionary.GetValues(out var count), count); - _array.@group = group.Key; - + _array.collection = new EntityCollection(typeSafeDictionary.GetValues(out var count), count, + typeSafeDictionary.entityIDs); + _array.@group = group.key; return true; } diff --git a/com.sebaslab.svelto.ecs/Extensions/Svelto/EntitiesDBFiltersExtension.cs b/com.sebaslab.svelto.ecs/Extensions/Svelto/EntitiesDBFiltersExtension.cs index 996345d..5699381 100644 --- a/com.sebaslab.svelto.ecs/Extensions/Svelto/EntitiesDBFiltersExtension.cs +++ b/com.sebaslab.svelto.ecs/Extensions/Svelto/EntitiesDBFiltersExtension.cs @@ -5,10 +5,10 @@ namespace Svelto.ECS { public static class EntitiesDBFiltersExtension { - public static bool AddEntityToFilter(this EntitiesDB.Filters filters, int filtersID, EGID egid, N mapper) where N : IEGIDMultiMapper + public static bool AddEntityToFilter(this EntitiesDB.LegacyFilters legacyFilters, int filtersID, EGID egid, N mapper) where N : IEGIDMultiMapper { ref var filter = - ref filters.CreateOrGetFilterForGroup(filtersID, egid.groupID, new RefWrapperType(mapper.entityType)); + ref legacyFilters.CreateOrGetFilterForGroup(filtersID, egid.groupID, new RefWrapperType(mapper.entityType)); return filter.Add(egid.entityID, mapper); } diff --git a/com.sebaslab.svelto.ecs/Extensions/Svelto/EntityCollectionExtension.cs b/com.sebaslab.svelto.ecs/Extensions/Svelto/EntityCollectionExtension.cs index 5f68988..316db5b 100644 --- a/com.sebaslab.svelto.ecs/Extensions/Svelto/EntityCollectionExtension.cs +++ b/com.sebaslab.svelto.ecs/Extensions/Svelto/EntityCollectionExtension.cs @@ -1,251 +1,242 @@ using System.Runtime.CompilerServices; using Svelto.DataStructures; -using Svelto.DataStructures.Native; using Svelto.ECS.Hybrid; +using Svelto.ECS.Internal; namespace Svelto.ECS { public static class EntityCollectionExtension { [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Deconstruct - (in this EntityCollection ec, out NB buffer, out int count) where T1 : unmanaged, IEntityComponent + public static void Deconstruct(in this EntityCollection ec, out NB buffer, out int count) + where T1 : unmanaged, IEntityComponent { buffer = ec._nativedBuffer; - count = (int) ec.count; + count = (int)ec.count; } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Deconstruct - (in this EntityCollection ec, out NB buffer1, out NB buffer2, out int count) - where T1 : unmanaged, IEntityComponent where T2 : unmanaged, IEntityComponent + public static void Deconstruct(in this EntityCollection ec, out NB buffer, + out NativeEntityIDs entityIDs, out int count) where T1 : unmanaged, IEntityComponent { - buffer1 = ec.buffer1._nativedBuffer; - buffer2 = ec.buffer2._nativedBuffer; - count = (int) ec.count; + buffer = ec._nativedBuffer; + count = (int)ec.count; + entityIDs = ec._nativedIndices; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Deconstruct(in this EntityCollection ec, out NB buffer1, + out NB buffer2, out NativeEntityIDs entityIDs, out int count) where T1 : unmanaged, IEntityComponent + where T2 : unmanaged, IEntityComponent + { + buffer1 = ec.buffer1._nativedBuffer; + buffer2 = ec.buffer2._nativedBuffer; + count = ec.count; + entityIDs = ec.buffer1._nativedIndices; } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Deconstruct - (in this EntityCollection ec, out NB buffer1, out NB buffer2, out NB buffer3 - , out int count) where T1 : unmanaged, IEntityComponent - where T2 : unmanaged, IEntityComponent - where T3 : unmanaged, IEntityComponent + public static void Deconstruct(in this EntityCollection ec, out NB buffer1, + out NB buffer2, out int count) where T1 : unmanaged, IEntityComponent + where T2 : unmanaged, IEntityComponent { buffer1 = ec.buffer1._nativedBuffer; buffer2 = ec.buffer2._nativedBuffer; - buffer3 = ec.buffer3._nativedBuffer; - count = (int) ec.count; + count = (int)ec.count; } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Deconstruct - (in this EntityCollection ec, out NB buffer1, out NB buffer2, out NB buffer3 - , out NB buffer4, out int count) where T1 : unmanaged, IEntityComponent - where T2 : unmanaged, IEntityComponent - where T3 : unmanaged, IEntityComponent - where T4 : unmanaged, IEntityComponent + public static void Deconstruct(in this EntityCollection ec, out NB buffer1, + out NB buffer2, out NB buffer3, out int count) where T1 : unmanaged, IEntityComponent + where T2 : unmanaged, IEntityComponent + where T3 : unmanaged, IEntityComponent { buffer1 = ec.buffer1._nativedBuffer; buffer2 = ec.buffer2._nativedBuffer; buffer3 = ec.buffer3._nativedBuffer; - buffer4 = ec.buffer4._nativedBuffer; - count = (int) ec.count; + count = (int)ec.count; } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static BT> ToBuffer(in this EntityCollection ec) where T1 : unmanaged, IEntityComponent + public static void Deconstruct(in this EntityCollection ec, out NB buffer1, + out NB buffer2, out NB buffer3, out NativeEntityIDs entityIDs, out int count) + where T1 : unmanaged, IEntityComponent + where T2 : unmanaged, IEntityComponent + where T3 : unmanaged, IEntityComponent { - return new BT>(ec._nativedBuffer, ec.count); + buffer1 = ec.buffer1._nativedBuffer; + buffer2 = ec.buffer2._nativedBuffer; + buffer3 = ec.buffer3._nativedBuffer; + count = (int)ec.count; + entityIDs = ec.buffer1._nativedIndices; } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static BT, NB> ToBuffers - (in this EntityCollection ec) - where T2 : unmanaged, IEntityComponent where T1 : unmanaged, IEntityComponent + public static void Deconstruct(in this EntityCollection ec, out NB buffer1, + out NB buffer2, out NB buffer3, out NB buffer4, out int count) + where T1 : unmanaged, IEntityComponent + where T2 : unmanaged, IEntityComponent + where T3 : unmanaged, IEntityComponent + where T4 : unmanaged, IEntityComponent { - return new BT, NB>(ec.buffer1._nativedBuffer, ec.buffer2._nativedBuffer, ec.count); + buffer1 = ec.buffer1._nativedBuffer; + buffer2 = ec.buffer2._nativedBuffer; + buffer3 = ec.buffer3._nativedBuffer; + buffer4 = ec.buffer4._nativedBuffer; + count = (int)ec.count; } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static BT, NB, NB> ToBuffers - (in this EntityCollection ec) - where T2 : unmanaged, IEntityComponent + public static void Deconstruct(in this EntityCollection ec, out NB buffer1, + out NB buffer2, out NB buffer3, out NB buffer4, out NativeEntityIDs entityIDs, out int count) where T1 : unmanaged, IEntityComponent + where T2 : unmanaged, IEntityComponent where T3 : unmanaged, IEntityComponent + where T4 : unmanaged, IEntityComponent { - return new BT, NB, NB>(ec.buffer1._nativedBuffer, ec.buffer2._nativedBuffer - , ec.buffer3._nativedBuffer, ec.count); + buffer1 = ec.buffer1._nativedBuffer; + buffer2 = ec.buffer2._nativedBuffer; + buffer3 = ec.buffer3._nativedBuffer; + buffer4 = ec.buffer4._nativedBuffer; + entityIDs = ec.buffer1._nativedIndices; + count = (int)ec.count; } } public static class EntityCollectionExtensionB { [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Deconstruct - (in this EntityCollection ec, out MB buffer, out int count) where T1 : struct, IEntityViewComponent + public static void Deconstruct(in this EntityCollection ec, out MB buffer, out int count) + where T1 : struct, IEntityViewComponent { buffer = ec._managedBuffer; - count = (int) ec.count; + count = (int)ec.count; } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static BT> ToBuffer(in this EntityCollection ec) where T1 : struct, IEntityViewComponent + public static void Deconstruct(in this EntityCollection ec, out MB buffer, + out ManagedEntityIDs entityIDs, out int count) where T1 : struct, IEntityViewComponent { - return new BT>(ec._managedBuffer, ec.count); + buffer = ec._managedBuffer; + count = (int)ec.count; + entityIDs = ec._managedIndices; } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Deconstruct - (in this EntityCollection ec, out MB buffer1, out MB buffer2, out int count) - where T1 : struct, IEntityViewComponent where T2 : struct, IEntityViewComponent + public static void Deconstruct(in this EntityCollection ec, out MB buffer1, + out MB buffer2, out int count) where T1 : struct, IEntityViewComponent + where T2 : struct, IEntityViewComponent { buffer1 = ec.buffer1._managedBuffer; buffer2 = ec.buffer2._managedBuffer; - count = (int) ec.count; + count = (int)ec.count; } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static (MB buffer1, MB buffer2, uint count) ToBuffers - (in this EntityCollection ec) - where T2 : struct, IEntityViewComponent where T1 : struct, IEntityViewComponent + public static void Deconstruct(in this EntityCollection ec, out MB buffer1, + out MB buffer2, out ManagedEntityIDs entityIDs, out int count) where T1 : struct, IEntityViewComponent + where T2 : struct, IEntityViewComponent { - return (ec.buffer1._managedBuffer, ec.buffer2._managedBuffer, ec.count); + buffer1 = ec.buffer1._managedBuffer; + buffer2 = ec.buffer2._managedBuffer; + count = (int)ec.count; + entityIDs = ec.buffer1._managedIndices; } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Deconstruct - (in this EntityCollection ec, out MB buffer1, out MB buffer2, out MB buffer3 - , out int count) where T1 : struct, IEntityViewComponent - where T2 : struct, IEntityViewComponent - where T3 : struct, IEntityViewComponent + public static void Deconstruct(in this EntityCollection ec, out MB buffer1, + out MB buffer2, out MB buffer3, out int count) where T1 : struct, IEntityViewComponent + where T2 : struct, IEntityViewComponent + where T3 : struct, IEntityViewComponent { buffer1 = ec.buffer1._managedBuffer; buffer2 = ec.buffer2._managedBuffer; buffer3 = ec.buffer3._managedBuffer; - count = (int) ec.count; + count = (int)ec.count; } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static (MB buffer1, MB buffer2, MB buffer3, uint count) ToBuffers - (in this EntityCollection ec) - where T2 : struct, IEntityViewComponent + public static void Deconstruct(in this EntityCollection ec, out MB buffer1, + out MB buffer2, out MB buffer3, out ManagedEntityIDs entityIDs, out int count) where T1 : struct, IEntityViewComponent + where T2 : struct, IEntityViewComponent where T3 : struct, IEntityViewComponent { - return (ec.buffer1._managedBuffer, ec.buffer2._managedBuffer, ec.buffer3._managedBuffer, ec.count); + buffer1 = ec.buffer1._managedBuffer; + buffer2 = ec.buffer2._managedBuffer; + buffer3 = ec.buffer3._managedBuffer; + count = (int)ec.count; + entityIDs = ec.buffer1._managedIndices; } } public static class EntityCollectionExtensionC { [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static (NB buffer1, MB buffer2, uint count) ToBuffers - (in this EntityCollection ec) - where T1 : unmanaged, IEntityComponent where T2 : struct, IEntityViewComponent - { - return (ec.buffer1._nativedBuffer, ec.buffer2._managedBuffer, ec.count); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static (NB buffer1, MB buffer2, MB buffer3, uint count) ToBuffers - (in this EntityCollection ec) - where T1 : unmanaged, IEntityComponent + public static void Deconstruct(in this EntityCollection ec, out NB buffer1, + out MB buffer2, out int count) where T1 : unmanaged, IEntityComponent where T2 : struct, IEntityViewComponent - where T3 : struct, IEntityViewComponent - { - return (ec.buffer1._nativedBuffer, ec.buffer2._managedBuffer, ec.buffer3._managedBuffer, ec.count); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Deconstruct - (in this EntityCollection ec, out NB buffer1, out MB buffer2, out int count) - where T1 : unmanaged, IEntityComponent where T2 : struct, IEntityViewComponent { buffer1 = ec.buffer1._nativedBuffer; buffer2 = ec.buffer2._managedBuffer; - count = (int) ec.count; + count = (int)ec.count; } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Deconstruct - (in this EntityCollection ec, out NB buffer1, out MB buffer2, out MB buffer3, out int count) - where T1 : unmanaged, IEntityComponent + public static void Deconstruct(in this EntityCollection ec, out NB buffer1, + out MB buffer2, out MB buffer3, out int count) where T1 : unmanaged, IEntityComponent where T2 : struct, IEntityViewComponent where T3 : struct, IEntityViewComponent { buffer1 = ec.buffer1._nativedBuffer; buffer2 = ec.buffer2._managedBuffer; buffer3 = ec.buffer3._managedBuffer; - count = (int) ec.count; + count = (int)ec.count; } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Deconstruct - (in this EntityCollection ec, out NB buffer1, out NB buffer2, out NB buffer3 - , out MB buffer4, out int count) where T1 : unmanaged, IEntityComponent - where T2 : unmanaged, IEntityComponent - where T3 : unmanaged, IEntityComponent - where T4 : struct, IEntityViewComponent + public static void Deconstruct(in this EntityCollection ec, out NB buffer1, + out NB buffer2, out NB buffer3, out MB buffer4, out int count) + where T1 : unmanaged, IEntityComponent + where T2 : unmanaged, IEntityComponent + where T3 : unmanaged, IEntityComponent + where T4 : struct, IEntityViewComponent { buffer1 = ec.buffer1._nativedBuffer; buffer2 = ec.buffer2._nativedBuffer; buffer3 = ec.buffer3._nativedBuffer; buffer4 = ec.buffer4._managedBuffer; - count = (int) ec.count; + count = (int)ec.count; } } public static class EntityCollectionExtensionD { [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Deconstruct - (in this EntityCollection ec, out NB buffer1, out NB buffer2, out MB buffer3 - , out int count) where T1 : unmanaged, IEntityComponent - where T2 : unmanaged, IEntityComponent - where T3 : struct, IEntityViewComponent + public static void Deconstruct(in this EntityCollection ec, out NB buffer1, + out NB buffer2, out MB buffer3, out int count) where T1 : unmanaged, IEntityComponent + where T2 : unmanaged, IEntityComponent + where T3 : struct, IEntityViewComponent { buffer1 = ec.buffer1._nativedBuffer; buffer2 = ec.buffer2._nativedBuffer; buffer3 = ec.buffer3._managedBuffer; - count = (int) ec.count; + count = (int)ec.count; } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static (NB buffer1, NB buffer2, MB buffer3, uint count) ToBuffers - (in this EntityCollection ec) + public static void Deconstruct(in this EntityCollection ec, out NB buffer1, + out NB buffer2, out MB buffer3, out MB buffer4, out int count) where T1 : unmanaged, IEntityComponent where T2 : unmanaged, IEntityComponent where T3 : struct, IEntityViewComponent - { - return (ec.buffer1._nativedBuffer, ec.buffer2._nativedBuffer, ec.buffer3._managedBuffer, ec.count); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static BT, NB, NB, NB> ToBuffers - (in this EntityCollection ec) - where T2 : unmanaged, IEntityComponent - where T1 : unmanaged, IEntityComponent - where T3 : unmanaged, IEntityComponent - where T4 : unmanaged, IEntityComponent - { - return new BT, NB, NB, NB>(ec.buffer1._nativedBuffer, ec.buffer2._nativedBuffer - , ec.buffer3._nativedBuffer, ec.buffer4._nativedBuffer, ec.count); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Deconstruct - (in this EntityCollection ec, out NB buffer1, out NB buffer2, out MB buffer3 - , out MB buffer4, out int count) where T1 : unmanaged, IEntityComponent - where T2 : unmanaged, IEntityComponent - where T3 : struct, IEntityViewComponent - where T4 : struct, IEntityViewComponent + where T4 : struct, IEntityViewComponent { buffer1 = ec.buffer1._nativedBuffer; buffer2 = ec.buffer2._nativedBuffer; buffer3 = ec.buffer3._managedBuffer; buffer4 = ec.buffer4._managedBuffer; - count = (int) ec.count; + count = (int)ec.count; } } } \ No newline at end of file diff --git a/com.sebaslab.svelto.ecs/Extensions/Svelto/EntityManagedDBExtensions.cs b/com.sebaslab.svelto.ecs/Extensions/Svelto/EntityManagedDBExtensions.cs index 5ff4ace..a3483e1 100644 --- a/com.sebaslab.svelto.ecs/Extensions/Svelto/EntityManagedDBExtensions.cs +++ b/com.sebaslab.svelto.ecs/Extensions/Svelto/EntityManagedDBExtensions.cs @@ -1,5 +1,4 @@ using System.Runtime.CompilerServices; -using Svelto.Common; using Svelto.DataStructures; using Svelto.ECS.Hybrid; using Svelto.ECS.Internal; diff --git a/com.sebaslab.svelto.ecs/Extensions/Svelto/FilterGroupExtensions.cs b/com.sebaslab.svelto.ecs/Extensions/Svelto/FilterGroupExtensions.cs index 1faeffb..108f203 100644 --- a/com.sebaslab.svelto.ecs/Extensions/Svelto/FilterGroupExtensions.cs +++ b/com.sebaslab.svelto.ecs/Extensions/Svelto/FilterGroupExtensions.cs @@ -4,15 +4,15 @@ namespace Svelto.ECS { public static class FilterGroupExtensions { - public static bool Add(this FilterGroup filter, uint entityID, N mapper) where N : IEGIDMultiMapper + public static bool Add(this LegacyFilterGroup legacyFilter, uint entityID, N mapper) where N : IEGIDMultiMapper { #if DEBUG && !PROFILE_SVELTO - if (mapper.Exists(filter._exclusiveGroupStruct, entityID) == false) + if (mapper.Exists(legacyFilter._exclusiveGroupStruct, entityID) == false) throw new ECSException( - $"trying adding an entity {entityID} to filter {mapper.entityType} - {filter._ID} with group {filter._exclusiveGroupStruct}, but entity is not found! "); + $"trying adding an entity {entityID} to filter {mapper.entityType} - {legacyFilter._ID} with group {legacyFilter._exclusiveGroupStruct}, but entity is not found! "); #endif - return filter.InternalAdd(entityID, mapper.GetIndex(filter._exclusiveGroupStruct, entityID)); + return legacyFilter.InternalAdd(entityID, mapper.GetIndex(legacyFilter._exclusiveGroupStruct, entityID)); } } diff --git a/com.sebaslab.svelto.ecs/Extensions/Svelto/GroupsEnumerable.cs b/com.sebaslab.svelto.ecs/Extensions/Svelto/GroupsEnumerable.cs index 6425531..31d986b 100644 --- a/com.sebaslab.svelto.ecs/Extensions/Svelto/GroupsEnumerable.cs +++ b/com.sebaslab.svelto.ecs/Extensions/Svelto/GroupsEnumerable.cs @@ -40,11 +40,11 @@ namespace Svelto.ECS if (!exclusiveGroupStruct.IsEnabled()) continue; - var entityCollection1 = _entitiesDB.QueryEntities(exclusiveGroupStruct); + var entityCollection = _entitiesDB.QueryEntities(exclusiveGroupStruct); + if (entityCollection.count == 0) + continue; - var array = entityCollection1; - _buffers = new EntityCollection(array.buffer1, array.buffer2, array.buffer3 - , array.buffer4); + _buffers = entityCollection; break; } @@ -120,7 +120,7 @@ namespace Svelto.ECS if (!exclusiveGroupStruct.IsEnabled()) continue; - var entityCollection = _entitiesDB.QueryEntities(exclusiveGroupStruct); + EntityCollection entityCollection = _entitiesDB.QueryEntities(exclusiveGroupStruct); if (entityCollection.count == 0) continue; diff --git a/com.sebaslab.svelto.ecs/Extensions/Unity/DOTS/Jobs/DisposeJob.cs b/com.sebaslab.svelto.ecs/Extensions/Unity/DOTS/Jobs/DisposeJob.cs index 2ccc059..d3d3c35 100644 --- a/com.sebaslab.svelto.ecs/Extensions/Unity/DOTS/Jobs/DisposeJob.cs +++ b/com.sebaslab.svelto.ecs/Extensions/Unity/DOTS/Jobs/DisposeJob.cs @@ -2,7 +2,7 @@ using System; using Unity.Jobs; -namespace Svelto.ECS.Extensions.Unity +namespace Svelto.ECS.SveltoOnDOTS { public struct DisposeJob:IJob where T:struct,IDisposable { diff --git a/com.sebaslab.svelto.ecs/Extensions/Unity/DOTS/Jobs/IJobifiedEngine.cs b/com.sebaslab.svelto.ecs/Extensions/Unity/DOTS/Jobs/IJobifiedEngine.cs index cc7b021..d311da0 100644 --- a/com.sebaslab.svelto.ecs/Extensions/Unity/DOTS/Jobs/IJobifiedEngine.cs +++ b/com.sebaslab.svelto.ecs/Extensions/Unity/DOTS/Jobs/IJobifiedEngine.cs @@ -1,7 +1,7 @@ #if UNITY_JOBS using Unity.Jobs; -namespace Svelto.ECS.Extensions.Unity +namespace Svelto.ECS.SveltoOnDOTS { public interface IJobifiedEngine : IEngine { diff --git a/com.sebaslab.svelto.ecs/Extensions/Unity/DOTS/Jobs/SortedJobifiedEnginesGroup.cs b/com.sebaslab.svelto.ecs/Extensions/Unity/DOTS/Jobs/SortedJobifiedEnginesGroup.cs index eb58c32..66ab23e 100644 --- a/com.sebaslab.svelto.ecs/Extensions/Unity/DOTS/Jobs/SortedJobifiedEnginesGroup.cs +++ b/com.sebaslab.svelto.ecs/Extensions/Unity/DOTS/Jobs/SortedJobifiedEnginesGroup.cs @@ -3,7 +3,7 @@ using Svelto.DataStructures; using Unity.Jobs; using Svelto.Common; -namespace Svelto.ECS.Extensions.Unity +namespace Svelto.ECS.SveltoOnDOTS { /// /// Note sorted jobs run in serial @@ -15,38 +15,40 @@ namespace Svelto.ECS.Extensions.Unity { protected SortedJobifiedEnginesGroup(FasterList engines) { - _name = "SortedJobifiedEnginesGroup - "+this.GetType().Name; + _name = "SortedJobifiedEnginesGroup - " + this.GetType().Name; _instancedSequence = new Sequence(engines); } public JobHandle Execute(JobHandle inputHandles) { - var sequenceItems = _instancedSequence.items; + var sequenceItems = _instancedSequence.items; JobHandle combinedHandles = inputHandles; using (var profiler = new PlatformProfiler(_name)) { for (var index = 0; index < sequenceItems.count; index++) { var engine = sequenceItems[index]; - using (profiler.Sample(engine.name)) combinedHandles = engine.Execute(combinedHandles); + using (profiler.Sample(engine.name)) + combinedHandles = engine.Execute(combinedHandles); } } - return combinedHandles; + return combinedHandles; } public string name => _name; - - readonly string _name; + + readonly string _name; readonly Sequence _instancedSequence; - } - - public abstract class SortedJobifiedEnginesGroup: IJobifiedGroupEngine + } + + public abstract class + SortedJobifiedEnginesGroup : IJobifiedGroupEngine where SequenceOrder : struct, ISequenceOrder where Interface : class, IJobifiedEngine { protected SortedJobifiedEnginesGroup(FasterList engines) { - _name = "SortedJobifiedEnginesGroup - "+this.GetType().Name; + _name = "SortedJobifiedEnginesGroup - " + this.GetType().Name; _instancedSequence = new Sequence(engines); } @@ -58,7 +60,8 @@ namespace Svelto.ECS.Extensions.Unity for (var index = 0; index < sequenceItems.count; index++) { var engine = sequenceItems[index]; - using (profiler.Sample(engine.name)) combinedHandles = engine.Execute(combinedHandles, ref param); + using (profiler.Sample(engine.name)) + combinedHandles = engine.Execute(combinedHandles, ref param); } } @@ -66,8 +69,8 @@ namespace Svelto.ECS.Extensions.Unity } public string name => _name; - - readonly string _name; + + readonly string _name; readonly Sequence _instancedSequence; } } diff --git a/com.sebaslab.svelto.ecs/Extensions/Unity/DOTS/Jobs/UnityJobExtensions.cs b/com.sebaslab.svelto.ecs/Extensions/Unity/DOTS/Jobs/UnityJobExtensions.cs index de261d3..9a365fa 100644 --- a/com.sebaslab.svelto.ecs/Extensions/Unity/DOTS/Jobs/UnityJobExtensions.cs +++ b/com.sebaslab.svelto.ecs/Extensions/Unity/DOTS/Jobs/UnityJobExtensions.cs @@ -1,11 +1,11 @@ #if UNITY_JOBS using System; -using Svelto.ECS.Extensions.Unity; +using Svelto.ECS.SveltoOnDOTS; using Unity.Jobs; namespace Svelto.ECS { - public static class UnityJobExtensions2 + public static class UnityJobExtensions { public static JobHandle ScheduleDispose (this T1 disposable, JobHandle inputDeps) where T1 : struct, IDisposable diff --git a/com.sebaslab.svelto.ecs/Extensions/Unity/DOTS/Jobs/UnsortedJobifiedEnginesGroup.cs b/com.sebaslab.svelto.ecs/Extensions/Unity/DOTS/Jobs/UnsortedJobifiedEnginesGroup.cs index d68c773..cd9ed72 100644 --- a/com.sebaslab.svelto.ecs/Extensions/Unity/DOTS/Jobs/UnsortedJobifiedEnginesGroup.cs +++ b/com.sebaslab.svelto.ecs/Extensions/Unity/DOTS/Jobs/UnsortedJobifiedEnginesGroup.cs @@ -3,29 +3,30 @@ using Svelto.Common; using Svelto.DataStructures; using Unity.Jobs; -namespace Svelto.ECS.Extensions.Unity +namespace Svelto.ECS.SveltoOnDOTS { /// /// Note unsorted jobs run in parallel /// /// - public abstract class UnsortedJobifiedEnginesGroup:IJobifiedEngine where Interface : class, IJobifiedEngine + public abstract class UnsortedJobifiedEnginesGroup : IJobifiedEngine + where Interface : class, IJobifiedEngine { protected UnsortedJobifiedEnginesGroup(FasterList engines) { - _name = "JobifiedEnginesGroup - "+this.GetType().Name; - _engines = engines; + _name = "JobifiedEnginesGroup - " + this.GetType().Name; + _engines = engines; } - + protected UnsortedJobifiedEnginesGroup() { - _name = "JobifiedEnginesGroup - "+this.GetType().Name; + _name = "JobifiedEnginesGroup - " + this.GetType().Name; _engines = new FasterList(); } public JobHandle Execute(JobHandle inputHandles) { - var engines = _engines; + var engines = _engines; JobHandle combinedHandles = inputHandles; using (var profiler = new PlatformProfiler(_name)) { @@ -52,13 +53,14 @@ namespace Svelto.ECS.Extensions.Unity protected readonly FasterList _engines; readonly string _name; } - - public abstract class JobifiedEnginesGroup: IJobifiedGroupEngine where Interface : class, IJobifiedEngine + + public abstract class UnsortedJobifiedEnginesGroup : IJobifiedGroupEngine + where Interface : class, IJobifiedEngine { - protected JobifiedEnginesGroup(FasterList engines) + protected UnsortedJobifiedEnginesGroup(FasterList engines) { - _name = "JobifiedEnginesGroup - "+this.GetType().Name; - _engines = engines; + _name = "JobifiedEnginesGroup - " + this.GetType().Name; + _engines = engines; } public JobHandle Execute(JobHandle combinedHandles, ref Param _param) @@ -70,15 +72,16 @@ namespace Svelto.ECS.Extensions.Unity { var engine = engines[index]; using (profiler.Sample(engine.name)) - combinedHandles = JobHandle.CombineDependencies(combinedHandles, engine.Execute(combinedHandles, ref _param)); + combinedHandles = JobHandle.CombineDependencies(combinedHandles, + engine.Execute(combinedHandles, ref _param)); } } return combinedHandles; } - + public string name => _name; - + readonly string _name; readonly FasterReadOnlyList _engines; diff --git a/com.sebaslab.svelto.ecs/Extensions/Unity/DOTS/Native/NativeEGIDMapper.cs b/com.sebaslab.svelto.ecs/Extensions/Unity/DOTS/Native/NativeEGIDMapper.cs deleted file mode 100644 index e5f976d..0000000 --- a/com.sebaslab.svelto.ecs/Extensions/Unity/DOTS/Native/NativeEGIDMapper.cs +++ /dev/null @@ -1,108 +0,0 @@ -#if UNITY_NATIVE -using System; -using System.Runtime.CompilerServices; -using Svelto.Common; -using Svelto.DataStructures; -using Svelto.DataStructures.Native; - -namespace Svelto.ECS.Native -{ - /// - /// Note: this class should really be ref struct by design. It holds the reference of a dictionary that can become - /// invalid. Unfortunately it can be a ref struct, because Jobs needs to hold if by paramater. So the deal is - /// that a job can use it as long as nothing else is modifying the entities database and the NativeEGIDMapper - /// is disposed right after the use. - /// - public readonly struct NativeEGIDMapper : IEGIDMapper where T : unmanaged, IEntityComponent - { - public NativeEGIDMapper - (ExclusiveGroupStruct groupStructId - , SveltoDictionary>, NativeStrategy, NativeStrategy> - toNative) : this() - { - groupID = groupStructId; - _map = new SveltoDictionaryNative(); - - _map.UnsafeCast(toNative); - } - - public int count => _map.count; - public Type entityType => TypeCache.type; - public ExclusiveGroupStruct groupID { get; } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ref T Entity(uint entityID) - { -#if DEBUG - if (_map.TryFindIndex(entityID, out var findIndex) == false) - throw new Exception("Entity not found in this group ".FastConcat(typeof(T).ToString())); -#else - _map.TryFindIndex(entityID, out var findIndex); -#endif - return ref _map.GetDirectValueByRef(findIndex); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool TryGetEntity(uint entityID, out T value) - { - if (_map.count > 0 && _map.TryFindIndex(entityID, out var index)) - unsafe - { - value = Unsafe.AsRef(Unsafe.Add((void*) _map.GetValues(out _).ToNativeArray(out _) - , (int) index)); - return true; - } - - value = default; - return false; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public NB GetArrayAndEntityIndex(uint entityID, out uint index) - { - if (_map.TryFindIndex(entityID, out index)) - return new NB(_map.GetValues(out var count).ToNativeArray(out _), count); - -#if DEBUG - throw new ECSException("Entity not found"); -#else - return default; -#endif - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool TryGetArrayAndEntityIndex(uint entityID, out uint index, out NB array) - { - index = 0; - if (_map.count > 0 && _map.TryFindIndex(entityID, out index)) - { - array = new NB(_map.GetValues(out var count).ToNativeArray(out _), count); - return true; - } - - array = default; - return false; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool Exists(uint idEntityId) - { - return _map.count > 0 && _map.TryFindIndex(idEntityId, out _); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public uint GetIndex(uint entityID) - { - return _map.GetIndex(entityID); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool FindIndex(uint valueKey, out uint index) - { - return _map.TryFindIndex(valueKey, out index); - } - - readonly SveltoDictionaryNative _map; - } -} -#endif \ No newline at end of file diff --git a/com.sebaslab.svelto.ecs/Extensions/Unity/DOTS/Native/NativeEGIDMultiMapper.cs b/com.sebaslab.svelto.ecs/Extensions/Unity/DOTS/Native/NativeEGIDMultiMapper.cs deleted file mode 100644 index 2ed4b9d..0000000 --- a/com.sebaslab.svelto.ecs/Extensions/Unity/DOTS/Native/NativeEGIDMultiMapper.cs +++ /dev/null @@ -1,63 +0,0 @@ -#if UNITY_NATIVE -using System; -using Svelto.DataStructures; -using Svelto.DataStructures.Native; - -namespace Svelto.ECS.Native -{ - /// - /// Note: this class should really be ref struct by design. It holds the reference of a dictionary that can become - /// invalid. Unfortunately it can be a ref struct, because Jobs needs to hold if by paramater. So the deal is - /// that a job can use it as long as nothing else is modifying the entities database and the NativeEGIDMultiMapper - /// is disposed right after the use. - /// - public struct NativeEGIDMultiMapper : IDisposable where T : unmanaged, IEntityComponent - { - public NativeEGIDMultiMapper - (SveltoDictionary>, NativeStrategy, - NativeStrategy>, NativeStrategy>, - NativeStrategy>, NativeStrategy, - NativeStrategy>>, NativeStrategy> dictionary) - { - _dic = dictionary; - } - - public int count => (int) _dic.count; - - public void Dispose() - { - _dic.Dispose(); - } - - public ref T Entity(EGID entity) - { -#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); - } - - public bool Exists(EGID entity) - { - return _dic.TryFindIndex(entity.groupID, out var index) - && _dic.GetDirectValueByRef(index).ContainsKey(entity.entityID); - } - - 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); - } - - SveltoDictionary>, NativeStrategy, - NativeStrategy>, NativeStrategy>, - NativeStrategy>, NativeStrategy, - NativeStrategy>>, NativeStrategy> _dic; - } -} -#endif \ No newline at end of file diff --git a/com.sebaslab.svelto.ecs/Extensions/Unity/DOTS/UECS/DOTSSveltoEGID.cs b/com.sebaslab.svelto.ecs/Extensions/Unity/DOTS/UECS/DOTSSveltoEGID.cs new file mode 100644 index 0000000..2fddcf5 --- /dev/null +++ b/com.sebaslab.svelto.ecs/Extensions/Unity/DOTS/UECS/DOTSSveltoEGID.cs @@ -0,0 +1,55 @@ +#if UNITY_ECS +using Unity.Entities; + +namespace Svelto.ECS.SveltoOnDOTS +{ + /// + /// DOTS component to keep track of the associated Svelto.ECS entity + /// + public struct DOTSSveltoEGID : IComponentData + { + public EGID egid; + + public DOTSSveltoEGID(EGID egid) { this.egid = egid; } + } + + /// + /// DOTS component to be able to query all the DOTS entities found in a Svelto.ECS group + /// + public readonly struct DOTSSveltoGroupID : ISharedComponentData + { + readonly ExclusiveGroupStruct group; + + public DOTSSveltoGroupID(ExclusiveGroupStruct exclusiveGroup) + { + @group = exclusiveGroup; + } + + public static implicit operator ExclusiveGroupStruct(DOTSSveltoGroupID group) + { + return group.@group; + } + } + + struct DOTSEntityToSetup : ISharedComponentData + { + internal readonly ExclusiveGroupStruct group; + + public DOTSEntityToSetup(ExclusiveGroupStruct exclusiveGroup) + { + @group = exclusiveGroup; + } + } + + public interface IEntityComponentForDOTS: IEntityComponent + { + public Entity dotsEntity { get; set; } + } + + + public struct DOTSEntityComponent:IEntityComponentForDOTS + { + public Entity dotsEntity { get; set; } + } +} +#endif \ No newline at end of file diff --git a/com.sebaslab.svelto.ecs/Extensions/Unity/DOTS/UECS/EntityCommandBufferForSvelto.cs b/com.sebaslab.svelto.ecs/Extensions/Unity/DOTS/UECS/EntityCommandBufferForSvelto.cs new file mode 100644 index 0000000..b5d8907 --- /dev/null +++ b/com.sebaslab.svelto.ecs/Extensions/Unity/DOTS/UECS/EntityCommandBufferForSvelto.cs @@ -0,0 +1,215 @@ +#if UNITY_ECS +//#if !UNITY_ECS_050 +#define SLOW_SVELTO_ECB //Using EntityManager directly is much faster than using ECB because of the shared components +//#endif +using System; +using System.Runtime.CompilerServices; +using Unity.Entities; + +namespace Svelto.ECS.SveltoOnDOTS +{ + public readonly struct EntityCommandBufferForSvelto + { + internal EntityCommandBufferForSvelto(EntityCommandBuffer value, EntityManager manager) + { + _ECB = value; + _EManager = manager; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Entity CreatePureDOTSEntity(EntityArchetype jointArchetype) + { +#if SLOW_SVELTO_ECB + return _EManager.CreateEntity(jointArchetype); +#else + return _ECB.CreateEntity(jointArchetype); +#endif + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void SetComponent(Entity e, in T component) where T : struct, IComponentData + { +#if SLOW_SVELTO_ECB + _EManager.SetComponentData(e, component); +#else + _ECB.SetComponent(e, component); +#endif + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void SetSharedComponent(Entity e, in T component) where T : struct, ISharedComponentData + { +#if SLOW_SVELTO_ECB + _EManager.SetSharedComponentData(e, component); +#else + _ECB.SetSharedComponent(e, component); +#endif + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + ///Not ready for prime time with BURST yet, maybe with DOTS 1.0 + public static Entity CreateDOTSEntityOnSvelto(int sortKey, EntityCommandBuffer.ParallelWriter writer, + Entity entityComponentPrefabEntity, EGID egid, bool mustHandleDOTSComponent) + { +#if !SLOW_SVELTO_ECB + Entity dotsEntity = writer.Instantiate(sortKey, entityComponentPrefabEntity); + + //SharedComponentData can be used to group the DOTS ECS entities exactly like the Svelto ones + writer.AddSharedComponent(sortKey, dotsEntity, new DOTSSveltoGroupID(egid.groupID)); + writer.AddComponent(sortKey, dotsEntity, new DOTSSveltoEGID(egid)); + if (mustHandleDOTSComponent) + writer.AddSharedComponent(sortKey, dotsEntity, new DOTSEntityToSetup(egid.groupID)); + + return dotsEntity; +#endif + throw new NotSupportedException(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal Entity CreateDOTSEntityOnSvelto(Entity entityComponentPrefabEntity, EGID egid, + bool mustHandleDOTSComponent) + { +#if SLOW_SVELTO_ECB + Entity dotsEntity = _EManager.Instantiate(entityComponentPrefabEntity); + + //SharedComponentData can be used to group the DOTS ECS entities exactly like the Svelto ones + _EManager.AddSharedComponentData(dotsEntity, new DOTSSveltoGroupID(egid.groupID)); + _EManager.AddComponentData(dotsEntity, new DOTSSveltoEGID(egid)); + if (mustHandleDOTSComponent) + _EManager.AddSharedComponentData(dotsEntity, new DOTSEntityToSetup(egid.groupID)); +#else + Entity dotsEntity = _ECB.Instantiate(entityComponentPrefabEntity); + + //SharedComponentData can be used to group the DOTS ECS entities exactly like the Svelto ones + _ECB.AddSharedComponent(dotsEntity, new DOTSSveltoGroupID(egid.groupID)); + _ECB.AddComponent(dotsEntity, new DOTSSveltoEGID(egid)); + if (mustHandleDOTSComponent) + _ECB.AddSharedComponent(dotsEntity, new DOTSEntityToSetup(egid.groupID)); +#endif + + return dotsEntity; + } + + /// + /// This method assumes that the Svelto entity with EGID egid has also dotsEntityComponent + /// among the descriptors + /// + /// + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal Entity CreateDOTSEntityOnSvelto(EntityArchetype archetype, EGID egid, bool mustHandleDOTSComponent) + { +#if SLOW_SVELTO_ECB + Entity dotsEntity = _EManager.CreateEntity(archetype); + + //SharedComponentData can be used to group the DOTS ECS entities exactly like the Svelto ones + _EManager.AddSharedComponentData(dotsEntity, new DOTSSveltoGroupID(egid.groupID)); + _EManager.AddComponentData(dotsEntity, new DOTSSveltoEGID(egid)); + if (mustHandleDOTSComponent) + _EManager.AddSharedComponentData(dotsEntity, new DOTSEntityToSetup(egid.groupID)); +#else + Entity dotsEntity = _ECB.CreateEntity(archetype); + + //SharedComponentData can be used to group the DOTS ECS entities exactly like the Svelto ones + _ECB.AddSharedComponent(dotsEntity, new DOTSSveltoGroupID(egid.groupID)); + _ECB.AddComponent(dotsEntity, new DOTSSveltoEGID(egid)); + if (mustHandleDOTSComponent) + _ECB.AddSharedComponent(dotsEntity, new DOTSEntityToSetup(egid.groupID)); +#endif + + return dotsEntity; + } + + /// + /// in this case the user decided to create a DOTS entity that is self managed and not managed + /// by the framework + /// + /// + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal Entity CreateDOTSEntityUnmanaged(EntityArchetype archetype) + { +#if SLOW_SVELTO_ECB + return _EManager.CreateEntity(archetype); +#else + return _ECB.CreateEntity(archetype); +#endif + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void DestroyEntity(Entity e) + { +#if SLOW_SVELTO_ECB + _EManager.DestroyEntity(e); +#else + _ECB.DestroyEntity(e); +#endif + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void RemoveComponent(Entity dotsEntity) + { +#if SLOW_SVELTO_ECB + _EManager.RemoveComponent(dotsEntity); +#else + _ECB.RemoveComponent(dotsEntity); +#endif + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void AddComponent(Entity dotsEntity) where T : struct, IComponentData + { +#if SLOW_SVELTO_ECB + _EManager.AddComponent(dotsEntity); +#else + _ECB.AddComponent(dotsEntity); +#endif + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void AddComponent(Entity dotsEntity, in T component) where T : struct, IComponentData + { +#if SLOW_SVELTO_ECB + _EManager.AddComponentData(dotsEntity, component); +#else + _ECB.AddComponent(dotsEntity, component); +#endif + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void AddSharedComponent(Entity dotsEntity, in T component) where T : struct, ISharedComponentData + { +#if SLOW_SVELTO_ECB + _EManager.AddSharedComponentData(dotsEntity, component); +#else + _ECB.AddSharedComponent(dotsEntity, component); +#endif + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void AddBuffer(Entity dotsEntity) where T : struct, IBufferElementData + { +#if SLOW_SVELTO_ECB + _EManager.AddBuffer(dotsEntity); +#else + _ECB.AddBuffer(dotsEntity); +#endif + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public EntityCommandBuffer.ParallelWriter AsParallelWriter() + { +#if SLOW_SVELTO_ECB + throw new System.Exception(); +#else + return _ECB.AsParallelWriter(); +#endif + } + + readonly EntityCommandBuffer _ECB; + readonly EntityManager _EManager; + } +} +#endif \ No newline at end of file diff --git a/com.sebaslab.svelto.ecs/Extensions/Unity/DOTS/UECS/ISveltoOnDOTSSubmission.cs b/com.sebaslab.svelto.ecs/Extensions/Unity/DOTS/UECS/ISveltoOnDOTSSubmission.cs new file mode 100644 index 0000000..5162408 --- /dev/null +++ b/com.sebaslab.svelto.ecs/Extensions/Unity/DOTS/UECS/ISveltoOnDOTSSubmission.cs @@ -0,0 +1,17 @@ +#if UNITY_ECS +using Unity.Jobs; + +namespace Svelto.ECS.SveltoOnDOTS +{ + /// + /// Note: we don't want implementations of ISveltoDOTSSubmission + /// to be able to add directly Submission or HandleLifeTime engines as + /// the implementation are not aware of EnginesRoot so cannot satisfy Engines + /// that implement IEngine interfaces + /// + public interface ISveltoOnDOTSSubmission + { + void SubmitEntities(JobHandle jobHandle); + } +} +#endif \ No newline at end of file diff --git a/com.sebaslab.svelto.ecs/Extensions/Unity/DOTS/UECS/JobifiedSveltoEngines.cs b/com.sebaslab.svelto.ecs/Extensions/Unity/DOTS/UECS/JobifiedSveltoEngines.cs index be86b38..b85b53d 100644 --- a/com.sebaslab.svelto.ecs/Extensions/Unity/DOTS/UECS/JobifiedSveltoEngines.cs +++ b/com.sebaslab.svelto.ecs/Extensions/Unity/DOTS/UECS/JobifiedSveltoEngines.cs @@ -1,9 +1,9 @@ #if UNITY_ECS -namespace Svelto.ECS.Extensions.Unity +namespace Svelto.ECS.SveltoOnDOTS { public enum JobifiedSveltoEngines { - SveltoOverUECS + SveltoOnDOTS } } #endif \ No newline at end of file diff --git a/com.sebaslab.svelto.ecs/Extensions/Unity/DOTS/UECS/SubmissionEngine.cs b/com.sebaslab.svelto.ecs/Extensions/Unity/DOTS/UECS/SubmissionEngine.cs deleted file mode 100644 index 75ca6ac..0000000 --- a/com.sebaslab.svelto.ecs/Extensions/Unity/DOTS/UECS/SubmissionEngine.cs +++ /dev/null @@ -1,29 +0,0 @@ -#if UNITY_ECS -using Svelto.Common; -using Unity.Entities; -using Unity.Jobs; - -namespace Svelto.ECS.Extensions.Unity -{ - public interface IUpdateBeforeSubmission - { - JobHandle BeforeSubmissionUpdate(JobHandle jobHandle); - string name { get; } - } - - public interface IUpdateAfterSubmission - { - JobHandle AfterSubmissionUpdate(JobHandle jobHandle); - string name { get; } - } - - public abstract class SubmissionEngine : SystemBase, IEngine - { - public EntityCommandBuffer ECB { get; internal set; } - - protected sealed override void OnUpdate() {} - - public string name => TypeToString.Name(this); - } -} -#endif \ No newline at end of file diff --git a/com.sebaslab.svelto.ecs/Extensions/Unity/DOTS/UECS/SveltoOnDOTSEnginesGroup.cs b/com.sebaslab.svelto.ecs/Extensions/Unity/DOTS/UECS/SveltoOnDOTSEnginesGroup.cs new file mode 100644 index 0000000..360e644 --- /dev/null +++ b/com.sebaslab.svelto.ecs/Extensions/Unity/DOTS/UECS/SveltoOnDOTSEnginesGroup.cs @@ -0,0 +1,122 @@ +#if UNITY_ECS +using Svelto.Common; +using Svelto.ECS.Schedulers; +using Unity.Entities; +using Unity.Jobs; + +namespace Svelto.ECS.SveltoOnDOTS +{ + /// + /// This is a high level class to abstract the complexity of creating a Svelto ECS application that interacts + /// with DOTS ECS. However this is designed to make it work almost out of the box, but it should be eventually + /// substituted by project customized code. + /// This is a JobifiedEngine and as such it expect to be ticked. Normally it must be executed in a + /// SortedEnginesGroup as step that happens after the Svelto jobified engines run. + /// + /// The flow should be: + /// Svelto (GameLogic) Engines Run first + /// Then this Engine runs, which causes: + /// Jobs to be completed (it's a sync point) + /// Synchronizations engines to be executed (Svelto to DOTS ECS) + /// Submission of Entities to be executed + /// Svelto Add/Remove callbacks to be called + /// ISubmissionEngines to be executed + /// DOTS ECS engines to executed + /// Synchronizations engines to be executed (DOTS ECS To Svelto) + /// + [Sequenced(nameof(JobifiedSveltoEngines.SveltoOnDOTS))] + public class SveltoOnDOTSEnginesGroup : IJobifiedEngine + { + public SveltoOnDOTSEnginesGroup(EnginesRoot enginesRoot) + { + DBC.ECS.Check.Require(enginesRoot.scheduler is SimpleEntitiesSubmissionScheduler + , "The Engines root must use a EntitiesSubmissionScheduler scheduler implementation"); + + CreateUnityECSWorldForSvelto(enginesRoot.scheduler as SimpleEntitiesSubmissionScheduler, enginesRoot); + } + + public World world { get; private set; } + + public JobHandle Execute(JobHandle inputDeps) + { + //this is a sync point, there won't be pending jobs after this + _sveltoDotsEntitiesSubmissionGroup.SubmitEntities(inputDeps); + + //Mixed explicit job dependency and internal automatic ECS dependency system + //Write in to DOTS ECS entities so the DOTS ECS dependencies react on the components touched + var handle = _syncSveltoToDotsGroup.Execute(default); + + //As long as pure DOTS ECS systems do not use external containers (like native arrays and so) the Unity + //automatic dependencies system will guarantee that there won't be race conditions + world.Update(); + + //this svelto group of DOTS ECS SystemBase systems + return _syncDotsToSveltoGroup.Execute(handle); + } + + public string name => nameof(SveltoOnDOTSEnginesGroup); + + public void AddSveltoToDOTSEngine(SyncSveltoToDOTSEngine engine) + { + //it's a Svelto Engine/DOTS ECS SystemBase so it must be added in the DOTS ECS world AND svelto enginesRoot + world.AddSystem(engine); + _enginesRoot.AddEngine(engine); + + _syncSveltoToDotsGroup.Add(engine); + } + + public void AddDOTSToSveltoEngine(SyncDOTSToSveltoEngine engine) + { + //it's a Svelto Engine/DOTS ECS SystemBase so it must be added in the DOTS ECS world AND svelto enginesRoot + world.AddSystem(engine); + _enginesRoot.AddEngine(engine); + + _syncDotsToSveltoGroup.Add(engine); + } + + public void AddDOTSSubmissionEngine(SveltoOnDOTSHandleCreationEngine submissionEngine) + { + _sveltoDotsEntitiesSubmissionGroup.Add(submissionEngine); + + if (submissionEngine is IEngine enginesRootEngine) + _enginesRoot.AddEngine(enginesRootEngine); + } + + public void Dispose() + { + world.Dispose(); + } + + void CreateUnityECSWorldForSvelto(SimpleEntitiesSubmissionScheduler scheduler, EnginesRoot enginesRoot) + { + world = new World("Svelto<>DOTS world"); + + var systems = DefaultWorldInitialization.GetAllSystems(WorldSystemFilterFlags.Default); + DefaultWorldInitialization.AddSystemsToRootLevelSystemGroups(world, systems); + World.DefaultGameObjectInjectionWorld = world; + + //This is the DOTS ECS group that takes care of all the DOTS ECS systems that creates entities + //it also submits Svelto entities + _sveltoDotsEntitiesSubmissionGroup = new SveltoOnDOTSEntitiesSubmissionGroup(scheduler, enginesRoot); + //This is the group that handles the DOTS ECS sync systems that copy the svelto entities values to DOTS ECS entities + enginesRoot.AddEngine(_sveltoDotsEntitiesSubmissionGroup); + world.AddSystem(_sveltoDotsEntitiesSubmissionGroup); + _syncSveltoToDotsGroup = new SyncSveltoToDOTSGroup(); + enginesRoot.AddEngine(_syncSveltoToDotsGroup); + _syncDotsToSveltoGroup = new SyncDOTSToSveltoGroup(); + enginesRoot.AddEngine(_syncDotsToSveltoGroup); + //This is the group that handles the DOTS ECS sync systems that copy the DOTS ECS entities values to svelto entities + //enginesRoot.AddEngine(new SveltoDOTS ECSEntitiesSubmissionGroup(scheduler, world)); + enginesRoot.AddEngine(this); + + _enginesRoot = enginesRoot; + } + + EnginesRoot _enginesRoot; + + SveltoOnDOTSEntitiesSubmissionGroup _sveltoDotsEntitiesSubmissionGroup; + SyncSveltoToDOTSGroup _syncSveltoToDotsGroup; + SyncDOTSToSveltoGroup _syncDotsToSveltoGroup; + } +} +#endif \ No newline at end of file diff --git a/com.sebaslab.svelto.ecs/Extensions/Unity/DOTS/UECS/SveltoOnDOTSEntitiesSubmissionGroup.cs b/com.sebaslab.svelto.ecs/Extensions/Unity/DOTS/UECS/SveltoOnDOTSEntitiesSubmissionGroup.cs new file mode 100644 index 0000000..237c433 --- /dev/null +++ b/com.sebaslab.svelto.ecs/Extensions/Unity/DOTS/UECS/SveltoOnDOTSEntitiesSubmissionGroup.cs @@ -0,0 +1,196 @@ +#if UNITY_ECS +using System; +using System.Collections.Generic; +using Svelto.Common; +using Svelto.DataStructures; +using Svelto.ECS.Native; +using Svelto.ECS.Schedulers; +using Unity.Entities; +using Unity.Jobs; +using Allocator = Unity.Collections.Allocator; + +namespace Svelto.ECS.SveltoOnDOTS +{ + /// + /// SveltoDOTS ECSEntitiesSubmissionGroup expand the _submissionScheduler responsibility to integrate the + /// submission of Svelto entities with the submission of DOTS ECS entities using EntityCommandBuffer. + /// As there is just one submissionScheduler per enginesRoot, there should be only one SveltoDOTS + /// ECSEntitiesSubmissionGroup + /// per engines group. It's expected use is showed in the class SveltoOnDOTS ECSEnginesGroup which should be used + /// instead of using this class directly. + /// Groups DOTS ECS/Svelto SystemBase engines that creates DOTS ECS entities. + /// Flow: + /// Complete all the jobs used as input dependencies (this is a sync point) + /// Create the new frame Command Buffer to use + /// Svelto entities are submitted + /// Svelto Add and remove callback are called + /// ECB is injected in all the registered engines + /// all the OnUpdate of the registered engines/systems are called + /// the DOTS ECS command buffer is flushed + /// all the DOTS ECS entities created that need Svelto information will be processed + /// + [DisableAutoCreation] + public sealed partial class SveltoOnDOTSEntitiesSubmissionGroup : SystemBase, IQueryingEntitiesEngine, + ISveltoOnDOTSSubmission + { + public SveltoOnDOTSEntitiesSubmissionGroup(SimpleEntitiesSubmissionScheduler submissionScheduler, + EnginesRoot enginesRoot) + { + _submissionScheduler = submissionScheduler; + _submissionEngines = new FasterList(); + _cachedList = new List(); + _sveltoOnDotsHandleLifeTimeEngines = new FasterList(); + + var defaultSveltoOnDotsHandleLifeTimeEngine = new SveltoOnDOTSHandleLifeTimeEngine(); + + enginesRoot.AddEngine(defaultSveltoOnDotsHandleLifeTimeEngine); + _sveltoOnDotsHandleLifeTimeEngines.Add(defaultSveltoOnDotsHandleLifeTimeEngine); + } + + public EntitiesDB entitiesDB { get; set; } + + public void Ready() { } + + //Right, when you record a command outside of a job using the regular ECB, you don't pass it a sort key. + //We instead use a constant for the main thread that is actually set to Int32.MaxValue. Where as the commands + //that are recording from jobs with the ParallelWriter, get a lower value sort key from the job. Because we + //playback the commands in order based on this sort key, the ParallelWriter commands end up happening before + //the main thread commands. This is where your error is coming from because the Instantiate command happens at + //the end because it's sort key is Int32.MaxValue. + //We don't recommend mixing the main thread and ParallelWriter commands in a single ECB for this reason. + public void SubmitEntities(JobHandle jobHandle) + { + if (_submissionScheduler.paused == true) + return; + + using (var profiler = new PlatformProfiler("SveltoDOTSEntitiesSubmissionGroup")) + { + using (profiler.Sample("PreSubmissionPhase")) + { + PreSubmissionPhase(ref jobHandle, profiler); + } + + //Submit Svelto Entities, calls Add/Remove/MoveTo that can be used by the IDOTS ECSSubmissionEngines + _submissionScheduler.SubmitEntities(); + + using (profiler.Sample("AfterSubmissionPhase")) + { + AfterSubmissionPhase(profiler); + } + } + } + + public void Add(SveltoOnDOTSHandleCreationEngine engine) + { + // Console.LogDebug($"Add Submission Engine {engine} to the DOTS world {_ECBSystem.World.Name}"); + + //this is temporary enabled because of engines that needs EntityManagers for the wrong reasons. + _submissionEngines.Add(engine); + engine.entityManager = EntityManager; + engine.OnCreate(); + } + + public void Add(ISveltoOnDOTSHandleLifeTimeEngine engine) + { + // Console.LogDebug($"Add Submission Engine {engine} to the DOTS world {_ECBSystem.World.Name}"); + + _sveltoOnDotsHandleLifeTimeEngines.Add(engine); + } + + void PreSubmissionPhase(ref JobHandle jobHandle, PlatformProfiler profiler) + { + using (profiler.Sample("Complete All Pending Jobs")) jobHandle.Complete(); + + _entityCommandBuffer = new EntityCommandBuffer((Allocator)Common.Allocator.TempJob); + + foreach (var system in _submissionEngines) + system.entityCommandBuffer = + new EntityCommandBufferForSvelto(_entityCommandBuffer, World.EntityManager); + + foreach (var system in _sveltoOnDotsHandleLifeTimeEngines) + system.entityCommandBuffer = + new EntityCommandBufferForSvelto(_entityCommandBuffer, World.EntityManager); + } + + void AfterSubmissionPhase(PlatformProfiler profiler) + { + JobHandle combinedHandle = default; + for (var i = 0; i < _submissionEngines.count; i++) + { + try + { + combinedHandle = JobHandle.CombineDependencies(combinedHandle, _submissionEngines[i].OnUpdate()); + } + catch (Exception e) + { + Console.LogException(e, _submissionEngines[i].name); + + throw; + } + } + + using (profiler.Sample("Playback Command Buffer")) + { + _entityCommandBuffer.Playback(EntityManager); + _entityCommandBuffer.Dispose(); + } + + using (profiler.Sample("ConvertPendingEntities")) + ConvertPendingEntities(combinedHandle); + } + + //Note: when this is called, the CommandBuffer is flushed so the not temporary DOTS entity ID will be used + void ConvertPendingEntities(JobHandle combinedHandle) + { + var entityCommandBuffer = new EntityCommandBuffer((Allocator)Common.Allocator.TempJob); + var cmd = entityCommandBuffer.AsParallelWriter(); + + _cachedList.Clear(); + + //note with DOTS 0.17 unfortunately this allocates a lot :( + EntityManager.GetAllUniqueSharedComponentData(_cachedList); + + Dependency = JobHandle.CombineDependencies(Dependency, combinedHandle); + + for (int i = 0; i < _cachedList.Count; i++) + { + var dotsEntityToSetup = _cachedList[i]; + if (dotsEntityToSetup.@group == ExclusiveGroupStruct.Invalid) continue; + + var mapper = entitiesDB.QueryNativeMappedEntities(dotsEntityToSetup.@group); + + //Note: for some reason GetAllUniqueSharedComponentData returns DOTSEntityToSetup with valid values + //that are not used anymore by any entity. Something to keep an eye on if fixed on future versions + //of DOTS + + Entities.ForEach((Entity entity, int entityInQueryIndex, in DOTSSveltoEGID egid) => + { + mapper.Entity(egid.egid.entityID).dotsEntity = entity; + cmd.RemoveComponent(entityInQueryIndex, entity); + }).WithSharedComponentFilter(dotsEntityToSetup).ScheduleParallel(); + } + + Dependency.Complete(); + + entityCommandBuffer.Playback(EntityManager); + entityCommandBuffer.Dispose(); + } + + protected override void OnCreate() + { + } + + protected override void OnUpdate() + { + throw new NotSupportedException("if this is called something broke the original design"); + } + + readonly FasterList _submissionEngines; + readonly FasterList _sveltoOnDotsHandleLifeTimeEngines; + + readonly SimpleEntitiesSubmissionScheduler _submissionScheduler; + readonly List _cachedList; + EntityCommandBuffer _entityCommandBuffer; + } +} +#endif \ No newline at end of file diff --git a/com.sebaslab.svelto.ecs/Extensions/Unity/DOTS/UECS/SveltoOnDOTSHandleCreationEngine.cs b/com.sebaslab.svelto.ecs/Extensions/Unity/DOTS/UECS/SveltoOnDOTSHandleCreationEngine.cs new file mode 100644 index 0000000..8247cc8 --- /dev/null +++ b/com.sebaslab.svelto.ecs/Extensions/Unity/DOTS/UECS/SveltoOnDOTSHandleCreationEngine.cs @@ -0,0 +1,60 @@ +#if UNITY_ECS +using System; +using System.Runtime.CompilerServices; +using Unity.Entities; +using Unity.Jobs; + +namespace Svelto.ECS.SveltoOnDOTS +{ + /// + /// SubmissionEngine is a dedicated DOTS ECS Svelto.ECS engine that allows using the DOTS ECS + /// EntityCommandBuffer for fast creation of DOTS entities + /// + public abstract class SveltoOnDOTSHandleCreationEngine + { + protected EntityCommandBufferForSvelto ECB { get; private set; } + + protected internal EntityManager entityManager + { + // [Obsolete( + // "Attention: the use of EntityManager directly is deprecated. ECB MUST BE USED INSTEAD")] + get; + internal set; + } + + internal EntityCommandBufferForSvelto entityCommandBuffer + { + set => ECB = value; + } + + protected EntityArchetype CreateArchetype(params ComponentType[] types) + { + return entityManager.CreateArchetype(types); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected Entity CreateDOTSEntityOnSvelto(Entity entityComponentPrefabEntity, EGID egid, + bool mustHandleDOTSComponent) + { + return ECB.CreateDOTSEntityOnSvelto(entityComponentPrefabEntity, egid, mustHandleDOTSComponent); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected Entity CreateDOTSEntityOnSvelto(EntityArchetype archetype, EGID egid, bool mustHandleDOTSComponent) + { + return ECB.CreateDOTSEntityOnSvelto(archetype, egid, mustHandleDOTSComponent); + } + + protected internal virtual void OnCreate() + { + } + + protected internal virtual JobHandle OnUpdate() + { + return default; + } + + public abstract string name { get; } + } +} +#endif \ No newline at end of file diff --git a/com.sebaslab.svelto.ecs/Extensions/Unity/DOTS/UECS/SveltoOnDOTSHandleLifeTimeEngine.cs b/com.sebaslab.svelto.ecs/Extensions/Unity/DOTS/UECS/SveltoOnDOTSHandleLifeTimeEngine.cs new file mode 100644 index 0000000..696c5c5 --- /dev/null +++ b/com.sebaslab.svelto.ecs/Extensions/Unity/DOTS/UECS/SveltoOnDOTSHandleLifeTimeEngine.cs @@ -0,0 +1,44 @@ +#if UNITY_ECS + +namespace Svelto.ECS.SveltoOnDOTS +{ + public interface ISveltoOnDOTSHandleLifeTimeEngine + { + EntityCommandBufferForSvelto entityCommandBuffer { set; } + } + + public class SveltoOnDOTSHandleLifeTimeEngine : ISveltoOnDOTSHandleLifeTimeEngine, + IReactOnRemove, + IReactOnSwapEx where DOTSEntityComponent : unmanaged, IEntityComponentForDOTS + { + public void Remove(ref DOTSEntityComponent entityComponent, EGID egid) + { + ECB.DestroyEntity(entityComponent.dotsEntity); + } + + EntityCommandBufferForSvelto ECB { get; set; } + + public EntityCommandBufferForSvelto entityCommandBuffer + { + set => ECB = value; + } + + public void MovedTo((uint start, uint end) rangeOfEntities, in EntityCollection collection, + ExclusiveGroupStruct _, ExclusiveGroupStruct toGroup) + { + var (buffer, entityIDs, _) = collection; + + for (uint i = rangeOfEntities.start; i < rangeOfEntities.end; i++) + { + ref var entityComponent = ref buffer[i]; + ECB.SetSharedComponent(entityComponent.dotsEntity, new DOTSSveltoGroupID(toGroup)); + + ECB.SetComponent(entityComponent.dotsEntity, new DOTSSveltoEGID + { + egid = new EGID(entityIDs[i], toGroup) + }); + } + } + } +} +#endif \ No newline at end of file diff --git a/com.sebaslab.svelto.ecs/Extensions/Unity/DOTS/UECS/SveltoOverUECSEnginesGroup.cs b/com.sebaslab.svelto.ecs/Extensions/Unity/DOTS/UECS/SveltoOverUECSEnginesGroup.cs deleted file mode 100644 index aea088c..0000000 --- a/com.sebaslab.svelto.ecs/Extensions/Unity/DOTS/UECS/SveltoOverUECSEnginesGroup.cs +++ /dev/null @@ -1,118 +0,0 @@ -#if UNITY_ECS -using Svelto.Common; -using Svelto.ECS.Schedulers; -using Unity.Entities; -using Unity.Jobs; - -namespace Svelto.ECS.Extensions.Unity -{ - /// - /// This is a high level class to abstract the complexity of creating a Svelto ECS application that interacts - /// with UECS. However this is designed to make it work almost out of the box, but it should be eventually - /// substituted by project customized code. - /// This is a JobifiedEngine and as such it expect to be ticked. Normally it must be executed in a - /// SortedEnginesGroup as step that happens after the Svelto jobified engines run. - /// - /// The flow should be: - /// Svelto (GameLogic) Engines Run first - /// Then this Engine runs, which causes: - /// Jobs to be completed (it's a sync point) - /// Synchronizations engines to be executed (Svelto to UECS) - /// Submission of Entities to be executed - /// Svelto Add/Remove callbacks to be called - /// ISubmissionEngines to be executed - /// UECS engines to executed - /// Synchronizations engines to be executed (UECS To Svelto) - /// - [Sequenced(nameof(JobifiedSveltoEngines.SveltoOverUECS))] - public class SveltoOverUECSEnginesGroup: IJobifiedEngine - { - public SveltoOverUECSEnginesGroup(EnginesRoot enginesRoot) - { - DBC.ECS.Check.Require(enginesRoot.scheduler is SimpleEntitiesSubmissionScheduler, "The Engines root must use a EntitiesSubmissionScheduler scheduler implementation"); - - CreateUnityECSWorldForSvelto(enginesRoot.scheduler as SimpleEntitiesSubmissionScheduler, enginesRoot); - } - - public World world { get; private set; } - - void CreateUnityECSWorldForSvelto(SimpleEntitiesSubmissionScheduler scheduler, EnginesRoot enginesRoot) - { - world = new World("Svelto<>UECS world"); - - var systems = DefaultWorldInitialization.GetAllSystems(WorldSystemFilterFlags.Default); - DefaultWorldInitialization.AddSystemsToRootLevelSystemGroups(world, systems); - World.DefaultGameObjectInjectionWorld = world; - - //This is the UECS group that takes care of all the UECS systems that creates entities - //it also submits Svelto entities - _sveltoUecsEntitiesSubmissionGroup = new SveltoUECSEntitiesSubmissionGroup(scheduler); - //This is the group that handles the UECS sync systems that copy the svelto entities values to UECS entities - enginesRoot.AddEngine(_sveltoUecsEntitiesSubmissionGroup); - world.AddSystem(_sveltoUecsEntitiesSubmissionGroup); - _syncSveltoToUecsGroup = new SyncSveltoToUECSGroup(); - enginesRoot.AddEngine(_syncSveltoToUecsGroup); - _syncUecsToSveltoGroup = new SyncUECSToSveltoGroup(); - enginesRoot.AddEngine(_syncUecsToSveltoGroup); - //This is the group that handles the UECS sync systems that copy the UECS entities values to svelto entities - //enginesRoot.AddEngine(new SveltoUECSEntitiesSubmissionGroup(scheduler, world)); - enginesRoot.AddEngine(this); - - _enginesRoot = enginesRoot; - } - - public JobHandle Execute(JobHandle inputDeps) - { - //this is a sync point, there won't be pending jobs after this - _sveltoUecsEntitiesSubmissionGroup.SubmitEntities(inputDeps); - - //Mixed explicit job dependency and internal automatic ECS dependency system - //Write in to UECS entities so the UECS dependencies react on the components touched - var handle = _syncSveltoToUecsGroup.Execute(default); - - //As long as pure UECS systems do not use external containers (like native arrays and so) the Unity - //automatic dependencies system will guarantee that there won't be race conditions - world.Update(); - - //this svelto group of UECS SystemBase systems - return _syncUecsToSveltoGroup.Execute(handle); - } - - public void AddUECSSubmissionEngine(SubmissionEngine submissionEngine) - { - _sveltoUecsEntitiesSubmissionGroup.Add(submissionEngine); - _enginesRoot.AddEngine(submissionEngine); - } - - public void AddSveltoToUECSEngine(SyncSveltoToUECSEngine engine) - { - //it's a Svelto Engine/UECS SystemBase so it must be added in the UECS world AND svelto enginesRoot - world.AddSystem(engine); - _enginesRoot.AddEngine(engine); - - _syncSveltoToUecsGroup.Add(engine); - } - - public void AddUECSToSveltoEngine(SyncUECSToSveltoEngine engine) - { - //it's a Svelto Engine/UECS SystemBase so it must be added in the UECS world AND svelto enginesRoot - world.AddSystem(engine); - _enginesRoot.AddEngine(engine); - - _syncUecsToSveltoGroup.Add(engine); - } - - public void Dispose() - { - world.Dispose(); - } - - public string name => nameof(SveltoOverUECSEnginesGroup); - - SveltoUECSEntitiesSubmissionGroup _sveltoUecsEntitiesSubmissionGroup; - SyncSveltoToUECSGroup _syncSveltoToUecsGroup; - SyncUECSToSveltoGroup _syncUecsToSveltoGroup; - EnginesRoot _enginesRoot; - } -} -#endif \ No newline at end of file diff --git a/com.sebaslab.svelto.ecs/Extensions/Unity/DOTS/UECS/SveltoUECSEntitiesSubmissionGroup.cs b/com.sebaslab.svelto.ecs/Extensions/Unity/DOTS/UECS/SveltoUECSEntitiesSubmissionGroup.cs deleted file mode 100644 index 3fbb3c3..0000000 --- a/com.sebaslab.svelto.ecs/Extensions/Unity/DOTS/UECS/SveltoUECSEntitiesSubmissionGroup.cs +++ /dev/null @@ -1,241 +0,0 @@ -#if UNITY_ECS -using System.Collections; -using Svelto.Common; -using Svelto.DataStructures; -using Svelto.ECS.Native; -using Svelto.ECS.Schedulers; -using Unity.Entities; -using Unity.Jobs; - -namespace Svelto.ECS.Extensions.Unity -{ - /// - /// Group of UECS/Svelto SystemBase engines that creates UECS entities. - /// Svelto entities are submitted - /// Svelto Add and remove callback are called - /// OnUpdate of the systems are called - /// finally the UECS command buffer is flushed - /// Note: I cannot use Unity ComponentSystemGroups nor I can rely on the SystemBase Dependency field to - /// solve external dependencies. External dependencies are tracked, but only linked to the UECS components operations - /// With Dependency I cannot guarantee that an external container is used before previous jobs working on it are completed - /// - [DisableAutoCreation] - public sealed class SveltoUECSEntitiesSubmissionGroup : SystemBase, IQueryingEntitiesEngine , IReactOnAddAndRemove - , IReactOnSwap, ISveltoUECSSubmission - { - public SveltoUECSEntitiesSubmissionGroup(SimpleEntitiesSubmissionScheduler submissionScheduler) - { - _submissionScheduler = submissionScheduler; - _engines = new FasterList(); - _afterSubmissionEngines = new FasterList(); - _beforeSubmissionEngines = new FasterList(); - } - - protected override void OnCreate() - { - _ECBSystem = World.CreateSystem(); - _entityQuery = GetEntityQuery(typeof(UpdateUECSEntityAfterSubmission)); - } - - public EntitiesDB entitiesDB { get; set; } - - public void Ready() { } - - public void Add(ref UECSEntityComponent entityComponent, EGID egid) { } - - public void Remove(ref UECSEntityComponent entityComponent, EGID egid) - { - _ECB.DestroyEntity(entityComponent.uecsEntity); - } - - public void MovedTo(ref UECSEntityComponent entityComponent, ExclusiveGroupStruct previousGroup, EGID egid) - { - _ECB.SetSharedComponent(entityComponent.uecsEntity, new UECSSveltoGroupID(egid.groupID)); - } - - public void Add(SubmissionEngine engine) - { - Svelto.Console.LogDebug($"Add Engine {engine} to the UECS world {_ECBSystem.World.Name}"); - - _ECBSystem.World.AddSystem(engine); - if (engine is IUpdateAfterSubmission afterSubmission) - _afterSubmissionEngines.Add(afterSubmission); - if (engine is IUpdateBeforeSubmission beforeSubmission) - _beforeSubmissionEngines.Add(beforeSubmission); - _engines.Add(engine); - } - - public void SubmitEntities(JobHandle jobHandle) - { - if (_submissionScheduler.paused) - return; - - using (var profiler = new PlatformProfiler("SveltoUECSEntitiesSubmissionGroup - PreSubmissionPhase")) - { - PreSubmissionPhase(ref jobHandle, profiler); - - //Submit Svelto Entities, calls Add/Remove/MoveTo that can be used by the IUECSSubmissionEngines - using (profiler.Sample("Submit svelto entities")) - { - _submissionScheduler.SubmitEntities(); - } - - AfterSubmissionPhase(profiler); - } - } - - public IEnumerator SubmitEntitiesAsync(JobHandle jobHandle, uint maxEntities) - { - if (_submissionScheduler.paused) - yield break; - - using (var profiler = new PlatformProfiler("SveltoUECSEntitiesSubmissionGroup - PreSubmissionPhase")) - { - PreSubmissionPhase(ref jobHandle, profiler); - - var submitEntitiesAsync = _submissionScheduler.SubmitEntitiesAsync(maxEntities); - - //Submit Svelto Entities, calls Add/Remove/MoveTo that can be used by the IUECSSubmissionEngines - while (true) - { - using (profiler.Sample("Submit svelto entities async")) - { - submitEntitiesAsync.MoveNext(); - } - - if (submitEntitiesAsync.Current == true) - { - using (profiler.Yield()) - yield return null; - } - else - break; - } - - AfterSubmissionPhase(profiler); - } - } - - void PreSubmissionPhase(ref JobHandle jobHandle, PlatformProfiler profiler) - { - JobHandle BeforeECBFlushEngines() - { - JobHandle jobHandle = default; - - //execute submission engines and complete jobs because of this I don't need to do _ECBSystem.AddJobHandleForProducer(Dependency); - - for (var index = 0; index < _beforeSubmissionEngines.count; index++) - { - ref var engine = ref _beforeSubmissionEngines[index]; - using (profiler.Sample(engine.name)) - { - jobHandle = JobHandle.CombineDependencies(jobHandle, engine.BeforeSubmissionUpdate(jobHandle)); - } - } - - return jobHandle; - } - - using (profiler.Sample("Complete All Pending Jobs")) - { - jobHandle.Complete(); - } - - //prepare the entity command buffer to be used by the registered engines - var entityCommandBuffer = _ECBSystem.CreateCommandBuffer(); - - foreach (var system in _engines) - { - system.ECB = entityCommandBuffer; - } - - _ECB = entityCommandBuffer; - - RemovePreviousMarkingComponents(entityCommandBuffer); - - using (profiler.Sample("Before Submission Engines")) - { - BeforeECBFlushEngines().Complete(); - } - } - - void AfterSubmissionPhase(PlatformProfiler profiler) - { - JobHandle AfterECBFlushEngines() - { - JobHandle jobHandle = default; - - //execute submission engines and complete jobs because of this I don't need to do _ECBSystem.AddJobHandleForProducer(Dependency); - for (var index = 0; index < _afterSubmissionEngines.count; index++) - { - ref var engine = ref _afterSubmissionEngines[index]; - using (profiler.Sample(engine.name)) - { - jobHandle = JobHandle.CombineDependencies(jobHandle, engine.AfterSubmissionUpdate(jobHandle)); - } - } - - return jobHandle; - } - - using (profiler.Sample("Flush Command Buffer")) - { - _ECBSystem.Update(); - } - - ConvertPendingEntities().Complete(); - - using (profiler.Sample("After Submission Engines")) - { - AfterECBFlushEngines().Complete(); - } - } - - void RemovePreviousMarkingComponents(EntityCommandBuffer ECB) - { - ECB.RemoveComponentForEntityQuery(_entityQuery); - } - - JobHandle ConvertPendingEntities() - { - if (_entityQuery.IsEmpty == false) - { - NativeEGIDMultiMapper mapper = - entitiesDB.QueryNativeMappedEntities( - entitiesDB.FindGroups(), Allocator.TempJob); - - Entities.ForEach((Entity id, ref UpdateUECSEntityAfterSubmission egidComponent) => - { - mapper.Entity(egidComponent.egid).uecsEntity = id; - }).ScheduleParallel(); - - mapper.ScheduleDispose(Dependency); - } - - return Dependency; - } - - readonly SimpleEntitiesSubmissionScheduler _submissionScheduler; - SubmissionEntitiesCommandBufferSystem _ECBSystem; - readonly FasterList _engines; - readonly FasterList _beforeSubmissionEngines; - readonly FasterList _afterSubmissionEngines; - - [DisableAutoCreation] - class SubmissionEntitiesCommandBufferSystem : EntityCommandBufferSystem { } - - protected override void OnUpdate() { } - - EntityQuery _entityQuery; - EntityCommandBuffer _ECB; - } - - public interface ISveltoUECSSubmission - { - void Add(SubmissionEngine engine); - - void SubmitEntities(JobHandle jobHandle); - IEnumerator SubmitEntitiesAsync(JobHandle jobHandle, uint maxEntities); - } -} -#endif \ No newline at end of file diff --git a/com.sebaslab.svelto.ecs/Extensions/Unity/DOTS/UECS/SyncDOTSToSveltoGroup.cs b/com.sebaslab.svelto.ecs/Extensions/Unity/DOTS/UECS/SyncDOTSToSveltoGroup.cs new file mode 100644 index 0000000..fd7d2ba --- /dev/null +++ b/com.sebaslab.svelto.ecs/Extensions/Unity/DOTS/UECS/SyncDOTSToSveltoGroup.cs @@ -0,0 +1,81 @@ +#if UNITY_ECS +using Svelto.Common; +using Svelto.DataStructures; +using Unity.Entities; +using Unity.Jobs; + +namespace Svelto.ECS.SveltoOnDOTS +{ + public class SyncDOTSToSveltoGroup : UnsortedJobifiedEnginesGroup {} + + public class SortedSyncDOTSToSveltoGroup : SortedJobifiedEnginesGroup, + ISyncDOTSToSveltoEngine where T_Order : struct, ISequenceOrder + { + public SortedSyncDOTSToSveltoGroup(FasterList engines) : base(engines) + { + } + } + + public interface ISyncDOTSToSveltoEngine : IJobifiedEngine { } + + public abstract partial class SyncDOTSToSveltoEngine : SystemBase, ISyncDOTSToSveltoEngine + { + //The dependency returned is enough for the Svelto Engines running after this to take in consideration + //the Systembase jobs. The svelto engines do not need to take in consideration the new dependencies created + //by the World.Update because those are independent and are needed only by the next World.Update() jobs + public JobHandle Execute(JobHandle inputDeps) + { + _inputDeps = inputDeps; + + Update(); //this complete the previous frame jobs so dependency cannot be modified atr this point + + return Dependency; + } + + //TODO if this is correct must change SyncDOTSToSveltoGroup too + protected sealed override void OnUpdate() + { + //SysteBase jobs that will use this Dependency will wait for inputDeps to be completed before to execute + Dependency = JobHandle.CombineDependencies(Dependency, _inputDeps); + + OnSveltoUpdate(); + } + + protected abstract void OnSveltoUpdate(); + + public abstract string name { get; } + + JobHandle _inputDeps; + } + + public abstract partial class SortedSyncDOTSToSveltoEngine : SystemBase, ISyncDOTSToSveltoEngine + { + //The dependency returned is enough for the Svelto Engines running after this to take in consideration + //the Systembase jobs. The svelto engines do not need to take in consideration the new dependencies created + //by the World.Update because those are independent and are needed only by the next World.Update() jobs + public JobHandle Execute(JobHandle inputDeps) + { + _inputDeps = inputDeps; + + Update(); //this complete the previous frame jobs so dependency cannot be modified atr this point + + return Dependency; + } + + //TODO if this is correct must change SyncDOTSToSveltoGroup too + protected sealed override void OnUpdate() + { + //SysteBase jobs that will use this Dependency will wait for inputDeps to be completed before to execute + Dependency = JobHandle.CombineDependencies(Dependency, _inputDeps); + + OnSveltoUpdate(); + } + + protected abstract void OnSveltoUpdate(); + + public abstract string name { get; } + + JobHandle _inputDeps; + } +} +#endif \ No newline at end of file diff --git a/com.sebaslab.svelto.ecs/Extensions/Unity/DOTS/UECS/SyncSveltoToUECSGroup.cs b/com.sebaslab.svelto.ecs/Extensions/Unity/DOTS/UECS/SyncSveltoToDOTSGroup.cs similarity index 89% rename from com.sebaslab.svelto.ecs/Extensions/Unity/DOTS/UECS/SyncSveltoToUECSGroup.cs rename to com.sebaslab.svelto.ecs/Extensions/Unity/DOTS/UECS/SyncSveltoToDOTSGroup.cs index 1839b0d..6427e30 100644 --- a/com.sebaslab.svelto.ecs/Extensions/Unity/DOTS/UECS/SyncSveltoToUECSGroup.cs +++ b/com.sebaslab.svelto.ecs/Extensions/Unity/DOTS/UECS/SyncSveltoToDOTSGroup.cs @@ -2,7 +2,7 @@ using Unity.Entities; using Unity.Jobs; -namespace Svelto.ECS.Extensions.Unity +namespace Svelto.ECS.SveltoOnDOTS { /// /// HOW DOTS SYSTEMBASE DEPENDENCY SYSTEM WORKS: @@ -50,24 +50,36 @@ namespace Svelto.ECS.Extensions.Unity // Tim Johansson 2 hours ago // Yes, you would have to do it every time - public class SyncSveltoToUECSGroup : UnsortedJobifiedEnginesGroup {} + public class SyncSveltoToDOTSGroup : UnsortedJobifiedEnginesGroup {} - public abstract class SyncSveltoToUECSEngine : SystemBase, IJobifiedEngine + public abstract partial class SyncSveltoToDOTSEngine : SystemBase, IJobifiedEngine { //The dependency returned is enough for the Svelto Engines running after this to take in consideration //the Systembase jobs. The svelto engines do not need to take in consideration the new dependencies created //by the World.Update because those are independent and are needed only by the next World.Update() jobs public JobHandle Execute(JobHandle inputDeps) { - //SysteBase jobs that will use this Dependency will wait for inputDeps to be completed before to execute - Dependency = JobHandle.CombineDependencies(Dependency, inputDeps); + _inputDeps = inputDeps; - Update(); + Update(); //this complete the previous frame jobs so dependency cannot be modified atr this point return Dependency; } + //TODO if this is correct must change SyncDOTSToSveltoGroup too + protected sealed override void OnUpdate() + { + //SysteBase jobs that will use this Dependency will wait for inputDeps to be completed before to execute + Dependency = JobHandle.CombineDependencies(Dependency, _inputDeps); + + OnSveltoUpdate(); + } + + protected abstract void OnSveltoUpdate(); + public abstract string name { get; } + + JobHandle _inputDeps; } } #endif \ No newline at end of file diff --git a/com.sebaslab.svelto.ecs/Extensions/Unity/DOTS/UECS/SyncUECSToSveltoGroup.cs b/com.sebaslab.svelto.ecs/Extensions/Unity/DOTS/UECS/SyncUECSToSveltoGroup.cs deleted file mode 100644 index 585c93e..0000000 --- a/com.sebaslab.svelto.ecs/Extensions/Unity/DOTS/UECS/SyncUECSToSveltoGroup.cs +++ /dev/null @@ -1,26 +0,0 @@ -#if UNITY_ECS -using Unity.Entities; -using Unity.Jobs; - -namespace Svelto.ECS.Extensions.Unity -{ - public class SyncUECSToSveltoGroup : UnsortedJobifiedEnginesGroup - { - - } - - public abstract class SyncUECSToSveltoEngine : SystemBase, IJobifiedEngine - { - public JobHandle Execute(JobHandle inputDeps) - { - Dependency = JobHandle.CombineDependencies(Dependency, inputDeps); - - Update(); - - return Dependency; - } - - public abstract string name { get; } - } -} -#endif \ No newline at end of file diff --git a/com.sebaslab.svelto.ecs/Extensions/Unity/DOTS/UECS/UECSSveltoEGID.cs b/com.sebaslab.svelto.ecs/Extensions/Unity/DOTS/UECS/UECSSveltoEGID.cs deleted file mode 100644 index 84d9dcb..0000000 --- a/com.sebaslab.svelto.ecs/Extensions/Unity/DOTS/UECS/UECSSveltoEGID.cs +++ /dev/null @@ -1,40 +0,0 @@ -#if UNITY_ECS -using Unity.Entities; - -namespace Svelto.ECS.Extensions.Unity -{ - public struct UECSSveltoEGID : IComponentData - { - public EGID egid; - - public UECSSveltoEGID(EGID egid) { this.egid = egid; } - } - - public struct UECSSveltoGroupID : ISharedComponentData - { - public readonly ExclusiveGroupStruct group; - - public UECSSveltoGroupID(ExclusiveGroupStruct exclusiveGroup) - { - @group = exclusiveGroup; - } - - public static implicit operator ExclusiveGroupStruct(UECSSveltoGroupID group) - { - return group.@group; - } - } - - public struct UpdateUECSEntityAfterSubmission : IComponentData - { - public EGID egid; - - public UpdateUECSEntityAfterSubmission(EGID egid) { this.egid = egid; } - } - - public struct UECSEntityComponent : IEntityComponent - { - public Entity uecsEntity; - } -} -#endif \ No newline at end of file diff --git a/com.sebaslab.svelto.ecs/Extensions/Unity/GameObjects/Implementors/IECSManager.cs b/com.sebaslab.svelto.ecs/Extensions/Unity/GameObjects/Implementors/IECSManager.cs new file mode 100644 index 0000000..52b96c4 --- /dev/null +++ b/com.sebaslab.svelto.ecs/Extensions/Unity/GameObjects/Implementors/IECSManager.cs @@ -0,0 +1,6 @@ +namespace Svelto.ECS.Extensions.Unity +{ + public interface IECSManager + { + } +} \ No newline at end of file diff --git a/com.sebaslab.svelto.ecs/Extensions/Unity/GameObjects/Implementors/IUseMultipleResourceManagerImplementor.cs b/com.sebaslab.svelto.ecs/Extensions/Unity/GameObjects/Implementors/IUseMultipleResourceManagerImplementor.cs new file mode 100644 index 0000000..e68e6c9 --- /dev/null +++ b/com.sebaslab.svelto.ecs/Extensions/Unity/GameObjects/Implementors/IUseMultipleResourceManagerImplementor.cs @@ -0,0 +1,9 @@ +using Svelto.ECS.Hybrid; + +namespace Svelto.ECS.Extensions.Unity +{ + public interface IUseMultipleResourceManagerImplementor: IImplementor + { + IECSManager[] resourceManager { set; } + } +} \ No newline at end of file diff --git a/com.sebaslab.svelto.ecs/Extensions/Unity/GameObjects/Implementors/IUseResourceManagerImplementor.cs b/com.sebaslab.svelto.ecs/Extensions/Unity/GameObjects/Implementors/IUseResourceManagerImplementor.cs new file mode 100644 index 0000000..2b2efea --- /dev/null +++ b/com.sebaslab.svelto.ecs/Extensions/Unity/GameObjects/Implementors/IUseResourceManagerImplementor.cs @@ -0,0 +1,9 @@ +using Svelto.ECS.Hybrid; + +namespace Svelto.ECS.Extensions.Unity +{ + public interface IUseResourceManagerImplementor: IImplementor + { + IECSManager resourceManager { set; } + } +} \ No newline at end of file diff --git a/com.sebaslab.svelto.ecs/Extensions/Unity/GameObjects/SveltoGUIHelper.cs b/com.sebaslab.svelto.ecs/Extensions/Unity/GameObjects/SveltoGUIHelper.cs deleted file mode 100644 index 36a5026..0000000 --- a/com.sebaslab.svelto.ecs/Extensions/Unity/GameObjects/SveltoGUIHelper.cs +++ /dev/null @@ -1,161 +0,0 @@ -#if UNITY_5 || UNITY_5_3_OR_NEWER -using System; -using Svelto.ECS.Hybrid; -using UnityEngine; - -namespace Svelto.ECS.Extensions.Unity -{ - // "New Svelto GUI Patterns are now available" - public static class SveltoGUIHelper - { - /// - /// This is the suggested way to create GUIs from prefabs now. - /// - public static T CreateFromPrefab - (ref uint startIndex, Transform contextHolder, IEntityFactory factory, ExclusiveGroup group - , bool searchImplementorsInChildren = false, string groupNamePostfix = null) - where T : MonoBehaviour, IEntityDescriptorHolder - { - Create(new EGID(startIndex++, group), contextHolder, factory, out var holder); - var children = contextHolder.GetComponentsInChildren(true); - - foreach (var child in children) - { - if (child.GetType() != typeof(T)) - { - var monoBehaviour = child as MonoBehaviour; - IImplementor[] childImplementors; - if (searchImplementorsInChildren == false) - childImplementors = monoBehaviour.GetComponents(); - else - childImplementors = monoBehaviour.GetComponentsInChildren(true); - - startIndex = InternalBuildAll(startIndex, child, factory, group, childImplementors - , groupNamePostfix); - } - } - - return holder; - } - - /// - /// Creates all the entities in a hierarchy. This was commonly used to create entities from gameobjects - /// already present in the scene - /// - public static uint CreateAll - (uint startIndex, ExclusiveGroup group, Transform contextHolder, IEntityFactory factory - , string groupNamePostfix = null) where T : MonoBehaviour, IEntityDescriptorHolder - { - var holders = contextHolder.GetComponentsInChildren(true); - - foreach (var holder in holders) - { - var implementors = holder.GetComponents(); - try - { - startIndex = InternalBuildAll(startIndex, holder, factory, group, implementors, groupNamePostfix); - } - catch (Exception ex) - { - throw new Exception($"When building entity from game object {Path(holder.transform)}", ex); - } - } - - return startIndex; - } - - public static EntityInitializer Create(EGID ID, Transform contextHolder, IEntityFactory factory, out T holder - , bool searchImplementorsInChildren = false) where T : MonoBehaviour, IEntityDescriptorHolder - { - holder = contextHolder.GetComponentInChildren(true); - if (holder == null) - { - throw new Exception($"Could not find holder {typeof(T).Name} in {contextHolder.name}"); - } - - var implementors = searchImplementorsInChildren == false - ? holder.GetComponents() - : holder.GetComponentsInChildren(true); - - return factory.BuildEntity(ID, holder.GetDescriptor(), implementors); - } - - public static EntityInitializer Create - (EGID ID, Transform contextHolder, IEntityFactory factory, bool searchImplementorsInChildren = false) - where T : MonoBehaviour, IEntityDescriptorHolder - { - return Create(ID, contextHolder, factory, out _, searchImplementorsInChildren); - } - - /// - /// Works like CreateAll but only builds entities with holders that have the same group specified - /// This is a very specific case and I still need to decide if I want it in the framework - /// - public static uint CreateAllInMatchingGroup - (uint startId, ExclusiveGroup exclusiveGroup, Transform contextHolder, IEntityFactory factory) - where T : MonoBehaviour, IEntityDescriptorHolder - { - var holders = contextHolder.GetComponentsInChildren(true); - - foreach (var holder in holders) - { - if (string.IsNullOrEmpty(holder.groupName) == false) - { - var realGroup = ExclusiveGroup.Search(holder.groupName); - if (realGroup != exclusiveGroup) - continue; - } - else - { - continue; - } - - var implementors = holder.GetComponents(); - - startId = InternalBuildAll(startId, holder, factory, exclusiveGroup, implementors, null); - } - - return startId; - } - - static string Path(Transform go) - { - string s = go.name; - while (go.parent != null) - { - go = go.parent; - s = go.name + "/" + s; - } - - return s; - } - - static uint InternalBuildAll - (uint startIndex, IEntityDescriptorHolder descriptorHolder, IEntityFactory factory, ExclusiveGroup group - , IImplementor[] implementors, string groupNamePostfix) - { - ExclusiveGroupStruct realGroup = group; - - if (string.IsNullOrEmpty(descriptorHolder.groupName) == false) - { - realGroup = ExclusiveGroup.Search(!string.IsNullOrEmpty(groupNamePostfix) - ? $"{descriptorHolder.groupName}{groupNamePostfix}" - : descriptorHolder.groupName); - } - - EGID egid; - var holderId = descriptorHolder.id; - if (holderId == 0) - egid = new EGID(startIndex++, realGroup); - else - egid = new EGID(holderId, realGroup); - - var init = factory.BuildEntity(egid, descriptorHolder.GetDescriptor(), implementors); - - init.Init(new EntityHierarchyComponent(group)); - - return startIndex; - } - } -} -#endif \ No newline at end of file diff --git a/com.sebaslab.svelto.ecs/Extensions/Unity/UnityEntitiesSubmissionScheduler.cs b/com.sebaslab.svelto.ecs/Extensions/Unity/UnityEntitiesSubmissionScheduler.cs index a44bf43..df771cc 100644 --- a/com.sebaslab.svelto.ecs/Extensions/Unity/UnityEntitiesSubmissionScheduler.cs +++ b/com.sebaslab.svelto.ecs/Extensions/Unity/UnityEntitiesSubmissionScheduler.cs @@ -1,4 +1,5 @@ #if UNITY_5 || UNITY_5_3_OR_NEWER +using System; using Object = UnityEngine.Object; using UnityEngine; @@ -23,16 +24,19 @@ namespace Svelto.ECS.Schedulers.Unity } } - public override bool paused { get; set; } - void SubmitEntities() { - if (paused == false) + try + { + _onTick.SubmitEntities(); + } + catch (Exception e) { - var enumerator = _onTick.submitEntities; - enumerator.MoveNext(); - - while (enumerator.Current == true) enumerator.MoveNext(); + paused = true; + + Svelto.Console.LogException(e); + + throw; } } diff --git a/com.sebaslab.svelto.ecs/Serialization/ComposedComponentSerializer.cs b/com.sebaslab.svelto.ecs/Serialization/ComposedComponentSerializer.cs index c955b16..ca3bbb3 100644 --- a/com.sebaslab.svelto.ecs/Serialization/ComposedComponentSerializer.cs +++ b/com.sebaslab.svelto.ecs/Serialization/ComposedComponentSerializer.cs @@ -17,7 +17,7 @@ namespace Svelto.ECS.Serialization { foreach (IComponentSerializer s in _serializers) { - serializationData.data.ExpandBy(s.size); + serializationData.data.IncrementCountBy(s.size); if (s.SerializeSafe(value, serializationData)) return true; } @@ -36,7 +36,7 @@ namespace Svelto.ECS.Serialization throw new Exception($"ComposedComponentSerializer for {typeof(T)} did not deserialize any data!"); } - public uint size => 0; - IComponentSerializer[] _serializers; + public uint size => 0; + readonly IComponentSerializer[] _serializers; } } \ No newline at end of file diff --git a/com.sebaslab.svelto.ecs/Serialization/DefaultSerializer.cs b/com.sebaslab.svelto.ecs/Serialization/DefaultSerializer.cs index 4474863..9532aeb 100644 --- a/com.sebaslab.svelto.ecs/Serialization/DefaultSerializer.cs +++ b/com.sebaslab.svelto.ecs/Serialization/DefaultSerializer.cs @@ -15,9 +15,10 @@ namespace Svelto.ECS.Serialization field.IsPrivate == false) throw new ECSException($"field cannot be serialised {fieldFieldType} in {_type.FullName}"); } - +#if SLOW_SVELTO_SUBMISSION if (_type.GetProperties().Length > (ComponentBuilder.HAS_EGID ? 1 : 0)) throw new ECSException("serializable entity struct must be property less ".FastConcat(_type.FullName)); +#endif } public uint size => SIZEOFT; diff --git a/com.sebaslab.svelto.ecs/Serialization/DefaultVersioningFactory.cs b/com.sebaslab.svelto.ecs/Serialization/DefaultVersioningFactory.cs index 484740c..862ce8c 100644 --- a/com.sebaslab.svelto.ecs/Serialization/DefaultVersioningFactory.cs +++ b/com.sebaslab.svelto.ecs/Serialization/DefaultVersioningFactory.cs @@ -3,19 +3,23 @@ using Svelto.Common; namespace Svelto.ECS.Serialization { //TODO: Unit test. Delete this comment once Unit test is written +#if ENABLE_IL2CPP + [UnityEngine.Scripting.Preserve] +#endif public class DefaultVersioningFactory : IDeserializationFactory where T : IEntityDescriptor, new() { - public EntityInitializer BuildDeserializedEntity - (EGID egid, ISerializationData serializationData, ISerializableEntityDescriptor entityDescriptor - , int serializationType, IEntitySerialization entitySerialization, IEntityFactory factory - , bool enginesRootIsDeserializationOnly) + public EntityInitializer BuildDeserializedEntity(EGID egid, ISerializationData serializationData, + ISerializableEntityDescriptor entityDescriptor, int serializationType, + IEntitySerialization entitySerialization, IEntityFactory factory, bool enginesRootIsDeserializationOnly) { - var entityDescriptorEntitiesToSerialize = enginesRootIsDeserializationOnly ? entityDescriptor.componentsToSerialize : entityDescriptor.componentsToBuild; + var entityDescriptorEntitiesToSerialize = enginesRootIsDeserializationOnly + ? entityDescriptor.componentsToSerialize + : entityDescriptor.componentsToBuild; var initializer = factory.BuildEntity(egid, entityDescriptorEntitiesToSerialize, TypeCache.type); - entitySerialization.DeserializeEntityComponents(serializationData, entityDescriptor, ref initializer - , serializationType); + entitySerialization.DeserializeEntityComponents(serializationData, entityDescriptor, ref initializer, + serializationType); return initializer; } diff --git a/com.sebaslab.svelto.ecs/Serialization/EnginesRoot.GenericEntitySerialization.cs b/com.sebaslab.svelto.ecs/Serialization/EnginesRoot.GenericEntitySerialization.cs index 8fa80ee..1b2ff8d 100644 --- a/com.sebaslab.svelto.ecs/Serialization/EnginesRoot.GenericEntitySerialization.cs +++ b/com.sebaslab.svelto.ecs/Serialization/EnginesRoot.GenericEntitySerialization.cs @@ -1,4 +1,5 @@ using System; +using System.Runtime.CompilerServices; using Svelto.DataStructures; using Svelto.ECS.Serialization; @@ -17,11 +18,11 @@ namespace Svelto.ECS uint descriptorHash = serializableEntityComponent.descriptorHash; SerializationDescriptorMap serializationDescriptorMap = _enginesRoot._serializationDescriptorMap; - var entityDescriptor = serializationDescriptorMap.GetDescriptorFromHash(descriptorHash); - var entityComponentsToSerialise = entityDescriptor.componentsToSerialize; + ISerializableEntityDescriptor entityDescriptor = serializationDescriptorMap.GetDescriptorFromHash(descriptorHash); + ISerializableComponentBuilder[] entityComponentsToSerialise = entityDescriptor.componentsToSerialize; var header = - new SerializableEntityHeader(descriptorHash, egid, (byte) entityComponentsToSerialise.Length); + new SerializableEntityHeader(descriptorHash, egid, (byte)entityComponentsToSerialise.Length); header.Copy(serializationData); for (int index = 0; index < entityComponentsToSerialise.Length; index++) @@ -33,8 +34,8 @@ namespace Svelto.ECS } } - public EntityInitializer DeserializeNewEntity - (EGID egid, ISerializationData serializationData, int serializationType) + public EntityInitializer DeserializeNewEntity(EGID egid, ISerializationData serializationData, + int serializationType) { //todo: SerializableEntityHeader may be needed to be customizable var serializableEntityHeader = new SerializableEntityHeader(serializationData); @@ -44,9 +45,8 @@ namespace Svelto.ECS var entityDescriptor = serializationDescriptorMap.GetDescriptorFromHash(descriptorHash); IDeserializationFactory factory = serializationDescriptorMap.GetSerializationFactory(descriptorHash); - return factory.BuildDeserializedEntity(egid, serializationData, entityDescriptor, serializationType - , this, this._enginesRoot.GenerateEntityFactory() - , _enginesRoot._isDeserializationOnly); + return factory.BuildDeserializedEntity(egid, serializationData, entityDescriptor, serializationType, + this, this._enginesRoot.GenerateEntityFactory(), _enginesRoot is SerializingEnginesRoot); } public void DeserializeEntity(ISerializationData serializationData, int serializationType) @@ -65,9 +65,9 @@ namespace Svelto.ECS DeserializeEntityInternal(serializationData, egid, serializableEntityHeader, serializationType); } - public void DeserializeEntityComponents - (ISerializationData serializationData, ISerializableEntityDescriptor entityDescriptor - , ref EntityInitializer initializer, int serializationType) + public void DeserializeEntityComponents(ISerializationData serializationData, + ISerializableEntityDescriptor entityDescriptor, ref EntityInitializer initializer, + int serializationType) { foreach (var serializableEntityBuilder in entityDescriptor.componentsToSerialize) { @@ -87,12 +87,13 @@ namespace Svelto.ECS /// /// /// - public T DeserializeEntityComponent - (ISerializationData serializationData, ISerializableEntityDescriptor entityDescriptor - , int serializationType) where T : unmanaged, IEntityComponent + public T DeserializeEntityComponent(ISerializationData serializationData, + ISerializableEntityDescriptor entityDescriptor, int serializationType) + where T : unmanaged, IEntityComponent { var readPos = serializationData.dataPos; T entityComponent = default; + foreach (var serializableEntityBuilder in entityDescriptor.componentsToSerialize) { if (serializableEntityBuilder is SerializableComponentBuilder entityBuilder) @@ -109,27 +110,24 @@ namespace Svelto.ECS return entityComponent; } - public void DeserializeEntityToSwap(EGID localEgid, EGID toEgid) + public void DeserializeEntityToSwap(EGID fromEGID, EGID toEGID, [CallerMemberName] string caller = null) { EntitiesDB entitiesDb = _enginesRoot._entitiesDB; - ref var serializableEntityComponent = - ref entitiesDb.QueryEntity(localEgid); + ref var serializableEntityComponent = ref entitiesDb.QueryEntity(fromEGID); SerializationDescriptorMap serializationDescriptorMap = _enginesRoot._serializationDescriptorMap; uint descriptorHash = serializableEntityComponent.descriptorHash; var entityDescriptor = serializationDescriptorMap.GetDescriptorFromHash(descriptorHash); - var entitySubmitOperation = - new EntitySubmitOperation(EntitySubmitOperationType.Swap, localEgid, toEgid - , entityDescriptor.componentsToBuild); + _enginesRoot.CheckRemoveEntityID(fromEGID, entityDescriptor.realType, caller); + _enginesRoot.CheckAddEntityID(toEGID, entityDescriptor.realType, caller); - _enginesRoot.CheckRemoveEntityID(localEgid, entityDescriptor.realType); - _enginesRoot.CheckAddEntityID(toEgid, entityDescriptor.realType); - - _enginesRoot.QueueEntitySubmitOperation(entitySubmitOperation); + /// Serializable Entity Descriptors can be extended so we need to use FindRealComponents + _enginesRoot.QueueSwapEntityOperation(fromEGID, toEGID, + _enginesRoot.FindRealComponents(fromEGID, entityDescriptor.componentsToBuild), caller); } - public void DeserializeEntityToDelete(EGID egid) + public void DeserializeEntityToDelete(EGID egid, [CallerMemberName] string caller = null) { EntitiesDB entitiesDB = _enginesRoot._entitiesDB; ref var serializableEntityComponent = ref entitiesDB.QueryEntity(egid); @@ -138,13 +136,49 @@ namespace Svelto.ECS SerializationDescriptorMap serializationDescriptorMap = _enginesRoot._serializationDescriptorMap; var entityDescriptor = serializationDescriptorMap.GetDescriptorFromHash(descriptorHash); - _enginesRoot.CheckRemoveEntityID(egid, entityDescriptor.realType); + _enginesRoot.CheckRemoveEntityID(egid, entityDescriptor.realType, caller); - var entitySubmitOperation = - new EntitySubmitOperation(EntitySubmitOperationType.Remove, egid, egid - , entityDescriptor.componentsToBuild); + try + { + /// Serializable Entity Descriptors can be extended so we need to use FindRealComponents + _enginesRoot.QueueRemoveEntityOperation(egid, + _enginesRoot.FindRealComponents(egid, entityDescriptor.componentsToBuild), caller); + } + catch + { + Svelto.Console.LogError( + $"something went wrong while deserializing entity {entityDescriptor.realType}"); - _enginesRoot.QueueEntitySubmitOperation(entitySubmitOperation); + throw; + } + } + + public void SkipEntityDeserialization(ISerializationData serializationData, int serializationType, + int numberOfEntities) + { + uint dataPositionBeforeHeader = serializationData.dataPos; + var serializableEntityHeader = new SerializableEntityHeader(serializationData); + + uint headerSize = serializationData.dataPos - dataPositionBeforeHeader; + + uint descriptorHash = serializableEntityHeader.descriptorHash; + SerializationDescriptorMap serializationDescriptorMap = _enginesRoot._serializationDescriptorMap; + var entityDescriptor = serializationDescriptorMap.GetDescriptorFromHash(descriptorHash); + + uint componentSizeTotal = 0; + + foreach (var serializableEntityBuilder in entityDescriptor.componentsToSerialize) + { + componentSizeTotal += serializableEntityBuilder.Size(serializationType); + } + + //When constructing an SerializableEntityHeader the data position of the serializationData is incremented by the size of the header. + //Since a header is needed to get the entity descriptor, we need to account for one less header than usual, since the data has already + //been incremented once. + var totalBytesToSkip = (uint)(headerSize * (numberOfEntities - 1)) + + (uint)(componentSizeTotal * numberOfEntities); + + serializationData.dataPos += totalBytesToSkip; } public uint GetHashFromGroup(ExclusiveGroupStruct groupStruct) @@ -164,11 +198,13 @@ namespace Svelto.ECS serializationDescriptorMap.RegisterSerializationFactory(deserializationFactory); } - internal EntitySerialization(EnginesRoot enginesRoot) { _enginesRoot = enginesRoot; } + internal EntitySerialization(EnginesRoot enginesRoot) + { + _root = new Svelto.DataStructures.WeakReference(enginesRoot); + } - void SerializeEntityComponent - (EGID entityGID, ISerializableComponentBuilder componentBuilder, ISerializationData serializationData - , int serializationType) + void SerializeEntityComponent(EGID entityGID, ISerializableComponentBuilder componentBuilder, + ISerializationData serializationData, int serializationType) { ExclusiveGroupStruct groupId = entityGID.groupID; Type entityType = componentBuilder.GetEntityComponentType(); @@ -180,15 +216,14 @@ namespace Svelto.ECS componentBuilder.Serialize(entityGID.entityID, safeDictionary, serializationData, serializationType); } - void DeserializeEntityInternal - (ISerializationData serializationData, EGID egid, SerializableEntityHeader serializableEntityHeader - , int serializationType) + void DeserializeEntityInternal(ISerializationData serializationData, EGID egid, + SerializableEntityHeader serializableEntityHeader, int serializationType) { SerializationDescriptorMap descriptorMap = _enginesRoot._serializationDescriptorMap; var entityDescriptor = descriptorMap.GetDescriptorFromHash(serializableEntityHeader.descriptorHash); - if (_enginesRoot._groupEntityComponentsDB.TryGetValue(egid.groupID, out var entitiesInGroupPerType) - == false) + if (_enginesRoot._groupEntityComponentsDB.TryGetValue(egid.groupID, out var entitiesInGroupPerType) == + false) throw new Exception("Entity Serialization failed"); foreach (var serializableEntityBuilder in entityDescriptor.componentsToSerialize) @@ -197,16 +232,18 @@ namespace Svelto.ECS new RefWrapperType(serializableEntityBuilder.GetEntityComponentType()), out var safeDictionary); serializationData.BeginNextEntityComponent(); - serializableEntityBuilder.Deserialize(egid.entityID, safeDictionary, serializationData - , serializationType); + serializableEntityBuilder.Deserialize(egid.entityID, safeDictionary, serializationData, + serializationType); } } - readonly EnginesRoot _enginesRoot; + EnginesRoot _enginesRoot => _root.Target; + readonly Svelto.DataStructures.WeakReference _root; } - public IEntitySerialization GenerateEntitySerializer() { return new EntitySerialization(this); } - - readonly bool _isDeserializationOnly; + public IEntitySerialization GenerateEntitySerializer() + { + return new EntitySerialization(this); + } } } \ No newline at end of file diff --git a/com.sebaslab.svelto.ecs/Serialization/EnginesRoot.SerializableEntityHeader.cs b/com.sebaslab.svelto.ecs/Serialization/EnginesRoot.SerializableEntityHeader.cs index 6e80b10..868a4d1 100644 --- a/com.sebaslab.svelto.ecs/Serialization/EnginesRoot.SerializableEntityHeader.cs +++ b/com.sebaslab.svelto.ecs/Serialization/EnginesRoot.SerializableEntityHeader.cs @@ -43,7 +43,7 @@ namespace Svelto.ECS internal void Copy(ISerializationData serializationData) { - serializationData.data.ExpandBy(SIZE); + serializationData.data.IncrementCountBy(SIZE); // Splitting the descriptorHash_ (uint, 32 bit) into four bytes. serializationData.data[serializationData.dataPos++] = (byte) (descriptorHash & 0xff); diff --git a/com.sebaslab.svelto.ecs/Serialization/IEntitySerialization.cs b/com.sebaslab.svelto.ecs/Serialization/IEntitySerialization.cs index 5bb6ce1..c1bf424 100644 --- a/com.sebaslab.svelto.ecs/Serialization/IEntitySerialization.cs +++ b/com.sebaslab.svelto.ecs/Serialization/IEntitySerialization.cs @@ -1,3 +1,5 @@ +using System.Runtime.CompilerServices; + namespace Svelto.ECS.Serialization { public interface IEntitySerialization @@ -49,19 +51,29 @@ namespace Svelto.ECS.Serialization /// EntityInitializer DeserializeNewEntity(EGID egid, ISerializationData serializationData, int serializationType); + /// + /// Skips over entities without deserializing them, but incrementing the data position of the serialization data + /// as if it had + /// + /// + /// + /// + void SkipEntityDeserialization(ISerializationData serializationData, int serializationType, + int numberOfEntities); /// /// Special Entity Swap method that works without knowing the EntityDescriptor to swap /// - /// - /// - void DeserializeEntityToSwap(EGID localEgid, EGID toEgid); + /// + /// + /// + void DeserializeEntityToSwap(EGID fromEGID, EGID toEGID, [CallerMemberName] string caller = null); /// /// Special Entity delete method that works without knowing the EntityDescriptor to delete /// /// - void DeserializeEntityToDelete(EGID egid); + void DeserializeEntityToDelete(EGID egid, [CallerMemberName] string caller = null); uint GetHashFromGroup(ExclusiveGroupStruct groupStruct); diff --git a/com.sebaslab.svelto.ecs/Serialization/PartialSerializer.cs b/com.sebaslab.svelto.ecs/Serialization/PartialSerializer.cs index 42bc959..063f983 100644 --- a/com.sebaslab.svelto.ecs/Serialization/PartialSerializer.cs +++ b/com.sebaslab.svelto.ecs/Serialization/PartialSerializer.cs @@ -30,19 +30,30 @@ namespace Svelto.ECS.Serialization myMembers[i].IsPrivate == false) throw new ECSException($"field cannot be serialised {fieldType} in {myType.FullName}"); - var offset = Marshal.OffsetOf(myMembers[i].Name); - var sizeOf = (uint)Marshal.SizeOf(fieldType); + var offset = Marshal.OffsetOf(myMembers[i].Name); + uint sizeOf; + if (fieldType == typeof(bool)) + sizeOf = 1; + else + sizeOf = (uint)Marshal.SizeOf(fieldType); + offsets.Add(((uint) offset.ToInt32(), sizeOf)); totalSize += sizeOf; } } } + if (myType.IsExplicitLayout == false) + throw new ECSException($"PartialSerializer requires explicit layout {myType}"); +#if SLOW_SVELTO_SUBMISSION if (myType.GetProperties().Length > (ComponentBuilder.HAS_EGID ? 1 : 0)) throw new ECSException("serializable entity struct must be property less ".FastConcat(myType.FullName)); +#endif + if (totalSize == 0) + throw new ECSException($"{typeof(T).Name} is being serialized with {nameof(PartialSerializer)} but has size 0!"); } - public bool Serialize(in T value, ISerializationData serializationData) + public bool Serialize(in T value, ISerializationData serializationData) { unsafe { @@ -63,7 +74,7 @@ namespace Svelto.ECS.Serialization return true; } - public bool Deserialize(ref T value, ISerializationData serializationData) + public bool Deserialize(ref T value, ISerializationData serializationData) { unsafe { diff --git a/com.sebaslab.svelto.ecs/Serialization/SerializableComponentBuilder.cs b/com.sebaslab.svelto.ecs/Serialization/SerializableComponentBuilder.cs index 45a9482..bcc4baa 100644 --- a/com.sebaslab.svelto.ecs/Serialization/SerializableComponentBuilder.cs +++ b/com.sebaslab.svelto.ecs/Serialization/SerializableComponentBuilder.cs @@ -31,7 +31,7 @@ namespace Svelto.ECS.Serialization serializationData.dataPos = (uint) serializationData.data.count; - serializationData.data.ExpandBy(componentSerializer.size); + serializationData.data.IncrementCountBy(componentSerializer.size); componentSerializer.SerializeSafe(val, serializationData); } @@ -58,7 +58,7 @@ namespace Svelto.ECS.Serialization { IComponentSerializer componentSerializer = _serializers[(int) serializationType]; - componentSerializer.DeserializeSafe(ref initializer.GetOrCreate(), serializationData); + componentSerializer.DeserializeSafe(ref initializer.GetOrAdd(), serializationData); } public uint Size(int serializationType) diff --git a/com.sebaslab.svelto.ecs/Serialization/SerializableEntityComponent.cs b/com.sebaslab.svelto.ecs/Serialization/SerializableEntityComponent.cs index 57e1b2d..ed45a27 100644 --- a/com.sebaslab.svelto.ecs/Serialization/SerializableEntityComponent.cs +++ b/com.sebaslab.svelto.ecs/Serialization/SerializableEntityComponent.cs @@ -1,9 +1,7 @@ namespace Svelto.ECS { - struct SerializableEntityComponent : IEntityComponent, INeedEGID + struct SerializableEntityComponent : IEntityComponent { public uint descriptorHash; - - public EGID ID { get; set; } } } \ No newline at end of file diff --git a/com.sebaslab.svelto.ecs/Serialization/SerializingEnginesRoot.cs b/com.sebaslab.svelto.ecs/Serialization/SerializingEnginesRoot.cs new file mode 100644 index 0000000..465c848 --- /dev/null +++ b/com.sebaslab.svelto.ecs/Serialization/SerializingEnginesRoot.cs @@ -0,0 +1,16 @@ +using Svelto.ECS.Schedulers; + +namespace Svelto.ECS +{ + public class SerializingEnginesRoot : EnginesRoot + { + public SerializingEnginesRoot + (EntitiesSubmissionScheduler entitiesComponentScheduler) : base(entitiesComponentScheduler) + { } + + public SerializingEnginesRoot + (EntitiesSubmissionScheduler entitiesComponentScheduler, EnginesReadyOption enginesWaitForReady) : base( + entitiesComponentScheduler, enginesWaitForReady) + {} + } +} \ No newline at end of file diff --git a/com.sebaslab.svelto.ecs/Svelto.ECS.asmdef b/com.sebaslab.svelto.ecs/Svelto.ECS.asmdef index b13bc96..b04a2ef 100644 --- a/com.sebaslab.svelto.ecs/Svelto.ECS.asmdef +++ b/com.sebaslab.svelto.ecs/Svelto.ECS.asmdef @@ -1,58 +1,63 @@ { - "name": "Svelto.ECS", - "rootNamespace": "", - "references": [ - "Unity.Entities", - "Unity.Collections", - "Unity.Burst", - "Unity.Jobs", - "Svelto.Common" - ], - "includePlatforms": [], - "excludePlatforms": [], - "allowUnsafeCode": true, - "overrideReferences": true, - "precompiledReferences": [ - "System.Runtime.CompilerServices.Unsafe.dll" - ], - "autoReferenced": true, - "defineConstraints": [], - "versionDefines": [ - { - "name": "Unity", - "expression": "2019.3", - "define": "UNITY_JOBS" - }, - { - "name": "com.unity.entities", - "expression": "", - "define": "UNITY_ECS" - }, - { - "name": "com.unity.burst", - "expression": "", - "define": "UNITY_BURST" - }, - { - "name": "com.unity.collections", - "expression": "", - "define": "UNITY_COLLECTIONS" - }, - { - "name": "com.unity.entities", - "expression": "", - "define": "UNITY_NATIVE" - }, - { - "name": "com.unity.burst", - "expression": "", - "define": "UNITY_NATIVE" - }, - { - "name": "com.unity.collections", - "expression": "", - "define": "UNITY_NATIVE" - } - ], - "noEngineReferences": false + "name": "Svelto.ECS", + "rootNamespace": "", + "references": [ + "Unity.Entities", + "Unity.Collections", + "Unity.Burst", + "Unity.Jobs", + "Svelto.Common" + ], + "includePlatforms": [], + "excludePlatforms": [], + "allowUnsafeCode": true, + "overrideReferences": true, + "precompiledReferences": [ + "System.Runtime.CompilerServices.Unsafe.dll" + ], + "autoReferenced": true, + "defineConstraints": [], + "versionDefines": [ + { + "name": "Unity", + "expression": "2019.3", + "define": "UNITY_JOBS" + }, + { + "name": "com.unity.entities", + "expression": "", + "define": "UNITY_ECS" + }, + { + "name": "com.unity.burst", + "expression": "", + "define": "UNITY_BURST" + }, + { + "name": "com.unity.collections", + "expression": "", + "define": "UNITY_COLLECTIONS" + }, + { + "name": "com.unity.entities", + "expression": "", + "define": "UNITY_NATIVE" + }, + { + "name": "com.unity.burst", + "expression": "", + "define": "UNITY_NATIVE" + }, + { + "name": "com.unity.collections", + "expression": "", + "define": "UNITY_NATIVE" + }, + { + "name": "com.unity.entities", + "expression": "0.45", + "define": "UNITY_ECS_050" + } + ], + "noEngineReferences": false } \ No newline at end of file diff --git a/com.sebaslab.svelto.ecs/Svelto.ECS.csproj b/com.sebaslab.svelto.ecs/Svelto.ECS.csproj index 6119d6b..94e7dd3 100644 --- a/com.sebaslab.svelto.ecs/Svelto.ECS.csproj +++ b/com.sebaslab.svelto.ecs/Svelto.ECS.csproj @@ -4,8 +4,11 @@ 8 netstandard2.0 Svelto - 3.2.1 - 3.2.1 + 3.2.0 + 3.2.0 + false + Debug;Release;SlowSubmissionRelease;SlowSubmissionDebug + AnyCPU Svelto.ECS @@ -29,9 +32,19 @@ false true + + TRACE;SLOW_SVELTO_SUBMISSION;RELEASE + true + true + + + TRACE;SLOW_SVELTO_SUBMISSION;DEBUG + true + true + - + diff --git a/com.sebaslab.svelto.ecs/package.json b/com.sebaslab.svelto.ecs/package.json index 8194a2d..0084f87 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.2.4" + "com.sebaslab.svelto.common": "3.3.0" }, "keywords": [ "svelto", @@ -19,7 +19,7 @@ "svelto.ecs" ], "name": "com.sebaslab.svelto.ecs", - "version": "3.2.6", + "version": "3.3.0", "type": "library", "unity": "2019.3" } \ No newline at end of file diff --git a/com.sebaslab.svelto.ecs/version.json b/com.sebaslab.svelto.ecs/version.json index 339ceaa..8b25297 100644 --- a/com.sebaslab.svelto.ecs/version.json +++ b/com.sebaslab.svelto.ecs/version.json @@ -1,3 +1,3 @@ { - "version": "3.2.6" + "version": "3.3.0" }