From 0dfdf22aff8222ebfd8758dd8d9e752407ed17ba Mon Sep 17 00:00:00 2001 From: GitHub Date: Sat, 2 Oct 2021 16:34:45 +0000 Subject: [PATCH] UPM package version 3.2.0 --- CHANGELOG.md | 24 +- Components/EntityReferenceComponent.cs | 11 + .../EntityReferenceComponent.cs.meta | 2 +- Core/AssemblyUtility.cs | 39 ++ .../AssemblyUtility.cs.meta | 2 +- Core/CheckEntityUtilities.cs | 19 +- Core/ComponentBuilder.CheckFields.cs | 44 +- Core/ComponentBuilder.cs | 150 +++--- Core/EGID.cs | 22 +- Core/EGIDMapper.cs | 2 - Core/EnginesGroup/IStepEngine.cs | 2 +- Core/EnginesGroup/SortedEnginesGroup.cs | 45 ++ Core/EnginesGroup/UnsortedEnginesGroup.cs | 34 ++ ...EnginesRoot.DoubleBufferedEntitiesToAdd.cs | 60 ++- Core/EnginesRoot.Engines.cs | 273 ++++++---- Core/EnginesRoot.Entities.cs | 177 ++++--- Core/EnginesRoot.GenericEntityFactory.cs | 22 +- Core/EnginesRoot.GenericEntityFunctions.cs | 30 +- Core/EnginesRoot.Submission.cs | 252 ++++----- Core/EntitiesDB.FindGroups.cs | 215 +++++--- Core/EntitiesDB.cs | 159 +++--- Core/EntityCollection.cs | 2 +- .../DynamicEntityDescriptor.cs | 175 ++++-- .../ExtendibleEntityDescriptor.cs | 24 +- .../GenericEntityDescriptor.cs | 6 +- Core/EntityDescriptorTemplate.cs | 2 +- Core/EntityFactory.cs | 70 +-- Core/EntityInitializer.cs | 37 +- Debugger.meta => Core/EntityReference.meta | 2 +- .../EntityReference/EnginesRoot.LocatorMap.cs | 227 ++++++++ .../EnginesRoot.LocatorMap.cs.meta | 2 +- Core/EntityReference/EntitiesDB.References.cs | 32 ++ .../EntitiesDB.References.cs.meta | 2 +- Core/EntityReference/EntityReference.cs | 78 +++ Core/EntityReference/EntityReference.cs.meta | 11 + .../EntityReferenceMapElement.cs | 20 + .../EntityReferenceMapElement.cs.meta | 11 + Core/EntitySubmissionScheduler.cs | 2 - Core/EntityViewUtility.cs | 58 +- Core/Filters/EntitiesDB.GroupFilters.cs | 63 ++- Core/Filters/FilterGroup.cs | 68 ++- Core/Filters/GroupFilters.cs | 4 +- Core/GlobalTypeID.cs | 9 +- Core/GroupHashMap.cs | 115 ++++ Core/GroupHashMap.cs.meta | 11 + Core/GroupNamesMap.cs | 26 + Core/GroupNamesMap.cs.meta | 11 + Core/Groups/ExclusiveBuildGroup.cs | 14 +- Core/Groups/ExclusiveGroup.cs | 115 +--- Core/Groups/ExclusiveGroupBitmask.cs | 15 + Core/Groups/ExclusiveGroupBitmask.cs.meta | 11 + Core/Groups/ExclusiveGroupStruct.cs | 83 +-- Core/Groups/GroupCompound.cs | 499 ++++++++++-------- Core/Groups/NamedExclusiveGroup.cs | 3 +- Core/Hybrid/IEntityDescriptorHolder.cs | 2 +- Core/Hybrid/ValueReference.cs | 30 +- Core/IComponentBuilder.cs | 10 +- Core/IEntityFactory.cs | 18 +- Core/IEntityFunctions.cs | 12 +- Core/INeedEGID.cs | 4 +- Core/INeedEntityReference.cs | 15 + Core/INeedEntityReference.cs.meta | 11 + Core/QueryGroups.cs | 223 +++++--- Core/ReactEngineContainer.cs | 16 + Core/ReactEngineContainer.cs.meta | 11 + Core/SetEGIDWithoutBoxing.cs | 32 +- Core/SimpleEntitiesSubmissionScheduler.cs | 56 +- Core/Streams/Consumer.cs | 44 +- Core/Streams/EnginesRoot.Streams.cs | 6 +- Core/Streams/EntitiesDB.Streams.cs | 10 +- Core/Streams/EntitiesStreams.cs | 16 +- Core/Streams/EntityStream.cs | 6 +- .../GenericentityStreamConsumerFactory.cs | 3 + DataStructures/FastTypeSafeDictionary.cs | 293 ---------- DataStructures/ITypeSafeDictionary.cs | 8 +- DataStructures/TypeSafeDictionary.cs | 235 +++++---- DataStructures/Unmanaged/AtomicNativeBags.cs | 19 +- DataStructures/Unmanaged/NativeBag.cs | 9 +- .../Unmanaged/NativeDynamicArray.cs | 55 +- .../Unmanaged/NativeDynamicArrayCast.cs | 17 + .../NativeDynamicArrayUnityExtension.cs | 2 +- DataStructures/Unmanaged/SharedNativeInt.cs | 45 +- .../Unmanaged/ThreadSafeNativeBag.cs | 2 +- DataStructures/Unmanaged/UnsafeArray.cs | 22 +- Debugger/ExclusiveGroupDebugger.cs | 70 --- Dispatcher/DispatchOnChange.cs | 25 - Dispatcher/DispatchOnSet.cs | 48 -- Dispatcher/ReactiveValue.cs | 97 ++++ Dispatcher/ReactiveValue.cs.meta | 11 + ECSResources/ECSResources.cs | 25 +- ECSResources/ECSString.cs | 10 +- Extensions/DisposeDisposablesEngine.cs | 24 + Extensions/DisposeDisposablesEngine.cs.meta | 11 + Extensions/Svelto/AllGroupsEnumerable.cs | 9 +- Extensions/Svelto/EGIDMultiMapper.cs | 149 ++++++ Extensions/Svelto/EGIDMultiMapper.cs.meta | 11 + .../Svelto/EntitiesDBFiltersExtension.cs | 16 + .../Svelto/EntitiesDBFiltersExtension.cs.meta | 11 + .../Svelto/EntityManagedDBExtensions.cs | 51 +- Extensions/Svelto/EntityNativeDBExtensions.cs | 245 +++++---- Extensions/Svelto/FilterGroupExtensions.cs | 19 + .../Svelto/FilterGroupExtensions.cs.meta | 11 + Extensions/Svelto/GroupsEnumerable.cs | 48 +- Extensions/Unity/DOTS/Jobs/IJobifiedEngine.cs | 3 + .../DOTS/Jobs/JobifiedEnginesGroup.cs.meta | 11 - .../DOTS/Jobs/SortedJobifiedEnginesGroup.cs | 2 +- .../Unity/DOTS/Jobs/UnityJobExtensions.cs | 38 +- ...oup.cs => UnsortedJobifiedEnginesGroup.cs} | 13 +- .../Jobs/UnsortedJobifiedEnginesGroup.cs.meta | 11 + .../Native/EnginesRoot.NativeOperation.cs | 41 +- .../Unity/DOTS/Native/NativeEGIDMapper.cs | 8 +- .../DOTS/Native/NativeEGIDMultiMapper.cs | 47 +- .../Unity/DOTS/Native/NativeEntityFactory.cs | 24 +- .../DOTS/Native/NativeEntityInitializer.cs | 8 +- .../Unity/DOTS/Native/NativeEntityRemove.cs | 2 +- .../Unity/DOTS/Native/NativeEntitySwap.cs | 2 +- .../Native/UnityEntityDBExtensions.cs.meta | 11 - ...ns.cs => UnityNativeEntityDBExtensions.cs} | 24 +- .../UnityNativeEntityDBExtensions.cs.meta | 11 + .../Unity/DOTS/UECS/SubmissionEngine.cs | 23 +- .../DOTS/UECS/SveltoOverUECSEnginesGroup.cs | 18 +- .../UECS/SveltoUECSEntitiesSubmissionGroup.cs | 226 +++++--- .../Unity/DOTS/UECS/SyncSveltoToUECSGroup.cs | 58 +- .../Unity/DOTS/UECS/SyncUECSToSveltoGroup.cs | 2 +- Extensions/Unity/DOTS/UECS/UECSSveltoEGID.cs | 23 +- Extensions/Unity/EGIDHolderImplementor.cs | 32 -- .../Unity/EGIDHolderImplementor.cs.meta | 11 - .../Unity/EntityDescriptorHolderHelper.cs | 30 -- .../EntityDescriptorHolderHelper.cs.meta | 11 - Extensions/Unity/GameObjects.meta | 8 + .../Unity/GameObjects/AbstractionLayer.meta | 8 + .../GameObjects/AbstractionLayer/GOManager.cs | 31 ++ .../AbstractionLayer/GOManager.cs.meta | 11 + .../GameObjects/EntityDescriptorHolder.cs | 69 +++ .../EntityDescriptorHolder.cs.meta | 11 + .../GenericEntityDescriptorHolder.cs | 3 +- .../GenericEntityDescriptorHolder.cs.meta | 2 +- .../Unity/GameObjects/Implementors.meta | 8 + .../EntityReferenceHolderImplementor.cs | 13 + .../EntityReferenceHolderImplementor.cs.meta | 11 + .../Unity/GameObjects/ListToPopupDrawer.cs | 40 ++ .../GameObjects/ListToPopupDrawer.cs.meta | 11 + .../{ => GameObjects}/SveltoGUIHelper.cs | 148 +++--- .../{ => GameObjects}/SveltoGUIHelper.cs.meta | 2 +- Extensions/Unity/MonoScheduler.cs | 35 ++ Extensions/Unity/MonoScheduler.cs.meta | 11 + .../Unity/UnityEntitiesSubmissionScheduler.cs | 40 +- README.md | 17 +- Serialization/DefaultVersioningFactory.cs | 15 +- .../EnginesRoot.GenericEntitySerialization.cs | 47 +- .../EnginesRoot.SerializableEntityHeader.cs | 2 +- .../EntitiesDB.SerializationDescriptorMap.cs | 130 ++--- Serialization/IComponentSerializer.cs | 4 +- Serialization/IEntitySerialization.cs | 4 + .../ISerializableComponentBuilder.cs | 2 + .../ISerializableEntityDescriptor.cs | 2 +- Serialization/SerializableComponentBuilder.cs | 15 +- Serialization/SerializableEntityDescriptor.cs | 9 +- Svelto.ECS.asmdef | 122 ++--- Svelto.ECS.csproj | 53 +- Svelto.ECS.nuspec | 20 + Svelto.ECS.nuspec.meta | 7 + Svelto.ECS.targets | 9 + Svelto.ECS.targets.meta | 7 + package.json | 4 +- 165 files changed, 4403 insertions(+), 2733 deletions(-) create mode 100644 Components/EntityReferenceComponent.cs rename Debugger/ExclusiveGroupDebugger.cs.meta => Components/EntityReferenceComponent.cs.meta (83%) create mode 100644 Core/AssemblyUtility.cs rename DataStructures/FastTypeSafeDictionary.cs.meta => Core/AssemblyUtility.cs.meta (83%) rename Debugger.meta => Core/EntityReference.meta (77%) create mode 100644 Core/EntityReference/EnginesRoot.LocatorMap.cs rename Dispatcher/DispatchOnSet.cs.meta => Core/EntityReference/EnginesRoot.LocatorMap.cs.meta (83%) create mode 100644 Core/EntityReference/EntitiesDB.References.cs rename Dispatcher/DispatchOnChange.cs.meta => Core/EntityReference/EntitiesDB.References.cs.meta (83%) create mode 100644 Core/EntityReference/EntityReference.cs create mode 100644 Core/EntityReference/EntityReference.cs.meta create mode 100644 Core/EntityReference/EntityReferenceMapElement.cs create mode 100644 Core/EntityReference/EntityReferenceMapElement.cs.meta create mode 100644 Core/GroupHashMap.cs create mode 100644 Core/GroupHashMap.cs.meta create mode 100644 Core/GroupNamesMap.cs create mode 100644 Core/GroupNamesMap.cs.meta create mode 100644 Core/Groups/ExclusiveGroupBitmask.cs create mode 100644 Core/Groups/ExclusiveGroupBitmask.cs.meta create mode 100644 Core/INeedEntityReference.cs create mode 100644 Core/INeedEntityReference.cs.meta create mode 100644 Core/ReactEngineContainer.cs create mode 100644 Core/ReactEngineContainer.cs.meta delete mode 100644 DataStructures/FastTypeSafeDictionary.cs delete mode 100644 Debugger/ExclusiveGroupDebugger.cs delete mode 100644 Dispatcher/DispatchOnChange.cs delete mode 100644 Dispatcher/DispatchOnSet.cs create mode 100644 Dispatcher/ReactiveValue.cs create mode 100644 Dispatcher/ReactiveValue.cs.meta create mode 100644 Extensions/DisposeDisposablesEngine.cs create mode 100644 Extensions/DisposeDisposablesEngine.cs.meta create mode 100644 Extensions/Svelto/EGIDMultiMapper.cs create mode 100644 Extensions/Svelto/EGIDMultiMapper.cs.meta create mode 100644 Extensions/Svelto/EntitiesDBFiltersExtension.cs create mode 100644 Extensions/Svelto/EntitiesDBFiltersExtension.cs.meta create mode 100644 Extensions/Svelto/FilterGroupExtensions.cs create mode 100644 Extensions/Svelto/FilterGroupExtensions.cs.meta delete mode 100644 Extensions/Unity/DOTS/Jobs/JobifiedEnginesGroup.cs.meta rename Extensions/Unity/DOTS/Jobs/{JobifiedEnginesGroup.cs => UnsortedJobifiedEnginesGroup.cs} (79%) create mode 100644 Extensions/Unity/DOTS/Jobs/UnsortedJobifiedEnginesGroup.cs.meta delete mode 100644 Extensions/Unity/DOTS/Native/UnityEntityDBExtensions.cs.meta rename Extensions/Unity/DOTS/Native/{UnityEntityDBExtensions.cs => UnityNativeEntityDBExtensions.cs} (75%) create mode 100644 Extensions/Unity/DOTS/Native/UnityNativeEntityDBExtensions.cs.meta delete mode 100644 Extensions/Unity/EGIDHolderImplementor.cs delete mode 100644 Extensions/Unity/EGIDHolderImplementor.cs.meta delete mode 100644 Extensions/Unity/EntityDescriptorHolderHelper.cs delete mode 100644 Extensions/Unity/EntityDescriptorHolderHelper.cs.meta create mode 100644 Extensions/Unity/GameObjects.meta create mode 100644 Extensions/Unity/GameObjects/AbstractionLayer.meta create mode 100644 Extensions/Unity/GameObjects/AbstractionLayer/GOManager.cs create mode 100644 Extensions/Unity/GameObjects/AbstractionLayer/GOManager.cs.meta create mode 100644 Extensions/Unity/GameObjects/EntityDescriptorHolder.cs create mode 100644 Extensions/Unity/GameObjects/EntityDescriptorHolder.cs.meta rename Extensions/Unity/{ => GameObjects}/GenericEntityDescriptorHolder.cs (91%) rename Extensions/Unity/{ => GameObjects}/GenericEntityDescriptorHolder.cs.meta (83%) create mode 100644 Extensions/Unity/GameObjects/Implementors.meta create mode 100644 Extensions/Unity/GameObjects/Implementors/EntityReferenceHolderImplementor.cs create mode 100644 Extensions/Unity/GameObjects/Implementors/EntityReferenceHolderImplementor.cs.meta create mode 100644 Extensions/Unity/GameObjects/ListToPopupDrawer.cs create mode 100644 Extensions/Unity/GameObjects/ListToPopupDrawer.cs.meta rename Extensions/Unity/{ => GameObjects}/SveltoGUIHelper.cs (56%) rename Extensions/Unity/{ => GameObjects}/SveltoGUIHelper.cs.meta (83%) create mode 100644 Extensions/Unity/MonoScheduler.cs create mode 100644 Extensions/Unity/MonoScheduler.cs.meta create mode 100644 Svelto.ECS.nuspec create mode 100644 Svelto.ECS.nuspec.meta create mode 100644 Svelto.ECS.targets create mode 100644 Svelto.ECS.targets.meta diff --git a/CHANGELOG.md b/CHANGELOG.md index 05f0373..62e5111 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,21 @@ # Changelog -All notable changes to this project will be documented in this file. I created this file with Svelto.ECS version 3.1. +All notable changes to this project will be documented in this file. Changes are listed in random order of importance. + +## [3.2.0] + +* Improved checks on Svelto rules for the declaration of components and view components. This set of rules is not final yet (ideally one day they should be moved to static analyzers) +* Introduce the concept of Entity Reference. It's a very light weight identifier to keep track of entities EGID that can change dynamically (EGIDs change when groups are swapped), Entity References never change. The underlying code will be optimised even further in future. +* Introduced the concept of Disabled Group. Once a group is marked as disabled, queries will always ignore it. +* Merged DispatchOnSet and DispatchOnChange and renamed to ReactiveValue. This class will be superseded by better patterns in future. +* Added FindGroups with 4 components +* Improved QueryGroups interface +* Improved DynamicEntityDescriptor interface +* Improved ExtendibleEntityDescriptor interface +* Improved Native memory support +* Improved Svelto and Unity DOTS integration +* Improved and fixed Serialization code +* Ensure that the creation of static groups is deterministic (GroupHashMap) + ## [3.1.3] @@ -25,14 +41,16 @@ All notable changes to this project will be documented in this file. I created t ### Changed * rearrange folders structures for clarity -* added DoubleEntitiesEnumerator, as seen in MiniExample 4, to allow a double iteration of the same group skipping already checked tuples +* added DoubleEntitiesEnumerator, as seen in MiniExample 4, to allow a double iteration of the same group skipping + already checked tuples * reengineered the behaviour of WaitForSubmissionEnumerator * removed redudant SimpleEntitySubmissionSchedulerInterface interface * renamed BuildGroup in to ExclusiveBuildGroup * renamed EntityComponentInitializer to EntityInitializer * Entity Submission now can optionally be time sliced (based on number of entities to submit per slice) * working on the Unity extension Submission Engine, still WIP -* added the possibility to hold a reference in a EntityViewComponent. This reference cannot be accesses as an object, but can be converted to the original object in OOP abstract layers +* added the possibility to hold a reference in a EntityViewComponent. This reference cannot be accesses as an object, + but can be converted to the original object in OOP abstract layers * renamed NativeEntityComponentInitializer to NativeEntityInitializer ### Fixed diff --git a/Components/EntityReferenceComponent.cs b/Components/EntityReferenceComponent.cs new file mode 100644 index 0000000..803ed1c --- /dev/null +++ b/Components/EntityReferenceComponent.cs @@ -0,0 +1,11 @@ +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/Debugger/ExclusiveGroupDebugger.cs.meta b/Components/EntityReferenceComponent.cs.meta similarity index 83% rename from Debugger/ExclusiveGroupDebugger.cs.meta rename to Components/EntityReferenceComponent.cs.meta index b043d69..ae318eb 100644 --- a/Debugger/ExclusiveGroupDebugger.cs.meta +++ b/Components/EntityReferenceComponent.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: fd73f27c31073381b9b694f3f32941f3 +guid: 845c1c4af4ca3dd598319878015a84f3 MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/Core/AssemblyUtility.cs b/Core/AssemblyUtility.cs new file mode 100644 index 0000000..7b31797 --- /dev/null +++ b/Core/AssemblyUtility.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections.Generic; +using System.Reflection; + +public static class AssemblyUtility +{ + static readonly List AssemblyList = new List(); + + static AssemblyUtility() + { + var assemblyName = Assembly.GetExecutingAssembly().GetName(); + Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); + + foreach (Assembly assembly in assemblies) + { + AssemblyName[] referencedAssemblies = assembly.GetReferencedAssemblies(); + if (Array.Exists(referencedAssemblies, (a) => a.Name == assemblyName.Name)) + { + AssemblyList.Add(assembly); + } + } + } + + public static IEnumerable GetTypesSafe(Assembly assembly) + { + try + { + Type[] types = assembly.GetTypes(); + + return types; + } + catch (ReflectionTypeLoadException e) + { + return e.Types; + } + } + + public static List GetCompatibleAssemblies() { return AssemblyList; } +} \ No newline at end of file diff --git a/DataStructures/FastTypeSafeDictionary.cs.meta b/Core/AssemblyUtility.cs.meta similarity index 83% rename from DataStructures/FastTypeSafeDictionary.cs.meta rename to Core/AssemblyUtility.cs.meta index c5151b8..ac8d749 100644 --- a/DataStructures/FastTypeSafeDictionary.cs.meta +++ b/Core/AssemblyUtility.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 5aab8c4d657d3850b22b7a8d7b0038b6 +guid: 5f088a8b64103e2a99034b2ada59fa05 MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/Core/CheckEntityUtilities.cs b/Core/CheckEntityUtilities.cs index 1860f01..d8088c9 100644 --- a/Core/CheckEntityUtilities.cs +++ b/Core/CheckEntityUtilities.cs @@ -4,6 +4,7 @@ using System.Diagnostics; #endif using System; using System.Collections.Generic; +using System.Runtime.CompilerServices; using Svelto.DataStructures; namespace Svelto.ECS @@ -17,18 +18,18 @@ namespace Svelto.ECS #if DONT_USE [Conditional("CHECK_ALL")] #endif - void CheckRemoveEntityID(EGID egid, Type entityDescriptorType, string caller = "") + void CheckRemoveEntityID(EGID egid, Type entityDescriptorType, [CallerMemberName] string caller = null) { if (_multipleOperationOnSameEGIDChecker.ContainsKey(egid) == true) throw new ECSException( - "Executing multiple structural changes in one submission on the same entity is not supported " + "Executing multiple structural changes (remove) in one submission on the same entity is not supported " .FastConcat(" caller: ", caller, " ").FastConcat(egid.entityID).FastConcat(" groupid: ") .FastConcat(egid.groupID.ToName()).FastConcat(" type: ") .FastConcat(entityDescriptorType != null ? entityDescriptorType.Name : "not available") - .FastConcat(" operation was: ") + .FastConcat(" previous operation was: ") .FastConcat(_multipleOperationOnSameEGIDChecker[egid] == 1 ? "add" : "remove")); - if (_idChecker.TryGetValue(egid.groupID, out var hash)) + if (_idChecker.TryGetValue((uint) 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) @@ -45,18 +46,18 @@ namespace Svelto.ECS #if DONT_USE [Conditional("CHECK_ALL")] #endif - void CheckAddEntityID(EGID egid, Type entityDescriptorType, string caller = "") + void CheckAddEntityID(EGID egid, Type entityDescriptorType, [CallerMemberName] string caller = null) { if (_multipleOperationOnSameEGIDChecker.ContainsKey(egid) == true) throw new ECSException( - "Executing multiple structural changes in one submission on the same entity is not supported " + "Executing multiple structural changes (build) on the same entity is not supported " .FastConcat(" caller: ", caller, " ").FastConcat(egid.entityID).FastConcat(" groupid: ") .FastConcat(egid.groupID.ToName()).FastConcat(" type: ") .FastConcat(entityDescriptorType != null ? entityDescriptorType.Name : "not available") - .FastConcat(" operation was: ") + .FastConcat(" previous operation was: ") .FastConcat(_multipleOperationOnSameEGIDChecker[egid] == 1 ? "add" : "remove")); - var hash = _idChecker.GetOrCreate(egid.groupID, () => new HashSet()); + var hash = _idChecker.GetOrCreate((uint) 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) @@ -72,7 +73,7 @@ namespace Svelto.ECS #if DONT_USE [Conditional("CHECK_ALL")] #endif - void RemoveGroupID(ExclusiveBuildGroup groupID) { _idChecker.Remove(groupID); } + void RemoveGroupID(ExclusiveBuildGroup groupID) { _idChecker.Remove((uint)groupID); } #if DONT_USE [Conditional("CHECK_ALL")] diff --git a/Core/ComponentBuilder.CheckFields.cs b/Core/ComponentBuilder.CheckFields.cs index e933d36..e33b87e 100644 --- a/Core/ComponentBuilder.CheckFields.cs +++ b/Core/ComponentBuilder.CheckFields.cs @@ -4,7 +4,6 @@ using System.Diagnostics; #endif using System; using System.Reflection; -using Svelto.Common; namespace Svelto.ECS { @@ -46,45 +45,41 @@ namespace Svelto.ECS if (fields.Length < 1) { - ProcessError("No valid fields found in Entity View Components", entityComponentType); + ProcessError("Entity View Components must have at least one interface declared as public field and not property", entityComponentType); } for (int i = fields.Length - 1; i >= 0; --i) { FieldInfo fieldInfo = fields[i]; - if (fieldInfo.FieldType.IsInterfaceEx() == true) + if (fieldInfo.FieldType.IsInterfaceEx() == false) { - PropertyInfo[] properties = fieldInfo.FieldType.GetProperties( - BindingFlags.Public | BindingFlags.Instance - | BindingFlags.DeclaredOnly); + ProcessError("Entity View Components must hold only entity components interfaces." + , entityComponentType); + } + + PropertyInfo[] properties = fieldInfo.FieldType.GetProperties( + BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly); - for (int j = properties.Length - 1; j >= 0; --j) + for (int j = properties.Length - 1; j >= 0; --j) + { + if (properties[j].PropertyType.IsGenericType) { - if (properties[j].PropertyType.IsGenericType) + Type genericTypeDefinition = properties[j].PropertyType.GetGenericTypeDefinition(); + if (genericTypeDefinition == RECATIVEVALUETYPE) { - Type genericTypeDefinition = properties[j].PropertyType.GetGenericTypeDefinition(); - if (genericTypeDefinition == DISPATCHONSETTYPE - || genericTypeDefinition == DISPATCHONCHANGETYPE) - { - continue; - } + continue; } + } - Type propertyType = properties[j].PropertyType; - + Type propertyType = properties[j].PropertyType; + if (propertyType != STRINGTYPE) + { //for EntityComponentStructs, component fields that are structs that hold strings //are allowed SubCheckFields(propertyType, entityComponentType, isStringAllowed: true); } } - else - if (fieldInfo.FieldType.IsUnmanagedEx() == false) - { - ProcessError("Entity View Components must hold only public interfaces, strings or unmanaged type fields.", - entityComponentType); - - } } } } @@ -129,8 +124,7 @@ namespace Svelto.ECS throw new ECSException(message, entityComponentType); } - static readonly Type DISPATCHONCHANGETYPE = typeof(DispatchOnChange<>); - static readonly Type DISPATCHONSETTYPE = typeof(DispatchOnSet<>); + static readonly Type RECATIVEVALUETYPE = typeof(ReactiveValue<>); static readonly Type EGIDType = typeof(EGID); static readonly Type EXCLUSIVEGROUPSTRUCTTYPE = typeof(ExclusiveGroupStruct); static readonly Type SERIALIZABLE_ENTITY_STRUCT = typeof(SerializableEntityComponent); diff --git a/Core/ComponentBuilder.cs b/Core/ComponentBuilder.cs index bd23c5d..5311994 100644 --- a/Core/ComponentBuilder.cs +++ b/Core/ComponentBuilder.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Reflection; +using DBC.ECS; using Svelto.Common; using Svelto.DataStructures; using Svelto.ECS.Hybrid; @@ -9,110 +10,110 @@ using Svelto.Utilities; namespace Svelto.ECS { - public class ComponentBuilder : IComponentBuilder where T : struct, IEntityComponent + struct ComponentBuilderComparer : IEqualityComparer { - public ComponentBuilder() + public bool Equals(IComponentBuilder x, IComponentBuilder y) { - _initializer = DEFAULT_IT; + return x.GetEntityComponentType() == y.GetEntityComponentType(); } - public ComponentBuilder(in T initializer) : this() + public int GetHashCode(IComponentBuilder obj) { - _initializer = initializer; + return obj.GetEntityComponentType().GetHashCode(); } + } + + 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; - public bool isUnmanaged => IS_UNMANAGED; + static readonly T DEFAULT_IT; + static readonly string ENTITY_COMPONENT_NAME; + static readonly bool IS_UNMANAGED; + public static bool HAS_REFERENCE; - public void BuildEntityAndAddToList(ref ITypeSafeDictionary dictionary, EGID egid, - IEnumerable implementors) + static ComponentBuilder() { - if (dictionary == null) - dictionary = TypeSafeDictionaryFactory.Create(); + 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(); - var castedDic = dictionary as ITypeSafeDictionary; + if (IS_UNMANAGED) + EntityComponentIDMap.Register(new Filler()); - T entityComponent = default; - - if (IS_ENTITY_VIEW_COMPONENT) - { - DBC.ECS.Check.Require(castedDic.ContainsKey(egid.entityID) == false, - $"building an entity with already used entity id! id: '{(ulong) egid}', {ENTITY_COMPONENT_NAME}"); + SetEGIDWithoutBoxing.Warmup(); - this.FillEntityComponent(ref entityComponent, EntityViewComponentCache.cachedFields, implementors, - EntityViewComponentCache.implementorsByType, EntityViewComponentCache.cachedTypes); + ComponentBuilderUtilities.CheckFields(ENTITY_COMPONENT_TYPE, IS_ENTITY_VIEW_COMPONENT); - castedDic.Add(egid.entityID, entityComponent); + if (IS_ENTITY_VIEW_COMPONENT) + { + EntityViewComponentCache.InitCache(); } else { - DBC.ECS.Check.Require(!castedDic.ContainsKey(egid.entityID), - $"building an entity with already used entity id! id: '{egid.entityID}'"); - - castedDic.Add(egid.entityID, _initializer); + 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}"); } } - ITypeSafeDictionary IComponentBuilder.Preallocate(ref ITypeSafeDictionary dictionary, uint size) - { - return Preallocate(ref dictionary, size); - } + public ComponentBuilder() { _initializer = DEFAULT_IT; } - static ITypeSafeDictionary Preallocate(ref ITypeSafeDictionary dictionary, uint size) - { - if (dictionary == null) - dictionary = TypeSafeDictionaryFactory.Create(size); - else - dictionary.SetCapacity(size); - - return dictionary; - } + public ComponentBuilder(in T initializer) : this() { _initializer = initializer; } - public Type GetEntityComponentType() - { - return ENTITY_COMPONENT_TYPE; - } + public bool isUnmanaged => IS_UNMANAGED; - static ComponentBuilder() + public void BuildEntityAndAddToList(ITypeSafeDictionary dictionary, EGID egid, IEnumerable implementors) { - 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); - ENTITY_COMPONENT_NAME = ENTITY_COMPONENT_TYPE.ToString(); - IS_UNMANAGED = ENTITY_COMPONENT_TYPE.IsUnmanagedEx(); + var castedDic = dictionary as ITypeSafeDictionary; - if (IS_UNMANAGED) - EntityComponentIDMap.Register(new Filler()); - - SetEGIDWithoutBoxing.Warmup(); - - ComponentBuilderUtilities.CheckFields(ENTITY_COMPONENT_TYPE, IS_ENTITY_VIEW_COMPONENT); + T entityComponent = default; if (IS_ENTITY_VIEW_COMPONENT) - EntityViewComponentCache.InitCache(); + { + 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); + + castedDic.Add(egid.entityID, entityComponent); + } else { - 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}"); + 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); } - readonly T _initializer; + public ITypeSafeDictionary CreateDictionary(uint size) { return TypeSafeDictionaryFactory.Create(size); } - internal static readonly Type ENTITY_COMPONENT_TYPE; - public static readonly bool HAS_EGID; - internal static readonly bool IS_ENTITY_VIEW_COMPONENT; + public Type GetEntityComponentType() { return ENTITY_COMPONENT_TYPE; } - static readonly T DEFAULT_IT; - static readonly string ENTITY_COMPONENT_NAME; - static bool IS_UNMANAGED; + public override int GetHashCode() { return _initializer.GetHashCode(); } + + static void Preallocate(ITypeSafeDictionary dictionary, uint size) { dictionary.SetCapacity(size); } + + readonly T _initializer; /// - /// Note: this static class will hold forever the references of the entities implementors. These references - /// are not even cleared when the engines root is destroyed, as they are static references. - /// It must be seen as an application-wide cache system. Honestly, I am not sure if this may cause leaking - /// issues in some kind of applications. To remember. + /// Note: this static class will hold forever the references of the entities implementors. These references + /// are not even cleared when the engines root is destroyed, as they are static references. + /// It must be seen as an application-wide cache system. Honestly, I am not sure if this may cause leaking + /// issues in some kind of applications. To remember. /// static class EntityViewComponentCache { @@ -127,12 +128,12 @@ namespace Svelto.ECS { cachedFields = new FasterList>>(); - var type = typeof(T); + var type = typeof(T); var fields = type.GetFields(BindingFlags.Public | BindingFlags.Instance); - + for (var i = fields.Length - 1; i >= 0; --i) { - var field = fields[i]; + var field = fields[i]; if (field.FieldType.IsInterface == true) { var setter = FastInvoke.MakeSetter(field); @@ -141,6 +142,10 @@ namespace Svelto.ECS cachedFields.Add(new KeyValuePair>(field.FieldType, setter)); } } +#if DEBUG && !PROFILE_SVELTO + if (fields.Length == 0) + Console.LogWarning($"No fields found in component {type}. Are you declaring only properties?"); +#endif cachedTypes = new Dictionary(); @@ -151,8 +156,7 @@ namespace Svelto.ECS #endif } - internal static void InitCache() - {} + internal static void InitCache() { } } } } \ No newline at end of file diff --git a/Core/EGID.cs b/Core/EGID.cs index fc50271..43591fd 100644 --- a/Core/EGID.cs +++ b/Core/EGID.cs @@ -1,4 +1,5 @@ using System; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; #pragma warning disable 660,661 @@ -14,8 +15,6 @@ namespace Svelto.ECS [FieldOffset(4)] public readonly ExclusiveGroupStruct groupID; [FieldOffset(0)] readonly ulong _GID; - public static readonly EGID Empty = new EGID(); - public static bool operator ==(EGID obj1, EGID obj2) { return obj1._GID == obj2._GID; @@ -28,14 +27,14 @@ namespace Svelto.ECS public EGID(uint entityID, ExclusiveGroupStruct groupID) : this() { - _GID = MAKE_GLOBAL_ID(entityID, groupID); +#if DEBUG && !PROFILE_SVELTO + if (groupID == (ExclusiveGroupStruct)default) + throw new Exception("Trying to use a not initialised group ID"); +#endif + + _GID = MAKE_GLOBAL_ID(entityID, (uint) groupID); } - public EGID(uint entityID, ExclusiveBuildGroup groupID) : this() - { - _GID = MAKE_GLOBAL_ID(entityID, groupID.group); - } - static ulong MAKE_GLOBAL_ID(uint entityId, uint groupId) { return (ulong)groupId << 32 | ((ulong)entityId & 0xFFFFFFFF); @@ -82,7 +81,14 @@ namespace Svelto.ECS public override string ToString() { var value = groupID.ToName(); + return "id ".FastConcat(entityID).FastConcat(" group ").FastConcat(value); } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public EntityReference ToEntityReference(EntitiesDB entitiesDB) + { + return entitiesDB.GetEntityReference(this); + } } } diff --git a/Core/EGIDMapper.cs b/Core/EGIDMapper.cs index bfadf1c..6966d1f 100644 --- a/Core/EGIDMapper.cs +++ b/Core/EGIDMapper.cs @@ -6,8 +6,6 @@ using Svelto.ECS.Internal; namespace Svelto.ECS { /// - /// Note: does mono devirtualize sealed classes? If so it could be worth to use TypeSafeDictionary instead of - /// the interface /// /// public readonly struct EGIDMapper: IEGIDMapper where T : struct, IEntityComponent diff --git a/Core/EnginesGroup/IStepEngine.cs b/Core/EnginesGroup/IStepEngine.cs index bf4c249..12e7b50 100644 --- a/Core/EnginesGroup/IStepEngine.cs +++ b/Core/EnginesGroup/IStepEngine.cs @@ -14,7 +14,7 @@ namespace Svelto.ECS string name { get; } } - //this must stay IStep Engine as it may be part of a group itself + //this must stay IStepEngine as it may be part of a group itself public interface IStepGroupEngine : IStepEngine { } diff --git a/Core/EnginesGroup/SortedEnginesGroup.cs b/Core/EnginesGroup/SortedEnginesGroup.cs index e2d9224..acfa0f2 100644 --- a/Core/EnginesGroup/SortedEnginesGroup.cs +++ b/Core/EnginesGroup/SortedEnginesGroup.cs @@ -3,6 +3,46 @@ using Svelto.Common; namespace Svelto.ECS { + /// + /// SortedEnginesGroup is a practical way to group engines that can be ticked together. The class requires a + /// SequenceOrder struct that define the order of execution. The pattern to use is the following: + /// First define as many enums as you want with the ID of the engines to use. E.G.: + /// public enum WiresCompositionEngineNames + ///{ + /// WiresTimeRunningGroup, + /// WiresPreInitTimeRunningGroup, + /// WiresInitTimeStoppedGroup, + /// WiresInitTimeRunningGroup + ///} + /// + /// then link these ID to the actual engines, using the attribute Sequenced: + /// + /// [Sequenced(nameof(WiresCompositionEngineNames.WiresTimeRunningGroup))] + /// class WiresTimeRunningGroup : UnsortedDeterministicEnginesGroupg {} + /// + /// Note that the engine can be another group itself (like in this example). + /// + /// then define the ISequenceOrder struct. E.G.: + /// public struct DeterministicTimeRunningEnginesOrder: ISequenceOrder + /// { + /// private static readonly string[] order = + /// { + /// nameof(TriggerEngineNames.PreWiresTimeRunningTriggerGroup), + /// nameof(TimerEnginesNames.PreWiresTimeRunningTimerGroup), + /// nameof(WiresCompositionEngineNames.WiresTimeRunningGroup), + /// nameof(SyncGroupEnginesGroups.UnsortedDeterministicTimeRunningGroup) + /// }; + /// public string[] enginesOrder => order; + /// } + /// + /// Now you can use the Type you just created (i.e.: DeterministicTimeRunningEnginesOrder) as generic parameter + /// of the SortedEnginesGroup. + /// While the system may look convoluted, is an effective way to keep the engines assemblies decoupled from + /// each other + /// The class is abstract and it requires a user defined interface to push the user to use recognisable names meaningful + /// to the context where they are used. + /// + /// user defined interface that implements IStepEngine public abstract class SortedEnginesGroup : IStepGroupEngine where SequenceOrder : struct, ISequenceOrder where Interface : IStepEngine { @@ -31,6 +71,11 @@ namespace Svelto.ECS readonly Sequence _instancedSequence; } + /// + /// Similar to SortedEnginesGroup except for the fact that an optional parameter can be passed to the engines + /// + /// + /// Specialised Parameter that can be passed to all the engines in the group public abstract class SortedEnginesGroup: IStepGroupEngine where SequenceOrder : struct, ISequenceOrder where Interface : IStepEngine { diff --git a/Core/EnginesGroup/UnsortedEnginesGroup.cs b/Core/EnginesGroup/UnsortedEnginesGroup.cs index c36572a..c281911 100644 --- a/Core/EnginesGroup/UnsortedEnginesGroup.cs +++ b/Core/EnginesGroup/UnsortedEnginesGroup.cs @@ -3,9 +3,22 @@ using Svelto.DataStructures; namespace Svelto.ECS { + /// + /// UnsortedEnginesGroup is a practical way to group engines that can be ticked together. As the name suggest + /// there is no way to defines an order, although the engines will run in the same order they are added. + /// It is abstract and it requires a user defined interface to push the user to use recognisable names meaningful + /// to the context where they are used. + /// + /// user defined interface that implements IStepEngine public abstract class UnsortedEnginesGroup : IStepGroupEngine where Interface : IStepEngine { + protected UnsortedEnginesGroup() + { + _name = "UnsortedEnginesGroup - "+this.GetType().Name; + _instancedSequence = new FasterList(); + } + protected UnsortedEnginesGroup(FasterList engines) { _name = "UnsortedEnginesGroup - "+this.GetType().Name; @@ -24,6 +37,11 @@ namespace Svelto.ECS } } } + + public void Add(Interface engine) + { + _instancedSequence.Add(engine); + } public string name => _name; @@ -31,9 +49,20 @@ namespace Svelto.ECS readonly FasterList _instancedSequence; } + /// + /// Similar to UnsortedEnginesGroup except for the fact that an optional parameter can be passed to the engines + /// + /// + /// Specialised Parameter that can be passed to all the engines in the group public abstract class UnsortedEnginesGroup : IStepGroupEngine where Interface : IStepEngine { + protected UnsortedEnginesGroup() + { + _name = "UnsortedEnginesGroup - "+this.GetType().Name; + _instancedSequence = new FasterList(); + } + protected UnsortedEnginesGroup(FasterList engines) { _name = "UnsortedEnginesGroup - "+this.GetType().Name; @@ -52,6 +81,11 @@ namespace Svelto.ECS } } } + + public void Add(Interface engine) + { + _instancedSequence.Add(engine); + } public string name => _name; diff --git a/Core/EnginesRoot.DoubleBufferedEntitiesToAdd.cs b/Core/EnginesRoot.DoubleBufferedEntitiesToAdd.cs index d62f522..95028c5 100644 --- a/Core/EnginesRoot.DoubleBufferedEntitiesToAdd.cs +++ b/Core/EnginesRoot.DoubleBufferedEntitiesToAdd.cs @@ -42,6 +42,7 @@ namespace Svelto.ECS } } + //reset the number of entities created so far otherEntitiesCreatedPerGroup.FastClear(); other.FastClear(); return; @@ -74,23 +75,25 @@ namespace Svelto.ECS } } + //reset the number of entities created so far otherEntitiesCreatedPerGroup.FastClear(); } } - - /// - /// To avoid extra allocation, I don't clear the dictionaries, so I need an extra data structure - /// to keep count of the number of entities submitted this frame - /// - internal FasterDictionary currentEntitiesCreatedPerGroup; - internal FasterDictionary otherEntitiesCreatedPerGroup; - + //Before I tried for the third time to use a SparseSet instead of FasterDictionary, remember that - //while group indices are sequential, they may not be used in a sequential order. Sparaset needs + //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; + /// + /// 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; + readonly FasterDictionary> _entityComponentsToAddBufferA = new FasterDictionary>(); @@ -142,6 +145,45 @@ namespace Svelto.ECS } } } + + internal void IncrementEntityCount(ExclusiveGroupStruct groupID) + { + currentEntitiesCreatedPerGroup.GetOrCreate(groupID)++; + } + + internal bool AnyEntityCreated() + { + return currentEntitiesCreatedPerGroup.count > 0; + } + + internal bool AnyOtherEntityCreated() + { + return otherEntitiesCreatedPerGroup.count > 0; + } + + internal void Preallocate + (ExclusiveGroupStruct groupID, uint numberOfEntities, IComponentBuilder[] entityComponentsToBuild) + { + void PreallocateDictionaries(FasterDictionary> fasterDictionary1) + { + FasterDictionary group = + fasterDictionary1.GetOrCreate((uint) groupID, () => new FasterDictionary()); + + foreach (var componentBuilder in entityComponentsToBuild) + { + var entityComponentType = componentBuilder.GetEntityComponentType(); + var safeDictionary = @group.GetOrCreate(new RefWrapperType(entityComponentType) + , () => componentBuilder.CreateDictionary(numberOfEntities)); + componentBuilder.Preallocate(safeDictionary, numberOfEntities); + } + } + + PreallocateDictionaries(current); + PreallocateDictionaries(other); + + currentEntitiesCreatedPerGroup.GetOrCreate(groupID); + otherEntitiesCreatedPerGroup.GetOrCreate(groupID); + } } } } \ No newline at end of file diff --git a/Core/EnginesRoot.Engines.cs b/Core/EnginesRoot.Engines.cs index 30f1123..2247f6f 100644 --- a/Core/EnginesRoot.Engines.cs +++ b/Core/EnginesRoot.Engines.cs @@ -1,5 +1,4 @@ using System; -using System.Collections; using System.Collections.Generic; using Svelto.Common; using Svelto.DataStructures; @@ -10,73 +9,52 @@ namespace Svelto.ECS { public sealed partial class EnginesRoot { - public struct EntitiesSubmitter + static EnginesRoot() { - public EntitiesSubmitter(EnginesRoot enginesRoot) : this() - { - _weakReference = new Svelto.DataStructures.WeakReference(enginesRoot); - } - - public bool IsUnused => _weakReference.IsValid == false; - - public IEnumerator Invoke(uint maxNumberOfOperationsPerFrame) - { - var entitiesSubmissionScheduler = _weakReference.Target._scheduler; - if (_weakReference.IsValid && entitiesSubmissionScheduler.paused == false) - { - var submitEntityComponents = - _weakReference.Target.SubmitEntityComponents(maxNumberOfOperationsPerFrame); - DBC.ECS.Check.Require(entitiesSubmissionScheduler.isRunning == false - , "A submission started while the previous one was still flushing"); - entitiesSubmissionScheduler.isRunning = true; - while (submitEntityComponents.MoveNext() == true) - yield return null; - - entitiesSubmissionScheduler.isRunning = false; - ++entitiesSubmissionScheduler.iteration; - } - } - - readonly Svelto.DataStructures.WeakReference _weakReference; + GroupHashMap.Init(); + SerializationDescriptorMap.Init(); } - - readonly EntitiesSubmissionScheduler _scheduler; - public EntitiesSubmissionScheduler scheduler => _scheduler; - /// - /// 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 - /// periodically if new entity must be submitted to the database and the engines. It's an external - /// dependencies to be independent by the running platform as the user can define it. - /// The EntitySubmissionScheduler cannot hold an EnginesRoot reference, that's why - /// it must receive a weak reference of the EnginesRoot callback. + /// 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 + /// periodically if new entity must be submitted to the database and the engines. It's an external + /// dependencies to be independent by the running platform as the user can define it. + /// The EntitySubmissionScheduler cannot hold an EnginesRoot reference, that's why + /// it must receive a weak reference of the EnginesRoot callback. /// public EnginesRoot(EntitiesSubmissionScheduler entitiesComponentScheduler) { - _entitiesOperations = new FasterDictionary(); - serializationDescriptorMap = new SerializationDescriptorMap(); - _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 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(); _groupEntityComponentsDB = new FasterDictionary>(); _groupsPerEntity = new FasterDictionary>(); _groupedEntityToAdd = new DoubleBufferedEntitiesToAdd(); - _entityStreams = EntitiesStreams.Create(); _groupFilters = new FasterDictionary>(); - _entitiesDB = new EntitiesDB(this); + _entityLocator.InitEntityReferenceMap(); + _entitiesDB = new EntitiesDB(this,_entityLocator); - _scheduler = entitiesComponentScheduler; - _scheduler.onTick = new EntitiesSubmitter(this); + scheduler = entitiesComponentScheduler; + scheduler.onTick = new EntitiesSubmitter(this); #if UNITY_NATIVE AllocateNativeOperations(); #endif @@ -89,10 +67,12 @@ namespace Svelto.ECS _isDeserializationOnly = isDeserializationOnly; } + public EntitiesSubmissionScheduler scheduler { get; } + /// - /// Dispose an EngineRoot once not used anymore, so that all the - /// engines are notified with the entities removed. - /// It's a clean up process. + /// Dispose an EngineRoot once not used anymore, so that all the + /// engines are notified with the entities removed. + /// It's a clean up process. /// public void Dispose() { @@ -102,10 +82,9 @@ namespace Svelto.ECS { //Note: The engines are disposed before the the remove callback to give the chance to behave //differently if a remove happens as a consequence of a dispose - //The pattern is to implement the IDisposable interface and set a flag in the engine. The + //The pattern is to implement the IDisposable interface and set a flag in the engine. The //remove callback will then behave differently according the flag. foreach (var engine in _disposableEngines) - { try { if (engine is IDisposingEngine dengine) @@ -114,33 +93,28 @@ namespace Svelto.ECS } catch (Exception e) { - Svelto.Console.LogException(e); + Console.LogException(e); } - } foreach (var groups in _groupEntityComponentsDB) - { - foreach (var entityList in groups.Value) - try - { - entityList.Value.ExecuteEnginesRemoveCallbacks(_reactiveEnginesAddRemoveOnDispose, profiler - , new ExclusiveGroupStruct(groups.Key)); - } - catch (Exception e) - { - Svelto.Console.LogException(e); - } - } + foreach (var entityList in groups.Value) + try + { + entityList.Value.ExecuteEnginesRemoveCallbacks(_reactiveEnginesAddRemoveOnDispose, profiler + , new ExclusiveGroupStruct(groups.Key)); + } + catch (Exception e) + { + Console.LogException(e); + } 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(); @@ -164,6 +138,9 @@ namespace Svelto.ECS _transientEntitiesOperations.Clear(); _groupedEntityToAdd.Dispose(); + + _entityLocator.DisposeEntityReferenceMap(); + _entityStreams.Dispose(); scheduler.Dispose(); } @@ -171,13 +148,6 @@ namespace Svelto.ECS GC.SuppressFinalize(this); } - ~EnginesRoot() - { - Console.LogWarning("Engines Root has been garbage collected, don't forget to call Dispose()!"); - - Dispose(); - } - public void AddEngine(IEngine engine) { var type = engine.GetType(); @@ -191,13 +161,13 @@ namespace Svelto.ECS try { if (engine is IReactOnAddAndRemove viewEngine) - CheckReactEngineComponents(viewEngine, _reactiveEnginesAddRemove); + CheckReactEngineComponents(viewEngine, _reactiveEnginesAddRemove, type.Name); if (engine is IReactOnDispose viewEngineDispose) - CheckReactEngineComponents(viewEngineDispose, _reactiveEnginesAddRemoveOnDispose); + CheckReactEngineComponents(viewEngineDispose, _reactiveEnginesAddRemoveOnDispose, type.Name); if (engine is IReactOnSwap viewEngineSwap) - CheckReactEngineComponents(viewEngineSwap, _reactiveEnginesSwap); + CheckReactEngineComponents(viewEngineSwap, _reactiveEnginesSwap, type.Name); if (engine is IReactOnSubmission submissionEngine) _reactiveEnginesSubmission.Add(submissionEngine); @@ -221,24 +191,38 @@ namespace Svelto.ECS } } - void CheckReactEngineComponents(T engine, FasterDictionary> engines) + void NotifyReactiveEnginesOnSubmission() + { + var enginesCount = _reactiveEnginesSubmission.count; + for (var i = 0; i < enginesCount; i++) + _reactiveEnginesSubmission[i].EntitiesSubmitted(); + } + + ~EnginesRoot() + { + Console.LogWarning("Engines Root has been garbage collected, don't forget to call Dispose()!"); + + Dispose(); + } + + 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); + AddEngineToList(engine, genericArguments, engines, typeName); } - } } static void AddEngineToList - (T engine, Type[] entityComponentTypes, FasterDictionary> engines) + (T engine, Type[] entityComponentTypes + , FasterDictionary> engines, string typeName) where T : class, IReactEngine { for (var i = 0; i < entityComponentTypes.Length; i++) @@ -247,22 +231,107 @@ namespace Svelto.ECS if (engines.TryGetValue(new RefWrapperType(type), out var list) == false) { - list = new FasterList(); + list = new FasterList(); engines.Add(new RefWrapperType(type), list); } - list.Add(engine); + list.Add(new ReactEngineContainer(engine, typeName)); } } - readonly FasterDictionary> _reactiveEnginesAddRemove; - readonly FasterDictionary> _reactiveEnginesAddRemoveOnDispose; - readonly FasterDictionary> _reactiveEnginesSwap; - readonly FasterList _reactiveEnginesSubmission; - readonly FasterList _disposableEngines; - readonly FasterList _enginesSet; - readonly HashSet _enginesTypeSet; - internal bool _isDisposing; + internal bool _isDisposing; + readonly FasterList _disposableEngines; + readonly FasterList _enginesSet; + readonly HashSet _enginesTypeSet; + + 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?"); + + var enginesRootTarget = _enginesRoot.Target; + var entitiesSubmissionScheduler = enginesRootTarget.scheduler; + + if (entitiesSubmissionScheduler.paused == false) + { + DBC.ECS.Check.Require(entitiesSubmissionScheduler.isRunning == false + , "A submission started while the previous one was still flushing"); + entitiesSubmissionScheduler.isRunning = true; + + 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 + } + +#if DEBUG && !PROFILE_SVELTO + if (iterations == 5) + throw new ECSException("possible circular submission detected"); +#endif + if (hasEverSubmitted) + enginesRootTarget.NotifyReactiveEnginesOnSubmission(); + } + + entitiesSubmissionScheduler.isRunning = false; + ++entitiesSubmissionScheduler.iteration; + } + + yield return false; + } + } + + public uint maxNumberOfOperationsPerFrame + { + set => _enginesRoot.Target._maxNumberOfOperationsPerFrame = value; + } + + readonly Svelto.DataStructures.WeakReference _enginesRoot; + + internal readonly IEnumerator submitEntities; + readonly IEnumerator _privateSubmitEntities; + } } } \ No newline at end of file diff --git a/Core/EnginesRoot.Entities.cs b/Core/EnginesRoot.Entities.cs index 1192ae2..126822d 100644 --- a/Core/EnginesRoot.Entities.cs +++ b/Core/EnginesRoot.Entities.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Runtime.CompilerServices; -using DBC.ECS; using Svelto.Common; using Svelto.DataStructures; using Svelto.ECS.Internal; @@ -17,40 +16,45 @@ 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 = null) { CheckAddEntityID(entityID, descriptorType); - Check.Require(entityID.groupID != 0, "invalid group detected, are you using new ExclusiveGroupStruct() instead of new ExclusiveGroup()?"); - var dic = EntityFactory.BuildGroupedEntities(entityID, _groupedEntityToAdd, componentsToBuild - , implementors, descriptorType); + DBC.ECS.Check.Require((uint)entityID.groupID != 0 + , "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 +#endif + ); - return new EntityInitializer(entityID, dic); + return new EntityInitializer(entityID, dic, reference); } - ///-------------------------------------------- - void Preallocate(ExclusiveGroupStruct groupID, uint size) where T : IEntityDescriptor, new() + /// + /// Preallocate memory to avoid the impact to resize arrays when many entities are submitted at once + /// + void Preallocate(ExclusiveGroupStruct groupID, uint numberOfEntities, IComponentBuilder[] entityComponentsToBuild) { - using (var profiler = new PlatformProfiler("Preallocate")) + void PreallocateEntitiesToAdd() { - var entityComponentsToBuild = EntityDescriptorTemplate.descriptor.componentsToBuild; - var numberOfEntityComponents = entityComponentsToBuild.Length; + _groupedEntityToAdd.Preallocate(groupID, numberOfEntities, entityComponentsToBuild); + } - FasterDictionary group = GetOrCreateGroup(groupID, profiler); + void PreallocateDBGroup() + { + var numberOfEntityComponents = entityComponentsToBuild.Length; + FasterDictionary group = GetOrCreateDBGroup(groupID); for (var index = 0; index < numberOfEntityComponents; index++) { @@ -58,17 +62,20 @@ namespace Svelto.ECS var entityComponentType = entityComponentBuilder.GetEntityComponentType(); var refWrapper = new RefWrapperType(entityComponentType); - if (group.TryGetValue(refWrapper, out var dbList) == false) - group[refWrapper] = entityComponentBuilder.Preallocate(ref dbList, size); - else - dbList.SetCapacity(size); + var dbList = group.GetOrCreate(refWrapper, ()=>entityComponentBuilder.CreateDictionary(numberOfEntities)); + entityComponentBuilder.Preallocate(dbList, numberOfEntities); if (_groupsPerEntity.TryGetValue(refWrapper, out var groupedGroup) == false) - groupedGroup = _groupsPerEntity[refWrapper] = new FasterDictionary(); + groupedGroup = _groupsPerEntity[refWrapper] = + new FasterDictionary(); groupedGroup[groupID] = dbList; } } + + PreallocateDBGroup(); + PreallocateEntitiesToAdd(); + _entityLocator.PreallocateReferenceMaps(groupID, numberOfEntities); } ///-------------------------------------------- @@ -77,23 +84,24 @@ namespace Svelto.ECS { using (var sampler = new PlatformProfiler("Move Entity From Engines")) { - var fromGroup = GetGroup(fromEntityGID.groupID); + 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) + , out var entityInfoDic) && (entityInfoDic as ITypeSafeDictionary).TryGetValue( fromEntityGID.entityID, out var entityInfo)) SwapOrRemoveEntityComponents(fromEntityGID, toEntityGID, entityInfo.componentsToBuild, fromGroup - , sampler); + , 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) + void SwapOrRemoveEntityComponents + (EGID fromEntityGID, EGID? toEntityGID, IComponentBuilder[] entitiesToMove + , FasterDictionary fromGroup, in PlatformProfiler sampler) { using (sampler.Sample("MoveEntityComponents")) { @@ -103,41 +111,48 @@ namespace Svelto.ECS //Swap is not like adding a new entity. While adding new entities happen at the end of submission //Adding an entity to a group due to a swap of groups happens now. - if (toEntityGID != null) + if (toEntityGID.HasValue) { - var toGroupID = toEntityGID.Value.groupID; + var entityGid = toEntityGID.Value; + _entityLocator.UpdateEntityReference(fromEntityGID, entityGid); - toGroup = GetOrCreateGroup(toGroupID, sampler); + var toGroupID = entityGid.groupID; + + toGroup = GetOrCreateDBGroup(toGroupID); //Add all the entities to the dictionary for (var i = 0; i < length; i++) - CopyEntityToDictionary(fromEntityGID, toEntityGID.Value, fromGroup, toGroup - , entitiesToMove[i].GetEntityComponentType(), sampler); + 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); + , entitiesToMove[i].GetEntityComponentType(), sampler); //then remove all the entities from the dictionary for (var i = 0; i < length; i++) - RemoveEntityFromDictionary(fromEntityGID, fromGroup, entitiesToMove[i].GetEntityComponentType(), - sampler); + RemoveEntityFromDictionary(fromEntityGID, fromGroup, entitiesToMove[i].GetEntityComponentType() + , sampler); } } void CopyEntityToDictionary (EGID entityGID, EGID toEntityGID, FasterDictionary fromGroup - , FasterDictionary toGroup, Type entityComponentType, - in PlatformProfiler sampler) + , FasterDictionary toGroup, Type entityComponentType + , in PlatformProfiler sampler) { using (sampler.Sample("CopyEntityToDictionary")) { var wrapper = new RefWrapperType(entityComponentType); ITypeSafeDictionary fromTypeSafeDictionary = - GetTypeSafeDictionary(entityGID.groupID, fromGroup, wrapper); + GetTypeSafeDictionary((uint) entityGID.groupID, fromGroup, wrapper); #if DEBUG && !PROFILE_SVELTO if (fromTypeSafeDictionary.Has(entityGID.entityID) == false) @@ -154,14 +169,14 @@ namespace Svelto.ECS void ExecuteEnginesSwapOrRemoveCallbacks (EGID entityGID, EGID? toEntityGID, FasterDictionary fromGroup - , FasterDictionary toGroup, Type entityComponentType - , in PlatformProfiler profiler) + , 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); + var fromTypeSafeDictionary = GetTypeSafeDictionary((uint) entityGID.groupID, fromGroup, refWrapper); ITypeSafeDictionary toEntitiesDictionary = null; if (toGroup != null) @@ -172,19 +187,21 @@ namespace Svelto.ECS throw new EntityNotFoundException(entityGID, entityComponentType); #endif fromTypeSafeDictionary.ExecuteEnginesSwapOrRemoveCallbacks(entityGID, toEntityGID, toEntitiesDictionary - , toEntityGID == null ? _reactiveEnginesAddRemove : _reactiveEnginesSwap, in profiler); + , toEntityGID == null + ? _reactiveEnginesAddRemove + : _reactiveEnginesSwap, in profiler); } } [MethodImpl(MethodImplOptions.AggressiveInlining)] void RemoveEntityFromDictionary (EGID entityGID, FasterDictionary fromGroup, Type entityComponentType - , in PlatformProfiler sampler) + , in PlatformProfiler sampler) { using (sampler.Sample("RemoveEntityFromDictionary")) { var refWrapper = new RefWrapperType(entityComponentType); - var fromTypeSafeDictionary = GetTypeSafeDictionary(entityGID.groupID, fromGroup, refWrapper); + var fromTypeSafeDictionary = GetTypeSafeDictionary((uint) entityGID.groupID, fromGroup, refWrapper); fromTypeSafeDictionary.RemoveEntityFromDictionary(entityGID); } @@ -199,27 +216,31 @@ namespace Svelto.ECS /// /// /// - void SwapEntitiesBetweenGroups(ExclusiveGroupStruct fromIdGroupId, ExclusiveGroupStruct toGroupId, in PlatformProfiler profiler) + void SwapEntitiesBetweenGroups + (ExclusiveGroupStruct fromIdGroupId, ExclusiveGroupStruct toGroupId, in PlatformProfiler profiler) { using (profiler.Sample("SwapEntitiesBetweenGroups")) { - FasterDictionary fromGroup = GetGroup(fromIdGroupId); - FasterDictionary toGroup = GetOrCreateGroup(toGroupId, profiler); + FasterDictionary fromGroup = GetDBGroup(fromIdGroupId); + FasterDictionary toGroup = GetOrCreateDBGroup(toGroupId); + + _entityLocator.UpdateAllGroupReferenceLocators(fromIdGroupId, (uint) toGroupId); foreach (var dictionaryOfEntities in fromGroup) { ITypeSafeDictionary toEntitiesDictionary = GetOrCreateTypeSafeDictionary(toGroupId, toGroup, dictionaryOfEntities.Key - , dictionaryOfEntities.Value); + , dictionaryOfEntities.Value); var groupsOfEntityType = _groupsPerEntity[dictionaryOfEntities.Key]; - var groupOfEntitiesToCopyAndClear = groupsOfEntityType[fromIdGroupId]; - toEntitiesDictionary.AddEntitiesFromDictionary(groupOfEntitiesToCopyAndClear, toGroupId); - + + toEntitiesDictionary.AddEntitiesFromDictionary(groupOfEntitiesToCopyAndClear, (uint) toGroupId, this); + //call all the MoveTo callbacks dictionaryOfEntities.Value.ExecuteEnginesAddOrSwapCallbacks(_reactiveEnginesSwap - , dictionaryOfEntities.Value, new ExclusiveGroupStruct(fromIdGroupId), new ExclusiveGroupStruct(toGroupId), profiler); + , dictionaryOfEntities.Value, new ExclusiveGroupStruct(fromIdGroupId) + , new ExclusiveGroupStruct(toGroupId), profiler); //todo: if it's unmanaged, I can use fastclear groupOfEntitiesToCopyAndClear.Clear(); @@ -227,33 +248,29 @@ namespace Svelto.ECS } } - FasterDictionary GetGroup(ExclusiveGroupStruct fromIdGroupId) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + FasterDictionary GetDBGroup(ExclusiveGroupStruct fromIdGroupId) { if (_groupEntityComponentsDB.TryGetValue(fromIdGroupId - , out FasterDictionary - fromGroup) == false) - throw new ECSException("Group doesn't exist: ".FastConcat(fromIdGroupId)); + , out FasterDictionary + fromGroup) == false) + throw new ECSException("Group doesn't exist: ".FastConcat((uint) fromIdGroupId)); return fromGroup; } - FasterDictionary GetOrCreateGroup(ExclusiveGroupStruct toGroupId, - in PlatformProfiler profiler) - { - using (profiler.Sample("GetOrCreateGroup")) - { - if (_groupEntityComponentsDB.TryGetValue( - toGroupId, out FasterDictionary toGroup) == false) - toGroup = _groupEntityComponentsDB[toGroupId] = - new FasterDictionary(); - return toGroup; - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + FasterDictionary GetOrCreateDBGroup + (ExclusiveGroupStruct toGroupId) + { + return _groupEntityComponentsDB.GetOrCreate( + toGroupId, () => new FasterDictionary()); } ITypeSafeDictionary GetOrCreateTypeSafeDictionary - (ExclusiveGroupStruct groupId, FasterDictionary toGroup, RefWrapperType type - , ITypeSafeDictionary fromTypeSafeDictionary) + (ExclusiveGroupStruct groupId, FasterDictionary toGroup + , RefWrapperType type, ITypeSafeDictionary fromTypeSafeDictionary) { //be sure that the TypeSafeDictionary for the entity Type exists if (toGroup.TryGetValue(type, out ITypeSafeDictionary toEntitiesDictionary) == false) @@ -264,7 +281,8 @@ namespace Svelto.ECS //update GroupsPerEntity if (_groupsPerEntity.TryGetValue(type, out var groupedGroup) == false) - groupedGroup = _groupsPerEntity[type] = new FasterDictionary(); + groupedGroup = _groupsPerEntity[type] = + new FasterDictionary(); groupedGroup[groupId] = toEntitiesDictionary; return toEntitiesDictionary; @@ -283,16 +301,17 @@ namespace Svelto.ECS void RemoveEntitiesFromGroup(ExclusiveGroupStruct groupID, in PlatformProfiler profiler) { + _entityLocator.RemoveAllGroupReferenceLocators(groupID); + if (_groupEntityComponentsDB.TryGetValue(groupID, out var dictionariesOfEntities)) { foreach (var dictionaryOfEntities in dictionariesOfEntities) { dictionaryOfEntities.Value.ExecuteEnginesRemoveCallbacks(_reactiveEnginesAddRemove, profiler - , new ExclusiveGroupStruct(groupID)); + , new ExclusiveGroupStruct(groupID)); dictionaryOfEntities.Value.FastClear(); - var groupsOfEntityType = - _groupsPerEntity[dictionaryOfEntities.Key]; + var groupsOfEntityType = _groupsPerEntity[dictionaryOfEntities.Key]; groupsOfEntityType[groupID].FastClear(); } } diff --git a/Core/EnginesRoot.GenericEntityFactory.cs b/Core/EnginesRoot.GenericEntityFactory.cs index fc0a80e..9e1f53d 100644 --- a/Core/EnginesRoot.GenericEntityFactory.cs +++ b/Core/EnginesRoot.GenericEntityFactory.cs @@ -1,8 +1,8 @@ -using System; +using System; using System.Collections.Generic; using Svelto.Common; -namespace Svelto.ECS + namespace Svelto.ECS { public partial class EnginesRoot { @@ -34,12 +34,7 @@ namespace Svelto.ECS { return _enginesRoot.Target.BuildEntity(egid, entityDescriptor.componentsToBuild, TypeCache.type, implementors); } -#if UNITY_NATIVE - public NativeEntityFactory ToNative(string callerName) where T : IEntityDescriptor, new() - { - return _enginesRoot.Target.ProvideNativeEntityFactoryQueue(callerName); - } -#endif + public EntityInitializer BuildEntity (uint entityID, ExclusiveBuildGroup groupStructId, T descriptorEntity, IEnumerable implementors) where T : IEntityDescriptor @@ -48,16 +43,23 @@ namespace Svelto.ECS , descriptorEntity.componentsToBuild, TypeCache.type, implementors); } - public void PreallocateEntitySpace(ExclusiveGroupStruct groupStructId, uint size) + public void PreallocateEntitySpace(ExclusiveGroupStruct groupStructId, uint numberOfEntities) where T : IEntityDescriptor, new() { - _enginesRoot.Target.Preallocate(groupStructId, size); + _enginesRoot.Target.Preallocate(groupStructId, numberOfEntities, EntityDescriptorTemplate.descriptor.componentsToBuild); } public EntityInitializer BuildEntity(EGID egid, IComponentBuilder[] componentsToBuild, Type type, IEnumerable implementors = null) { return _enginesRoot.Target.BuildEntity(egid, componentsToBuild, type, implementors); } + +#if UNITY_NATIVE + public Svelto.ECS.Native.NativeEntityFactory ToNative(string callerName) where T : IEntityDescriptor, new() + { + return _enginesRoot.Target.ProvideNativeEntityFactoryQueue(callerName); + } +#endif //enginesRoot is a weakreference because GenericEntityStreamConsumerFactory can be injected inside //engines of other enginesRoot diff --git a/Core/EnginesRoot.GenericEntityFunctions.cs b/Core/EnginesRoot.GenericEntityFunctions.cs index 801700d..23e045a 100644 --- a/Core/EnginesRoot.GenericEntityFunctions.cs +++ b/Core/EnginesRoot.GenericEntityFunctions.cs @@ -19,18 +19,18 @@ namespace Svelto.ECS } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void RemoveEntity(uint entityID, ExclusiveBuildGroup groupID, [CallerMemberName] string memberName = "") where T : + public void RemoveEntity(uint entityID, ExclusiveBuildGroup groupID) where T : IEntityDescriptor, new() { - RemoveEntity(new EGID(entityID, groupID), memberName); + RemoveEntity(new EGID(entityID, groupID)); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void RemoveEntity(EGID entityEGID, [CallerMemberName] string memberName = "") where T : IEntityDescriptor, new() + public void RemoveEntity(EGID entityEGID) where T : IEntityDescriptor, new() { - DBC.ECS.Check.Require(entityEGID.groupID != 0, "invalid group detected"); + DBC.ECS.Check.Require((uint)entityEGID.groupID != 0, "invalid group detected"); var descriptorComponentsToBuild = EntityDescriptorTemplate.descriptor.componentsToBuild; - _enginesRoot.Target.CheckRemoveEntityID(entityEGID, TypeCache.type, memberName); + _enginesRoot.Target.CheckRemoveEntityID(entityEGID, TypeCache.type); _enginesRoot.Target.QueueEntitySubmitOperation( new EntitySubmitOperation(EntitySubmitOperationType.Remove, entityEGID, entityEGID, @@ -40,7 +40,7 @@ namespace Svelto.ECS [MethodImpl(MethodImplOptions.AggressiveInlining)] public void RemoveEntitiesFromGroup(ExclusiveBuildGroup groupID) { - DBC.ECS.Check.Require(groupID != 0, "invalid group detected"); + DBC.ECS.Check.Require((uint)groupID != 0, "invalid group detected"); _enginesRoot.Target.RemoveGroupID(groupID); _enginesRoot.Target.QueueEntitySubmitOperation( @@ -112,34 +112,32 @@ namespace Svelto.ECS } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void SwapEntityGroup(EGID fromID, ExclusiveBuildGroup toGroupID - , ExclusiveBuildGroup mustBeFromGroup) + public void SwapEntityGroup(EGID fromID, ExclusiveBuildGroup mustBeFromGroup, ExclusiveBuildGroup toGroupID) where T : IEntityDescriptor, new() { if (fromID.groupID != mustBeFromGroup) - throw new ECSException("Entity is not coming from the expected group"); + throw new ECSException($"Entity is not coming from the expected group. Expected {mustBeFromGroup} is {fromID.groupID}"); SwapEntityGroup(fromID, toGroupID); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void SwapEntityGroup(EGID fromID, EGID toID - , ExclusiveBuildGroup mustBeFromGroup) + public void SwapEntityGroup(EGID fromID, EGID toID, ExclusiveBuildGroup mustBeFromGroup) where T : IEntityDescriptor, new() { if (fromID.groupID != mustBeFromGroup) - throw new ECSException("Entity is not coming from the expected group"); + throw new ECSException($"Entity is not coming from the expected group Expected {mustBeFromGroup} is {fromID.groupID}"); SwapEntityGroup(fromID, toID); } #if UNITY_NATIVE - public NativeEntityRemove ToNativeRemove(string memberName) where T : IEntityDescriptor, new() + public Svelto.ECS.Native.NativeEntityRemove ToNativeRemove(string memberName) where T : IEntityDescriptor, new() { return _enginesRoot.Target.ProvideNativeEntityRemoveQueue(memberName); } - public NativeEntitySwap ToNativeSwap(string memberName) where T : IEntityDescriptor, new() + public Svelto.ECS.Native.NativeEntitySwap ToNativeSwap(string memberName) where T : IEntityDescriptor, new() { return _enginesRoot.Target.ProvideNativeEntitySwapQueue(memberName); } @@ -149,8 +147,8 @@ namespace Svelto.ECS public void SwapEntityGroup(EGID fromID, EGID toID) where T : IEntityDescriptor, new() { - DBC.ECS.Check.Require(fromID.groupID != 0, "invalid group detected"); - DBC.ECS.Check.Require(toID.groupID != 0, "invalid group detected"); + DBC.ECS.Check.Require((uint)fromID.groupID != 0, "invalid group detected"); + DBC.ECS.Check.Require((uint)toID.groupID != 0, "invalid group detected"); var enginesRootTarget = _enginesRoot.Target; var descriptorComponentsToBuild = EntityDescriptorTemplate.descriptor.componentsToBuild; diff --git a/Core/EnginesRoot.Submission.cs b/Core/EnginesRoot.Submission.cs index 4f31ec9..eee0275 100644 --- a/Core/EnginesRoot.Submission.cs +++ b/Core/EnginesRoot.Submission.cs @@ -1,196 +1,178 @@ -using System.Collections; +using System.Collections.Generic; using Svelto.Common; using Svelto.DataStructures; -using Svelto.ECS.Internal; namespace Svelto.ECS { public partial class EnginesRoot { - readonly FasterList _transientEntitiesOperations; - - IEnumerator SubmitEntityComponents(uint maxNumberOfOperations) - { - using (var profiler = new PlatformProfiler("Svelto.ECS - Entities Submission")) - { - int iterations = 0; - do - { - var submitEntityComponents = SingleSubmission(profiler, maxNumberOfOperations); - while (submitEntityComponents.MoveNext() == true) - yield return null; - } while ((_groupedEntityToAdd.currentEntitiesCreatedPerGroup.count > 0 || - _entitiesOperations.count > 0) && ++iterations < 5); - -#if DEBUG && !PROFILE_SVELTO - if (iterations == 5) - throw new ECSException("possible circular submission detected"); -#endif - } - } - /// /// 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, uint maxNumberOfOperations) + IEnumerator SingleSubmission(PlatformProfiler profiler) { -#if UNITY_NATIVE - NativeOperationSubmission(profiler); -#endif - ClearChecks(); - - bool entitiesAreSubmitted = false; - uint numberOfOperations = 0; - - if (_entitiesOperations.count > 0) + while (true) { - using (profiler.Sample("Remove and Swap operations")) - { - _transientEntitiesOperations.FastClear(); - _entitiesOperations.CopyValuesTo(_transientEntitiesOperations); - _entitiesOperations.FastClear(); + DBC.ECS.Check.Require(_maxNumberOfOperationsPerFrame > 0); + + ClearChecks(); - EntitySubmitOperation[] entitiesOperations = _transientEntitiesOperations.ToArrayFast(out var count); - for (var i = 0; i < count; i++) + uint numberOfOperations = 0; + + if (_entitiesOperations.count > 0) + { + using (var sample = profiler.Sample("Remove and Swap operations")) { - try + _transientEntitiesOperations.FastClear(); + _entitiesOperations.CopyValuesTo(_transientEntitiesOperations); + _entitiesOperations.FastClear(); + + EntitySubmitOperation[] entitiesOperations = + _transientEntitiesOperations.ToArrayFast(out var count); + + for (var i = 0; i < count; i++) { - switch (entitiesOperations[i].type) + try { - 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; + switch (entitiesOperations[i].type) + { + 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; + } } - } - catch - { - var str = "Crash while executing Entity Operation " - .FastConcat(entitiesOperations[i].type.ToString()); - - - Svelto.Console.LogError(str.FastConcat(" ") + catch + { + var str = "Crash while executing Entity Operation ".FastConcat( + entitiesOperations[i].type.ToString()); + + Svelto.Console.LogError(str.FastConcat(" ") #if DEBUG && !PROFILE_SVELTO .FastConcat(entitiesOperations[i].trace.ToString()) #endif - ); + ); - throw; - } + throw; + } - ++numberOfOperations; + ++numberOfOperations; - if ((uint)numberOfOperations >= (uint)maxNumberOfOperations) - { - yield return null; - - numberOfOperations = 0; - + if ((uint) numberOfOperations >= (uint) _maxNumberOfOperationsPerFrame) + { + using (sample.Yield()) + yield return true; + + numberOfOperations = 0; + } } } } - entitiesAreSubmitted = true; - } - - _groupedEntityToAdd.Swap(); + _groupedEntityToAdd.Swap(); - if (_groupedEntityToAdd.otherEntitiesCreatedPerGroup.count > 0) - { - using (profiler.Sample("Add operations")) + if (_groupedEntityToAdd.AnyOtherEntityCreated()) { - try + using (var outerSampler = profiler.Sample("Add operations")) { - using (profiler.Sample("Add entities to database")) + try { - //each group is indexed by entity view type. for each type there is a dictionary indexed by entityID - foreach (var groupToSubmit in _groupedEntityToAdd.otherEntitiesCreatedPerGroup) + using (profiler.Sample("Add entities to database")) { - var groupID = groupToSubmit.Key; - var groupDB = GetOrCreateGroup(groupID, profiler); - - //add the entityComponents in the group - foreach (var entityComponentsToSubmit in _groupedEntityToAdd.other[groupID]) + //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 type = entityComponentsToSubmit.Key; - var targetTypeSafeDictionary = entityComponentsToSubmit.Value; - var wrapper = new RefWrapperType(type); + var groupID = new ExclusiveGroupStruct(groupToSubmit.Key); + var groupDB = GetOrCreateDBGroup(groupID); - ITypeSafeDictionary dbDic = GetOrCreateTypeSafeDictionary(groupID, groupDB, wrapper, - targetTypeSafeDictionary); + //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 generate this frame. - dbDic.AddEntitiesFromDictionary(targetTypeSafeDictionary, groupID); + //Fill the DB with the entity components generated this frame. + dbDic.AddEntitiesFromDictionary(targetTypeSafeDictionary, (uint) groupID, this); + } } } - } - //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 (profiler.Sample("Add entities to engines")) - { - foreach (var groupToSubmit in _groupedEntityToAdd.otherEntitiesCreatedPerGroup) + //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")) { - var groupID = groupToSubmit.Key; - var groupDB = _groupEntityComponentsDB[groupID]; + foreach (var groupToSubmit in _groupedEntityToAdd.other) + { + var groupID = new ExclusiveGroupStruct(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 _groupedEntityToAdd.other[groupID]) - { - var realDic = groupDB[new RefWrapperType(entityComponentsToSubmit.Key)]; + foreach (var entityComponentsToSubmit in groupToSubmit.Value) + { + var realDic = groupDB[new RefWrapperType(entityComponentsToSubmit.Key)]; - entityComponentsToSubmit.Value.ExecuteEnginesAddOrSwapCallbacks(_reactiveEnginesAddRemove, realDic, - null, new ExclusiveGroupStruct(groupID), in profiler); - - numberOfOperations += entityComponentsToSubmit.Value.count; + entityComponentsToSubmit.Value.ExecuteEnginesAddOrSwapCallbacks( + _reactiveEnginesAddRemove, realDic, null, new ExclusiveGroupStruct(groupID) + , in profiler); - if (numberOfOperations >= maxNumberOfOperations) - { - yield return null; - - numberOfOperations = 0; + numberOfOperations += entityComponentsToSubmit.Value.count; + + if (numberOfOperations >= _maxNumberOfOperationsPerFrame) + { + using (outerSampler.Yield()) + using (sampler.Yield()) + { + yield return true; + } + + numberOfOperations = 0; + } } } } } - } - finally - { - using (profiler.Sample("clear double buffering")) + finally { - //other can be cleared now, but let's avoid deleting the dictionary every time - _groupedEntityToAdd.ClearOther(); + using (profiler.Sample("clear double buffering")) + { + //other can be cleared now, but let's avoid deleting the dictionary every time + _groupedEntityToAdd.ClearOther(); + } } } } - - entitiesAreSubmitted = true; - } - if (entitiesAreSubmitted) - { - var enginesCount = _reactiveEnginesSubmission.count; - for (int i = 0; i < enginesCount; i++) - _reactiveEnginesSubmission[i].EntitiesSubmitted(); + yield return false; } } + + bool HasMadeNewStructuralChangesInThisIteration() + { + return _groupedEntityToAdd.AnyEntityCreated() || _entitiesOperations.count > 0; + } - readonly DoubleBufferedEntitiesToAdd _groupedEntityToAdd; + readonly DoubleBufferedEntitiesToAdd _groupedEntityToAdd; readonly FasterDictionary _entitiesOperations; + readonly FasterList _transientEntitiesOperations; + uint _maxNumberOfOperationsPerFrame; } } \ No newline at end of file diff --git a/Core/EntitiesDB.FindGroups.cs b/Core/EntitiesDB.FindGroups.cs index 0c7df8f..329cc60 100644 --- a/Core/EntitiesDB.FindGroups.cs +++ b/Core/EntitiesDB.FindGroups.cs @@ -1,4 +1,5 @@ using System; +using System.Runtime.CompilerServices; using System.Threading; using Svelto.DataStructures; using Svelto.ECS.Internal; @@ -9,34 +10,42 @@ namespace Svelto.ECS { public LocalFasterReadOnlyList FindGroups() where T1 : IEntityComponent { - FasterList result = groups.Value; + FasterList result = localgroups.Value.groupArray; result.FastClear(); if (groupsPerEntity.TryGetValue(TypeRefWrapper.wrapper - , out FasterDictionary result1) == false) + , out FasterDictionary result1) + == false) return result; - + var result1Count = result1.count; var fasterDictionaryNodes1 = result1.unsafeKeys; - + for (int j = 0; j < result1Count; j++) { - result.Add(new ExclusiveGroupStruct(fasterDictionaryNodes1[j].key)); + var group = fasterDictionaryNodes1[j].key; + if (group.IsEnabled()) + { + result.Add(group); + } } - + return result; } - - public LocalFasterReadOnlyList FindGroups() where T1 : IEntityComponent where T2 : IEntityComponent + + public LocalFasterReadOnlyList FindGroups() + where T1 : IEntityComponent where T2 : IEntityComponent { - FasterList result = groups.Value; + FasterList result = localgroups.Value.groupArray; result.FastClear(); if (groupsPerEntity.TryGetValue(TypeRefWrapper.wrapper - , out FasterDictionary result1) == false) + , out FasterDictionary result1) + == false) return result; if (groupsPerEntity.TryGetValue(TypeRefWrapper.wrapper - , out FasterDictionary result2) == false) + , out FasterDictionary result2) + == false) return result; - + var result1Count = result1.count; var result2Count = result2.count; var fasterDictionaryNodes1 = result1.unsafeKeys; @@ -45,6 +54,8 @@ namespace Svelto.ECS for (int i = 0; i < result1Count; i++) { var groupID = fasterDictionaryNodes1[i].key; + if (!groupID.IsEnabled()) continue; + for (int j = 0; j < result2Count; j++) { //if the same group is found used with both T1 and T2 @@ -62,6 +73,11 @@ namespace Svelto.ECS /// /// Remember that this operation os O(N*M*P) where N,M,P are the number of groups where each component /// is found. + /// TODO: I have to find once for ever a solution to be sure that the entities in the groups match + /// Currently this returns group where the entities are found, but the entities may not match in these + /// groups. + /// Checking the size of the entities is an early check, needed, but not sufficient, as entities components may + /// coincidentally match in number but not from which entities they are generated /// /// /// @@ -70,61 +86,115 @@ namespace Svelto.ECS public LocalFasterReadOnlyList FindGroups() where T1 : IEntityComponent where T2 : IEntityComponent where T3 : IEntityComponent { - FasterList result = groups.Value; - result.FastClear(); - if (groupsPerEntity.TryGetValue(TypeRefWrapper.wrapper - , out FasterDictionary groupOfEntities1) == false) - return result; - if (groupsPerEntity.TryGetValue(TypeRefWrapper.wrapper - , out FasterDictionary groupOfEntities2) == false) - return result; - if (groupsPerEntity.TryGetValue(TypeRefWrapper.wrapper - , out FasterDictionary groupOfEntities3) == false) - return result; - - var result1Count = groupOfEntities1.count; - var result2Count = groupOfEntities2.count; - var result3Count = groupOfEntities3.count; - var fasterDictionaryNodes1 = groupOfEntities1.unsafeKeys; - var fasterDictionaryNodes2 = groupOfEntities2.unsafeKeys; - var fasterDictionaryNodes3 = groupOfEntities3.unsafeKeys; - - // - //TODO: I have to find once for ever a solution to be sure that the entities in the groups match - //Currently this returns group where the entities are found, but the entities may not match in these - //groups. - //Checking the size of the entities is an early check, needed, but not sufficient, as entities components may - //coincidentally match in number but not from which entities they are generated - - //foreach group where T1 is found - for (int i = 0; i < result1Count; i++) + FasterList> localArray = + localgroups.Value.listOfGroups; + + if (groupsPerEntity.TryGetValue(TypeRefWrapper.wrapper, out localArray[0]) == false || localArray[0].count == 0) + return new LocalFasterReadOnlyList( + FasterReadOnlyList.DefaultEmptyList); + if (groupsPerEntity.TryGetValue(TypeRefWrapper.wrapper, out localArray[1]) == false || localArray[1].count == 0) + return new LocalFasterReadOnlyList( + FasterReadOnlyList.DefaultEmptyList); + if (groupsPerEntity.TryGetValue(TypeRefWrapper.wrapper, out localArray[2]) == false || localArray[2].count == 0) + return new LocalFasterReadOnlyList( + FasterReadOnlyList.DefaultEmptyList); + + localgroups.Value.groups.FastClear(); + + FasterDictionary localGroups = localgroups.Value.groups; + + int startIndex = 0; + int min = int.MaxValue; + + for (int i = 0; i < 3; i++) + if (localArray[i].count < min) + { + min = localArray[i].count; + startIndex = i; + } + + foreach (var value in localArray[startIndex]) { - var groupT1 = fasterDictionaryNodes1[i].key; - - //foreach group where T2 is found - for (int j = 0; j < result2Count; ++j) + if (value.Key.IsEnabled()) { - if (groupT1 == fasterDictionaryNodes2[j].key) - { - //foreach group where T3 is found - for (int k = 0; k < result3Count; ++k) - { - if (groupT1 == fasterDictionaryNodes3[k].key) - { - result.Add(new ExclusiveGroupStruct(groupT1)); - break; - } - } - - break; - } + localGroups.Add(value.Key, value.Key); } } - return result; + var groupData = localArray[++startIndex % 3]; + localGroups.Intersect(groupData); + if (localGroups.count == 0) + return new LocalFasterReadOnlyList( + FasterReadOnlyList.DefaultEmptyList); + groupData = localArray[++startIndex % 3]; + localGroups.Intersect(groupData); + + return new LocalFasterReadOnlyList(localGroups.unsafeValues + , (uint) localGroups.count); + } + + public LocalFasterReadOnlyList FindGroups() + where T1 : IEntityComponent + where T2 : IEntityComponent + where T3 : IEntityComponent + where T4 : IEntityComponent + { + FasterList> localArray = + localgroups.Value.listOfGroups; + + if (groupsPerEntity.TryGetValue(TypeRefWrapper.wrapper, out localArray[0]) == false || localArray[0].count == 0) + return new LocalFasterReadOnlyList( + FasterReadOnlyList.DefaultEmptyList); + if (groupsPerEntity.TryGetValue(TypeRefWrapper.wrapper, out localArray[1]) == false || localArray[1].count == 0) + return new LocalFasterReadOnlyList( + FasterReadOnlyList.DefaultEmptyList); + if (groupsPerEntity.TryGetValue(TypeRefWrapper.wrapper, out localArray[2]) == false || localArray[2].count == 0) + return new LocalFasterReadOnlyList( + FasterReadOnlyList.DefaultEmptyList); + if (groupsPerEntity.TryGetValue(TypeRefWrapper.wrapper, out localArray[3]) == false || localArray[3].count == 0) + return new LocalFasterReadOnlyList( + FasterReadOnlyList.DefaultEmptyList); + + localgroups.Value.groups.FastClear(); + + FasterDictionary localGroups = localgroups.Value.groups; + + int startIndex = 0; + int min = int.MaxValue; + + for (int i = 0; i < 4; i++) + if (localArray[i].count < min) + { + min = localArray[i].count; + startIndex = i; + } + + foreach (var value in localArray[startIndex]) + { + if (value.Key.IsEnabled()) + { + localGroups.Add(value.Key, value.Key); + } + } + + var groupData = localArray[++startIndex & 3]; //&3 == %4 + localGroups.Intersect(groupData); + if (localGroups.count == 0) + return new LocalFasterReadOnlyList( + FasterReadOnlyList.DefaultEmptyList); + groupData = localArray[++startIndex & 3]; + localGroups.Intersect(groupData); + if (localGroups.count == 0) + return new LocalFasterReadOnlyList( + FasterReadOnlyList.DefaultEmptyList); + groupData = localArray[++startIndex & 3]; + localGroups.Intersect(groupData); + + return new LocalFasterReadOnlyList(localGroups.unsafeValues + , (uint) localGroups.count); } - internal FasterDictionary FindGroups_INTERNAL(Type type) + internal FasterDictionary FindGroups_INTERNAL(Type type) { if (groupsPerEntity.ContainsKey(new RefWrapperType(type)) == false) return _emptyDictionary; @@ -134,21 +204,20 @@ namespace Svelto.ECS struct GroupsList { - static GroupsList() - { - groups = new FasterList(); - } + internal FasterDictionary groups; + internal FasterList> listOfGroups; + public FasterList groupArray; + } - static readonly FasterList groups; + static readonly ThreadLocal localgroups = new ThreadLocal(() => + { + GroupsList gl = default; - public static implicit operator FasterList(in GroupsList list) - { - return list.reference; - } + gl.groups = new FasterDictionary(); + gl.listOfGroups = FasterList>.PreInit(4); + gl.groupArray = new FasterList(1); - FasterList reference => groups; - } - - static readonly ThreadLocal groups = new ThreadLocal(); + return gl; + }); } } \ No newline at end of file diff --git a/Core/EntitiesDB.cs b/Core/EntitiesDB.cs index d65fa37..9f47185 100644 --- a/Core/EntitiesDB.cs +++ b/Core/EntitiesDB.cs @@ -12,12 +12,14 @@ namespace Svelto.ECS { public partial class EntitiesDB { - internal EntitiesDB(EnginesRoot enginesRoot) + internal EntitiesDB(EnginesRoot enginesRoot, EnginesRoot.LocatorMap entityReferencesMap) { - _enginesRoot = enginesRoot; + _enginesRoot = enginesRoot; + _entityReferencesMap = entityReferencesMap; } - EntityCollection InternalQueryEntities(FasterDictionary entitiesInGroupPerType) + EntityCollection InternalQueryEntities + (FasterDictionary entitiesInGroupPerType) where T : struct, IEntityComponent { uint count = 0; @@ -58,79 +60,85 @@ namespace Svelto.ECS { if (groupEntityComponentsDB.TryGetValue(groupStruct, out var entitiesInGroupPerType) == false) { - return new EntityCollection(new EntityCollection(RetrieveEmptyEntityComponentArray(), 0), - new EntityCollection(RetrieveEmptyEntityComponentArray(), 0)); + return new EntityCollection(new EntityCollection(RetrieveEmptyEntityComponentArray(), 0) + , new EntityCollection( + RetrieveEmptyEntityComponentArray(), 0)); } - + var T1entities = InternalQueryEntities(entitiesInGroupPerType); var T2entities = InternalQueryEntities(entitiesInGroupPerType); #if DEBUG && !PROFILE_SVELTO if (T1entities.count != T2entities.count) throw new ECSException("Entity components count do not match in group. Entity 1: ' count: " - .FastConcat(T1entities.count).FastConcat(" ", typeof(T1).ToString()) - .FastConcat("'. Entity 2: ' count: ".FastConcat(T2entities.count) - .FastConcat(" ", typeof(T2).ToString()) - .FastConcat("' group: ", groupStruct.ToName()))); + .FastConcat(T1entities.count).FastConcat(" ", typeof(T1).ToString()) + .FastConcat("'. Entity 2: ' count: ".FastConcat(T2entities.count) + .FastConcat(" ", typeof(T2).ToString()) + .FastConcat( + "' group: ", groupStruct.ToName()))); #endif return new EntityCollection(T1entities, T2entities); } - + public EntityCollection QueryEntities(ExclusiveGroupStruct groupStruct) where T1 : struct, IEntityComponent where T2 : struct, IEntityComponent where T3 : struct, IEntityComponent { if (groupEntityComponentsDB.TryGetValue(groupStruct, out var entitiesInGroupPerType) == false) { return new EntityCollection( - new EntityCollection(RetrieveEmptyEntityComponentArray(), 0), - new EntityCollection(RetrieveEmptyEntityComponentArray(), 0), - new EntityCollection(RetrieveEmptyEntityComponentArray(), 0)); + new EntityCollection(RetrieveEmptyEntityComponentArray(), 0) + , new EntityCollection(RetrieveEmptyEntityComponentArray(), 0) + , new EntityCollection(RetrieveEmptyEntityComponentArray(), 0)); } - + var T1entities = InternalQueryEntities(entitiesInGroupPerType); var T2entities = InternalQueryEntities(entitiesInGroupPerType); var T3entities = InternalQueryEntities(entitiesInGroupPerType); #if DEBUG && !PROFILE_SVELTO if (T1entities.count != T2entities.count || T2entities.count != T3entities.count) throw new ECSException("Entity components count do not match in group. Entity 1: " - .FastConcat(typeof(T1).ToString()).FastConcat(" count: ") - .FastConcat(T1entities.count).FastConcat( - " Entity 2: " - .FastConcat(typeof(T2).ToString()).FastConcat(" count: ") - .FastConcat(T2entities.count) - .FastConcat(" Entity 3: ".FastConcat(typeof(T3).ToString())) - .FastConcat(" count: ").FastConcat(T3entities.count))); + .FastConcat(typeof(T1).ToString()).FastConcat(" count: ") + .FastConcat(T1entities.count).FastConcat( + " Entity 2: ".FastConcat(typeof(T2).ToString()).FastConcat(" count: ") + .FastConcat(T2entities.count) + .FastConcat(" Entity 3: ".FastConcat(typeof(T3).ToString())) + .FastConcat(" count: ").FastConcat(T3entities.count))); #endif return new EntityCollection(T1entities, T2entities, T3entities); } - + public EntityCollection QueryEntities(ExclusiveGroupStruct groupStruct) - where T1 : struct, IEntityComponent where T2 : struct, IEntityComponent where T3 : struct, IEntityComponent where T4 : struct, IEntityComponent + where T1 : struct, IEntityComponent + where T2 : struct, IEntityComponent + where T3 : struct, IEntityComponent + where T4 : struct, IEntityComponent { if (groupEntityComponentsDB.TryGetValue(groupStruct, out var entitiesInGroupPerType) == false) { return new EntityCollection( - new EntityCollection(RetrieveEmptyEntityComponentArray(), 0), - new EntityCollection(RetrieveEmptyEntityComponentArray(), 0), - new EntityCollection(RetrieveEmptyEntityComponentArray(), 0), - new EntityCollection(RetrieveEmptyEntityComponentArray(), 0)); + new EntityCollection(RetrieveEmptyEntityComponentArray(), 0) + , new EntityCollection(RetrieveEmptyEntityComponentArray(), 0) + , new EntityCollection(RetrieveEmptyEntityComponentArray(), 0) + , new EntityCollection(RetrieveEmptyEntityComponentArray(), 0)); } - + var T1entities = InternalQueryEntities(entitiesInGroupPerType); var T2entities = InternalQueryEntities(entitiesInGroupPerType); var T3entities = InternalQueryEntities(entitiesInGroupPerType); var T4entities = InternalQueryEntities(entitiesInGroupPerType); #if DEBUG && !PROFILE_SVELTO - if (T1entities.count != T2entities.count || T2entities.count != T3entities.count) + if (T1entities.count != T2entities.count || T2entities.count != T3entities.count + || T3entities.count != T4entities.count) throw new ECSException("Entity components count do not match in group. Entity 1: " - .FastConcat(typeof(T1).ToString()).FastConcat(" count: ") - .FastConcat(T1entities.count).FastConcat( - " Entity 2: " - .FastConcat(typeof(T2).ToString()).FastConcat(" count: ") - .FastConcat(T2entities.count) - .FastConcat(" Entity 3: ".FastConcat(typeof(T3).ToString())) - .FastConcat(" count: ").FastConcat(T3entities.count))); + .FastConcat(typeof(T1).ToString()).FastConcat(" count: ") + .FastConcat(T1entities.count).FastConcat( + " Entity 2: ".FastConcat(typeof(T2).ToString()).FastConcat(" count: ") + .FastConcat(T2entities.count) + .FastConcat(" Entity 3: ".FastConcat(typeof(T3).ToString())) + .FastConcat(" count: ").FastConcat(T3entities.count) + .FastConcat(" Entity 4: ".FastConcat(typeof(T4).ToString())) + .FastConcat(" count: ").FastConcat(T4entities.count))); #endif return new EntityCollection(T1entities, T2entities, T3entities, T4entities); @@ -142,13 +150,20 @@ namespace Svelto.ECS return new GroupsEnumerable(this, groups); } + /// + /// Note: Remember that EntityViewComponents are always put at the end of the generic parameters tuple. + /// It won't compile otherwise + /// + /// public GroupsEnumerable QueryEntities(in LocalFasterReadOnlyList groups) where T1 : struct, IEntityComponent where T2 : struct, IEntityComponent { return new GroupsEnumerable(this, groups); } + - public GroupsEnumerable QueryEntities(in LocalFasterReadOnlyList groups) + public GroupsEnumerable QueryEntities + (in LocalFasterReadOnlyList groups) where T1 : struct, IEntityComponent where T2 : struct, IEntityComponent where T3 : struct, IEntityComponent { return new GroupsEnumerable(this, groups); @@ -156,8 +171,10 @@ namespace Svelto.ECS public GroupsEnumerable QueryEntities (in LocalFasterReadOnlyList groups) - where T1 : struct, IEntityComponent where T2 : struct, IEntityComponent - where T3 : struct, IEntityComponent where T4 : struct, IEntityComponent + where T1 : struct, IEntityComponent + where T2 : struct, IEntityComponent + where T3 : struct, IEntityComponent + where T4 : struct, IEntityComponent { return new GroupsEnumerable(this, groups); } @@ -167,7 +184,7 @@ namespace Svelto.ECS where T : struct, IEntityComponent { if (SafeQueryEntityDictionary(groupStructId, out var typeSafeDictionary) == false) - throw new EntityGroupNotFoundException(typeof(T) , groupStructId.ToName()); + throw new EntityGroupNotFoundException(typeof(T), groupStructId.ToName()); return (typeSafeDictionary as ITypeSafeDictionary).ToEGIDMapper(groupStructId); } @@ -239,11 +256,12 @@ namespace Svelto.ECS public bool IsDisposing => _enginesRoot._isDisposing; [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal bool SafeQueryEntityDictionary(out ITypeSafeDictionary typeSafeDictionary, - FasterDictionary entitiesInGroupPerType) - where T : IEntityComponent + internal bool SafeQueryEntityDictionary + (out ITypeSafeDictionary typeSafeDictionary + , FasterDictionary entitiesInGroupPerType) where T : IEntityComponent { - if (entitiesInGroupPerType.TryGetValue(new RefWrapperType(TypeCache.type), out var safeDictionary) == false) + if (entitiesInGroupPerType.TryGetValue(new RefWrapperType(TypeCache.type), out var safeDictionary) + == false) { typeSafeDictionary = default; return false; @@ -254,10 +272,10 @@ namespace Svelto.ECS return true; } - + [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal bool SafeQueryEntityDictionary(ExclusiveGroupStruct group, out ITypeSafeDictionary typeSafeDictionary) - where T : IEntityComponent + internal bool SafeQueryEntityDictionary + (ExclusiveGroupStruct group, out ITypeSafeDictionary typeSafeDictionary) where T : IEntityComponent { if (UnsafeQueryEntityDictionary(group, TypeCache.type, out var safeDictionary) == false) { @@ -272,7 +290,8 @@ namespace Svelto.ECS } [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal bool UnsafeQueryEntityDictionary(ExclusiveGroupStruct group, Type type, out ITypeSafeDictionary typeSafeDictionary) + internal bool UnsafeQueryEntityDictionary + (ExclusiveGroupStruct group, Type type, out ITypeSafeDictionary typeSafeDictionary) { //search for the group if (groupEntityComponentsDB.TryGetValue(group, out var entitiesInGroupPerType) == false) @@ -285,36 +304,6 @@ namespace Svelto.ECS return entitiesInGroupPerType.TryGetValue(new RefWrapperType(type), out typeSafeDictionary); } - internal bool FindIndex(uint entityID, ExclusiveGroupStruct @group, Type type, out uint index) - { - EGID entityGID = new EGID(entityID, @group); - - index = default; - - if (UnsafeQueryEntityDictionary(@group, type, out var safeDictionary) == false) - return false; - - if (safeDictionary.TryFindIndex(entityGID.entityID, out index) == false) - return false; - - return true; - } - - internal uint GetIndex(uint entityID, ExclusiveGroupStruct @group, Type type) - { - EGID entityGID = new EGID(entityID, @group); - - if (UnsafeQueryEntityDictionary(@group, type, out var safeDictionary) == false) - { - throw new EntityNotFoundException(entityGID, type); - } - - if (safeDictionary.TryFindIndex(entityGID.entityID, out var index) == false) - throw new EntityNotFoundException(entityGID, type); - - return index; - } - [MethodImpl(MethodImplOptions.AggressiveInlining)] static IBuffer RetrieveEmptyEntityComponentArray() where T : struct, IEntityComponent { @@ -353,14 +342,16 @@ namespace Svelto.ECS //group, then indexable per type, then indexable per EGID. however the TypeSafeDictionary can return an array of //values directly, that can be iterated over, so that is possible to iterate over all the entity components of //a specific type inside a specific group. -FasterDictionary> + FasterDictionary> groupEntityComponentsDB => _enginesRoot._groupEntityComponentsDB; -//for each entity view type, return the groups (dictionary of entities indexed by entity id) where they are -//found indexed by group id. TypeSafeDictionary are never created, they instead point to the ones hold -//by _groupEntityComponentsDB -// >> + //for each entity view type, return the groups (dictionary of entities indexed by entity id) where they are + //found indexed by group id. TypeSafeDictionary are never created, they instead point to the ones hold + //by _groupEntityComponentsDB + // >> FasterDictionary> groupsPerEntity => _enginesRoot._groupsPerEntity; + + EnginesRoot.LocatorMap _entityReferencesMap; } } \ No newline at end of file diff --git a/Core/EntityCollection.cs b/Core/EntityCollection.cs index 803328f..b81c062 100644 --- a/Core/EntityCollection.cs +++ b/Core/EntityCollection.cs @@ -6,7 +6,7 @@ namespace Svelto.ECS { public readonly ref struct EntityCollection where T : struct, IEntityComponent { - static readonly bool IsUnmanaged = TypeSafeDictionary.IsUnmanaged; + static readonly bool IsUnmanaged = TypeSafeDictionary.isUnmanaged; public EntityCollection(IBuffer buffer, uint count) : this() { diff --git a/Core/EntityDescriptor/DynamicEntityDescriptor.cs b/Core/EntityDescriptor/DynamicEntityDescriptor.cs index d44a193..7f94560 100644 --- a/Core/EntityDescriptor/DynamicEntityDescriptor.cs +++ b/Core/EntityDescriptor/DynamicEntityDescriptor.cs @@ -7,6 +7,8 @@ namespace Svelto.ECS /// DynamicEntityDescriptor can be used to add entity components to an existing EntityDescriptor that act as flags, /// at building time. /// This method allocates, so it shouldn't be abused + /// TODO:Unit test cases where there could be duplicates of components, especially EntityInfoComponent. + /// Test DynamicED of DynamicED /// /// public struct DynamicEntityDescriptor : IDynamicEntityDescriptor where TType : IEntityDescriptor, new() @@ -14,87 +16,156 @@ namespace Svelto.ECS internal DynamicEntityDescriptor(bool isExtendible) : this() { var defaultEntities = EntityDescriptorTemplate.descriptor.componentsToBuild; - var length = defaultEntities.Length; + var length = defaultEntities.Length; - ComponentsToBuild = new IComponentBuilder[length + 1]; - - Array.Copy(defaultEntities, 0, ComponentsToBuild, 0, length); + if (FetchEntityInfoComponent(defaultEntities) == -1) + { + _componentsToBuild = new IComponentBuilder[length + 1]; - //assign it after otherwise the previous copy will overwrite the value in case the item - //is already present - ComponentsToBuild[length] = new ComponentBuilder - ( - new EntityInfoComponent + Array.Copy(defaultEntities, 0, _componentsToBuild, 0, length); + //assign it after otherwise the previous copy will overwrite the value in case the item + //is already present + _componentsToBuild[length] = new ComponentBuilder(new EntityInfoComponent { - componentsToBuild = ComponentsToBuild - } - ); + componentsToBuild = _componentsToBuild + }); + } + else + { + _componentsToBuild = new IComponentBuilder[length]; + + Array.Copy(defaultEntities, 0, _componentsToBuild, 0, length); + } } - public DynamicEntityDescriptor(IComponentBuilder[] extraEntityBuilders) : this() + public DynamicEntityDescriptor(IComponentBuilder[] extraEntityBuilders) : this(true) { var extraEntitiesLength = extraEntityBuilders.Length; - ComponentsToBuild = Construct(extraEntitiesLength, extraEntityBuilders, - EntityDescriptorTemplate.descriptor.componentsToBuild); + _componentsToBuild = Construct(extraEntitiesLength, extraEntityBuilders); } - public DynamicEntityDescriptor(FasterList extraEntityBuilders) : this() + public DynamicEntityDescriptor(FasterList extraEntityBuilders) : this(true) { - var extraEntities = extraEntityBuilders.ToArrayFast(out _); + var extraEntities = extraEntityBuilders.ToArrayFast(out _); var extraEntitiesLength = extraEntityBuilders.count; - ComponentsToBuild = Construct((int) extraEntitiesLength, extraEntities, - EntityDescriptorTemplate.descriptor.componentsToBuild); + _componentsToBuild = Construct((int)extraEntitiesLength, extraEntities); } public void ExtendWith() where T : IEntityDescriptor, new() { - var newEntitiesToBuild = EntityDescriptorTemplate.descriptor.componentsToBuild; + var extraEntities = EntityDescriptorTemplate.descriptor.componentsToBuild; - ComponentsToBuild = Construct(newEntitiesToBuild.Length, newEntitiesToBuild, ComponentsToBuild); + _componentsToBuild = Construct(extraEntities.Length, extraEntities); } - + public void ExtendWith(IComponentBuilder[] extraEntities) { - ComponentsToBuild = Construct(extraEntities.Length, extraEntities, ComponentsToBuild); + _componentsToBuild = Construct(extraEntities.Length, extraEntities); + } + + public void ExtendWith(FasterList extraEntities) + { + _componentsToBuild = Construct(extraEntities.count, extraEntities.ToArrayFast(out _)); + } + + public void Add() where T : struct, IEntityComponent + { + IComponentBuilder[] extraEntities = { new ComponentBuilder() }; + _componentsToBuild = Construct(extraEntities.Length, extraEntities); } - static IComponentBuilder[] Construct(int extraEntitiesLength, IComponentBuilder[] extraEntities, - IComponentBuilder[] startingEntities) + public void Add() where T : struct, IEntityComponent where U : struct, IEntityComponent { - IComponentBuilder[] localEntitiesToBuild; + IComponentBuilder[] extraEntities = { new ComponentBuilder(), new ComponentBuilder() }; + _componentsToBuild = Construct(extraEntities.Length, extraEntities); + } - if (extraEntitiesLength == 0) + public void Add() where T : struct, IEntityComponent + where U : struct, IEntityComponent + where V : struct, IEntityComponent + { + IComponentBuilder[] extraEntities = { - localEntitiesToBuild = startingEntities; - return localEntitiesToBuild; - } + new ComponentBuilder(), new ComponentBuilder(), new ComponentBuilder() + }; + _componentsToBuild = Construct(extraEntities.Length, extraEntities); + } - var defaultEntities = startingEntities; - - var index = SetupEntityInfoComponent(defaultEntities, out localEntitiesToBuild, extraEntitiesLength); + /// + /// Note: unluckily I didn't design the serialization system to be component order independent, so unless + /// I do something about it, this method cannot be optimized, the logic of the component order must stay + /// untouched (no reordering, no use of dictionaries). Components order must stay as it comes, as + /// well as extracomponents order. + /// Speed, however, is not a big issue for this class, as the data is always composed once per entity descriptor + /// at static constructor time + /// + /// + IComponentBuilder[] Construct(int extraComponentsLength, IComponentBuilder[] extraComponents) + { + IComponentBuilder[] MergeLists + (IComponentBuilder[] startingComponents, IComponentBuilder[] newComponents, int newComponentsLength) + { + var startComponents = + new FasterDictionary, IComponentBuilder>(); + var xtraComponents = + new FasterDictionary, IComponentBuilder>(); + + for (uint i = 0; i < startingComponents.Length; i++) + startComponents + [new RefWrapper(startingComponents[i])] = + startingComponents[i]; + + for (uint i = 0; i < newComponentsLength; i++) + xtraComponents[new RefWrapper(newComponents[i])] = + newComponents[i]; - Array.Copy(extraEntities, 0, localEntitiesToBuild, defaultEntities.Length, extraEntitiesLength); + xtraComponents.Exclude(startComponents); - //assign it after otherwise the previous copy will overwrite the value in case the item - //is already present - localEntitiesToBuild[index] = new ComponentBuilder - ( - new EntityInfoComponent + if (newComponentsLength != xtraComponents.count) { - componentsToBuild = localEntitiesToBuild + newComponentsLength = xtraComponents.count; + + uint index = 0; + foreach (var couple in xtraComponents) + newComponents[index++] = couple.Key.value; } - ); - return localEntitiesToBuild; + IComponentBuilder[] componentBuilders = + new IComponentBuilder[newComponentsLength + startingComponents.Length]; + + Array.Copy(startingComponents, 0, componentBuilders, 0, startingComponents.Length); + Array.Copy(newComponents, 0, componentBuilders, startingComponents.Length, newComponentsLength); + + var entityInfoComponentIndex = FetchEntityInfoComponent(componentBuilders); + + DBC.ECS.Check.Assert(entityInfoComponentIndex != -1); + + componentBuilders[entityInfoComponentIndex] = new ComponentBuilder( + new EntityInfoComponent + { + componentsToBuild = componentBuilders + }); + + return componentBuilders; + } + + if (extraComponentsLength == 0) + { + return _componentsToBuild; + } + + var safeCopyOfExtraComponents = new IComponentBuilder[extraComponentsLength]; + Array.Copy(extraComponents, safeCopyOfExtraComponents, extraComponentsLength); + + return MergeLists(_componentsToBuild, safeCopyOfExtraComponents, extraComponentsLength); } - static int SetupEntityInfoComponent(IComponentBuilder[] defaultEntities, out IComponentBuilder[] componentsToBuild, - int extraLenght) + static int FetchEntityInfoComponent(IComponentBuilder[] defaultEntities) { int length = defaultEntities.Length; - int index = -1; + int index = -1; for (var i = 0; i < length; i++) { @@ -106,21 +177,11 @@ namespace Svelto.ECS } } - if (index == -1) - { - index = length + extraLenght; - componentsToBuild = new IComponentBuilder[index + 1]; - } - else - componentsToBuild = new IComponentBuilder[length + extraLenght]; - - Array.Copy(defaultEntities, 0, componentsToBuild, 0, length); - return index; } - public IComponentBuilder[] componentsToBuild => ComponentsToBuild; + public IComponentBuilder[] componentsToBuild => _componentsToBuild; - IComponentBuilder[] ComponentsToBuild; + IComponentBuilder[] _componentsToBuild; } } \ No newline at end of file diff --git a/Core/EntityDescriptor/ExtendibleEntityDescriptor.cs b/Core/EntityDescriptor/ExtendibleEntityDescriptor.cs index 6e479f8..207120f 100644 --- a/Core/EntityDescriptor/ExtendibleEntityDescriptor.cs +++ b/Core/EntityDescriptor/ExtendibleEntityDescriptor.cs @@ -21,7 +21,7 @@ namespace Svelto.ECS /// } /// /// - public class ExtendibleEntityDescriptor : IDynamicEntityDescriptor where TType : IEntityDescriptor, new() + public abstract class ExtendibleEntityDescriptor : IDynamicEntityDescriptor where TType : IEntityDescriptor, new() { static ExtendibleEntityDescriptor() { @@ -30,30 +30,44 @@ namespace Svelto.ECS $"SerializableEntityDescriptors cannot be used as base entity descriptor: {typeof(TType)}"); } - public ExtendibleEntityDescriptor(IComponentBuilder[] extraEntities) + protected ExtendibleEntityDescriptor(IComponentBuilder[] extraEntities) { _dynamicDescriptor = new DynamicEntityDescriptor(extraEntities); } - public ExtendibleEntityDescriptor() + protected ExtendibleEntityDescriptor() { _dynamicDescriptor = new DynamicEntityDescriptor(true); } - public ExtendibleEntityDescriptor ExtendWith() where T : IEntityDescriptor, new() + protected ExtendibleEntityDescriptor ExtendWith() where T : IEntityDescriptor, new() { _dynamicDescriptor.ExtendWith(); return this; } - public ExtendibleEntityDescriptor ExtendWith(IComponentBuilder[] extraEntities) + protected ExtendibleEntityDescriptor ExtendWith(IComponentBuilder[] extraEntities) { _dynamicDescriptor.ExtendWith(extraEntities); return this; } + + protected void Add() where T : struct, IEntityComponent + { + _dynamicDescriptor.Add(); + } + protected void Add() where T : struct, IEntityComponent where U : struct, IEntityComponent + { + _dynamicDescriptor.Add(); + } + protected void Add() where T : struct, IEntityComponent where U : struct, IEntityComponent where V : struct, IEntityComponent + { + _dynamicDescriptor.Add(); + } + public IComponentBuilder[] componentsToBuild => _dynamicDescriptor.componentsToBuild; DynamicEntityDescriptor _dynamicDescriptor; diff --git a/Core/EntityDescriptor/GenericEntityDescriptor.cs b/Core/EntityDescriptor/GenericEntityDescriptor.cs index a10a149..0ef48bb 100644 --- a/Core/EntityDescriptor/GenericEntityDescriptor.cs +++ b/Core/EntityDescriptor/GenericEntityDescriptor.cs @@ -2,7 +2,7 @@ { public abstract class GenericEntityDescriptor : IEntityDescriptor where T : struct, IEntityComponent { - static readonly IComponentBuilder[] _componentBuilders; + internal 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 { - static readonly IComponentBuilder[] _componentBuilders; + internal 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 { - static readonly IComponentBuilder[] _componentBuilders; + internal static readonly IComponentBuilder[] _componentBuilders; static GenericEntityDescriptor() { diff --git a/Core/EntityDescriptorTemplate.cs b/Core/EntityDescriptorTemplate.cs index e7b1525..5de23bc 100644 --- a/Core/EntityDescriptorTemplate.cs +++ b/Core/EntityDescriptorTemplate.cs @@ -2,7 +2,7 @@ using System; namespace Svelto.ECS { - static class EntityDescriptorTemplate where TType : IEntityDescriptor, new() + public static class EntityDescriptorTemplate where TType : IEntityDescriptor, new() { static EntityDescriptorTemplate() { diff --git a/Core/EntityFactory.cs b/Core/EntityFactory.cs index ef1760a..0c723ed 100644 --- a/Core/EntityFactory.cs +++ b/Core/EntityFactory.cs @@ -7,76 +7,84 @@ namespace Svelto.ECS.Internal static class EntityFactory { public static FasterDictionary BuildGroupedEntities - (EGID egid, EnginesRoot.DoubleBufferedEntitiesToAdd groupEntitiesToAdd - , IComponentBuilder[] componentsToBuild, IEnumerable implementors, Type implementorType) + (EGID egid, EnginesRoot.DoubleBufferedEntitiesToAdd groupEntitiesToAdd, IComponentBuilder[] componentsToBuild + , IEnumerable implementors +#if DEBUG && !PROFILE_SVELTO + , Type descriptorType +#endif + ) { var group = FetchEntityGroup(egid.groupID, groupEntitiesToAdd); - BuildEntitiesAndAddToGroup(egid, group, componentsToBuild, implementors, implementorType); + BuildEntitiesAndAddToGroup(egid, group, componentsToBuild, implementors +#if DEBUG && !PROFILE_SVELTO + , descriptorType +#endif + ); return group; } - static FasterDictionary FetchEntityGroup(ExclusiveGroupStruct groupID, - EnginesRoot.DoubleBufferedEntitiesToAdd groupEntityComponentsByType) + static FasterDictionary FetchEntityGroup + (ExclusiveGroupStruct groupID, EnginesRoot.DoubleBufferedEntitiesToAdd groupEntityComponentsByType) { - if (groupEntityComponentsByType.current.TryGetValue(groupID, out var group) == false) + if (groupEntityComponentsByType.current.TryGetValue((uint) groupID, out var group) == false) { group = new FasterDictionary(); - - groupEntityComponentsByType.current.Add(groupID, group); + + groupEntityComponentsByType.current.Add((uint) groupID, group); } - if (groupEntityComponentsByType.currentEntitiesCreatedPerGroup.TryGetValue(groupID, out var value) == false) - groupEntityComponentsByType.currentEntitiesCreatedPerGroup[groupID] = 0; - else - groupEntityComponentsByType.currentEntitiesCreatedPerGroup[groupID] = value+1; - + //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, Type implementorType) + , IComponentBuilder[] componentBuilders, IEnumerable implementors +#if DEBUG && !PROFILE_SVELTO + , Type descriptorType +#endif + ) { - var count = componentBuilders.Length; +#if DEBUG && !PROFILE_SVELTO + DBC.ECS.Check.Require(componentBuilders != null, $"Invalid Entity Descriptor {descriptorType}"); +#endif + var numberOfComponents = componentBuilders.Length; #if DEBUG && !PROFILE_SVELTO HashSet types = new HashSet(); - for (var index = 0; index < count; ++index) + for (var index = 0; index < numberOfComponents; ++index) { var entityComponentType = componentBuilders[index].GetEntityComponentType(); if (types.Contains(entityComponentType)) { - throw new ECSException($"EntityBuilders must be unique inside an EntityDescriptor. Descriptor Type {implementorType} Component Type: {entityComponentType}"); + throw new ECSException( + $"EntityBuilders must be unique inside an EntityDescriptor. Descriptor Type {descriptorType} Component Type: {entityComponentType}"); } types.Add(entityComponentType); } #endif - for (var index = 0; index < count; ++index) + for (var index = 0; index < numberOfComponents; ++index) { var entityComponentBuilder = componentBuilders[index]; - var entityComponentType = entityComponentBuilder.GetEntityComponentType(); - BuildEntity(entityID, @group, entityComponentType, entityComponentBuilder, implementors); + BuildEntity(entityID, @group, entityComponentBuilder, implementors); } } - static void BuildEntity(EGID entityID, FasterDictionary group, - Type entityComponentType, IComponentBuilder componentBuilder, IEnumerable implementors) + static void BuildEntity(EGID entityID, FasterDictionary group + , IComponentBuilder componentBuilder, IEnumerable implementors) { - var entityComponentsPoolWillBeCreated = - group.TryGetValue(new RefWrapperType(entityComponentType), out var safeDictionary) == false; - - //passing the undefined entityComponentsByType inside the entityComponentBuilder will allow it to be created with the - //correct type and casted back to the undefined list. that's how the list will be eventually of the target - //type. - componentBuilder.BuildEntityAndAddToList(ref safeDictionary, entityID, implementors); + var entityComponentType = componentBuilder.GetEntityComponentType(); + var safeDictionary = group.GetOrCreate(new RefWrapperType(entityComponentType), (ref IComponentBuilder cb) => cb.CreateDictionary(1), ref componentBuilder); - if (entityComponentsPoolWillBeCreated) - group.Add(new RefWrapperType(entityComponentType), safeDictionary); + //if the safeDictionary hasn't been created yet, it will be created inside this method. + componentBuilder.BuildEntityAndAddToList(safeDictionary, entityID, implementors); } } } \ No newline at end of file diff --git a/Core/EntityInitializer.cs b/Core/EntityInitializer.cs index dd2b2e3..6e800cc 100644 --- a/Core/EntityInitializer.cs +++ b/Core/EntityInitializer.cs @@ -1,22 +1,27 @@ 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) + public EntityInitializer + (EGID id, FasterDictionary group, in EntityReference reference) { - _group = group; - _ID = id; + _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) return; + if (_group.TryGetValue(new RefWrapperType(ComponentBuilder.ENTITY_COMPONENT_TYPE) + , out var typeSafeDictionary) == false) + return; var dictionary = (ITypeSafeDictionary) typeSafeDictionary; @@ -29,23 +34,23 @@ namespace Svelto.ECS public ref T GetOrCreate() where T : struct, IEntityComponent { - ref var entityDictionary = ref _group.GetOrCreate(new RefWrapperType(ComponentBuilder.ENTITY_COMPONENT_TYPE) - , TypeSafeDictionaryFactory.Create); + ref var entityDictionary = ref _group.GetOrCreate( + new RefWrapperType(ComponentBuilder.ENTITY_COMPONENT_TYPE), TypeSafeDictionaryFactory.Create); var dictionary = (ITypeSafeDictionary) entityDictionary; return ref dictionary.GetOrCreate(_ID.entityID); } - + public ref T Get() where T : struct, IEntityComponent { - return ref (_group[new RefWrapperType(ComponentBuilder.ENTITY_COMPONENT_TYPE)] as ITypeSafeDictionary)[ - _ID.entityID]; + return ref (_group[new RefWrapperType(ComponentBuilder.ENTITY_COMPONENT_TYPE)] as ITypeSafeDictionary) + [_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; @@ -55,8 +60,8 @@ namespace Svelto.ECS return false; } - - readonly EGID _ID; + + readonly EGID _ID; readonly FasterDictionary _group; } } \ No newline at end of file diff --git a/Debugger.meta b/Core/EntityReference.meta similarity index 77% rename from Debugger.meta rename to Core/EntityReference.meta index 647647c..5db3264 100644 --- a/Debugger.meta +++ b/Core/EntityReference.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: d2793f2ae73e357f9773b68721bbe468 +guid: 089dbe82b8343ef58115bf686c9428c1 folderAsset: yes DefaultImporter: externalObjects: {} diff --git a/Core/EntityReference/EnginesRoot.LocatorMap.cs b/Core/EntityReference/EnginesRoot.LocatorMap.cs new file mode 100644 index 0000000..a3cdb8c --- /dev/null +++ b/Core/EntityReference/EnginesRoot.LocatorMap.cs @@ -0,0 +1,227 @@ +using System.Runtime.CompilerServices; +using Svelto.Common; +using Svelto.DataStructures; +using Svelto.ECS.DataStructures; +using Svelto.ECS.Reference; + +namespace Svelto.ECS +{ + // The EntityLocatorMap provides a bidirectional map to help locate entities without using an EGID which might + // change in runtime. The Entity Locator map uses a reusable unique identifier struct called EntityLocator to + // find the last known EGID from last entity submission. + public partial class EnginesRoot + { + public struct LocatorMap + { + internal EntityReference ClaimReference() + { + int tempFreeIndex; + int newFreeIndex; + uint version; + + do + { + tempFreeIndex = _nextFreeIndex; + // Check if we need to create a new EntityLocator or whether we can recycle an existing one. + if ((uint)tempFreeIndex >= _entityReferenceMap.count) + { + newFreeIndex = tempFreeIndex + 1; + version = 0; + } + else + { + ref EntityReferenceMapElement element = ref _entityReferenceMap[tempFreeIndex]; + // The recycle entities form a linked list, using the egid.entityID to store the next element. + newFreeIndex = (int)element.egid.entityID; + version = element.version; + } + } while (tempFreeIndex != _nextFreeIndex.CompareExchange(newFreeIndex, tempFreeIndex)); + +#if DEBUG && !PROFILE_SVELTO + // This code should be safe since we own the tempFreeIndex, this allows us to later check that nothing went wrong. + if (tempFreeIndex < _entityReferenceMap.count) + { + _entityReferenceMap[tempFreeIndex] = new EntityReferenceMapElement(new EGID(0, 0), version); + } +#endif + + return new EntityReference((uint)tempFreeIndex + 1, version); + } + + internal void SetReference(EntityReference reference, EGID egid) + { + // Since references can be claimed in parallel now, it might happen that they are set out of order, + // so we need to resize instead of add. + if (reference.index >= _entityReferenceMap.count) + { +#if DEBUG && !PROFILE_SVELTO //THIS IS TO VALIDATE DATE DBC LIKE + for (var i = _entityReferenceMap.count; i <= reference.index; i++) + { + _entityReferenceMap.Add(new EntityReferenceMapElement(default, 0)); + } +#else + _entityReferenceMap.AddAt(reference.index); +#endif + } + +#if DEBUG && !PROFILE_SVELTO + // These debug tests should be enough to detect if indices are being used correctly under native factories + if (_entityReferenceMap[reference.index].version != reference.version || + _entityReferenceMap[reference.index].egid.groupID != ExclusiveGroupStruct.Invalid) + { + throw new ECSException("Entity reference already set. This should never happen, please report it."); + } +#endif + + _entityReferenceMap[reference.index] = new EntityReferenceMapElement(egid, reference.version); + + // Update reverse map from egid to locator. + var groupMap = + _egidToReferenceMap.GetOrCreate(egid.groupID + , () => new SharedSveltoDictionaryNative(0)); + groupMap[egid.entityID] = reference; + } + + internal void UpdateEntityReference(EGID from, EGID to) + { + var reference = FetchAndRemoveReference(@from); + + _entityReferenceMap[reference.index].egid = to; + + var groupMap = + _egidToReferenceMap.GetOrCreate( + to.groupID, () => new SharedSveltoDictionaryNative(0)); + groupMap[to.entityID] = reference; + } + + internal void RemoveEntityReference(EGID egid) + { + var reference = FetchAndRemoveReference(@egid); + + // Invalidate the entity locator element by bumping its version and setting the egid to point to a not existing element. + ref var entityReferenceMapElement = ref _entityReferenceMap[reference.index]; + entityReferenceMapElement.egid = new EGID((uint)(int)_nextFreeIndex, 0); + entityReferenceMapElement.version++; + + // Mark the element as the last element used. + _nextFreeIndex.Set((int)reference.index); + } + + EntityReference FetchAndRemoveReference(EGID @from) + { + var egidToReference = _egidToReferenceMap[@from.groupID]; + var reference = egidToReference[@from.entityID]; + egidToReference.Remove(@from.entityID); + + return reference; + } + + internal void RemoveAllGroupReferenceLocators(ExclusiveGroupStruct groupId) + { + if (_egidToReferenceMap.TryGetValue(groupId, out var groupMap) == false) + return; + + // 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)); + + _egidToReferenceMap.Remove(groupId); + } + + internal void UpdateAllGroupReferenceLocators(ExclusiveGroupStruct fromGroupId, uint toGroupId) + { + if (_egidToReferenceMap.TryGetValue(fromGroupId, out var groupMap) == false) + return; + + // 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)); + + _egidToReferenceMap.Remove(fromGroupId); + } + + public EntityReference GetEntityReference(EGID egid) + { + if (_egidToReferenceMap.TryGetValue(egid.groupID, out var groupMap)) + { + if (groupMap.TryGetValue(egid.entityID, out var locator)) + return locator; +#if DEBUG && !PROFILE_SVELTO + else throw new ECSException($"Entity {egid} does not exist. Are you creating it? Try getting it from initializer.reference."); +#endif + } + + return EntityReference.Invalid; + } + + public bool TryGetEGID(EntityReference reference, out EGID egid) + { + egid = default; + if (reference == EntityReference.Invalid) + return false; + // Make sure we are querying for the current version of the locator. + // Otherwise the locator is pointing to a removed entity. + if (_entityReferenceMap[reference.index].version == reference.version) + { + egid = _entityReferenceMap[reference.index].egid; + return true; + } + + return false; + } + + public EGID GetEGID(EntityReference reference) + { + if (reference == EntityReference.Invalid) + throw new ECSException("Invalid Reference"); + // Make sure we are querying for the current version of the locator. + // Otherwise the locator is pointing to a removed entity. + if (_entityReferenceMap[reference.index].version != reference.version) + throw new ECSException("outdated Reference"); + + return _entityReferenceMap[reference.index].egid; + } + + internal void PreallocateReferenceMaps(ExclusiveGroupStruct groupID, uint size) + { + _egidToReferenceMap + .GetOrCreate(groupID, () => new SharedSveltoDictionaryNative(size)) + .SetCapacity(size); + + _entityReferenceMap.Resize(size); + } + + internal void InitEntityReferenceMap() + { + _nextFreeIndex = SharedNativeInt.Create(0, Allocator.Persistent); + _entityReferenceMap = + new NativeDynamicArrayCast( + NativeDynamicArray.Alloc()); + _egidToReferenceMap = + new SharedSveltoDictionaryNative>(0); + } + + internal void DisposeEntityReferenceMap() + { + _nextFreeIndex.Dispose(); + _entityReferenceMap.Dispose(); + + foreach (var element in _egidToReferenceMap) + element.Value.Dispose(); + _egidToReferenceMap.Dispose(); + } + + SharedNativeInt _nextFreeIndex; + NativeDynamicArrayCast _entityReferenceMap; + + SharedSveltoDictionaryNative> + _egidToReferenceMap; + } + + internal LocatorMap entityLocator => _entityLocator; + LocatorMap _entityLocator; + } +} \ No newline at end of file diff --git a/Dispatcher/DispatchOnSet.cs.meta b/Core/EntityReference/EnginesRoot.LocatorMap.cs.meta similarity index 83% rename from Dispatcher/DispatchOnSet.cs.meta rename to Core/EntityReference/EnginesRoot.LocatorMap.cs.meta index fd879f4..619fcea 100644 --- a/Dispatcher/DispatchOnSet.cs.meta +++ b/Core/EntityReference/EnginesRoot.LocatorMap.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: f6f0b271d84c3396a85079f8d3aabc5b +guid: aa84340f68b23e37b0de98db457a481d MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/Core/EntityReference/EntitiesDB.References.cs b/Core/EntityReference/EntitiesDB.References.cs new file mode 100644 index 0000000..ca72635 --- /dev/null +++ b/Core/EntityReference/EntitiesDB.References.cs @@ -0,0 +1,32 @@ +using System.Runtime.CompilerServices; +using Svelto.ECS.Reference; + +namespace Svelto.ECS +{ + public partial class EntitiesDB + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool TryGetEGID(EntityReference entityReference, out EGID egid) + { + return _entityReferencesMap.TryGetEGID(entityReference, out egid); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public EGID GetEGID(EntityReference entityReference) + { + return _entityReferencesMap.GetEGID(entityReference); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public EnginesRoot.LocatorMap GetEntityLocator() + { + return _entityReferencesMap; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public EntityReference GetEntityReference(EGID egid) + { + return _entityReferencesMap.GetEntityReference(egid); + } + } +} \ No newline at end of file diff --git a/Dispatcher/DispatchOnChange.cs.meta b/Core/EntityReference/EntitiesDB.References.cs.meta similarity index 83% rename from Dispatcher/DispatchOnChange.cs.meta rename to Core/EntityReference/EntitiesDB.References.cs.meta index 1bde51f..c8410d0 100644 --- a/Dispatcher/DispatchOnChange.cs.meta +++ b/Core/EntityReference/EntitiesDB.References.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 8152434c555d3766b56aa64998b69aaf +guid: cb8b3078bb8f3ace95125a0e9003eaf5 MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/Core/EntityReference/EntityReference.cs b/Core/EntityReference/EntityReference.cs new file mode 100644 index 0000000..b737ad7 --- /dev/null +++ b/Core/EntityReference/EntityReference.cs @@ -0,0 +1,78 @@ +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +#pragma warning disable 660,661 + +namespace Svelto.ECS +{ + /// + /// Todo: EntityReference shouldn't map EGIDs as dictionaries keys but directly the indices in the EntityDB arrays + /// + [Serialization.DoNotSerialize] + [Serializable] + [StructLayout(LayoutKind.Explicit)] + public struct EntityReference : IEquatable + { + [FieldOffset(0)] public readonly uint uniqueID; + [FieldOffset(4)] public readonly uint version; + [FieldOffset(0)] readonly ulong _GID; + + internal uint index => uniqueID - 1; + + public static bool operator ==(EntityReference obj1, EntityReference obj2) + { + return obj1._GID == obj2._GID; + } + + public static bool operator !=(EntityReference obj1, EntityReference obj2) + { + return obj1._GID != obj2._GID; + } + + public EntityReference(uint uniqueId) : this(uniqueId, 0) {} + + public EntityReference(uint uniqueId, uint version) : this() + { + _GID = MAKE_GLOBAL_ID(uniqueId, version); + } + + public bool Equals(EntityReference other) + { + return _GID == other._GID; + } + + public bool Equals(EntityReference x, EntityReference y) + { + return x._GID == y._GID; + } + + public override string ToString() + { + return "id:".FastConcat(uniqueID).FastConcat(" version:").FastConcat(version); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public EGID ToEGID(EntitiesDB entitiesDB) + { + DBC.ECS.Check.Require(this != Invalid, "Invalid Reference Used"); + + return entitiesDB.GetEGID(this); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool ToEGID(EntitiesDB entitiesDB, out EGID egid) + { + DBC.ECS.Check.Require(this != Invalid, "Invalid Reference Used"); + + return entitiesDB.TryGetEGID(this, out egid); + } + + static ulong MAKE_GLOBAL_ID(uint uniqueId, uint version) + { + return (ulong)version << 32 | ((ulong)uniqueId & 0xFFFFFFFF); + } + + public static EntityReference Invalid => default; + } +} diff --git a/Core/EntityReference/EntityReference.cs.meta b/Core/EntityReference/EntityReference.cs.meta new file mode 100644 index 0000000..022d1f4 --- /dev/null +++ b/Core/EntityReference/EntityReference.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0d74cba5c0b833028e4c5f06345376b3 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Core/EntityReference/EntityReferenceMapElement.cs b/Core/EntityReference/EntityReferenceMapElement.cs new file mode 100644 index 0000000..28923cf --- /dev/null +++ b/Core/EntityReference/EntityReferenceMapElement.cs @@ -0,0 +1,20 @@ +namespace Svelto.ECS.Reference +{ + struct EntityReferenceMapElement + { + internal EGID egid; + internal uint version; + + internal EntityReferenceMapElement(EGID egid) + { + this.egid = egid; + version = 0; + } + + internal EntityReferenceMapElement(EGID egid, uint version) + { + this.egid = egid; + this.version = version; + } + } +} \ No newline at end of file diff --git a/Core/EntityReference/EntityReferenceMapElement.cs.meta b/Core/EntityReference/EntityReferenceMapElement.cs.meta new file mode 100644 index 0000000..634cdcc --- /dev/null +++ b/Core/EntityReference/EntityReferenceMapElement.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 601c96428dc73bb5a953eaac8ee0b443 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Core/EntitySubmissionScheduler.cs b/Core/EntitySubmissionScheduler.cs index ac24d91..f023054 100644 --- a/Core/EntitySubmissionScheduler.cs +++ b/Core/EntitySubmissionScheduler.cs @@ -1,5 +1,3 @@ -using System.Collections; - namespace Svelto.ECS.Schedulers { public abstract class EntitiesSubmissionScheduler diff --git a/Core/EntityViewUtility.cs b/Core/EntityViewUtility.cs index 6f964d9..a990550 100644 --- a/Core/EntityViewUtility.cs +++ b/Core/EntityViewUtility.cs @@ -32,7 +32,7 @@ namespace Svelto.ECS const string NOT_FOUND_EXCEPTION = "Svelto.ECS Implementor not found for an EntityComponent. "; - public static void FillEntityComponent + public static void SetEntityViewComponentImplementors (this IComponentBuilder componentBuilder, ref T entityComponent , FasterList>> entityComponentBlazingFastReflection , IEnumerable implementors @@ -43,49 +43,45 @@ namespace Svelto.ECS #endif , Dictionary cachedTypeInterfaces) { - //efficient way to collect the fields of every EntityComponentType - var setters = FasterList>>.NoVirt.ToArrayFast( - entityComponentBlazingFastReflection, out var count); + DBC.ECS.Check.Require(implementors != null, NULL_IMPLEMENTOR_ERROR.FastConcat(" entityComponent " + , componentBuilder + .GetEntityComponentType().ToString())); - //todo this should happen once per T, not once per Build - if (implementors != null) + foreach (var implementor in implementors) { - foreach (var implementor in implementors) + DBC.ECS.Check.Require(implementor != null, "invalid null implementor used to build an entity"); { - if (implementor != null) - { - var type = implementor.GetType(); + var type = implementor.GetType(); - if (cachedTypeInterfaces.TryGetValue(type, out var interfaces) == false) - interfaces = cachedTypeInterfaces[type] = type.GetInterfacesEx(); + //fetch all the interfaces that the implementor implements + if (cachedTypeInterfaces.TryGetValue(type, out var interfaces) == false) + interfaces = cachedTypeInterfaces[type] = type.GetInterfacesEx(); - for (var iindex = 0; iindex < interfaces.Length; iindex++) - { - var componentType = interfaces[iindex]; + for (var iindex = 0; iindex < interfaces.Length; iindex++) + { + var componentType = interfaces[iindex]; + //an implementor can implement multiple interfaces, so for each interface we reference + //the implementation object. Multiple entity view component fields can then be implemented + //by the same implementor #if DEBUG && !PROFILE_SVELTO - if (implementorsByType.TryGetValue(componentType, out var implementorData)) - { - implementorData.numberOfImplementations++; - implementorsByType[componentType] = implementorData; - } - else - implementorsByType[componentType] = new ECSTuple(implementor, 1); + if (implementorsByType.TryGetValue(componentType, out var implementorData)) + { + implementorData.numberOfImplementations++; + implementorsByType[componentType] = implementorData; + } + else + implementorsByType[componentType] = new ECSTuple(implementor, 1); #else implementorsByType[componentType] = implementor; #endif - } } -#if DEBUG && !PROFILE_SVELTO - else - { - Console.Log(NULL_IMPLEMENTOR_ERROR.FastConcat(" entityComponent " - , componentBuilder - .GetEntityComponentType().ToString())); - } -#endif } } + //efficient way to collect the fields of every EntityComponentType + var setters = FasterList>>.NoVirt.ToArrayFast( + entityComponentBlazingFastReflection, out var count); + for (var i = 0; i < count; i++) { var fieldSetter = setters[i]; diff --git a/Core/Filters/EntitiesDB.GroupFilters.cs b/Core/Filters/EntitiesDB.GroupFilters.cs index af599d9..92b9813 100644 --- a/Core/Filters/EntitiesDB.GroupFilters.cs +++ b/Core/Filters/EntitiesDB.GroupFilters.cs @@ -14,27 +14,27 @@ namespace Svelto.ECS public readonly struct Filters { public Filters - (FasterDictionary> filters) + (FasterDictionary> filters) { - _filters = filters; + _filters = filters; } public ref FilterGroup CreateOrGetFilterForGroup(int filterID, ExclusiveGroupStruct groupID) where T : struct, IEntityComponent { var refWrapper = TypeRefWrapper.wrapper; - + return ref CreateOrGetFilterForGroup(filterID, groupID, refWrapper); } - ref FilterGroup CreateOrGetFilterForGroup(int filterID, ExclusiveGroupStruct groupID, RefWrapperType refWrapper) + 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)); + GroupFilters filters = fasterDictionary.GetOrCreate( + groupID, () => new GroupFilters(new SharedSveltoDictionaryNative(0), groupID)); return ref filters.CreateOrGetFilter(filterID); } @@ -43,7 +43,7 @@ namespace Svelto.ECS { if (_filters.TryGetValue(TypeRefWrapper.wrapper, out var fasterDictionary) == false) return false; - + return fasterDictionary.ContainsKey(groupID); } @@ -58,16 +58,17 @@ namespace Svelto.ECS return false; } - + public ref GroupFilters CreateOrGetFiltersForGroup(ExclusiveGroupStruct groupID) where T : struct, IEntityComponent { - var fasterDictionary = - _filters.GetOrCreate(TypeRefWrapper.wrapper, () => new FasterDictionary()); + var fasterDictionary = _filters.GetOrCreate(TypeRefWrapper.wrapper + , () => + new FasterDictionary()); - return ref - fasterDictionary.GetOrCreate( - groupID, () => new GroupFilters(new SharedSveltoDictionaryNative(0), groupID)); + return ref fasterDictionary.GetOrCreate( + groupID, () => new GroupFilters(new SharedSveltoDictionaryNative(0), groupID)); } public ref GroupFilters GetFiltersForGroup(ExclusiveGroupStruct groupID) @@ -75,8 +76,7 @@ namespace Svelto.ECS { #if DEBUG && !PROFILE_SVELTO if (_filters.ContainsKey(TypeRefWrapper.wrapper) == false) - throw new ECSException( - $"trying to fetch not existing filters, type {typeof(T)}"); + throw new ECSException($"trying to fetch not existing filters, type {typeof(T)}"); if (_filters[TypeRefWrapper.wrapper].ContainsKey(groupID) == false) throw new ECSException( $"trying to fetch not existing filters, type {typeof(T)} group {groupID.ToName()}"); @@ -90,15 +90,14 @@ namespace Svelto.ECS { #if DEBUG && !PROFILE_SVELTO if (_filters.ContainsKey(TypeRefWrapper.wrapper) == false) - throw new ECSException( - $"trying to fetch not existing filters, type {typeof(T)}"); + throw new ECSException($"trying to fetch not existing filters, type {typeof(T)}"); if (_filters[TypeRefWrapper.wrapper].ContainsKey(groupID) == false) throw new ECSException( $"trying to fetch not existing filters, type {typeof(T)} group {groupID.ToName()}"); #endif return ref _filters[TypeRefWrapper.wrapper][groupID].GetFilter(filterId); } - + public bool TryGetFilterForGroup(int filterId, ExclusiveGroupStruct groupID, out FilterGroup groupFilter) where T : struct, IEntityComponent { @@ -131,8 +130,9 @@ namespace Svelto.ECS { if (_filters.TryGetValue(TypeRefWrapper.wrapper, out var fasterDictionary) == true) { - DBC.ECS.Check.Require(fasterDictionary.ContainsKey(exclusiveGroupStruct), $"trying to clear filter not present in group {exclusiveGroupStruct}"); - + DBC.ECS.Check.Require(fasterDictionary.ContainsKey(exclusiveGroupStruct) + , $"trying to clear filter not present in group {exclusiveGroupStruct}"); + fasterDictionary[exclusiveGroupStruct].ClearFilter(filterID); } } @@ -174,7 +174,7 @@ namespace Svelto.ECS } } - public bool TryRemoveEntityFromFilter(int filtersID, EGID egid) where T : unmanaged, IEntityComponent + public bool TryRemoveEntityFromFilter(int filtersID, EGID egid) where T : struct, IEntityComponent { if (TryGetFilterForGroup(filtersID, egid.groupID, out var filter)) { @@ -184,29 +184,28 @@ namespace Svelto.ECS return false; } - public void RemoveEntityFromFilter(int filtersID, EGID egid) where T : unmanaged, IEntityComponent + public void RemoveEntityFromFilter(int filtersID, EGID egid) where T : struct, IEntityComponent { ref var filter = ref GetFilterForGroup(filtersID, egid.groupID); filter.Remove(egid.entityID); } - public void AddEntityToFilter(int filtersID, EGID egid, N mapper) where N:IEGIDMapper + public bool AddEntityToFilter(int filtersID, EGID egid, N mapper) where N : IEGIDMapper { - ref var filter = ref CreateOrGetFilterForGroup(filtersID, egid.groupID, new RefWrapperType(mapper.entityType)); + ref var filter = + ref CreateOrGetFilterForGroup(filtersID, egid.groupID, new RefWrapperType(mapper.entityType)); - filter.Add(egid.entityID, mapper); + return filter.Add(egid.entityID, mapper); } readonly FasterDictionary> _filters; } - public Filters GetFilters() - { - return new Filters(_filters); - } + public Filters GetFilters() { return new Filters(_filters); } + - FasterDictionary> _filters - => _enginesRoot._groupFilters; + FasterDictionary> _filters => + _enginesRoot._groupFilters; } } \ No newline at end of file diff --git a/Core/Filters/FilterGroup.cs b/Core/Filters/FilterGroup.cs index 99091dc..86f05b3 100644 --- a/Core/Filters/FilterGroup.cs +++ b/Core/Filters/FilterGroup.cs @@ -1,4 +1,5 @@ -using Svelto.Common; +using System.Runtime.CompilerServices; +using Svelto.Common; using Svelto.DataStructures; using Svelto.ECS.DataStructures; @@ -23,7 +24,7 @@ namespace Svelto.ECS //from the index, find the entityID _reverseEIDs = new NativeDynamicArrayCast(NativeDynamicArray.Alloc(Allocator.Persistent)); //from the entityID, find the index - _indexOfEntityInDenseList = new SharedSveltoDictionaryNative(0, Allocator.Persistent); + _indexOfEntityInDenseList = new SharedSveltoDictionaryNative(0); _exclusiveGroupStruct = exclusiveGroupStruct; _ID = ID; } @@ -33,30 +34,15 @@ namespace Svelto.ECS /// public FilteredIndices filteredIndices => new FilteredIndices(_denseListOfIndicesToEntityComponentArray); - public void Add(uint entityID, N mapper) where N:IEGIDMapper + public bool Add(uint entityID, N mapper) where N:IEGIDMapper { #if DEBUG && !PROFILE_SVELTO - if (_denseListOfIndicesToEntityComponentArray.isValid == false) - throw new ECSException($"using an invalid filter"); - if (_indexOfEntityInDenseList.ContainsKey(entityID) == true) - throw new ECSException( - $"trying to add an existing entity {entityID} to filter {mapper.entityType} - {_ID} with group {mapper.groupID}"); if (mapper.Exists(entityID) == false) throw new ECSException( $"trying adding an entity {entityID} to filter {mapper.entityType} - {_ID} with group {mapper.groupID}, but entity is not found! "); #endif - //Get the index of the Entity in the component array - var indexOfEntityInBufferComponent = mapper.GetIndex(entityID); - - //add the index in the list of filtered indices - _denseListOfIndicesToEntityComponentArray.Add(indexOfEntityInBufferComponent); - - //inverse map: need to get the entityID from the index. This wouldn't be needed with a real sparseset - var lastIndex = (uint) (_denseListOfIndicesToEntityComponentArray.Count() - 1); - _reverseEIDs.AddAt(lastIndex) = entityID; - //remember the entities indices. This is needed to remove entities from the filter - _indexOfEntityInDenseList.Add(entityID, lastIndex); + return InternalAdd(entityID, mapper.GetIndex(entityID)); } public void Remove(uint entityID) @@ -71,6 +57,15 @@ namespace Svelto.ECS InternalRemove(entityID); } + public bool Exists(uint entityID) + { +#if DEBUG && !PROFILE_SVELTO + if (_denseListOfIndicesToEntityComponentArray.isValid == false) + throw new ECSException($"invalid Filter"); +#endif + return _indexOfEntityInDenseList.ContainsKey(entityID); + } + public bool TryRemove(uint entityID) { #if DEBUG && !PROFILE_SVELTO @@ -99,7 +94,7 @@ namespace Svelto.ECS /// point to entities that were not the original ones. On structural changes /// (specifically entities swapped or removed) /// the filters must then be rebuilt. It would be too slow to add this in the standard flow of Svelto in - /// the current state, so calling this method is a user responsibility. + /// the current state, so calling this method is a user responsibility. /// public void RebuildIndicesOnStructuralChange(N mapper) where N:IEGIDMapper { @@ -146,6 +141,29 @@ namespace Svelto.ECS _reverseEIDs.Dispose(); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal bool InternalAdd(uint entityID, uint indexOfEntityInBufferComponent) + { + #if DEBUG && !PROFILE_SVELTO + if (_denseListOfIndicesToEntityComponentArray.isValid == false) + throw new ECSException($"using an invalid filter"); + #endif + if (_indexOfEntityInDenseList.ContainsKey(entityID) == true) + return false; + + //add the index in the list of filtered indices + _denseListOfIndicesToEntityComponentArray.Add(indexOfEntityInBufferComponent); + + //inverse map: need to get the entityID from the index. This wouldn't be needed with a real sparseset + var lastIndex = (uint) (_denseListOfIndicesToEntityComponentArray.Count() - 1); + _reverseEIDs.AddAt(lastIndex) = entityID; + + //remember the entities indices. This is needed to remove entities from the filter + _indexOfEntityInDenseList.Add(entityID, lastIndex); + + return true; + } + void InternalRemove(uint entityID) { var count = (uint) _denseListOfIndicesToEntityComponentArray.Count(); @@ -157,7 +175,7 @@ namespace Svelto.ECS var indexInDenseListFromEGID = _indexOfEntityInDenseList[entityID]; //get the entityID of the last entity in the filter array uint entityIDToMove = _reverseEIDs[count - 1]; - + //the last index of the last entity is updated to the slot of the deleted entity if (entityIDToMove != entityID) { @@ -165,13 +183,13 @@ namespace Svelto.ECS //the reverseEGID is updated accordingly _reverseEIDs[indexInDenseListFromEGID] = entityIDToMove; } - + // _reverseEIDs.UnorderedRemoveAt(count - 1); //finally remove the deleted entity from the filters array _denseListOfIndicesToEntityComponentArray.UnorderedRemoveAt(indexInDenseListFromEGID); - + //remove the entity to delete from the tracked Entity _indexOfEntityInDenseList.Remove(entityID); } @@ -188,7 +206,7 @@ namespace Svelto.ECS NativeDynamicArrayCast _reverseEIDs; //forced to use this because it's not a real sparse set SharedSveltoDictionaryNative _indexOfEntityInDenseList; - readonly ExclusiveGroupStruct _exclusiveGroupStruct; - readonly int _ID; + internal readonly ExclusiveGroupStruct _exclusiveGroupStruct; + internal readonly int _ID; } } \ No newline at end of file diff --git a/Core/Filters/GroupFilters.cs b/Core/Filters/GroupFilters.cs index 99004d8..c6567cf 100644 --- a/Core/Filters/GroupFilters.cs +++ b/Core/Filters/GroupFilters.cs @@ -40,8 +40,8 @@ namespace Svelto.ECS return filters.TryGetValue(filterIndex, out filter); } - public SveltoDictionary>, NativeStrategy - , NativeStrategy>.SveltoDictionaryKeyValueEnumerator GetEnumerator() + public SveltoDictionaryKeyValueEnumerator>, NativeStrategy + , NativeStrategy> GetEnumerator() { return filters.GetEnumerator(); } diff --git a/Core/GlobalTypeID.cs b/Core/GlobalTypeID.cs index 8e4cc18..426a286 100644 --- a/Core/GlobalTypeID.cs +++ b/Core/GlobalTypeID.cs @@ -23,7 +23,7 @@ namespace Svelto.ECS { static Filler() { - DBC.ECS.Check.Require(TypeCache.IsUnmanaged == true, "invalid type used"); + DBC.ECS.Check.Require(TypeCache.isUnmanaged == true, "invalid type used"); } //it's an internal interface @@ -52,7 +52,12 @@ namespace Svelto.ECS static class EntityComponentIDMap { - static readonly FasterList TYPE_IDS = new FasterList(); + static readonly FasterList TYPE_IDS; + + static EntityComponentIDMap() + { + TYPE_IDS = new FasterList(); + } internal static void Register(IFiller entityBuilder) where T : struct, IEntityComponent { diff --git a/Core/GroupHashMap.cs b/Core/GroupHashMap.cs new file mode 100644 index 0000000..8e761d9 --- /dev/null +++ b/Core/GroupHashMap.cs @@ -0,0 +1,115 @@ +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Text; +using Svelto.ECS.Serialization; + +namespace Svelto.ECS +{ + static class GroupHashMap + { + /// + /// c# Static constructors are guaranteed to be thread safe + /// + + public static void Init() + { + List assemblies = AssemblyUtility.GetCompatibleAssemblies(); + foreach (Assembly assembly in assemblies) + { + try + { + var typeOfExclusiveGroup = typeof(ExclusiveGroup); + var typeOfExclusiveGroupStruct = typeof(ExclusiveGroupStruct); + + foreach (Type type in AssemblyUtility.GetTypesSafe(assembly)) + { + if (type != null && type.IsClass && type.IsSealed && type.IsAbstract) //this means only static classes + { + var fields = type.GetFields(); + + foreach (var field in fields) + { + if (field.IsStatic) + { + if (typeOfExclusiveGroup.IsAssignableFrom(field.FieldType)) + { + var group = (ExclusiveGroup)field.GetValue(null); + var name = $"{type.FullName}.{field.Name}"; +#if DEBUG + GroupNamesMap.idToName[(uint)@group] = $"{name} {(uint)group})"; +#endif + RegisterGroup(group, name); + } + else + { + if (typeOfExclusiveGroupStruct.IsAssignableFrom(field.FieldType)) + { + var group = (ExclusiveGroupStruct)field.GetValue(null); + var name = $"{type.FullName}.{field.Name}"; +#if DEBUG + GroupNamesMap.idToName[(uint)@group] = $"{name} {(uint)group})"; +#endif + RegisterGroup(@group, name); + } + } + } + } + } + } + } + catch + { + Console.LogDebugWarning( + "something went wrong while gathering group names on the assembly: ".FastConcat(assembly.FullName)); + } + } + } + + public static void RegisterGroup(ExclusiveGroupStruct exclusiveGroupStruct, string name) + { + //Group already registered by another field referencing the same group + if (_hashByGroups.ContainsKey(exclusiveGroupStruct)) + return; + + var nameHash = DesignatedHash.Hash(Encoding.ASCII.GetBytes(name)); + + if(_groupsByHash.ContainsKey(nameHash)) + throw new ECSException($"Group hash collision with {name} and {_groupsByHash[nameHash]}"); + + Console.LogDebug($"Reigstering group {name} with ID {(uint)exclusiveGroupStruct} to {nameHash}"); + + _groupsByHash.Add(nameHash, exclusiveGroupStruct); + _hashByGroups.Add(exclusiveGroupStruct, nameHash); + } + + public static uint GetHashFromGroup(ExclusiveGroupStruct groupStruct) + { +#if DEBUG + if (_hashByGroups.ContainsKey(groupStruct) == false) + throw new ECSException($"Attempted to get hash from unregistered group {groupStruct}"); +#endif + + return _hashByGroups[groupStruct]; + } + + public static ExclusiveGroupStruct GetGroupFromHash(uint groupHash) + { +#if DEBUG + if (_groupsByHash.ContainsKey(groupHash) == false) + throw new ECSException($"Attempted to get group from unregistered hash {groupHash}"); +#endif + + return _groupsByHash[groupHash]; + } + + static readonly Dictionary _groupsByHash; + static readonly Dictionary _hashByGroups; + + static GroupHashMap() + { + _groupsByHash = new Dictionary(); + _hashByGroups = new Dictionary(); + } + } +} \ No newline at end of file diff --git a/Core/GroupHashMap.cs.meta b/Core/GroupHashMap.cs.meta new file mode 100644 index 0000000..8dabe15 --- /dev/null +++ b/Core/GroupHashMap.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: dd3f86070a8934a18ace9dfe566d9784 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Core/GroupNamesMap.cs b/Core/GroupNamesMap.cs new file mode 100644 index 0000000..2731ce6 --- /dev/null +++ b/Core/GroupNamesMap.cs @@ -0,0 +1,26 @@ +using System.Collections.Generic; +using Svelto.ECS; + +static class GroupNamesMap +{ +#if DEBUG + static GroupNamesMap() { idToName = new Dictionary(); } + + internal static readonly Dictionary idToName; +#endif +#if DEBUG + public static string ToName(this in ExclusiveGroupStruct group) + { + var idToName = GroupNamesMap.idToName; + if (idToName.TryGetValue((uint)@group, out var name) == false) + name = $""; + + return name; + } +#else + public static string ToName(this in ExclusiveGroupStruct group) + { + return ((uint)group).ToString(); + } +#endif +} \ No newline at end of file diff --git a/Core/GroupNamesMap.cs.meta b/Core/GroupNamesMap.cs.meta new file mode 100644 index 0000000..20602e9 --- /dev/null +++ b/Core/GroupNamesMap.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 1b34885b789f32b99d3dc9131491f56f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Core/Groups/ExclusiveBuildGroup.cs b/Core/Groups/ExclusiveBuildGroup.cs index 87aca8b..465c841 100644 --- a/Core/Groups/ExclusiveBuildGroup.cs +++ b/Core/Groups/ExclusiveBuildGroup.cs @@ -17,9 +17,19 @@ namespace Svelto.ECS return new ExclusiveBuildGroup(group); } - public static implicit operator uint(ExclusiveBuildGroup groupStruct) + public static implicit operator ExclusiveGroupStruct(ExclusiveBuildGroup group) { - return groupStruct.group; + return new ExclusiveGroupStruct(group.group); + } + + public static explicit operator uint(ExclusiveBuildGroup groupStruct) + { + return (uint) groupStruct.@group; + } + + public override string ToString() + { + return this.group.ToName(); } internal ExclusiveGroupStruct @group { get; } diff --git a/Core/Groups/ExclusiveGroup.cs b/Core/Groups/ExclusiveGroup.cs index 6f691fa..b931d0e 100644 --- a/Core/Groups/ExclusiveGroup.cs +++ b/Core/Groups/ExclusiveGroup.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Runtime.CompilerServices; #pragma warning disable 660,661 @@ -17,20 +18,20 @@ namespace Svelto.ECS /// public static ExclusiveGroup[] GroupOfGroups = { MyExclusiveGroup1, ...}; //for each on this! /// } /// - + ///To debug it use in your debug window: Svelto.ECS.Debugger.EGID.GetGroupNameFromId(groupID) - public class ExclusiveGroup + public sealed class ExclusiveGroup { - public const uint MaxNumberOfExclusiveGroups = 2 << 20; - - public ExclusiveGroup() + public const uint MaxNumberOfExclusiveGroups = 2 << 20; + + public ExclusiveGroup(ExclusiveGroupBitmask bitmask = 0) { - _group = ExclusiveGroupStruct.Generate(); + _group = ExclusiveGroupStruct.Generate((byte)bitmask); } - public ExclusiveGroup(string recognizeAs) + public ExclusiveGroup(string recognizeAs, ExclusiveGroupBitmask bitmask = 0) { - _group = ExclusiveGroupStruct.Generate(); + _group = ExclusiveGroupStruct.Generate((byte)bitmask); _knownGroups.Add(recognizeAs, _group); } @@ -43,14 +44,26 @@ namespace Svelto.ECS #endif } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Disable() + { + _group.Disable(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Enable() + { + _group.Enable(); + } + public static implicit operator ExclusiveGroupStruct(ExclusiveGroup group) { return group._group; } - + public static explicit operator uint(ExclusiveGroup group) { - return group._group; + return (uint) @group._group; } public static ExclusiveGroupStruct operator+(ExclusiveGroup a, uint b) @@ -63,7 +76,7 @@ namespace Svelto.ECS #endif return a._group + b; } - + //todo document the use case for this method public static ExclusiveGroupStruct Search(string holderGroupName) { @@ -84,84 +97,6 @@ namespace Svelto.ECS #if DEBUG readonly ushort _range; #endif - readonly ExclusiveGroupStruct _group; + ExclusiveGroupStruct _group; } } - -#if future - public static void ConstructStaticGroups() - { - Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); - - // Assemblies or types aren't guaranteed to be returned in the same order, - // and I couldn't find proof that `GetTypes()` returns them in fixed order either, - // even for builds made with the exact same source code. - // So will sort reflection results by name before constructing groups. - var groupFields = new List>(); - - foreach (Assembly assembly in assemblies) - { - Type[] types = GetTypesSafe(assembly); - - foreach (Type type in types) - { - if (type == null || !type.IsClass) - { - continue; - } - - // Groups defined as static members in static classes - if (type.IsSealed && type.IsAbstract) - { - FieldInfo[] fields = type.GetFields(); - foreach(var field in fields) - { - if (field.IsStatic && typeof(ExclusiveGroup).IsAssignableFrom(field.FieldType)) - { - groupFields.Add(new KeyValuePair($"{type.FullName}.{field.Name}", field)); - } - } - } - // Groups defined as classes - else if (type.BaseType != null - && type.BaseType.IsGenericType - && type.BaseType.GetGenericTypeDefinition() == typeof(ExclusiveGroup<>)) - { - FieldInfo field = type.GetField("Group", - BindingFlags.Static | BindingFlags.Public | BindingFlags.FlattenHierarchy); - - groupFields.Add(new KeyValuePair(type.FullName, field)); - } - } - } - - groupFields.Sort((a, b) => string.CompareOrdinal(a.Key, b.Key)); - - for (int i = 0; i < groupFields.Count; ++i) - { - groupFields[i].Value.GetValue(null); -#if DEBUG - var group = (ExclusiveGroup) groupFields[i].Value.GetValue(null); - groupNames[(uint) group] = groupFields[i].Key; -#endif - } - } - - static Type[] GetTypesSafe(Assembly assembly) - { - try - { - Type[] types = assembly.GetTypes(); - - return types; - } - catch (ReflectionTypeLoadException e) - { - return e.Types; - } - } - -#if DEBUG - static string[] groupNames = new string[ExclusiveGroup.MaxNumberOfExclusiveGroups]; -#endif -#endif \ No newline at end of file diff --git a/Core/Groups/ExclusiveGroupBitmask.cs b/Core/Groups/ExclusiveGroupBitmask.cs new file mode 100644 index 0000000..002a1f6 --- /dev/null +++ b/Core/Groups/ExclusiveGroupBitmask.cs @@ -0,0 +1,15 @@ +namespace Svelto.ECS +{ + public enum ExclusiveGroupBitmask : byte + { + NONE = 0, + DISABLED_BIT = 0b00000001, + UNUSED_BIT_2 = 0b00000010, + UNUSED_BIT_3 = 0b00000100, + UNUSED_BIT_4 = 0b00001000, + UNUSED_BIT_5 = 0b00010000, + UNUSED_BIT_6 = 0b00100000, + UNUSED_BIT_7 = 0b01000000, + UNUSED_BIT_8 = 0b10000000, + } +} \ No newline at end of file diff --git a/Core/Groups/ExclusiveGroupBitmask.cs.meta b/Core/Groups/ExclusiveGroupBitmask.cs.meta new file mode 100644 index 0000000..e31b7e1 --- /dev/null +++ b/Core/Groups/ExclusiveGroupBitmask.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ee49249ab9483bfa892f14f74c32a828 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Core/Groups/ExclusiveGroupStruct.cs b/Core/Groups/ExclusiveGroupStruct.cs index 30d25e5..bd24981 100644 --- a/Core/Groups/ExclusiveGroupStruct.cs +++ b/Core/Groups/ExclusiveGroupStruct.cs @@ -1,51 +1,80 @@ using System; -using System.Collections.Generic; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using Svelto.DataStructures; namespace Svelto.ECS { [StructLayout(LayoutKind.Explicit, Size = 4)] - public struct ExclusiveGroupStruct : IEquatable, IComparable, - IEqualityComparer + //the type doesn't implement IEqualityComparer, what implements it is a custom comparer + public struct ExclusiveGroupStruct : IEquatable, IComparable { + public static readonly ExclusiveGroupStruct Invalid = default; //must stay here because of Burst + + public ExclusiveGroupStruct(byte[] data, uint pos):this() + { + _id = (uint)( + data[pos] + | data[++pos] << 8 + | data[++pos] << 16 + ); + _bytemask = (byte) (data[++pos] << 24); + + DBC.ECS.Check.Ensure(_id < _globalId, "Invalid group ID deserialiased"); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] public override bool Equals(object obj) { return obj is ExclusiveGroupStruct other && Equals(other); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public override int GetHashCode() { return (int) _id; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator ==(ExclusiveGroupStruct c1, ExclusiveGroupStruct c2) { return c1.Equals(c2); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator !=(ExclusiveGroupStruct c1, ExclusiveGroupStruct c2) { return c1.Equals(c2) == false; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool Equals(ExclusiveGroupStruct other) { return other._id == _id; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public int CompareTo(ExclusiveGroupStruct other) { return other._id.CompareTo(_id); } - public bool Equals(ExclusiveGroupStruct x, ExclusiveGroupStruct y) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly bool IsEnabled() { - return x._id == y._id; + return (_bytemask & (byte)ExclusiveGroupBitmask.DISABLED_BIT) == 0; } - public int GetHashCode(ExclusiveGroupStruct obj) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal void Disable() { - return _id.GetHashCode(); + _bytemask |= (byte)ExclusiveGroupBitmask.DISABLED_BIT; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal void Enable() + { + _bytemask &= (byte)(~ExclusiveGroupBitmask.DISABLED_BIT); } public override string ToString() @@ -53,6 +82,20 @@ namespace Svelto.ECS return this.ToName(); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static explicit operator uint(ExclusiveGroupStruct groupStruct) + { + return groupStruct._id; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ExclusiveGroupStruct operator+(ExclusiveGroupStruct a, uint b) + { + var group = new ExclusiveGroupStruct {_id = a._id + b}; + + return @group; + } + internal static ExclusiveGroupStruct Generate(byte bitmask = 0) { ExclusiveGroupStruct groupStruct; @@ -82,33 +125,9 @@ namespace Svelto.ECS _id = groupID; } - public ExclusiveGroupStruct(byte[] data, uint pos):this() - { - _id = (uint)( - data[pos] - | data[++pos] << 8 - | data[++pos] << 16 - ); - _bytemask = (byte) (data[++pos] << 24); - - DBC.ECS.Check.Ensure(_id < _globalId, "Invalid group ID deserialiased"); - } - - public static implicit operator uint(ExclusiveGroupStruct groupStruct) - { - return groupStruct._id; - } - - public static ExclusiveGroupStruct operator+(ExclusiveGroupStruct a, uint b) - { - var group = new ExclusiveGroupStruct {_id = a._id + b}; - - return @group; - } - [FieldOffset(0)] uint _id; [FieldOffset(3)] byte _bytemask; - static uint _globalId = 1; //it starts from 1 because default EGID is considered not initalized value + static uint _globalId = 1; //it starts from 1 because default EGID is considered not initialized value } } \ No newline at end of file diff --git a/Core/Groups/GroupCompound.cs b/Core/Groups/GroupCompound.cs index 25be5bd..8ae5530 100644 --- a/Core/Groups/GroupCompound.cs +++ b/Core/Groups/GroupCompound.cs @@ -1,225 +1,294 @@ using System; +using System.Collections.Generic; using System.Threading; using Svelto.DataStructures; namespace Svelto.ECS { + public abstract class GroupCompound where G1 : GroupTag + where G2 : GroupTag + where G3 : GroupTag + where G4 : GroupTag + { + static readonly FasterList _Groups; + static readonly HashSet _GroupsHashSet; + + public static FasterReadOnlyList Groups => + new FasterReadOnlyList(_Groups); + + public static ExclusiveBuildGroup BuildGroup => new ExclusiveBuildGroup(_Groups[0]); + + static int isInitializing; + + static GroupCompound() + { + if (Interlocked.CompareExchange(ref isInitializing, 1, 0) == 0) + { + _Groups = new FasterList(1); + + var Group = new ExclusiveGroup(); + _Groups.Add(Group); + _GroupsHashSet = new HashSet(_Groups.ToArrayFast(out _)); + + GroupCompound.Add(Group); + GroupCompound.Add(Group); + GroupCompound.Add(Group); + GroupCompound.Add(Group); + + GroupCompound.Add(Group); // and must share the same array + GroupCompound.Add(Group); + GroupCompound.Add(Group); + GroupCompound.Add(Group); + GroupCompound.Add(Group); + GroupCompound.Add(Group); + + //This is done here to be sure that the group is added once per group tag + //(if done inside the previous group compound it would be added multiple times) + GroupTag.Add(Group); + GroupTag.Add(Group); + GroupTag.Add(Group); + GroupTag.Add(Group); + +#if DEBUG + GroupNamesMap.idToName[(uint) Group] = + $"Compound: {typeof(G1).Name}-{typeof(G2).Name}-{typeof(G3).Name}-{typeof(G4).Name} ID {(uint) Group}"; +#endif + GroupHashMap.RegisterGroup(BuildGroup, + $"Compound: {typeof(G1).Name}-{typeof(G2).Name}-{typeof(G3).Name}-{typeof(G4).Name}"); + + //all the combinations must share the same group and group hashset + GroupCompound._Groups = _Groups; + GroupCompound._Groups = _Groups; + GroupCompound._Groups = _Groups; + GroupCompound._Groups = _Groups; + GroupCompound._Groups = _Groups; + GroupCompound._Groups = _Groups; + GroupCompound._Groups = _Groups; + GroupCompound._Groups = _Groups; + GroupCompound._Groups = _Groups; + GroupCompound._Groups = _Groups; + GroupCompound._Groups = _Groups; + GroupCompound._Groups = _Groups; + GroupCompound._Groups = _Groups; + GroupCompound._Groups = _Groups; + GroupCompound._Groups = _Groups; + GroupCompound._Groups = _Groups; + GroupCompound._Groups = _Groups; + GroupCompound._Groups = _Groups; + GroupCompound._Groups = _Groups; + GroupCompound._Groups = _Groups; + GroupCompound._Groups = _Groups; + GroupCompound._Groups = _Groups; + GroupCompound._Groups = _Groups; + + GroupCompound._GroupsHashSet = _GroupsHashSet; + GroupCompound._GroupsHashSet = _GroupsHashSet; + GroupCompound._GroupsHashSet = _GroupsHashSet; + GroupCompound._GroupsHashSet = _GroupsHashSet; + GroupCompound._GroupsHashSet = _GroupsHashSet; + GroupCompound._GroupsHashSet = _GroupsHashSet; + GroupCompound._GroupsHashSet = _GroupsHashSet; + GroupCompound._GroupsHashSet = _GroupsHashSet; + GroupCompound._GroupsHashSet = _GroupsHashSet; + GroupCompound._GroupsHashSet = _GroupsHashSet; + GroupCompound._GroupsHashSet = _GroupsHashSet; + GroupCompound._GroupsHashSet = _GroupsHashSet; + GroupCompound._GroupsHashSet = _GroupsHashSet; + GroupCompound._GroupsHashSet = _GroupsHashSet; + GroupCompound._GroupsHashSet = _GroupsHashSet; + GroupCompound._GroupsHashSet = _GroupsHashSet; + GroupCompound._GroupsHashSet = _GroupsHashSet; + GroupCompound._GroupsHashSet = _GroupsHashSet; + GroupCompound._GroupsHashSet = _GroupsHashSet; + GroupCompound._GroupsHashSet = _GroupsHashSet; + GroupCompound._GroupsHashSet = _GroupsHashSet; + GroupCompound._GroupsHashSet = _GroupsHashSet; + GroupCompound._GroupsHashSet = _GroupsHashSet; + } + } + + internal static void Add(ExclusiveGroupStruct @group) + { + for (int i = 0; i < _Groups.count; ++i) + if (_Groups[i] == group) + throw new Exception("temporary must be transformed in unit test"); + + _Groups.Add(group); + _GroupsHashSet.Add(group); + } + + public static bool Includes(ExclusiveGroupStruct @group) { return _GroupsHashSet.Contains(@group); } + } + + public abstract class GroupCompound + where G1 : GroupTag where G2 : GroupTag where G3 : GroupTag + { + static readonly FasterList _Groups; + static readonly HashSet _GroupsHashSet; + + public static FasterReadOnlyList Groups => + new FasterReadOnlyList(_Groups); + + public static ExclusiveBuildGroup BuildGroup => new ExclusiveBuildGroup(_Groups[0]); + + static int isInitializing; + + internal static void Add(ExclusiveGroupStruct group) + { + for (var i = 0; i < _Groups.count; ++i) + if (_Groups[i] == group) + throw new Exception("temporary must be transformed in unit test"); + + _Groups.Add(group); + _GroupsHashSet.Add(group); + } + + public static bool Includes(ExclusiveGroupStruct @group) { return _GroupsHashSet.Contains(@group); } + + static GroupCompound() + { + if (Interlocked.CompareExchange(ref isInitializing, 1, 0) == 0) + { + _Groups = new FasterList(1); + + var Group = new ExclusiveGroup(); + _Groups.Add(Group); + _GroupsHashSet = new HashSet(_Groups.ToArrayFast(out _)); + + GroupCompound.Add(Group); // and must share the same array + GroupCompound.Add(Group); + GroupCompound.Add(Group); + + //This is done here to be sure that the group is added once per group tag + //(if done inside the previous group compound it would be added multiple times) + GroupTag.Add(Group); + GroupTag.Add(Group); + GroupTag.Add(Group); + +#if DEBUG + GroupNamesMap.idToName[(uint) Group] = + $"Compound: {typeof(G1).Name}-{typeof(G2).Name}-{typeof(G3).Name} ID {(uint) Group}"; +#endif + GroupHashMap.RegisterGroup(BuildGroup, + $"Compound: {typeof(G1).Name}-{typeof(G2).Name}-{typeof(G3).Name}"); + + //all the combinations must share the same group and group hashset + GroupCompound._Groups = _Groups; + GroupCompound._Groups = _Groups; + GroupCompound._Groups = _Groups; + GroupCompound._Groups = _Groups; + GroupCompound._Groups = _Groups; + + GroupCompound._GroupsHashSet = _GroupsHashSet; + GroupCompound._GroupsHashSet = _GroupsHashSet; + GroupCompound._GroupsHashSet = _GroupsHashSet; + GroupCompound._GroupsHashSet = _GroupsHashSet; + GroupCompound._GroupsHashSet = _GroupsHashSet; + } + } + } + + public abstract class GroupCompound where G1 : GroupTag where G2 : GroupTag + { + static readonly FasterList _Groups; + static readonly HashSet _GroupsHashSet; + + public static FasterReadOnlyList Groups => + new FasterReadOnlyList(_Groups); + + public static ExclusiveBuildGroup BuildGroup => new ExclusiveBuildGroup(_Groups[0]); + + static int isInitializing; + + internal static void Add(ExclusiveGroupStruct group) + { + for (var i = 0; i < _Groups.count; ++i) + if (_Groups[i] == group) + throw new Exception("temporary must be transformed in unit test"); + + _Groups.Add(group); + _GroupsHashSet.Add(group); + } + + public static bool Includes(ExclusiveGroupStruct @group) { return _GroupsHashSet.Contains(@group); } + + static GroupCompound() + { + if (Interlocked.CompareExchange(ref isInitializing, 1, 0) == 0) + { + var Group = new ExclusiveGroup(); + + _Groups = new FasterList(1); + _Groups.Add(Group); + _GroupsHashSet = new HashSet(_Groups.ToArrayFast(out _)); + + //every abstract group preemptively adds this group, it may or may not be empty in future + GroupTag.Add(Group); + GroupTag.Add(Group); + +#if DEBUG + GroupNamesMap.idToName[(uint) Group] = $"Compound: {typeof(G1).Name}-{typeof(G2).Name} ID {(uint) Group}"; +#endif + GroupHashMap.RegisterGroup(BuildGroup, + $"Compound: {typeof(G1).Name}-{typeof(G2).Name}"); + + GroupCompound._Groups = _Groups; + GroupCompound._GroupsHashSet = _GroupsHashSet; + } + } + } + /// - /// Very naive fail safe, but at least it's simple to understand and safe + ///A Group Tag holds initially just a group, itself. However the number of groups can grow with the number of + ///combinations of GroupTags including this one. This because a GroupTag is an adjective and different entities + ///can use the same adjective together with other ones. However since I need to be able to iterate over all the + ///groups with the same adjective, a group tag needs to hold all the groups sharing it. /// - static class GroupCompoundInitializer + /// + public abstract class GroupTag where T : GroupTag { - internal static readonly ThreadLocal isInitializing4 = new ThreadLocal(); - internal static readonly ThreadLocal isInitializing3 = new ThreadLocal(); - internal static readonly ThreadLocal isInitializing2 = new ThreadLocal(); - } - - public abstract class GroupCompound - where G1 : GroupTag where G2 : GroupTag where G3 : GroupTag where G4 : GroupTag - { - static readonly FasterList _Groups; - - public static FasterReadOnlyList Groups => new FasterReadOnlyList(_Groups); - public static ExclusiveBuildGroup BuildGroup => new ExclusiveBuildGroup(_Groups[0]); - - static GroupCompound() - { - if (GroupCompoundInitializer.isInitializing4.Value == false) - { - _Groups = new FasterList(1); - - var Group = new ExclusiveGroup(); - _Groups.Add(Group); - - GroupCompound.Add(Group); - GroupCompound.Add(Group); - GroupCompound.Add(Group); - GroupCompound.Add(Group); - - GroupCompound.Add(Group); // and must share the same array - GroupCompound.Add(Group); - GroupCompound.Add(Group); - GroupCompound.Add(Group); - GroupCompound.Add(Group); - GroupCompound.Add(Group); - - //This is done here to be sure that the group is added once per group tag - //(if done inside the previous group compound it would be added multiple times) - GroupTag.Add(Group); - GroupTag.Add(Group); - GroupTag.Add(Group); - GroupTag.Add(Group); - - #if DEBUG - GroupMap.idToName[(uint) Group] = $"Compound: {typeof(G1).Name}-{typeof(G2).Name}-{typeof(G3).Name}-{typeof(G4).Name} ID {(uint)Group}"; - #endif - GroupCompoundInitializer.isInitializing4.Value = true; - //all the combinations must share the same group - GroupCompound._Groups = _Groups; - GroupCompound._Groups = _Groups; - GroupCompound._Groups = _Groups; - GroupCompound._Groups = _Groups; - GroupCompound._Groups = _Groups; - GroupCompound._Groups = _Groups; - GroupCompound._Groups = _Groups; - GroupCompound._Groups = _Groups; - GroupCompound._Groups = _Groups; - GroupCompound._Groups = _Groups; - GroupCompound._Groups = _Groups; - GroupCompound._Groups = _Groups; - GroupCompound._Groups = _Groups; - GroupCompound._Groups = _Groups; - GroupCompound._Groups = _Groups; - GroupCompound._Groups = _Groups; - GroupCompound._Groups = _Groups; - GroupCompound._Groups = _Groups; - GroupCompound._Groups = _Groups; - GroupCompound._Groups = _Groups; - GroupCompound._Groups = _Groups; - GroupCompound._Groups = _Groups; - GroupCompound._Groups = _Groups; - GroupCompoundInitializer.isInitializing4.Value = false; - } - } - - public static void Add(ExclusiveGroupStruct @group) - { - for (int i = 0; i < _Groups.count; ++i) - if (_Groups[i] == group) - throw new Exception("temporary must be transformed in unit test"); - - _Groups.Add(group); - } - } - - public abstract class GroupCompound - where G1 : GroupTag where G2 : GroupTag where G3 : GroupTag - { - static readonly FasterList _Groups; - - public static FasterReadOnlyList Groups => - new FasterReadOnlyList(_Groups); - - public static ExclusiveBuildGroup BuildGroup => new ExclusiveBuildGroup(_Groups[0]); - - public static void Add(ExclusiveGroupStruct group) - { - for (var i = 0; i < _Groups.count; ++i) - if (_Groups[i] == group) - throw new Exception("temporary must be transformed in unit test"); - - _Groups.Add(group); - } - - static GroupCompound() - { - if (GroupCompoundInitializer.isInitializing3.Value == false) - { - _Groups = new FasterList(1); - - var Group = new ExclusiveGroup(); - _Groups.Add(Group); - - GroupCompound.Add(Group); // and must share the same array - GroupCompound.Add(Group); - GroupCompound.Add(Group); - - //This is done here to be sure that the group is added once per group tag - //(if done inside the previous group compound it would be added multiple times) - GroupTag.Add(Group); - GroupTag.Add(Group); - GroupTag.Add(Group); - - #if DEBUG - GroupMap.idToName[(uint) Group] = $"Compound: {typeof(G1).Name}-{typeof(G2).Name}-{typeof(G3).Name} ID {(uint)Group}"; - #endif - //all the combinations must share the same group - GroupCompoundInitializer.isInitializing3.Value = true; - GroupCompound._Groups = _Groups; - GroupCompound._Groups = _Groups; - GroupCompound._Groups = _Groups; - GroupCompound._Groups = _Groups; - GroupCompound._Groups = _Groups; - GroupCompoundInitializer.isInitializing3.Value = false; - } - } - } - - public abstract class GroupCompound where G1 : GroupTag where G2 : GroupTag - { - static readonly FasterList _Groups; - - public static FasterReadOnlyList Groups => - new FasterReadOnlyList(_Groups); - - public static ExclusiveBuildGroup BuildGroup => new ExclusiveBuildGroup(_Groups[0]); - - public static void Add(ExclusiveGroupStruct group) - { - for (var i = 0; i < _Groups.count; ++i) - if (_Groups[i] == group) - throw new Exception("temporary must be transformed in unit test"); - - _Groups.Add(group); - } - - static GroupCompound() - { - if (GroupCompoundInitializer.isInitializing2.Value == false) - { - var Group = new ExclusiveGroup(); - - _Groups = new FasterList(1); - _Groups.Add(Group); - - //every abstract group preemptively adds this group, it may or may not be empty in future - GroupTag.Add(Group); - GroupTag.Add(Group); - - #if DEBUG - GroupMap.idToName[(uint) Group] = $"Compound: {typeof(G1).Name}-{typeof(G2).Name} ID {(uint)Group}"; - #endif - GroupCompoundInitializer.isInitializing2.Value = true; - GroupCompound._Groups = _Groups; - GroupCompoundInitializer.isInitializing2.Value = false; - } - } - } - - /// - ///A Group Tag holds initially just a group, itself. However the number of groups can grow with the number of - ///combinations of GroupTags including this one. This because a GroupTag is an adjective and different entities - ///can use the same adjective together with other ones. However since I need to be able to iterate over all the - ///groups with the same adjective, a group tag needs to hold all the groups sharing it. - /// - /// - public abstract class GroupTag where T : GroupTag - { - static readonly FasterList _Groups = new FasterList(1); - - public static FasterReadOnlyList Groups => - new FasterReadOnlyList(_Groups); - - public static ExclusiveBuildGroup BuildGroup => new ExclusiveBuildGroup(_Groups[0]); - - static GroupTag() - { - var group = new ExclusiveGroup(); - _Groups.Add(group); - - #if DEBUG - GroupMap.idToName[(uint) group] = $"Compound: {typeof(T).Name} ID {(uint)group}"; - #endif - } - - //Each time a new combination of group tags is found a new group is added. - internal static void Add(ExclusiveGroupStruct group) - { - for (var i = 0; i < _Groups.count; ++i) - if (_Groups[i] == group) - throw new Exception("temporary must be transformed in unit test"); - - _Groups.Add(group); - } - } + static readonly FasterList _Groups = new FasterList(1); + static readonly HashSet _GroupsHashSet; + + public static FasterReadOnlyList Groups => + new FasterReadOnlyList(_Groups); + + public static ExclusiveBuildGroup BuildGroup => new ExclusiveBuildGroup(_Groups[0]); + + static int isInitializing; + + static GroupTag() + { + if (Interlocked.CompareExchange(ref isInitializing, 1, 0) == 0) + { + var group = new ExclusiveGroup(); + _Groups.Add(group); + _GroupsHashSet = new HashSet(_Groups.ToArrayFast(out _)); + +#if DEBUG + var typeInfo = typeof(T); + var typeInfoBaseType = typeInfo.BaseType; + if (typeInfoBaseType.GenericTypeArguments[0] != typeInfo) + throw new ECSException("Invalid Group Tag declared"); + + GroupNamesMap.idToName[(uint)group] = $"Compound: {typeInfo.Name} ID {(uint)group}"; +#endif + GroupHashMap.RegisterGroup(BuildGroup, + $"Compound: {typeof(T).FullName}"); + } + } + + //Each time a new combination of group tags is found a new group is added. + internal static void Add(ExclusiveGroupStruct group) + { + for (var i = 0; i < _Groups.count; ++i) + if (_Groups[i] == group) + throw new Exception("temporary must be transformed in unit test"); + + _Groups.Add(group); + _GroupsHashSet.Add(group); + } + + public static bool Includes(ExclusiveGroupStruct @group) { return _GroupsHashSet.Contains(@group); } + } } \ No newline at end of file diff --git a/Core/Groups/NamedExclusiveGroup.cs b/Core/Groups/NamedExclusiveGroup.cs index a35d06b..1f60756 100644 --- a/Core/Groups/NamedExclusiveGroup.cs +++ b/Core/Groups/NamedExclusiveGroup.cs @@ -14,8 +14,9 @@ namespace Svelto.ECS static NamedExclusiveGroup() { #if DEBUG - GroupMap.idToName[(uint) Group] = $"{name} ID {(uint)Group}"; + GroupNamesMap.idToName[(uint) Group] = $"{name} ID {(uint)Group}"; #endif + GroupHashMap.RegisterGroup(Group, $"{name}"); } // protected NamedExclusiveGroup(string recognizeAs) : base(recognizeAs) {} // protected NamedExclusiveGroup(ushort range) : base(range) {} diff --git a/Core/Hybrid/IEntityDescriptorHolder.cs b/Core/Hybrid/IEntityDescriptorHolder.cs index bde2442..b1ec0e9 100644 --- a/Core/Hybrid/IEntityDescriptorHolder.cs +++ b/Core/Hybrid/IEntityDescriptorHolder.cs @@ -1,4 +1,4 @@ -namespace Svelto.ECS +namespace Svelto.ECS.Hybrid { public interface IEntityDescriptorHolder { diff --git a/Core/Hybrid/ValueReference.cs b/Core/Hybrid/ValueReference.cs index 0a4c802..ed5a9ac 100644 --- a/Core/Hybrid/ValueReference.cs +++ b/Core/Hybrid/ValueReference.cs @@ -1,13 +1,37 @@ +using System; using System.Runtime.InteropServices; namespace Svelto.ECS.Hybrid { - public struct ValueReference where T:class, IImplementor + /// + /// 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/ + /// + /// + public struct ValueReference : IDisposable where T:class, IImplementor { + 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 static explicit operator T(ValueReference t) => (T) t._pointer.Target; + public W Convert(W implementer) where W:T, new() + { + var pointerTarget = _pointer.Target; + return (W)pointerTarget; + } - GCHandle _pointer; + public void Dispose() + { + _pointer.Free(); + } + + GCHandle _pointer; } } \ No newline at end of file diff --git a/Core/IComponentBuilder.cs b/Core/IComponentBuilder.cs index cc242e4..a1fb0db 100644 --- a/Core/IComponentBuilder.cs +++ b/Core/IComponentBuilder.cs @@ -6,11 +6,11 @@ namespace Svelto.ECS { public interface IComponentBuilder { - void BuildEntityAndAddToList(ref ITypeSafeDictionary dictionary, EGID egid, - IEnumerable implementors); - ITypeSafeDictionary Preallocate(ref ITypeSafeDictionary dictionary, uint size); + void BuildEntityAndAddToList(ITypeSafeDictionary dictionary, EGID egid, IEnumerable implementors); + void Preallocate(ITypeSafeDictionary dictionary, uint size); + ITypeSafeDictionary CreateDictionary(uint size); - Type GetEntityComponentType(); - bool isUnmanaged { get; } + Type GetEntityComponentType(); + bool isUnmanaged { get; } } } \ No newline at end of file diff --git a/Core/IEntityFactory.cs b/Core/IEntityFactory.cs index 0a7c180..d5ba023 100644 --- a/Core/IEntityFactory.cs +++ b/Core/IEntityFactory.cs @@ -25,19 +25,14 @@ namespace Svelto.ECS /// /// /// - /// - void PreallocateEntitySpace(ExclusiveGroupStruct groupStructId, uint size) + /// + void PreallocateEntitySpace(ExclusiveGroupStruct groupStructId, uint numberOfEntities) where T : IEntityDescriptor, new(); /// /// The EntityDescriptor doesn't need to be ever instantiated. It just describes the Entity /// itself in terms of EntityComponents to build. The Implementors are passed to fill the - /// references of the EntityComponents components. Please read the articles on my blog - /// to understand better the terminologies - /// Using this function is like building a normal entity, but the entity components - /// are grouped by groupID to be more efficiently processed inside engines and - /// improve cache locality. Either class entityComponents and struct entityComponents can be - /// grouped. + /// references of the Entity View Components components if present. /// /// /// @@ -51,17 +46,18 @@ namespace Svelto.ECS where T : IEntityDescriptor, new(); EntityInitializer BuildEntity(uint entityID, ExclusiveBuildGroup groupStructId, - T descriptorEntity, IEnumerable implementors = null) + T descriptorEntity, IEnumerable implementors = null) where T : IEntityDescriptor; EntityInitializer BuildEntity(EGID egid, T entityDescriptor, IEnumerable implementors = null) where T : IEntityDescriptor; + //Todo: analyze if this can be internal or just related to serialization EntityInitializer BuildEntity - (EGID egid, IComponentBuilder[] componentsToBuild, Type type, IEnumerable implementors = null); + (EGID egid, IComponentBuilder[] componentsToBuild, Type descriptorType, IEnumerable implementors = null); #if UNITY_NATIVE - NativeEntityFactory ToNative(string callerName) where T : IEntityDescriptor, new(); + Svelto.ECS.Native.NativeEntityFactory ToNative(string callerName) where T : IEntityDescriptor, new(); #endif } } \ No newline at end of file diff --git a/Core/IEntityFunctions.cs b/Core/IEntityFunctions.cs index 1dd15bc..b3f6312 100644 --- a/Core/IEntityFunctions.cs +++ b/Core/IEntityFunctions.cs @@ -1,5 +1,3 @@ -using System.Runtime.CompilerServices; - namespace Svelto.ECS { public interface IEntityFunctions @@ -7,8 +5,8 @@ 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, [CallerMemberName] string memberName = "") where T : IEntityDescriptor, new(); - void RemoveEntity(EGID entityegid, [CallerMemberName] string memberName = "") where T : IEntityDescriptor, new(); + void RemoveEntity(uint entityID, ExclusiveBuildGroup groupID) where T : IEntityDescriptor, new(); + void RemoveEntity(EGID entityegid) where T : IEntityDescriptor, new(); void RemoveEntitiesFromGroup(ExclusiveBuildGroup groupID); @@ -19,7 +17,7 @@ namespace Svelto.ECS void SwapEntityGroup(EGID fromID, ExclusiveBuildGroup toGroupID) where T : IEntityDescriptor, new(); - void SwapEntityGroup(EGID fromID, ExclusiveBuildGroup toGroupID, ExclusiveBuildGroup mustBeFromGroup) + void SwapEntityGroup(EGID fromID, ExclusiveBuildGroup fromGroup, ExclusiveBuildGroup toGroupID) where T : IEntityDescriptor, new(); void SwapEntityGroup(EGID fromID, EGID toId) where T : IEntityDescriptor, new(); @@ -27,8 +25,8 @@ namespace Svelto.ECS void SwapEntityGroup(EGID fromID, EGID toId, ExclusiveBuildGroup mustBeFromGroup) where T : IEntityDescriptor, new(); #if UNITY_NATIVE - NativeEntityRemove ToNativeRemove(string memberName) where T : IEntityDescriptor, new(); - 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/Core/INeedEGID.cs b/Core/INeedEGID.cs index 1812438..9cd3176 100644 --- a/Core/INeedEGID.cs +++ b/Core/INeedEGID.cs @@ -2,10 +2,12 @@ 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 /// public interface INeedEGID { - //The set is used only for the framework, but it must stay there + //The set is used only by the framework, but it must stay there EGID ID { get; set; } } } \ No newline at end of file diff --git a/Core/INeedEntityReference.cs b/Core/INeedEntityReference.cs new file mode 100644 index 0000000..f672b8d --- /dev/null +++ b/Core/INeedEntityReference.cs @@ -0,0 +1,15 @@ +using Svelto.ECS.Reference; + +namespace Svelto.ECS +{ + /// + /// The use of this is an exception and it's necessary for deprecated design only + /// It currently exist because of the publisher/consumer behavior, but the publisher/consumer must not be + /// considered an ECS pattern. + /// Other uses are invalid. + /// + public interface INeedEntityReference + { + EntityReference selfReference { get; set; } + } +} \ No newline at end of file diff --git a/Core/INeedEntityReference.cs.meta b/Core/INeedEntityReference.cs.meta new file mode 100644 index 0000000..cd7f563 --- /dev/null +++ b/Core/INeedEntityReference.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 3482762c10273a8e95993752e8bfa0fa +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Core/QueryGroups.cs b/Core/QueryGroups.cs index 42b9b20..a40a12a 100644 --- a/Core/QueryGroups.cs +++ b/Core/QueryGroups.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using System.Runtime.CompilerServices; using System.Threading; using Svelto.DataStructures; @@ -9,145 +10,195 @@ namespace Svelto.ECS.Experimental static GroupsList() { groups = new FasterList(); + sets = new HashSet(); } static readonly FasterList groups; - - public FasterList reference => groups; + static readonly HashSet sets; + + public void Reset() { sets.Clear(); } + + public void AddRange(ExclusiveGroupStruct[] groupsToAdd, int length) + { + for (int i = 0; i < length; i++) + { + sets.Add(groupsToAdd[i]); + } + } + + 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]); + } + } + + public void Exclude(ExclusiveGroupStruct groupsToIgnore) { sets.Remove(groupsToIgnore); } + + public void EnsureCapacity(uint preparecount) { groups.EnsureCapacity(preparecount); } + + public FasterList Evaluate() + { + groups.FastClear(); + + foreach (var item in sets) + { + groups.Add(item); + } + + return groups; + } } public ref struct QueryGroups { static readonly ThreadLocal groups = new ThreadLocal(); - public QueryGroups(LocalFasterReadOnlyList findGroups) + public QueryGroups(LocalFasterReadOnlyList groups) { - var groupsValue = groups.Value; - var group = groupsValue.reference; + var groupsValue = QueryGroups.groups.Value; - group.FastClear(); - for (int i = 0; i < findGroups.count; i++) - group.Add(findGroups[i]); + groupsValue.Reset(); + groupsValue.AddRange(groups.ToArrayFast(out var count), count); } - public QueryGroups(ExclusiveGroupStruct findGroups) + public QueryGroups(ExclusiveGroupStruct @group) { var groupsValue = groups.Value; - var group = groupsValue.reference; - group.FastClear(); - group.Add(findGroups); + groupsValue.Reset(); + groupsValue.Add(@group); } public QueryGroups(uint preparecount) { var groupsValue = groups.Value; - var group = groupsValue.reference; - group.FastClear(); - group.EnsureCapacity(preparecount); + groupsValue.Reset(); + groupsValue.EnsureCapacity(preparecount); } - public QueryResult Except(ExclusiveGroupStruct[] groupsToIgnore) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public QueryGroups Union(ExclusiveGroupStruct group) { - var group = groups.Value.reference; - var groupsCount = group.count; + var groupsValue = groups.Value; - for (int i = 0; i < groupsToIgnore.Length; i++) - for (int j = 0; j < groupsCount; j++) - { - if (groupsToIgnore[i] == group[j]) - { - group.UnorderedRemoveAt(j); - j--; - groupsCount--; - } - } + groupsValue.Add(group); - return new QueryResult(group); + return this; } - - public QueryResult Except(FasterList groupsToIgnore) + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public QueryGroups Union(LocalFasterReadOnlyList groups) { - var group = groups.Value.reference; - var groupsCount = group.count; + var groupsValue = QueryGroups.groups.Value; - for (int i = 0; i < groupsToIgnore.count; i++) - for (int j = 0; j < groupsCount; j++) - { - if (groupsToIgnore[i] == group[j]) - { - group.UnorderedRemoveAt(j); - j--; - groupsCount--; - } - } + groupsValue.AddRange(groups.ToArrayFast(out var count), count); - return new QueryResult(group); + return this; } - - public QueryResult Except(ExclusiveGroupStruct groupsToIgnore) + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public QueryGroups Except(ExclusiveGroupStruct group) { - var group = groups.Value.reference; - var groupsCount = group.count; + var groupsValue = groups.Value; - for (int j = 0; j < groupsCount; j++) - if (groupsToIgnore == group[j]) - { - group.UnorderedRemoveAt(j); - j--; - groupsCount--; - } + groupsValue.Exclude(group); - return new QueryResult(group); + return this; } - + [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int Count - (EntitiesDB entitiesDB, in LocalFasterReadOnlyList groups) where T : struct, IEntityComponent + public QueryGroups Except(ExclusiveGroupStruct[] groupsToIgnore) { - int count = 0; + var groupsValue = QueryGroups.groups.Value; - var groupsCount = groups.count; - for (int i = 0; i < groupsCount; ++i) - { - count += entitiesDB.Count(groups[i]); - } + groupsValue.Exclude(groupsToIgnore, groupsToIgnore.Length); - return count; + return this; } - - public QueryResult WithAny(EntitiesDB entitiesDB) - where T : struct, IEntityComponent + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public QueryGroups Except(LocalFasterReadOnlyList groupsToIgnore) { - var group = groups.Value.reference; - var groupsCount = group.count; + var groupsValue = QueryGroups.groups.Value; - for (var i = 0; i < groupsCount; i++) - { - if (entitiesDB.Count(group[i]) == 0) - { - group.UnorderedRemoveAt(i); - i--; - groupsCount--; - } - } + groupsValue.Exclude(groupsToIgnore.ToArrayFast(out var count), count); + + return this; + } - return new QueryResult(group); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public QueryGroups Except(FasterList groupsToIgnore) + { + var groupsValue = QueryGroups.groups.Value; + + groupsValue.Exclude(groupsToIgnore.ToArrayFast(out var count), count); + + return this; } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Add(ExclusiveGroupStruct group) + public QueryGroups Except(FasterReadOnlyList groupsToIgnore) + { + var groupsValue = QueryGroups.groups.Value; + + groupsValue.Exclude(groupsToIgnore.ToArrayFast(out var count), count); + + return this; + } + + // public QueryGroups WithAny(EntitiesDB entitiesDB) + // where T : struct, IEntityComponent + // { + // var group = groups.Value.reference; + // var groupsCount = group.count; + // + // for (uint i = 0; i < groupsCount; i++) + // { + // if (entitiesDB.Count(group[i]) == 0) + // { + // group.UnorderedRemoveAt(i); + // i--; + // groupsCount--; + // } + // } + // + // return this; + // } + + public QueryResult Evaluate() { - groups.Value.reference.Add(group); + var groupsValue = groups.Value; + + return new QueryResult(groupsValue.Evaluate()); } } public readonly ref struct QueryResult { - readonly FasterReadOnlyList _group; public QueryResult(FasterList @group) { _group = @group; } - - public FasterReadOnlyList result => _group; + + public LocalFasterReadOnlyList result => _group; + + readonly FasterReadOnlyList _group; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int Count(EntitiesDB entitiesDB) + where T : struct, IEntityComponent + { + int count = 0; + + var groupsCount = result.count; + for (int i = 0; i < groupsCount; ++i) + { + count += entitiesDB.Count(result[i]); + } + + return count; + } } } \ No newline at end of file diff --git a/Core/ReactEngineContainer.cs b/Core/ReactEngineContainer.cs new file mode 100644 index 0000000..cf43856 --- /dev/null +++ b/Core/ReactEngineContainer.cs @@ -0,0 +1,16 @@ +using Svelto.ECS.Internal; + +namespace Svelto.ECS +{ + public readonly struct ReactEngineContainer + { + public readonly string name; + public readonly IReactEngine engine; + + public ReactEngineContainer(IReactEngine engine, string name) + { + this.name = name; + this.engine = engine; + } + } +} \ No newline at end of file diff --git a/Core/ReactEngineContainer.cs.meta b/Core/ReactEngineContainer.cs.meta new file mode 100644 index 0000000..02ccde0 --- /dev/null +++ b/Core/ReactEngineContainer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: deb235c2d9a63d67a6be762e4ffac611 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Core/SetEGIDWithoutBoxing.cs b/Core/SetEGIDWithoutBoxing.cs index d5af6e1..87c8def 100644 --- a/Core/SetEGIDWithoutBoxing.cs +++ b/Core/SetEGIDWithoutBoxing.cs @@ -1,10 +1,14 @@ +using Svelto.ECS.Reference; + namespace Svelto.ECS.Internal { delegate void SetEGIDWithoutBoxingActionCast(ref T target, EGID egid) where T : struct, IEntityComponent; + delegate void SetReferenceWithoutBoxingActionCast(ref T target, EntityReference egid) where T : struct, IEntityComponent; static class SetEGIDWithoutBoxing where T : struct, IEntityComponent { - public static readonly SetEGIDWithoutBoxingActionCast SetIDWithoutBoxing = MakeSetter(); + public static readonly SetEGIDWithoutBoxingActionCast SetIDWithoutBoxing = MakeSetter(); + public static readonly SetReferenceWithoutBoxingActionCast SetRefWithoutBoxing = MakeSetterReference(); public static void Warmup() { } @@ -29,12 +33,38 @@ namespace Svelto.ECS.Internal return null; } + static SetReferenceWithoutBoxingActionCast MakeSetterReference() + { + if (ComponentBuilder.HAS_REFERENCE) + { +#if !ENABLE_IL2CPP + var method = typeof(Trick).GetMethod(nameof(Trick.SetEGIDImplRef)).MakeGenericMethod(typeof(T)); + return (SetReferenceWithoutBoxingActionCast) System.Delegate.CreateDelegate( + typeof(SetReferenceWithoutBoxingActionCast), method); +#else + return (ref T target, EntityReference reference) => + { + var needEgid = (target as INeedEntityReference); + needEgid.selfReference = reference; + target = (T) needEgid; + }; +#endif + } + + return null; + } + static class Trick { public static void SetEGIDImpl(ref U target, EGID egid) where U : struct, INeedEGID { target.ID = egid; } + + public static void SetEGIDImplRef(ref U target, EntityReference reference) where U : struct, INeedEntityReference + { + target.selfReference = reference; + } } } } \ No newline at end of file diff --git a/Core/SimpleEntitiesSubmissionScheduler.cs b/Core/SimpleEntitiesSubmissionScheduler.cs index 293c09e..386ed14 100644 --- a/Core/SimpleEntitiesSubmissionScheduler.cs +++ b/Core/SimpleEntitiesSubmissionScheduler.cs @@ -1,5 +1,5 @@ using System; -using System.Collections; +using System.Collections.Generic; namespace Svelto.ECS.Schedulers { @@ -7,52 +7,54 @@ namespace Svelto.ECS.Schedulers { public SimpleEntitiesSubmissionScheduler(uint maxNumberOfOperationsPerFrame = UInt32.MaxValue) { - _maxNumberOfOperationsPerFrame = maxNumberOfOperationsPerFrame; + _enumerator = SubmitEntitiesAsync(maxNumberOfOperationsPerFrame); } - - public IEnumerator SubmitEntitiesAsync() - { - if (paused == false) - { - var submitEntities = _onTick.Invoke(_maxNumberOfOperationsPerFrame); - - while (submitEntities.MoveNext()) - yield return null; - } - } - - public IEnumerator SubmitEntitiesAsync(uint maxNumberOfOperationsPerFrame) + + public IEnumerator SubmitEntitiesAsync() { return _enumerator; } + + public IEnumerator SubmitEntitiesAsync(uint maxNumberOfOperations) { - if (paused == false) + EnginesRoot.EntitiesSubmitter entitiesSubmitter = _onTick.Value; + entitiesSubmitter.maxNumberOfOperationsPerFrame = maxNumberOfOperations; + + while (true) { - var submitEntities = _onTick.Invoke(maxNumberOfOperationsPerFrame); - - while (submitEntities.MoveNext()) - yield return null; + if (paused == false) + { + var entitiesSubmitterSubmitEntities = entitiesSubmitter.submitEntities; + + entitiesSubmitterSubmitEntities.MoveNext(); + + if (entitiesSubmitterSubmitEntities.Current == true) + yield return true; + else + yield return false; + } } } public void SubmitEntities() { - var enumerator = SubmitEntitiesAsync(); + _enumerator.MoveNext(); - while (enumerator.MoveNext()); + while (_enumerator.Current == true) + _enumerator.MoveNext(); } - public override bool paused { get; set; } + public override bool paused { get; set; } public override void Dispose() { } protected internal override EnginesRoot.EntitiesSubmitter onTick { set { - DBC.ECS.Check.Require(_onTick.IsUnused, "a scheduler can be exclusively used by one enginesRoot only"); - + DBC.ECS.Check.Require(_onTick == null, "a scheduler can be exclusively used by one enginesRoot only"); + _onTick = value; } } - EnginesRoot.EntitiesSubmitter _onTick; - readonly uint _maxNumberOfOperationsPerFrame; + EnginesRoot.EntitiesSubmitter? _onTick; + readonly IEnumerator _enumerator; } } \ No newline at end of file diff --git a/Core/Streams/Consumer.cs b/Core/Streams/Consumer.cs index ffdc88b..8c79dcf 100644 --- a/Core/Streams/Consumer.cs +++ b/Core/Streams/Consumer.cs @@ -15,13 +15,16 @@ namespace Svelto.ECS #endif _ringBuffer = new RingBuffer>((int) capacity, #if DEBUG && !PROFILE_SVELTO - _name + _name #else string.Empty #endif ); - mustBeDisposed = MemoryUtilities.Alloc(sizeof(bool), Allocator.Persistent); + mustBeDisposed = MemoryUtilities.Alloc(1, Allocator.Persistent); *(bool*) mustBeDisposed = false; + + isActive = MemoryUtilities.Alloc(1, Allocator.Persistent); + *(bool*) isActive = true; } } @@ -33,7 +36,11 @@ namespace Svelto.ECS internal void Enqueue(in T entity, in EGID egid) { - _ringBuffer.Enqueue((entity, egid)); + unsafe + { + if (*(bool*)isActive) + _ringBuffer.Enqueue((entity, egid)); + } } public bool TryDequeue(out T entity) @@ -45,6 +52,9 @@ namespace Svelto.ECS return tryDequeue; } + //Note: it is correct to publish the EGID at the moment of the publishing, as the responsibility of + //the publisher consumer is not tracking the real state of the entity in the database at the + //moment of the consumption, but it's instead to store a copy of the entity at the moment of the publishing public bool TryDequeue(out T entity, out EGID id) { var tryDequeue = _ringBuffer.TryDequeue(out var values); @@ -55,10 +65,7 @@ namespace Svelto.ECS return tryDequeue; } - public void Flush() - { - _ringBuffer.Reset(); - } + public void Flush() { _ringBuffer.Reset(); } public void Dispose() { @@ -68,20 +75,35 @@ namespace Svelto.ECS } } - public uint Count() - { - return (uint) _ringBuffer.Count; - } + public uint Count() { return (uint) _ringBuffer.Count; } public void Free() { MemoryUtilities.Free(mustBeDisposed, Allocator.Persistent); + MemoryUtilities.Free(isActive, Allocator.Persistent); + } + + public void Pause() + { + unsafe + { + *(bool*) isActive = false; + } + } + + public void Resume() + { + unsafe + { + *(bool*) isActive = true; + } } readonly RingBuffer> _ringBuffer; internal readonly ExclusiveGroupStruct group; internal readonly bool hasGroup; + internal IntPtr isActive; internal IntPtr mustBeDisposed; #if DEBUG && !PROFILE_SVELTO diff --git a/Core/Streams/EnginesRoot.Streams.cs b/Core/Streams/EnginesRoot.Streams.cs index 3c2e29c..c1d369d 100644 --- a/Core/Streams/EnginesRoot.Streams.cs +++ b/Core/Streams/EnginesRoot.Streams.cs @@ -1,12 +1,16 @@ -namespace Svelto.ECS +using System.Runtime.CompilerServices; + +namespace Svelto.ECS { public partial class EnginesRoot { + [MethodImpl(MethodImplOptions.AggressiveInlining)] internal Consumer GenerateConsumer(string name, uint capacity) where T : unmanaged, IEntityComponent { return _entityStreams.GenerateConsumer(name, capacity); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] internal Consumer GenerateConsumer(ExclusiveGroupStruct group, string name, uint capacity) where T : unmanaged, IEntityComponent { diff --git a/Core/Streams/EntitiesDB.Streams.cs b/Core/Streams/EntitiesDB.Streams.cs index d5d47f2..609d1fb 100644 --- a/Core/Streams/EntitiesDB.Streams.cs +++ b/Core/Streams/EntitiesDB.Streams.cs @@ -5,15 +5,13 @@ namespace Svelto.ECS public partial class EntitiesDB { [MethodImpl(MethodImplOptions.AggressiveInlining)] + //Todo I should rename this method to reflect it's original intention public void PublishEntityChange(EGID egid) where T : unmanaged, IEntityComponent { + //Note: it is correct to publish the EGID at the moment of the publishing, as the responsibility of + //the publisher consumer is not tracking the real state of the entity in the database at the + //moment of the consumption, but it's instead to store a copy of the entity at the moment of the publishing _entityStream.PublishEntity(ref this.QueryEntity(egid), egid); } -#if later - public ThreadSafeNativeEntityStream GenerateThreadSafePublisher() where T: unmanaged, IEntityComponent - { - return _entityStream.GenerateThreadSafePublisher(this); - } -#endif } } \ No newline at end of file diff --git a/Core/Streams/EntitiesStreams.cs b/Core/Streams/EntitiesStreams.cs index 6d12992..c78de92 100644 --- a/Core/Streams/EntitiesStreams.cs +++ b/Core/Streams/EntitiesStreams.cs @@ -14,7 +14,7 @@ namespace Svelto.ECS /// one only /// - you want to communicate between EnginesRoots /// - internal struct EntitiesStreams : IDisposable + struct EntitiesStreams : IDisposable { internal Consumer GenerateConsumer(string name, uint capacity) where T : unmanaged, IEntityComponent @@ -34,16 +34,6 @@ namespace Svelto.ECS var typeSafeStream = (EntityStream) _streams[TypeRefWrapper.wrapper]; return typeSafeStream.GenerateConsumer(group, name, capacity); } -#if later - public ThreadSafeNativeEntityStream GenerateThreadSafePublisher(EntitiesDB entitiesDB) where T: unmanaged, IEntityComponent - { - var threadSafeNativeEntityStream = new ThreadSafeNativeEntityStream(entitiesDB); - - _streams[TypeRefWrapper.wrapper] = threadSafeNativeEntityStream; - - return threadSafeNativeEntityStream; - } -#endif internal void PublishEntity(ref T entity, EGID egid) where T : unmanaged, IEntityComponent { @@ -62,11 +52,11 @@ namespace Svelto.ECS public static EntitiesStreams Create() { var stream = new EntitiesStreams(); - stream._streams = ManagedSveltoDictionary.Create(); + stream._streams = FasterDictionary.Construct(); return stream; } - ManagedSveltoDictionary _streams; + FasterDictionary _streams; } } \ No newline at end of file diff --git a/Core/Streams/EntityStream.cs b/Core/Streams/EntityStream.cs index 1825013..def2e1c 100644 --- a/Core/Streams/EntityStream.cs +++ b/Core/Streams/EntityStream.cs @@ -33,15 +33,15 @@ namespace Svelto.ECS if (*(bool*) _consumers[i].mustBeDisposed) { _consumers[i].Free(); - _consumers.UnorderedRemoveAt(i); + _consumers.UnorderedRemoveAt((uint) i); --i; continue; } if (_consumers[i].hasGroup) { - if (egid.groupID == _consumers[i].@group) - _consumers[i].Enqueue(entity, egid); + if (egid.groupID == _consumers[i].@group) + _consumers[i].Enqueue(entity, egid); } else { diff --git a/Core/Streams/GenericentityStreamConsumerFactory.cs b/Core/Streams/GenericentityStreamConsumerFactory.cs index 2e893c2..05c7f13 100644 --- a/Core/Streams/GenericentityStreamConsumerFactory.cs +++ b/Core/Streams/GenericentityStreamConsumerFactory.cs @@ -1,3 +1,4 @@ +using System.Runtime.CompilerServices; using Svelto.DataStructures; namespace Svelto.ECS @@ -9,12 +10,14 @@ namespace Svelto.ECS _enginesRoot = new WeakReference(weakReference); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public Consumer GenerateConsumer(string name, uint capacity) where T : unmanaged, IEntityComponent { return _enginesRoot.Target.GenerateConsumer(name, capacity); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public Consumer GenerateConsumer(ExclusiveGroupStruct @group, string name, uint capacity) where T : unmanaged, IEntityComponent { diff --git a/DataStructures/FastTypeSafeDictionary.cs b/DataStructures/FastTypeSafeDictionary.cs deleted file mode 100644 index 15aa8ef..0000000 --- a/DataStructures/FastTypeSafeDictionary.cs +++ /dev/null @@ -1,293 +0,0 @@ -#if EXPERIMENTAL -using System; -using System.Runtime.CompilerServices; -using Svelto.Common; -using Svelto.DataStructures; - -namespace Svelto.ECS.Internal -{ - sealed class FastTypeSafeDictionary : ITypeSafeDictionary where TValue : struct, IEntityComponent - { - static readonly Type _type = typeof(TValue); - static readonly string _typeName = _type.Name; - static readonly bool _hasEgid = typeof(INeedEGID).IsAssignableFrom(_type); - - public FastTypeSafeDictionary(uint size) { _implementation = new SetDictionary(size); } - - public FastTypeSafeDictionary() { _implementation = new SetDictionary(1); } - - /// - /// Add entities from external typeSafeDictionary - /// - /// - /// - /// - public void AddEntitiesFromDictionary(ITypeSafeDictionary entitiesToSubmit, uint groupId) - { - var typeSafeDictionary = entitiesToSubmit as FastTypeSafeDictionary; - - foreach (var tuple in typeSafeDictionary) - { - try - { - if (_hasEgid) - SetEGIDWithoutBoxing.SetIDWithoutBoxing(ref typeSafeDictionary.unsafeValues[tuple.Key], - new EGID(tuple.Key, groupId)); - - _implementation.Add(tuple.Value); - } - catch (Exception e) - { - throw new - TypeSafeDictionaryException("trying to add an EntityComponent with the same ID more than once Entity: ". - FastConcat(typeof(TValue).ToString()).FastConcat(", group "). - FastConcat(groupId).FastConcat(", id ").FastConcat(tuple.Key), e); - } - } - } - - public void AddEntityToDictionary(EGID fromEntityGid, EGID toEntityID, ITypeSafeDictionary toGroup) - { - var valueIndex = _implementation.GetIndex(fromEntityGid.entityID); - - if (toGroup != null) - { - var toGroupCasted = toGroup as ITypeSafeDictionary; - ref var entity = ref _implementation.unsafeValues[(int) valueIndex]; - - if (_hasEgid) SetEGIDWithoutBoxing.SetIDWithoutBoxing(ref entity, toEntityID); - - toGroupCasted.Add(fromEntityGid.entityID, entity); - } - } - - public void AddEntitiesToEngines(FasterDictionary> entityComponentEnginesDB, - ITypeSafeDictionary realDic, - ExclusiveGroupStruct @group, - in PlatformProfiler profiler) - { - var typeSafeDictionary = realDic as ITypeSafeDictionary; - - //this can be optimized, should pass all the entities and not restart the process for each one - foreach (var value in _implementation) - AddEntityComponentToEngines(entityComponentEnginesDB, ref typeSafeDictionary.GetValueByRef(value.Key), null, - in profiler, new EGID(value.Key, group)); - } - - public void RemoveEntitiesFromEngines( - FasterDictionary> entityComponentEnginesDB, in PlatformProfiler profiler, - ExclusiveGroupStruct @group) - { - foreach (var value in _implementation) - RemoveEntityComponentFromEngines(entityComponentEnginesDB, ref _implementation.GetValueByRef(value.Key), null, - in profiler, new EGID(value.Key, group)); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FastClear() { _implementation.FastClear(); } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool Has(uint key) { return _implementation.ContainsKey(key); } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void RemoveEntityFromDictionary(EGID fromEntityGid) - { - _implementation.Remove(fromEntityGid.entityID); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void SetCapacity(uint size) { throw new NotImplementedException(); } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Trim() { _implementation.Trim(); } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Clear() { _implementation.Clear(); } - - public void MoveEntityFromEngines(EGID fromEntityGid, EGID? toEntityID, ITypeSafeDictionary toGroup, - FasterDictionary> engines, - in PlatformProfiler profiler) - { - var valueIndex = _implementation.GetIndex(fromEntityGid.entityID); - - ref var entity = ref _implementation.unsafeValues[(int) valueIndex]; - - if (toGroup != null) - { - RemoveEntityComponentFromEngines(engines, ref entity, fromEntityGid.groupID, in profiler, fromEntityGid); - - var toGroupCasted = toGroup as ITypeSafeDictionary; - var previousGroup = fromEntityGid.groupID; - - if (_hasEgid) SetEGIDWithoutBoxing.SetIDWithoutBoxing(ref entity, toEntityID.Value); - - var index = toGroupCasted.GetIndex(toEntityID.Value.entityID); - - AddEntityComponentToEngines(engines, ref toGroupCasted.unsafeValues[(int) index], previousGroup, in profiler, - toEntityID.Value); - } - else - RemoveEntityComponentFromEngines(engines, ref entity, null, in profiler, fromEntityGid); - } - - public uint Count - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => _implementation.count; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ITypeSafeDictionary Create() { return new FastTypeSafeDictionary(); } - - void AddEntityComponentToEngines(FasterDictionary> entityComponentEnginesDB, - ref TValue entity, - ExclusiveGroupStruct? previousGroup, - in PlatformProfiler profiler, - EGID egid) - { - //get all the engines linked to TValue - if (!entityComponentEnginesDB.TryGetValue(new RefWrapperType(_type), out var entityComponentsEngines)) return; - - if (previousGroup == null) - { - for (var i = 0; i < entityComponentsEngines.count; i++) - try - { - using (profiler.Sample(entityComponentsEngines[i], _typeName)) - { - (entityComponentsEngines[i] as IReactOnAddAndRemove).Add(ref entity, egid); - } - } - catch (Exception e) - { - throw new - ECSException("Code crashed inside Add callback ".FastConcat(typeof(TValue).ToString()), e); - } - } - else - { - for (var i = 0; i < entityComponentsEngines.count; i++) - try - { - using (profiler.Sample(entityComponentsEngines[i], _typeName)) - { - (entityComponentsEngines[i] as IReactOnSwap).MovedTo(ref entity, previousGroup.Value, - egid); - } - } - catch (Exception e) - { - throw new - ECSException("Code crashed inside MovedTo callback ".FastConcat(typeof(TValue).ToString()), - e); - } - } - } - - static void RemoveEntityComponentFromEngines(FasterDictionary> @group, - ref TValue entity, - ExclusiveGroupStruct? previousGroup, - in PlatformProfiler profiler, - EGID egid) - { - if (!@group.TryGetValue(new RefWrapperType(_type), out var entityComponentsEngines)) return; - - if (previousGroup == null) - { - for (var i = 0; i < entityComponentsEngines.count; i++) - try - { - using (profiler.Sample(entityComponentsEngines[i], _typeName)) - (entityComponentsEngines[i] as IReactOnAddAndRemove).Remove(ref entity, egid); - } - catch (Exception e) - { - throw new - ECSException("Code crashed inside Remove callback ".FastConcat(typeof(TValue).ToString()), - e); - } - } -#if SEEMS_UNNECESSARY - else - { - for (var i = 0; i < entityComponentsEngines.Count; i++) - try - { - using (profiler.Sample(entityComponentsEngines[i], _typeName)) - (entityComponentsEngines[i] as IReactOnSwap).MovedFrom(ref entity, egid); - } - catch (Exception e) - { - throw new ECSException( - "Code crashed inside Remove callback ".FastConcat(typeof(TValue).ToString()), e); - } - } -#endif - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public TValue[] GetValuesArray(out uint count) - { - var managedBuffer = _implementation.GetValuesArray(out count); - return managedBuffer; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool ContainsKey(uint egidEntityId) { return _implementation.ContainsKey(egidEntityId); } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Add(uint egidEntityId, in TValue entityComponent) { _implementation.Add(entityComponent); } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public SetDictionary.SetDictionaryKeyValueEnumerator GetEnumerator() - { - return _implementation.GetEnumerator(); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ref TValue GetValueByRef(uint key) { return ref _implementation.GetValueByRef(key); } - - public TValue this[uint idEntityId] - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => _implementation[idEntityId]; - [MethodImpl(MethodImplOptions.AggressiveInlining)] - set => _implementation[idEntityId] = value; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public uint GetIndex(uint valueEntityId) { return _implementation.GetIndex(valueEntityId); } - - public TValue[] unsafeValues - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => _implementation.unsafeValues; - } - - public SetDictionary implementation => _implementation; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool TryGetValue(uint entityId, out TValue item) - { - return _implementation.TryGetValue(entityId, out item); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ref TValue GetOrCreate(uint idEntityId) { throw new NotImplementedException(); } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool TryFindIndex(uint entityId, out uint index) - { - return _implementation.TryFindIndex(entityId, out index); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ref TValue GetDirectValue(uint findElementIndex) - { - return ref _implementation.GetDirectValue(findElementIndex); - } - - readonly SetDictionary _implementation; - } -} -#endif \ No newline at end of file diff --git a/DataStructures/ITypeSafeDictionary.cs b/DataStructures/ITypeSafeDictionary.cs index 77d24e1..4cebac4 100644 --- a/DataStructures/ITypeSafeDictionary.cs +++ b/DataStructures/ITypeSafeDictionary.cs @@ -21,14 +21,14 @@ namespace Svelto.ECS.Internal ITypeSafeDictionary Create(); //todo: there is something wrong in the design of the execute callback methods. Something to cleanup - void ExecuteEnginesAddOrSwapCallbacks(FasterDictionary> entityComponentEnginesDb, + 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, + FasterDictionary> engines, in PlatformProfiler profiler); + void ExecuteEnginesRemoveCallbacks(FasterDictionary> entityComponentEnginesDB, in PlatformProfiler profiler, ExclusiveGroupStruct @group); - void AddEntitiesFromDictionary(ITypeSafeDictionary entitiesToSubmit, uint groupId); + void AddEntitiesFromDictionary(ITypeSafeDictionary entitiesToSubmit, uint groupId, EnginesRoot enginesRoot); void AddEntityToDictionary(EGID fromEntityGid, EGID toEntityID, ITypeSafeDictionary toGroup); void RemoveEntityFromDictionary(EGID fromEntityGid); diff --git a/DataStructures/TypeSafeDictionary.cs b/DataStructures/TypeSafeDictionary.cs index e3c3710..7415cc3 100644 --- a/DataStructures/TypeSafeDictionary.cs +++ b/DataStructures/TypeSafeDictionary.cs @@ -8,14 +8,14 @@ namespace Svelto.ECS.Internal { sealed class TypeSafeDictionary : ITypeSafeDictionary where TValue : struct, IEntityComponent { - static readonly Type _type = typeof(TValue); - static readonly string _typeName = _type.Name; - static readonly bool _hasEgid = typeof(INeedEGID).IsAssignableFrom(_type); + static readonly Type _type = typeof(TValue); + static readonly bool _hasEgid = typeof(INeedEGID).IsAssignableFrom(_type); + static readonly bool _hasReference = typeof(INeedEntityReference).IsAssignableFrom(_type); - internal static readonly bool IsUnmanaged = + internal static readonly bool isUnmanaged = _type.IsUnmanagedEx() && (typeof(IEntityViewComponent).IsAssignableFrom(_type) == false); - SveltoDictionary>, ManagedStrategy, + internal SveltoDictionary>, ManagedStrategy, ManagedStrategy> implMgd; //used directly by native methods @@ -24,43 +24,88 @@ namespace Svelto.ECS.Internal public TypeSafeDictionary(uint size) { - if (IsUnmanaged) + if (isUnmanaged) implUnmgd = new SveltoDictionary>, - NativeStrategy, NativeStrategy>(size); + NativeStrategy, NativeStrategy>(size, Allocator.Persistent); else { - implMgd = new SveltoDictionary>, - ManagedStrategy, ManagedStrategy>(size); + implMgd = new SveltoDictionary>, + ManagedStrategy, ManagedStrategy>(size, Allocator.Managed); } } [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Add(uint egidEntityId, in TValue entityComponent) { - if (IsUnmanaged) + 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 + { + 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); + + toGroupCasted.Add(toEntityID.entityID, entity); + } + } + } /// - /// Add entities from external typeSafeDictionary + /// Add entities from external typeSafeDictionary (todo: add use case) /// /// /// + /// /// - public void AddEntitiesFromDictionary(ITypeSafeDictionary entitiesToSubmit, uint groupId) + public void AddEntitiesFromDictionary + (ITypeSafeDictionary entitiesToSubmit, uint groupId, EnginesRoot enginesRoot) { - if (IsUnmanaged) + var safeDictionary = (entitiesToSubmit as TypeSafeDictionary); + if (isUnmanaged) { - var typeSafeDictionary = (entitiesToSubmit as TypeSafeDictionary).implUnmgd; + var typeSafeDictionary = safeDictionary.implUnmgd; foreach (var tuple in typeSafeDictionary) try { + var egid = new EGID(tuple.Key, groupId); if (_hasEgid) SetEGIDWithoutBoxing.SetIDWithoutBoxing( - ref tuple.Value, new EGID(tuple.Key, groupId)); + ref tuple.Value, egid); + + if (_hasReference) + SetEGIDWithoutBoxing.SetRefWithoutBoxing( + ref tuple.Value, enginesRoot.entityLocator.GetEntityReference(egid)); implUnmgd.Add(tuple.Key, tuple.Value); } @@ -74,7 +119,7 @@ namespace Svelto.ECS.Internal } else { - var typeSafeDictionary = (entitiesToSubmit as TypeSafeDictionary).implMgd; + var typeSafeDictionary = safeDictionary.implMgd; foreach (var tuple in typeSafeDictionary) try @@ -96,11 +141,11 @@ namespace Svelto.ECS.Internal } public void ExecuteEnginesAddOrSwapCallbacks - (FasterDictionary> entityComponentEnginesDB + (FasterDictionary> entityComponentEnginesDB , ITypeSafeDictionary realDic, ExclusiveGroupStruct? fromGroup, ExclusiveGroupStruct toGroup , in PlatformProfiler profiler) { - if (IsUnmanaged) + if (isUnmanaged) { var typeSafeDictionary = realDic as ITypeSafeDictionary; @@ -122,46 +167,10 @@ namespace Svelto.ECS.Internal } } - 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 - { - 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); - - toGroupCasted.Add(toEntityID.entityID, entity); - } - } - } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Clear() { - if (IsUnmanaged) + if (isUnmanaged) { implUnmgd.Clear(); } @@ -174,7 +183,7 @@ namespace Svelto.ECS.Internal [MethodImpl(MethodImplOptions.AggressiveInlining)] public void FastClear() { - if (IsUnmanaged) + if (isUnmanaged) { implUnmgd.FastClear(); } @@ -187,7 +196,7 @@ namespace Svelto.ECS.Internal [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool ContainsKey(uint egidEntityId) { - if (IsUnmanaged) + if (isUnmanaged) { return implUnmgd.ContainsKey(egidEntityId); } @@ -203,77 +212,77 @@ namespace Svelto.ECS.Internal [MethodImpl(MethodImplOptions.AggressiveInlining)] public uint GetIndex(uint valueEntityId) { - if (IsUnmanaged) + if (isUnmanaged) { - return this.implUnmgd.GetIndex(valueEntityId); + return implUnmgd.GetIndex(valueEntityId); } else { - return this.implMgd.GetIndex(valueEntityId); + return implMgd.GetIndex(valueEntityId); } } [MethodImpl(MethodImplOptions.AggressiveInlining)] public ref TValue GetOrCreate(uint idEntityId) { - if (IsUnmanaged) + if (isUnmanaged) { - return ref this.implUnmgd.GetOrCreate(idEntityId); + return ref implUnmgd.GetOrCreate(idEntityId); } else { - return ref this.implMgd.GetOrCreate(idEntityId); + return ref implMgd.GetOrCreate(idEntityId); } } [MethodImpl(MethodImplOptions.AggressiveInlining)] public IBuffer GetValues(out uint count) { - if (IsUnmanaged) + if (isUnmanaged) { - return this.implUnmgd.GetValues(out count); + return implUnmgd.GetValues(out count); } else { - return this.implMgd.GetValues(out count); + return implMgd.GetValues(out count); } } [MethodImpl(MethodImplOptions.AggressiveInlining)] public ref TValue GetDirectValueByRef(uint key) { - if (IsUnmanaged) + if (isUnmanaged) { - return ref this.implUnmgd.GetDirectValueByRef(key); + return ref implUnmgd.GetDirectValueByRef(key); } else { - return ref this.implMgd.GetDirectValueByRef(key); + return ref implMgd.GetDirectValueByRef(key); } } [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool Has(uint key) { - if (IsUnmanaged) + if (isUnmanaged) { - return this.implUnmgd.ContainsKey(key); + return implUnmgd.ContainsKey(key); } else { - return this.implMgd.ContainsKey(key); + return implMgd.ContainsKey(key); } } public void ExecuteEnginesSwapOrRemoveCallbacks (EGID fromEntityGid, EGID? toEntityID, ITypeSafeDictionary toGroup - , FasterDictionary> engines, in PlatformProfiler profiler) + , FasterDictionary> engines, in PlatformProfiler profiler) { - if (IsUnmanaged) + if (isUnmanaged) { - var valueIndex = this.implUnmgd.GetIndex(fromEntityGid.entityID); + var valueIndex = implUnmgd.GetIndex(fromEntityGid.entityID); - ref var entity = ref this.implUnmgd.GetDirectValueByRef(valueIndex); + ref var entity = ref implUnmgd.GetDirectValueByRef(valueIndex); //move if (toGroup != null) @@ -281,6 +290,7 @@ namespace Svelto.ECS.Internal var toGroupCasted = toGroup as ITypeSafeDictionary; var previousGroup = fromEntityGid.groupID; + //todo: why is setting the EGID if this code just execute callbacks? if (_hasEgid) SetEGIDWithoutBoxing.SetIDWithoutBoxing(ref entity, toEntityID.Value); @@ -297,15 +307,16 @@ namespace Svelto.ECS.Internal } else { - var valueIndex = this.implMgd.GetIndex(fromEntityGid.entityID); + var valueIndex = implMgd.GetIndex(fromEntityGid.entityID); - ref var entity = ref this.implMgd.GetDirectValueByRef(valueIndex); + ref var entity = ref implMgd.GetDirectValueByRef(valueIndex); if (toGroup != null) { var toGroupCasted = toGroup as ITypeSafeDictionary; var previousGroup = fromEntityGid.groupID; + //todo: why is setting the EGID if this code just execute callbacks? if (_hasEgid) SetEGIDWithoutBoxing.SetIDWithoutBoxing(ref entity, toEntityID.Value); @@ -322,10 +333,10 @@ namespace Svelto.ECS.Internal } public void ExecuteEnginesRemoveCallbacks - (FasterDictionary> engines, in PlatformProfiler profiler + (FasterDictionary> engines, in PlatformProfiler profiler , ExclusiveGroupStruct group) { - if (IsUnmanaged) + if (isUnmanaged) { foreach (var value in implUnmgd) ExecuteEnginesRemoveCallbackOnSingleEntity(engines, ref implUnmgd.GetValueByRef(value.Key) @@ -342,46 +353,46 @@ namespace Svelto.ECS.Internal [MethodImpl(MethodImplOptions.AggressiveInlining)] public void RemoveEntityFromDictionary(EGID fromEntityGid) { - if (IsUnmanaged) + if (isUnmanaged) { - this.implUnmgd.Remove(fromEntityGid.entityID); + implUnmgd.Remove(fromEntityGid.entityID); } else { - this.implMgd.Remove(fromEntityGid.entityID); + implMgd.Remove(fromEntityGid.entityID); } } [MethodImpl(MethodImplOptions.AggressiveInlining)] public void SetCapacity(uint size) { - if (IsUnmanaged) + if (isUnmanaged) { - this.implUnmgd.SetCapacity(size); + implUnmgd.ExpandTo(size); } else { - this.implMgd.SetCapacity(size); + implMgd.ExpandTo(size); } } [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Trim() { - if (IsUnmanaged) + if (isUnmanaged) { - this.implUnmgd.Trim(); + implUnmgd.Trim(); } else { - this.implMgd.Trim(); + implMgd.Trim(); } } [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool TryFindIndex(uint entityId, out uint index) { - if (IsUnmanaged) + if (isUnmanaged) { return implUnmgd.TryFindIndex(entityId, out index); } @@ -393,7 +404,7 @@ namespace Svelto.ECS.Internal public void KeysEvaluator(Action action) { - if (IsUnmanaged) + if (isUnmanaged) { foreach (var key in implUnmgd.keys) { @@ -412,13 +423,13 @@ namespace Svelto.ECS.Internal [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool TryGetValue(uint entityId, out TValue item) { - if (IsUnmanaged) + if (isUnmanaged) { - return this.implUnmgd.TryGetValue(entityId, out item); + return implUnmgd.TryGetValue(entityId, out item); } else { - return this.implMgd.TryGetValue(entityId, out item); + return implMgd.TryGetValue(entityId, out item); } } @@ -427,13 +438,13 @@ namespace Svelto.ECS.Internal [MethodImpl(MethodImplOptions.AggressiveInlining)] get { - if (IsUnmanaged) + if (isUnmanaged) { - return (uint) this.implUnmgd.count; + return (uint) implUnmgd.count; } else { - return (uint) this.implMgd.count; + return (uint) implMgd.count; } } } @@ -443,19 +454,19 @@ namespace Svelto.ECS.Internal [MethodImpl(MethodImplOptions.AggressiveInlining)] get { - if (IsUnmanaged) + if (isUnmanaged) { - return ref this.implUnmgd.GetValueByRef(idEntityId); + return ref implUnmgd.GetValueByRef(idEntityId); } else { - return ref this.implMgd.GetValueByRef(idEntityId); + return ref implMgd.GetValueByRef(idEntityId); } } } static void ExecuteEnginesRemoveCallbackOnSingleEntity - (FasterDictionary> engines, ref TValue entity + (FasterDictionary> engines, ref TValue entity , in PlatformProfiler profiler, EGID egid) { if (!engines.TryGetValue(new RefWrapperType(_type), out var entityComponentsEngines)) @@ -464,14 +475,14 @@ namespace Svelto.ECS.Internal for (var i = 0; i < entityComponentsEngines.count; i++) try { - using (profiler.Sample(entityComponentsEngines[i], _typeName)) + using (profiler.Sample(entityComponentsEngines[i].name)) { - (entityComponentsEngines[i] as IReactOnAddAndRemove).Remove(ref entity, egid); + (entityComponentsEngines[i].engine as IReactOnAddAndRemove).Remove(ref entity, egid); } } catch { - Svelto.Console.LogError( + Console.LogError( "Code crashed inside Remove callback ".FastConcat(typeof(TValue).ToString())); throw; @@ -479,7 +490,7 @@ namespace Svelto.ECS.Internal } void ExecuteEnginesAddOrSwapCallbacksOnSingleEntity - (FasterDictionary> engines, ref TValue entity + (FasterDictionary> engines, ref TValue entity , ExclusiveGroupStruct? previousGroup, in PlatformProfiler profiler, EGID egid) { //get all the engines linked to TValue @@ -491,14 +502,14 @@ namespace Svelto.ECS.Internal for (var i = 0; i < entityComponentsEngines.count; i++) try { - using (profiler.Sample(entityComponentsEngines[i], _typeName)) + using (profiler.Sample(entityComponentsEngines[i].name)) { - (entityComponentsEngines[i] as IReactOnAddAndRemove).Add(ref entity, egid); + (entityComponentsEngines[i].engine as IReactOnAddAndRemove).Add(ref entity, egid); } } catch { - Svelto.Console.LogError( + Console.LogError( "Code crashed inside Add callback ".FastConcat(typeof(TValue).ToString())); throw; @@ -509,15 +520,15 @@ namespace Svelto.ECS.Internal for (var i = 0; i < entityComponentsEngines.count; i++) try { - using (profiler.Sample(entityComponentsEngines[i], _typeName)) + using (profiler.Sample(entityComponentsEngines[i].name)) { - (entityComponentsEngines[i] as IReactOnSwap).MovedTo( + (entityComponentsEngines[i].engine as IReactOnSwap).MovedTo( ref entity, previousGroup.Value, egid); } } catch (Exception) { - Svelto.Console.LogError( + Console.LogError( "Code crashed inside MoveTo callback ".FastConcat(typeof(TValue).ToString())); throw; @@ -527,7 +538,7 @@ namespace Svelto.ECS.Internal public void Dispose() { - if (IsUnmanaged) + if (isUnmanaged) implUnmgd.Dispose(); else implMgd.Dispose(); diff --git a/DataStructures/Unmanaged/AtomicNativeBags.cs b/DataStructures/Unmanaged/AtomicNativeBags.cs index 3d9208e..f7ce630 100644 --- a/DataStructures/Unmanaged/AtomicNativeBags.cs +++ b/DataStructures/Unmanaged/AtomicNativeBags.cs @@ -9,12 +9,6 @@ namespace Svelto.ECS.DataStructures { public unsafe struct AtomicNativeBags:IDisposable { - [global::Unity.Collections.LowLevel.Unsafe.NativeDisableUnsafePtrRestriction] - - NativeBag* _data; - readonly Allocator _allocator; - readonly uint _threadsCount; - public uint count => _threadsCount; public AtomicNativeBags(Allocator allocator) @@ -42,16 +36,20 @@ namespace Svelto.ECS.DataStructures [MethodImpl(MethodImplOptions.AggressiveInlining)] public ref NativeBag GetBuffer(int index) { +#if DEBUG if (_data == null) throw new Exception("using invalid AtomicNativeBags"); +#endif return ref MemoryUtilities.ArrayElementAsRef((IntPtr) _data, index); } public void Dispose() { +#if DEBUG if (_data == null) throw new Exception("using invalid AtomicNativeBags"); +#endif for (int i = 0; i < _threadsCount; i++) { @@ -63,14 +61,23 @@ namespace Svelto.ECS.DataStructures public void Clear() { +#if DEBUG if (_data == null) throw new Exception("using invalid AtomicNativeBags"); +#endif for (int i = 0; i < _threadsCount; i++) { GetBuffer(i).Clear(); } } + +#if UNITY_COLLECTIONS + [global::Unity.Collections.LowLevel.Unsafe.NativeDisableUnsafePtrRestriction] +#endif + NativeBag* _data; + readonly Allocator _allocator; + readonly uint _threadsCount; } } #endif \ No newline at end of file diff --git a/DataStructures/Unmanaged/NativeBag.cs b/DataStructures/Unmanaged/NativeBag.cs index cb2ac97..e2252fe 100644 --- a/DataStructures/Unmanaged/NativeBag.cs +++ b/DataStructures/Unmanaged/NativeBag.cs @@ -78,8 +78,7 @@ namespace Svelto.ECS.DataStructures { unsafe { - var sizeOf = MemoryUtilities.SizeOf(); - var listData = (UnsafeBlob*) MemoryUtilities.Alloc((uint) sizeOf, allocator); + var listData = (UnsafeBlob*) MemoryUtilities.Alloc((uint) 1, allocator); //clear to nullify the pointers //MemoryUtilities.MemClear((IntPtr) listData, (uint) sizeOf); @@ -130,7 +129,7 @@ namespace Svelto.ECS.DataStructures { #endif _queue->Dispose(); - MemoryUtilities.Free((IntPtr) _queue, _queue->allocator); + MemoryUtilities.Free((IntPtr) _queue, _queue->allocator); _queue = null; #if ENABLE_THREAD_SAFE_CHECKS } @@ -151,6 +150,7 @@ namespace Svelto.ECS.DataStructures var sizeOf = MemoryUtilities.SizeOf(); if (_queue->space - sizeOf < 0) + //Todo: NativeBag is very complicated. At the time of writing of this comment I don't remember if the sizeof really needs to be aligned by 4. To check and change this comment _queue->Realloc((uint) ((_queue->capacity + MemoryUtilities.Align4((uint) sizeOf)) * 2.0f)); #if ENABLE_THREAD_SAFE_CHECKS @@ -182,6 +182,7 @@ namespace Svelto.ECS.DataStructures #endif var sizeOf = MemoryUtilities.SizeOf(); if (_queue->space - sizeOf < 0) + //Todo: NativeBag is very complicated. At the time of writing of this comment I don't remember if the sizeof really needs to be aligned by 4. To check and change this comment _queue->Realloc((uint) ((_queue->capacity + MemoryUtilities.Align4((uint) sizeOf)) * 2.0f)); _queue->Write(item); @@ -272,7 +273,7 @@ namespace Svelto.ECS.DataStructures #if ENABLE_THREAD_SAFE_CHECKS int _threadSentinel; #endif -#if UNITY_NATIVE +#if UNITY_COLLECTIONS [global::Unity.Collections.LowLevel.Unsafe.NativeDisableUnsafePtrRestriction] #endif unsafe UnsafeBlob* _queue; diff --git a/DataStructures/Unmanaged/NativeDynamicArray.cs b/DataStructures/Unmanaged/NativeDynamicArray.cs index 86771e8..dd05cc7 100644 --- a/DataStructures/Unmanaged/NativeDynamicArray.cs +++ b/DataStructures/Unmanaged/NativeDynamicArray.cs @@ -34,6 +34,20 @@ namespace Svelto.ECS.DataStructures return (_list->count / MemoryUtilities.SizeOf()); } } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int Size() + { + unsafe + { +#if DEBUG && !PROFILE_SVELTO + if (_list == null) + throw new Exception("NativeDynamicArray: null-access"); + +#endif + return (_list->count); + } + } [MethodImpl(MethodImplOptions.AggressiveInlining)] public int Capacity() where T : struct @@ -51,6 +65,11 @@ namespace Svelto.ECS.DataStructures } } + public static NativeDynamicArray Alloc(uint newLength = 0) where T : struct + { + return Alloc(Allocator.Persistent, newLength); + } + public static NativeDynamicArray Alloc(Allocator allocator, uint newLength = 0) where T : struct { unsafe @@ -60,16 +79,13 @@ namespace Svelto.ECS.DataStructures #else NativeDynamicArray rtnStruc = default; #endif - var sizeOf = MemoryUtilities.SizeOf(); - - uint structSize = (uint) MemoryUtilities.SizeOf(); - UnsafeArray* listData = (UnsafeArray*) MemoryUtilities.Alloc(structSize, allocator); + UnsafeArray* listData = (UnsafeArray*) MemoryUtilities.Alloc(1, allocator); //clear to nullify the pointers //MemoryUtilities.MemClear((IntPtr) listData, structSize); rtnStruc._allocator = allocator; - listData->Realloc((uint) (newLength * sizeOf), allocator); + listData->Realloc(newLength, allocator); rtnStruc._list = listData; @@ -93,6 +109,12 @@ namespace Svelto.ECS.DataStructures return ref _list->Get(index); } } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ref T Get(int index) where T : struct + { + return ref Get((uint) index); + } [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Set(uint index, in T value) where T : struct @@ -118,6 +140,8 @@ namespace Svelto.ECS.DataStructures throw new Exception("NativeDynamicArray: null-access"); #endif _list->Dispose(_allocator); + MemoryUtilities.Free((IntPtr) _list, _allocator); + _list = null; } @@ -132,10 +156,10 @@ namespace Svelto.ECS.DataStructures if (_hashType != TypeHash.hash) throw new Exception("NativeDynamicArray: not expected type used"); #endif - var structSize = (uint) MemoryUtilities.SizeOf(); - - if (_list->space - (int) structSize < 0) - _list->Realloc((uint) (((uint) ((Count() + 1) * 1.5f) * (float) structSize)), _allocator); + if (Count() == Capacity()) + { + _list->Realloc((uint) ((Capacity() + 1) * 1.5f), _allocator); + } _list->Add(item); } @@ -155,7 +179,7 @@ namespace Svelto.ECS.DataStructures var structSize = (uint) MemoryUtilities.SizeOf(); if (index >= Capacity()) - _list->Realloc((uint) (((index + 1) * 1.5f) * structSize), _allocator); + _list->Realloc((uint) ((index + 1) * 1.5f), _allocator); var writeIndex = (index + 1) * structSize; if (_list->count < writeIndex) @@ -165,7 +189,7 @@ namespace Svelto.ECS.DataStructures } } - public void Grow(uint newCapacity) where T : struct + public void Resize(uint newCapacity) where T : struct { unsafe { @@ -174,13 +198,8 @@ namespace Svelto.ECS.DataStructures throw new Exception("NativeDynamicArray: null-access"); if (_hashType != TypeHash.hash) throw new Exception("NativeDynamicArray: not expected type used"); - if (newCapacity <= Capacity()) - throw new Exception("New capacity must be greater than current one"); #endif - uint structSize = (uint) MemoryUtilities.SizeOf(); - - uint size = (uint) (newCapacity * structSize); - _list->Realloc((uint) size, _allocator); + _list->Realloc((uint) newCapacity, _allocator); } } @@ -358,7 +377,7 @@ namespace Svelto.ECS.DataStructures } } -#if UNITY_NATIVE +#if UNITY_COLLECTIONS [global::Unity.Burst.NoAlias] [global::Unity.Collections.LowLevel.Unsafe.NativeDisableUnsafePtrRestriction] #endif unsafe UnsafeArray* _list; diff --git a/DataStructures/Unmanaged/NativeDynamicArrayCast.cs b/DataStructures/Unmanaged/NativeDynamicArrayCast.cs index 3c45777..8eca9a2 100644 --- a/DataStructures/Unmanaged/NativeDynamicArrayCast.cs +++ b/DataStructures/Unmanaged/NativeDynamicArrayCast.cs @@ -1,9 +1,14 @@ using System.Runtime.CompilerServices; +using Svelto.Common; namespace Svelto.ECS.DataStructures { public struct NativeDynamicArrayCast where T : struct { + public NativeDynamicArrayCast(uint size, Allocator allocator) + { + _array = NativeDynamicArray.Alloc(allocator, size); + } public NativeDynamicArrayCast(NativeDynamicArray array) : this() { _array = array; } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -14,6 +19,12 @@ namespace Svelto.ECS.DataStructures [MethodImpl(MethodImplOptions.AggressiveInlining)] get => _array.Count(); } + + public int capacity + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _array.Capacity(); + } public ref T this[int index] { @@ -44,6 +55,12 @@ namespace Svelto.ECS.DataStructures [MethodImpl(MethodImplOptions.AggressiveInlining)] public ref T AddAt(uint lastIndex) { return ref _array.AddAt(lastIndex); } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Resize(uint newSize) { _array.Resize(newSize); } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public NativeDynamicArray ToNativeArray() { return _array; } public bool isValid => _array.isValid; diff --git a/DataStructures/Unmanaged/NativeDynamicArrayUnityExtension.cs b/DataStructures/Unmanaged/NativeDynamicArrayUnityExtension.cs index 3a0dd90..3688940 100644 --- a/DataStructures/Unmanaged/NativeDynamicArrayUnityExtension.cs +++ b/DataStructures/Unmanaged/NativeDynamicArrayUnityExtension.cs @@ -1,4 +1,4 @@ -#if UNITY_NATIVE +#if UNITY_COLLECTIONS using Unity.Collections; using Unity.Collections.LowLevel.Unsafe; diff --git a/DataStructures/Unmanaged/SharedNativeInt.cs b/DataStructures/Unmanaged/SharedNativeInt.cs index 6810c79..60d82da 100644 --- a/DataStructures/Unmanaged/SharedNativeInt.cs +++ b/DataStructures/Unmanaged/SharedNativeInt.cs @@ -6,7 +6,7 @@ namespace Svelto.ECS.DataStructures { public struct SharedNativeInt: IDisposable { -#if UNITY_NATIVE +#if UNITY_COLLECTIONS [global::Unity.Collections.LowLevel.Unsafe.NativeDisableUnsafePtrRestriction] #endif unsafe int* data; @@ -34,15 +34,15 @@ namespace Svelto.ECS.DataStructures return current; } } - + public static implicit operator int(SharedNativeInt t) { unsafe { -#if DEBUG && !PROFILE_SVELTO - if (t.data == null) +#if DEBUG && !PROFILE_SVELTO + if (t.data == null) throw new Exception("using disposed SharedInt"); -#endif +#endif return *t.data; } } @@ -66,12 +66,12 @@ namespace Svelto.ECS.DataStructures #if DEBUG && !PROFILE_SVELTO if (data == null) throw new Exception("null-access"); -#endif - +#endif + return Interlocked.Decrement(ref *data); } } - + public int Increment() { unsafe @@ -79,12 +79,12 @@ namespace Svelto.ECS.DataStructures #if DEBUG && !PROFILE_SVELTO if (data == null) throw new Exception("null-access"); -#endif - +#endif + return Interlocked.Increment(ref *data); } } - + public int Add(int val) { unsafe @@ -92,12 +92,25 @@ namespace Svelto.ECS.DataStructures #if DEBUG && !PROFILE_SVELTO if (data == null) throw new Exception("null-access"); -#endif - +#endif + return Interlocked.Add(ref *data, val); } } - + + public int CompareExchange(int value, int compare) + { + unsafe + { + #if DEBUG && !PROFILE_SVELTO + if (data == null) + throw new Exception("null-access"); + #endif + + return Interlocked.CompareExchange(ref *data, value, compare); + } + } + public void Set(int val) { unsafe @@ -105,8 +118,8 @@ namespace Svelto.ECS.DataStructures #if DEBUG && !PROFILE_SVELTO if (data == null) throw new Exception("null-access"); -#endif - +#endif + Volatile.Write(ref *data, val); } } diff --git a/DataStructures/Unmanaged/ThreadSafeNativeBag.cs b/DataStructures/Unmanaged/ThreadSafeNativeBag.cs index d0517ee..d5d7187 100644 --- a/DataStructures/Unmanaged/ThreadSafeNativeBag.cs +++ b/DataStructures/Unmanaged/ThreadSafeNativeBag.cs @@ -183,7 +183,7 @@ namespace Svelto.ECS.DataStructures } } -#if UNITY_NATIVE +#if UNITY_COLLECTIONS [global::Unity.Collections.LowLevel.Unsafe.NativeDisableUnsafePtrRestriction] #endif unsafe UnsafeBlob* _queue; diff --git a/DataStructures/Unmanaged/UnsafeArray.cs b/DataStructures/Unmanaged/UnsafeArray.cs index a70c112..668d543 100644 --- a/DataStructures/Unmanaged/UnsafeArray.cs +++ b/DataStructures/Unmanaged/UnsafeArray.cs @@ -88,16 +88,19 @@ namespace Svelto.ECS.DataStructures } [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal void Realloc(uint newCapacity, Allocator allocator) + internal void Realloc(uint newCapacity, Allocator allocator) where T : struct { unsafe { + var structSize = (uint) MemoryUtilities.SizeOf(); + + uint newCapacityInBytes = structSize * newCapacity; if (_ptr == null) - _ptr = (byte*) MemoryUtilities.Alloc(newCapacity, allocator); + _ptr = (byte*) MemoryUtilities.Alloc(newCapacityInBytes, allocator); else - _ptr = (byte*) MemoryUtilities.Realloc((IntPtr) _ptr, (uint) count, newCapacity, allocator); + _ptr = (byte*) MemoryUtilities.Realloc((IntPtr) _ptr, newCapacityInBytes, allocator, (uint) count); - _capacity = newCapacity; + _capacity = newCapacityInBytes; } } @@ -106,9 +109,12 @@ namespace Svelto.ECS.DataStructures { unsafe { - if (ptr != null) - MemoryUtilities.Free((IntPtr) ptr, allocator); - +#if DEBUG && !PROFILE_SVELTO + if (ptr == null) + throw new Exception("UnsafeArray: try to dispose an already disposed array"); +#endif + MemoryUtilities.Free((IntPtr) ptr, allocator); + _ptr = null; _writeIndex = 0; _capacity = 0; @@ -127,7 +133,7 @@ namespace Svelto.ECS.DataStructures _writeIndex = count; } -#if UNITY_NATIVE +#if UNITY_COLLECTIONS [global::Unity.Collections.LowLevel.Unsafe.NativeDisableUnsafePtrRestriction] #endif unsafe byte* _ptr; diff --git a/Debugger/ExclusiveGroupDebugger.cs b/Debugger/ExclusiveGroupDebugger.cs deleted file mode 100644 index 3ebf160..0000000 --- a/Debugger/ExclusiveGroupDebugger.cs +++ /dev/null @@ -1,70 +0,0 @@ -using Svelto.ECS; - -#if DEBUG -using System; -using System.Collections.Generic; -using System.Reflection; - -public static class ExclusiveGroupDebugger -{ - static ExclusiveGroupDebugger() - { - Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); - foreach (Assembly assembly in assemblies) - { - Type[] types = assembly.GetTypes(); - - foreach (Type type in types) - { - if (type != null && type.IsClass && type.IsSealed && type.IsAbstract) //this means only static classes - { - var fields = type.GetFields(); - foreach (var field in fields) - { - if (field.IsStatic && typeof(ExclusiveGroup).IsAssignableFrom(field.FieldType)) - { - var group = (ExclusiveGroup) field.GetValue(null); - string name = $"{type.FullName}.{field.Name} ({(uint)group})"; - GroupMap.idToName[(ExclusiveGroupStruct) group] = name; - } - - if (field.IsStatic && typeof(ExclusiveGroupStruct).IsAssignableFrom(field.FieldType)) - { - var group = (ExclusiveGroupStruct) field.GetValue(null); - - string name = $"{type.FullName}.{field.Name} ({(uint)group})"; - GroupMap.idToName[@group] = name; - } - } - } - } - } - } - - public static string ToName(this in ExclusiveGroupStruct group) - { - if (GroupMap.idToName.TryGetValue(group, out var name) == false) - name = $""; - - return name; - } -} - -public static class GroupMap -{ - static GroupMap() - { - GroupMap.idToName = new Dictionary(); - } - - internal static readonly Dictionary idToName; -} -#else -public static class ExclusiveGroupDebugger -{ - public static string ToName(this in ExclusiveGroupStruct group) - { - return ((uint)group).ToString(); - } -} -#endif \ No newline at end of file diff --git a/Dispatcher/DispatchOnChange.cs b/Dispatcher/DispatchOnChange.cs deleted file mode 100644 index b7ae294..0000000 --- a/Dispatcher/DispatchOnChange.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System; - -namespace Svelto.ECS -{ - public class DispatchOnChange : DispatchOnSet where T:IEquatable - { - public DispatchOnChange(EGID senderID, T initialValue = default(T)) : base(senderID) - { - _value = initialValue; - } - - public DispatchOnChange(EGID senderID, Action callback) : base(senderID, callback) {} - - public new T value - { - set - { - if (value.Equals(_value) == false) - base.value = value; - } - - get => _value; - } - } -} diff --git a/Dispatcher/DispatchOnSet.cs b/Dispatcher/DispatchOnSet.cs deleted file mode 100644 index 37c6e43..0000000 --- a/Dispatcher/DispatchOnSet.cs +++ /dev/null @@ -1,48 +0,0 @@ -using System; - -namespace Svelto.ECS -{ - public class DispatchOnSet - { - public DispatchOnSet(EGID senderID, Action callback):this(senderID) - { - NotifyOnValueSet(callback); - } - public DispatchOnSet(EGID senderID) { _senderID = senderID; } - - public T value - { - set - { - _value = value; - - if (_paused == false) - _subscriber(_senderID, value); - } - } - - public void NotifyOnValueSet(Action action) - { -#if DEBUG && !PROFILE_SVELTO - DBC.ECS.Check.Require(_subscriber == null, $"{this.GetType().Name}: listener already registered"); -#endif - _subscriber = action; - _paused = false; - } - - public void StopNotify() - { - _subscriber = null; - _paused = true; - } - - public void PauseNotify() { _paused = true; } - public void ResumeNotify() { _paused = false; } - - protected T _value; - readonly EGID _senderID; - - Action _subscriber; - bool _paused; - } -} \ No newline at end of file diff --git a/Dispatcher/ReactiveValue.cs b/Dispatcher/ReactiveValue.cs new file mode 100644 index 0000000..d62540e --- /dev/null +++ b/Dispatcher/ReactiveValue.cs @@ -0,0 +1,97 @@ +using System; +using System.Collections.Generic; + +namespace Svelto.ECS +{ + /// + /// Reasons why unfortunately this cannot be a struct: + /// the user must remember to create interface with ref getters + /// ref getters cannot have set, while we sometimes use set to initialise values + /// the struct will be valid even if it has not ever been initialised + /// + /// 1 and 3 are possibly solvable, but 2 is a problem + /// + /// { + public class ReactiveValue + { + public ReactiveValue + (EntityReference senderID, Action callback, T initialValue = default + , bool notifyImmediately = false, ReactiveType notifyOnChange = ReactiveType.ReactOnChange) + { + _subscriber = callback; + + if (notifyImmediately) + _subscriber(_senderID, initialValue); + + _senderID = senderID; + _value = initialValue; + _notifyOnChange = notifyOnChange; + } + + public ReactiveValue(EntityReference senderID, Action callback, ReactiveType notifyOnChange) + { + _subscriber = callback; + _notifyOnChange = notifyOnChange; + _senderID = senderID; + } + + public T value + { + set + { + if (_notifyOnChange == ReactiveType.ReactOnSet || + EqualityComparer.Default.Equals(_value) == false) + { + if (_paused == false) + _subscriber(_senderID, value); + + //all the subscribers relies on the actual value not being changed yet, as the second parameter + //is the new value + _value = value; + } + } + get => _value; + } + + public void PauseNotify() + { + _paused = true; + } + + public void ResumeNotify() + { + _paused = false; + } + + public void ForceValue(in T value) + { + if (_paused == false) + _subscriber(_senderID, value); + + _value = value; + } + + public void SetValueWithoutNotify(in T value) + { + _value = value; + } + + public void StopNotify() + { + _subscriber = null; + _paused = true; + } + + readonly ReactiveType _notifyOnChange; + readonly EntityReference _senderID; + bool _paused; + Action _subscriber; + T _value; + } + + public enum ReactiveType + { + ReactOnSet, + ReactOnChange + } +} \ No newline at end of file diff --git a/Dispatcher/ReactiveValue.cs.meta b/Dispatcher/ReactiveValue.cs.meta new file mode 100644 index 0000000..2862df0 --- /dev/null +++ b/Dispatcher/ReactiveValue.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 19119d77198e38e5abbf810e68b6eecc +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/ECSResources/ECSResources.cs b/ECSResources/ECSResources.cs index a1effb4..6824dd5 100644 --- a/ECSResources/ECSResources.cs +++ b/ECSResources/ECSResources.cs @@ -2,12 +2,12 @@ using Svelto.DataStructures; namespace Svelto.ECS.Experimental { - public struct ECSResources - { - internal uint id; - - public static implicit operator T(ECSResources ecsString) { return ResourcesECSDB.FromECS(ecsString.id); } - } + // struct ECSResources + // { + // internal uint id; + // + // public static implicit operator T(ECSResources ecsString) { return ResourcesECSDB.FromECS(ecsString.id); } + // } /// /// To do. Or we reuse the ID or we need to clear this @@ -22,7 +22,7 @@ namespace Svelto.ECS.Experimental return ref _resources[(int) id - 1]; } - internal static uint ToECS(T resource) + internal static uint ToECS(in T resource) { _resources.Add(resource); @@ -37,15 +37,4 @@ namespace Svelto.ECS.Experimental return default; } } - - public static class ResourceExtensions - { - public static void Set(ref this ECSResources resource, T newText) - { - if (resource.id != 0) - ResourcesECSDB.resources(resource.id) = newText; - else - resource.id = ResourcesECSDB.ToECS(newText); - } - } } \ No newline at end of file diff --git a/ECSResources/ECSString.cs b/ECSResources/ECSString.cs index 50fef70..339ca8b 100644 --- a/ECSResources/ECSString.cs +++ b/ECSResources/ECSString.cs @@ -3,15 +3,11 @@ using System.Runtime.InteropServices; namespace Svelto.ECS.Experimental { + /// + /// Todo: the entityDB should be aware of the ECSString and recycle it on entity removal + /// [Serialization.DoNotSerialize] [StructLayout(LayoutKind.Explicit)] - /// - /// Note: I should extend this to reuse unused id - /// - - //todo ResourcesECSDB must be used only inside entity components. Same for ECSString. - //what I could do is that if the component is removed from the database, a reference counter to the object - //will be modified. If 0 is reached, the ID should be recycled. public struct ECSString:IEquatable { [FieldOffset(0)] uint _id; diff --git a/Extensions/DisposeDisposablesEngine.cs b/Extensions/DisposeDisposablesEngine.cs new file mode 100644 index 0000000..ff9daaa --- /dev/null +++ b/Extensions/DisposeDisposablesEngine.cs @@ -0,0 +1,24 @@ +using System; + +namespace Svelto.ECS +{ + [AllowMultiple] + public class DisposeDisposablesEngine : IEngine, IDisposable + { + public DisposeDisposablesEngine(IDisposable[] disposable) + { + _disposable = disposable; + } + + public void Dispose() + { + foreach (var d in _disposable) + { + d.Dispose(); + } + } + + IDisposable[] _disposable; + } + +} \ No newline at end of file diff --git a/Extensions/DisposeDisposablesEngine.cs.meta b/Extensions/DisposeDisposablesEngine.cs.meta new file mode 100644 index 0000000..ea48975 --- /dev/null +++ b/Extensions/DisposeDisposablesEngine.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b8cf2ef276e131d4bc74a3aea150a91a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Extensions/Svelto/AllGroupsEnumerable.cs b/Extensions/Svelto/AllGroupsEnumerable.cs index 54673e3..38d4537 100644 --- a/Extensions/Svelto/AllGroupsEnumerable.cs +++ b/Extensions/Svelto/AllGroupsEnumerable.cs @@ -6,7 +6,7 @@ namespace Svelto.ECS { /// /// ToDo it would be interesting to have a version of this dedicated to unmanaged, IEntityComponent - /// that can be burstifiable + /// that can be burstifiable /// /// public readonly struct AllGroupsEnumerable where T1 : struct, IEntityComponent @@ -38,6 +38,9 @@ namespace Svelto.ECS while (_db.MoveNext() == true) { var group = _db.Current; + if (group.Key.IsEnabled() == false) + continue; + ITypeSafeDictionary typeSafeDictionary = @group.Value as ITypeSafeDictionary; if (typeSafeDictionary.count == 0) @@ -54,9 +57,9 @@ namespace Svelto.ECS public GroupCollection Current => _array; - SveltoDictionary>, ManagedStrategy, - ManagedStrategy>.SveltoDictionaryKeyValueEnumerator _db; + ManagedStrategy> _db; GroupCollection _array; } diff --git a/Extensions/Svelto/EGIDMultiMapper.cs b/Extensions/Svelto/EGIDMultiMapper.cs new file mode 100644 index 0000000..b460fe2 --- /dev/null +++ b/Extensions/Svelto/EGIDMultiMapper.cs @@ -0,0 +1,149 @@ +using System; +using System.Runtime.CompilerServices; +using Svelto.Common; +using Svelto.DataStructures; +using Svelto.ECS.Hybrid; + +namespace Svelto.ECS +{ + namespace Native + { + public struct EGIDMultiMapper where T : unmanaged, IEntityComponent + { + public EGIDMultiMapper + (SveltoDictionary>, NativeStrategy, NativeStrategy>, + ManagedStrategy>, + ManagedStrategy>, NativeStrategy + , NativeStrategy>>, NativeStrategy> dictionary) + { + _dic = dictionary; + } + + public int count + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _dic.count; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ref T Entity(EGID entity) + { +#if DEBUG && !PROFILE_SVELTO + if (Exists(entity) == false) + throw new Exception("NativeEGIDMultiMapper: Entity not found"); +#endif + ref var sveltoDictionary = ref _dic.GetValueByRef(entity.groupID); + return ref sveltoDictionary.GetValueByRef(entity.entityID); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Exists(EGID entity) + { + return _dic.TryFindIndex(entity.groupID, out var index) + && _dic.GetDirectValueByRef(index).ContainsKey(entity.entityID); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool TryGetEntity(EGID entity, out T component) + { + component = default; + return _dic.TryFindIndex(entity.groupID, out var index) + && _dic.GetDirectValueByRef(index).TryGetValue(entity.entityID, out component); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool FindIndex(ExclusiveGroupStruct group, uint entityID, out uint index) + { + index = 0; + return _dic.TryFindIndex(group, out var groupIndex) && + _dic.GetDirectValueByRef(groupIndex).TryFindIndex(entityID, out index); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public uint GetIndex(ExclusiveGroupStruct group, uint entityID) + { + uint groupIndex = _dic.GetIndex(group); + return _dic.GetDirectValueByRef(groupIndex).GetIndex(entityID); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Exists(ExclusiveGroupStruct group, uint entityID) + { + return _dic.TryFindIndex(group, out var groupIndex) && + _dic.GetDirectValueByRef(groupIndex).ContainsKey(entityID); + } + + public Type entityType => TypeCache.type; + + SveltoDictionary>, NativeStrategy, + NativeStrategy>, ManagedStrategy>, + ManagedStrategy>, NativeStrategy, + NativeStrategy>>, NativeStrategy> _dic; + } + + public interface IEGIDMultiMapper + { + bool FindIndex(ExclusiveGroupStruct group, uint entityID, out uint index); + + uint GetIndex(ExclusiveGroupStruct group, uint entityID); + + bool Exists(ExclusiveGroupStruct group, uint entityID); + + Type entityType { get; } + } + } + + public struct EGIDMultiMapper where T : struct, IEntityViewComponent + { + public EGIDMultiMapper + (SveltoDictionary>, ManagedStrategy, + ManagedStrategy>, ManagedStrategy>, + ManagedStrategy>, ManagedStrategy, + ManagedStrategy>>, ManagedStrategy> dictionary) + { + _dic = dictionary; + } + + public int count + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _dic.count; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ref T Entity(EGID entity) + { +#if DEBUG && !PROFILE_SVELTO + if (Exists(entity) == false) + throw new Exception("NativeEGIDMultiMapper: Entity not found"); +#endif + ref var sveltoDictionary = ref _dic.GetValueByRef(entity.groupID); + return ref sveltoDictionary.GetValueByRef(entity.entityID); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Exists(EGID entity) + { + return _dic.TryFindIndex(entity.groupID, out var index) + && _dic.GetDirectValueByRef(index).ContainsKey(entity.entityID); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool TryGetEntity(EGID entity, out T component) + { + component = default; + return _dic.TryFindIndex(entity.groupID, out var index) + && _dic.GetDirectValueByRef(index).TryGetValue(entity.entityID, out component); + } + + SveltoDictionary>, ManagedStrategy, + ManagedStrategy>, ManagedStrategy>, + ManagedStrategy>, ManagedStrategy, + ManagedStrategy>>, ManagedStrategy> _dic; + } +} \ No newline at end of file diff --git a/Extensions/Svelto/EGIDMultiMapper.cs.meta b/Extensions/Svelto/EGIDMultiMapper.cs.meta new file mode 100644 index 0000000..6c053fd --- /dev/null +++ b/Extensions/Svelto/EGIDMultiMapper.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 42e6dde9fd3038beb8be99ba65d2e847 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Extensions/Svelto/EntitiesDBFiltersExtension.cs b/Extensions/Svelto/EntitiesDBFiltersExtension.cs new file mode 100644 index 0000000..996345d --- /dev/null +++ b/Extensions/Svelto/EntitiesDBFiltersExtension.cs @@ -0,0 +1,16 @@ +using Svelto.DataStructures; +using Svelto.ECS.Native; + +namespace Svelto.ECS +{ + public static class EntitiesDBFiltersExtension + { + public static bool AddEntityToFilter(this EntitiesDB.Filters filters, int filtersID, EGID egid, N mapper) where N : IEGIDMultiMapper + { + ref var filter = + ref filters.CreateOrGetFilterForGroup(filtersID, egid.groupID, new RefWrapperType(mapper.entityType)); + + return filter.Add(egid.entityID, mapper); + } + } +} \ No newline at end of file diff --git a/Extensions/Svelto/EntitiesDBFiltersExtension.cs.meta b/Extensions/Svelto/EntitiesDBFiltersExtension.cs.meta new file mode 100644 index 0000000..c7958eb --- /dev/null +++ b/Extensions/Svelto/EntitiesDBFiltersExtension.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 5a41ce2123ff3f6b892a1c04d5a57c4b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Extensions/Svelto/EntityManagedDBExtensions.cs b/Extensions/Svelto/EntityManagedDBExtensions.cs index ceafe28..5ff4ace 100644 --- a/Extensions/Svelto/EntityManagedDBExtensions.cs +++ b/Extensions/Svelto/EntityManagedDBExtensions.cs @@ -1,4 +1,5 @@ using System.Runtime.CompilerServices; +using Svelto.Common; using Svelto.DataStructures; using Svelto.ECS.Hybrid; using Svelto.ECS.Internal; @@ -8,7 +9,8 @@ namespace Svelto.ECS public static class EntityManagedDBExtensions { [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static MB QueryEntitiesAndIndex(this EntitiesDB entitiesDb, EGID entityGID, out uint index) where T : struct, IEntityViewComponent + public static MB QueryEntitiesAndIndex + (this EntitiesDB entitiesDb, EGID entityGID, out uint index) where T : struct, IEntityViewComponent { if (entitiesDb.QueryEntitiesAndIndexInternal(entityGID, out index, out MB array) == true) return array; @@ -17,7 +19,8 @@ namespace Svelto.ECS } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool TryQueryEntitiesAndIndex(this EntitiesDB entitiesDb, EGID entityGID, out uint index, out MB array) + public static bool TryQueryEntitiesAndIndex + (this EntitiesDB entitiesDb, EGID entityGID, out uint index, out MB array) where T : struct, IEntityViewComponent { if (entitiesDb.QueryEntitiesAndIndexInternal(entityGID, out index, out array) == true) @@ -25,9 +28,10 @@ namespace Svelto.ECS return false; } - + [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool TryQueryEntitiesAndIndex(this EntitiesDB entitiesDb, uint id, ExclusiveGroupStruct group, out uint index, out MB array) + public static bool TryQueryEntitiesAndIndex + (this EntitiesDB entitiesDb, uint id, ExclusiveGroupStruct group, out uint index, out MB array) where T : struct, IEntityViewComponent { if (entitiesDb.QueryEntitiesAndIndexInternal(new EGID(id, group), out index, out array) == true) @@ -35,9 +39,11 @@ namespace Svelto.ECS return false; } - + [MethodImpl(MethodImplOptions.AggressiveInlining)] - static bool QueryEntitiesAndIndexInternal(this EntitiesDB entitiesDb, EGID entityGID, out uint index, out MB buffer) where T : struct, IEntityViewComponent + static bool QueryEntitiesAndIndexInternal + (this EntitiesDB entitiesDb, EGID entityGID, out uint index, out MB buffer) + where T : struct, IEntityViewComponent { index = 0; buffer = default; @@ -46,28 +52,31 @@ namespace Svelto.ECS if (safeDictionary.TryFindIndex(entityGID.entityID, out index) == false) return false; - + buffer = (MB) (safeDictionary as ITypeSafeDictionary).GetValues(out _); return true; } - + [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static ref T QueryEntity(this EntitiesDB entitiesDb, EGID entityGID) where T : struct, IEntityViewComponent + public static ref T QueryEntity + (this EntitiesDB entitiesDb, EGID entityGID) where T : struct, IEntityViewComponent { var array = entitiesDb.QueryEntitiesAndIndex(entityGID, out var index); - + return ref array[(int) index]; } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static ref T QueryEntity(this EntitiesDB entitiesDb, uint id, ExclusiveGroupStruct group) where T : struct, IEntityViewComponent + public static ref T QueryEntity + (this EntitiesDB entitiesDb, uint id, ExclusiveGroupStruct group) where T : struct, IEntityViewComponent { return ref entitiesDb.QueryEntity(new EGID(id, group)); } - + [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static ref T QueryUniqueEntity(this EntitiesDB entitiesDb, ExclusiveGroupStruct group) where T : struct, IEntityViewComponent + public static ref T QueryUniqueEntity + (this EntitiesDB entitiesDb, ExclusiveGroupStruct group) where T : struct, IEntityViewComponent { var (entities, entitiescount) = entitiesDb.QueryEntities(@group); @@ -80,9 +89,10 @@ namespace Svelto.ECS #endif return ref entities[0]; } - + [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static MB GetArrayAndEntityIndex(this EGIDMapper mapper, uint entityID, out uint index) where T : struct, IEntityViewComponent + public static MB GetArrayAndEntityIndex + (this EGIDMapper mapper, uint entityID, out uint index) where T : struct, IEntityViewComponent { if (mapper._map.TryFindIndex(entityID, out index)) { @@ -93,7 +103,9 @@ namespace Svelto.ECS } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool TryGetArrayAndEntityIndex(this EGIDMapper mapper, uint entityID, out uint index, out MB array) where T : struct, IEntityViewComponent + public static bool TryGetArrayAndEntityIndex + (this EGIDMapper mapper, uint entityID, out uint index, out MB array) + where T : struct, IEntityViewComponent { index = default; if (mapper._map != null && mapper._map.TryFindIndex(entityID, out index)) @@ -105,5 +117,12 @@ namespace Svelto.ECS array = default; return false; } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static AllGroupsEnumerable QueryEntities(this EntitiesDB db) + where T1 :struct, IEntityComponent + { + return new AllGroupsEnumerable(db); + } } } \ No newline at end of file diff --git a/Extensions/Svelto/EntityNativeDBExtensions.cs b/Extensions/Svelto/EntityNativeDBExtensions.cs index bec4e41..baf42ca 100644 --- a/Extensions/Svelto/EntityNativeDBExtensions.cs +++ b/Extensions/Svelto/EntityNativeDBExtensions.cs @@ -1,125 +1,154 @@ using System.Runtime.CompilerServices; +using Svelto.Common; using Svelto.DataStructures; 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 namespace Svelto.ECS { public static class EntityNativeDBExtensions { [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static AllGroupsEnumerable QueryEntities(this EntitiesDB db) - where T1 :struct, IEntityComponent + public static NB QueryEntitiesAndIndex + (this EntitiesDB entitiesDb, EGID entityGID, out uint index) where T : unmanaged, IEntityComponent { - return new AllGroupsEnumerable(db); + if (entitiesDb.QueryEntitiesAndIndexInternal(entityGID, out index, out NB array) == true) + return array; + + throw new EntityNotFoundException(entityGID, typeof(T)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static NB QueryEntitiesAndIndex + (this EntitiesDB entitiesDb, uint id, ExclusiveGroupStruct group, out uint index) + where T : unmanaged, IEntityComponent + { + EGID entityGID = new EGID(id, group); + if (entitiesDb.QueryEntitiesAndIndexInternal(entityGID, out index, out NB array) == true) + return array; + + throw new EntityNotFoundException(entityGID, typeof(T)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool TryQueryEntitiesAndIndex + (this EntitiesDB entitiesDb, EGID entityGID, out uint index, out NB array) + where T : unmanaged, IEntityComponent + { + if (entitiesDb.QueryEntitiesAndIndexInternal(entityGID, out index, out array) == true) + return true; + + return false; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool TryQueryEntitiesAndIndex + (this EntitiesDB entitiesDb, uint id, ExclusiveGroupStruct group, out uint index, out NB array) + where T : unmanaged, IEntityComponent + { + if (entitiesDb.QueryEntitiesAndIndexInternal(new EGID(id, group), out index, out array) == true) + return true; + + return false; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool TryGetEntity(this EntitiesDB entitiesDb, uint entityID, ExclusiveGroupStruct @group, out T value) + where T : unmanaged, IEntityComponent + { + if (TryQueryEntitiesAndIndex(entitiesDb, entityID, group, out var index, out var array)) + { + value = array[index]; + return true; + } + + value = default; + return false; } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static NB QueryEntitiesAndIndex(this EntitiesDB entitiesDb, EGID entityGID, out uint index) where T : unmanaged, IEntityComponent - { - if (entitiesDb.QueryEntitiesAndIndexInternal(entityGID, out index, out NB array) == true) - return array; - - throw new EntityNotFoundException(entityGID, typeof(T)); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static NB QueryEntitiesAndIndex(this EntitiesDB entitiesDb, uint id, ExclusiveGroupStruct group, out uint index) where T : unmanaged, IEntityComponent - { - EGID entityGID = new EGID(id, group); - if (entitiesDb.QueryEntitiesAndIndexInternal(entityGID, out index, out NB array) == true) - return array; - - throw new EntityNotFoundException(entityGID, typeof(T)); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool TryQueryEntitiesAndIndex(this EntitiesDB entitiesDb, EGID entityGID, out uint index, out NB array) - where T : unmanaged, IEntityComponent - { - if (entitiesDb.QueryEntitiesAndIndexInternal(entityGID, out index, out array) == true) - return true; - - return false; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool TryQueryEntitiesAndIndex(this EntitiesDB entitiesDb, uint id, ExclusiveGroupStruct group, out uint index, out NB array) - where T : unmanaged, IEntityComponent - { - if (entitiesDb.QueryEntitiesAndIndexInternal(new EGID(id, group), out index, out array) == true) - return true; - - return false; - } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - static bool QueryEntitiesAndIndexInternal(this EntitiesDB entitiesDb, EGID entityGID, out uint index, out NB buffer) where T : unmanaged, IEntityComponent - { - index = 0; - buffer = default; - if (entitiesDb.SafeQueryEntityDictionary(entityGID.groupID, out var safeDictionary) == false) - return false; - - if (safeDictionary.TryFindIndex(entityGID.entityID, out index) == false) - return false; - - buffer = (NB) (safeDictionary as ITypeSafeDictionary).GetValues(out _); - - return true; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static ref T QueryEntity(this EntitiesDB entitiesDb, EGID entityGID) where T : unmanaged, IEntityComponent - { - var array = entitiesDb.QueryEntitiesAndIndex(entityGID, out var index); - - return ref array[(int) index]; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static ref T QueryEntity(this EntitiesDB entitiesDb, uint id, ExclusiveGroupStruct group) where T : unmanaged, IEntityComponent - { - return ref entitiesDb.QueryEntity(new EGID(id, group)); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static ref T QueryUniqueEntity(this EntitiesDB entitiesDb, ExclusiveGroupStruct group) where T : unmanaged, IEntityComponent - { - var (entities, entitiescount) = entitiesDb.QueryEntities(@group); + [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) + where T : unmanaged, IEntityComponent + { + index = 0; + buffer = default; + if (entitiesDb.SafeQueryEntityDictionary(entityGID.groupID, out var safeDictionary) == false) + return false; + + if (safeDictionary.TryFindIndex(entityGID.entityID, out index) == false) + return false; + + buffer = (NB) (safeDictionary as ITypeSafeDictionary).GetValues(out _); + + return true; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ref T QueryEntity + (this EntitiesDB entitiesDb, EGID entityGID) where T : unmanaged, IEntityComponent + { + var array = entitiesDb.QueryEntitiesAndIndex(entityGID, out var index); + + return ref array[(int) index]; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ref T QueryEntity + (this EntitiesDB entitiesDb, uint id, ExclusiveGroupStruct group) where T : unmanaged, IEntityComponent + { + return ref entitiesDb.QueryEntity(new EGID(id, group)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ref T QueryUniqueEntity + (this EntitiesDB entitiesDb, ExclusiveGroupStruct group) where T : unmanaged, IEntityComponent + { + var (entities, entitiescount) = entitiesDb.QueryEntities(@group); #if DEBUG && !PROFILE_SVELTO - if (entitiescount == 0) - throw new ECSException("Unique entity not found '".FastConcat(typeof(T).ToString()).FastConcat("'")); - if (entitiescount != 1) - throw new ECSException("Unique entities must be unique! '".FastConcat(typeof(T).ToString()) - .FastConcat("'")); + if (entitiescount == 0) + throw new ECSException("Unique entity not found '".FastConcat(typeof(T).ToString()).FastConcat("'")); + if (entitiescount != 1) + throw new ECSException("Unique entities must be unique! '".FastConcat(typeof(T).ToString()) + .FastConcat("'")); #endif - return ref entities[0]; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static NB GetArrayAndEntityIndex(this EGIDMapper mapper, uint entityID, out uint index) where T : unmanaged, IEntityComponent - { - if (mapper._map.TryFindIndex(entityID, out index)) - { - return (NB) mapper._map.GetValues(out _); - } - - throw new ECSException("Entity not found"); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool TryGetArrayAndEntityIndex(this EGIDMapper mapper, uint entityID, out uint index, out NB array) where T : unmanaged, IEntityComponent - { - index = default; - if (mapper._map != null && mapper._map.TryFindIndex(entityID, out index)) - { - array = (NB) mapper._map.GetValues(out _); - return true; - } - - array = default; - return false; - } + return ref entities[0]; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static NB GetArrayAndEntityIndex + (this EGIDMapper mapper, uint entityID, out uint index) where T : unmanaged, IEntityComponent + { + if (mapper._map.TryFindIndex(entityID, out index)) + { + return (NB) mapper._map.GetValues(out _); + } + + throw new ECSException("Entity not found"); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool TryGetArrayAndEntityIndex + (this EGIDMapper mapper, uint entityID, out uint index, out NB array) + where T : unmanaged, IEntityComponent + { + index = default; + if (mapper._map != null && mapper._map.TryFindIndex(entityID, out index)) + { + array = (NB) mapper._map.GetValues(out _); + return true; + } + + array = default; + return false; + } } } \ No newline at end of file diff --git a/Extensions/Svelto/FilterGroupExtensions.cs b/Extensions/Svelto/FilterGroupExtensions.cs new file mode 100644 index 0000000..1faeffb --- /dev/null +++ b/Extensions/Svelto/FilterGroupExtensions.cs @@ -0,0 +1,19 @@ +using Svelto.ECS.Native; + +namespace Svelto.ECS +{ + public static class FilterGroupExtensions + { + public static bool Add(this FilterGroup filter, uint entityID, N mapper) where N : IEGIDMultiMapper + { + #if DEBUG && !PROFILE_SVELTO + if (mapper.Exists(filter._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! "); + #endif + + return filter.InternalAdd(entityID, mapper.GetIndex(filter._exclusiveGroupStruct, entityID)); + } + + } +} \ No newline at end of file diff --git a/Extensions/Svelto/FilterGroupExtensions.cs.meta b/Extensions/Svelto/FilterGroupExtensions.cs.meta new file mode 100644 index 0000000..d9cabb3 --- /dev/null +++ b/Extensions/Svelto/FilterGroupExtensions.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 76cca529d6b538cd89a354152123777c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Extensions/Svelto/GroupsEnumerable.cs b/Extensions/Svelto/GroupsEnumerable.cs index b1b9559..f5fbfc5 100644 --- a/Extensions/Svelto/GroupsEnumerable.cs +++ b/Extensions/Svelto/GroupsEnumerable.cs @@ -1,4 +1,3 @@ -using DBC.ECS; using Svelto.DataStructures; namespace Svelto.ECS @@ -37,20 +36,15 @@ namespace Svelto.ECS //attention, the while is necessary to skip empty groups while (++_indexGroup < _groups.count) { - var entityCollection1 = _entitiesDB.QueryEntities(_groups[_indexGroup]); - if (entityCollection1.count == 0) - continue; - var entityCollection2 = _entitiesDB.QueryEntities(_groups[_indexGroup]); - if (entityCollection2.count == 0) + var exclusiveGroupStruct = _groups[_indexGroup]; + if (!exclusiveGroupStruct.IsEnabled()) continue; - Check.Assert(entityCollection1.count == entityCollection2.count - , "congratulation, you found a bug in Svelto, please report it"); + var entityCollection1 = _entitiesDB.QueryEntities(exclusiveGroupStruct); var array = entityCollection1; - var array2 = entityCollection2; _buffers = new EntityCollection(array.buffer1, array.buffer2, array.buffer3 - , array2); + , array.buffer4); break; } @@ -79,7 +73,7 @@ namespace Svelto.ECS readonly EntitiesDB _db; readonly LocalFasterReadOnlyList _groups; - public ref struct RefCurrent + public readonly ref struct RefCurrent { public RefCurrent(in EntityCollection buffers, ExclusiveGroupStruct group) { @@ -104,7 +98,6 @@ namespace Svelto.ECS { public GroupsEnumerable(EntitiesDB db, in LocalFasterReadOnlyList groups) { - DBC.ECS.Check.Require(groups.count > 0, "can't initialise a query without valid groups"); _db = db; _groups = groups; } @@ -123,7 +116,11 @@ namespace Svelto.ECS //attention, the while is necessary to skip empty groups while (++_indexGroup < _groups.count) { - var entityCollection = _entitiesDB.QueryEntities(_groups[_indexGroup]); + var exclusiveGroupStruct = _groups[_indexGroup]; + if (!exclusiveGroupStruct.IsEnabled()) + continue; + + var entityCollection = _entitiesDB.QueryEntities(exclusiveGroupStruct); if (entityCollection.count == 0) continue; @@ -156,7 +153,7 @@ namespace Svelto.ECS readonly EntitiesDB _db; readonly LocalFasterReadOnlyList _groups; - public ref struct RefCurrent + public readonly ref struct RefCurrent { public RefCurrent(in EntityCollection buffers, ExclusiveGroupStruct group) { @@ -198,7 +195,11 @@ namespace Svelto.ECS //attention, the while is necessary to skip empty groups while (++_indexGroup < _groups.count) { - var entityCollection = _db.QueryEntities(_groups[_indexGroup]); + var exclusiveGroupStruct = _groups[_indexGroup]; + if (!exclusiveGroupStruct.IsEnabled()) + continue; + + var entityCollection = _db.QueryEntities(exclusiveGroupStruct); if (entityCollection.count == 0) continue; @@ -231,7 +232,7 @@ namespace Svelto.ECS readonly EntitiesDB _db; readonly LocalFasterReadOnlyList _groups; - public ref struct RefCurrent + public readonly ref struct RefCurrent { public RefCurrent(in EntityCollection buffers, ExclusiveGroupStruct group) { @@ -272,14 +273,19 @@ namespace Svelto.ECS //attention, the while is necessary to skip empty groups while (++_indexGroup < _groups.count) { - var entityCollection = _db.QueryEntities(_groups[_indexGroup]); + var exclusiveGroupStruct = _groups[_indexGroup]; + + if (!exclusiveGroupStruct.IsEnabled()) + continue; + + var entityCollection = _db.QueryEntities(exclusiveGroupStruct); if (entityCollection.count == 0) continue; _buffer = entityCollection; break; } - + var moveNext = _indexGroup < _groups.count; if (moveNext == false) @@ -318,14 +324,14 @@ namespace Svelto.ECS buffers = _buffers; group = _group; } - + public void Deconstruct(out EntityCollection buffers) { buffers = _buffers; } - public readonly EntityCollection _buffers; - public readonly ExclusiveGroupStruct _group; + internal readonly EntityCollection _buffers; + internal readonly ExclusiveGroupStruct _group; } } } \ No newline at end of file diff --git a/Extensions/Unity/DOTS/Jobs/IJobifiedEngine.cs b/Extensions/Unity/DOTS/Jobs/IJobifiedEngine.cs index ab091a9..cc7b021 100644 --- a/Extensions/Unity/DOTS/Jobs/IJobifiedEngine.cs +++ b/Extensions/Unity/DOTS/Jobs/IJobifiedEngine.cs @@ -16,6 +16,9 @@ namespace Svelto.ECS.Extensions.Unity string name { get; } } + + public interface IJobifiedGroupEngine : IJobifiedEngine + { } } #endif \ No newline at end of file diff --git a/Extensions/Unity/DOTS/Jobs/JobifiedEnginesGroup.cs.meta b/Extensions/Unity/DOTS/Jobs/JobifiedEnginesGroup.cs.meta deleted file mode 100644 index 37cd12c..0000000 --- a/Extensions/Unity/DOTS/Jobs/JobifiedEnginesGroup.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: c4f616d57eee3e94bb54d709ccfcce4d -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Extensions/Unity/DOTS/Jobs/SortedJobifiedEnginesGroup.cs b/Extensions/Unity/DOTS/Jobs/SortedJobifiedEnginesGroup.cs index 7693866..eb58c32 100644 --- a/Extensions/Unity/DOTS/Jobs/SortedJobifiedEnginesGroup.cs +++ b/Extensions/Unity/DOTS/Jobs/SortedJobifiedEnginesGroup.cs @@ -10,7 +10,7 @@ namespace Svelto.ECS.Extensions.Unity /// /// /// - public abstract class SortedJobifiedEnginesGroup + public abstract class SortedJobifiedEnginesGroup : IJobifiedEngine where SequenceOrder : struct, ISequenceOrder where Interface : class, IJobifiedEngine { protected SortedJobifiedEnginesGroup(FasterList engines) diff --git a/Extensions/Unity/DOTS/Jobs/UnityJobExtensions.cs b/Extensions/Unity/DOTS/Jobs/UnityJobExtensions.cs index 312c703..de261d3 100644 --- a/Extensions/Unity/DOTS/Jobs/UnityJobExtensions.cs +++ b/Extensions/Unity/DOTS/Jobs/UnityJobExtensions.cs @@ -12,33 +12,59 @@ namespace Svelto.ECS { return new DisposeJob(disposable).Schedule(inputDeps); } - + public static JobHandle ScheduleDispose - (this T1 disposable1, T2 disposable2, JobHandle inputDeps) + (this T1 disposable1, T2 disposable2, JobHandle inputDeps) where T1 : struct, IDisposable where T2 : struct, IDisposable { return new DisposeJob(disposable1, disposable2).Schedule(inputDeps); } - + public static JobHandle ScheduleParallel (this JOB job, int iterations, JobHandle inputDeps) where JOB: struct, IJobParallelFor { if (iterations <= 0) return inputDeps; - + var innerloopBatchCount = ProcessorCount.BatchSize((uint) iterations); return job.Schedule((int)iterations, innerloopBatchCount, inputDeps); } - + public static JobHandle ScheduleParallel (this JOB job, uint iterations, JobHandle inputDeps) where JOB: struct, IJobParallelFor { if (iterations == 0) return inputDeps; - + var innerloopBatchCount = ProcessorCount.BatchSize(iterations); return job.Schedule((int)iterations, innerloopBatchCount, inputDeps); } + + public static JobHandle ScheduleParallelAndCombine + (this JOB job, int iterations, JobHandle inputDeps, JobHandle combinedDeps) where JOB: struct, IJobParallelFor + { + if (iterations == 0) + return inputDeps; + + var innerloopBatchCount = ProcessorCount.BatchSize((uint)iterations); + var jobDeps = job.Schedule(iterations, innerloopBatchCount, inputDeps); + + return JobHandle.CombineDependencies(combinedDeps, jobDeps); + } + + public static JobHandle ScheduleAndCombine + (this JOB job, JobHandle inputDeps, JobHandle combinedDeps) where JOB : struct, IJob + { + var jobDeps = job.Schedule(inputDeps); + return JobHandle.CombineDependencies(combinedDeps, jobDeps); + } + + public static JobHandle ScheduleAndCombine + (this JOB job, int arrayLength, JobHandle inputDeps, JobHandle combinedDeps) where JOB : struct, IJobFor + { + var jobDeps = job.Schedule(arrayLength, inputDeps); + return JobHandle.CombineDependencies(combinedDeps, jobDeps); + } } } #endif \ No newline at end of file diff --git a/Extensions/Unity/DOTS/Jobs/JobifiedEnginesGroup.cs b/Extensions/Unity/DOTS/Jobs/UnsortedJobifiedEnginesGroup.cs similarity index 79% rename from Extensions/Unity/DOTS/Jobs/JobifiedEnginesGroup.cs rename to Extensions/Unity/DOTS/Jobs/UnsortedJobifiedEnginesGroup.cs index 148d10e..d68c773 100644 --- a/Extensions/Unity/DOTS/Jobs/JobifiedEnginesGroup.cs +++ b/Extensions/Unity/DOTS/Jobs/UnsortedJobifiedEnginesGroup.cs @@ -5,21 +5,19 @@ using Unity.Jobs; namespace Svelto.ECS.Extensions.Unity { - public interface IJobifiedGroupEngine : IJobifiedEngine - { } /// /// Note unsorted jobs run in parallel /// /// - public abstract class JobifiedEnginesGroup:IJobifiedEngine where Interface : class, IJobifiedEngine + public abstract class UnsortedJobifiedEnginesGroup:IJobifiedEngine where Interface : class, IJobifiedEngine { - protected JobifiedEnginesGroup(FasterList engines) + protected UnsortedJobifiedEnginesGroup(FasterList engines) { _name = "JobifiedEnginesGroup - "+this.GetType().Name; _engines = engines; } - protected JobifiedEnginesGroup() + protected UnsortedJobifiedEnginesGroup() { _name = "JobifiedEnginesGroup - "+this.GetType().Name; _engines = new FasterList(); @@ -36,7 +34,7 @@ namespace Svelto.ECS.Extensions.Unity ref var engine = ref engines[index]; using (profiler.Sample(engine.name)) { - combinedHandles = engine.Execute(inputHandles); + combinedHandles = JobHandle.CombineDependencies(combinedHandles, engine.Execute(inputHandles)); } } } @@ -71,7 +69,8 @@ namespace Svelto.ECS.Extensions.Unity for (var index = 0; index < engines.count; index++) { var engine = engines[index]; - using (profiler.Sample(engine.name)) combinedHandles = engine.Execute(combinedHandles, ref _param); + using (profiler.Sample(engine.name)) + combinedHandles = JobHandle.CombineDependencies(combinedHandles, engine.Execute(combinedHandles, ref _param)); } } diff --git a/Extensions/Unity/DOTS/Jobs/UnsortedJobifiedEnginesGroup.cs.meta b/Extensions/Unity/DOTS/Jobs/UnsortedJobifiedEnginesGroup.cs.meta new file mode 100644 index 0000000..8a08806 --- /dev/null +++ b/Extensions/Unity/DOTS/Jobs/UnsortedJobifiedEnginesGroup.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 5eb8024fa96139c2962eed697c0c2551 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Extensions/Unity/DOTS/Native/EnginesRoot.NativeOperation.cs b/Extensions/Unity/DOTS/Native/EnginesRoot.NativeOperation.cs index 5fc9658..6c10a86 100644 --- a/Extensions/Unity/DOTS/Native/EnginesRoot.NativeOperation.cs +++ b/Extensions/Unity/DOTS/Native/EnginesRoot.NativeOperation.cs @@ -5,21 +5,17 @@ using Svelto.Common; using Svelto.DataStructures; using Svelto.ECS.DataStructures; using Svelto.ECS.Internal; +using Svelto.ECS.Native; namespace Svelto.ECS { public partial class EnginesRoot { - //todo: I very likely don't need to create one for each native entity factory, the same can be reused - readonly AtomicNativeBags _nativeAddOperationQueue = new AtomicNativeBags(Common.Allocator.Persistent); - readonly AtomicNativeBags _nativeRemoveOperationQueue = new AtomicNativeBags(Common.Allocator.Persistent); - readonly AtomicNativeBags _nativeSwapOperationQueue = new AtomicNativeBags(Common.Allocator.Persistent); - NativeEntityRemove ProvideNativeEntityRemoveQueue(string memberName) where T : IEntityDescriptor, new() { - //DBC.ECS.Check.Require(EntityDescriptorTemplate.descriptor.IsUnmanaged(), "can't remove entities with not native types"); + //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 I maybe able to provide a _nativeSwap.SwapEntity _nativeRemoveOperations.Add(new NativeOperationRemove( EntityDescriptorTemplate.descriptor.componentsToBuild, TypeCache.type , memberName)); @@ -29,7 +25,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)); @@ -44,14 +40,15 @@ namespace Svelto.ECS _nativeAddOperations.Add( new NativeOperationBuild(EntityDescriptorTemplate.descriptor.componentsToBuild, TypeCache.type, memberName)); - return new NativeEntityFactory(_nativeAddOperationQueue, _nativeAddOperations.count - 1); + return new NativeEntityFactory(_nativeAddOperationQueue, _nativeAddOperations.count - 1, _entityLocator); } - void NativeOperationSubmission(in PlatformProfiler profiler) + void FlushNativeOperations(in PlatformProfiler profiler) { using (profiler.Sample("Native Remove/Swap Operations")) { var removeBuffersCount = _nativeRemoveOperationQueue.count; + //todo, I don't like that this scans all the queues even if they are empty for (int i = 0; i < removeBuffersCount; i++) { ref var buffer = ref _nativeRemoveOperationQueue.GetBuffer(i); @@ -105,18 +102,26 @@ namespace Svelto.ECS { var componentsIndex = buffer.Dequeue(); var egid = buffer.Dequeue(); + var reference = buffer.Dequeue(); var componentCounts = buffer.Dequeue(); - + + Check.Require((uint)egid.groupID != 0, "invalid group detected, are you using new ExclusiveGroupStruct() instead of new ExclusiveGroup()?"); + var componentBuilders = _nativeAddOperations[componentsIndex].components; +#if DEBUG && !PROFILE_SVELTO var entityDescriptorType = _nativeAddOperations[componentsIndex].entityDescriptorType; CheckAddEntityID(egid, entityDescriptorType, _nativeAddOperations[componentsIndex].caller); +#endif - Check.Require(egid.groupID != 0, "invalid group detected, are you using new ExclusiveGroupStruct() instead of new ExclusiveGroup()?"); - + _entityLocator.SetReference(reference, egid); var dic = EntityFactory.BuildGroupedEntities(egid, _groupedEntityToAdd, componentBuilders - , null, entityDescriptorType); + , null +#if DEBUG && !PROFILE_SVELTO + , entityDescriptorType +#endif + ); - var init = new EntityInitializer(egid, dic); + var init = new EntityInitializer(egid, dic, reference); //only called if Init is called on the initialized (there is something to init) while (componentCounts > 0) @@ -126,7 +131,6 @@ namespace Svelto.ECS var typeID = buffer.Dequeue(); IFiller entityBuilder = EntityComponentIDMap.GetTypeFromID(typeID); - //after the typeID, I expect the serialized component entityBuilder.FillFromByteArray(init, buffer); } @@ -145,6 +149,11 @@ 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; + readonly AtomicNativeBags _nativeSwapOperationQueue; } readonly struct DoubleEGID diff --git a/Extensions/Unity/DOTS/Native/NativeEGIDMapper.cs b/Extensions/Unity/DOTS/Native/NativeEGIDMapper.cs index 3bf1e21..cf953d8 100644 --- a/Extensions/Unity/DOTS/Native/NativeEGIDMapper.cs +++ b/Extensions/Unity/DOTS/Native/NativeEGIDMapper.cs @@ -4,7 +4,7 @@ using System.Runtime.CompilerServices; using Svelto.Common; using Svelto.DataStructures; -namespace Svelto.ECS +namespace Svelto.ECS.Native { public readonly struct NativeEGIDMapper:IEGIDMapper where T : unmanaged, IEntityComponent { @@ -22,7 +22,7 @@ namespace Svelto.ECS [MethodImpl(MethodImplOptions.AggressiveInlining)] public ref T Entity(uint entityID) { -#if DEBUG && !PROFILE_SVELTO +#if DEBUG if (_map.TryFindIndex(entityID, out var findIndex) == false) throw new Exception("Entity not found in this group ".FastConcat(typeof(T).ToString())); #else @@ -56,7 +56,11 @@ namespace Svelto.ECS 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)] diff --git a/Extensions/Unity/DOTS/Native/NativeEGIDMultiMapper.cs b/Extensions/Unity/DOTS/Native/NativeEGIDMultiMapper.cs index 1a5af5e..94ba669 100644 --- a/Extensions/Unity/DOTS/Native/NativeEGIDMultiMapper.cs +++ b/Extensions/Unity/DOTS/Native/NativeEGIDMultiMapper.cs @@ -2,32 +2,16 @@ using System; using Svelto.DataStructures; -namespace Svelto.ECS +namespace Svelto.ECS.Native { - public struct NativeEGIDMultiMapper:IDisposable where T : unmanaged, IEntityComponent + public struct NativeEGIDMultiMapper : IDisposable where T : unmanaged, IEntityComponent { - SveltoDictionary>, - NativeStrategy, - NativeStrategy>, - NativeStrategy>, - NativeStrategy>, - NativeStrategy, - NativeStrategy>>, - NativeStrategy> _dic; - public NativeEGIDMultiMapper - (SveltoDictionary>, - NativeStrategy, - NativeStrategy>, - NativeStrategy>, - NativeStrategy>, - NativeStrategy, - NativeStrategy>>, - NativeStrategy> dictionary) + (SveltoDictionary>, NativeStrategy, + NativeStrategy>, NativeStrategy>, + NativeStrategy>, NativeStrategy, + NativeStrategy>>, NativeStrategy> dictionary) { _dic = dictionary; } @@ -41,6 +25,10 @@ namespace Svelto.ECS 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); } @@ -50,6 +38,19 @@ namespace Svelto.ECS 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/Extensions/Unity/DOTS/Native/NativeEntityFactory.cs b/Extensions/Unity/DOTS/Native/NativeEntityFactory.cs index ff3eddc..c4d6969 100644 --- a/Extensions/Unity/DOTS/Native/NativeEntityFactory.cs +++ b/Extensions/Unity/DOTS/Native/NativeEntityFactory.cs @@ -1,41 +1,49 @@ #if UNITY_NATIVE using Svelto.ECS.DataStructures; -namespace Svelto.ECS +namespace Svelto.ECS.Native { public readonly struct NativeEntityFactory { - internal NativeEntityFactory(AtomicNativeBags addOperationQueue, int index) + internal NativeEntityFactory(AtomicNativeBags addOperationQueue, int index, EnginesRoot.LocatorMap entityLocator) { _index = index; _addOperationQueue = addOperationQueue; + _entityLocator = entityLocator; } public NativeEntityInitializer BuildEntity (uint eindex, ExclusiveBuildGroup exclusiveBuildGroup, int threadIndex) { + EntityReference reference = _entityLocator.ClaimReference(); + NativeBag unsafeBuffer = _addOperationQueue.GetBuffer(threadIndex + 1); unsafeBuffer.Enqueue(_index); unsafeBuffer.Enqueue(new EGID(eindex, exclusiveBuildGroup)); + unsafeBuffer.Enqueue(reference); unsafeBuffer.ReserveEnqueue(out var index) = 0; - return new NativeEntityInitializer(unsafeBuffer, index); + return new NativeEntityInitializer(unsafeBuffer, index, reference); } - + public NativeEntityInitializer BuildEntity(EGID egid, int threadIndex) { + EntityReference reference = _entityLocator.ClaimReference(); + NativeBag unsafeBuffer = _addOperationQueue.GetBuffer(threadIndex + 1); unsafeBuffer.Enqueue(_index); unsafeBuffer.Enqueue(new EGID(egid.entityID, egid.groupID)); + unsafeBuffer.Enqueue(reference); unsafeBuffer.ReserveEnqueue(out var index) = 0; - return new NativeEntityInitializer(unsafeBuffer, index); + return new NativeEntityInitializer(unsafeBuffer, index, reference); } - - readonly AtomicNativeBags _addOperationQueue; - readonly int _index; + + readonly EnginesRoot.LocatorMap _entityLocator; + readonly AtomicNativeBags _addOperationQueue; + readonly int _index; } } #endif \ No newline at end of file diff --git a/Extensions/Unity/DOTS/Native/NativeEntityInitializer.cs b/Extensions/Unity/DOTS/Native/NativeEntityInitializer.cs index dbd0225..e067f6c 100644 --- a/Extensions/Unity/DOTS/Native/NativeEntityInitializer.cs +++ b/Extensions/Unity/DOTS/Native/NativeEntityInitializer.cs @@ -1,16 +1,18 @@ using Svelto.ECS.DataStructures; -namespace Svelto.ECS +namespace Svelto.ECS.Native { public readonly ref struct NativeEntityInitializer { readonly NativeBag _unsafeBuffer; readonly UnsafeArrayIndex _index; + readonly EntityReference _reference; - public NativeEntityInitializer(in NativeBag unsafeBuffer, UnsafeArrayIndex index) + public NativeEntityInitializer(in NativeBag unsafeBuffer, UnsafeArrayIndex index, EntityReference reference) { _unsafeBuffer = unsafeBuffer; _index = index; + _reference = reference; } public void Init(in T component) where T : unmanaged, IEntityComponent @@ -22,5 +24,7 @@ namespace Svelto.ECS _unsafeBuffer.Enqueue(id); _unsafeBuffer.Enqueue(component); } + + public EntityReference reference => _reference; } } \ No newline at end of file diff --git a/Extensions/Unity/DOTS/Native/NativeEntityRemove.cs b/Extensions/Unity/DOTS/Native/NativeEntityRemove.cs index c2365dd..e214255 100644 --- a/Extensions/Unity/DOTS/Native/NativeEntityRemove.cs +++ b/Extensions/Unity/DOTS/Native/NativeEntityRemove.cs @@ -1,7 +1,7 @@ #if UNITY_NATIVE using Svelto.ECS.DataStructures; -namespace Svelto.ECS +namespace Svelto.ECS.Native { public readonly struct NativeEntityRemove { diff --git a/Extensions/Unity/DOTS/Native/NativeEntitySwap.cs b/Extensions/Unity/DOTS/Native/NativeEntitySwap.cs index a60d1ac..0b39d6f 100644 --- a/Extensions/Unity/DOTS/Native/NativeEntitySwap.cs +++ b/Extensions/Unity/DOTS/Native/NativeEntitySwap.cs @@ -1,7 +1,7 @@ #if UNITY_NATIVE using Svelto.ECS.DataStructures; -namespace Svelto.ECS +namespace Svelto.ECS.Native { public readonly struct NativeEntitySwap { diff --git a/Extensions/Unity/DOTS/Native/UnityEntityDBExtensions.cs.meta b/Extensions/Unity/DOTS/Native/UnityEntityDBExtensions.cs.meta deleted file mode 100644 index 2e5164b..0000000 --- a/Extensions/Unity/DOTS/Native/UnityEntityDBExtensions.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: bca3702c45ec31f595c130a8725d552c -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Extensions/Unity/DOTS/Native/UnityEntityDBExtensions.cs b/Extensions/Unity/DOTS/Native/UnityNativeEntityDBExtensions.cs similarity index 75% rename from Extensions/Unity/DOTS/Native/UnityEntityDBExtensions.cs rename to Extensions/Unity/DOTS/Native/UnityNativeEntityDBExtensions.cs index 41fefe7..9b9723c 100644 --- a/Extensions/Unity/DOTS/Native/UnityEntityDBExtensions.cs +++ b/Extensions/Unity/DOTS/Native/UnityNativeEntityDBExtensions.cs @@ -4,9 +4,9 @@ using Svelto.Common; using Svelto.DataStructures; using Svelto.ECS.Internal; -namespace Svelto.ECS +namespace Svelto.ECS.Native { - public static class UnityEntityDBExtensions + public static class UnityNativeEntityDBExtensions { internal static NativeEGIDMapper ToNativeEGIDMapper(this TypeSafeDictionary dic, ExclusiveGroupStruct groupStructId) where T : unmanaged, IEntityComponent @@ -43,21 +43,15 @@ namespace Svelto.ECS [MethodImpl(MethodImplOptions.AggressiveInlining)] public static NativeEGIDMultiMapper QueryNativeMappedEntities(this EntitiesDB entitiesDb, - LocalFasterReadOnlyList groups) + LocalFasterReadOnlyList groups, Allocator allocator) where T : unmanaged, IEntityComponent { - var dictionary = - new SveltoDictionary>, - NativeStrategy, - NativeStrategy>, - NativeStrategy>, - NativeStrategy>, - NativeStrategy, - NativeStrategy>>, - NativeStrategy> - ((uint) groups.count, Allocator.TempJob); + var dictionary = new SveltoDictionary>, NativeStrategy, NativeStrategy>, //value + NativeStrategy>, //strategy to store the key + NativeStrategy>, NativeStrategy, NativeStrategy>>, NativeStrategy> //strategy to store the value + ((uint) groups.count, allocator); foreach (var group in groups) { diff --git a/Extensions/Unity/DOTS/Native/UnityNativeEntityDBExtensions.cs.meta b/Extensions/Unity/DOTS/Native/UnityNativeEntityDBExtensions.cs.meta new file mode 100644 index 0000000..d886a19 --- /dev/null +++ b/Extensions/Unity/DOTS/Native/UnityNativeEntityDBExtensions.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: fe82457d36d83a8aa874f461541fe280 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Extensions/Unity/DOTS/UECS/SubmissionEngine.cs b/Extensions/Unity/DOTS/UECS/SubmissionEngine.cs index 089431a..75ca6ac 100644 --- a/Extensions/Unity/DOTS/UECS/SubmissionEngine.cs +++ b/Extensions/Unity/DOTS/UECS/SubmissionEngine.cs @@ -5,18 +5,23 @@ using Unity.Jobs; namespace Svelto.ECS.Extensions.Unity { - public abstract class SubmissionEngine : SystemBase, IJobifiedEngine + public interface IUpdateBeforeSubmission { - public JobHandle Execute(JobHandle inputDeps) - { - Dependency = JobHandle.CombineDependencies(Dependency, inputDeps); - - OnUpdate(); - - return Dependency; - } + 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); } diff --git a/Extensions/Unity/DOTS/UECS/SveltoOverUECSEnginesGroup.cs b/Extensions/Unity/DOTS/UECS/SveltoOverUECSEnginesGroup.cs index f4e7013..aea088c 100644 --- a/Extensions/Unity/DOTS/UECS/SveltoOverUECSEnginesGroup.cs +++ b/Extensions/Unity/DOTS/UECS/SveltoOverUECSEnginesGroup.cs @@ -11,9 +11,11 @@ namespace Svelto.ECS.Extensions.Unity /// 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 Engines Run - /// This Engine runs, which causeS: + /// 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 @@ -44,8 +46,10 @@ namespace Svelto.ECS.Extensions.Unity //This is the UECS group that takes care of all the UECS systems that creates entities //it also submits Svelto entities - _sveltoUecsEntitiesSubmissionGroup = new SveltoUECSEntitiesSubmissionGroup(scheduler, world); + _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(); @@ -74,10 +78,10 @@ namespace Svelto.ECS.Extensions.Unity return _syncUecsToSveltoGroup.Execute(handle); } - public void AddUECSSubmissionEngine(SubmissionEngine spawnUnityEntityOnSveltoEntityEngine) + public void AddUECSSubmissionEngine(SubmissionEngine submissionEngine) { - _sveltoUecsEntitiesSubmissionGroup.Add(spawnUnityEntityOnSveltoEntityEngine); - _enginesRoot.AddEngine(spawnUnityEntityOnSveltoEntityEngine); + _sveltoUecsEntitiesSubmissionGroup.Add(submissionEngine); + _enginesRoot.AddEngine(submissionEngine); } public void AddSveltoToUECSEngine(SyncSveltoToUECSEngine engine) diff --git a/Extensions/Unity/DOTS/UECS/SveltoUECSEntitiesSubmissionGroup.cs b/Extensions/Unity/DOTS/UECS/SveltoUECSEntitiesSubmissionGroup.cs index b9bfbdc..3fbb3c3 100644 --- a/Extensions/Unity/DOTS/UECS/SveltoUECSEntitiesSubmissionGroup.cs +++ b/Extensions/Unity/DOTS/UECS/SveltoUECSEntitiesSubmissionGroup.cs @@ -1,8 +1,8 @@ #if UNITY_ECS -using System; using System.Collections; using Svelto.Common; using Svelto.DataStructures; +using Svelto.ECS.Native; using Svelto.ECS.Schedulers; using Unity.Entities; using Unity.Jobs; @@ -19,39 +19,127 @@ namespace Svelto.ECS.Extensions.Unity /// solve external dependencies. External dependencies are tracked, but only linked to the UECS components operations /// With Dependency I cannot guarantee that an external container is used before previous jobs working on it are completed /// - public sealed class SveltoUECSEntitiesSubmissionGroup + [DisableAutoCreation] + public sealed class SveltoUECSEntitiesSubmissionGroup : SystemBase, IQueryingEntitiesEngine , IReactOnAddAndRemove + , IReactOnSwap, ISveltoUECSSubmission { - public SveltoUECSEntitiesSubmissionGroup(SimpleEntitiesSubmissionScheduler submissionScheduler, World UECSWorld) + public SveltoUECSEntitiesSubmissionGroup(SimpleEntitiesSubmissionScheduler submissionScheduler) { - _submissionScheduler = submissionScheduler; - _ECBSystem = UECSWorld.CreateSystem(); - _engines = new FasterList(); + _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) { - JobHandle RefHelper() + 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); - using (var profiler = new PlatformProfiler("SveltoUECSEntitiesSubmissionGroup")) + + for (var index = 0; index < _beforeSubmissionEngines.count; index++) { - for (var index = 0; index < _engines.count; index++) + ref var engine = ref _beforeSubmissionEngines[index]; + using (profiler.Sample(engine.name)) { - ref var engine = ref _engines[index]; - using (profiler.Sample(engine.name)) - { - jobHandle = engine.Execute(jobHandle); - } + jobHandle = JobHandle.CombineDependencies(jobHandle, engine.BeforeSubmissionUpdate(jobHandle)); } } return jobHandle; } - if (_submissionScheduler.paused) - return; - - jobHandle.Complete(); + 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(); @@ -61,77 +149,93 @@ namespace Svelto.ECS.Extensions.Unity system.ECB = entityCommandBuffer; } - //Submit Svelto Entities, calls Add/Remove/MoveTo that can be used by the IUECSSubmissionEngines - _submissionScheduler.SubmitEntities(); + _ECB = entityCommandBuffer; - jobHandle = RefHelper(); + RemovePreviousMarkingComponents(entityCommandBuffer); - //Sync Point as we must be sure that jobs that create/swap/remove entities are done - jobHandle.Complete(); - - //flush command buffer - _ECBSystem.Update(); + using (profiler.Sample("Before Submission Engines")) + { + BeforeECBFlushEngines().Complete(); + } } - public IEnumerator SubmitEntities(JobHandle jobHandle, uint maxEntities) + void AfterSubmissionPhase(PlatformProfiler profiler) { - JobHandle RefHelper() + JobHandle AfterECBFlushEngines() { + JobHandle jobHandle = default; + //execute submission engines and complete jobs because of this I don't need to do _ECBSystem.AddJobHandleForProducer(Dependency); - using (var profiler = new PlatformProfiler("SveltoUECSEntitiesSubmissionGroup")) + for (var index = 0; index < _afterSubmissionEngines.count; index++) { - for (var index = 0; index < _engines.count; index++) + ref var engine = ref _afterSubmissionEngines[index]; + using (profiler.Sample(engine.name)) { - ref var engine = ref _engines[index]; - using (profiler.Sample(engine.name)) - { - jobHandle = engine.Execute(jobHandle); - } + jobHandle = JobHandle.CombineDependencies(jobHandle, engine.AfterSubmissionUpdate(jobHandle)); } } return jobHandle; } - if (_submissionScheduler.paused) - yield break; - - jobHandle.Complete(); + using (profiler.Sample("Flush Command Buffer")) + { + _ECBSystem.Update(); + } - //prepare the entity command buffer to be used by the registered engines - var entityCommandBuffer = _ECBSystem.CreateCommandBuffer(); + ConvertPendingEntities().Complete(); - foreach (var system in _engines) + using (profiler.Sample("After Submission Engines")) { - system.ECB = entityCommandBuffer; + AfterECBFlushEngines().Complete(); } + } - //Submit Svelto Entities, calls Add/Remove/MoveTo that can be used by the IUECSSubmissionEngines - var submitEntitiesAsync = _submissionScheduler.SubmitEntitiesAsync(maxEntities); - while (submitEntitiesAsync.MoveNext()) - yield return null; + void RemovePreviousMarkingComponents(EntityCommandBuffer ECB) + { + ECB.RemoveComponentForEntityQuery(_entityQuery); + } - jobHandle = RefHelper(); + JobHandle ConvertPendingEntities() + { + if (_entityQuery.IsEmpty == false) + { + NativeEGIDMultiMapper mapper = + entitiesDB.QueryNativeMappedEntities( + entitiesDB.FindGroups(), Allocator.TempJob); - //Sync Point as we must be sure that jobs that create/swap/remove entities are done - jobHandle.Complete(); + Entities.ForEach((Entity id, ref UpdateUECSEntityAfterSubmission egidComponent) => + { + mapper.Entity(egidComponent.egid).uecsEntity = id; + }).ScheduleParallel(); - //flush command buffer - _ECBSystem.Update(); - } - - public void Add(SubmissionEngine engine) - { - _ECBSystem.World.AddSystem(engine); - _engines.Add(engine); + mapper.ScheduleDispose(Dependency); + } + + return Dependency; } - readonly SimpleEntitiesSubmissionScheduler _submissionScheduler; - readonly SubmissionEntitiesCommandBufferSystem _ECBSystem; - readonly FasterList _engines; + 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/Extensions/Unity/DOTS/UECS/SyncSveltoToUECSGroup.cs b/Extensions/Unity/DOTS/UECS/SyncSveltoToUECSGroup.cs index 33e8443..1839b0d 100644 --- a/Extensions/Unity/DOTS/UECS/SyncSveltoToUECSGroup.cs +++ b/Extensions/Unity/DOTS/UECS/SyncSveltoToUECSGroup.cs @@ -4,22 +4,70 @@ using Unity.Jobs; namespace Svelto.ECS.Extensions.Unity { - public class SyncSveltoToUECSGroup : JobifiedEnginesGroup - { - } + /// + /// HOW DOTS SYSTEMBASE DEPENDENCY SYSTEM WORKS: + /// EACH SYSTEMBASE DEPENDENCY REMEMBERS ONLY THE JOBHANDLES OF THE JOBS THAT TOUCH THE COMPONENTS ACCESSED + /// BY THE FOREACH. THEY WON'T AUTOMATICALLY CARRY OVER DEPENDENCIES OF JOBS TOUCHING OTHER COMPONENTS AS + /// THE DEPENDENCY IS OPTIMIZED TO CARRY ONLY WHAT IS OF INTEREST TO MAKE THE CURRENTLY ACCESSED COMPONENTS + /// WORK AS INTENDED. + /// HOWEVER THEY CARRY OVER EXTERNAL DEPENDENCIES COMBINED EXPLICITLY. + /// + // Tim Johansson 15 hours ago + // To make sure I got this right, you have something like systemA.Update(); systemB.Update(); systemC.Update() and you want to do systemC.Dependency.Complete() in order to wait for all jobs scheduled by any of the three systems? + // If so it will not work in all cases because the dependencies are forwarded based on what data you access like @cort says. + // If for example systemA is accessing component 1 and nothing else, and no other system is accessing component 1, any job scheduled by systemA will not be included. + // A bit more advanced, if systemB and systemC are both reading component 2 and do not share any other components, then the jobs from systemB will not be a dependency for systemC since readers of data do not have to wait for other readers. (edited) + // + // Cort Stratton 15 hours ago + // Tim is correct; the Dependency for systemC would not include jobs scheduled by systemA and systemB, if systemA and systemB do not share component dependencies with systemC. So completing systemC's dependency would not automatically complete ALL jobs from ALL previous systems. If that's your goal, you probably want something like EntityManager.CompleteAllJobs(), but that's a very expensive function and is mostly geared towards test code. + // + // Tim Johansson 1 hour ago + // There are never any automatic dependencies between systems unless they access the same data, and there is no dependency if both are reading the data (unless there is a writer between them). + // Whatever the final state of Dependency is in a system will be an input Dependency for later systems accessing the same data, there is no filtering so any combine dependency stored in Dependency will propagate assuming data is shared. + // So if systemA reads C1 and writes C2 what will happen is that after systemA has run we?ll look at the final state of Dependency. Will register that any system writing C1 will have to depend on it, any system reading or writing C2 will have to depend on it. + // When systemB runs it will check which dependencies are registered in the system for accessing the components it needs, and combine all of those. That is the input Dependency when the system starts. + // + // Tim Johansson 10 minutes ago + // If you set Dependency that will be propagated the same way regardless of if it is a job you schedule or a combined dependency. We?ll only look at the final state and propagate that for all types you accessed. So there is no special propagation of it unless there is shared data between the systems + // yes my previous (and last) question was not about components though, I am still just talking what if I want Dependency to care about external jobs? + // a. would combining Dependency with external jobs guarantee that the external jobs are executed before the current SystemBase Jobs are executed + // b. would the external jobs (handles) be carried to the next SystemBase or I have to combine the external job handles with the current Dependency before each SystemBase update? + // I hope this is clear, it's really my last doubt :disappointed: (edited) + // New + // + // Tim Johansson 1 minute ago + // a. Yes, as long as you combine before you schedule it will make the external jobs a dependency for everything you schedule (assuming you do not explicitly schedule with a different dependency). The Combine before Update in your sample would work. + // b. Depends on what you mean by ?next? SystemBase. The external jobs will be carried forward the exact same way as the jobs you schedule. So if the external dependency is forwarded or not depends on what data the two systems are accessing, there is not special code that forwards the dependency unless they share data + // Sebastiano Mandala 2 hours ago + // I see so if the external job handle is not about components, but external datastructures, I have to combine it every time + // + // Sebastiano Mandala 2 hours ago + // tthat is actually what I am doing so it would be fine + // + // Sebastiano Mandala 2 hours ago + // I just wanted to know I wasn't being over defensive + // + // Tim Johansson 2 hours ago + // Yes, you would have to do it every time + + public class SyncSveltoToUECSGroup : UnsortedJobifiedEnginesGroup {} public abstract class SyncSveltoToUECSEngine : 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); - + Update(); return Dependency; } - public abstract string name { get; } + public abstract string name { get; } } } #endif \ No newline at end of file diff --git a/Extensions/Unity/DOTS/UECS/SyncUECSToSveltoGroup.cs b/Extensions/Unity/DOTS/UECS/SyncUECSToSveltoGroup.cs index 6bccbe0..585c93e 100644 --- a/Extensions/Unity/DOTS/UECS/SyncUECSToSveltoGroup.cs +++ b/Extensions/Unity/DOTS/UECS/SyncUECSToSveltoGroup.cs @@ -4,7 +4,7 @@ using Unity.Jobs; namespace Svelto.ECS.Extensions.Unity { - public class SyncUECSToSveltoGroup : JobifiedEnginesGroup + public class SyncUECSToSveltoGroup : UnsortedJobifiedEnginesGroup { } diff --git a/Extensions/Unity/DOTS/UECS/UECSSveltoEGID.cs b/Extensions/Unity/DOTS/UECS/UECSSveltoEGID.cs index 8d460c9..b5307af 100644 --- a/Extensions/Unity/DOTS/UECS/UECSSveltoEGID.cs +++ b/Extensions/Unity/DOTS/UECS/UECSSveltoEGID.cs @@ -6,18 +6,35 @@ 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 uint group; - - public UECSSveltoGroupID(uint exclusiveGroup) { @group = exclusiveGroup; } - + + public UECSSveltoGroupID(ExclusiveGroupStruct exclusiveGroup) + { + @group = (uint) exclusiveGroup; + } + public static implicit operator ExclusiveGroupStruct(UECSSveltoGroupID group) { return new ExclusiveGroupStruct(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/Extensions/Unity/EGIDHolderImplementor.cs b/Extensions/Unity/EGIDHolderImplementor.cs deleted file mode 100644 index c4c4b88..0000000 --- a/Extensions/Unity/EGIDHolderImplementor.cs +++ /dev/null @@ -1,32 +0,0 @@ -#if UNITY_5 || UNITY_5_3_OR_NEWER -using Svelto.ECS.Hybrid; -using UnityEngine; - -namespace Svelto.ECS.Extensions.Unity -{ - public interface IEGIDHolder - { - EGID ID { set; } - } - - public struct EGIDTrackerViewComponent : IEntityViewComponent - { -#pragma warning disable 649 - public IEGIDHolder holder; -#pragma warning restore 649 - - EGID _ID; - - public EGID ID - { - get => _ID; - set => _ID = holder.ID = value; - } - } - - public class EGIDHolderImplementor : MonoBehaviour, IEGIDHolder, IImplementor - { - public EGID ID { get; set; } - } -} -#endif \ No newline at end of file diff --git a/Extensions/Unity/EGIDHolderImplementor.cs.meta b/Extensions/Unity/EGIDHolderImplementor.cs.meta deleted file mode 100644 index 7ff0349..0000000 --- a/Extensions/Unity/EGIDHolderImplementor.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 68093a2de8de38699ab6faab2d1b33f9 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Extensions/Unity/EntityDescriptorHolderHelper.cs b/Extensions/Unity/EntityDescriptorHolderHelper.cs deleted file mode 100644 index 087d755..0000000 --- a/Extensions/Unity/EntityDescriptorHolderHelper.cs +++ /dev/null @@ -1,30 +0,0 @@ -#if UNITY_5 || UNITY_5_3_OR_NEWER -using Svelto.ECS.Hybrid; -using UnityEngine; - -namespace Svelto.ECS.Extensions.Unity -{ - public static class EntityDescriptorHolderHelper - { - public static EntityInitializer CreateEntity(this Transform contextHolder, EGID ID, - IEntityFactory factory, out T holder) - where T : MonoBehaviour, IEntityDescriptorHolder - { - holder = contextHolder.GetComponentInChildren(true); - var implementors = holder.GetComponents(); - - return factory.BuildEntity(ID, holder.GetDescriptor(), implementors); - } - - public static EntityInitializer Create(this Transform contextHolder, EGID ID, - IEntityFactory factory) - where T : MonoBehaviour, IEntityDescriptorHolder - { - var holder = contextHolder.GetComponentInChildren(true); - var implementors = holder.GetComponents(); - - return factory.BuildEntity(ID, holder.GetDescriptor(), implementors); - } - } -} -#endif \ No newline at end of file diff --git a/Extensions/Unity/EntityDescriptorHolderHelper.cs.meta b/Extensions/Unity/EntityDescriptorHolderHelper.cs.meta deleted file mode 100644 index 40144ce..0000000 --- a/Extensions/Unity/EntityDescriptorHolderHelper.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 7f978d50a34c395bbf491f0e440a51ff -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Extensions/Unity/GameObjects.meta b/Extensions/Unity/GameObjects.meta new file mode 100644 index 0000000..0a7039f --- /dev/null +++ b/Extensions/Unity/GameObjects.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: f07454ee7f173c2a89e063627ee262cd +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Extensions/Unity/GameObjects/AbstractionLayer.meta b/Extensions/Unity/GameObjects/AbstractionLayer.meta new file mode 100644 index 0000000..5b6ac3f --- /dev/null +++ b/Extensions/Unity/GameObjects/AbstractionLayer.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 0c4bbc498c0e37a9964adcc0511d8e73 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Extensions/Unity/GameObjects/AbstractionLayer/GOManager.cs b/Extensions/Unity/GameObjects/AbstractionLayer/GOManager.cs new file mode 100644 index 0000000..5fbcf47 --- /dev/null +++ b/Extensions/Unity/GameObjects/AbstractionLayer/GOManager.cs @@ -0,0 +1,31 @@ +#if UNITY_5 || UNITY_5_3_OR_NEWER +using Svelto.DataStructures; +using UnityEngine; + +namespace Svelto.ECS.Extensions.Unity +{ + ///The class that wrap the OOP library must be known by the package only as much as possible. + ///what cannot be use through the package, is exposed through a public interface. Note though that this may + ///be considered a work around to better design. In this case, no methods are exposed. + public class GOManager + { + public uint RegisterEntity(PrimitiveType type) + { + var cubeObject = GameObject.CreatePrimitive(type); + + objects.Add(cubeObject.transform); + + return (uint) (objects.count - 1); + } + + public void SetParent(uint index, in uint parent) + { + objects[index].SetParent(objects[parent], false); + } + + public void SetPosition(uint index, in Vector3 position) { objects[index].localPosition = position; } + + readonly FasterList objects = new FasterList(); + } +} +#endif \ No newline at end of file diff --git a/Extensions/Unity/GameObjects/AbstractionLayer/GOManager.cs.meta b/Extensions/Unity/GameObjects/AbstractionLayer/GOManager.cs.meta new file mode 100644 index 0000000..afdf7d4 --- /dev/null +++ b/Extensions/Unity/GameObjects/AbstractionLayer/GOManager.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 83928a155e5a36c3821f33fcc9ea7a83 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Extensions/Unity/GameObjects/EntityDescriptorHolder.cs b/Extensions/Unity/GameObjects/EntityDescriptorHolder.cs new file mode 100644 index 0000000..1ee8d37 --- /dev/null +++ b/Extensions/Unity/GameObjects/EntityDescriptorHolder.cs @@ -0,0 +1,69 @@ +#if UNITY_5 || UNITY_5_3_OR_NEWER +using System; +using System.Collections.Generic; +using Svelto.ECS.Hybrid; +using UnityEngine; + +namespace Svelto.ECS.Extensions.Unity +{ + public class ListToPopupAttribute : PropertyAttribute + { + public Type classType; + public string listName; + + public ListToPopupAttribute(Type classType, string listName) + { + this.classType = classType; + this.listName = listName; + } + } + /// + /// I introduced this option thinking it could be a good idea, but I am not sure anymore. Although it's slightly + /// more annoying, extending GenericEntityDescriptorHolder is wiser than using this class. + /// Consider this experimental + /// Todo: sort in alphabetic order + /// Todo: hide inner descriptors + /// + public class EntityDescriptorHolder : MonoBehaviour, IEntityDescriptorHolder + { + public IEntityDescriptor GetDescriptor() { return type; } + + public string groupName => _groupName; + public ushort id => _id; + + [Tooltip( + "it's possible to name groups and query group by name. This entity will be created in a named group if inserted")] + [SerializeField] + string _groupName; + + [Tooltip("this entity will be created with the selected ID, if inserted. An ID must be unique in each group")] + [SerializeField] + ushort _id; + + [Tooltip("choose the entity type, not optional")] + [ListToPopup(typeof(EntityDescriptorHolder), "DescriptorList")] + [SerializeField] + string Descriptor; + + internal IEntityDescriptor type; + + static List DescriptorList = new List(); + + static EntityDescriptorHolder() + { + var assemblies = AssemblyUtility.GetCompatibleAssemblies(); + Type d1 = typeof(IEntityDescriptor); + + foreach (var assembly in assemblies) + foreach (Type type in AssemblyUtility.GetTypesSafe(assembly)) + { + if (type != null && d1.IsAssignableFrom(type) && type.IsAbstract == false) + { + DescriptorList.Add(type); + } + } + } + } +} + +#endif \ No newline at end of file diff --git a/Extensions/Unity/GameObjects/EntityDescriptorHolder.cs.meta b/Extensions/Unity/GameObjects/EntityDescriptorHolder.cs.meta new file mode 100644 index 0000000..1d052d3 --- /dev/null +++ b/Extensions/Unity/GameObjects/EntityDescriptorHolder.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 016caa9b887631ad8d8b5fcc990919b6 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Extensions/Unity/GenericEntityDescriptorHolder.cs b/Extensions/Unity/GameObjects/GenericEntityDescriptorHolder.cs similarity index 91% rename from Extensions/Unity/GenericEntityDescriptorHolder.cs rename to Extensions/Unity/GameObjects/GenericEntityDescriptorHolder.cs index 42b21e5..c000153 100644 --- a/Extensions/Unity/GenericEntityDescriptorHolder.cs +++ b/Extensions/Unity/GameObjects/GenericEntityDescriptorHolder.cs @@ -1,9 +1,10 @@ #if UNITY_5 || UNITY_5_3_OR_NEWER +using Svelto.ECS.Hybrid; using UnityEngine; namespace Svelto.ECS.Extensions.Unity { - public abstract class GenericEntityDescriptorHolder: MonoBehaviour , IEntityDescriptorHolder + public abstract class GenericEntityDescriptorHolder: MonoBehaviour, IEntityDescriptorHolder where T: IEntityDescriptor, new() { public IEntityDescriptor GetDescriptor() diff --git a/Extensions/Unity/GenericEntityDescriptorHolder.cs.meta b/Extensions/Unity/GameObjects/GenericEntityDescriptorHolder.cs.meta similarity index 83% rename from Extensions/Unity/GenericEntityDescriptorHolder.cs.meta rename to Extensions/Unity/GameObjects/GenericEntityDescriptorHolder.cs.meta index cf17134..5c84575 100644 --- a/Extensions/Unity/GenericEntityDescriptorHolder.cs.meta +++ b/Extensions/Unity/GameObjects/GenericEntityDescriptorHolder.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 535e7ebbe2563151bc3ff08e158ea1c9 +guid: 2bc22389e1143027a0f0698f31153733 MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/Extensions/Unity/GameObjects/Implementors.meta b/Extensions/Unity/GameObjects/Implementors.meta new file mode 100644 index 0000000..2a8414e --- /dev/null +++ b/Extensions/Unity/GameObjects/Implementors.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: f60f2aa405cf349fb3e8739522027cf1 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Extensions/Unity/GameObjects/Implementors/EntityReferenceHolderImplementor.cs b/Extensions/Unity/GameObjects/Implementors/EntityReferenceHolderImplementor.cs new file mode 100644 index 0000000..2957acf --- /dev/null +++ b/Extensions/Unity/GameObjects/Implementors/EntityReferenceHolderImplementor.cs @@ -0,0 +1,13 @@ +#if UNITY_5 || UNITY_5_3_OR_NEWER +using Svelto.ECS.Hybrid; +using UnityEngine; + +namespace Svelto.ECS.Extensions.Unity +{ + [DisallowMultipleComponent] + public class EntityReferenceHolderImplementor : MonoBehaviour, IImplementor + { + public EntityReference reference { get; set; } + } +} +#endif \ No newline at end of file diff --git a/Extensions/Unity/GameObjects/Implementors/EntityReferenceHolderImplementor.cs.meta b/Extensions/Unity/GameObjects/Implementors/EntityReferenceHolderImplementor.cs.meta new file mode 100644 index 0000000..4e31f24 --- /dev/null +++ b/Extensions/Unity/GameObjects/Implementors/EntityReferenceHolderImplementor.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4d6a79e76dce399eb4dbb2ddf9c62b9e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Extensions/Unity/GameObjects/ListToPopupDrawer.cs b/Extensions/Unity/GameObjects/ListToPopupDrawer.cs new file mode 100644 index 0000000..0351ef1 --- /dev/null +++ b/Extensions/Unity/GameObjects/ListToPopupDrawer.cs @@ -0,0 +1,40 @@ +#if UNITY_EDITOR +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using Svelto.ECS; +using Svelto.ECS.Extensions.Unity; +using UnityEditor; +using UnityEngine; + +[CustomPropertyDrawer(typeof(ListToPopupAttribute))] +public class ListToPopupDrawer : PropertyDrawer +{ + public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) + { + ListToPopupAttribute atb = attribute as ListToPopupAttribute; + List stringList = null; + + if (atb.classType.GetField(atb.listName, BindingFlags.Static | BindingFlags.NonPublic) != null) + { + stringList = atb.classType.GetField(atb.listName, BindingFlags.Static | BindingFlags.NonPublic).GetValue(atb.classType) as List; + } + + if (stringList != null && stringList.Count != 0) + { + int selectedIndex = Mathf.Max(0, stringList.FindIndex(t => t.Name == property.stringValue)); + + selectedIndex = EditorGUI.Popup(position, property.name, selectedIndex, stringList.Select(t => t.Name).ToArray()); + + property.stringValue = stringList[selectedIndex].Name; + (property.serializedObject.targetObject as EntityDescriptorHolder).type = + Activator.CreateInstance(stringList[selectedIndex]) as IEntityDescriptor; + } + else + { + EditorGUI.TextArea(position, "Error - no valid entity descriptors found"); + } + } +} +#endif \ No newline at end of file diff --git a/Extensions/Unity/GameObjects/ListToPopupDrawer.cs.meta b/Extensions/Unity/GameObjects/ListToPopupDrawer.cs.meta new file mode 100644 index 0000000..be09244 --- /dev/null +++ b/Extensions/Unity/GameObjects/ListToPopupDrawer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 174e88edc15232a883f1c7c3cbf9b799 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Extensions/Unity/SveltoGUIHelper.cs b/Extensions/Unity/GameObjects/SveltoGUIHelper.cs similarity index 56% rename from Extensions/Unity/SveltoGUIHelper.cs rename to Extensions/Unity/GameObjects/SveltoGUIHelper.cs index b639404..36a5026 100644 --- a/Extensions/Unity/SveltoGUIHelper.cs +++ b/Extensions/Unity/GameObjects/SveltoGUIHelper.cs @@ -5,21 +5,16 @@ 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 + 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); @@ -34,9 +29,9 @@ namespace Svelto.ECS.Extensions.Unity childImplementors = monoBehaviour.GetComponents(); else childImplementors = monoBehaviour.GetComponentsInChildren(true); - - startIndex = InternalBuildAll(startIndex, child, factory, group, childImplementors, - groupNamePostfix); + + startIndex = InternalBuildAll(startIndex, child, factory, group, childImplementors + , groupNamePostfix); } } @@ -47,15 +42,9 @@ namespace Svelto.ECS.Extensions.Unity /// 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 + public static uint CreateAll + (uint startIndex, ExclusiveGroup group, Transform contextHolder, IEntityFactory factory + , string groupNamePostfix = null) where T : MonoBehaviour, IEntityDescriptorHolder { var holders = contextHolder.GetComponentsInChildren(true); @@ -75,84 +64,36 @@ namespace Svelto.ECS.Extensions.Unity return startIndex; } - static string Path(Transform go) - { - string s = go.name; - while (go.parent != null) - { - go = go.parent; - s = go.name + "/" + s; - } - return s; - } - - public static EntityInitializer Create(EGID ID, Transform contextHolder, IEntityFactory factory, - out T holder, bool searchImplementorsInChildren = false) - where T : MonoBehaviour, IEntityDescriptorHolder + 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) ; + + 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) + public static EntityInitializer Create + (EGID ID, Transform contextHolder, IEntityFactory factory, bool searchImplementorsInChildren = false) where T : MonoBehaviour, IEntityDescriptorHolder { - var 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); - } - - 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; + 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, basically used only by ControlsScreenRowFactory. Not sure what the real - /// use case is. + /// This is a very specific case and I still need to decide if I want it in the framework /// - /// - /// The group to match - /// - /// - /// EntityDescriptorHolder type - /// Next available ID - public static uint CreateAllInMatchingGroup(uint startId, ExclusiveGroup exclusiveGroup, - Transform contextHolder, IEntityFactory factory) where T : MonoBehaviour, IEntityDescriptorHolder + public static uint CreateAllInMatchingGroup + (uint startId, ExclusiveGroup exclusiveGroup, Transform contextHolder, IEntityFactory factory) + where T : MonoBehaviour, IEntityDescriptorHolder { var holders = contextHolder.GetComponentsInChildren(true); @@ -176,6 +117,45 @@ namespace Svelto.ECS.Extensions.Unity 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 +#endif \ No newline at end of file diff --git a/Extensions/Unity/SveltoGUIHelper.cs.meta b/Extensions/Unity/GameObjects/SveltoGUIHelper.cs.meta similarity index 83% rename from Extensions/Unity/SveltoGUIHelper.cs.meta rename to Extensions/Unity/GameObjects/SveltoGUIHelper.cs.meta index 91734d5..ed11374 100644 --- a/Extensions/Unity/SveltoGUIHelper.cs.meta +++ b/Extensions/Unity/GameObjects/SveltoGUIHelper.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: f135b96c066236da96a194b58ed9acf4 +guid: 381ebd24db5a3c859eb7f64e531799a1 MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/Extensions/Unity/MonoScheduler.cs b/Extensions/Unity/MonoScheduler.cs new file mode 100644 index 0000000..c0b50bc --- /dev/null +++ b/Extensions/Unity/MonoScheduler.cs @@ -0,0 +1,35 @@ +#if UNITY_5 || UNITY_5_3_OR_NEWER +using System.Collections; +using UnityEngine; + +namespace Svelto.ECS.Schedulers.Unity +{ + class MonoScheduler : MonoBehaviour + { + public MonoScheduler() + { + _coroutine = Coroutine(); + } + + void Update() + { + _coroutine.MoveNext(); + } + + IEnumerator Coroutine() + { + while (true) + { + yield return _wait; + + onTick(); + } + } + + readonly WaitForEndOfFrame _wait = new WaitForEndOfFrame(); + readonly IEnumerator _coroutine; + + internal System.Action onTick; + } +} +#endif \ No newline at end of file diff --git a/Extensions/Unity/MonoScheduler.cs.meta b/Extensions/Unity/MonoScheduler.cs.meta new file mode 100644 index 0000000..19a5f44 --- /dev/null +++ b/Extensions/Unity/MonoScheduler.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ba3a7db4cabf3537b4fb9388efd1ab6f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Extensions/Unity/UnityEntitiesSubmissionScheduler.cs b/Extensions/Unity/UnityEntitiesSubmissionScheduler.cs index d33078c..a44bf43 100644 --- a/Extensions/Unity/UnityEntitiesSubmissionScheduler.cs +++ b/Extensions/Unity/UnityEntitiesSubmissionScheduler.cs @@ -1,7 +1,5 @@ #if UNITY_5 || UNITY_5_3_OR_NEWER -using System; using Object = UnityEngine.Object; -using System.Collections; using UnityEngine; namespace Svelto.ECS.Schedulers.Unity @@ -10,37 +8,9 @@ namespace Svelto.ECS.Schedulers.Unity //You can customize the scheduler if you wish public class UnityEntitiesSubmissionScheduler : EntitiesSubmissionScheduler { - class Scheduler : MonoBehaviour - { - public Scheduler() - { - _coroutine = Coroutine(); - } - - void Update() - { - _coroutine.MoveNext(); - } - - IEnumerator Coroutine() - { - while (true) - { - yield return _wait; - - onTick(); - } - } - - readonly WaitForEndOfFrame _wait = new WaitForEndOfFrame(); - readonly IEnumerator _coroutine; - - public System.Action onTick; - } - public UnityEntitiesSubmissionScheduler(string name) { - _scheduler = new GameObject(name).AddComponent(); + _scheduler = new GameObject(name).AddComponent(); GameObject.DontDestroyOnLoad(_scheduler.gameObject); _scheduler.onTick = SubmitEntities; } @@ -59,8 +29,10 @@ namespace Svelto.ECS.Schedulers.Unity { if (paused == false) { - var enumerator = _onTick.Invoke(UInt32.MaxValue); - while (enumerator.MoveNext()) ; + var enumerator = _onTick.submitEntities; + enumerator.MoveNext(); + + while (enumerator.Current == true) enumerator.MoveNext(); } } @@ -69,7 +41,7 @@ namespace Svelto.ECS.Schedulers.Unity set => _onTick = value; } - readonly Scheduler _scheduler; + readonly MonoScheduler _scheduler; EnginesRoot.EntitiesSubmitter _onTick; } } diff --git a/README.md b/README.md index ab027cc..40d604f 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,10 @@ -# Svelto Entity Component System 3.0 +# Svelto Entity Component System 3.x ===================================== Real ECS framework for c\#. Enables to write encapsulated, decoupled, maintainable, highly efficient, data oriented, cache friendly, code without pain. Although the framework is platform agnostic \(compatible with c\# 7 and .net standard 2.0\), it comes with several Unity extensions. ## Why using Svelto.ECS with Unity? -Svelto.ECS wasn't born just from the needs of a large team, but also as a result of years of reasoning behind software engineering applied to game development. Svelto.ECS hasn't been written just to develop faster code, it has been designed to help develop better code. Performance gains is just one of the benefits in using Svelto.ECS, as ECS is a great way to write cache-friendly code. Svelto.ECS has been developed with the idea of ECS being a paradigm and not just a pattern, letting the user shift completely away from Object Oriented Programming with consequent improvements of the code design and code maintainability. Svelto.ECS is the result of years of iteration of the ECS paradigm applied to real game development with the intent to be as foolproof as possible. +Svelto.ECS wasn't born just from the needs of a large team, but also as a result of years of reasoning behind software engineering applied to game development. Svelto.ECS hasn't been written just to develop faster code, it has been designed to help develop better code. Performance gains is just one of the benefits in using Svelto.ECS, as ECS is a great way to write cache-friendly code. Svelto.ECS has been developed with the idea of ECS being a paradigm and not just a pattern, letting the user shift completely away from Object Oriented Programming with consequent improvements of the code design and code maintainability. Svelto.ECS is the result of years of iteration of the ECS paradigm applied to real game development with the intent to be as foolproof as possible. Svelto.ECS has been designed to be used by a medium-size/large team working on long term projects where the cost of maintainability is relevant. Svelto.ECS can be educative for all kind of developers, but small teams must take in consideration the learning curve/cost of ECS in general and Svelto.ECS in particular. ## How to clone the repository: The folders Svelto.ECS and Svelto.Common, where present, are submodules pointing to the relative repositories. If you find them empty, you need to update them through the submodule command. Check some instructions here: https://github.com/sebas77/Svelto.ECS.Vanilla.Example/wiki @@ -17,11 +17,15 @@ read this article for more information: http://www.sebaslab.com/distributing-sve Unity Package System has a big deficiency when it comes to dll dependency solving: two packages cannot point to the same dependency from different sources. Since Unity Collection removed the Unsafe.dll dependency, I had to distribute my own package. This means that if you want to use Svelto from UPM, you will need Svelto unsafe dll to be the only unsafe dll in the project. Otherwise you just download the source code and solve dependencies manually. -## Official Examples +## Official Examples (A.K.A. where is the documentation?) + +Svelto doesn't have official documentation. The reason is that documentation is costly to mantain and...I reckon people don't need it, thanks to the highly documented and simple mini-examples. Please check and study them all regardless the platform you intend to use Svelto with. ### * **Mini Examples**: [https://github.com/sebas77/Svelto.MiniExamples](https://github.com/sebas77/Svelto.MiniExamples) \(including articles\) * **Unit Tests**: [https://github.com/sebas77/Svelto.ECS.Tests](https://github.com/sebas77/Svelto.ECS.Tests) +After that, you can get all the help you need from the official chat: + **Official Discord Server \(join to get help from me for free!\)** * [https://discord.gg/3qAdjDb](https://discord.gg/3qAdjDb) @@ -30,7 +34,7 @@ Unity Package System has a big deficiency when it comes to dll dependency solvin **Framework articles:** -* [Svelto ECS 3.0 is finally here](https://www.sebaslab.com/whats-new-in-svelto-ecs-3-0/) \(re-introducing svelto\) +* [Svelto ECS 3.0 is finally here](https://www.sebaslab.com/whats-new-in-svelto-ecs-3-0/) \(re-introducing svelto, this article is important for starters!\) * [Introducing Svelto ECS 2.9](http://www.sebaslab.com/introducing-svelto-ecs-2-9/) \(shows what's changed since 2.8\) * [Introducing Svelto ECS 2.8](http://www.sebaslab.com/introducing-svelto-ecs-2-8/) \(shows what's changed since 2.7\) * [Svelto.ECS 2.7: what’s new and best practices](http://www.sebaslab.com/svelto-2-7-whats-new-and-best-practices/) \(shows what's changed since 2.5\) @@ -40,6 +44,7 @@ Unity Package System has a big deficiency when it comes to dll dependency solvin **Theory related articles \(in order of publishing date\):** +* [OOP abstraction layer in an ECS-centric application](https://www.sebaslab.com/oop-abstraction-layer-in-a-ecs-centric-application/) \(this article is important for starters!\) * [Inversion of Control with Unity – part 1](http://www.sebaslab.com/ioc-container-for-unity3d-part-1/) * [Inversion of Control with Unity – part 2](http://www.sebaslab.com/ioc-container-for-unity3d-part-2/) * [The truth behind Inversion of Control – Part I – Dependency Injection](http://www.sebaslab.com/the-truth-behind-inversion-of-control-part-i-dependency-injection/) @@ -48,7 +53,6 @@ Unity Package System has a big deficiency when it comes to dll dependency solvin * [The truth behind Inversion of Control – Part IV – Dependency Inversion Principle](http://www.sebaslab.com/the-truth-behind-inversion-of-control-part-iv-dependency-inversion-principle/) * [The truth behind Inversion of Control – Part V – Entity Component System design to achieve true Inversion of Flow Control](http://www.sebaslab.com/the-truth-behind-inversion-of-control-part-v-drifting-away-from-ioc-containers/) * [The Quest for Maintainable Code and The Path to ECS](http://www.sebaslab.com/the-quest-for-maintainable-code-and-the-path-to-ecs/) -* [OOP abstraction layer in an ECS-centric application](https://www.sebaslab.com/oop-abstraction-layer-in-a-ecs-centric-application/) **Practical articles** @@ -79,10 +83,11 @@ Completely outdated and could even mislead. Feel free to update it if you have a ## I like the project, how can I help? -Hey, thanks a lot for considering this. You can help in several ways. The simplest is to talk about Svelto.ECS and spread the word, the more we are, the better it is for the community. Then you can help with the documentation, updating the wiki or writing your own articles. Svelto.ECS has all the features needed to make a game with the ECS pattern, but some areas are lacking: *A visual debugger and more unit tests are needed*. Other platforms other than Unity could get some love too: Stride Game, Godot, monogame, FNA or whatever supports c#. Porting to other languages, especially c++, would be awesome but probably pointless. Please check the lane dedicated to the community tasks list here: https://github.com/sebas77/Svelto.ECS/projects/1 and let me know if you want to take something on! +Hey, thanks a lot for considering this. You can help in several ways. The simplest is to talk about Svelto.ECS and spread the word, the more we are, the better it is for the community. Then you can help with the documentation, updating the wiki or writing your own articles. Svelto.ECS has all the features needed to make a game with the ECS pattern, but some areas are lacking: *A visual debugger and more unit tests are needed*. Other platforms other than Unity could get some love too: Stride Game, Godot, monogame, FNA or whatever supports c#. Porting to other languages, especially c++, would be awesome but probably pointless. Please check the lane dedicated to the community tasks list here: https://github.com/users/sebas77/projects/3 and let me know if you want to take something on! ## Svelto Framework is used to develop the following products\(\*\): +![image](https://user-images.githubusercontent.com/945379/123062411-65ee3600-d404-11eb-8dca-d30c28ed909d.png) ![Gamecraft](https://steamcdn-a.akamaihd.net/steamcommunity/public/images/clans/35037633/e05ca4fc6f20f1e6150a6ace1d12fe8cd145fa0d.png) ![Robocraft Infinity](https://i.ytimg.com/vi/m_4fpgHwoBs/maxresdefault.jpg) ![Cardlife](https://i.ytimg.com/vi/q2jaUZjnNyg/maxresdefault.jpg) diff --git a/Serialization/DefaultVersioningFactory.cs b/Serialization/DefaultVersioningFactory.cs index 917b52f..484740c 100644 --- a/Serialization/DefaultVersioningFactory.cs +++ b/Serialization/DefaultVersioningFactory.cs @@ -1,27 +1,18 @@ -using System.Collections.Generic; using Svelto.Common; namespace Svelto.ECS.Serialization { + //TODO: Unit test. Delete this comment once Unit test is written public class DefaultVersioningFactory : IDeserializationFactory where T : IEntityDescriptor, new() { - readonly IEnumerable _implementors; - - public DefaultVersioningFactory() {} - - public DefaultVersioningFactory(IEnumerable implementors) - { - _implementors = implementors; - } - public EntityInitializer BuildDeserializedEntity (EGID egid, ISerializationData serializationData, ISerializableEntityDescriptor entityDescriptor , int serializationType, IEntitySerialization entitySerialization, IEntityFactory factory , bool enginesRootIsDeserializationOnly) { - var entityDescriptorEntitiesToSerialize = enginesRootIsDeserializationOnly ? entityDescriptor.entitiesToSerialize : entityDescriptor.componentsToBuild; + var entityDescriptorEntitiesToSerialize = enginesRootIsDeserializationOnly ? entityDescriptor.componentsToSerialize : entityDescriptor.componentsToBuild; - var initializer = factory.BuildEntity(egid, entityDescriptorEntitiesToSerialize, TypeCache.type, _implementors); + var initializer = factory.BuildEntity(egid, entityDescriptorEntitiesToSerialize, TypeCache.type); entitySerialization.DeserializeEntityComponents(serializationData, entityDescriptor, ref initializer , serializationType); diff --git a/Serialization/EnginesRoot.GenericEntitySerialization.cs b/Serialization/EnginesRoot.GenericEntitySerialization.cs index b2a873d..8fa80ee 100644 --- a/Serialization/EnginesRoot.GenericEntitySerialization.cs +++ b/Serialization/EnginesRoot.GenericEntitySerialization.cs @@ -16,9 +16,9 @@ namespace Svelto.ECS ref var serializableEntityComponent = ref entitiesDb.QueryEntity(egid); uint descriptorHash = serializableEntityComponent.descriptorHash; - SerializationDescriptorMap serializationDescriptorMap = _enginesRoot.serializationDescriptorMap; + SerializationDescriptorMap serializationDescriptorMap = _enginesRoot._serializationDescriptorMap; var entityDescriptor = serializationDescriptorMap.GetDescriptorFromHash(descriptorHash); - var entityComponentsToSerialise = entityDescriptor.entitiesToSerialize; + var entityComponentsToSerialise = entityDescriptor.componentsToSerialize; var header = new SerializableEntityHeader(descriptorHash, egid, (byte) entityComponentsToSerialise.Length); @@ -40,7 +40,7 @@ namespace Svelto.ECS var serializableEntityHeader = new SerializableEntityHeader(serializationData); uint descriptorHash = serializableEntityHeader.descriptorHash; - SerializationDescriptorMap serializationDescriptorMap = _enginesRoot.serializationDescriptorMap; + SerializationDescriptorMap serializationDescriptorMap = _enginesRoot._serializationDescriptorMap; var entityDescriptor = serializationDescriptorMap.GetDescriptorFromHash(descriptorHash); IDeserializationFactory factory = serializationDescriptorMap.GetSerializationFactory(descriptorHash); @@ -69,27 +69,40 @@ namespace Svelto.ECS (ISerializationData serializationData, ISerializableEntityDescriptor entityDescriptor , ref EntityInitializer initializer, int serializationType) { - foreach (var serializableEntityBuilder in entityDescriptor.entitiesToSerialize) + foreach (var serializableEntityBuilder in entityDescriptor.componentsToSerialize) { serializationData.BeginNextEntityComponent(); serializableEntityBuilder.Deserialize(serializationData, initializer, serializationType); } } + /// + /// Note this has been left undocumented and forgot over the months. The initial version was obviously + /// wrong, as it wasn't looking for T but only assuming that T was the first component in the entity. + /// It's also weird or at least must be revalidated, the fact that serializationData works only as + /// a tape, so we need to reset datapos in case we do not want to forward the head. + /// + /// + /// + /// + /// + /// 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.entitiesToSerialize) + foreach (var serializableEntityBuilder in entityDescriptor.componentsToSerialize) { if (serializableEntityBuilder is SerializableComponentBuilder entityBuilder) { entityBuilder.Deserialize(serializationData, ref entityComponent, serializationType); - } - break; + break; + } + else + serializationData.dataPos += serializableEntityBuilder.Size(serializationType); } serializationData.dataPos = readPos; @@ -102,7 +115,7 @@ namespace Svelto.ECS ref var serializableEntityComponent = ref entitiesDb.QueryEntity(localEgid); - SerializationDescriptorMap serializationDescriptorMap = _enginesRoot.serializationDescriptorMap; + SerializationDescriptorMap serializationDescriptorMap = _enginesRoot._serializationDescriptorMap; uint descriptorHash = serializableEntityComponent.descriptorHash; var entityDescriptor = serializationDescriptorMap.GetDescriptorFromHash(descriptorHash); @@ -122,7 +135,7 @@ namespace Svelto.ECS ref var serializableEntityComponent = ref entitiesDB.QueryEntity(egid); uint descriptorHash = serializableEntityComponent.descriptorHash; - SerializationDescriptorMap serializationDescriptorMap = _enginesRoot.serializationDescriptorMap; + SerializationDescriptorMap serializationDescriptorMap = _enginesRoot._serializationDescriptorMap; var entityDescriptor = serializationDescriptorMap.GetDescriptorFromHash(descriptorHash); _enginesRoot.CheckRemoveEntityID(egid, entityDescriptor.realType); @@ -134,10 +147,20 @@ namespace Svelto.ECS _enginesRoot.QueueEntitySubmitOperation(entitySubmitOperation); } + public uint GetHashFromGroup(ExclusiveGroupStruct groupStruct) + { + return GroupHashMap.GetHashFromGroup(groupStruct); + } + + public ExclusiveGroupStruct GetGroupFromHash(uint groupHash) + { + return GroupHashMap.GetGroupFromHash(groupHash); + } + public void RegisterSerializationFactory(IDeserializationFactory deserializationFactory) where T : ISerializableEntityDescriptor, new() { - SerializationDescriptorMap serializationDescriptorMap = _enginesRoot.serializationDescriptorMap; + SerializationDescriptorMap serializationDescriptorMap = _enginesRoot._serializationDescriptorMap; serializationDescriptorMap.RegisterSerializationFactory(deserializationFactory); } @@ -161,14 +184,14 @@ namespace Svelto.ECS (ISerializationData serializationData, EGID egid, SerializableEntityHeader serializableEntityHeader , int serializationType) { - SerializationDescriptorMap descriptorMap = _enginesRoot.serializationDescriptorMap; + SerializationDescriptorMap descriptorMap = _enginesRoot._serializationDescriptorMap; var entityDescriptor = descriptorMap.GetDescriptorFromHash(serializableEntityHeader.descriptorHash); if (_enginesRoot._groupEntityComponentsDB.TryGetValue(egid.groupID, out var entitiesInGroupPerType) == false) throw new Exception("Entity Serialization failed"); - foreach (var serializableEntityBuilder in entityDescriptor.entitiesToSerialize) + foreach (var serializableEntityBuilder in entityDescriptor.componentsToSerialize) { entitiesInGroupPerType.TryGetValue( new RefWrapperType(serializableEntityBuilder.GetEntityComponentType()), out var safeDictionary); diff --git a/Serialization/EnginesRoot.SerializableEntityHeader.cs b/Serialization/EnginesRoot.SerializableEntityHeader.cs index 92cf959..35913a0 100644 --- a/Serialization/EnginesRoot.SerializableEntityHeader.cs +++ b/Serialization/EnginesRoot.SerializableEntityHeader.cs @@ -59,7 +59,7 @@ namespace Svelto.ECS serializationData.data[serializationData.dataPos++] = (byte) ((entityID >> 24) & 0xff); // Splitting the groupID (uint, 32 bit) into four bytes. - uint groupID = egid.groupID; + uint groupID = (uint) egid.groupID; serializationData.data[serializationData.dataPos++] = (byte) (groupID & 0xff); serializationData.data[serializationData.dataPos++] = (byte) ((groupID >> 8) & 0xff); serializationData.data[serializationData.dataPos++] = (byte) ((groupID >> 16) & 0xff); diff --git a/Serialization/EntitiesDB.SerializationDescriptorMap.cs b/Serialization/EntitiesDB.SerializationDescriptorMap.cs index 3e14cdb..7353e63 100644 --- a/Serialization/EntitiesDB.SerializationDescriptorMap.cs +++ b/Serialization/EntitiesDB.SerializationDescriptorMap.cs @@ -8,8 +8,23 @@ namespace Svelto.ECS { partial class EnginesRoot { + /// + /// The map of serializable entity hashes to the serializable entity builders (to know the entity structs + /// to serialize) + /// + readonly SerializationDescriptorMap _serializationDescriptorMap; + sealed class SerializationDescriptorMap { + static readonly Dictionary _descriptors; + static readonly Dictionary _defaultFactories; + + static SerializationDescriptorMap() + { + _descriptors = new Dictionary(); + _defaultFactories = new Dictionary(); + } + /// /// Here we want to register all the EntityDescriptors that need to be serialized for network play. /// @@ -18,17 +33,56 @@ namespace Svelto.ECS /// internal SerializationDescriptorMap() { - _descriptors = new Dictionary(); _factories = new Dictionary(); + + CopyDefaultFactories(); + } + + public ISerializableEntityDescriptor GetDescriptorFromHash(uint descriptorHash) + { +#if DEBUG && !PROFILE_SVELTO + DBC.ECS.Check.Require(_descriptors.ContainsKey(descriptorHash) + , $"Could not find descriptor linked to hash, wrong deserialization size? '{descriptorHash}'!"); +#endif + + return _descriptors[descriptorHash]; + } + public IDeserializationFactory GetSerializationFactory(uint descriptorHash) + { +#if DEBUG && !PROFILE_SVELTO + DBC.ECS.Check.Require(_descriptors.ContainsKey(descriptorHash) + , $"Could not find descriptor linked to descriptor hash, wrong deserialization size? '{descriptorHash}'!"); + DBC.ECS.Check.Require(_factories.ContainsKey(descriptorHash) + , $"Could not find factory linked to hash '{_descriptors[descriptorHash]}'!"); +#endif + return _factories[descriptorHash]; + } + + public void RegisterSerializationFactory(IDeserializationFactory deserializationFactory) + where Descriptor : ISerializableEntityDescriptor, new() + { + _factories[SerializationEntityDescriptorTemplate.hash] = deserializationFactory; + } + + void CopyDefaultFactories() + { + foreach (var factory in _defaultFactories) + { + _factories[factory.Key] = factory.Value; + } + } + + internal static void Init() + { using (new StandardProfiler("Assemblies Scan")) { - Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); + List assemblies = AssemblyUtility.GetCompatibleAssemblies(); - Type d1 = typeof(DefaultVersioningFactory<>); + Type defaultFactory = typeof(DefaultVersioningFactory<>); foreach (Assembly assembly in assemblies) { - foreach (Type type in GetTypesSafe(assembly)) + foreach (Type type in AssemblyUtility.GetTypesSafe(assembly)) { if (type != null && type.IsClass && type.IsAbstract == false && type.BaseType != null && type.BaseType.IsGenericType && type.BaseType.GetGenericTypeDefinition() @@ -36,28 +90,14 @@ namespace Svelto.ECS { var descriptor = Activator.CreateInstance(type) as ISerializableEntityDescriptor; - RegisterEntityDescriptor(descriptor, type, d1); + RegisterEntityDescriptor(descriptor, type, defaultFactory); } } } } - } - - static IEnumerable GetTypesSafe(Assembly assembly) - { - try - { - Type[] types = assembly.GetTypes(); - - return types; - } - catch (ReflectionTypeLoadException e) - { - return e.Types; - } } - void RegisterEntityDescriptor(ISerializableEntityDescriptor descriptor, Type type, Type d1) + static void RegisterEntityDescriptor(ISerializableEntityDescriptor descriptor, Type type, Type d1) { if (descriptor == null) return; @@ -67,52 +107,18 @@ namespace Svelto.ECS #if DEBUG && !PROFILE_SVELTO if (_descriptors.ContainsKey(descriptorHash)) { - throw new Exception($"Hash Collision of '{descriptor.GetType()}' against " + - $"'{_descriptors[descriptorHash]} ::: {descriptorHash}'"); + throw new Exception($"Hash Collision of '{descriptor.GetType()}' against " + + $"'{_descriptors[descriptorHash]} ::: {descriptorHash}'"); } #endif _descriptors[descriptorHash] = descriptor; - Type[] typeArgs = {type}; - var makeGenericType = d1.MakeGenericType(typeArgs); - var instance = Activator.CreateInstance(makeGenericType); - _factories.Add(descriptorHash, instance as IDeserializationFactory); - } - - public ISerializableEntityDescriptor GetDescriptorFromHash(uint descriptorHash) - { -#if DEBUG && !PROFILE_SVELTO - DBC.ECS.Check.Require(_descriptors.ContainsKey(descriptorHash), - $"Could not find descriptor linked to hash, wrong deserialization size? '{ descriptorHash}'!"); -#endif - - return _descriptors[descriptorHash]; + Type[] typeArgs = { type }; + var makeGenericType = d1.MakeGenericType(typeArgs); + var instance = Activator.CreateInstance(makeGenericType); + _defaultFactories.Add(descriptorHash, instance as IDeserializationFactory); } - public IDeserializationFactory GetSerializationFactory(uint descriptorHash) - { -#if DEBUG && !PROFILE_SVELTO - DBC.ECS.Check.Require(_descriptors.ContainsKey(descriptorHash), - $"Could not find descriptor linked to descriptor hash, wrong deserialization size? '{ descriptorHash}'!"); - DBC.ECS.Check.Require(_factories.ContainsKey(descriptorHash), - $"Could not find factory linked to hash '{ _descriptors[descriptorHash]}'!"); -#endif - return _factories[descriptorHash]; - } - - public void RegisterSerializationFactory(IDeserializationFactory deserializationFactory) - where Descriptor : ISerializableEntityDescriptor, new() - { - _factories[SerializationEntityDescriptorTemplate.hash] = deserializationFactory; - } - - readonly Dictionary _descriptors; - readonly Dictionary _factories; + readonly Dictionary _factories; } - - /// - /// The map of serializable entity hashes to the serializable entity builders (to know the entity structs - /// to serialize) - /// - SerializationDescriptorMap serializationDescriptorMap { get; } } -} +} \ No newline at end of file diff --git a/Serialization/IComponentSerializer.cs b/Serialization/IComponentSerializer.cs index 62b3367..a3bcff4 100644 --- a/Serialization/IComponentSerializer.cs +++ b/Serialization/IComponentSerializer.cs @@ -1,6 +1,5 @@ #if DEBUG && !PROFILE_SVELTO #endif - namespace Svelto.ECS.Serialization { public interface IComponentSerializer where T : unmanaged, IEntityComponent @@ -8,6 +7,9 @@ namespace Svelto.ECS.Serialization bool Serialize(in T value, ISerializationData serializationData); bool Deserialize(ref T value, ISerializationData serializationData); + //Todo: We currently use the value 0 (which I am not even sure if it's not ambiguous) to mark an entity as dynamic size. If zero the systems assumes that the first word to deserialize + //is the size of the entity. However this means that this field cannot reliably be used to know the size of the entity before hand, we need to either remove all the ambiguities in + //this sense or find another way to serialise dynamic size entities. uint size { get; } } } \ No newline at end of file diff --git a/Serialization/IEntitySerialization.cs b/Serialization/IEntitySerialization.cs index 686a52c..5bb6ce1 100644 --- a/Serialization/IEntitySerialization.cs +++ b/Serialization/IEntitySerialization.cs @@ -63,6 +63,10 @@ namespace Svelto.ECS.Serialization /// void DeserializeEntityToDelete(EGID egid); + uint GetHashFromGroup(ExclusiveGroupStruct groupStruct); + + ExclusiveGroupStruct GetGroupFromHash(uint groupHash); + void RegisterSerializationFactory(IDeserializationFactory deserializationFactory) where T : ISerializableEntityDescriptor, new(); diff --git a/Serialization/ISerializableComponentBuilder.cs b/Serialization/ISerializableComponentBuilder.cs index a3729a0..2b5ab89 100644 --- a/Serialization/ISerializableComponentBuilder.cs +++ b/Serialization/ISerializableComponentBuilder.cs @@ -12,5 +12,7 @@ namespace Svelto.ECS.Serialization void Deserialize(ISerializationData serializationData, in EntityInitializer initializer , int serializationType); + + uint Size(int serializationType); } } \ No newline at end of file diff --git a/Serialization/ISerializableEntityDescriptor.cs b/Serialization/ISerializableEntityDescriptor.cs index d755ab7..4b54e7b 100644 --- a/Serialization/ISerializableEntityDescriptor.cs +++ b/Serialization/ISerializableEntityDescriptor.cs @@ -5,7 +5,7 @@ namespace Svelto.ECS.Serialization public interface ISerializableEntityDescriptor : IDynamicEntityDescriptor { uint hash { get; } - ISerializableComponentBuilder[] entitiesToSerialize { get; } + ISerializableComponentBuilder[] componentsToSerialize { get; } Type realType { get; } } } \ No newline at end of file diff --git a/Serialization/SerializableComponentBuilder.cs b/Serialization/SerializableComponentBuilder.cs index 6fbdecf..45a9482 100644 --- a/Serialization/SerializableComponentBuilder.cs +++ b/Serialization/SerializableComponentBuilder.cs @@ -16,9 +16,8 @@ namespace Svelto.ECS.Serialization { public static readonly uint SIZE = (uint) MemoryUtilities.SizeOf(); - public void Serialize - (uint entityID, ITypeSafeDictionary dictionary, ISerializationData serializationData - , int serializationType) + public void Serialize(uint entityID, ITypeSafeDictionary dictionary, ISerializationData serializationData + , int serializationType) { IComponentSerializer componentSerializer = _serializers[serializationType]; @@ -36,9 +35,8 @@ namespace Svelto.ECS.Serialization componentSerializer.SerializeSafe(val, serializationData); } - public void Deserialize - (uint entityID, ITypeSafeDictionary dictionary, ISerializationData serializationData - , int serializationType) + public void Deserialize(uint entityID, ITypeSafeDictionary dictionary, ISerializationData serializationData + , int serializationType) { IComponentSerializer componentSerializer = _serializers[(int) serializationType]; @@ -63,6 +61,11 @@ namespace Svelto.ECS.Serialization componentSerializer.DeserializeSafe(ref initializer.GetOrCreate(), serializationData); } + public uint Size(int serializationType) + { + return _serializers[(int) serializationType].size; + } + public void Deserialize (ISerializationData serializationData, ref T entityComponent, int serializationType) { diff --git a/Serialization/SerializableEntityDescriptor.cs b/Serialization/SerializableEntityDescriptor.cs index dd676dd..7878d4b 100644 --- a/Serialization/SerializableEntityDescriptor.cs +++ b/Serialization/SerializableEntityDescriptor.cs @@ -21,8 +21,7 @@ namespace Svelto.ECS.Serialization var hashNameAttribute = Type.GetCustomAttribute(); if (hashNameAttribute == null) { - throw new Exception( - "HashName attribute not found on the serializable type ".FastConcat(Type.FullName)); + throw new Exception("HashName attribute not found on the serializable type ".FastConcat(Type.FullName)); } Hash = DesignatedHash.Hash(Encoding.ASCII.GetBytes(hashNameAttribute._name)); @@ -97,11 +96,11 @@ namespace Svelto.ECS.Serialization public IComponentBuilder[] componentsToBuild => ComponentsToBuild; public uint hash => Hash; public Type realType => Type; - public ISerializableComponentBuilder[] entitiesToSerialize => EntitiesToSerialize; + public ISerializableComponentBuilder[] componentsToSerialize => EntitiesToSerialize; - static readonly IComponentBuilder[] ComponentsToBuild; + static readonly IComponentBuilder[] ComponentsToBuild; static readonly FasterDictionary EntityComponentsToSerializeMap; - static readonly ISerializableComponentBuilder[] EntitiesToSerialize; + static readonly ISerializableComponentBuilder[] EntitiesToSerialize; static readonly uint Hash; static readonly Type SerializableStructType = typeof(SerializableEntityComponent); diff --git a/Svelto.ECS.asmdef b/Svelto.ECS.asmdef index 593ef33..b13bc96 100644 --- a/Svelto.ECS.asmdef +++ b/Svelto.ECS.asmdef @@ -1,68 +1,58 @@ { - "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.burst", - "expression": "", - "define": "UNITY_JOBS" - }, - { - "name": "com.sebaslab.svelto.ecs", - "expression": "", - "define": "UNITY_JOBS" - } - ], - "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" + } + ], + "noEngineReferences": false } \ No newline at end of file diff --git a/Svelto.ECS.csproj b/Svelto.ECS.csproj index 69bc333..4dbdf2c 100644 --- a/Svelto.ECS.csproj +++ b/Svelto.ECS.csproj @@ -1,29 +1,28 @@  - - Svelto.ECS - 7.3 - netstandard2.0 - - - Svelto.ECS - sebas77 - https://github.com/sebas77/Svelto.ECS - - - AnyCPU - false - true - - - AnyCPU - false - true - - - - - - - - + + Svelto.ECS + 8 + netstandard2.0 + + + Svelto.ECS + sebas77 + https://github.com/sebas77/Svelto.ECS + + + AnyCPU + false + true + + + AnyCPU + false + true + + + + + + + \ No newline at end of file diff --git a/Svelto.ECS.nuspec b/Svelto.ECS.nuspec new file mode 100644 index 0000000..86b5ed7 --- /dev/null +++ b/Svelto.ECS.nuspec @@ -0,0 +1,20 @@ + + + + Svelto.ECS + sebas77 + $version$ + C# Lightweight Data Oriented Entity Component System Framework + https://github.com/sebas77/Svelto.ECS + https://raw.githubusercontent.com/sebas77/Svelto.ECS/master/LICENSE + + + + + + + + + + + diff --git a/Svelto.ECS.nuspec.meta b/Svelto.ECS.nuspec.meta new file mode 100644 index 0000000..396c8da --- /dev/null +++ b/Svelto.ECS.nuspec.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: d3b86c23279530aabb231dc5304d6aa0 +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Svelto.ECS.targets b/Svelto.ECS.targets new file mode 100644 index 0000000..ac85f65 --- /dev/null +++ b/Svelto.ECS.targets @@ -0,0 +1,9 @@ + + + + + $(MSBuildThisFileDirectory)../lib/Release/netstandard2.0/Svelto.ECS.dll + $(MSBuildThisFileDirectory)../lib/Debug/netstandard2.0/Svelto.ECS.dll + + + \ No newline at end of file diff --git a/Svelto.ECS.targets.meta b/Svelto.ECS.targets.meta new file mode 100644 index 0000000..0f4eaeb --- /dev/null +++ b/Svelto.ECS.targets.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 863aab68f1d53126a44e0c589655b92c +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/package.json b/package.json index 2c3b1ea..6cf1dec 100644 --- a/package.json +++ b/package.json @@ -3,13 +3,13 @@ "category": "Svelto", "description": "Svelto ECS C# Lightweight Data Oriented Entity Component System Framework", "dependencies": { - "com.sebaslab.svelto.common": "3.1.3" + "com.sebaslab.svelto.common": "3.2.0" }, "keywords": [ "svelto" ], "name": "com.sebaslab.svelto.ecs", - "version": "3.1.3", + "version": "3.2.0", "type": "library", "unity": "2019.3" } \ No newline at end of file