diff --git a/CHANGELOG.md b/CHANGELOG.md index da1b1d3..cd53b4b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,10 @@ # Svelto.ECS Changelog All notable changes to this project will be documented in this file. Changes are listed in random order of importance. -## [3.4.5] - 04-2023 -* improved code after internal data refactoring and fixed some bugs -* Added static DynamicEntityDescriptor method to create a new DynamicEntityDescriptor without passing an array of components -* factory method to build entities without entity descriptor is now internal +## [3.4.6] - 05-2023 + +* SveltoOnDOTS bug fixes/improvements +* Comments and code cleanup ## [3.4.4] - 04-2023 diff --git a/Core/ComponentTypeID.cs b/Core/ComponentTypeID.cs index da40c66..6ecf992 100644 --- a/Core/ComponentTypeID.cs +++ b/Core/ComponentTypeID.cs @@ -20,6 +20,11 @@ namespace Svelto.ECS get => _id.Data; } + /// + /// c# Static constructors are guaranteed to be thread safe + /// The runtime guarantees that a static constructor is only called once. So even if a type is called by multiple threads at the same time, + /// the static constructor is always executed one time. To get a better understanding how this works, it helps to know what purpose it serves. + /// static ComponentTypeID() { Init(); diff --git a/Core/ComponentTypeMap.cs b/Core/ComponentTypeMap.cs index 5f17eb9..860c446 100644 --- a/Core/ComponentTypeMap.cs +++ b/Core/ComponentTypeMap.cs @@ -1,18 +1,19 @@ using System; +using System.Collections.Concurrent; using System.Runtime.CompilerServices; using Svelto.DataStructures; namespace Svelto.ECS { - public static class ComponentTypeMap + static class ComponentTypeMap { - static readonly FasterDictionary, ComponentID> _componentTypeMap = new FasterDictionary, ComponentID>(); - static readonly FasterDictionary _reverseComponentTypeMap = new FasterDictionary(); + static readonly ConcurrentDictionary _componentTypeMap = new ConcurrentDictionary(); + static readonly ConcurrentDictionary _reverseComponentTypeMap = new ConcurrentDictionary(); public static void Add(Type type, ComponentID idData) { - _componentTypeMap.Add(type, idData); - _reverseComponentTypeMap.Add(idData, type); + _componentTypeMap.TryAdd(type, idData); + _reverseComponentTypeMap.TryAdd(idData, type); } [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/Core/EnginesRoot.Engines.cs b/Core/EnginesRoot.Engines.cs index e196789..8892fe3 100644 --- a/Core/EnginesRoot.Engines.cs +++ b/Core/EnginesRoot.Engines.cs @@ -16,8 +16,8 @@ namespace Svelto.ECS { static EnginesRoot() { - EntityDescriptorsWarmup.Init(); - GroupHashMap.Init(); + EntityDescriptorsWarmup.WarmUp(); + GroupHashMap.WarmUp(); //SharedDictonary.Init(); SerializationDescriptorMap.Init(); diff --git a/Core/EntityDescriptor/DynamicEntityDescriptor.cs b/Core/EntityDescriptor/DynamicEntityDescriptor.cs index 0be72db..64f5e7d 100644 --- a/Core/EntityDescriptor/DynamicEntityDescriptor.cs +++ b/Core/EntityDescriptor/DynamicEntityDescriptor.cs @@ -45,7 +45,7 @@ namespace Svelto.ECS public DynamicEntityDescriptor(IComponentBuilder[] extraEntityBuilders) { - this = CreateDynamicEntityDescriptor(); + this = DynamicEntityDescriptor.CreateDynamicEntityDescriptor(); var extraEntitiesLength = extraEntityBuilders.Length; @@ -54,7 +54,7 @@ namespace Svelto.ECS public DynamicEntityDescriptor(FasterList extraEntityBuilders) { - this = CreateDynamicEntityDescriptor(); + this = DynamicEntityDescriptor.CreateDynamicEntityDescriptor(); var extraEntities = extraEntityBuilders.ToArrayFast(out _); var extraEntitiesLength = extraEntityBuilders.count; diff --git a/Core/EntityReference/EnginesRoot.LocatorMap.cs b/Core/EntityReference/EnginesRoot.LocatorMap.cs index dfa9f60..b8c2966 100644 --- a/Core/EntityReference/EnginesRoot.LocatorMap.cs +++ b/Core/EntityReference/EnginesRoot.LocatorMap.cs @@ -173,10 +173,10 @@ namespace Svelto.ECS public bool TryGetEGID(EntityReference reference, out EGID egid) { egid = default; -#if DEBUG && !PROFILE_SVELTO + if (reference == EntityReference.Invalid) return false; -#endif + // Make sure we are querying for the current version of the locator. // Otherwise the locator is pointing to a removed entity. ref var entityReferenceMapElement = ref _entityReferenceMap[reference.index]; diff --git a/Core/Groups/EntityDescriptorsWarmup.cs b/Core/Groups/EntityDescriptorsWarmup.cs index 9d26d97..9e60153 100644 --- a/Core/Groups/EntityDescriptorsWarmup.cs +++ b/Core/Groups/EntityDescriptorsWarmup.cs @@ -5,13 +5,13 @@ using Svelto.ECS.Internal; namespace Svelto.ECS { - public static class EntityDescriptorsWarmup + static class EntityDescriptorsWarmup { /// /// c# Static constructors are guaranteed to be thread safe /// Warmup all EntityDescriptors and ComponentTypeID classes to avoid huge overheads when they are first used /// - internal static void Init() + internal static void WarmUp() { List assemblies = AssemblyUtility.GetCompatibleAssemblies(); foreach (Assembly assembly in assemblies) @@ -30,10 +30,7 @@ namespace Svelto.ECS System.Runtime.CompilerServices.RuntimeHelpers.RunClassConstructor(warmup.TypeHandle); } - catch (Exception e) - { - continue; - } + catch { } } } @@ -51,10 +48,7 @@ namespace Svelto.ECS //this warms up the component builder. There could be different implementation of components builders for the same component type in theory System.Runtime.CompilerServices.RuntimeHelpers.RunClassConstructor(componentType.TypeHandle); } - catch (Exception e) - { - continue; - } + catch { } } } } diff --git a/Core/Groups/GroupCompound.cs b/Core/Groups/GroupCompound.cs index cc1ef6e..9946316 100644 --- a/Core/Groups/GroupCompound.cs +++ b/Core/Groups/GroupCompound.cs @@ -27,7 +27,10 @@ namespace Svelto.ECS { static GroupCompound() { - //avoid race conditions if compounds are using on multiple thread + //avoid race conditions if compounds are using on multiple thread. This shouldn't be necessary though since c# static constructors are guaranteed to be thread safe! + /// c# Static constructors are guaranteed to be thread safe + /// The runtime guarantees that a static constructor is only called once. So even if a type is called by multiple threads at the same time, + /// the static constructor is always executed one time. To get a better understanding how this works, it helps to know what purpose it serves. if (Interlocked.CompareExchange(ref isInitializing, 1, 0) == 0 && GroupCompoundInitializer.skipStaticCompoundConstructorsWith4Tags.Value == false) { diff --git a/Core/Groups/GroupHashMap.cs b/Core/Groups/GroupHashMap.cs index b525fee..bac7438 100644 --- a/Core/Groups/GroupHashMap.cs +++ b/Core/Groups/GroupHashMap.cs @@ -10,8 +10,12 @@ namespace Svelto.ECS { /// /// c# Static constructors are guaranteed to be thread safe + /// The runtime guarantees that a static constructor is only called once. So even if a type is called by multiple threads at the same time, + /// the static constructor is always executed one time. To get a better understanding how this works, it helps to know what purpose it serves. + /// + /// Warmup the group hash map. This will call all the static constructors of the group types /// - internal static void Init() + internal static void WarmUp() { List assemblies = AssemblyUtility.GetCompatibleAssemblies(); foreach (Assembly assembly in assemblies) @@ -26,8 +30,8 @@ namespace Svelto.ECS { CheckForGroupCompounds(type); - if (type != null && type.IsClass && type.IsSealed && - type.IsAbstract) //IsClass and IsSealed and IsAbstract means only static classes + //Search inside static types + if (type != null && type.IsClass && type.IsSealed && type.IsAbstract) //IsClass and IsSealed and IsAbstract means only static classes { var subClasses = type.GetNestedTypes(); @@ -40,8 +44,8 @@ namespace Svelto.ECS foreach (var field in fields) { - if (field.IsStatic - && (typeOfExclusiveGroup.IsAssignableFrom(field.FieldType) + if (field.IsStatic + && (typeOfExclusiveGroup.IsAssignableFrom(field.FieldType) || typeOfExclusiveGroupStruct.IsAssignableFrom(field.FieldType) || typeOfExclusiveBuildGroup.IsAssignableFrom(field.FieldType))) { @@ -52,7 +56,8 @@ namespace Svelto.ECS var group = (ExclusiveGroup)field.GetValue(null); groupIDAndBitMask = ((ExclusiveGroupStruct)@group).ToIDAndBitmask(); } - else if (typeOfExclusiveGroupStruct.IsAssignableFrom(field.FieldType)) + else + if (typeOfExclusiveGroupStruct.IsAssignableFrom(field.FieldType)) { var group = (ExclusiveGroupStruct)field.GetValue(null); groupIDAndBitMask = @group.ToIDAndBitmask(); @@ -94,7 +99,7 @@ namespace Svelto.ECS { if (typeof(ITouchedByReflection).IsAssignableFrom(type)) { - //this wil call the static constructor, but only once. Static constructors won't be called + //this calls the static constructor, but only once. Static constructors won't be called //more than once with this System.Runtime.CompilerServices.RuntimeHelpers.RunClassConstructor(type.BaseType.TypeHandle); } @@ -107,7 +112,7 @@ namespace Svelto.ECS /// /// /// - public static void RegisterGroup(ExclusiveGroupStruct exclusiveGroupStruct, string name) + internal static void RegisterGroup(ExclusiveGroupStruct exclusiveGroupStruct, string name) { //Group already registered by another field referencing the same group, can happen because //the group poked is a group compound which static constructor is already been called at this point @@ -125,7 +130,7 @@ namespace Svelto.ECS _hashByGroups.Add(exclusiveGroupStruct, nameHash); } - internal static uint GetHashFromGroup(ExclusiveGroupStruct groupStruct) + public static uint GetHashFromGroup(ExclusiveGroupStruct groupStruct) { #if DEBUG && !PROFILE_SVELTO if (_hashByGroups.ContainsKey(groupStruct) == false) @@ -135,7 +140,7 @@ namespace Svelto.ECS return _hashByGroups[groupStruct]; } - internal static ExclusiveGroupStruct GetGroupFromHash(uint groupHash) + public static ExclusiveGroupStruct GetGroupFromHash(uint groupHash) { #if DEBUG && !PROFILE_SVELTO if (_groupsByHash.ContainsKey(groupHash) == false) diff --git a/Extensions/Unity/DOTS/UECS/DOTSOperationsForSvelto.cs b/Extensions/Unity/DOTS/UECS/DOTSOperationsForSvelto.cs index d166bbc..5fb8076 100644 --- a/Extensions/Unity/DOTS/UECS/DOTSOperationsForSvelto.cs +++ b/Extensions/Unity/DOTS/UECS/DOTSOperationsForSvelto.cs @@ -20,14 +20,14 @@ namespace Svelto.ECS.SveltoOnDOTS public EntityArchetype CreateArchetype(params ComponentType[] types) { - return _EManager.CreateArchetype(types); + return EManager.CreateArchetype(types); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public void SetComponent(Entity e, in T component) where T : unmanaged, IComponentData { - _EManager.SetComponentData(e, component); + EManager.SetComponentData(e, component); } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -37,23 +37,23 @@ namespace Svelto.ECS.SveltoOnDOTS #if UNITY_ECS_100 _EManager.SetSharedComponent(e, component); #else - _EManager.SetSharedComponentData(e, component); + EManager.SetSharedComponentData(e, component); #endif } [MethodImpl(MethodImplOptions.AggressiveInlining)] public Entity CreateDOTSEntityOnSvelto(Entity prefabEntity, EGID egid) { - Entity dotsEntity = _EManager.Instantiate(prefabEntity); + Entity dotsEntity = EManager.Instantiate(prefabEntity); //SharedComponentData can be used to group the DOTS ECS entities exactly like the Svelto ones #if UNITY_ECS_100 _EManager.AddSharedComponent(dotsEntity, new DOTSSveltoGroupID(egid.groupID)); #else - _EManager.AddSharedComponentData(dotsEntity, new DOTSSveltoGroupID(egid.groupID)); + EManager.AddSharedComponentData(dotsEntity, new DOTSSveltoGroupID(egid.groupID)); #endif - _EManager.AddComponent(dotsEntity); - _EManager.SetComponentData(dotsEntity, new DOTSSveltoEGID(egid)); + EManager.AddComponent(dotsEntity); + EManager.SetComponentData(dotsEntity, new DOTSSveltoEGID(egid)); return dotsEntity; } @@ -68,16 +68,16 @@ namespace Svelto.ECS.SveltoOnDOTS [MethodImpl(MethodImplOptions.AggressiveInlining)] public Entity CreateDOTSEntityOnSvelto(EntityArchetype archetype, EGID egid) { - Entity dotsEntity = _EManager.CreateEntity(archetype); + Entity dotsEntity = EManager.CreateEntity(archetype); //SharedComponentData can be used to group the DOTS ECS entities exactly like the Svelto ones #if UNITY_ECS_100 _EManager.AddSharedComponent(dotsEntity, new DOTSSveltoGroupID(egid.groupID)); #else - _EManager.AddSharedComponentData(dotsEntity, new DOTSSveltoGroupID(egid.groupID)); + EManager.AddSharedComponentData(dotsEntity, new DOTSSveltoGroupID(egid.groupID)); #endif - _EManager.AddComponent(dotsEntity); - _EManager.SetComponentData(dotsEntity, new DOTSSveltoEGID(egid)); + EManager.AddComponent(dotsEntity); + EManager.SetComponentData(dotsEntity, new DOTSSveltoEGID(egid)); return dotsEntity; } @@ -92,34 +92,34 @@ namespace Svelto.ECS.SveltoOnDOTS [MethodImpl(MethodImplOptions.AggressiveInlining)] public Entity CreateDOTSEntity(EntityArchetype archetype) { - return _EManager.CreateEntity(archetype); + return EManager.CreateEntity(archetype); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public void DestroyEntity(Entity e) { - _EManager.DestroyEntity(e); + EManager.DestroyEntity(e); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public void RemoveComponent(Entity dotsEntity) { - _EManager.RemoveComponent(dotsEntity); + EManager.RemoveComponent(dotsEntity); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public void AddComponent(Entity dotsEntity) where T : unmanaged, IComponentData { - _EManager.AddComponent(dotsEntity); + EManager.AddComponent(dotsEntity); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public void AddComponent(Entity dotsEntity, in T component) where T : unmanaged, IComponentData { - _EManager.AddComponent(dotsEntity); - _EManager.SetComponentData(dotsEntity, component); + EManager.AddComponent(dotsEntity); + EManager.SetComponentData(dotsEntity, component); } public T GetComponent(Entity dotsEntity) where T : unmanaged, IComponentData @@ -127,7 +127,7 @@ namespace Svelto.ECS.SveltoOnDOTS #if UNITY_ECS_100 return _EManager.GetComponentData(dotsEntity); #else - return _EManager.GetComponentData(dotsEntity); + return EManager.GetComponentData(dotsEntity); #endif } @@ -138,7 +138,7 @@ namespace Svelto.ECS.SveltoOnDOTS #if UNITY_ECS_100 _EManager.AddSharedComponent(dotsEntity, component); #else - _EManager.AddSharedComponentData(dotsEntity, component); + EManager.AddSharedComponentData(dotsEntity, component); #endif } @@ -146,7 +146,7 @@ namespace Svelto.ECS.SveltoOnDOTS public void AddBuffer(Entity dotsEntity) where T : unmanaged, IBufferElementData { - _EManager.AddBuffer(dotsEntity); + EManager.AddBuffer(dotsEntity); } #if !(DEBUG && !PROFILE_SVELTO) @@ -154,7 +154,7 @@ namespace Svelto.ECS.SveltoOnDOTS #endif public void SetDebugName(Entity dotsEntity, string name) { - _EManager.SetName(dotsEntity, name); + EManager.SetName(dotsEntity, name); } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -166,7 +166,7 @@ namespace Svelto.ECS.SveltoOnDOTS #else for (int i = 0; i < nativeArray.Length; i++) { - _EManager.SetSharedComponentData(nativeArray[i], SCD); + EManager.SetSharedComponentData(nativeArray[i], SCD); } #endif } @@ -174,7 +174,7 @@ namespace Svelto.ECS.SveltoOnDOTS [MethodImpl(MethodImplOptions.AggressiveInlining)] public void AddComponentBatched(NativeArray DOTSEntities) { - _EManager.AddComponent(DOTSEntities); + EManager.AddComponent(DOTSEntities); } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -186,14 +186,14 @@ namespace Svelto.ECS.SveltoOnDOTS _jobHandle->Complete(); var count = (int)(range.rangeEnd - range.rangeStart); - var nativeArray = _EManager.Instantiate(prefab, count, _EManager.World.UpdateAllocator.ToAllocator); + var nativeArray = EManager.Instantiate(prefab, count, EManager.World.UpdateAllocator.ToAllocator); #if UNITY_ECS_100 _EManager.AddSharedComponent(nativeArray, new DOTSSveltoGroupID(groupID)); #else for (int i = 0; i < nativeArray.Length; i++) { - _EManager.AddSharedComponentData(nativeArray[i], new DOTSSveltoGroupID(groupID)); + EManager.AddSharedComponentData(nativeArray[i], new DOTSSveltoGroupID(groupID)); } #endif //Set Svelto DOTSEntityComponent dotsEntity field to the DOTS entity @@ -220,14 +220,14 @@ namespace Svelto.ECS.SveltoOnDOTS var count = (int)(range.rangeEnd - range.rangeStart); //DOTS entities track Svelto entities through this component - _EManager.AddComponent(nativeArray); + EManager.AddComponent(nativeArray); //set the DOTSSveltoEGID values var setDOTSSveltoEGIDJob = new SetDOTSSveltoEGID { sveltoStartIndex = range.rangeStart, createdEntities = nativeArray, - entityManager = _EManager, + entityManager = EManager, ids = sveltoIds, groupID = groupID }; @@ -249,7 +249,7 @@ namespace Svelto.ECS.SveltoOnDOTS [MethodImpl(MethodImplOptions.AggressiveInlining)] public void DestroyEntitiesBatched(NativeArray nativeArray) { - _EManager.DestroyEntity(nativeArray); + EManager.DestroyEntity(nativeArray); } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -298,6 +298,17 @@ namespace Svelto.ECS.SveltoOnDOTS }); } } + + EntityManager EManager + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + DBC.ECS.Check.Require(_EManager != default, "EManager not initialized, did you forget to add the engine as submission engine?"); + + return _EManager; + } + } readonly EntityManager _EManager; [NativeDisableUnsafePtrRestriction] readonly unsafe JobHandle* _jobHandle; diff --git a/Extensions/Unity/DOTS/UECS/SveltoOnDOTSEntitiesSubmissionGroup.cs b/Extensions/Unity/DOTS/UECS/SveltoOnDOTSEntitiesSubmissionGroup.cs index c9164d7..21c3f74 100644 --- a/Extensions/Unity/DOTS/UECS/SveltoOnDOTSEntitiesSubmissionGroup.cs +++ b/Extensions/Unity/DOTS/UECS/SveltoOnDOTSEntitiesSubmissionGroup.cs @@ -29,7 +29,7 @@ namespace Svelto.ECS.SveltoOnDOTS public SveltoOnDOTSEntitiesSubmissionGroup(SimpleEntitiesSubmissionScheduler submissionScheduler) { _submissionScheduler = submissionScheduler; - _submissionEngines = new FasterList(); + _structuralEngines = new FasterList(); } public EntitiesDB entitiesDB { get; set; } @@ -56,7 +56,7 @@ namespace Svelto.ECS.SveltoOnDOTS //Submit Svelto Entities, calls Add/Remove/MoveTo that can be used by the DOTS ECSSubmissionEngines _submissionScheduler.SubmitEntities(); - foreach (var engine in _submissionEngines) + foreach (var engine in _structuralEngines) engine.OnPostSubmission(); _dotsOperationsForSvelto.Complete(); @@ -65,8 +65,8 @@ namespace Svelto.ECS.SveltoOnDOTS public void Add(ISveltoOnDOTSStructuralEngine engine) { - _submissionEngines.Add(engine); - if (World != null) + _structuralEngines.Add(engine); + if (_isReady == true) { engine.DOTSOperations = _dotsOperationsForSvelto; engine.OnOperationsReady(); @@ -79,9 +79,10 @@ namespace Svelto.ECS.SveltoOnDOTS { _jobHandle = (JobHandle*) MemoryUtilities.NativeAlloc((uint)MemoryUtilities.SizeOf(), Allocator.Persistent); _dotsOperationsForSvelto = new DOTSOperationsForSvelto(World.EntityManager, _jobHandle); + _isReady = true; //initialise engines field while world was null - foreach (var engine in _submissionEngines) + foreach (var engine in _structuralEngines) { engine.DOTSOperations = _dotsOperationsForSvelto; engine.OnOperationsReady(); @@ -104,9 +105,10 @@ namespace Svelto.ECS.SveltoOnDOTS throw new NotSupportedException("if this is called something broke the original design"); } - readonly FasterList _submissionEngines; + readonly FasterList _structuralEngines; readonly SimpleEntitiesSubmissionScheduler _submissionScheduler; DOTSOperationsForSvelto _dotsOperationsForSvelto; + bool _isReady; unsafe JobHandle* _jobHandle; } } diff --git a/README.md b/README.md index 49559ad..83d5adf 100644 --- a/README.md +++ b/README.md @@ -85,6 +85,8 @@ While other frameworks typically limit user freedom to avoid exposing flaws in t **GroupCompounds** build on this idea by allowing users to change the "state"/"set"/"group" according to tags that serve effectively as adjective or state identifiers. +Entities can change state moving between sets swapping them in groups explcitly, rather than changing archetype. + ## Why using Svelto.ECS with Unity? Svelto.ECS doens't use a traditional archetype model like DOTS ECS does. The novel hybrid approach based on groups instead than archetypes has been designed to allow the user to take the right decision when it comes to states management. Svelto.ECS doesn't allow archetypes to change dynamically, the user cannot add or remove components after the entity is created. Handling entities states with components can quickly lead to very intensive structural changes operations, so groups have been introduced to avoid the wild explosion of states permutations and let the user see explicitly the cost of their decisions. diff --git a/package.json b/package.json index 8dfe4f4..79f83b9 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "url": "https://github.com/sebas77/Svelto.ECS.git" }, "dependencies": { - "com.sebaslab.svelto.common": "3.4.2" + "com.sebaslab.svelto.common": "3.4.3" }, "keywords": [ "svelto", @@ -19,7 +19,7 @@ "svelto.ecs" ], "name": "com.sebaslab.svelto.ecs", - "version": "3.4.5", + "version": "3.4.6", "type": "library", "unity": "2020.3" } \ No newline at end of file