From 99c4d404566346e09fd0976199ee842ddeac323f Mon Sep 17 00:00:00 2001 From: sebas77 Date: Sun, 19 Apr 2020 17:39:32 +0100 Subject: [PATCH] Svelto_ECS_3_0 beta first upload --- Svelto.Common | 2 +- Svelto.ECS/CheckEntityUtilities.cs | 16 +- .../{EntityBuilder.cs => ComponentBuilder.cs} | 147 +++-- Svelto.ECS/DBC.cs | 2 +- .../DataStructures/AtomicRingBuffers.cs | 74 +++ .../DataStructures/FastTypeSafeDictionary.cs | 298 +++++++++++ .../DataStructures/ITypeSafeDictionary.cs | 49 ++ Svelto.ECS/DataStructures/NativeBag.cs | 186 +++++++ .../DataStructures/NativeDynamicArray.cs | 219 ++++++++ .../DataStructures/SetEGIDWithoutBoxing.cs | 46 -- Svelto.ECS/DataStructures/SharedNativeInt.cs | 67 +++ Svelto.ECS/DataStructures/SharedNativeUInt.cs | 70 +++ .../DataStructures/TypeSafeDictionary.cs | 281 ++++++---- .../TypeSafeDictionaryUtilities.cs | 21 + Svelto.ECS/DataStructures/UnsafeArray.cs | 124 +++++ Svelto.ECS/DataStructures/UnsafeBlob.cs | 267 ++++++++++ Svelto.ECS/Dispatcher/DispatchOnChange.cs | 6 +- Svelto.ECS/Dispatcher/DispatchOnSet.cs | 15 +- Svelto.ECS/DynamicEntityDescriptor.cs | 64 +-- Svelto.ECS/ECSResources/ECSResources.cs | 4 +- Svelto.ECS/ECSResources/ECSString.cs | 30 +- Svelto.ECS/EGID.cs | 24 +- Svelto.ECS/EGIDComponent.cs | 7 + Svelto.ECS/EGIDMapper.cs | 43 +- .../EnginesRoot.DoubleBufferedEntityViews.cs | 23 +- Svelto.ECS/EnginesRoot.Engines.cs | 103 +++- Svelto.ECS/EnginesRoot.Entities.cs | 301 ++++++----- .../EnginesRoot.GenericEntityFactory.cs | 44 +- .../EnginesRoot.GenericEntityFunctions.cs | 71 ++- Svelto.ECS/EnginesRoot.Submission.cs | 82 ++- Svelto.ECS/EntitiesDB.cs | 288 ++++++---- Svelto.ECS/EntityBuilder.CheckFields.cs | 56 +- Svelto.ECS/EntityBuilder.cs.rej | 8 - Svelto.ECS/EntityCollection.cs | 504 +++++++++++++----- Svelto.ECS/EntityComponentInitializer.cs | 61 +++ Svelto.ECS/EntityDescriptorTemplate.cs | 2 +- Svelto.ECS/EntityFactory.cs | 63 +-- Svelto.ECS/EntityGroupNotFoundException.cs | 2 +- Svelto.ECS/EntityHierarchyComponent.cs | 11 + Svelto.ECS/EntityHierarchyStruct.cs | 11 - Svelto.ECS/EntityInfoView.cs | 4 +- Svelto.ECS/EntityStream.cs | 139 +++-- Svelto.ECS/EntityStructInitializer.cs | 75 --- Svelto.ECS/EntitySubmissionScheduler.cs | 2 +- Svelto.ECS/EntitySubmitOperation.cs | 14 +- Svelto.ECS/EntityViewUtility.cs | 96 ++-- Svelto.ECS/ExclusiveGroup.cs | 158 ++---- Svelto.ECS/ExclusiveGroupStruct.cs | 114 ++++ Svelto.ECS/ExecuteOnEntitiesDB.cs | 61 --- Svelto.ECS/ExtendibleEntityDescriptor.cs | 6 +- Svelto.ECS/Extensions/ProcessorCount.cs | 19 + .../Extensions/Svelto/EntityDBExtensions.cs | 70 +++ .../Svelto/NativeAllGroupsEnumerable.cs | 117 ++++ .../Svelto/NativeGroupsEnumerable.cs | 221 ++++++++ .../DOTS/CopySveltoToUECSEnginesGroup.cs | 30 ++ .../Extensions/Unity/DOTS/DisposeJob.cs | 84 +++ .../Unity/DOTS/EnginesRoot.NativeOperation.cs | 265 +++++++++ .../Extensions/Unity/DOTS/IJobifiedEngine.cs | 9 + .../Unity/DOTS/JobifedEnginesGroup.cs | 31 ++ .../Unity/DOTS/JobifiedSveltoEngines.cs | 10 + .../Unity/DOTS/PureUECSSystemsGroup.cs | 27 + .../Unity/DOTS/SortedJobifedEnginesGroup.cs | 31 ++ .../Extensions/Unity/DOTS/UECSSveltoEGID.cs | 23 + .../Unity/DOTS/UnityEntityDBExtensions.cs | 31 ++ .../Unity/GenericEntityDescriptorHolder.cs | 2 +- .../Extensions/Unity/SveltoGUIHelper.cs | 45 +- .../Unity/UnityEntitySubmissionScheduler.cs | 14 +- Svelto.ECS/FastGroup.cs | 27 + Svelto.ECS/GenericEntityDescriptor.cs | 90 ++-- .../GenericentityStreamConsumerFactory.cs | 15 +- Svelto.ECS/GlobalTypeID.cs | 60 +++ Svelto.ECS/GroupCompound.cs | 112 ++++ Svelto.ECS/Hybrid/IEntityViewComponent.cs | 6 + Svelto.ECS/Hybrid/IEntityViewStruct.cs | 6 - Svelto.ECS/Hybrid/IImplementor.cs | 2 +- ...IEntityBuilder.cs => IComponentBuilder.cs} | 4 +- Svelto.ECS/IEntitiesDB.cs | 194 +------ Svelto.ECS/IEntityComponent.cs | 16 + Svelto.ECS/IEntityFactory.cs | 30 +- Svelto.ECS/IEntityFunctions.cs | 31 +- Svelto.ECS/IEntityStruct.cs | 15 - Svelto.ECS/IQueryingEntitiesEngine.cs | 2 +- Svelto.ECS/IReactOnAddAndRemove.cs | 6 +- Svelto.ECS/IReactOnSwap.cs | 6 +- Svelto.ECS/LICENSE | 22 + Svelto.ECS/NativeEGIDMapper.cs | 79 +++ Svelto.ECS/README.md | 50 ++ Svelto.ECS/Sequencer.cs | 132 ----- .../ComposedComponentSerializer.cs | 42 ++ .../Serialization/ComposedSerializer.cs | 43 -- Svelto.ECS/Serialization/DefaultSerializer.cs | 25 +- .../Serialization/DefaultSerializerUtils.cs | 22 +- .../Serialization/DefaultVersioningFactory.cs | 37 +- Svelto.ECS/Serialization/DontSerialize.cs | 2 +- .../EnginesRoot.GenericEntitySerialization.cs | 96 ++-- .../EnginesRoot.SerializableEntityHeader.cs | 14 +- .../Serialization/EntitiesDB.DescriptorMap.cs | 36 +- .../Serialization/IComponentSerializer.cs | 13 + .../Serialization/IDeserializationFactory.cs | 4 +- .../Serialization/IEntitySerialization.cs | 20 +- .../ISerializableComponentBuilder.cs | 16 + .../ISerializableEntityBuilder.cs | 15 - .../ISerializableEntityDescriptor.cs | 4 +- .../Serialization/ISerializationData.cs | 2 +- Svelto.ECS/Serialization/ISerializer.cs | 55 -- Svelto.ECS/Serialization/PartialSerializer.cs | 16 +- .../SerializableComponentBuilder.cs | 110 ++++ .../SerializableEntityBuilder.cs | 95 ---- ...ruct.cs => SerializableEntityComponent.cs} | 2 +- .../SerializableEntityDescriptor.cs | 85 ++- Svelto.ECS/Serialization/SerializerExt.cs | 45 ++ .../Serialization/SimpleSerializationData.cs | 2 +- Svelto.ECS/Serialization/Unsafe.cs | 15 - Svelto.ECS/SetEGIDWithoutBoxing.cs | 33 ++ ...s => SimpleEntitiesSubmissionScheduler.cs} | 15 +- Svelto.ECS/Svelto.ECS.asmdef | 21 +- Svelto.ECS/TypeCache.cs | 9 - Svelto.ECS/TypeSafeDictionaryFactory.cs | 17 + Svelto.ECS/WaitForSubmissionEnumerator.cs | 8 +- Svelto.ECS/package.json | 15 + 120 files changed, 5105 insertions(+), 2124 deletions(-) rename Svelto.ECS/{EntityBuilder.cs => ComponentBuilder.cs} (51%) create mode 100644 Svelto.ECS/DataStructures/AtomicRingBuffers.cs create mode 100644 Svelto.ECS/DataStructures/FastTypeSafeDictionary.cs create mode 100644 Svelto.ECS/DataStructures/ITypeSafeDictionary.cs create mode 100644 Svelto.ECS/DataStructures/NativeBag.cs create mode 100644 Svelto.ECS/DataStructures/NativeDynamicArray.cs delete mode 100644 Svelto.ECS/DataStructures/SetEGIDWithoutBoxing.cs create mode 100644 Svelto.ECS/DataStructures/SharedNativeInt.cs create mode 100644 Svelto.ECS/DataStructures/SharedNativeUInt.cs create mode 100644 Svelto.ECS/DataStructures/TypeSafeDictionaryUtilities.cs create mode 100644 Svelto.ECS/DataStructures/UnsafeArray.cs create mode 100644 Svelto.ECS/DataStructures/UnsafeBlob.cs create mode 100644 Svelto.ECS/EGIDComponent.cs delete mode 100644 Svelto.ECS/EntityBuilder.cs.rej create mode 100644 Svelto.ECS/EntityComponentInitializer.cs create mode 100644 Svelto.ECS/EntityHierarchyComponent.cs delete mode 100644 Svelto.ECS/EntityHierarchyStruct.cs delete mode 100644 Svelto.ECS/EntityStructInitializer.cs create mode 100644 Svelto.ECS/ExclusiveGroupStruct.cs delete mode 100644 Svelto.ECS/ExecuteOnEntitiesDB.cs create mode 100644 Svelto.ECS/Extensions/ProcessorCount.cs create mode 100644 Svelto.ECS/Extensions/Svelto/EntityDBExtensions.cs create mode 100644 Svelto.ECS/Extensions/Svelto/NativeAllGroupsEnumerable.cs create mode 100644 Svelto.ECS/Extensions/Svelto/NativeGroupsEnumerable.cs create mode 100644 Svelto.ECS/Extensions/Unity/DOTS/CopySveltoToUECSEnginesGroup.cs create mode 100644 Svelto.ECS/Extensions/Unity/DOTS/DisposeJob.cs create mode 100644 Svelto.ECS/Extensions/Unity/DOTS/EnginesRoot.NativeOperation.cs create mode 100644 Svelto.ECS/Extensions/Unity/DOTS/IJobifiedEngine.cs create mode 100644 Svelto.ECS/Extensions/Unity/DOTS/JobifedEnginesGroup.cs create mode 100644 Svelto.ECS/Extensions/Unity/DOTS/JobifiedSveltoEngines.cs create mode 100644 Svelto.ECS/Extensions/Unity/DOTS/PureUECSSystemsGroup.cs create mode 100644 Svelto.ECS/Extensions/Unity/DOTS/SortedJobifedEnginesGroup.cs create mode 100644 Svelto.ECS/Extensions/Unity/DOTS/UECSSveltoEGID.cs create mode 100644 Svelto.ECS/Extensions/Unity/DOTS/UnityEntityDBExtensions.cs create mode 100644 Svelto.ECS/FastGroup.cs create mode 100644 Svelto.ECS/GlobalTypeID.cs create mode 100644 Svelto.ECS/GroupCompound.cs create mode 100644 Svelto.ECS/Hybrid/IEntityViewComponent.cs delete mode 100644 Svelto.ECS/Hybrid/IEntityViewStruct.cs rename Svelto.ECS/{IEntityBuilder.cs => IComponentBuilder.cs} (80%) create mode 100644 Svelto.ECS/IEntityComponent.cs delete mode 100644 Svelto.ECS/IEntityStruct.cs create mode 100644 Svelto.ECS/LICENSE create mode 100644 Svelto.ECS/NativeEGIDMapper.cs create mode 100644 Svelto.ECS/README.md delete mode 100644 Svelto.ECS/Sequencer.cs create mode 100644 Svelto.ECS/Serialization/ComposedComponentSerializer.cs delete mode 100644 Svelto.ECS/Serialization/ComposedSerializer.cs create mode 100644 Svelto.ECS/Serialization/IComponentSerializer.cs create mode 100644 Svelto.ECS/Serialization/ISerializableComponentBuilder.cs delete mode 100644 Svelto.ECS/Serialization/ISerializableEntityBuilder.cs delete mode 100644 Svelto.ECS/Serialization/ISerializer.cs create mode 100644 Svelto.ECS/Serialization/SerializableComponentBuilder.cs delete mode 100644 Svelto.ECS/Serialization/SerializableEntityBuilder.cs rename Svelto.ECS/Serialization/{SerializableEntityStruct.cs => SerializableEntityComponent.cs} (59%) create mode 100644 Svelto.ECS/Serialization/SerializerExt.cs delete mode 100644 Svelto.ECS/Serialization/Unsafe.cs create mode 100644 Svelto.ECS/SetEGIDWithoutBoxing.cs rename Svelto.ECS/{SimpleSubmissionEntityViewScheduler.cs => SimpleEntitiesSubmissionScheduler.cs} (59%) delete mode 100644 Svelto.ECS/TypeCache.cs create mode 100644 Svelto.ECS/TypeSafeDictionaryFactory.cs create mode 100644 Svelto.ECS/package.json diff --git a/Svelto.Common b/Svelto.Common index 4b6f1b9..111fccb 160000 --- a/Svelto.Common +++ b/Svelto.Common @@ -1 +1 @@ -Subproject commit 4b6f1b9f9a08cbafc9e0a46a1ad7bf30eba10e9c +Subproject commit 111fccbc4676ce1a5999570ca3c12f1918a9e763 diff --git a/Svelto.ECS/CheckEntityUtilities.cs b/Svelto.ECS/CheckEntityUtilities.cs index 0b061f6..e85326b 100644 --- a/Svelto.ECS/CheckEntityUtilities.cs +++ b/Svelto.ECS/CheckEntityUtilities.cs @@ -1,4 +1,4 @@ -#if DEBUG && !PROFILER +#if DEBUG && !PROFILE_SVELTO using System.Collections.Generic; using Svelto.DataStructures; #else @@ -7,9 +7,13 @@ using System.Diagnostics; namespace Svelto.ECS { + /// + /// Note: this check doesn't catch the case when an add and remove is done on the same entity before the next + /// submission. Two operations on the same entity are not allowed between submissions. + /// public partial class EnginesRoot { -#if DEBUG && !PROFILER +#if DEBUG && !PROFILE_SVELTO void CheckRemoveEntityID(EGID egid) { // Console.LogError("removed".FastConcat(egid.ToString())); @@ -48,14 +52,14 @@ namespace Svelto.ECS .FastConcat("' id: '") .FastConcat(egid.entityID) .FastConcat("' groupid: '") - .FastConcat(egid.groupID) + .FastConcat(egid.groupID) .FastConcat("'")); } - + hash.Add(egid.entityID); } - void RemoveGroupID(ExclusiveGroup.ExclusiveGroupStruct groupID) + void RemoveGroupID(ExclusiveGroupStruct groupID) { _idCheckers.Remove(groupID); } @@ -73,7 +77,7 @@ namespace Svelto.ECS } [Conditional("_CHECKS_DISABLED")] - void RemoveGroupID(ExclusiveGroup.ExclusiveGroupStruct groupID) + void RemoveGroupID(ExclusiveGroupStruct groupID) { } #endif diff --git a/Svelto.ECS/EntityBuilder.cs b/Svelto.ECS/ComponentBuilder.cs similarity index 51% rename from Svelto.ECS/EntityBuilder.cs rename to Svelto.ECS/ComponentBuilder.cs index 78aaffa..5b58344 100644 --- a/Svelto.ECS/EntityBuilder.cs +++ b/Svelto.ECS/ComponentBuilder.cs @@ -8,64 +8,19 @@ using Svelto.Utilities; namespace Svelto.ECS { - public class EntityBuilder : IEntityBuilder where T : struct, IEntityStruct + public class ComponentBuilder : IComponentBuilder where T : struct, IEntityComponent { - static class EntityView - { - internal static readonly FasterList>> cachedFields; - internal static readonly Dictionary cachedTypes; -#if DEBUG && !PROFILER - internal static readonly Dictionary> implementorsByType; -#else - internal static readonly Dictionary implementorsByType; -#endif - static EntityView() - { - cachedFields = new FasterList>>(); - - 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 setter = FastInvoke.MakeSetter(field); - - cachedFields.Add(new KeyValuePair>(field.FieldType, setter)); - } - - cachedTypes = new Dictionary(); - -#if DEBUG && !PROFILER - implementorsByType = new Dictionary>(); -#else - implementorsByType = new Dictionary(); -#endif - } - - internal static void InitCache() - {} - - internal static void BuildEntityView(out T entityView) - { - entityView = new T(); - } - } - - public EntityBuilder() + public ComponentBuilder() { _initializer = DEFAULT_IT; - EntityBuilderUtilities.CheckFields(ENTITY_VIEW_TYPE, NEEDS_REFLECTION); + EntityBuilderUtilities.CheckFields(ENTITY_COMPONENT_TYPE, IS_ENTITY_VIEW_COMPONENT); - if (NEEDS_REFLECTION) - EntityView.InitCache(); + if (IS_ENTITY_VIEW_COMPONENT) + EntityViewComponentCache.InitCache(); } - public EntityBuilder(in T initializer) : this() + public ComponentBuilder(in T initializer) : this() { _initializer = initializer; } @@ -73,24 +28,23 @@ namespace Svelto.ECS public void BuildEntityAndAddToList(ref ITypeSafeDictionary dictionary, EGID egid, IEnumerable implementors) { - if (dictionary == null) - dictionary = new TypeSafeDictionary(); + if (dictionary == null) + dictionary = TypeSafeDictionaryFactory.Create(); - var castedDic = dictionary as TypeSafeDictionary; + var castedDic = dictionary as ITypeSafeDictionary; - if (NEEDS_REFLECTION) + T entityComponent = default; + if (IS_ENTITY_VIEW_COMPONENT) { DBC.ECS.Check.Require(implementors != null, - $"Implementors not found while building an EntityView `{typeof(T)}`"); + $"Implementors not found while building an EntityComponent `{typeof(T)}`"); DBC.ECS.Check.Require(castedDic.ContainsKey(egid.entityID) == false, - $"building an entity with already used entity id! id: '{(ulong) egid}', {ENTITY_VIEW_NAME}"); - - EntityView.BuildEntityView(out var entityView); + $"building an entity with already used entity id! id: '{(ulong) egid}', {ENTITY_COMPONENT_NAME}"); - this.FillEntityView(ref entityView, entityViewBlazingFastReflection, implementors, - EntityView.implementorsByType, EntityView.cachedTypes); + this.FillEntityComponent(ref entityComponent, EntityViewComponentCache.cachedFields, implementors, + EntityViewComponentCache.implementorsByType, EntityViewComponentCache.cachedTypes); - castedDic.Add(egid.entityID, entityView); + castedDic.Add(egid.entityID, entityComponent); } else { @@ -101,7 +55,7 @@ namespace Svelto.ECS } } - ITypeSafeDictionary IEntityBuilder.Preallocate(ref ITypeSafeDictionary dictionary, uint size) + ITypeSafeDictionary IComponentBuilder.Preallocate(ref ITypeSafeDictionary dictionary, uint size) { return Preallocate(ref dictionary, size); } @@ -109,38 +63,77 @@ namespace Svelto.ECS static ITypeSafeDictionary Preallocate(ref ITypeSafeDictionary dictionary, uint size) { if (dictionary == null) - dictionary = new TypeSafeDictionary(size); + dictionary = TypeSafeDictionaryFactory.Create(size); else dictionary.SetCapacity(size); return dictionary; } - public Type GetEntityType() + public Type GetEntityComponentType() { - return ENTITY_VIEW_TYPE; + return ENTITY_COMPONENT_TYPE; } - static EntityBuilder() + static ComponentBuilder() { - ENTITY_VIEW_TYPE = typeof(T); + ENTITY_COMPONENT_TYPE = typeof(T); DEFAULT_IT = default; - NEEDS_REFLECTION = typeof(IEntityViewStruct).IsAssignableFrom(ENTITY_VIEW_TYPE); - HAS_EGID = typeof(INeedEGID).IsAssignableFrom(ENTITY_VIEW_TYPE); - ENTITY_VIEW_NAME = ENTITY_VIEW_TYPE.ToString(); + 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(); +#if UNITY_ECS + EntityComponentIDMap.Register(new Filler()); +#endif SetEGIDWithoutBoxing.Warmup(); } readonly T _initializer; - static FasterList>> entityViewBlazingFastReflection => - EntityView.cachedFields; - - internal static readonly Type ENTITY_VIEW_TYPE; + internal static readonly Type ENTITY_COMPONENT_TYPE; public static readonly bool HAS_EGID; static readonly T DEFAULT_IT; - static readonly bool NEEDS_REFLECTION; - static readonly string ENTITY_VIEW_NAME; + static readonly bool IS_ENTITY_VIEW_COMPONENT; + static readonly string ENTITY_COMPONENT_NAME; + + static class EntityViewComponentCache + { + internal static readonly FasterList>> cachedFields; + internal static readonly Dictionary cachedTypes; +#if DEBUG && !PROFILE_SVELTO + internal static readonly Dictionary> implementorsByType; +#else + internal static readonly Dictionary implementorsByType; +#endif + static EntityViewComponentCache() + { + cachedFields = new FasterList>>(); + + 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]; + DBC.ECS.Check.Require(field.FieldType.IsInterface == true, "Entity View Components must hold only public interfaces"); + var setter = FastInvoke.MakeSetter(field); + + //for each interface, cache the setter for this type + cachedFields.Add(new KeyValuePair>(field.FieldType, setter)); + } + + cachedTypes = new Dictionary(); + +#if DEBUG && !PROFILE_SVELTO + implementorsByType = new Dictionary>(); +#else + implementorsByType = new Dictionary(); +#endif + } + + internal static void InitCache() + {} + } } } \ No newline at end of file diff --git a/Svelto.ECS/DBC.cs b/Svelto.ECS/DBC.cs index 144c690..295fc45 100644 --- a/Svelto.ECS/DBC.cs +++ b/Svelto.ECS/DBC.cs @@ -1,4 +1,4 @@ -#if DISABLE_DBC || !DEBUG || PROFILER +#if DISABLE_DBC || !DEBUG || PROFILE_SVELTO #define DISABLE_CHECKS using System.Diagnostics; #endif diff --git a/Svelto.ECS/DataStructures/AtomicRingBuffers.cs b/Svelto.ECS/DataStructures/AtomicRingBuffers.cs new file mode 100644 index 0000000..98c646e --- /dev/null +++ b/Svelto.ECS/DataStructures/AtomicRingBuffers.cs @@ -0,0 +1,74 @@ +using System; +using System.Runtime.CompilerServices; +using Svelto.Common; +using Allocator = Svelto.Common.Allocator; + +namespace Svelto.ECS.DataStructures.Unity +{ + /// + /// A collection of intended to allow one buffer per thread. + /// from: https://github.com/jeffvella/UnityEcsEvents/blob/develop/Runtime/MultiAppendBuffer.cs + /// + public unsafe struct AtomicRingBuffers:IDisposable + { + public const int DefaultThreadIndex = -1; + public const int MinThreadIndex = DefaultThreadIndex; + +#if UNITY_ECS + [global::Unity.Collections.LowLevel.Unsafe.NativeDisableUnsafePtrRestriction] +#endif + NativeBag* _data; + public readonly Allocator Allocator; + readonly uint _threadsCount; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool IsInvalidThreadIndex(int index) => index < MinThreadIndex || index > _threadsCount; + + public AtomicRingBuffers(Common.Allocator allocator, uint threadsCount) + { + Allocator = allocator; + _threadsCount = threadsCount; + + var bufferSize = MemoryUtilities.SizeOf(); + var bufferCount = _threadsCount; + var allocationSize = bufferSize * bufferCount; + + var ptr = (byte*)MemoryUtilities.Alloc((uint) allocationSize, (uint) MemoryUtilities.AlignOf(), allocator); + MemoryUtilities.MemClear((IntPtr) ptr, (uint) allocationSize); + + for (int i = 0; i < bufferCount; i++) + { + var bufferPtr = (NativeBag*)(ptr + bufferSize * i); + var buffer = new NativeBag((uint) i, allocator); + MemoryUtilities.CopyStructureToPtr(ref buffer, (IntPtr) bufferPtr); + } + + _data = (NativeBag*)ptr; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ref NativeBag GetBuffer(int index) + { + return ref MemoryUtilities.ArrayElementAsRef((IntPtr) _data, index); + } + + public uint count => _threadsCount; + + public void Dispose() + { + for (int i = 0; i < _threadsCount; i++) + { + GetBuffer(i).Dispose(); + } + MemoryUtilities.Free((IntPtr) _data, Allocator); + } + + public void Clear() + { + for (int i = 0; i < _threadsCount; i++) + { + GetBuffer(i).Clear(); + } + } + } +} diff --git a/Svelto.ECS/DataStructures/FastTypeSafeDictionary.cs b/Svelto.ECS/DataStructures/FastTypeSafeDictionary.cs new file mode 100644 index 0000000..19cb75d --- /dev/null +++ b/Svelto.ECS/DataStructures/FastTypeSafeDictionary.cs @@ -0,0 +1,298 @@ +#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, FasterList> 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, FasterList> 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, FasterList> 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, FasterList> entityComponentEnginesDB, + ref TValue entity, + ExclusiveGroupStruct? previousGroup, + in PlatformProfiler profiler, + EGID egid) + { + //get all the engines linked to TValue + if (!entityComponentEnginesDB.TryGetValue(new RefWrapper(_type), out var entityComponentsEngines)) return; + + if (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, FasterList> @group, + ref TValue entity, + ExclusiveGroupStruct? previousGroup, + in PlatformProfiler profiler, + EGID egid) + { + if (!@group.TryGetValue(new RefWrapper(_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 object GenerateSentinel() + { + throw new NotImplementedException(); + } + + 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/Svelto.ECS/DataStructures/ITypeSafeDictionary.cs b/Svelto.ECS/DataStructures/ITypeSafeDictionary.cs new file mode 100644 index 0000000..d8a744b --- /dev/null +++ b/Svelto.ECS/DataStructures/ITypeSafeDictionary.cs @@ -0,0 +1,49 @@ +using System; +using Svelto.Common; +using Svelto.DataStructures; + +namespace Svelto.ECS.Internal +{ + public interface ITypeSafeDictionary : ITypeSafeDictionary where TValue : IEntityComponent + { + void Add(uint egidEntityId, in TValue entityComponent); + ref TValue GetValueByRef(uint key); + ref TValue this[uint idEntityId] { get; } + bool TryGetValue(uint entityId, out TValue item); + ref TValue GetOrCreate(uint idEntityId); + + TValue[] GetValuesArray(out uint count); + TValue[] unsafeValues { get; } + object GenerateSentinel(); + } + + public interface ITypeSafeDictionary + { + uint Count { get; } + ITypeSafeDictionary Create(); + + void AddEntitiesToEngines(FasterDictionary, FasterList> entityComponentEnginesDb, + ITypeSafeDictionary realDic, ExclusiveGroupStruct @group, in PlatformProfiler profiler); + + void RemoveEntitiesFromEngines(FasterDictionary, FasterList> entityComponentEnginesDB, + in PlatformProfiler profiler, ExclusiveGroupStruct @group); + + void AddEntitiesFromDictionary(ITypeSafeDictionary entitiesToSubmit, uint groupId); + + void MoveEntityFromEngines(EGID fromEntityGid, EGID? toEntityID, ITypeSafeDictionary toGroup, + FasterDictionary, FasterList> engines, in PlatformProfiler profiler); + + void AddEntityToDictionary(EGID fromEntityGid, EGID toEntityID, ITypeSafeDictionary toGroup); + + void RemoveEntityFromDictionary(EGID fromEntityGid); + + void SetCapacity(uint size); + void Trim(); + void Clear(); + void FastClear(); + bool Has(uint key); + bool ContainsKey(uint egidEntityId); + uint GetIndex(uint valueEntityId); + bool TryFindIndex(uint entityGidEntityId, out uint index); + } +} \ No newline at end of file diff --git a/Svelto.ECS/DataStructures/NativeBag.cs b/Svelto.ECS/DataStructures/NativeBag.cs new file mode 100644 index 0000000..354ff9b --- /dev/null +++ b/Svelto.ECS/DataStructures/NativeBag.cs @@ -0,0 +1,186 @@ +using System; +using System.Runtime.CompilerServices; +using Svelto.Common; + +namespace Svelto.ECS.DataStructures +{ + /// + /// Burst friendly RingBuffer on steroid: + /// it can: Enqueue/Dequeue, it wraps if there is enough space after dequeuing + /// It resizes if there isn't enough space left. + /// It's a "bag", you can queue and dequeue any T. Just be sure that you dequeue what you queue! No check on type + /// is done. + /// You can reserve a position in the queue to update it later. + /// The datastructure is a struct and it's "copyable" + /// I eventually decided to call it NativeBag and not NativeRingBuffer because it can also be used as + /// a preallocated memory pool where any kind of T can be stored as long as T is unmanaged + /// + public struct NativeBag : IDisposable + { +#if UNITY_ECS + [global::Unity.Collections.LowLevel.Unsafe.NativeDisableUnsafePtrRestriction] +#endif + unsafe UnsafeBlob* _queue; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool IsEmpty() + { + unsafe + { + if (_queue == null || _queue->ptr == null) + return true; + } + + return count == 0; + } + + public uint count + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + unsafe + { +#if DEBUG && !PROFILE_SVELTO + if (_queue == null) + throw new Exception("SimpleNativeArray: null-access"); +#endif + + return _queue->size; + } + } + } + + public uint capacity + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + unsafe + { +#if DEBUG && !PROFILE_SVELTO + if (_queue == null) + throw new Exception("SimpleNativeArray: null-access"); +#endif + + return _queue->capacity; + } + } + } + + public NativeBag(Allocator allocator) + { + unsafe + { + var sizeOf = MemoryUtilities.SizeOf(); + var listData = (UnsafeBlob*) MemoryUtilities.Alloc((uint) sizeOf + , (uint) MemoryUtilities.AlignOf() + , allocator); + + //clear to nullify the pointers + MemoryUtilities.MemClear((IntPtr) listData, (uint) sizeOf); + listData->allocator = allocator; +#if DEBUG && !PROFILE_SVELTO + listData->id = 0xDEADBEEF; +#endif + _queue = listData; + } + } + + public NativeBag(uint bufferID, Allocator allocator) + { + unsafe + { + var sizeOf = MemoryUtilities.SizeOf(); + var listData = (UnsafeBlob*) MemoryUtilities.Alloc((uint) sizeOf + , (uint) MemoryUtilities.AlignOf() + , allocator); + + //clear to nullify the pointers + MemoryUtilities.MemClear((IntPtr) listData, (uint) sizeOf); + listData->allocator = allocator; +#if DEBUG && !PROFILE_SVELTO + listData->id = bufferID; +#endif + _queue = listData; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public unsafe void Dispose() + { + if (_queue != null) + { + _queue->Dispose(); + _queue = null; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ref T ReserveEnqueue(out UnsafeArrayIndex index) where T : unmanaged + { + unsafe + { +#if DEBUG && !PROFILE_SVELTO + if (_queue == null) + throw new Exception("SimpleNativeArray: null-access"); +#endif + var sizeOf = MemoryUtilities.SizeOf(); + if (_queue->space - sizeOf < 0) + _queue->Realloc((uint) MemoryUtilities.AlignOf(), (uint) ((_queue->capacity + sizeOf) * 1.5f)); + + return ref _queue->Reserve(out index); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Enqueue(in T item) where T : struct + { + unsafe + { +#if DEBUG && !PROFILE_SVELTO + if (_queue == null) + throw new Exception("SimpleNativeArray: null-access"); +#endif + var sizeOf = MemoryUtilities.SizeOf(); + if (_queue->space - sizeOf < 0) + _queue->Realloc((uint) MemoryUtilities.AlignOf(), (uint) ((_queue->capacity + sizeOf) * 1.5f)); + + _queue->Write(item); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Clear() + { + unsafe + { +#if DEBUG && !PROFILE_SVELTO + if (_queue == null) + throw new Exception("SimpleNativeArray: null-access"); +#endif + _queue->Clear(); + } + } + + public T Dequeue() where T : struct + { + unsafe + { + return _queue->Read(); + } + } + + public ref T AccessReserved(UnsafeArrayIndex reserverIndex) where T : unmanaged + { + unsafe + { +#if DEBUG && !PROFILE_SVELTO + if (_queue == null) + throw new Exception("SimpleNativeArray: null-access"); +#endif + return ref _queue->AccessReserved(reserverIndex); + } + } + } +} \ No newline at end of file diff --git a/Svelto.ECS/DataStructures/NativeDynamicArray.cs b/Svelto.ECS/DataStructures/NativeDynamicArray.cs new file mode 100644 index 0000000..6e98aa4 --- /dev/null +++ b/Svelto.ECS/DataStructures/NativeDynamicArray.cs @@ -0,0 +1,219 @@ +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using Svelto.Common; +using Allocator = Svelto.Common.Allocator; + +namespace Svelto.ECS.DataStructures +{ + public struct NativeDynamicArray : IDisposable + { +#if UNITY_ECS + [global::Unity.Collections.LowLevel.Unsafe.NativeDisableUnsafePtrRestriction] +#endif + unsafe UnsafeArray* _list; +#if DEBUG && !PROFILE_SVELTO + int hashType; +#endif + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public uint Count() where T:unmanaged + { + unsafe + { +#if DEBUG && !PROFILE_SVELTO + if (_list == null) + throw new Exception("SimpleNativeArray: null-access"); + if (hashType != Svelto.Common.TypeHash.hash) + throw new Exception("SimpleNativeArray: not excepted type used"); + +#endif + return (uint) (_list->count / MemoryUtilities.SizeOf()); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public uint Capacity() where T:unmanaged + { + unsafe + { +#if DEBUG && !PROFILE_SVELTO + if (_list == null) + throw new Exception("SimpleNativeArray: null-access"); + if (hashType != Svelto.Common.TypeHash.hash) + throw new Exception("SimpleNativeArray: not excepted type used"); + +#endif + return (uint) (_list->capacity / MemoryUtilities.SizeOf()); + } + } + + public static NativeDynamicArray Alloc(Allocator allocator, uint newLength = 0) where T : unmanaged + { + unsafe + { + var rtnStruc = new NativeDynamicArray(); +#if DEBUG && !PROFILE_SVELTO + rtnStruc.hashType = TypeHash.hash; +#endif + var sizeOf = MemoryUtilities.SizeOf(); + var alignOf = MemoryUtilities.AlignOf(); + + uint pointerSize = (uint) MemoryUtilities.SizeOf(); + UnsafeArray* listData = + (UnsafeArray*) MemoryUtilities.Alloc(pointerSize + , (uint) MemoryUtilities.AlignOf(), allocator); + + //clear to nullify the pointers + MemoryUtilities.MemClear((IntPtr) listData, pointerSize); + + listData->allocator = allocator; + + listData->Realloc((uint) alignOf, (uint) (newLength * sizeOf)); + + rtnStruc._list = listData; + + return rtnStruc; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ref T Get(uint index) where T : unmanaged + { + unsafe + { +#if DEBUG && !PROFILE_SVELTO + if (_list == null) + throw new Exception("SimpleNativeArray: null-access"); + if (hashType != Svelto.Common.TypeHash.hash) + throw new Exception("SimpleNativeArray: not excepted type used"); + if (index >= Count()) + throw new Exception($"SimpleNativeArray: out of bound access, index {index} count {Count()}"); +#endif + return ref _list->Get(index); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Set(uint index, in T value) where T : unmanaged + { + unsafe + { +#if DEBUG && !PROFILE_SVELTO + if (_list == null) + throw new Exception("SimpleNativeArray: null-access"); + if (hashType != Svelto.Common.TypeHash.hash) + throw new Exception("SimpleNativeArray: not excepted type used"); + if (index >= Capacity()) + throw new Exception($"SimpleNativeArray: out of bound access, index {index} count {Count()}"); +#endif + _list->Set(index, value); + } + } + + public unsafe void Dispose() + { + if (_list != null) + { + _list->Dispose(); + _list = null; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Add(in T item) where T : unmanaged + { + unsafe + { +#if DEBUG && !PROFILE_SVELTO + if (_list == null) + throw new Exception("SimpleNativeArray: null-access"); + if (hashType != Svelto.Common.TypeHash.hash) + throw new Exception("SimpleNativeArray: not excepted type used"); +#endif + var structSize = (uint) MemoryUtilities.SizeOf(); + + if (_list->space - (int)structSize < 0) + _list->Realloc((uint) MemoryUtilities.AlignOf(), (uint) ((Count() + 1) * structSize * 1.5f)); + + //the idea is, considering the wrap, a read pointer must always be behind a writer pointer +#if DEBUG && !PROFILE_SVELTO + if (_list->space - (int)structSize < 0) + throw new Exception("no writing authorized"); +#endif + _list->Add(item); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Clear() + { + unsafe + { +#if DEBUG && !PROFILE_SVELTO + if (_list == null) + throw new Exception("SimpleNativeArray: null-access"); +#endif + _list->Clear(); + } + } + + public unsafe T* ToPTR() where T : unmanaged + { +#if DEBUG && !PROFILE_SVELTO + if (_list == null) + throw new Exception("SimpleNativeArray: null-access"); + if (hashType != Svelto.Common.TypeHash.hash) + throw new Exception("SimpleNativeArray: not excepted type used"); + +#endif + return (T*) _list->ptr; + } + + public T[] ToManagedArray() where T : unmanaged + { + unsafe + { +#if DEBUG && !PROFILE_SVELTO + if (_list == null) + throw new Exception("SimpleNativeArray: null-access"); + if (hashType != Svelto.Common.TypeHash.hash) + throw new Exception("SimpleNativeArray: not excepted type used"); + +#endif + var ret = new T[Count()]; + + var handle = GCHandle.Alloc(ret, GCHandleType.Pinned); + + Buffer.MemoryCopy(_list->ptr, (void*) handle.AddrOfPinnedObject(), _list->count, _list->count); + + handle.Free(); + + return ret; + } + } + + public T[] ToManagedArrayUntrimmed() where T : unmanaged + { + unsafe + { +#if DEBUG && !PROFILE_SVELTO + if (_list == null) + throw new Exception("SimpleNativeArray: null-access"); + if (hashType != Svelto.Common.TypeHash.hash) + throw new Exception("SimpleNativeArray: not excepted type used"); + +#endif + var ret = new T[Capacity()]; + + var handle = GCHandle.Alloc(ret, GCHandleType.Pinned); + + Buffer.MemoryCopy(_list->ptr, (void*) handle.AddrOfPinnedObject(), _list->capacity, _list->capacity); + + handle.Free(); + + return ret; + } + } + } +} diff --git a/Svelto.ECS/DataStructures/SetEGIDWithoutBoxing.cs b/Svelto.ECS/DataStructures/SetEGIDWithoutBoxing.cs deleted file mode 100644 index 828e598..0000000 --- a/Svelto.ECS/DataStructures/SetEGIDWithoutBoxing.cs +++ /dev/null @@ -1,46 +0,0 @@ -using System; -using System.Linq.Expressions; -using System.Reflection; - -namespace Svelto.ECS.Internal -{ - static class SetEGIDWithoutBoxing where T : struct, IEntityStruct - { - internal delegate void ActionCast(ref T target, EGID egid); - - public static readonly ActionCast SetIDWithoutBoxing = MakeSetter(); - - static ActionCast MakeSetter() - { - if (EntityBuilder.HAS_EGID) - { -#if !ENABLE_IL2CPP - Type myTypeA = typeof(T); - PropertyInfo myFieldInfo = myTypeA.GetProperty("ID"); - - ParameterExpression targetExp = Expression.Parameter(typeof(T).MakeByRefType(), "target"); - ParameterExpression valueExp = Expression.Parameter(typeof(EGID), "value"); - MemberExpression fieldExp = Expression.Property(targetExp, myFieldInfo); - BinaryExpression assignExp = Expression.Assign(fieldExp, valueExp); - - var setter = Expression.Lambda(assignExp, targetExp, valueExp).Compile(); - - return setter; -#else - return (ref T target, EGID value) => - { - var needEgid = (target as INeedEGID); - needEgid.ID = value; - target = (T) needEgid; - }; -#endif - } - - return null; - } - - public static void Warmup() - { - } - } -} \ No newline at end of file diff --git a/Svelto.ECS/DataStructures/SharedNativeInt.cs b/Svelto.ECS/DataStructures/SharedNativeInt.cs new file mode 100644 index 0000000..0ad3bbc --- /dev/null +++ b/Svelto.ECS/DataStructures/SharedNativeInt.cs @@ -0,0 +1,67 @@ +using System; +using System.Runtime.InteropServices; +using System.Threading; + +namespace Svelto.ECS.DataStructures +{ + public struct SharedNativeInt: IDisposable + { +#if UNITY_ECS + [global::Unity.Collections.LowLevel.Unsafe.NativeDisableUnsafePtrRestriction] +#endif + unsafe int* data; + + public static implicit operator SharedNativeInt(int t) + { + unsafe + { + var current = new SharedNativeInt(); + current.data = (int*) Marshal.AllocHGlobal(sizeof(int)); + *current.data = t; + + return current; + } + } + + public static explicit operator int(SharedNativeInt t) + { + unsafe + { +#if DEBUG && !PROFILE_SVELTO + if (t.data == null) + throw new Exception("using disposed SharedInt"); +#endif + + return *t.data; + } + } + + public void Dispose() + { + unsafe + { + if (data != null) + { + Marshal.FreeHGlobal((IntPtr) data); + data = null; + } + } + } + + public void Decrement() + { + unsafe + { + Interlocked.Decrement(ref *data); + } + } + + public void Increment() + { + unsafe + { + Interlocked.Increment(ref *data); + } + } + } +} \ No newline at end of file diff --git a/Svelto.ECS/DataStructures/SharedNativeUInt.cs b/Svelto.ECS/DataStructures/SharedNativeUInt.cs new file mode 100644 index 0000000..a6474b3 --- /dev/null +++ b/Svelto.ECS/DataStructures/SharedNativeUInt.cs @@ -0,0 +1,70 @@ +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Threading; + +namespace Svelto.ECS.DataStructures +{ + public struct SharedNativeUInt: IDisposable + { +#if UNITY_ECS + [global::Unity.Collections.LowLevel.Unsafe.NativeDisableUnsafePtrRestriction] +#endif + unsafe uint* data; + + public SharedNativeUInt(uint t) + { + unsafe + { + data = (uint*) Marshal.AllocHGlobal(sizeof(uint)); + *data = t; + } + } + + public static implicit operator uint(SharedNativeUInt t) + { + unsafe + { +#if DEBUG && !PROFILE_SVELTO + if (t.data == null) + throw new Exception("using disposed SharedUInt"); +#endif + + return *t.data; + } + } + + public void Dispose() + { + unsafe + { + if (data != null) + { + Marshal.FreeHGlobal((IntPtr) data); + data = null; + } + } + } + + public void Decrement() + { + unsafe + { + int result = Interlocked.Decrement(ref Unsafe.As(ref *data)); + +#if DEBUG && !PROFILE_SVELTO + if (result < 0) + throw new Exception("can't have negative numbers"); +#endif + } + } + + public void Increment() + { + unsafe + { + Interlocked.Increment(ref Unsafe.As(ref *data)); + } + } + } +} \ No newline at end of file diff --git a/Svelto.ECS/DataStructures/TypeSafeDictionary.cs b/Svelto.ECS/DataStructures/TypeSafeDictionary.cs index d902d72..94e38d7 100644 --- a/Svelto.ECS/DataStructures/TypeSafeDictionary.cs +++ b/Svelto.ECS/DataStructures/TypeSafeDictionary.cs @@ -1,47 +1,33 @@ using System; +using System.Runtime.CompilerServices; using Svelto.Common; using Svelto.DataStructures; namespace Svelto.ECS.Internal { - public interface ITypeSafeDictionary - { - int Count { get; } - ITypeSafeDictionary Create(); - - void AddEntitiesToEngines( - FasterDictionary, FasterList> entityViewEnginesDb, - ITypeSafeDictionary realDic, in PlatformProfiler profiler, ExclusiveGroup.ExclusiveGroupStruct @group); - - void RemoveEntitiesFromEngines(FasterDictionary, FasterList> entityViewEnginesDB, - in PlatformProfiler profiler, ExclusiveGroup.ExclusiveGroupStruct @group); - - void AddEntitiesFromDictionary(ITypeSafeDictionary entitiesToSubmit, uint groupId); - - void MoveEntityFromEngines(EGID fromEntityGid, EGID? toEntityID, ITypeSafeDictionary toGroup, - FasterDictionary, FasterList> engines, in PlatformProfiler profiler); - - void AddEntityToDictionary(EGID fromEntityGid, EGID toEntityID, ITypeSafeDictionary toGroup); - void RemoveEntityFromDictionary(EGID fromEntityGid, in PlatformProfiler profiler); - - void SetCapacity(uint size); - void Trim(); - void Clear(); - void FastClear(); - bool Has(uint entityIdEntityId); - } - - class TypeSafeDictionary : FasterDictionary, - ITypeSafeDictionary where TValue : struct, IEntityStruct + 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); - public TypeSafeDictionary(uint size) : base(size) {} - public TypeSafeDictionary() {} + public TypeSafeDictionary(uint size) + { + _implementation = new FasterDictionary(size); + } + + public TypeSafeDictionary() + { + _implementation = new FasterDictionary(1); + } - public void AddEntitiesFromDictionary(ITypeSafeDictionary entitiesToSubmit, uint groupId) + /// + /// Add entities from external typeSafeDictionary + /// + /// + /// + /// + public void AddEntitiesFromDictionary(ITypeSafeDictionary entitiesToSubmit, uint groupId) { var typeSafeDictionary = entitiesToSubmit as TypeSafeDictionary; @@ -49,166 +35,190 @@ namespace Svelto.ECS.Internal { try { - if (_hasEgid) SetEGIDWithoutBoxing.SetIDWithoutBoxing(ref tuple.Value, new EGID(tuple.Key, groupId)); + if (_hasEgid) + SetEGIDWithoutBoxing.SetIDWithoutBoxing(ref tuple.Value, new EGID(tuple.Key, groupId)); - Add(tuple.Key, tuple.Value); + _implementation.Add(tuple.Key, tuple.Value); } catch (Exception e) { - throw new TypeSafeDictionaryException( - "trying to add an EntityView with the same ID more than once Entity: " - .FastConcat(typeof(TValue).ToString()).FastConcat(", group ").FastConcat(groupId).FastConcat(", id ").FastConcat(tuple.Key), 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); - public void AddEntitiesToEngines( - FasterDictionary, FasterList> entityViewEnginesDB, - ITypeSafeDictionary realDic, in PlatformProfiler profiler, ExclusiveGroup.ExclusiveGroupStruct @group) + 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, FasterList> entityComponentEnginesDB, + ITypeSafeDictionary realDic, + ExclusiveGroupStruct @group, + in PlatformProfiler profiler) { - var typeSafeDictionary = realDic as TypeSafeDictionary; + 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 this) - AddEntityViewToEngines(entityViewEnginesDB, ref typeSafeDictionary.GetValueByRef(value.Key), null, - in profiler, new EGID(value.Key, group)); + foreach (var value in _implementation) + AddEntityComponentToEngines(entityComponentEnginesDB, ref typeSafeDictionary.GetValueByRef(value.Key), null, + in profiler, new EGID(value.Key, group)); } public void RemoveEntitiesFromEngines( - FasterDictionary, FasterList> entityViewEnginesDB, - in PlatformProfiler profiler, ExclusiveGroup.ExclusiveGroupStruct @group) + FasterDictionary, FasterList> entityComponentEnginesDB, in PlatformProfiler profiler, + ExclusiveGroupStruct @group) { - foreach (var value in this) - RemoveEntityViewFromEngines(entityViewEnginesDB, ref GetValueByRef(value.Key), null, in profiler, - new EGID(value.Key, group)); + foreach (var value in _implementation) + RemoveEntityComponentFromEngines(entityComponentEnginesDB, ref _implementation.GetValueByRef(value.Key), null, + in profiler, new EGID(value.Key, group)); } - public bool Has(uint entityIdEntityId) - { - return ContainsKey(entityIdEntityId); - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FastClear() { _implementation.FastClear(); } - public void RemoveEntityFromDictionary(EGID fromEntityGid, in PlatformProfiler profiler) - { - Remove(fromEntityGid.entityID); - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Has(uint key) { return _implementation.ContainsKey(key); } - public void AddEntityToDictionary(EGID fromEntityGid, EGID toEntityID, ITypeSafeDictionary toGroup) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void RemoveEntityFromDictionary(EGID fromEntityGid) { - var valueIndex = GetIndex(fromEntityGid.entityID); + _implementation.Remove(fromEntityGid.entityID); + } - if (toGroup != null) - { - var toGroupCasted = toGroup as TypeSafeDictionary; - ref var entity = ref valuesArray[valueIndex]; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void SetCapacity(uint size) { _implementation.SetCapacity(size); } - if (_hasEgid) SetEGIDWithoutBoxing.SetIDWithoutBoxing(ref entity, toEntityID); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Trim() { _implementation.Trim(); } - toGroupCasted.Add(fromEntityGid.entityID, entity); - } - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Clear() { _implementation.Clear(); } - public void MoveEntityFromEngines(EGID fromEntityGid, EGID? toEntityID, ITypeSafeDictionary toGroup, - FasterDictionary, FasterList> engines, in PlatformProfiler profiler) + public void MoveEntityFromEngines(EGID fromEntityGid, + EGID? toEntityID, ITypeSafeDictionary toGroup, + FasterDictionary, FasterList> engines, + in PlatformProfiler profiler) { - var valueIndex = GetIndex(fromEntityGid.entityID); - - ref var entity = ref valuesArray[valueIndex]; + var valueIndex = _implementation.GetIndex(fromEntityGid.entityID); + + ref var entity = ref _implementation.unsafeValues[(int) valueIndex]; if (toGroup != null) { - RemoveEntityViewFromEngines(engines, ref entity, fromEntityGid.groupID, in profiler, - fromEntityGid); + RemoveEntityComponentFromEngines(engines, ref entity, fromEntityGid.groupID, in profiler, fromEntityGid); - var toGroupCasted = toGroup as TypeSafeDictionary; + var toGroupCasted = toGroup as ITypeSafeDictionary; var previousGroup = fromEntityGid.groupID; if (_hasEgid) SetEGIDWithoutBoxing.SetIDWithoutBoxing(ref entity, toEntityID.Value); var index = toGroupCasted.GetIndex(toEntityID.Value.entityID); - AddEntityViewToEngines(engines, ref toGroupCasted.valuesArray[index], previousGroup, - in profiler, toEntityID.Value); + AddEntityComponentToEngines(engines, ref toGroupCasted.unsafeValues[(int) index], previousGroup, in profiler, + toEntityID.Value); } else - RemoveEntityViewFromEngines(engines, ref entity, null, in profiler, fromEntityGid); + RemoveEntityComponentFromEngines(engines, ref entity, null, in profiler, fromEntityGid); } - public ITypeSafeDictionary Create() + public uint Count { - return new TypeSafeDictionary(); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _implementation.count; } - void AddEntityViewToEngines(FasterDictionary, FasterList> entityViewEnginesDB, - ref TValue entity, ExclusiveGroup.ExclusiveGroupStruct? previousGroup, - in PlatformProfiler profiler, EGID egid) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ITypeSafeDictionary Create() { return new TypeSafeDictionary(); } + + void AddEntityComponentToEngines(FasterDictionary, FasterList> entityComponentEnginesDB, + ref TValue entity, + ExclusiveGroupStruct? previousGroup, + in PlatformProfiler profiler, + EGID egid) { //get all the engines linked to TValue - if (!entityViewEnginesDB.TryGetValue(new RefWrapper(_type), out var entityViewsEngines)) return; + if (!entityComponentEnginesDB.TryGetValue(new RefWrapper(_type), out var entityComponentsEngines)) return; if (previousGroup == null) { - for (var i = 0; i < entityViewsEngines.Count; i++) + for (var i = 0; i < entityComponentsEngines.count; i++) try { - using (profiler.Sample(entityViewsEngines[i], _typeName)) + using (profiler.Sample(entityComponentsEngines[i], _typeName)) { - (entityViewsEngines[i] as IReactOnAddAndRemove).Add(ref entity, egid); + (entityComponentsEngines[i] as IReactOnAddAndRemove).Add(ref entity, egid); } } catch (Exception e) { - throw new ECSException( - "Code crashed inside Add callback ".FastConcat(typeof(TValue).ToString()), e); + throw new + ECSException("Code crashed inside Add callback ".FastConcat(typeof(TValue).ToString()), e); } } else { - for (var i = 0; i < entityViewsEngines.Count; i++) + for (var i = 0; i < entityComponentsEngines.count; i++) try { - using (profiler.Sample(entityViewsEngines[i], _typeName)) + using (profiler.Sample(entityComponentsEngines[i], _typeName)) { - (entityViewsEngines[i] as IReactOnSwap).MovedTo(ref entity, previousGroup.Value, - egid); + (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); + throw new + ECSException("Code crashed inside MovedTo callback ".FastConcat(typeof(TValue).ToString()), + e); } } } - static void RemoveEntityViewFromEngines( - FasterDictionary, FasterList> @group, ref TValue entity, - ExclusiveGroup.ExclusiveGroupStruct? previousGroup, in PlatformProfiler profiler, EGID egid) + static void RemoveEntityComponentFromEngines(FasterDictionary, FasterList> @group, + ref TValue entity, + uint? previousGroup, + in PlatformProfiler profiler, + EGID egid) { - if (!@group.TryGetValue(new RefWrapper(_type), out var entityViewsEngines)) return; + if (!@group.TryGetValue(new RefWrapper(_type), out var entityComponentsEngines)) return; if (previousGroup == null) { - for (var i = 0; i < entityViewsEngines.Count; i++) + for (var i = 0; i < entityComponentsEngines.count; i++) try { - using (profiler.Sample(entityViewsEngines[i], _typeName)) - (entityViewsEngines[i] as IReactOnAddAndRemove).Remove(ref entity, egid); + 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); + throw new + ECSException("Code crashed inside Remove callback ".FastConcat(typeof(TValue).ToString()), + e); } } -#if SEEMS_UNNECESSARY +#if SEEMS_UNNECESSARY else { - for (var i = 0; i < entityViewsEngines.Count; i++) + for (var i = 0; i < entityComponentsEngines.Count; i++) try { - using (profiler.Sample(entityViewsEngines[i], _typeName)) - (entityViewsEngines[i] as IReactOnSwap).MovedFrom(ref entity, egid); + using (profiler.Sample(entityComponentsEngines[i], _typeName)) + (entityComponentsEngines[i] as IReactOnSwap).MovedFrom(ref entity, egid); } catch (Exception e) { @@ -218,5 +228,60 @@ namespace Svelto.ECS.Internal } #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(egidEntityId, entityComponent); } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public FasterDictionary.FasterDictionaryKeyValueEnumerator GetEnumerator() + { + return _implementation.GetEnumerator(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ref TValue GetValueByRef(uint key) { return ref _implementation.GetValueByRef(key); } + + public ref TValue this[uint idEntityId] + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => ref _implementation.GetValueByRef(idEntityId); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public uint GetIndex(uint valueEntityId) { return _implementation.GetIndex(valueEntityId); } + + public TValue[] unsafeValues + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _implementation.unsafeValues; + } + + public object GenerateSentinel() + { + return default; + } + + [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) { return ref _implementation.GetOrCreate(idEntityId); } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool TryFindIndex(uint entityId, out uint index) { return _implementation.TryFindIndex(entityId, out index); } + + internal FasterDictionary implementation => _implementation; + + readonly FasterDictionary _implementation; } } \ No newline at end of file diff --git a/Svelto.ECS/DataStructures/TypeSafeDictionaryUtilities.cs b/Svelto.ECS/DataStructures/TypeSafeDictionaryUtilities.cs new file mode 100644 index 0000000..4d21f9f --- /dev/null +++ b/Svelto.ECS/DataStructures/TypeSafeDictionaryUtilities.cs @@ -0,0 +1,21 @@ +namespace Svelto.ECS.Internal +{ + static class TypeSafeDictionaryUtilities + { + internal static EGIDMapper ToEGIDMapper(this ITypeSafeDictionary dic, + ExclusiveGroupStruct groupStructId) where T:struct, IEntityComponent + { + var mapper = new EGIDMapper(groupStructId, dic); + + return mapper; + } + + internal static NativeEGIDMapper ToNativeEGIDMapper(this TypeSafeDictionary dic, + ExclusiveGroupStruct groupStructId) where T : unmanaged, IEntityComponent + { + var mapper = new NativeEGIDMapper(groupStructId, dic.implementation.ToNative()); + + return mapper; + } + } +} \ No newline at end of file diff --git a/Svelto.ECS/DataStructures/UnsafeArray.cs b/Svelto.ECS/DataStructures/UnsafeArray.cs new file mode 100644 index 0000000..e488d68 --- /dev/null +++ b/Svelto.ECS/DataStructures/UnsafeArray.cs @@ -0,0 +1,124 @@ +using System; +using System.Runtime.CompilerServices; +using Svelto.Common; + +namespace Svelto.ECS.DataStructures +{ + struct UnsafeArray : IDisposable + { + internal unsafe byte* ptr => _ptr; + //expressed in bytes + internal uint capacity { get; private set; } + //expressed in bytes + internal uint count => _writeIndex; + //expressed in bytes + internal uint space => capacity - count; + + /// + /// + internal Allocator allocator; +#if DEBUG && !PROFILE_SVELTO +#pragma warning disable 649 + internal uint id; +#pragma warning restore 649 +#endif + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ref T Get(uint index) where T : unmanaged + { + unsafe + { + T* buffer = (T*) ptr; + return ref buffer[index]; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Set(uint index, in T value) where T : unmanaged + { + unsafe + { + int sizeOf = MemoryUtilities.SizeOf(); + uint writeIndex = (uint) (index * sizeOf); + +#if DEBUG && !PROFILE_SVELTO + if (capacity < writeIndex + sizeOf) + throw new Exception("no writing authorized"); +#endif + T* buffer = (T*) ptr; + buffer[index] = value; + + if (_writeIndex < writeIndex + sizeOf) + _writeIndex = (uint) (writeIndex + sizeOf); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Add(in T value) where T : unmanaged + { + unsafe + { + var structSize = MemoryUtilities.SizeOf(); + +#if DEBUG && !PROFILE_SVELTO + if (space - structSize < 0) + throw new Exception("no writing authorized"); +#endif + Unsafe.Write(ptr + _writeIndex, value); + + _writeIndex += (uint)structSize; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal void Realloc(uint alignOf, uint newCapacity) + { + unsafe + { + byte* newPointer = null; +#if DEBUG && !PROFILE_SVELTO + if (capacity > 0 && newCapacity <= capacity) + throw new Exception("new capacity must be bigger than current"); +#endif + if (newCapacity >= 0) + { + newPointer = (byte*) MemoryUtilities.Alloc(newCapacity, alignOf, allocator); + if (count > 0) + MemoryUtilities.MemCpy((IntPtr) newPointer, (IntPtr) ptr, count); + } + + if (ptr != null) + MemoryUtilities.Free((IntPtr) ptr, allocator); + + _ptr = newPointer; + capacity = newCapacity; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Dispose() + { + unsafe + { + if (ptr != null) + MemoryUtilities.Free((IntPtr) ptr, allocator); + + _ptr = null; + _writeIndex = 0; + capacity = 0; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Clear() + { + _writeIndex = 0; + } + +#if UNITY_ECS + [global::Unity.Collections.LowLevel.Unsafe.NativeDisableUnsafePtrRestriction] +#endif + unsafe byte* _ptr; + uint _writeIndex; + } +} \ No newline at end of file diff --git a/Svelto.ECS/DataStructures/UnsafeBlob.cs b/Svelto.ECS/DataStructures/UnsafeBlob.cs new file mode 100644 index 0000000..36d2491 --- /dev/null +++ b/Svelto.ECS/DataStructures/UnsafeBlob.cs @@ -0,0 +1,267 @@ +using System; +using System.Runtime.CompilerServices; +using Svelto.Common; + +namespace Svelto.ECS.DataStructures +{ + public struct UnsafeArrayIndex + { + internal uint index; + internal uint capacity; + } + + /// + /// Note: this must work inside burst, so it must follow burst restrictions + /// + struct UnsafeBlob : IDisposable + { + internal unsafe byte* ptr => _ptr; + //expressed in bytes + internal uint capacity { get; private set; } + //expressed in bytes + internal uint size => _writeIndex - _readIndex; + //expressed in bytes + internal uint space => capacity - size; + + /// + /// + internal Allocator allocator; +#if DEBUG && !PROFILE_SVELTO + internal uint id; +#endif + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal void Write(in T item) where T : struct + { + unsafe + { + var structSize = (uint) MemoryUtilities.SizeOf(); + + //the idea is, considering the wrap, a read pointer must always be behind a writer pointer +#if DEBUG && !PROFILE_SVELTO + if (space - (int)structSize < 0) + throw new Exception("no writing authorized"); +#endif + var head = _writeIndex % capacity; + + if (head + structSize <= capacity) + { + Unsafe.Write(ptr + head, item); + } + else + //copy with wrap, will start to copy and wrap for the reminder + { + var byteCountToEnd = capacity - head; + //need a copy to be sure that the GC won't move the data around + T copyItem = item; + void* asPointer = Unsafe.AsPointer(ref copyItem); + MemoryUtilities.MemCpy((IntPtr) (ptr + head), (IntPtr) asPointer, byteCountToEnd); + var restCount = structSize - byteCountToEnd; + //todo: check the difference between unaligned and standard + MemoryUtilities.MemCpy((IntPtr) ptr, (IntPtr) ((byte *)asPointer + byteCountToEnd), restCount); + } + + uint paddedStructSize = (uint) Align4(structSize); + + _writeIndex += paddedStructSize; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal void WriteUnaligned(in T item) where T : struct + { + unsafe + { + uint structSize = (uint) MemoryUtilities.SizeOf(); + + //the idea is, considering the wrap, a read pointer must always be behind a writer pointer +#if DEBUG && !PROFILE_SVELTO + if (space - (int)structSize < 0) + throw new Exception("no writing authorized"); +#endif + var pointer = _writeIndex % capacity; + + if (pointer + structSize <= capacity) + Unsafe.Write(ptr + pointer, item); + else + { + var byteCount = capacity - pointer; + T copyItem = item; + var asPointer = Unsafe.AsPointer(ref copyItem); + Unsafe.CopyBlock(ptr + pointer, asPointer, byteCount); + var restCount = structSize - byteCount; + Unsafe.CopyBlock(ptr, (byte *)asPointer + byteCount, restCount); + } + + _writeIndex += structSize; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal T Read() where T : struct + { + unsafe + { + var structSize = (uint) MemoryUtilities.SizeOf(); + +#if DEBUG && !PROFILE_SVELTO + if (size < structSize) //are there enough bytes to read? + throw new Exception("dequeuing empty queue or unexpected type dequeued"); + if (_readIndex > _writeIndex) + throw new Exception("unexpected read"); +#endif + var head = _readIndex % capacity; + uint paddedStructSize = (uint) Align4(structSize); + _readIndex += paddedStructSize; + + if (_readIndex == _writeIndex) + { + //resetting the Indices has the benefit to let the Reserve work in more occasions and + //the rapping happening less often. If the _readIndex reached the _writeIndex, it means + //that there is no data left to read, so we can start to write again from the begin of the memory + _writeIndex = 0; + _readIndex = 0; + } + + if (head + paddedStructSize <= capacity) + { + return Unsafe.Read(ptr + head); + } + else + { + T item = default; + var byteCountToEnd = capacity - head; + var asPointer = Unsafe.AsPointer(ref item); + //todo: check the difference between unaligned and standard + MemoryUtilities.MemCpy((IntPtr) asPointer, (IntPtr) (ptr + head), byteCountToEnd); + var restCount = structSize - byteCountToEnd; + MemoryUtilities.MemCpy((IntPtr) ((byte *)asPointer + byteCountToEnd), (IntPtr) ptr, restCount); + + return item; + } + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal ref T Reserve(out UnsafeArrayIndex index) where T : unmanaged + { + unsafe + { + var sizeOf = (uint) MemoryUtilities.SizeOf(); + + T* buffer = (T *)(byte*) (ptr + _writeIndex); +#if DEBUG && !PROFILE_SVELTO + if (_writeIndex > capacity) throw new Exception($"can't reserve if the writeIndex wrapped around the capacity, writeIndex {_writeIndex} capacity {capacity}"); + if (_writeIndex + sizeOf > capacity) throw new Exception("out of bound reserving"); +#endif + index = new UnsafeArrayIndex() + { + capacity = capacity + , index = _writeIndex + }; + + var align4 = (uint) Align4(sizeOf); + _writeIndex += align4; + + return ref buffer[0]; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal ref T AccessReserved(UnsafeArrayIndex index) where T : unmanaged + { + unsafe + { +#if DEBUG && !PROFILE_SVELTO + var size = MemoryUtilities.SizeOf(); + if (index.index + size > capacity) throw new Exception($"out of bound access, index {index.index} size {size} capacity {capacity}"); +#endif + T* buffer = (T*) (byte*)(ptr + index.index); + + return ref buffer[0]; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal void Realloc(uint alignOf, uint newCapacity) + { + unsafe + { + //be sure it's multiple of 4. Assuming that what we write is aligned to 4, then we will always have aligned wrapped heads + newCapacity = Align4(newCapacity); + + byte* newPointer = null; +#if DEBUG && !PROFILE_SVELTO + if (newCapacity <= capacity) + throw new Exception("new capacity must be bigger than current"); +#endif + if (newCapacity > 0) + { + newPointer = (byte*) MemoryUtilities.Alloc(newCapacity, alignOf, allocator); + if (size > 0) + { + var readerHead = _readIndex % capacity; + var writerHead = _writeIndex % capacity; + + if (readerHead < writerHead) + { + //copy to the new pointer, from th reader position + MemoryUtilities.MemCpy((IntPtr) newPointer, (IntPtr) (ptr + readerHead), _writeIndex - _readIndex); + } + //the assumption is that if size > 0 (so readerPointer and writerPointer are not the same) + //writerHead wrapped and reached readerHead. so I have to copy from readerHead to the end + //and from the start to writerHead (which is the same position of readerHead) + else + { + var byteCountToEnd = capacity - readerHead; + + MemoryUtilities.MemCpy((IntPtr) newPointer, (IntPtr) (ptr + readerHead), byteCountToEnd); + MemoryUtilities.MemCpy((IntPtr) (newPointer + byteCountToEnd), (IntPtr) ptr, writerHead); + } + } + } + + if (ptr != null) + MemoryUtilities.Free((IntPtr) ptr, allocator); + + _writeIndex = size; + _readIndex = 0; + + _ptr = newPointer; + capacity = newCapacity; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Dispose() + { + unsafe + { + if (ptr != null) + MemoryUtilities.Free((IntPtr) ptr, allocator); + + _ptr = null; + _writeIndex = 0; + capacity = 0; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Clear() + { + _writeIndex = 0; + _readIndex = 0; + } + + uint Align4(uint input) + { + return (uint)(Math.Ceiling(input / 4.0) * 4); + } + +#if UNITY_ECS + [global::Unity.Collections.LowLevel.Unsafe.NativeDisableUnsafePtrRestriction] +#endif + unsafe byte* _ptr; + uint _writeIndex, _readIndex; + } +} diff --git a/Svelto.ECS/Dispatcher/DispatchOnChange.cs b/Svelto.ECS/Dispatcher/DispatchOnChange.cs index d1ce756..c2a40aa 100644 --- a/Svelto.ECS/Dispatcher/DispatchOnChange.cs +++ b/Svelto.ECS/Dispatcher/DispatchOnChange.cs @@ -4,8 +4,10 @@ namespace Svelto.ECS { public class DispatchOnChange : DispatchOnSet where T:IEquatable { - public DispatchOnChange(EGID senderID) : base(senderID) - { } + public DispatchOnChange(EGID senderID, T initialValue = default(T)) : base(senderID) + { + _value = initialValue; + } public new T value { diff --git a/Svelto.ECS/Dispatcher/DispatchOnSet.cs b/Svelto.ECS/Dispatcher/DispatchOnSet.cs index d407ae8..8334d2e 100644 --- a/Svelto.ECS/Dispatcher/DispatchOnSet.cs +++ b/Svelto.ECS/Dispatcher/DispatchOnSet.cs @@ -16,18 +16,23 @@ namespace Svelto.ECS _value = value; if (_paused == false) - _subscribers(_senderID, value); + _subscriber(_senderID, value); } } public void NotifyOnValueSet(Action action) { - _subscribers += 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(Action action) + public void StopNotify() { - _subscribers -= action; + _subscriber = null; + _paused = true; } public void PauseNotify() { _paused = true; } @@ -36,7 +41,7 @@ namespace Svelto.ECS protected T _value; readonly EGID _senderID; - Action _subscribers; + Action _subscriber; bool _paused; } } diff --git a/Svelto.ECS/DynamicEntityDescriptor.cs b/Svelto.ECS/DynamicEntityDescriptor.cs index f40d3e7..df1da90 100644 --- a/Svelto.ECS/DynamicEntityDescriptor.cs +++ b/Svelto.ECS/DynamicEntityDescriptor.cs @@ -13,57 +13,57 @@ namespace Svelto.ECS { internal DynamicEntityDescriptor(bool isExtendible) : this() { - var defaultEntities = EntityDescriptorTemplate.descriptor.entitiesToBuild; + var defaultEntities = EntityDescriptorTemplate.descriptor.componentsToBuild; var length = defaultEntities.Length; - _entitiesToBuild = new IEntityBuilder[length + 1]; + ComponentsToBuild = new IComponentBuilder[length + 1]; - Array.Copy(defaultEntities, 0, _entitiesToBuild, 0, length); + 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 - _entitiesToBuild[length] = new EntityBuilder + ComponentsToBuild[length] = new ComponentBuilder ( - new EntityStructInfoView + new EntityInfoComponentView { - entitiesToBuild = _entitiesToBuild + componentsToBuild = ComponentsToBuild } ); } - public DynamicEntityDescriptor(IEntityBuilder[] extraEntityBuilders) : this() + public DynamicEntityDescriptor(IComponentBuilder[] extraEntityBuilders) : this() { var extraEntitiesLength = extraEntityBuilders.Length; - _entitiesToBuild = Construct(extraEntitiesLength, extraEntityBuilders, - EntityDescriptorTemplate.descriptor.entitiesToBuild); + ComponentsToBuild = Construct(extraEntitiesLength, extraEntityBuilders, + EntityDescriptorTemplate.descriptor.componentsToBuild); } - public DynamicEntityDescriptor(FasterList extraEntityBuilders) : this() + public DynamicEntityDescriptor(FasterList extraEntityBuilders) : this() { - var extraEntities = extraEntityBuilders.ToArrayFast(); - var extraEntitiesLength = extraEntityBuilders.Count; + var extraEntities = extraEntityBuilders.ToArrayFast(out _); + var extraEntitiesLength = extraEntityBuilders.count; - _entitiesToBuild = Construct(extraEntitiesLength, extraEntities, - EntityDescriptorTemplate.descriptor.entitiesToBuild); + ComponentsToBuild = Construct((int) extraEntitiesLength, extraEntities, + EntityDescriptorTemplate.descriptor.componentsToBuild); } public void ExtendWith() where T : IEntityDescriptor, new() { - var newEntitiesToBuild = EntityDescriptorTemplate.descriptor.entitiesToBuild; + var newEntitiesToBuild = EntityDescriptorTemplate.descriptor.componentsToBuild; - _entitiesToBuild = Construct(newEntitiesToBuild.Length, newEntitiesToBuild, _entitiesToBuild); + ComponentsToBuild = Construct(newEntitiesToBuild.Length, newEntitiesToBuild, ComponentsToBuild); } - public void ExtendWith(IEntityBuilder[] extraEntities) + public void ExtendWith(IComponentBuilder[] extraEntities) { - _entitiesToBuild = Construct(extraEntities.Length, extraEntities, _entitiesToBuild); + ComponentsToBuild = Construct(extraEntities.Length, extraEntities, ComponentsToBuild); } - static IEntityBuilder[] Construct(int extraEntitiesLength, IEntityBuilder[] extraEntities, - IEntityBuilder[] startingEntities) + static IComponentBuilder[] Construct(int extraEntitiesLength, IComponentBuilder[] extraEntities, + IComponentBuilder[] startingEntities) { - IEntityBuilder[] localEntitiesToBuild; + IComponentBuilder[] localEntitiesToBuild; if (extraEntitiesLength == 0) { @@ -74,24 +74,24 @@ namespace Svelto.ECS var defaultEntities = startingEntities; var length = defaultEntities.Length; - var index = SetupSpecialEntityStruct(defaultEntities, out localEntitiesToBuild, extraEntitiesLength); + var index = SetupSpecialEntityComponent(defaultEntities, out localEntitiesToBuild, extraEntitiesLength); Array.Copy(extraEntities, 0, localEntitiesToBuild, length, extraEntitiesLength); //assign it after otherwise the previous copy will overwrite the value in case the item //is already present - localEntitiesToBuild[index] = new EntityBuilder + localEntitiesToBuild[index] = new ComponentBuilder ( - new EntityStructInfoView + new EntityInfoComponentView { - entitiesToBuild = localEntitiesToBuild + componentsToBuild = localEntitiesToBuild } ); return localEntitiesToBuild; } - static int SetupSpecialEntityStruct(IEntityBuilder[] defaultEntities, out IEntityBuilder[] entitiesToBuild, + static int SetupSpecialEntityComponent(IComponentBuilder[] defaultEntities, out IComponentBuilder[] componentsToBuild, int extraLenght) { int length = defaultEntities.Length; @@ -100,7 +100,7 @@ namespace Svelto.ECS for (var i = 0; i < length; i++) { //the special entity already exists - if (defaultEntities[i].GetEntityType() == EntityBuilderUtilities.ENTITY_STRUCT_INFO_VIEW) + if (defaultEntities[i].GetEntityComponentType() == EntityBuilderUtilities.ENTITY_STRUCT_INFO_VIEW) { index = i; break; @@ -110,19 +110,19 @@ namespace Svelto.ECS if (index == -1) { index = length + extraLenght; - entitiesToBuild = new IEntityBuilder[index + 1]; + componentsToBuild = new IComponentBuilder[index + 1]; } else - entitiesToBuild = new IEntityBuilder[length + extraLenght]; + componentsToBuild = new IComponentBuilder[length + extraLenght]; - Array.Copy(defaultEntities, 0, entitiesToBuild, 0, length); + Array.Copy(defaultEntities, 0, componentsToBuild, 0, length); return index; } - public IEntityBuilder[] entitiesToBuild => _entitiesToBuild; + public IComponentBuilder[] componentsToBuild => ComponentsToBuild; - IEntityBuilder[] _entitiesToBuild; + IComponentBuilder[] ComponentsToBuild; } } \ No newline at end of file diff --git a/Svelto.ECS/ECSResources/ECSResources.cs b/Svelto.ECS/ECSResources/ECSResources.cs index 70ba614..f307b2b 100644 --- a/Svelto.ECS/ECSResources/ECSResources.cs +++ b/Svelto.ECS/ECSResources/ECSResources.cs @@ -22,12 +22,12 @@ namespace Svelto.ECS.Experimental { _resources.Add(resource); - return (uint)_resources.Count; + return (uint)_resources.count; } public static T FromECS(uint id) { - if (id - 1 < _resources.Count) + if (id - 1 < _resources.count) return _resources[(int) id - 1]; return default; diff --git a/Svelto.ECS/ECSResources/ECSString.cs b/Svelto.ECS/ECSResources/ECSString.cs index 4eac06c..b31e54e 100644 --- a/Svelto.ECS/ECSResources/ECSString.cs +++ b/Svelto.ECS/ECSResources/ECSString.cs @@ -5,34 +5,48 @@ namespace Svelto.ECS.Experimental [Serialization.DoNotSerialize] public struct ECSString:IEquatable { - uint id; + uint _id; public ECSString(string newText) { - id = ResourcesECSDB.ToECS(newText); + _id = ResourcesECSDB.ToECS(newText); + } + + ECSString(uint id) + { + _id = id; } public static implicit operator string(ECSString ecsString) { - return ResourcesECSDB.FromECS(ecsString.id); + return ResourcesECSDB.FromECS(ecsString._id); } public void Set(string newText) { - if (id != 0) - ResourcesECSDB.resources(id) = newText; + if (_id != 0) + ResourcesECSDB.resources(_id) = newText; else - id = ResourcesECSDB.ToECS(newText); + _id = ResourcesECSDB.ToECS(newText); + } + + public ECSString Copy() + { + DBC.ECS.Check.Require(_id != 0, "copying not initialized string"); + + var id = ResourcesECSDB.ToECS(ResourcesECSDB.resources(_id)); + + return new ECSString(id); } public bool Equals(ECSString other) { - return other.id == id; + return other._id == _id; } public override string ToString() { - return ResourcesECSDB.FromECS(id); + return ResourcesECSDB.FromECS(_id); } } } \ No newline at end of file diff --git a/Svelto.ECS/EGID.cs b/Svelto.ECS/EGID.cs index 4e7d768..176c19b 100644 --- a/Svelto.ECS/EGID.cs +++ b/Svelto.ECS/EGID.cs @@ -1,5 +1,5 @@ using System; -using System.Collections.Generic; +using System.Runtime.InteropServices; #pragma warning disable 660,661 @@ -8,12 +8,13 @@ namespace Svelto.ECS //todo: add debug map [Serialization.DoNotSerialize] [Serializable] - public struct EGID:IEquatable,IEqualityComparer,IComparable + [StructLayout(LayoutKind.Explicit)] + public struct EGID:IEquatable,IComparable { - public uint entityID => (uint) (_GID & 0xFFFFFFFF); - - public ExclusiveGroup.ExclusiveGroupStruct groupID => new ExclusiveGroup.ExclusiveGroupStruct((uint) (_GID >> 32)); - + [FieldOffset(0)] public readonly uint entityID; + [FieldOffset(4)] public readonly ExclusiveGroupStruct groupID; + [FieldOffset(0)] readonly ulong _GID; + public static bool operator ==(EGID obj1, EGID obj2) { return obj1._GID == obj2._GID; @@ -24,7 +25,7 @@ namespace Svelto.ECS return obj1._GID != obj2._GID; } - public EGID(uint entityID, ExclusiveGroup.ExclusiveGroupStruct groupID) : this() + public EGID(uint entityID, ExclusiveGroupStruct groupID) : this() { _GID = MAKE_GLOBAL_ID(entityID, groupID); } @@ -52,11 +53,16 @@ namespace Svelto.ECS return x == y; } - public int GetHashCode(EGID obj) + public override int GetHashCode() { return _GID.GetHashCode(); } + public int GetHashCode(EGID egid) + { + return egid.GetHashCode(); + } + public int CompareTo(EGID other) { return _GID.CompareTo(other._GID); @@ -71,7 +77,5 @@ namespace Svelto.ECS { return "id ".FastConcat(entityID).FastConcat(" group ").FastConcat(groupID); } - - readonly ulong _GID; } } diff --git a/Svelto.ECS/EGIDComponent.cs b/Svelto.ECS/EGIDComponent.cs new file mode 100644 index 0000000..c818065 --- /dev/null +++ b/Svelto.ECS/EGIDComponent.cs @@ -0,0 +1,7 @@ +namespace Svelto.ECS +{ + public struct EGIDComponent:IEntityComponent, INeedEGID + { + public EGID ID { get; set; } + } +} \ No newline at end of file diff --git a/Svelto.ECS/EGIDMapper.cs b/Svelto.ECS/EGIDMapper.cs index 939e20b..b3ce79c 100644 --- a/Svelto.ECS/EGIDMapper.cs +++ b/Svelto.ECS/EGIDMapper.cs @@ -1,37 +1,66 @@ using System; using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using Svelto.DataStructures; +using Svelto.ECS.Internal; namespace Svelto.ECS { - public struct EGIDMapper where T : struct, IEntityStruct + public readonly struct EGIDMapper where T : struct, IEntityComponent { - internal FasterDictionary map; + internal readonly ITypeSafeDictionary map; + public uint Length => map.Count; + public ExclusiveGroupStruct groupID { get; } + + public EGIDMapper(ExclusiveGroupStruct groupStructId, ITypeSafeDictionary dic):this() + { + groupID = groupStructId; + map = dic; + } [MethodImpl(MethodImplOptions.AggressiveInlining)] public ref T Entity(uint entityID) { -#if DEBUG && !PROFILER +#if DEBUG && !PROFILE_SVELTO if (map.TryFindIndex(entityID, out var findIndex) == false) throw new Exception("Entity not found in this group ".FastConcat(typeof(T).ToString())); #else map.TryFindIndex(entityID, out var findIndex); #endif - return ref map.valuesArray[findIndex]; + return ref map.unsafeValues[(int) findIndex]; } public bool TryGetEntity(uint entityID, out T value) { if (map.TryFindIndex(entityID, out var index)) { - value = map.GetDirectValue(index); + value = map.unsafeValues[index]; return true; } value = default; return false; } + + public T[] GetArrayAndEntityIndex(uint entityID, out uint index) + { + if (map.TryFindIndex(entityID, out index)) + { + return map.unsafeValues; + } + + throw new ECSException("Entity not found"); + } + + public bool TryGetArrayAndEntityIndex(uint entityID, out uint index, out T[] array) + { + if (map.TryFindIndex(entityID, out index)) + { + array = map.unsafeValues; + return true; + } + + array = default; + return false; + } } } diff --git a/Svelto.ECS/EnginesRoot.DoubleBufferedEntityViews.cs b/Svelto.ECS/EnginesRoot.DoubleBufferedEntityViews.cs index f6077f3..b602797 100644 --- a/Svelto.ECS/EnginesRoot.DoubleBufferedEntityViews.cs +++ b/Svelto.ECS/EnginesRoot.DoubleBufferedEntityViews.cs @@ -26,18 +26,18 @@ namespace Svelto.ECS public void ClearOther() { //do not clear the groups created so far, they will be reused, unless they are too many! - var otherCount = other.Count; + var otherCount = other.count; if (otherCount > MaximumNumberOfItemsPerFrameBeforeToClear) { otherEntitiesCreatedPerGroup.FastClear(); other.FastClear(); return; } - var otherValuesArray = other.valuesArray; + var otherValuesArray = other.unsafeValues; for (int i = 0; i < otherCount; ++i) { - var safeDictionariesCount = otherValuesArray[i].Count; - var safeDictionaries = otherValuesArray[i].valuesArray; + var safeDictionariesCount = otherValuesArray[i].count; + var safeDictionaries = otherValuesArray[i].unsafeValues; //do not remove the dictionaries of entities per type created so far, they will be reused if (safeDictionariesCount <= MaximumNumberOfItemsPerFrameBeforeToClear) { @@ -56,18 +56,25 @@ namespace Svelto.ECS 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 + //entities to be created sequentially (the index cannot be managed externally) internal FasterDictionary, ITypeSafeDictionary>> current; internal FasterDictionary, ITypeSafeDictionary>> other; readonly FasterDictionary, ITypeSafeDictionary>> - _entityViewsToAddBufferA = + _entityComponentsToAddBufferA = new FasterDictionary, ITypeSafeDictionary>>(); readonly FasterDictionary, ITypeSafeDictionary>> - _entityViewsToAddBufferB = + _entityComponentsToAddBufferB = new FasterDictionary, ITypeSafeDictionary>>(); readonly FasterDictionary _entitiesCreatedPerGroupA = new FasterDictionary(); @@ -78,8 +85,8 @@ namespace Svelto.ECS currentEntitiesCreatedPerGroup = _entitiesCreatedPerGroupA; otherEntitiesCreatedPerGroup = _entitiesCreatedPerGroupB; - current = _entityViewsToAddBufferA; - other = _entityViewsToAddBufferB; + current = _entityComponentsToAddBufferA; + other = _entityComponentsToAddBufferB; } } } diff --git a/Svelto.ECS/EnginesRoot.Engines.cs b/Svelto.ECS/EnginesRoot.Engines.cs index 9d7a31d..61cfafa 100644 --- a/Svelto.ECS/EnginesRoot.Engines.cs +++ b/Svelto.ECS/EnginesRoot.Engines.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using Svelto.Common; using Svelto.DataStructures; using Svelto.ECS.Internal; using Svelto.ECS.Schedulers; @@ -12,17 +13,20 @@ namespace Svelto.ECS { public EntitiesSubmitter(EnginesRoot enginesRoot) { - _weakReference = new DataStructures.WeakReference(enginesRoot); + _weakReference = new Svelto.DataStructures.WeakReference(enginesRoot); } public void Invoke() { if (_weakReference.IsValid) - _weakReference.Target.SubmitEntityViews(); + _weakReference.Target.SubmitEntityComponents(); } - readonly DataStructures.WeakReference _weakReference; + readonly Svelto.DataStructures.WeakReference _weakReference; } + + public IEntitiesSubmissionScheduler scheduler { get; } + /// /// 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 @@ -31,9 +35,9 @@ namespace Svelto.ECS /// The EntitySubmissionScheduler cannot hold an EnginesRoot reference, that's why /// it must receive a weak reference of the EnginesRoot callback. /// - public EnginesRoot(IEntitySubmissionScheduler entityViewScheduler) + public EnginesRoot(IEntitiesSubmissionScheduler entitiesComponentScheduler) { - _entitiesOperations = new FasterDictionary(); + _entitiesOperations = new ThreadSafeDictionary(); serializationDescriptorMap = new SerializationDescriptorMap(); _reactiveEnginesAddRemove = new FasterDictionary, FasterList>(); _reactiveEnginesSwap = new FasterDictionary, FasterList>(); @@ -42,26 +46,82 @@ namespace Svelto.ECS _disposableEngines = new FasterList(); _transientEntitiesOperations = new FasterList(); - _groupEntityViewsDB = new FasterDictionary, ITypeSafeDictionary>>(); + _groupEntityComponentsDB = new FasterDictionary, ITypeSafeDictionary>>(); _groupsPerEntity = new FasterDictionary, FasterDictionary>(); _groupedEntityToAdd = new DoubleBufferedEntitiesToAdd(); _entitiesStream = new EntitiesStream(); - _entitiesDB = new EntitiesDB(_groupEntityViewsDB, _groupsPerEntity, _entitiesStream); + _entitiesDB = new EntitiesDB(_groupEntityComponentsDB, _groupsPerEntity, _entitiesStream); - _scheduler = entityViewScheduler; - _scheduler.onTick = new EntitiesSubmitter(this); + scheduler = entitiesComponentScheduler; + scheduler.onTick = new EntitiesSubmitter(this); +#if UNITY_ECS + AllocateNativeOperations(); +#endif } - public EnginesRoot(IEntitySubmissionScheduler entityViewScheduler, bool isDeserializationOnly):this(entityViewScheduler) + public EnginesRoot(IEntitiesSubmissionScheduler entitiesComponentScheduler, bool isDeserializationOnly):this(entitiesComponentScheduler) { _isDeserializationOnly = isDeserializationOnly; } + + /// + /// 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() + { + using (var profiler = new PlatformProfiler("Final Dispose")) + { + foreach (var groups in _groupEntityComponentsDB) + { + foreach (var entityList in groups.Value) + { + entityList.Value.RemoveEntitiesFromEngines(_reactiveEnginesAddRemove, profiler, + new ExclusiveGroupStruct(groups.Key)); + } + } + + _groupEntityComponentsDB.Clear(); + _groupsPerEntity.Clear(); + + foreach (var engine in _disposableEngines) + engine.Dispose(); + + _disposableEngines.Clear(); + _enginesSet.Clear(); + _enginesTypeSet.Clear(); + _reactiveEnginesSwap.Clear(); + _reactiveEnginesAddRemove.Clear(); + + _entitiesOperations.Clear(); + _transientEntitiesOperations.Clear(); + scheduler.Dispose(); +#if DEBUG && !PROFILE_SVELTO + _idCheckers.Clear(); +#endif + _groupedEntityToAdd = null; + + _entitiesStream.Dispose(); + } + + 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(); var refWrapper = new RefWrapper(type); + DBC.ECS.Check.Require(engine != null, "Engine to add is invalid or null"); DBC.ECS.Check.Require( _enginesTypeSet.Contains(refWrapper) == false || type.ContainsCustomAttribute(typeof(AllowMultipleAttribute)) == true, @@ -70,10 +130,10 @@ namespace Svelto.ECS try { if (engine is IReactOnAddAndRemove viewEngine) - CheckEntityViewsEngine(viewEngine, _reactiveEnginesAddRemove); + CheckEntityComponentsEngine(viewEngine, _reactiveEnginesAddRemove); if (engine is IReactOnSwap viewEngineSwap) - CheckEntityViewsEngine(viewEngineSwap, _reactiveEnginesSwap); + CheckEntityComponentsEngine(viewEngineSwap, _reactiveEnginesSwap); _enginesTypeSet.Add(refWrapper); _enginesSet.Add(engine); @@ -81,10 +141,10 @@ namespace Svelto.ECS if (engine is IDisposable) _disposableEngines.Add(engine as IDisposable); - if (engine is IQueryingEntitiesEngine queryableEntityViewEngine) + if (engine is IQueryingEntitiesEngine queryableEntityComponentEngine) { - queryableEntityViewEngine.entitiesDB = _entitiesDB; - queryableEntityViewEngine.Ready(); + queryableEntityComponentEngine.entitiesDB = _entitiesDB; + queryableEntityComponentEngine.Ready(); } } catch (Exception e) @@ -93,7 +153,7 @@ namespace Svelto.ECS } } - void CheckEntityViewsEngine(T engine, FasterDictionary, FasterList> engines) + void CheckEntityComponentsEngine(T engine, FasterDictionary, FasterList> engines) where T : class, IEngine { var interfaces = engine.GetType().GetInterfaces(); @@ -109,13 +169,13 @@ namespace Svelto.ECS } } - static void AddEngine(T engine, Type[] entityViewTypes, + static void AddEngine(T engine, Type[] entityComponentTypes, FasterDictionary, FasterList> engines) where T : class, IEngine { - for (var i = 0; i < entityViewTypes.Length; i++) + for (var i = 0; i < entityComponentTypes.Length; i++) { - var type = entityViewTypes[i]; + var type = entityComponentTypes[i]; AddEngine(engine, engines, type); } @@ -137,8 +197,7 @@ namespace Svelto.ECS readonly FasterDictionary, FasterList> _reactiveEnginesAddRemove; readonly FasterDictionary, FasterList> _reactiveEnginesSwap; readonly FasterList _disposableEngines; - - readonly FasterList _enginesSet; - readonly HashSet _enginesTypeSet; + readonly FasterList _enginesSet; + readonly HashSet _enginesTypeSet; } } \ No newline at end of file diff --git a/Svelto.ECS/EnginesRoot.Entities.cs b/Svelto.ECS/EnginesRoot.Entities.cs index 08e3ffc..2b8eae7 100644 --- a/Svelto.ECS/EnginesRoot.Entities.cs +++ b/Svelto.ECS/EnginesRoot.Entities.cs @@ -4,62 +4,12 @@ using System.Runtime.CompilerServices; using Svelto.Common; using Svelto.DataStructures; using Svelto.ECS.Internal; +using Svelto.ECS.Schedulers; namespace Svelto.ECS { public partial class EnginesRoot : IDisposable { - /// - /// 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() - { - using (var profiler = new PlatformProfiler("Final Dispose")) - { - foreach (var groups in _groupEntityViewsDB) - { - foreach (var entityList in groups.Value) - { - entityList.Value.RemoveEntitiesFromEngines(_reactiveEnginesAddRemove, - profiler, new ExclusiveGroup.ExclusiveGroupStruct(groups.Key)); - } - } - - _groupEntityViewsDB.Clear(); - _groupsPerEntity.Clear(); - - foreach (var engine in _disposableEngines) - engine.Dispose(); - - _disposableEngines.Clear(); - _enginesSet.Clear(); - _enginesTypeSet.Clear(); - _reactiveEnginesSwap.Clear(); - _reactiveEnginesAddRemove.Clear(); - - _entitiesOperations.Clear(); - _transientEntitiesOperations.Clear(); - _scheduler.Dispose(); -#if DEBUG && !PROFILER - _idCheckers.Clear(); -#endif - _groupedEntityToAdd = null; - - _entitiesStream.Dispose(); - } - - GC.SuppressFinalize(this); - } - - ~EnginesRoot() - { - Console.LogWarning("Engines Root has been garbage collected, don't forget to call Dispose()!"); - - Dispose(); - } - ///-------------------------------------------- /// public IEntityStreamConsumerFactory GenerateConsumerFactory() @@ -79,41 +29,37 @@ namespace Svelto.ECS ///-------------------------------------------- [MethodImpl(MethodImplOptions.AggressiveInlining)] - EntityStructInitializer BuildEntity(EGID entityID, IEntityBuilder[] entitiesToBuild, + EntityComponentInitializer BuildEntity(EGID entityID, IComponentBuilder[] componentsToBuild, IEnumerable implementors = null) { CheckAddEntityID(entityID); - var dic = EntityFactory.BuildGroupedEntities(entityID, _groupedEntityToAdd, - entitiesToBuild, implementors); + var dic = EntityFactory.BuildGroupedEntities(entityID, _groupedEntityToAdd, componentsToBuild, implementors); - return new EntityStructInitializer(entityID, dic); + return new EntityComponentInitializer(entityID, dic); } ///-------------------------------------------- - void Preallocate(uint groupID, uint size) where T : IEntityDescriptor, new() + void Preallocate(ExclusiveGroupStruct groupID, uint size) where T : IEntityDescriptor, new() { - var entityViewsToBuild = EntityDescriptorTemplate.descriptor.entitiesToBuild; - var numberOfEntityViews = entityViewsToBuild.Length; + var entityComponentsToBuild = EntityDescriptorTemplate.descriptor.componentsToBuild; + var numberOfEntityComponents = entityComponentsToBuild.Length; - //reserve space in the database - if (_groupEntityViewsDB.TryGetValue(groupID, out var group) == false) - group = _groupEntityViewsDB[groupID] = new FasterDictionary, ITypeSafeDictionary>(); + FasterDictionary, ITypeSafeDictionary> group = GetOrCreateGroup(groupID); - for (var index = 0; index < numberOfEntityViews; index++) + for (var index = 0; index < numberOfEntityComponents; index++) { - var entityViewBuilder = entityViewsToBuild[index]; - var entityViewType = entityViewBuilder.GetEntityType(); + var entityComponentBuilder = entityComponentsToBuild[index]; + var entityComponentType = entityComponentBuilder.GetEntityComponentType(); - var refWrapper = new RefWrapper(entityViewType); + var refWrapper = new RefWrapper(entityComponentType); if (group.TryGetValue(refWrapper, out var dbList) == false) - group[refWrapper] = entityViewBuilder.Preallocate(ref dbList, size); + group[refWrapper] = entityComponentBuilder.Preallocate(ref dbList, size); else dbList.SetCapacity(size); if (_groupsPerEntity.TryGetValue(refWrapper, out var groupedGroup) == false) - groupedGroup = _groupsPerEntity[refWrapper] = - new FasterDictionary(); + groupedGroup = _groupsPerEntity[refWrapper] = new FasterDictionary(); groupedGroup[groupID] = dbList; } @@ -121,29 +67,27 @@ namespace Svelto.ECS ///-------------------------------------------- /// - void MoveEntityFromAndToEngines(IEntityBuilder[] entityBuilders, EGID fromEntityGID, EGID? toEntityGID) + void MoveEntityFromAndToEngines(IComponentBuilder[] entityBuilders, EGID fromEntityGID, EGID? toEntityGID) { using (var sampler = new PlatformProfiler("Move Entity From Engines")) { - //for each entity view generated by the entity descriptor - if (_groupEntityViewsDB.TryGetValue(fromEntityGID.groupID, out var fromGroup) == false) - throw new ECSException("from group not found eid: ".FastConcat(fromEntityGID.entityID) - .FastConcat(" group: ").FastConcat(fromEntityGID.groupID)); + var fromGroup = GetGroup(fromEntityGID.groupID); //Check if there is an EntityInfoView linked to this entity, if so it's a DynamicEntityDescriptor! if (fromGroup.TryGetValue(new RefWrapper(EntityBuilderUtilities.ENTITY_STRUCT_INFO_VIEW), out var entityInfoViewDic) && - (entityInfoViewDic as TypeSafeDictionary).TryGetValue( - fromEntityGID.entityID, out var entityInfoView)) - MoveEntities(fromEntityGID, toEntityGID, entityInfoView.entitiesToBuild, fromGroup, sampler); + (entityInfoViewDic as ITypeSafeDictionary).TryGetValue(fromEntityGID.entityID, + out var entityInfoView)) + MoveEntityComponents(fromEntityGID, toEntityGID, entityInfoView.componentsToBuild, fromGroup, sampler); //otherwise it's a normal static entity descriptor else - MoveEntities(fromEntityGID, toEntityGID, entityBuilders, fromGroup, sampler); + MoveEntityComponents(fromEntityGID, toEntityGID, entityBuilders, fromGroup, sampler); } } - void MoveEntities(EGID fromEntityGID, EGID? toEntityGID, IEntityBuilder[] entitiesToMove, - FasterDictionary, ITypeSafeDictionary> fromGroup, PlatformProfiler sampler) + void MoveEntityComponents(EGID fromEntityGID, EGID? toEntityGID, IComponentBuilder[] entitiesToMove, + FasterDictionary, ITypeSafeDictionary> fromGroup, + PlatformProfiler sampler) { FasterDictionary, ITypeSafeDictionary> toGroup = null; @@ -151,136 +95,188 @@ namespace Svelto.ECS { var toGroupID = toEntityGID.Value.groupID; - if (_groupEntityViewsDB.TryGetValue(toGroupID, out toGroup) == false) - toGroup = _groupEntityViewsDB[toGroupID] = new FasterDictionary, ITypeSafeDictionary>(); + toGroup = GetOrCreateGroup(toGroupID); //Add all the entities to the dictionary for (var i = 0; i < entitiesToMove.Length; i++) CopyEntityToDictionary(fromEntityGID, toEntityGID.Value, fromGroup, toGroup, - entitiesToMove[i].GetEntityType()); + entitiesToMove[i].GetEntityComponentType()); } //call all the callbacks for (var i = 0; i < entitiesToMove.Length; i++) - MoveEntityViewFromAndToEngines(fromEntityGID, toEntityGID, fromGroup, toGroup, - entitiesToMove[i].GetEntityType(), sampler); + MoveEntityComponentFromAndToEngines(fromEntityGID, toEntityGID, fromGroup, toGroup, + entitiesToMove[i].GetEntityComponentType(), sampler); //then remove all the entities from the dictionary - for (var i = 0; i < entitiesToMove.Length; i++) - RemoveEntityFromDictionary(fromEntityGID, fromGroup, entitiesToMove[i].GetEntityType(), sampler); + for (var i = 0; i < entitiesToMove.Length; i++) + RemoveEntityFromDictionary(fromEntityGID, fromGroup, entitiesToMove[i].GetEntityComponentType(), sampler); } void CopyEntityToDictionary(EGID entityGID, EGID toEntityGID, FasterDictionary, ITypeSafeDictionary> fromGroup, - FasterDictionary, ITypeSafeDictionary> toGroup, Type entityViewType) + FasterDictionary, ITypeSafeDictionary> toGroup, + Type entityComponentType) { - var wrapper = new RefWrapper(entityViewType); + var wrapper = new RefWrapper(entityComponentType); - if (fromGroup.TryGetValue(wrapper, out var fromTypeSafeDictionary) == false) - { - throw new ECSException("no entities in from group eid: ".FastConcat(entityGID.entityID) - .FastConcat(" group: ").FastConcat(entityGID.groupID)); - } + ITypeSafeDictionary fromTypeSafeDictionary = GetTypeSafeDictionary(entityGID.groupID, fromGroup, wrapper); -#if DEBUG && !PROFILER +#if DEBUG && !PROFILE_SVELTO if (fromTypeSafeDictionary.Has(entityGID.entityID) == false) { - throw new EntityNotFoundException(entityGID, entityViewType); + throw new EntityNotFoundException(entityGID, entityComponentType); } #endif - if (toGroup.TryGetValue(wrapper, out var toEntitiesDictionary) == false) - { - toEntitiesDictionary = fromTypeSafeDictionary.Create(); - toGroup.Add(wrapper, toEntitiesDictionary); - } - - //todo: this must be unit tested properly - if (_groupsPerEntity.TryGetValue(wrapper, out var groupedGroup) == false) - groupedGroup = _groupsPerEntity[wrapper] = - new FasterDictionary(); - - groupedGroup[toEntityGID.groupID] = toEntitiesDictionary; + ITypeSafeDictionary toEntitiesDictionary = + GetOrCreateTypeSafeDictionary(toEntityGID.groupID, toGroup, wrapper, fromTypeSafeDictionary); fromTypeSafeDictionary.AddEntityToDictionary(entityGID, toEntityGID, toEntitiesDictionary); } - void MoveEntityViewFromAndToEngines(EGID entityGID, EGID? toEntityGID, + void MoveEntityComponentFromAndToEngines(EGID entityGID, EGID? toEntityGID, FasterDictionary, ITypeSafeDictionary> fromGroup, - FasterDictionary, ITypeSafeDictionary> toGroup, Type entityViewType, - in PlatformProfiler profiler) + FasterDictionary, ITypeSafeDictionary> toGroup, + Type entityComponentType, in PlatformProfiler profiler) { //add all the entities - var refWrapper = new RefWrapper(entityViewType); - if (fromGroup.TryGetValue(refWrapper, out var fromTypeSafeDictionary) == false) - { - throw new ECSException("no entities in from group eid: ".FastConcat(entityGID.entityID) - .FastConcat(" group: ").FastConcat(entityGID.groupID)); - } + var refWrapper = new RefWrapper(entityComponentType); + var fromTypeSafeDictionary = GetTypeSafeDictionary(entityGID.groupID, fromGroup, refWrapper); ITypeSafeDictionary toEntitiesDictionary = null; if (toGroup != null) toEntitiesDictionary = toGroup[refWrapper]; //this is guaranteed to exist by AddEntityToDictionary -#if DEBUG && !PROFILER +#if DEBUG && !PROFILE_SVELTO if (fromTypeSafeDictionary.Has(entityGID.entityID) == false) - throw new EntityNotFoundException(entityGID, entityViewType); + throw new EntityNotFoundException(entityGID, entityComponentType); #endif - fromTypeSafeDictionary.MoveEntityFromEngines(entityGID, toEntityGID, - toEntitiesDictionary, toEntityGID == null ? _reactiveEnginesAddRemove : _reactiveEnginesSwap, - in profiler); + fromTypeSafeDictionary.MoveEntityFromEngines(entityGID, toEntityGID, toEntitiesDictionary, + toEntityGID == null ? _reactiveEnginesAddRemove : _reactiveEnginesSwap, in profiler); } void RemoveEntityFromDictionary(EGID entityGID, - FasterDictionary, ITypeSafeDictionary> fromGroup, Type entityViewType, - in PlatformProfiler profiler) + FasterDictionary, ITypeSafeDictionary> fromGroup, + Type entityComponentType, in PlatformProfiler profiler) { - var refWrapper = new RefWrapper(entityViewType); - if (fromGroup.TryGetValue(refWrapper, out var fromTypeSafeDictionary) == false) - { - throw new ECSException("no entities in from group eid: ".FastConcat(entityGID.entityID) - .FastConcat(" group: ").FastConcat(entityGID.groupID)); - } + var refWrapper = new RefWrapper(entityComponentType); + var fromTypeSafeDictionary = GetTypeSafeDictionary(entityGID.groupID, fromGroup, refWrapper); - fromTypeSafeDictionary.RemoveEntityFromDictionary(entityGID, profiler); + fromTypeSafeDictionary.RemoveEntityFromDictionary(entityGID); - if (fromTypeSafeDictionary.Count == 0) //clean up + //if (fromTypeSafeDictionary.Count == 0) //clean up { //todo: this must be unit tested properly - _groupsPerEntity[refWrapper].Remove(entityGID.groupID); + //_groupsPerEntity[refWrapper].Remove(entityGID.groupID); //I don't remove the group if empty on purpose, in case it needs to be reused } } /// - /// Todo: I should keep the group, but I need to mark the group as deleted for the Exist function to work + /// Swap all the entities from one group to another /// - /// + /// + /// /// - void RemoveGroupAndEntitiesFromDB(uint groupID, in PlatformProfiler profiler) + void SwapEntitiesBetweenGroups(uint fromIdGroupId, uint toGroupId, in PlatformProfiler profiler) + { + FasterDictionary, ITypeSafeDictionary> fromGroup = GetGroup(fromIdGroupId); + FasterDictionary, ITypeSafeDictionary> toGroup = GetOrCreateGroup(toGroupId); + + foreach (FasterDictionary, ITypeSafeDictionary>.KeyValuePairFast dictionaryOfEntities in + fromGroup) + { + //call all the MoveTo callbacks + dictionaryOfEntities.Value.AddEntitiesToEngines(_reactiveEnginesAddRemove, dictionaryOfEntities.Value, + new ExclusiveGroupStruct(toGroupId), profiler); + + ITypeSafeDictionary toEntitiesDictionary = GetOrCreateTypeSafeDictionary(toGroupId, toGroup, + dictionaryOfEntities.Key, dictionaryOfEntities.Value); + + FasterDictionary groupsOfEntityType = + _groupsPerEntity[dictionaryOfEntities.Key]; + + ITypeSafeDictionary typeSafeDictionary = groupsOfEntityType[fromIdGroupId]; + toEntitiesDictionary.AddEntitiesFromDictionary(typeSafeDictionary, toGroupId); + + typeSafeDictionary.FastClear(); + } + } + + FasterDictionary, ITypeSafeDictionary> GetGroup(uint fromIdGroupId) + { + if (_groupEntityComponentsDB.TryGetValue(fromIdGroupId, + out FasterDictionary, ITypeSafeDictionary> fromGroup) == false) + throw new ECSException("Group doesn't exist: ".FastConcat(fromIdGroupId)); + return fromGroup; + } + + FasterDictionary, ITypeSafeDictionary> GetOrCreateGroup(uint toGroupId) { - var dictionariesOfEntities = _groupEntityViewsDB[groupID]; + if (_groupEntityComponentsDB.TryGetValue(toGroupId, + out FasterDictionary, ITypeSafeDictionary> toGroup) == false) + toGroup = _groupEntityComponentsDB[toGroupId] = + new FasterDictionary, ITypeSafeDictionary>(); + return toGroup; + } + + ITypeSafeDictionary GetOrCreateTypeSafeDictionary(uint groupId, + FasterDictionary, ITypeSafeDictionary> toGroup, + RefWrapper type, ITypeSafeDictionary fromTypeSafeDictionary) + { + //be sure that the TypeSafeDictionary for the entity Type exists + if (toGroup.TryGetValue(type, out ITypeSafeDictionary toEntitiesDictionary) == + false) + { + toEntitiesDictionary = fromTypeSafeDictionary.Create(); + toGroup.Add(type, toEntitiesDictionary); + } + + //update GroupsPerEntity + if (_groupsPerEntity.TryGetValue(type, out var groupedGroup) == false) + groupedGroup = _groupsPerEntity[type] = + new FasterDictionary(); + + groupedGroup[groupId] = toEntitiesDictionary; + return toEntitiesDictionary; + } + + static ITypeSafeDictionary GetTypeSafeDictionary(uint groupID, + FasterDictionary, ITypeSafeDictionary> @group, RefWrapper refWrapper) + { + if (@group.TryGetValue(refWrapper, out ITypeSafeDictionary fromTypeSafeDictionary) == false) + { + throw new ECSException("no group found: ".FastConcat(groupID)); + } + + return fromTypeSafeDictionary; + } + + void RemoveGroupAndEntities(uint groupID, in PlatformProfiler profiler) + { + FasterDictionary, ITypeSafeDictionary> dictionariesOfEntities = + _groupEntityComponentsDB[groupID]; + foreach (var dictionaryOfEntities in dictionariesOfEntities) { dictionaryOfEntities.Value.RemoveEntitiesFromEngines(_reactiveEnginesAddRemove, profiler, - new ExclusiveGroup.ExclusiveGroupStruct(groupID)); - var groupedGroupOfEntities = _groupsPerEntity[dictionaryOfEntities.Key]; - groupedGroupOfEntities.Remove(groupID); - } + new ExclusiveGroupStruct(groupID)); + dictionaryOfEntities.Value.FastClear(); - //careful, in this case I assume you really don't want to use this group anymore - //so I remove it from the database - _groupEntityViewsDB.Remove(groupID); + FasterDictionary groupsOfEntityType = + _groupsPerEntity[dictionaryOfEntities.Key]; + groupsOfEntityType[groupID].FastClear(); + } } - internal Consumer GenerateConsumer(string name, uint capacity) where T : unmanaged, IEntityStruct + internal Consumer GenerateConsumer(string name, uint capacity) where T : unmanaged, IEntityComponent { return _entitiesStream.GenerateConsumer(name, capacity); } - - public Consumer GenerateConsumer(ExclusiveGroup group, string name, uint capacity) where T : unmanaged, - IEntityStruct + internal Consumer GenerateConsumer(ExclusiveGroup group, string name, uint capacity) + where T : unmanaged, IEntityComponent { return _entitiesStream.GenerateConsumer(group, name, capacity); } @@ -288,15 +284,16 @@ namespace Svelto.ECS //one datastructure rule them all: //split by group //split by type per group. It's possible to get all the entities of a give type T per group thanks - //to the FasterDictionary capabilities OR it's possible to get a specific entityView indexed by + //to the FasterDictionary capabilities OR it's possible to get a specific entityComponent indexed by //ID. This ID doesn't need to be the EGID, it can be just the entityID //for each group id, save a dictionary indexed by entity type of entities indexed by id - //ITypeSafeDictionary = Key = entityID, Value = EntityStruct - readonly FasterDictionary, ITypeSafeDictionary>> _groupEntityViewsDB; + // group EntityComponentType entityID, EntityComponent + readonly FasterDictionary, ITypeSafeDictionary>> _groupEntityComponentsDB; //for each entity view type, return the groups (dictionary of entities indexed by entity id) where they are - //found indexed by group id - //EntityViewType //groupID //entityID, EntityStruct + //found indexed by group id. TypeSafeDictionary are never created, they instead point to the ones hold + //by _groupEntityComponentsDB + // EntityComponentType groupID entityID, EntityComponent readonly FasterDictionary, FasterDictionary> _groupsPerEntity; readonly EntitiesDB _entitiesDB; diff --git a/Svelto.ECS/EnginesRoot.GenericEntityFactory.cs b/Svelto.ECS/EnginesRoot.GenericEntityFactory.cs index 8308e86..01a1baf 100644 --- a/Svelto.ECS/EnginesRoot.GenericEntityFactory.cs +++ b/Svelto.ECS/EnginesRoot.GenericEntityFactory.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using Svelto.DataStructures; +using Svelto.ECS.Internal; namespace Svelto.ECS { @@ -12,45 +13,50 @@ namespace Svelto.ECS _enginesRoot = new WeakReference(weakReference); } - public EntityStructInitializer BuildEntity(uint entityID, - ExclusiveGroup.ExclusiveGroupStruct groupStructId, IEnumerable implementors = null) + public EntityComponentInitializer BuildEntity + (uint entityID, ExclusiveGroupStruct groupStructId, IEnumerable implementors = null) where T : IEntityDescriptor, new() { - return _enginesRoot.Target.BuildEntity(new EGID(entityID, groupStructId), - EntityDescriptorTemplate.descriptor.entitiesToBuild, implementors); + return _enginesRoot.Target.BuildEntity(new EGID(entityID, groupStructId) + , EntityDescriptorTemplate.descriptor.componentsToBuild + , implementors); } - public EntityStructInitializer BuildEntity(EGID egid, IEnumerable implementors = null) + public EntityComponentInitializer BuildEntity(EGID egid, IEnumerable implementors = null) where T : IEntityDescriptor, new() { - return _enginesRoot.Target.BuildEntity(egid, - EntityDescriptorTemplate.descriptor.entitiesToBuild, implementors); + return _enginesRoot.Target.BuildEntity( + egid, EntityDescriptorTemplate.descriptor.componentsToBuild + , implementors); } - public EntityStructInitializer BuildEntity(EGID egid, T entityDescriptor, - IEnumerable implementors) - where T : IEntityDescriptor + public EntityComponentInitializer BuildEntity + (EGID egid, T entityDescriptor, IEnumerable implementors) where T : IEntityDescriptor { - return _enginesRoot.Target.BuildEntity(egid, entityDescriptor.entitiesToBuild, implementors); + return _enginesRoot.Target.BuildEntity(egid, entityDescriptor.componentsToBuild, implementors); } - - public EntityStructInitializer BuildEntity(uint entityID, - ExclusiveGroup.ExclusiveGroupStruct groupStructId, T descriptorEntity, IEnumerable implementors) +#if UNITY_ECS + public NativeEntityFactory ToNative(Unity.Collections.Allocator allocator) where T : IEntityDescriptor, new() + { + return _enginesRoot.Target.ProvideNativeEntityFactoryQueue(); + } +#endif + public EntityComponentInitializer BuildEntity + (uint entityID, ExclusiveGroupStruct groupStructId, T descriptorEntity, IEnumerable implementors) where T : IEntityDescriptor { - return _enginesRoot.Target.BuildEntity(new EGID(entityID, groupStructId), - descriptorEntity.entitiesToBuild, - implementors); + return _enginesRoot.Target.BuildEntity(new EGID(entityID, groupStructId) + , descriptorEntity.componentsToBuild, implementors); } - public void PreallocateEntitySpace(ExclusiveGroup.ExclusiveGroupStruct groupStructId, uint size) + public void PreallocateEntitySpace(ExclusiveGroupStruct groupStructId, uint size) where T : IEntityDescriptor, new() { _enginesRoot.Target.Preallocate(groupStructId, size); } //enginesRoot is a weakreference because GenericEntityStreamConsumerFactory can be injected inside -//engines of other enginesRoot + //engines of other enginesRoot readonly WeakReference _enginesRoot; } } diff --git a/Svelto.ECS/EnginesRoot.GenericEntityFunctions.cs b/Svelto.ECS/EnginesRoot.GenericEntityFunctions.cs index d5a2cbd..36e351f 100644 --- a/Svelto.ECS/EnginesRoot.GenericEntityFunctions.cs +++ b/Svelto.ECS/EnginesRoot.GenericEntityFunctions.cs @@ -1,6 +1,6 @@ -using System.Diagnostics; +using System; +using System.Diagnostics; using System.Runtime.CompilerServices; -using Svelto.DataStructures; namespace Svelto.ECS { @@ -10,15 +10,15 @@ namespace Svelto.ECS /// todo: EnginesRoot was a weakreference to give the change to inject /// entityfunctions from other engines root. It probably should be reverted /// - sealed class GenericEntityFunctions : IEntityFunctions + class GenericEntityFunctions : IEntityFunctions { internal GenericEntityFunctions(EnginesRoot weakReference) { - _enginesRoot = new WeakReference(weakReference); + _enginesRoot = new Svelto.DataStructures.WeakReference(weakReference); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void RemoveEntity(uint entityID, ExclusiveGroup.ExclusiveGroupStruct groupID) where T : + public void RemoveEntity(uint entityID, ExclusiveGroupStruct groupID) where T : IEntityDescriptor, new() { RemoveEntity(new EGID(entityID, groupID)); @@ -31,11 +31,23 @@ namespace Svelto.ECS _enginesRoot.Target.QueueEntitySubmitOperation( new EntitySubmitOperation(EntitySubmitOperationType.Remove, entityEGID, entityEGID, - EntityDescriptorTemplate.descriptor.entitiesToBuild)); + EntityDescriptorTemplate.descriptor.componentsToBuild)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void RemoveAllEntities(ExclusiveGroupStruct group) where T : IEntityDescriptor, new() + { + throw new NotImplementedException(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void RemoveAllEntities() where T : IEntityDescriptor, new() + { + throw new NotImplementedException(); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void RemoveGroupAndEntities(ExclusiveGroup.ExclusiveGroupStruct groupID) + public void RemoveGroupAndEntities(ExclusiveGroupStruct groupID) { _enginesRoot.Target.RemoveGroupID(groupID); @@ -44,23 +56,32 @@ namespace Svelto.ECS } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void SwapEntityGroup(uint entityID, ExclusiveGroup.ExclusiveGroupStruct fromGroupID, - ExclusiveGroup.ExclusiveGroupStruct toGroupID) + public void SwapEntitiesInGroup(ExclusiveGroupStruct fromGroupID, ExclusiveGroupStruct toGroupID) + { + throw new NotImplementedException("can't run this until I add the checks!"); + + _enginesRoot.Target.QueueEntitySubmitOperation( + new EntitySubmitOperation(EntitySubmitOperationType.SwapGroup, new EGID(0, fromGroupID), + new EGID(0, toGroupID))); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void SwapEntityGroup(uint entityID, ExclusiveGroupStruct fromGroupID, ExclusiveGroupStruct toGroupID) where T : IEntityDescriptor, new() { SwapEntityGroup(new EGID(entityID, fromGroupID), toGroupID); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void SwapEntityGroup(EGID fromID, ExclusiveGroup.ExclusiveGroupStruct toGroupID) + public void SwapEntityGroup(EGID fromID, ExclusiveGroupStruct toGroupID) where T : IEntityDescriptor, new() { SwapEntityGroup(fromID, new EGID(fromID.entityID, (uint) toGroupID)); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void SwapEntityGroup(EGID fromID, ExclusiveGroup.ExclusiveGroupStruct toGroupID - , ExclusiveGroup.ExclusiveGroupStruct mustBeFromGroup) + public void SwapEntityGroup(EGID fromID, ExclusiveGroupStruct toGroupID + , ExclusiveGroupStruct mustBeFromGroup) where T : IEntityDescriptor, new() { if (fromID.groupID != mustBeFromGroup) @@ -71,7 +92,7 @@ namespace Svelto.ECS [MethodImpl(MethodImplOptions.AggressiveInlining)] public void SwapEntityGroup(EGID fromID, EGID toID - , ExclusiveGroup.ExclusiveGroupStruct mustBeFromGroup) + , ExclusiveGroupStruct mustBeFromGroup) where T : IEntityDescriptor, new() { if (fromID.groupID != mustBeFromGroup) @@ -80,6 +101,18 @@ namespace Svelto.ECS SwapEntityGroup(fromID, toID); } +#if UNITY_ECS + public NativeEntityRemove ToNativeRemove() where T : IEntityDescriptor, new() + { + return _enginesRoot.Target.ProvideNativeEntityRemoveQueue(); + } + + public NativeEntitySwap ToNativeSwap() where T : IEntityDescriptor, new() + { + return _enginesRoot.Target.ProvideNativeEntitySwapQueue(); + } +#endif + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void SwapEntityGroup(EGID fromID, EGID toID) where T : IEntityDescriptor, new() @@ -89,17 +122,17 @@ namespace Svelto.ECS _enginesRoot.Target.QueueEntitySubmitOperation( new EntitySubmitOperation(EntitySubmitOperationType.Swap, - fromID, toID, EntityDescriptorTemplate.descriptor.entitiesToBuild)); + fromID, toID, EntityDescriptorTemplate.descriptor.componentsToBuild)); } - + //enginesRoot is a weakreference because GenericEntityStreamConsumerFactory can be injected inside //engines of other enginesRoot - readonly WeakReference _enginesRoot; + readonly Svelto.DataStructures.WeakReference _enginesRoot; } void QueueEntitySubmitOperation(EntitySubmitOperation entitySubmitOperation) { -#if DEBUG && !PROFILER +#if DEBUG && !PROFILE_SVELTO entitySubmitOperation.trace = new StackFrame(1, true); #endif _entitiesOperations.Add((ulong) entitySubmitOperation.fromID, entitySubmitOperation); @@ -107,14 +140,14 @@ namespace Svelto.ECS void QueueEntitySubmitOperation(EntitySubmitOperation entitySubmitOperation) where T : IEntityDescriptor { -#if DEBUG && !PROFILER +#if DEBUG && !PROFILE_SVELTO entitySubmitOperation.trace = new StackFrame(1, true); if (_entitiesOperations.TryGetValue((ulong) entitySubmitOperation.fromID, out var entitySubmitedOperation)) { if (entitySubmitedOperation != entitySubmitOperation) throw new ECSException("Only one entity operation per submission is allowed" - .FastConcat(" entityViewType: ") + .FastConcat(" entityComponentType: ") .FastConcat(typeof(T).Name) .FastConcat(" submission type ", entitySubmitOperation.type.ToString(), " from ID: ", entitySubmitOperation.fromID.entityID.ToString()) diff --git a/Svelto.ECS/EnginesRoot.Submission.cs b/Svelto.ECS/EnginesRoot.Submission.cs index f0e8963..b1b546a 100644 --- a/Svelto.ECS/EnginesRoot.Submission.cs +++ b/Svelto.ECS/EnginesRoot.Submission.cs @@ -2,7 +2,6 @@ using Svelto.Common; using Svelto.DataStructures; using Svelto.ECS.Internal; -using Svelto.ECS.Schedulers; namespace Svelto.ECS { @@ -10,7 +9,7 @@ namespace Svelto.ECS { readonly FasterList _transientEntitiesOperations; - void SubmitEntityViews() + void SubmitEntityComponents() { using (var profiler = new PlatformProfiler("Svelto.ECS - Entities Submission")) { @@ -18,10 +17,10 @@ namespace Svelto.ECS do { SingleSubmission(profiler); - } while ((_groupedEntityToAdd.currentEntitiesCreatedPerGroup.Count > 0 || + } while ((_groupedEntityToAdd.currentEntitiesCreatedPerGroup.count > 0 || _entitiesOperations.Count > 0) && ++iterations < 5); -#if DEBUG && !PROFILER +#if DEBUG && !PROFILE_SVELTO if (iterations == 5) throw new ECSException("possible circular submission detected"); #endif @@ -30,17 +29,20 @@ namespace Svelto.ECS void SingleSubmission(in PlatformProfiler profiler) { +#if UNITY_ECS + NativeOperationSubmission(profiler); +#endif + if (_entitiesOperations.Count > 0) { using (profiler.Sample("Remove and Swap operations")) { _transientEntitiesOperations.FastClear(); - var entitySubmitOperations = _entitiesOperations.GetValuesArray(out var count); - _transientEntitiesOperations.AddRange(entitySubmitOperations, count); + _entitiesOperations.CopyValuesTo(_transientEntitiesOperations); _entitiesOperations.FastClear(); - var entitiesOperations = _transientEntitiesOperations.ToArrayFast(); - for (var i = 0; i < _transientEntitiesOperations.Count; i++) + EntitySubmitOperation[] entitiesOperations = _transientEntitiesOperations.ToArrayFast(out var count); + for (var i = 0; i < count; i++) { try { @@ -48,17 +50,20 @@ namespace Svelto.ECS { case EntitySubmitOperationType.Swap: MoveEntityFromAndToEngines(entitiesOperations[i].builders, - entitiesOperations[i].fromID, - entitiesOperations[i].toID); + entitiesOperations[i].fromID, entitiesOperations[i].toID); break; case EntitySubmitOperationType.Remove: MoveEntityFromAndToEngines(entitiesOperations[i].builders, entitiesOperations[i].fromID, null); break; case EntitySubmitOperationType.RemoveGroup: - RemoveGroupAndEntitiesFromDB( + RemoveGroupAndEntities( entitiesOperations[i].fromID.groupID, profiler); break; + case EntitySubmitOperationType.SwapGroup: + SwapEntitiesBetweenGroups(entitiesOperations[i].fromID.groupID, + entitiesOperations[i].toID.groupID, profiler); + break; } } catch (Exception e) @@ -67,7 +72,7 @@ namespace Svelto.ECS .FastConcat(entitiesOperations[i].type.ToString()); throw new ECSException(str.FastConcat(" ") -#if DEBUG && !PROFILER +#if DEBUG && !PROFILE_SVELTO .FastConcat(entitiesOperations[i].trace.ToString()) #endif , e); @@ -78,17 +83,17 @@ namespace Svelto.ECS _groupedEntityToAdd.Swap(); - if (_groupedEntityToAdd.otherEntitiesCreatedPerGroup.Count > 0) + if (_groupedEntityToAdd.otherEntitiesCreatedPerGroup.count > 0) { using (profiler.Sample("Add operations")) { try { - AddEntityViewsToTheDBAndSuitableEngines(profiler); + AddEntityComponentsToTheDBAndSuitableEngines(profiler); } finally { - using (profiler.Sample("clear operates double buffering")) + using (profiler.Sample("clear 6operates double buffering")) { //other can be cleared now, but let's avoid deleting the dictionary every time _groupedEntityToAdd.ClearOther(); @@ -98,7 +103,7 @@ namespace Svelto.ECS } } - void AddEntityViewsToTheDBAndSuitableEngines(in PlatformProfiler profiler) + void AddEntityComponentsToTheDBAndSuitableEngines(in PlatformProfiler profiler) { using (profiler.Sample("Add entities to database")) { @@ -106,30 +111,21 @@ namespace Svelto.ECS foreach (var groupOfEntitiesToSubmit in _groupedEntityToAdd.otherEntitiesCreatedPerGroup) { var groupID = groupOfEntitiesToSubmit.Key; + + FasterDictionary, ITypeSafeDictionary> groupDB = GetOrCreateGroup(groupID); - //if the group doesn't exist in the current DB let's create it first - if (_groupEntityViewsDB.TryGetValue(groupID, out var groupDB) == false) - groupDB = _groupEntityViewsDB[groupID] = - new FasterDictionary, ITypeSafeDictionary>(); - - //add the entityViews in the group - foreach (var entityViewsToSubmit in _groupedEntityToAdd.other[groupID]) + //add the entityComponents in the group + foreach (var entityComponentsToSubmit in _groupedEntityToAdd.other[groupID]) { - var type = entityViewsToSubmit.Key; - var typeSafeDictionary = entityViewsToSubmit.Value; - + var type = entityComponentsToSubmit.Key; + var targetTypeSafeDictionary = entityComponentsToSubmit.Value; var wrapper = new RefWrapper(type); - if (groupDB.TryGetValue(wrapper, out var dbDic) == false) - dbDic = groupDB[wrapper] = typeSafeDictionary.Create(); - //Fill the DB with the entity views generate this frame. - dbDic.AddEntitiesFromDictionary(typeSafeDictionary, groupID); - - if (_groupsPerEntity.TryGetValue(wrapper, out var groupedGroup) == false) - groupedGroup = _groupsPerEntity[wrapper] = - new FasterDictionary(); + ITypeSafeDictionary dbDic = GetOrCreateTypeSafeDictionary(groupID, groupDB, wrapper, + targetTypeSafeDictionary); - groupedGroup[groupID] = dbDic; + //Fill the DB with the entity views generate this frame. + dbDic.AddEntitiesFromDictionary(targetTypeSafeDictionary, groupID); } } } @@ -141,22 +137,20 @@ namespace Svelto.ECS foreach (var groupToSubmit in _groupedEntityToAdd.otherEntitiesCreatedPerGroup) { var groupID = groupToSubmit.Key; + var groupDB = _groupEntityComponentsDB[groupID]; - var groupDB = _groupEntityViewsDB[groupID]; - - foreach (var entityViewsToSubmit in _groupedEntityToAdd.other[groupID]) + foreach (var entityComponentsToSubmit in _groupedEntityToAdd.other[groupID]) { - var realDic = groupDB[new RefWrapper(entityViewsToSubmit.Key)]; + var realDic = groupDB[new RefWrapper(entityComponentsToSubmit.Key)]; - entityViewsToSubmit.Value.AddEntitiesToEngines(_reactiveEnginesAddRemove, realDic, in profiler, - new ExclusiveGroup.ExclusiveGroupStruct(groupToSubmit.Key)); + entityComponentsToSubmit.Value.AddEntitiesToEngines(_reactiveEnginesAddRemove, realDic, + new ExclusiveGroupStruct(groupToSubmit.Key), in profiler); } } } } - DoubleBufferedEntitiesToAdd _groupedEntityToAdd; - readonly IEntitySubmissionScheduler _scheduler; - readonly FasterDictionary _entitiesOperations; + DoubleBufferedEntitiesToAdd _groupedEntityToAdd; + readonly ThreadSafeDictionary _entitiesOperations; } } \ No newline at end of file diff --git a/Svelto.ECS/EntitiesDB.cs b/Svelto.ECS/EntitiesDB.cs index fb49c7a..40f94f6 100644 --- a/Svelto.ECS/EntitiesDB.cs +++ b/Svelto.ECS/EntitiesDB.cs @@ -1,164 +1,191 @@ -#if DEBUG && !PROFILER +#if DEBUG && !PROFILE_SVELTO #define ENABLE_DEBUG_FUNC #endif using System; using System.Runtime.CompilerServices; +using Svelto.Common; using Svelto.DataStructures; +using Svelto.ECS.Internal; -namespace Svelto.ECS.Internal +namespace Svelto.ECS { - partial class EntitiesDB : IEntitiesDB + public class EntitiesDB { internal EntitiesDB( - FasterDictionary, ITypeSafeDictionary>> groupEntityViewsDB, + FasterDictionary, ITypeSafeDictionary>> groupEntityComponentsDB, FasterDictionary, FasterDictionary> groupsPerEntity, EntitiesStream entityStream) { - _groupEntityViewsDB = groupEntityViewsDB; + _groupEntityComponentsDB = groupEntityComponentsDB; _groupsPerEntity = groupsPerEntity; _entityStream = entityStream; } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ref T QueryUniqueEntity(ExclusiveGroup.ExclusiveGroupStruct group) where T : struct, IEntityStruct + public ref T QueryUniqueEntity(ExclusiveGroupStruct group) where T : struct, IEntityComponent { - var entities = QueryEntities(group, out var count); + var entities = QueryEntities(group).ToFastAccess(out var count); +#if DEBUG && !PROFILE_SVELTO if (count == 0) throw new ECSException("Unique entity not found '".FastConcat(typeof(T).ToString()).FastConcat("'")); if (count != 1) throw new ECSException("Unique entities must be unique! '".FastConcat(typeof(T).ToString()) .FastConcat("'")); +#endif return ref entities[0]; } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ref T QueryEntity(EGID entityGID) where T : struct, IEntityStruct + public ref T QueryEntity(EGID entityGID) where T : struct, IEntityComponent { T[] array; if ((array = QueryEntitiesAndIndexInternal(entityGID, out var index)) != null) - return ref array[index]; + return ref array[(int) index]; throw new EntityNotFoundException(entityGID, typeof(T)); } - public ref T QueryEntity(uint id, ExclusiveGroup.ExclusiveGroupStruct group) where T : struct, IEntityStruct + public ref T QueryEntity(uint id, ExclusiveGroupStruct group) where T : struct, IEntityComponent { return ref QueryEntity(new EGID(id, group)); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public T[] QueryEntities(ExclusiveGroup.ExclusiveGroupStruct groupStruct, out uint count) - where T : struct, IEntityStruct + /// + /// The QueryEntities follows the rule that entities could always be iterated regardless if they + /// are 0, 1 or N. In case of 0 it returns an empty array. This allows to use the same for iteration + /// regardless the number of entities built. + /// + /// + /// + /// + public EntityCollection QueryEntities(ExclusiveGroupStruct groupStructId) + where T : struct, IEntityComponent { - uint group = groupStruct; - count = 0; - if (SafeQueryEntityDictionary(group, out TypeSafeDictionary typeSafeDictionary) == false) - return RetrieveEmptyEntityViewArray(); + T[] ret; + uint count = 0; + //object sentinel = default; + if (SafeQueryEntityDictionary(groupStructId, out var typeSafeDictionary) == false) + ret = RetrieveEmptyEntityComponentArray(); + else + { + var safeDictionary = (typeSafeDictionary as ITypeSafeDictionary); + ret = safeDictionary.GetValuesArray(out count); + // sentinel = safeDictionary.GenerateSentinel(); + } - return typeSafeDictionary.GetValuesArray(out count); + return new EntityCollection(ret, count); } - public EntityCollection QueryEntities(ExclusiveGroup.ExclusiveGroupStruct groupStruct) - where T : struct, IEntityStruct + public EntityCollection QueryEntities( + ExclusiveGroupStruct groupStruct) + where T1 : struct, IEntityComponent where T2 : struct, IEntityComponent { - return new EntityCollection(QueryEntities(groupStruct, out var count), count); - } + var T1entities = QueryEntities(groupStruct); + var T2entities = QueryEntities(groupStruct); - public EntityCollection QueryEntities(ExclusiveGroup.ExclusiveGroupStruct groupStruct) - where T1 : struct, IEntityStruct where T2 : struct, IEntityStruct - { - return new EntityCollection(QueryEntities(groupStruct, out var count), count); + if (T1entities.count != T2entities.count) + throw new ECSException("Entity views 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("'"))); + + return new EntityCollection(T1entities, T2entities); } - public EntityCollection QueryEntities(ExclusiveGroup.ExclusiveGroupStruct groupStruct) - where T1 : struct, IEntityStruct where T2 : struct, IEntityStruct where T3 : struct, IEntityStruct + public EntityCollection + QueryEntities(ExclusiveGroupStruct groupStruct) + where T1 : struct, IEntityComponent where T2 : struct, IEntityComponent where T3 : struct, IEntityComponent { - return new EntityCollection(QueryEntities(groupStruct, out var count), count); + var T1entities = QueryEntities(groupStruct); + var T2entities = QueryEntities(groupStruct); + var T3entities = QueryEntities(groupStruct); + + if (T1entities.count != T2entities.count || T2entities.count != T3entities.count) + throw new ECSException("Entity views 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))); + + return new EntityCollection(T1entities, + T2entities, T3entities); } - public EntityCollections QueryEntities(ExclusiveGroup[] groups) where T : struct, IEntityStruct + public EntityCollections QueryEntities(ExclusiveGroup[] groups) where T : struct, IEntityComponent { return new EntityCollections(this, groups); } public EntityCollections QueryEntities(ExclusiveGroup[] groups) - where T1 : struct, IEntityStruct where T2 : struct, IEntityStruct + where T1 : struct, IEntityComponent where T2 : struct, IEntityComponent { return new EntityCollections(this, groups); } + + public EntityCollections QueryEntities(ExclusiveGroup[] groups) + where T1 : struct, IEntityComponent where T2 : struct, IEntityComponent where T3 : struct, IEntityComponent + { + return new EntityCollections(this, groups); + } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public (T1[], T2[]) QueryEntities(ExclusiveGroup.ExclusiveGroupStruct groupStruct, out uint count) - where T1 : struct, IEntityStruct - where T2 : struct, IEntityStruct + public EGIDMapper QueryMappedEntities(ExclusiveGroupStruct groupStructId) + where T : struct, IEntityComponent { - var T1entities = QueryEntities(groupStruct, out var countCheck); - var T2entities = QueryEntities(groupStruct, out count); - - if (count != countCheck) - { - throw new ECSException("Entity views count do not match in group. Entity 1: ' count: " - .FastConcat(countCheck) - .FastConcat(typeof(T1).ToString()) - .FastConcat("'. Entity 2: ' count: ".FastConcat(count) - .FastConcat(typeof(T2).ToString()) - .FastConcat("'"))); - } + if (SafeQueryEntityDictionary(groupStructId, out var typeSafeDictionary) == false) + throw new EntityGroupNotFoundException(typeof(T)); - return (T1entities, T2entities); + return (typeSafeDictionary as ITypeSafeDictionary).ToEGIDMapper(groupStructId); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public (T1[], T2[], T3[]) QueryEntities - (ExclusiveGroup.ExclusiveGroupStruct groupStruct, out uint count) - where T1 : struct, IEntityStruct where T2 : struct, IEntityStruct where T3 : struct, IEntityStruct + public NativeEGIDMapper QueryNativeMappedEntities(ExclusiveGroupStruct groupStructId) + where T : unmanaged, IEntityComponent { - var T1entities = QueryEntities(groupStruct, out var countCheck1); - var T2entities = QueryEntities(groupStruct, out var countCheck2); - var T3entities = QueryEntities(groupStruct, out count); - - if (count != countCheck1 || count != countCheck2) - throw new ECSException("Entity views count do not match in group. Entity 1: " - .FastConcat(typeof(T1).ToString()).FastConcat(" count: ").FastConcat(countCheck1).FastConcat( - " Entity 2: ".FastConcat(typeof(T2).ToString()) - .FastConcat(" count: ").FastConcat(countCheck2) - .FastConcat(" Entity 3: ".FastConcat(typeof(T3).ToString())).FastConcat(" count: ") - .FastConcat(count))); + if (SafeQueryEntityDictionary(groupStructId, out var typeSafeDictionary) == false) + throw new EntityGroupNotFoundException(typeof(T)); - return (T1entities, T2entities, T3entities); + return (typeSafeDictionary as TypeSafeDictionary).ToNativeEGIDMapper(groupStructId); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public EGIDMapper QueryMappedEntities(ExclusiveGroup.ExclusiveGroupStruct groupStructId) - where T : struct, IEntityStruct + public bool TryQueryMappedEntities(ExclusiveGroupStruct groupStructId, + out EGIDMapper mapper) + where T : struct, IEntityComponent { - if (SafeQueryEntityDictionary(groupStructId, out TypeSafeDictionary typeSafeDictionary) == false) - throw new EntityGroupNotFoundException(groupStructId, typeof(T)); + mapper = default; + if (SafeQueryEntityDictionary(groupStructId, out var typeSafeDictionary) == false || + typeSafeDictionary.Count == 0) + return false; - EGIDMapper mapper; - mapper.map = typeSafeDictionary; + mapper = (typeSafeDictionary as ITypeSafeDictionary).ToEGIDMapper(groupStructId); - return mapper; + return true; } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool TryQueryMappedEntities(ExclusiveGroup.ExclusiveGroupStruct groupStructId, - out EGIDMapper mapper) - where T : struct, IEntityStruct + public bool TryQueryNativeMappedEntities(ExclusiveGroupStruct groupStructId, + out NativeEGIDMapper mapper) + where T : unmanaged, IEntityComponent { mapper = default; - if (SafeQueryEntityDictionary(groupStructId, out TypeSafeDictionary typeSafeDictionary) == false) + if (SafeQueryEntityDictionary(groupStructId, out var typeSafeDictionary) == false || + typeSafeDictionary.Count == 0) return false; - mapper.map = typeSafeDictionary; + mapper = (typeSafeDictionary as TypeSafeDictionary).ToNativeEGIDMapper(groupStructId); return true; } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public T[] QueryEntitiesAndIndex(EGID entityGID, out uint index) where T : struct, IEntityStruct + public T[] QueryEntitiesAndIndex(EGID entityGID, out uint index) where T : struct, IEntityComponent { T[] array; if ((array = QueryEntitiesAndIndexInternal(entityGID, out index)) != null) @@ -169,7 +196,7 @@ namespace Svelto.ECS.Internal [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool TryQueryEntitiesAndIndex(EGID entityGid, out uint index, out T[] array) - where T : struct, IEntityStruct + where T : struct, IEntityComponent { if ((array = QueryEntitiesAndIndexInternal(entityGid, out index)) != null) return true; @@ -178,77 +205,108 @@ namespace Svelto.ECS.Internal } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public T[] QueryEntitiesAndIndex(uint id, ExclusiveGroup.ExclusiveGroupStruct group, out uint index) - where T : struct, IEntityStruct + public T[] QueryEntitiesAndIndex(uint id, ExclusiveGroupStruct @group, out uint index) + where T : struct, IEntityComponent { - return QueryEntitiesAndIndex(new EGID(id, group), out index); + return QueryEntitiesAndIndex(new EGID(id, @group), out index); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool TryQueryEntitiesAndIndex(uint id, ExclusiveGroup.ExclusiveGroupStruct group, out uint index, - out T[] array) where T : struct, IEntityStruct + public bool TryQueryEntitiesAndIndex + (uint id, ExclusiveGroupStruct group, out uint index, out T[] array) + where T : struct, IEntityComponent { - return TryQueryEntitiesAndIndex(new EGID(id, group), out index, out array); + return TryQueryEntitiesAndIndex(new EGID(id, @group), out index, out array); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool Exists(EGID entityGID) where T : struct, IEntityStruct + public bool Exists(EGID entityGID) where T : struct, IEntityComponent { - if (SafeQueryEntityDictionary(entityGID.groupID, out TypeSafeDictionary casted) == false) return false; + if (SafeQueryEntityDictionary(entityGID.groupID, out var casted) == false) return false; return casted != null && casted.ContainsKey(entityGID.entityID); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool Exists(uint id, ExclusiveGroup.ExclusiveGroupStruct group) where T : struct, IEntityStruct + public bool Exists(uint id, ExclusiveGroupStruct group) where T : struct, IEntityComponent { - if (SafeQueryEntityDictionary(group, out TypeSafeDictionary casted) == false) return false; + if (SafeQueryEntityDictionary(group, out var casted) == false) return false; return casted != null && casted.ContainsKey(id); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool Exists(ExclusiveGroup.ExclusiveGroupStruct gid) + public bool ExistsAndIsNotEmpty(ExclusiveGroupStruct gid) { - return _groupEntityViewsDB.ContainsKey(gid); + if (_groupEntityComponentsDB.TryGetValue(gid, + out FasterDictionary, ITypeSafeDictionary> group) == true) + { + return group.count > 0; + } + + return false; } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool HasAny(ExclusiveGroup.ExclusiveGroupStruct groupStruct) where T : struct, IEntityStruct + public bool HasAny(ExclusiveGroupStruct groupStruct) where T : struct, IEntityComponent { - QueryEntities(groupStruct, out var count); - return count > 0; + return QueryEntities(groupStruct).count > 0; } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public uint Count(ExclusiveGroup.ExclusiveGroupStruct groupStruct) where T : struct, IEntityStruct + public uint Count(ExclusiveGroupStruct groupStruct) where T : struct, IEntityComponent { - QueryEntities(groupStruct, out var count); - return count; + return QueryEntities(groupStruct).count; } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void PublishEntityChange(EGID egid) where T : unmanaged, IEntityStruct + public void PublishEntityChange(EGID egid) where T : unmanaged, IEntityComponent { _entityStream.PublishEntity(ref QueryEntity(egid), egid); } + public void ExecuteOnAllEntities(ExecuteOnAllEntitiesAction action) where T : struct, IEntityComponent + { + if (_groupsPerEntity.TryGetValue(TypeRefWrapper.wrapper, out var dictionary)) + foreach (var pair in dictionary) + { + var entities = (pair.Value as ITypeSafeDictionary).GetValuesArray(out var count); + + if (count > 0) + action(entities, new ExclusiveGroupStruct(pair.Key), count, this); + } + } + + public void ExecuteOnAllEntities(ref W value, ExecuteOnAllEntitiesAction action) + where T : struct, IEntityComponent + { + if (_groupsPerEntity.TryGetValue(TypeRefWrapper.wrapper, out var dic)) + foreach (var pair in dic) + { + var entities = (pair.Value as ITypeSafeDictionary).GetValuesArray(out var innerCount); + + if (innerCount > 0) + action(entities, new ExclusiveGroupStruct(pair.Key), innerCount, this, + ref value); + } + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] - T[] QueryEntitiesAndIndexInternal(EGID entityGID, out uint index) where T : struct, IEntityStruct + T[] QueryEntitiesAndIndexInternal(EGID entityGID, out uint index) where T : struct, IEntityComponent { index = 0; - if (SafeQueryEntityDictionary(entityGID.groupID, out TypeSafeDictionary safeDictionary) == false) + if (SafeQueryEntityDictionary(entityGID.groupID, out var safeDictionary) == false) return null; if (safeDictionary.TryFindIndex(entityGID.entityID, out index) == false) return null; - return safeDictionary.GetValuesArray(out _); + return (safeDictionary as ITypeSafeDictionary).unsafeValues; } [MethodImpl(MethodImplOptions.AggressiveInlining)] - bool SafeQueryEntityDictionary(uint group, out TypeSafeDictionary typeSafeDictionary) - where T : struct, IEntityStruct + bool SafeQueryEntityDictionary(uint group, out ITypeSafeDictionary typeSafeDictionary) + where T : struct, IEntityComponent { if (UnsafeQueryEntityDictionary(group, TypeCache.type, out var safeDictionary) == false) { @@ -257,7 +315,7 @@ namespace Svelto.ECS.Internal } //return the indexes entities if they exist - typeSafeDictionary = safeDictionary as TypeSafeDictionary; + typeSafeDictionary = safeDictionary; return true; } @@ -266,7 +324,7 @@ namespace Svelto.ECS.Internal internal bool UnsafeQueryEntityDictionary(uint group, Type type, out ITypeSafeDictionary typeSafeDictionary) { //search for the group - if (_groupEntityViewsDB.TryGetValue(group, out var entitiesInGroupPerType) == false) + if (_groupEntityComponentsDB.TryGetValue(group, out var entitiesInGroupPerType) == false) { typeSafeDictionary = null; return false; @@ -277,25 +335,31 @@ namespace Svelto.ECS.Internal } [MethodImpl(MethodImplOptions.AggressiveInlining)] - static T[] RetrieveEmptyEntityViewArray() + static T[] RetrieveEmptyEntityComponentArray() { return EmptyList.emptyArray; } + static class EmptyList + { + internal static readonly T[] emptyArray = new T[0]; + } + + internal FasterDictionary FindGroups() where T1 : unmanaged, IEntityComponent + { + return _groupsPerEntity[TypeRefWrapper.wrapper]; + } + + readonly EntitiesStream _entityStream; + //grouped set of entity views, this is the standard way to handle entity views entity views are grouped per //group, then indexable per type, then indexable per EGID. however the TypeSafeDictionary can return an array of //values directly, that can be iterated over, so that is possible to iterate over all the entity views of //a specific type inside a specific group. - readonly FasterDictionary, ITypeSafeDictionary>> _groupEntityViewsDB; + readonly FasterDictionary, ITypeSafeDictionary>> _groupEntityComponentsDB; - //needed to be able to iterate over all the entities of the same type regardless the group - //may change in future + //needed to be able to track in which groups a specific entity type can be found. + //may change in future as it could be expanded to support queries readonly FasterDictionary, FasterDictionary> _groupsPerEntity; - readonly EntitiesStream _entityStream; - - static class EmptyList - { - internal static readonly T[] emptyArray = new T[0]; - } } } \ No newline at end of file diff --git a/Svelto.ECS/EntityBuilder.CheckFields.cs b/Svelto.ECS/EntityBuilder.CheckFields.cs index f1f2d25..0895f16 100644 --- a/Svelto.ECS/EntityBuilder.CheckFields.cs +++ b/Svelto.ECS/EntityBuilder.CheckFields.cs @@ -1,4 +1,4 @@ -#if !DEBUG || PROFILER +#if !DEBUG || PROFILE_SVELTO #define DISABLE_CHECKS using System.Diagnostics; #endif @@ -15,40 +15,40 @@ namespace Svelto.ECS #if DISABLE_CHECKS [Conditional("_CHECKS_DISABLED")] #endif - public static void CheckFields(Type entityStructType, bool needsReflection, bool isStringAllowed = false) + public static void CheckFields(Type entityComponentType, bool needsReflection, bool isStringAllowed = false) { - if (entityStructType == ENTITY_STRUCT_INFO_VIEW || - entityStructType == EGIDType || - entityStructType == EXCLUSIVEGROUPSTRUCTTYPE || - entityStructType == SERIALIZABLE_ENTITY_STRUCT) + if (entityComponentType == ENTITY_STRUCT_INFO_VIEW || + entityComponentType == EGIDType || + entityComponentType == EXCLUSIVEGROUPSTRUCTTYPE || + entityComponentType == SERIALIZABLE_ENTITY_STRUCT) { return; } if (needsReflection == false) { - if (entityStructType.IsClass) + if (entityComponentType.IsClass) { - throw new EntityStructException("EntityStructs must be structs.", entityStructType); + throw new EntityComponentException("EntityComponents must be structs.", entityComponentType); } - FieldInfo[] fields = entityStructType.GetFields(BindingFlags.Public | BindingFlags.Instance); + FieldInfo[] fields = entityComponentType.GetFields(BindingFlags.Public | BindingFlags.Instance); for (var i = fields.Length - 1; i >= 0; --i) { FieldInfo fieldInfo = fields[i]; Type fieldType = fieldInfo.FieldType; - SubCheckFields(fieldType, entityStructType, isStringAllowed); + SubCheckFields(fieldType, entityComponentType, isStringAllowed); } } else { - FieldInfo[] fields = entityStructType.GetFields(BindingFlags.Public | BindingFlags.Instance); + FieldInfo[] fields = entityComponentType.GetFields(BindingFlags.Public | BindingFlags.Instance); if (fields.Length < 1) { - ProcessError("Entity View Structs must hold only entity components interfaces.", entityStructType); + ProcessError("Entity View Structs must hold only entity components interfaces.", entityComponentType); } for (int i = fields.Length - 1; i >= 0; --i) @@ -58,7 +58,7 @@ namespace Svelto.ECS if (fieldInfo.FieldType.IsInterfaceEx() == false) { ProcessError("Entity View Structs must hold only entity components interfaces.", - entityStructType); + entityComponentType); } PropertyInfo[] properties = fieldInfo.FieldType.GetProperties( @@ -79,16 +79,16 @@ namespace Svelto.ECS Type propertyType = properties[j].PropertyType; if (propertyType != STRINGTYPE) { - //for EntityViewStructs, component fields that are structs that hold strings + //for EntityComponentStructs, component fields that are structs that hold strings //are allowed - SubCheckFields(propertyType, entityStructType, isStringAllowed: true); + SubCheckFields(propertyType, entityComponentType, isStringAllowed: true); } } } } } - static void SubCheckFields(Type fieldType, Type entityStructType, bool isStringAllowed = false) + static void SubCheckFields(Type fieldType, Type entityComponentType, bool isStringAllowed = false) { if (fieldType.IsPrimitive || fieldType.IsValueType || (isStringAllowed == true && fieldType == STRINGTYPE)) { @@ -100,38 +100,38 @@ namespace Svelto.ECS return; } - ProcessError(MSG, entityStructType, fieldType); + ProcessError(MSG, entityComponentType, fieldType); } - static void ProcessError(string message, Type entityViewType, Type fieldType = null) + static void ProcessError(string message, Type entityComponentType, Type fieldType = null) { if (fieldType != null) { - throw new EntityStructException(message, entityViewType, fieldType); + throw new EntityComponentException(message, entityComponentType, fieldType); } - throw new EntityStructException(message, entityViewType); + throw new EntityComponentException(message, entityComponentType); } static readonly Type DISPATCHONCHANGETYPE = typeof(DispatchOnChange<>); static readonly Type DISPATCHONSETTYPE = typeof(DispatchOnSet<>); static readonly Type EGIDType = typeof(EGID); - static readonly Type EXCLUSIVEGROUPSTRUCTTYPE = typeof(ExclusiveGroup.ExclusiveGroupStruct); - static readonly Type SERIALIZABLE_ENTITY_STRUCT = typeof(SerializableEntityStruct); + static readonly Type EXCLUSIVEGROUPSTRUCTTYPE = typeof(ExclusiveGroupStruct); + static readonly Type SERIALIZABLE_ENTITY_STRUCT = typeof(SerializableEntityComponent); static readonly Type STRINGTYPE = typeof(string); - internal static readonly Type ENTITY_STRUCT_INFO_VIEW = typeof(EntityStructInfoView); + internal static readonly Type ENTITY_STRUCT_INFO_VIEW = typeof(EntityInfoComponentView); } - public class EntityStructException : Exception + public class EntityComponentException : Exception { - public EntityStructException(string message, Type entityViewType, Type type) : - base(message.FastConcat(" entity view: '", entityViewType.ToString(), "', field: '", type.ToString())) + public EntityComponentException(string message, Type entityComponentType, Type type) : + base(message.FastConcat(" entity view: '", entityComponentType.ToString(), "', field: '", type.ToString())) { } - public EntityStructException(string message, Type entityViewType) : - base(message.FastConcat(" entity view: ", entityViewType.ToString())) + public EntityComponentException(string message, Type entityComponentType) : + base(message.FastConcat(" entity view: ", entityComponentType.ToString())) { } } diff --git a/Svelto.ECS/EntityBuilder.cs.rej b/Svelto.ECS/EntityBuilder.cs.rej deleted file mode 100644 index 9ea149e..0000000 --- a/Svelto.ECS/EntityBuilder.cs.rej +++ /dev/null @@ -1,8 +0,0 @@ -diff a/Assets/Svelto/Svelto.ECS/EntityBuilder.cs b/Assets/Svelto/Svelto.ECS/EntityBuilder.cs (rejected hunks) -@@ -1,5 +1,6 @@ - using System; - using System.Collections.Generic; -+using DBC.ECS; - using Svelto.DataStructures; - using Svelto.ECS.Hybrid; - using Svelto.ECS.Internal; diff --git a/Svelto.ECS/EntityCollection.cs b/Svelto.ECS/EntityCollection.cs index 355e0e8..31130c6 100644 --- a/Svelto.ECS/EntityCollection.cs +++ b/Svelto.ECS/EntityCollection.cs @@ -1,82 +1,154 @@ using System; -using System.Collections; -using System.Collections.Generic; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Threading; +using Svelto.DataStructures; namespace Svelto.ECS { - public struct EntityCollection + public struct EntityCollection where T : IEntityComponent { - public EntityCollection(T[] array, uint count) + public EntityCollection(T[] array, uint count) : this() { - _array = array; + _buffer.Set(array); _count = count; } - public EntityIterator GetEnumerator() + public EntityCollection(MB buffer, uint count) + { + _buffer = buffer; + _count = count; + } + + public uint count => _count; + + readonly MB _buffer; + readonly uint _count; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public T[] ToFastAccess(out uint actualCount) { - return new EntityIterator(_array, _count); + actualCount = _count; + return _buffer.ToManagedArray(); } - readonly T[] _array; - readonly uint _count; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public NB ToNativeBuffer() where NT : unmanaged, T + { + return new NB(Unsafe.As(_buffer.ToManagedArray()), _count); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public MB ToBuffer(out uint count) + { + count = _count; + return _buffer; + } - public struct EntityIterator : IEnumerator + public ref T this[uint i] { - public EntityIterator(T[] array, uint count) : this() + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => ref _buffer[i]; + } + + public ref T this[int i] + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => ref _buffer[i]; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public EntityIterator GetEnumerator() { return new EntityIterator(_buffer, _count); } + + public struct EntityIterator + { + public EntityIterator(MB array, uint count) : this() { - _array = array; + _array = array.ToManagedArray(); _count = count; _index = -1; } - public bool MoveNext() - { - return ++_index < _count; - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool MoveNext() { return ++_index < _count; } - public void Reset() + public ref T Current { - _index = -1; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => ref _array[_index]; } - public ref T Current => ref _array[_index]; - - T IEnumerator.Current => throw new NotImplementedException(); - object IEnumerator.Current => throw new NotImplementedException(); - - public void Dispose() {} - - readonly T[] _array; + readonly T[] _array; readonly uint _count; - int _index; + int _index; } } - + public struct EntityCollection + where T1 : IEntityComponent where T2 : IEntityComponent { - public EntityCollection(in (T1[], T2[]) array, uint count) + public EntityCollection(in EntityCollection array1, in EntityCollection array2) { - _array = array; - _count = count; + _array1 = array1; + _array2 = array2; } - public EntityIterator GetEnumerator() + public uint count => _array1.count; + + public EntityCollection Item2 + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _array2; + } + + public EntityCollection Item1 + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _array1; + } + + readonly EntityCollection _array1; + readonly EntityCollection _array2; + + public (T1[], T2[]) ToFastAccess(out uint count) + { + count = this.count; + + return (_array1.ToFastAccess(out _), _array2.ToFastAccess(out _)); + } + + public BT, MB> ToBuffers() { - return new EntityIterator(_array, _count); + var bufferTuple = new BT, MB> + (_array1.ToBuffer(out _), _array2.ToBuffer(out _), count); + return bufferTuple; } - readonly (T1[], T2[]) _array; - readonly uint _count; + public BT, NB> ToNativeBuffers() + where NT2 : unmanaged, T2 where NT1 : unmanaged, T1 + { + var bufferTuple = new BT, NB> + (_array1.ToNativeBuffer(), _array2.ToNativeBuffer(), count); + + return bufferTuple; + } - public struct EntityIterator : IEnumerator> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public EntityIterator GetEnumerator() { - public EntityIterator((T1[], T2[]) array, uint count) : this() + return new EntityIterator(this); + } + + public struct EntityIterator + { + public EntityIterator(in EntityCollection array1) : this() { - _array = array; - _count = count; + _array1 = array1; + _count = array1.count; _index = -1; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool MoveNext() { return ++_index < _count; @@ -87,86 +159,158 @@ namespace Svelto.ECS _index = -1; } - public ValueRef Current => new ValueRef(_array, (uint) _index); + public ValueRef Current + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => new ValueRef(_array1, (uint) _index); + } - ValueRef IEnumerator>. Current => throw new NotImplementedException(); - object IEnumerator.Current => throw new NotImplementedException(); + readonly EntityCollection _array1; + readonly uint _count; + int _index; + } + } - public void Dispose() {} + public struct EntityCollection + where T3 : IEntityComponent where T2 : IEntityComponent where T1 : IEntityComponent + { + public EntityCollection( + in EntityCollection array1, in EntityCollection array2, + in EntityCollection array3) + { + _array1 = array1; + _array2 = array2; + _array3 = array3; + } - readonly (T1[], T2[]) _array; - readonly uint _count; - int _index; + public EntityCollection Item1 + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _array1; + } + + public EntityCollection Item2 + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _array2; + } + + public EntityCollection Item3 + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _array3; } + + public uint count => Item1.count; + + public (T1[], T2[], T3[]) ToFastAccess(out uint count) + { + count = this.count; + + return (_array1.ToFastAccess(out _), _array2.ToFastAccess(out _), _array3.ToFastAccess(out _)); + } + + public BT, MB, MB> ToBuffers() + { + var bufferTuple = new BT, MB, MB> + (_array1.ToBuffer(out _), _array2.ToBuffer(out _), _array3.ToBuffer(out _), count); + return bufferTuple; + } + + public BT, NB, NB> ToNativeBuffers() + where NT2 : unmanaged, T2 where NT1 : unmanaged, T1 where NT3 : unmanaged, T3 + { + var bufferTuple = new BT, NB, NB> + (_array1.ToNativeBuffer(), _array2.ToNativeBuffer(), _array3.ToNativeBuffer(), count); + + return bufferTuple; + } + + readonly EntityCollection _array1; + readonly EntityCollection _array2; + readonly EntityCollection _array3; } - - public struct EntityCollection + + public struct EntityCollections where T : struct, IEntityComponent { - public EntityCollection(in (T1[], T2[], T3[]) array, uint count) + public EntityCollections(EntitiesDB db, ExclusiveGroup[] groups) : this() { - _array = array; - _count = count; + _db = db; + _groups = groups; } - public EntityIterator GetEnumerator() + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public EntityGroupsIterator GetEnumerator() { - return new EntityIterator(_array, _count); + return new EntityGroupsIterator(_db, _groups); } - readonly (T1[], T2[], T3[]) _array; - readonly uint _count; + readonly EntitiesDB _db; + readonly ExclusiveGroup[] _groups; - public struct EntityIterator : IEnumerator> + public struct EntityGroupsIterator { - public EntityIterator((T1[], T2[], T3[]) array, uint count) : this() + public EntityGroupsIterator(EntitiesDB db, ExclusiveGroup[] groups) : this() { - _array = array; - _count = count; + _db = db; + _groups = groups; + _indexGroup = -1; _index = -1; } public bool MoveNext() { + //attention, the while is necessary to skip empty groups + while (_index + 1 >= _count && ++_indexGroup < _groups.Length) + { + _index = -1; + _array = _db.QueryEntities(_groups[_indexGroup]); + _count = _array.count; + } + return ++_index < _count; } public void Reset() { _index = -1; + _indexGroup = -1; + _count = 0; } - public ValueRef Current => new ValueRef(_array, (uint) _index); - - ValueRef IEnumerator>.Current => throw new NotImplementedException(); - object IEnumerator. Current => throw new NotImplementedException(); + public ref T Current => ref _array[(uint) _index]; - public void Dispose() {} + readonly EntitiesDB _db; + readonly ExclusiveGroup[] _groups; - readonly (T1[], T2[], T3[]) _array; - readonly uint _count; - int _index; + EntityCollection _array; + uint _count; + int _index; + int _indexGroup; } } - - public struct EntityCollections where T : struct, IEntityStruct + + public struct EntityCollections + where T1 : struct, IEntityComponent where T2 : struct, IEntityComponent { - public EntityCollections(IEntitiesDB db, ExclusiveGroup[] groups) : this() + public EntityCollections(EntitiesDB db, ExclusiveGroup[] groups) : this() { _db = db; _groups = groups; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public EntityGroupsIterator GetEnumerator() { return new EntityGroupsIterator(_db, _groups); } - readonly IEntitiesDB _db; + readonly EntitiesDB _db; readonly ExclusiveGroup[] _groups; - public struct EntityGroupsIterator : IEnumerator + public struct EntityGroupsIterator { - public EntityGroupsIterator(IEntitiesDB db, ExclusiveGroup[] groups) : this() + public EntityGroupsIterator(EntitiesDB db, ExclusiveGroup[] groups) : this() { _db = db; _groups = groups; @@ -176,58 +320,64 @@ namespace Svelto.ECS public bool MoveNext() { - while (_index + 1 >= _count && ++_indexGroup < _groups.Length) + //attention, the while is necessary to skip empty groups + while (_index + 1 >= _array1.count && ++_indexGroup < _groups.Length) { _index = -1; - _array = _db.QueryEntities(_groups[_indexGroup], out _count); + _array1 = _db.QueryEntities(_groups[_indexGroup]); } - return ++_index < _count; + return ++_index < _array1.count; } public void Reset() { _index = -1; _indexGroup = -1; - _count = 0; - } - - public ref T Current => ref _array[_index]; - T IEnumerator.Current => throw new NotImplementedException(); - object IEnumerator.Current => throw new NotImplementedException(); + _array1 = _db.QueryEntities(_groups[0]); + } - public void Dispose() {} + public ValueRef Current + { + get + { + var valueRef = + new ValueRef(_array1, (uint) _index); + return valueRef; + } + } - readonly IEntitiesDB _db; + readonly EntitiesDB _db; readonly ExclusiveGroup[] _groups; + int _index; + int _indexGroup; - T[] _array; - uint _count; - int _index; - int _indexGroup; + EntityCollection _array1; } } - - public struct EntityCollections where T1 : struct, IEntityStruct where T2 : struct, IEntityStruct + + public struct EntityCollections + where T1 : struct, IEntityComponent where T2 : struct, IEntityComponent where T3 : struct, IEntityComponent { - public EntityCollections(IEntitiesDB db, ExclusiveGroup[] groups) : this() + public EntityCollections(EntitiesDB db, ExclusiveGroup[] groups) : this() { _db = db; _groups = groups; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public EntityGroupsIterator GetEnumerator() { return new EntityGroupsIterator(_db, _groups); } - readonly IEntitiesDB _db; + readonly EntitiesDB _db; readonly ExclusiveGroup[] _groups; - public struct EntityGroupsIterator : IEnumerator> + public struct EntityGroupsIterator { - public EntityGroupsIterator(IEntitiesDB db, ExclusiveGroup[] groups) : this() + public EntityGroupsIterator(EntitiesDB db, ExclusiveGroup[] groups) : this() { _db = db; _groups = groups; @@ -237,17 +387,13 @@ namespace Svelto.ECS public bool MoveNext() { + //attention, the while is necessary to skip empty groups while (_index + 1 >= _count && ++_indexGroup < _groups.Length) { _index = -1; - var array1 = _db.QueryEntities(_groups[_indexGroup], out _count); - var array2 = _db.QueryEntities(_groups[_indexGroup], out var count1); - _array = (array1, array2); - -#if DEBUG && !PROFILER - if (_count != count1) - throw new ECSException("number of entities in group doesn't match"); -#endif + _array1 = _db.QueryEntities(_groups[_indexGroup]); + _count = _array1.count; + } return ++_index < _count; @@ -258,69 +404,159 @@ namespace Svelto.ECS _index = -1; _indexGroup = -1; - var array1 = _db.QueryEntities(_groups[0], out _count); - var array2 = _db.QueryEntities(_groups[0], out var count1); - _array = (array1, array2); -#if DEBUG && !PROFILER - if (_count != count1) - throw new ECSException("number of entities in group doesn't match"); -#endif + _array1 = _db.QueryEntities(_groups[0]); + _count = _array1.count; } - public ValueRef Current + public ValueRef Current { get { - var valueRef = new ValueRef(_array, (uint) _index); + var valueRef = + new ValueRef(_array1, (uint) _index); return valueRef; } } - ValueRef IEnumerator>.Current => throw new NotImplementedException(); - object IEnumerator.Current => throw new NotImplementedException(); - - public void Dispose() {} - - readonly IEntitiesDB _db; + readonly EntitiesDB _db; readonly ExclusiveGroup[] _groups; uint _count; int _index; int _indexGroup; - (T1[], T2[]) _array; + + EntityCollection _array1; } } - public struct ValueRef + public readonly struct BT : IDisposable where BufferT1 : IDisposable + where BufferT2 : IDisposable + where BufferT3 : IDisposable + where BufferT4 : IDisposable + { + public readonly BufferT1 buffer1; + public readonly BufferT2 buffer2; + public readonly BufferT3 buffer3; + public readonly BufferT4 buffer4; + public readonly uint count; + + public BT(BufferT1 bufferT1, BufferT2 bufferT2, BufferT3 bufferT3, BufferT4 bufferT4, uint count) : this() + { + this.buffer1 = bufferT1; + this.buffer2 = bufferT2; + this.buffer3 = bufferT3; + this.buffer4 = bufferT4; + this.count = count; + } + + public void Dispose() + { + buffer1.Dispose(); + buffer2.Dispose(); + buffer3.Dispose(); + buffer4.Dispose(); + } + } + + public readonly struct BT : IDisposable where BufferT1 : IDisposable + where BufferT2 : IDisposable + where BufferT3 : IDisposable + { + public readonly BufferT1 buffer1; + public readonly BufferT2 buffer2; + public readonly BufferT3 buffer3; + public readonly uint count; + + public BT(BufferT1 bufferT1, BufferT2 bufferT2, BufferT3 bufferT3, uint count) : this() + { + this.buffer1 = bufferT1; + this.buffer2 = bufferT2; + this.buffer3 = bufferT3; + this.count = count; + } + + public void Dispose() + { + buffer1.Dispose(); + buffer2.Dispose(); + buffer3.Dispose(); + } + } + + public readonly struct BT : IDisposable + where BufferT1 : IDisposable where BufferT2 : IDisposable + { + public readonly BufferT1 buffer1; + public readonly BufferT2 buffer2; + public readonly uint count; + + public BT(BufferT1 bufferT1, BufferT2 bufferT2, uint count) : this() + { + this.buffer1 = bufferT1; + this.buffer2 = bufferT2; + this.count = count; + } + + public void Dispose() + { + buffer1.Dispose(); + buffer2.Dispose(); + } + } + + public ref struct ValueRef where T2 : IEntityComponent where T1 : IEntityComponent { - readonly (T1[], T2[]) array; + readonly EntityCollection array1; readonly uint index; - public ValueRef(in (T1[], T2[]) entity1, uint i) + public ValueRef(in EntityCollection entity2, uint i) { - array = entity1; + array1 = entity2; index = i; } - public ref T1 entityStructA => ref array.Item1[index]; - public ref T2 entityStructB => ref array.Item2[index]; + public ref T1 entityComponentA + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => ref array1.Item1[index]; + } + + public ref T2 entityComponentB + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => ref array1.Item2[index]; + } } - - public struct ValueRef + + public ref struct ValueRef + where T2 : IEntityComponent where T1 : IEntityComponent where T3 : IEntityComponent { - readonly (T1[], T2[], T3[]) array; + readonly EntityCollection array1; readonly uint index; - public ValueRef(in (T1[], T2[], T3[]) entity1, uint i) + public ValueRef(in EntityCollection entity, uint i) { - array = entity1; - index = i; + array1 = entity; + index = i; } - public ref T1 entityStructA => ref array.Item1[index]; - public ref T2 entityStructB => ref array.Item2[index]; - public ref T3 entityStructC => ref array.Item3[index]; + public ref T1 entityComponentA + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => ref array1.Item1[index]; + } + public ref T2 entityComponentB + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => ref array1.Item2[index]; + } + + public ref T3 entityComponentC + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => ref array1.Item3[index]; + } } -} +} \ No newline at end of file diff --git a/Svelto.ECS/EntityComponentInitializer.cs b/Svelto.ECS/EntityComponentInitializer.cs new file mode 100644 index 0000000..cadba63 --- /dev/null +++ b/Svelto.ECS/EntityComponentInitializer.cs @@ -0,0 +1,61 @@ +using System; +using Svelto.DataStructures; +using Svelto.ECS.Internal; + +namespace Svelto.ECS +{ + public readonly ref struct EntityComponentInitializer + { + public EntityComponentInitializer(EGID id, FasterDictionary, ITypeSafeDictionary> group) + { + _group = group; + _ID = id; + } + + public void Init(T initializer) where T : struct, IEntityComponent + { + if (_group.TryGetValue(new RefWrapper(ComponentBuilder.ENTITY_COMPONENT_TYPE), + out var typeSafeDictionary) == false) return; + + var dictionary = (ITypeSafeDictionary) typeSafeDictionary; + + if (ComponentBuilder.HAS_EGID) + SetEGIDWithoutBoxing.SetIDWithoutBoxing(ref initializer, _ID); + + if (dictionary.TryFindIndex(_ID.entityID, out var findElementIndex)) + dictionary.unsafeValues[findElementIndex] = initializer; + } + + public ref T GetOrCreate() where T : struct, IEntityComponent + { + ref var entityDictionary = ref _group.GetOrCreate(new RefWrapper(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 RefWrapper(ComponentBuilder.ENTITY_COMPONENT_TYPE)] as ITypeSafeDictionary)[ + _ID.entityID]; + } + + public bool Has() where T : struct, IEntityComponent + { + if (_group.TryGetValue(new RefWrapper(ComponentBuilder.ENTITY_COMPONENT_TYPE), + out var typeSafeDictionary)) + { + var dictionary = (ITypeSafeDictionary) typeSafeDictionary; + + if (dictionary.ContainsKey(_ID.entityID)) + return true; + } + + return false; + } + + readonly EGID _ID; + readonly FasterDictionary, ITypeSafeDictionary> _group; + } +} \ No newline at end of file diff --git a/Svelto.ECS/EntityDescriptorTemplate.cs b/Svelto.ECS/EntityDescriptorTemplate.cs index b96e3bd..7ce178c 100644 --- a/Svelto.ECS/EntityDescriptorTemplate.cs +++ b/Svelto.ECS/EntityDescriptorTemplate.cs @@ -2,7 +2,7 @@ namespace Svelto.ECS { public interface IEntityDescriptor { - IEntityBuilder[] entitiesToBuild { get; } + IComponentBuilder[] componentsToBuild { get; } } static class EntityDescriptorTemplate where TType : IEntityDescriptor, new() diff --git a/Svelto.ECS/EntityFactory.cs b/Svelto.ECS/EntityFactory.cs index 3556ae8..281b396 100644 --- a/Svelto.ECS/EntityFactory.cs +++ b/Svelto.ECS/EntityFactory.cs @@ -7,91 +7,76 @@ namespace Svelto.ECS.Internal static class EntityFactory { public static FasterDictionary, ITypeSafeDictionary> BuildGroupedEntities(EGID egid, - EnginesRoot.DoubleBufferedEntitiesToAdd groupEntitiesToAdd, - IEntityBuilder[] entitiesToBuild, + EnginesRoot.DoubleBufferedEntitiesToAdd groupEntitiesToAdd, IComponentBuilder[] componentsToBuild, IEnumerable implementors) { var group = FetchEntityGroup(egid.groupID, groupEntitiesToAdd); - BuildEntitiesAndAddToGroup(egid, group, entitiesToBuild, implementors); + BuildEntitiesAndAddToGroup(egid, group, componentsToBuild, implementors); return group; } static FasterDictionary, ITypeSafeDictionary> FetchEntityGroup(uint groupID, - EnginesRoot.DoubleBufferedEntitiesToAdd groupEntityViewsByType) + EnginesRoot.DoubleBufferedEntitiesToAdd groupEntityComponentsByType) { - if (groupEntityViewsByType.current.TryGetValue(groupID, out var group) == false) + if (groupEntityComponentsByType.current.TryGetValue(groupID, out var group) == false) { group = new FasterDictionary, ITypeSafeDictionary>(); - groupEntityViewsByType.current.Add(groupID, group); + groupEntityComponentsByType.current.Add(groupID, group); } - if (groupEntityViewsByType.currentEntitiesCreatedPerGroup.TryGetValue(groupID, out var value) == false) - groupEntityViewsByType.currentEntitiesCreatedPerGroup[groupID] = 0; + if (groupEntityComponentsByType.currentEntitiesCreatedPerGroup.TryGetValue(groupID, out var value) == false) + groupEntityComponentsByType.currentEntitiesCreatedPerGroup[groupID] = 0; else - groupEntityViewsByType.currentEntitiesCreatedPerGroup[groupID] = value+1; + groupEntityComponentsByType.currentEntitiesCreatedPerGroup[groupID] = value+1; return group; } static void BuildEntitiesAndAddToGroup(EGID entityID, FasterDictionary, ITypeSafeDictionary> group, - IEntityBuilder[] entityBuilders, IEnumerable implementors) + IComponentBuilder[] entityBuilders, IEnumerable implementors) { -#if DEBUG && !PROFILER +#if DEBUG && !PROFILE_SVELTO HashSet types = new HashSet(); #endif - InternalBuild(entityID, group, entityBuilders, implementors -#if DEBUG && !PROFILER - , types -#endif - ); - } - - static void InternalBuild(EGID entityID, FasterDictionary, ITypeSafeDictionary> group, - IEntityBuilder[] entityBuilders, IEnumerable implementors -#if DEBUG && !PROFILER - , HashSet types -#endif - ) - { var count = entityBuilders.Length; -#if DEBUG && !PROFILER +#if DEBUG && !PROFILE_SVELTO for (var index = 0; index < count; ++index) { - var entityViewType = entityBuilders[index].GetEntityType(); - if (types.Contains(entityViewType)) + var entityComponentType = entityBuilders[index].GetEntityComponentType(); + if (types.Contains(entityComponentType)) { throw new ECSException("EntityBuilders must be unique inside an EntityDescriptor"); } - types.Add(entityViewType); + types.Add(entityComponentType); } #endif for (var index = 0; index < count; ++index) { - var entityStructBuilder = entityBuilders[index]; - var entityViewType = entityStructBuilder.GetEntityType(); + var entityComponentBuilder = entityBuilders[index]; + var entityComponentType = entityComponentBuilder.GetEntityComponentType(); - BuildEntity(entityID, group, entityViewType, entityStructBuilder, implementors); + BuildEntity(entityID, @group, entityComponentType, entityComponentBuilder, implementors); } } static void BuildEntity(EGID entityID, FasterDictionary, ITypeSafeDictionary> group, - Type entityViewType, IEntityBuilder entityBuilder, IEnumerable implementors) + Type entityComponentType, IComponentBuilder componentBuilder, IEnumerable implementors) { - var entityViewsPoolWillBeCreated = - group.TryGetValue(new RefWrapper(entityViewType), out var safeDictionary) == false; + var entityComponentsPoolWillBeCreated = + group.TryGetValue(new RefWrapper(entityComponentType), out var safeDictionary) == false; - //passing the undefined entityViewsByType inside the entityViewBuilder will allow it to be created with the + //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. - entityBuilder.BuildEntityAndAddToList(ref safeDictionary, entityID, implementors); + componentBuilder.BuildEntityAndAddToList(ref safeDictionary, entityID, implementors); - if (entityViewsPoolWillBeCreated) - group.Add(new RefWrapper(entityViewType), safeDictionary); + if (entityComponentsPoolWillBeCreated) + group.Add(new RefWrapper(entityComponentType), safeDictionary); } } } \ No newline at end of file diff --git a/Svelto.ECS/EntityGroupNotFoundException.cs b/Svelto.ECS/EntityGroupNotFoundException.cs index 63fb6e6..fbc49dd 100644 --- a/Svelto.ECS/EntityGroupNotFoundException.cs +++ b/Svelto.ECS/EntityGroupNotFoundException.cs @@ -4,7 +4,7 @@ namespace Svelto.ECS.Internal { class EntityGroupNotFoundException : Exception { - public EntityGroupNotFoundException(uint groupId, Type type) + public EntityGroupNotFoundException(Type type) : base("entity group not found ".FastConcat(type.ToString())) { } diff --git a/Svelto.ECS/EntityHierarchyComponent.cs b/Svelto.ECS/EntityHierarchyComponent.cs new file mode 100644 index 0000000..22f3a2d --- /dev/null +++ b/Svelto.ECS/EntityHierarchyComponent.cs @@ -0,0 +1,11 @@ +namespace Svelto.ECS +{ + public struct EntityHierarchyComponent: IEntityComponent, INeedEGID + { + public readonly ExclusiveGroupStruct parentGroup; + + public EntityHierarchyComponent(ExclusiveGroup group): this() { parentGroup = group; } + + public EGID ID { get; set; } + } +} \ No newline at end of file diff --git a/Svelto.ECS/EntityHierarchyStruct.cs b/Svelto.ECS/EntityHierarchyStruct.cs deleted file mode 100644 index c0fe044..0000000 --- a/Svelto.ECS/EntityHierarchyStruct.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace Svelto.ECS -{ - public struct EntityHierarchyStruct: IEntityStruct, INeedEGID - { - public readonly ExclusiveGroup.ExclusiveGroupStruct parentGroup; - - public EntityHierarchyStruct(ExclusiveGroup group): this() { parentGroup = group; } - - public EGID ID { get; set; } - } -} \ No newline at end of file diff --git a/Svelto.ECS/EntityInfoView.cs b/Svelto.ECS/EntityInfoView.cs index 98711f8..0fe10b1 100644 --- a/Svelto.ECS/EntityInfoView.cs +++ b/Svelto.ECS/EntityInfoView.cs @@ -1,7 +1,7 @@ namespace Svelto.ECS { - struct EntityStructInfoView: IEntityStruct + struct EntityInfoComponentView: IEntityComponent { - public IEntityBuilder[] entitiesToBuild; + public IComponentBuilder[] componentsToBuild; } } \ No newline at end of file diff --git a/Svelto.ECS/EntityStream.cs b/Svelto.ECS/EntityStream.cs index d8aaf24..c4bf033 100644 --- a/Svelto.ECS/EntityStream.cs +++ b/Svelto.ECS/EntityStream.cs @@ -1,4 +1,5 @@ using System; +using System.Runtime.InteropServices; using Svelto.DataStructures; namespace Svelto.ECS @@ -14,25 +15,28 @@ namespace Svelto.ECS /// one only /// - you want to communicate between EnginesRoots /// - class EntitiesStream : IDisposable { - internal Consumer GenerateConsumer(string name, uint capacity) where T : unmanaged, IEntityStruct + internal Consumer GenerateConsumer(string name, uint capacity) + where T : unmanaged, IEntityComponent { - if (_streams.ContainsKey(TypeRefWrapper.wrapper) == false) _streams[TypeRefWrapper.wrapper] = new EntityStream(); + if (_streams.ContainsKey(TypeRefWrapper.wrapper) == false) + _streams[TypeRefWrapper.wrapper] = new EntityStream(); return (_streams[TypeRefWrapper.wrapper] as EntityStream).GenerateConsumer(name, capacity); } public Consumer GenerateConsumer(ExclusiveGroup group, string name, uint capacity) - where T : unmanaged, IEntityStruct + where T : unmanaged, IEntityComponent { - if (_streams.ContainsKey(TypeRefWrapper.wrapper) == false) _streams[TypeRefWrapper.wrapper] = new EntityStream(); + if (_streams.ContainsKey(TypeRefWrapper.wrapper) == false) + _streams[TypeRefWrapper.wrapper] = new EntityStream(); - return (_streams[TypeRefWrapper.wrapper] as EntityStream).GenerateConsumer(group, name, capacity); + EntityStream typeSafeStream = (EntityStream) _streams[TypeRefWrapper.wrapper]; + return typeSafeStream.GenerateConsumer(group, name, capacity); } - internal void PublishEntity(ref T entity, EGID egid) where T : unmanaged, IEntityStruct + internal void PublishEntity(ref T entity, EGID egid) where T : unmanaged, IEntityComponent { if (_streams.TryGetValue(TypeRefWrapper.wrapper, out var typeSafeStream)) (typeSafeStream as EntityStream).PublishEntity(ref entity, egid); @@ -50,77 +54,96 @@ namespace Svelto.ECS } interface ITypeSafeStream - {} + { } - class EntityStream : ITypeSafeStream where T : unmanaged, IEntityStruct + public class EntityStream : ITypeSafeStream where T : unmanaged, IEntityComponent { - public void PublishEntity(ref T entity, EGID egid) + ~EntityStream() + { + for (int i = 0; i < _consumers.Count; i++) + _consumers[i].Free(); + } + + internal EntityStream() + { + _consumers = new ThreadSafeFasterList>(); + } + + internal void PublishEntity(ref T entity, EGID egid) { for (int i = 0; i < _consumers.Count; i++) { - if (_consumers[i]._hasGroup) + unsafe { - if (egid.groupID == _consumers[i]._group) + if (*(bool *)_consumers[i].mustBeDisposed) + { + _consumers[i].Free(); + _consumers.UnorderedRemoveAt(i); + --i; + continue; + } + + if (_consumers[i].hasGroup) + { + if (egid.groupID == _consumers[i].@group) + { + _consumers[i].Enqueue(entity, egid); + } + } + else { _consumers[i].Enqueue(entity, egid); } } - else - { - _consumers[i].Enqueue(entity, egid); - } } } - public Consumer GenerateConsumer(string name, uint capacity) + internal Consumer GenerateConsumer(string name, uint capacity) { - var consumer = new Consumer(name, capacity, this); + var consumer = new Consumer(name, capacity); _consumers.Add(consumer); return consumer; } - public Consumer GenerateConsumer(ExclusiveGroup group, string name, uint capacity) + internal Consumer GenerateConsumer(ExclusiveGroup group, string name, uint capacity) { - var consumer = new Consumer(group, name, capacity, this); + var consumer = new Consumer(group, name, capacity); _consumers.Add(consumer); return consumer; } - public void RemoveConsumer(Consumer consumer) - { - _consumers.UnorderedRemove(consumer); - } - - readonly FasterListThreadSafe> _consumers = new FasterListThreadSafe>(); + readonly ThreadSafeFasterList> _consumers; } - public struct Consumer : IDisposable where T : unmanaged, IEntityStruct + public struct Consumer :IDisposable where T : unmanaged, IEntityComponent { - internal Consumer(string name, uint capacity, EntityStream stream):this() + internal Consumer(string name, uint capacity) : this() { -#if DEBUG && !PROFILER - _name = name; + unsafe + { +#if DEBUG && !PROFILE_SVELTO + _name = name; #endif - _ringBuffer = new RingBuffer>((int) capacity, -#if DEBUG && !PROFILER - _name + _ringBuffer = new RingBuffer>((int) capacity, +#if DEBUG && !PROFILE_SVELTO + _name #else string.Empty #endif ); - - _stream = stream; + mustBeDisposed = Marshal.AllocHGlobal(sizeof(bool)); + *((bool*) mustBeDisposed) = false; + } } - internal Consumer(ExclusiveGroup group, string name, uint capacity, EntityStream stream) : this(name, - capacity, stream) + internal Consumer(ExclusiveGroup group, string name, uint capacity) : this(name, capacity) { - _group = group; - _hasGroup = true; + this.@group = @group; + hasGroup = true; } internal void Enqueue(in T entity, in EGID egid) @@ -146,18 +169,38 @@ namespace Svelto.ECS return tryDequeue; } - public void Flush() { _ringBuffer.Reset(); } - public void Dispose() { _stream.RemoveConsumer(this); } - public uint Count() { return (uint) _ringBuffer.Count; } - readonly RingBuffer> _ringBuffer; - readonly EntityStream _stream; + public void Flush() + { + _ringBuffer.Reset(); + } + + public void Dispose() + { + unsafe + { + *(bool *)mustBeDisposed = true; + } + } + + public uint Count() + { + return (uint) _ringBuffer.Count; + } + + public void Free() + { + Marshal.FreeHGlobal(mustBeDisposed); + } + + readonly RingBuffer> _ringBuffer; - internal readonly ExclusiveGroup _group; - internal readonly bool _hasGroup; + internal readonly ExclusiveGroup @group; + internal readonly bool hasGroup; + internal IntPtr mustBeDisposed; -#if DEBUG && !PROFILER +#if DEBUG && !PROFILE_SVELTO readonly string _name; #endif } -} +} \ No newline at end of file diff --git a/Svelto.ECS/EntityStructInitializer.cs b/Svelto.ECS/EntityStructInitializer.cs deleted file mode 100644 index ed85d53..0000000 --- a/Svelto.ECS/EntityStructInitializer.cs +++ /dev/null @@ -1,75 +0,0 @@ -using System; -using Svelto.DataStructures; -using Svelto.ECS.Internal; - -namespace Svelto.ECS -{ - public ref struct EntityStructInitializer - { - public EntityStructInitializer(EGID id, FasterDictionary, ITypeSafeDictionary> group) - { - _group = group; - _ID = id; - } - - public void Init(T initializer) where T : struct, IEntityStruct - { - if (_group.TryGetValue(new RefWrapper(EntityBuilder.ENTITY_VIEW_TYPE), - out var typeSafeDictionary) == false) return; - - var dictionary = (TypeSafeDictionary) typeSafeDictionary; - - if (EntityBuilder.HAS_EGID) - SetEGIDWithoutBoxing.SetIDWithoutBoxing(ref initializer, _ID); - - if (dictionary.TryFindIndex(_ID.entityID, out var findElementIndex)) - dictionary.GetDirectValue(findElementIndex) = initializer; - } - - public void CopyFrom(T initializer) where T : struct, IEntityStruct - { - var dictionary = (TypeSafeDictionary) _group[new RefWrapper(EntityBuilder.ENTITY_VIEW_TYPE)]; - - if (EntityBuilder.HAS_EGID) - SetEGIDWithoutBoxing.SetIDWithoutBoxing(ref initializer, _ID); - - dictionary[_ID.entityID] = initializer; - } - - public ref T GetOrCreate() where T : struct, IEntityStruct - { - ref var entityDictionary = ref _group.GetOrCreate(new RefWrapper(EntityBuilder.ENTITY_VIEW_TYPE) - , () => new TypeSafeDictionary()); - var dictionary = (TypeSafeDictionary) entityDictionary; - - return ref dictionary.GetOrCreate(_ID.entityID); - } - - public T Get() where T : struct, IEntityStruct - { - return (_group[new RefWrapper(EntityBuilder.ENTITY_VIEW_TYPE)] as TypeSafeDictionary)[_ID.entityID]; - } - - public bool Has() where T : struct, IEntityStruct - { - if (_group.TryGetValue(new RefWrapper(EntityBuilder.ENTITY_VIEW_TYPE), - out var typeSafeDictionary)) - { - var dictionary = (TypeSafeDictionary) typeSafeDictionary; - - if (dictionary.ContainsKey(_ID.entityID)) - return true; - } - - return false; - } - - public static EntityStructInitializer CreateEmptyInitializer() - { - return new EntityStructInitializer(new EGID(), new FasterDictionary, ITypeSafeDictionary>()); - } - - readonly EGID _ID; - readonly FasterDictionary, ITypeSafeDictionary> _group; - } -} \ No newline at end of file diff --git a/Svelto.ECS/EntitySubmissionScheduler.cs b/Svelto.ECS/EntitySubmissionScheduler.cs index e6d0ffc..d9997db 100644 --- a/Svelto.ECS/EntitySubmissionScheduler.cs +++ b/Svelto.ECS/EntitySubmissionScheduler.cs @@ -2,7 +2,7 @@ using System; namespace Svelto.ECS.Schedulers { - public interface IEntitySubmissionScheduler: IDisposable + public interface IEntitiesSubmissionScheduler: IDisposable { EnginesRoot.EntitiesSubmitter onTick { set; } } diff --git a/Svelto.ECS/EntitySubmitOperation.cs b/Svelto.ECS/EntitySubmitOperation.cs index 57299be..8f02ff7 100644 --- a/Svelto.ECS/EntitySubmitOperation.cs +++ b/Svelto.ECS/EntitySubmitOperation.cs @@ -1,5 +1,4 @@ using System; -using System.Diagnostics; namespace Svelto.ECS { @@ -9,21 +8,21 @@ namespace Svelto.ECS : IEquatable { public readonly EntitySubmitOperationType type; - public readonly IEntityBuilder[] builders; + public readonly IComponentBuilder[] builders; public readonly EGID fromID; public readonly EGID toID; -#if DEBUG && !PROFILER - public StackFrame trace; +#if DEBUG && !PROFILE_SVELTO + public System.Diagnostics.StackFrame trace; #endif public EntitySubmitOperation(EntitySubmitOperationType operation, EGID from, EGID to, - IEntityBuilder[] builders = null) + IComponentBuilder[] builders = null) { type = operation; this.builders = builders; fromID = from; toID = to; -#if DEBUG && !PROFILER +#if DEBUG && !PROFILE_SVELTO trace = default; #endif } @@ -48,6 +47,7 @@ namespace Svelto.ECS { Swap, Remove, - RemoveGroup + RemoveGroup, + SwapGroup } } \ No newline at end of file diff --git a/Svelto.ECS/EntityViewUtility.cs b/Svelto.ECS/EntityViewUtility.cs index 8e4be9b..e8120b4 100644 --- a/Svelto.ECS/EntityViewUtility.cs +++ b/Svelto.ECS/EntityViewUtility.cs @@ -1,58 +1,66 @@ using System; using System.Collections.Generic; using Svelto.DataStructures; -using Svelto.ECS.Internal; using Svelto.Utilities; namespace Svelto.ECS { -#if DEBUG && !PROFILER +#if DEBUG && !PROFILE_SVELTO struct ECSTuple { - public readonly T1 implementorType; + public readonly T1 instance; public T2 numberOfImplementations; public ECSTuple(T1 implementor, T2 v) { - implementorType = implementor; + instance = implementor; numberOfImplementations = v; } } #endif - static class EntityViewUtility + static class EntityComponentUtility { + const string DUPLICATE_IMPLEMENTOR_ERROR = + "Svelto.ECS the same component is implemented with more than one implementor. This is " + + "considered an error and MUST be fixed. "; + + const string NULL_IMPLEMENTOR_ERROR = + "Svelto.ECS Null implementor, please be careful about the implementors passed to avoid " + + "performance loss "; - public static void FillEntityView(this IEntityBuilder entityBuilder - , ref T entityView - , FasterList>> - entityViewBlazingFastReflection - , IEnumerable implementors, -#if DEBUG && !PROFILER - Dictionary> implementorsByType + const string NOT_FOUND_EXCEPTION = + "Svelto.ECS Implementor not found for an EntityComponent. "; + + public static void FillEntityComponent + (this IComponentBuilder componentBuilder, ref T entityComponent + , FasterList>> entityComponentBlazingFastReflection + , IEnumerable implementors +#if DEBUG && !PROFILE_SVELTO + ,Dictionary> implementorsByType #else - Dictionary implementorsByType + , Dictionary implementorsByType #endif - , Dictionary cachedTypes - ) + , Dictionary cachedTypeInterfaces) { - //efficient way to collect the fields of every EntityViewType - var setters = - FasterList>>.NoVirt.ToArrayFast(entityViewBlazingFastReflection, out var count); + //efficient way to collect the fields of every EntityComponentType + var setters = FasterList>>.NoVirt.ToArrayFast( + entityComponentBlazingFastReflection, out var count); + //todo this should happen once per T, not once per Build foreach (var implementor in implementors) { if (implementor != null) { var type = implementor.GetType(); - if (cachedTypes.TryGetValue(type, out var interfaces) == false) - interfaces = cachedTypes[type] = type.GetInterfacesEx(); + 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]; -#if DEBUG && !PROFILER +#if DEBUG && !PROFILE_SVELTO if (implementorsByType.TryGetValue(componentType, out var implementorData)) { implementorData.numberOfImplementations++; @@ -65,11 +73,11 @@ namespace Svelto.ECS #endif } } -#if DEBUG && !PROFILER +#if DEBUG && !PROFILE_SVELTO else { - Console.Log(NULL_IMPLEMENTOR_ERROR.FastConcat(" entityView ", - entityBuilder.GetEntityType().ToString())); + Console.Log(NULL_IMPLEMENTOR_ERROR.FastConcat(" entityComponent ", + componentBuilder.GetEntityComponentType().ToString())); } #endif } @@ -79,47 +87,33 @@ namespace Svelto.ECS var fieldSetter = setters[i]; var fieldType = fieldSetter.Key; -#if DEBUG && !PROFILER - ECSTuple component; +#if DEBUG && !PROFILE_SVELTO + ECSTuple implementor; #else - object component; + object implementor; #endif - if (implementorsByType.TryGetValue(fieldType, out component) == false) + if (implementorsByType.TryGetValue(fieldType, out implementor) == false) { - var e = new ECSException(NOT_FOUND_EXCEPTION + " Component Type: " + fieldType.Name + - " - EntityView: " + entityBuilder.GetEntityType().Name); + var e = new ECSException(NOT_FOUND_EXCEPTION + " Component Type: " + fieldType.Name + + " - EntityComponent: " + componentBuilder.GetEntityComponentType().Name); throw e; } -#if DEBUG && !PROFILER - if (component.numberOfImplementations > 1) +#if DEBUG && !PROFILE_SVELTO + if (implementor.numberOfImplementations > 1) throw new ECSException(DUPLICATE_IMPLEMENTOR_ERROR.FastConcat( - "Component Type: ", fieldType.Name, - " implementor: ", - component.implementorType - .ToString()) + - " - EntityView: " + - entityBuilder.GetEntityType().Name); + "Component Type: ", fieldType.Name, " implementor: ", implementor.instance.ToString()) + + " - EntityComponent: " + componentBuilder.GetEntityComponentType().Name); #endif -#if DEBUG && !PROFILER - fieldSetter.Value(ref entityView, component.implementorType); +#if DEBUG && !PROFILE_SVELTO + fieldSetter.Value(ref entityComponent, implementor.instance); #else - fieldSetter.Value(ref entityView, component); + fieldSetter.Value(ref entityComponent, implementor); #endif } implementorsByType.Clear(); } - - const string DUPLICATE_IMPLEMENTOR_ERROR = - "Svelto.ECS the same component is implemented with more than one implementor. This is " + - "considered an error and MUST be fixed. "; - - const string NULL_IMPLEMENTOR_ERROR = - "Svelto.ECS Null implementor, please be careful about the implementors passed to avoid " + - "performance loss "; - - const string NOT_FOUND_EXCEPTION = "Svelto.ECS Implementor not found for an EntityView. "; } } \ No newline at end of file diff --git a/Svelto.ECS/ExclusiveGroup.cs b/Svelto.ECS/ExclusiveGroup.cs index 959f7cc..e3c0487 100644 --- a/Svelto.ECS/ExclusiveGroup.cs +++ b/Svelto.ECS/ExclusiveGroup.cs @@ -1,10 +1,27 @@ using System; using System.Collections.Generic; +using Svelto.ECS.Internal; #pragma warning disable 660,661 namespace Svelto.ECS { + /// + /// still experimental alternative to ExclusiveGroup, use this like: + /// use this like: + /// public class TriggersGroup : ExclusiveGroup {} + /// + /// + public abstract class NamedExclusiveGroup + { + public static ExclusiveGroup Group = new ExclusiveGroup(); + public static string name = typeof(T).FullName; + +// protected NamedExclusiveGroup() { } + // protected NamedExclusiveGroup(string recognizeAs) : base(recognizeAs) {} + // protected NamedExclusiveGroup(ushort range) : base(range) {} + } + /// /// Exclusive Groups guarantee that the GroupID is unique. /// @@ -17,26 +34,12 @@ namespace Svelto.ECS /// public static ExclusiveGroup[] GroupOfGroups = { MyExclusiveGroup1, ...}; //for each on this! /// } /// - /// - - ///use this like: - /// public class TriggersGroup : ExclusiveGroup {} - public abstract class NamedExclusiveGroup:ExclusiveGroup - { - public static ExclusiveGroup Group = new ExclusiveGroup(); - public static string name = typeof(T).FullName; - - public NamedExclusiveGroup() { } - - public NamedExclusiveGroup(string recognizeAs) : base(recognizeAs) - {} - - public NamedExclusiveGroup(ushort range) : base(range) - {} - } - + + ///To debug it use in your debug window: Svelto.ECS.Debugger.EGID.GetGroupNameFromId(groupID) public class ExclusiveGroup { + public const uint MaxNumberOfExclusiveGroups = 2 << 20; + public ExclusiveGroup() { _group = ExclusiveGroupStruct.Generate(); @@ -46,7 +49,7 @@ namespace Svelto.ECS { _group = ExclusiveGroupStruct.Generate(); - _serialisedGroups.Add(recognizeAs, _group); + _knownGroups.Add(recognizeAs, _group); } public ExclusiveGroup(ushort range) @@ -61,7 +64,7 @@ namespace Svelto.ECS { return group._group; } - + public static explicit operator uint(ExclusiveGroup group) { return group._group; @@ -71,118 +74,33 @@ namespace Svelto.ECS { #if DEBUG if (a._range == 0) - throw new ECSException("adding values to a not ranged ExclusiveGroup"); + throw new ECSException($"Adding values to a not ranged ExclusiveGroup: {(uint)a}"); if (b >= a._range) - throw new ECSException("Using out of range group"); -#endif + throw new ECSException($"Using out of range group: {(uint)a} + {b}"); +#endif return a._group + b; } - - readonly ExclusiveGroupStruct _group; - - //I use this as parameter because it must not be possible to pass null Exclusive Groups. - public struct ExclusiveGroupStruct : IEquatable, IComparable, - IEqualityComparer + + public static ExclusiveGroupStruct Search(string holderGroupName) { - public static bool operator ==(ExclusiveGroupStruct c1, ExclusiveGroupStruct c2) - { - return c1.Equals(c2); - } - - public static bool operator !=(ExclusiveGroupStruct c1, ExclusiveGroupStruct c2) - { - return c1.Equals(c2) == false; - } - - public bool Equals(ExclusiveGroupStruct other) - { - return other._id == _id; - } - - public int CompareTo(ExclusiveGroupStruct other) - { - return other._id.CompareTo(_id); - } - - public bool Equals(ExclusiveGroupStruct x, ExclusiveGroupStruct y) - { - return x._id == y._id; - } - - public int GetHashCode(ExclusiveGroupStruct obj) - { - return _id.GetHashCode(); - } - - internal static ExclusiveGroupStruct Generate() - { - ExclusiveGroupStruct groupStruct; - - groupStruct._id = _globalId; - DBC.ECS.Check.Require(_globalId + 1 < ushort.MaxValue, "too many exclusive groups created"); - _globalId++; - - return groupStruct; - } - - /// - /// Use this constructor to reserve N groups - /// - internal ExclusiveGroupStruct(ushort range) - { - _id = _globalId; - DBC.ECS.Check.Require(_globalId + range < ushort.MaxValue, "too many exclusive groups created"); - _globalId += range; - } - - internal ExclusiveGroupStruct(uint groupID) - { - _id = groupID; - } - - public ExclusiveGroupStruct(byte[] data, uint pos) - { - _id = (uint)( - data[pos++] - | data[pos++] << 8 - | data[pos++] << 16 - | 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(); - - group._id = a._id + b; - - return group; - } + if (_knownGroups.ContainsKey(holderGroupName) == false) + throw new Exception("Named Group Not Found ".FastConcat(holderGroupName)); - uint _id; - static uint _globalId; + return _knownGroups[holderGroupName]; } - public static ExclusiveGroupStruct Search(string holderGroupName) + public override string ToString() { - if (_serialisedGroups.ContainsKey(holderGroupName) == false) - throw new Exception("Named Group Not Found ".FastConcat(holderGroupName)); - - return _serialisedGroups[holderGroupName]; + return _group.ToString(); } - static readonly Dictionary _serialisedGroups = new Dictionary _knownGroups = new Dictionary(); + #if DEBUG readonly ushort _range; -#endif +#endif + readonly ExclusiveGroupStruct _group; } } @@ -260,6 +178,6 @@ namespace Svelto.ECS } #if DEBUG - static string[] groupNames = new string[ushort.MaxValue]; + static string[] groupNames = new string[ExclusiveGroup.MaxNumberOfExclusiveGroups]; #endif #endif \ No newline at end of file diff --git a/Svelto.ECS/ExclusiveGroupStruct.cs b/Svelto.ECS/ExclusiveGroupStruct.cs new file mode 100644 index 0000000..e683dd4 --- /dev/null +++ b/Svelto.ECS/ExclusiveGroupStruct.cs @@ -0,0 +1,114 @@ +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using Svelto.ECS.Internal; + +namespace Svelto.ECS +{ + [StructLayout(LayoutKind.Explicit, Size = 4)] + public struct ExclusiveGroupStruct : IEquatable, IComparable, + IEqualityComparer + { + public override bool Equals(object obj) + { + return obj is ExclusiveGroupStruct other && Equals(other); + } + + public override int GetHashCode() + { + return (int) _id; + } + + public static bool operator ==(ExclusiveGroupStruct c1, ExclusiveGroupStruct c2) + { + return c1.Equals(c2); + } + + public static bool operator !=(ExclusiveGroupStruct c1, ExclusiveGroupStruct c2) + { + return c1.Equals(c2) == false; + } + + public bool Equals(ExclusiveGroupStruct other) + { + return other._id == _id; + } + + public int CompareTo(ExclusiveGroupStruct other) + { + return other._id.CompareTo(_id); + } + + public bool Equals(ExclusiveGroupStruct x, ExclusiveGroupStruct y) + { + return x._id == y._id; + } + + public int GetHashCode(ExclusiveGroupStruct obj) + { + return _id.GetHashCode(); + } + + public override string ToString() + { + return _id.ToString(); + } + + internal static ExclusiveGroupStruct Generate(byte bitmask = 0) + { + ExclusiveGroupStruct groupStruct; + + groupStruct._id = _globalId; + groupStruct._bytemask = bitmask; + DBC.ECS.Check.Require(_globalId + 1 < ExclusiveGroup.MaxNumberOfExclusiveGroups, "too many exclusive groups created"); + _globalId++; + + return groupStruct; + } + + internal ExclusiveGroupStruct(ExclusiveGroupStruct @group):this() { this = group; } + + /// + /// Use this constructor to reserve N groups + /// + internal ExclusiveGroupStruct(ushort range):this() + { + _id = _globalId; + DBC.ECS.Check.Require(_globalId + range < ExclusiveGroup.MaxNumberOfExclusiveGroups, "too many exclusive groups created"); + _globalId += range; + } + + internal ExclusiveGroupStruct(uint groupID):this() + { + _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; + } +} \ No newline at end of file diff --git a/Svelto.ECS/ExecuteOnEntitiesDB.cs b/Svelto.ECS/ExecuteOnEntitiesDB.cs deleted file mode 100644 index 375dfb4..0000000 --- a/Svelto.ECS/ExecuteOnEntitiesDB.cs +++ /dev/null @@ -1,61 +0,0 @@ -using System; -using Svelto.DataStructures; - -namespace Svelto.ECS.Internal -{ - partial class EntitiesDB - { - public void ExecuteOnAllEntities(Action action) - where T : struct, IEntityStruct - { - var type = typeof(T); - - if (_groupsPerEntity.TryGetValue(new RefWrapper(type), out var dictionary)) - { - foreach (var pair in dictionary) - { - var entities = (pair.Value as TypeSafeDictionary).GetValuesArray(out var innerCount); - - if (innerCount > 0) - action(entities, new ExclusiveGroup.ExclusiveGroupStruct(pair.Key), innerCount, this); - } - } - } - - public void ExecuteOnAllEntities - (W value, Action action) - where T : struct, IEntityStruct - { - var type = typeof(T); - - if (_groupsPerEntity.TryGetValue(new RefWrapper(type), out var dic)) - { - foreach (var pair in dic) - { - var entities = (pair.Value as TypeSafeDictionary).GetValuesArray(out var innerCount); - - if (innerCount > 0) - action(entities, new ExclusiveGroup.ExclusiveGroupStruct(pair.Key), innerCount, this, value); - } - } - } - - public void ExecuteOnAllEntities - (ref W value, ExecuteOnAllEntitiesAction action) - where T : struct, IEntityStruct - { - var type = typeof(T); - - if (_groupsPerEntity.TryGetValue(new RefWrapper(type), out var dic)) - { - foreach (var pair in dic) - { - var entities = (pair.Value as TypeSafeDictionary).GetValuesArray(out var innerCount); - - if (innerCount > 0) - action(entities, new ExclusiveGroup.ExclusiveGroupStruct(pair.Key), innerCount, this, ref value); - } - } - } - } -} \ No newline at end of file diff --git a/Svelto.ECS/ExtendibleEntityDescriptor.cs b/Svelto.ECS/ExtendibleEntityDescriptor.cs index 3b420fd..71dc7de 100644 --- a/Svelto.ECS/ExtendibleEntityDescriptor.cs +++ b/Svelto.ECS/ExtendibleEntityDescriptor.cs @@ -17,7 +17,7 @@ namespace Svelto.ECS $"SerializableEntityDescriptors cannot be used as base entity descriptor: {typeof(TType)}"); } - public ExtendibleEntityDescriptor(IEntityBuilder[] extraEntities) + public ExtendibleEntityDescriptor(IComponentBuilder[] extraEntities) { _dynamicDescriptor = new DynamicEntityDescriptor(extraEntities); } @@ -34,14 +34,14 @@ namespace Svelto.ECS return this; } - public ExtendibleEntityDescriptor ExtendWith(IEntityBuilder[] extraEntities) + public ExtendibleEntityDescriptor ExtendWith(IComponentBuilder[] extraEntities) { _dynamicDescriptor.ExtendWith(extraEntities); return this; } - public IEntityBuilder[] entitiesToBuild => _dynamicDescriptor.entitiesToBuild; + public IComponentBuilder[] componentsToBuild => _dynamicDescriptor.componentsToBuild; DynamicEntityDescriptor _dynamicDescriptor; } diff --git a/Svelto.ECS/Extensions/ProcessorCount.cs b/Svelto.ECS/Extensions/ProcessorCount.cs new file mode 100644 index 0000000..3f89ac2 --- /dev/null +++ b/Svelto.ECS/Extensions/ProcessorCount.cs @@ -0,0 +1,19 @@ +using System; + +namespace Svelto.ECS +{ + internal static class ProcessorCount + { + static readonly int processorCount = Environment.ProcessorCount; + + public static int BatchSize(uint totalIterations) + { + var iterationsPerBatch = totalIterations / processorCount; + + if (iterationsPerBatch < 16) + return 16; + + return (int) iterationsPerBatch; + } + } +} \ No newline at end of file diff --git a/Svelto.ECS/Extensions/Svelto/EntityDBExtensions.cs b/Svelto.ECS/Extensions/Svelto/EntityDBExtensions.cs new file mode 100644 index 0000000..80032ee --- /dev/null +++ b/Svelto.ECS/Extensions/Svelto/EntityDBExtensions.cs @@ -0,0 +1,70 @@ +using System; +using Svelto.DataStructures; + +namespace Svelto.ECS +{ + public static class EntityDBExtensions + { + public static NativeGroupsEnumerable NativeGroupsIterator(this EntitiesDB db, + ExclusiveGroupStruct[] groups) + where T1 : unmanaged, IEntityComponent where T2 : unmanaged, IEntityComponent + { + return new NativeGroupsEnumerable(db, groups); + } + + public static NativeGroupsEnumerable NativeGroupsIterator + (this EntitiesDB db, ExclusiveGroupStruct[] groups) + where T1 : unmanaged, IEntityComponent where T2 : unmanaged, IEntityComponent + where T3 : unmanaged, IEntityComponent + { + return new NativeGroupsEnumerable(db, groups); + } + + public static NativeGroupsEnumerable NativeGroupsIterator + (this EntitiesDB db, ExclusiveGroupStruct[] groups) + where T1 : unmanaged, IEntityComponent where T2 : unmanaged, IEntityComponent + where T3 : unmanaged, IEntityComponent where T4 : unmanaged, IEntityComponent + { + return new NativeGroupsEnumerable(db, groups); + } + + public static NativeGroupsEnumerable NativeGroupsIterator(this EntitiesDB db, ExclusiveGroupStruct[] groups) + where T1 : unmanaged, IEntityComponent + { + return new NativeGroupsEnumerable(db, groups); + } + + public static NativeAllGroupsEnumerable NativeGroupsIterator(this EntitiesDB db) + where T1 : unmanaged, IEntityComponent + { + return new NativeAllGroupsEnumerable(db); + } +#if TO_BE_FINISHED + public static NativeAllGroupsEnumerable NativeGroupsIterator(this EntitiesDB db) + where T1 : unmanaged, IEntityComponent where T2 : unmanaged, IEntityComponent + { + return new NativeAllGroupsEnumerable(db); + } +#endif + public static NB NativeEntitiesBuffer(this EntitiesDB db, ExclusiveGroupStruct @group) + where T : unmanaged, IEntityComponent + { + return db.QueryEntities(group).ToNativeBuffer(); + } + + public static BT, NB> NativeEntitiesBuffer(this EntitiesDB db, ExclusiveGroupStruct @group) + where T1 : unmanaged, IEntityComponent + where T2 : unmanaged, IEntityComponent + { + return db.QueryEntities(group).ToNativeBuffers(); + } + + public static BT, NB, NB> NativeEntitiesBuffer(this EntitiesDB db, ExclusiveGroupStruct @group) + where T1 : unmanaged, IEntityComponent + where T2 : unmanaged, IEntityComponent + where T3 : unmanaged, IEntityComponent + { + return db.QueryEntities(group).ToNativeBuffers(); + } + } +} \ No newline at end of file diff --git a/Svelto.ECS/Extensions/Svelto/NativeAllGroupsEnumerable.cs b/Svelto.ECS/Extensions/Svelto/NativeAllGroupsEnumerable.cs new file mode 100644 index 0000000..0bc873b --- /dev/null +++ b/Svelto.ECS/Extensions/Svelto/NativeAllGroupsEnumerable.cs @@ -0,0 +1,117 @@ +using Svelto.DataStructures; +using Svelto.ECS.Internal; + +namespace Svelto.ECS +{ + public struct NativeAllGroupsEnumerable where T1 : unmanaged, IEntityComponent + { + public NativeAllGroupsEnumerable(EntitiesDB db) + { + _db = db; + } + + public struct NativeGroupsIterator + { + public NativeGroupsIterator(EntitiesDB db) : this() + { + _db = db.FindGroups().GetEnumerator(); + } + + public bool MoveNext() + { + //attention, the while is necessary to skip empty groups + while (_db.MoveNext() == true) + { + FasterDictionary.KeyValuePairFast group = _db.Current; + + ITypeSafeDictionary typeSafeDictionary = @group.Value as ITypeSafeDictionary; + + if (typeSafeDictionary.Count == 0) continue; + + _array = new EntityCollection(typeSafeDictionary.GetValuesArray(out var count), count) + .ToNativeBuffer(); + + return true; + } + + return false; + } + + public void Reset() + { + } + + public NB Current => _array; + + readonly FasterDictionary.FasterDictionaryKeyValueEnumerator _db; + + NB _array; + } + + public NativeGroupsIterator GetEnumerator() + { + return new NativeGroupsIterator(_db); + } + + readonly EntitiesDB _db; + } +#if TO_BE_FINISHED + public struct NativeAllGroupsEnumerable + where T1 : unmanaged, IEntityComponent where T2 : unmanaged, IEntityComponent + { + public NativeAllGroupsEnumerable(EntitiesDB db) + { + _db = db; + } + + public struct NativeGroupsIterator + { + public NativeGroupsIterator(EntitiesDB db) : this() + { + _db = db.FindGroups().GetEnumerator(); + } + + public bool MoveNext() + { + //attention, the while is necessary to skip empty groups + while (_db.MoveNext() == true) + { + FasterDictionary.KeyValuePairFast group = _db.Current; + + ITypeSafeDictionary typeSafeDictionary1 = @group.Value as ITypeSafeDictionary; + ITypeSafeDictionary typeSafeDictionary2 = @group.Value as ITypeSafeDictionary; + + DBC.ECS.Check.Require(typeSafeDictionary1.Count != typeSafeDictionary2.Count + , "entities count do not match"); + + if (typeSafeDictionary1.Count == 0) continue; + + _array = new BT, NB>()(new EntityCollection(typeSafeDictionary1.GetValuesArray(out var count), count) + .ToNativeBuffer(); + + return true; + } + + return false; + } + + public void Reset() + { + } + + public BT, NB> Current => _array; + + readonly FasterDictionary.FasterDictionaryKeyValueEnumerator _db; + + BT, NB> _array; + } + + public NativeGroupsIterator GetEnumerator() + { + return new NativeGroupsIterator(_db); + } + + readonly EntitiesDB _db; + } +#endif +} diff --git a/Svelto.ECS/Extensions/Svelto/NativeGroupsEnumerable.cs b/Svelto.ECS/Extensions/Svelto/NativeGroupsEnumerable.cs new file mode 100644 index 0000000..c494727 --- /dev/null +++ b/Svelto.ECS/Extensions/Svelto/NativeGroupsEnumerable.cs @@ -0,0 +1,221 @@ +using Svelto.DataStructures; + +namespace Svelto.ECS +{ + public struct NativeGroupsEnumerable + where T1 : unmanaged, IEntityComponent where T2 : unmanaged, IEntityComponent + where T3 : unmanaged, IEntityComponent where T4 : unmanaged, IEntityComponent + { + readonly EntitiesDB _db; + readonly ExclusiveGroupStruct[] _groups; + + public NativeGroupsEnumerable(EntitiesDB db, ExclusiveGroupStruct[] groups) + { + _db = db; + _groups = groups; + } + + public struct NativeGroupsIterator + { + public NativeGroupsIterator(EntitiesDB db, ExclusiveGroupStruct[] groups) : this() + { + _groups = groups; + _indexGroup = -1; + _entitiesDB = db; + } + + public bool MoveNext() + { + //attention, the while is necessary to skip empty groups + while (++_indexGroup < _groups.Length) + { + var entityCollection = _entitiesDB.QueryEntities(_groups[_indexGroup]); + if (entityCollection.count == 0) continue; + var entityCollection2 = _entitiesDB.QueryEntities(_groups[_indexGroup]); + if (entityCollection2.count == 0) continue; + + DBC.ECS.Check.Assert(entityCollection.count == entityCollection2.count, "congratulation, you found a bug in Svelto, please report it"); + + BT, NB, NB> array = entityCollection.ToNativeBuffers(); + NB array2 = entityCollection2.ToNativeBuffer(); + _array= new BT, NB, NB, NB>(array.buffer1, array.buffer2, array.buffer3, array2, entityCollection.count); + break; + } + + return _indexGroup < _groups.Length; + } + + public void Reset() { _indexGroup = -1; } + + public BT, NB, NB, NB> Current => _array; + + readonly ExclusiveGroupStruct[] _groups; + + int _indexGroup; + BT, NB, NB, NB> _array; + readonly EntitiesDB _entitiesDB; + } + + public NativeGroupsIterator GetEnumerator() { return new NativeGroupsIterator(_db, _groups); } + } + + public struct NativeGroupsEnumerable + where T1 : unmanaged, IEntityComponent where T2 : unmanaged, IEntityComponent where T3 : unmanaged, IEntityComponent + { + readonly EntitiesDB _db; + readonly ExclusiveGroupStruct[] _groups; + + public NativeGroupsEnumerable(EntitiesDB db, ExclusiveGroupStruct[] groups) + { + _db = db; + _groups = groups; + } + + public struct NativeGroupsIterator + { + public NativeGroupsIterator(EntitiesDB db, ExclusiveGroupStruct[] groups) : this() + { + _groups = groups; + _indexGroup = -1; + _entitiesDB = db; + } + + public bool MoveNext() + { + //attention, the while is necessary to skip empty groups + while (++_indexGroup < _groups.Length) + { + var entityCollection = _entitiesDB.QueryEntities(_groups[_indexGroup]); + if (entityCollection.count == 0) continue; + + _array = entityCollection.ToNativeBuffers(); + break; + } + + return _indexGroup < _groups.Length; + } + + public void Reset() { _indexGroup = -1; } + + public BT, NB, NB> Current => _array; + + readonly ExclusiveGroupStruct[] _groups; + + int _indexGroup; + BT, NB, NB> _array; + readonly EntitiesDB _entitiesDB; + } + + public NativeGroupsIterator GetEnumerator() { return new NativeGroupsIterator(_db, _groups); } + } + + public struct NativeGroupsEnumerable where T1 : unmanaged, IEntityComponent where T2 : unmanaged, IEntityComponent + { + public NativeGroupsEnumerable(EntitiesDB db, ExclusiveGroupStruct[] groups) + { + _db = db; + _groups = groups; + } + + public struct NativeGroupsIterator + { + public NativeGroupsIterator(EntitiesDB db, ExclusiveGroupStruct[] groups) : this() + { + _db = db; + _groups = groups; + _indexGroup = -1; + } + + public bool MoveNext() + { + //attention, the while is necessary to skip empty groups + while (++_indexGroup < _groups.Length) + { + var entityCollection = _db.QueryEntities(_groups[_indexGroup]); + if (entityCollection.count == 0) continue; + + _array = entityCollection.ToNativeBuffers(); + break; + } + + return _indexGroup < _groups.Length; + } + + public void Reset() + { + _indexGroup = -1; + } + + public BT, NB> Current => _array; + + readonly EntitiesDB _db; + readonly ExclusiveGroupStruct[] _groups; + + int _indexGroup; + BT, NB> _array; + } + + public NativeGroupsIterator GetEnumerator() + { + return new NativeGroupsIterator(_db, _groups); + } + + readonly EntitiesDB _db; + readonly ExclusiveGroupStruct[] _groups; + } + + public struct NativeGroupsEnumerable where T1 : unmanaged, IEntityComponent + { + public NativeGroupsEnumerable(EntitiesDB db, ExclusiveGroupStruct[] groups) + { + _db = db; + _groups = groups; + } + + public struct NativeGroupsIterator + { + public NativeGroupsIterator(EntitiesDB db, ExclusiveGroupStruct[] groups) : this() + { + _db = db; + _groups = groups; + _indexGroup = -1; + } + + public bool MoveNext() + { + //attention, the while is necessary to skip empty groups + while (++_indexGroup < _groups.Length) + { + var entityCollection = _db.QueryEntities(_groups[_indexGroup]); + if (entityCollection.count == 0) continue; + + _array = entityCollection.ToNativeBuffer(); + break; + } + + return _indexGroup < _groups.Length; + } + + public void Reset() + { + _indexGroup = -1; + } + + public NB Current => _array; + + readonly EntitiesDB _db; + readonly ExclusiveGroupStruct[] _groups; + + int _indexGroup; + NB _array; + } + + public NativeGroupsIterator GetEnumerator() + { + return new NativeGroupsIterator(_db, _groups); + } + + readonly EntitiesDB _db; + readonly ExclusiveGroupStruct[] _groups; + } +} \ No newline at end of file diff --git a/Svelto.ECS/Extensions/Unity/DOTS/CopySveltoToUECSEnginesGroup.cs b/Svelto.ECS/Extensions/Unity/DOTS/CopySveltoToUECSEnginesGroup.cs new file mode 100644 index 0000000..523a04f --- /dev/null +++ b/Svelto.ECS/Extensions/Unity/DOTS/CopySveltoToUECSEnginesGroup.cs @@ -0,0 +1,30 @@ +#if UNITY_ECS +using Svelto.Common; +using Unity.Entities; +using Unity.Jobs; + +namespace Svelto.ECS.Extensions.Unity +{ + [Sequenced(nameof(JobifiedSveltoEngines.CopySveltoToUECSEnginesGroup))] + [DisableAutoCreation] + public class CopySveltoToUECSEnginesGroup : ComponentSystemGroup, IJobifiedEngine + { + public JobHandle Execute(JobHandle _jobHandle) + { + foreach (var engine in Systems) + (engine as ICopySveltoToUECSEngine).jobHandle = _jobHandle; + + Update(); + + return _jobHandle; + } + + readonly SimulationSystemGroup _simulationSystemGroup; + } + + public interface ICopySveltoToUECSEngine:IEngine + { + JobHandle jobHandle { set; } + } +} +#endif \ No newline at end of file diff --git a/Svelto.ECS/Extensions/Unity/DOTS/DisposeJob.cs b/Svelto.ECS/Extensions/Unity/DOTS/DisposeJob.cs new file mode 100644 index 0000000..670241d --- /dev/null +++ b/Svelto.ECS/Extensions/Unity/DOTS/DisposeJob.cs @@ -0,0 +1,84 @@ +#if UNITY_2019_2_OR_NEWER +using System; +using Unity.Jobs; + +namespace Svelto.ECS.Extensions.Unity +{ + public struct DisposeJob:IJob where T:struct,IDisposable + { + public DisposeJob(in T disposable) + { + _entityCollection = disposable; + } + + public void Execute() + { + try + { + _entityCollection.Dispose(); + } + catch (Exception e) + { + Console.LogException(e, this.GetType().ToString().FastConcat(" ")); + } + } + + readonly T _entityCollection; + } + + public struct DisposeJob:IJob + where T1:struct,IDisposable where T2:struct,IDisposable + { + public DisposeJob(in T1 disposable1, in T2 disposable2) + { + _entityCollection1 = disposable1; + _entityCollection2 = disposable2; + } + + public void Execute() + { + try + { + _entityCollection1.Dispose(); + _entityCollection2.Dispose(); + } + catch (Exception e) + { + Console.LogException(e, this.GetType().ToString().FastConcat(" ")); + } + } + + readonly T1 _entityCollection1; + readonly T2 _entityCollection2; + } + + public struct DisposeJob:IJob + where T1:struct,IDisposable where T2:struct,IDisposable where T3:struct,IDisposable + { + public DisposeJob(in T1 disposable1, in T2 disposable2, in T3 disposable3) + { + _entityCollection1 = disposable1; + _entityCollection2 = disposable2; + _entityCollection3 = disposable3; + } + + public void Execute() + { + try + { + _entityCollection1.Dispose(); + _entityCollection2.Dispose(); + _entityCollection3.Dispose(); + } + catch (Exception e) + { + Console.LogException(e, this.GetType().ToString().FastConcat(" ")); + } + } + + readonly T1 _entityCollection1; + readonly T2 _entityCollection2; + readonly T3 _entityCollection3; + } +} +#endif \ No newline at end of file diff --git a/Svelto.ECS/Extensions/Unity/DOTS/EnginesRoot.NativeOperation.cs b/Svelto.ECS/Extensions/Unity/DOTS/EnginesRoot.NativeOperation.cs new file mode 100644 index 0000000..6c5e2df --- /dev/null +++ b/Svelto.ECS/Extensions/Unity/DOTS/EnginesRoot.NativeOperation.cs @@ -0,0 +1,265 @@ +#if UNITY_ECS +using Svelto.Common; +using Svelto.DataStructures; +using Svelto.ECS.DataStructures; +using Svelto.ECS.DataStructures.Unity; +using Unity.Jobs.LowLevel.Unsafe; + +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 AtomicRingBuffers _addOperationQueue = + new AtomicRingBuffers(Common.Allocator.Persistent, JobsUtility.MaxJobThreadCount + 1); + + readonly AtomicRingBuffers _removeOperationQueue = + new AtomicRingBuffers(Common.Allocator.Persistent, JobsUtility.MaxJobThreadCount + 1); + + readonly AtomicRingBuffers _swapOperationQueue = + new AtomicRingBuffers(Common.Allocator.Persistent, JobsUtility.MaxJobThreadCount + 1); + + NativeEntityRemove ProvideNativeEntityRemoveQueue() where T : IEntityDescriptor, new() + { + //todo: remove operation array and store entity descriptor hash in the return value + //todo I maybe able to provide a _nativeSwap.SwapEntity + _nativeRemoveOperations.Add( + new NativeOperationRemove(EntityDescriptorTemplate.descriptor.componentsToBuild)); + + return new NativeEntityRemove(_removeOperationQueue, _nativeRemoveOperations.count - 1); + } + + NativeEntitySwap ProvideNativeEntitySwapQueue() where T : IEntityDescriptor, new() + { + //todo: remove operation array and store entity descriptor hash in the return value + _nativeSwapOperations.Add( + new NativeOperationSwap(EntityDescriptorTemplate.descriptor.componentsToBuild)); + + return new NativeEntitySwap(_swapOperationQueue, _nativeSwapOperations.count - 1); + } + + NativeEntityFactory ProvideNativeEntityFactoryQueue() where T : IEntityDescriptor, new() + { + //todo: remove operation array and store entity descriptor hash in the return value + _nativeAddOperations.Add( + new NativeOperationBuild(EntityDescriptorTemplate.descriptor.componentsToBuild)); + + return new NativeEntityFactory(_addOperationQueue, _nativeAddOperations.count - 1); + } + + void NativeOperationSubmission(in PlatformProfiler profiler) + { + using (profiler.Sample("Native Remove/Swap Operations")) + { + for (int i = 0; i < _removeOperationQueue.count; i++) + { + ref var buffer = ref _removeOperationQueue.GetBuffer(i); + + while (buffer.IsEmpty() == false) + { + var componentsIndex = buffer.Dequeue(); + var entityEGID = buffer.Dequeue(); + CheckRemoveEntityID(entityEGID); + QueueEntitySubmitOperation(new EntitySubmitOperation( + EntitySubmitOperationType.Remove, entityEGID, entityEGID + , _nativeRemoveOperations[componentsIndex].entityComponents)); + } + } + + for (int i = 0; i < _swapOperationQueue.count; i++) + { + ref var buffer = ref _swapOperationQueue.GetBuffer(i); + + while (buffer.IsEmpty() == false) + { + var componentsIndex = buffer.Dequeue(); + var entityEGID = buffer.Dequeue(); + + CheckRemoveEntityID(entityEGID.@from); + CheckAddEntityID(entityEGID.to); + + QueueEntitySubmitOperation(new EntitySubmitOperation( + EntitySubmitOperationType.Swap, entityEGID.@from, entityEGID.to + , _nativeSwapOperations[componentsIndex].entityComponents)); + } + } + } + + using (profiler.Sample("Native Add Operations")) + { + for (int i = 0; i < _addOperationQueue.count; i++) + { + ref var buffer = ref _addOperationQueue.GetBuffer(i); + + while (buffer.IsEmpty() == false) + { + var componentsIndex = buffer.Dequeue(); + var egid = buffer.Dequeue(); + var componentCounts = buffer.Dequeue(); + + EntityComponentInitializer init = + BuildEntity(egid, _nativeAddOperations[componentsIndex].components); + + while (componentCounts > 0) + { + componentCounts--; + + var typeID = buffer.Dequeue(); + + IFiller entityBuilder = EntityComponentIDMap.GetTypeFromID(typeID); + + //after the typeID, I expect the serialized component + entityBuilder.FillFromByteArray(init, buffer); + } + } + } + } + } + + void AllocateNativeOperations() + { + _nativeRemoveOperations = new FasterList(); + _nativeSwapOperations = new FasterList(); + _nativeAddOperations = new FasterList(); + } + + FasterList _nativeRemoveOperations; + FasterList _nativeSwapOperations; + FasterList _nativeAddOperations; + } + + readonly struct DoubleEGID + { + internal readonly EGID from; + internal readonly EGID to; + + public DoubleEGID(EGID from1, EGID to1) + { + from = from1; + to = to1; + } + } + + public readonly struct NativeEntityRemove + { + readonly AtomicRingBuffers _removeQueue; + readonly uint _indexRemove; + + internal NativeEntityRemove(AtomicRingBuffers EGIDsToRemove, uint indexRemove) + { + _removeQueue = EGIDsToRemove; + _indexRemove = indexRemove; + } + + public void RemoveEntity(EGID egid, int threadIndex) + { + var simpleNativeBag = _removeQueue.GetBuffer(threadIndex); + + simpleNativeBag.Enqueue(_indexRemove); + simpleNativeBag.Enqueue(egid); + } + } + + public readonly struct NativeEntitySwap + { + readonly AtomicRingBuffers _swapQueue; + readonly uint _indexSwap; + + internal NativeEntitySwap(AtomicRingBuffers EGIDsToSwap, uint indexSwap) + { + _swapQueue = EGIDsToSwap; + _indexSwap = indexSwap; + } + + public void SwapEntity(EGID from, EGID to, int threadIndex) + { + var simpleNativeBag = _swapQueue.GetBuffer(threadIndex); + simpleNativeBag.Enqueue(_indexSwap); + simpleNativeBag.Enqueue(new DoubleEGID(from, to)); + } + + public void SwapEntity(EGID from, ExclusiveGroupStruct to, int threadIndex) + { + var simpleNativeBag = _swapQueue.GetBuffer(threadIndex); + simpleNativeBag.Enqueue(_indexSwap); + simpleNativeBag.Enqueue(new DoubleEGID(from, new EGID(from.entityID, to))); + } + } + + public readonly struct NativeEntityFactory + { + readonly AtomicRingBuffers _addOperationQueue; + readonly uint _index; + + internal NativeEntityFactory(AtomicRingBuffers addOperationQueue, uint index) + { + _index = index; + _addOperationQueue = addOperationQueue; + } + + public NativeEntityComponentInitializer BuildEntity + (uint eindex, ExclusiveGroupStruct buildGroup, int threadIndex) + { + NativeBag unsafeBuffer = _addOperationQueue.GetBuffer(threadIndex + 1); + + unsafeBuffer.Enqueue(_index); + unsafeBuffer.Enqueue(new EGID(eindex, buildGroup)); + unsafeBuffer.ReserveEnqueue(out var index) = 0; + + return new NativeEntityComponentInitializer(unsafeBuffer, index); + } + } + + public readonly ref struct NativeEntityComponentInitializer + { + readonly NativeBag _unsafeBuffer; + readonly UnsafeArrayIndex _index; + + public NativeEntityComponentInitializer(in NativeBag unsafeBuffer, UnsafeArrayIndex index) + { + _unsafeBuffer = unsafeBuffer; + _index = index; + } + + public void Init(in T component) where T : unmanaged, IEntityComponent + { + uint id = EntityComponentID.ID.Data; + + _unsafeBuffer.AccessReserved(_index)++; + + _unsafeBuffer.Enqueue(id); + _unsafeBuffer.Enqueue(component); + } + } + + struct NativeOperationBuild + { + internal readonly IComponentBuilder[] components; + + public NativeOperationBuild(IComponentBuilder[] descriptorEntityComponentsToBuild) + { + components = descriptorEntityComponentsToBuild; + } + } + + readonly struct NativeOperationRemove + { + internal readonly IComponentBuilder[] entityComponents; + + public NativeOperationRemove(IComponentBuilder[] descriptorEntitiesToBuild) + { + entityComponents = descriptorEntitiesToBuild; + } + } + + readonly struct NativeOperationSwap + { + internal readonly IComponentBuilder[] entityComponents; + + public NativeOperationSwap(IComponentBuilder[] descriptorEntitiesToBuild) + { + entityComponents = descriptorEntitiesToBuild; + } + } +} +#endif \ No newline at end of file diff --git a/Svelto.ECS/Extensions/Unity/DOTS/IJobifiedEngine.cs b/Svelto.ECS/Extensions/Unity/DOTS/IJobifiedEngine.cs new file mode 100644 index 0000000..b52863b --- /dev/null +++ b/Svelto.ECS/Extensions/Unity/DOTS/IJobifiedEngine.cs @@ -0,0 +1,9 @@ +using Unity.Jobs; + +namespace Svelto.ECS.Extensions.Unity +{ + public interface IJobifiedEngine : IEngine + { + JobHandle Execute(JobHandle _jobHandle); + } +} \ No newline at end of file diff --git a/Svelto.ECS/Extensions/Unity/DOTS/JobifedEnginesGroup.cs b/Svelto.ECS/Extensions/Unity/DOTS/JobifedEnginesGroup.cs new file mode 100644 index 0000000..3566aff --- /dev/null +++ b/Svelto.ECS/Extensions/Unity/DOTS/JobifedEnginesGroup.cs @@ -0,0 +1,31 @@ +using Svelto.DataStructures; +using Unity.Jobs; + +namespace Svelto.ECS.Extensions.Unity +{ + public abstract class JobifedEnginesGroup + where Interface : class, IJobifiedEngine + { + protected JobifedEnginesGroup(FasterReadOnlyList engines, bool completeEachJob = false) + { + _engines = engines; + _completeEachJob = completeEachJob; + } + + public JobHandle Execute(JobHandle combinedHandles) + { + var fasterReadOnlyList = _engines; + for (var index = 0; index < fasterReadOnlyList.Count; index++) + { + var engine = fasterReadOnlyList[index]; + combinedHandles = JobHandle.CombineDependencies(combinedHandles, engine.Execute(combinedHandles)); + if (_completeEachJob) combinedHandles.Complete(); + } + + return combinedHandles; + } + + readonly FasterReadOnlyList _engines; + readonly bool _completeEachJob; + } +} \ No newline at end of file diff --git a/Svelto.ECS/Extensions/Unity/DOTS/JobifiedSveltoEngines.cs b/Svelto.ECS/Extensions/Unity/DOTS/JobifiedSveltoEngines.cs new file mode 100644 index 0000000..98a9ec4 --- /dev/null +++ b/Svelto.ECS/Extensions/Unity/DOTS/JobifiedSveltoEngines.cs @@ -0,0 +1,10 @@ +#if UNITY_ECS +namespace Svelto.ECS.Extensions.Unity +{ + public enum JobifiedSveltoEngines + { + CopySveltoToUECSEnginesGroup, + PureUECSSystemsGroup + } +} +#endif \ No newline at end of file diff --git a/Svelto.ECS/Extensions/Unity/DOTS/PureUECSSystemsGroup.cs b/Svelto.ECS/Extensions/Unity/DOTS/PureUECSSystemsGroup.cs new file mode 100644 index 0000000..64c50b0 --- /dev/null +++ b/Svelto.ECS/Extensions/Unity/DOTS/PureUECSSystemsGroup.cs @@ -0,0 +1,27 @@ +#if UNITY_ECS +using Svelto.Common; +using Unity.Entities; +using Unity.Jobs; + +namespace Svelto.ECS.Extensions.Unity +{ + [Sequenced(nameof(JobifiedSveltoEngines.PureUECSSystemsGroup))] + [DisableAutoCreation] + public class PureUECSSystemsGroup : IJobifiedEngine + { + public PureUECSSystemsGroup(World world) + { + _world = world; + } + + public JobHandle Execute(JobHandle _jobHandle) + { + _world.Update(); + + return _jobHandle; + } + + readonly World _world; + } +} +#endif \ No newline at end of file diff --git a/Svelto.ECS/Extensions/Unity/DOTS/SortedJobifedEnginesGroup.cs b/Svelto.ECS/Extensions/Unity/DOTS/SortedJobifedEnginesGroup.cs new file mode 100644 index 0000000..4648862 --- /dev/null +++ b/Svelto.ECS/Extensions/Unity/DOTS/SortedJobifedEnginesGroup.cs @@ -0,0 +1,31 @@ +#if UNITY_2019_1_OR_NEWER +using Svelto.DataStructures; +using Unity.Jobs; +using Svelto.Common; + +namespace Svelto.ECS.Extensions.Unity +{ + public abstract class SortedJobifedEnginesGroup + where SequenceOrder : struct, ISequenceOrder where Interface : class, IJobifiedEngine + { + protected SortedJobifedEnginesGroup(FasterReadOnlyList engines) + { + _instancedSequence = new Sequence(engines); + } + + public JobHandle Execute(JobHandle combinedHandles) + { + var fasterReadOnlyList = _instancedSequence.items; + for (var index = 0; index < fasterReadOnlyList.Count; index++) + { + var engine = fasterReadOnlyList[index]; + combinedHandles = JobHandle.CombineDependencies(combinedHandles, engine.Execute(combinedHandles)); + } + + return combinedHandles; + } + + readonly Sequence _instancedSequence; + } +} +#endif \ No newline at end of file diff --git a/Svelto.ECS/Extensions/Unity/DOTS/UECSSveltoEGID.cs b/Svelto.ECS/Extensions/Unity/DOTS/UECSSveltoEGID.cs new file mode 100644 index 0000000..8d460c9 --- /dev/null +++ b/Svelto.ECS/Extensions/Unity/DOTS/UECSSveltoEGID.cs @@ -0,0 +1,23 @@ +#if UNITY_ECS +using Unity.Entities; + +namespace Svelto.ECS.Extensions.Unity +{ + public struct UECSSveltoEGID : IComponentData + { + public EGID egid; + } + + public struct UECSSveltoGroupID : ISharedComponentData + { + public readonly uint group; + + public UECSSveltoGroupID(uint exclusiveGroup) { @group = exclusiveGroup; } + + public static implicit operator ExclusiveGroupStruct(UECSSveltoGroupID group) + { + return new ExclusiveGroupStruct(group.@group); + } + } +} +#endif \ No newline at end of file diff --git a/Svelto.ECS/Extensions/Unity/DOTS/UnityEntityDBExtensions.cs b/Svelto.ECS/Extensions/Unity/DOTS/UnityEntityDBExtensions.cs new file mode 100644 index 0000000..d081e5b --- /dev/null +++ b/Svelto.ECS/Extensions/Unity/DOTS/UnityEntityDBExtensions.cs @@ -0,0 +1,31 @@ +#if UNITY_2019_2_OR_NEWER +using System; +using Svelto.Common; +using Unity.Jobs; + +namespace Svelto.ECS.Extensions.Unity +{ + public static class UnityEntityDBExtensions + { + public static JobHandle ScheduleDispose + (this T1 disposable, JobHandle inputDeps) where T1 : struct, IDisposable + { + return new DisposeJob(disposable).Schedule(inputDeps); + } + + public static JobHandle ScheduleDispose + (this T1 disposable1, T2 disposable2, JobHandle inputDeps) + where T1 : struct, IDisposable where T2 : struct, IDisposable + { + return new DisposeJob(disposable1, disposable2).Schedule(inputDeps); + } + + public static JobHandle ScheduleParallel + (this JOB job, uint iterations, JobHandle inputDeps) where JOB: struct, IJobParallelFor + { + var innerloopBatchCount = ProcessorCount.BatchSize(iterations); + return job.Schedule((int)iterations, innerloopBatchCount, inputDeps); + } + } +} +#endif \ No newline at end of file diff --git a/Svelto.ECS/Extensions/Unity/GenericEntityDescriptorHolder.cs b/Svelto.ECS/Extensions/Unity/GenericEntityDescriptorHolder.cs index 28b6898..aeaa210 100644 --- a/Svelto.ECS/Extensions/Unity/GenericEntityDescriptorHolder.cs +++ b/Svelto.ECS/Extensions/Unity/GenericEntityDescriptorHolder.cs @@ -1,7 +1,7 @@ #if UNITY_5 || UNITY_5_3_OR_NEWER using UnityEngine; -namespace Svelto.ECS.Unity +namespace Svelto.ECS.Extensions.Unity { public abstract class GenericEntityDescriptorHolder: MonoBehaviour , IEntityDescriptorHolder diff --git a/Svelto.ECS/Extensions/Unity/SveltoGUIHelper.cs b/Svelto.ECS/Extensions/Unity/SveltoGUIHelper.cs index 160615d..bac02ff 100644 --- a/Svelto.ECS/Extensions/Unity/SveltoGUIHelper.cs +++ b/Svelto.ECS/Extensions/Unity/SveltoGUIHelper.cs @@ -1,7 +1,8 @@ #if UNITY_5 || UNITY_5_3_OR_NEWER +using Svelto.ECS.Hybrid; using UnityEngine; -namespace Svelto.ECS.Unity +namespace Svelto.ECS.Extensions.Unity { public static class SveltoGUIHelper { @@ -30,6 +31,7 @@ namespace Svelto.ECS.Unity return holder; } + public static T Create(EGID ID, Transform contextHolder, IEntityFactory factory) where T : MonoBehaviour, IEntityDescriptorHolder { @@ -44,7 +46,7 @@ namespace Svelto.ECS.Unity return holder; } - public static EntityStructInitializer CreateWithEntity(EGID ID, Transform contextHolder, + public static EntityComponentInitializer CreateWithEntity(EGID ID, Transform contextHolder, IEntityFactory factory, out T holder) where T : MonoBehaviour, IEntityDescriptorHolder { @@ -72,7 +74,7 @@ namespace Svelto.ECS.Unity static uint InternalBuildAll(uint startIndex, IEntityDescriptorHolder descriptorHolder, IEntityFactory factory, ExclusiveGroup group, IImplementor[] implementors, string groupNamePostfix) { - ExclusiveGroup.ExclusiveGroupStruct realGroup = group; + ExclusiveGroupStruct realGroup = group; if (string.IsNullOrEmpty(descriptorHolder.groupName) == false) { @@ -90,10 +92,45 @@ namespace Svelto.ECS.Unity var init = factory.BuildEntity(egid, descriptorHolder.GetDescriptor(), implementors); - init.Init(new EntityHierarchyStruct(group)); + init.Init(new EntityHierarchyComponent(group)); return startIndex; } + + /// + /// Works like CreateAll but only builds entities with holders that have the same group specfied + /// + /// + /// 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 + { + var holders = contextHolder.GetComponentsInChildren(true); + + foreach (var holder in holders) + { + if (string.IsNullOrEmpty(holder.groupName) == false) + { + var realGroup = ExclusiveGroup.Search(holder.groupName); + if (realGroup != exclusiveGroup) + continue; + } + else + { + continue; + } + + var implementors = holder.GetComponents(); + + startId = InternalBuildAll(startId, holder, factory, exclusiveGroup, implementors, null); + } + + return startId; + } } } #endif diff --git a/Svelto.ECS/Extensions/Unity/UnityEntitySubmissionScheduler.cs b/Svelto.ECS/Extensions/Unity/UnityEntitySubmissionScheduler.cs index 4f4e120..c15f997 100644 --- a/Svelto.ECS/Extensions/Unity/UnityEntitySubmissionScheduler.cs +++ b/Svelto.ECS/Extensions/Unity/UnityEntitySubmissionScheduler.cs @@ -1,6 +1,5 @@ #if UNITY_5 || UNITY_5_3_OR_NEWER using Object = UnityEngine.Object; -using System; using System.Collections; using UnityEngine; @@ -8,7 +7,7 @@ namespace Svelto.ECS.Schedulers.Unity { //The EntitySubmissionScheduler has been introduced to make the entity views submission logic platform independent //You can customize the scheduler if you wish - public class UnityEntitySubmissionScheduler : IEntitySubmissionScheduler + public class UnityEntitiesSubmissionScheduler : IEntitiesSubmissionScheduler { class Scheduler : MonoBehaviour { @@ -33,16 +32,19 @@ namespace Svelto.ECS.Schedulers.Unity } readonly WaitForEndOfFrame _wait = new WaitForEndOfFrame(); - readonly IEnumerator _coroutine; + readonly IEnumerator _coroutine; public EnginesRoot.EntitiesSubmitter onTick; } - public UnityEntitySubmissionScheduler(string name = "ECSScheduler") { _name = name; } + public UnityEntitiesSubmissionScheduler(string name = "ECSScheduler") { _name = name; } public void Dispose() { - Object.Destroy(_scheduler.gameObject); + if (_scheduler != null && _scheduler.gameObject != null) + { + Object.Destroy(_scheduler.gameObject); + } } public EnginesRoot.EntitiesSubmitter onTick @@ -59,7 +61,7 @@ namespace Svelto.ECS.Schedulers.Unity } } - Scheduler _scheduler; + Scheduler _scheduler; readonly string _name; } } diff --git a/Svelto.ECS/FastGroup.cs b/Svelto.ECS/FastGroup.cs new file mode 100644 index 0000000..836bf35 --- /dev/null +++ b/Svelto.ECS/FastGroup.cs @@ -0,0 +1,27 @@ +#if later +namespace Svelto.ECS +{ + public class FastGroup + { + internal static uint entitiesCount; + + public FastGroup() + { + _group = ExclusiveGroupStruct.Generate(1); + } + + public static implicit operator ExclusiveGroupStruct(FastGroup group) + { + return group._group; + } + + public static explicit operator uint(FastGroup group) + { + return group._group; + } + + readonly ExclusiveGroupStruct _group; + public uint value => _group; + } +} +#endif \ No newline at end of file diff --git a/Svelto.ECS/GenericEntityDescriptor.cs b/Svelto.ECS/GenericEntityDescriptor.cs index a8eb7a3..6d95ec3 100644 --- a/Svelto.ECS/GenericEntityDescriptor.cs +++ b/Svelto.ECS/GenericEntityDescriptor.cs @@ -1,104 +1,104 @@ namespace Svelto.ECS { - public abstract class GenericEntityDescriptor : IEntityDescriptor where T : struct, IEntityStruct + public abstract class GenericEntityDescriptor : IEntityDescriptor where T : struct, IEntityComponent { - static readonly IEntityBuilder[] _entityBuilders; - static GenericEntityDescriptor() { _entityBuilders = new IEntityBuilder[] {new EntityBuilder()}; } + static readonly IComponentBuilder[] _entityBuilders; + static GenericEntityDescriptor() { _entityBuilders = new IComponentBuilder[] {new ComponentBuilder()}; } - public IEntityBuilder[] entitiesToBuild => _entityBuilders; + public IComponentBuilder[] componentsToBuild => _entityBuilders; } public abstract class GenericEntityDescriptor : IEntityDescriptor - where T : struct, IEntityStruct where U : struct, IEntityStruct + where T : struct, IEntityComponent where U : struct, IEntityComponent { - static readonly IEntityBuilder[] _entityBuilders; + static readonly IComponentBuilder[] _entityBuilders; static GenericEntityDescriptor() { - _entityBuilders = new IEntityBuilder[] {new EntityBuilder(), new EntityBuilder()}; + _entityBuilders = new IComponentBuilder[] {new ComponentBuilder(), new ComponentBuilder()}; } - public IEntityBuilder[] entitiesToBuild => _entityBuilders; + public IComponentBuilder[] componentsToBuild => _entityBuilders; } public abstract class GenericEntityDescriptor : IEntityDescriptor - where T : struct, IEntityStruct where U : struct, IEntityStruct where V : struct, IEntityStruct + where T : struct, IEntityComponent where U : struct, IEntityComponent where V : struct, IEntityComponent { - static readonly IEntityBuilder[] _entityBuilders; + static readonly IComponentBuilder[] _entityBuilders; static GenericEntityDescriptor() { - _entityBuilders = new IEntityBuilder[] + _entityBuilders = new IComponentBuilder[] { - new EntityBuilder(), - new EntityBuilder(), - new EntityBuilder() + new ComponentBuilder(), + new ComponentBuilder(), + new ComponentBuilder() }; } - public IEntityBuilder[] entitiesToBuild => _entityBuilders; + public IComponentBuilder[] componentsToBuild => _entityBuilders; } public abstract class GenericEntityDescriptor : IEntityDescriptor - where T : struct, IEntityStruct where U : struct, IEntityStruct where V : struct, IEntityStruct - where W : struct, IEntityStruct + where T : struct, IEntityComponent where U : struct, IEntityComponent where V : struct, IEntityComponent + where W : struct, IEntityComponent { - static readonly IEntityBuilder[] _entityBuilders; + static readonly IComponentBuilder[] _entityBuilders; static GenericEntityDescriptor() { - _entityBuilders = new IEntityBuilder[] + _entityBuilders = new IComponentBuilder[] { - new EntityBuilder(), - new EntityBuilder(), - new EntityBuilder(), - new EntityBuilder() + new ComponentBuilder(), + new ComponentBuilder(), + new ComponentBuilder(), + new ComponentBuilder() }; } - public IEntityBuilder[] entitiesToBuild => _entityBuilders; + public IComponentBuilder[] componentsToBuild => _entityBuilders; } public abstract class GenericEntityDescriptor : IEntityDescriptor - where T : struct, IEntityStruct where U : struct, IEntityStruct where V : struct, IEntityStruct - where W : struct, IEntityStruct where X : struct, IEntityStruct + where T : struct, IEntityComponent where U : struct, IEntityComponent where V : struct, IEntityComponent + where W : struct, IEntityComponent where X : struct, IEntityComponent { - static readonly IEntityBuilder[] _entityBuilders; + static readonly IComponentBuilder[] _entityBuilders; static GenericEntityDescriptor() { - _entityBuilders = new IEntityBuilder[] + _entityBuilders = new IComponentBuilder[] { - new EntityBuilder(), - new EntityBuilder(), - new EntityBuilder(), - new EntityBuilder(), - new EntityBuilder() + new ComponentBuilder(), + new ComponentBuilder(), + new ComponentBuilder(), + new ComponentBuilder(), + new ComponentBuilder() }; } - public IEntityBuilder[] entitiesToBuild => _entityBuilders; + public IComponentBuilder[] componentsToBuild => _entityBuilders; } public abstract class GenericEntityDescriptor : IEntityDescriptor - where T : struct, IEntityStruct where U : struct, IEntityStruct where V : struct, IEntityStruct - where W : struct, IEntityStruct where X : struct, IEntityStruct where Y : struct, IEntityStruct + where T : struct, IEntityComponent where U : struct, IEntityComponent where V : struct, IEntityComponent + where W : struct, IEntityComponent where X : struct, IEntityComponent where Y : struct, IEntityComponent { - static readonly IEntityBuilder[] _entityBuilders; + static readonly IComponentBuilder[] _entityBuilders; static GenericEntityDescriptor() { - _entityBuilders = new IEntityBuilder[] + _entityBuilders = new IComponentBuilder[] { - new EntityBuilder(), - new EntityBuilder(), - new EntityBuilder(), - new EntityBuilder(), - new EntityBuilder(), - new EntityBuilder() + new ComponentBuilder(), + new ComponentBuilder(), + new ComponentBuilder(), + new ComponentBuilder(), + new ComponentBuilder(), + new ComponentBuilder() }; } - public IEntityBuilder[] entitiesToBuild => _entityBuilders; + public IComponentBuilder[] componentsToBuild => _entityBuilders; } } \ No newline at end of file diff --git a/Svelto.ECS/GenericentityStreamConsumerFactory.cs b/Svelto.ECS/GenericentityStreamConsumerFactory.cs index 4fc0269..6aee53d 100644 --- a/Svelto.ECS/GenericentityStreamConsumerFactory.cs +++ b/Svelto.ECS/GenericentityStreamConsumerFactory.cs @@ -9,12 +9,14 @@ namespace Svelto.ECS _enginesRoot = new WeakReference(weakReference); } - public Consumer GenerateConsumer(string name, uint capacity) where T : unmanaged, IEntityStruct + public Consumer GenerateConsumer(string name, uint capacity) + where T : unmanaged, IEntityComponent { return _enginesRoot.Target.GenerateConsumer(name, capacity); } - public Consumer GenerateConsumer(ExclusiveGroup group, string name, uint capacity) where T : unmanaged, IEntityStruct + public Consumer GenerateConsumer(ExclusiveGroup group, string name, uint capacity) + where T : unmanaged, IEntityComponent { return _enginesRoot.Target.GenerateConsumer(group, name, capacity); } @@ -23,11 +25,12 @@ namespace Svelto.ECS //engines of other enginesRoot readonly WeakReference _enginesRoot; } - + public interface IEntityStreamConsumerFactory { - Consumer GenerateConsumer(string name, uint capacity) where T : unmanaged, IEntityStruct; - Consumer GenerateConsumer(ExclusiveGroup group, string name, uint capacity) - where T : unmanaged, IEntityStruct; + Consumer GenerateConsumer(string name, uint capacity) where T : unmanaged, IEntityComponent; + + Consumer GenerateConsumer(ExclusiveGroup group, string name, uint capacity) + where T : unmanaged, IEntityComponent; } } \ No newline at end of file diff --git a/Svelto.ECS/GlobalTypeID.cs b/Svelto.ECS/GlobalTypeID.cs new file mode 100644 index 0000000..7a2aa7f --- /dev/null +++ b/Svelto.ECS/GlobalTypeID.cs @@ -0,0 +1,60 @@ +#if UNITY_ECS +using System.Threading; +using Svelto.DataStructures; +using Svelto.ECS.DataStructures; +using Unity.Burst; + +namespace Svelto.ECS +{ + public class GlobalTypeID + { + internal static uint NextID() + { + return (uint) (Interlocked.Increment(ref value) - 1); + } + + static GlobalTypeID() + { + value = 0; + } + + static int value; + } + + static class EntityComponentID + { + internal static readonly SharedStatic ID = SharedStatic.GetOrCreate(); + } + + interface IFiller + { + void FillFromByteArray(EntityComponentInitializer init, NativeBag buffer); + } + + class Filler: IFiller where T : struct, IEntityComponent + { + void IFiller.FillFromByteArray(EntityComponentInitializer init, NativeBag buffer) + { + var component = buffer.Dequeue(); + + init.Init(component); + } + } + + static class EntityComponentIDMap + { + static readonly FasterList TYPE_IDS = new FasterList(); + + internal static void Register(IFiller entityBuilder) where T : struct, IEntityComponent + { + var location = EntityComponentID.ID.Data = GlobalTypeID.NextID(); + TYPE_IDS.Add(location, entityBuilder); + } + + internal static IFiller GetTypeFromID(uint typeId) + { + return TYPE_IDS[typeId]; + } + } +} +#endif \ No newline at end of file diff --git a/Svelto.ECS/GroupCompound.cs b/Svelto.ECS/GroupCompound.cs new file mode 100644 index 0000000..d232e96 --- /dev/null +++ b/Svelto.ECS/GroupCompound.cs @@ -0,0 +1,112 @@ +using System; +using Svelto.ECS.Internal; + +namespace Svelto.ECS +{ + public static class GroupCompound + where G1 : GroupTag where G2 : GroupTag where G3 : GroupTag + { + public static readonly ExclusiveGroupStruct[] Groups; + + static GroupCompound() + { + if ((Groups = GroupCompound.Groups) == null) + if ((Groups = GroupCompound.Groups) == null) + if ((Groups = GroupCompound.Groups) == null) + if ((Groups = GroupCompound.Groups) == null) + if ((Groups = GroupCompound.Groups) == null) + { + Groups = new ExclusiveGroupStruct[1]; + + var Group = new ExclusiveGroup(); + Groups[0] = Group; + + Console.LogDebug("".FastConcat(typeof(G1).ToString().FastConcat("-", typeof(G2).ToString(), "-").FastConcat(typeof(G3).ToString(), "- Initialized ", Groups[0].ToString()), "")); + + GroupCompound.Add(Group); // and must share the same array + GroupCompound.Add(Group); + GroupCompound.Add(Group); + + GroupTag.Add(Group); + GroupTag.Add(Group); + GroupTag.Add(Group); + } + else + Console.LogDebug(typeof(G1).ToString().FastConcat("-", typeof(G2).ToString(), "-").FastConcat(typeof(G3).ToString(), "-", Groups[0].ToString())); + } + + public static ExclusiveGroupStruct BuildGroup => new ExclusiveGroupStruct(Groups[0]); + } + + public static class GroupCompound where G1 : GroupTag where G2 : GroupTag + { + public static ExclusiveGroupStruct[] Groups; + + static GroupCompound() + { + Groups = GroupCompound.Groups; + + if (Groups == null) + { + Groups = new ExclusiveGroupStruct[1]; + var Group = new ExclusiveGroup(); + Groups[0] = Group; + + Console.LogDebug("".FastConcat(typeof(G1).ToString().FastConcat("-", typeof(G2).ToString(), "- initialized ", Groups[0].ToString()), "")); + + //every abstract group preemptively adds this group, it may or may not be empty in future + GroupTag.Add(Group); + GroupTag.Add(Group); + } + else + Console.LogDebug(typeof(G1).ToString().FastConcat("-", typeof(G2).ToString(), "-", Groups[0].ToString())); + } + + public static ExclusiveGroupStruct BuildGroup => new ExclusiveGroupStruct(Groups[0]); + + public static void Add(ExclusiveGroupStruct @group) + { + for (int i = 0; i < Groups.Length; ++i) + if (Groups[i] == group) + throw new Exception("temporary must be transformed in unit test"); + + Array.Resize(ref Groups, Groups.Length + 1); + + Groups[Groups.Length - 1] = group; + + GroupCompound.Groups = Groups; + + Console.LogDebug(typeof(G1).ToString().FastConcat("-", typeof(G2).ToString(), "- Add ", group.ToString())); + } + } + + //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 + { + public static ExclusiveGroupStruct[] Groups = new ExclusiveGroupStruct[1]; + + static GroupTag() + { + Groups[0] = new ExclusiveGroup(); + + Console.LogDebug(typeof(T).ToString() + "-" + Groups[0].ToString()); + } + + //Each time a new combination of group tags is found a new group is added. + internal static void Add(ExclusiveGroup @group) + { + for (int i = 0; i < Groups.Length; ++i) + if (Groups[i] == group) + throw new Exception("temporary must be transformed in unit test"); + + Array.Resize(ref Groups, Groups.Length + 1); + + Groups[Groups.Length - 1] = group; + + Console.LogDebug(typeof(T).ToString().FastConcat("- Add ", group.ToString())); + } + } +} diff --git a/Svelto.ECS/Hybrid/IEntityViewComponent.cs b/Svelto.ECS/Hybrid/IEntityViewComponent.cs new file mode 100644 index 0000000..179e9fd --- /dev/null +++ b/Svelto.ECS/Hybrid/IEntityViewComponent.cs @@ -0,0 +1,6 @@ +namespace Svelto.ECS.Hybrid +{ + public interface IEntityViewComponent:IEntityComponent, INeedEGID + {} +} + diff --git a/Svelto.ECS/Hybrid/IEntityViewStruct.cs b/Svelto.ECS/Hybrid/IEntityViewStruct.cs deleted file mode 100644 index b351a50..0000000 --- a/Svelto.ECS/Hybrid/IEntityViewStruct.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Svelto.ECS.Hybrid -{ - public interface IEntityViewStruct:IEntityStruct, INeedEGID - {} -} - diff --git a/Svelto.ECS/Hybrid/IImplementor.cs b/Svelto.ECS/Hybrid/IImplementor.cs index 6d58780..56ba68d 100644 --- a/Svelto.ECS/Hybrid/IImplementor.cs +++ b/Svelto.ECS/Hybrid/IImplementor.cs @@ -1,4 +1,4 @@ -namespace Svelto.ECS.Unity +namespace Svelto.ECS.Hybrid { public interface IImplementor { diff --git a/Svelto.ECS/IEntityBuilder.cs b/Svelto.ECS/IComponentBuilder.cs similarity index 80% rename from Svelto.ECS/IEntityBuilder.cs rename to Svelto.ECS/IComponentBuilder.cs index da7b527..0b532c5 100644 --- a/Svelto.ECS/IEntityBuilder.cs +++ b/Svelto.ECS/IComponentBuilder.cs @@ -4,12 +4,12 @@ using Svelto.ECS.Internal; namespace Svelto.ECS { - public interface IEntityBuilder + public interface IComponentBuilder { void BuildEntityAndAddToList(ref ITypeSafeDictionary dictionary, EGID egid, IEnumerable implementors); ITypeSafeDictionary Preallocate(ref ITypeSafeDictionary dictionary, uint size); - Type GetEntityType(); + Type GetEntityComponentType(); } } \ No newline at end of file diff --git a/Svelto.ECS/IEntitiesDB.cs b/Svelto.ECS/IEntitiesDB.cs index b2734a2..70cdb47 100644 --- a/Svelto.ECS/IEntitiesDB.cs +++ b/Svelto.ECS/IEntitiesDB.cs @@ -1,193 +1,7 @@ -using System; - namespace Svelto.ECS { - public delegate void ExecuteOnAllEntitiesAction(T[] entities, ExclusiveGroup.ExclusiveGroupStruct group, - uint count, IEntitiesDB db, ref W value); - - public interface IEntitiesDB - { - /////////////////////////////////////////////////// - // Query entities - // ECS systems are meant to work on a set of Entities. These methods allow to iterate over entity - // structs inside a given group or an array of groups - /////////////////////////////////////////////////// - - /// - /// Fast and raw return of entities buffer. - /// - /// - /// - /// - /// - T[] QueryEntities(ExclusiveGroup.ExclusiveGroupStruct groupStruct, out uint count) - where T : struct, IEntityStruct; - (T1[], T2[]) QueryEntities(ExclusiveGroup.ExclusiveGroupStruct groupStruct, out uint count) - where T1 : struct, IEntityStruct where T2 : struct, IEntityStruct; - (T1[], T2[], T3[]) QueryEntities(ExclusiveGroup.ExclusiveGroupStruct groupStruct, out uint count) - where T1 : struct, IEntityStruct where T2 : struct, IEntityStruct where T3 : struct, IEntityStruct; - - /// - /// return entities that can be iterated through the EntityCollection iterator - /// - /// - /// - /// - EntityCollection QueryEntities(ExclusiveGroup.ExclusiveGroupStruct groupStruct) - where T : struct, IEntityStruct; - EntityCollection QueryEntities(ExclusiveGroup.ExclusiveGroupStruct groupStruct) - where T1 : struct, IEntityStruct where T2 : struct, IEntityStruct; - EntityCollection QueryEntities(ExclusiveGroup.ExclusiveGroupStruct groupStruct) - where T1 : struct, IEntityStruct - where T2 : struct, IEntityStruct - where T3 : struct, IEntityStruct; - - /// - /// return entities found in multiple groups, that can be iterated through the EntityCollection iterator - /// This method is useful to write abstracted engines - /// - /// - /// - EntityCollections QueryEntities(ExclusiveGroup[] groups) where T : struct, IEntityStruct; - EntityCollections QueryEntities(ExclusiveGroup[] groups) - where T1 : struct, IEntityStruct where T2 : struct, IEntityStruct; - - /////////////////////////////////////////////////// - // Query entities regardless the group - // these methods are necessary to create abstracted engines. Engines that can iterate over entities regardless - // the group - /////////////////////////////////////////////////// - - /// - /// Execute an action on ALL the entities regardless the group. This function doesn't guarantee cache - /// friendliness even if just EntityStructs are used. Safety checks are in place, - /// - /// - /// - void ExecuteOnAllEntities(Action action) - where T : struct, IEntityStruct; - - /// - /// same as above, but can pass some external data to avoid allocations - /// - /// - /// - /// - /// - void ExecuteOnAllEntities(ref W value, ExecuteOnAllEntitiesAction action) - where T : struct, IEntityStruct; - - void ExecuteOnAllEntities(W value, Action action) - where T : struct, IEntityStruct; - - /////////////////////////////////////////////////// - // Query single entities - // ECS systems are meant to work on a set of Entities. Working on a single entity is sometime necessary, hence - // the following methods - // However Because of the double hashing required to identify a specific entity, these function are slower than - // other query methods when used multiple times! - /////////////////////////////////////////////////// - - /// - /// QueryUniqueEntity is a contract method that explicitly declare the intention to have just on entity in a - /// specific group, usually used for GUI elements - /// - /// - /// - /// - ref T QueryUniqueEntity(ExclusiveGroup.ExclusiveGroupStruct group) where T : struct, IEntityStruct; - - /// - /// return a specific entity by reference. - /// - /// - /// - /// - ref T QueryEntity(EGID entityGid) where T : struct, IEntityStruct; - ref T QueryEntity(uint id, ExclusiveGroup.ExclusiveGroupStruct group) where T : struct, IEntityStruct; - - /// - /// - ///QueryEntitiesAndIndex is useful to optimize cases when multiple entity structs from the same entity must - /// be queried. This is the use case: - /// - ///ref var ghostPosition = ref entitiesDB.QueryEntitiesAndIndex - /// (MockupRenderingGroups.GhostCubeID, out var index)[index]; - ///ref var ghostScaling = ref entitiesDB.QueryEntities - /// (MockupRenderingGroups.GhostCubeID.groupID, out _)[index]; - ///ref var ghostRotation = ref entitiesDB.QueryEntities - /// (MockupRenderingGroups.GhostCubeID.groupID, out _)[index]; - ///ref var ghostResource = ref entitiesDB.QueryEntities - /// (MockupRenderingGroups.GhostCubeID.groupID, out _)[index]; - /// - /// - /// - /// - /// - /// - T[] QueryEntitiesAndIndex(EGID entityGid, out uint index) where T : struct, IEntityStruct; - T[] QueryEntitiesAndIndex(uint id, ExclusiveGroup.ExclusiveGroupStruct group, out uint index) - where T : struct, IEntityStruct; - - /// - /// Like QueryEntitiesAndIndex and only way to get an index only if exists - /// - /// - /// - /// - /// - bool TryQueryEntitiesAndIndex(EGID entityGid, out uint index, out T[] array) where T : struct, IEntityStruct; - bool TryQueryEntitiesAndIndex - (uint id, ExclusiveGroup.ExclusiveGroupStruct group, out uint index, out T[] array) - where T : struct, IEntityStruct; - - /// - /// this method returns a mapped version of the entity array so that is possible to work on multiple entities - /// inside the group through their EGID. This version skip a level of indirection so it's a bit faster than - /// using QueryEntity multiple times (with different EGIDs). - /// However mapping can be slow so it must be used for not performance critical paths - /// - /// - /// - EGIDMapper QueryMappedEntities(ExclusiveGroup.ExclusiveGroupStruct groupStructId) - where T : struct, IEntityStruct; - bool TryQueryMappedEntities(ExclusiveGroup.ExclusiveGroupStruct groupStructId, out EGIDMapper mapper) - where T : struct, IEntityStruct; - - /////////////////////////////////////////////////// - // Utility methods - /////////////////////////////////////////////////// - - /// - /// check if a specific entity exists - /// - /// - /// - /// - bool Exists(EGID egid) where T : struct, IEntityStruct; - bool Exists(uint id, ExclusiveGroup.ExclusiveGroupStruct group) where T : struct, IEntityStruct; - bool Exists(ExclusiveGroup.ExclusiveGroupStruct gid); - - /// - /// know if there is any entity struct in a specific group - /// - /// - /// - /// - bool HasAny(ExclusiveGroup.ExclusiveGroupStruct groupStruct) where T : struct, IEntityStruct; - - /// - /// Count the number of entity structs in a specific group - /// - /// - /// - /// - uint Count(ExclusiveGroup.ExclusiveGroupStruct groupStruct) where T : struct, IEntityStruct; - - /// - /// - /// - /// - void PublishEntityChange(EGID egid) where T : unmanaged, IEntityStruct; - } + public delegate void ExecuteOnAllEntitiesAction(T[] prefabStruct, ExclusiveGroupStruct group, + uint count, EntitiesDB db, ref W instances); + public delegate void ExecuteOnAllEntitiesAction(T[] entities, ExclusiveGroupStruct group, + uint count, EntitiesDB db); } diff --git a/Svelto.ECS/IEntityComponent.cs b/Svelto.ECS/IEntityComponent.cs new file mode 100644 index 0000000..1b3bd40 --- /dev/null +++ b/Svelto.ECS/IEntityComponent.cs @@ -0,0 +1,16 @@ +namespace Svelto.ECS +{ + ///EntityComponent MUST implement IEntityComponent + public interface IEntityComponent + { + } + + /// + /// use INeedEGID on an IEntityComponent only if you need the EGID + /// + public interface INeedEGID + { + //The set is used only for the framework, but it must stay there + EGID ID { get; set; } + } +} \ No newline at end of file diff --git a/Svelto.ECS/IEntityFactory.cs b/Svelto.ECS/IEntityFactory.cs index 14b7e89..a281761 100644 --- a/Svelto.ECS/IEntityFactory.cs +++ b/Svelto.ECS/IEntityFactory.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using Svelto.ECS.Internal; namespace Svelto.ECS { @@ -25,29 +26,29 @@ namespace Svelto.ECS /// /// /// - void PreallocateEntitySpace(ExclusiveGroup.ExclusiveGroupStruct groupStructId, uint size) + void PreallocateEntitySpace(ExclusiveGroupStruct groupStructId, uint size) where T : IEntityDescriptor, new(); /// /// The EntityDescriptor doesn't need to be ever instantiated. It just describes the Entity - /// itself in terms of EntityViews to build. The Implementors are passed to fill the - /// references of the EntityViews components. Please read the articles on my blog + /// 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 views /// are grouped by groupID to be more efficiently processed inside engines and - /// improve cache locality. Either class entityViews and struct entityViews can be + /// improve cache locality. Either class entityComponents and struct entityComponents can be /// grouped. /// /// /// /// /// - EntityStructInitializer BuildEntity(uint entityID, ExclusiveGroup.ExclusiveGroupStruct groupStructId, - IEnumerable implementors = null) + EntityComponentInitializer BuildEntity(uint entityID, ExclusiveGroupStruct groupStructId, + IEnumerable implementors = null) where T : IEntityDescriptor, new(); - EntityStructInitializer BuildEntity(EGID egid, IEnumerable implementors = null) - where T:IEntityDescriptor, new(); + EntityComponentInitializer BuildEntity(EGID egid, IEnumerable implementors = null) + where T : IEntityDescriptor, new(); /// /// When the type of the entity is not known (this is a special case!) an EntityDescriptorInfo @@ -57,10 +58,15 @@ namespace Svelto.ECS /// /// /// - EntityStructInitializer BuildEntity(uint entityID, ExclusiveGroup.ExclusiveGroupStruct groupStructId, - T descriptorEntity, IEnumerable implementors = null) where T : IEntityDescriptor; + EntityComponentInitializer BuildEntity(uint entityID, ExclusiveGroupStruct groupStructId, + T descriptorEntity, IEnumerable implementors = null) + where T : IEntityDescriptor; - EntityStructInitializer BuildEntity(EGID egid, T entityDescriptor, IEnumerable implementors = null) + EntityComponentInitializer BuildEntity(EGID egid, T entityDescriptor, IEnumerable implementors = null) where T : IEntityDescriptor; + +#if UNITY_ECS + NativeEntityFactory ToNative(Unity.Collections.Allocator allocator) where T : IEntityDescriptor, new(); +#endif } -} +} \ No newline at end of file diff --git a/Svelto.ECS/IEntityFunctions.cs b/Svelto.ECS/IEntityFunctions.cs index 9d04e0d..9e8b0e1 100644 --- a/Svelto.ECS/IEntityFunctions.cs +++ b/Svelto.ECS/IEntityFunctions.cs @@ -5,16 +5,31 @@ 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, ExclusiveGroup.ExclusiveGroupStruct groupID) where T : IEntityDescriptor, new(); + void RemoveEntity(uint entityID, ExclusiveGroupStruct groupID) where T : IEntityDescriptor, new(); void RemoveEntity(EGID entityegid) where T : IEntityDescriptor, new(); - void RemoveGroupAndEntities(ExclusiveGroup.ExclusiveGroupStruct groupID); - - void SwapEntityGroup(uint entityID, ExclusiveGroup.ExclusiveGroupStruct fromGroupID, ExclusiveGroup.ExclusiveGroupStruct toGroupID) where T : IEntityDescriptor, new(); - void SwapEntityGroup(EGID fromID, ExclusiveGroup.ExclusiveGroupStruct toGroupID) where T : IEntityDescriptor, new(); - void SwapEntityGroup(EGID fromID, ExclusiveGroup.ExclusiveGroupStruct toGroupID, ExclusiveGroup.ExclusiveGroupStruct mustBeFromGroup) where T : IEntityDescriptor, new(); - + void RemoveAllEntities(ExclusiveGroupStruct group) where T : IEntityDescriptor, new(); + void RemoveAllEntities() where T : IEntityDescriptor, new(); + + void RemoveGroupAndEntities(ExclusiveGroupStruct groupID); + + void SwapEntitiesInGroup(ExclusiveGroupStruct fromGroupID, ExclusiveGroupStruct toGroupID); + + void SwapEntityGroup(uint entityID, ExclusiveGroupStruct fromGroupID, ExclusiveGroupStruct toGroupID) + where T : IEntityDescriptor, new(); + + void SwapEntityGroup(EGID fromID, ExclusiveGroupStruct toGroupID) where T : IEntityDescriptor, new(); + + void SwapEntityGroup(EGID fromID, ExclusiveGroupStruct toGroupID, ExclusiveGroupStruct mustBeFromGroup) + where T : IEntityDescriptor, new(); + void SwapEntityGroup(EGID fromID, EGID toId) where T : IEntityDescriptor, new(); - void SwapEntityGroup(EGID fromID, EGID toId, ExclusiveGroup.ExclusiveGroupStruct mustBeFromGroup) where T : IEntityDescriptor, new(); + + void SwapEntityGroup(EGID fromID, EGID toId, ExclusiveGroupStruct mustBeFromGroup) + where T : IEntityDescriptor, new(); +#if UNITY_ECS + NativeEntityRemove ToNativeRemove() where T : IEntityDescriptor, new(); + NativeEntitySwap ToNativeSwap() where T : IEntityDescriptor, new(); +#endif } } \ No newline at end of file diff --git a/Svelto.ECS/IEntityStruct.cs b/Svelto.ECS/IEntityStruct.cs deleted file mode 100644 index 379b4b4..0000000 --- a/Svelto.ECS/IEntityStruct.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace Svelto.ECS -{ - ///EntityStruct MUST implement IEntityStruct - public interface IEntityStruct - { - } - - /// - /// use INeedEGID on an IEntityStruct only if you need the EGID - /// - public interface INeedEGID - { - EGID ID { get; set; } - } -} \ No newline at end of file diff --git a/Svelto.ECS/IQueryingEntitiesEngine.cs b/Svelto.ECS/IQueryingEntitiesEngine.cs index 57eae65..326e0a0 100644 --- a/Svelto.ECS/IQueryingEntitiesEngine.cs +++ b/Svelto.ECS/IQueryingEntitiesEngine.cs @@ -2,7 +2,7 @@ namespace Svelto.ECS { public interface IQueryingEntitiesEngine : IEngine { - IEntitiesDB entitiesDB { set; } + EntitiesDB entitiesDB { set; } void Ready(); } diff --git a/Svelto.ECS/IReactOnAddAndRemove.cs b/Svelto.ECS/IReactOnAddAndRemove.cs index 0e35eb7..f3e5375 100644 --- a/Svelto.ECS/IReactOnAddAndRemove.cs +++ b/Svelto.ECS/IReactOnAddAndRemove.cs @@ -2,9 +2,9 @@ using Svelto.ECS.Internal; namespace Svelto.ECS { - public interface IReactOnAddAndRemove : IReactOnAddAndRemove where T : IEntityStruct + public interface IReactOnAddAndRemove : IReactOnAddAndRemove where T : IEntityComponent { - void Add(ref T entityView, EGID egid); - void Remove(ref T entityView, EGID egid); + void Add(ref T entityComponent, EGID egid); + void Remove(ref T entityComponent, EGID egid); } } \ No newline at end of file diff --git a/Svelto.ECS/IReactOnSwap.cs b/Svelto.ECS/IReactOnSwap.cs index e9d0f0d..85210c1 100644 --- a/Svelto.ECS/IReactOnSwap.cs +++ b/Svelto.ECS/IReactOnSwap.cs @@ -2,11 +2,11 @@ using Svelto.ECS.Internal; namespace Svelto.ECS { - public interface IReactOnSwap : IReactOnSwap where T : IEntityStruct + public interface IReactOnSwap : IReactOnSwap where T : IEntityComponent { - void MovedTo(ref T entityView, ExclusiveGroup.ExclusiveGroupStruct previousGroup, EGID egid); + void MovedTo(ref T entityComponent, ExclusiveGroupStruct previousGroup, EGID egid); #if SEEMS_UNNECESSARY - void MovedFrom(ref T entityView, EGID egid); + void MovedFrom(ref T entityComponent, EGID egid); #endif } } \ No newline at end of file diff --git a/Svelto.ECS/LICENSE b/Svelto.ECS/LICENSE new file mode 100644 index 0000000..4c02fa4 --- /dev/null +++ b/Svelto.ECS/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2015 Sebastiano Mandalà + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/Svelto.ECS/NativeEGIDMapper.cs b/Svelto.ECS/NativeEGIDMapper.cs new file mode 100644 index 0000000..fdcab62 --- /dev/null +++ b/Svelto.ECS/NativeEGIDMapper.cs @@ -0,0 +1,79 @@ +using System; +using System.Runtime.CompilerServices; +using Svelto.DataStructures; + +namespace Svelto.ECS +{ + public readonly struct NativeEGIDMapper:IDisposable where T : unmanaged, IEntityComponent + { + readonly NativeFasterDictionaryStruct map; + public ExclusiveGroupStruct groupID { get; } + + public NativeEGIDMapper(ExclusiveGroupStruct groupStructId, NativeFasterDictionaryStruct toNative):this() + { + groupID = groupStructId; + map = toNative; + } + + public uint Count => map.Count; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ref T Entity(uint entityID) + { + unsafe + { +#if DEBUG && !PROFILE_SVELTO + if (map.TryFindIndex(entityID, out var findIndex) == false) + throw new Exception("Entity not found in this group ".FastConcat(typeof(T).ToString())); +#else + map.TryFindIndex(entityID, out var findIndex); +#endif + return ref map.unsafeValues[(int) findIndex]; + } + } + + public bool TryGetEntity(uint entityID, out T value) + { + if (map.TryFindIndex(entityID, out var index)) + { + value = map.GetDirectValue(index); + return true; + } + + value = default; + return false; + } + + public unsafe NBGetArrayAndEntityIndex(uint entityID, out uint index) + { + if (map.TryFindIndex(entityID, out index)) + { + return new NB(map.unsafeValues, map.Count); + } + + throw new ECSException("Entity not found"); + } + + public unsafe bool TryGetArrayAndEntityIndex(uint entityID, out uint index, out NB array) + { + if (map.TryFindIndex(entityID, out index)) + { + array = new NB(map.unsafeValues, map.Count); + return true; + } + + array = default; + return false; + } + + public void Dispose() + { + map.Dispose(); + } + + public bool Exists(uint idEntityId) + { + return map.Count > 0 && map.TryFindIndex(idEntityId, out _); + } + } +} \ No newline at end of file diff --git a/Svelto.ECS/README.md b/Svelto.ECS/README.md new file mode 100644 index 0000000..ef18d33 --- /dev/null +++ b/Svelto.ECS/README.md @@ -0,0 +1,50 @@ +Svelto Entity Component System for Unity +===================================== + +**Note: The alpha stage of Svelto 2.0 is almost completed, so if you are here to experiment with it, please use the current alpha branch** + +Real Entity-Component-System for c# and Unity (it can be adapted for other c# platforms too). Enables to write encapsulated, uncoupled, highly efficient, data oriented, cache friendly, multi-threaded, code without pain. + +you can find working examples to learn how to use the framework here: + +https://github.com/sebas77/Svelto-ECS-Example (unity) + +https://github.com/sebas77/Svelto.ECS.Vanilla.Example (.net core and standard) + +I advise to clone the example repositories separately from the framework one, both under the same Unity project Assets folder. + +relative article: + +http://www.sebaslab.com/svelto-ecs-2-0-almost-production-ready/ + +http://www.sebaslab.com/ecs-1-0/ + +If you want to know more about the theory and rationale behind this framework: + +http://www.sebaslab.com/ioc-container-for-unity3d-part-1/ + +http://www.sebaslab.com/ioc-container-for-unity3d-part-2/ + +http://www.sebaslab.com/the-truth-behind-inversion-of-control-part-i-dependency-injection/ + +http://www.sebaslab.com/the-truth-behind-inversion-of-control-part-ii-inversion-of-control/ + +http://www.sebaslab.com/the-truth-behind-inversion-of-control-part-iii-entity-component-systems/ + +http://www.sebaslab.com/the-truth-behind-inversion-of-control-part-iv-dependency-inversion-principle/ + +http://www.sebaslab.com/the-truth-behind-inversion-of-control-part-v-drifting-away-from-ioc-containers/ + +new article on optimizations: + +http://www.sebaslab.com/svelto-ecs-svelto-tasks-to-write-data-oriented-cache-friendly-multi-threaded-code-in-unity/ + +Note: if you ever build something with Svelto.ECS that you can share with the community, please do and let me know. Other coders need more examples. + +Copyright (c) Sebastiano Mandalà + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/Svelto.ECS/Sequencer.cs b/Svelto.ECS/Sequencer.cs deleted file mode 100644 index da67c6f..0000000 --- a/Svelto.ECS/Sequencer.cs +++ /dev/null @@ -1,132 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; - -namespace Svelto.ECS -{ - public class Steps - { - internal readonly Dictionary _steps; - - public Steps(params Step[] values) - { - _steps = new Dictionary(); - - for (var i = 0; i < values.Length; i++) - _steps.Add(values[i].from, values[i].to); - } - } - - public class To - { - public To(IStep engine) - { - this.engine = engine; - } - - public To(params IStep[] engines) - { - this.engines = engines; - } - - public IStep engine { get; set; } - public IStep[] engines { get; set; } - } - - public class To:To, IEnumerable where C : struct, IConvertible - { - internal readonly Dictionary[]> _tos = new Dictionary[]>(); - - public IEnumerator GetEnumerator() - { - throw new NotImplementedException(); - } - - public void Add(C condition, params IStep[] engine) - { - _tos[condition] = engine; - } - } - - public interface IStep - { - void Step(EGID id); - } - - public interface IStep where C:struct,IConvertible - { - void Step(C condition, EGID id); - } - - public struct Step - { - public IEngine from { get; set; } - public To to { get; set; } - } - - /// - /// The sequencer has just one goal: define a complex sequence of engines to call. The sequence is not - /// just "sequential", but can create branches and loops. - /// With the time, I figure out that the majority of the times this class is useful in the rare cases where - /// order execution of the engine is necessary/ - /// Using branching is even rarer, but still useful sometimes. - /// I used loops only once. - /// There is the chance that this class will become obsolete over the time, as by design engines should - /// never rely on the order of execution - /// Using this class to let engines from different EnginesRoot communicate will also become obsolete, as - /// better solution will be implemented once I have the time - /// Trying to work out how to initialize this structure can be painful. This is by design as this class must - /// be initialized using the following pattern: - /// instancedSequence.SetSequence( - /// new Steps //list of steps - /// ( - /// new Step // first step - /// { - /// from = menuOptionEnablerEngine, //starting engine - /// to = new To //targets - /// { - /// { - /// ItemActionsPanelEnableCondition.Enable, //condition 1 - /// menuPanelEnablerEngine //targets for condition 1 - /// }, - /// { - /// ItemActionsPanelEnableCondition.Disable,//condition 2 - /// menuPanelEnablerEngine //targets for condition 2 - /// } - /// } - /// }) - /// ); - /// - public class Sequencer where S: Sequencer, new() - { - public void SetSequence(Steps steps) - { - _steps = steps; - } - - public void Next(IEngine engine, C condition, EGID id) where C:struct, IConvertible - { - var branch = condition; - var to = (_steps._steps[engine] as To); - - var steps = to._tos[branch]; - - for (var i = 0; i < steps.Length; i++) - steps[i].Step(condition, id); - } - - public void Next(IEngine engine, EGID id) - { - var to = _steps._steps[engine]; - var steps = to.engines; - - if (steps != null && steps.Length > 1) - for (var i = 0; i < steps.Length; i++) - steps[i].Step(id); - else - to.engine.Step(id); - } - - Steps _steps; - } -} \ No newline at end of file diff --git a/Svelto.ECS/Serialization/ComposedComponentSerializer.cs b/Svelto.ECS/Serialization/ComposedComponentSerializer.cs new file mode 100644 index 0000000..c955b16 --- /dev/null +++ b/Svelto.ECS/Serialization/ComposedComponentSerializer.cs @@ -0,0 +1,42 @@ +using System; + +namespace Svelto.ECS.Serialization +{ + public class ComposedComponentSerializer : IComponentSerializer + where T : unmanaged, IEntityComponent where X : class, IComponentSerializer, new() + where Y : class, IComponentSerializer, new() + { + public ComposedComponentSerializer() + { + _serializers = new IComponentSerializer[2]; + _serializers[0] = new X(); + _serializers[1] = new Y(); + } + + public bool Serialize(in T value, ISerializationData serializationData) + { + foreach (IComponentSerializer s in _serializers) + { + serializationData.data.ExpandBy(s.size); + if (s.SerializeSafe(value, serializationData)) + return true; + } + + throw new Exception($"ComposedComponentSerializer for {typeof(T)} did not serialize any data!"); + } + + public bool Deserialize(ref T value, ISerializationData serializationData) + { + foreach (IComponentSerializer s in _serializers) + { + if (s.DeserializeSafe(ref value, serializationData)) + return true; + } + + throw new Exception($"ComposedComponentSerializer for {typeof(T)} did not deserialize any data!"); + } + + public uint size => 0; + IComponentSerializer[] _serializers; + } +} \ No newline at end of file diff --git a/Svelto.ECS/Serialization/ComposedSerializer.cs b/Svelto.ECS/Serialization/ComposedSerializer.cs deleted file mode 100644 index c78bdf3..0000000 --- a/Svelto.ECS/Serialization/ComposedSerializer.cs +++ /dev/null @@ -1,43 +0,0 @@ -using System; - -namespace Svelto.ECS.Serialization -{ - public class ComposedSerializer : ISerializer - where T : unmanaged, IEntityStruct - where X : class, ISerializer, new() - where Y : class, ISerializer, new() - { - public ComposedSerializer() - { - _serializers = new ISerializer[2]; - _serializers[0] = new X(); - _serializers[1] = new Y(); - } - - public bool Serialize(in T value, ISerializationData serializationData) - { - foreach (ISerializer s in _serializers) - { - serializationData.data.ExpandBy(s.size); - if (s.SerializeSafe(value, serializationData)) - return true; - } - - throw new Exception($"ComposedSerializer for {typeof(T)} did not serialize any data!"); - } - - public bool Deserialize(ref T value, ISerializationData serializationData) - { - foreach (ISerializer s in _serializers) - { - if (s.DeserializeSafe(ref value, serializationData)) - return true; - } - - throw new Exception($"ComposedSerializer for {typeof(T)} did not deserialize any data!"); - } - - public uint size => 0; - ISerializer[] _serializers; - } -} \ No newline at end of file diff --git a/Svelto.ECS/Serialization/DefaultSerializer.cs b/Svelto.ECS/Serialization/DefaultSerializer.cs index 38e905f..3c705ac 100644 --- a/Svelto.ECS/Serialization/DefaultSerializer.cs +++ b/Svelto.ECS/Serialization/DefaultSerializer.cs @@ -1,30 +1,28 @@ -using Svelto.Common; - namespace Svelto.ECS.Serialization { - public class DefaultSerializer : ISerializer where T : unmanaged, IEntityStruct + public class DefaultSerializer : IComponentSerializer where T : unmanaged, IEntityComponent { - public static readonly uint SIZEOFT = SerializableEntityBuilder.SIZE; - - public uint size => SIZEOFT; + static readonly uint SIZEOFT = SerializableComponentBuilder.SIZE; static DefaultSerializer() { var _type = typeof(T); foreach (var field in _type.GetFields()) - { - if (field.FieldType.ContainsCustomAttribute(typeof(DoNotSerializeAttribute)) && field.IsPrivate == false) + if (field.FieldType.ContainsCustomAttribute(typeof(DoNotSerializeAttribute)) && + field.IsPrivate == false) throw new ECSException("field cannot be serialised ".FastConcat(_type.FullName)); - } - if (_type.GetProperties().Length > (EntityBuilder.HAS_EGID ? 1 : 0)) + if (_type.GetProperties().Length > (ComponentBuilder.HAS_EGID ? 1 : 0)) throw new ECSException("serializable entity struct must be property less ".FastConcat(_type.FullName)); } + public uint size => SIZEOFT; + public bool Serialize(in T value, ISerializationData serializationData) { - DefaultSerializerUtils.CopyToByteArray(value, serializationData.data.ToArrayFast(), serializationData.dataPos); + DefaultSerializerUtils.CopyToByteArray(value, serializationData.data.ToArrayFast(out _), + serializationData.dataPos); serializationData.dataPos += SIZEOFT; @@ -33,11 +31,12 @@ namespace Svelto.ECS.Serialization public bool Deserialize(ref T value, ISerializationData serializationData) { - value = DefaultSerializerUtils.CopyFromByteArray(serializationData.data.ToArrayFast(), serializationData.dataPos); + value = DefaultSerializerUtils.CopyFromByteArray(serializationData.data.ToArrayFast(out _), + serializationData.dataPos); serializationData.dataPos += SIZEOFT; return true; } } -} +} \ No newline at end of file diff --git a/Svelto.ECS/Serialization/DefaultSerializerUtils.cs b/Svelto.ECS/Serialization/DefaultSerializerUtils.cs index fcc600d..c0f8cdd 100644 --- a/Svelto.ECS/Serialization/DefaultSerializerUtils.cs +++ b/Svelto.ECS/Serialization/DefaultSerializerUtils.cs @@ -3,8 +3,16 @@ using Svelto.ECS; public static class DefaultSerializerUtils { - public static unsafe void CopyToByteArray(in T src, byte[] data, uint offsetDst) where T : unmanaged, IEntityStruct + public static unsafe void CopyToByteArray(in T src, byte[] data, uint offsetDst) where T : unmanaged, IEntityComponent { +#if DEBUG && !PROFILE_SVELTO + if (data.Length - offsetDst < sizeof(T)) + { + throw new IndexOutOfRangeException( + $"Data out of bounds when copying struct {typeof(T).GetType().Name}. data.Length: {data.Length}, offsetDst: {offsetDst}"); + } +#endif + fixed (void* dstPtr = data) { void* dstOffsetPtr; @@ -21,9 +29,17 @@ public static class DefaultSerializerUtils } } - public static unsafe T CopyFromByteArray(byte[] data, uint offsetSrc) where T : unmanaged, IEntityStruct + public static unsafe T CopyFromByteArray(byte[] data, uint offsetSrc) where T : unmanaged, IEntityComponent { - T dst = new T(); + T dst = default; + +#if DEBUG && !PROFILE_SVELTO + if (data.Length - offsetSrc < sizeof(T)) + { + throw new IndexOutOfRangeException( + $"Data out of bounds when copying struct {dst.GetType().Name}. data.Length: {data.Length}, offsetSrc: {offsetSrc}"); + } +#endif void* dstPtr = &dst; fixed (void* srcPtr = data) diff --git a/Svelto.ECS/Serialization/DefaultVersioningFactory.cs b/Svelto.ECS/Serialization/DefaultVersioningFactory.cs index 5c3960d..971aae0 100644 --- a/Svelto.ECS/Serialization/DefaultVersioningFactory.cs +++ b/Svelto.ECS/Serialization/DefaultVersioningFactory.cs @@ -4,25 +4,32 @@ namespace Svelto.ECS.Serialization { public class DefaultVersioningFactory : IDeserializationFactory where T : IEntityDescriptor, new() { - public EntityStructInitializer BuildDeserializedEntity(EGID egid, - ISerializationData serializationData, - ISerializableEntityDescriptor entityDescriptor, - SerializationType serializationType, - IEntitySerialization entitySerialization) + readonly IEntityFactory _factory; + readonly IEnumerable _implementors; + + public DefaultVersioningFactory(IEntityFactory factory) + { + _factory = factory; + } + + public DefaultVersioningFactory(IEntityFactory factory, IEnumerable implementors) + { + _factory = factory; + _implementors = implementors; + } + + public EntityComponentInitializer BuildDeserializedEntity(EGID egid, + ISerializationData serializationData, + ISerializableEntityDescriptor entityDescriptor, + int serializationType, + IEntitySerialization entitySerialization) { var initializer = _factory.BuildEntity(egid, _implementors); - - entitySerialization.DeserializeEntityStructs(serializationData, entityDescriptor, ref initializer, serializationType); + + entitySerialization.DeserializeEntityComponents(serializationData, entityDescriptor, ref initializer, + serializationType); return initializer; } - - public DefaultVersioningFactory(IEntityFactory factory) { _factory = factory; } - public DefaultVersioningFactory(IEntityFactory factory, IEnumerable implementors) { _factory = factory; - _implementors = implementors; - } - - readonly IEntityFactory _factory; - readonly IEnumerable _implementors; } } \ No newline at end of file diff --git a/Svelto.ECS/Serialization/DontSerialize.cs b/Svelto.ECS/Serialization/DontSerialize.cs index f5cba8a..414e16a 100644 --- a/Svelto.ECS/Serialization/DontSerialize.cs +++ b/Svelto.ECS/Serialization/DontSerialize.cs @@ -1,6 +1,6 @@ namespace Svelto.ECS.Serialization { - public class DontSerialize : ISerializer where T : unmanaged, IEntityStruct + public class DontSerialize : IComponentSerializer where T : unmanaged, IEntityComponent { public uint size => 0; diff --git a/Svelto.ECS/Serialization/EnginesRoot.GenericEntitySerialization.cs b/Svelto.ECS/Serialization/EnginesRoot.GenericEntitySerialization.cs index 0ca5c63..afdfc26 100644 --- a/Svelto.ECS/Serialization/EnginesRoot.GenericEntitySerialization.cs +++ b/Svelto.ECS/Serialization/EnginesRoot.GenericEntitySerialization.cs @@ -1,19 +1,8 @@ using System; -using Svelto.Common; -using Svelto.ECS.Internal; using Svelto.ECS.Serialization; namespace Svelto.ECS { - //todo: this should not be at framework level - public enum SerializationType - { - Network, - Storage, - - Length - } - public partial class EnginesRoot { readonly bool _isDeserializationOnly; @@ -21,33 +10,33 @@ namespace Svelto.ECS sealed class EntitySerialization : IEntitySerialization { public void SerializeEntity(EGID egid, ISerializationData serializationData, - SerializationType serializationType) + int serializationType) { var entitiesDb = _enginesRoot._entitiesDB; //needs to retrieve the meta data associated with the entity - ref var serializableEntityStruct = ref entitiesDb.QueryEntity(egid); - uint descriptorHash = serializableEntityStruct.descriptorHash; + ref var serializableEntityComponent = ref entitiesDb.QueryEntity(egid); + uint descriptorHash = serializableEntityComponent.descriptorHash; SerializationDescriptorMap serializationDescriptorMap = _enginesRoot.serializationDescriptorMap; var entityDescriptor = serializationDescriptorMap.GetDescriptorFromHash(descriptorHash); - var entityStructsToSerialise = entityDescriptor.entitiesToSerialize; + var entityComponentsToSerialise = entityDescriptor.entitiesToSerialize; var header = - new SerializableEntityHeader(descriptorHash, egid, (byte) entityStructsToSerialise.Length); + new SerializableEntityHeader(descriptorHash, egid, (byte) entityComponentsToSerialise.Length); header.Copy(serializationData); - for (int index = 0; index < entityStructsToSerialise.Length; index++) + for (int index = 0; index < entityComponentsToSerialise.Length; index++) { - var entityBuilder = entityStructsToSerialise[index]; + var entityBuilder = entityComponentsToSerialise[index]; - serializationData.BeginNextEntityStruct(); - SerializeEntityStruct(egid, entityBuilder, serializationData, serializationType); + serializationData.BeginNextEntityComponent(); + SerializeEntityComponent(egid, entityBuilder, serializationData, serializationType); } } - public EntityStructInitializer DeserializeNewEntity(EGID egid, ISerializationData serializationData, - SerializationType serializationType) + public EntityComponentInitializer DeserializeNewEntity(EGID egid, ISerializationData serializationData, + int serializationType) { //todo: SerializableEntityHeader may be needed to be customizable var serializableEntityHeader = new SerializableEntityHeader(serializationData); @@ -58,14 +47,14 @@ namespace Svelto.ECS var entityDescriptor = serializationDescriptorMap.GetDescriptorFromHash(descriptorHash); //default factory + //todo: we have a default factory, why don't we always register that instead? if (factory == null) { var initializer = _enginesRoot.BuildEntity(egid, - _enginesRoot._isDeserializationOnly - ? entityDescriptor.entitiesToSerialize - : entityDescriptor.entitiesToBuild); + _enginesRoot._isDeserializationOnly ? entityDescriptor.entitiesToSerialize + : entityDescriptor.componentsToBuild); - DeserializeEntityStructs(serializationData, entityDescriptor, ref initializer, serializationType); + DeserializeEntityComponents(serializationData, entityDescriptor, ref initializer, serializationType); return initializer; } @@ -75,7 +64,7 @@ namespace Svelto.ECS this); } - public void DeserializeEntity(ISerializationData serializationData, SerializationType serializationType) + public void DeserializeEntity(ISerializationData serializationData, int serializationType) { var serializableEntityHeader = new SerializableEntityHeader(serializationData); @@ -85,38 +74,57 @@ namespace Svelto.ECS } public void DeserializeEntity(EGID egid, ISerializationData serializationData, - SerializationType serializationType) + int serializationType) { var serializableEntityHeader = new SerializableEntityHeader(serializationData); DeserializeEntityInternal(serializationData, egid, serializableEntityHeader, serializationType); } - public void DeserializeEntityStructs(ISerializationData serializationData, + public void DeserializeEntityComponents(ISerializationData serializationData, ISerializableEntityDescriptor entityDescriptor, - ref EntityStructInitializer initializer, SerializationType serializationType) + ref EntityComponentInitializer initializer, int serializationType) { foreach (var serializableEntityBuilder in entityDescriptor.entitiesToSerialize) { - serializationData.BeginNextEntityStruct(); + serializationData.BeginNextEntityComponent(); serializableEntityBuilder.Deserialize(serializationData, initializer, serializationType); } } + 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) + { + if (serializableEntityBuilder is SerializableComponentBuilder entityBuilder) + { + entityBuilder.Deserialize(serializationData, ref entityComponent, serializationType); + } + break; + } + + serializationData.dataPos = readPos; + return entityComponent; + } + public void DeserializeEntityToSwap(EGID localEgid, EGID toEgid) { EntitiesDB entitiesDb = _enginesRoot._entitiesDB; - ref var serializableEntityStruct = ref entitiesDb.QueryEntity(localEgid); + ref var serializableEntityComponent = ref entitiesDb.QueryEntity(localEgid); SerializationDescriptorMap serializationDescriptorMap = _enginesRoot.serializationDescriptorMap; - uint descriptorHash = serializableEntityStruct.descriptorHash; + uint descriptorHash = serializableEntityComponent.descriptorHash; var entityDescriptor = serializationDescriptorMap.GetDescriptorFromHash(descriptorHash); var entitySubmitOperation = new EntitySubmitOperation( EntitySubmitOperationType.Swap, localEgid, toEgid, - entityDescriptor.entitiesToBuild); + entityDescriptor.componentsToBuild); _enginesRoot.CheckRemoveEntityID(localEgid); _enginesRoot.CheckAddEntityID(toEgid); @@ -127,8 +135,8 @@ namespace Svelto.ECS public void DeserializeEntityToDelete(EGID egid) { EntitiesDB entitiesDB = _enginesRoot._entitiesDB; - ref var serializableEntityStruct = ref entitiesDB.QueryEntity(egid); - uint descriptorHash = serializableEntityStruct.descriptorHash; + ref var serializableEntityComponent = ref entitiesDB.QueryEntity(egid); + uint descriptorHash = serializableEntityComponent.descriptorHash; SerializationDescriptorMap serializationDescriptorMap = _enginesRoot.serializationDescriptorMap; var entityDescriptor = serializationDescriptorMap.GetDescriptorFromHash(descriptorHash); @@ -139,7 +147,7 @@ namespace Svelto.ECS EntitySubmitOperationType.Remove, egid, egid, - entityDescriptor.entitiesToBuild); + entityDescriptor.componentsToBuild); _enginesRoot.QueueEntitySubmitOperation(entitySubmitOperation); } @@ -156,22 +164,22 @@ namespace Svelto.ECS _enginesRoot = enginesRoot; } - void SerializeEntityStruct(EGID entityGID, ISerializableEntityBuilder entityBuilder, - ISerializationData serializationData, SerializationType serializationType) + void SerializeEntityComponent(EGID entityGID, ISerializableComponentBuilder componentBuilder, + ISerializationData serializationData, int serializationType) { uint groupId = entityGID.groupID; - Type entityType = entityBuilder.GetEntityType(); + Type entityType = componentBuilder.GetEntityComponentType(); if (!_enginesRoot._entitiesDB.UnsafeQueryEntityDictionary(groupId, entityType, out var safeDictionary)) { throw new Exception("Entity Serialization failed"); } - entityBuilder.Serialize(entityGID.entityID, safeDictionary, serializationData, serializationType); + componentBuilder.Serialize(entityGID.entityID, safeDictionary, serializationData, serializationType); } void DeserializeEntityInternal(ISerializationData serializationData, EGID egid, - SerializableEntityHeader serializableEntityHeader, SerializationType serializationType) + SerializableEntityHeader serializableEntityHeader, int serializationType) { SerializationDescriptorMap descriptorMap = _enginesRoot.serializationDescriptorMap; var entityDescriptor = descriptorMap.GetDescriptorFromHash(serializableEntityHeader.descriptorHash); @@ -179,9 +187,9 @@ namespace Svelto.ECS foreach (var serializableEntityBuilder in entityDescriptor.entitiesToSerialize) { _enginesRoot._entitiesDB.UnsafeQueryEntityDictionary(egid.groupID, - serializableEntityBuilder.GetEntityType(), out var safeDictionary); + serializableEntityBuilder.GetEntityComponentType(), out var safeDictionary); - serializationData.BeginNextEntityStruct(); + serializationData.BeginNextEntityComponent(); serializableEntityBuilder.Deserialize(egid.entityID, safeDictionary, serializationData, serializationType); } diff --git a/Svelto.ECS/Serialization/EnginesRoot.SerializableEntityHeader.cs b/Svelto.ECS/Serialization/EnginesRoot.SerializableEntityHeader.cs index 32ccc67..c5f5232 100644 --- a/Svelto.ECS/Serialization/EnginesRoot.SerializableEntityHeader.cs +++ b/Svelto.ECS/Serialization/EnginesRoot.SerializableEntityHeader.cs @@ -1,5 +1,3 @@ -using Svelto.DataStructures; - namespace Svelto.ECS { public partial class EnginesRoot @@ -7,13 +5,13 @@ namespace Svelto.ECS struct SerializableEntityHeader { public readonly uint descriptorHash; - public readonly byte entityStructsCount; + public readonly byte entityComponentsCount; const uint SIZE = 4 + 4 + 4 + 1; - internal SerializableEntityHeader(uint descriptorHash_, EGID egid_, byte entityStructsCount_) + internal SerializableEntityHeader(uint descriptorHash_, EGID egid_, byte entityComponentsCount_) { - entityStructsCount = entityStructsCount_; + entityComponentsCount = entityComponentsCount_; descriptorHash = descriptorHash_; egid = egid_; } @@ -38,9 +36,9 @@ namespace Svelto.ECS | serializationData.data[serializationData.dataPos++] << 16 | serializationData.data[serializationData.dataPos++] << 24); - entityStructsCount = serializationData.data[serializationData.dataPos++]; + entityComponentsCount = serializationData.data[serializationData.dataPos++]; - egid = new EGID(entityID, new ExclusiveGroup.ExclusiveGroupStruct(groupID)); + egid = new EGID(entityID, new ExclusiveGroupStruct(groupID)); } internal void Copy(ISerializationData serializationData) @@ -67,7 +65,7 @@ namespace Svelto.ECS serializationData.data[serializationData.dataPos++] = (byte) ((groupID >> 16) & 0xff); serializationData.data[serializationData.dataPos++] = (byte) ((groupID >> 24) & 0xff); - serializationData.data[serializationData.dataPos++] = entityStructsCount; + serializationData.data[serializationData.dataPos++] = entityComponentsCount; } internal readonly EGID egid; //this can't be used safely! diff --git a/Svelto.ECS/Serialization/EntitiesDB.DescriptorMap.cs b/Svelto.ECS/Serialization/EntitiesDB.DescriptorMap.cs index 297f793..2d16d84 100644 --- a/Svelto.ECS/Serialization/EntitiesDB.DescriptorMap.cs +++ b/Svelto.ECS/Serialization/EntitiesDB.DescriptorMap.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Reflection; +using Svelto.Common; using Svelto.ECS.Serialization; namespace Svelto.ECS @@ -10,28 +11,40 @@ namespace Svelto.ECS sealed class SerializationDescriptorMap { /// - /// Use reflection to register all the ISerializableEntityDescriptor to be used for serialization + /// Here we want to register all the EntityDescriptors that need to be serialized for network play. + /// + /// Remember! This needs to in sync across different clients and server as the values are serialized across + /// the network also want this to not change so we can save to a DB /// internal SerializationDescriptorMap() { _descriptors = new Dictionary(); _factories = new Dictionary(); - Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); - foreach (Assembly assembly in assemblies) + using (new StandardProfiler("Assemblies Scan")) { - foreach (Type type in GetTypesSafe(assembly)) + Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); + // Assembly executingAssembly = Assembly.GetExecutingAssembly(); + + foreach (Assembly assembly in assemblies) { - if (type != null && type.IsClass && type.IsAbstract == false && type.BaseType != null && type.BaseType.IsGenericType && - type.BaseType.GetGenericTypeDefinition() == typeof(SerializableEntityDescriptor<>)) + // if (assembly.GetReferencedAssemblies().Contains(executingAssembly.GetName())) { - var descriptor = Activator.CreateInstance(type) as ISerializableEntityDescriptor; + foreach (Type type in GetTypesSafe(assembly)) + { + if (type != null && type.IsClass && type.IsAbstract == false && type.BaseType != null + && type.BaseType.IsGenericType && type.BaseType.GetGenericTypeDefinition() + == typeof(SerializableEntityDescriptor<>)) + { + var descriptor = Activator.CreateInstance(type) as ISerializableEntityDescriptor; - RegisterEntityDescriptor(descriptor); + RegisterEntityDescriptor(descriptor); + } + } } } } - } + } static IEnumerable GetTypesSafe(Assembly assembly) { @@ -56,7 +69,7 @@ namespace Svelto.ECS uint descriptorHash = descriptor.hash; -#if DEBUG && !PROFILER +#if DEBUG && !PROFILE_SVELTO if (_descriptors.ContainsKey(descriptorHash)) { throw new Exception($"Hash Collision of '{descriptor.GetType()}' against " + @@ -69,7 +82,7 @@ namespace Svelto.ECS public ISerializableEntityDescriptor GetDescriptorFromHash(uint descriptorID) { -#if DEBUG && !PROFILER +#if DEBUG && !PROFILE_SVELTO DBC.ECS.Check.Require(_descriptors.ContainsKey(descriptorID), $"Could not find descriptor with ID '{descriptorID}'!"); #endif @@ -88,6 +101,7 @@ namespace Svelto.ECS _factories.Add(SerializationEntityDescriptorTemplate.hash, deserializationFactory); } + readonly Dictionary _descriptors; readonly Dictionary _factories; } diff --git a/Svelto.ECS/Serialization/IComponentSerializer.cs b/Svelto.ECS/Serialization/IComponentSerializer.cs new file mode 100644 index 0000000..62b3367 --- /dev/null +++ b/Svelto.ECS/Serialization/IComponentSerializer.cs @@ -0,0 +1,13 @@ +#if DEBUG && !PROFILE_SVELTO +#endif + +namespace Svelto.ECS.Serialization +{ + public interface IComponentSerializer where T : unmanaged, IEntityComponent + { + bool Serialize(in T value, ISerializationData serializationData); + bool Deserialize(ref T value, ISerializationData serializationData); + + uint size { get; } + } +} \ No newline at end of file diff --git a/Svelto.ECS/Serialization/IDeserializationFactory.cs b/Svelto.ECS/Serialization/IDeserializationFactory.cs index 1045f71..9fce087 100644 --- a/Svelto.ECS/Serialization/IDeserializationFactory.cs +++ b/Svelto.ECS/Serialization/IDeserializationFactory.cs @@ -2,8 +2,8 @@ namespace Svelto.ECS.Serialization { public interface IDeserializationFactory { - EntityStructInitializer BuildDeserializedEntity(EGID egid, ISerializationData serializationData, - ISerializableEntityDescriptor entityDescriptor, SerializationType serializationType, + EntityComponentInitializer BuildDeserializedEntity(EGID egid, ISerializationData serializationData, + ISerializableEntityDescriptor entityDescriptor, int serializationType, IEntitySerialization entitySerialization); } } diff --git a/Svelto.ECS/Serialization/IEntitySerialization.cs b/Svelto.ECS/Serialization/IEntitySerialization.cs index 6fb1ef8..2ff055d 100644 --- a/Svelto.ECS/Serialization/IEntitySerialization.cs +++ b/Svelto.ECS/Serialization/IEntitySerialization.cs @@ -9,7 +9,7 @@ namespace Svelto.ECS.Serialization /// /// /// Size in bytes of the newly instantiated entity - void SerializeEntity(EGID egid, ISerializationData serializationData, SerializationType serializationType); + void SerializeEntity(EGID egid, ISerializationData serializationData, int serializationType); /// /// Deserialize a serializationData and copy directly onto the appropriate entities @@ -17,7 +17,7 @@ namespace Svelto.ECS.Serialization /// /// /// - void DeserializeEntity(ISerializationData serializationData, SerializationType serializationType); + void DeserializeEntity(ISerializationData serializationData, int serializationType); /// /// Deserialize a serializationData and copy directly onto the appropriate entities with explicit EGID @@ -26,18 +26,18 @@ namespace Svelto.ECS.Serialization /// /// /// - void DeserializeEntity(EGID egid, ISerializationData serializationData, SerializationType serializationType); + void DeserializeEntity(EGID egid, ISerializationData serializationData, int serializationType); /// - /// Deserialize a serializationData and copy directly to an previously created EntityStructInitializer + /// Deserialize a serializationData and copy directly to an previously created EntityComponentInitializer /// /// /// /// /// - void DeserializeEntityStructs(ISerializationData serializationData, + void DeserializeEntityComponents(ISerializationData serializationData, ISerializableEntityDescriptor entityDescriptor, - ref EntityStructInitializer initializer, SerializationType serializationType); + ref EntityComponentInitializer initializer, int serializationType); /// /// Contrary to the other Deserialize methods that assume that the entity exists, this method is used to deserialise @@ -47,8 +47,8 @@ namespace Svelto.ECS.Serialization /// /// /// - EntityStructInitializer DeserializeNewEntity(EGID egid, ISerializationData serializationData, - SerializationType serializationType); + EntityComponentInitializer DeserializeNewEntity(EGID egid, ISerializationData serializationData, + int serializationType); /// /// Special Entity Swap method that works without knowing the EntityDescriptor to swap @@ -65,5 +65,9 @@ namespace Svelto.ECS.Serialization void RegisterSerializationFactory(IDeserializationFactory deserializationFactory) where T : ISerializableEntityDescriptor, new(); + + T DeserializeEntityComponent(ISerializationData serializationData, + ISerializableEntityDescriptor entityDescriptor, int serializationType) + where T : unmanaged, IEntityComponent; } } \ No newline at end of file diff --git a/Svelto.ECS/Serialization/ISerializableComponentBuilder.cs b/Svelto.ECS/Serialization/ISerializableComponentBuilder.cs new file mode 100644 index 0000000..2b1e906 --- /dev/null +++ b/Svelto.ECS/Serialization/ISerializableComponentBuilder.cs @@ -0,0 +1,16 @@ +using Svelto.ECS.Internal; + +namespace Svelto.ECS.Serialization +{ + public interface ISerializableComponentBuilder : IComponentBuilder + { + void Serialize(uint id, ITypeSafeDictionary dictionary, ISerializationData serializationData + , int serializationType); + + void Deserialize(uint id, ITypeSafeDictionary dictionary, ISerializationData serializationData + , int serializationType); + + void Deserialize(ISerializationData serializationData, in EntityComponentInitializer initializer + , int serializationType); + } +} \ No newline at end of file diff --git a/Svelto.ECS/Serialization/ISerializableEntityBuilder.cs b/Svelto.ECS/Serialization/ISerializableEntityBuilder.cs deleted file mode 100644 index 91c3433..0000000 --- a/Svelto.ECS/Serialization/ISerializableEntityBuilder.cs +++ /dev/null @@ -1,15 +0,0 @@ -using Svelto.ECS.Internal; - -namespace Svelto.ECS.Serialization -{ - public interface ISerializableEntityBuilder : IEntityBuilder - { - void Serialize(uint id, ITypeSafeDictionary dictionary, ISerializationData serializationData, SerializationType serializationType); - - void Deserialize(uint id, ITypeSafeDictionary dictionary, ISerializationData serializationData, SerializationType serializationType); - - void Deserialize(ISerializationData serializationData, in EntityStructInitializer initializer, SerializationType serializationType); - - void CopySerializedEntityStructs(in EntityStructInitializer sourceInitializer, in EntityStructInitializer destinationInitializer, SerializationType serializationType); - } -} diff --git a/Svelto.ECS/Serialization/ISerializableEntityDescriptor.cs b/Svelto.ECS/Serialization/ISerializableEntityDescriptor.cs index f441bde..bf61048 100644 --- a/Svelto.ECS/Serialization/ISerializableEntityDescriptor.cs +++ b/Svelto.ECS/Serialization/ISerializableEntityDescriptor.cs @@ -3,8 +3,6 @@ namespace Svelto.ECS.Serialization public interface ISerializableEntityDescriptor : IEntityDescriptor { uint hash { get; } - ISerializableEntityBuilder[] entitiesToSerialize { get; } - - void CopySerializedEntityStructs(in EntityStructInitializer sourceInitializer, in EntityStructInitializer destinationInitializer, SerializationType serializationType); + ISerializableComponentBuilder[] entitiesToSerialize { get; } } } \ No newline at end of file diff --git a/Svelto.ECS/Serialization/ISerializationData.cs b/Svelto.ECS/Serialization/ISerializationData.cs index 852d43a..1556d6d 100644 --- a/Svelto.ECS/Serialization/ISerializationData.cs +++ b/Svelto.ECS/Serialization/ISerializationData.cs @@ -9,6 +9,6 @@ namespace Svelto.ECS void ReuseAsNew(); void Reset(); - void BeginNextEntityStruct(); + void BeginNextEntityComponent(); } } \ No newline at end of file diff --git a/Svelto.ECS/Serialization/ISerializer.cs b/Svelto.ECS/Serialization/ISerializer.cs deleted file mode 100644 index 39fa588..0000000 --- a/Svelto.ECS/Serialization/ISerializer.cs +++ /dev/null @@ -1,55 +0,0 @@ -#if DEBUG && !PROFILER -using System; -#endif - -namespace Svelto.ECS.Serialization -{ - public interface ISerializer - where T : unmanaged, IEntityStruct - { - bool Serialize(in T value, ISerializationData serializationData); - bool Deserialize(ref T value, ISerializationData serializationData); - - uint size { get; } - } - - public static class SerializerExt - { - public static bool SerializeSafe(this ISerializer serializer, in T value, ISerializationData serializationData) - where T : unmanaged, IEntityStruct - { -#if DEBUG && !PROFILER - uint posBefore = serializationData.dataPos; -#endif - bool res = serializer.Serialize(value, serializationData); -#if DEBUG && !PROFILER - // size == 0 is a special case when we don't know the size in advance - if (serializer.size != 0 && serializationData.dataPos != posBefore + serializer.size) - { - throw new IndexOutOfRangeException( - $"Size mismatch when serializing {typeof(T).FullName} using {serializer.GetType().FullName}, " + - $"expected offset {posBefore + serializer.size}, got {serializationData.dataPos}"); - } -#endif - return res; - } - - public static bool DeserializeSafe(this ISerializer serializer, ref T value, ISerializationData serializationData) - where T : unmanaged, IEntityStruct - { -#if DEBUG && !PROFILER - uint posBefore = serializationData.dataPos; -#endif - bool res = serializer.Deserialize(ref value, serializationData); -#if DEBUG && !PROFILER - if (serializer.size != 0 && serializationData.dataPos != posBefore + serializer.size) - { - throw new IndexOutOfRangeException( - $"Size mismatch when deserializing {typeof(T).FullName} using {serializer.GetType().FullName}, " + - $"expected offset {posBefore + serializer.size}, got {serializationData.dataPos}"); - } -#endif - return res; - } - } -} diff --git a/Svelto.ECS/Serialization/PartialSerializer.cs b/Svelto.ECS/Serialization/PartialSerializer.cs index a21348f..443f363 100644 --- a/Svelto.ECS/Serialization/PartialSerializer.cs +++ b/Svelto.ECS/Serialization/PartialSerializer.cs @@ -10,8 +10,8 @@ namespace Svelto.ECS.Serialization public class PartialSerializerFieldAttribute : Attribute {} - public class PartialSerializer : ISerializer - where T : unmanaged, IEntityStruct + public class PartialSerializer : IComponentSerializer + where T : unmanaged, IEntityComponent { static PartialSerializer() { @@ -36,7 +36,7 @@ namespace Svelto.ECS.Serialization } } - if (myType.GetProperties().Length > (EntityBuilder.HAS_EGID ? 1 : 0)) + if (myType.GetProperties().Length > (ComponentBuilder.HAS_EGID ? 1 : 0)) throw new ECSException("serializable entity struct must be property less ".FastConcat(myType.FullName)); } @@ -44,15 +44,15 @@ namespace Svelto.ECS.Serialization { unsafe { - fixed (byte* dataptr = serializationData.data.ToArrayFast()) + fixed (byte* dataptr = serializationData.data.ToArrayFast(out _)) { - var entityStruct = value; + var entityComponent = value; foreach ((uint offset, uint size) offset in offsets) { - byte* srcPtr = (byte*) &entityStruct + offset.offset; + byte* srcPtr = (byte*) &entityComponent + offset.offset; //todo move to Unsafe Copy when available as it is faster Buffer.MemoryCopy(srcPtr, dataptr + serializationData.dataPos, - serializationData.data.Count - serializationData.dataPos, offset.size); + serializationData.data.count - serializationData.dataPos, offset.size); serializationData.dataPos += offset.size; } } @@ -66,7 +66,7 @@ namespace Svelto.ECS.Serialization unsafe { T tempValue = value; //todo: temporary solution I want to get rid of this copy - fixed (byte* dataptr = serializationData.data.ToArrayFast()) + fixed (byte* dataptr = serializationData.data.ToArrayFast(out _)) foreach ((uint offset, uint size) offset in offsets) { byte* dstPtr = (byte*) &tempValue + offset.offset; diff --git a/Svelto.ECS/Serialization/SerializableComponentBuilder.cs b/Svelto.ECS/Serialization/SerializableComponentBuilder.cs new file mode 100644 index 0000000..de86568 --- /dev/null +++ b/Svelto.ECS/Serialization/SerializableComponentBuilder.cs @@ -0,0 +1,110 @@ +using System; +using Svelto.Common; +using Svelto.ECS.Internal; + +namespace Svelto.ECS.Serialization +{ + public struct SerializersInfo where SerializationEnum:Enum + { + public uint numberOfSerializationTypes => (uint) length; + + static readonly int length = Enum.GetNames(typeof(SerializationEnum)).Length; + } + + public class SerializableComponentBuilder : ComponentBuilder, ISerializableComponentBuilder + where T : unmanaged, IEntityComponent + { + public static readonly uint SIZE = UnsafeUtils.SizeOf(); + + public void Serialize + (uint entityID, ITypeSafeDictionary dictionary, ISerializationData serializationData + , int serializationType) + { + IComponentSerializer componentSerializer = _serializers[serializationType]; + + var safeDictionary = (ITypeSafeDictionary) dictionary; + if (safeDictionary.TryFindIndex(entityID, out uint index) == false) + { + throw new ECSException("Entity Serialization failed"); + } + + var values = safeDictionary.unsafeValues; + ref T val = ref values[(int) index]; + + serializationData.dataPos = (uint) serializationData.data.count; + + serializationData.data.ExpandBy(componentSerializer.size); + componentSerializer.SerializeSafe(val, serializationData); + } + + public void Deserialize + (uint entityID, ITypeSafeDictionary dictionary, ISerializationData serializationData + , int serializationType) + { + IComponentSerializer componentSerializer = _serializers[(int) serializationType]; + + // Handle the case when an entity struct is gone + var safeDictionary = (ITypeSafeDictionary) dictionary; + if (safeDictionary.TryFindIndex(entityID, out uint index) == false) + { + throw new ECSException("Entity Deserialization failed"); + } + + var values = safeDictionary.unsafeValues; + ref T val = ref values[(int) index]; + + componentSerializer.DeserializeSafe(ref val, serializationData); + } + + public void Deserialize + (ISerializationData serializationData, in EntityComponentInitializer initializer + , int serializationType) + { + IComponentSerializer componentSerializer = _serializers[(int) serializationType]; + + componentSerializer.DeserializeSafe(ref initializer.GetOrCreate(), serializationData); + } + + public void Deserialize + (ISerializationData serializationData, ref T entityComponent, int serializationType) + { + IComponentSerializer componentSerializer = _serializers[(int) serializationType]; + componentSerializer.DeserializeSafe(ref entityComponent, serializationData); + } + + private protected IComponentSerializer[] _serializers; + } + + public class SerializableComponentBuilder : SerializableComponentBuilder + where T : unmanaged, IEntityComponent where SerializationType : Enum + { + static SerializableComponentBuilder() { } + + public SerializableComponentBuilder(params ValueTuple>[] serializers) + { + var length = new SerializersInfo().numberOfSerializationTypes; + + _serializers = new IComponentSerializer[(int)length]; + for (int i = 0; i < serializers.Length; i++) + { + ref (int, IComponentSerializer) s = ref serializers[i]; + _serializers[(int) s.Item1] = s.Item2; + } + + // Just in case the above are the same type + if (serializers.Length > 0) + { + for (int i = 0; i < (int) length; i++) + { + if (_serializers[i] == null) + _serializers[i] = new DontSerialize(); + } + } + else + for (int i = 0; i < (int) length; i++) + { + _serializers[i] = new DefaultSerializer(); + } + } + } +} \ No newline at end of file diff --git a/Svelto.ECS/Serialization/SerializableEntityBuilder.cs b/Svelto.ECS/Serialization/SerializableEntityBuilder.cs deleted file mode 100644 index e34caee..0000000 --- a/Svelto.ECS/Serialization/SerializableEntityBuilder.cs +++ /dev/null @@ -1,95 +0,0 @@ -using System; -using Svelto.Common; -using Svelto.ECS.Internal; - -namespace Svelto.ECS.Serialization -{ - public class SerializableEntityBuilder : EntityBuilder, ISerializableEntityBuilder - where T : unmanaged, IEntityStruct - { - public static readonly uint SIZE = UnsafeUtils.SizeOf(); - - static SerializableEntityBuilder() - {} - - public SerializableEntityBuilder() - { - _serializers = new ISerializer[(int) SerializationType.Length]; - for (int i = 0; i < (int) SerializationType.Length; i++) - { - _serializers[i] = new DefaultSerializer(); - } - } - - public SerializableEntityBuilder(params ValueTuple>[] serializers) - { - _serializers = new ISerializer[(int) SerializationType.Length]; - for (int i = 0; i < serializers.Length; i++) - { - ref (SerializationType, ISerializer) s = ref serializers[i]; - _serializers[(int) s.Item1] = s.Item2; - } - - // Just in case the above are the same type - for (int i = 0; i < (int) SerializationType.Length; i++) - { - if (_serializers[i] == null) _serializers[i] = new DontSerialize(); - } - } - - public void Serialize(uint entityID, ITypeSafeDictionary dictionary, - ISerializationData serializationData, SerializationType serializationType) - { - ISerializer serializer = _serializers[(int)serializationType]; - - var safeDictionary = (TypeSafeDictionary) dictionary; - if (safeDictionary.TryFindIndex(entityID, out uint index) == false) - { - throw new ECSException("Entity Serialization failed"); - } - - T[] values = safeDictionary.GetValuesArray(out _); - ref T val = ref values[index]; - - serializationData.dataPos = (uint) serializationData.data.Count; - - serializationData.data.ExpandBy(serializer.size); - serializer.SerializeSafe(val, serializationData); - } - - public void Deserialize(uint entityID, ITypeSafeDictionary dictionary, - ISerializationData serializationData, SerializationType serializationType) - { - ISerializer serializer = _serializers[(int) serializationType]; - - // Handle the case when an entity struct is gone - var safeDictionary = (TypeSafeDictionary) dictionary; - if (safeDictionary.TryFindIndex(entityID, out uint index) == false) - { - throw new ECSException("Entity Deserialization failed"); - } - - T[] values = safeDictionary.GetValuesArray(out _); - ref T val = ref values[index]; - - serializer.DeserializeSafe(ref val, serializationData); - } - - public void Deserialize(ISerializationData serializationData - , in EntityStructInitializer initializer, SerializationType serializationType) - { - ISerializer serializer = _serializers[(int) serializationType]; - - serializer.DeserializeSafe(ref initializer.GetOrCreate(), serializationData); - } - - public void CopySerializedEntityStructs(in EntityStructInitializer sourceInitializer, - in EntityStructInitializer destinationInitializer, SerializationType serializationType) - { - if ((_serializers[(int) serializationType] is DontSerialize) == false) - destinationInitializer.CopyFrom(sourceInitializer.Get()); - } - - readonly ISerializer[] _serializers; - } -} \ No newline at end of file diff --git a/Svelto.ECS/Serialization/SerializableEntityStruct.cs b/Svelto.ECS/Serialization/SerializableEntityComponent.cs similarity index 59% rename from Svelto.ECS/Serialization/SerializableEntityStruct.cs rename to Svelto.ECS/Serialization/SerializableEntityComponent.cs index 1e10932..57e1b2d 100644 --- a/Svelto.ECS/Serialization/SerializableEntityStruct.cs +++ b/Svelto.ECS/Serialization/SerializableEntityComponent.cs @@ -1,6 +1,6 @@ namespace Svelto.ECS { - internal struct SerializableEntityStruct : IEntityStruct, INeedEGID + struct SerializableEntityComponent : IEntityComponent, INeedEGID { public uint descriptorHash; diff --git a/Svelto.ECS/Serialization/SerializableEntityDescriptor.cs b/Svelto.ECS/Serialization/SerializableEntityDescriptor.cs index f31b902..b758bb6 100644 --- a/Svelto.ECS/Serialization/SerializableEntityDescriptor.cs +++ b/Svelto.ECS/Serialization/SerializableEntityDescriptor.cs @@ -16,107 +16,94 @@ namespace Svelto.ECS.Serialization { static SerializableEntityDescriptor() { - IEntityBuilder[] defaultEntities = EntityDescriptorTemplate.descriptor.entitiesToBuild; + IComponentBuilder[] defaultEntities = EntityDescriptorTemplate.descriptor.componentsToBuild; 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)); - var (index, dynamicIndex) = SetupSpecialEntityStruct(defaultEntities, out _entitiesToBuild); + var (index, dynamicIndex) = SetupSpecialEntityComponent(defaultEntities, out ComponentsToBuild); if (index == -1) { - index = _entitiesToBuild.Length - 1; + index = ComponentsToBuild.Length - 1; } // Stores the hash of this EntityDescriptor - _entitiesToBuild[index] = new EntityBuilder - ( - new SerializableEntityStruct - { - descriptorHash = _hash - } - ); + ComponentsToBuild[index] = new ComponentBuilder(new SerializableEntityComponent + { + descriptorHash = _hash + }); // If the current serializable is an ExtendibleDescriptor, I have to update it. if (dynamicIndex != -1) { - _entitiesToBuild[dynamicIndex] = new EntityBuilder - ( - new EntityStructInfoView - { - entitiesToBuild = _entitiesToBuild - } - ); + ComponentsToBuild[dynamicIndex] = new ComponentBuilder(new EntityInfoComponentView + { + componentsToBuild = ComponentsToBuild + }); } ///// - var entitiesToSerialize = new FasterList(); - _entitiesToSerializeMap = new FasterDictionary, ISerializableEntityBuilder>(); - foreach (IEntityBuilder e in defaultEntities) + var entitiesToSerialize = new FasterList(); + _entityComponentsToSerializeMap = new FasterDictionary, ISerializableComponentBuilder>(); + foreach (IComponentBuilder e in defaultEntities) { - if (e is ISerializableEntityBuilder serializableEntityBuilder) + if (e is ISerializableComponentBuilder serializableEntityBuilder) { - var entityType = serializableEntityBuilder.GetEntityType(); - _entitiesToSerializeMap[new RefWrapper(entityType)] = serializableEntityBuilder; + var entityType = serializableEntityBuilder.GetEntityComponentType(); + _entityComponentsToSerializeMap[new RefWrapper(entityType)] = serializableEntityBuilder; entitiesToSerialize.Add(serializableEntityBuilder); } } - + _entitiesToSerialize = entitiesToSerialize.ToArray(); } - static (int indexSerial, int indexDynamic) SetupSpecialEntityStruct(IEntityBuilder[] defaultEntities, - out IEntityBuilder[] entitiesToBuild) + static (int indexSerial, int indexDynamic) SetupSpecialEntityComponent + (IComponentBuilder[] defaultEntities, out IComponentBuilder[] componentsToBuild) { - int length = defaultEntities.Length; + int length = defaultEntities.Length; int newLenght = length + 1; - int indexSerial = -1; + int indexSerial = -1; int indexDynamic = -1; for (var i = 0; i < length; ++i) { - if (defaultEntities[i].GetEntityType() == _serializableStructType) + if (defaultEntities[i].GetEntityComponentType() == _serializableStructType) { indexSerial = i; --newLenght; } - if (defaultEntities[i].GetEntityType() == EntityBuilderUtilities.ENTITY_STRUCT_INFO_VIEW) + if (defaultEntities[i].GetEntityComponentType() == EntityBuilderUtilities.ENTITY_STRUCT_INFO_VIEW) { indexDynamic = i; } } - entitiesToBuild = new IEntityBuilder[newLenght]; + componentsToBuild = new IComponentBuilder[newLenght]; - Array.Copy(defaultEntities, 0, entitiesToBuild, 0, length); + Array.Copy(defaultEntities, 0, componentsToBuild, 0, length); return (indexSerial, indexDynamic); } - - public void CopySerializedEntityStructs(in EntityStructInitializer sourceInitializer, in EntityStructInitializer destinationInitializer, SerializationType serializationType) - { - foreach (ISerializableEntityBuilder e in entitiesToSerialize) - { - e.CopySerializedEntityStructs(sourceInitializer, destinationInitializer, serializationType); - } - } - public IEntityBuilder[] entitiesToBuild => _entitiesToBuild; - public uint hash => _hash; - public ISerializableEntityBuilder[] entitiesToSerialize => _entitiesToSerialize; + public IComponentBuilder[] componentsToBuild => ComponentsToBuild; + public uint hash => _hash; + public ISerializableComponentBuilder[] entitiesToSerialize => _entitiesToSerialize; - static readonly IEntityBuilder[] _entitiesToBuild; - static readonly FasterDictionary, ISerializableEntityBuilder> _entitiesToSerializeMap; - static readonly ISerializableEntityBuilder[] _entitiesToSerialize; + static readonly IComponentBuilder[] ComponentsToBuild; + static readonly FasterDictionary, ISerializableComponentBuilder> _entityComponentsToSerializeMap; + static readonly ISerializableComponentBuilder[] _entitiesToSerialize; static readonly uint _hash; - static readonly Type _serializableStructType = typeof(SerializableEntityStruct); + static readonly Type _serializableStructType = typeof(SerializableEntityComponent); static readonly Type _type = typeof(TType); } -} +} \ No newline at end of file diff --git a/Svelto.ECS/Serialization/SerializerExt.cs b/Svelto.ECS/Serialization/SerializerExt.cs new file mode 100644 index 0000000..4387d3b --- /dev/null +++ b/Svelto.ECS/Serialization/SerializerExt.cs @@ -0,0 +1,45 @@ +using System; + +namespace Svelto.ECS.Serialization { + public static class SerializerExt + { + public static bool SerializeSafe + (this IComponentSerializer componentSerializer, in T value, ISerializationData serializationData) + where T : unmanaged, IEntityComponent + { +#if DEBUG && !PROFILE_SVELTO + uint posBefore = serializationData.dataPos; +#endif + bool res = componentSerializer.Serialize(value, serializationData); +#if DEBUG && !PROFILE_SVELTO + // size == 0 is a special case when we don't know the size in advance + if (componentSerializer.size != 0 && serializationData.dataPos != posBefore + componentSerializer.size) + { + throw new IndexOutOfRangeException( + $"Size mismatch when serializing {typeof(T).FullName} using {componentSerializer.GetType().FullName}, " + + $"expected offset {posBefore + componentSerializer.size}, got {serializationData.dataPos}"); + } +#endif + return res; + } + + public static bool DeserializeSafe + (this IComponentSerializer componentSerializer, ref T value, ISerializationData serializationData) + where T : unmanaged, IEntityComponent + { +#if DEBUG && !PROFILE_SVELTO + uint posBefore = serializationData.dataPos; +#endif + bool res = componentSerializer.Deserialize(ref value, serializationData); +#if DEBUG && !PROFILE_SVELTO + if (componentSerializer.size != 0 && serializationData.dataPos != posBefore + componentSerializer.size) + { + throw new IndexOutOfRangeException( + $"Size mismatch when deserializing {typeof(T).FullName} using {componentSerializer.GetType().FullName}, " + + $"expected offset {posBefore + componentSerializer.size}, got {serializationData.dataPos}"); + } +#endif + return res; + } + } +} \ No newline at end of file diff --git a/Svelto.ECS/Serialization/SimpleSerializationData.cs b/Svelto.ECS/Serialization/SimpleSerializationData.cs index 71c7d54..e1563f9 100644 --- a/Svelto.ECS/Serialization/SimpleSerializationData.cs +++ b/Svelto.ECS/Serialization/SimpleSerializationData.cs @@ -31,7 +31,7 @@ namespace Svelto.ECS dataPos = 0; } - public void BeginNextEntityStruct() + public void BeginNextEntityComponent() {} } } \ No newline at end of file diff --git a/Svelto.ECS/Serialization/Unsafe.cs b/Svelto.ECS/Serialization/Unsafe.cs deleted file mode 100644 index dd6ea52..0000000 --- a/Svelto.ECS/Serialization/Unsafe.cs +++ /dev/null @@ -1,15 +0,0 @@ -using Svelto.ECS; - -namespace Svelto.Common -{ - public class UnsafeUtils - { - public static uint SizeOf() where T : unmanaged, IEntityStruct - { - unsafe - { - return (uint) sizeof(T); - } - } - } -} \ No newline at end of file diff --git a/Svelto.ECS/SetEGIDWithoutBoxing.cs b/Svelto.ECS/SetEGIDWithoutBoxing.cs new file mode 100644 index 0000000..b11533b --- /dev/null +++ b/Svelto.ECS/SetEGIDWithoutBoxing.cs @@ -0,0 +1,33 @@ +using System; + +namespace Svelto.ECS.Internal +{ + public delegate void SetEGIDWithoutBoxingActionCast(ref T target, EGID egid) where T : struct, IEntityComponent; + + static class SetEGIDWithoutBoxing where T : struct, IEntityComponent + { + public static readonly SetEGIDWithoutBoxingActionCast SetIDWithoutBoxing = MakeSetter(); + + public static void Warmup() { } + + static SetEGIDWithoutBoxingActionCast MakeSetter() + { + if (ComponentBuilder.HAS_EGID) + { + var method = typeof(Trick).GetMethod(nameof(Trick.SetEGIDImpl)).MakeGenericMethod(typeof(T)); + return (SetEGIDWithoutBoxingActionCast) Delegate.CreateDelegate( + typeof(SetEGIDWithoutBoxingActionCast), method); + } + + return null; + } + + static class Trick + { + public static void SetEGIDImpl(ref U target, EGID egid) where U : struct, INeedEGID + { + target.ID = egid; + } + } + } +} \ No newline at end of file diff --git a/Svelto.ECS/SimpleSubmissionEntityViewScheduler.cs b/Svelto.ECS/SimpleEntitiesSubmissionScheduler.cs similarity index 59% rename from Svelto.ECS/SimpleSubmissionEntityViewScheduler.cs rename to Svelto.ECS/SimpleEntitiesSubmissionScheduler.cs index e2fe657..3404b02 100644 --- a/Svelto.ECS/SimpleSubmissionEntityViewScheduler.cs +++ b/Svelto.ECS/SimpleEntitiesSubmissionScheduler.cs @@ -1,25 +1,22 @@ -using System; -using Svelto.ECS.Schedulers; +using Svelto.ECS.Schedulers; namespace Svelto.ECS { //This scheduler shouldn't be used in production and it's meant to be used for Unit Tests only - public class SimpleSubmissionEntityViewScheduler : IEntitySubmissionScheduler + public class SimpleEntitiesSubmissionScheduler : IEntitiesSubmissionScheduler { public void SubmitEntities() { _onTick.Invoke(); } - EnginesRoot.EntitiesSubmitter IEntitySubmissionScheduler.onTick + EnginesRoot.EntitiesSubmitter IEntitiesSubmissionScheduler.onTick { set => _onTick = value; } - - EnginesRoot.EntitiesSubmitter _onTick; - public void Dispose() - { - } + public void Dispose() { } + + EnginesRoot.EntitiesSubmitter _onTick; } } \ No newline at end of file diff --git a/Svelto.ECS/Svelto.ECS.asmdef b/Svelto.ECS/Svelto.ECS.asmdef index d561d80..ac3faf3 100644 --- a/Svelto.ECS/Svelto.ECS.asmdef +++ b/Svelto.ECS/Svelto.ECS.asmdef @@ -1,15 +1,26 @@ { "name": "Svelto.ECS", "references": [ - "Svelto.Common" + "Unity.Entities", + "Unity.Collections", + "Unity.Burst", + "Svelto.Common_3" ], - "optionalUnityReferences": [], "includePlatforms": [], "excludePlatforms": [], "allowUnsafeCode": true, - "overrideReferences": false, - "precompiledReferences": [], + "overrideReferences": true, + "precompiledReferences": [ + "System.Runtime.CompilerServices.Unsafe.dll" + ], "autoReferenced": true, "defineConstraints": [], - "versionDefines": [] + "versionDefines": [ + { + "name": "com.unity.entities", + "expression": "", + "define": "UNITY_ECS" + } + ], + "noEngineReferences": false } \ No newline at end of file diff --git a/Svelto.ECS/TypeCache.cs b/Svelto.ECS/TypeCache.cs deleted file mode 100644 index bd10908..0000000 --- a/Svelto.ECS/TypeCache.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System; - -namespace Svelto.ECS.Internal -{ - public class TypeCache - { - public static Type type = typeof(T); - } -} \ No newline at end of file diff --git a/Svelto.ECS/TypeSafeDictionaryFactory.cs b/Svelto.ECS/TypeSafeDictionaryFactory.cs new file mode 100644 index 0000000..2b7f94f --- /dev/null +++ b/Svelto.ECS/TypeSafeDictionaryFactory.cs @@ -0,0 +1,17 @@ +using Svelto.ECS.Internal; + +namespace Svelto.ECS +{ + static class TypeSafeDictionaryFactory where T : struct, IEntityComponent + { + public static ITypeSafeDictionary Create() + { + return new TypeSafeDictionary(); + } + + public static ITypeSafeDictionary Create(uint size) + { + return new TypeSafeDictionary(size); + } + } +} \ No newline at end of file diff --git a/Svelto.ECS/WaitForSubmissionEnumerator.cs b/Svelto.ECS/WaitForSubmissionEnumerator.cs index d5a1732..acf4816 100644 --- a/Svelto.ECS/WaitForSubmissionEnumerator.cs +++ b/Svelto.ECS/WaitForSubmissionEnumerator.cs @@ -11,19 +11,19 @@ namespace Svelto.ECS } readonly IEntityFactory _entityFactory; - readonly IEntitiesDB _entitiesDB; + readonly EntitiesDB _entitiesDB; readonly IEntityFunctions _entityFunctions; int _state; public WaitForSubmissionEnumerator(IEntityFunctions entityFunctions, IEntityFactory entityFactory, - IEntitiesDB entitiesDb) + EntitiesDB entitiesDb) { _entityFactory = entityFactory; _entityFunctions = entityFunctions; _entitiesDB = entitiesDb; } - + public bool MoveNext() { switch (_state) @@ -55,7 +55,7 @@ namespace Svelto.ECS public object Current { get; } - struct SubmissionSignalStruct : IEntityStruct + struct SubmissionSignalStruct : IEntityComponent {} int _counter; diff --git a/Svelto.ECS/package.json b/Svelto.ECS/package.json new file mode 100644 index 0000000..f4717d5 --- /dev/null +++ b/Svelto.ECS/package.json @@ -0,0 +1,15 @@ +{ + "displayName": "Svelto ECS", + "category": "Svelto", + "description": "Svelto ECS C# Lightweight Data Oriented Entity Component System Framework", + "dependencies": { + "com.sebaslab.svelto.common": "2.9.4" + }, + "keywords": [ + "svelto" + ], + "name": "com.sebaslab.svelto.ecs", + "unity": "2019.2", + "version": "2.9.5", + "type": "library" +}