diff --git a/CheckEntityUtilities.cs b/CheckEntityUtilities.cs index 0b061f6..020b4b2 100644 --- a/CheckEntityUtilities.cs +++ b/CheckEntityUtilities.cs @@ -1,81 +1,85 @@ -#if DEBUG && !PROFILER +#if !DEBUG || PROFILE_SVELTO +#define DONT_USE +#endif +using System; using System.Collections.Generic; -using Svelto.DataStructures; -#else using System.Diagnostics; -#endif +using Svelto.DataStructures; namespace Svelto.ECS { + /// + /// Note: this check doesn't catch the case when an add and remove is done on the same entity before the nextI am + /// submission. Two operations on the same entity are not allowed between submissions. + /// public partial class EnginesRoot { -#if DEBUG && !PROFILER - void CheckRemoveEntityID(EGID egid) +#if DONT_USE + [Conditional("CHECK_ALL")] +#endif + void CheckRemoveEntityID(EGID egid, Type entityDescriptorType, string caller = "") { - // Console.LogError("removed".FastConcat(egid.ToString())); - if (_idCheckers.TryGetValue(egid.groupID, out var hash)) - { - if (hash.Contains(egid.entityID) == false) - throw new ECSException("Entity with not found ID is about to be removed: id: " - .FastConcat(egid.entityID) - .FastConcat(" groupid: ") - .FastConcat(egid.groupID)); + if (_multipleOperationOnSameEGIDChecker.ContainsKey(egid) == true) + throw new ECSException( + "Executing multiple structural changes in one submission on the same entity is not supported " + .FastConcat(" caller: ", caller, " ").FastConcat(egid.entityID).FastConcat(" groupid: ") + .FastConcat(egid.groupID.ToName()).FastConcat(" type: ") + .FastConcat(entityDescriptorType != null ? entityDescriptorType.Name : "not available") + .FastConcat(" operation was: ") + .FastConcat(_multipleOperationOnSameEGIDChecker[egid] == 1 ? "add" : "remove")); - hash.Remove(egid.entityID); + if (_idChecker.TryGetValue(egid.groupID, out var hash)) + if (hash.Contains(egid.entityID) == false) + throw new ECSException("Trying to remove an Entity never submitted in the database " + .FastConcat(" caller: ", caller, " ").FastConcat(egid.entityID) + .FastConcat(" groupid: ").FastConcat(egid.groupID.ToName()) + .FastConcat(" type: ") + .FastConcat(entityDescriptorType != null + ? entityDescriptorType.Name + : "not available")); + else + hash.Remove(egid.entityID); - if (hash.Count == 0) - _idCheckers.Remove(egid.groupID); - } - else - { - throw new ECSException("Entity with not found ID is about to be removed: id: " - .FastConcat(egid.entityID) - .FastConcat(" groupid: ") - .FastConcat(egid.groupID)); - } + _multipleOperationOnSameEGIDChecker.Add(egid, 0); } - - void CheckAddEntityID(EGID egid) +#if DONT_USE + [Conditional("CHECK_ALL")] +#endif + void CheckAddEntityID(EGID egid, Type entityDescriptorType, string caller = "") { -// Console.LogError("added ".FastConcat(egid.ToString())); - - if (_idCheckers.TryGetValue(egid.groupID, out var hash) == false) - hash = _idCheckers[egid.groupID] = new HashSet(); - else - { - if (hash.Contains(egid.entityID)) - throw new ECSException("Entity with used ID is about to be built: '" - .FastConcat("' id: '") - .FastConcat(egid.entityID) - .FastConcat("' groupid: '") - .FastConcat(egid.groupID) - .FastConcat("'")); - } + if (_multipleOperationOnSameEGIDChecker.ContainsKey(egid) == true) + throw new ECSException( + "Executing multiple structural changes in one submission on the same entity is not supported " + .FastConcat(" caller: ", caller, " ").FastConcat(egid.entityID).FastConcat(" groupid: ") + .FastConcat(egid.groupID.ToName()).FastConcat(" type: ") + .FastConcat(entityDescriptorType != null ? entityDescriptorType.Name : "not available") + .FastConcat(" operation was: ") + .FastConcat(_multipleOperationOnSameEGIDChecker[egid] == 1 ? "add" : "remove")); + var hash = _idChecker.GetOrCreate(egid.groupID, () => new HashSet()); + if (hash.Contains(egid.entityID) == true) + throw new ECSException("Trying to add an Entity already submitted to the database " + .FastConcat(" caller: ", caller, " ").FastConcat(egid.entityID) + .FastConcat(" groupid: ").FastConcat(egid.groupID.ToName()).FastConcat(" type: ") + .FastConcat(entityDescriptorType != null + ? entityDescriptorType.Name + : "not available")); hash.Add(egid.entityID); - } - - void RemoveGroupID(ExclusiveGroup.ExclusiveGroupStruct groupID) - { - _idCheckers.Remove(groupID); + _multipleOperationOnSameEGIDChecker.Add(egid, 1); + } - readonly FasterDictionary> _idCheckers = new FasterDictionary>(); -#else - [Conditional("_CHECKS_DISABLED")] - void CheckRemoveEntityID(EGID egid) - { - } +#if DONT_USE + [Conditional("CHECK_ALL")] +#endif + void RemoveGroupID(BuildGroup groupID) { _idChecker.Remove(groupID); } - [Conditional("_CHECKS_DISABLED")] - void CheckAddEntityID(EGID egid) - { - } - - [Conditional("_CHECKS_DISABLED")] - void RemoveGroupID(ExclusiveGroup.ExclusiveGroupStruct groupID) - { - } +#if DONT_USE + [Conditional("CHECK_ALL")] #endif + void ClearChecks() { _multipleOperationOnSameEGIDChecker.FastClear(); } + + readonly FasterDictionary _multipleOperationOnSameEGIDChecker = new FasterDictionary(); + readonly FasterDictionary> _idChecker = new FasterDictionary>(); } -} +} \ No newline at end of file diff --git a/ComponentBuilder.CheckFields.cs b/ComponentBuilder.CheckFields.cs new file mode 100644 index 0000000..e933d36 --- /dev/null +++ b/ComponentBuilder.CheckFields.cs @@ -0,0 +1,142 @@ +#if !DEBUG || PROFILE_SVELTO +#define DISABLE_CHECKS +using System.Diagnostics; +#endif +using System; +using System.Reflection; +using Svelto.Common; + +namespace Svelto.ECS +{ + static class ComponentBuilderUtilities + { + const string MSG = "Entity Components and Entity View Components fields cannot hold managed fields outside the Svelto rules."; + +#if DISABLE_CHECKS + [Conditional("_CHECKS_DISABLED")] +#endif + public static void CheckFields(Type entityComponentType, bool needsReflection, bool isStringAllowed = false) + { + if (entityComponentType == ENTITY_INFO_COMPONENT || entityComponentType == EGIDType || + entityComponentType == EXCLUSIVEGROUPSTRUCTTYPE || entityComponentType == SERIALIZABLE_ENTITY_STRUCT) + { + return; + } + + if (needsReflection == false) + { + if (entityComponentType.IsClass) + { + throw new ECSException("EntityComponents must be structs.", entityComponentType); + } + + 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, entityComponentType, isStringAllowed); + } + } + else + { + FieldInfo[] fields = entityComponentType.GetFields(BindingFlags.Public | BindingFlags.Instance); + + if (fields.Length < 1) + { + ProcessError("No valid fields found in Entity View Components", entityComponentType); + } + + for (int i = fields.Length - 1; i >= 0; --i) + { + FieldInfo fieldInfo = fields[i]; + + if (fieldInfo.FieldType.IsInterfaceEx() == true) + { + PropertyInfo[] properties = fieldInfo.FieldType.GetProperties( + BindingFlags.Public | BindingFlags.Instance + | BindingFlags.DeclaredOnly); + + for (int j = properties.Length - 1; j >= 0; --j) + { + if (properties[j].PropertyType.IsGenericType) + { + Type genericTypeDefinition = properties[j].PropertyType.GetGenericTypeDefinition(); + if (genericTypeDefinition == DISPATCHONSETTYPE + || genericTypeDefinition == DISPATCHONCHANGETYPE) + { + continue; + } + } + + Type propertyType = properties[j].PropertyType; + + //for EntityComponentStructs, component fields that are structs that hold strings + //are allowed + SubCheckFields(propertyType, entityComponentType, isStringAllowed: true); + } + } + else + if (fieldInfo.FieldType.IsUnmanagedEx() == false) + { + ProcessError("Entity View Components must hold only public interfaces, strings or unmanaged type fields.", + entityComponentType); + + } + } + } + } + + static bool IsString(Type type) + { + return type == STRINGTYPE || type == STRINGBUILDERTYPE; + } + + /// + /// This method checks the fields if it's an IEntityComponent, but checks all the properties if it's + /// IEntityViewComponent + /// + /// + /// + /// + static void SubCheckFields(Type fieldType, Type entityComponentType, bool isStringAllowed = false) + { + //pass if it's Primitive or C# 8 unmanaged, or it's a string and string are allowed + //this check must allow pointers are they are unmanaged types + if ((isStringAllowed == true && IsString(fieldType) == true) || fieldType.IsValueTypeEx() == true) + { + //if it's a struct we have to check the fields recursively + if (IsString(fieldType) == false) + { + CheckFields(fieldType, false, isStringAllowed); + } + + return; + } + + ProcessError(MSG, entityComponentType, fieldType); + } + + static void ProcessError(string message, Type entityComponentType, Type fieldType = null) + { + if (fieldType != null) + { + throw new ECSException(message, entityComponentType, fieldType); + } + + throw new ECSException(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(ExclusiveGroupStruct); + static readonly Type SERIALIZABLE_ENTITY_STRUCT = typeof(SerializableEntityComponent); + static readonly Type STRINGTYPE = typeof(string); + static readonly Type STRINGBUILDERTYPE = typeof(System.Text.StringBuilder); + + internal static readonly Type ENTITY_INFO_COMPONENT = typeof(EntityInfoComponent); + } +} \ No newline at end of file diff --git a/EntityBuilder.CheckFields.cs.meta b/ComponentBuilder.CheckFields.cs.meta similarity index 83% rename from EntityBuilder.CheckFields.cs.meta rename to ComponentBuilder.CheckFields.cs.meta index bbd626f..3eed1a2 100644 --- a/EntityBuilder.CheckFields.cs.meta +++ b/ComponentBuilder.CheckFields.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: ea4e6d9818ba3189beab2cf40d7e8e15 +guid: b8801fb2bdee37a6aa48c7ab61badd55 MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/ComponentBuilder.cs b/ComponentBuilder.cs new file mode 100644 index 0000000..a948d2d --- /dev/null +++ b/ComponentBuilder.cs @@ -0,0 +1,155 @@ +using System; +using System.Collections.Generic; +using System.Reflection; +using Svelto.Common; +using Svelto.DataStructures; +using Svelto.ECS.Hybrid; +using Svelto.ECS.Internal; +using Svelto.Utilities; + +namespace Svelto.ECS +{ + public class ComponentBuilder : IComponentBuilder where T : struct, IEntityComponent + { + public ComponentBuilder() + { + _initializer = DEFAULT_IT; + } + + public ComponentBuilder(in T initializer) : this() + { + _initializer = initializer; + } + + public void BuildEntityAndAddToList(ref ITypeSafeDictionary dictionary, EGID egid, + IEnumerable implementors) + { + if (dictionary == null) + dictionary = TypeSafeDictionaryFactory.Create(); + + var castedDic = dictionary as ITypeSafeDictionary; + + T entityComponent = default; + + if (IS_ENTITY_VIEW_COMPONENT) + { + DBC.ECS.Check.Require(castedDic.ContainsKey(egid.entityID) == false, + $"building an entity with already used entity id! id: '{(ulong) egid}', {ENTITY_COMPONENT_NAME}"); + + this.FillEntityComponent(ref entityComponent, EntityViewComponentCache.cachedFields, implementors, + EntityViewComponentCache.implementorsByType, EntityViewComponentCache.cachedTypes); + + castedDic.Add(egid.entityID, entityComponent); + } + else + { + DBC.ECS.Check.Require(!castedDic.ContainsKey(egid.entityID), + $"building an entity with already used entity id! id: '{egid.entityID}'"); + + castedDic.Add(egid.entityID, _initializer); + } + } + + ITypeSafeDictionary IComponentBuilder.Preallocate(ref ITypeSafeDictionary dictionary, uint size) + { + return Preallocate(ref dictionary, size); + } + + static ITypeSafeDictionary Preallocate(ref ITypeSafeDictionary dictionary, uint size) + { + if (dictionary == null) + dictionary = TypeSafeDictionaryFactory.Create(size); + else + dictionary.SetCapacity(size); + + return dictionary; + } + + public Type GetEntityComponentType() + { + return ENTITY_COMPONENT_TYPE; + } + + static ComponentBuilder() + { + ENTITY_COMPONENT_TYPE = typeof(T); + DEFAULT_IT = default; + IS_ENTITY_VIEW_COMPONENT = typeof(IEntityViewComponent).IsAssignableFrom(ENTITY_COMPONENT_TYPE); + HAS_EGID = typeof(INeedEGID).IsAssignableFrom(ENTITY_COMPONENT_TYPE); + ENTITY_COMPONENT_NAME = ENTITY_COMPONENT_TYPE.ToString(); + var IS_UNMANAGED = ENTITY_COMPONENT_TYPE.IsUnmanagedEx(); + + if (IS_UNMANAGED) + EntityComponentIDMap.Register(new Filler()); + + SetEGIDWithoutBoxing.Warmup(); + + ComponentBuilderUtilities.CheckFields(ENTITY_COMPONENT_TYPE, IS_ENTITY_VIEW_COMPONENT); + + if (IS_ENTITY_VIEW_COMPONENT) + EntityViewComponentCache.InitCache(); + else + { + if (ENTITY_COMPONENT_TYPE != ComponentBuilderUtilities.ENTITY_INFO_COMPONENT && ENTITY_COMPONENT_TYPE.IsUnmanagedEx() == false) + throw new Exception($"Entity Component check failed, unexpected struct type (must be unmanaged) {ENTITY_COMPONENT_TYPE}"); + } + } + + + readonly T _initializer; + + internal static readonly Type ENTITY_COMPONENT_TYPE; + public static readonly bool HAS_EGID; + internal static readonly bool IS_ENTITY_VIEW_COMPONENT; + + static readonly T DEFAULT_IT; + static readonly string ENTITY_COMPONENT_NAME; + + /// + /// Note: this static class will hold forever the references of the entities implementors. These references + /// are not even cleared when the engines root is destroyed, as they are static references. + /// It must be seen as an application-wide cache system. Honestly, I am not sure if this may cause leaking + /// issues in some kind of applications. To remember. + /// + static class EntityViewComponentCache + { + 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]; + if (field.FieldType.IsInterface == true) + { + 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/EntityBuilder.cs.meta b/ComponentBuilder.cs.meta similarity index 83% rename from EntityBuilder.cs.meta rename to ComponentBuilder.cs.meta index 0e9dece..42001e1 100644 --- a/EntityBuilder.cs.meta +++ b/ComponentBuilder.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: cc541a5ea51e37898f2e56854ab8c4fb +guid: cf16c7aee929396a99cb63c9d8242a91 MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/EntityBuilder.cs.rej.meta b/Components.meta similarity index 67% rename from EntityBuilder.cs.rej.meta rename to Components.meta index 9fc1da0..7530c7c 100644 --- a/EntityBuilder.cs.rej.meta +++ b/Components.meta @@ -1,5 +1,6 @@ fileFormatVersion: 2 -guid: 7f991729576f3f0fa1771f61c9f77c15 +guid: 7163f266434d335e810da967a4c4b3ce +folderAsset: yes DefaultImporter: externalObjects: {} userData: diff --git a/Components/EGIDComponent.cs b/Components/EGIDComponent.cs new file mode 100644 index 0000000..c818065 --- /dev/null +++ b/Components/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/EnginesRoot.DoubleBufferedEntityViews.cs.meta b/Components/EGIDComponent.cs.meta similarity index 83% rename from EnginesRoot.DoubleBufferedEntityViews.cs.meta rename to Components/EGIDComponent.cs.meta index 49b6acd..516d005 100644 --- a/EnginesRoot.DoubleBufferedEntityViews.cs.meta +++ b/Components/EGIDComponent.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 31949720b3a734b4a94de73465ef307f +guid: 486ed173f6753a56b9f8b9ec44c7bfc3 MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/Components/EntityHierarchyComponent.cs b/Components/EntityHierarchyComponent.cs new file mode 100644 index 0000000..22f3a2d --- /dev/null +++ b/Components/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/Components/EntityHierarchyComponent.cs.meta b/Components/EntityHierarchyComponent.cs.meta new file mode 100644 index 0000000..7be9f38 --- /dev/null +++ b/Components/EntityHierarchyComponent.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b278b4ce4e7f34f0a2434b7de79b7cbb +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Components/LinkedEntityComponent.cs b/Components/LinkedEntityComponent.cs new file mode 100644 index 0000000..03583d4 --- /dev/null +++ b/Components/LinkedEntityComponent.cs @@ -0,0 +1,7 @@ +namespace Svelto.ECS +{ + public struct LinkedEntityComponent : IEntityComponent + { + public EGID linkedEntity; + } +} \ No newline at end of file diff --git a/Components/LinkedEntityComponent.cs.meta b/Components/LinkedEntityComponent.cs.meta new file mode 100644 index 0000000..e25cd44 --- /dev/null +++ b/Components/LinkedEntityComponent.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b2536af786e0381aa68a66f6569358bf +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/DBC.cs b/DBC.cs index 144c690..295fc45 100644 --- a/DBC.cs +++ b/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/DataStructures/FastTypeSafeDictionary.cs b/DataStructures/FastTypeSafeDictionary.cs new file mode 100644 index 0000000..15aa8ef --- /dev/null +++ b/DataStructures/FastTypeSafeDictionary.cs @@ -0,0 +1,293 @@ +#if EXPERIMENTAL +using System; +using System.Runtime.CompilerServices; +using Svelto.Common; +using Svelto.DataStructures; + +namespace Svelto.ECS.Internal +{ + sealed class FastTypeSafeDictionary : ITypeSafeDictionary where TValue : struct, IEntityComponent + { + static readonly Type _type = typeof(TValue); + static readonly string _typeName = _type.Name; + static readonly bool _hasEgid = typeof(INeedEGID).IsAssignableFrom(_type); + + public FastTypeSafeDictionary(uint size) { _implementation = new SetDictionary(size); } + + public FastTypeSafeDictionary() { _implementation = new SetDictionary(1); } + + /// + /// Add entities from external typeSafeDictionary + /// + /// + /// + /// + public void AddEntitiesFromDictionary(ITypeSafeDictionary entitiesToSubmit, uint groupId) + { + var typeSafeDictionary = entitiesToSubmit as FastTypeSafeDictionary; + + foreach (var tuple in typeSafeDictionary) + { + try + { + if (_hasEgid) + SetEGIDWithoutBoxing.SetIDWithoutBoxing(ref typeSafeDictionary.unsafeValues[tuple.Key], + new EGID(tuple.Key, groupId)); + + _implementation.Add(tuple.Value); + } + catch (Exception e) + { + throw new + TypeSafeDictionaryException("trying to add an EntityComponent with the same ID more than once Entity: ". + FastConcat(typeof(TValue).ToString()).FastConcat(", group "). + FastConcat(groupId).FastConcat(", id ").FastConcat(tuple.Key), e); + } + } + } + + public void AddEntityToDictionary(EGID fromEntityGid, EGID toEntityID, ITypeSafeDictionary toGroup) + { + var valueIndex = _implementation.GetIndex(fromEntityGid.entityID); + + if (toGroup != null) + { + var toGroupCasted = toGroup as ITypeSafeDictionary; + ref var entity = ref _implementation.unsafeValues[(int) valueIndex]; + + if (_hasEgid) SetEGIDWithoutBoxing.SetIDWithoutBoxing(ref entity, toEntityID); + + toGroupCasted.Add(fromEntityGid.entityID, entity); + } + } + + public void AddEntitiesToEngines(FasterDictionary> entityComponentEnginesDB, + ITypeSafeDictionary realDic, + ExclusiveGroupStruct @group, + in PlatformProfiler profiler) + { + var typeSafeDictionary = realDic as ITypeSafeDictionary; + + //this can be optimized, should pass all the entities and not restart the process for each one + foreach (var value in _implementation) + AddEntityComponentToEngines(entityComponentEnginesDB, ref typeSafeDictionary.GetValueByRef(value.Key), null, + in profiler, new EGID(value.Key, group)); + } + + public void RemoveEntitiesFromEngines( + FasterDictionary> entityComponentEnginesDB, in PlatformProfiler profiler, + ExclusiveGroupStruct @group) + { + foreach (var value in _implementation) + RemoveEntityComponentFromEngines(entityComponentEnginesDB, ref _implementation.GetValueByRef(value.Key), null, + in profiler, new EGID(value.Key, group)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FastClear() { _implementation.FastClear(); } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Has(uint key) { return _implementation.ContainsKey(key); } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void RemoveEntityFromDictionary(EGID fromEntityGid) + { + _implementation.Remove(fromEntityGid.entityID); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void SetCapacity(uint size) { throw new NotImplementedException(); } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Trim() { _implementation.Trim(); } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Clear() { _implementation.Clear(); } + + public void MoveEntityFromEngines(EGID fromEntityGid, EGID? toEntityID, ITypeSafeDictionary toGroup, + FasterDictionary> engines, + in PlatformProfiler profiler) + { + var valueIndex = _implementation.GetIndex(fromEntityGid.entityID); + + ref var entity = ref _implementation.unsafeValues[(int) valueIndex]; + + if (toGroup != null) + { + RemoveEntityComponentFromEngines(engines, ref entity, fromEntityGid.groupID, in profiler, fromEntityGid); + + var toGroupCasted = toGroup as ITypeSafeDictionary; + var previousGroup = fromEntityGid.groupID; + + if (_hasEgid) SetEGIDWithoutBoxing.SetIDWithoutBoxing(ref entity, toEntityID.Value); + + var index = toGroupCasted.GetIndex(toEntityID.Value.entityID); + + AddEntityComponentToEngines(engines, ref toGroupCasted.unsafeValues[(int) index], previousGroup, in profiler, + toEntityID.Value); + } + else + RemoveEntityComponentFromEngines(engines, ref entity, null, in profiler, fromEntityGid); + } + + public uint Count + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _implementation.count; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ITypeSafeDictionary Create() { return new FastTypeSafeDictionary(); } + + void AddEntityComponentToEngines(FasterDictionary> entityComponentEnginesDB, + ref TValue entity, + ExclusiveGroupStruct? previousGroup, + in PlatformProfiler profiler, + EGID egid) + { + //get all the engines linked to TValue + if (!entityComponentEnginesDB.TryGetValue(new RefWrapperType(_type), out var entityComponentsEngines)) return; + + if (previousGroup == null) + { + for (var i = 0; i < entityComponentsEngines.count; i++) + try + { + using (profiler.Sample(entityComponentsEngines[i], _typeName)) + { + (entityComponentsEngines[i] as IReactOnAddAndRemove).Add(ref entity, egid); + } + } + catch (Exception e) + { + throw new + ECSException("Code crashed inside Add callback ".FastConcat(typeof(TValue).ToString()), e); + } + } + else + { + for (var i = 0; i < entityComponentsEngines.count; i++) + try + { + using (profiler.Sample(entityComponentsEngines[i], _typeName)) + { + (entityComponentsEngines[i] as IReactOnSwap).MovedTo(ref entity, previousGroup.Value, + egid); + } + } + catch (Exception e) + { + throw new + ECSException("Code crashed inside MovedTo callback ".FastConcat(typeof(TValue).ToString()), + e); + } + } + } + + static void RemoveEntityComponentFromEngines(FasterDictionary> @group, + ref TValue entity, + ExclusiveGroupStruct? previousGroup, + in PlatformProfiler profiler, + EGID egid) + { + if (!@group.TryGetValue(new RefWrapperType(_type), out var entityComponentsEngines)) return; + + if (previousGroup == null) + { + for (var i = 0; i < entityComponentsEngines.count; i++) + try + { + using (profiler.Sample(entityComponentsEngines[i], _typeName)) + (entityComponentsEngines[i] as IReactOnAddAndRemove).Remove(ref entity, egid); + } + catch (Exception e) + { + throw new + ECSException("Code crashed inside Remove callback ".FastConcat(typeof(TValue).ToString()), + e); + } + } +#if SEEMS_UNNECESSARY + else + { + for (var i = 0; i < entityComponentsEngines.Count; i++) + try + { + using (profiler.Sample(entityComponentsEngines[i], _typeName)) + (entityComponentsEngines[i] as IReactOnSwap).MovedFrom(ref entity, egid); + } + catch (Exception e) + { + throw new ECSException( + "Code crashed inside Remove callback ".FastConcat(typeof(TValue).ToString()), e); + } + } +#endif + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public TValue[] GetValuesArray(out uint count) + { + var managedBuffer = _implementation.GetValuesArray(out count); + return managedBuffer; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool ContainsKey(uint egidEntityId) { return _implementation.ContainsKey(egidEntityId); } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Add(uint egidEntityId, in TValue entityComponent) { _implementation.Add(entityComponent); } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public SetDictionary.SetDictionaryKeyValueEnumerator GetEnumerator() + { + return _implementation.GetEnumerator(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ref TValue GetValueByRef(uint key) { return ref _implementation.GetValueByRef(key); } + + public TValue this[uint idEntityId] + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _implementation[idEntityId]; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set => _implementation[idEntityId] = value; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public uint GetIndex(uint valueEntityId) { return _implementation.GetIndex(valueEntityId); } + + public TValue[] unsafeValues + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _implementation.unsafeValues; + } + + public SetDictionary implementation => _implementation; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool TryGetValue(uint entityId, out TValue item) + { + return _implementation.TryGetValue(entityId, out item); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ref TValue GetOrCreate(uint idEntityId) { throw new NotImplementedException(); } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool TryFindIndex(uint entityId, out uint index) + { + return _implementation.TryFindIndex(entityId, out index); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ref TValue GetDirectValue(uint findElementIndex) + { + return ref _implementation.GetDirectValue(findElementIndex); + } + + readonly SetDictionary _implementation; + } +} +#endif \ No newline at end of file diff --git a/DataStructures/FastTypeSafeDictionary.cs.meta b/DataStructures/FastTypeSafeDictionary.cs.meta new file mode 100644 index 0000000..c5151b8 --- /dev/null +++ b/DataStructures/FastTypeSafeDictionary.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 5aab8c4d657d3850b22b7a8d7b0038b6 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/DataStructures/ITypeSafeDictionary.cs b/DataStructures/ITypeSafeDictionary.cs new file mode 100644 index 0000000..77d24e1 --- /dev/null +++ b/DataStructures/ITypeSafeDictionary.cs @@ -0,0 +1,47 @@ +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 this[uint idEntityId] { get; } + bool TryGetValue(uint entityId, out TValue item); + ref TValue GetOrCreate(uint idEntityId); + + IBuffer GetValues(out uint count); + ref TValue GetDirectValueByRef(uint key); + } + + public interface ITypeSafeDictionary:IDisposable + { + uint count { get; } + ITypeSafeDictionary Create(); + + //todo: there is something wrong in the design of the execute callback methods. Something to cleanup + void ExecuteEnginesAddOrSwapCallbacks(FasterDictionary> entityComponentEnginesDb, + ITypeSafeDictionary realDic, ExclusiveGroupStruct? fromGroup, ExclusiveGroupStruct toGroup, in PlatformProfiler profiler); + void ExecuteEnginesSwapOrRemoveCallbacks(EGID fromEntityGid, EGID? toEntityID, ITypeSafeDictionary toGroup, + FasterDictionary> engines, in PlatformProfiler profiler); + void ExecuteEnginesRemoveCallbacks(FasterDictionary> entityComponentEnginesDB, + in PlatformProfiler profiler, ExclusiveGroupStruct @group); + + void AddEntitiesFromDictionary(ITypeSafeDictionary entitiesToSubmit, uint groupId); + + 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); + + void KeysEvaluator(System.Action action); + } +} \ No newline at end of file diff --git a/DataStructures/ITypeSafeDictionary.cs.meta b/DataStructures/ITypeSafeDictionary.cs.meta new file mode 100644 index 0000000..3b595f0 --- /dev/null +++ b/DataStructures/ITypeSafeDictionary.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 186bc7d192ae3700b0cbd86c710b6630 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/DataStructures/SetEGIDWithoutBoxing.cs b/DataStructures/SetEGIDWithoutBoxing.cs deleted file mode 100644 index 828e598..0000000 --- a/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/DataStructures/ThreadSafeNativeBagTest.cs b/DataStructures/ThreadSafeNativeBagTest.cs new file mode 100644 index 0000000..b001c65 --- /dev/null +++ b/DataStructures/ThreadSafeNativeBagTest.cs @@ -0,0 +1,74 @@ +// using System.Threading.Tasks; +// using NUnit.Framework; +// using Svelto.Common; +// using Svelto.ECS.DataStructures; +// +// namespace Svelto.ECS.Tests.Common.DataStructures +// { +// [TestFixture] +// public class ThreadSafeNativeBagTest +// { +// [Test] +// public void TestByteReallocWorks() +// { +// var threadNativeBag = new ThreadSafeNativeBag(Allocator.Persistent); +// +// Parallel.Invoke(() => +// { +// for (int i = 0; i < 100; i++) +// { +// threadNativeBag.Enqueue((int)1); +// } +// } +// , // close first Action +// () => +// { +// for (int i = 0; i < 100; i++) +// { +// threadNativeBag.Enqueue((int)2); +// } +// } +// , //close second Action +// +// () => +// { +// for (int i = 0; i < 100; i++) +// { +// threadNativeBag.Enqueue(3); +// } +// } //close third Action +// ); //close parallel.invoke +// +// // for (int i = 0; i < 100; i++) +// // { +// // threadNativeBag.Enqueue(1); +// // } +// +// int oneCount = 0, twoCount = 0, threeCount = 0; +// +// while (threadNativeBag.count > 0) +// { +// var value = threadNativeBag.Dequeue(); +// +// switch (value) +// { +// case 1: +// oneCount++; +// break; +// case 2: +// twoCount++; +// break; +// case 3: +// threeCount++; +// break; +// } +// } +// +// Assert.That(oneCount, Is.EqualTo(100)); +// Assert.That(twoCount, Is.EqualTo(100)); +// Assert.That(threeCount, Is.EqualTo(100)); +// +// threadNativeBag.Dispose(); +// } +// } +// } \ No newline at end of file diff --git a/DataStructures/ThreadSafeNativeBagTest.cs.meta b/DataStructures/ThreadSafeNativeBagTest.cs.meta new file mode 100644 index 0000000..8a36335 --- /dev/null +++ b/DataStructures/ThreadSafeNativeBagTest.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 6783b8d49c5935fd8f863f6d78e003ef +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/DataStructures/TypeSafeDictionary.cs b/DataStructures/TypeSafeDictionary.cs index d902d72..c74f124 100644 --- a/DataStructures/TypeSafeDictionary.cs +++ b/DataStructures/TypeSafeDictionary.cs @@ -1,222 +1,536 @@ using System; +using System.Runtime.CompilerServices; using Svelto.Common; using Svelto.DataStructures; +using Svelto.ECS.Hybrid; namespace Svelto.ECS.Internal { - public interface ITypeSafeDictionary + sealed class TypeSafeDictionary : ITypeSafeDictionary where TValue : struct, IEntityComponent { - int Count { get; } - ITypeSafeDictionary Create(); + static readonly Type _type = typeof(TValue); + static readonly string _typeName = _type.Name; + static readonly bool _hasEgid = typeof(INeedEGID).IsAssignableFrom(_type); - void AddEntitiesToEngines( - FasterDictionary, FasterList> entityViewEnginesDb, - ITypeSafeDictionary realDic, in PlatformProfiler profiler, ExclusiveGroup.ExclusiveGroupStruct @group); + internal static readonly bool IsUnmanaged = + _type.IsUnmanagedEx() && (typeof(IEntityViewComponent).IsAssignableFrom(_type) == false); - void RemoveEntitiesFromEngines(FasterDictionary, FasterList> entityViewEnginesDB, - in PlatformProfiler profiler, ExclusiveGroup.ExclusiveGroupStruct @group); + SveltoDictionary>, ManagedStrategy, + ManagedStrategy> implMgd; - void AddEntitiesFromDictionary(ITypeSafeDictionary entitiesToSubmit, uint groupId); + //used directly by native methods + internal SveltoDictionary>, NativeStrategy, + NativeStrategy> implUnmgd; - void MoveEntityFromEngines(EGID fromEntityGid, EGID? toEntityID, ITypeSafeDictionary toGroup, - FasterDictionary, FasterList> engines, in PlatformProfiler profiler); + public TypeSafeDictionary(uint size) + { + if (IsUnmanaged) + implUnmgd = new SveltoDictionary>, + NativeStrategy, NativeStrategy>(size); + else + { + implMgd = new SveltoDictionary>, + ManagedStrategy, ManagedStrategy>(size); + } + } - void AddEntityToDictionary(EGID fromEntityGid, EGID toEntityID, ITypeSafeDictionary toGroup); - void RemoveEntityFromDictionary(EGID fromEntityGid, in PlatformProfiler profiler); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Add(uint egidEntityId, in TValue entityComponent) + { + if (IsUnmanaged) + implUnmgd.Add(egidEntityId, entityComponent); + else + implMgd.Add(egidEntityId, entityComponent); + } - void SetCapacity(uint size); - void Trim(); - void Clear(); - void FastClear(); - bool Has(uint entityIdEntityId); - } + /// + /// Add entities from external typeSafeDictionary + /// + /// + /// + /// + public void AddEntitiesFromDictionary(ITypeSafeDictionary entitiesToSubmit, uint groupId) + { + if (IsUnmanaged) + { + var typeSafeDictionary = (entitiesToSubmit as TypeSafeDictionary).implUnmgd; - class TypeSafeDictionary : FasterDictionary, - ITypeSafeDictionary where TValue : struct, IEntityStruct - { - static readonly Type _type = typeof(TValue); - static readonly string _typeName = _type.Name; - static readonly bool _hasEgid = typeof(INeedEGID).IsAssignableFrom(_type); + foreach (var tuple in typeSafeDictionary) + try + { + if (_hasEgid) + SetEGIDWithoutBoxing.SetIDWithoutBoxing( + ref tuple.Value, new EGID(tuple.Key, groupId)); - public TypeSafeDictionary(uint size) : base(size) {} - public TypeSafeDictionary() {} + implUnmgd.Add(tuple.Key, tuple.Value); + } + catch (Exception e) + { + Console.LogException( + e, "trying to add an EntityComponent with the same ID more than once Entity: ".FastConcat(typeof(TValue).ToString()).FastConcat(", group ").FastConcat(groupId).FastConcat(", id ").FastConcat(tuple.Key)); - public void AddEntitiesFromDictionary(ITypeSafeDictionary entitiesToSubmit, uint groupId) + throw; + } + } + else + { + var typeSafeDictionary = (entitiesToSubmit as TypeSafeDictionary).implMgd; + + foreach (var tuple in typeSafeDictionary) + try + { + if (_hasEgid) + SetEGIDWithoutBoxing.SetIDWithoutBoxing( + ref tuple.Value, new EGID(tuple.Key, groupId)); + + implMgd.Add(tuple.Key, tuple.Value); + } + catch (Exception e) + { + Console.LogException( + e, "trying to add an EntityComponent with the same ID more than once Entity: ".FastConcat(typeof(TValue).ToString()).FastConcat(", group ").FastConcat(groupId).FastConcat(", id ").FastConcat(tuple.Key)); + + throw; + } + } + } + + public void ExecuteEnginesAddOrSwapCallbacks + (FasterDictionary> entityComponentEnginesDB + , ITypeSafeDictionary realDic, ExclusiveGroupStruct? fromGroup, ExclusiveGroupStruct toGroup + , in PlatformProfiler profiler) { - var typeSafeDictionary = entitiesToSubmit as TypeSafeDictionary; + if (IsUnmanaged) + { + var typeSafeDictionary = realDic as ITypeSafeDictionary; - foreach (var tuple in typeSafeDictionary) + //this can be optimized, should pass all the entities and not restart the process for each one + foreach (var value in implUnmgd) + ExecuteEnginesAddOrSwapCallbacksOnSingleEntity(entityComponentEnginesDB, ref typeSafeDictionary[value.Key] + , fromGroup, in profiler, new EGID(value.Key, toGroup)); + } + else { - try + 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 implMgd) + ExecuteEnginesAddOrSwapCallbacksOnSingleEntity(entityComponentEnginesDB, ref typeSafeDictionary[value.Key] + , fromGroup, in profiler, new EGID(value.Key, toGroup)); + } + } + + public void AddEntityToDictionary(EGID fromEntityGid, EGID toEntityID, ITypeSafeDictionary toGroup) + { + if (IsUnmanaged) + { + var valueIndex = implUnmgd.GetIndex(fromEntityGid.entityID); + + DBC.ECS.Check.Require(toGroup != null + , "Invalid To Group"); //todo check this, if it's right merge GetIndex { - if (_hasEgid) SetEGIDWithoutBoxing.SetIDWithoutBoxing(ref tuple.Value, new EGID(tuple.Key, groupId)); + var toGroupCasted = toGroup as ITypeSafeDictionary; + ref var entity = ref implUnmgd.GetDirectValueByRef(valueIndex); - Add(tuple.Key, tuple.Value); + if (_hasEgid) + SetEGIDWithoutBoxing.SetIDWithoutBoxing(ref entity, toEntityID); + + toGroupCasted.Add(toEntityID.entityID, entity); } - catch (Exception e) + } + else + { + var valueIndex = implMgd.GetIndex(fromEntityGid.entityID); + + DBC.ECS.Check.Require(toGroup != null + , "Invalid To Group"); //todo check this, if it's right merge GetIndex { - 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); + var toGroupCasted = toGroup as ITypeSafeDictionary; + ref var entity = ref implMgd.GetDirectValueByRef(valueIndex); + + if (_hasEgid) + SetEGIDWithoutBoxing.SetIDWithoutBoxing(ref entity, toEntityID); + + toGroupCasted.Add(toEntityID.entityID, entity); } } } - public void AddEntitiesToEngines( - FasterDictionary, FasterList> entityViewEnginesDB, - ITypeSafeDictionary realDic, in PlatformProfiler profiler, ExclusiveGroup.ExclusiveGroupStruct @group) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Clear() { - var typeSafeDictionary = realDic as TypeSafeDictionary; - - //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)); + if (IsUnmanaged) + { + implUnmgd.Clear(); + } + else + { + implMgd.Clear(); + } } - public void RemoveEntitiesFromEngines( - FasterDictionary, FasterList> entityViewEnginesDB, - in PlatformProfiler profiler, ExclusiveGroup.ExclusiveGroupStruct @group) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FastClear() { - foreach (var value in this) - RemoveEntityViewFromEngines(entityViewEnginesDB, ref GetValueByRef(value.Key), null, in profiler, - new EGID(value.Key, group)); + if (IsUnmanaged) + { + implUnmgd.FastClear(); + } + else + { + implMgd.FastClear(); + } } - public bool Has(uint entityIdEntityId) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool ContainsKey(uint egidEntityId) { - return ContainsKey(entityIdEntityId); + if (IsUnmanaged) + { + return implUnmgd.ContainsKey(egidEntityId); + } + else + { + return implMgd.ContainsKey(egidEntityId); + } } - public void RemoveEntityFromDictionary(EGID fromEntityGid, in PlatformProfiler profiler) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ITypeSafeDictionary Create() { return TypeSafeDictionaryFactory.Create(1); } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public uint GetIndex(uint valueEntityId) { - Remove(fromEntityGid.entityID); + if (IsUnmanaged) + { + return this.implUnmgd.GetIndex(valueEntityId); + } + else + { + return this.implMgd.GetIndex(valueEntityId); + } } - public void AddEntityToDictionary(EGID fromEntityGid, EGID toEntityID, ITypeSafeDictionary toGroup) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ref TValue GetOrCreate(uint idEntityId) { - var valueIndex = GetIndex(fromEntityGid.entityID); + if (IsUnmanaged) + { + return ref this.implUnmgd.GetOrCreate(idEntityId); + } + else + { + return ref this.implMgd.GetOrCreate(idEntityId); + } + } - if (toGroup != null) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public IBuffer GetValues(out uint count) + { + if (IsUnmanaged) { - var toGroupCasted = toGroup as TypeSafeDictionary; - ref var entity = ref valuesArray[valueIndex]; + return this.implUnmgd.GetValues(out count); + } + else + { + return this.implMgd.GetValues(out count); + } + } - if (_hasEgid) SetEGIDWithoutBoxing.SetIDWithoutBoxing(ref entity, toEntityID); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ref TValue GetDirectValueByRef(uint key) + { + if (IsUnmanaged) + { + return ref this.implUnmgd.GetDirectValueByRef(key); + } + else + { + return ref this.implMgd.GetDirectValueByRef(key); + } + } - toGroupCasted.Add(fromEntityGid.entityID, entity); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Has(uint key) + { + if (IsUnmanaged) + { + return this.implUnmgd.ContainsKey(key); + } + else + { + return this.implMgd.ContainsKey(key); } } - public void MoveEntityFromEngines(EGID fromEntityGid, EGID? toEntityID, ITypeSafeDictionary toGroup, - FasterDictionary, FasterList> engines, in PlatformProfiler profiler) + public void ExecuteEnginesSwapOrRemoveCallbacks + (EGID fromEntityGid, EGID? toEntityID, ITypeSafeDictionary toGroup + , FasterDictionary> engines, in PlatformProfiler profiler) { - var valueIndex = GetIndex(fromEntityGid.entityID); - - ref var entity = ref valuesArray[valueIndex]; + if (IsUnmanaged) + { + var valueIndex = this.implUnmgd.GetIndex(fromEntityGid.entityID); - if (toGroup != null) + ref var entity = ref this.implUnmgd.GetDirectValueByRef(valueIndex); + + //move + if (toGroup != null) + { + var toGroupCasted = toGroup as ITypeSafeDictionary; + var previousGroup = fromEntityGid.groupID; + + if (_hasEgid) + SetEGIDWithoutBoxing.SetIDWithoutBoxing(ref entity, toEntityID.Value); + + var index = toGroupCasted.GetIndex(toEntityID.Value.entityID); + + ExecuteEnginesAddOrSwapCallbacksOnSingleEntity(engines, ref toGroupCasted.GetDirectValueByRef(index) + , previousGroup, in profiler, toEntityID.Value); + } + //remove + else + { + ExecuteEnginesRemoveCallbackOnSingleEntity(engines, ref entity, in profiler, fromEntityGid); + } + } + else { - RemoveEntityViewFromEngines(engines, ref entity, fromEntityGid.groupID, in profiler, - fromEntityGid); + var valueIndex = this.implMgd.GetIndex(fromEntityGid.entityID); + + ref var entity = ref this.implMgd.GetDirectValueByRef(valueIndex); - var toGroupCasted = toGroup as TypeSafeDictionary; - var previousGroup = fromEntityGid.groupID; + if (toGroup != null) + { + var toGroupCasted = toGroup as ITypeSafeDictionary; + var previousGroup = fromEntityGid.groupID; - if (_hasEgid) SetEGIDWithoutBoxing.SetIDWithoutBoxing(ref entity, toEntityID.Value); + if (_hasEgid) + SetEGIDWithoutBoxing.SetIDWithoutBoxing(ref entity, toEntityID.Value); - var index = toGroupCasted.GetIndex(toEntityID.Value.entityID); + var index = toGroupCasted.GetIndex(toEntityID.Value.entityID); - AddEntityViewToEngines(engines, ref toGroupCasted.valuesArray[index], previousGroup, - in profiler, toEntityID.Value); + ExecuteEnginesAddOrSwapCallbacksOnSingleEntity(engines, ref toGroupCasted.GetDirectValueByRef(index) + , previousGroup, in profiler, toEntityID.Value); + } + else + { + ExecuteEnginesRemoveCallbackOnSingleEntity(engines, ref entity, in profiler, fromEntityGid); + } + } + } + + public void ExecuteEnginesRemoveCallbacks + (FasterDictionary> engines, in PlatformProfiler profiler + , ExclusiveGroupStruct group) + { + if (IsUnmanaged) + { + foreach (var value in implUnmgd) + ExecuteEnginesRemoveCallbackOnSingleEntity(engines, ref implUnmgd.GetValueByRef(value.Key) + , in profiler, new EGID(value.Key, group)); } else - RemoveEntityViewFromEngines(engines, ref entity, null, in profiler, fromEntityGid); + { + foreach (var value in implMgd) + ExecuteEnginesRemoveCallbackOnSingleEntity(engines, ref implMgd.GetValueByRef(value.Key) + , in profiler, new EGID(value.Key, group)); + } } - public ITypeSafeDictionary Create() + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void RemoveEntityFromDictionary(EGID fromEntityGid) { - return new TypeSafeDictionary(); + if (IsUnmanaged) + { + this.implUnmgd.Remove(fromEntityGid.entityID); + } + else + { + this.implMgd.Remove(fromEntityGid.entityID); + } } - void AddEntityViewToEngines(FasterDictionary, FasterList> entityViewEnginesDB, - ref TValue entity, ExclusiveGroup.ExclusiveGroupStruct? previousGroup, - in PlatformProfiler profiler, EGID egid) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void SetCapacity(uint size) { - //get all the engines linked to TValue - if (!entityViewEnginesDB.TryGetValue(new RefWrapper(_type), out var entityViewsEngines)) return; + if (IsUnmanaged) + { + this.implUnmgd.SetCapacity(size); + } + else + { + this.implMgd.SetCapacity(size); + } + } - if (previousGroup == null) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Trim() + { + if (IsUnmanaged) { - for (var i = 0; i < entityViewsEngines.Count; i++) - try - { - using (profiler.Sample(entityViewsEngines[i], _typeName)) - { - (entityViewsEngines[i] as IReactOnAddAndRemove).Add(ref entity, egid); - } - } - catch (Exception e) - { - throw new ECSException( - "Code crashed inside Add callback ".FastConcat(typeof(TValue).ToString()), e); - } + this.implUnmgd.Trim(); } else { - for (var i = 0; i < entityViewsEngines.Count; i++) - try - { - using (profiler.Sample(entityViewsEngines[i], _typeName)) - { - (entityViewsEngines[i] as IReactOnSwap).MovedTo(ref entity, previousGroup.Value, - egid); - } - } - catch (Exception e) + this.implMgd.Trim(); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool TryFindIndex(uint entityId, out uint index) + { + if (IsUnmanaged) + { + return implUnmgd.TryFindIndex(entityId, out index); + } + else + { + return implMgd.TryFindIndex(entityId, out index); + } + } + + public void KeysEvaluator(Action action) + { + if (IsUnmanaged) + { + foreach (var key in implUnmgd.keys) + { + action(key); + } + } + else + { + foreach (var key in implMgd.keys) + { + action(key); + } + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool TryGetValue(uint entityId, out TValue item) + { + if (IsUnmanaged) + { + return this.implUnmgd.TryGetValue(entityId, out item); + } + else + { + return this.implMgd.TryGetValue(entityId, out item); + } + } + + public uint count + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + if (IsUnmanaged) + { + return (uint) this.implUnmgd.count; + } + else + { + return (uint) this.implMgd.count; + } + } + } + + public ref TValue this[uint idEntityId] + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + if (IsUnmanaged) + { + return ref this.implUnmgd.GetValueByRef(idEntityId); + } + else + { + return ref this.implMgd.GetValueByRef(idEntityId); + } + } + } + + static void ExecuteEnginesRemoveCallbackOnSingleEntity + (FasterDictionary> engines, ref TValue entity + , in PlatformProfiler profiler, EGID egid) + { + if (!engines.TryGetValue(new RefWrapperType(_type), out var entityComponentsEngines)) + return; + + for (var i = 0; i < entityComponentsEngines.count; i++) + try + { + using (profiler.Sample(entityComponentsEngines[i], _typeName)) { - throw new ECSException( - "Code crashed inside MovedTo callback ".FastConcat(typeof(TValue).ToString()), e); + (entityComponentsEngines[i] as IReactOnAddAndRemove).Remove(ref entity, egid); } - } + } + catch + { + Svelto.Console.LogError( + "Code crashed inside Remove callback ".FastConcat(typeof(TValue).ToString())); + + throw; + } } - static void RemoveEntityViewFromEngines( - FasterDictionary, FasterList> @group, ref TValue entity, - ExclusiveGroup.ExclusiveGroupStruct? previousGroup, in PlatformProfiler profiler, EGID egid) + void ExecuteEnginesAddOrSwapCallbacksOnSingleEntity + (FasterDictionary> engines, ref TValue entity + , ExclusiveGroupStruct? previousGroup, in PlatformProfiler profiler, EGID egid) { - if (!@group.TryGetValue(new RefWrapper(_type), out var entityViewsEngines)) return; + //get all the engines linked to TValue + if (!engines.TryGetValue(new RefWrapperType(_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).Add(ref entity, egid); + } } - catch (Exception e) + catch { - throw new ECSException( - "Code crashed inside Remove callback ".FastConcat(typeof(TValue).ToString()), e); + Svelto.Console.LogError( + "Code crashed inside Add callback ".FastConcat(typeof(TValue).ToString())); + + throw; } } -#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).MovedTo( + ref entity, previousGroup.Value, egid); + } } - catch (Exception e) + catch (Exception) { - throw new ECSException( - "Code crashed inside Remove callback ".FastConcat(typeof(TValue).ToString()), e); + Svelto.Console.LogError( + "Code crashed inside MoveTo callback ".FastConcat(typeof(TValue).ToString())); + + throw; } } -#endif + } + + public void Dispose() + { + if (IsUnmanaged) + implUnmgd.Dispose(); + else + implMgd.Dispose(); + + GC.SuppressFinalize(this); } } } \ No newline at end of file diff --git a/DataStructures/TypeSafeDictionaryUtilities.cs b/DataStructures/TypeSafeDictionaryUtilities.cs new file mode 100644 index 0000000..57af0d1 --- /dev/null +++ b/DataStructures/TypeSafeDictionaryUtilities.cs @@ -0,0 +1,13 @@ +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; + } + } +} \ No newline at end of file diff --git a/DataStructures/TypeSafeDictionaryUtilities.cs.meta b/DataStructures/TypeSafeDictionaryUtilities.cs.meta new file mode 100644 index 0000000..6cc8686 --- /dev/null +++ b/DataStructures/TypeSafeDictionaryUtilities.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a2196c9108d435e6ac91b30c0aed8644 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/DataStructures/Unmanaged.meta b/DataStructures/Unmanaged.meta new file mode 100644 index 0000000..1a29363 --- /dev/null +++ b/DataStructures/Unmanaged.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: c887c28f847537e58b00adf544344895 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/DataStructures/Unmanaged/AtomicNativeBags.cs b/DataStructures/Unmanaged/AtomicNativeBags.cs new file mode 100644 index 0000000..3d9208e --- /dev/null +++ b/DataStructures/Unmanaged/AtomicNativeBags.cs @@ -0,0 +1,76 @@ +#if UNITY_NATIVE //because of the thread count, ATM this is only for unity +using System; +using System.Runtime.CompilerServices; +using Svelto.Common; +using Unity.Jobs.LowLevel.Unsafe; +using Allocator = Svelto.Common.Allocator; + +namespace Svelto.ECS.DataStructures +{ + public unsafe struct AtomicNativeBags:IDisposable + { + [global::Unity.Collections.LowLevel.Unsafe.NativeDisableUnsafePtrRestriction] + + NativeBag* _data; + readonly Allocator _allocator; + readonly uint _threadsCount; + + public uint count => _threadsCount; + + public AtomicNativeBags(Allocator allocator) + { + _allocator = allocator; + _threadsCount = JobsUtility.MaxJobThreadCount + 1; + + var bufferSize = MemoryUtilities.SizeOf(); + var bufferCount = _threadsCount; + var allocationSize = bufferSize * bufferCount; + + var ptr = (byte*)MemoryUtilities.Alloc((uint) allocationSize, allocator); + // MemoryUtilities.MemClear((IntPtr) ptr, (uint) allocationSize); + + for (int i = 0; i < bufferCount; i++) + { + var bufferPtr = (NativeBag*)(ptr + bufferSize * i); + var buffer = new NativeBag(allocator); + MemoryUtilities.CopyStructureToPtr(ref buffer, (IntPtr) bufferPtr); + } + + _data = (NativeBag*)ptr; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ref NativeBag GetBuffer(int index) + { + if (_data == null) + throw new Exception("using invalid AtomicNativeBags"); + + return ref MemoryUtilities.ArrayElementAsRef((IntPtr) _data, index); + } + + public void Dispose() + { + if (_data == null) + throw new Exception("using invalid AtomicNativeBags"); + + for (int i = 0; i < _threadsCount; i++) + { + GetBuffer(i).Dispose(); + } + MemoryUtilities.Free((IntPtr) _data, _allocator); + _data = null; + } + + public void Clear() + { + if (_data == null) + throw new Exception("using invalid AtomicNativeBags"); + + for (int i = 0; i < _threadsCount; i++) + { + GetBuffer(i).Clear(); + } + } + } +} +#endif \ No newline at end of file diff --git a/DataStructures/Unmanaged/AtomicNativeBags.cs.meta b/DataStructures/Unmanaged/AtomicNativeBags.cs.meta new file mode 100644 index 0000000..6a94442 --- /dev/null +++ b/DataStructures/Unmanaged/AtomicNativeBags.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 9903f36f7416334c99d89982bf84cf2a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/DataStructures/Unmanaged/NativeBag.cs b/DataStructures/Unmanaged/NativeBag.cs new file mode 100644 index 0000000..96e89c6 --- /dev/null +++ b/DataStructures/Unmanaged/NativeBag.cs @@ -0,0 +1,281 @@ +#if DEBUG && !PROFILE_SVELTO +#define ENABLE_DEBUG_CHECKS +#endif + +#if DEBUG && !PROFILE_SVELTO +//#define ENABLE_THREAD_SAFE_CHECKS +#endif + +using System; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Threading; +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 NativeBag 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 + { + public uint count + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + unsafe + { + BasicTests(); +#if ENABLE_THREAD_SAFE_CHECKS + try + { +#endif + return _queue->size; +#if ENABLE_THREAD_SAFE_CHECKS + } + finally + { + Volatile.Write(ref _threadSentinel, 0); + } +#endif + } + } + } + + public uint capacity + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + unsafe + { + BasicTests(); +#if ENABLE_THREAD_SAFE_CHECKS + try + { +#endif + return _queue->capacity; +#if ENABLE_THREAD_SAFE_CHECKS + } + finally + { + Volatile.Write(ref _threadSentinel, 0); + } +#endif + } + } + } + + public NativeBag(Allocator allocator) + { + unsafe + { + var sizeOf = MemoryUtilities.SizeOf(); + var listData = (UnsafeBlob*) MemoryUtilities.Alloc((uint) sizeOf, allocator); + + //clear to nullify the pointers + //MemoryUtilities.MemClear((IntPtr) listData, (uint) sizeOf); + listData->allocator = allocator; + _queue = listData; +#if ENABLE_THREAD_SAFE_CHECKS + _threadSentinel = 0; +#endif + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool IsEmpty() + { + unsafe + { + BasicTests(); +#if ENABLE_THREAD_SAFE_CHECKS + try + { +#endif + if (_queue == null || _queue->ptr == null) + return true; +#if ENABLE_THREAD_SAFE_CHECKS + } + finally + { + Volatile.Write(ref _threadSentinel, 0); + } +#endif + } + + return count == 0; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public unsafe void Dispose() + { + if (_queue != null) + { +#if ENABLE_THREAD_SAFE_CHECKS + //todo: this must be unit tested + if (Interlocked.CompareExchange(ref _threadSentinel, 1, 0) != 0) + throw new Exception("NativeBag is not thread safe, reading and writing operations can happen" + + "on different threads, but not simultaneously"); + + try + { +#endif + _queue->Dispose(); + MemoryUtilities.Free((IntPtr) _queue, _queue->allocator); + _queue = null; +#if ENABLE_THREAD_SAFE_CHECKS + } + finally + { + Volatile.Write(ref _threadSentinel, 0); + } +#endif + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ref T ReserveEnqueue(out UnsafeArrayIndex index) where T : struct + { + unsafe + { + BasicTests(); + + var sizeOf = MemoryUtilities.SizeOf(); + if (_queue->space - sizeOf < 0) + _queue->Realloc((uint) ((_queue->capacity + MemoryUtilities.Align4((uint) sizeOf)) * 2.0f)); + +#if ENABLE_THREAD_SAFE_CHECKS + try + { +#endif + + return ref _queue->Reserve(out index); +#if ENABLE_THREAD_SAFE_CHECKS + } + finally + { + Volatile.Write(ref _threadSentinel, 0); + } +#endif + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Enqueue(in T item) where T : struct + { + unsafe + { + BasicTests(); + +#if ENABLE_THREAD_SAFE_CHECKS + try + { +#endif + var sizeOf = MemoryUtilities.SizeOf(); + if (_queue->space - sizeOf < 0) + _queue->Realloc((uint) ((_queue->capacity + MemoryUtilities.Align4((uint) sizeOf)) * 2.0f)); + + _queue->Write(item); +#if ENABLE_THREAD_SAFE_CHECKS + } + finally + { + Volatile.Write(ref _threadSentinel, 0); + } +#endif + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Clear() + { + unsafe + { + BasicTests(); +#if ENABLE_THREAD_SAFE_CHECKS + try + { +#endif + _queue->Clear(); +#if ENABLE_THREAD_SAFE_CHECKS + } + finally + { + Volatile.Write(ref _threadSentinel, 0); + } +#endif + } + } + + public T Dequeue() where T : struct + { + unsafe + { + BasicTests(); +#if ENABLE_THREAD_SAFE_CHECKS + try + { +#endif + return _queue->Read(); +#if ENABLE_THREAD_SAFE_CHECKS + } + finally + { + Volatile.Write(ref _threadSentinel, 0); + } +#endif + } + } + + internal ref T AccessReserved(UnsafeArrayIndex reserverIndex) where T : struct + { + unsafe + { + BasicTests(); +#if ENABLE_THREAD_SAFE_CHECKS + try + { +#endif + return ref _queue->AccessReserved(reserverIndex); +#if ENABLE_THREAD_SAFE_CHECKS + } + finally + { + Volatile.Write(ref _threadSentinel, 0); + } +#endif + } + } + + [Conditional("ENABLE_DEBUG_CHECKS")] + unsafe void BasicTests() + { + if (_queue == null) + throw new Exception("SimpleNativeArray: null-access"); +#if ENABLE_THREAD_SAFE_CHECKS + todo: this must be unit tested + if (Interlocked.CompareExchange(ref _threadSentinel, 1, 0) != 0) + throw new Exception("NativeBag is not thread safe, reading and writing operations can happen" + + "on different threads, but not simultaneously"); +#endif + } + +#if ENABLE_THREAD_SAFE_CHECKS + int _threadSentinel; +#endif +#if UNITY_NATIVE + [global::Unity.Collections.LowLevel.Unsafe.NativeDisableUnsafePtrRestriction] +#endif + unsafe UnsafeBlob* _queue; + } +} \ No newline at end of file diff --git a/DataStructures/Unmanaged/NativeBag.cs.meta b/DataStructures/Unmanaged/NativeBag.cs.meta new file mode 100644 index 0000000..f813053 --- /dev/null +++ b/DataStructures/Unmanaged/NativeBag.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 5775773e479e3b1db95b31e996594fb8 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/DataStructures/Unmanaged/NativeDynamicArray.cs b/DataStructures/Unmanaged/NativeDynamicArray.cs new file mode 100644 index 0000000..86771e8 --- /dev/null +++ b/DataStructures/Unmanaged/NativeDynamicArray.cs @@ -0,0 +1,370 @@ +using System; +using System.Runtime.CompilerServices; +using Svelto.Common; +using Allocator = Svelto.Common.Allocator; + +namespace Svelto.ECS.DataStructures +{ + public struct NativeDynamicArray : IDisposable + { + public bool isValid + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + unsafe + { + return _list != null; + } + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int Count() where T : struct + { + unsafe + { +#if DEBUG && !PROFILE_SVELTO + if (_list == null) + throw new Exception("NativeDynamicArray: null-access"); + if (_hashType != TypeHash.hash) + throw new Exception($"NativeDynamicArray: not expected type used"); + +#endif + return (_list->count / MemoryUtilities.SizeOf()); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int Capacity() where T : struct + { + unsafe + { +#if DEBUG && !PROFILE_SVELTO + if (_list == null) + throw new Exception("NativeDynamicArray: null-access"); + if (_hashType != TypeHash.hash) + throw new Exception("NativeDynamicArray: not expected type used"); + +#endif + return (_list->capacity / MemoryUtilities.SizeOf()); + } + } + + public static NativeDynamicArray Alloc(Allocator allocator, uint newLength = 0) where T : struct + { + unsafe + { +#if DEBUG && !PROFILE_SVELTO + var rtnStruc = new NativeDynamicArray {_hashType = TypeHash.hash}; +#else + NativeDynamicArray rtnStruc = default; +#endif + var sizeOf = MemoryUtilities.SizeOf(); + + uint structSize = (uint) MemoryUtilities.SizeOf(); + UnsafeArray* listData = (UnsafeArray*) MemoryUtilities.Alloc(structSize, allocator); + + //clear to nullify the pointers + //MemoryUtilities.MemClear((IntPtr) listData, structSize); + + rtnStruc._allocator = allocator; + listData->Realloc((uint) (newLength * sizeOf), allocator); + + rtnStruc._list = listData; + + return rtnStruc; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ref T Get(uint index) where T : struct + { + unsafe + { +#if DEBUG && !PROFILE_SVELTO + if (_list == null) + throw new Exception("NativeDynamicArray: null-access"); + if (_hashType != TypeHash.hash) + throw new Exception("NativeDynamicArray: not expected type used"); + if (index >= Count()) + throw new Exception($"NativeDynamicArray: 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 : struct + { + unsafe + { +#if DEBUG && !PROFILE_SVELTO + if (_list == null) + throw new Exception("NativeDynamicArray: null-access"); + if (_hashType != TypeHash.hash) + throw new Exception("NativeDynamicArray: not expected type used"); + if (index >= Capacity()) + throw new Exception($"NativeDynamicArray: out of bound access, index {index} capacity {Capacity()}"); +#endif + _list->Set(index, value); + } + } + + public unsafe void Dispose() + { +#if DEBUG && !PROFILE_SVELTO + if (_list == null) + throw new Exception("NativeDynamicArray: null-access"); +#endif + _list->Dispose(_allocator); + _list = null; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Add(in T item) where T : struct + { + unsafe + { +#if DEBUG && !PROFILE_SVELTO + if (_list == null) + throw new Exception("NativeDynamicArray: null-access"); + if (_hashType != TypeHash.hash) + throw new Exception("NativeDynamicArray: not expected type used"); +#endif + var structSize = (uint) MemoryUtilities.SizeOf(); + + if (_list->space - (int) structSize < 0) + _list->Realloc((uint) (((uint) ((Count() + 1) * 1.5f) * (float) structSize)), _allocator); + + _list->Add(item); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ref T AddAt(uint index) where T : struct + { + unsafe + { +#if DEBUG && !PROFILE_SVELTO + if (_list == null) + throw new Exception("NativeDynamicArray: null-access"); + if (_hashType != TypeHash.hash) + throw new Exception("NativeDynamicArray: not expected type used"); +#endif + var structSize = (uint) MemoryUtilities.SizeOf(); + + if (index >= Capacity()) + _list->Realloc((uint) (((index + 1) * 1.5f) * structSize), _allocator); + + var writeIndex = (index + 1) * structSize; + if (_list->count < writeIndex) + _list->SetCountTo(writeIndex); + + return ref _list->Get(index); + } + } + + public void Grow(uint newCapacity) where T : struct + { + unsafe + { +#if DEBUG && !PROFILE_SVELTO + if (_list == null) + throw new Exception("NativeDynamicArray: null-access"); + if (_hashType != TypeHash.hash) + throw new Exception("NativeDynamicArray: not expected type used"); + if (newCapacity <= Capacity()) + throw new Exception("New capacity must be greater than current one"); +#endif + uint structSize = (uint) MemoryUtilities.SizeOf(); + + uint size = (uint) (newCapacity * structSize); + _list->Realloc((uint) size, _allocator); + } + } + + public void SetCount(uint count) where T : struct + { + unsafe + { +#if DEBUG && !PROFILE_SVELTO + if (_list == null) + throw new Exception("NativeDynamicArray: null-access"); + if (_hashType != TypeHash.hash) + throw new Exception("NativeDynamicArray: not expected type used"); +#endif + uint structSize = (uint) MemoryUtilities.SizeOf(); + uint size = (uint) (count * structSize); + + _list->SetCountTo((uint) size); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void AddWithoutGrow(in T item) where T : struct + { + unsafe + { +#if DEBUG && !PROFILE_SVELTO + if (_list == null) + throw new Exception("NativeDynamicArray: null-access"); + if (_hashType != TypeHash.hash) + throw new Exception("NativeDynamicArray: not expected type used"); + + var structSize = (uint) MemoryUtilities.SizeOf(); + + if (_list->space - (int)structSize < 0) + throw new Exception("NativeDynamicArray: no writing authorized"); +#endif + _list->Add(item); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void UnorderedRemoveAt(uint index) where T : struct + { + unsafe + { +#if DEBUG && !PROFILE_SVELTO + if (_list == null) + throw new Exception("NativeDynamicArray: null-access"); + if (_hashType != TypeHash.hash) + throw new Exception("NativeDynamicArray: not expected type used"); + if (Count() == 0) + throw new Exception("NativeDynamicArray: empty array invalid operation"); +#endif + var indexToMove = Count() - 1; + if (index < indexToMove) + { + Set(index, Get((uint) indexToMove)); + } + + _list->Pop(); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FastClear() + { + unsafe + { +#if DEBUG && !PROFILE_SVELTO + if (_list == null) + throw new Exception("NativeDynamicArray: null-access"); +#endif + _list->Clear(); + } + } + + public unsafe T* ToPTR() where T : unmanaged + { +#if DEBUG && !PROFILE_SVELTO + if (_list == null) + throw new Exception("NativeDynamicArray: null-access"); + if (_hashType != TypeHash.hash) + throw new Exception("NativeDynamicArray: not expected type used"); + +#endif + return (T*) _list->ptr; + } + + public IntPtr ToIntPTR() where T : struct + { + unsafe + { +#if DEBUG && !PROFILE_SVELTO + if (_list == null) + throw new Exception("NativeDynamicArray: null-access"); + if (_hashType != TypeHash.hash) + throw new Exception("NativeDynamicArray: not expected type used"); + +#endif + return (IntPtr) _list->ptr; + } + } + + public T[] ToManagedArray() where T : unmanaged + { + unsafe + { +#if DEBUG && !PROFILE_SVELTO + if (_list == null) + throw new Exception("NativeDynamicArray: null-access"); + if (_hashType != TypeHash.hash) + throw new Exception("NativeDynamicArray: not expected type used"); + +#endif + var count = Count(); + var ret = new T[count]; + var lengthToCopyInBytes = count * MemoryUtilities.SizeOf(); + + fixed (void* handle = ret) + { + Unsafe.CopyBlock(handle, _list->ptr, (uint) lengthToCopyInBytes); + } + + return ret; + } + } + + public T[] ToManagedArrayUntrimmed() where T : unmanaged + { + unsafe + { +#if DEBUG && !PROFILE_SVELTO + if (_list == null) + throw new Exception("NativeDynamicArray: null-access"); + if (_hashType != TypeHash.hash) + throw new Exception("NativeDynamicArray: not expected type used"); +#endif + var capacity = Capacity(); + var lengthToCopyInBytes = capacity * MemoryUtilities.SizeOf(); + var ret = new T[capacity]; + + fixed (void* handle = ret) + { + Unsafe.CopyBlock(handle, _list->ptr, (uint) lengthToCopyInBytes); + } + + return ret; + } + } + + public void RemoveAt(uint index) where T : struct + { + unsafe + { +#if DEBUG && !PROFILE_SVELTO + if (_list == null) + throw new Exception("NativeDynamicArray: null-access"); + if (_hashType != TypeHash.hash) + throw new Exception("NativeDynamicArray: not expected type used"); +#endif + + var sizeOf = MemoryUtilities.SizeOf(); + //Unsafe.CopyBlock may not be memory overlapping safe (memcpy vs memmove) + Buffer.MemoryCopy(_list->ptr + (index + 1) * sizeOf, _list->ptr + index * sizeOf, _list->count + , (uint) ((Count() - (index + 1)) * sizeOf)); + _list->Pop(); + } + } + + public void MemClear() + { + unsafe + { + MemoryUtilities.MemClear((IntPtr) _list->ptr, (uint) _list->capacity); + } + } + +#if UNITY_NATIVE + [global::Unity.Burst.NoAlias] [global::Unity.Collections.LowLevel.Unsafe.NativeDisableUnsafePtrRestriction] +#endif + unsafe UnsafeArray* _list; +#if DEBUG && !PROFILE_SVELTO + int _hashType; +#endif + Allocator _allocator; + } +} \ No newline at end of file diff --git a/DataStructures/Unmanaged/NativeDynamicArray.cs.meta b/DataStructures/Unmanaged/NativeDynamicArray.cs.meta new file mode 100644 index 0000000..bac16aa --- /dev/null +++ b/DataStructures/Unmanaged/NativeDynamicArray.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 711d4a28132031fca4870da7c8dae575 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/DataStructures/Unmanaged/NativeDynamicArrayCast.cs b/DataStructures/Unmanaged/NativeDynamicArrayCast.cs new file mode 100644 index 0000000..a34078b --- /dev/null +++ b/DataStructures/Unmanaged/NativeDynamicArrayCast.cs @@ -0,0 +1,48 @@ +using System.Runtime.CompilerServices; + +namespace Svelto.ECS.DataStructures +{ + public struct NativeDynamicArrayCast where T : struct + { + public NativeDynamicArrayCast(NativeDynamicArray array) : this() { _array = array; } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int Count() => _array.Count(); + + public int count => _array.Count(); + + public ref T this[int index] + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => ref _array.Get((uint) index); + } + + public ref T this[uint index] + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => ref _array.Get(index); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Add(in T id) { _array.Add(id); } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void UnorderedRemoveAt(uint index) { _array.UnorderedRemoveAt(index); } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void RemoveAt(uint index) { _array.RemoveAt(index); } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Clear() { _array.FastClear(); } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Dispose() { _array.Dispose(); } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ref T AddAt(uint lastIndex) { return ref _array.AddAt(lastIndex); } + + public bool isValid => _array.isValid; + + NativeDynamicArray _array; + } +} \ No newline at end of file diff --git a/DataStructures/Unmanaged/NativeDynamicArrayCast.cs.meta b/DataStructures/Unmanaged/NativeDynamicArrayCast.cs.meta new file mode 100644 index 0000000..29115ef --- /dev/null +++ b/DataStructures/Unmanaged/NativeDynamicArrayCast.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 60ca1e40bef23e8db37576d0f3dbc631 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/DataStructures/Unmanaged/NativeDynamicArrayUnityExtension.cs b/DataStructures/Unmanaged/NativeDynamicArrayUnityExtension.cs new file mode 100644 index 0000000..3a0dd90 --- /dev/null +++ b/DataStructures/Unmanaged/NativeDynamicArrayUnityExtension.cs @@ -0,0 +1,23 @@ +#if UNITY_NATIVE +using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; + +namespace Svelto.ECS.DataStructures +{ + public static class NativeDynamicArrayUnityExtension + { + public static NativeArray ToNativeArray(this NativeDynamicArray array) where T : struct + { + unsafe + { + var nativeArray = NativeArrayUnsafeUtility.ConvertExistingDataToNativeArray( + (void*) array.ToIntPTR(), (int) array.Count(), Allocator.None); +#if ENABLE_UNITY_COLLECTIONS_CHECKS + NativeArrayUnsafeUtility.SetAtomicSafetyHandle(ref nativeArray, AtomicSafetyHandle.Create()); +#endif + return nativeArray; + } + } + } +} +#endif \ No newline at end of file diff --git a/DataStructures/Unmanaged/NativeDynamicArrayUnityExtension.cs.meta b/DataStructures/Unmanaged/NativeDynamicArrayUnityExtension.cs.meta new file mode 100644 index 0000000..040dcf7 --- /dev/null +++ b/DataStructures/Unmanaged/NativeDynamicArrayUnityExtension.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 668917ff718433479e84c1f270e88377 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/DataStructures/Unmanaged/SharedNativeInt.cs b/DataStructures/Unmanaged/SharedNativeInt.cs new file mode 100644 index 0000000..e180084 --- /dev/null +++ b/DataStructures/Unmanaged/SharedNativeInt.cs @@ -0,0 +1,115 @@ +using System; +using System.Runtime.InteropServices; +using System.Threading; +using Svelto.Common; + +namespace Svelto.ECS.DataStructures +{ + public struct SharedNativeInt: IDisposable + { +#if UNITY_NATIVE + [global::Unity.Collections.LowLevel.Unsafe.NativeDisableUnsafePtrRestriction] +#endif + unsafe int* data; + + Allocator _allocator; + + public SharedNativeInt(Allocator allocator) + { + unsafe + { + _allocator = allocator; + data = (int*) MemoryUtilities.Alloc(sizeof(int), allocator); + } + } + + public static SharedNativeInt Create(int t, Allocator allocator) + { + unsafe + { + var current = new SharedNativeInt(); + current._allocator = allocator; + current.data = (int*) MemoryUtilities.Alloc(sizeof(int), allocator); + *current.data = t; + + return current; + } + } + + public static implicit 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) + { + MemoryUtilities.Free((IntPtr) data, _allocator); + data = null; + } + } + } + + public int Decrement() + { + unsafe + { +#if DEBUG && !PROFILE_SVELTO + if (data == null) + throw new Exception("null-access"); +#endif + + return Interlocked.Decrement(ref *data); + } + } + + public int Increment() + { + unsafe + { +#if DEBUG && !PROFILE_SVELTO + if (data == null) + throw new Exception("null-access"); +#endif + + return Interlocked.Increment(ref *data); + } + } + + public int Add(int val) + { + unsafe + { +#if DEBUG && !PROFILE_SVELTO + if (data == null) + throw new Exception("null-access"); +#endif + + return Interlocked.Add(ref *data, val); + } + } + + public void Set(int val) + { + unsafe + { +#if DEBUG && !PROFILE_SVELTO + if (data == null) + throw new Exception("null-access"); +#endif + + Volatile.Write(ref *data, val); + } + } + } +} \ No newline at end of file diff --git a/DataStructures/Unmanaged/SharedNativeInt.cs.meta b/DataStructures/Unmanaged/SharedNativeInt.cs.meta new file mode 100644 index 0000000..7d387e1 --- /dev/null +++ b/DataStructures/Unmanaged/SharedNativeInt.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 109ecc8b65fa3a55a287c25fe70ef472 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/DataStructures/Unmanaged/ThreadSafeNativeBag.cs b/DataStructures/Unmanaged/ThreadSafeNativeBag.cs new file mode 100644 index 0000000..d0517ee --- /dev/null +++ b/DataStructures/Unmanaged/ThreadSafeNativeBag.cs @@ -0,0 +1,194 @@ +#if later +using System; +using System.Runtime.CompilerServices; +using System.Threading; +using Svelto.Common; +using Svelto.Utilities; + +namespace Svelto.ECS.DataStructures +{ + /// + /// Burst friendly Ring Buffer 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 NativeBag 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 ThreadSafeNativeBag : IDisposable + { + 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 ThreadSafeNativeBag(Allocator allocator) + { + unsafe + { + var sizeOf = MemoryUtilities.SizeOf(); + var listData = (UnsafeBlob*) MemoryUtilities.Alloc((uint) sizeOf, allocator); + + //clear to nullify the pointers + //MemoryUtilities.MemClear((IntPtr) listData, (uint) sizeOf); + listData->allocator = allocator; + _queue = listData; + } + + _writingGuard = 0; + } + + public ThreadSafeNativeBag(Allocator allocator, uint capacity) + { + unsafe + { + var sizeOf = MemoryUtilities.SizeOf(); + var listData = (UnsafeBlob*) MemoryUtilities.Alloc((uint) sizeOf, allocator); + + //clear to nullify the pointers + //MemoryUtilities.MemClear((IntPtr) listData, (uint) sizeOf); + listData->allocator = allocator; + _queue = listData; + _queue->Realloc(capacity); + } + + _writingGuard = 0; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool IsEmpty() + { + unsafe + { + if (_queue == null || _queue->ptr == null) + return true; + } + + return count == 0; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public unsafe void Dispose() + { + if (_queue != null) + { + _queue->Dispose(); + MemoryUtilities.Free((IntPtr) _queue, _queue->allocator); + _queue = null; + } + } + + [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(); + var alignedSize = (uint) MemoryUtilities.SizeOfAligned(); + + Interlocked.MemoryBarrier(); + Reset: + var oldCapacity = _queue->capacity; + var spaceleft = oldCapacity - (_queue->_writeIndex - _queue->_readIndex) - sizeOf; + + while (spaceleft < 0) + { + //if _writingGuard is not equal to 0, it means that another thread increased the + //value so it's possible the reallocing is already happening OR it means that + //writing are still in progress and we must be sure that are all flushed first + if (Interlocked.CompareExchange(ref _writingGuard, 1, 0) != 0) + { + ThreadUtility.Yield(); + goto Reset; + } + + var newCapacity = (uint) ((oldCapacity + alignedSize) * 2.0f); + Svelto.Console.Log($"realloc {newCapacity}"); + _queue->Realloc(newCapacity); + + Volatile.Write(ref _writingGuard, 0); + } + + int writeIndex; + + //look for the first available slot to write in + writeIndex = _queue->_writeIndex; + if (Interlocked.CompareExchange(ref _queue->_writeIndex, (int) (writeIndex + alignedSize) + , writeIndex) != writeIndex) + { + ThreadUtility.Yield(); + goto Reset; + } + + Interlocked.Increment(ref _writingGuard); + _queue->Write(item, (uint) writeIndex); + Interlocked.Decrement(ref _writingGuard); + } + } + + [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(); + } + } + +#if UNITY_NATIVE + [global::Unity.Collections.LowLevel.Unsafe.NativeDisableUnsafePtrRestriction] +#endif + unsafe UnsafeBlob* _queue; + + int _writingGuard; + } +} +#endif \ No newline at end of file diff --git a/DataStructures/Unmanaged/ThreadSafeNativeBag.cs.meta b/DataStructures/Unmanaged/ThreadSafeNativeBag.cs.meta new file mode 100644 index 0000000..8860462 --- /dev/null +++ b/DataStructures/Unmanaged/ThreadSafeNativeBag.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ecdfbc4967aa30eebd81d08e327f9857 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/DataStructures/Unmanaged/UnsafeArray.cs b/DataStructures/Unmanaged/UnsafeArray.cs new file mode 100644 index 0000000..a70c112 --- /dev/null +++ b/DataStructures/Unmanaged/UnsafeArray.cs @@ -0,0 +1,138 @@ +using System; +using System.Runtime.CompilerServices; +using Svelto.Common; + +namespace Svelto.ECS.DataStructures +{ + struct UnsafeArray + { + internal unsafe byte* ptr => _ptr; + + //expressed in bytes + internal int capacity => (int) _capacity; + + //expressed in bytes + internal int count => (int) _writeIndex; + + //expressed in bytes + internal int space => capacity - count; + +#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 : struct + { + unsafe + { +#if DEBUG && !PROFILE_SVELTO + uint sizeOf = (uint) MemoryUtilities.SizeOf(); + if (index + sizeOf > _writeIndex) + throw new Exception("no reading authorized"); +#endif + return ref Unsafe.AsRef(Unsafe.Add(ptr, (int) index)); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Set(uint index, in T value) where T : struct + { + unsafe + { + uint sizeOf = (uint) MemoryUtilities.SizeOf(); + uint writeIndex = (uint) (index * sizeOf); + +#if DEBUG && !PROFILE_SVELTO + if (_capacity < writeIndex + sizeOf) + throw new Exception("no writing authorized"); +#endif + Unsafe.AsRef(Unsafe.Add(_ptr, (int) index)) = value; + + if (_writeIndex < writeIndex + sizeOf) + _writeIndex = (uint) (writeIndex + sizeOf); + } + } + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Add(in T value) where T : struct + { + 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)] + public ref T Pop() where T : struct + { + unsafe + { + var structSize = MemoryUtilities.SizeOf(); + + _writeIndex -= (uint)structSize; + + return ref Unsafe.AsRef(ptr + _writeIndex); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal void Realloc(uint newCapacity, Allocator allocator) + { + unsafe + { + if (_ptr == null) + _ptr = (byte*) MemoryUtilities.Alloc(newCapacity, allocator); + else + _ptr = (byte*) MemoryUtilities.Realloc((IntPtr) _ptr, (uint) count, newCapacity, allocator); + + _capacity = newCapacity; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Dispose(Allocator allocator) + { + unsafe + { + if (ptr != null) + MemoryUtilities.Free((IntPtr) ptr, allocator); + + _ptr = null; + _writeIndex = 0; + _capacity = 0; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Clear() + { + _writeIndex = 0; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void SetCountTo(uint count) + { + _writeIndex = count; + } + +#if UNITY_NATIVE + [global::Unity.Collections.LowLevel.Unsafe.NativeDisableUnsafePtrRestriction] +#endif + unsafe byte* _ptr; + + uint _writeIndex; + uint _capacity; + } +} \ No newline at end of file diff --git a/DataStructures/Unmanaged/UnsafeArray.cs.meta b/DataStructures/Unmanaged/UnsafeArray.cs.meta new file mode 100644 index 0000000..1a9ccf9 --- /dev/null +++ b/DataStructures/Unmanaged/UnsafeArray.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0acd30efabd337da835fcfda3b966aee +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/DataStructures/Unmanaged/UnsafeBlob.cs b/DataStructures/Unmanaged/UnsafeBlob.cs new file mode 100644 index 0000000..591d5c4 --- /dev/null +++ b/DataStructures/Unmanaged/UnsafeBlob.cs @@ -0,0 +1,302 @@ +using System; +using System.Runtime.CompilerServices; +using Svelto.Common; + +namespace Svelto.ECS.DataStructures +{ + //ToDO to complete in future version of svelto, maybe removed + public struct UnsafeArrayIndex + { + internal uint index; + internal uint capacity; + } + + /// + /// Note: this must work inside burst, so it must follow burst restrictions + /// Note: All the svelto native structures + /// + struct UnsafeBlob : IDisposable + { + internal unsafe byte* ptr { get; set; } + + //expressed in bytes + internal uint capacity { get; private set; } + + //expressed in bytes + internal uint size => (uint)_writeIndex - _readIndex; + + //expressed in bytes + internal uint space => capacity - size; + + /// + /// + internal Allocator allocator; + + [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 writeHead = _writeIndex % capacity; + + if (writeHead + structSize <= capacity) + { + Unsafe.Write(ptr + writeHead, item); + } + else + //copy with wrap, will start to copy and wrap for the reminder + { + var byteCountToEnd = capacity - writeHead; + + var localCopyToAvoidGcIssues = item; + //read and copy the first portion of Item until the end of the stream + Unsafe.CopyBlock(ptr + writeHead, Unsafe.AsPointer(ref localCopyToAvoidGcIssues), (uint)byteCountToEnd); + + var restCount = structSize - byteCountToEnd; + + //read and copy the remainder + Unsafe.CopyBlock(ptr, (byte*) Unsafe.AsPointer(ref localCopyToAvoidGcIssues) + byteCountToEnd + , (uint)restCount); + } + + //this is may seems a waste if you are going to use an unsafeBlob just for bytes, but it's necessary for mixed types. + //it's still possible to use WriteUnaligned though + int paddedStructSize = (int) MemoryUtilities.Align4(structSize); + + _writeIndex += paddedStructSize; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal void Write(in T item, uint writeIndex) 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 + var writeHead = writeIndex % capacity; + + if (writeHead + structSize <= capacity) + { + Unsafe.Write(ptr + writeHead, item); + } + else //copy with wrap, will start to copy and wrap for the reminder + { + var byteCountToEnd = capacity - writeHead; + + var localCopyToAvoidGcIssues = item; + //read and copy the first portion of Item until the end of the stream + Unsafe.CopyBlock(ptr + writeHead, Unsafe.AsPointer(ref localCopyToAvoidGcIssues), byteCountToEnd); + + var restCount = structSize - byteCountToEnd; + + //read and copy the remainder + Unsafe.CopyBlock(ptr, (byte*) Unsafe.AsPointer(ref localCopyToAvoidGcIssues) + byteCountToEnd + , restCount); + } + } + } + +// [MethodImpl(MethodImplOptions.AggressiveInlining)] +// //ToDo: remove this and create an UnsafeBlobUnaligned, used on NativeRingBuffer where T cannot change +// internal void WriteUnaligned(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 pointer = _writeIndex % capacity; +// +// if (pointer + structSize <= capacity) +// { +// Unsafe.Write(ptr + pointer, item); +// } +// else +// { +// var byteCount = capacity - pointer; +// +// var localCopyToAvoidGCIssues = item; +// +// Unsafe.CopyBlockUnaligned(ptr + pointer, Unsafe.AsPointer(ref localCopyToAvoidGCIssues), byteCount); +// +// var restCount = structSize - byteCount; +// Unsafe.CopyBlockUnaligned(ptr, (byte*) Unsafe.AsPointer(ref localCopyToAvoidGCIssues) + 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; + var paddedStructSize = MemoryUtilities.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); + + T item = default; + var byteCountToEnd = capacity - head; + Unsafe.CopyBlock(Unsafe.AsPointer(ref item), ptr + head, byteCountToEnd); + + var restCount = structSize - byteCountToEnd; + Unsafe.CopyBlock((byte*) Unsafe.AsPointer(ref item) + byteCountToEnd, ptr, restCount); + + return item; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal ref T Reserve(out UnsafeArrayIndex index) where T : struct + { + unsafe + { + var sizeOf = (uint) MemoryUtilities.SizeOf(); + + ref var buffer = ref Unsafe.AsRef(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 = (uint)_writeIndex + }; + + int align4 = (int) MemoryUtilities.Align4(sizeOf); + _writeIndex += align4; + + return ref buffer; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal ref T AccessReserved(UnsafeArrayIndex index) where T : struct + { + 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 + return ref Unsafe.AsRef(ptr + index.index); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal void Realloc(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 = MemoryUtilities.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, allocator); + if (size > 0) + { + var readerHead = _readIndex % capacity; + var writerHead = _writeIndex % capacity; + + if (readerHead < writerHead) + { + //copy to the new pointer, from th reader position + var currentSize = _writeIndex - _readIndex; + Unsafe.CopyBlock(newPointer, ptr + readerHead, (uint)currentSize); + } + //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; + + Unsafe.CopyBlock(newPointer, ptr + readerHead, byteCountToEnd); + Unsafe.CopyBlock(newPointer + byteCountToEnd, ptr, (uint)writerHead); + } + } + } + + if (ptr != null) + MemoryUtilities.Free((IntPtr) ptr, allocator); + + ptr = newPointer; + capacity = newCapacity; + + _readIndex = 0; + _writeIndex = (int)size; + } + } + + [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; + } + + internal int _writeIndex; + internal uint _readIndex; + } +} \ No newline at end of file diff --git a/DataStructures/Unmanaged/UnsafeBlob.cs.meta b/DataStructures/Unmanaged/UnsafeBlob.cs.meta new file mode 100644 index 0000000..f2e2275 --- /dev/null +++ b/DataStructures/Unmanaged/UnsafeBlob.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 62fedd45acd134729d5be903fc2d5b26 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Debugger.meta b/Debugger.meta new file mode 100644 index 0000000..647647c --- /dev/null +++ b/Debugger.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: d2793f2ae73e357f9773b68721bbe468 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Debugger/ExclusiveGroupDebugger.cs b/Debugger/ExclusiveGroupDebugger.cs new file mode 100644 index 0000000..3ebf160 --- /dev/null +++ b/Debugger/ExclusiveGroupDebugger.cs @@ -0,0 +1,70 @@ +using Svelto.ECS; + +#if DEBUG +using System; +using System.Collections.Generic; +using System.Reflection; + +public static class ExclusiveGroupDebugger +{ + static ExclusiveGroupDebugger() + { + Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); + foreach (Assembly assembly in assemblies) + { + Type[] types = assembly.GetTypes(); + + foreach (Type type in types) + { + if (type != null && type.IsClass && type.IsSealed && type.IsAbstract) //this means only static classes + { + var fields = type.GetFields(); + foreach (var field in fields) + { + if (field.IsStatic && typeof(ExclusiveGroup).IsAssignableFrom(field.FieldType)) + { + var group = (ExclusiveGroup) field.GetValue(null); + string name = $"{type.FullName}.{field.Name} ({(uint)group})"; + GroupMap.idToName[(ExclusiveGroupStruct) group] = name; + } + + if (field.IsStatic && typeof(ExclusiveGroupStruct).IsAssignableFrom(field.FieldType)) + { + var group = (ExclusiveGroupStruct) field.GetValue(null); + + string name = $"{type.FullName}.{field.Name} ({(uint)group})"; + GroupMap.idToName[@group] = name; + } + } + } + } + } + } + + public static string ToName(this in ExclusiveGroupStruct group) + { + if (GroupMap.idToName.TryGetValue(group, out var name) == false) + name = $""; + + return name; + } +} + +public static class GroupMap +{ + static GroupMap() + { + GroupMap.idToName = new Dictionary(); + } + + internal static readonly Dictionary idToName; +} +#else +public static class ExclusiveGroupDebugger +{ + public static string ToName(this in ExclusiveGroupStruct group) + { + return ((uint)group).ToString(); + } +} +#endif \ No newline at end of file diff --git a/Debugger/ExclusiveGroupDebugger.cs.meta b/Debugger/ExclusiveGroupDebugger.cs.meta new file mode 100644 index 0000000..b043d69 --- /dev/null +++ b/Debugger/ExclusiveGroupDebugger.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: fd73f27c31073381b9b694f3f32941f3 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Dispatcher/DispatchOnChange.cs b/Dispatcher/DispatchOnChange.cs index d1ce756..c2a40aa 100644 --- a/Dispatcher/DispatchOnChange.cs +++ b/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/Dispatcher/DispatchOnSet.cs b/Dispatcher/DispatchOnSet.cs index d407ae8..8334d2e 100644 --- a/Dispatcher/DispatchOnSet.cs +++ b/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/DynamicEntityDescriptor.cs b/DynamicEntityDescriptor.cs index f40d3e7..d44a193 100644 --- a/DynamicEntityDescriptor.cs +++ b/DynamicEntityDescriptor.cs @@ -4,66 +4,66 @@ using Svelto.DataStructures; namespace Svelto.ECS { /// - /// DynamicEntityDescriptor can be used to add entity views to an existing EntityDescriptor that act as flags, + /// DynamicEntityDescriptor can be used to add entity components to an existing EntityDescriptor that act as flags, /// at building time. /// This method allocates, so it shouldn't be abused /// /// - public struct DynamicEntityDescriptor : IEntityDescriptor where TType : IEntityDescriptor, new() + public struct DynamicEntityDescriptor : IDynamicEntityDescriptor where TType : IEntityDescriptor, new() { 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 EntityInfoComponent { - 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) { @@ -72,26 +72,25 @@ namespace Svelto.ECS } var defaultEntities = startingEntities; - var length = defaultEntities.Length; - - var index = SetupSpecialEntityStruct(defaultEntities, out localEntitiesToBuild, extraEntitiesLength); + + var index = SetupEntityInfoComponent(defaultEntities, out localEntitiesToBuild, extraEntitiesLength); - Array.Copy(extraEntities, 0, localEntitiesToBuild, length, extraEntitiesLength); + Array.Copy(extraEntities, 0, localEntitiesToBuild, defaultEntities.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 EntityInfoComponent { - entitiesToBuild = localEntitiesToBuild + componentsToBuild = localEntitiesToBuild } ); return localEntitiesToBuild; } - static int SetupSpecialEntityStruct(IEntityBuilder[] defaultEntities, out IEntityBuilder[] entitiesToBuild, + static int SetupEntityInfoComponent(IComponentBuilder[] defaultEntities, out IComponentBuilder[] componentsToBuild, int extraLenght) { int length = defaultEntities.Length; @@ -100,7 +99,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() == ComponentBuilderUtilities.ENTITY_INFO_COMPONENT) { index = i; break; @@ -110,19 +109,18 @@ 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 IComponentBuilder[] componentsToBuild => ComponentsToBuild; - public IEntityBuilder[] entitiesToBuild => _entitiesToBuild; - - IEntityBuilder[] _entitiesToBuild; + IComponentBuilder[] ComponentsToBuild; } } \ No newline at end of file diff --git a/ECSException.cs b/ECSException.cs index b815f84..34951fe 100644 --- a/ECSException.cs +++ b/ECSException.cs @@ -9,5 +9,15 @@ namespace Svelto.ECS public ECSException(string message, Exception innerE):base("".FastConcat(message, ""), innerE) {} + + public ECSException(string message, Type entityComponentType, Type type) : + base(message.FastConcat(" entity view: '", entityComponentType.Name, "', field: '", type.Name)) + { + } + + public ECSException(string message, Type entityComponentType) : + base(message.FastConcat(" entity view: ", entityComponentType.Name)) + { + } } } \ No newline at end of file diff --git a/ECSResources/ECSResources.cs b/ECSResources/ECSResources.cs index 70ba614..a1effb4 100644 --- a/ECSResources/ECSResources.cs +++ b/ECSResources/ECSResources.cs @@ -9,6 +9,10 @@ namespace Svelto.ECS.Experimental public static implicit operator T(ECSResources ecsString) { return ResourcesECSDB.FromECS(ecsString.id); } } + /// + /// To do. Or we reuse the ID or we need to clear this + /// + /// static class ResourcesECSDB { static readonly FasterList _resources = new FasterList(); @@ -22,12 +26,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/ECSResources/ECSString.cs b/ECSResources/ECSString.cs index 4eac06c..50fef70 100644 --- a/ECSResources/ECSString.cs +++ b/ECSResources/ECSString.cs @@ -1,38 +1,96 @@ using System; +using System.Runtime.InteropServices; namespace Svelto.ECS.Experimental { [Serialization.DoNotSerialize] + [StructLayout(LayoutKind.Explicit)] + /// + /// Note: I should extend this to reuse unused id + /// + + //todo ResourcesECSDB must be used only inside entity components. Same for ECSString. + //what I could do is that if the component is removed from the database, a reference counter to the object + //will be modified. If 0 is reached, the ID should be recycled. public struct ECSString:IEquatable { - uint id; + [FieldOffset(0)] uint _id; + [FieldOffset(4)] uint _versioning; + [FieldOffset(0)] long _realID; - public ECSString(string newText) + public ECSString(string newText):this() { - id = ResourcesECSDB.ToECS(newText); + _id = ResourcesECSDB.ToECS(newText); + } + + ECSString(uint id):this() + { + _id = id; } public static implicit operator string(ECSString ecsString) { - return ResourcesECSDB.FromECS(ecsString.id); + return ResourcesECSDB.FromECS(ecsString._id); } - + + /// + /// Note: Setting null String could be a good way to signal a disposing of the ID so that + /// it can be recycled. + /// Zero id must be a null string + /// + /// public void Set(string newText) { - if (id != 0) - ResourcesECSDB.resources(id) = newText; + if (_id != 0) + { + if (ResourcesECSDB.resources(_id).Equals(newText) == false) + { + ResourcesECSDB.resources(_id) = newText; + + _versioning++; + } + } else - id = ResourcesECSDB.ToECS(newText); + _id = ResourcesECSDB.ToECS(newText); } - public bool Equals(ECSString other) + public ECSString Copy() { - return other.id == id; + DBC.ECS.Check.Require(_id != 0, "copying not initialized string"); + + var id = ResourcesECSDB.ToECS(ResourcesECSDB.resources(_id)); + + return new ECSString(id); } public override string ToString() { - return ResourcesECSDB.FromECS(id); + return ResourcesECSDB.FromECS(_id); + } + + public bool Equals(ECSString other) + { + return _realID == other._realID; + } + + public static bool operator==(ECSString options1, ECSString options2) + { + return options1._realID == options2._realID; + } + + public static bool operator!=(ECSString options1, ECSString options2) + { + return options1._realID != options2._realID; + } + + public override bool Equals(object obj) + { + throw new NotSupportedException(); //this is on purpose + } + + public override int GetHashCode() + { + return _realID.GetHashCode(); } } } \ No newline at end of file diff --git a/EGID.cs b/EGID.cs index 4e7d768..e2a0f77 100644 --- a/EGID.cs +++ b/EGID.cs @@ -1,19 +1,21 @@ using System; -using System.Collections.Generic; +using System.Runtime.InteropServices; #pragma warning disable 660,661 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 readonly EGID Empty = new EGID(); + public static bool operator ==(EGID obj1, EGID obj2) { return obj1._GID == obj2._GID; @@ -24,10 +26,15 @@ 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); } + + public EGID(uint entityID, BuildGroup groupID) : this() + { + _GID = MAKE_GLOBAL_ID(entityID, groupID.group); + } static ulong MAKE_GLOBAL_ID(uint entityId, uint groupId) { @@ -52,11 +59,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); @@ -69,9 +81,8 @@ namespace Svelto.ECS public override string ToString() { - return "id ".FastConcat(entityID).FastConcat(" group ").FastConcat(groupID); + var value = groupID.ToName(); + return "id ".FastConcat(entityID).FastConcat(" group ").FastConcat(value); } - - readonly ulong _GID; } } diff --git a/EGIDMapper.cs b/EGIDMapper.cs index 939e20b..3596a9d 100644 --- a/EGIDMapper.cs +++ b/EGIDMapper.cs @@ -1,37 +1,82 @@ using System; using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; +using Svelto.Common; using Svelto.DataStructures; +using Svelto.ECS.Internal; namespace Svelto.ECS { - public struct EGIDMapper where T : struct, IEntityStruct + /// + /// Note: does mono devirtualize sealed classes? If so it could be worth to use TypeSafeDictionary instead of + /// the interface + /// + /// + public readonly struct EGIDMapper: IEGIDMapper where T : struct, IEntityComponent { - internal FasterDictionary map; + public uint length => _map.count; + public ExclusiveGroupStruct groupID { get; } + public Type entityType => TypeCache.type; + + internal EGIDMapper(ExclusiveGroupStruct groupStructId, ITypeSafeDictionary dic) : this() + { + groupID = groupStructId; + _map = dic; + } [MethodImpl(MethodImplOptions.AggressiveInlining)] public ref T Entity(uint entityID) { -#if DEBUG && !PROFILER - if (map.TryFindIndex(entityID, out var findIndex) == false) - throw new Exception("Entity not found in this group ".FastConcat(typeof(T).ToString())); +#if DEBUG && !PROFILE_SVELTO + if (_map == null) + throw new System.Exception("Not initialized EGIDMapper in this group ".FastConcat(typeof(T).ToString())); + if (_map.TryFindIndex(entityID, out var findIndex) == false) + throw new System.Exception("Entity not found in this group ".FastConcat(typeof(T).ToString())); #else - map.TryFindIndex(entityID, out var findIndex); + _map.TryFindIndex(entityID, out var findIndex); #endif - return ref map.valuesArray[findIndex]; + return ref _map.GetDirectValueByRef(findIndex); } - + public bool TryGetEntity(uint entityID, out T value) { - if (map.TryFindIndex(entityID, out var index)) + if (_map != null && _map.TryFindIndex(entityID, out var index)) { - value = map.GetDirectValue(index); + value = _map.GetDirectValueByRef(index); return true; } value = default; return false; } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Exists(uint idEntityId) + { + return _map.count > 0 && _map.TryFindIndex(idEntityId, out _); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public uint GetIndex(uint entityID) + { + return _map.GetIndex(entityID); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool FindIndex(uint valueKey, out uint index) + { + return _map.TryFindIndex(valueKey, out index); + } + + internal readonly ITypeSafeDictionary _map; } -} + public interface IEGIDMapper + { + bool FindIndex(uint valueKey, out uint index); + uint GetIndex(uint entityID); + bool Exists(uint idEntityId); + + ExclusiveGroupStruct groupID { get; } + Type entityType { get; } + } +} \ No newline at end of file diff --git a/EnginesRoot.DoubleBufferedEntitiesToAdd.cs b/EnginesRoot.DoubleBufferedEntitiesToAdd.cs new file mode 100644 index 0000000..86322d4 --- /dev/null +++ b/EnginesRoot.DoubleBufferedEntitiesToAdd.cs @@ -0,0 +1,148 @@ +using System; +using Svelto.DataStructures; +using Svelto.ECS.Internal; + +namespace Svelto.ECS +{ + public partial class EnginesRoot + { + internal class DoubleBufferedEntitiesToAdd + { + const int MaximumNumberOfItemsPerFrameBeforeToClear = 100; + + internal void Swap() + { + Swap(ref current, ref other); + Swap(ref currentEntitiesCreatedPerGroup, ref otherEntitiesCreatedPerGroup); + } + + void Swap(ref T item1, ref T item2) + { + var toSwap = item2; + item2 = item1; + item1 = toSwap; + } + + public void ClearOther() + { + //do not clear the groups created so far, they will be reused, unless they are too many! + var otherCount = other.count; + if (otherCount > MaximumNumberOfItemsPerFrameBeforeToClear) + { + FasterDictionary[] otherValuesArray = other.unsafeValues; + for (int i = 0; i < otherCount; ++i) + { + var safeDictionariesCount = otherValuesArray[i].count; + ITypeSafeDictionary[] safeDictionaries = otherValuesArray[i].unsafeValues; + { + for (int j = 0; j < safeDictionariesCount; ++j) + { + //clear the dictionary of entities create do far (it won't allocate though) + safeDictionaries[j].Dispose(); + } + } + } + + otherEntitiesCreatedPerGroup.FastClear(); + other.FastClear(); + return; + } + + { + FasterDictionary[] otherValuesArray = other.unsafeValues; + for (int i = 0; i < otherCount; ++i) + { + var safeDictionariesCount = otherValuesArray[i].count; + ITypeSafeDictionary[] safeDictionaries = otherValuesArray[i].unsafeValues; + //do not remove the dictionaries of entities per type created so far, they will be reused + if (safeDictionariesCount <= MaximumNumberOfItemsPerFrameBeforeToClear) + { + for (int j = 0; j < safeDictionariesCount; ++j) + { + //clear the dictionary of entities create do far (it won't allocate though) + safeDictionaries[j].FastClear(); + } + } + else + { + for (int j = 0; j < safeDictionariesCount; ++j) + { + //clear the dictionary of entities create do far (it won't allocate though) + safeDictionaries[j].Dispose(); + } + + otherValuesArray[i].FastClear(); + } + } + + 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> current; + internal FasterDictionary> other; + + readonly FasterDictionary> + _entityComponentsToAddBufferA = + new FasterDictionary>(); + + readonly FasterDictionary> + _entityComponentsToAddBufferB = + new FasterDictionary>(); + + readonly FasterDictionary _entitiesCreatedPerGroupA = new FasterDictionary(); + readonly FasterDictionary _entitiesCreatedPerGroupB = new FasterDictionary(); + + public DoubleBufferedEntitiesToAdd() + { + currentEntitiesCreatedPerGroup = _entitiesCreatedPerGroupA; + otherEntitiesCreatedPerGroup = _entitiesCreatedPerGroupB; + + current = _entityComponentsToAddBufferA; + other = _entityComponentsToAddBufferB; + } + + public void Dispose() + { + { + var otherValuesArray = other.unsafeValues; + for (int i = 0; i < other.count; ++i) + { + 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 + for (int j = 0; j < safeDictionariesCount; ++j) + { + //clear the dictionary of entities create do far (it won't allocate though) + safeDictionaries[j].Dispose(); + } + } + } + { + var currentValuesArray = current.unsafeValues; + for (int i = 0; i < current.count; ++i) + { + var safeDictionariesCount = currentValuesArray[i].count; + var safeDictionaries = currentValuesArray[i].unsafeValues; + //do not remove the dictionaries of entities per type created so far, they will be reused + for (int j = 0; j < safeDictionariesCount; ++j) + { + //clear the dictionary of entities create do far (it won't allocate though) + safeDictionaries[j].Dispose(); + } + } + } + } + } + } +} \ No newline at end of file diff --git a/EnginesRoot.DoubleBufferedEntitiesToAdd.cs.meta b/EnginesRoot.DoubleBufferedEntitiesToAdd.cs.meta new file mode 100644 index 0000000..df1be29 --- /dev/null +++ b/EnginesRoot.DoubleBufferedEntitiesToAdd.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 5bf312fc57853c4d8368dcb99141a1e9 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/EnginesRoot.DoubleBufferedEntityViews.cs b/EnginesRoot.DoubleBufferedEntityViews.cs deleted file mode 100644 index f6077f3..0000000 --- a/EnginesRoot.DoubleBufferedEntityViews.cs +++ /dev/null @@ -1,86 +0,0 @@ -using System; -using Svelto.DataStructures; -using Svelto.ECS.Internal; - -namespace Svelto.ECS -{ - public partial class EnginesRoot - { - internal class DoubleBufferedEntitiesToAdd - { - const int MaximumNumberOfItemsPerFrameBeforeToClear = 100; - - internal void Swap() - { - Swap(ref current, ref other); - Swap(ref currentEntitiesCreatedPerGroup, ref otherEntitiesCreatedPerGroup); - } - - void Swap(ref T item1, ref T item2) - { - var toSwap = item2; - item2 = item1; - item1 = toSwap; - } - - public void ClearOther() - { - //do not clear the groups created so far, they will be reused, unless they are too many! - var otherCount = other.Count; - if (otherCount > MaximumNumberOfItemsPerFrameBeforeToClear) - { - otherEntitiesCreatedPerGroup.FastClear(); - other.FastClear(); - return; - } - var otherValuesArray = other.valuesArray; - for (int i = 0; i < otherCount; ++i) - { - var safeDictionariesCount = otherValuesArray[i].Count; - var safeDictionaries = otherValuesArray[i].valuesArray; - //do not remove the dictionaries of entities per type created so far, they will be reused - if (safeDictionariesCount <= MaximumNumberOfItemsPerFrameBeforeToClear) - { - for (int j = 0; j < safeDictionariesCount; ++j) - { - //clear the dictionary of entities create do far (it won't allocate though) - safeDictionaries[j].FastClear(); - } - } - else - { - otherValuesArray[i].FastClear(); - } - } - - otherEntitiesCreatedPerGroup.FastClear(); - } - - internal FasterDictionary currentEntitiesCreatedPerGroup; - internal FasterDictionary otherEntitiesCreatedPerGroup; - - internal FasterDictionary, ITypeSafeDictionary>> current; - internal FasterDictionary, ITypeSafeDictionary>> other; - - readonly FasterDictionary, ITypeSafeDictionary>> - _entityViewsToAddBufferA = - new FasterDictionary, ITypeSafeDictionary>>(); - - readonly FasterDictionary, ITypeSafeDictionary>> - _entityViewsToAddBufferB = - new FasterDictionary, ITypeSafeDictionary>>(); - - readonly FasterDictionary _entitiesCreatedPerGroupA = new FasterDictionary(); - readonly FasterDictionary _entitiesCreatedPerGroupB = new FasterDictionary(); - - public DoubleBufferedEntitiesToAdd() - { - currentEntitiesCreatedPerGroup = _entitiesCreatedPerGroupA; - otherEntitiesCreatedPerGroup = _entitiesCreatedPerGroupB; - - current = _entityViewsToAddBufferA; - other = _entityViewsToAddBufferB; - } - } - } -} \ No newline at end of file diff --git a/EnginesRoot.Engines.cs b/EnginesRoot.Engines.cs index 9d7a31d..7b3b388 100644 --- a/EnginesRoot.Engines.cs +++ b/EnginesRoot.Engines.cs @@ -1,28 +1,35 @@ using System; using System.Collections.Generic; +using Svelto.Common; using Svelto.DataStructures; using Svelto.ECS.Internal; using Svelto.ECS.Schedulers; namespace Svelto.ECS { - public partial class EnginesRoot + public sealed partial class EnginesRoot { - public struct EntitiesSubmitter + public readonly struct EntitiesSubmitter { public EntitiesSubmitter(EnginesRoot enginesRoot) { - _weakReference = new DataStructures.WeakReference(enginesRoot); + _weakReference = new Svelto.DataStructures.WeakReference(enginesRoot); } + public bool IsUnused => _weakReference.IsValid == false; + public void Invoke() { if (_weakReference.IsValid) - _weakReference.Target.SubmitEntityViews(); + _weakReference.Target.SubmitEntityComponents(); } - readonly DataStructures.WeakReference _weakReference; + readonly Svelto.DataStructures.WeakReference _weakReference; } + + readonly EntitiesSubmissionScheduler _scheduler; + public IEntitiesSubmissionScheduler scheduler => _scheduler; + /// /// Engines root contextualize your engines and entities. You don't need to limit yourself to one EngineRoot /// as multiple engines root could promote separation of scopes. The EntitySubmissionScheduler checks @@ -31,49 +38,156 @@ 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(EntitiesSubmissionScheduler entitiesComponentScheduler) { - _entitiesOperations = new FasterDictionary(); - serializationDescriptorMap = new SerializationDescriptorMap(); - _reactiveEnginesAddRemove = new FasterDictionary, FasterList>(); - _reactiveEnginesSwap = new FasterDictionary, FasterList>(); - _enginesSet = new FasterList(); - _enginesTypeSet = new HashSet(); - _disposableEngines = new FasterList(); + _entitiesOperations = new ThreadSafeDictionary(); + serializationDescriptorMap = new SerializationDescriptorMap(); + _reactiveEnginesAddRemove = new FasterDictionary>(); + _reactiveEnginesSwap = new FasterDictionary>(); + _reactiveEnginesSubmission = new FasterList(); + _enginesSet = new FasterList(); + _enginesTypeSet = new HashSet(); + _disposableEngines = new FasterList(); _transientEntitiesOperations = new FasterList(); - _groupEntityViewsDB = new FasterDictionary, ITypeSafeDictionary>>(); - _groupsPerEntity = new FasterDictionary, FasterDictionary>(); + _groupEntityComponentsDB = + new FasterDictionary>(); + _groupsPerEntity = + new FasterDictionary>(); _groupedEntityToAdd = new DoubleBufferedEntitiesToAdd(); - _entitiesStream = new EntitiesStream(); - _entitiesDB = new EntitiesDB(_groupEntityViewsDB, _groupsPerEntity, _entitiesStream); + _entityStreams = EntitiesStreams.Create(); + _groupFilters = + new FasterDictionary>(); + _entitiesDB = new EntitiesDB(this); - _scheduler = entityViewScheduler; + _scheduler = entitiesComponentScheduler; _scheduler.onTick = new EntitiesSubmitter(this); +#if UNITY_NATIVE + AllocateNativeOperations(); +#endif } - - public EnginesRoot(IEntitySubmissionScheduler entityViewScheduler, bool isDeserializationOnly):this(entityViewScheduler) + + public EnginesRoot + (EntitiesSubmissionScheduler 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() + { + _isDisposing = true; + + using (var profiler = new PlatformProfiler("Final Dispose")) + { + //Note: The engines are disposed before the the remove callback to give the chance to behave + //differently if a remove happens as a consequence of a dispose + //The pattern is to implement the IDisposable interface and set a flag in the engine. The + //remove callback will then behave differently according the flag. + foreach (var engine in _disposableEngines) + { + try + { + if (engine is IDisposingEngine dengine) + dengine.isDisposing = true; + engine.Dispose(); + } + catch (Exception e) + { + Svelto.Console.LogException(e); + } + } + + foreach (FasterDictionary>. + KeyValuePairFast groups in _groupEntityComponentsDB) + { + foreach (FasterDictionary.KeyValuePairFast entityList in groups + .Value) + try + { + entityList.Value.ExecuteEnginesRemoveCallbacks(_reactiveEnginesAddRemove, profiler + , new ExclusiveGroupStruct(groups.Key)); + } + catch (Exception e) + { + Svelto.Console.LogException(e); + } + } + + foreach (FasterDictionary>. + KeyValuePairFast groups in _groupEntityComponentsDB) + { + foreach (FasterDictionary.KeyValuePairFast entityList in groups + .Value) + entityList.Value.Dispose(); + } + + foreach (FasterDictionary>. + KeyValuePairFast type in _groupFilters) + foreach (FasterDictionary.KeyValuePairFast group in type.Value) + group.Value.Dispose(); + + _groupFilters.Clear(); + +#if UNITY_NATIVE + _addOperationQueue.Dispose(); + _removeOperationQueue.Dispose(); + _swapOperationQueue.Dispose(); +#endif + _groupEntityComponentsDB.Clear(); + _groupsPerEntity.Clear(); + + _disposableEngines.Clear(); + _enginesSet.Clear(); + _enginesTypeSet.Clear(); + _reactiveEnginesSwap.Clear(); + _reactiveEnginesAddRemove.Clear(); + _reactiveEnginesSubmission.Clear(); + + _entitiesOperations.Clear(); + _transientEntitiesOperations.Clear(); + + _groupedEntityToAdd.Dispose(); + _entityStreams.Dispose(); + scheduler.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); + var type = engine.GetType(); + var refWrapper = new RefWrapperType(type); + DBC.ECS.Check.Require(engine != null, "Engine to add is invalid or null"); DBC.ECS.Check.Require( - _enginesTypeSet.Contains(refWrapper) == false || - type.ContainsCustomAttribute(typeof(AllowMultipleAttribute)) == true, - "The same engine has been added more than once, if intentional, use [AllowMultiple] class attribute " - .FastConcat(engine.ToString())); + _enginesTypeSet.Contains(refWrapper) == false + || type.ContainsCustomAttribute(typeof(AllowMultipleAttribute)) == true + , "The same engine has been added more than once, if intentional, use [AllowMultiple] class attribute " + .FastConcat(engine.ToString())); try { if (engine is IReactOnAddAndRemove viewEngine) - CheckEntityViewsEngine(viewEngine, _reactiveEnginesAddRemove); + CheckReactEngineComponents(viewEngine, _reactiveEnginesAddRemove); if (engine is IReactOnSwap viewEngineSwap) - CheckEntityViewsEngine(viewEngineSwap, _reactiveEnginesSwap); + CheckReactEngineComponents(viewEngineSwap, _reactiveEnginesSwap); + + if (engine is IReactOnSubmission submissionEngine) + _reactiveEnginesSubmission.Add(submissionEngine); _enginesTypeSet.Add(refWrapper); _enginesSet.Add(engine); @@ -81,20 +195,21 @@ 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) { - throw new ECSException("Code crashed while adding engine ".FastConcat(engine.GetType().ToString(), " "), e); + throw new ECSException("Code crashed while adding engine ".FastConcat(engine.GetType().ToString(), " ") + , e); } } - void CheckEntityViewsEngine(T engine, FasterDictionary, FasterList> engines) - where T : class, IEngine + void CheckReactEngineComponents(T engine, FasterDictionary> engines) + where T : class, IReactEngine { var interfaces = engine.GetType().GetInterfaces(); @@ -109,36 +224,37 @@ namespace Svelto.ECS } } - static void AddEngine(T engine, Type[] entityViewTypes, - FasterDictionary, FasterList> engines) - where T : class, IEngine + static void AddEngine + (T engine, Type[] entityComponentTypes, FasterDictionary> engines) + where T : class, IReactEngine { - 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); } } - static void AddEngine(T engine, FasterDictionary, FasterList> engines, Type type) - where T : class, IEngine + static void AddEngine(T engine, FasterDictionary> engines, Type type) + where T : class, IReactEngine { - if (engines.TryGetValue(new RefWrapper(type), out var list) == false) + if (engines.TryGetValue(new RefWrapperType(type), out var list) == false) { - list = new FasterList(); + list = new FasterList(); - engines.Add(new RefWrapper(type), list); + engines.Add(new RefWrapperType(type), list); } list.Add(engine); } - readonly FasterDictionary, FasterList> _reactiveEnginesAddRemove; - readonly FasterDictionary, FasterList> _reactiveEnginesSwap; - readonly FasterList _disposableEngines; - - readonly FasterList _enginesSet; - readonly HashSet _enginesTypeSet; + readonly FasterDictionary> _reactiveEnginesAddRemove; + readonly FasterDictionary> _reactiveEnginesSwap; + readonly FasterList _reactiveEnginesSubmission; + readonly FasterList _disposableEngines; + readonly FasterList _enginesSet; + readonly HashSet _enginesTypeSet; + internal bool _isDisposing; } } \ No newline at end of file diff --git a/EnginesRoot.Entities.cs b/EnginesRoot.Entities.cs index 08e3ffc..fadbabb 100644 --- a/EnginesRoot.Entities.cs +++ b/EnginesRoot.Entities.cs @@ -1,65 +1,15 @@ using System; using System.Collections.Generic; using System.Runtime.CompilerServices; +using DBC.ECS; using Svelto.Common; using Svelto.DataStructures; using Svelto.ECS.Internal; namespace Svelto.ECS { - public partial class EnginesRoot : IDisposable + public partial class EnginesRoot : IDisposable, IUnitTestingInterface { - /// - /// 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,227 +29,304 @@ namespace Svelto.ECS ///-------------------------------------------- [MethodImpl(MethodImplOptions.AggressiveInlining)] - EntityStructInitializer BuildEntity(EGID entityID, IEntityBuilder[] entitiesToBuild, + EntityComponentInitializer BuildEntity + (EGID entityID, IComponentBuilder[] componentsToBuild, Type descriptorType, IEnumerable implementors = null) { - CheckAddEntityID(entityID); + CheckAddEntityID(entityID, descriptorType); + Check.Require(entityID.groupID != 0, "invalid group detected, are you using new ExclusiveGroupStruct() instead of new ExclusiveGroup()?"); - var dic = EntityFactory.BuildGroupedEntities(entityID, _groupedEntityToAdd, - entitiesToBuild, implementors); + var dic = EntityFactory.BuildGroupedEntities(entityID, _groupedEntityToAdd, componentsToBuild + , implementors, descriptorType); - 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; + using (var profiler = new PlatformProfiler("Preallocate")) + { + 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 group = GetOrCreateGroup(groupID, profiler); - for (var index = 0; index < numberOfEntityViews; index++) - { - var entityViewBuilder = entityViewsToBuild[index]; - var entityViewType = entityViewBuilder.GetEntityType(); + for (var index = 0; index < numberOfEntityComponents; index++) + { + var entityComponentBuilder = entityComponentsToBuild[index]; + var entityComponentType = entityComponentBuilder.GetEntityComponentType(); - var refWrapper = new RefWrapper(entityViewType); - if (group.TryGetValue(refWrapper, out var dbList) == false) - group[refWrapper] = entityViewBuilder.Preallocate(ref dbList, size); - else - dbList.SetCapacity(size); + var refWrapper = new RefWrapperType(entityComponentType); + if (group.TryGetValue(refWrapper, out var dbList) == false) + group[refWrapper] = entityComponentBuilder.Preallocate(ref dbList, size); + else + dbList.SetCapacity(size); - if (_groupsPerEntity.TryGetValue(refWrapper, out var groupedGroup) == false) - groupedGroup = _groupsPerEntity[refWrapper] = - new FasterDictionary(); + if (_groupsPerEntity.TryGetValue(refWrapper, out var groupedGroup) == false) + groupedGroup = _groupsPerEntity[refWrapper] = new FasterDictionary(); - groupedGroup[groupID] = dbList; + groupedGroup[groupID] = dbList; + } } } ///-------------------------------------------- /// - void MoveEntityFromAndToEngines(IEntityBuilder[] entityBuilders, EGID fromEntityGID, EGID? toEntityGID) + void MoveEntityFromAndToEngines(IComponentBuilder[] componentBuilders, 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)); - - //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); + var fromGroup = GetGroup(fromEntityGID.groupID); + + //Check if there is an EntityInfo linked to this entity, if so it's a DynamicEntityDescriptor! + if (fromGroup.TryGetValue(new RefWrapperType(ComponentBuilderUtilities.ENTITY_INFO_COMPONENT) + , out var entityInfoDic) + && (entityInfoDic as ITypeSafeDictionary).TryGetValue( + fromEntityGID.entityID, out var entityInfo)) + SwapOrRemoveEntityComponents(fromEntityGID, toEntityGID, entityInfo.componentsToBuild, fromGroup + , sampler); //otherwise it's a normal static entity descriptor else - MoveEntities(fromEntityGID, toEntityGID, entityBuilders, fromGroup, sampler); + SwapOrRemoveEntityComponents(fromEntityGID, toEntityGID, componentBuilders, fromGroup, sampler); } } - void MoveEntities(EGID fromEntityGID, EGID? toEntityGID, IEntityBuilder[] entitiesToMove, - FasterDictionary, ITypeSafeDictionary> fromGroup, PlatformProfiler sampler) + void SwapOrRemoveEntityComponents(EGID fromEntityGID, EGID? toEntityGID, IComponentBuilder[] entitiesToMove + , FasterDictionary fromGroup, in PlatformProfiler sampler) { - FasterDictionary, ITypeSafeDictionary> toGroup = null; - - if (toEntityGID != null) + using (sampler.Sample("MoveEntityComponents")) { - var toGroupID = toEntityGID.Value.groupID; + var length = entitiesToMove.Length; - if (_groupEntityViewsDB.TryGetValue(toGroupID, out toGroup) == false) - toGroup = _groupEntityViewsDB[toGroupID] = new FasterDictionary, ITypeSafeDictionary>(); + FasterDictionary toGroup = null; - //Add all the entities to the dictionary - for (var i = 0; i < entitiesToMove.Length; i++) - CopyEntityToDictionary(fromEntityGID, toEntityGID.Value, fromGroup, toGroup, - entitiesToMove[i].GetEntityType()); - } + //Swap is not like adding a new entity. While adding new entities happen at the end of submission + //Adding an entity to a group due to a swap of groups happens now. + if (toEntityGID != null) + { + var toGroupID = toEntityGID.Value.groupID; - //call all the callbacks - for (var i = 0; i < entitiesToMove.Length; i++) - MoveEntityViewFromAndToEngines(fromEntityGID, toEntityGID, fromGroup, toGroup, - entitiesToMove[i].GetEntityType(), sampler); + toGroup = GetOrCreateGroup(toGroupID, sampler); - //then remove all the entities from the dictionary - for (var i = 0; i < entitiesToMove.Length; i++) - RemoveEntityFromDictionary(fromEntityGID, fromGroup, entitiesToMove[i].GetEntityType(), sampler); - } + //Add all the entities to the dictionary + for (var i = 0; i < length; i++) + CopyEntityToDictionary(fromEntityGID, toEntityGID.Value, fromGroup, toGroup + , entitiesToMove[i].GetEntityComponentType(), sampler); + } - void CopyEntityToDictionary(EGID entityGID, EGID toEntityGID, - FasterDictionary, ITypeSafeDictionary> fromGroup, - FasterDictionary, ITypeSafeDictionary> toGroup, Type entityViewType) - { - var wrapper = new RefWrapper(entityViewType); + //call all the callbacks + for (var i = 0; i < length; i++) + ExecuteEnginesSwapOrRemoveCallbacks(fromEntityGID, toEntityGID, fromGroup, toGroup + , entitiesToMove[i].GetEntityComponentType(), sampler); - 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)); + //then remove all the entities from the dictionary + for (var i = 0; i < length; i++) + RemoveEntityFromDictionary(fromEntityGID, fromGroup, entitiesToMove[i].GetEntityComponentType(), + sampler); } + } -#if DEBUG && !PROFILER - if (fromTypeSafeDictionary.Has(entityGID.entityID) == false) - { - throw new EntityNotFoundException(entityGID, entityViewType); - } -#endif - if (toGroup.TryGetValue(wrapper, out var toEntitiesDictionary) == false) + void CopyEntityToDictionary + (EGID entityGID, EGID toEntityGID, FasterDictionary fromGroup + , FasterDictionary toGroup, Type entityComponentType, + in PlatformProfiler sampler) + { + using (sampler.Sample("CopyEntityToDictionary")) { - toEntitiesDictionary = fromTypeSafeDictionary.Create(); - toGroup.Add(wrapper, toEntitiesDictionary); - } + var wrapper = new RefWrapperType(entityComponentType); - //todo: this must be unit tested properly - if (_groupsPerEntity.TryGetValue(wrapper, out var groupedGroup) == false) - groupedGroup = _groupsPerEntity[wrapper] = - new FasterDictionary(); + ITypeSafeDictionary fromTypeSafeDictionary = + GetTypeSafeDictionary(entityGID.groupID, fromGroup, wrapper); - groupedGroup[toEntityGID.groupID] = toEntitiesDictionary; +#if DEBUG && !PROFILE_SVELTO + if (fromTypeSafeDictionary.Has(entityGID.entityID) == false) + { + throw new EntityNotFoundException(entityGID, entityComponentType); + } +#endif + ITypeSafeDictionary toEntitiesDictionary = + GetOrCreateTypeSafeDictionary(toEntityGID.groupID, toGroup, wrapper, fromTypeSafeDictionary); - fromTypeSafeDictionary.AddEntityToDictionary(entityGID, toEntityGID, toEntitiesDictionary); + fromTypeSafeDictionary.AddEntityToDictionary(entityGID, toEntityGID, toEntitiesDictionary); + } } - void MoveEntityViewFromAndToEngines(EGID entityGID, EGID? toEntityGID, - FasterDictionary, ITypeSafeDictionary> fromGroup, - FasterDictionary, ITypeSafeDictionary> toGroup, Type entityViewType, - in PlatformProfiler profiler) + void ExecuteEnginesSwapOrRemoveCallbacks + (EGID entityGID, EGID? toEntityGID, FasterDictionary fromGroup + , FasterDictionary toGroup, Type entityComponentType + , in PlatformProfiler profiler) { - //add all the entities - var refWrapper = new RefWrapper(entityViewType); - if (fromGroup.TryGetValue(refWrapper, out var fromTypeSafeDictionary) == false) + using (profiler.Sample("MoveEntityComponentFromAndToEngines")) { - throw new ECSException("no entities in from group eid: ".FastConcat(entityGID.entityID) - .FastConcat(" group: ").FastConcat(entityGID.groupID)); - } + //add all the entities + var refWrapper = new RefWrapperType(entityComponentType); + var fromTypeSafeDictionary = GetTypeSafeDictionary(entityGID.groupID, fromGroup, refWrapper); - ITypeSafeDictionary toEntitiesDictionary = null; - if (toGroup != null) - toEntitiesDictionary = toGroup[refWrapper]; //this is guaranteed to exist by AddEntityToDictionary + ITypeSafeDictionary toEntitiesDictionary = null; + if (toGroup != null) + toEntitiesDictionary = toGroup[refWrapper]; //this is guaranteed to exist by AddEntityToDictionary -#if DEBUG && !PROFILER - if (fromTypeSafeDictionary.Has(entityGID.entityID) == false) - throw new EntityNotFoundException(entityGID, entityViewType); +#if DEBUG && !PROFILE_SVELTO + if (fromTypeSafeDictionary.Has(entityGID.entityID) == false) + throw new EntityNotFoundException(entityGID, entityComponentType); #endif - fromTypeSafeDictionary.MoveEntityFromEngines(entityGID, toEntityGID, - toEntitiesDictionary, toEntityGID == null ? _reactiveEnginesAddRemove : _reactiveEnginesSwap, - in profiler); + fromTypeSafeDictionary.ExecuteEnginesSwapOrRemoveCallbacks(entityGID, toEntityGID, toEntitiesDictionary + , toEntityGID == null ? _reactiveEnginesAddRemove : _reactiveEnginesSwap, in profiler); + } } - void RemoveEntityFromDictionary(EGID entityGID, - FasterDictionary, ITypeSafeDictionary> fromGroup, Type entityViewType, - in PlatformProfiler profiler) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + void RemoveEntityFromDictionary + (EGID entityGID, FasterDictionary fromGroup, Type entityComponentType + , in PlatformProfiler sampler) { - var refWrapper = new RefWrapper(entityViewType); - if (fromGroup.TryGetValue(refWrapper, out var fromTypeSafeDictionary) == false) + using (sampler.Sample("RemoveEntityFromDictionary")) { - throw new ECSException("no entities in from group eid: ".FastConcat(entityGID.entityID) - .FastConcat(" group: ").FastConcat(entityGID.groupID)); - } - - fromTypeSafeDictionary.RemoveEntityFromDictionary(entityGID, profiler); + var refWrapper = new RefWrapperType(entityComponentType); + var fromTypeSafeDictionary = GetTypeSafeDictionary(entityGID.groupID, fromGroup, refWrapper); - if (fromTypeSafeDictionary.Count == 0) //clean up - { - //todo: this must be unit tested properly - _groupsPerEntity[refWrapper].Remove(entityGID.groupID); - //I don't remove the group if empty on purpose, in case it needs to be reused + fromTypeSafeDictionary.RemoveEntityFromDictionary(entityGID); } } /// - /// 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 + /// + /// TODO: write unit test that also tests that this calls MoveTo callbacks and not Add or Remove. + /// also that the passing EGID is the same of a component with EGID /// - /// + /// + /// /// - void RemoveGroupAndEntitiesFromDB(uint groupID, in PlatformProfiler profiler) + void SwapEntitiesBetweenGroups(ExclusiveGroupStruct fromIdGroupId, ExclusiveGroupStruct toGroupId, in PlatformProfiler profiler) { - var dictionariesOfEntities = _groupEntityViewsDB[groupID]; - - foreach (var dictionaryOfEntities in dictionariesOfEntities) + using (profiler.Sample("SwapEntitiesBetweenGroups")) { - dictionaryOfEntities.Value.RemoveEntitiesFromEngines(_reactiveEnginesAddRemove, profiler, - new ExclusiveGroup.ExclusiveGroupStruct(groupID)); - var groupedGroupOfEntities = _groupsPerEntity[dictionaryOfEntities.Key]; - groupedGroupOfEntities.Remove(groupID); + FasterDictionary fromGroup = GetGroup(fromIdGroupId); + FasterDictionary toGroup = GetOrCreateGroup(toGroupId, profiler); + + foreach (var dictionaryOfEntities in fromGroup) + { + ITypeSafeDictionary toEntitiesDictionary = + GetOrCreateTypeSafeDictionary(toGroupId, toGroup, dictionaryOfEntities.Key + , dictionaryOfEntities.Value); + + var groupsOfEntityType = _groupsPerEntity[dictionaryOfEntities.Key]; + + var groupOfEntitiesToCopyAndClear = groupsOfEntityType[fromIdGroupId]; + toEntitiesDictionary.AddEntitiesFromDictionary(groupOfEntitiesToCopyAndClear, toGroupId); + + //call all the MoveTo callbacks + dictionaryOfEntities.Value.ExecuteEnginesAddOrSwapCallbacks(_reactiveEnginesSwap + , dictionaryOfEntities.Value, new ExclusiveGroupStruct(fromIdGroupId), new ExclusiveGroupStruct(toGroupId), profiler); + + //todo: if it's unmanaged, I can use fastclear + groupOfEntitiesToCopyAndClear.Clear(); + } } + } + + FasterDictionary GetGroup(ExclusiveGroupStruct fromIdGroupId) + { + if (_groupEntityComponentsDB.TryGetValue(fromIdGroupId + , out FasterDictionary + fromGroup) == false) + throw new ECSException("Group doesn't exist: ".FastConcat(fromIdGroupId)); + + return fromGroup; + } + + FasterDictionary GetOrCreateGroup(ExclusiveGroupStruct toGroupId, + in PlatformProfiler profiler) + { + using (profiler.Sample("GetOrCreateGroup")) + { + if (_groupEntityComponentsDB.TryGetValue( + toGroupId, out FasterDictionary toGroup) == false) + toGroup = _groupEntityComponentsDB[toGroupId] = + new FasterDictionary(); - //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); + return toGroup; + } } - internal Consumer GenerateConsumer(string name, uint capacity) where T : unmanaged, IEntityStruct + ITypeSafeDictionary GetOrCreateTypeSafeDictionary + (ExclusiveGroupStruct groupId, FasterDictionary toGroup, RefWrapperType type + , ITypeSafeDictionary fromTypeSafeDictionary) { - return _entitiesStream.GenerateConsumer(name, capacity); + //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 @group, RefWrapperType refWrapper) + { + if (@group.TryGetValue(refWrapper, out ITypeSafeDictionary fromTypeSafeDictionary) == false) + { + throw new ECSException("no group found: ".FastConcat(groupID)); + } + + return fromTypeSafeDictionary; + } - public Consumer GenerateConsumer(ExclusiveGroup group, string name, uint capacity) where T : unmanaged, - IEntityStruct + void RemoveEntitiesFromGroup(ExclusiveGroupStruct groupID, in PlatformProfiler profiler) { - return _entitiesStream.GenerateConsumer(group, name, capacity); + if (_groupEntityComponentsDB.TryGetValue(groupID, out var dictionariesOfEntities)) + { + foreach (FasterDictionary.KeyValuePairFast dictionaryOfEntities + in dictionariesOfEntities) + { + dictionaryOfEntities.Value.ExecuteEnginesRemoveCallbacks(_reactiveEnginesAddRemove, profiler + , new ExclusiveGroupStruct(groupID)); + dictionaryOfEntities.Value.FastClear(); + + var groupsOfEntityType = + _groupsPerEntity[dictionaryOfEntities.Key]; + groupsOfEntityType[groupID].FastClear(); + } + } } //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 + internal readonly FasterDictionary> + _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 - readonly FasterDictionary, FasterDictionary> _groupsPerEntity; + //found indexed by group id. TypeSafeDictionary are never created, they instead point to the ones hold + //by _groupEntityComponentsDB + // >> + internal readonly FasterDictionary> + _groupsPerEntity; - readonly EntitiesDB _entitiesDB; - readonly EntitiesStream _entitiesStream; + //The filters stored for each component and group + internal readonly FasterDictionary> + _groupFilters; + + readonly EntitiesDB _entitiesDB; + + EntitiesDB IUnitTestingInterface.entitiesForTesting => _entitiesDB; + } + + public interface IUnitTestingInterface + { + EntitiesDB entitiesForTesting { get; } } } \ No newline at end of file diff --git a/EnginesRoot.GenericEntityFactory.cs b/EnginesRoot.GenericEntityFactory.cs index 8308e86..e13432b 100644 --- a/EnginesRoot.GenericEntityFactory.cs +++ b/EnginesRoot.GenericEntityFactory.cs @@ -1,5 +1,6 @@ -using System.Collections.Generic; -using Svelto.DataStructures; +using System; +using System.Collections.Generic; +using Svelto.Common; namespace Svelto.ECS { @@ -9,49 +10,58 @@ namespace Svelto.ECS { public GenericEntityFactory(EnginesRoot weakReference) { - _enginesRoot = new WeakReference(weakReference); + _enginesRoot = new Svelto.DataStructures.WeakReference(weakReference); } - public EntityStructInitializer BuildEntity(uint entityID, - ExclusiveGroup.ExclusiveGroupStruct groupStructId, IEnumerable implementors = null) + public EntityComponentInitializer BuildEntity + (uint entityID, BuildGroup 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 + , TypeCache.type, 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, TypeCache.type, 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, TypeCache.type, implementors); } - - public EntityStructInitializer BuildEntity(uint entityID, - ExclusiveGroup.ExclusiveGroupStruct groupStructId, T descriptorEntity, IEnumerable implementors) +#if UNITY_NATIVE + public NativeEntityFactory ToNative(string memberName) where T : IEntityDescriptor, new() + { + return _enginesRoot.Target.ProvideNativeEntityFactoryQueue(memberName); + } +#endif + public EntityComponentInitializer BuildEntity + (uint entityID, BuildGroup 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, TypeCache.type, 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); } + + public EntityComponentInitializer BuildEntity(EGID egid, IComponentBuilder[] componentsToBuild, Type type, IEnumerable implementors = null) + { + return _enginesRoot.Target.BuildEntity(egid, componentsToBuild, type, implementors); + } //enginesRoot is a weakreference because GenericEntityStreamConsumerFactory can be injected inside -//engines of other enginesRoot - readonly WeakReference _enginesRoot; + //engines of other enginesRoot + readonly Svelto.DataStructures.WeakReference _enginesRoot; } } } \ No newline at end of file diff --git a/EnginesRoot.GenericEntityFunctions.cs b/EnginesRoot.GenericEntityFunctions.cs index d5a2cbd..8b9a537 100644 --- a/EnginesRoot.GenericEntityFunctions.cs +++ b/EnginesRoot.GenericEntityFunctions.cs @@ -1,6 +1,8 @@ -using System.Diagnostics; +using System; using System.Runtime.CompilerServices; +using Svelto.Common; using Svelto.DataStructures; +using Svelto.ECS.Internal; namespace Svelto.ECS { @@ -8,17 +10,17 @@ namespace Svelto.ECS { /// /// todo: EnginesRoot was a weakreference to give the change to inject - /// entityfunctions from other engines root. It probably should be reverted + /// entity functions 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, BuildGroup groupID) where T : IEntityDescriptor, new() { RemoveEntity(new EGID(entityID, groupID)); @@ -27,40 +29,92 @@ namespace Svelto.ECS [MethodImpl(MethodImplOptions.AggressiveInlining)] public void RemoveEntity(EGID entityEGID) where T : IEntityDescriptor, new() { - _enginesRoot.Target.CheckRemoveEntityID(entityEGID); + DBC.ECS.Check.Require(entityEGID.groupID != 0, "invalid group detected"); + var descriptorComponentsToBuild = EntityDescriptorTemplate.descriptor.componentsToBuild; + _enginesRoot.Target.CheckRemoveEntityID(entityEGID, TypeCache.type); _enginesRoot.Target.QueueEntitySubmitOperation( new EntitySubmitOperation(EntitySubmitOperationType.Remove, entityEGID, entityEGID, - EntityDescriptorTemplate.descriptor.entitiesToBuild)); + descriptorComponentsToBuild)); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void RemoveGroupAndEntities(ExclusiveGroup.ExclusiveGroupStruct groupID) + public void RemoveEntitiesFromGroup(BuildGroup groupID) { + DBC.ECS.Check.Require(groupID != 0, "invalid group detected"); _enginesRoot.Target.RemoveGroupID(groupID); _enginesRoot.Target.QueueEntitySubmitOperation( new EntitySubmitOperation(EntitySubmitOperationType.RemoveGroup, new EGID(0, groupID), new EGID())); } + // [MethodImpl(MethodImplOptions.AggressiveInlining)] + // void RemoveAllEntities(ExclusiveGroup group) + // where D : IEntityDescriptor, new() where S : unmanaged, IEntityComponent + // { + // var targetEntitiesDB = _enginesRoot.Target._entitiesDB; + // var (buffer, count) = targetEntitiesDB.QueryEntities(@group); + // for (uint i = 0; i < count; ++i) + // { + // RemoveEntity(new EGID(i, group)); + // } + // } + // + // [MethodImpl(MethodImplOptions.AggressiveInlining)] + // void RemoveAllEntities() + // where D : IEntityDescriptor, new() where S : unmanaged, IEntityComponent + // { + // var targetEntitiesDB = _enginesRoot.Target._entitiesDB; + // foreach (var ((buffer, count), exclusiveGroupStruct) in targetEntitiesDB.QueryEntities()) + // for (uint i = 0; i < count; ++i) + // { + // RemoveEntity(new EGID(i, exclusiveGroupStruct)); + // } + // } + [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void SwapEntityGroup(uint entityID, ExclusiveGroup.ExclusiveGroupStruct fromGroupID, - ExclusiveGroup.ExclusiveGroupStruct toGroupID) + public void SwapEntitiesInGroup(BuildGroup fromGroupID, BuildGroup toGroupID) + where T : IEntityDescriptor, new() + { + if (_enginesRoot.Target._groupEntityComponentsDB.TryGetValue( + fromGroupID.group, out FasterDictionary entitiesInGroupPerType) + == true) + { +#if DEBUG && !PROFILE_SVELTO + IComponentBuilder[] components = EntityDescriptorTemplate.descriptor.componentsToBuild; + var dictionary = entitiesInGroupPerType[new RefWrapperType(components[0].GetEntityComponentType())]; + + dictionary.KeysEvaluator((key) => + { + _enginesRoot.Target.CheckRemoveEntityID(new EGID(key, fromGroupID), TypeCache.type); + _enginesRoot.Target.CheckAddEntityID(new EGID(key, toGroupID), TypeCache.type); + }); + +#endif + _enginesRoot.Target.QueueEntitySubmitOperation( + new EntitySubmitOperation(EntitySubmitOperationType.SwapGroup, new EGID(0, fromGroupID) + , new EGID(0, toGroupID))); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void SwapEntityGroup(uint entityID, BuildGroup fromGroupID, + BuildGroup 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, BuildGroup 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, BuildGroup toGroupID + , BuildGroup mustBeFromGroup) where T : IEntityDescriptor, new() { if (fromID.groupID != mustBeFromGroup) @@ -71,7 +125,7 @@ namespace Svelto.ECS [MethodImpl(MethodImplOptions.AggressiveInlining)] public void SwapEntityGroup(EGID fromID, EGID toID - , ExclusiveGroup.ExclusiveGroupStruct mustBeFromGroup) + , BuildGroup mustBeFromGroup) where T : IEntityDescriptor, new() { if (fromID.groupID != mustBeFromGroup) @@ -80,47 +134,65 @@ namespace Svelto.ECS SwapEntityGroup(fromID, toID); } +#if UNITY_NATIVE + public NativeEntityRemove ToNativeRemove(string memberName) where T : IEntityDescriptor, new() + { + return _enginesRoot.Target.ProvideNativeEntityRemoveQueue(memberName); + } + + public NativeEntitySwap ToNativeSwap(string memberName) where T : IEntityDescriptor, new() + { + return _enginesRoot.Target.ProvideNativeEntitySwapQueue(memberName); + } +#endif + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void SwapEntityGroup(EGID fromID, EGID toID) where T : IEntityDescriptor, new() { - _enginesRoot.Target.CheckRemoveEntityID(fromID); - _enginesRoot.Target.CheckAddEntityID(toID); + DBC.ECS.Check.Require(fromID.groupID != 0, "invalid group detected"); + DBC.ECS.Check.Require(toID.groupID != 0, "invalid group detected"); - _enginesRoot.Target.QueueEntitySubmitOperation( + var enginesRootTarget = _enginesRoot.Target; + var descriptorComponentsToBuild = EntityDescriptorTemplate.descriptor.componentsToBuild; + + enginesRootTarget.CheckRemoveEntityID(fromID, TypeCache.type); + enginesRootTarget.CheckAddEntityID(toID, TypeCache.type); + + enginesRootTarget.QueueEntitySubmitOperation( new EntitySubmitOperation(EntitySubmitOperationType.Swap, - fromID, toID, EntityDescriptorTemplate.descriptor.entitiesToBuild)); + fromID, toID, descriptorComponentsToBuild)); } - + //enginesRoot is a weakreference because GenericEntityStreamConsumerFactory can be injected inside -//engines of other enginesRoot - readonly WeakReference _enginesRoot; + //engines of other enginesRoot + readonly Svelto.DataStructures.WeakReference _enginesRoot; } void QueueEntitySubmitOperation(EntitySubmitOperation entitySubmitOperation) { -#if DEBUG && !PROFILER - entitySubmitOperation.trace = new StackFrame(1, true); +#if DEBUG && !PROFILE_SVELTO + entitySubmitOperation.trace = new System.Diagnostics.StackFrame(1, true); #endif _entitiesOperations.Add((ulong) entitySubmitOperation.fromID, entitySubmitOperation); } void QueueEntitySubmitOperation(EntitySubmitOperation entitySubmitOperation) where T : IEntityDescriptor { -#if DEBUG && !PROFILER - entitySubmitOperation.trace = new StackFrame(1, true); +#if DEBUG && !PROFILE_SVELTO + entitySubmitOperation.trace = new System.Diagnostics.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(typeof(T).Name) - .FastConcat(" submission type ", entitySubmitOperation.type.ToString(), + .FastConcat(" entityComponentType: ") + .FastConcat(typeof(T).Name) + .FastConcat(" submission type ", entitySubmitOperation.type.ToString(), " from ID: ", entitySubmitOperation.fromID.entityID.ToString()) - .FastConcat(" previous operation type: ", + .FastConcat(" previous operation type: ", _entitiesOperations[(ulong) entitySubmitOperation.fromID].type - .ToString())); + .ToString())); } else #endif diff --git a/EnginesRoot.Submission.cs b/EnginesRoot.Submission.cs index f0e8963..559c800 100644 --- a/EnginesRoot.Submission.cs +++ b/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,29 +17,40 @@ 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 } } + /// + /// Todo: it would be probably better to split even further the logic between submission and callbacks + /// Something to do when I will optimize the callbacks + /// + /// void SingleSubmission(in PlatformProfiler profiler) { +#if UNITY_NATIVE + NativeOperationSubmission(profiler); +#endif + ClearChecks(); + + bool entitiesAreSubmitted = false; + 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,57 +58,74 @@ 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( + RemoveEntitiesFromGroup( entitiesOperations[i].fromID.groupID, profiler); break; + case EntitySubmitOperationType.SwapGroup: + SwapEntitiesBetweenGroups(entitiesOperations[i].fromID.groupID, + entitiesOperations[i].toID.groupID, profiler); + break; } } - catch (Exception e) + catch { var str = "Crash while executing Entity Operation " .FastConcat(entitiesOperations[i].type.ToString()); - - throw new ECSException(str.FastConcat(" ") -#if DEBUG && !PROFILER - .FastConcat(entitiesOperations[i].trace.ToString()) + + + Svelto.Console.LogError(str.FastConcat(" ") +#if DEBUG && !PROFILE_SVELTO + .FastConcat(entitiesOperations[i].trace.ToString()) #endif - , e); + ); + + throw; } } } + + entitiesAreSubmitted = true; } _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(); } } } + + entitiesAreSubmitted = true; + } + + if (entitiesAreSubmitted) + { + var enginesCount = _reactiveEnginesSubmission.count; + for (int i = 0; i < enginesCount; i++) + _reactiveEnginesSubmission[i].EntitiesSubmitted(); } } - void AddEntityViewsToTheDBAndSuitableEngines(in PlatformProfiler profiler) + void AddEntityComponentsToTheDBAndSuitableEngines(in PlatformProfiler profiler) { using (profiler.Sample("Add entities to database")) { @@ -106,57 +133,46 @@ namespace Svelto.ECS foreach (var groupOfEntitiesToSubmit in _groupedEntityToAdd.otherEntitiesCreatedPerGroup) { var groupID = groupOfEntitiesToSubmit.Key; + + var groupDB = GetOrCreateGroup(groupID, profiler); - //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 RefWrapperType(type); - var wrapper = new RefWrapper(type); - if (groupDB.TryGetValue(wrapper, out var dbDic) == false) - dbDic = groupDB[wrapper] = typeSafeDictionary.Create(); + ITypeSafeDictionary dbDic = GetOrCreateTypeSafeDictionary(groupID, groupDB, wrapper, + targetTypeSafeDictionary); - //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(); - - groupedGroup[groupID] = dbDic; + //Fill the DB with the entity components generate this frame. + dbDic.AddEntitiesFromDictionary(targetTypeSafeDictionary, groupID); } } } - //then submit everything in the engines, so that the DB is up to date with all the entity views and struct + //then submit everything in the engines, so that the DB is up to date with all the entity components //created by the entity built using (profiler.Sample("Add entities to engines")) { foreach (var groupToSubmit in _groupedEntityToAdd.otherEntitiesCreatedPerGroup) { 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 RefWrapperType(entityComponentsToSubmit.Key)]; - entityViewsToSubmit.Value.AddEntitiesToEngines(_reactiveEnginesAddRemove, realDic, in profiler, - new ExclusiveGroup.ExclusiveGroupStruct(groupToSubmit.Key)); + entityComponentsToSubmit.Value.ExecuteEnginesAddOrSwapCallbacks(_reactiveEnginesAddRemove, realDic, + null, new ExclusiveGroupStruct(groupToSubmit.Key), in profiler); } } } } - DoubleBufferedEntitiesToAdd _groupedEntityToAdd; - readonly IEntitySubmissionScheduler _scheduler; - readonly FasterDictionary _entitiesOperations; + readonly DoubleBufferedEntitiesToAdd _groupedEntityToAdd; + readonly ThreadSafeDictionary _entitiesOperations; } } \ No newline at end of file diff --git a/EntitiesDB.FindGroups.cs b/EntitiesDB.FindGroups.cs new file mode 100644 index 0000000..0c7df8f --- /dev/null +++ b/EntitiesDB.FindGroups.cs @@ -0,0 +1,154 @@ +using System; +using System.Threading; +using Svelto.DataStructures; +using Svelto.ECS.Internal; + +namespace Svelto.ECS +{ + public partial class EntitiesDB + { + public LocalFasterReadOnlyList FindGroups() where T1 : IEntityComponent + { + FasterList result = groups.Value; + result.FastClear(); + if (groupsPerEntity.TryGetValue(TypeRefWrapper.wrapper + , out FasterDictionary result1) == false) + return result; + + var result1Count = result1.count; + var fasterDictionaryNodes1 = result1.unsafeKeys; + + for (int j = 0; j < result1Count; j++) + { + result.Add(new ExclusiveGroupStruct(fasterDictionaryNodes1[j].key)); + } + + return result; + } + + public LocalFasterReadOnlyList FindGroups() where T1 : IEntityComponent where T2 : IEntityComponent + { + FasterList result = groups.Value; + result.FastClear(); + if (groupsPerEntity.TryGetValue(TypeRefWrapper.wrapper + , out FasterDictionary result1) == false) + return result; + if (groupsPerEntity.TryGetValue(TypeRefWrapper.wrapper + , out FasterDictionary result2) == false) + return result; + + var result1Count = result1.count; + var result2Count = result2.count; + var fasterDictionaryNodes1 = result1.unsafeKeys; + var fasterDictionaryNodes2 = result2.unsafeKeys; + + for (int i = 0; i < result1Count; i++) + { + var groupID = fasterDictionaryNodes1[i].key; + for (int j = 0; j < result2Count; j++) + { + //if the same group is found used with both T1 and T2 + if (groupID == fasterDictionaryNodes2[j].key) + { + result.Add(new ExclusiveGroupStruct(groupID)); + break; + } + } + } + + return result; + } + + /// + /// Remember that this operation os O(N*M*P) where N,M,P are the number of groups where each component + /// is found. + /// + /// + /// + /// + /// + public LocalFasterReadOnlyList FindGroups() + where T1 : IEntityComponent where T2 : IEntityComponent where T3 : IEntityComponent + { + FasterList result = groups.Value; + result.FastClear(); + if (groupsPerEntity.TryGetValue(TypeRefWrapper.wrapper + , out FasterDictionary groupOfEntities1) == false) + return result; + if (groupsPerEntity.TryGetValue(TypeRefWrapper.wrapper + , out FasterDictionary groupOfEntities2) == false) + return result; + if (groupsPerEntity.TryGetValue(TypeRefWrapper.wrapper + , out FasterDictionary groupOfEntities3) == false) + return result; + + var result1Count = groupOfEntities1.count; + var result2Count = groupOfEntities2.count; + var result3Count = groupOfEntities3.count; + var fasterDictionaryNodes1 = groupOfEntities1.unsafeKeys; + var fasterDictionaryNodes2 = groupOfEntities2.unsafeKeys; + var fasterDictionaryNodes3 = groupOfEntities3.unsafeKeys; + + // + //TODO: I have to find once for ever a solution to be sure that the entities in the groups match + //Currently this returns group where the entities are found, but the entities may not match in these + //groups. + //Checking the size of the entities is an early check, needed, but not sufficient, as entities components may + //coincidentally match in number but not from which entities they are generated + + //foreach group where T1 is found + for (int i = 0; i < result1Count; i++) + { + var groupT1 = fasterDictionaryNodes1[i].key; + + //foreach group where T2 is found + for (int j = 0; j < result2Count; ++j) + { + if (groupT1 == fasterDictionaryNodes2[j].key) + { + //foreach group where T3 is found + for (int k = 0; k < result3Count; ++k) + { + if (groupT1 == fasterDictionaryNodes3[k].key) + { + result.Add(new ExclusiveGroupStruct(groupT1)); + break; + } + } + + break; + } + } + } + + return result; + } + + internal FasterDictionary FindGroups_INTERNAL(Type type) + { + if (groupsPerEntity.ContainsKey(new RefWrapperType(type)) == false) + return _emptyDictionary; + + return groupsPerEntity[new RefWrapperType(type)]; + } + + struct GroupsList + { + static GroupsList() + { + groups = new FasterList(); + } + + static readonly FasterList groups; + + public static implicit operator FasterList(in GroupsList list) + { + return list.reference; + } + + FasterList reference => groups; + } + + static readonly ThreadLocal groups = new ThreadLocal(); + } +} \ No newline at end of file diff --git a/EntitiesDB.FindGroups.cs.meta b/EntitiesDB.FindGroups.cs.meta new file mode 100644 index 0000000..3183390 --- /dev/null +++ b/EntitiesDB.FindGroups.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 11e0317f53f0374d9924cae6235eacdb +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/EntitiesDB.cs b/EntitiesDB.cs index fb49c7a..d65fa37 100644 --- a/EntitiesDB.cs +++ b/EntitiesDB.cs @@ -1,254 +1,263 @@ -#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 partial class EntitiesDB { - internal EntitiesDB( - FasterDictionary, ITypeSafeDictionary>> groupEntityViewsDB, - FasterDictionary, FasterDictionary> groupsPerEntity, - EntitiesStream entityStream) + internal EntitiesDB(EnginesRoot enginesRoot) { - _groupEntityViewsDB = groupEntityViewsDB; - _groupsPerEntity = groupsPerEntity; - _entityStream = entityStream; + _enginesRoot = enginesRoot; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ref T QueryUniqueEntity(ExclusiveGroup.ExclusiveGroupStruct group) where T : struct, IEntityStruct - { - var entities = QueryEntities(group, out var count); - - 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("'")); - return ref entities[0]; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ref T QueryEntity(EGID entityGID) where T : struct, IEntityStruct + EntityCollection InternalQueryEntities(FasterDictionary entitiesInGroupPerType) + where T : struct, IEntityComponent { - T[] array; - if ((array = QueryEntitiesAndIndexInternal(entityGID, out var index)) != null) - return ref array[index]; + uint count = 0; + IBuffer buffer; + if (SafeQueryEntityDictionary(out var typeSafeDictionary, entitiesInGroupPerType) == false) + buffer = RetrieveEmptyEntityComponentArray(); + else + { + var safeDictionary = (typeSafeDictionary as ITypeSafeDictionary); + buffer = safeDictionary.GetValues(out count); + } - throw new EntityNotFoundException(entityGID, typeof(T)); + return new EntityCollection(buffer, count); } - public ref T QueryEntity(uint id, ExclusiveGroup.ExclusiveGroupStruct group) 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 { - return ref QueryEntity(new EGID(id, group)); + if (groupEntityComponentsDB.TryGetValue(groupStructId, out var entitiesInGroupPerType) == false) + { + var buffer = RetrieveEmptyEntityComponentArray(); + return new EntityCollection(buffer, 0); + } + + return InternalQueryEntities(entitiesInGroupPerType); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public T[] QueryEntities(ExclusiveGroup.ExclusiveGroupStruct groupStruct, out uint count) - where T : struct, IEntityStruct + public EntityCollection QueryEntities(ExclusiveGroupStruct groupStruct) + where T1 : struct, IEntityComponent where T2 : struct, IEntityComponent { - uint group = groupStruct; - count = 0; - if (SafeQueryEntityDictionary(group, out TypeSafeDictionary typeSafeDictionary) == false) - return RetrieveEmptyEntityViewArray(); + if (groupEntityComponentsDB.TryGetValue(groupStruct, out var entitiesInGroupPerType) == false) + { + return new EntityCollection(new EntityCollection(RetrieveEmptyEntityComponentArray(), 0), + new EntityCollection(RetrieveEmptyEntityComponentArray(), 0)); + } + + var T1entities = InternalQueryEntities(entitiesInGroupPerType); + var T2entities = InternalQueryEntities(entitiesInGroupPerType); +#if DEBUG && !PROFILE_SVELTO + if (T1entities.count != T2entities.count) + throw new ECSException("Entity components count do not match in group. Entity 1: ' count: " + .FastConcat(T1entities.count).FastConcat(" ", typeof(T1).ToString()) + .FastConcat("'. Entity 2: ' count: ".FastConcat(T2entities.count) + .FastConcat(" ", typeof(T2).ToString()) + .FastConcat("' group: ", groupStruct.ToName()))); +#endif - return typeSafeDictionary.GetValuesArray(out count); + return new EntityCollection(T1entities, T2entities); } - public EntityCollection QueryEntities(ExclusiveGroup.ExclusiveGroupStruct groupStruct) - where T : 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); - } + if (groupEntityComponentsDB.TryGetValue(groupStruct, out var entitiesInGroupPerType) == false) + { + return new EntityCollection( + new EntityCollection(RetrieveEmptyEntityComponentArray(), 0), + new EntityCollection(RetrieveEmptyEntityComponentArray(), 0), + new EntityCollection(RetrieveEmptyEntityComponentArray(), 0)); + } + + var T1entities = InternalQueryEntities(entitiesInGroupPerType); + var T2entities = InternalQueryEntities(entitiesInGroupPerType); + var T3entities = InternalQueryEntities(entitiesInGroupPerType); +#if DEBUG && !PROFILE_SVELTO + if (T1entities.count != T2entities.count || T2entities.count != T3entities.count) + throw new ECSException("Entity components count do not match in group. Entity 1: " + .FastConcat(typeof(T1).ToString()).FastConcat(" count: ") + .FastConcat(T1entities.count).FastConcat( + " Entity 2: " + .FastConcat(typeof(T2).ToString()).FastConcat(" count: ") + .FastConcat(T2entities.count) + .FastConcat(" Entity 3: ".FastConcat(typeof(T3).ToString())) + .FastConcat(" count: ").FastConcat(T3entities.count))); +#endif - public EntityCollection QueryEntities(ExclusiveGroup.ExclusiveGroupStruct groupStruct) - where T1 : struct, IEntityStruct where T2 : struct, IEntityStruct - { - return new EntityCollection(QueryEntities(groupStruct, out var count), count); + return new EntityCollection(T1entities, T2entities, T3entities); } - - 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 where T4 : struct, IEntityComponent { - return new EntityCollection(QueryEntities(groupStruct, out var count), count); + if (groupEntityComponentsDB.TryGetValue(groupStruct, out var entitiesInGroupPerType) == false) + { + return new EntityCollection( + new EntityCollection(RetrieveEmptyEntityComponentArray(), 0), + new EntityCollection(RetrieveEmptyEntityComponentArray(), 0), + new EntityCollection(RetrieveEmptyEntityComponentArray(), 0), + new EntityCollection(RetrieveEmptyEntityComponentArray(), 0)); + } + + var T1entities = InternalQueryEntities(entitiesInGroupPerType); + var T2entities = InternalQueryEntities(entitiesInGroupPerType); + var T3entities = InternalQueryEntities(entitiesInGroupPerType); + var T4entities = InternalQueryEntities(entitiesInGroupPerType); +#if DEBUG && !PROFILE_SVELTO + if (T1entities.count != T2entities.count || T2entities.count != T3entities.count) + throw new ECSException("Entity components count do not match in group. Entity 1: " + .FastConcat(typeof(T1).ToString()).FastConcat(" count: ") + .FastConcat(T1entities.count).FastConcat( + " Entity 2: " + .FastConcat(typeof(T2).ToString()).FastConcat(" count: ") + .FastConcat(T2entities.count) + .FastConcat(" Entity 3: ".FastConcat(typeof(T3).ToString())) + .FastConcat(" count: ").FastConcat(T3entities.count))); +#endif + + return new EntityCollection(T1entities, T2entities, T3entities, T4entities); } - public EntityCollections QueryEntities(ExclusiveGroup[] groups) where T : struct, IEntityStruct + public GroupsEnumerable QueryEntities + (in LocalFasterReadOnlyList groups) where T : struct, IEntityComponent { - return new EntityCollections(this, groups); + return new GroupsEnumerable(this, groups); } - public EntityCollections QueryEntities(ExclusiveGroup[] groups) - where T1 : struct, IEntityStruct where T2 : struct, IEntityStruct + public GroupsEnumerable QueryEntities(in LocalFasterReadOnlyList groups) + where T1 : struct, IEntityComponent where T2 : struct, IEntityComponent { - return new EntityCollections(this, groups); + return new GroupsEnumerable(this, groups); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public (T1[], T2[]) QueryEntities(ExclusiveGroup.ExclusiveGroupStruct groupStruct, out uint count) - where T1 : struct, IEntityStruct - where T2 : struct, IEntityStruct + public GroupsEnumerable QueryEntities(in LocalFasterReadOnlyList groups) + where T1 : struct, IEntityComponent where T2 : struct, IEntityComponent where T3 : 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("'"))); - } - - return (T1entities, T2entities); + return new GroupsEnumerable(this, groups); } - [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 GroupsEnumerable QueryEntities + (in LocalFasterReadOnlyList groups) + where T1 : struct, IEntityComponent where T2 : struct, IEntityComponent + where T3 : struct, IEntityComponent where T4 : struct, 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))); - - return (T1entities, T2entities, T3entities); + return new GroupsEnumerable(this, groups); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public EGIDMapper QueryMappedEntities(ExclusiveGroup.ExclusiveGroupStruct groupStructId) - where T : struct, IEntityStruct + public EGIDMapper QueryMappedEntities(ExclusiveGroupStruct groupStructId) + where T : struct, IEntityComponent { - if (SafeQueryEntityDictionary(groupStructId, out TypeSafeDictionary typeSafeDictionary) == false) - throw new EntityGroupNotFoundException(groupStructId, typeof(T)); + if (SafeQueryEntityDictionary(groupStructId, out var typeSafeDictionary) == false) + throw new EntityGroupNotFoundException(typeof(T) , groupStructId.ToName()); - EGIDMapper mapper; - mapper.map = typeSafeDictionary; - - return mapper; + return (typeSafeDictionary as ITypeSafeDictionary).ToEGIDMapper(groupStructId); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool TryQueryMappedEntities(ExclusiveGroup.ExclusiveGroupStruct groupStructId, - out EGIDMapper mapper) - where T : struct, IEntityStruct + public bool TryQueryMappedEntities + (ExclusiveGroupStruct groupStructId, out EGIDMapper mapper) where T : struct, 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 ITypeSafeDictionary).ToEGIDMapper(groupStructId); return true; } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public T[] QueryEntitiesAndIndex(EGID entityGID, out uint index) where T : struct, IEntityStruct + public bool Exists(EGID entityGID) where T : struct, IEntityComponent { - T[] array; - if ((array = QueryEntitiesAndIndexInternal(entityGID, out index)) != null) - return array; + if (SafeQueryEntityDictionary(entityGID.groupID, out var casted) == false) + return false; - throw new EntityNotFoundException(entityGID, typeof(T)); + return casted != null && casted.ContainsKey(entityGID.entityID); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool TryQueryEntitiesAndIndex(EGID entityGid, out uint index, out T[] array) - where T : struct, IEntityStruct + public bool Exists(uint id, ExclusiveGroupStruct group) where T : struct, IEntityComponent { - if ((array = QueryEntitiesAndIndexInternal(entityGid, out index)) != null) - return true; + if (SafeQueryEntityDictionary(group, out var casted) == false) + return false; - return false; + return casted != null && casted.ContainsKey(id); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public T[] QueryEntitiesAndIndex(uint id, ExclusiveGroup.ExclusiveGroupStruct group, out uint index) - where T : struct, IEntityStruct + public bool ExistsAndIsNotEmpty(ExclusiveGroupStruct gid) { - return QueryEntitiesAndIndex(new EGID(id, group), out index); - } + if (groupEntityComponentsDB.TryGetValue( + gid, out FasterDictionary group) == true) + { + return group.count > 0; + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool TryQueryEntitiesAndIndex(uint id, ExclusiveGroup.ExclusiveGroupStruct group, out uint index, - out T[] array) where T : struct, IEntityStruct - { - return TryQueryEntitiesAndIndex(new EGID(id, group), out index, out array); + return false; } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool Exists(EGID entityGID) where T : struct, IEntityStruct + public bool HasAny(ExclusiveGroupStruct groupStruct) where T : struct, IEntityComponent { - if (SafeQueryEntityDictionary(entityGID.groupID, out TypeSafeDictionary casted) == false) return false; - - return casted != null && casted.ContainsKey(entityGID.entityID); + return Count(groupStruct) > 0; } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool Exists(uint id, ExclusiveGroup.ExclusiveGroupStruct group) where T : struct, IEntityStruct + public int Count(ExclusiveGroupStruct groupStruct) where T : struct, IEntityComponent { - if (SafeQueryEntityDictionary(group, out TypeSafeDictionary casted) == false) return false; + if (SafeQueryEntityDictionary(groupStruct, out var typeSafeDictionary) == false) + return 0; - return casted != null && casted.ContainsKey(id); + return (int) typeSafeDictionary.count; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool Exists(ExclusiveGroup.ExclusiveGroupStruct gid) + public bool FoundInGroups() where T1 : IEntityComponent { - return _groupEntityViewsDB.ContainsKey(gid); + return groupsPerEntity.ContainsKey(TypeRefWrapper.wrapper); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool HasAny(ExclusiveGroup.ExclusiveGroupStruct groupStruct) where T : struct, IEntityStruct - { - QueryEntities(groupStruct, out var count); - return count > 0; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public uint Count(ExclusiveGroup.ExclusiveGroupStruct groupStruct) where T : struct, IEntityStruct - { - QueryEntities(groupStruct, out var count); - return count; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void PublishEntityChange(EGID egid) where T : unmanaged, IEntityStruct - { - _entityStream.PublishEntity(ref QueryEntity(egid), egid); - } + public bool IsDisposing => _enginesRoot._isDisposing; [MethodImpl(MethodImplOptions.AggressiveInlining)] - T[] QueryEntitiesAndIndexInternal(EGID entityGID, out uint index) where T : struct, IEntityStruct + internal bool SafeQueryEntityDictionary(out ITypeSafeDictionary typeSafeDictionary, + FasterDictionary entitiesInGroupPerType) + where T : IEntityComponent { - index = 0; - if (SafeQueryEntityDictionary(entityGID.groupID, out TypeSafeDictionary safeDictionary) == false) - return null; + if (entitiesInGroupPerType.TryGetValue(new RefWrapperType(TypeCache.type), out var safeDictionary) == false) + { + typeSafeDictionary = default; + return false; + } - if (safeDictionary.TryFindIndex(entityGID.entityID, out index) == false) - return null; + //return the indexes entities if they exist + typeSafeDictionary = safeDictionary; - return safeDictionary.GetValuesArray(out _); + return true; } - + [MethodImpl(MethodImplOptions.AggressiveInlining)] - bool SafeQueryEntityDictionary(uint group, out TypeSafeDictionary typeSafeDictionary) - where T : struct, IEntityStruct + internal bool SafeQueryEntityDictionary(ExclusiveGroupStruct group, out ITypeSafeDictionary typeSafeDictionary) + where T : IEntityComponent { if (UnsafeQueryEntityDictionary(group, TypeCache.type, out var safeDictionary) == false) { @@ -257,45 +266,101 @@ namespace Svelto.ECS.Internal } //return the indexes entities if they exist - typeSafeDictionary = safeDictionary as TypeSafeDictionary; + typeSafeDictionary = safeDictionary; return true; } [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal bool UnsafeQueryEntityDictionary(uint group, Type type, out ITypeSafeDictionary typeSafeDictionary) + internal bool UnsafeQueryEntityDictionary(ExclusiveGroupStruct 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; } //search for the indexed entities in the group - return entitiesInGroupPerType.TryGetValue(new RefWrapper(type), out typeSafeDictionary); + return entitiesInGroupPerType.TryGetValue(new RefWrapperType(type), out typeSafeDictionary); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - static T[] RetrieveEmptyEntityViewArray() + internal bool FindIndex(uint entityID, ExclusiveGroupStruct @group, Type type, out uint index) { - return EmptyList.emptyArray; + EGID entityGID = new EGID(entityID, @group); + + index = default; + + if (UnsafeQueryEntityDictionary(@group, type, out var safeDictionary) == false) + return false; + + if (safeDictionary.TryFindIndex(entityGID.entityID, out index) == false) + return false; + + return true; } - //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; + internal uint GetIndex(uint entityID, ExclusiveGroupStruct @group, Type type) + { + EGID entityGID = new EGID(entityID, @group); + + if (UnsafeQueryEntityDictionary(@group, type, out var safeDictionary) == false) + { + throw new EntityNotFoundException(entityGID, type); + } + + if (safeDictionary.TryFindIndex(entityGID.entityID, out var index) == false) + throw new EntityNotFoundException(entityGID, type); - //needed to be able to iterate over all the entities of the same type regardless the group - //may change in future - readonly FasterDictionary, FasterDictionary> _groupsPerEntity; - readonly EntitiesStream _entityStream; + return index; + } - static class EmptyList + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static IBuffer RetrieveEmptyEntityComponentArray() where T : struct, IEntityComponent { - internal static readonly T[] emptyArray = new T[0]; + return EmptyList.emptyArray; } + + static class EmptyList where T : struct, IEntityComponent + { + internal static readonly IBuffer emptyArray; + + static EmptyList() + { + if (ComponentBuilder.IS_ENTITY_VIEW_COMPONENT) + { + MB b = default; + + emptyArray = b; + } + else + { + NB b = default; + + emptyArray = b; + } + } + } + + static readonly FasterDictionary _emptyDictionary = + new FasterDictionary(); + + readonly EnginesRoot _enginesRoot; + + EntitiesStreams _entityStream => _enginesRoot._entityStreams; + + //grouped set of entity components, this is the standard way to handle entity components 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 components of + //a specific type inside a specific group. +FasterDictionary> + groupEntityComponentsDB => _enginesRoot._groupEntityComponentsDB; + +//for each entity view type, return the groups (dictionary of entities indexed by entity id) where they are +//found indexed by group id. TypeSafeDictionary are never created, they instead point to the ones hold +//by _groupEntityComponentsDB +// >> + FasterDictionary> groupsPerEntity => + _enginesRoot._groupsPerEntity; } } \ No newline at end of file diff --git a/EntityBuilder.CheckFields.cs b/EntityBuilder.CheckFields.cs deleted file mode 100644 index f1f2d25..0000000 --- a/EntityBuilder.CheckFields.cs +++ /dev/null @@ -1,138 +0,0 @@ -#if !DEBUG || PROFILER -#define DISABLE_CHECKS -using System.Diagnostics; -#endif -using System; -using System.Reflection; - -namespace Svelto.ECS -{ - internal static class EntityBuilderUtilities - { - const string MSG = "Entity Structs field and Entity View Struct components must hold value types."; - - -#if DISABLE_CHECKS - [Conditional("_CHECKS_DISABLED")] -#endif - public static void CheckFields(Type entityStructType, bool needsReflection, bool isStringAllowed = false) - { - if (entityStructType == ENTITY_STRUCT_INFO_VIEW || - entityStructType == EGIDType || - entityStructType == EXCLUSIVEGROUPSTRUCTTYPE || - entityStructType == SERIALIZABLE_ENTITY_STRUCT) - { - return; - } - - if (needsReflection == false) - { - if (entityStructType.IsClass) - { - throw new EntityStructException("EntityStructs must be structs.", entityStructType); - } - - FieldInfo[] fields = entityStructType.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); - } - } - else - { - FieldInfo[] fields = entityStructType.GetFields(BindingFlags.Public | BindingFlags.Instance); - - if (fields.Length < 1) - { - ProcessError("Entity View Structs must hold only entity components interfaces.", entityStructType); - } - - for (int i = fields.Length - 1; i >= 0; --i) - { - FieldInfo fieldInfo = fields[i]; - - if (fieldInfo.FieldType.IsInterfaceEx() == false) - { - ProcessError("Entity View Structs must hold only entity components interfaces.", - entityStructType); - } - - PropertyInfo[] properties = fieldInfo.FieldType.GetProperties( - BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly); - - for (int j = properties.Length - 1; j >= 0; --j) - { - if (properties[j].PropertyType.IsGenericType) - { - Type genericTypeDefinition = properties[j].PropertyType.GetGenericTypeDefinition(); - if (genericTypeDefinition == DISPATCHONSETTYPE || - genericTypeDefinition == DISPATCHONCHANGETYPE) - { - continue; - } - } - - Type propertyType = properties[j].PropertyType; - if (propertyType != STRINGTYPE) - { - //for EntityViewStructs, component fields that are structs that hold strings - //are allowed - SubCheckFields(propertyType, entityStructType, isStringAllowed: true); - } - } - } - } - } - - static void SubCheckFields(Type fieldType, Type entityStructType, bool isStringAllowed = false) - { - if (fieldType.IsPrimitive || fieldType.IsValueType || (isStringAllowed == true && fieldType == STRINGTYPE)) - { - if (fieldType.IsValueType && !fieldType.IsEnum && fieldType.IsPrimitive == false) - { - CheckFields(fieldType, false, isStringAllowed); - } - - return; - } - - ProcessError(MSG, entityStructType, fieldType); - } - - static void ProcessError(string message, Type entityViewType, Type fieldType = null) - { - if (fieldType != null) - { - throw new EntityStructException(message, entityViewType, fieldType); - } - - throw new EntityStructException(message, entityViewType); - } - - 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 STRINGTYPE = typeof(string); - - internal static readonly Type ENTITY_STRUCT_INFO_VIEW = typeof(EntityStructInfoView); - } - - public class EntityStructException : Exception - { - public EntityStructException(string message, Type entityViewType, Type type) : - base(message.FastConcat(" entity view: '", entityViewType.ToString(), "', field: '", type.ToString())) - { - } - - public EntityStructException(string message, Type entityViewType) : - base(message.FastConcat(" entity view: ", entityViewType.ToString())) - { - } - } -} \ No newline at end of file diff --git a/EntityBuilder.cs b/EntityBuilder.cs deleted file mode 100644 index 78aaffa..0000000 --- a/EntityBuilder.cs +++ /dev/null @@ -1,146 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Reflection; -using Svelto.DataStructures; -using Svelto.ECS.Hybrid; -using Svelto.ECS.Internal; -using Svelto.Utilities; - -namespace Svelto.ECS -{ - public class EntityBuilder : IEntityBuilder where T : struct, IEntityStruct - { - 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() - { - _initializer = DEFAULT_IT; - - EntityBuilderUtilities.CheckFields(ENTITY_VIEW_TYPE, NEEDS_REFLECTION); - - if (NEEDS_REFLECTION) - EntityView.InitCache(); - } - - public EntityBuilder(in T initializer) : this() - { - _initializer = initializer; - } - - public void BuildEntityAndAddToList(ref ITypeSafeDictionary dictionary, EGID egid, - IEnumerable implementors) - { - if (dictionary == null) - dictionary = new TypeSafeDictionary(); - - var castedDic = dictionary as TypeSafeDictionary; - - if (NEEDS_REFLECTION) - { - DBC.ECS.Check.Require(implementors != null, - $"Implementors not found while building an EntityView `{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); - - this.FillEntityView(ref entityView, entityViewBlazingFastReflection, implementors, - EntityView.implementorsByType, EntityView.cachedTypes); - - castedDic.Add(egid.entityID, entityView); - } - else - { - DBC.ECS.Check.Require(!castedDic.ContainsKey(egid.entityID), - $"building an entity with already used entity id! id: '{egid.entityID}'"); - - castedDic.Add(egid.entityID, _initializer); - } - } - - ITypeSafeDictionary IEntityBuilder.Preallocate(ref ITypeSafeDictionary dictionary, uint size) - { - return Preallocate(ref dictionary, size); - } - - static ITypeSafeDictionary Preallocate(ref ITypeSafeDictionary dictionary, uint size) - { - if (dictionary == null) - dictionary = new TypeSafeDictionary(size); - else - dictionary.SetCapacity(size); - - return dictionary; - } - - public Type GetEntityType() - { - return ENTITY_VIEW_TYPE; - } - - static EntityBuilder() - { - ENTITY_VIEW_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(); - SetEGIDWithoutBoxing.Warmup(); - } - - readonly T _initializer; - - static FasterList>> entityViewBlazingFastReflection => - EntityView.cachedFields; - - internal static readonly Type ENTITY_VIEW_TYPE; - public static readonly bool HAS_EGID; - - static readonly T DEFAULT_IT; - static readonly bool NEEDS_REFLECTION; - static readonly string ENTITY_VIEW_NAME; - } -} \ No newline at end of file diff --git a/EntityBuilder.cs.rej b/EntityBuilder.cs.rej deleted file mode 100644 index 9ea149e..0000000 --- a/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/EntityCollection.cs b/EntityCollection.cs index 355e0e8..c39ed69 100644 --- a/EntityCollection.cs +++ b/EntityCollection.cs @@ -1,326 +1,221 @@ -using System; -using System.Collections; -using System.Collections.Generic; +using System.Runtime.CompilerServices; +using Svelto.DataStructures; +using Svelto.ECS.Internal; namespace Svelto.ECS { - public struct EntityCollection + public readonly ref struct EntityCollection where T : struct, IEntityComponent { - public EntityCollection(T[] array, uint count) + static readonly bool IsUnmanaged = TypeSafeDictionary.IsUnmanaged; + + public EntityCollection(IBuffer buffer, uint count):this() { - _array = array; - _count = count; + if (IsUnmanaged) + _nativedBuffer = (NB) buffer; + else + _managedBuffer = (MB) buffer; + + _count = count; } - public EntityIterator GetEnumerator() - { - return new EntityIterator(_array, _count); - } - - readonly T[] _array; - readonly uint _count; - - public struct EntityIterator : IEnumerator - { - public EntityIterator(T[] array, uint count) : this() - { - _array = array; - _count = count; - _index = -1; - } - - public bool MoveNext() - { - return ++_index < _count; - } - - public void Reset() - { - _index = -1; - } - - public ref T Current => ref _array[_index]; + public uint count => _count; - T IEnumerator.Current => throw new NotImplementedException(); - object IEnumerator.Current => throw new NotImplementedException(); + internal readonly MB _managedBuffer; + internal readonly NB _nativedBuffer; - public void Dispose() {} - - readonly T[] _array; - readonly uint _count; - int _index; - } + readonly uint _count; } - - public struct EntityCollection + + public readonly ref struct EntityCollection where T1 : struct, IEntityComponent where T2 : struct, IEntityComponent { - public EntityCollection(in (T1[], T2[]) array, uint count) + internal EntityCollection(in EntityCollection array1, in EntityCollection array2) { - _array = array; - _count = count; + _array1 = array1; + _array2 = array2; } - public EntityIterator GetEnumerator() + public uint count => _array1.count; + + internal EntityCollection buffer2 { - return new EntityIterator(_array, _count); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _array2; } - readonly (T1[], T2[]) _array; - readonly uint _count; - - public struct EntityIterator : IEnumerator> + internal EntityCollection buffer1 { - public EntityIterator((T1[], T2[]) array, uint count) : this() - { - _array = array; - _count = count; - _index = -1; - } - - public bool MoveNext() - { - return ++_index < _count; - } - - public void Reset() - { - _index = -1; - } - - public ValueRef Current => new ValueRef(_array, (uint) _index); - - ValueRef IEnumerator>. Current => throw new NotImplementedException(); - object IEnumerator.Current => throw new NotImplementedException(); - - public void Dispose() {} - - readonly (T1[], T2[]) _array; - readonly uint _count; - int _index; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _array1; } + + readonly EntityCollection _array1; + readonly EntityCollection _array2; } - - public struct EntityCollection + + public readonly ref struct EntityCollection where T3 : struct, IEntityComponent + where T2 : struct, IEntityComponent + where T1 : struct, IEntityComponent { - public EntityCollection(in (T1[], T2[], T3[]) array, uint count) + internal EntityCollection + (in EntityCollection array1, in EntityCollection array2, in EntityCollection array3) { - _array = array; - _count = count; + _array1 = array1; + _array2 = array2; + _array3 = array3; } - public EntityIterator GetEnumerator() + internal EntityCollection buffer1 { - return new EntityIterator(_array, _count); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _array1; } - readonly (T1[], T2[], T3[]) _array; - readonly uint _count; - - public struct EntityIterator : IEnumerator> + internal EntityCollection buffer2 { - public EntityIterator((T1[], T2[], T3[]) array, uint count) : this() - { - _array = array; - _count = count; - _index = -1; - } - - public bool MoveNext() - { - return ++_index < _count; - } - - public void Reset() - { - _index = -1; - } - - public ValueRef Current => new ValueRef(_array, (uint) _index); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _array2; + } - ValueRef IEnumerator>.Current => throw new NotImplementedException(); - object IEnumerator. Current => throw new NotImplementedException(); + internal EntityCollection buffer3 + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _array3; + } - public void Dispose() {} + internal uint count => buffer1.count; - readonly (T1[], T2[], T3[]) _array; - readonly uint _count; - int _index; - } + readonly EntityCollection _array1; + readonly EntityCollection _array2; + readonly EntityCollection _array3; } - - public struct EntityCollections where T : struct, IEntityStruct + + public readonly ref struct EntityCollection + where T1 : struct, IEntityComponent + where T2 : struct, IEntityComponent + where T3 : struct, IEntityComponent + where T4 : struct, IEntityComponent { - public EntityCollections(IEntitiesDB db, ExclusiveGroup[] groups) : this() + internal EntityCollection + (in EntityCollection array1, in EntityCollection array2, in EntityCollection array3, in EntityCollection array4) { - _db = db; - _groups = groups; + _array1 = array1; + _array2 = array2; + _array3 = array3; + _array4 = array4; } - public EntityGroupsIterator GetEnumerator() + internal EntityCollection Item1 { - return new EntityGroupsIterator(_db, _groups); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _array1; } - readonly IEntitiesDB _db; - readonly ExclusiveGroup[] _groups; - - public struct EntityGroupsIterator : IEnumerator + internal EntityCollection Item2 { - public EntityGroupsIterator(IEntitiesDB db, ExclusiveGroup[] groups) : this() - { - _db = db; - _groups = groups; - _indexGroup = -1; - _index = -1; - } - - public bool MoveNext() - { - while (_index + 1 >= _count && ++_indexGroup < _groups.Length) - { - _index = -1; - _array = _db.QueryEntities(_groups[_indexGroup], out _count); - } - - return ++_index < _count; - } - - public void Reset() - { - _index = -1; - _indexGroup = -1; - _count = 0; - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _array2; + } - public ref T Current => ref _array[_index]; + internal EntityCollection Item3 + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _array3; + } + + internal EntityCollection Item4 + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _array4; + } - T IEnumerator.Current => throw new NotImplementedException(); - object IEnumerator.Current => throw new NotImplementedException(); + internal uint count => _array1.count; - public void Dispose() {} + readonly EntityCollection _array1; + readonly EntityCollection _array2; + readonly EntityCollection _array3; + readonly EntityCollection _array4; + } - readonly IEntitiesDB _db; - readonly ExclusiveGroup[] _groups; + public readonly struct BT + { + public readonly BufferT1 buffer1; + public readonly BufferT2 buffer2; + public readonly BufferT3 buffer3; + public readonly BufferT4 buffer4; + public readonly int count; - T[] _array; - uint _count; - int _index; - int _indexGroup; + 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 = (int) count; } } - public struct EntityCollections where T1 : struct, IEntityStruct where T2 : struct, IEntityStruct + public readonly struct BT { - public EntityCollections(IEntitiesDB db, ExclusiveGroup[] groups) : this() - { - _db = db; - _groups = groups; - } + public readonly BufferT1 buffer1; + public readonly BufferT2 buffer2; + public readonly BufferT3 buffer3; + public readonly int count; - public EntityGroupsIterator GetEnumerator() + public BT(BufferT1 bufferT1, BufferT2 bufferT2, BufferT3 bufferT3, uint count) : this() { - return new EntityGroupsIterator(_db, _groups); + this.buffer1 = bufferT1; + this.buffer2 = bufferT2; + this.buffer3 = bufferT3; + this.count = (int) count; } - readonly IEntitiesDB _db; - readonly ExclusiveGroup[] _groups; - - public struct EntityGroupsIterator : IEnumerator> + public void Deconstruct(out BufferT1 bufferT1, out BufferT2 bufferT2, out BufferT3 bufferT3, out int count) { - public EntityGroupsIterator(IEntitiesDB db, ExclusiveGroup[] groups) : this() - { - _db = db; - _groups = groups; - _indexGroup = -1; - _index = -1; - } - - public bool MoveNext() - { - 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 - } - - return ++_index < _count; - } - - public void Reset() - { - _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 - } - - public ValueRef Current - { - get - { - var valueRef = new ValueRef(_array, (uint) _index); - return valueRef; - } - } - - ValueRef IEnumerator>.Current => throw new NotImplementedException(); - object IEnumerator.Current => throw new NotImplementedException(); - - public void Dispose() {} - - readonly IEntitiesDB _db; - readonly ExclusiveGroup[] _groups; - uint _count; - int _index; - int _indexGroup; - (T1[], T2[]) _array; + bufferT1 = buffer1; + bufferT2 = buffer2; + bufferT3 = buffer3; + count = this.count; } } - - public struct ValueRef - { - readonly (T1[], T2[]) array; - readonly uint index; + public readonly struct BT + { + public readonly BufferT1 buffer; + public readonly int count; - public ValueRef(in (T1[], T2[]) entity1, uint i) + public BT(BufferT1 bufferT1, uint count) : this() { - array = entity1; - index = i; + this.buffer = bufferT1; + this.count = (int) count; } - - public ref T1 entityStructA => ref array.Item1[index]; - public ref T2 entityStructB => ref array.Item2[index]; + + public void Deconstruct(out BufferT1 bufferT1, out int count) + { + bufferT1 = buffer; + count = this.count; + } + + public static implicit operator BufferT1(BT t) => t.buffer; } - - public struct ValueRef - { - readonly (T1[], T2[], T3[]) array; - readonly uint index; + public readonly struct BT + { + public readonly BufferT1 buffer1; + public readonly BufferT2 buffer2; + public readonly int count; - public ValueRef(in (T1[], T2[], T3[]) entity1, uint i) + public BT(BufferT1 bufferT1, BufferT2 bufferT2, uint count) : this() { - array = entity1; - index = i; + this.buffer1 = bufferT1; + this.buffer2 = bufferT2; + this.count = (int) count; + } + + public void Deconstruct(out BufferT1 bufferT1, out BufferT2 bufferT2, out int count) + { + bufferT1 = buffer1; + bufferT2 = buffer2; + count = this.count; } - - public ref T1 entityStructA => ref array.Item1[index]; - public ref T2 entityStructB => ref array.Item2[index]; - public ref T3 entityStructC => ref array.Item3[index]; - } } diff --git a/EntityComponentInitializer.cs b/EntityComponentInitializer.cs new file mode 100644 index 0000000..72cbad2 --- /dev/null +++ b/EntityComponentInitializer.cs @@ -0,0 +1,63 @@ +using System; +using Svelto.DataStructures; +using Svelto.ECS.Internal; + +namespace Svelto.ECS +{ + public readonly ref struct EntityComponentInitializer + { + public EntityComponentInitializer(EGID id, FasterDictionary group) + { + _group = group; + _ID = id; + } + + public EGID EGID => _ID; + + public void Init(T initializer) where T : struct, IEntityComponent + { + if (_group.TryGetValue(new RefWrapperType(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.GetDirectValueByRef(findElementIndex) = initializer; + } + + public ref T GetOrCreate() where T : struct, IEntityComponent + { + ref var entityDictionary = ref _group.GetOrCreate(new RefWrapperType(ComponentBuilder.ENTITY_COMPONENT_TYPE) + , TypeSafeDictionaryFactory.Create); + var dictionary = (ITypeSafeDictionary) entityDictionary; + + return ref dictionary.GetOrCreate(_ID.entityID); + } + + public ref T Get() where T : struct, IEntityComponent + { + return ref (_group[new RefWrapperType(ComponentBuilder.ENTITY_COMPONENT_TYPE)] as ITypeSafeDictionary)[ + _ID.entityID]; + } + + public bool Has() where T : struct, IEntityComponent + { + if (_group.TryGetValue(new RefWrapperType(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 _group; + } +} \ No newline at end of file diff --git a/EntityComponentInitializer.cs.meta b/EntityComponentInitializer.cs.meta new file mode 100644 index 0000000..619a26e --- /dev/null +++ b/EntityComponentInitializer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 005da4cbd47736e1bc2fb1676cb30190 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/EntityDescriptorTemplate.cs b/EntityDescriptorTemplate.cs index b96e3bd..d32b16b 100644 --- a/EntityDescriptorTemplate.cs +++ b/EntityDescriptorTemplate.cs @@ -1,17 +1,26 @@ +using System; + namespace Svelto.ECS { public interface IEntityDescriptor { - IEntityBuilder[] entitiesToBuild { get; } + IComponentBuilder[] componentsToBuild { get; } + } + + public interface IDynamicEntityDescriptor: IEntityDescriptor + { } static class EntityDescriptorTemplate where TType : IEntityDescriptor, new() { static EntityDescriptorTemplate() { - descriptor = new TType(); + realDescriptor = new TType(); + descriptor = realDescriptor; } - public static IEntityDescriptor descriptor { get; } + public static TType realDescriptor { get; } + public static Type type => typeof(TType); + public static IEntityDescriptor descriptor { get; } } } diff --git a/EntityFactory.cs b/EntityFactory.cs index 3556ae8..ef1760a 100644 --- a/EntityFactory.cs +++ b/EntityFactory.cs @@ -6,92 +6,77 @@ namespace Svelto.ECS.Internal { static class EntityFactory { - public static FasterDictionary, ITypeSafeDictionary> BuildGroupedEntities(EGID egid, - EnginesRoot.DoubleBufferedEntitiesToAdd groupEntitiesToAdd, - IEntityBuilder[] entitiesToBuild, - IEnumerable implementors) + public static FasterDictionary BuildGroupedEntities + (EGID egid, EnginesRoot.DoubleBufferedEntitiesToAdd groupEntitiesToAdd + , IComponentBuilder[] componentsToBuild, IEnumerable implementors, Type implementorType) { var group = FetchEntityGroup(egid.groupID, groupEntitiesToAdd); - BuildEntitiesAndAddToGroup(egid, group, entitiesToBuild, implementors); + BuildEntitiesAndAddToGroup(egid, group, componentsToBuild, implementors, implementorType); return group; } - static FasterDictionary, ITypeSafeDictionary> FetchEntityGroup(uint groupID, - EnginesRoot.DoubleBufferedEntitiesToAdd groupEntityViewsByType) + static FasterDictionary FetchEntityGroup(ExclusiveGroupStruct groupID, + 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>(); + group = new FasterDictionary(); - 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) + static void BuildEntitiesAndAddToGroup + (EGID entityID, FasterDictionary @group + , IComponentBuilder[] componentBuilders, IEnumerable implementors, Type implementorType) { -#if DEBUG && !PROFILER + var count = componentBuilders.Length; + +#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 for (var index = 0; index < count; ++index) { - var entityViewType = entityBuilders[index].GetEntityType(); - if (types.Contains(entityViewType)) + var entityComponentType = componentBuilders[index].GetEntityComponentType(); + if (types.Contains(entityComponentType)) { - throw new ECSException("EntityBuilders must be unique inside an EntityDescriptor"); + throw new ECSException($"EntityBuilders must be unique inside an EntityDescriptor. Descriptor Type {implementorType} Component Type: {entityComponentType}"); } - types.Add(entityViewType); + types.Add(entityComponentType); } #endif for (var index = 0; index < count; ++index) { - var entityStructBuilder = entityBuilders[index]; - var entityViewType = entityStructBuilder.GetEntityType(); + var entityComponentBuilder = componentBuilders[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) + static void BuildEntity(EGID entityID, FasterDictionary group, + Type entityComponentType, IComponentBuilder componentBuilder, IEnumerable implementors) { - var entityViewsPoolWillBeCreated = - group.TryGetValue(new RefWrapper(entityViewType), out var safeDictionary) == false; + var entityComponentsPoolWillBeCreated = + group.TryGetValue(new RefWrapperType(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 RefWrapperType(entityComponentType), safeDictionary); } } } \ No newline at end of file diff --git a/EntityGroupNotFoundException.cs b/EntityGroupNotFoundException.cs index 63fb6e6..be306f0 100644 --- a/EntityGroupNotFoundException.cs +++ b/EntityGroupNotFoundException.cs @@ -4,8 +4,8 @@ namespace Svelto.ECS.Internal { class EntityGroupNotFoundException : Exception { - public EntityGroupNotFoundException(uint groupId, Type type) - : base("entity group not found ".FastConcat(type.ToString())) + public EntityGroupNotFoundException(Type type, string toName) + : base($"entity group {toName} not used for component type ".FastConcat(type.ToString())) { } } diff --git a/EntityHierarchyStruct.cs b/EntityHierarchyStruct.cs deleted file mode 100644 index c0fe044..0000000 --- a/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/EntityHierarchyStruct.cs.meta b/EntityHierarchyStruct.cs.meta deleted file mode 100644 index 693ad62..0000000 --- a/EntityHierarchyStruct.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: dd25991b7ae539ff89f73ae33b98106f -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/EntityInfoView.cs b/EntityInfoView.cs index 98711f8..edbb968 100644 --- a/EntityInfoView.cs +++ b/EntityInfoView.cs @@ -1,7 +1,7 @@ namespace Svelto.ECS { - struct EntityStructInfoView: IEntityStruct + struct EntityInfoComponent: IEntityComponent { - public IEntityBuilder[] entitiesToBuild; + public IComponentBuilder[] componentsToBuild; } } \ No newline at end of file diff --git a/EntityNotFoundException.cs b/EntityNotFoundException.cs index e2e4fda..576b536 100644 --- a/EntityNotFoundException.cs +++ b/EntityNotFoundException.cs @@ -5,7 +5,7 @@ namespace Svelto.ECS public class EntityNotFoundException : Exception { public EntityNotFoundException(EGID entityEGID, Type entityType) : base( - $"entity of type '{entityType}' with ID '{entityEGID.entityID}', group '{(uint) entityEGID.groupID}' not found!") + $"entity of type '{entityType}' with ID '{entityEGID.entityID}', group '{entityEGID.groupID.ToName()}' not found!") { } } diff --git a/EntityStream.cs b/EntityStream.cs deleted file mode 100644 index d8aaf24..0000000 --- a/EntityStream.cs +++ /dev/null @@ -1,163 +0,0 @@ -using System; -using Svelto.DataStructures; - -namespace Svelto.ECS -{ - /// - /// Do not use this class in place of a normal polling. - /// I eventually realised than in ECS no form of communication other than polling entity components can exist. - /// Using groups, you can have always an optimal set of entity components to poll, so EntityStreams must be used - /// only if: - /// - you want to polling engine to be able to track all the entity changes happening in between polls and not - /// just the current state - /// - you want a thread-safe way to read entity states, which includes all the state changes and not the last - /// one only - /// - you want to communicate between EnginesRoots - /// - - class EntitiesStream : IDisposable - { - internal Consumer GenerateConsumer(string name, uint capacity) where T : unmanaged, IEntityStruct - { - 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 - { - if (_streams.ContainsKey(TypeRefWrapper.wrapper) == false) _streams[TypeRefWrapper.wrapper] = new EntityStream(); - - return (_streams[TypeRefWrapper.wrapper] as EntityStream).GenerateConsumer(group, name, capacity); - } - - internal void PublishEntity(ref T entity, EGID egid) where T : unmanaged, IEntityStruct - { - if (_streams.TryGetValue(TypeRefWrapper.wrapper, out var typeSafeStream)) - (typeSafeStream as EntityStream).PublishEntity(ref entity, egid); - else - Console.LogDebug("No Consumers are waiting for this entity to change ", typeof(T)); - } - - readonly ThreadSafeDictionary, ITypeSafeStream> _streams = - new ThreadSafeDictionary, ITypeSafeStream>(); - - public void Dispose() - { - _streams.Clear(); - } - } - - interface ITypeSafeStream - {} - - class EntityStream : ITypeSafeStream where T : unmanaged, IEntityStruct - { - public void PublishEntity(ref T entity, EGID egid) - { - for (int i = 0; i < _consumers.Count; i++) - { - if (_consumers[i]._hasGroup) - { - if (egid.groupID == _consumers[i]._group) - { - _consumers[i].Enqueue(entity, egid); - } - } - else - { - _consumers[i].Enqueue(entity, egid); - } - } - } - - public Consumer GenerateConsumer(string name, uint capacity) - { - var consumer = new Consumer(name, capacity, this); - - _consumers.Add(consumer); - - return consumer; - } - - public Consumer GenerateConsumer(ExclusiveGroup group, string name, uint capacity) - { - var consumer = new Consumer(group, name, capacity, this); - - _consumers.Add(consumer); - - return consumer; - } - - public void RemoveConsumer(Consumer consumer) - { - _consumers.UnorderedRemove(consumer); - } - - readonly FasterListThreadSafe> _consumers = new FasterListThreadSafe>(); - } - - public struct Consumer : IDisposable where T : unmanaged, IEntityStruct - { - internal Consumer(string name, uint capacity, EntityStream stream):this() - { -#if DEBUG && !PROFILER - _name = name; -#endif - _ringBuffer = new RingBuffer>((int) capacity, -#if DEBUG && !PROFILER - _name -#else - string.Empty -#endif - ); - - _stream = stream; - } - - internal Consumer(ExclusiveGroup group, string name, uint capacity, EntityStream stream) : this(name, - capacity, stream) - { - _group = group; - _hasGroup = true; - } - - internal void Enqueue(in T entity, in EGID egid) - { - _ringBuffer.Enqueue((entity, egid)); - } - - public bool TryDequeue(out T entity) - { - var tryDequeue = _ringBuffer.TryDequeue(out var values); - - entity = values.Item1; - - return tryDequeue; - } - - public bool TryDequeue(out T entity, out EGID id) - { - var tryDequeue = _ringBuffer.TryDequeue(out var values); - - entity = values.Item1; - id = values.Item2; - - 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; - - internal readonly ExclusiveGroup _group; - internal readonly bool _hasGroup; - -#if DEBUG && !PROFILER - readonly string _name; -#endif - } -} diff --git a/EntityStructInitializer.cs b/EntityStructInitializer.cs deleted file mode 100644 index ed85d53..0000000 --- a/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/EntityStructInitializer.cs.meta b/EntityStructInitializer.cs.meta deleted file mode 100644 index 6613f8b..0000000 --- a/EntityStructInitializer.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: fb6bf1ad9be534e8a24b96e680030bb8 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/EntitySubmissionScheduler.cs b/EntitySubmissionScheduler.cs index e6d0ffc..d5af356 100644 --- a/EntitySubmissionScheduler.cs +++ b/EntitySubmissionScheduler.cs @@ -2,8 +2,20 @@ using System; namespace Svelto.ECS.Schedulers { - public interface IEntitySubmissionScheduler: IDisposable + public interface IEntitiesSubmissionScheduler: IDisposable { - EnginesRoot.EntitiesSubmitter onTick { set; } + bool paused { get; set; } + } + + public abstract class EntitiesSubmissionScheduler: IEntitiesSubmissionScheduler + { + protected internal abstract EnginesRoot.EntitiesSubmitter onTick { set; } + public abstract void Dispose(); + public abstract bool paused { get; set; } + } + + public abstract class ISimpleEntitiesSubmissionScheduler: EntitiesSubmissionScheduler + { + public abstract void SubmitEntities(); } } \ No newline at end of file diff --git a/EntitySubmitOperation.cs b/EntitySubmitOperation.cs index 57299be..3f6ecf5 100644 --- a/EntitySubmitOperation.cs +++ b/EntitySubmitOperation.cs @@ -1,5 +1,4 @@ using System; -using System.Diagnostics; namespace Svelto.ECS { @@ -9,25 +8,37 @@ 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 } - + + public EntitySubmitOperation + (EntitySubmitOperationType operation, ExclusiveGroupStruct @group + , IComponentBuilder[] descriptorComponentsToBuild):this() + { + type = operation; + this.builders = descriptorComponentsToBuild; + fromID = new EGID(0, group); +#if DEBUG && !PROFILE_SVELTO + trace = default; +#endif + } + public static bool operator ==(EntitySubmitOperation obj1, EntitySubmitOperation obj2) { return obj1.Equals(obj2); @@ -48,6 +59,7 @@ namespace Svelto.ECS { Swap, Remove, - RemoveGroup + RemoveGroup, + SwapGroup } } \ No newline at end of file diff --git a/EntityViewUtility.cs b/EntityViewUtility.cs index 8e4be9b..6f964d9 100644 --- a/EntityViewUtility.cs +++ b/EntityViewUtility.cs @@ -1,77 +1,89 @@ 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); - - foreach (var implementor in implementors) + //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 + if (implementors != null) { - if (implementor != null) + foreach (var implementor in implementors) { - var type = implementor.GetType(); + 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 (implementorsByType.TryGetValue(componentType, out var implementorData)) + for (var iindex = 0; iindex < interfaces.Length; iindex++) { - implementorData.numberOfImplementations++; - implementorsByType[componentType] = implementorData; - } - else - implementorsByType[componentType] = new ECSTuple(implementor, 1); + var componentType = interfaces[iindex]; +#if DEBUG && !PROFILE_SVELTO + if (implementorsByType.TryGetValue(componentType, out var implementorData)) + { + implementorData.numberOfImplementations++; + implementorsByType[componentType] = implementorData; + } + else + implementorsByType[componentType] = new ECSTuple(implementor, 1); #else implementorsByType[componentType] = implementor; #endif + } + } +#if DEBUG && !PROFILE_SVELTO + else + { + Console.Log(NULL_IMPLEMENTOR_ERROR.FastConcat(" entityComponent " + , componentBuilder + .GetEntityComponentType().ToString())); } - } -#if DEBUG && !PROFILER - else - { - Console.Log(NULL_IMPLEMENTOR_ERROR.FastConcat(" entityView ", - entityBuilder.GetEntityType().ToString())); - } #endif + } } for (var i = 0; i < count; i++) @@ -79,47 +91,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/ExclusiveGroup.cs b/ExclusiveGroup.cs index 959f7cc..b4fdac3 100644 --- a/ExclusiveGroup.cs +++ b/ExclusiveGroup.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Runtime.CompilerServices; #pragma warning disable 660,661 @@ -17,26 +18,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 +33,7 @@ namespace Svelto.ECS { _group = ExclusiveGroupStruct.Generate(); - _serialisedGroups.Add(recognizeAs, _group); + _knownGroups.Add(recognizeAs, _group); } public ExclusiveGroup(ushort range) @@ -61,7 +48,7 @@ namespace Svelto.ECS { return group._group; } - + public static explicit operator uint(ExclusiveGroup group) { return group._group; @@ -71,118 +58,34 @@ 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 + + //todo document the use case for this method + 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 +163,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/ExclusiveGroupStruct.cs b/ExclusiveGroupStruct.cs new file mode 100644 index 0000000..8bb0a9c --- /dev/null +++ b/ExclusiveGroupStruct.cs @@ -0,0 +1,139 @@ +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; + +namespace Svelto.ECS +{ + public readonly struct BuildGroup + { + internal BuildGroup(ExclusiveGroupStruct group) + { + this.group = group; + } + + public static implicit operator BuildGroup(ExclusiveGroupStruct group) + { + return new BuildGroup(group); + } + + public static implicit operator BuildGroup(ExclusiveGroup group) + { + return new BuildGroup(group); + } + + public static implicit operator uint(BuildGroup groupStruct) + { + return groupStruct.group; + } + + internal ExclusiveGroupStruct @group { get; } + } + + [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 this.ToName(); + } + + 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 = 1; //it starts from 1 because default EGID is considered not initalized value + } +} \ No newline at end of file diff --git a/ExclusiveGroupStruct.cs.meta b/ExclusiveGroupStruct.cs.meta new file mode 100644 index 0000000..6a5f657 --- /dev/null +++ b/ExclusiveGroupStruct.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 67dde70e73a3374a87d0e9b93385405a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/ExecuteOnEntitiesDB.cs b/ExecuteOnEntitiesDB.cs deleted file mode 100644 index 375dfb4..0000000 --- a/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/ExecuteOnEntitiesDB.cs.meta b/ExecuteOnEntitiesDB.cs.meta deleted file mode 100644 index ff35559..0000000 --- a/ExecuteOnEntitiesDB.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 43acb9dc60f93889a5814ea34494169b -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/ExtendibleEntityDescriptor.cs b/ExtendibleEntityDescriptor.cs index 3b420fd..384823c 100644 --- a/ExtendibleEntityDescriptor.cs +++ b/ExtendibleEntityDescriptor.cs @@ -8,7 +8,7 @@ namespace Svelto.ECS /// to swap and remove specialized entities from abstract engines /// /// - public class ExtendibleEntityDescriptor : IEntityDescriptor where TType : IEntityDescriptor, new() + public class ExtendibleEntityDescriptor : IDynamicEntityDescriptor where TType : IEntityDescriptor, new() { static ExtendibleEntityDescriptor() { @@ -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/Extensions/ProcessorCount.cs b/Extensions/ProcessorCount.cs new file mode 100644 index 0000000..4df4450 --- /dev/null +++ b/Extensions/ProcessorCount.cs @@ -0,0 +1,19 @@ +using System; + +namespace Svelto.ECS +{ + internal static class ProcessorCount + { + public static readonly int processorCount = Environment.ProcessorCount; + + public static int BatchSize(uint totalIterations) + { + var iterationsPerBatch = totalIterations / processorCount; + + if (iterationsPerBatch < 32) + return 32; + + return (int) iterationsPerBatch; + } + } +} \ No newline at end of file diff --git a/Extensions/ProcessorCount.cs.meta b/Extensions/ProcessorCount.cs.meta new file mode 100644 index 0000000..0dc4530 --- /dev/null +++ b/Extensions/ProcessorCount.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ce4c5807b1b43a598f0b8d7820575611 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Extensions/Svelto.meta b/Extensions/Svelto.meta new file mode 100644 index 0000000..3132538 --- /dev/null +++ b/Extensions/Svelto.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 30192a9315e83c87a194215a241c9fa1 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Extensions/Svelto/AllGroupsEnumerable.cs b/Extensions/Svelto/AllGroupsEnumerable.cs new file mode 100644 index 0000000..eb746df --- /dev/null +++ b/Extensions/Svelto/AllGroupsEnumerable.cs @@ -0,0 +1,70 @@ +using Svelto.Common; +using Svelto.DataStructures; +using Svelto.ECS.Internal; + +namespace Svelto.ECS +{ + /// + /// ToDo it would be interesting to have a version of this dedicated to unmanaged, IEntityComponent + /// that can be burstifiable + /// + /// + public readonly struct AllGroupsEnumerable where T1 : struct, IEntityComponent + { + public ref struct GroupCollection + { + internal EntityCollection collection; + internal ExclusiveGroupStruct group; + + public void Deconstruct(out EntityCollection collection, out ExclusiveGroupStruct group) + { + collection = this.collection; + group = this.@group; + } + } + + public AllGroupsEnumerable(EntitiesDB db) + { + _db = db; + } + + public ref struct GroupsIterator + { + public GroupsIterator(EntitiesDB db) : this() + { + _db = db.FindGroups_INTERNAL(TypeCache.type).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.collection = new EntityCollection(typeSafeDictionary.GetValues(out var count), count); + _array.@group = new ExclusiveGroupStruct(group.Key); + + return true; + } + + return false; + } + + public GroupCollection Current => _array; + + FasterDictionary.FasterDictionaryKeyValueEnumerator _db; + GroupCollection _array; + } + + public GroupsIterator GetEnumerator() + { + return new GroupsIterator(_db); + } + + readonly EntitiesDB _db; + } +} diff --git a/Extensions/Svelto/AllGroupsEnumerable.cs.meta b/Extensions/Svelto/AllGroupsEnumerable.cs.meta new file mode 100644 index 0000000..ea434bd --- /dev/null +++ b/Extensions/Svelto/AllGroupsEnumerable.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 6a6569334364384ea95a60579864448c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Extensions/Svelto/EntityCollectionExtension.cs b/Extensions/Svelto/EntityCollectionExtension.cs new file mode 100644 index 0000000..b9b4153 --- /dev/null +++ b/Extensions/Svelto/EntityCollectionExtension.cs @@ -0,0 +1,251 @@ +using System; +using System.Runtime.CompilerServices; +using Svelto.DataStructures; +using Svelto.ECS.Hybrid; + +namespace Svelto.ECS +{ + public static class EntityCollectionExtension + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Deconstruct + (in this EntityCollection ec, out NB buffer, out int count) where T1 : unmanaged, IEntityComponent + { + buffer = ec._nativedBuffer; + count = (int) ec.count; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Deconstruct + (in this EntityCollection ec, out NB buffer1, out NB buffer2, out int count) + where T1 : unmanaged, IEntityComponent where T2 : unmanaged, IEntityComponent + { + buffer1 = ec.buffer1._nativedBuffer; + buffer2 = ec.buffer2._nativedBuffer; + count = (int) ec.count; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Deconstruct + (in this EntityCollection ec, out NB buffer1, out NB buffer2, out NB buffer3 + , out int count) where T1 : unmanaged, IEntityComponent + where T2 : unmanaged, IEntityComponent + where T3 : unmanaged, IEntityComponent + { + buffer1 = ec.buffer1._nativedBuffer; + buffer2 = ec.buffer2._nativedBuffer; + buffer3 = ec.buffer3._nativedBuffer; + count = (int) ec.count; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Deconstruct + (in this EntityCollection ec, out NB buffer1, out NB buffer2, out NB buffer3 + , out NB buffer4, out int count) where T1 : unmanaged, IEntityComponent + where T2 : unmanaged, IEntityComponent + where T3 : unmanaged, IEntityComponent + where T4 : unmanaged, IEntityComponent + { + buffer1 = ec.Item1._nativedBuffer; + buffer2 = ec.Item2._nativedBuffer; + buffer3 = ec.Item3._nativedBuffer; + buffer4 = ec.Item4._nativedBuffer; + count = (int) ec.count; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static BT> ToBuffer(in this EntityCollection ec) where T1 : unmanaged, IEntityComponent + { + return new BT>(ec._nativedBuffer, ec.count); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static BT, NB> ToBuffers + (in this EntityCollection ec) + where T2 : unmanaged, IEntityComponent where T1 : unmanaged, IEntityComponent + { + return new BT, NB>(ec.buffer1._nativedBuffer, ec.buffer2._nativedBuffer, ec.count); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static BT, NB, NB> ToBuffers + (in this EntityCollection ec) + where T2 : unmanaged, IEntityComponent + where T1 : unmanaged, IEntityComponent + where T3 : unmanaged, IEntityComponent + { + return new BT, NB, NB>(ec.buffer1._nativedBuffer, ec.buffer2._nativedBuffer + , ec.buffer3._nativedBuffer, ec.count); + } + } + + public static class EntityCollectionExtensionB + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Deconstruct + (in this EntityCollection ec, out MB buffer, out int count) where T1 : struct, IEntityViewComponent + { + buffer = ec._managedBuffer; + count = (int) ec.count; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static BT> ToBuffer(in this EntityCollection ec) where T1 : struct, IEntityViewComponent + { + return new BT>(ec._managedBuffer, ec.count); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Deconstruct + (in this EntityCollection ec, out MB buffer1, out MB buffer2, out int count) + where T1 : struct, IEntityViewComponent where T2 : struct, IEntityViewComponent + { + buffer1 = ec.buffer1._managedBuffer; + buffer2 = ec.buffer2._managedBuffer; + count = (int) ec.count; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static (MB buffer1, MB buffer2, uint count) ToBuffers + (in this EntityCollection ec) + where T2 : struct, IEntityViewComponent where T1 : struct, IEntityViewComponent + { + return (ec.buffer1._managedBuffer, ec.buffer2._managedBuffer, ec.count); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Deconstruct + (in this EntityCollection ec, out MB buffer1, out MB buffer2, out MB buffer3 + , out int count) where T1 : struct, IEntityViewComponent + where T2 : struct, IEntityViewComponent + where T3 : struct, IEntityViewComponent + { + buffer1 = ec.buffer1._managedBuffer; + buffer2 = ec.buffer2._managedBuffer; + buffer3 = ec.buffer3._managedBuffer; + count = (int) ec.count; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static (MB buffer1, MB buffer2, MB buffer3, uint count) ToBuffers + (in this EntityCollection ec) + where T2 : struct, IEntityViewComponent + where T1 : struct, IEntityViewComponent + where T3 : struct, IEntityViewComponent + { + return (ec.buffer1._managedBuffer, ec.buffer2._managedBuffer, ec.buffer3._managedBuffer, ec.count); + } + } + + public static class EntityCollectionExtensionC + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static (NB buffer1, MB buffer2, uint count) ToBuffers + (in this EntityCollection ec) + where T1 : unmanaged, IEntityComponent where T2 : struct, IEntityViewComponent + { + return (ec.buffer1._nativedBuffer, ec.buffer2._managedBuffer, ec.count); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static (NB buffer1, MB buffer2, MB buffer3, uint count) ToBuffers + (in this EntityCollection ec) + where T1 : unmanaged, IEntityComponent + where T2 : struct, IEntityViewComponent + where T3 : struct, IEntityViewComponent + { + return (ec.buffer1._nativedBuffer, ec.buffer2._managedBuffer, ec.buffer3._managedBuffer, ec.count); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Deconstruct + (in this EntityCollection ec, out NB buffer1, out MB buffer2, out int count) + where T1 : unmanaged, IEntityComponent where T2 : struct, IEntityViewComponent + { + buffer1 = ec.buffer1._nativedBuffer; + buffer2 = ec.buffer2._managedBuffer; + count = (int) ec.count; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Deconstruct + (in this EntityCollection ec, out NB buffer1, out MB buffer2, out MB buffer3, out int count) + where T1 : unmanaged, IEntityComponent + where T2 : struct, IEntityViewComponent + where T3 : struct, IEntityViewComponent + { + buffer1 = ec.buffer1._nativedBuffer; + buffer2 = ec.buffer2._managedBuffer; + buffer3 = ec.buffer3._managedBuffer; + count = (int) ec.count; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Deconstruct + (in this EntityCollection ec, out NB buffer1, out NB buffer2, out NB buffer3 + , out MB buffer4, out int count) where T1 : unmanaged, IEntityComponent + where T2 : unmanaged, IEntityComponent + where T3 : unmanaged, IEntityComponent + where T4 : struct, IEntityViewComponent + { + buffer1 = ec.Item1._nativedBuffer; + buffer2 = ec.Item2._nativedBuffer; + buffer3 = ec.Item3._nativedBuffer; + buffer4 = ec.Item4._managedBuffer; + count = (int) ec.count; + } + } + + public static class EntityCollectionExtensionD + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Deconstruct + (in this EntityCollection ec, out NB buffer1, out NB buffer2, out MB buffer3 + , out int count) where T1 : unmanaged, IEntityComponent + where T2 : unmanaged, IEntityComponent + where T3 : struct, IEntityViewComponent + { + buffer1 = ec.buffer1._nativedBuffer; + buffer2 = ec.buffer2._nativedBuffer; + buffer3 = ec.buffer3._managedBuffer; + count = (int) ec.count; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static (NB buffer1, NB buffer2, MB buffer3, uint count) ToBuffers + (in this EntityCollection ec) + where T1 : unmanaged, IEntityComponent + where T2 : unmanaged, IEntityComponent + where T3 : struct, IEntityViewComponent + { + return (ec.buffer1._nativedBuffer, ec.buffer2._nativedBuffer, ec.buffer3._managedBuffer, ec.count); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static BT, NB, NB, NB> ToBuffers + (in this EntityCollection ec) + where T2 : unmanaged, IEntityComponent + where T1 : unmanaged, IEntityComponent + where T3 : unmanaged, IEntityComponent + where T4 : unmanaged, IEntityComponent + { + return new BT, NB, NB, NB>(ec.Item1._nativedBuffer, ec.Item2._nativedBuffer + , ec.Item3._nativedBuffer, ec.Item4._nativedBuffer, ec.count); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Deconstruct + (in this EntityCollection ec, out NB buffer1, out NB buffer2, out MB buffer3 + , out MB buffer4, out int count) where T1 : unmanaged, IEntityComponent + where T2 : unmanaged, IEntityComponent + where T3 : struct, IEntityViewComponent + where T4 : struct, IEntityViewComponent + { + buffer1 = ec.Item1._nativedBuffer; + buffer2 = ec.Item2._nativedBuffer; + buffer3 = ec.Item3._managedBuffer; + buffer4 = ec.Item4._managedBuffer; + count = (int) ec.count; + } + } +} \ No newline at end of file diff --git a/Extensions/Svelto/EntityCollectionExtension.cs.meta b/Extensions/Svelto/EntityCollectionExtension.cs.meta new file mode 100644 index 0000000..13f3ce6 --- /dev/null +++ b/Extensions/Svelto/EntityCollectionExtension.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 27ea6afd09113d0ebdfba19bde433004 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Extensions/Svelto/EntityManagedDBExtensions.cs b/Extensions/Svelto/EntityManagedDBExtensions.cs new file mode 100644 index 0000000..ceafe28 --- /dev/null +++ b/Extensions/Svelto/EntityManagedDBExtensions.cs @@ -0,0 +1,109 @@ +using System.Runtime.CompilerServices; +using Svelto.DataStructures; +using Svelto.ECS.Hybrid; +using Svelto.ECS.Internal; + +namespace Svelto.ECS +{ + public static class EntityManagedDBExtensions + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static MB QueryEntitiesAndIndex(this EntitiesDB entitiesDb, EGID entityGID, out uint index) where T : struct, IEntityViewComponent + { + if (entitiesDb.QueryEntitiesAndIndexInternal(entityGID, out index, out MB array) == true) + return array; + + throw new EntityNotFoundException(entityGID, typeof(T)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool TryQueryEntitiesAndIndex(this EntitiesDB entitiesDb, EGID entityGID, out uint index, out MB array) + where T : struct, IEntityViewComponent + { + if (entitiesDb.QueryEntitiesAndIndexInternal(entityGID, out index, out array) == true) + return true; + + return false; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool TryQueryEntitiesAndIndex(this EntitiesDB entitiesDb, uint id, ExclusiveGroupStruct group, out uint index, out MB array) + where T : struct, IEntityViewComponent + { + if (entitiesDb.QueryEntitiesAndIndexInternal(new EGID(id, group), out index, out array) == true) + return true; + + return false; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static bool QueryEntitiesAndIndexInternal(this EntitiesDB entitiesDb, EGID entityGID, out uint index, out MB buffer) where T : struct, IEntityViewComponent + { + index = 0; + buffer = default; + if (entitiesDb.SafeQueryEntityDictionary(entityGID.groupID, out var safeDictionary) == false) + return false; + + if (safeDictionary.TryFindIndex(entityGID.entityID, out index) == false) + return false; + + buffer = (MB) (safeDictionary as ITypeSafeDictionary).GetValues(out _); + + return true; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ref T QueryEntity(this EntitiesDB entitiesDb, EGID entityGID) where T : struct, IEntityViewComponent + { + var array = entitiesDb.QueryEntitiesAndIndex(entityGID, out var index); + + return ref array[(int) index]; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ref T QueryEntity(this EntitiesDB entitiesDb, uint id, ExclusiveGroupStruct group) where T : struct, IEntityViewComponent + { + return ref entitiesDb.QueryEntity(new EGID(id, group)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ref T QueryUniqueEntity(this EntitiesDB entitiesDb, ExclusiveGroupStruct group) where T : struct, IEntityViewComponent + { + var (entities, entitiescount) = entitiesDb.QueryEntities(@group); + +#if DEBUG && !PROFILE_SVELTO + if (entitiescount == 0) + throw new ECSException("Unique entity not found '".FastConcat(typeof(T).ToString()).FastConcat("'")); + if (entitiescount != 1) + throw new ECSException("Unique entities must be unique! '".FastConcat(typeof(T).ToString()) + .FastConcat("'")); +#endif + return ref entities[0]; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static MB GetArrayAndEntityIndex(this EGIDMapper mapper, uint entityID, out uint index) where T : struct, IEntityViewComponent + { + if (mapper._map.TryFindIndex(entityID, out index)) + { + return (MB) mapper._map.GetValues(out _); + } + + throw new ECSException("Entity not found"); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool TryGetArrayAndEntityIndex(this EGIDMapper mapper, uint entityID, out uint index, out MB array) where T : struct, IEntityViewComponent + { + index = default; + if (mapper._map != null && mapper._map.TryFindIndex(entityID, out index)) + { + array = (MB) mapper._map.GetValues(out _); + return true; + } + + array = default; + return false; + } + } +} \ No newline at end of file diff --git a/Extensions/Svelto/EntityManagedDBExtensions.cs.meta b/Extensions/Svelto/EntityManagedDBExtensions.cs.meta new file mode 100644 index 0000000..3fbcadb --- /dev/null +++ b/Extensions/Svelto/EntityManagedDBExtensions.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 45c4aaf99be337d0b1599b27d7da352f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Extensions/Svelto/EntityNativeDBExtensions.cs b/Extensions/Svelto/EntityNativeDBExtensions.cs new file mode 100644 index 0000000..bec4e41 --- /dev/null +++ b/Extensions/Svelto/EntityNativeDBExtensions.cs @@ -0,0 +1,125 @@ +using System.Runtime.CompilerServices; +using Svelto.DataStructures; +using Svelto.ECS.Internal; + +namespace Svelto.ECS +{ + public static class EntityNativeDBExtensions + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static AllGroupsEnumerable QueryEntities(this EntitiesDB db) + where T1 :struct, IEntityComponent + { + return new AllGroupsEnumerable(db); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static NB QueryEntitiesAndIndex(this EntitiesDB entitiesDb, EGID entityGID, out uint index) where T : unmanaged, IEntityComponent + { + if (entitiesDb.QueryEntitiesAndIndexInternal(entityGID, out index, out NB array) == true) + return array; + + throw new EntityNotFoundException(entityGID, typeof(T)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static NB QueryEntitiesAndIndex(this EntitiesDB entitiesDb, uint id, ExclusiveGroupStruct group, out uint index) where T : unmanaged, IEntityComponent + { + EGID entityGID = new EGID(id, group); + if (entitiesDb.QueryEntitiesAndIndexInternal(entityGID, out index, out NB array) == true) + return array; + + throw new EntityNotFoundException(entityGID, typeof(T)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool TryQueryEntitiesAndIndex(this EntitiesDB entitiesDb, EGID entityGID, out uint index, out NB array) + where T : unmanaged, IEntityComponent + { + if (entitiesDb.QueryEntitiesAndIndexInternal(entityGID, out index, out array) == true) + return true; + + return false; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool TryQueryEntitiesAndIndex(this EntitiesDB entitiesDb, uint id, ExclusiveGroupStruct group, out uint index, out NB array) + where T : unmanaged, IEntityComponent + { + if (entitiesDb.QueryEntitiesAndIndexInternal(new EGID(id, group), out index, out array) == true) + return true; + + return false; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static bool QueryEntitiesAndIndexInternal(this EntitiesDB entitiesDb, EGID entityGID, out uint index, out NB buffer) where T : unmanaged, IEntityComponent + { + index = 0; + buffer = default; + if (entitiesDb.SafeQueryEntityDictionary(entityGID.groupID, out var safeDictionary) == false) + return false; + + if (safeDictionary.TryFindIndex(entityGID.entityID, out index) == false) + return false; + + buffer = (NB) (safeDictionary as ITypeSafeDictionary).GetValues(out _); + + return true; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ref T QueryEntity(this EntitiesDB entitiesDb, EGID entityGID) where T : unmanaged, IEntityComponent + { + var array = entitiesDb.QueryEntitiesAndIndex(entityGID, out var index); + + return ref array[(int) index]; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ref T QueryEntity(this EntitiesDB entitiesDb, uint id, ExclusiveGroupStruct group) where T : unmanaged, IEntityComponent + { + return ref entitiesDb.QueryEntity(new EGID(id, group)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ref T QueryUniqueEntity(this EntitiesDB entitiesDb, ExclusiveGroupStruct group) where T : unmanaged, IEntityComponent + { + var (entities, entitiescount) = entitiesDb.QueryEntities(@group); + +#if DEBUG && !PROFILE_SVELTO + if (entitiescount == 0) + throw new ECSException("Unique entity not found '".FastConcat(typeof(T).ToString()).FastConcat("'")); + if (entitiescount != 1) + throw new ECSException("Unique entities must be unique! '".FastConcat(typeof(T).ToString()) + .FastConcat("'")); +#endif + return ref entities[0]; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static NB GetArrayAndEntityIndex(this EGIDMapper mapper, uint entityID, out uint index) where T : unmanaged, IEntityComponent + { + if (mapper._map.TryFindIndex(entityID, out index)) + { + return (NB) mapper._map.GetValues(out _); + } + + throw new ECSException("Entity not found"); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool TryGetArrayAndEntityIndex(this EGIDMapper mapper, uint entityID, out uint index, out NB array) where T : unmanaged, IEntityComponent + { + index = default; + if (mapper._map != null && mapper._map.TryFindIndex(entityID, out index)) + { + array = (NB) mapper._map.GetValues(out _); + return true; + } + + array = default; + return false; + } + } +} \ No newline at end of file diff --git a/Extensions/Svelto/EntityNativeDBExtensions.cs.meta b/Extensions/Svelto/EntityNativeDBExtensions.cs.meta new file mode 100644 index 0000000..3641492 --- /dev/null +++ b/Extensions/Svelto/EntityNativeDBExtensions.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 319a6a0269bf3d1bbca89f52ea1d0aeb +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Extensions/Svelto/ExclusiveGroupExtensions.cs b/Extensions/Svelto/ExclusiveGroupExtensions.cs new file mode 100644 index 0000000..eb9b61e --- /dev/null +++ b/Extensions/Svelto/ExclusiveGroupExtensions.cs @@ -0,0 +1,38 @@ +using System.Runtime.CompilerServices; +using Svelto.DataStructures; + +namespace Svelto.ECS +{ + public static class ExclusiveGroupExtensions + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool FoundIn(this in ExclusiveGroupStruct group, ExclusiveGroupStruct[] groups) + { + for (int i = 0; i < groups.Length; ++i) + if (groups[i] == group) + return true; + + return false; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool FoundIn(this in ExclusiveGroupStruct group, FasterList groups) + { + for (int i = 0; i < groups.count; ++i) + if (groups[i] == group) + return true; + + return false; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool FoundIn(this in ExclusiveGroupStruct group, LocalFasterReadOnlyList groups) + { + for (int i = 0; i < groups.count; ++i) + if (groups[i] == group) + return true; + + return false; + } + } +} \ No newline at end of file diff --git a/Extensions/Svelto/ExclusiveGroupExtensions.cs.meta b/Extensions/Svelto/ExclusiveGroupExtensions.cs.meta new file mode 100644 index 0000000..df6e9f5 --- /dev/null +++ b/Extensions/Svelto/ExclusiveGroupExtensions.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0144593d40ca364a9f684cc88530b661 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Extensions/Svelto/GroupsEnumerable.cs b/Extensions/Svelto/GroupsEnumerable.cs new file mode 100644 index 0000000..465ed8a --- /dev/null +++ b/Extensions/Svelto/GroupsEnumerable.cs @@ -0,0 +1,326 @@ +using DBC.ECS; +using Svelto.DataStructures; + +namespace Svelto.ECS +{ + /// + /// NOTE THESE ENUMERABLES EXIST TO AVOID BOILERPLATE CODE AS THEY SKIP 0 SIZED GROUPS + /// However if the normal pattern with the double foreach is used, this is not necessary + /// Note: atm cannot be ref structs because they are returned in a valuetuple + /// + /// + /// + /// + /// + public readonly ref struct GroupsEnumerable where T1 : struct, IEntityComponent + where T2 : struct, IEntityComponent + where T3 : struct, IEntityComponent + where T4 : struct, IEntityComponent + { + readonly EntitiesDB _db; + readonly LocalFasterReadOnlyList _groups; + + public GroupsEnumerable(EntitiesDB db, in LocalFasterReadOnlyList groups) + { + _db = db; + _groups = groups; + } + + public ref struct GroupsIterator + { + public GroupsIterator(EntitiesDB db, in LocalFasterReadOnlyList groups) : this() + { + _groups = groups; + _indexGroup = -1; + _entitiesDB = db; + } + + public bool MoveNext() + { + //attention, the while is necessary to skip empty groups + while (++_indexGroup < _groups.count) + { + var entityCollection1 = _entitiesDB.QueryEntities(_groups[_indexGroup]); + if (entityCollection1.count == 0) + continue; + var entityCollection2 = _entitiesDB.QueryEntities(_groups[_indexGroup]); + if (entityCollection2.count == 0) + continue; + + Check.Assert(entityCollection1.count == entityCollection2.count + , "congratulation, you found a bug in Svelto, please report it"); + + var array = entityCollection1; + var array2 = entityCollection2; + _buffers = new EntityCollection(array.buffer1, array.buffer2, array.buffer3, array2); + break; + } + + var moveNext = _indexGroup < _groups.count; + + if (moveNext == false) + Reset(); + + return moveNext; + } + + public void Reset() { _indexGroup = -1; } + + public RefCurrent Current => new RefCurrent(_buffers, _groups[_indexGroup]); + + readonly LocalFasterReadOnlyList _groups; + + int _indexGroup; + EntityCollection _buffers; + readonly EntitiesDB _entitiesDB; + } + + public GroupsIterator GetEnumerator() { return new GroupsIterator(_db, _groups); } + } + + public ref struct RefCurrent where T1 : struct, IEntityComponent + where T2 : struct, IEntityComponent + where T3 : struct, IEntityComponent + where T4 : struct, IEntityComponent + { + public RefCurrent(in EntityCollection buffers, ExclusiveGroupStruct group) + { + _buffers = buffers; + _group = group; + } + + public void Deconstruct(out EntityCollection buffers, out ExclusiveGroupStruct group) + { + buffers = _buffers; + group = _group; + } + + public readonly EntityCollection _buffers; + public readonly ExclusiveGroupStruct _group; + } + + /// + /// ToDo source gen could return the implementation of IBuffer directly, but cannot be done manually + /// + /// + /// + /// + public readonly ref struct GroupsEnumerable where T1 : struct, IEntityComponent + where T2 : struct, IEntityComponent + where T3 : struct, IEntityComponent + { + readonly EntitiesDB _db; + readonly LocalFasterReadOnlyList _groups; + + public GroupsEnumerable(EntitiesDB db, in LocalFasterReadOnlyList groups) + { + _db = db; + _groups = groups; + } + + public ref struct GroupsIterator + { + public GroupsIterator(EntitiesDB db, in LocalFasterReadOnlyList groups) : this() + { + _groups = groups; + _indexGroup = -1; + _entitiesDB = db; + } + + public bool MoveNext() + { + //attention, the while is necessary to skip empty groups + while (++_indexGroup < _groups.count) + { + var entityCollection = _entitiesDB.QueryEntities(_groups[_indexGroup]); + if (entityCollection.count == 0) + continue; + + _buffers = entityCollection; + break; + } + + var moveNext = _indexGroup < _groups.count; + + if (moveNext == false) + Reset(); + + return moveNext; + } + + public void Reset() { _indexGroup = -1; } + + public RefCurrent Current => new RefCurrent(_buffers, _groups[_indexGroup]); + + readonly LocalFasterReadOnlyList _groups; + + int _indexGroup; + EntityCollection _buffers; + readonly EntitiesDB _entitiesDB; + } + + public GroupsIterator GetEnumerator() { return new GroupsIterator(_db, _groups); } + } + + public ref struct RefCurrent where T1 : struct, IEntityComponent + where T2 : struct, IEntityComponent + where T3 : struct, IEntityComponent + { + public RefCurrent(in EntityCollection buffers, ExclusiveGroupStruct group) + { + _buffers = buffers; + _group = group; + } + + public void Deconstruct(out EntityCollection buffers, out ExclusiveGroupStruct group) + { + buffers = _buffers; + group = _group; + } + + public readonly EntityCollection _buffers; + public readonly ExclusiveGroupStruct _group; + } + + public readonly ref struct GroupsEnumerable + where T1 : struct, IEntityComponent where T2 : struct, IEntityComponent + { + public GroupsEnumerable(EntitiesDB db, in LocalFasterReadOnlyList groups) + { + _db = db; + _groups = groups; + } + + public ref struct GroupsIterator + { + public GroupsIterator(EntitiesDB db, in LocalFasterReadOnlyList groups) : this() + { + _db = db; + _groups = groups; + _indexGroup = -1; + } + + public bool MoveNext() + { + //attention, the while is necessary to skip empty groups + while (++_indexGroup < _groups.count) + { + var entityCollection = _db.QueryEntities(_groups[_indexGroup]); + if (entityCollection.count == 0) + continue; + + _buffers = entityCollection; + break; + } + + var moveNext = _indexGroup < _groups.count; + + if (moveNext == false) + Reset(); + + return moveNext; + } + + public void Reset() { _indexGroup = -1; } + + public RefCurrent Current => new RefCurrent(_buffers, _groups[_indexGroup]); + + readonly EntitiesDB _db; + readonly LocalFasterReadOnlyList _groups; + + int _indexGroup; + EntityCollection _buffers; + } + + public GroupsIterator GetEnumerator() { return new GroupsIterator(_db, _groups); } + + readonly EntitiesDB _db; + readonly LocalFasterReadOnlyList _groups; + } + + public ref struct RefCurrent where T1 : struct, IEntityComponent where T2 : struct, IEntityComponent + { + public RefCurrent(in EntityCollection buffers, ExclusiveGroupStruct group) + { + _buffers = buffers; + _group = group; + } + + public void Deconstruct(out EntityCollection buffers, out ExclusiveGroupStruct group) + { + buffers = _buffers; + group = _group; + } + + public readonly EntityCollection _buffers; + public readonly ExclusiveGroupStruct _group; + } + + public readonly ref struct GroupsEnumerable where T1 : struct, IEntityComponent + { + public GroupsEnumerable(EntitiesDB db, in LocalFasterReadOnlyList groups) + { + _db = db; + _groups = groups; + } + + public ref struct GroupsIterator + { + public GroupsIterator(EntitiesDB db, in LocalFasterReadOnlyList groups) : this() + { + _db = db; + _groups = groups; + _indexGroup = -1; + } + + public bool MoveNext() + { + //attention, the while is necessary to skip empty groups + while (++_indexGroup < _groups.count) + { + var entityCollection = _db.QueryEntities(_groups[_indexGroup]); + if (entityCollection.count == 0) + continue; + + _buffer = entityCollection; + break; + } + + return _indexGroup < _groups.count; + } + + public void Reset() { _indexGroup = -1; } + + public RefCurrent Current => new RefCurrent(_buffer, _groups[_indexGroup]); + + readonly EntitiesDB _db; + readonly LocalFasterReadOnlyList _groups; + + int _indexGroup; + EntityCollection _buffer; + } + + public GroupsIterator GetEnumerator() { return new GroupsIterator(_db, _groups); } + + readonly EntitiesDB _db; + readonly LocalFasterReadOnlyList _groups; + } + + public ref struct RefCurrent where T1 : struct, IEntityComponent + { + public RefCurrent(in EntityCollection buffers, ExclusiveGroupStruct group) + { + _buffers = buffers; + _group = group; + } + + public void Deconstruct(out EntityCollection buffers, out ExclusiveGroupStruct group) + { + buffers = _buffers; + group = _group; + } + + public readonly EntityCollection _buffers; + public readonly ExclusiveGroupStruct _group; + } +} \ No newline at end of file diff --git a/Extensions/Svelto/GroupsEnumerable.cs.meta b/Extensions/Svelto/GroupsEnumerable.cs.meta new file mode 100644 index 0000000..4686475 --- /dev/null +++ b/Extensions/Svelto/GroupsEnumerable.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 70f7ac1fd99a382aa69b58a54d26e348 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Extensions/Unity/DOTS.meta b/Extensions/Unity/DOTS.meta new file mode 100644 index 0000000..ffe3f8c --- /dev/null +++ b/Extensions/Unity/DOTS.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 53f06410a1af307e96b7bc5cdfb5987a +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Extensions/Unity/DOTS/Jobs.meta b/Extensions/Unity/DOTS/Jobs.meta new file mode 100644 index 0000000..6652b13 --- /dev/null +++ b/Extensions/Unity/DOTS/Jobs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 53598e9dfa153d77a4e206e8caf455a2 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Extensions/Unity/DOTS/Jobs/DisposeJob.cs b/Extensions/Unity/DOTS/Jobs/DisposeJob.cs new file mode 100644 index 0000000..670241d --- /dev/null +++ b/Extensions/Unity/DOTS/Jobs/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/Extensions/Unity/DOTS/Jobs/DisposeJob.cs.meta b/Extensions/Unity/DOTS/Jobs/DisposeJob.cs.meta new file mode 100644 index 0000000..0545216 --- /dev/null +++ b/Extensions/Unity/DOTS/Jobs/DisposeJob.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 052c620f27503e8f8a97ca6d1b01018d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Extensions/Unity/DOTS/Jobs/JobifiedEnginesGroup.cs b/Extensions/Unity/DOTS/Jobs/JobifiedEnginesGroup.cs new file mode 100644 index 0000000..2a5eb10 --- /dev/null +++ b/Extensions/Unity/DOTS/Jobs/JobifiedEnginesGroup.cs @@ -0,0 +1,102 @@ +#if UNITY_2019_2_OR_NEWER +using Svelto.Common; +using Svelto.DataStructures; +using Unity.Jobs; + +namespace Svelto.ECS.Extensions.Unity +{ + public interface IJobifiedEngine : IEngine + { + JobHandle Execute(JobHandle inputDeps); + + string name { get; } + } + + public interface IJobifiedEngine : IEngine + { + JobHandle Execute(JobHandle inputDeps, ref T _param); + + string name { get; } + } + + public interface IJobifiedGroupEngine : IJobifiedEngine + { } + /// + /// Note unsorted jobs run in parallel + /// + /// + public abstract class JobifiedEnginesGroup:IJobifiedEngine where Interface : class, IJobifiedEngine + { + protected JobifiedEnginesGroup(FasterList engines) + { + _name = "JobifiedEnginesGroup - "+this.GetType().Name; + _engines = engines; + } + + protected JobifiedEnginesGroup() + { + _name = "JobifiedEnginesGroup - "+this.GetType().Name; + _engines = new FasterList(); + } + + public JobHandle Execute(JobHandle inputHandles) + { + var engines = _engines; + JobHandle combinedHandles = inputHandles; + using (var profiler = new PlatformProfiler(_name)) + { + for (var index = 0; index < engines.count; index++) + { + ref var engine = ref engines[index]; + using (profiler.Sample(engine.name)) + { + combinedHandles = engine.Execute(inputHandles); + } + } + } + + return combinedHandles; + } + + protected internal void Add(Interface engine) + { + _engines.Add(engine); + } + + public string name => _name; + + protected readonly FasterList _engines; + readonly string _name; + } + + public abstract class JobifiedEnginesGroup: IJobifiedGroupEngine where Interface : class, IJobifiedEngine + { + protected JobifiedEnginesGroup(FasterList engines) + { + _name = "JobifiedEnginesGroup - "+this.GetType().Name; + _engines = engines; + } + + public JobHandle Execute(JobHandle combinedHandles, ref Param _param) + { + var engines = _engines; + using (var profiler = new PlatformProfiler(_name)) + { + for (var index = 0; index < engines.count; index++) + { + var engine = engines[index]; + using (profiler.Sample(engine.name)) combinedHandles = engine.Execute(combinedHandles, ref _param); + } + } + + return combinedHandles; + } + + public string name => _name; + + readonly string _name; + + readonly FasterReadOnlyList _engines; + } +} +#endif \ No newline at end of file diff --git a/Extensions/Unity/DOTS/Jobs/JobifiedEnginesGroup.cs.meta b/Extensions/Unity/DOTS/Jobs/JobifiedEnginesGroup.cs.meta new file mode 100644 index 0000000..37cd12c --- /dev/null +++ b/Extensions/Unity/DOTS/Jobs/JobifiedEnginesGroup.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c4f616d57eee3e94bb54d709ccfcce4d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Extensions/Unity/DOTS/Jobs/SortedJobifiedEnginesGroup.cs b/Extensions/Unity/DOTS/Jobs/SortedJobifiedEnginesGroup.cs new file mode 100644 index 0000000..f0c7546 --- /dev/null +++ b/Extensions/Unity/DOTS/Jobs/SortedJobifiedEnginesGroup.cs @@ -0,0 +1,74 @@ +#if UNITY_2019_1_OR_NEWER +using Svelto.DataStructures; +using Unity.Jobs; +using Svelto.Common; + +namespace Svelto.ECS.Extensions.Unity +{ + /// + /// Note sorted jobs run in serial + /// + /// + /// + public abstract class SortedJobifiedEnginesGroup + where SequenceOrder : struct, ISequenceOrder where Interface : class, IJobifiedEngine + { + protected SortedJobifiedEnginesGroup(FasterList engines) + { + _name = "SortedJobifiedEnginesGroup - "+this.GetType().Name; + _instancedSequence = new Sequence(engines); + } + + public JobHandle Execute(JobHandle inputHandles) + { + var sequenceItems = _instancedSequence.items; + JobHandle combinedHandles = inputHandles; + using (var profiler = new PlatformProfiler(_name)) + { + for (var index = 0; index < sequenceItems.count; index++) + { + var engine = sequenceItems[index]; + using (profiler.Sample(engine.name)) combinedHandles = engine.Execute(combinedHandles); + } + } + + return combinedHandles; + } + + public string name => _name; + + readonly string _name; + readonly Sequence _instancedSequence; + } + + public abstract class SortedJobifiedEnginesGroup: IJobifiedGroupEngine + where SequenceOrder : struct, ISequenceOrder where Interface : class, IJobifiedEngine + { + protected SortedJobifiedEnginesGroup(FasterList engines) + { + _name = "SortedJobifiedEnginesGroup - "+this.GetType().Name; + _instancedSequence = new Sequence(engines); + } + + public JobHandle Execute(JobHandle combinedHandles, ref Parameter param) + { + var sequenceItems = _instancedSequence.items; + using (var profiler = new PlatformProfiler(_name)) + { + for (var index = 0; index < sequenceItems.count; index++) + { + var engine = sequenceItems[index]; + using (profiler.Sample(engine.name)) combinedHandles = engine.Execute(combinedHandles, ref param); + } + } + + return combinedHandles; + } + + public string name => _name; + + readonly string _name; + readonly Sequence _instancedSequence; + } +} +#endif \ No newline at end of file diff --git a/Extensions/Unity/DOTS/Jobs/SortedJobifiedEnginesGroup.cs.meta b/Extensions/Unity/DOTS/Jobs/SortedJobifiedEnginesGroup.cs.meta new file mode 100644 index 0000000..c22feed --- /dev/null +++ b/Extensions/Unity/DOTS/Jobs/SortedJobifiedEnginesGroup.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 60bbf861924d3b8f9a7648113821681c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Extensions/Unity/DOTS/Jobs/UnityJobExtensions.cs b/Extensions/Unity/DOTS/Jobs/UnityJobExtensions.cs new file mode 100644 index 0000000..7d0df74 --- /dev/null +++ b/Extensions/Unity/DOTS/Jobs/UnityJobExtensions.cs @@ -0,0 +1,68 @@ +#if UNITY_NATIVE +using System; +using Svelto.ECS.Extensions.Unity; +using Unity.Jobs; + +namespace Svelto.ECS +{ +#if UNITY_JOBS + public static class UnityJobExtensions + { + public static JobHandle ScheduleParallel + (this JOB job, uint iterations, JobHandle inputDeps) where JOB: struct, IJobParallelForBatch + { + if (iterations == 0) + return inputDeps; + + var innerloopBatchCount = ProcessorCount.BatchSize((uint) iterations); + return job.ScheduleBatch((int)iterations, innerloopBatchCount, inputDeps); + } + + public static JobHandle ScheduleParallel + (this JOB job, int iterations, JobHandle inputDeps) where JOB: struct, IJobParallelForBatch + { + if (iterations <= 0) + return inputDeps; + + var innerloopBatchCount = ProcessorCount.BatchSize((uint) iterations); + return job.ScheduleBatch((int)iterations, innerloopBatchCount, inputDeps); + } + } +#endif + public static class UnityJobExtensions2 + { + 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, int iterations, JobHandle inputDeps) where JOB: struct, IJobParallelFor + { + if (iterations <= 0) + return inputDeps; + + var innerloopBatchCount = ProcessorCount.BatchSize((uint) iterations); + return job.Schedule((int)iterations, innerloopBatchCount, inputDeps); + } + + public static JobHandle ScheduleParallel + (this JOB job, uint iterations, JobHandle inputDeps) where JOB: struct, IJobParallelFor + { + if (iterations == 0) + return inputDeps; + + var innerloopBatchCount = ProcessorCount.BatchSize(iterations); + return job.Schedule((int)iterations, innerloopBatchCount, inputDeps); + } + } +} +#endif \ No newline at end of file diff --git a/Extensions/Unity/DOTS/Jobs/UnityJobExtensions.cs.meta b/Extensions/Unity/DOTS/Jobs/UnityJobExtensions.cs.meta new file mode 100644 index 0000000..fde5efb --- /dev/null +++ b/Extensions/Unity/DOTS/Jobs/UnityJobExtensions.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ac4f575eff3d38b7905658a315edbef6 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Extensions/Unity/DOTS/Native.meta b/Extensions/Unity/DOTS/Native.meta new file mode 100644 index 0000000..a102e87 --- /dev/null +++ b/Extensions/Unity/DOTS/Native.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 7ec1b42035f83c698c4d67112a8336c5 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Extensions/Unity/DOTS/Native/EnginesRoot.NativeOperation.cs b/Extensions/Unity/DOTS/Native/EnginesRoot.NativeOperation.cs new file mode 100644 index 0000000..0302254 --- /dev/null +++ b/Extensions/Unity/DOTS/Native/EnginesRoot.NativeOperation.cs @@ -0,0 +1,186 @@ +#if UNITY_NATIVE +using System; +using Svelto.Common; +using Svelto.DataStructures; +using Svelto.ECS.DataStructures; + +namespace Svelto.ECS +{ + public partial class EnginesRoot + { + //todo: I very likely don't need to create one for each native entity factory, the same can be reused + readonly AtomicNativeBags _addOperationQueue = + new AtomicNativeBags(Common.Allocator.Persistent); + + readonly AtomicNativeBags _removeOperationQueue = + new AtomicNativeBags(Common.Allocator.Persistent); + + readonly AtomicNativeBags _swapOperationQueue = + new AtomicNativeBags(Common.Allocator.Persistent); + + NativeEntityRemove ProvideNativeEntityRemoveQueue(string memberName) 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, TypeCache.type, memberName)); + + return new NativeEntityRemove(_removeOperationQueue, _nativeRemoveOperations.count - 1); + } + + NativeEntitySwap ProvideNativeEntitySwapQueue(string memberName) where T : IEntityDescriptor, new() + { + //todo: remove operation array and store entity descriptor hash in the return value + _nativeSwapOperations.Add( + new NativeOperationSwap(EntityDescriptorTemplate.descriptor.componentsToBuild, TypeCache.type, memberName)); + + return new NativeEntitySwap(_swapOperationQueue, _nativeSwapOperations.count - 1); + } + + NativeEntityFactory ProvideNativeEntityFactoryQueue(string memberName) where T : IEntityDescriptor, new() + { + //todo: remove operation array and store entity descriptor hash in the return value + _nativeAddOperations.Add( + new NativeOperationBuild(EntityDescriptorTemplate.descriptor.componentsToBuild, TypeCache.type)); + + 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(); + var nativeRemoveOperation = _nativeRemoveOperations[componentsIndex]; + CheckRemoveEntityID(entityEGID, nativeRemoveOperation.entityDescriptorType); + QueueEntitySubmitOperation(new EntitySubmitOperation( + EntitySubmitOperationType.Remove, entityEGID, entityEGID + , nativeRemoveOperation.components)); + } + } + + 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(); + + var componentBuilders = _nativeSwapOperations[componentsIndex].components; + + CheckRemoveEntityID(entityEGID.@from, _nativeSwapOperations[componentsIndex].entityDescriptorType, _nativeSwapOperations[componentsIndex].caller ); + CheckAddEntityID(entityEGID.to, _nativeSwapOperations[componentsIndex].entityDescriptorType, _nativeSwapOperations[componentsIndex].caller); + + QueueEntitySubmitOperation(new EntitySubmitOperation( + EntitySubmitOperationType.Swap, entityEGID.@from, entityEGID.to + , componentBuilders)); + } + } + } + + 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, _nativeAddOperations[componentsIndex].entityDescriptorType); + + //only called if Init is called on the initialized (there is something to init) + 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; + } + } + + readonly struct NativeOperationBuild + { + internal readonly IComponentBuilder[] components; + internal readonly Type entityDescriptorType; + + public NativeOperationBuild(IComponentBuilder[] descriptorComponentsToBuild, Type entityDescriptorType) + { + this.entityDescriptorType = entityDescriptorType; + components = descriptorComponentsToBuild; + } + } + + readonly struct NativeOperationRemove + { + internal readonly IComponentBuilder[] components; + internal readonly Type entityDescriptorType; + internal readonly string caller; + + public NativeOperationRemove(IComponentBuilder[] descriptorComponentsToRemove, Type entityDescriptorType, string caller) + { + this.caller = caller; + components = descriptorComponentsToRemove; + this.entityDescriptorType = entityDescriptorType; + } + } + + readonly struct NativeOperationSwap + { + internal readonly IComponentBuilder[] components; + internal readonly Type entityDescriptorType; + internal readonly string caller; + + public NativeOperationSwap(IComponentBuilder[] descriptorComponentsToSwap, Type entityDescriptorType, string caller) + { + this.caller = caller; + components = descriptorComponentsToSwap; + this.entityDescriptorType = entityDescriptorType; + } + } +} +#endif \ No newline at end of file diff --git a/Extensions/Unity/DOTS/Native/EnginesRoot.NativeOperation.cs.meta b/Extensions/Unity/DOTS/Native/EnginesRoot.NativeOperation.cs.meta new file mode 100644 index 0000000..3ed7a0c --- /dev/null +++ b/Extensions/Unity/DOTS/Native/EnginesRoot.NativeOperation.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2951c865c51635b39dd47906953760d9 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Extensions/Unity/DOTS/Native/NativeEGIDMapper.cs b/Extensions/Unity/DOTS/Native/NativeEGIDMapper.cs new file mode 100644 index 0000000..3bf1e21 --- /dev/null +++ b/Extensions/Unity/DOTS/Native/NativeEGIDMapper.cs @@ -0,0 +1,97 @@ +#if UNITY_NATIVE +using System; +using System.Runtime.CompilerServices; +using Svelto.Common; +using Svelto.DataStructures; + +namespace Svelto.ECS +{ + public readonly struct NativeEGIDMapper:IEGIDMapper where T : unmanaged, IEntityComponent + { + public NativeEGIDMapper + (ExclusiveGroupStruct groupStructId, SveltoDictionaryNative toNative) : this() + { + groupID = groupStructId; + _map = toNative; + } + + public int count => _map.count; + public Type entityType => TypeCache.type; + public ExclusiveGroupStruct groupID { get; } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ref T Entity(uint entityID) + { +#if DEBUG && !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.GetDirectValueByRef(findIndex); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool TryGetEntity(uint entityID, out T value) + { + if (_map.count > 0 && _map.TryFindIndex(entityID, out var index)) + { + unsafe + { + value = Unsafe.AsRef(Unsafe.Add((void*) _map.GetValues(out _).ToNativeArray(out _) + , (int) index)); + return true; + } + } + + value = default; + return false; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public NB GetArrayAndEntityIndex(uint entityID, out uint index) + { + if (_map.TryFindIndex(entityID, out index)) + { + return new NB(_map.GetValues(out var count).ToNativeArray(out _), count); + } + + throw new ECSException("Entity not found"); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool TryGetArrayAndEntityIndex(uint entityID, out uint index, out NB array) + { + index = 0; + if (_map.count > 0 && _map.TryFindIndex(entityID, out index)) + { + array = new NB(_map.GetValues(out var count).ToNativeArray(out _), count); + return true; + } + + array = default; + return false; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Exists(uint idEntityId) + { + return _map.count > 0 && _map.TryFindIndex(idEntityId, out _); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public uint GetIndex(uint entityID) + { + return _map.GetIndex(entityID); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool FindIndex(uint valueKey, out uint index) + { + return _map.TryFindIndex(valueKey, out index); + } + + readonly ReadonlySveltoDictionaryNative _map; + } +} +#endif \ No newline at end of file diff --git a/Extensions/Unity/DOTS/Native/NativeEGIDMapper.cs.meta b/Extensions/Unity/DOTS/Native/NativeEGIDMapper.cs.meta new file mode 100644 index 0000000..9974694 --- /dev/null +++ b/Extensions/Unity/DOTS/Native/NativeEGIDMapper.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 9e9fba3714c931f6986db1e6aa8ee73c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Extensions/Unity/DOTS/Native/NativeEGIDMultiMapper.cs b/Extensions/Unity/DOTS/Native/NativeEGIDMultiMapper.cs new file mode 100644 index 0000000..cac1afd --- /dev/null +++ b/Extensions/Unity/DOTS/Native/NativeEGIDMultiMapper.cs @@ -0,0 +1,55 @@ +#if UNITY_NATIVE +using System; +using Svelto.DataStructures; + +namespace Svelto.ECS +{ + public struct NativeEGIDMultiMapper:IDisposable where T : unmanaged, IEntityComponent + { + SveltoDictionary>, + NativeStrategy, + NativeStrategy>, + NativeStrategy>, + NativeStrategy>, + NativeStrategy, + NativeStrategy>>, + NativeStrategy> _dic; + + public NativeEGIDMultiMapper + (SveltoDictionary>, + NativeStrategy, + NativeStrategy>, + NativeStrategy>, + NativeStrategy>, + NativeStrategy, + NativeStrategy>>, + NativeStrategy> dictionary) + { + _dic = dictionary; + } + + public int count => (int) _dic.count; + + public void Dispose() + { + _dic.Dispose(); + } + + public ref T Entity(EGID entity) + { + ref var sveltoDictionary = ref _dic.GetValueByRef(entity.groupID); + return ref sveltoDictionary.GetValueByRef(entity.entityID); + } + + public bool Exists(EGID entity) + { + return _dic.TryFindIndex(entity.groupID, out var index) + && _dic.GetDirectValueByRef(index).ContainsKey(entity.entityID); + } + } +} +#endif \ No newline at end of file diff --git a/Extensions/Unity/DOTS/Native/NativeEGIDMultiMapper.cs.meta b/Extensions/Unity/DOTS/Native/NativeEGIDMultiMapper.cs.meta new file mode 100644 index 0000000..b4321ce --- /dev/null +++ b/Extensions/Unity/DOTS/Native/NativeEGIDMultiMapper.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 33698cc01a9533678cab59984e155361 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Extensions/Unity/DOTS/Native/NativeEntityComponentInitializer.cs b/Extensions/Unity/DOTS/Native/NativeEntityComponentInitializer.cs new file mode 100644 index 0000000..1d988b6 --- /dev/null +++ b/Extensions/Unity/DOTS/Native/NativeEntityComponentInitializer.cs @@ -0,0 +1,26 @@ +using Svelto.ECS.DataStructures; + +namespace Svelto.ECS +{ + 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); + } + } +} \ No newline at end of file diff --git a/Extensions/Unity/DOTS/Native/NativeEntityComponentInitializer.cs.meta b/Extensions/Unity/DOTS/Native/NativeEntityComponentInitializer.cs.meta new file mode 100644 index 0000000..e287740 --- /dev/null +++ b/Extensions/Unity/DOTS/Native/NativeEntityComponentInitializer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d63198265758388c8d03afb9fa128938 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Extensions/Unity/DOTS/Native/NativeEntityFactory.cs b/Extensions/Unity/DOTS/Native/NativeEntityFactory.cs new file mode 100644 index 0000000..7351b16 --- /dev/null +++ b/Extensions/Unity/DOTS/Native/NativeEntityFactory.cs @@ -0,0 +1,41 @@ +#if UNITY_NATIVE +using Svelto.ECS.DataStructures; + +namespace Svelto.ECS +{ + public readonly struct NativeEntityFactory + { + readonly AtomicNativeBags _addOperationQueue; + readonly int _index; + + internal NativeEntityFactory(AtomicNativeBags addOperationQueue, int index) + { + _index = index; + _addOperationQueue = addOperationQueue; + } + + public NativeEntityComponentInitializer BuildEntity + (uint eindex, BuildGroup 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 NativeEntityComponentInitializer BuildEntity(EGID egid, int threadIndex) + { + NativeBag unsafeBuffer = _addOperationQueue.GetBuffer(threadIndex + 1); + + unsafeBuffer.Enqueue(_index); + unsafeBuffer.Enqueue(new EGID(egid.entityID, egid.groupID)); + unsafeBuffer.ReserveEnqueue(out var index) = 0; + + return new NativeEntityComponentInitializer(unsafeBuffer, index); + } + } +} +#endif \ No newline at end of file diff --git a/Extensions/Unity/DOTS/Native/NativeEntityFactory.cs.meta b/Extensions/Unity/DOTS/Native/NativeEntityFactory.cs.meta new file mode 100644 index 0000000..ff15489 --- /dev/null +++ b/Extensions/Unity/DOTS/Native/NativeEntityFactory.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 583ccdfdfc033521909090b26ea6a261 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Extensions/Unity/DOTS/Native/NativeEntityRemove.cs b/Extensions/Unity/DOTS/Native/NativeEntityRemove.cs new file mode 100644 index 0000000..c2365dd --- /dev/null +++ b/Extensions/Unity/DOTS/Native/NativeEntityRemove.cs @@ -0,0 +1,26 @@ +#if UNITY_NATIVE +using Svelto.ECS.DataStructures; + +namespace Svelto.ECS +{ + public readonly struct NativeEntityRemove + { + readonly AtomicNativeBags _removeQueue; + readonly int _indexRemove; + + internal NativeEntityRemove(AtomicNativeBags EGIDsToRemove, int indexRemove) + { + _removeQueue = EGIDsToRemove; + _indexRemove = indexRemove; + } + + public void RemoveEntity(EGID egid, int threadIndex) + { + var simpleNativeBag = _removeQueue.GetBuffer(threadIndex); + + simpleNativeBag.Enqueue(_indexRemove); + simpleNativeBag.Enqueue(egid); + } + } +} +#endif \ No newline at end of file diff --git a/Extensions/Unity/DOTS/Native/NativeEntityRemove.cs.meta b/Extensions/Unity/DOTS/Native/NativeEntityRemove.cs.meta new file mode 100644 index 0000000..f6d269c --- /dev/null +++ b/Extensions/Unity/DOTS/Native/NativeEntityRemove.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4507f4573a0931248bf82cb0ee75c12d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Extensions/Unity/DOTS/Native/NativeEntitySwap.cs b/Extensions/Unity/DOTS/Native/NativeEntitySwap.cs new file mode 100644 index 0000000..53de378 --- /dev/null +++ b/Extensions/Unity/DOTS/Native/NativeEntitySwap.cs @@ -0,0 +1,33 @@ +#if UNITY_NATIVE +using Svelto.ECS.DataStructures; + +namespace Svelto.ECS +{ + public readonly struct NativeEntitySwap + { + readonly AtomicNativeBags _swapQueue; + readonly int _indexSwap; + + internal NativeEntitySwap(AtomicNativeBags EGIDsToSwap, int 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, BuildGroup to, int threadIndex) + { + var simpleNativeBag = _swapQueue.GetBuffer(threadIndex); + simpleNativeBag.Enqueue(_indexSwap); + simpleNativeBag.Enqueue(new DoubleEGID(from, new EGID(from.entityID, to))); + } + } +} +#endif \ No newline at end of file diff --git a/Extensions/Unity/DOTS/Native/NativeEntitySwap.cs.meta b/Extensions/Unity/DOTS/Native/NativeEntitySwap.cs.meta new file mode 100644 index 0000000..9f7ea82 --- /dev/null +++ b/Extensions/Unity/DOTS/Native/NativeEntitySwap.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 091158cad65b3cdb84c965a82ee9d4e7 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Extensions/Unity/DOTS/Native/UnityEntityDBExtensions.cs b/Extensions/Unity/DOTS/Native/UnityEntityDBExtensions.cs new file mode 100644 index 0000000..4917887 --- /dev/null +++ b/Extensions/Unity/DOTS/Native/UnityEntityDBExtensions.cs @@ -0,0 +1,73 @@ +#if UNITY_NATIVE +using System.Runtime.CompilerServices; +using Svelto.Common; +using Svelto.DataStructures; +using Svelto.ECS.Internal; + +namespace Svelto.ECS +{ + public static class UnityEntityDBExtensions + { + internal static NativeEGIDMapper ToNativeEGIDMapper(this TypeSafeDictionary dic, + ExclusiveGroupStruct groupStructId) where T : unmanaged, IEntityComponent + { + var mapper = new NativeEGIDMapper(groupStructId, dic.implUnmgd); + + return mapper; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static NativeEGIDMapper QueryNativeMappedEntities(this EntitiesDB entitiesDb, ExclusiveGroupStruct groupStructId) + where T : unmanaged, IEntityComponent + { + if (entitiesDb.SafeQueryEntityDictionary(groupStructId, out var typeSafeDictionary) == false) + throw new EntityGroupNotFoundException(typeof(T), groupStructId.ToName()); + + return (typeSafeDictionary as TypeSafeDictionary).ToNativeEGIDMapper(groupStructId); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool TryQueryNativeMappedEntities(this EntitiesDB entitiesDb, ExclusiveGroupStruct groupStructId, + out NativeEGIDMapper mapper) + where T : unmanaged, IEntityComponent + { + mapper = default; + if (entitiesDb.SafeQueryEntityDictionary(groupStructId, out var typeSafeDictionary) == false || + typeSafeDictionary.count == 0) + return false; + + mapper = (typeSafeDictionary as TypeSafeDictionary).ToNativeEGIDMapper(groupStructId); + + return true; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static NativeEGIDMultiMapper QueryNativeMappedEntities(this EntitiesDB entitiesDb, + LocalFasterReadOnlyList groups) + where T : unmanaged, IEntityComponent + { + var dictionary = + new SveltoDictionary>, + NativeStrategy, + NativeStrategy>, + NativeStrategy>, + NativeStrategy>, + NativeStrategy, + NativeStrategy>>, + NativeStrategy> + ((uint) groups.count, Allocator.TempJob); + + foreach (var group in groups) + { + if (entitiesDb.SafeQueryEntityDictionary(group, out var typeSafeDictionary) == true) + if (typeSafeDictionary.count > 0) + dictionary.Add(group, ((TypeSafeDictionary)typeSafeDictionary).implUnmgd); + } + + return new NativeEGIDMultiMapper(dictionary); + } + } +} +#endif \ No newline at end of file diff --git a/Extensions/Unity/DOTS/Native/UnityEntityDBExtensions.cs.meta b/Extensions/Unity/DOTS/Native/UnityEntityDBExtensions.cs.meta new file mode 100644 index 0000000..2e5164b --- /dev/null +++ b/Extensions/Unity/DOTS/Native/UnityEntityDBExtensions.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: bca3702c45ec31f595c130a8725d552c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Extensions/Unity/DOTS/UECS.meta b/Extensions/Unity/DOTS/UECS.meta new file mode 100644 index 0000000..37b80be --- /dev/null +++ b/Extensions/Unity/DOTS/UECS.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 7daf208c4e9736d0b56506f054950ba7 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Extensions/Unity/DOTS/UECS/IUECSSubmissionEngine.cs b/Extensions/Unity/DOTS/UECS/IUECSSubmissionEngine.cs new file mode 100644 index 0000000..b86f9db --- /dev/null +++ b/Extensions/Unity/DOTS/UECS/IUECSSubmissionEngine.cs @@ -0,0 +1,12 @@ +#if UNITY_ECS +using Unity.Entities; + +namespace Svelto.ECS.Extensions.Unity +{ + public interface IUECSSubmissionEngine : IJobifiedEngine + { + EntityCommandBuffer ECB { get; set;} + EntityManager EM { get; set;} + } +} +#endif \ No newline at end of file diff --git a/Extensions/Unity/DOTS/UECS/IUECSSubmissionEngine.cs.meta b/Extensions/Unity/DOTS/UECS/IUECSSubmissionEngine.cs.meta new file mode 100644 index 0000000..f823e55 --- /dev/null +++ b/Extensions/Unity/DOTS/UECS/IUECSSubmissionEngine.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: dab4dcacbe67305d81c39274cf4ceafc +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Extensions/Unity/DOTS/UECS/JobifiedSveltoEngines.cs b/Extensions/Unity/DOTS/UECS/JobifiedSveltoEngines.cs new file mode 100644 index 0000000..be86b38 --- /dev/null +++ b/Extensions/Unity/DOTS/UECS/JobifiedSveltoEngines.cs @@ -0,0 +1,9 @@ +#if UNITY_ECS +namespace Svelto.ECS.Extensions.Unity +{ + public enum JobifiedSveltoEngines + { + SveltoOverUECS + } +} +#endif \ No newline at end of file diff --git a/Extensions/Unity/DOTS/UECS/JobifiedSveltoEngines.cs.meta b/Extensions/Unity/DOTS/UECS/JobifiedSveltoEngines.cs.meta new file mode 100644 index 0000000..7f8d965 --- /dev/null +++ b/Extensions/Unity/DOTS/UECS/JobifiedSveltoEngines.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b5a1a76ae7e93e7c914710224e755000 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Extensions/Unity/DOTS/UECS/SveltoOverUECSEnginesGroup.cs b/Extensions/Unity/DOTS/UECS/SveltoOverUECSEnginesGroup.cs new file mode 100644 index 0000000..309fc74 --- /dev/null +++ b/Extensions/Unity/DOTS/UECS/SveltoOverUECSEnginesGroup.cs @@ -0,0 +1,100 @@ +#if UNITY_ECS +using Svelto.Common; +using Svelto.ECS.Schedulers; +using Unity.Entities; +using Unity.Jobs; + +namespace Svelto.ECS.Extensions.Unity +{ + [Sequenced(nameof(JobifiedSveltoEngines.SveltoOverUECS))] + public class SveltoOverUECSEnginesGroup: IJobifiedEngine + { + public SveltoOverUECSEnginesGroup(EnginesRoot enginesRoot) + { + DBC.ECS.Check.Require(enginesRoot.scheduler is ISimpleEntitiesSubmissionScheduler, "The Engines root must use a EntitiesSubmissionScheduler scheduler implementation"); + + CreateUnityECSWorldForSvelto(enginesRoot.scheduler as ISimpleEntitiesSubmissionScheduler, enginesRoot); + } + + public World world { get; private set; } + + void CreateUnityECSWorldForSvelto(ISimpleEntitiesSubmissionScheduler scheduler, EnginesRoot enginesRoot) + { + world = new World("Svelto<>UECS world"); + + var systems = DefaultWorldInitialization.GetAllSystems(WorldSystemFilterFlags.Default); + DefaultWorldInitialization.AddSystemsToRootLevelSystemGroups(world, systems); + World.DefaultGameObjectInjectionWorld = world; + + //This is the UECS group that takes care of all the UECS systems that creates entities + //it also submits Svelto entities + _sveltoUecsEntitiesSubmissionGroup = new SveltoUECSEntitiesSubmissionGroup(scheduler, world); + enginesRoot.AddEngine(_sveltoUecsEntitiesSubmissionGroup); + //This is the group that handles the UECS sync systems that copy the svelto entities values to UECS entities + _syncSveltoToUecsGroup = new SyncSveltoToUECSGroup(); + enginesRoot.AddEngine(_syncSveltoToUecsGroup); + _syncUecsToSveltoGroup = new SyncUECSToSveltoGroup(); + enginesRoot.AddEngine(_syncUecsToSveltoGroup); + //This is the group that handles the UECS sync systems that copy the UECS entities values to svelto entities + //enginesRoot.AddEngine(new SveltoUECSEntitiesSubmissionGroup(scheduler, world)); + enginesRoot.AddEngine(this); + + _enginesRoot = enginesRoot; + } + + public JobHandle Execute(JobHandle inputDeps) + { + //this is a sync point, there won't be pending jobs after this + _sveltoUecsEntitiesSubmissionGroup.Execute(inputDeps); + + //Mixed explicit job dependency and internal automatic ECS dependency system + //Write in to UECS entities so the UECS dependencies react on the components touched + var handle = _syncSveltoToUecsGroup.Execute(default); + + //As long as pure UECS systems do not use external containers (like native arrays and so) the Unity + //automatic dependencies system will guarantee that there won't be race conditions + world.Update(); + + //this svelto group of UECS SystemBase systems + return _syncUecsToSveltoGroup.Execute(handle); + } + + public void AddUECSSubmissionEngine(IUECSSubmissionEngine spawnUnityEntityOnSveltoEntityEngine) + { + _sveltoUecsEntitiesSubmissionGroup.Add(spawnUnityEntityOnSveltoEntityEngine); + _enginesRoot.AddEngine(spawnUnityEntityOnSveltoEntityEngine); + } + + public void AddSveltoToUECSEngine(SyncSveltoToUECSEngine engine) + { + //it's a Svelto Engine/UECS SystemBase so it must be added in the UECS world AND svelto enginesRoot + world.AddSystem(engine); + _enginesRoot.AddEngine(engine); + + _syncSveltoToUecsGroup.Add(engine); + } + + public void AddUECSToSveltoEngine(SyncUECSToSveltoEngine engine) + { + //it's a Svelto Engine/UECS SystemBase so it must be added in the UECS world AND svelto enginesRoot + world.AddSystem(engine); + _enginesRoot.AddEngine(engine); + + _syncUecsToSveltoGroup.Add(engine); + } + + public void Dispose() + { + world.Dispose(); + } + + public string name => nameof(SveltoOverUECSEnginesGroup); + + SveltoUECSEntitiesSubmissionGroup _sveltoUecsEntitiesSubmissionGroup; + SyncSveltoToUECSGroup _syncSveltoToUecsGroup; + SyncUECSToSveltoGroup _syncUecsToSveltoGroup; + EnginesRoot _enginesRoot; + + } +} +#endif \ No newline at end of file diff --git a/Extensions/Unity/DOTS/UECS/SveltoOverUECSEnginesGroup.cs.meta b/Extensions/Unity/DOTS/UECS/SveltoOverUECSEnginesGroup.cs.meta new file mode 100644 index 0000000..9995497 --- /dev/null +++ b/Extensions/Unity/DOTS/UECS/SveltoOverUECSEnginesGroup.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 01e754ea03db374d9973bd01227e6567 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Extensions/Unity/DOTS/UECS/SveltoUECSEntitiesSubmissionGroup.cs b/Extensions/Unity/DOTS/UECS/SveltoUECSEntitiesSubmissionGroup.cs new file mode 100644 index 0000000..ca835d6 --- /dev/null +++ b/Extensions/Unity/DOTS/UECS/SveltoUECSEntitiesSubmissionGroup.cs @@ -0,0 +1,61 @@ +#if UNITY_ECS +using Svelto.ECS.Schedulers; +using Unity.Entities; +using Unity.Jobs; + +namespace Svelto.ECS.Extensions.Unity +{ + /// + /// Group of UECS/Svelto SystemBase engines that creates UECS entities. + /// Svelto entities are submitted + /// Svelto Add and remove callback are called + /// OnUpdate of the systems are called + /// finally the UECS command buffer is flushed + /// Note: I cannot use Unity ComponentSystemGroups nor I can rely on the SystemBase Dependency field to + /// solve external dependencies. External dependencies are tracked, but only linked to the UECS components operations + /// With Dependency I cannot guarantee that an external container is used before previous jobs working on it are completed + /// + public class SveltoUECSEntitiesSubmissionGroup : JobifiedEnginesGroup + { + public SveltoUECSEntitiesSubmissionGroup + (ISimpleEntitiesSubmissionScheduler submissionScheduler, World UECSWorld) + { + _submissionScheduler = submissionScheduler; + _ECBSystem = UECSWorld.CreateSystem(); + } + + public new void Execute(JobHandle jobHandle) + { + //Sync Point as we must be sure that jobs that create/swap/remove entities are done + jobHandle.Complete(); + + if (_submissionScheduler.paused) + return; + + //prepare the entity command buffer to be used by the registered engines + var entityCommandBuffer = _ECBSystem.CreateCommandBuffer(); + + foreach (var system in _engines) + { + system.ECB = entityCommandBuffer; + system.EM = _ECBSystem.EntityManager; + } + + //Submit Svelto Entities, calls Add/Remove/MoveTo that can be used by the IUECSSubmissionEngines + _submissionScheduler.SubmitEntities(); + + //execute submission engines and complete jobs + base.Execute(default).Complete(); + + //flush command buffer + _ECBSystem.Update(); + } + + readonly ISimpleEntitiesSubmissionScheduler _submissionScheduler; + readonly SubmissionEntitiesCommandBufferSystem _ECBSystem; + + [DisableAutoCreation] + class SubmissionEntitiesCommandBufferSystem : EntityCommandBufferSystem { } + } +} +#endif \ No newline at end of file diff --git a/Extensions/Unity/DOTS/UECS/SveltoUECSEntitiesSubmissionGroup.cs.meta b/Extensions/Unity/DOTS/UECS/SveltoUECSEntitiesSubmissionGroup.cs.meta new file mode 100644 index 0000000..844efeb --- /dev/null +++ b/Extensions/Unity/DOTS/UECS/SveltoUECSEntitiesSubmissionGroup.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 5a7372a586523348a8ed02322c6d7766 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Extensions/Unity/DOTS/UECS/SyncSveltoToUECSGroup.cs b/Extensions/Unity/DOTS/UECS/SyncSveltoToUECSGroup.cs new file mode 100644 index 0000000..33e8443 --- /dev/null +++ b/Extensions/Unity/DOTS/UECS/SyncSveltoToUECSGroup.cs @@ -0,0 +1,25 @@ +#if UNITY_ECS +using Unity.Entities; +using Unity.Jobs; + +namespace Svelto.ECS.Extensions.Unity +{ + public class SyncSveltoToUECSGroup : JobifiedEnginesGroup + { + } + + public abstract class SyncSveltoToUECSEngine : SystemBase, IJobifiedEngine + { + public JobHandle Execute(JobHandle inputDeps) + { + Dependency = JobHandle.CombineDependencies(Dependency, inputDeps); + + Update(); + + return Dependency; + } + + public abstract string name { get; } + } +} +#endif \ No newline at end of file diff --git a/Extensions/Unity/DOTS/UECS/SyncSveltoToUECSGroup.cs.meta b/Extensions/Unity/DOTS/UECS/SyncSveltoToUECSGroup.cs.meta new file mode 100644 index 0000000..cba921c --- /dev/null +++ b/Extensions/Unity/DOTS/UECS/SyncSveltoToUECSGroup.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: fcd38855546a3ca2bc0ea22965e43b4b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Extensions/Unity/DOTS/UECS/SyncUECSToSveltoGroup.cs b/Extensions/Unity/DOTS/UECS/SyncUECSToSveltoGroup.cs new file mode 100644 index 0000000..6bccbe0 --- /dev/null +++ b/Extensions/Unity/DOTS/UECS/SyncUECSToSveltoGroup.cs @@ -0,0 +1,26 @@ +#if UNITY_ECS +using Unity.Entities; +using Unity.Jobs; + +namespace Svelto.ECS.Extensions.Unity +{ + public class SyncUECSToSveltoGroup : JobifiedEnginesGroup + { + + } + + public abstract class SyncUECSToSveltoEngine : SystemBase, IJobifiedEngine + { + public JobHandle Execute(JobHandle inputDeps) + { + Dependency = JobHandle.CombineDependencies(Dependency, inputDeps); + + Update(); + + return Dependency; + } + + public abstract string name { get; } + } +} +#endif \ No newline at end of file diff --git a/Extensions/Unity/DOTS/UECS/SyncUECSToSveltoGroup.cs.meta b/Extensions/Unity/DOTS/UECS/SyncUECSToSveltoGroup.cs.meta new file mode 100644 index 0000000..d8b16da --- /dev/null +++ b/Extensions/Unity/DOTS/UECS/SyncUECSToSveltoGroup.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 1b2ba9d343ed34eb949844504432e11b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Extensions/Unity/DOTS/UECS/UECSSveltoEGID.cs b/Extensions/Unity/DOTS/UECS/UECSSveltoEGID.cs new file mode 100644 index 0000000..8d460c9 --- /dev/null +++ b/Extensions/Unity/DOTS/UECS/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/Extensions/Unity/DOTS/UECS/UECSSveltoEGID.cs.meta b/Extensions/Unity/DOTS/UECS/UECSSveltoEGID.cs.meta new file mode 100644 index 0000000..9597da0 --- /dev/null +++ b/Extensions/Unity/DOTS/UECS/UECSSveltoEGID.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2cf2df46b8b831cd84751d5dd1036f13 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Extensions/Unity/EGIDHolderImplementor.cs b/Extensions/Unity/EGIDHolderImplementor.cs new file mode 100644 index 0000000..c4c4b88 --- /dev/null +++ b/Extensions/Unity/EGIDHolderImplementor.cs @@ -0,0 +1,32 @@ +#if UNITY_5 || UNITY_5_3_OR_NEWER +using Svelto.ECS.Hybrid; +using UnityEngine; + +namespace Svelto.ECS.Extensions.Unity +{ + public interface IEGIDHolder + { + EGID ID { set; } + } + + public struct EGIDTrackerViewComponent : IEntityViewComponent + { +#pragma warning disable 649 + public IEGIDHolder holder; +#pragma warning restore 649 + + EGID _ID; + + public EGID ID + { + get => _ID; + set => _ID = holder.ID = value; + } + } + + public class EGIDHolderImplementor : MonoBehaviour, IEGIDHolder, IImplementor + { + public EGID ID { get; set; } + } +} +#endif \ No newline at end of file diff --git a/Extensions/Unity/EGIDHolderImplementor.cs.meta b/Extensions/Unity/EGIDHolderImplementor.cs.meta new file mode 100644 index 0000000..7ff0349 --- /dev/null +++ b/Extensions/Unity/EGIDHolderImplementor.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 68093a2de8de38699ab6faab2d1b33f9 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Extensions/Unity/EntityDescriptorHolderHelper.cs b/Extensions/Unity/EntityDescriptorHolderHelper.cs new file mode 100644 index 0000000..609c845 --- /dev/null +++ b/Extensions/Unity/EntityDescriptorHolderHelper.cs @@ -0,0 +1,30 @@ +#if UNITY_ECS +using Svelto.ECS.Hybrid; +using UnityEngine; + +namespace Svelto.ECS.Extensions.Unity +{ + public static class EntityDescriptorHolderHelper + { + public static EntityComponentInitializer CreateEntity(this Transform contextHolder, EGID ID, + IEntityFactory factory, out T holder) + where T : MonoBehaviour, IEntityDescriptorHolder + { + holder = contextHolder.GetComponentInChildren(true); + var implementors = holder.GetComponents(); + + return factory.BuildEntity(ID, holder.GetDescriptor(), implementors); + } + + public static EntityComponentInitializer Create(this Transform contextHolder, EGID ID, + IEntityFactory factory) + where T : MonoBehaviour, IEntityDescriptorHolder + { + var holder = contextHolder.GetComponentInChildren(true); + var implementors = holder.GetComponents(); + + return factory.BuildEntity(ID, holder.GetDescriptor(), implementors); + } + } +} +#endif \ No newline at end of file diff --git a/Extensions/Unity/EntityDescriptorHolderHelper.cs.meta b/Extensions/Unity/EntityDescriptorHolderHelper.cs.meta new file mode 100644 index 0000000..40144ce --- /dev/null +++ b/Extensions/Unity/EntityDescriptorHolderHelper.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 7f978d50a34c395bbf491f0e440a51ff +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Extensions/Unity/GenericEntityDescriptorHolder.cs b/Extensions/Unity/GenericEntityDescriptorHolder.cs index 28b6898..42b21e5 100644 --- a/Extensions/Unity/GenericEntityDescriptorHolder.cs +++ b/Extensions/Unity/GenericEntityDescriptorHolder.cs @@ -1,10 +1,9 @@ #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 + public abstract class GenericEntityDescriptorHolder: MonoBehaviour , IEntityDescriptorHolder where T: IEntityDescriptor, new() { public IEntityDescriptor GetDescriptor() @@ -12,6 +11,11 @@ namespace Svelto.ECS.Unity return EntityDescriptorTemplate.descriptor; } + public T GetRealDescriptor() + { + return EntityDescriptorTemplate.realDescriptor; + } + public string groupName => _groupName; public ushort id => _id; diff --git a/Extensions/Unity/SveltoGUIHelper.cs b/Extensions/Unity/SveltoGUIHelper.cs index 160615d..e24303b 100644 --- a/Extensions/Unity/SveltoGUIHelper.cs +++ b/Extensions/Unity/SveltoGUIHelper.cs @@ -1,78 +1,123 @@ #if UNITY_5 || UNITY_5_3_OR_NEWER +using System; +using Svelto.ECS.Hybrid; using UnityEngine; -namespace Svelto.ECS.Unity +namespace Svelto.ECS.Extensions.Unity { public static class SveltoGUIHelper { + /// + /// This is the suggested way to create GUIs from prefabs now. + /// + /// + /// + /// + /// + /// + /// + /// + /// public static T CreateFromPrefab(ref uint startIndex, Transform contextHolder, IEntityFactory factory, - ExclusiveGroup group, string groupNamePostfix = null) where T : MonoBehaviour, IEntityDescriptorHolder + ExclusiveGroup group, bool searchImplementorsInChildren = false, string groupNamePostfix = null) where T : MonoBehaviour, IEntityDescriptorHolder { - var holder = Create(new EGID(startIndex++, group), contextHolder, factory); - var childs = contextHolder.GetComponentsInChildren(true); + Create(new EGID(startIndex++, group), contextHolder, factory, out var holder); + var children = contextHolder.GetComponentsInChildren(true); - foreach (var child in childs) + foreach (var child in children) { if (child.GetType() != typeof(T)) { - var monoBehaviour = child as MonoBehaviour; - var childImplementors = monoBehaviour.GetComponents(); - startIndex = InternalBuildAll( - startIndex, - child, - factory, - group, - childImplementors, - groupNamePostfix); + var monoBehaviour = child as MonoBehaviour; + IImplementor[] childImplementors; + if (searchImplementorsInChildren == false) + childImplementors = monoBehaviour.GetComponents(); + else + childImplementors = monoBehaviour.GetComponentsInChildren(true); + + startIndex = InternalBuildAll(startIndex, child, factory, group, childImplementors, + groupNamePostfix); } } return holder; } - public static T Create(EGID ID, Transform contextHolder, IEntityFactory factory) - where T : MonoBehaviour, IEntityDescriptorHolder + /// + /// Creates all the entities in a hierarchy. This was commonly used to create entities from gameobjects + /// already present in the scene + /// + /// + /// + /// + /// + /// + /// + /// + public static uint CreateAll(uint startIndex, ExclusiveGroup group, + Transform contextHolder, IEntityFactory factory, string groupNamePostfix = null) where T : MonoBehaviour, IEntityDescriptorHolder { - var holder = contextHolder.GetComponentInChildren(true); - DBC.ECS.Check.Assert(holder != null, $"`{nameof(holder)}` is null! No component of type " + - $"`{typeof(T)}` was found between its children."); - - var implementors = holder.GetComponents(); + var holders = contextHolder.GetComponentsInChildren(true); - factory.BuildEntity(ID, holder.GetDescriptor(), implementors); + foreach (var holder in holders) + { + var implementors = holder.GetComponents(); + try + { + startIndex = InternalBuildAll(startIndex, holder, factory, group, implementors, groupNamePostfix); + } + catch (Exception ex) + { + throw new Exception($"When building entity from game object {Path(holder.transform)}", ex); + } + } - return holder; + return startIndex; } - public static EntityStructInitializer CreateWithEntity(EGID ID, Transform contextHolder, - IEntityFactory factory, out T holder) + static string Path(Transform go) + { + string s = go.name; + while (go.parent != null) + { + go = go.parent; + s = go.name + "/" + s; + } + return s; + } + + public static EntityComponentInitializer Create(EGID ID, Transform contextHolder, IEntityFactory factory, + out T holder, bool searchImplementorsInChildren = false) where T : MonoBehaviour, IEntityDescriptorHolder { holder = contextHolder.GetComponentInChildren(true); - var implementors = holder.GetComponents(); + if (holder == null) + { + throw new Exception($"Could not find holder {typeof(T).Name} in {contextHolder.name}"); + } + var implementors = searchImplementorsInChildren == false ? holder.GetComponents() : holder.GetComponentsInChildren(true) ; return factory.BuildEntity(ID, holder.GetDescriptor(), implementors); } - public static uint CreateAll(uint startIndex, ExclusiveGroup group, - Transform contextHolder, IEntityFactory factory, string groupNamePostfix = null) where T : MonoBehaviour, IEntityDescriptorHolder + public static EntityComponentInitializer Create(EGID ID, Transform contextHolder, + IEntityFactory factory, bool searchImplementorsInChildren = false) + where T : MonoBehaviour, IEntityDescriptorHolder { - var holders = contextHolder.GetComponentsInChildren(true); - - foreach (var holder in holders) + var holder = contextHolder.GetComponentInChildren(true); + if (holder == null) { - var implementors = holder.GetComponents(); - - startIndex = InternalBuildAll(startIndex, holder, factory, group, implementors, groupNamePostfix); + throw new Exception($"Could not find holder {typeof(T).Name} in {contextHolder.name}"); } + var implementors = searchImplementorsInChildren == false ? holder.GetComponents() : holder.GetComponentsInChildren(true) ; - return startIndex; + return factory.BuildEntity(ID, holder.GetDescriptor(), implementors); } static uint InternalBuildAll(uint startIndex, IEntityDescriptorHolder descriptorHolder, - IEntityFactory factory, ExclusiveGroup group, IImplementor[] implementors, string groupNamePostfix) + IEntityFactory factory, ExclusiveGroup group, IImplementor[] implementors, string groupNamePostfix) { - ExclusiveGroup.ExclusiveGroupStruct realGroup = group; + ExclusiveGroupStruct realGroup = group; if (string.IsNullOrEmpty(descriptorHolder.groupName) == false) { @@ -90,10 +135,47 @@ 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 specified + /// This is a very specific case, basically used only by ControlsScreenRowFactory. Not sure what the real + /// use case is. + /// + /// + /// 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/Extensions/Unity/UnityEntitiesSubmissionScheduler.cs b/Extensions/Unity/UnityEntitiesSubmissionScheduler.cs new file mode 100644 index 0000000..f58a5b3 --- /dev/null +++ b/Extensions/Unity/UnityEntitiesSubmissionScheduler.cs @@ -0,0 +1,72 @@ +#if UNITY_5 || UNITY_5_3_OR_NEWER +using Object = UnityEngine.Object; +using System.Collections; +using UnityEngine; + +namespace Svelto.ECS.Schedulers.Unity +{ + //The EntitySubmissionScheduler has been introduced to make the entity components submission logic platform independent + //You can customize the scheduler if you wish + public class UnityEntitiesSubmissionScheduler : EntitiesSubmissionScheduler + { + class Scheduler : MonoBehaviour + { + public Scheduler() + { + _coroutine = Coroutine(); + } + + void Update() + { + _coroutine.MoveNext(); + } + + IEnumerator Coroutine() + { + while (true) + { + yield return _wait; + + onTick(); + } + } + + readonly WaitForEndOfFrame _wait = new WaitForEndOfFrame(); + readonly IEnumerator _coroutine; + + public System.Action onTick; + } + + public UnityEntitiesSubmissionScheduler(string name) + { + _scheduler = new GameObject(name).AddComponent(); + GameObject.DontDestroyOnLoad(_scheduler.gameObject); + _scheduler.onTick = SubmitEntities; + } + + public override void Dispose() + { + if (_scheduler != null && _scheduler.gameObject != null) + { + Object.Destroy(_scheduler.gameObject); + } + } + + public override bool paused { get; set; } + + void SubmitEntities() + { + if (paused == false) + _onTick.Invoke(); + } + + protected internal override EnginesRoot.EntitiesSubmitter onTick + { + set => _onTick = value; + } + + readonly Scheduler _scheduler; + EnginesRoot.EntitiesSubmitter _onTick; + } +} +#endif \ No newline at end of file diff --git a/Extensions/Unity/UnityEntitiesSubmissionScheduler.cs.meta b/Extensions/Unity/UnityEntitiesSubmissionScheduler.cs.meta new file mode 100644 index 0000000..a2af4e2 --- /dev/null +++ b/Extensions/Unity/UnityEntitiesSubmissionScheduler.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b19cf0516b0e39dea2b35c127e098c78 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Extensions/Unity/UnityEntitySubmissionScheduler.cs b/Extensions/Unity/UnityEntitySubmissionScheduler.cs deleted file mode 100644 index 4f4e120..0000000 --- a/Extensions/Unity/UnityEntitySubmissionScheduler.cs +++ /dev/null @@ -1,66 +0,0 @@ -#if UNITY_5 || UNITY_5_3_OR_NEWER -using Object = UnityEngine.Object; -using System; -using System.Collections; -using UnityEngine; - -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 - { - class Scheduler : MonoBehaviour - { - public Scheduler() - { - _coroutine = Coroutine(); - } - - void Update() - { - _coroutine.MoveNext(); - } - - IEnumerator Coroutine() - { - while (true) - { - yield return _wait; - - onTick.Invoke(); - } - } - - readonly WaitForEndOfFrame _wait = new WaitForEndOfFrame(); - readonly IEnumerator _coroutine; - - public EnginesRoot.EntitiesSubmitter onTick; - } - - public UnityEntitySubmissionScheduler(string name = "ECSScheduler") { _name = name; } - - public void Dispose() - { - Object.Destroy(_scheduler.gameObject); - } - - public EnginesRoot.EntitiesSubmitter onTick - { - set - { - if (_scheduler == null) - { - _scheduler = new GameObject(_name).AddComponent(); - GameObject.DontDestroyOnLoad(_scheduler.gameObject); - } - - _scheduler.onTick = value; - } - } - - Scheduler _scheduler; - readonly string _name; - } -} -#endif \ No newline at end of file diff --git a/Extensions/Unity/UnityEntitySubmissionScheduler.cs.meta b/Extensions/Unity/UnityEntitySubmissionScheduler.cs.meta deleted file mode 100644 index 9ec72cc..0000000 --- a/Extensions/Unity/UnityEntitySubmissionScheduler.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 3d9e1cc4e543344191b84926a4820cf0 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Filters.meta b/Filters.meta new file mode 100644 index 0000000..760b9c8 --- /dev/null +++ b/Filters.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 661a733f7383361088023fba3ffea29b +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Filters/EntitiesDB.GroupFilters.cs b/Filters/EntitiesDB.GroupFilters.cs new file mode 100644 index 0000000..77e5684 --- /dev/null +++ b/Filters/EntitiesDB.GroupFilters.cs @@ -0,0 +1,213 @@ +using System; +using Svelto.DataStructures; + +namespace Svelto.ECS +{ + /// + /// This feature must be eventually tied to the new ExclusiveGroup that won't allow the use of custom EntitiesID + /// The filters could be updated when entities buffer changes during the submission, while now this process + /// is completely manual. + /// Making this working properly is not in my priorities right now, as I will need to add the new group type + /// AND optimize the submission process to be able to add more overhead + /// + public partial class EntitiesDB + { + public readonly struct Filters + { + public Filters + (FasterDictionary> filters) + { + _filters = filters; + } + + public ref FilterGroup CreateOrGetFilterForGroup(int filterID, ExclusiveGroupStruct groupID) + where T : struct, IEntityComponent + { + var refWrapper = TypeRefWrapper.wrapper; + + return ref CreateOrGetFilterForGroup(filterID, groupID, refWrapper); + } + + ref FilterGroup CreateOrGetFilterForGroup(int filterID, ExclusiveGroupStruct groupID, RefWrapperType refWrapper) + { + var fasterDictionary = + _filters.GetOrCreate(refWrapper, () => new FasterDictionary()); + + GroupFilters filters = + fasterDictionary.GetOrCreate( + groupID, () => new GroupFilters(new SharedSveltoDictionaryNative(0), groupID)); + + return ref filters.CreateOrGetFilter(filterID); + } + + public bool HasFiltersForGroup(ExclusiveGroupStruct groupID) where T : struct, IEntityComponent + { + if (_filters.TryGetValue(TypeRefWrapper.wrapper, out var fasterDictionary) == false) + return false; + + return fasterDictionary.ContainsKey(groupID); + } + + public bool HasFilterForGroup(int filterID, ExclusiveGroupStruct groupID) + where T : struct, IEntityComponent + { + if (_filters.TryGetValue(TypeRefWrapper.wrapper, out var fasterDictionary) == false) + return false; + + if (fasterDictionary.TryGetValue(groupID, out var result)) + return result.HasFilter(filterID); + + return false; + } + + public ref GroupFilters CreateOrGetFiltersForGroup(ExclusiveGroupStruct groupID) + where T : struct, IEntityComponent + { + var fasterDictionary = + _filters.GetOrCreate(TypeRefWrapper.wrapper, () => new FasterDictionary()); + + return ref + fasterDictionary.GetOrCreate( + groupID, () => new GroupFilters(new SharedSveltoDictionaryNative(0), groupID)); + } + + public ref GroupFilters GetFiltersForGroup(ExclusiveGroupStruct groupID) + where T : struct, IEntityComponent + { +#if DEBUG && !PROFILE_SVELTO + if (_filters.ContainsKey(TypeRefWrapper.wrapper) == false) + throw new ECSException( + $"trying to fetch not existing filters, type {typeof(T)}"); + if (_filters[TypeRefWrapper.wrapper].ContainsKey(groupID) == false) + throw new ECSException( + $"trying to fetch not existing filters, type {typeof(T)} group {groupID.ToName()}"); +#endif + + return ref _filters[TypeRefWrapper.wrapper].GetValueByRef(groupID); + } + + public ref FilterGroup GetFilterForGroup(int filterId, ExclusiveGroupStruct groupID) + where T : struct, IEntityComponent + { +#if DEBUG && !PROFILE_SVELTO + if (_filters.ContainsKey(TypeRefWrapper.wrapper) == false) + throw new ECSException( + $"trying to fetch not existing filters, type {typeof(T)}"); + if (_filters[TypeRefWrapper.wrapper].ContainsKey(groupID) == false) + throw new ECSException( + $"trying to fetch not existing filters, type {typeof(T)} group {groupID.ToName()}"); +#endif + return ref _filters[TypeRefWrapper.wrapper][groupID].GetFilter(filterId); + } + + public bool TryGetFilterForGroup(int filterId, ExclusiveGroupStruct groupID, out FilterGroup groupFilter) + where T : struct, IEntityComponent + { + groupFilter = default; + + if (_filters.TryGetValue(TypeRefWrapper.wrapper, out var fasterDictionary) == false) + return false; + + if (fasterDictionary.TryGetValue(groupID, out var groupFilters) == false) + return false; + + if (groupFilters.TryGetFilter(filterId, out groupFilter) == false) + return false; + + return true; + } + + public bool TryGetFiltersForGroup(ExclusiveGroupStruct groupID, out GroupFilters groupFilters) + where T : struct, IEntityComponent + { + groupFilters = default; + + if (_filters.TryGetValue(TypeRefWrapper.wrapper, out var fasterDictionary) == false) + return false; + + return fasterDictionary.TryGetValue(groupID, out groupFilters); + } + + public void ClearFilter(int filterID, ExclusiveGroupStruct exclusiveGroupStruct) + { + if (_filters.TryGetValue(TypeRefWrapper.wrapper, out var fasterDictionary) == true) + { + DBC.ECS.Check.Require(fasterDictionary.ContainsKey(exclusiveGroupStruct), $"trying to clear filter not present in group {exclusiveGroupStruct}"); + + fasterDictionary[exclusiveGroupStruct].ClearFilter(filterID); + } + } + + public void ClearFilters(int filterID) + { + if (_filters.TryGetValue(TypeRefWrapper.wrapper, out var fasterDictionary) == true) + { + foreach (var filtersPerGroup in fasterDictionary) + filtersPerGroup.Value.ClearFilter(filterID); + } + } + + public void DisposeFilters(ExclusiveGroupStruct exclusiveGroupStruct) + { + if (_filters.TryGetValue(TypeRefWrapper.wrapper, out var fasterDictionary) == true) + { + fasterDictionary[exclusiveGroupStruct].DisposeFilters(); + fasterDictionary.Remove(exclusiveGroupStruct); + } + } + + public void DisposeFilters() + { + if (_filters.TryGetValue(TypeRefWrapper.wrapper, out var fasterDictionary) == true) + { + foreach (var filtersPerGroup in fasterDictionary) + filtersPerGroup.Value.DisposeFilters(); + } + + _filters.Remove(TypeRefWrapper.wrapper); + } + + public void DisposeFilterForGroup(int resetFilterID, ExclusiveGroupStruct @group) + { + if (_filters.TryGetValue(TypeRefWrapper.wrapper, out var fasterDictionary) == true) + { + fasterDictionary[group].DisposeFilter(resetFilterID); + } + } + + public bool TryRemoveEntityFromFilter(int filtersID, EGID egid) where T : unmanaged, IEntityComponent + { + if (TryGetFilterForGroup(filtersID, egid.groupID, out var filter)) + { + return filter.TryRemove(egid.entityID); + } + + return false; + } + + public void RemoveEntityFromFilter(int filtersID, EGID egid) where T : unmanaged, IEntityComponent + { + ref var filter = ref GetFilterForGroup(filtersID, egid.groupID); + + filter.Remove(egid.entityID); + } + + public void AddEntityToFilter(int filtersID, EGID egid, N mapper) where N:IEGIDMapper + { + ref var filter = ref CreateOrGetFilterForGroup(filtersID, egid.groupID, new RefWrapperType(mapper.entityType)); + + filter.Add(egid.entityID, mapper); + } + + readonly FasterDictionary> _filters; + } + + public Filters GetFilters() + { + return new Filters(_filters); + } + + FasterDictionary> _filters + => _enginesRoot._groupFilters; + } +} \ No newline at end of file diff --git a/Filters/EntitiesDB.GroupFilters.cs.meta b/Filters/EntitiesDB.GroupFilters.cs.meta new file mode 100644 index 0000000..deb8ca1 --- /dev/null +++ b/Filters/EntitiesDB.GroupFilters.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e2644d7bdd9e3c4d96a07f93a5a28497 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Filters/FilterGroup.cs b/Filters/FilterGroup.cs new file mode 100644 index 0000000..99091dc --- /dev/null +++ b/Filters/FilterGroup.cs @@ -0,0 +1,194 @@ +using Svelto.Common; +using Svelto.DataStructures; +using Svelto.ECS.DataStructures; + +namespace Svelto.ECS +{ + /// + /// In order to complete this feature, I need to be able to detect if the entity pointed by the filter + /// is still present. + /// This feature should work only with groups where entityID cannot be chosen by the user, so that + /// a real sparse set can be used like explained at: https://skypjack.github.io/2020-08-02-ecs-baf-part-9/ + /// For a sparse set to work, the index in the sparse list must coincide with the ID of the entity + /// so that from the dense list (that holds unordered entity index), I can get to the sparse list index + /// sparse[0] = position in the dense list of the entity 0 + /// dense[index] = entity ID but also index in the sparse list of the same entity ID + /// + public struct FilterGroup + { + internal FilterGroup(ExclusiveGroupStruct exclusiveGroupStruct, int ID) + { + _denseListOfIndicesToEntityComponentArray = + new NativeDynamicArrayCast(NativeDynamicArray.Alloc(Allocator.Persistent)); + //from the index, find the entityID + _reverseEIDs = new NativeDynamicArrayCast(NativeDynamicArray.Alloc(Allocator.Persistent)); + //from the entityID, find the index + _indexOfEntityInDenseList = new SharedSveltoDictionaryNative(0, Allocator.Persistent); + _exclusiveGroupStruct = exclusiveGroupStruct; + _ID = ID; + } + + /// + /// Todo: how to detect if the indices are still pointing to valid entities? + /// + public FilteredIndices filteredIndices => new FilteredIndices(_denseListOfIndicesToEntityComponentArray); + + public void Add(uint entityID, N mapper) where N:IEGIDMapper + { +#if DEBUG && !PROFILE_SVELTO + if (_denseListOfIndicesToEntityComponentArray.isValid == false) + throw new ECSException($"using an invalid filter"); + if (_indexOfEntityInDenseList.ContainsKey(entityID) == true) + throw new ECSException( + $"trying to add an existing entity {entityID} to filter {mapper.entityType} - {_ID} with group {mapper.groupID}"); + if (mapper.Exists(entityID) == false) + throw new ECSException( + $"trying adding an entity {entityID} to filter {mapper.entityType} - {_ID} with group {mapper.groupID}, but entity is not found! "); +#endif + //Get the index of the Entity in the component array + var indexOfEntityInBufferComponent = mapper.GetIndex(entityID); + + //add the index in the list of filtered indices + _denseListOfIndicesToEntityComponentArray.Add(indexOfEntityInBufferComponent); + + //inverse map: need to get the entityID from the index. This wouldn't be needed with a real sparseset + var lastIndex = (uint) (_denseListOfIndicesToEntityComponentArray.Count() - 1); + _reverseEIDs.AddAt(lastIndex) = entityID; + + //remember the entities indices. This is needed to remove entities from the filter + _indexOfEntityInDenseList.Add(entityID, lastIndex); + } + + public void Remove(uint entityID) + { +#if DEBUG && !PROFILE_SVELTO + if (_denseListOfIndicesToEntityComponentArray.isValid == false) + throw new ECSException($"invalid Filter"); + if (_indexOfEntityInDenseList.ContainsKey(entityID) == false) + throw new ECSException( + $"trying to remove a not existing entity {new EGID(entityID, _exclusiveGroupStruct)} from filter"); +#endif + InternalRemove(entityID); + } + + public bool TryRemove(uint entityID) + { +#if DEBUG && !PROFILE_SVELTO + if (_denseListOfIndicesToEntityComponentArray.isValid == false) + throw new ECSException($"invalid Filter"); +#endif + if (_indexOfEntityInDenseList.ContainsKey(entityID) == false) + return false; + + InternalRemove(entityID); + + return true; + } + + /// + /// Filters were initially designed to be used for tagging operations within submissions of entities. + /// They were designed as a fast tagging mechanism to be used within the submission frame. However I then + /// extended it, but the extension includes a drawback: + ///If filters are not in sync with the operations of remove and swap, filters may end up pointing to + ///invalid indices. I need to put in place a way to be able to recognised an invalid filter. + ///This is currently a disadvantage of the filters. The filters are not updated by the framework + ///but they must be updated by the user. + ///When to use this method: Add and Removed should be used to add and remove entities in the filters. This is + /// valid as long as no structural changes happen in the group of entities involved. + /// IF structural changes happen, the indices stored in the filters won't be valid anymore as they will possibly + /// point to entities that were not the original ones. On structural changes + /// (specifically entities swapped or removed) + /// the filters must then be rebuilt. It would be too slow to add this in the standard flow of Svelto in + /// the current state, so calling this method is a user responsibility. + /// + public void RebuildIndicesOnStructuralChange(N mapper) where N:IEGIDMapper + { +#if DEBUG && !PROFILE_SVELTO + if (_denseListOfIndicesToEntityComponentArray.isValid == false) + throw new ECSException($"invalid Filter"); +#endif + _denseListOfIndicesToEntityComponentArray.Clear(); + _reverseEIDs.Clear(); + + foreach (var value in _indexOfEntityInDenseList) + if (mapper.FindIndex(value.Key, out var indexOfEntityInBufferComponent) == true) + { + _denseListOfIndicesToEntityComponentArray.Add(indexOfEntityInBufferComponent); + var lastIndex = (uint) (_denseListOfIndicesToEntityComponentArray.Count() - 1); + _reverseEIDs.AddAt(lastIndex) = value.Key; + } + + _indexOfEntityInDenseList.Clear(); + + for (uint i = 0; i < _reverseEIDs.Count(); i++) + _indexOfEntityInDenseList[_reverseEIDs[i]] = i; + } + + public void Clear() + { +#if DEBUG && !PROFILE_SVELTO + if (_denseListOfIndicesToEntityComponentArray.isValid == false) + throw new ECSException($"invalid Filter"); +#endif + _indexOfEntityInDenseList.FastClear(); + _reverseEIDs.Clear(); + _denseListOfIndicesToEntityComponentArray.Clear(); + } + + internal void Dispose() + { +#if DEBUG && !PROFILE_SVELTO + if (_denseListOfIndicesToEntityComponentArray.isValid == false) + throw new ECSException($"invalid Filter"); +#endif + _denseListOfIndicesToEntityComponentArray.Dispose(); + _indexOfEntityInDenseList.Dispose(); + _reverseEIDs.Dispose(); + } + + void InternalRemove(uint entityID) + { + var count = (uint) _denseListOfIndicesToEntityComponentArray.Count(); + if (count > 0) + { + if (count > 1) + { + //get the index in the filter array of the entity to delete + var indexInDenseListFromEGID = _indexOfEntityInDenseList[entityID]; + //get the entityID of the last entity in the filter array + uint entityIDToMove = _reverseEIDs[count - 1]; + + //the last index of the last entity is updated to the slot of the deleted entity + if (entityIDToMove != entityID) + { + _indexOfEntityInDenseList[entityIDToMove] = indexInDenseListFromEGID; + //the reverseEGID is updated accordingly + _reverseEIDs[indexInDenseListFromEGID] = entityIDToMove; + } + + // + _reverseEIDs.UnorderedRemoveAt(count - 1); + + //finally remove the deleted entity from the filters array + _denseListOfIndicesToEntityComponentArray.UnorderedRemoveAt(indexInDenseListFromEGID); + + //remove the entity to delete from the tracked Entity + _indexOfEntityInDenseList.Remove(entityID); + } + else + { + _indexOfEntityInDenseList.FastClear(); + _reverseEIDs.Clear(); + _denseListOfIndicesToEntityComponentArray.Clear(); + } + } + } + + NativeDynamicArrayCast _denseListOfIndicesToEntityComponentArray; + NativeDynamicArrayCast _reverseEIDs; //forced to use this because it's not a real sparse set + SharedSveltoDictionaryNative _indexOfEntityInDenseList; + + readonly ExclusiveGroupStruct _exclusiveGroupStruct; + readonly int _ID; + } +} \ No newline at end of file diff --git a/Filters/FilterGroup.cs.meta b/Filters/FilterGroup.cs.meta new file mode 100644 index 0000000..7dd88f8 --- /dev/null +++ b/Filters/FilterGroup.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 820d8be7dde53b5b94864955b2a26a67 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Filters/FilteredIndices.cs b/Filters/FilteredIndices.cs new file mode 100644 index 0000000..47697cd --- /dev/null +++ b/Filters/FilteredIndices.cs @@ -0,0 +1,21 @@ +using System.Runtime.CompilerServices; +using Svelto.ECS.DataStructures; + +namespace Svelto.ECS +{ + public readonly struct FilteredIndices + { + public FilteredIndices(NativeDynamicArrayCast denseListOfIndicesToEntityComponentArray) + { + _denseListOfIndicesToEntityComponentArray = denseListOfIndicesToEntityComponentArray; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int Count() => _denseListOfIndicesToEntityComponentArray.Count(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public uint Get(uint index) => _denseListOfIndicesToEntityComponentArray[index]; + + readonly NativeDynamicArrayCast _denseListOfIndicesToEntityComponentArray; + } +} \ No newline at end of file diff --git a/Filters/FilteredIndices.cs.meta b/Filters/FilteredIndices.cs.meta new file mode 100644 index 0000000..aa47509 --- /dev/null +++ b/Filters/FilteredIndices.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8311e83888a9391a821923ab0940cb81 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Filters/GroupFilters.cs b/Filters/GroupFilters.cs new file mode 100644 index 0000000..4226a23 --- /dev/null +++ b/Filters/GroupFilters.cs @@ -0,0 +1,103 @@ +using Svelto.DataStructures; + +namespace Svelto.ECS +{ + public struct GroupFilters + { + internal GroupFilters(SharedSveltoDictionaryNative filters, ExclusiveGroupStruct group) + { + this.filters = filters; + _group = @group; + } + + public ref FilterGroup GetFilter(int filterIndex) + { +#if DEBUG && !PROFILE_SVELTO + if (filters.isValid == false) + throw new ECSException($"trying to fetch not existing filters {filterIndex} group {_group.ToName()}"); + if (filters.ContainsKey(filterIndex) == false) + throw new ECSException($"trying to fetch not existing filters {filterIndex} group {_group.ToName()}"); +#endif + return ref filters.GetValueByRef(filterIndex); + } + + public bool HasFilter(int filterIndex) { return filters.ContainsKey(filterIndex); } + + public void ClearFilter(int filterIndex) + { + if (filters.TryFindIndex(filterIndex, out var index)) + filters.GetValues(out _)[index].Clear(); + } + + public void ClearFilters() + { + foreach (var filter in filters) + filter.Value.Clear(); + } + + public bool TryGetFilter(int filterIndex, out FilterGroup filter) + { + return filters.TryGetValue(filterIndex, out filter); + } + + public SveltoDictionary>, NativeStrategy + , NativeStrategy>.SveltoDictionaryKeyValueEnumerator GetEnumerator() + { + return filters.GetEnumerator(); + } + + //Note the following methods are internal because I was pondering the idea to be able to return + //the list of GroupFilters linked to a specific filter ID. However this would mean to be able to + //maintain a revers map which at this moment seems too much and also would need the following + //method to be for ever internal (at this point in time I am not sure it's a good idea) + internal void DisposeFilter(int filterIndex) + { + if (filters.TryFindIndex(filterIndex, out var index)) + { + ref var filterGroup = ref filters.GetValues(out _)[index]; + + filterGroup.Dispose(); + + filters.Remove(filterIndex); + } + } + + internal void DisposeFilters() + { + //must release the native buffers! + foreach (var filter in filters) + filter.Value.Dispose(); + + filters.FastClear(); + } + + internal ref FilterGroup CreateOrGetFilter(int filterID) + { + if (filters.TryFindIndex(filterID, out var index) == false) + { + var orGetFilterForGroup = new FilterGroup(_group, filterID); + + filters[filterID] = orGetFilterForGroup; + + return ref filters.GetValueByRef(filterID); + } + + return ref filters.GetValues(out _)[index]; + } + + internal void Dispose() + { + foreach (var filter in filters) + { + filter.Value.Dispose(); + } + + filters.Dispose(); + } + + readonly ExclusiveGroupStruct _group; + + //filterID, filter + SharedSveltoDictionaryNative filters; + } +} \ No newline at end of file diff --git a/Filters/GroupFilters.cs.meta b/Filters/GroupFilters.cs.meta new file mode 100644 index 0000000..afb6a65 --- /dev/null +++ b/Filters/GroupFilters.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 36769f644df630609a3f3c4589bab898 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/GenericEntityDescriptor.cs b/GenericEntityDescriptor.cs index a8eb7a3..a10a149 100644 --- a/GenericEntityDescriptor.cs +++ b/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[] _componentBuilders; + static GenericEntityDescriptor() { _componentBuilders = new IComponentBuilder[] {new ComponentBuilder()}; } - public IEntityBuilder[] entitiesToBuild => _entityBuilders; + public IComponentBuilder[] componentsToBuild => _componentBuilders; } 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[] _componentBuilders; static GenericEntityDescriptor() { - _entityBuilders = new IEntityBuilder[] {new EntityBuilder(), new EntityBuilder()}; + _componentBuilders = new IComponentBuilder[] {new ComponentBuilder(), new ComponentBuilder()}; } - public IEntityBuilder[] entitiesToBuild => _entityBuilders; + public IComponentBuilder[] componentsToBuild => _componentBuilders; } 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[] _componentBuilders; static GenericEntityDescriptor() { - _entityBuilders = new IEntityBuilder[] + _componentBuilders = new IComponentBuilder[] { - new EntityBuilder(), - new EntityBuilder(), - new EntityBuilder() + new ComponentBuilder(), + new ComponentBuilder(), + new ComponentBuilder() }; } - public IEntityBuilder[] entitiesToBuild => _entityBuilders; + public IComponentBuilder[] componentsToBuild => _componentBuilders; } 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[] _componentBuilders; static GenericEntityDescriptor() { - _entityBuilders = new IEntityBuilder[] + _componentBuilders = 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 => _componentBuilders; } 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[] _componentBuilders; static GenericEntityDescriptor() { - _entityBuilders = new IEntityBuilder[] + _componentBuilders = 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 => _componentBuilders; } 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[] _componentBuilders; static GenericEntityDescriptor() { - _entityBuilders = new IEntityBuilder[] + _componentBuilders = 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 => _componentBuilders; } } \ No newline at end of file diff --git a/GlobalTypeID.cs b/GlobalTypeID.cs new file mode 100644 index 0000000..78a12c6 --- /dev/null +++ b/GlobalTypeID.cs @@ -0,0 +1,65 @@ +using System.Threading; +using Svelto.Common; +using Svelto.DataStructures; +using Svelto.ECS.DataStructures; + +namespace Svelto.ECS +{ + public class GlobalTypeID + { + internal static uint NextID() { return (uint) (Interlocked.Increment(ref value) - 1); } + + static GlobalTypeID() { value = 0; } + + static int value; + } + + interface IFiller + { + void FillFromByteArray(EntityComponentInitializer init, NativeBag buffer); + } + + class Filler : IFiller where T : struct, IEntityComponent + { + static Filler() + { + DBC.ECS.Check.Require(TypeCache.IsUnmanaged == true, "invalid type used"); + } + + //it's an internal interface + public void FillFromByteArray(EntityComponentInitializer init, NativeBag buffer) + { + var component = buffer.Dequeue(); + + init.Init(component); + } + } + + static class EntityComponentID + { +#if UNITY_NATIVE + internal static readonly Unity.Burst.SharedStatic ID = + Unity.Burst.SharedStatic.GetOrCreate(); +#else + internal struct SharedStatic + { + public uint Data; + } + + internal static SharedStatic ID; +#endif + } + + static class EntityComponentIDMap + { + static readonly FasterList TYPE_IDS = new FasterList(); + + internal static void Register(IFiller entityBuilder) where T : struct, IEntityComponent + { + var location = EntityComponentID.ID.Data = GlobalTypeID.NextID(); + TYPE_IDS.AddAt(location, entityBuilder); + } + + internal static IFiller GetTypeFromID(uint typeId) { return TYPE_IDS[typeId]; } + } +} \ No newline at end of file diff --git a/GlobalTypeID.cs.meta b/GlobalTypeID.cs.meta new file mode 100644 index 0000000..06f8a43 --- /dev/null +++ b/GlobalTypeID.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 740c683eaf643b0ea295428cd190c318 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/GroupCompound.cs b/GroupCompound.cs new file mode 100644 index 0000000..53badc1 --- /dev/null +++ b/GroupCompound.cs @@ -0,0 +1,225 @@ +using System; +using System.Threading; +using Svelto.DataStructures; + +namespace Svelto.ECS +{ + /// + /// Very naive fail safe, but at least it's simple to understand and safe + /// + static class GroupCompoundInitializer + { + internal static readonly ThreadLocal isInitializing4 = new ThreadLocal(); + internal static readonly ThreadLocal isInitializing3 = new ThreadLocal(); + internal static readonly ThreadLocal isInitializing2 = new ThreadLocal(); + } + + public abstract class GroupCompound + where G1 : GroupTag where G2 : GroupTag where G3 : GroupTag where G4 : GroupTag + { + static readonly FasterList _Groups; + + public static FasterReadOnlyList Groups => new FasterReadOnlyList(_Groups); + public static BuildGroup BuildGroup => new BuildGroup(_Groups[0]); + + static GroupCompound() + { + if (GroupCompoundInitializer.isInitializing4.Value == false) + { + _Groups = new FasterList(1); + + var Group = new ExclusiveGroup(); + _Groups.Add(Group); + + GroupCompound.Add(Group); + GroupCompound.Add(Group); + GroupCompound.Add(Group); + GroupCompound.Add(Group); + + GroupCompound.Add(Group); // and must share the same array + GroupCompound.Add(Group); + GroupCompound.Add(Group); + GroupCompound.Add(Group); + GroupCompound.Add(Group); + GroupCompound.Add(Group); + + //This is done here to be sure that the group is added once per group tag + //(if done inside the previous group compound it would be added multiple times) + GroupTag.Add(Group); + GroupTag.Add(Group); + GroupTag.Add(Group); + GroupTag.Add(Group); + + #if DEBUG + GroupMap.idToName[(uint) Group] = $"Compound: {typeof(G1).Name}-{typeof(G2).Name}-{typeof(G3).Name}-{typeof(G4).Name} ID {(uint)Group}"; + #endif + GroupCompoundInitializer.isInitializing4.Value = true; + //all the combinations must share the same group + GroupCompound._Groups = _Groups; + GroupCompound._Groups = _Groups; + GroupCompound._Groups = _Groups; + GroupCompound._Groups = _Groups; + GroupCompound._Groups = _Groups; + GroupCompound._Groups = _Groups; + GroupCompound._Groups = _Groups; + GroupCompound._Groups = _Groups; + GroupCompound._Groups = _Groups; + GroupCompound._Groups = _Groups; + GroupCompound._Groups = _Groups; + GroupCompound._Groups = _Groups; + GroupCompound._Groups = _Groups; + GroupCompound._Groups = _Groups; + GroupCompound._Groups = _Groups; + GroupCompound._Groups = _Groups; + GroupCompound._Groups = _Groups; + GroupCompound._Groups = _Groups; + GroupCompound._Groups = _Groups; + GroupCompound._Groups = _Groups; + GroupCompound._Groups = _Groups; + GroupCompound._Groups = _Groups; + GroupCompound._Groups = _Groups; + GroupCompoundInitializer.isInitializing4.Value = false; + } + } + + public static void Add(ExclusiveGroupStruct @group) + { + for (int i = 0; i < _Groups.count; ++i) + if (_Groups[i] == group) + throw new Exception("temporary must be transformed in unit test"); + + _Groups.Add(group); + } + } + + public abstract class GroupCompound + where G1 : GroupTag where G2 : GroupTag where G3 : GroupTag + { + static readonly FasterList _Groups; + + public static FasterReadOnlyList Groups => + new FasterReadOnlyList(_Groups); + + public static BuildGroup BuildGroup => new BuildGroup(_Groups[0]); + + public static void Add(ExclusiveGroupStruct group) + { + for (var i = 0; i < _Groups.count; ++i) + if (_Groups[i] == group) + throw new Exception("temporary must be transformed in unit test"); + + _Groups.Add(group); + } + + static GroupCompound() + { + if (GroupCompoundInitializer.isInitializing3.Value == false) + { + _Groups = new FasterList(1); + + var Group = new ExclusiveGroup(); + _Groups.Add(Group); + + GroupCompound.Add(Group); // and must share the same array + GroupCompound.Add(Group); + GroupCompound.Add(Group); + + //This is done here to be sure that the group is added once per group tag + //(if done inside the previous group compound it would be added multiple times) + GroupTag.Add(Group); + GroupTag.Add(Group); + GroupTag.Add(Group); + + #if DEBUG + GroupMap.idToName[(uint) Group] = $"Compound: {typeof(G1).Name}-{typeof(G2).Name}-{typeof(G3).Name} ID {(uint)Group}"; + #endif + //all the combinations must share the same group + GroupCompoundInitializer.isInitializing3.Value = true; + GroupCompound._Groups = _Groups; + GroupCompound._Groups = _Groups; + GroupCompound._Groups = _Groups; + GroupCompound._Groups = _Groups; + GroupCompound._Groups = _Groups; + GroupCompoundInitializer.isInitializing3.Value = false; + } + } + } + + public abstract class GroupCompound where G1 : GroupTag where G2 : GroupTag + { + static readonly FasterList _Groups; + + public static FasterReadOnlyList Groups => + new FasterReadOnlyList(_Groups); + + public static BuildGroup BuildGroup => new BuildGroup(_Groups[0]); + + public static void Add(ExclusiveGroupStruct group) + { + for (var i = 0; i < _Groups.count; ++i) + if (_Groups[i] == group) + throw new Exception("temporary must be transformed in unit test"); + + _Groups.Add(group); + } + + static GroupCompound() + { + if (GroupCompoundInitializer.isInitializing2.Value == false) + { + var Group = new ExclusiveGroup(); + + _Groups = new FasterList(1); + _Groups.Add(Group); + + //every abstract group preemptively adds this group, it may or may not be empty in future + GroupTag.Add(Group); + GroupTag.Add(Group); + + #if DEBUG + GroupMap.idToName[(uint) Group] = $"Compound: {typeof(G1).Name}-{typeof(G2).Name} ID {(uint)Group}"; + #endif + GroupCompoundInitializer.isInitializing2.Value = true; + GroupCompound._Groups = _Groups; + GroupCompoundInitializer.isInitializing2.Value = false; + } + } + } + + /// + ///A Group Tag holds initially just a group, itself. However the number of groups can grow with the number of + ///combinations of GroupTags including this one. This because a GroupTag is an adjective and different entities + ///can use the same adjective together with other ones. However since I need to be able to iterate over all the + ///groups with the same adjective, a group tag needs to hold all the groups sharing it. + /// + /// + public abstract class GroupTag where T : GroupTag + { + static readonly FasterList _Groups = new FasterList(1); + + public static FasterReadOnlyList Groups => + new FasterReadOnlyList(_Groups); + + public static BuildGroup BuildGroup => new BuildGroup(_Groups[0]); + + static GroupTag() + { + var group = new ExclusiveGroup(); + _Groups.Add(group); + + #if DEBUG + GroupMap.idToName[(uint) group] = $"Compound: {typeof(T).Name} ID {(uint)group}"; + #endif + } + + //Each time a new combination of group tags is found a new group is added. + internal static void Add(ExclusiveGroupStruct group) + { + for (var i = 0; i < _Groups.count; ++i) + if (_Groups[i] == group) + throw new Exception("temporary must be transformed in unit test"); + + _Groups.Add(group); + } + } +} \ No newline at end of file diff --git a/GroupCompound.cs.meta b/GroupCompound.cs.meta new file mode 100644 index 0000000..0d5fe69 --- /dev/null +++ b/GroupCompound.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b4c8e3ece545370bbdd5fb72949c5984 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Hybrid/IEntityViewComponent.cs b/Hybrid/IEntityViewComponent.cs new file mode 100644 index 0000000..7bee311 --- /dev/null +++ b/Hybrid/IEntityViewComponent.cs @@ -0,0 +1,9 @@ +namespace Svelto.ECS.Hybrid +{ + public interface IManagedComponent:IEntityComponent + {} + + public interface IEntityViewComponent:IManagedComponent, INeedEGID + {} +} + diff --git a/Hybrid/IEntityViewComponent.cs.meta b/Hybrid/IEntityViewComponent.cs.meta new file mode 100644 index 0000000..611c32c --- /dev/null +++ b/Hybrid/IEntityViewComponent.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 98b35f1b576439b68efee7e27c7c3f47 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Hybrid/IEntityViewStruct.cs b/Hybrid/IEntityViewStruct.cs deleted file mode 100644 index b351a50..0000000 --- a/Hybrid/IEntityViewStruct.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Svelto.ECS.Hybrid -{ - public interface IEntityViewStruct:IEntityStruct, INeedEGID - {} -} - diff --git a/Hybrid/IEntityViewStruct.cs.meta b/Hybrid/IEntityViewStruct.cs.meta deleted file mode 100644 index a3f2d8b..0000000 --- a/Hybrid/IEntityViewStruct.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 32f656cd4be03ed5bb67af259795aada -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Hybrid/IImplementor.cs b/Hybrid/IImplementor.cs index 6d58780..56ba68d 100644 --- a/Hybrid/IImplementor.cs +++ b/Hybrid/IImplementor.cs @@ -1,4 +1,4 @@ -namespace Svelto.ECS.Unity +namespace Svelto.ECS.Hybrid { public interface IImplementor { diff --git a/IEntityBuilder.cs b/IComponentBuilder.cs similarity index 80% rename from IEntityBuilder.cs rename to IComponentBuilder.cs index da7b527..0b532c5 100644 --- a/IEntityBuilder.cs +++ b/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/IComponentBuilder.cs.meta b/IComponentBuilder.cs.meta new file mode 100644 index 0000000..adcf084 --- /dev/null +++ b/IComponentBuilder.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2104c9265d383a0fbaf1b7e720fa044f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/IDisposingEngine.cs b/IDisposingEngine.cs new file mode 100644 index 0000000..49045f9 --- /dev/null +++ b/IDisposingEngine.cs @@ -0,0 +1,9 @@ +using System; + +namespace Svelto.ECS +{ + public interface IDisposingEngine: IDisposable + { + bool isDisposing { set; } + } +} \ No newline at end of file diff --git a/IDisposingEngine.cs.meta b/IDisposingEngine.cs.meta new file mode 100644 index 0000000..e336309 --- /dev/null +++ b/IDisposingEngine.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: aff503a030833df49da65ec7cfdce9a4 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/IEngine.cs b/IEngine.cs index b671fd9..5c5c8e9 100644 --- a/IEngine.cs +++ b/IEngine.cs @@ -1,3 +1,5 @@ +using Svelto.ECS.Internal; + namespace Svelto.ECS.Internal { public interface IReactEngine: IEngine @@ -14,4 +16,20 @@ namespace Svelto.ECS { public interface IEngine {} + + public interface IReactOnAddAndRemove : IReactOnAddAndRemove where T : IEntityComponent + { + void Add(ref T entityComponent, EGID egid); + void Remove(ref T entityComponent, EGID egid); + } + + public interface IReactOnSwap : IReactOnSwap where T : IEntityComponent + { + void MovedTo(ref T entityComponent, ExclusiveGroupStruct previousGroup, EGID egid); + } + + public interface IReactOnSubmission:IReactEngine + { + void EntitiesSubmitted(); + } } \ No newline at end of file diff --git a/IEntitiesDB.cs b/IEntitiesDB.cs deleted file mode 100644 index b2734a2..0000000 --- a/IEntitiesDB.cs +++ /dev/null @@ -1,193 +0,0 @@ -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; - } -} diff --git a/IEntitiesDB.cs.meta b/IEntitiesDB.cs.meta deleted file mode 100644 index 1854f23..0000000 --- a/IEntitiesDB.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 6a666335b7563288be4b534dd86de3c6 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/IEntityBuilder.cs.meta b/IEntityBuilder.cs.meta deleted file mode 100644 index 12ad551..0000000 --- a/IEntityBuilder.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: deecddede71d32318303a5a8ddab46d9 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/IEntityComponent.cs b/IEntityComponent.cs new file mode 100644 index 0000000..d5269f7 --- /dev/null +++ b/IEntityComponent.cs @@ -0,0 +1,16 @@ +namespace Svelto.ECS +{ + ///Entity Components MUST implement IEntityComponent + public interface IEntityComponent + { + } + + /// + /// use INeedEGID on an IEntityComponent only if you need the EGID. consider using EGIDComponent instead + /// + 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/IEntityComponent.cs.meta b/IEntityComponent.cs.meta new file mode 100644 index 0000000..8788a77 --- /dev/null +++ b/IEntityComponent.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4652689ed0b73fae9b01d8835918badd +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/IEntityFactory.cs b/IEntityFactory.cs index 14b7e89..6220c2b 100644 --- a/IEntityFactory.cs +++ b/IEntityFactory.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; namespace Svelto.ECS @@ -25,42 +26,42 @@ 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 + /// Using this function is like building a normal entity, but the entity components /// are grouped by groupID to be more efficiently processed inside engines and - /// improve cache locality. Either class 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, BuildGroup 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 - /// can be built in place of the generic parameter T. - /// - /// - /// - /// - /// - EntityStructInitializer BuildEntity(uint entityID, ExclusiveGroup.ExclusiveGroupStruct groupStructId, - T descriptorEntity, IEnumerable implementors = null) where T : IEntityDescriptor; + EntityComponentInitializer BuildEntity(uint entityID, BuildGroup 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; + + EntityComponentInitializer BuildEntity + (EGID egid, IComponentBuilder[] componentsToBuild, Type type, IEnumerable implementors = null); + +#if UNITY_NATIVE + NativeEntityFactory ToNative(string memberName) where T : IEntityDescriptor, new(); +#endif } -} +} \ No newline at end of file diff --git a/IEntityFunctions.cs b/IEntityFunctions.cs index 9d04e0d..cee25a4 100644 --- a/IEntityFunctions.cs +++ b/IEntityFunctions.cs @@ -5,16 +5,28 @@ 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, BuildGroup 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 RemoveEntitiesFromGroup(BuildGroup groupID); + + void SwapEntitiesInGroup(BuildGroup fromGroupID, BuildGroup toGroupID) where T : IEntityDescriptor, new(); + + void SwapEntityGroup(uint entityID, BuildGroup fromGroupID, BuildGroup toGroupID) + where T : IEntityDescriptor, new(); + + void SwapEntityGroup(EGID fromID, BuildGroup toGroupID) where T : IEntityDescriptor, new(); + + void SwapEntityGroup(EGID fromID, BuildGroup toGroupID, BuildGroup 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, BuildGroup mustBeFromGroup) + where T : IEntityDescriptor, new(); +#if UNITY_NATIVE + NativeEntityRemove ToNativeRemove(string memberName) where T : IEntityDescriptor, new(); + NativeEntitySwap ToNativeSwap(string memberName) where T : IEntityDescriptor, new(); +#endif } } \ No newline at end of file diff --git a/IEntityStruct.cs b/IEntityStruct.cs deleted file mode 100644 index 379b4b4..0000000 --- a/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/IEntityStruct.cs.meta b/IEntityStruct.cs.meta deleted file mode 100644 index fd31aa5..0000000 --- a/IEntityStruct.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 47d543b5838e3b1c9b2b84282509e457 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/IQueryingEntitiesEngine.cs b/IQueryingEntitiesEngine.cs index 57eae65..326e0a0 100644 --- a/IQueryingEntitiesEngine.cs +++ b/IQueryingEntitiesEngine.cs @@ -2,7 +2,7 @@ namespace Svelto.ECS { public interface IQueryingEntitiesEngine : IEngine { - IEntitiesDB entitiesDB { set; } + EntitiesDB entitiesDB { set; } void Ready(); } diff --git a/IReactOnAddAndRemove.cs b/IReactOnAddAndRemove.cs deleted file mode 100644 index 0e35eb7..0000000 --- a/IReactOnAddAndRemove.cs +++ /dev/null @@ -1,10 +0,0 @@ -using Svelto.ECS.Internal; - -namespace Svelto.ECS -{ - public interface IReactOnAddAndRemove : IReactOnAddAndRemove where T : IEntityStruct - { - void Add(ref T entityView, EGID egid); - void Remove(ref T entityView, EGID egid); - } - } \ No newline at end of file diff --git a/IReactOnAddAndRemove.cs.meta b/IReactOnAddAndRemove.cs.meta deleted file mode 100644 index fccb227..0000000 --- a/IReactOnAddAndRemove.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 9f9014097a5736309bea0a23b3b24079 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/IReactOnSwap.cs b/IReactOnSwap.cs deleted file mode 100644 index e9d0f0d..0000000 --- a/IReactOnSwap.cs +++ /dev/null @@ -1,12 +0,0 @@ -using Svelto.ECS.Internal; - -namespace Svelto.ECS -{ - public interface IReactOnSwap : IReactOnSwap where T : IEntityStruct - { - void MovedTo(ref T entityView, ExclusiveGroup.ExclusiveGroupStruct previousGroup, EGID egid); -#if SEEMS_UNNECESSARY - void MovedFrom(ref T entityView, EGID egid); -#endif - } -} \ No newline at end of file diff --git a/IReactOnSwap.cs.meta b/IReactOnSwap.cs.meta deleted file mode 100644 index 7b012ae..0000000 --- a/IReactOnSwap.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: dfa9bda6967d3b6b8901fe2fdc4caa88 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/NamedExclusiveGroup.cs b/NamedExclusiveGroup.cs new file mode 100644 index 0000000..a35d06b --- /dev/null +++ b/NamedExclusiveGroup.cs @@ -0,0 +1,23 @@ +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; + + static NamedExclusiveGroup() + { +#if DEBUG + GroupMap.idToName[(uint) Group] = $"{name} ID {(uint)Group}"; +#endif + } + // protected NamedExclusiveGroup(string recognizeAs) : base(recognizeAs) {} + // protected NamedExclusiveGroup(ushort range) : base(range) {} + } +} \ No newline at end of file diff --git a/NamedExclusiveGroup.cs.meta b/NamedExclusiveGroup.cs.meta new file mode 100644 index 0000000..60c9a37 --- /dev/null +++ b/NamedExclusiveGroup.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 362902783441376594e2c3b205be9758 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/QueryGroups.cs b/QueryGroups.cs new file mode 100644 index 0000000..42b9b20 --- /dev/null +++ b/QueryGroups.cs @@ -0,0 +1,153 @@ +using System.Runtime.CompilerServices; +using System.Threading; +using Svelto.DataStructures; + +namespace Svelto.ECS.Experimental +{ + struct GroupsList + { + static GroupsList() + { + groups = new FasterList(); + } + + static readonly FasterList groups; + + public FasterList reference => groups; + } + + public ref struct QueryGroups + { + static readonly ThreadLocal groups = new ThreadLocal(); + + public QueryGroups(LocalFasterReadOnlyList findGroups) + { + var groupsValue = groups.Value; + var group = groupsValue.reference; + + group.FastClear(); + for (int i = 0; i < findGroups.count; i++) + group.Add(findGroups[i]); + } + + public QueryGroups(ExclusiveGroupStruct findGroups) + { + var groupsValue = groups.Value; + var group = groupsValue.reference; + + group.FastClear(); + group.Add(findGroups); + } + + public QueryGroups(uint preparecount) + { + var groupsValue = groups.Value; + var group = groupsValue.reference; + + group.FastClear(); + group.EnsureCapacity(preparecount); + } + + public QueryResult Except(ExclusiveGroupStruct[] groupsToIgnore) + { + var group = groups.Value.reference; + var groupsCount = group.count; + + for (int i = 0; i < groupsToIgnore.Length; i++) + for (int j = 0; j < groupsCount; j++) + { + if (groupsToIgnore[i] == group[j]) + { + group.UnorderedRemoveAt(j); + j--; + groupsCount--; + } + } + + return new QueryResult(group); + } + + public QueryResult Except(FasterList groupsToIgnore) + { + var group = groups.Value.reference; + var groupsCount = group.count; + + for (int i = 0; i < groupsToIgnore.count; i++) + for (int j = 0; j < groupsCount; j++) + { + if (groupsToIgnore[i] == group[j]) + { + group.UnorderedRemoveAt(j); + j--; + groupsCount--; + } + } + + return new QueryResult(group); + } + + public QueryResult Except(ExclusiveGroupStruct groupsToIgnore) + { + var group = groups.Value.reference; + var groupsCount = group.count; + + for (int j = 0; j < groupsCount; j++) + if (groupsToIgnore == group[j]) + { + group.UnorderedRemoveAt(j); + j--; + groupsCount--; + } + + return new QueryResult(group); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int Count + (EntitiesDB entitiesDB, in LocalFasterReadOnlyList groups) where T : struct, IEntityComponent + { + int count = 0; + + var groupsCount = groups.count; + for (int i = 0; i < groupsCount; ++i) + { + count += entitiesDB.Count(groups[i]); + } + + return count; + } + + public QueryResult WithAny(EntitiesDB entitiesDB) + where T : struct, IEntityComponent + { + var group = groups.Value.reference; + var groupsCount = group.count; + + for (var i = 0; i < groupsCount; i++) + { + if (entitiesDB.Count(group[i]) == 0) + { + group.UnorderedRemoveAt(i); + i--; + groupsCount--; + } + } + + return new QueryResult(group); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Add(ExclusiveGroupStruct group) + { + groups.Value.reference.Add(group); + } + } + + public readonly ref struct QueryResult + { + readonly FasterReadOnlyList _group; + public QueryResult(FasterList @group) { _group = @group; } + + public FasterReadOnlyList result => _group; + } +} \ No newline at end of file diff --git a/QueryGroups.cs.meta b/QueryGroups.cs.meta new file mode 100644 index 0000000..050c2e2 --- /dev/null +++ b/QueryGroups.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a23c1bfaf8dd3e8082b63b0aa9974351 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/README.md b/README.md index 12652d9..ce18d30 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,11 @@ -# Svelto Entity Component System 2.9 - +# Svelto Entity Component System 3.0 ===================================== -Real ECS framework for c\#. Enables to write encapsulated, decoupled, maintainable, highly efficient, data oriented, cache friendly, multi-threaded (if used with Svelto.Tasks), code without pain. Although the framework is platform agnostic \(compatible with c\# 7 and .net standard 2.0\), it comes with several Unity extensions. +Real ECS framework for c\#. Enables to write encapsulated, decoupled, maintainable, highly efficient, data oriented, cache friendly, code without pain. Although the framework is platform agnostic \(compatible with c\# 7 and .net standard 2.0\), it comes with several Unity extensions. ## Why using Svelto.ECS with Unity? -_Svelto.ECS wasn't born just from the needs of a large team, but also as result of years of reasoning behind software engineering applied to game development\(\*\). Compared to Unity.ECS, the main goals and reasons for Svelto.ECS to exist are different enough to justify its on going development \(plus Svelto is platform agnostic, so it has been written with portability in mind\). Svelto.ECS hasn't been written just to develop faster code, it has been built to help develop better code. Performance gain is one of the benefits in using Svelto.ECS, as ECS in general is a great way to write cache-friendly code. However Svelto.ECS has been designed around the shift of paradigm from Object Oriented Programming and the consequent improvement of the code design and maintainability. Svelto.ECS is the result of years of iteration of the ECS paradigm applied to real game development with the intent to be "junior coder proof"._ +_Svelto.ECS wasn't born just from the needs of a large team, but also as result of years of reasoning behind software engineering applied to game development\(\*\). Svelto.ECS hasn't been written just to develop faster code, it has been designed to help develop better code. Performance gain is just one of the benefits in using Svelto.ECS, as ECS in general is a great way to write cache-friendly code. Svelto.ECS has been developed with the idea of ECS being a paradigm and not just a pattern, letting the user shifting completely away from Object Oriented Programming with consequent improvements of the code design and code maintainability. Svelto.ECS is the result of years of iteration of the ECS paradigm applied to real game development with the intent to be as fool proof as possible. ## How to clone the repository: The folders Svelto.ECS, Svelto.Tasks and Svelto.Common, where present, are submodules pointing to the relative repositories. If you find them empty, you need to update them through the submodule command. Check some instructions here: https://github.com/sebas77/Svelto.ECS.Vanilla.Example/wiki @@ -18,10 +17,9 @@ read this article for more information:http://www.sebaslab.com/distributing-svel ## Official Examples ### * **Mini Examples**: [https://github.com/sebas77/Svelto.MiniExamples](https://github.com/sebas77/Svelto.MiniExamples) \(including articles\) -* **Unity Boids Simulation**: [https://github.com/sebas77/Svelto.ECS.Examples.Boids](https://github.com/sebas77/Svelto.ECS.Examples.Boids) \(including article\) * **Unit Tests**: [https://github.com/sebas77/Svelto.ECS.Tests](https://github.com/sebas77/Svelto.ECS.Tests) -**Official Chat \(join to get help from me for free!\)** +**Official Discord Server \(join to get help from me for free!\)** * [https://discord.gg/3qAdjDb](https://discord.gg/3qAdjDb) @@ -29,15 +27,13 @@ read this article for more information:http://www.sebaslab.com/distributing-svel **Framework articles:** -* [http://www.sebaslab.com/introducing-svelto-ecs-2-9/](http://www.sebaslab.com/introducing-svelto-ecs-2-9/) \(shows what's changed since 2.8\) -* [http://www.sebaslab.com/introducing-svelto-ecs-2-8/](http://www.sebaslab.com/introducing-svelto-ecs-2-8/) \(shows what's changed since 2.7\) -* [http://www.sebaslab.com/svelto-2-7-whats-new-and-best-practices/](http://www.sebaslab.com/svelto-2-7-whats-new-and-best-practices/) \(shows what's changed since 2.5\) -* [http://www.sebaslab.com/svelto-ecs-2-5-and-allocation-0-code/](http://www.sebaslab.com/svelto-ecs-2-5-and-allocation-0-code/) \(shows what's changed since 2.0\) -* [http://www.sebaslab.com/svelto-ecs-2-0-almost-production-ready/](http://www.sebaslab.com/svelto-ecs-2-0-almost-production-ready/) \(shows what's changed since 1.0\) -* [http://www.sebaslab.com/ecs-1-0/](http://www.sebaslab.com/ecs-1-0/) -* [http://www.sebaslab.com/learning-svelto-ecs-by-example-the-unity-survival-example/](http://www.sebaslab.com/learning-svelto-ecs-by-example-the-unity-survival-example/) -* [http://www.sebaslab.com/learning-svelto-ecs-by-example-the-vanilla-example/](http://www.sebaslab.com/learning-svelto-ecs-by-example-the-vanilla-example/) -* [http://www.sebaslab.com/svelto-ecs-svelto-tasks-to-write-data-oriented-cache-friendly-multi-threaded-code-in-unity/](http://www.sebaslab.com/svelto-ecs-svelto-tasks-to-write-data-oriented-cache-friendly-multi-threaded-code-in-unity/) +* [Svelto ECS 3.0 is finally here (article in progress)] +* [Introducing Svelto ECS 2.9](http://www.sebaslab.com/introducing-svelto-ecs-2-9/) \(shows what's changed since 2.8\) +* [Introducing Svelto ECS 2.8](http://www.sebaslab.com/introducing-svelto-ecs-2-8/) \(shows what's changed since 2.7\) +* [Svelto.ECS 2.7: what’s new and best practices](http://www.sebaslab.com/svelto-2-7-whats-new-and-best-practices/) \(shows what's changed since 2.5\) +* [Introducing Svelto ECS 2.5](http://www.sebaslab.com/svelto-ecs-2-5-and-allocation-0-code/) \(shows what's changed since 2.0\) +* [Svelto.ECS 2.0 is production ready](http://www.sebaslab.com/svelto-ecs-2-0-almost-production-ready/) \(shows what's changed since 1.0\) +* [Svelto ECS is now production ready](http://www.sebaslab.com/ecs-1-0/) **Theory related articles \(in order of publishing date\):** @@ -50,32 +46,40 @@ read this article for more information:http://www.sebaslab.com/distributing-svel * [http://www.sebaslab.com/the-truth-behind-inversion-of-control-part-v-drifting-away-from-ioc-containers/](http://www.sebaslab.com/the-truth-behind-inversion-of-control-part-v-drifting-away-from-ioc-containers/) * [http://www.sebaslab.com/the-quest-for-maintainable-code-and-the-path-to-ecs/](http://www.sebaslab.com/the-quest-for-maintainable-code-and-the-path-to-ecs/) +**Practical articles** + +* [Svelto.ECS Internals: How to avoid boxing when using structs with reflection](https://www.sebaslab.com/casting-a-struct-into-an-interface-inside-a-generic-method-without-boxing/) +* [Svelto ECS 3.0 Internals: profiling the Entity Collection](https://www.sebaslab.com/svelto-ecs-3-0-internals-the-entity-collection/) +* [Svelto ECS 3.0 Internals: Support Native Memory Natively](https://www.sebaslab.com/svelto-ecs-3-0-internals-support-native-memory-natively/) +* [Svelto MiniExamples: GUI and Services Layer with Unity](https://www.sebaslab.com/svelto-miniexamples-gui-and-services-layer/) +* [Svelto Mini (Unity) Examples: Doofuses Must Eat](https://www.sebaslab.com/svelto-mini-examples-doofuses-must-eat/) +* [Svelto Mini Examples: The Unity Survival Example](http://www.sebaslab.com/learning-svelto-ecs-by-example-the-unity-survival-example/) +* [Learning Svelto.ECS by example – The Vanilla Example](http://www.sebaslab.com/learning-svelto-ecs-by-example-the-vanilla-example/) +* [Porting a boid simulation from UnityECS/Jobs to Svelto.ECS/Tasks](https://www.sebaslab.com/porting-a-boid-simulation-from-unityecs-to-svelto-ecs/) +* [Svelto.ECS+Tasks to write Data Oriented, Cache Friendly, Multi-Threaded code](http://www.sebaslab.com/svelto-ecs-svelto-tasks-to-write-data-oriented-cache-friendly-multi-threaded-code-in-unity/) + + Note: I included the IoC articles just to show how I shifted over the years from using an IoC container to use an ECS framework and the rationale behind its adoption. **The perfect companion for Svelto.ECS is Svelto.Tasks to run the logic of the Systems even on other threads!** * [https://github.com/sebas77/Svelto.Tasks](https://github.com/sebas77/Svelto.Tasks) -## Users Generated Content \(may use old versions of Svelto and be quite outdated\) +## Users Generated Content \(I removed all the outdated articles, so this is a call for new ones!\) -* [https://github.com/grovemaster/Unity3D-Game-App](https://github.com/grovemaster/Unity3D-Game-App) -* [https://github.com/colonelsalt/ZombieDeathBoomECS](https://github.com/colonelsalt/ZombieDeathBoomECS) -* [https://eagergames.wordpress.com/category/ecs/](https://eagergames.wordpress.com/category/ecs/) \(Dario Oliveri\) -* [https://blogs.msdn.microsoft.com/uk\_faculty\_connection/2018/05/08/entity-component-system-in-unity-a-tutorial/](https://blogs.msdn.microsoft.com/uk_faculty_connection/2018/05/08/entity-component-system-in-unity-a-tutorial/) \(Lee Stott\) -* [https://github.com/sebas77/Svelto.ECS.Debugger](https://github.com/sebas77/Svelto.ECS.Debugger) \(work just started\) -* [https://github.com/NED-Studio/LGK.Inspector](https://github.com/NED-Studio/LGK.Inspector) \(probably not working anymore\) +* [A Beginner’s Guide to Svelto.ECS (3.0) with Unity by Jiheh Ritterling](https://jiheh.medium.com/a-beginners-guide-to-svelto-ecs-3-0-with-unity-e9dbc88a2145) ## In case of bugs -Best option is to fork and clone [https://github.com/sebas77/Svelto.ECS.Tests](https://github.com/sebas77/Svelto.ECS.Tests), add a new test to reproduce the problem and request a pull. Then open a github, I come here pretty often :\). Also feel free to contact me on twitter or leave comments on the blog! +Best option is to fork and clone [https://github.com/sebas77/Svelto.ECS.Tests](https://github.com/sebas77/Svelto.ECS.Tests), add new tests to reproduce the problem and request a pull. I will then fix the issue. Also feel free to contact me on Discord. ## [The Github wiki page](https://github.com/sebas77/Svelto.ECS/wiki) -It needs love and as far as I understood, anyone can edit it. Feel free to do so if you have a good understanding of Svelto! I don't update old articles, while the wiki update is not consistent, so the only way to be sure you get it right is to join our Discord channel and ask questions. +Completely outdated and could even mislead. Feel free to update it if you have a good understanding of Svelto! I decided I won't update it anymore as it's better for me to focus on other parts of Svelto development. If you need any help, you will need to join the aforementioned discord server. ## I like the project, how can I help? -Hey thanks a lot for considering this. You can help in several ways. The simplest is to talk about Svelto.ECS and spread the word, more we are, better it is for the community. Then you can help with the documentation, updating the wiki or writing your own articles. Svelto.ECS has all the features needed to make a game with the ECS pattern, but some areas are lacking: A visual debugger and more unit tests are needed. Other platforms other than Unity could get some love too: Xenko, Godot and monogame. Porting to other languages, expecially c++, would be awesome! +Hey thanks a lot for considering this. You can help in several ways. The simplest is to talk about Svelto.ECS and spread the word, more we are, better it is for the community. Then you can help with the documentation, updating the wiki or writing your own articles. Svelto.ECS has all the features needed to make a game with the ECS pattern, but some areas are lacking: *A visual debugger and more unit tests are needed*. Other platforms other than Unity could get some love too: Stride Game, Godot, monogame, FNA or whatever supports c#. Porting to other languages, expecially c++, would be awesome but probably pointless. Please check the lane dedicated to the community tasks list here: https://github.com/sebas77/Svelto.ECS/projects/1 and let me know if you want to take something on! ## Svelto Framework is used to develop the following products\(\*\): diff --git a/Sequencer.cs b/Sequencer.cs deleted file mode 100644 index da67c6f..0000000 --- a/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/Sequencer.cs.meta b/Sequencer.cs.meta deleted file mode 100644 index 3619825..0000000 --- a/Sequencer.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 335f579b436238f78303330c07505d54 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Serialization/ComposedComponentSerializer.cs b/Serialization/ComposedComponentSerializer.cs new file mode 100644 index 0000000..c955b16 --- /dev/null +++ b/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/Serialization/ComposedComponentSerializer.cs.meta b/Serialization/ComposedComponentSerializer.cs.meta new file mode 100644 index 0000000..24cb455 --- /dev/null +++ b/Serialization/ComposedComponentSerializer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 75a3a8cbc34e3b248b65202969a2331e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Serialization/ComposedSerializer.cs b/Serialization/ComposedSerializer.cs deleted file mode 100644 index c78bdf3..0000000 --- a/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/Serialization/ComposedSerializer.cs.meta b/Serialization/ComposedSerializer.cs.meta deleted file mode 100644 index cb83706..0000000 --- a/Serialization/ComposedSerializer.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: e2785d3fa84b32e99c5bce1e49577126 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Serialization/DefaultSerializer.cs b/Serialization/DefaultSerializer.cs index 38e905f..4474863 100644 --- a/Serialization/DefaultSerializer.cs +++ b/Serialization/DefaultSerializer.cs @@ -1,12 +1,8 @@ -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() { @@ -14,17 +10,22 @@ namespace Svelto.ECS.Serialization foreach (var field in _type.GetFields()) { - if (field.FieldType.ContainsCustomAttribute(typeof(DoNotSerializeAttribute)) && field.IsPrivate == false) - throw new ECSException("field cannot be serialised ".FastConcat(_type.FullName)); + var fieldFieldType = field.FieldType; + if (fieldFieldType.ContainsCustomAttribute(typeof(DoNotSerializeAttribute)) && + field.IsPrivate == false) + throw new ECSException($"field cannot be serialised {fieldFieldType} in {_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 +34,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/Serialization/DefaultSerializerUtils.cs b/Serialization/DefaultSerializerUtils.cs index fcc600d..c0f8cdd 100644 --- a/Serialization/DefaultSerializerUtils.cs +++ b/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/Serialization/DefaultVersioningFactory.cs b/Serialization/DefaultVersioningFactory.cs index 5c3960d..2e3dfa4 100644 --- a/Serialization/DefaultVersioningFactory.cs +++ b/Serialization/DefaultVersioningFactory.cs @@ -1,28 +1,32 @@ using System.Collections.Generic; +using Svelto.Common; 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 IEnumerable _implementors; + + public DefaultVersioningFactory() {} + + public DefaultVersioningFactory(IEnumerable implementors) + { + _implementors = implementors; + } + + public EntityComponentInitializer BuildDeserializedEntity + (EGID egid, ISerializationData serializationData, ISerializableEntityDescriptor entityDescriptor + , int serializationType, IEntitySerialization entitySerialization, IEntityFactory factory + , bool enginesRootIsDeserializationOnly) { - var initializer = _factory.BuildEntity(egid, _implementors); - - entitySerialization.DeserializeEntityStructs(serializationData, entityDescriptor, ref initializer, serializationType); + var entityDescriptorEntitiesToSerialize = enginesRootIsDeserializationOnly ? entityDescriptor.entitiesToSerialize : entityDescriptor.componentsToBuild; + + var initializer = factory.BuildEntity(egid, entityDescriptorEntitiesToSerialize, TypeCache.type, _implementors); + + 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/Serialization/DontSerialize.cs b/Serialization/DontSerialize.cs index f5cba8a..414e16a 100644 --- a/Serialization/DontSerialize.cs +++ b/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/Serialization/EnginesRoot.GenericEntitySerialization.cs b/Serialization/EnginesRoot.GenericEntitySerialization.cs index 0ca5c63..98d856f 100644 --- a/Serialization/EnginesRoot.GenericEntitySerialization.cs +++ b/Serialization/EnginesRoot.GenericEntitySerialization.cs @@ -1,81 +1,55 @@ using System; -using Svelto.Common; -using Svelto.ECS.Internal; +using Svelto.DataStructures; 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; - sealed class EntitySerialization : IEntitySerialization { - public void SerializeEntity(EGID egid, ISerializationData serializationData, - SerializationType serializationType) + public void SerializeEntity(EGID egid, ISerializationData serializationData, 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); uint descriptorHash = serializableEntityHeader.descriptorHash; SerializationDescriptorMap serializationDescriptorMap = _enginesRoot.serializationDescriptorMap; - var factory = serializationDescriptorMap.GetSerializationFactory(descriptorHash); var entityDescriptor = serializationDescriptorMap.GetDescriptorFromHash(descriptorHash); + IDeserializationFactory factory = serializationDescriptorMap.GetSerializationFactory(descriptorHash); - //default factory - if (factory == null) - { - var initializer = _enginesRoot.BuildEntity(egid, - _enginesRoot._isDeserializationOnly - ? entityDescriptor.entitiesToSerialize - : entityDescriptor.entitiesToBuild); - - DeserializeEntityStructs(serializationData, entityDescriptor, ref initializer, serializationType); - - return initializer; - } - - //custom factory - return factory.BuildDeserializedEntity(egid, serializationData, entityDescriptor, serializationType, - this); + return factory.BuildDeserializedEntity(egid, serializationData, entityDescriptor, serializationType + , this, this._enginesRoot.GenerateEntityFactory() + , _enginesRoot._isDeserializationOnly); } - public void DeserializeEntity(ISerializationData serializationData, SerializationType serializationType) + public void DeserializeEntity(ISerializationData serializationData, int serializationType) { var serializableEntityHeader = new SerializableEntityHeader(serializationData); @@ -84,62 +58,78 @@ namespace Svelto.ECS DeserializeEntityInternal(serializationData, egid, serializableEntityHeader, serializationType); } - public void DeserializeEntity(EGID egid, ISerializationData serializationData, - SerializationType serializationType) + public void DeserializeEntity(EGID egid, ISerializationData serializationData, int serializationType) { var serializableEntityHeader = new SerializableEntityHeader(serializationData); DeserializeEntityInternal(serializationData, egid, serializableEntityHeader, serializationType); } - public void DeserializeEntityStructs(ISerializationData serializationData, - ISerializableEntityDescriptor entityDescriptor, - ref EntityStructInitializer initializer, SerializationType serializationType) + public void DeserializeEntityComponents + (ISerializationData serializationData, ISerializableEntityDescriptor entityDescriptor + , 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); + var entitySubmitOperation = + new EntitySubmitOperation(EntitySubmitOperationType.Swap, localEgid, toEgid + , entityDescriptor.componentsToBuild); - _enginesRoot.CheckRemoveEntityID(localEgid); - _enginesRoot.CheckAddEntityID(toEgid); + _enginesRoot.CheckRemoveEntityID(localEgid, entityDescriptor.realType); + _enginesRoot.CheckAddEntityID(toEgid, entityDescriptor.realType); _enginesRoot.QueueEntitySubmitOperation(entitySubmitOperation); } public void DeserializeEntityToDelete(EGID egid) { - EntitiesDB entitiesDB = _enginesRoot._entitiesDB; - ref var serializableEntityStruct = ref entitiesDB.QueryEntity(egid); - uint descriptorHash = serializableEntityStruct.descriptorHash; + EntitiesDB entitiesDB = _enginesRoot._entitiesDB; + ref var serializableEntityComponent = ref entitiesDB.QueryEntity(egid); + uint descriptorHash = serializableEntityComponent.descriptorHash; SerializationDescriptorMap serializationDescriptorMap = _enginesRoot.serializationDescriptorMap; var entityDescriptor = serializationDescriptorMap.GetDescriptorFromHash(descriptorHash); - _enginesRoot.CheckRemoveEntityID(egid); + _enginesRoot.CheckRemoveEntityID(egid, entityDescriptor.realType); - var entitySubmitOperation = new EntitySubmitOperation( - EntitySubmitOperationType.Remove, - egid, - egid, - entityDescriptor.entitiesToBuild); + var entitySubmitOperation = + new EntitySubmitOperation(EntitySubmitOperationType.Remove, egid, egid + , entityDescriptor.componentsToBuild); _enginesRoot.QueueEntitySubmitOperation(entitySubmitOperation); } @@ -151,48 +141,49 @@ namespace Svelto.ECS serializationDescriptorMap.RegisterSerializationFactory(deserializationFactory); } - internal EntitySerialization(EnginesRoot enginesRoot) - { - _enginesRoot = enginesRoot; - } + internal EntitySerialization(EnginesRoot enginesRoot) { _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(); - if (!_enginesRoot._entitiesDB.UnsafeQueryEntityDictionary(groupId, entityType, - out var safeDictionary)) + ExclusiveGroupStruct groupId = entityGID.groupID; + 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) + void DeserializeEntityInternal + (ISerializationData serializationData, EGID egid, SerializableEntityHeader serializableEntityHeader + , int serializationType) { SerializationDescriptorMap descriptorMap = _enginesRoot.serializationDescriptorMap; var entityDescriptor = descriptorMap.GetDescriptorFromHash(serializableEntityHeader.descriptorHash); + if (_enginesRoot._groupEntityComponentsDB.TryGetValue(egid.groupID, out var entitiesInGroupPerType) + == false) + throw new Exception("Entity Serialization failed"); + foreach (var serializableEntityBuilder in entityDescriptor.entitiesToSerialize) { - _enginesRoot._entitiesDB.UnsafeQueryEntityDictionary(egid.groupID, - serializableEntityBuilder.GetEntityType(), out var safeDictionary); + entitiesInGroupPerType.TryGetValue( + new RefWrapperType(serializableEntityBuilder.GetEntityComponentType()), out var safeDictionary); - serializationData.BeginNextEntityStruct(); - serializableEntityBuilder.Deserialize(egid.entityID, safeDictionary, serializationData, - serializationType); + serializationData.BeginNextEntityComponent(); + serializableEntityBuilder.Deserialize(egid.entityID, safeDictionary, serializationData + , serializationType); } } readonly EnginesRoot _enginesRoot; } - public IEntitySerialization GenerateEntitySerializer() - { - return new EntitySerialization(this); - } + public IEntitySerialization GenerateEntitySerializer() { return new EntitySerialization(this); } + + readonly bool _isDeserializationOnly; } } \ No newline at end of file diff --git a/Serialization/EnginesRoot.SerializableEntityHeader.cs b/Serialization/EnginesRoot.SerializableEntityHeader.cs index 32ccc67..92cf959 100644 --- a/Serialization/EnginesRoot.SerializableEntityHeader.cs +++ b/Serialization/EnginesRoot.SerializableEntityHeader.cs @@ -1,19 +1,17 @@ -using Svelto.DataStructures; - namespace Svelto.ECS { public partial class EnginesRoot { - struct SerializableEntityHeader + readonly 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/Serialization/EntitiesDB.DescriptorMap.cs b/Serialization/EntitiesDB.DescriptorMap.cs deleted file mode 100644 index 297f793..0000000 --- a/Serialization/EntitiesDB.DescriptorMap.cs +++ /dev/null @@ -1,101 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Reflection; -using Svelto.ECS.Serialization; - -namespace Svelto.ECS -{ - partial class EnginesRoot - { - sealed class SerializationDescriptorMap - { - /// - /// Use reflection to register all the ISerializableEntityDescriptor to be used for serialization - /// - internal SerializationDescriptorMap() - { - _descriptors = new Dictionary(); - _factories = new Dictionary(); - - Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); - foreach (Assembly assembly in assemblies) - { - 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); - } - } - } - } - - static IEnumerable GetTypesSafe(Assembly assembly) - { - try - { - Type[] types = assembly.GetTypes(); - - return types; - } - catch (ReflectionTypeLoadException e) - { - return e.Types; - } - } - - void RegisterEntityDescriptor(ISerializableEntityDescriptor descriptor) - { - if (descriptor == null) - { - return; - } - - uint descriptorHash = descriptor.hash; - -#if DEBUG && !PROFILER - if (_descriptors.ContainsKey(descriptorHash)) - { - throw new Exception($"Hash Collision of '{descriptor.GetType()}' against " + - $"'{_descriptors[descriptorHash]} ::: {descriptorHash}'"); - } -#endif - - _descriptors[descriptorHash] = descriptor; - } - - public ISerializableEntityDescriptor GetDescriptorFromHash(uint descriptorID) - { -#if DEBUG && !PROFILER - DBC.ECS.Check.Require(_descriptors.ContainsKey(descriptorID), - $"Could not find descriptor with ID '{descriptorID}'!"); -#endif - - return _descriptors[descriptorID]; - } - - public IDeserializationFactory GetSerializationFactory(uint descriptorID) - { - return _factories.TryGetValue(descriptorID, out var factory) ? factory : null; - } - - public void RegisterSerializationFactory(IDeserializationFactory deserializationFactory) - where Descriptor : ISerializableEntityDescriptor, new() - { - _factories.Add(SerializationEntityDescriptorTemplate.hash, deserializationFactory); - } - - readonly Dictionary _descriptors; - readonly Dictionary _factories; - } - - /// - /// The map of serializable entity hashes to the serializable entity builders (to know the entity structs - /// to serialize) - /// - SerializationDescriptorMap serializationDescriptorMap { get; } - } -} diff --git a/Serialization/EntitiesDB.DescriptorMap.cs.meta b/Serialization/EntitiesDB.DescriptorMap.cs.meta deleted file mode 100644 index 4d524ac..0000000 --- a/Serialization/EntitiesDB.DescriptorMap.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 97b274f2accd3767899a5f61197e856b -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Serialization/EntitiesDB.SerializationDescriptorMap.cs b/Serialization/EntitiesDB.SerializationDescriptorMap.cs new file mode 100644 index 0000000..e301625 --- /dev/null +++ b/Serialization/EntitiesDB.SerializationDescriptorMap.cs @@ -0,0 +1,119 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using Svelto.Common; +using Svelto.ECS.Serialization; + +namespace Svelto.ECS +{ + partial class EnginesRoot + { + sealed class SerializationDescriptorMap + { + /// + /// 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(); + + using (new StandardProfiler("Assemblies Scan")) + { + Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); + + Type d1 = typeof(DefaultVersioningFactory<>); + foreach (Assembly assembly in assemblies) + { + 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, type, d1); + } + } + } + } + } + + static IEnumerable GetTypesSafe(Assembly assembly) + { + try + { + Type[] types = assembly.GetTypes(); + + return types; + } + catch (ReflectionTypeLoadException e) + { + return e.Types; + } + } + + void RegisterEntityDescriptor(ISerializableEntityDescriptor descriptor, Type type, Type d1) + { + if (descriptor == null) + return; + + uint descriptorHash = descriptor.hash; + +#if DEBUG && !PROFILE_SVELTO + if (_descriptors.ContainsKey(descriptorHash)) + { + throw new Exception($"Hash Collision of '{descriptor.GetType()}' against " + + $"'{_descriptors[descriptorHash]} ::: {descriptorHash}'"); + } +#endif + _descriptors[descriptorHash] = descriptor; + Type[] typeArgs = {type}; + var makeGenericType = d1.MakeGenericType(typeArgs); + var instance = Activator.CreateInstance(makeGenericType); + _factories.Add(descriptorHash, instance as IDeserializationFactory); + } + + public ISerializableEntityDescriptor GetDescriptorFromHash(uint descriptorHash) + { +#if DEBUG && !PROFILE_SVELTO + DBC.ECS.Check.Require(_descriptors.ContainsKey(descriptorHash), + $"Could not find descriptor linked to hash, wrong deserialization size? '{ descriptorHash}'!"); +#endif + + return _descriptors[descriptorHash]; + } + + public IDeserializationFactory GetSerializationFactory(uint descriptorHash) + { +#if DEBUG && !PROFILE_SVELTO + DBC.ECS.Check.Require(_descriptors.ContainsKey(descriptorHash), + $"Could not find descriptor linked to descriptor hash, wrong deserialization size? '{ descriptorHash}'!"); + DBC.ECS.Check.Require(_factories.ContainsKey(descriptorHash), + $"Could not find factory linked to hash '{ _descriptors[descriptorHash]}'!"); +#endif + return _factories[descriptorHash]; + } + + public void RegisterSerializationFactory(IDeserializationFactory deserializationFactory) + where Descriptor : ISerializableEntityDescriptor, new() + { + _factories[SerializationEntityDescriptorTemplate.hash] = deserializationFactory; + } + + readonly Dictionary _descriptors; + readonly Dictionary _factories; + } + + /// + /// The map of serializable entity hashes to the serializable entity builders (to know the entity structs + /// to serialize) + /// + SerializationDescriptorMap serializationDescriptorMap { get; } + } +} diff --git a/Serialization/EntitiesDB.SerializationDescriptorMap.cs.meta b/Serialization/EntitiesDB.SerializationDescriptorMap.cs.meta new file mode 100644 index 0000000..5204828 --- /dev/null +++ b/Serialization/EntitiesDB.SerializationDescriptorMap.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ce2b9e7b71cc3a8fbbb5028a954b1e0f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Serialization/HashNameAttribute.cs b/Serialization/HashNameAttribute.cs index f2c6ed2..efead7e 100644 --- a/Serialization/HashNameAttribute.cs +++ b/Serialization/HashNameAttribute.cs @@ -10,6 +10,6 @@ namespace Svelto.ECS.Serialization _name = name; } - internal string _name; + internal readonly string _name; } } \ No newline at end of file diff --git a/Serialization/IComponentSerializer.cs b/Serialization/IComponentSerializer.cs new file mode 100644 index 0000000..62b3367 --- /dev/null +++ b/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/Serialization/IComponentSerializer.cs.meta b/Serialization/IComponentSerializer.cs.meta new file mode 100644 index 0000000..aa1f376 --- /dev/null +++ b/Serialization/IComponentSerializer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 24bcdec40afb3a88b4b48253718fd774 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Serialization/IDeserializationFactory.cs b/Serialization/IDeserializationFactory.cs index 1045f71..b35226e 100644 --- a/Serialization/IDeserializationFactory.cs +++ b/Serialization/IDeserializationFactory.cs @@ -2,8 +2,9 @@ namespace Svelto.ECS.Serialization { public interface IDeserializationFactory { - EntityStructInitializer BuildDeserializedEntity(EGID egid, ISerializationData serializationData, - ISerializableEntityDescriptor entityDescriptor, SerializationType serializationType, - IEntitySerialization entitySerialization); + EntityComponentInitializer BuildDeserializedEntity + (EGID egid, ISerializationData serializationData, ISerializableEntityDescriptor entityDescriptor + , int serializationType, IEntitySerialization entitySerialization, IEntityFactory factory + , bool enginesRootIsDeserializationOnly); } } diff --git a/Serialization/IEntitySerialization.cs b/Serialization/IEntitySerialization.cs index 6fb1ef8..2ff055d 100644 --- a/Serialization/IEntitySerialization.cs +++ b/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/Serialization/ISerializableComponentBuilder.cs b/Serialization/ISerializableComponentBuilder.cs new file mode 100644 index 0000000..2b1e906 --- /dev/null +++ b/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/Serialization/ISerializableComponentBuilder.cs.meta b/Serialization/ISerializableComponentBuilder.cs.meta new file mode 100644 index 0000000..5d3677d --- /dev/null +++ b/Serialization/ISerializableComponentBuilder.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 661e763682323e0db00206ec9e00de92 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Serialization/ISerializableEntityBuilder.cs b/Serialization/ISerializableEntityBuilder.cs deleted file mode 100644 index 91c3433..0000000 --- a/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/Serialization/ISerializableEntityBuilder.cs.meta b/Serialization/ISerializableEntityBuilder.cs.meta deleted file mode 100644 index 773d5d9..0000000 --- a/Serialization/ISerializableEntityBuilder.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 648364dbfc4731fcb5ccbb5b74672410 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Serialization/ISerializableEntityDescriptor.cs b/Serialization/ISerializableEntityDescriptor.cs index f441bde..d755ab7 100644 --- a/Serialization/ISerializableEntityDescriptor.cs +++ b/Serialization/ISerializableEntityDescriptor.cs @@ -1,10 +1,11 @@ +using System; + namespace Svelto.ECS.Serialization { - public interface ISerializableEntityDescriptor : IEntityDescriptor + public interface ISerializableEntityDescriptor : IDynamicEntityDescriptor { - uint hash { get; } - ISerializableEntityBuilder[] entitiesToSerialize { get; } - - void CopySerializedEntityStructs(in EntityStructInitializer sourceInitializer, in EntityStructInitializer destinationInitializer, SerializationType serializationType); + uint hash { get; } + ISerializableComponentBuilder[] entitiesToSerialize { get; } + Type realType { get; } } } \ No newline at end of file diff --git a/Serialization/ISerializationData.cs b/Serialization/ISerializationData.cs index 852d43a..1556d6d 100644 --- a/Serialization/ISerializationData.cs +++ b/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/Serialization/ISerializer.cs b/Serialization/ISerializer.cs deleted file mode 100644 index 39fa588..0000000 --- a/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/Serialization/ISerializer.cs.meta b/Serialization/ISerializer.cs.meta deleted file mode 100644 index 833adaf..0000000 --- a/Serialization/ISerializer.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: a23b9ab3bb783b5a8a117426c42f9e6a -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Serialization/PartialSerializer.cs b/Serialization/PartialSerializer.cs index a21348f..42bc959 100644 --- a/Serialization/PartialSerializer.cs +++ b/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() { @@ -25,18 +25,20 @@ namespace Svelto.ECS.Serialization { if (myAttributes[j] is PartialSerializerFieldAttribute) { - if (myMembers[i].FieldType == typeof(EGID)) - throw new ECSException("EGID fields cannot be serialised ".FastConcat(myType.FullName)); + var fieldType = myMembers[i].FieldType; + if (fieldType.ContainsCustomAttribute(typeof(DoNotSerializeAttribute)) && + myMembers[i].IsPrivate == false) + throw new ECSException($"field cannot be serialised {fieldType} in {myType.FullName}"); var offset = Marshal.OffsetOf(myMembers[i].Name); - var sizeOf = (uint)Marshal.SizeOf(myMembers[i].FieldType); + var sizeOf = (uint)Marshal.SizeOf(fieldType); offsets.Add(((uint) offset.ToInt32(), sizeOf)); totalSize += sizeOf; } } } - 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 +46,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 +68,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/Serialization/SerializableComponentBuilder.cs b/Serialization/SerializableComponentBuilder.cs new file mode 100644 index 0000000..d3ba176 --- /dev/null +++ b/Serialization/SerializableComponentBuilder.cs @@ -0,0 +1,108 @@ +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 = (uint) MemoryUtilities.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"); + } + + ref T val = ref safeDictionary.GetDirectValueByRef(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"); + } + + ref T val = ref safeDictionary.GetDirectValueByRef(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/Serialization/SerializableComponentBuilder.cs.meta b/Serialization/SerializableComponentBuilder.cs.meta new file mode 100644 index 0000000..eab72e1 --- /dev/null +++ b/Serialization/SerializableComponentBuilder.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 84c6a47b61763fedb7679f23f4a92951 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Serialization/SerializableEntityBuilder.cs b/Serialization/SerializableEntityBuilder.cs deleted file mode 100644 index e34caee..0000000 --- a/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/Serialization/SerializableEntityBuilder.cs.meta b/Serialization/SerializableEntityBuilder.cs.meta deleted file mode 100644 index ab8eeb8..0000000 --- a/Serialization/SerializableEntityBuilder.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: cf6b7d646996386fb96895f46f08e6e8 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Serialization/SerializableEntityStruct.cs b/Serialization/SerializableEntityComponent.cs similarity index 59% rename from Serialization/SerializableEntityStruct.cs rename to Serialization/SerializableEntityComponent.cs index 1e10932..57e1b2d 100644 --- a/Serialization/SerializableEntityStruct.cs +++ b/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/Serialization/SerializableEntityComponent.cs.meta b/Serialization/SerializableEntityComponent.cs.meta new file mode 100644 index 0000000..e75210a --- /dev/null +++ b/Serialization/SerializableEntityComponent.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 7d2433718ea73155896e4d16a8060487 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Serialization/SerializableEntityDescriptor.cs b/Serialization/SerializableEntityDescriptor.cs index f31b902..dd676dd 100644 --- a/Serialization/SerializableEntityDescriptor.cs +++ b/Serialization/SerializableEntityDescriptor.cs @@ -16,107 +16,95 @@ namespace Svelto.ECS.Serialization { static SerializableEntityDescriptor() { - IEntityBuilder[] defaultEntities = EntityDescriptorTemplate.descriptor.entitiesToBuild; + IComponentBuilder[] defaultEntities = EntityDescriptorTemplate.descriptor.componentsToBuild; - var hashNameAttribute = _type.GetCustomAttribute(); + 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)); + 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 EntityInfoComponent + { + componentsToBuild = ComponentsToBuild + }); } ///// - var entitiesToSerialize = new FasterList(); - _entitiesToSerializeMap = new FasterDictionary, ISerializableEntityBuilder>(); - foreach (IEntityBuilder e in defaultEntities) + var entitiesToSerialize = new FasterList(); + EntityComponentsToSerializeMap = new FasterDictionary(); + 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 RefWrapperType(entityType)] = serializableEntityBuilder; entitiesToSerialize.Add(serializableEntityBuilder); } } - - _entitiesToSerialize = entitiesToSerialize.ToArray(); + + 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() == ComponentBuilderUtilities.ENTITY_INFO_COMPONENT) { 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 Type realType => Type; + public ISerializableComponentBuilder[] entitiesToSerialize => EntitiesToSerialize; - static readonly IEntityBuilder[] _entitiesToBuild; - static readonly FasterDictionary, ISerializableEntityBuilder> _entitiesToSerializeMap; - static readonly ISerializableEntityBuilder[] _entitiesToSerialize; + static readonly IComponentBuilder[] ComponentsToBuild; + static readonly FasterDictionary EntityComponentsToSerializeMap; + static readonly ISerializableComponentBuilder[] EntitiesToSerialize; - static readonly uint _hash; - static readonly Type _serializableStructType = typeof(SerializableEntityStruct); - static readonly Type _type = typeof(TType); + static readonly uint Hash; + static readonly Type SerializableStructType = typeof(SerializableEntityComponent); + static readonly Type Type = typeof(TType); } -} +} \ No newline at end of file diff --git a/Serialization/SerializableEntityStruct.cs.meta b/Serialization/SerializableEntityStruct.cs.meta deleted file mode 100644 index 46d99ff..0000000 --- a/Serialization/SerializableEntityStruct.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 95f2511f3ed430cdab31d84267aef98b -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Serialization/SerializerExt.cs b/Serialization/SerializerExt.cs new file mode 100644 index 0000000..c7d455d --- /dev/null +++ b/Serialization/SerializerExt.cs @@ -0,0 +1,44 @@ +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 System.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 System.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/Serialization/SerializerExt.cs.meta b/Serialization/SerializerExt.cs.meta new file mode 100644 index 0000000..7316c65 --- /dev/null +++ b/Serialization/SerializerExt.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 1fc5a233f97735efa37496a57617df33 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Serialization/SimpleSerializationData.cs b/Serialization/SimpleSerializationData.cs index 71c7d54..e1563f9 100644 --- a/Serialization/SimpleSerializationData.cs +++ b/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/Serialization/Unsafe.cs b/Serialization/Unsafe.cs deleted file mode 100644 index dd6ea52..0000000 --- a/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/Serialization/Unsafe.cs.meta b/Serialization/Unsafe.cs.meta deleted file mode 100644 index 0d86eef..0000000 --- a/Serialization/Unsafe.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 20f1ecdfa99f37fcbcf27259ddd33761 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/SetEGIDWithoutBoxing.cs b/SetEGIDWithoutBoxing.cs new file mode 100644 index 0000000..bc81645 --- /dev/null +++ b/SetEGIDWithoutBoxing.cs @@ -0,0 +1,42 @@ +using System.Runtime.CompilerServices; + +namespace Svelto.ECS.Internal +{ + 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) + { +#if !ENABLE_IL2CPP + var method = typeof(Trick).GetMethod(nameof(Trick.SetEGIDImpl)).MakeGenericMethod(typeof(T)); + return (SetEGIDWithoutBoxingActionCast) System.Delegate.CreateDelegate( + typeof(SetEGIDWithoutBoxingActionCast), method); +#else + return (ref T target, EGID egid) => + { + var needEgid = (target as INeedEGID); + needEgid.ID = egid; + target = (T) needEgid; + }; +#endif + } + + return null; + } + + static class Trick + { + public static void SetEGIDImpl(ref U target, EGID egid) where U : struct, INeedEGID + { + target.ID = egid; + } + } + } +} \ No newline at end of file diff --git a/DataStructures/SetEGIDWithoutBoxing.cs.meta b/SetEGIDWithoutBoxing.cs.meta similarity index 83% rename from DataStructures/SetEGIDWithoutBoxing.cs.meta rename to SetEGIDWithoutBoxing.cs.meta index 200f42e..5530359 100644 --- a/DataStructures/SetEGIDWithoutBoxing.cs.meta +++ b/SetEGIDWithoutBoxing.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 51c301e8cd823216a2926f6f61110cb6 +guid: 49b9484bd0a43721bbb45a6676d44987 MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/SimpleEntitiesSubmissionScheduler.cs b/SimpleEntitiesSubmissionScheduler.cs new file mode 100644 index 0000000..512a49f --- /dev/null +++ b/SimpleEntitiesSubmissionScheduler.cs @@ -0,0 +1,30 @@ +using Svelto.ECS.Schedulers; + +namespace Svelto.ECS.Schedulers +{ + //This scheduler shouldn't be used in production and it's meant to be used for Unit Tests only + public sealed class SimpleEntitiesSubmissionScheduler : ISimpleEntitiesSubmissionScheduler + { + public override void SubmitEntities() + { + if (paused == false) + _onTick.Invoke(); + } + + protected internal override EnginesRoot.EntitiesSubmitter onTick + { + set + { + DBC.ECS.Check.Require(_onTick.IsUnused , "a scheduler can be exclusively used by one enginesRoot only"); + + _onTick = value; + } + } + + public override bool paused { get; set; } + + public override void Dispose() { } + + EnginesRoot.EntitiesSubmitter _onTick; + } +} \ No newline at end of file diff --git a/SimpleEntitiesSubmissionScheduler.cs.meta b/SimpleEntitiesSubmissionScheduler.cs.meta new file mode 100644 index 0000000..cc27f5f --- /dev/null +++ b/SimpleEntitiesSubmissionScheduler.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 442baaf96b073bcda1728f7be4985308 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/SimpleSubmissionEntityViewScheduler.cs b/SimpleSubmissionEntityViewScheduler.cs deleted file mode 100644 index e2fe657..0000000 --- a/SimpleSubmissionEntityViewScheduler.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System; -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 void SubmitEntities() - { - _onTick.Invoke(); - } - - EnginesRoot.EntitiesSubmitter IEntitySubmissionScheduler.onTick - { - set => _onTick = value; - } - - EnginesRoot.EntitiesSubmitter _onTick; - - public void Dispose() - { - } - } -} \ No newline at end of file diff --git a/SimpleSubmissionEntityViewScheduler.cs.meta b/SimpleSubmissionEntityViewScheduler.cs.meta deleted file mode 100644 index b57af3d..0000000 --- a/SimpleSubmissionEntityViewScheduler.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 43e12e02577b353dbe1118bdb28cadbf -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/SortedEnginesGroup.cs b/SortedEnginesGroup.cs new file mode 100644 index 0000000..dc4d7ec --- /dev/null +++ b/SortedEnginesGroup.cs @@ -0,0 +1,83 @@ +using Svelto.DataStructures; +using Svelto.Common; + +namespace Svelto.ECS +{ + public interface IStepEngine : IEngine + { + void Step(); + + string name { get; } + } + + public interface IStepEngine : IEngine + { + void Step(ref T _param); + + string name { get; } + } + + public interface IStepGroupEngine : IStepEngine + { + } + + public interface IStepGroupEngine : IStepEngine + { + } + + public abstract class SortedEnginesGroup : IStepGroupEngine + where SequenceOrder : struct, ISequenceOrder where Interface : IStepEngine + { + protected SortedEnginesGroup(FasterList engines) + { + _name = "SortedEnginesGroup - "+this.GetType().Name; + _instancedSequence = new Sequence(engines); + } + + public void Step() + { + var sequenceItems = _instancedSequence.items; + using (var profiler = new PlatformProfiler(_name)) + { + for (var index = 0; index < sequenceItems.count; index++) + { + var engine = sequenceItems[index]; + using (profiler.Sample(engine.name)) engine.Step(); + } + } + } + + public string name => _name; + + readonly string _name; + readonly Sequence _instancedSequence; + } + + public abstract class SortedEnginesGroup: IStepGroupEngine + where SequenceOrder : struct, ISequenceOrder where Interface : IStepEngine + { + protected SortedEnginesGroup(FasterList engines) + { + _name = "SortedEnginesGroup - "+this.GetType().Name; + _instancedSequence = new Sequence(engines); + } + + public void Step(ref Parameter param) + { + var sequenceItems = _instancedSequence.items; + using (var profiler = new PlatformProfiler(_name)) + { + for (var index = 0; index < sequenceItems.count; index++) + { + var engine = sequenceItems[index]; + using (profiler.Sample(engine.name)) engine.Step(ref param); + } + } + } + + public string name => _name; + + readonly string _name; + readonly Sequence _instancedSequence; + } +} diff --git a/SortedEnginesGroup.cs.meta b/SortedEnginesGroup.cs.meta new file mode 100644 index 0000000..5c1c011 --- /dev/null +++ b/SortedEnginesGroup.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 690435cd81303cf593cf1c5bdf96e70f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Streams.meta b/Streams.meta new file mode 100644 index 0000000..d3f18fb --- /dev/null +++ b/Streams.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 949ebb1ddf5136ea8894e09bba93aade +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Streams/Consumer.cs b/Streams/Consumer.cs new file mode 100644 index 0000000..ffdc88b --- /dev/null +++ b/Streams/Consumer.cs @@ -0,0 +1,91 @@ +using System; +using Svelto.Common; +using Svelto.DataStructures; + +namespace Svelto.ECS +{ + public struct Consumer : IDisposable where T : unmanaged, IEntityComponent + { + internal Consumer(string name, uint capacity) : this() + { + unsafe + { +#if DEBUG && !PROFILE_SVELTO + _name = name; +#endif + _ringBuffer = new RingBuffer>((int) capacity, +#if DEBUG && !PROFILE_SVELTO + _name +#else + string.Empty +#endif + ); + mustBeDisposed = MemoryUtilities.Alloc(sizeof(bool), Allocator.Persistent); + *(bool*) mustBeDisposed = false; + } + } + + internal Consumer(ExclusiveGroupStruct group, string name, uint capacity) : this(name, capacity) + { + this.group = group; + hasGroup = true; + } + + internal void Enqueue(in T entity, in EGID egid) + { + _ringBuffer.Enqueue((entity, egid)); + } + + public bool TryDequeue(out T entity) + { + var tryDequeue = _ringBuffer.TryDequeue(out var values); + + entity = values.Item1; + + return tryDequeue; + } + + public bool TryDequeue(out T entity, out EGID id) + { + var tryDequeue = _ringBuffer.TryDequeue(out var values); + + entity = values.Item1; + id = values.Item2; + + return tryDequeue; + } + + public void Flush() + { + _ringBuffer.Reset(); + } + + public void Dispose() + { + unsafe + { + *(bool*) mustBeDisposed = true; + } + } + + public uint Count() + { + return (uint) _ringBuffer.Count; + } + + public void Free() + { + MemoryUtilities.Free(mustBeDisposed, Allocator.Persistent); + } + + readonly RingBuffer> _ringBuffer; + + internal readonly ExclusiveGroupStruct group; + internal readonly bool hasGroup; + internal IntPtr mustBeDisposed; + +#if DEBUG && !PROFILE_SVELTO + readonly string _name; +#endif + } +} \ No newline at end of file diff --git a/Streams/Consumer.cs.meta b/Streams/Consumer.cs.meta new file mode 100644 index 0000000..8fdf7d4 --- /dev/null +++ b/Streams/Consumer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e90417a44ee539088f95c95c60cc14a2 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Streams/EnginesRoot.Streams.cs b/Streams/EnginesRoot.Streams.cs new file mode 100644 index 0000000..3c2e29c --- /dev/null +++ b/Streams/EnginesRoot.Streams.cs @@ -0,0 +1,18 @@ +namespace Svelto.ECS +{ + public partial class EnginesRoot + { + internal Consumer GenerateConsumer(string name, uint capacity) where T : unmanaged, IEntityComponent + { + return _entityStreams.GenerateConsumer(name, capacity); + } + + internal Consumer GenerateConsumer(ExclusiveGroupStruct group, string name, uint capacity) + where T : unmanaged, IEntityComponent + { + return _entityStreams.GenerateConsumer(@group, name, capacity); + } + + internal readonly EntitiesStreams _entityStreams; + } +} \ No newline at end of file diff --git a/Streams/EnginesRoot.Streams.cs.meta b/Streams/EnginesRoot.Streams.cs.meta new file mode 100644 index 0000000..4cbb36c --- /dev/null +++ b/Streams/EnginesRoot.Streams.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 811b766279f630978d8b8b8d9f9ab37e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Streams/EntitiesDB.Streams.cs b/Streams/EntitiesDB.Streams.cs new file mode 100644 index 0000000..6b2f4c1 --- /dev/null +++ b/Streams/EntitiesDB.Streams.cs @@ -0,0 +1,20 @@ +using System; +using System.Runtime.CompilerServices; + +namespace Svelto.ECS +{ + public partial class EntitiesDB + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void PublishEntityChange(EGID egid) where T : unmanaged, IEntityComponent + { + _entityStream.PublishEntity(ref this.QueryEntity(egid), egid); + } +#if later + public ThreadSafeNativeEntityStream GenerateThreadSafePublisher() where T: unmanaged, IEntityComponent + { + return _entityStream.GenerateThreadSafePublisher(this); + } +#endif + } +} \ No newline at end of file diff --git a/Streams/EntitiesDB.Streams.cs.meta b/Streams/EntitiesDB.Streams.cs.meta new file mode 100644 index 0000000..aa49f73 --- /dev/null +++ b/Streams/EntitiesDB.Streams.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0f2d5c7623463601bed8b9f8d25c210a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Streams/EntitiesStreams.cs b/Streams/EntitiesStreams.cs new file mode 100644 index 0000000..6d12992 --- /dev/null +++ b/Streams/EntitiesStreams.cs @@ -0,0 +1,72 @@ +using System; +using Svelto.DataStructures; + +namespace Svelto.ECS +{ + /// + /// I eventually realised that, with the ECS design, no form of communication other than polling entity components can + /// exist. + /// Using groups, you can have always an optimal set of entity components to poll. However EntityStreams + /// can be useful if: + /// - you need to react on seldom entity changes, usually due to user events + /// - you want engines to be able to track entity changes + /// - you want a thread-safe way to read entity states, which includes all the state changes and not the last + /// one only + /// - you want to communicate between EnginesRoots + /// + internal struct EntitiesStreams : IDisposable + { + internal Consumer GenerateConsumer(string name, uint capacity) + where T : unmanaged, IEntityComponent + { + if (_streams.ContainsKey(TypeRefWrapper.wrapper) == false) + _streams[TypeRefWrapper.wrapper] = new EntityStream(); + + return (_streams[TypeRefWrapper.wrapper] as EntityStream).GenerateConsumer(name, capacity); + } + + public Consumer GenerateConsumer(ExclusiveGroupStruct group, string name, uint capacity) + where T : unmanaged, IEntityComponent + { + if (_streams.ContainsKey(TypeRefWrapper.wrapper) == false) + _streams[TypeRefWrapper.wrapper] = new EntityStream(); + + var typeSafeStream = (EntityStream) _streams[TypeRefWrapper.wrapper]; + return typeSafeStream.GenerateConsumer(group, name, capacity); + } +#if later + public ThreadSafeNativeEntityStream GenerateThreadSafePublisher(EntitiesDB entitiesDB) where T: unmanaged, IEntityComponent + { + var threadSafeNativeEntityStream = new ThreadSafeNativeEntityStream(entitiesDB); + + _streams[TypeRefWrapper.wrapper] = threadSafeNativeEntityStream; + + return threadSafeNativeEntityStream; + } +#endif + + internal void PublishEntity(ref T entity, EGID egid) where T : unmanaged, IEntityComponent + { + if (_streams.TryGetValue(TypeRefWrapper.wrapper, out var typeSafeStream)) + (typeSafeStream as EntityStream).PublishEntity(ref entity, egid); + else + Console.LogDebug("No Consumers are waiting for this entity to change ", typeof(T)); + } + + public void Dispose() + { + foreach (var stream in _streams) + stream.Value.Dispose(); + } + + public static EntitiesStreams Create() + { + var stream = new EntitiesStreams(); + stream._streams = ManagedSveltoDictionary.Create(); + + return stream; + } + + ManagedSveltoDictionary _streams; + } +} \ No newline at end of file diff --git a/Streams/EntitiesStreams.cs.meta b/Streams/EntitiesStreams.cs.meta new file mode 100644 index 0000000..38efc7d --- /dev/null +++ b/Streams/EntitiesStreams.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0969c421a99f328582f902b595a6fdfc +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Streams/EntityStream.cs b/Streams/EntityStream.cs new file mode 100644 index 0000000..1825013 --- /dev/null +++ b/Streams/EntityStream.cs @@ -0,0 +1,71 @@ +using Svelto.DataStructures; + +namespace Svelto.ECS +{ + internal interface ITypeSafeStream + { + void Dispose(); + } + + public class EntityStream : ITypeSafeStream where T : unmanaged, IEntityComponent + { + readonly ThreadSafeFasterList> _consumers; + + internal EntityStream() + { + _consumers = new ThreadSafeFasterList>(); + } + + ~EntityStream() + { + for (var i = 0; i < _consumers.count; i++) + _consumers[i].Free(); + } + + public void Dispose() + { } + + internal void PublishEntity(ref T entity, EGID egid) + { + for (var i = 0; i < _consumers.count; i++) + unsafe + { + 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); + } + } + } + + internal Consumer GenerateConsumer(string name, uint capacity) + { + var consumer = new Consumer(name, capacity); + + _consumers.Add(consumer); + + return consumer; + } + + internal Consumer GenerateConsumer(ExclusiveGroupStruct group, string name, uint capacity) + { + var consumer = new Consumer(group, name, capacity); + + _consumers.Add(consumer); + + return consumer; + } + } +} \ No newline at end of file diff --git a/EntityStream.cs.meta b/Streams/EntityStream.cs.meta similarity index 83% rename from EntityStream.cs.meta rename to Streams/EntityStream.cs.meta index 673342f..d97f58d 100644 --- a/EntityStream.cs.meta +++ b/Streams/EntityStream.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 8b089cb2ab09338a83274f6d98d8a358 +guid: b6c9debed17b35ad97a6d6fd05c1e674 MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/GenericentityStreamConsumerFactory.cs b/Streams/GenericentityStreamConsumerFactory.cs similarity index 68% rename from GenericentityStreamConsumerFactory.cs rename to Streams/GenericentityStreamConsumerFactory.cs index 4fc0269..2e893c2 100644 --- a/GenericentityStreamConsumerFactory.cs +++ b/Streams/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(ExclusiveGroupStruct @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(ExclusiveGroupStruct @group, string name, uint capacity) + where T : unmanaged, IEntityComponent; } } \ No newline at end of file diff --git a/GenericentityStreamConsumerFactory.cs.meta b/Streams/GenericentityStreamConsumerFactory.cs.meta similarity index 83% rename from GenericentityStreamConsumerFactory.cs.meta rename to Streams/GenericentityStreamConsumerFactory.cs.meta index 53681eb..4f07489 100644 --- a/GenericentityStreamConsumerFactory.cs.meta +++ b/Streams/GenericentityStreamConsumerFactory.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: ad39a190a43335538afbcd55e2615fd3 +guid: e59b74ad5c7433aabb1af5306b8990c5 MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/Streams/ThreadSafeNativeEntityStream.cs b/Streams/ThreadSafeNativeEntityStream.cs new file mode 100644 index 0000000..a648384 --- /dev/null +++ b/Streams/ThreadSafeNativeEntityStream.cs @@ -0,0 +1,31 @@ +namespace Svelto.ECS +{ + /// + /// This EntityStream can be used in parallel jobs, but does NOT guarantee order. + /// + /// + public struct ThreadSafeNativeEntityStream : ITypeSafeStream + { + public ThreadSafeNativeEntityStream(EntitiesDB entitiesDB) + { + } + + public void Dispose() + { + + } + + /// + /// I am thinking to pass the component and do the queryEntity only as a validation + /// + /// + /// + public void PublishEntityChange(in T entityComponent, EGID id) + { +#if DEBUG && !PROFILE_SVELTO + +#endif + + } + } +} \ No newline at end of file diff --git a/Streams/ThreadSafeNativeEntityStream.cs.meta b/Streams/ThreadSafeNativeEntityStream.cs.meta new file mode 100644 index 0000000..bba3586 --- /dev/null +++ b/Streams/ThreadSafeNativeEntityStream.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 88e3ed2a84aa3618b082b42ef9c1c582 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Svelto.ECS.asmdef b/Svelto.ECS.asmdef index d561d80..6ccb718 100644 --- a/Svelto.ECS.asmdef +++ b/Svelto.ECS.asmdef @@ -1,15 +1,57 @@ { "name": "Svelto.ECS", "references": [ + "Unity.Entities", + "Unity.Collections", + "Unity.Burst", + "Unity.Jobs", "Svelto.Common" ], - "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" + }, + { + "name": "com.unity.burst", + "expression": "", + "define": "UNITY_BURST" + }, + { + "name": "com.unity.collections", + "expression": "", + "define": "UNITY_COLLECTIONS" + }, + { + "name": "com.unity.entities", + "expression": "", + "define": "UNITY_NATIVE" + }, + { + "name": "com.unity.burst", + "expression": "", + "define": "UNITY_NATIVE" + }, + { + "name": "com.unity.collections", + "expression": "", + "define": "UNITY_NATIVE" + }, + { + "name": "com.unity.jobs", + "expression": "", + "define": "UNITY_JOBS" + } + ], + "noEngineReferences": false } \ No newline at end of file diff --git a/Svelto.ECS.csproj b/Svelto.ECS.csproj index 9fdf31b..1e77aca 100644 --- a/Svelto.ECS.csproj +++ b/Svelto.ECS.csproj @@ -15,6 +15,7 @@ true + diff --git a/TypeCache.cs b/TypeCache.cs deleted file mode 100644 index bd10908..0000000 --- a/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/TypeCache.cs.meta b/TypeCache.cs.meta deleted file mode 100644 index af59d8a..0000000 --- a/TypeCache.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 1186f2e0b3013993b3c0dae74b9524f1 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/TypeSafeDictionaryFactory.cs b/TypeSafeDictionaryFactory.cs new file mode 100644 index 0000000..7b2c550 --- /dev/null +++ b/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(1); + } + + public static ITypeSafeDictionary Create(uint size) + { + return new TypeSafeDictionary(size); + } + } +} \ No newline at end of file diff --git a/TypeSafeDictionaryFactory.cs.meta b/TypeSafeDictionaryFactory.cs.meta new file mode 100644 index 0000000..b0cf072 --- /dev/null +++ b/TypeSafeDictionaryFactory.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 04c8b41c23ff37e8a4c2d032f5aaf21e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnsortedEnginesGroup.cs b/UnsortedEnginesGroup.cs new file mode 100644 index 0000000..2362f80 --- /dev/null +++ b/UnsortedEnginesGroup.cs @@ -0,0 +1,33 @@ +using Svelto.Common; +using Svelto.DataStructures; + +namespace Svelto.ECS +{ + public abstract class UnsortedEnginesGroup : IStepGroupEngine + where Interface : IStepEngine + { + protected UnsortedEnginesGroup(FasterList engines) + { + _name = "UnsortedEnginesGroup - "+this.GetType().Name; + _instancedSequence = engines; + } + + public void Step() + { + var sequenceItems = _instancedSequence; + using (var profiler = new PlatformProfiler(_name)) + { + for (var index = 0; index < sequenceItems.count; index++) + { + var engine = sequenceItems[index]; + using (profiler.Sample(engine.name)) engine.Step(); + } + } + } + + public string name => _name; + + readonly string _name; + readonly FasterList _instancedSequence; + } +} \ No newline at end of file diff --git a/UnsortedEnginesGroup.cs.meta b/UnsortedEnginesGroup.cs.meta new file mode 100644 index 0000000..9142963 --- /dev/null +++ b/UnsortedEnginesGroup.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 885e43f2997833bcacd6681fd4744a33 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/WaitForSubmissionEnumerator.cs b/WaitForSubmissionEnumerator.cs index d5a1732..acf4816 100644 --- a/WaitForSubmissionEnumerator.cs +++ b/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/package.json b/package.json index a81cb3c..edb1753 100644 --- a/package.json +++ b/package.json @@ -3,13 +3,12 @@ "category": "Svelto", "description": "Svelto ECS C# Lightweight Data Oriented Entity Component System Framework", "dependencies": { - "com.sebaslab.svelto.common": "2.9.12" + "com.sebaslab.svelto.common": "3.0.0" }, "keywords": [ "svelto" ], "name": "com.sebaslab.svelto.ecs", - "unity": "2019.2", - "version": "2.9.12", + "version": "3.0.0", "type": "framework" } \ No newline at end of file