Browse Source

Update Svelto.ECS to 3.2

pull/70/head
sebas77 3 years ago
parent
commit
8a651bae0a
100 changed files with 3375 additions and 2215 deletions
  1. +3
    -0
      .gitignore
  2. +1
    -1
      Svelto.Common
  3. +21
    -3
      Svelto.ECS/CHANGELOG.md
  4. +11
    -0
      Svelto.ECS/Components/EntityReferenceComponent.cs
  5. +39
    -0
      Svelto.ECS/Core/AssemblyUtility.cs
  6. +10
    -9
      Svelto.ECS/Core/CheckEntityUtilities.cs
  7. +19
    -25
      Svelto.ECS/Core/ComponentBuilder.CheckFields.cs
  8. +77
    -73
      Svelto.ECS/Core/ComponentBuilder.cs
  9. +14
    -8
      Svelto.ECS/Core/EGID.cs
  10. +0
    -2
      Svelto.ECS/Core/EGIDMapper.cs
  11. +1
    -1
      Svelto.ECS/Core/EnginesGroup/IStepEngine.cs
  12. +45
    -0
      Svelto.ECS/Core/EnginesGroup/SortedEnginesGroup.cs
  13. +34
    -0
      Svelto.ECS/Core/EnginesGroup/UnsortedEnginesGroup.cs
  14. +51
    -9
      Svelto.ECS/Core/EnginesRoot.DoubleBufferedEntitiesToAdd.cs
  15. +171
    -102
      Svelto.ECS/Core/EnginesRoot.Engines.cs
  16. +98
    -79
      Svelto.ECS/Core/EnginesRoot.Entities.cs
  17. +12
    -10
      Svelto.ECS/Core/EnginesRoot.GenericEntityFactory.cs
  18. +14
    -16
      Svelto.ECS/Core/EnginesRoot.GenericEntityFunctions.cs
  19. +117
    -135
      Svelto.ECS/Core/EnginesRoot.Submission.cs
  20. +142
    -73
      Svelto.ECS/Core/EntitiesDB.FindGroups.cs
  21. +75
    -84
      Svelto.ECS/Core/EntitiesDB.cs
  22. +1
    -1
      Svelto.ECS/Core/EntityCollection.cs
  23. +118
    -57
      Svelto.ECS/Core/EntityDescriptor/DynamicEntityDescriptor.cs
  24. +19
    -5
      Svelto.ECS/Core/EntityDescriptor/ExtendibleEntityDescriptor.cs
  25. +3
    -3
      Svelto.ECS/Core/EntityDescriptor/GenericEntityDescriptor.cs
  26. +1
    -1
      Svelto.ECS/Core/EntityDescriptorTemplate.cs
  27. +39
    -31
      Svelto.ECS/Core/EntityFactory.cs
  28. +21
    -16
      Svelto.ECS/Core/EntityInitializer.cs
  29. +227
    -0
      Svelto.ECS/Core/EntityReference/EnginesRoot.LocatorMap.cs
  30. +32
    -0
      Svelto.ECS/Core/EntityReference/EntitiesDB.References.cs
  31. +78
    -0
      Svelto.ECS/Core/EntityReference/EntityReference.cs
  32. +20
    -0
      Svelto.ECS/Core/EntityReference/EntityReferenceMapElement.cs
  33. +0
    -2
      Svelto.ECS/Core/EntitySubmissionScheduler.cs
  34. +27
    -31
      Svelto.ECS/Core/EntityViewUtility.cs
  35. +31
    -32
      Svelto.ECS/Core/Filters/EntitiesDB.GroupFilters.cs
  36. +43
    -25
      Svelto.ECS/Core/Filters/FilterGroup.cs
  37. +2
    -2
      Svelto.ECS/Core/Filters/GroupFilters.cs
  38. +7
    -2
      Svelto.ECS/Core/GlobalTypeID.cs
  39. +115
    -0
      Svelto.ECS/Core/GroupHashMap.cs
  40. +26
    -0
      Svelto.ECS/Core/GroupNamesMap.cs
  41. +12
    -2
      Svelto.ECS/Core/Groups/ExclusiveBuildGroup.cs
  42. +25
    -90
      Svelto.ECS/Core/Groups/ExclusiveGroup.cs
  43. +15
    -0
      Svelto.ECS/Core/Groups/ExclusiveGroupBitmask.cs
  44. +51
    -32
      Svelto.ECS/Core/Groups/ExclusiveGroupStruct.cs
  45. +284
    -215
      Svelto.ECS/Core/Groups/GroupCompound.cs
  46. +2
    -1
      Svelto.ECS/Core/Groups/NamedExclusiveGroup.cs
  47. +1
    -1
      Svelto.ECS/Core/Hybrid/IEntityDescriptorHolder.cs
  48. +27
    -3
      Svelto.ECS/Core/Hybrid/ValueReference.cs
  49. +5
    -5
      Svelto.ECS/Core/IComponentBuilder.cs
  50. +7
    -11
      Svelto.ECS/Core/IEntityFactory.cs
  51. +5
    -7
      Svelto.ECS/Core/IEntityFunctions.cs
  52. +3
    -1
      Svelto.ECS/Core/INeedEGID.cs
  53. +15
    -0
      Svelto.ECS/Core/INeedEntityReference.cs
  54. +137
    -86
      Svelto.ECS/Core/QueryGroups.cs
  55. +16
    -0
      Svelto.ECS/Core/ReactEngineContainer.cs
  56. +31
    -1
      Svelto.ECS/Core/SetEGIDWithoutBoxing.cs
  57. +29
    -27
      Svelto.ECS/Core/SimpleEntitiesSubmissionScheduler.cs
  58. +33
    -11
      Svelto.ECS/Core/Streams/Consumer.cs
  59. +5
    -1
      Svelto.ECS/Core/Streams/EnginesRoot.Streams.cs
  60. +4
    -6
      Svelto.ECS/Core/Streams/EntitiesDB.Streams.cs
  61. +3
    -13
      Svelto.ECS/Core/Streams/EntitiesStreams.cs
  62. +3
    -3
      Svelto.ECS/Core/Streams/EntityStream.cs
  63. +3
    -0
      Svelto.ECS/Core/Streams/GenericentityStreamConsumerFactory.cs
  64. +0
    -293
      Svelto.ECS/DataStructures/FastTypeSafeDictionary.cs
  65. +4
    -4
      Svelto.ECS/DataStructures/ITypeSafeDictionary.cs
  66. +123
    -112
      Svelto.ECS/DataStructures/TypeSafeDictionary.cs
  67. +13
    -6
      Svelto.ECS/DataStructures/Unmanaged/AtomicNativeBags.cs
  68. +5
    -4
      Svelto.ECS/DataStructures/Unmanaged/NativeBag.cs
  69. +37
    -18
      Svelto.ECS/DataStructures/Unmanaged/NativeDynamicArray.cs
  70. +17
    -0
      Svelto.ECS/DataStructures/Unmanaged/NativeDynamicArrayCast.cs
  71. +1
    -1
      Svelto.ECS/DataStructures/Unmanaged/NativeDynamicArrayUnityExtension.cs
  72. +29
    -16
      Svelto.ECS/DataStructures/Unmanaged/SharedNativeInt.cs
  73. +1
    -1
      Svelto.ECS/DataStructures/Unmanaged/ThreadSafeNativeBag.cs
  74. +14
    -8
      Svelto.ECS/DataStructures/Unmanaged/UnsafeArray.cs
  75. +0
    -70
      Svelto.ECS/Debugger/ExclusiveGroupDebugger.cs
  76. +0
    -25
      Svelto.ECS/Dispatcher/DispatchOnChange.cs
  77. +0
    -48
      Svelto.ECS/Dispatcher/DispatchOnSet.cs
  78. +97
    -0
      Svelto.ECS/Dispatcher/ReactiveValue.cs
  79. +7
    -18
      Svelto.ECS/ECSResources/ECSResources.cs
  80. +3
    -7
      Svelto.ECS/ECSResources/ECSString.cs
  81. +24
    -0
      Svelto.ECS/Extensions/DisposeDisposablesEngine.cs
  82. +6
    -3
      Svelto.ECS/Extensions/Svelto/AllGroupsEnumerable.cs
  83. +149
    -0
      Svelto.ECS/Extensions/Svelto/EGIDMultiMapper.cs
  84. +16
    -0
      Svelto.ECS/Extensions/Svelto/EntitiesDBFiltersExtension.cs
  85. +35
    -16
      Svelto.ECS/Extensions/Svelto/EntityManagedDBExtensions.cs
  86. +137
    -108
      Svelto.ECS/Extensions/Svelto/EntityNativeDBExtensions.cs
  87. +19
    -0
      Svelto.ECS/Extensions/Svelto/FilterGroupExtensions.cs
  88. +27
    -21
      Svelto.ECS/Extensions/Svelto/GroupsEnumerable.cs
  89. +3
    -0
      Svelto.ECS/Extensions/Unity/DOTS/Jobs/IJobifiedEngine.cs
  90. +1
    -1
      Svelto.ECS/Extensions/Unity/DOTS/Jobs/SortedJobifiedEnginesGroup.cs
  91. +32
    -6
      Svelto.ECS/Extensions/Unity/DOTS/Jobs/UnityJobExtensions.cs
  92. +6
    -7
      Svelto.ECS/Extensions/Unity/DOTS/Jobs/UnsortedJobifiedEnginesGroup.cs
  93. +25
    -16
      Svelto.ECS/Extensions/Unity/DOTS/Native/EnginesRoot.NativeOperation.cs
  94. +6
    -2
      Svelto.ECS/Extensions/Unity/DOTS/Native/NativeEGIDMapper.cs
  95. +24
    -23
      Svelto.ECS/Extensions/Unity/DOTS/Native/NativeEGIDMultiMapper.cs
  96. +16
    -8
      Svelto.ECS/Extensions/Unity/DOTS/Native/NativeEntityFactory.cs
  97. +6
    -2
      Svelto.ECS/Extensions/Unity/DOTS/Native/NativeEntityInitializer.cs
  98. +1
    -1
      Svelto.ECS/Extensions/Unity/DOTS/Native/NativeEntityRemove.cs
  99. +1
    -1
      Svelto.ECS/Extensions/Unity/DOTS/Native/NativeEntitySwap.cs
  100. +9
    -15
      Svelto.ECS/Extensions/Unity/DOTS/Native/UnityNativeEntityDBExtensions.cs

+ 3
- 0
.gitignore View File

@@ -2,3 +2,6 @@
/obj
/bin/Release/netstandard2.0
/bin/Debug/netstandard2.0
Svelto.ECS/Svelto.ECS.sdpkg.user
Svelto.ECS/obj/
Svelto.ECS/.idea/

+ 1
- 1
Svelto.Common

@@ -1 +1 @@
Subproject commit 6b254fb2d729d6bbb806b006e17573c9c81c27c5
Subproject commit 9b2780ebed4358bce64def40ecf42e7274f31b2a

+ 21
- 3
Svelto.ECS/CHANGELOG.md View File

@@ -1,5 +1,21 @@
# Changelog
All notable changes to this project will be documented in this file. I created this file with Svelto.ECS version 3.1.
All notable changes to this project will be documented in this file. Changes are listed in random order of importance.

## [3.2.0]

* Improved checks on Svelto rules for the declaration of components and view components. This set of rules is not final yet (ideally one day they should be moved to static analyzers)
* Introduce the concept of Entity Reference. It's a very light weight identifier to keep track of entities EGID that can change dynamically (EGIDs change when groups are swapped), Entity References never change. The underlying code will be optimised even further in future.
* Introduced the concept of Disabled Group. Once a group is marked as disabled, queries will always ignore it.
* Merged DispatchOnSet and DispatchOnChange and renamed to ReactiveValue. This class will be superseded by better patterns in future.
* Added FindGroups with 4 components
* Improved QueryGroups interface
* Improved DynamicEntityDescriptor interface
* Improved ExtendibleEntityDescriptor interface
* Improved Native memory support
* Improved Svelto and Unity DOTS integration
* Improved and fixed Serialization code
* Ensure that the creation of static groups is deterministic (GroupHashMap)


## [3.1.3]

@@ -25,14 +41,16 @@ All notable changes to this project will be documented in this file. I created t
### Changed

* rearrange folders structures for clarity
* added DoubleEntitiesEnumerator, as seen in MiniExample 4, to allow a double iteration of the same group skipping already checked tuples
* added DoubleEntitiesEnumerator, as seen in MiniExample 4, to allow a double iteration of the same group skipping
already checked tuples
* reengineered the behaviour of WaitForSubmissionEnumerator
* removed redudant SimpleEntitySubmissionSchedulerInterface interface
* renamed BuildGroup in to ExclusiveBuildGroup
* renamed EntityComponentInitializer to EntityInitializer
* Entity Submission now can optionally be time sliced (based on number of entities to submit per slice)
* working on the Unity extension Submission Engine, still WIP
* added the possibility to hold a reference in a EntityViewComponent. This reference cannot be accesses as an object, but can be converted to the original object in OOP abstract layers
* added the possibility to hold a reference in a EntityViewComponent. This reference cannot be accesses as an object,
but can be converted to the original object in OOP abstract layers
* renamed NativeEntityComponentInitializer to NativeEntityInitializer

### Fixed


+ 11
- 0
Svelto.ECS/Components/EntityReferenceComponent.cs View File

@@ -0,0 +1,11 @@
using Svelto.ECS.Reference;

namespace Svelto.ECS
{
//To do: this should be removed and the only reason it exists is to solve some edge case scenarios with
//the publish/consumer pattern
public struct EntityReferenceComponent:IEntityComponent, INeedEntityReference
{
public EntityReference selfReference { get; set; }
}
}

+ 39
- 0
Svelto.ECS/Core/AssemblyUtility.cs View File

@@ -0,0 +1,39 @@
using System;
using System.Collections.Generic;
using System.Reflection;

public static class AssemblyUtility
{
static readonly List<Assembly> AssemblyList = new List<Assembly>();
static AssemblyUtility()
{
var assemblyName = Assembly.GetExecutingAssembly().GetName();
Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();

foreach (Assembly assembly in assemblies)
{
AssemblyName[] referencedAssemblies = assembly.GetReferencedAssemblies();
if (Array.Exists(referencedAssemblies, (a) => a.Name == assemblyName.Name))
{
AssemblyList.Add(assembly);
}
}
}

public static IEnumerable<Type> GetTypesSafe(Assembly assembly)
{
try
{
Type[] types = assembly.GetTypes();

return types;
}
catch (ReflectionTypeLoadException e)
{
return e.Types;
}
}

public static List<Assembly> GetCompatibleAssemblies() { return AssemblyList; }
}

+ 10
- 9
Svelto.ECS/Core/CheckEntityUtilities.cs View File

@@ -4,6 +4,7 @@ using System.Diagnostics;
#endif
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using Svelto.DataStructures;

namespace Svelto.ECS
@@ -17,18 +18,18 @@ namespace Svelto.ECS
#if DONT_USE
[Conditional("CHECK_ALL")]
#endif
void CheckRemoveEntityID(EGID egid, Type entityDescriptorType, string caller = "")
void CheckRemoveEntityID(EGID egid, Type entityDescriptorType, [CallerMemberName] string caller = null)
{
if (_multipleOperationOnSameEGIDChecker.ContainsKey(egid) == true)
throw new ECSException(
"Executing multiple structural changes in one submission on the same entity is not supported "
"Executing multiple structural changes (remove) in one submission on the same entity is not supported "
.FastConcat(" caller: ", caller, " ").FastConcat(egid.entityID).FastConcat(" groupid: ")
.FastConcat(egid.groupID.ToName()).FastConcat(" type: ")
.FastConcat(entityDescriptorType != null ? entityDescriptorType.Name : "not available")
.FastConcat(" operation was: ")
.FastConcat(" previous operation was: ")
.FastConcat(_multipleOperationOnSameEGIDChecker[egid] == 1 ? "add" : "remove"));

if (_idChecker.TryGetValue(egid.groupID, out var hash))
if (_idChecker.TryGetValue((uint) egid.groupID, out var hash))
if (hash.Contains(egid.entityID) == false)
throw new ECSException("Trying to remove an Entity never submitted in the database "
.FastConcat(" caller: ", caller, " ").FastConcat(egid.entityID)
@@ -45,18 +46,18 @@ namespace Svelto.ECS
#if DONT_USE
[Conditional("CHECK_ALL")]
#endif
void CheckAddEntityID(EGID egid, Type entityDescriptorType, string caller = "")
void CheckAddEntityID(EGID egid, Type entityDescriptorType, [CallerMemberName] string caller = null)
{
if (_multipleOperationOnSameEGIDChecker.ContainsKey(egid) == true)
throw new ECSException(
"Executing multiple structural changes in one submission on the same entity is not supported "
"Executing multiple structural changes (build) on the same entity is not supported "
.FastConcat(" caller: ", caller, " ").FastConcat(egid.entityID).FastConcat(" groupid: ")
.FastConcat(egid.groupID.ToName()).FastConcat(" type: ")
.FastConcat(entityDescriptorType != null ? entityDescriptorType.Name : "not available")
.FastConcat(" operation was: ")
.FastConcat(" previous operation was: ")
.FastConcat(_multipleOperationOnSameEGIDChecker[egid] == 1 ? "add" : "remove"));

var hash = _idChecker.GetOrCreate(egid.groupID, () => new HashSet<uint>());
var hash = _idChecker.GetOrCreate((uint) egid.groupID, () => new HashSet<uint>());
if (hash.Contains(egid.entityID) == true)
throw new ECSException("Trying to add an Entity already submitted to the database "
.FastConcat(" caller: ", caller, " ").FastConcat(egid.entityID)
@@ -72,7 +73,7 @@ namespace Svelto.ECS
#if DONT_USE
[Conditional("CHECK_ALL")]
#endif
void RemoveGroupID(ExclusiveBuildGroup groupID) { _idChecker.Remove(groupID); }
void RemoveGroupID(ExclusiveBuildGroup groupID) { _idChecker.Remove((uint)groupID); }

#if DONT_USE
[Conditional("CHECK_ALL")]


+ 19
- 25
Svelto.ECS/Core/ComponentBuilder.CheckFields.cs View File

@@ -4,7 +4,6 @@ using System.Diagnostics;
#endif
using System;
using System.Reflection;
using Svelto.Common;

namespace Svelto.ECS
{
@@ -46,45 +45,41 @@ namespace Svelto.ECS

if (fields.Length < 1)
{
ProcessError("No valid fields found in Entity View Components", entityComponentType);
ProcessError("Entity View Components must have at least one interface declared as public field and not property", entityComponentType);
}

for (int i = fields.Length - 1; i >= 0; --i)
{
FieldInfo fieldInfo = fields[i];

if (fieldInfo.FieldType.IsInterfaceEx() == true)
if (fieldInfo.FieldType.IsInterfaceEx() == false)
{
PropertyInfo[] properties = fieldInfo.FieldType.GetProperties(
BindingFlags.Public | BindingFlags.Instance
| BindingFlags.DeclaredOnly);
ProcessError("Entity View Components must hold only entity components interfaces."
, entityComponentType);
}

PropertyInfo[] properties = fieldInfo.FieldType.GetProperties(
BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly);

for (int j = properties.Length - 1; j >= 0; --j)
for (int j = properties.Length - 1; j >= 0; --j)
{
if (properties[j].PropertyType.IsGenericType)
{
if (properties[j].PropertyType.IsGenericType)
Type genericTypeDefinition = properties[j].PropertyType.GetGenericTypeDefinition();
if (genericTypeDefinition == RECATIVEVALUETYPE)
{
Type genericTypeDefinition = properties[j].PropertyType.GetGenericTypeDefinition();
if (genericTypeDefinition == DISPATCHONSETTYPE
|| genericTypeDefinition == DISPATCHONCHANGETYPE)
{
continue;
}
continue;
}
}

Type propertyType = properties[j].PropertyType;

Type propertyType = properties[j].PropertyType;
if (propertyType != STRINGTYPE)
{
//for EntityComponentStructs, component fields that are structs that hold strings
//are allowed
SubCheckFields(propertyType, entityComponentType, isStringAllowed: true);
}
}
else
if (fieldInfo.FieldType.IsUnmanagedEx() == false)
{
ProcessError("Entity View Components must hold only public interfaces, strings or unmanaged type fields.",
entityComponentType);

}
}
}
}
@@ -129,8 +124,7 @@ namespace Svelto.ECS
throw new ECSException(message, entityComponentType);
}

static readonly Type DISPATCHONCHANGETYPE = typeof(DispatchOnChange<>);
static readonly Type DISPATCHONSETTYPE = typeof(DispatchOnSet<>);
static readonly Type RECATIVEVALUETYPE = typeof(ReactiveValue<>);
static readonly Type EGIDType = typeof(EGID);
static readonly Type EXCLUSIVEGROUPSTRUCTTYPE = typeof(ExclusiveGroupStruct);
static readonly Type SERIALIZABLE_ENTITY_STRUCT = typeof(SerializableEntityComponent);


+ 77
- 73
Svelto.ECS/Core/ComponentBuilder.cs View File

@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using DBC.ECS;
using Svelto.Common;
using Svelto.DataStructures;
using Svelto.ECS.Hybrid;
@@ -9,110 +10,110 @@ using Svelto.Utilities;

namespace Svelto.ECS
{
public class ComponentBuilder<T> : IComponentBuilder where T : struct, IEntityComponent
struct ComponentBuilderComparer : IEqualityComparer<IComponentBuilder>
{
public ComponentBuilder()
public bool Equals(IComponentBuilder x, IComponentBuilder y)
{
_initializer = DEFAULT_IT;
return x.GetEntityComponentType() == y.GetEntityComponentType();
}

public ComponentBuilder(in T initializer) : this()
public int GetHashCode(IComponentBuilder obj)
{
_initializer = initializer;
return obj.GetEntityComponentType().GetHashCode();
}
}
public class ComponentBuilder<T> : IComponentBuilder
where T : struct, IEntityComponent
{
internal static readonly Type ENTITY_COMPONENT_TYPE;
public static readonly bool HAS_EGID;
internal static readonly bool IS_ENTITY_VIEW_COMPONENT;

public bool isUnmanaged => IS_UNMANAGED;
static readonly T DEFAULT_IT;
static readonly string ENTITY_COMPONENT_NAME;
static readonly bool IS_UNMANAGED;
public static bool HAS_REFERENCE;

public void BuildEntityAndAddToList(ref ITypeSafeDictionary dictionary, EGID egid,
IEnumerable<object> implementors)
static ComponentBuilder()
{
if (dictionary == null)
dictionary = TypeSafeDictionaryFactory<T>.Create();
ENTITY_COMPONENT_TYPE = typeof(T);
DEFAULT_IT = default;
IS_ENTITY_VIEW_COMPONENT = typeof(IEntityViewComponent).IsAssignableFrom(ENTITY_COMPONENT_TYPE);
HAS_EGID = typeof(INeedEGID).IsAssignableFrom(ENTITY_COMPONENT_TYPE);
HAS_REFERENCE = typeof(INeedEntityReference).IsAssignableFrom(ENTITY_COMPONENT_TYPE);
ENTITY_COMPONENT_NAME = ENTITY_COMPONENT_TYPE.ToString();
IS_UNMANAGED = ENTITY_COMPONENT_TYPE.IsUnmanagedEx();

var castedDic = dictionary as ITypeSafeDictionary<T>;
if (IS_UNMANAGED)
EntityComponentIDMap.Register<T>(new Filler<T>());

T entityComponent = default;
if (IS_ENTITY_VIEW_COMPONENT)
{
DBC.ECS.Check.Require(castedDic.ContainsKey(egid.entityID) == false,
$"building an entity with already used entity id! id: '{(ulong) egid}', {ENTITY_COMPONENT_NAME}");
SetEGIDWithoutBoxing<T>.Warmup();

this.FillEntityComponent(ref entityComponent, EntityViewComponentCache.cachedFields, implementors,
EntityViewComponentCache.implementorsByType, EntityViewComponentCache.cachedTypes);
ComponentBuilderUtilities.CheckFields(ENTITY_COMPONENT_TYPE, IS_ENTITY_VIEW_COMPONENT);

castedDic.Add(egid.entityID, entityComponent);
if (IS_ENTITY_VIEW_COMPONENT)
{
EntityViewComponentCache.InitCache();
}
else
{
DBC.ECS.Check.Require(!castedDic.ContainsKey(egid.entityID),
$"building an entity with already used entity id! id: '{egid.entityID}'");
castedDic.Add(egid.entityID, _initializer);
if (ENTITY_COMPONENT_TYPE != ComponentBuilderUtilities.ENTITY_INFO_COMPONENT
&& ENTITY_COMPONENT_TYPE.IsUnmanagedEx() == false)
throw new Exception(
$"Entity Component check failed, unexpected struct type (must be unmanaged) {ENTITY_COMPONENT_TYPE}");
}
}

ITypeSafeDictionary IComponentBuilder.Preallocate(ref ITypeSafeDictionary dictionary, uint size)
{
return Preallocate(ref dictionary, size);
}
public ComponentBuilder() { _initializer = DEFAULT_IT; }

static ITypeSafeDictionary Preallocate(ref ITypeSafeDictionary dictionary, uint size)
{
if (dictionary == null)
dictionary = TypeSafeDictionaryFactory<T>.Create(size);
else
dictionary.SetCapacity(size);

return dictionary;
}
public ComponentBuilder(in T initializer) : this() { _initializer = initializer; }

public Type GetEntityComponentType()
{
return ENTITY_COMPONENT_TYPE;
}
public bool isUnmanaged => IS_UNMANAGED;

static ComponentBuilder()
public void BuildEntityAndAddToList(ITypeSafeDictionary dictionary, EGID egid, IEnumerable<object> implementors)
{
ENTITY_COMPONENT_TYPE = typeof(T);
DEFAULT_IT = default;
IS_ENTITY_VIEW_COMPONENT = typeof(IEntityViewComponent).IsAssignableFrom(ENTITY_COMPONENT_TYPE);
HAS_EGID = typeof(INeedEGID).IsAssignableFrom(ENTITY_COMPONENT_TYPE);
ENTITY_COMPONENT_NAME = ENTITY_COMPONENT_TYPE.ToString();
IS_UNMANAGED = ENTITY_COMPONENT_TYPE.IsUnmanagedEx();
var castedDic = dictionary as ITypeSafeDictionary<T>;

if (IS_UNMANAGED)
EntityComponentIDMap.Register<T>(new Filler<T>());
SetEGIDWithoutBoxing<T>.Warmup();
ComponentBuilderUtilities.CheckFields(ENTITY_COMPONENT_TYPE, IS_ENTITY_VIEW_COMPONENT);
T entityComponent = default;

if (IS_ENTITY_VIEW_COMPONENT)
EntityViewComponentCache.InitCache();
{
Check.Require(castedDic.ContainsKey(egid.entityID) == false
, $"building an entity with already used entity id! id: '{(ulong) egid}', {ENTITY_COMPONENT_NAME}");

this.SetEntityViewComponentImplementors(ref entityComponent, EntityViewComponentCache.cachedFields
, implementors, EntityViewComponentCache.implementorsByType
, EntityViewComponentCache.cachedTypes);

castedDic.Add(egid.entityID, entityComponent);
}
else
{
if (ENTITY_COMPONENT_TYPE != ComponentBuilderUtilities.ENTITY_INFO_COMPONENT && ENTITY_COMPONENT_TYPE.IsUnmanagedEx() == false)
throw new Exception($"Entity Component check failed, unexpected struct type (must be unmanaged) {ENTITY_COMPONENT_TYPE}");
Check.Require(!castedDic.ContainsKey(egid.entityID)
, $"building an entity with already used entity id! id: '{egid.entityID}'");

castedDic.Add(egid.entityID, _initializer);
}
}

void IComponentBuilder.Preallocate(ITypeSafeDictionary dictionary, uint size) { Preallocate(dictionary, size); }

readonly T _initializer;
public ITypeSafeDictionary CreateDictionary(uint size) { return TypeSafeDictionaryFactory<T>.Create(size); }

internal static readonly Type ENTITY_COMPONENT_TYPE;
public static readonly bool HAS_EGID;
internal static readonly bool IS_ENTITY_VIEW_COMPONENT;
public Type GetEntityComponentType() { return ENTITY_COMPONENT_TYPE; }

static readonly T DEFAULT_IT;
static readonly string ENTITY_COMPONENT_NAME;
static bool IS_UNMANAGED;
public override int GetHashCode() { return _initializer.GetHashCode(); }

static void Preallocate(ITypeSafeDictionary dictionary, uint size) { dictionary.SetCapacity(size); }

readonly T _initializer;

/// <summary>
/// Note: this static class will hold forever the references of the entities implementors. These references
/// are not even cleared when the engines root is destroyed, as they are static references.
/// It must be seen as an application-wide cache system. Honestly, I am not sure if this may cause leaking
/// issues in some kind of applications. To remember.
/// Note: this static class will hold forever the references of the entities implementors. These references
/// are not even cleared when the engines root is destroyed, as they are static references.
/// It must be seen as an application-wide cache system. Honestly, I am not sure if this may cause leaking
/// issues in some kind of applications. To remember.
/// </summary>
static class EntityViewComponentCache
{
@@ -127,12 +128,12 @@ namespace Svelto.ECS
{
cachedFields = new FasterList<KeyValuePair<Type, FastInvokeActionCast<T>>>();

var type = typeof(T);
var type = typeof(T);
var fields = type.GetFields(BindingFlags.Public | BindingFlags.Instance);
for (var i = fields.Length - 1; i >= 0; --i)
{
var field = fields[i];
var field = fields[i];
if (field.FieldType.IsInterface == true)
{
var setter = FastInvoke<T>.MakeSetter(field);
@@ -141,6 +142,10 @@ namespace Svelto.ECS
cachedFields.Add(new KeyValuePair<Type, FastInvokeActionCast<T>>(field.FieldType, setter));
}
}
#if DEBUG && !PROFILE_SVELTO
if (fields.Length == 0)
Console.LogWarning($"No fields found in component {type}. Are you declaring only properties?");
#endif

cachedTypes = new Dictionary<Type, Type[]>();

@@ -151,8 +156,7 @@ namespace Svelto.ECS
#endif
}

internal static void InitCache()
{}
internal static void InitCache() { }
}
}
}

+ 14
- 8
Svelto.ECS/Core/EGID.cs View File

@@ -1,4 +1,5 @@
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

#pragma warning disable 660,661
@@ -14,8 +15,6 @@ namespace Svelto.ECS
[FieldOffset(4)] public readonly ExclusiveGroupStruct groupID;
[FieldOffset(0)] readonly ulong _GID;

public static readonly EGID Empty = new EGID();
public static bool operator ==(EGID obj1, EGID obj2)
{
return obj1._GID == obj2._GID;
@@ -28,14 +27,14 @@ namespace Svelto.ECS

public EGID(uint entityID, ExclusiveGroupStruct groupID) : this()
{
_GID = MAKE_GLOBAL_ID(entityID, groupID);
#if DEBUG && !PROFILE_SVELTO
if (groupID == (ExclusiveGroupStruct)default)
throw new Exception("Trying to use a not initialised group ID");
#endif
_GID = MAKE_GLOBAL_ID(entityID, (uint) groupID);
}
public EGID(uint entityID, ExclusiveBuildGroup groupID) : this()
{
_GID = MAKE_GLOBAL_ID(entityID, groupID.group);
}

static ulong MAKE_GLOBAL_ID(uint entityId, uint groupId)
{
return (ulong)groupId << 32 | ((ulong)entityId & 0xFFFFFFFF);
@@ -82,7 +81,14 @@ namespace Svelto.ECS
public override string ToString()
{
var value = groupID.ToName();
return "id ".FastConcat(entityID).FastConcat(" group ").FastConcat(value);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public EntityReference ToEntityReference(EntitiesDB entitiesDB)
{
return entitiesDB.GetEntityReference(this);
}
}
}

+ 0
- 2
Svelto.ECS/Core/EGIDMapper.cs View File

@@ -6,8 +6,6 @@ using Svelto.ECS.Internal;
namespace Svelto.ECS
{
/// <summary>
/// Note: does mono devirtualize sealed classes? If so it could be worth to use TypeSafeDictionary instead of
/// the interface
/// </summary>
/// <typeparam name="T"></typeparam>
public readonly struct EGIDMapper<T>: IEGIDMapper where T : struct, IEntityComponent


+ 1
- 1
Svelto.ECS/Core/EnginesGroup/IStepEngine.cs View File

@@ -14,7 +14,7 @@ namespace Svelto.ECS
string name { get; }
}
//this must stay IStep Engine as it may be part of a group itself
//this must stay IStepEngine as it may be part of a group itself
public interface IStepGroupEngine : IStepEngine
{
}


+ 45
- 0
Svelto.ECS/Core/EnginesGroup/SortedEnginesGroup.cs View File

@@ -3,6 +3,46 @@ using Svelto.Common;

namespace Svelto.ECS
{
/// <summary>
/// SortedEnginesGroup is a practical way to group engines that can be ticked together. The class requires a
/// SequenceOrder struct that define the order of execution. The pattern to use is the following:
/// First define as many enums as you want with the ID of the engines to use. E.G.:
/// public enum WiresCompositionEngineNames
///{
/// WiresTimeRunningGroup,
/// WiresPreInitTimeRunningGroup,
/// WiresInitTimeStoppedGroup,
/// WiresInitTimeRunningGroup
///}
///
/// then link these ID to the actual engines, using the attribute Sequenced:
///
/// [Sequenced(nameof(WiresCompositionEngineNames.WiresTimeRunningGroup))]
/// class WiresTimeRunningGroup : UnsortedDeterministicEnginesGroup<IDeterministicTimeRunning>g {}
///
/// Note that the engine can be another group itself (like in this example).
///
/// then define the ISequenceOrder struct. E.G.:
/// public struct DeterministicTimeRunningEnginesOrder: ISequenceOrder
/// {
/// private static readonly string[] order =
/// {
/// nameof(TriggerEngineNames.PreWiresTimeRunningTriggerGroup),
/// nameof(TimerEnginesNames.PreWiresTimeRunningTimerGroup),
/// nameof(WiresCompositionEngineNames.WiresTimeRunningGroup),
/// nameof(SyncGroupEnginesGroups.UnsortedDeterministicTimeRunningGroup)
/// };
/// public string[] enginesOrder => order;
/// }
///
/// Now you can use the Type you just created (i.e.: DeterministicTimeRunningEnginesOrder) as generic parameter
/// of the SortedEnginesGroup.
/// While the system may look convoluted, is an effective way to keep the engines assemblies decoupled from
/// each other
/// The class is abstract and it requires a user defined interface to push the user to use recognisable names meaningful
/// to the context where they are used.
/// </summary>
/// <typeparam name="Interface">user defined interface that implements IStepEngine</typeparam>
public abstract class SortedEnginesGroup<Interface, SequenceOrder> : IStepGroupEngine
where SequenceOrder : struct, ISequenceOrder where Interface : IStepEngine
{
@@ -31,6 +71,11 @@ namespace Svelto.ECS
readonly Sequence<Interface, SequenceOrder> _instancedSequence;
}
/// <summary>
/// Similar to SortedEnginesGroup except for the fact that an optional parameter can be passed to the engines
/// </summary>
/// <typeparam name="Interface"></typeparam>
/// <typeparam name="Parameter">Specialised Parameter that can be passed to all the engines in the group</typeparam>
public abstract class SortedEnginesGroup<Interface, Parameter, SequenceOrder>: IStepGroupEngine<Parameter>
where SequenceOrder : struct, ISequenceOrder where Interface : IStepEngine<Parameter>
{


+ 34
- 0
Svelto.ECS/Core/EnginesGroup/UnsortedEnginesGroup.cs View File

@@ -3,9 +3,22 @@ using Svelto.DataStructures;

namespace Svelto.ECS
{
/// <summary>
/// UnsortedEnginesGroup is a practical way to group engines that can be ticked together. As the name suggest
/// there is no way to defines an order, although the engines will run in the same order they are added.
/// It is abstract and it requires a user defined interface to push the user to use recognisable names meaningful
/// to the context where they are used.
/// </summary>
/// <typeparam name="Interface">user defined interface that implements IStepEngine</typeparam>
public abstract class UnsortedEnginesGroup<Interface> : IStepGroupEngine
where Interface : IStepEngine
{
protected UnsortedEnginesGroup()
{
_name = "UnsortedEnginesGroup - "+this.GetType().Name;
_instancedSequence = new FasterList<Interface>();
}
protected UnsortedEnginesGroup(FasterList<Interface> engines)
{
_name = "UnsortedEnginesGroup - "+this.GetType().Name;
@@ -24,6 +37,11 @@ namespace Svelto.ECS
}
}
}
public void Add(Interface engine)
{
_instancedSequence.Add(engine);
}

public string name => _name;
@@ -31,9 +49,20 @@ namespace Svelto.ECS
readonly FasterList<Interface> _instancedSequence;
}
/// <summary>
/// Similar to UnsortedEnginesGroup except for the fact that an optional parameter can be passed to the engines
/// </summary>
/// <typeparam name="Interface"></typeparam>
/// <typeparam name="Parameter">Specialised Parameter that can be passed to all the engines in the group</typeparam>
public abstract class UnsortedEnginesGroup<Interface, Parameter> : IStepGroupEngine<Parameter>
where Interface : IStepEngine<Parameter>
{
protected UnsortedEnginesGroup()
{
_name = "UnsortedEnginesGroup - "+this.GetType().Name;
_instancedSequence = new FasterList<Interface>();
}
protected UnsortedEnginesGroup(FasterList<Interface> engines)
{
_name = "UnsortedEnginesGroup - "+this.GetType().Name;
@@ -52,6 +81,11 @@ namespace Svelto.ECS
}
}
}
public void Add(Interface engine)
{
_instancedSequence.Add(engine);
}

public string name => _name;


+ 51
- 9
Svelto.ECS/Core/EnginesRoot.DoubleBufferedEntitiesToAdd.cs View File

@@ -42,6 +42,7 @@ namespace Svelto.ECS
}
}
//reset the number of entities created so far
otherEntitiesCreatedPerGroup.FastClear();
other.FastClear();
return;
@@ -74,23 +75,25 @@ namespace Svelto.ECS
}
}

//reset the number of entities created so far
otherEntitiesCreatedPerGroup.FastClear();
}
}

/// <summary>
/// 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
/// </summary>
internal FasterDictionary<ExclusiveGroupStruct, uint> currentEntitiesCreatedPerGroup;
internal FasterDictionary<ExclusiveGroupStruct, uint> otherEntitiesCreatedPerGroup;

//Before I tried for the third time to use a SparseSet instead of FasterDictionary, remember that
//while group indices are sequential, they may not be used in a sequential order. Sparaset needs
//while group indices are sequential, they may not be used in a sequential order. Sparseset needs
//entities to be created sequentially (the index cannot be managed externally)
internal FasterDictionary<uint, FasterDictionary<RefWrapperType, ITypeSafeDictionary>> current;
internal FasterDictionary<uint, FasterDictionary<RefWrapperType, ITypeSafeDictionary>> other;

/// <summary>
/// To avoid extra allocation, I don't clear the groups, so I need an extra data structure
/// to keep count of the number of entities built this frame. At the moment the actual number
/// of entities built is not used
/// </summary>
FasterDictionary<ExclusiveGroupStruct, uint> currentEntitiesCreatedPerGroup;
FasterDictionary<ExclusiveGroupStruct, uint> otherEntitiesCreatedPerGroup;

readonly FasterDictionary<uint, FasterDictionary<RefWrapperType, ITypeSafeDictionary>>
_entityComponentsToAddBufferA =
new FasterDictionary<uint, FasterDictionary<RefWrapperType, ITypeSafeDictionary>>();
@@ -142,6 +145,45 @@ namespace Svelto.ECS
}
}
}

internal void IncrementEntityCount(ExclusiveGroupStruct groupID)
{
currentEntitiesCreatedPerGroup.GetOrCreate(groupID)++;
}

internal bool AnyEntityCreated()
{
return currentEntitiesCreatedPerGroup.count > 0;
}

internal bool AnyOtherEntityCreated()
{
return otherEntitiesCreatedPerGroup.count > 0;
}

internal void Preallocate
(ExclusiveGroupStruct groupID, uint numberOfEntities, IComponentBuilder[] entityComponentsToBuild)
{
void PreallocateDictionaries(FasterDictionary<uint, FasterDictionary<RefWrapperType, ITypeSafeDictionary>> fasterDictionary1)
{
FasterDictionary<RefWrapperType, ITypeSafeDictionary> group =
fasterDictionary1.GetOrCreate((uint) groupID, () => new FasterDictionary<RefWrapperType, ITypeSafeDictionary>());

foreach (var componentBuilder in entityComponentsToBuild)
{
var entityComponentType = componentBuilder.GetEntityComponentType();
var safeDictionary = @group.GetOrCreate(new RefWrapperType(entityComponentType)
, () => componentBuilder.CreateDictionary(numberOfEntities));
componentBuilder.Preallocate(safeDictionary, numberOfEntities);
}
}

PreallocateDictionaries(current);
PreallocateDictionaries(other);

currentEntitiesCreatedPerGroup.GetOrCreate(groupID);
otherEntitiesCreatedPerGroup.GetOrCreate(groupID);
}
}
}
}

+ 171
- 102
Svelto.ECS/Core/EnginesRoot.Engines.cs View File

@@ -1,5 +1,4 @@
using System;
using System.Collections;
using System.Collections.Generic;
using Svelto.Common;
using Svelto.DataStructures;
@@ -10,73 +9,52 @@ namespace Svelto.ECS
{
public sealed partial class EnginesRoot
{
public struct EntitiesSubmitter
static EnginesRoot()
{
public EntitiesSubmitter(EnginesRoot enginesRoot) : this()
{
_weakReference = new Svelto.DataStructures.WeakReference<EnginesRoot>(enginesRoot);
}

public bool IsUnused => _weakReference.IsValid == false;

public IEnumerator Invoke(uint maxNumberOfOperationsPerFrame)
{
var entitiesSubmissionScheduler = _weakReference.Target._scheduler;
if (_weakReference.IsValid && entitiesSubmissionScheduler.paused == false)
{
var submitEntityComponents =
_weakReference.Target.SubmitEntityComponents(maxNumberOfOperationsPerFrame);
DBC.ECS.Check.Require(entitiesSubmissionScheduler.isRunning == false
, "A submission started while the previous one was still flushing");
entitiesSubmissionScheduler.isRunning = true;
while (submitEntityComponents.MoveNext() == true)
yield return null;

entitiesSubmissionScheduler.isRunning = false;
++entitiesSubmissionScheduler.iteration;
}
}

readonly Svelto.DataStructures.WeakReference<EnginesRoot> _weakReference;
GroupHashMap.Init();
SerializationDescriptorMap.Init();
}

readonly EntitiesSubmissionScheduler _scheduler;
public EntitiesSubmissionScheduler scheduler => _scheduler;

/// <summary>
/// Engines root contextualize your engines and entities. You don't need to limit yourself to one EngineRoot
/// as multiple engines root could promote separation of scopes. The EntitySubmissionScheduler checks
/// periodically if new entity must be submitted to the database and the engines. It's an external
/// dependencies to be independent by the running platform as the user can define it.
/// The EntitySubmissionScheduler cannot hold an EnginesRoot reference, that's why
/// it must receive a weak reference of the EnginesRoot callback.
/// Engines root contextualize your engines and entities. You don't need to limit yourself to one EngineRoot
/// as multiple engines root could promote separation of scopes. The EntitySubmissionScheduler checks
/// periodically if new entity must be submitted to the database and the engines. It's an external
/// dependencies to be independent by the running platform as the user can define it.
/// The EntitySubmissionScheduler cannot hold an EnginesRoot reference, that's why
/// it must receive a weak reference of the EnginesRoot callback.
/// </summary>
public EnginesRoot(EntitiesSubmissionScheduler entitiesComponentScheduler)
{
_entitiesOperations = new FasterDictionary<ulong, EntitySubmitOperation>();
serializationDescriptorMap = new SerializationDescriptorMap();
_reactiveEnginesAddRemove = new FasterDictionary<RefWrapperType, FasterList<IReactEngine>>();
_reactiveEnginesAddRemoveOnDispose = new FasterDictionary<RefWrapperType, FasterList<IReactEngine>>();
_reactiveEnginesSwap = new FasterDictionary<RefWrapperType, FasterList<IReactEngine>>();
_reactiveEnginesSubmission = new FasterList<IReactOnSubmission>();
_enginesSet = new FasterList<IEngine>();
_enginesTypeSet = new HashSet<Type>();
_disposableEngines = new FasterList<IDisposable>();
_transientEntitiesOperations = new FasterList<EntitySubmitOperation>();
_entitiesOperations = new FasterDictionary<ulong, EntitySubmitOperation>();
#if UNITY_NATIVE //because of the thread count, ATM this is only for unity
_nativeSwapOperationQueue = new DataStructures.AtomicNativeBags(Allocator.Persistent);
_nativeRemoveOperationQueue = new DataStructures.AtomicNativeBags(Allocator.Persistent);
_nativeAddOperationQueue = new DataStructures.AtomicNativeBags(Allocator.Persistent);
#endif
_serializationDescriptorMap = new SerializationDescriptorMap();
_maxNumberOfOperationsPerFrame = uint.MaxValue;
_reactiveEnginesAddRemove = new FasterDictionary<RefWrapperType, FasterList<ReactEngineContainer>>();
_reactiveEnginesAddRemoveOnDispose =
new FasterDictionary<RefWrapperType, FasterList<ReactEngineContainer>>();
_reactiveEnginesSwap = new FasterDictionary<RefWrapperType, FasterList<ReactEngineContainer>>();
_reactiveEnginesSubmission = new FasterList<IReactOnSubmission>();
_enginesSet = new FasterList<IEngine>();
_enginesTypeSet = new HashSet<Type>();
_disposableEngines = new FasterList<IDisposable>();
_transientEntitiesOperations = new FasterList<EntitySubmitOperation>();

_groupEntityComponentsDB =
new FasterDictionary<ExclusiveGroupStruct, FasterDictionary<RefWrapperType, ITypeSafeDictionary>>();
_groupsPerEntity =
new FasterDictionary<RefWrapperType, FasterDictionary<ExclusiveGroupStruct, ITypeSafeDictionary>>();
_groupedEntityToAdd = new DoubleBufferedEntitiesToAdd();

_entityStreams = EntitiesStreams.Create();
_groupFilters =
new FasterDictionary<RefWrapperType, FasterDictionary<ExclusiveGroupStruct, GroupFilters>>();
_entitiesDB = new EntitiesDB(this);
_entityLocator.InitEntityReferenceMap();
_entitiesDB = new EntitiesDB(this,_entityLocator);

_scheduler = entitiesComponentScheduler;
_scheduler.onTick = new EntitiesSubmitter(this);
scheduler = entitiesComponentScheduler;
scheduler.onTick = new EntitiesSubmitter(this);
#if UNITY_NATIVE
AllocateNativeOperations();
#endif
@@ -89,10 +67,12 @@ namespace Svelto.ECS
_isDeserializationOnly = isDeserializationOnly;
}

public EntitiesSubmissionScheduler scheduler { get; }

/// <summary>
/// Dispose an EngineRoot once not used anymore, so that all the
/// engines are notified with the entities removed.
/// It's a clean up process.
/// Dispose an EngineRoot once not used anymore, so that all the
/// engines are notified with the entities removed.
/// It's a clean up process.
/// </summary>
public void Dispose()
{
@@ -102,10 +82,9 @@ namespace Svelto.ECS
{
//Note: The engines are disposed before the the remove callback to give the chance to behave
//differently if a remove happens as a consequence of a dispose
//The pattern is to implement the IDisposable interface and set a flag in the engine. The
//The pattern is to implement the IDisposable interface and set a flag in the engine. The
//remove callback will then behave differently according the flag.
foreach (var engine in _disposableEngines)
{
try
{
if (engine is IDisposingEngine dengine)
@@ -114,33 +93,28 @@ namespace Svelto.ECS
}
catch (Exception e)
{
Svelto.Console.LogException(e);
Console.LogException(e);
}
}

foreach (var groups in _groupEntityComponentsDB)
{
foreach (var entityList in groups.Value)
try
{
entityList.Value.ExecuteEnginesRemoveCallbacks(_reactiveEnginesAddRemoveOnDispose, profiler
, new ExclusiveGroupStruct(groups.Key));
}
catch (Exception e)
{
Svelto.Console.LogException(e);
}
}
foreach (var entityList in groups.Value)
try
{
entityList.Value.ExecuteEnginesRemoveCallbacks(_reactiveEnginesAddRemoveOnDispose, profiler
, new ExclusiveGroupStruct(groups.Key));
}
catch (Exception e)
{
Console.LogException(e);
}

foreach (var groups in _groupEntityComponentsDB)
{
foreach (var entityList in groups.Value)
entityList.Value.Dispose();
}
foreach (var entityList in groups.Value)
entityList.Value.Dispose();

foreach (var type in _groupFilters)
foreach (var group in type.Value)
group.Value.Dispose();
foreach (var group in type.Value)
group.Value.Dispose();

_groupFilters.Clear();

@@ -164,6 +138,9 @@ namespace Svelto.ECS
_transientEntitiesOperations.Clear();

_groupedEntityToAdd.Dispose();

_entityLocator.DisposeEntityReferenceMap();
_entityStreams.Dispose();
scheduler.Dispose();
}
@@ -171,13 +148,6 @@ namespace Svelto.ECS
GC.SuppressFinalize(this);
}

~EnginesRoot()
{
Console.LogWarning("Engines Root has been garbage collected, don't forget to call Dispose()!");

Dispose();
}

public void AddEngine(IEngine engine)
{
var type = engine.GetType();
@@ -191,13 +161,13 @@ namespace Svelto.ECS
try
{
if (engine is IReactOnAddAndRemove viewEngine)
CheckReactEngineComponents(viewEngine, _reactiveEnginesAddRemove);
CheckReactEngineComponents(viewEngine, _reactiveEnginesAddRemove, type.Name);

if (engine is IReactOnDispose viewEngineDispose)
CheckReactEngineComponents(viewEngineDispose, _reactiveEnginesAddRemoveOnDispose);
CheckReactEngineComponents(viewEngineDispose, _reactiveEnginesAddRemoveOnDispose, type.Name);

if (engine is IReactOnSwap viewEngineSwap)
CheckReactEngineComponents(viewEngineSwap, _reactiveEnginesSwap);
CheckReactEngineComponents(viewEngineSwap, _reactiveEnginesSwap, type.Name);

if (engine is IReactOnSubmission submissionEngine)
_reactiveEnginesSubmission.Add(submissionEngine);
@@ -221,24 +191,38 @@ namespace Svelto.ECS
}
}

void CheckReactEngineComponents<T>(T engine, FasterDictionary<RefWrapperType, FasterList<IReactEngine>> engines)
void NotifyReactiveEnginesOnSubmission()
{
var enginesCount = _reactiveEnginesSubmission.count;
for (var i = 0; i < enginesCount; i++)
_reactiveEnginesSubmission[i].EntitiesSubmitted();
}

~EnginesRoot()
{
Console.LogWarning("Engines Root has been garbage collected, don't forget to call Dispose()!");

Dispose();
}

void CheckReactEngineComponents<T>
(T engine, FasterDictionary<RefWrapperType, FasterList<ReactEngineContainer>> engines, string typeName)
where T : class, IReactEngine
{
var interfaces = engine.GetType().GetInterfaces();

foreach (var interf in interfaces)
{
if (interf.IsGenericTypeEx() && typeof(T).IsAssignableFrom(interf))
{
var genericArguments = interf.GetGenericArgumentsEx();

AddEngineToList(engine, genericArguments, engines);
AddEngineToList(engine, genericArguments, engines, typeName);
}
}
}

static void AddEngineToList<T>
(T engine, Type[] entityComponentTypes, FasterDictionary<RefWrapperType, FasterList<IReactEngine>> engines)
(T engine, Type[] entityComponentTypes
, FasterDictionary<RefWrapperType, FasterList<ReactEngineContainer>> engines, string typeName)
where T : class, IReactEngine
{
for (var i = 0; i < entityComponentTypes.Length; i++)
@@ -247,22 +231,107 @@ namespace Svelto.ECS

if (engines.TryGetValue(new RefWrapperType(type), out var list) == false)
{
list = new FasterList<IReactEngine>();
list = new FasterList<ReactEngineContainer>();

engines.Add(new RefWrapperType(type), list);
}

list.Add(engine);
list.Add(new ReactEngineContainer(engine, typeName));
}
}

readonly FasterDictionary<RefWrapperType, FasterList<IReactEngine>> _reactiveEnginesAddRemove;
readonly FasterDictionary<RefWrapperType, FasterList<IReactEngine>> _reactiveEnginesAddRemoveOnDispose;
readonly FasterDictionary<RefWrapperType, FasterList<IReactEngine>> _reactiveEnginesSwap;
readonly FasterList<IReactOnSubmission> _reactiveEnginesSubmission;
readonly FasterList<IDisposable> _disposableEngines;
readonly FasterList<IEngine> _enginesSet;
readonly HashSet<Type> _enginesTypeSet;
internal bool _isDisposing;
internal bool _isDisposing;
readonly FasterList<IDisposable> _disposableEngines;
readonly FasterList<IEngine> _enginesSet;
readonly HashSet<Type> _enginesTypeSet;

readonly FasterDictionary<RefWrapperType, FasterList<ReactEngineContainer>> _reactiveEnginesAddRemove;
readonly FasterDictionary<RefWrapperType, FasterList<ReactEngineContainer>> _reactiveEnginesAddRemoveOnDispose;
readonly FasterList<IReactOnSubmission> _reactiveEnginesSubmission;
readonly FasterDictionary<RefWrapperType, FasterList<ReactEngineContainer>> _reactiveEnginesSwap;

public struct EntitiesSubmitter
{
public EntitiesSubmitter(EnginesRoot enginesRoot) : this()
{
_enginesRoot = new Svelto.DataStructures.WeakReference<EnginesRoot>(enginesRoot);
_privateSubmitEntities =
_enginesRoot.Target.SingleSubmission(new PlatformProfiler());
submitEntities = Invoke(); //this must be last to capture all the variables
}

IEnumerator<bool> Invoke()
{
while (true)
{
DBC.ECS.Check.Require(_enginesRoot.IsValid, "ticking an GCed engines root?");

var enginesRootTarget = _enginesRoot.Target;
var entitiesSubmissionScheduler = enginesRootTarget.scheduler;

if (entitiesSubmissionScheduler.paused == false)
{
DBC.ECS.Check.Require(entitiesSubmissionScheduler.isRunning == false
, "A submission started while the previous one was still flushing");
entitiesSubmissionScheduler.isRunning = true;

using (var profiler = new PlatformProfiler("Svelto.ECS - Entities Submission"))
{
var iterations = 0;
var hasEverSubmitted = false;
#if UNITY_NATIVE
enginesRootTarget.FlushNativeOperations(profiler);
#endif

//todo: proper unit test structural changes made as result of add/remove callbacks
while (enginesRootTarget.HasMadeNewStructuralChangesInThisIteration() && iterations++ < 5)
{
hasEverSubmitted = true;

while (true)
{
_privateSubmitEntities.MoveNext();
if (_privateSubmitEntities.Current == true)
{
using (profiler.Yield())
{
yield return true;
}
}
else
break;
}
#if UNITY_NATIVE
if (enginesRootTarget.HasMadeNewStructuralChangesInThisIteration())
enginesRootTarget.FlushNativeOperations(profiler);
#endif
}

#if DEBUG && !PROFILE_SVELTO
if (iterations == 5)
throw new ECSException("possible circular submission detected");
#endif
if (hasEverSubmitted)
enginesRootTarget.NotifyReactiveEnginesOnSubmission();
}

entitiesSubmissionScheduler.isRunning = false;
++entitiesSubmissionScheduler.iteration;
}

yield return false;
}
}

public uint maxNumberOfOperationsPerFrame
{
set => _enginesRoot.Target._maxNumberOfOperationsPerFrame = value;
}

readonly Svelto.DataStructures.WeakReference<EnginesRoot> _enginesRoot;

internal readonly IEnumerator<bool> submitEntities;
readonly IEnumerator<bool> _privateSubmitEntities;
}
}
}

+ 98
- 79
Svelto.ECS/Core/EnginesRoot.Entities.cs View File

@@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using DBC.ECS;
using Svelto.Common;
using Svelto.DataStructures;
using Svelto.ECS.Internal;
@@ -17,40 +16,45 @@ namespace Svelto.ECS
return new GenericEntityStreamConsumerFactory(this);
}

public IEntityFactory GenerateEntityFactory()
{
return new GenericEntityFactory(this);
}

public IEntityFunctions GenerateEntityFunctions()
{
return new GenericEntityFunctions(this);
}
public IEntityFactory GenerateEntityFactory() { return new GenericEntityFactory(this); }
public IEntityFunctions GenerateEntityFunctions() { return new GenericEntityFunctions(this); }

///--------------------------------------------
[MethodImpl(MethodImplOptions.AggressiveInlining)]
EntityInitializer BuildEntity
(EGID entityID, IComponentBuilder[] componentsToBuild, Type descriptorType,
IEnumerable<object> implementors = null)
EntityInitializer BuildEntity(EGID entityID, IComponentBuilder[] componentsToBuild, Type descriptorType
, IEnumerable<object> implementors = null)
{
CheckAddEntityID(entityID, descriptorType);
Check.Require(entityID.groupID != 0, "invalid group detected, are you using new ExclusiveGroupStruct() instead of new ExclusiveGroup()?");

var dic = EntityFactory.BuildGroupedEntities(entityID, _groupedEntityToAdd, componentsToBuild
, implementors, descriptorType);
DBC.ECS.Check.Require((uint)entityID.groupID != 0
, "invalid group detected, are you using new ExclusiveGroupStruct() instead of new ExclusiveGroup()?");

var reference = _entityLocator.ClaimReference();
_entityLocator.SetReference(reference, entityID);

var dic = EntityFactory.BuildGroupedEntities(entityID, _groupedEntityToAdd, componentsToBuild, implementors
#if DEBUG && !PROFILE_SVELTO
, descriptorType
#endif
);

return new EntityInitializer(entityID, dic);
return new EntityInitializer(entityID, dic, reference);
}

///--------------------------------------------
void Preallocate<T>(ExclusiveGroupStruct groupID, uint size) where T : IEntityDescriptor, new()
/// <summary>
/// Preallocate memory to avoid the impact to resize arrays when many entities are submitted at once
/// </summary>
void Preallocate(ExclusiveGroupStruct groupID, uint numberOfEntities, IComponentBuilder[] entityComponentsToBuild)
{
using (var profiler = new PlatformProfiler("Preallocate"))
void PreallocateEntitiesToAdd()
{
var entityComponentsToBuild = EntityDescriptorTemplate<T>.descriptor.componentsToBuild;
var numberOfEntityComponents = entityComponentsToBuild.Length;
_groupedEntityToAdd.Preallocate(groupID, numberOfEntities, entityComponentsToBuild);
}

FasterDictionary<RefWrapperType, ITypeSafeDictionary> group = GetOrCreateGroup(groupID, profiler);
void PreallocateDBGroup()
{
var numberOfEntityComponents = entityComponentsToBuild.Length;
FasterDictionary<RefWrapperType, ITypeSafeDictionary> group = GetOrCreateDBGroup(groupID);

for (var index = 0; index < numberOfEntityComponents; index++)
{
@@ -58,17 +62,20 @@ namespace Svelto.ECS
var entityComponentType = entityComponentBuilder.GetEntityComponentType();

var refWrapper = new RefWrapperType(entityComponentType);
if (group.TryGetValue(refWrapper, out var dbList) == false)
group[refWrapper] = entityComponentBuilder.Preallocate(ref dbList, size);
else
dbList.SetCapacity(size);
var dbList = group.GetOrCreate(refWrapper, ()=>entityComponentBuilder.CreateDictionary(numberOfEntities));
entityComponentBuilder.Preallocate(dbList, numberOfEntities);

if (_groupsPerEntity.TryGetValue(refWrapper, out var groupedGroup) == false)
groupedGroup = _groupsPerEntity[refWrapper] = new FasterDictionary<ExclusiveGroupStruct, ITypeSafeDictionary>();
groupedGroup = _groupsPerEntity[refWrapper] =
new FasterDictionary<ExclusiveGroupStruct, ITypeSafeDictionary>();

groupedGroup[groupID] = dbList;
}
}

PreallocateDBGroup();
PreallocateEntitiesToAdd();
_entityLocator.PreallocateReferenceMaps(groupID, numberOfEntities);
}

///--------------------------------------------
@@ -77,23 +84,24 @@ namespace Svelto.ECS
{
using (var sampler = new PlatformProfiler("Move Entity From Engines"))
{
var fromGroup = GetGroup(fromEntityGID.groupID);
var fromGroup = GetDBGroup(fromEntityGID.groupID);

//Check if there is an EntityInfo linked to this entity, if so it's a DynamicEntityDescriptor!
if (fromGroup.TryGetValue(new RefWrapperType(ComponentBuilderUtilities.ENTITY_INFO_COMPONENT)
, out var entityInfoDic)
, out var entityInfoDic)
&& (entityInfoDic as ITypeSafeDictionary<EntityInfoComponent>).TryGetValue(
fromEntityGID.entityID, out var entityInfo))
SwapOrRemoveEntityComponents(fromEntityGID, toEntityGID, entityInfo.componentsToBuild, fromGroup
, sampler);
, sampler);
//otherwise it's a normal static entity descriptor
else
SwapOrRemoveEntityComponents(fromEntityGID, toEntityGID, componentBuilders, fromGroup, sampler);
}
}

void SwapOrRemoveEntityComponents(EGID fromEntityGID, EGID? toEntityGID, IComponentBuilder[] entitiesToMove
, FasterDictionary<RefWrapperType, ITypeSafeDictionary> fromGroup, in PlatformProfiler sampler)
void SwapOrRemoveEntityComponents
(EGID fromEntityGID, EGID? toEntityGID, IComponentBuilder[] entitiesToMove
, FasterDictionary<RefWrapperType, ITypeSafeDictionary> fromGroup, in PlatformProfiler sampler)
{
using (sampler.Sample("MoveEntityComponents"))
{
@@ -103,41 +111,48 @@ namespace Svelto.ECS

//Swap is not like adding a new entity. While adding new entities happen at the end of submission
//Adding an entity to a group due to a swap of groups happens now.
if (toEntityGID != null)
if (toEntityGID.HasValue)
{
var toGroupID = toEntityGID.Value.groupID;
var entityGid = toEntityGID.Value;
_entityLocator.UpdateEntityReference(fromEntityGID, entityGid);

toGroup = GetOrCreateGroup(toGroupID, sampler);
var toGroupID = entityGid.groupID;

toGroup = GetOrCreateDBGroup(toGroupID);

//Add all the entities to the dictionary
for (var i = 0; i < length; i++)
CopyEntityToDictionary(fromEntityGID, toEntityGID.Value, fromGroup, toGroup
, entitiesToMove[i].GetEntityComponentType(), sampler);
CopyEntityToDictionary(fromEntityGID, entityGid, fromGroup, toGroup
, entitiesToMove[i].GetEntityComponentType(), sampler);
}
else
{
_entityLocator.RemoveEntityReference(fromEntityGID);
}

//call all the callbacks
for (var i = 0; i < length; i++)
ExecuteEnginesSwapOrRemoveCallbacks(fromEntityGID, toEntityGID, fromGroup, toGroup
, entitiesToMove[i].GetEntityComponentType(), sampler);
, entitiesToMove[i].GetEntityComponentType(), sampler);

//then remove all the entities from the dictionary
for (var i = 0; i < length; i++)
RemoveEntityFromDictionary(fromEntityGID, fromGroup, entitiesToMove[i].GetEntityComponentType(),
sampler);
RemoveEntityFromDictionary(fromEntityGID, fromGroup, entitiesToMove[i].GetEntityComponentType()
, sampler);
}
}

void CopyEntityToDictionary
(EGID entityGID, EGID toEntityGID, FasterDictionary<RefWrapperType, ITypeSafeDictionary> fromGroup
, FasterDictionary<RefWrapperType, ITypeSafeDictionary> toGroup, Type entityComponentType,
in PlatformProfiler sampler)
, FasterDictionary<RefWrapperType, ITypeSafeDictionary> toGroup, Type entityComponentType
, in PlatformProfiler sampler)
{
using (sampler.Sample("CopyEntityToDictionary"))
{
var wrapper = new RefWrapperType(entityComponentType);

ITypeSafeDictionary fromTypeSafeDictionary =
GetTypeSafeDictionary(entityGID.groupID, fromGroup, wrapper);
GetTypeSafeDictionary((uint) entityGID.groupID, fromGroup, wrapper);

#if DEBUG && !PROFILE_SVELTO
if (fromTypeSafeDictionary.Has(entityGID.entityID) == false)
@@ -154,14 +169,14 @@ namespace Svelto.ECS

void ExecuteEnginesSwapOrRemoveCallbacks
(EGID entityGID, EGID? toEntityGID, FasterDictionary<RefWrapperType, ITypeSafeDictionary> fromGroup
, FasterDictionary<RefWrapperType, ITypeSafeDictionary> toGroup, Type entityComponentType
, in PlatformProfiler profiler)
, FasterDictionary<RefWrapperType, ITypeSafeDictionary> toGroup, Type entityComponentType
, in PlatformProfiler profiler)
{
using (profiler.Sample("MoveEntityComponentFromAndToEngines"))
{
//add all the entities
var refWrapper = new RefWrapperType(entityComponentType);
var fromTypeSafeDictionary = GetTypeSafeDictionary(entityGID.groupID, fromGroup, refWrapper);
var fromTypeSafeDictionary = GetTypeSafeDictionary((uint) entityGID.groupID, fromGroup, refWrapper);

ITypeSafeDictionary toEntitiesDictionary = null;
if (toGroup != null)
@@ -172,19 +187,21 @@ namespace Svelto.ECS
throw new EntityNotFoundException(entityGID, entityComponentType);
#endif
fromTypeSafeDictionary.ExecuteEnginesSwapOrRemoveCallbacks(entityGID, toEntityGID, toEntitiesDictionary
, toEntityGID == null ? _reactiveEnginesAddRemove : _reactiveEnginesSwap, in profiler);
, toEntityGID == null
? _reactiveEnginesAddRemove
: _reactiveEnginesSwap, in profiler);
}
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
void RemoveEntityFromDictionary
(EGID entityGID, FasterDictionary<RefWrapperType, ITypeSafeDictionary> fromGroup, Type entityComponentType
, in PlatformProfiler sampler)
, in PlatformProfiler sampler)
{
using (sampler.Sample("RemoveEntityFromDictionary"))
{
var refWrapper = new RefWrapperType(entityComponentType);
var fromTypeSafeDictionary = GetTypeSafeDictionary(entityGID.groupID, fromGroup, refWrapper);
var fromTypeSafeDictionary = GetTypeSafeDictionary((uint) entityGID.groupID, fromGroup, refWrapper);

fromTypeSafeDictionary.RemoveEntityFromDictionary(entityGID);
}
@@ -199,27 +216,31 @@ namespace Svelto.ECS
/// <param name="fromIdGroupId"></param>
/// <param name="toGroupId"></param>
/// <param name="profiler"></param>
void SwapEntitiesBetweenGroups(ExclusiveGroupStruct fromIdGroupId, ExclusiveGroupStruct toGroupId, in PlatformProfiler profiler)
void SwapEntitiesBetweenGroups
(ExclusiveGroupStruct fromIdGroupId, ExclusiveGroupStruct toGroupId, in PlatformProfiler profiler)
{
using (profiler.Sample("SwapEntitiesBetweenGroups"))
{
FasterDictionary<RefWrapperType, ITypeSafeDictionary> fromGroup = GetGroup(fromIdGroupId);
FasterDictionary<RefWrapperType, ITypeSafeDictionary> toGroup = GetOrCreateGroup(toGroupId, profiler);
FasterDictionary<RefWrapperType, ITypeSafeDictionary> fromGroup = GetDBGroup(fromIdGroupId);
FasterDictionary<RefWrapperType, ITypeSafeDictionary> toGroup = GetOrCreateDBGroup(toGroupId);

_entityLocator.UpdateAllGroupReferenceLocators(fromIdGroupId, (uint) toGroupId);

foreach (var dictionaryOfEntities in fromGroup)
{
ITypeSafeDictionary toEntitiesDictionary =
GetOrCreateTypeSafeDictionary(toGroupId, toGroup, dictionaryOfEntities.Key
, dictionaryOfEntities.Value);
, dictionaryOfEntities.Value);

var groupsOfEntityType = _groupsPerEntity[dictionaryOfEntities.Key];

var groupOfEntitiesToCopyAndClear = groupsOfEntityType[fromIdGroupId];
toEntitiesDictionary.AddEntitiesFromDictionary(groupOfEntitiesToCopyAndClear, toGroupId);

toEntitiesDictionary.AddEntitiesFromDictionary(groupOfEntitiesToCopyAndClear, (uint) toGroupId, this);

//call all the MoveTo callbacks
dictionaryOfEntities.Value.ExecuteEnginesAddOrSwapCallbacks(_reactiveEnginesSwap
, dictionaryOfEntities.Value, new ExclusiveGroupStruct(fromIdGroupId), new ExclusiveGroupStruct(toGroupId), profiler);
, dictionaryOfEntities.Value, new ExclusiveGroupStruct(fromIdGroupId)
, new ExclusiveGroupStruct(toGroupId), profiler);

//todo: if it's unmanaged, I can use fastclear
groupOfEntitiesToCopyAndClear.Clear();
@@ -227,33 +248,29 @@ namespace Svelto.ECS
}
}

FasterDictionary<RefWrapperType, ITypeSafeDictionary> GetGroup(ExclusiveGroupStruct fromIdGroupId)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
FasterDictionary<RefWrapperType, ITypeSafeDictionary> GetDBGroup(ExclusiveGroupStruct fromIdGroupId)
{
if (_groupEntityComponentsDB.TryGetValue(fromIdGroupId
, out FasterDictionary<RefWrapperType, ITypeSafeDictionary>
fromGroup) == false)
throw new ECSException("Group doesn't exist: ".FastConcat(fromIdGroupId));
, out FasterDictionary<RefWrapperType, ITypeSafeDictionary>
fromGroup) == false)
throw new ECSException("Group doesn't exist: ".FastConcat((uint) fromIdGroupId));

return fromGroup;
}

FasterDictionary<RefWrapperType, ITypeSafeDictionary> GetOrCreateGroup(ExclusiveGroupStruct toGroupId,
in PlatformProfiler profiler)
{
using (profiler.Sample("GetOrCreateGroup"))
{
if (_groupEntityComponentsDB.TryGetValue(
toGroupId, out FasterDictionary<RefWrapperType, ITypeSafeDictionary> toGroup) == false)
toGroup = _groupEntityComponentsDB[toGroupId] =
new FasterDictionary<RefWrapperType, ITypeSafeDictionary>();

return toGroup;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
FasterDictionary<RefWrapperType, ITypeSafeDictionary> GetOrCreateDBGroup
(ExclusiveGroupStruct toGroupId)
{
return _groupEntityComponentsDB.GetOrCreate(
toGroupId, () => new FasterDictionary<RefWrapperType, ITypeSafeDictionary>());
}

ITypeSafeDictionary GetOrCreateTypeSafeDictionary
(ExclusiveGroupStruct groupId, FasterDictionary<RefWrapperType, ITypeSafeDictionary> toGroup, RefWrapperType type
, ITypeSafeDictionary fromTypeSafeDictionary)
(ExclusiveGroupStruct groupId, FasterDictionary<RefWrapperType, ITypeSafeDictionary> toGroup
, RefWrapperType type, ITypeSafeDictionary fromTypeSafeDictionary)
{
//be sure that the TypeSafeDictionary for the entity Type exists
if (toGroup.TryGetValue(type, out ITypeSafeDictionary toEntitiesDictionary) == false)
@@ -264,7 +281,8 @@ namespace Svelto.ECS

//update GroupsPerEntity
if (_groupsPerEntity.TryGetValue(type, out var groupedGroup) == false)
groupedGroup = _groupsPerEntity[type] = new FasterDictionary<ExclusiveGroupStruct, ITypeSafeDictionary>();
groupedGroup = _groupsPerEntity[type] =
new FasterDictionary<ExclusiveGroupStruct, ITypeSafeDictionary>();

groupedGroup[groupId] = toEntitiesDictionary;
return toEntitiesDictionary;
@@ -283,16 +301,17 @@ namespace Svelto.ECS

void RemoveEntitiesFromGroup(ExclusiveGroupStruct groupID, in PlatformProfiler profiler)
{
_entityLocator.RemoveAllGroupReferenceLocators(groupID);

if (_groupEntityComponentsDB.TryGetValue(groupID, out var dictionariesOfEntities))
{
foreach (var dictionaryOfEntities in dictionariesOfEntities)
{
dictionaryOfEntities.Value.ExecuteEnginesRemoveCallbacks(_reactiveEnginesAddRemove, profiler
, new ExclusiveGroupStruct(groupID));
, new ExclusiveGroupStruct(groupID));
dictionaryOfEntities.Value.FastClear();

var groupsOfEntityType =
_groupsPerEntity[dictionaryOfEntities.Key];
var groupsOfEntityType = _groupsPerEntity[dictionaryOfEntities.Key];
groupsOfEntityType[groupID].FastClear();
}
}


+ 12
- 10
Svelto.ECS/Core/EnginesRoot.GenericEntityFactory.cs View File

@@ -1,8 +1,8 @@
using System;
using System;
using System.Collections.Generic;
using Svelto.Common;

namespace Svelto.ECS
namespace Svelto.ECS
{
public partial class EnginesRoot
{
@@ -34,12 +34,7 @@ namespace Svelto.ECS
{
return _enginesRoot.Target.BuildEntity(egid, entityDescriptor.componentsToBuild, TypeCache<T>.type, implementors);
}
#if UNITY_NATIVE
public NativeEntityFactory ToNative<T>(string callerName) where T : IEntityDescriptor, new()
{
return _enginesRoot.Target.ProvideNativeEntityFactoryQueue<T>(callerName);
}
#endif

public EntityInitializer BuildEntity<T>
(uint entityID, ExclusiveBuildGroup groupStructId, T descriptorEntity, IEnumerable<object> implementors)
where T : IEntityDescriptor
@@ -48,16 +43,23 @@ namespace Svelto.ECS
, descriptorEntity.componentsToBuild, TypeCache<T>.type, implementors);
}

public void PreallocateEntitySpace<T>(ExclusiveGroupStruct groupStructId, uint size)
public void PreallocateEntitySpace<T>(ExclusiveGroupStruct groupStructId, uint numberOfEntities)
where T : IEntityDescriptor, new()
{
_enginesRoot.Target.Preallocate<T>(groupStructId, size);
_enginesRoot.Target.Preallocate(groupStructId, numberOfEntities, EntityDescriptorTemplate<T>.descriptor.componentsToBuild);
}
public EntityInitializer BuildEntity(EGID egid, IComponentBuilder[] componentsToBuild, Type type, IEnumerable<object> implementors = null)
{
return _enginesRoot.Target.BuildEntity(egid, componentsToBuild, type, implementors);
}
#if UNITY_NATIVE
public Svelto.ECS.Native.NativeEntityFactory ToNative<T>(string callerName) where T : IEntityDescriptor, new()
{
return _enginesRoot.Target.ProvideNativeEntityFactoryQueue<T>(callerName);
}
#endif

//enginesRoot is a weakreference because GenericEntityStreamConsumerFactory can be injected inside
//engines of other enginesRoot


+ 14
- 16
Svelto.ECS/Core/EnginesRoot.GenericEntityFunctions.cs View File

@@ -19,18 +19,18 @@ namespace Svelto.ECS
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void RemoveEntity<T>(uint entityID, ExclusiveBuildGroup groupID, [CallerMemberName] string memberName = "") where T :
public void RemoveEntity<T>(uint entityID, ExclusiveBuildGroup groupID) where T :
IEntityDescriptor, new()
{
RemoveEntity<T>(new EGID(entityID, groupID), memberName);
RemoveEntity<T>(new EGID(entityID, groupID));
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void RemoveEntity<T>(EGID entityEGID, [CallerMemberName] string memberName = "") where T : IEntityDescriptor, new()
public void RemoveEntity<T>(EGID entityEGID) where T : IEntityDescriptor, new()
{
DBC.ECS.Check.Require(entityEGID.groupID != 0, "invalid group detected");
DBC.ECS.Check.Require((uint)entityEGID.groupID != 0, "invalid group detected");
var descriptorComponentsToBuild = EntityDescriptorTemplate<T>.descriptor.componentsToBuild;
_enginesRoot.Target.CheckRemoveEntityID(entityEGID, TypeCache<T>.type, memberName);
_enginesRoot.Target.CheckRemoveEntityID(entityEGID, TypeCache<T>.type);

_enginesRoot.Target.QueueEntitySubmitOperation<T>(
new EntitySubmitOperation(EntitySubmitOperationType.Remove, entityEGID, entityEGID,
@@ -40,7 +40,7 @@ namespace Svelto.ECS
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void RemoveEntitiesFromGroup(ExclusiveBuildGroup groupID)
{
DBC.ECS.Check.Require(groupID != 0, "invalid group detected");
DBC.ECS.Check.Require((uint)groupID != 0, "invalid group detected");
_enginesRoot.Target.RemoveGroupID(groupID);

_enginesRoot.Target.QueueEntitySubmitOperation(
@@ -112,34 +112,32 @@ namespace Svelto.ECS
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SwapEntityGroup<T>(EGID fromID, ExclusiveBuildGroup toGroupID
, ExclusiveBuildGroup mustBeFromGroup)
public void SwapEntityGroup<T>(EGID fromID, ExclusiveBuildGroup mustBeFromGroup, ExclusiveBuildGroup toGroupID)
where T : IEntityDescriptor, new()
{
if (fromID.groupID != mustBeFromGroup)
throw new ECSException("Entity is not coming from the expected group");
throw new ECSException($"Entity is not coming from the expected group. Expected {mustBeFromGroup} is {fromID.groupID}");

SwapEntityGroup<T>(fromID, toGroupID);
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SwapEntityGroup<T>(EGID fromID, EGID toID
, ExclusiveBuildGroup mustBeFromGroup)
public void SwapEntityGroup<T>(EGID fromID, EGID toID, ExclusiveBuildGroup mustBeFromGroup)
where T : IEntityDescriptor, new()
{
if (fromID.groupID != mustBeFromGroup)
throw new ECSException("Entity is not coming from the expected group");
throw new ECSException($"Entity is not coming from the expected group Expected {mustBeFromGroup} is {fromID.groupID}");

SwapEntityGroup<T>(fromID, toID);
}

#if UNITY_NATIVE
public NativeEntityRemove ToNativeRemove<T>(string memberName) where T : IEntityDescriptor, new()
public Svelto.ECS.Native.NativeEntityRemove ToNativeRemove<T>(string memberName) where T : IEntityDescriptor, new()
{
return _enginesRoot.Target.ProvideNativeEntityRemoveQueue<T>(memberName);
}

public NativeEntitySwap ToNativeSwap<T>(string memberName) where T : IEntityDescriptor, new()
public Svelto.ECS.Native.NativeEntitySwap ToNativeSwap<T>(string memberName) where T : IEntityDescriptor, new()
{
return _enginesRoot.Target.ProvideNativeEntitySwapQueue<T>(memberName);
}
@@ -149,8 +147,8 @@ namespace Svelto.ECS
public void SwapEntityGroup<T>(EGID fromID, EGID toID)
where T : IEntityDescriptor, new()
{
DBC.ECS.Check.Require(fromID.groupID != 0, "invalid group detected");
DBC.ECS.Check.Require(toID.groupID != 0, "invalid group detected");
DBC.ECS.Check.Require((uint)fromID.groupID != 0, "invalid group detected");
DBC.ECS.Check.Require((uint)toID.groupID != 0, "invalid group detected");

var enginesRootTarget = _enginesRoot.Target;
var descriptorComponentsToBuild = EntityDescriptorTemplate<T>.descriptor.componentsToBuild;


+ 117
- 135
Svelto.ECS/Core/EnginesRoot.Submission.cs View File

@@ -1,196 +1,178 @@
using System.Collections;
using System.Collections.Generic;
using Svelto.Common;
using Svelto.DataStructures;
using Svelto.ECS.Internal;

namespace Svelto.ECS
{
public partial class EnginesRoot
{
readonly FasterList<EntitySubmitOperation> _transientEntitiesOperations;

IEnumerator SubmitEntityComponents(uint maxNumberOfOperations)
{
using (var profiler = new PlatformProfiler("Svelto.ECS - Entities Submission"))
{
int iterations = 0;
do
{
var submitEntityComponents = SingleSubmission(profiler, maxNumberOfOperations);
while (submitEntityComponents.MoveNext() == true)
yield return null;
} while ((_groupedEntityToAdd.currentEntitiesCreatedPerGroup.count > 0 ||
_entitiesOperations.count > 0) && ++iterations < 5);

#if DEBUG && !PROFILE_SVELTO
if (iterations == 5)
throw new ECSException("possible circular submission detected");
#endif
}
}

/// <summary>
/// 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
/// </summary>
/// <param name="profiler"></param>
/// <param name="maxNumberOfOperations"></param>
IEnumerator SingleSubmission(PlatformProfiler profiler, uint maxNumberOfOperations)
IEnumerator<bool> SingleSubmission(PlatformProfiler profiler)
{
#if UNITY_NATIVE
NativeOperationSubmission(profiler);
#endif
ClearChecks();

bool entitiesAreSubmitted = false;
uint numberOfOperations = 0;
if (_entitiesOperations.count > 0)
while (true)
{
using (profiler.Sample("Remove and Swap operations"))
{
_transientEntitiesOperations.FastClear();
_entitiesOperations.CopyValuesTo(_transientEntitiesOperations);
_entitiesOperations.FastClear();
DBC.ECS.Check.Require(_maxNumberOfOperationsPerFrame > 0);
ClearChecks();

EntitySubmitOperation[] entitiesOperations = _transientEntitiesOperations.ToArrayFast(out var count);
for (var i = 0; i < count; i++)
uint numberOfOperations = 0;

if (_entitiesOperations.count > 0)
{
using (var sample = profiler.Sample("Remove and Swap operations"))
{
try
_transientEntitiesOperations.FastClear();
_entitiesOperations.CopyValuesTo(_transientEntitiesOperations);
_entitiesOperations.FastClear();

EntitySubmitOperation[] entitiesOperations =
_transientEntitiesOperations.ToArrayFast(out var count);
for (var i = 0; i < count; i++)
{
switch (entitiesOperations[i].type)
try
{
case EntitySubmitOperationType.Swap:
MoveEntityFromAndToEngines(entitiesOperations[i].builders,
entitiesOperations[i].fromID, entitiesOperations[i].toID);
break;
case EntitySubmitOperationType.Remove:
MoveEntityFromAndToEngines(entitiesOperations[i].builders,
entitiesOperations[i].fromID, null);
break;
case EntitySubmitOperationType.RemoveGroup:
RemoveEntitiesFromGroup(
entitiesOperations[i].fromID.groupID, profiler);
break;
case EntitySubmitOperationType.SwapGroup:
SwapEntitiesBetweenGroups(entitiesOperations[i].fromID.groupID,
entitiesOperations[i].toID.groupID, profiler);
break;
switch (entitiesOperations[i].type)
{
case EntitySubmitOperationType.Swap:
MoveEntityFromAndToEngines(entitiesOperations[i].builders
, entitiesOperations[i].fromID
, entitiesOperations[i].toID);
break;
case EntitySubmitOperationType.Remove:
MoveEntityFromAndToEngines(entitiesOperations[i].builders
, entitiesOperations[i].fromID, null);
break;
case EntitySubmitOperationType.RemoveGroup:
RemoveEntitiesFromGroup(entitiesOperations[i].fromID.groupID, profiler);
break;
case EntitySubmitOperationType.SwapGroup:
SwapEntitiesBetweenGroups(entitiesOperations[i].fromID.groupID
, entitiesOperations[i].toID.groupID, profiler);
break;
}
}
}
catch
{
var str = "Crash while executing Entity Operation "
.FastConcat(entitiesOperations[i].type.ToString());
Svelto.Console.LogError(str.FastConcat(" ")
catch
{
var str = "Crash while executing Entity Operation ".FastConcat(
entitiesOperations[i].type.ToString());

Svelto.Console.LogError(str.FastConcat(" ")
#if DEBUG && !PROFILE_SVELTO
.FastConcat(entitiesOperations[i].trace.ToString())
#endif
);
);

throw;
}
throw;
}

++numberOfOperations;
++numberOfOperations;

if ((uint)numberOfOperations >= (uint)maxNumberOfOperations)
{
yield return null;
numberOfOperations = 0;
if ((uint) numberOfOperations >= (uint) _maxNumberOfOperationsPerFrame)
{
using (sample.Yield())
yield return true;

numberOfOperations = 0;
}
}
}
}

entitiesAreSubmitted = true;
}

_groupedEntityToAdd.Swap();
_groupedEntityToAdd.Swap();

if (_groupedEntityToAdd.otherEntitiesCreatedPerGroup.count > 0)
{
using (profiler.Sample("Add operations"))
if (_groupedEntityToAdd.AnyOtherEntityCreated())
{
try
using (var outerSampler = profiler.Sample("Add operations"))
{
using (profiler.Sample("Add entities to database"))
try
{
//each group is indexed by entity view type. for each type there is a dictionary indexed by entityID
foreach (var groupToSubmit in _groupedEntityToAdd.otherEntitiesCreatedPerGroup)
using (profiler.Sample("Add entities to database"))
{
var groupID = groupToSubmit.Key;
var groupDB = GetOrCreateGroup(groupID, profiler);

//add the entityComponents in the group
foreach (var entityComponentsToSubmit in _groupedEntityToAdd.other[groupID])
//each group is indexed by entity view type. for each type there is a dictionary indexed by entityID
foreach (var groupToSubmit in _groupedEntityToAdd.other)
{
var type = entityComponentsToSubmit.Key;
var targetTypeSafeDictionary = entityComponentsToSubmit.Value;
var wrapper = new RefWrapperType(type);
var groupID = new ExclusiveGroupStruct(groupToSubmit.Key);
var groupDB = GetOrCreateDBGroup(groupID);

ITypeSafeDictionary dbDic = GetOrCreateTypeSafeDictionary(groupID, groupDB, wrapper,
targetTypeSafeDictionary);
//add the entityComponents in the group
foreach (var entityComponentsToSubmit in groupToSubmit.Value)
{
var type = entityComponentsToSubmit.Key;
var targetTypeSafeDictionary = entityComponentsToSubmit.Value;
var wrapper = new RefWrapperType(type);

var dbDic = GetOrCreateTypeSafeDictionary(
groupID, groupDB, wrapper, targetTypeSafeDictionary);

//Fill the DB with the entity components generate this frame.
dbDic.AddEntitiesFromDictionary(targetTypeSafeDictionary, groupID);
//Fill the DB with the entity components generated this frame.
dbDic.AddEntitiesFromDictionary(targetTypeSafeDictionary, (uint) groupID, this);
}
}
}
}

//then submit everything in the engines, so that the DB is up to date with all the entity components
//created by the entity built
using (profiler.Sample("Add entities to engines"))
{
foreach (var groupToSubmit in _groupedEntityToAdd.otherEntitiesCreatedPerGroup)
//then submit everything in the engines, so that the DB is up to date with all the entity components
//created by the entity built
using (var sampler = profiler.Sample("Add entities to engines"))
{
var groupID = groupToSubmit.Key;
var groupDB = _groupEntityComponentsDB[groupID];
foreach (var groupToSubmit in _groupedEntityToAdd.other)
{
var groupID = new ExclusiveGroupStruct(groupToSubmit.Key);
var groupDB = GetDBGroup(groupID);
//entityComponentsToSubmit is the array of components found in the groupID per component type.
//if there are N entities to submit, and M components type to add for each entity, this foreach will run NxM times.
foreach (var entityComponentsToSubmit in _groupedEntityToAdd.other[groupID])
{
var realDic = groupDB[new RefWrapperType(entityComponentsToSubmit.Key)];
foreach (var entityComponentsToSubmit in groupToSubmit.Value)
{
var realDic = groupDB[new RefWrapperType(entityComponentsToSubmit.Key)];

entityComponentsToSubmit.Value.ExecuteEnginesAddOrSwapCallbacks(_reactiveEnginesAddRemove, realDic,
null, new ExclusiveGroupStruct(groupID), in profiler);
numberOfOperations += entityComponentsToSubmit.Value.count;
entityComponentsToSubmit.Value.ExecuteEnginesAddOrSwapCallbacks(
_reactiveEnginesAddRemove, realDic, null, new ExclusiveGroupStruct(groupID)
, in profiler);

if (numberOfOperations >= maxNumberOfOperations)
{
yield return null;
numberOfOperations = 0;
numberOfOperations += entityComponentsToSubmit.Value.count;

if (numberOfOperations >= _maxNumberOfOperationsPerFrame)
{
using (outerSampler.Yield())
using (sampler.Yield())
{
yield return true;
}

numberOfOperations = 0;
}
}
}
}
}
}
finally
{
using (profiler.Sample("clear double buffering"))
finally
{
//other can be cleared now, but let's avoid deleting the dictionary every time
_groupedEntityToAdd.ClearOther();
using (profiler.Sample("clear double buffering"))
{
//other can be cleared now, but let's avoid deleting the dictionary every time
_groupedEntityToAdd.ClearOther();
}
}
}
}
entitiesAreSubmitted = true;
}

if (entitiesAreSubmitted)
{
var enginesCount = _reactiveEnginesSubmission.count;
for (int i = 0; i < enginesCount; i++)
_reactiveEnginesSubmission[i].EntitiesSubmitted();
yield return false;
}
}
bool HasMadeNewStructuralChangesInThisIteration()
{
return _groupedEntityToAdd.AnyEntityCreated() || _entitiesOperations.count > 0;
}

readonly DoubleBufferedEntitiesToAdd _groupedEntityToAdd;
readonly DoubleBufferedEntitiesToAdd _groupedEntityToAdd;
readonly FasterDictionary<ulong, EntitySubmitOperation> _entitiesOperations;
readonly FasterList<EntitySubmitOperation> _transientEntitiesOperations;
uint _maxNumberOfOperationsPerFrame;
}
}

+ 142
- 73
Svelto.ECS/Core/EntitiesDB.FindGroups.cs View File

@@ -1,4 +1,5 @@
using System;
using System.Runtime.CompilerServices;
using System.Threading;
using Svelto.DataStructures;
using Svelto.ECS.Internal;
@@ -9,34 +10,42 @@ namespace Svelto.ECS
{
public LocalFasterReadOnlyList<ExclusiveGroupStruct> FindGroups<T1>() where T1 : IEntityComponent
{
FasterList<ExclusiveGroupStruct> result = groups.Value;
FasterList<ExclusiveGroupStruct> result = localgroups.Value.groupArray;
result.FastClear();
if (groupsPerEntity.TryGetValue(TypeRefWrapper<T1>.wrapper
, out FasterDictionary<ExclusiveGroupStruct, ITypeSafeDictionary> result1) == false)
, out FasterDictionary<ExclusiveGroupStruct, ITypeSafeDictionary> result1)
== false)
return result;
var result1Count = result1.count;
var fasterDictionaryNodes1 = result1.unsafeKeys;
for (int j = 0; j < result1Count; j++)
{
result.Add(new ExclusiveGroupStruct(fasterDictionaryNodes1[j].key));
var group = fasterDictionaryNodes1[j].key;
if (group.IsEnabled())
{
result.Add(group);
}
}
return result;
}
public LocalFasterReadOnlyList<ExclusiveGroupStruct> FindGroups<T1, T2>() where T1 : IEntityComponent where T2 : IEntityComponent

public LocalFasterReadOnlyList<ExclusiveGroupStruct> FindGroups<T1, T2>()
where T1 : IEntityComponent where T2 : IEntityComponent
{
FasterList<ExclusiveGroupStruct> result = groups.Value;
FasterList<ExclusiveGroupStruct> result = localgroups.Value.groupArray;
result.FastClear();
if (groupsPerEntity.TryGetValue(TypeRefWrapper<T1>.wrapper
, out FasterDictionary<ExclusiveGroupStruct, ITypeSafeDictionary> result1) == false)
, out FasterDictionary<ExclusiveGroupStruct, ITypeSafeDictionary> result1)
== false)
return result;
if (groupsPerEntity.TryGetValue(TypeRefWrapper<T2>.wrapper
, out FasterDictionary<ExclusiveGroupStruct, ITypeSafeDictionary> result2) == false)
, out FasterDictionary<ExclusiveGroupStruct, ITypeSafeDictionary> result2)
== false)
return result;
var result1Count = result1.count;
var result2Count = result2.count;
var fasterDictionaryNodes1 = result1.unsafeKeys;
@@ -45,6 +54,8 @@ namespace Svelto.ECS
for (int i = 0; i < result1Count; i++)
{
var groupID = fasterDictionaryNodes1[i].key;
if (!groupID.IsEnabled()) continue;

for (int j = 0; j < result2Count; j++)
{
//if the same group is found used with both T1 and T2
@@ -62,6 +73,11 @@ namespace Svelto.ECS
/// <summary>
/// Remember that this operation os O(N*M*P) where N,M,P are the number of groups where each component
/// is found.
/// TODO: I have to find once for ever a solution to be sure that the entities in the groups match
/// Currently this returns group where the entities are found, but the entities may not match in these
/// groups.
/// Checking the size of the entities is an early check, needed, but not sufficient, as entities components may
/// coincidentally match in number but not from which entities they are generated
/// </summary>
/// <typeparam name="T1"></typeparam>
/// <typeparam name="T2"></typeparam>
@@ -70,61 +86,115 @@ namespace Svelto.ECS
public LocalFasterReadOnlyList<ExclusiveGroupStruct> FindGroups<T1, T2, T3>()
where T1 : IEntityComponent where T2 : IEntityComponent where T3 : IEntityComponent
{
FasterList<ExclusiveGroupStruct> result = groups.Value;
result.FastClear();
if (groupsPerEntity.TryGetValue(TypeRefWrapper<T1>.wrapper
, out FasterDictionary<ExclusiveGroupStruct, ITypeSafeDictionary> groupOfEntities1) == false)
return result;
if (groupsPerEntity.TryGetValue(TypeRefWrapper<T2>.wrapper
, out FasterDictionary<ExclusiveGroupStruct, ITypeSafeDictionary> groupOfEntities2) == false)
return result;
if (groupsPerEntity.TryGetValue(TypeRefWrapper<T3>.wrapper
, out FasterDictionary<ExclusiveGroupStruct, ITypeSafeDictionary> groupOfEntities3) == false)
return result;
var result1Count = groupOfEntities1.count;
var result2Count = groupOfEntities2.count;
var result3Count = groupOfEntities3.count;
var fasterDictionaryNodes1 = groupOfEntities1.unsafeKeys;
var fasterDictionaryNodes2 = groupOfEntities2.unsafeKeys;
var fasterDictionaryNodes3 = groupOfEntities3.unsafeKeys;
//
//TODO: I have to find once for ever a solution to be sure that the entities in the groups match
//Currently this returns group where the entities are found, but the entities may not match in these
//groups.
//Checking the size of the entities is an early check, needed, but not sufficient, as entities components may
//coincidentally match in number but not from which entities they are generated
//foreach group where T1 is found
for (int i = 0; i < result1Count; i++)
FasterList<FasterDictionary<ExclusiveGroupStruct, ITypeSafeDictionary>> localArray =
localgroups.Value.listOfGroups;
if (groupsPerEntity.TryGetValue(TypeRefWrapper<T1>.wrapper, out localArray[0]) == false || localArray[0].count == 0)
return new LocalFasterReadOnlyList<ExclusiveGroupStruct>(
FasterReadOnlyList<ExclusiveGroupStruct>.DefaultEmptyList);
if (groupsPerEntity.TryGetValue(TypeRefWrapper<T2>.wrapper, out localArray[1]) == false || localArray[1].count == 0)
return new LocalFasterReadOnlyList<ExclusiveGroupStruct>(
FasterReadOnlyList<ExclusiveGroupStruct>.DefaultEmptyList);
if (groupsPerEntity.TryGetValue(TypeRefWrapper<T3>.wrapper, out localArray[2]) == false || localArray[2].count == 0)
return new LocalFasterReadOnlyList<ExclusiveGroupStruct>(
FasterReadOnlyList<ExclusiveGroupStruct>.DefaultEmptyList);
localgroups.Value.groups.FastClear();
FasterDictionary<ExclusiveGroupStruct, ExclusiveGroupStruct> localGroups = localgroups.Value.groups;
int startIndex = 0;
int min = int.MaxValue;
for (int i = 0; i < 3; i++)
if (localArray[i].count < min)
{
min = localArray[i].count;
startIndex = i;
}
foreach (var value in localArray[startIndex])
{
var groupT1 = fasterDictionaryNodes1[i].key;
//foreach group where T2 is found
for (int j = 0; j < result2Count; ++j)
if (value.Key.IsEnabled())
{
if (groupT1 == fasterDictionaryNodes2[j].key)
{
//foreach group where T3 is found
for (int k = 0; k < result3Count; ++k)
{
if (groupT1 == fasterDictionaryNodes3[k].key)
{
result.Add(new ExclusiveGroupStruct(groupT1));
break;
}
}
break;
}
localGroups.Add(value.Key, value.Key);
}
}

return result;
var groupData = localArray[++startIndex % 3];
localGroups.Intersect(groupData);
if (localGroups.count == 0)
return new LocalFasterReadOnlyList<ExclusiveGroupStruct>(
FasterReadOnlyList<ExclusiveGroupStruct>.DefaultEmptyList);
groupData = localArray[++startIndex % 3];
localGroups.Intersect(groupData);

return new LocalFasterReadOnlyList<ExclusiveGroupStruct>(localGroups.unsafeValues
, (uint) localGroups.count);
}

public LocalFasterReadOnlyList<ExclusiveGroupStruct> FindGroups<T1, T2, T3, T4>()
where T1 : IEntityComponent
where T2 : IEntityComponent
where T3 : IEntityComponent
where T4 : IEntityComponent
{
FasterList<FasterDictionary<ExclusiveGroupStruct, ITypeSafeDictionary>> localArray =
localgroups.Value.listOfGroups;

if (groupsPerEntity.TryGetValue(TypeRefWrapper<T1>.wrapper, out localArray[0]) == false || localArray[0].count == 0)
return new LocalFasterReadOnlyList<ExclusiveGroupStruct>(
FasterReadOnlyList<ExclusiveGroupStruct>.DefaultEmptyList);
if (groupsPerEntity.TryGetValue(TypeRefWrapper<T2>.wrapper, out localArray[1]) == false || localArray[1].count == 0)
return new LocalFasterReadOnlyList<ExclusiveGroupStruct>(
FasterReadOnlyList<ExclusiveGroupStruct>.DefaultEmptyList);
if (groupsPerEntity.TryGetValue(TypeRefWrapper<T3>.wrapper, out localArray[2]) == false || localArray[2].count == 0)
return new LocalFasterReadOnlyList<ExclusiveGroupStruct>(
FasterReadOnlyList<ExclusiveGroupStruct>.DefaultEmptyList);
if (groupsPerEntity.TryGetValue(TypeRefWrapper<T4>.wrapper, out localArray[3]) == false || localArray[3].count == 0)
return new LocalFasterReadOnlyList<ExclusiveGroupStruct>(
FasterReadOnlyList<ExclusiveGroupStruct>.DefaultEmptyList);

localgroups.Value.groups.FastClear();

FasterDictionary<ExclusiveGroupStruct, ExclusiveGroupStruct> localGroups = localgroups.Value.groups;

int startIndex = 0;
int min = int.MaxValue;

for (int i = 0; i < 4; i++)
if (localArray[i].count < min)
{
min = localArray[i].count;
startIndex = i;
}

foreach (var value in localArray[startIndex])
{
if (value.Key.IsEnabled())
{
localGroups.Add(value.Key, value.Key);
}
}

var groupData = localArray[++startIndex & 3]; //&3 == %4
localGroups.Intersect(groupData);
if (localGroups.count == 0)
return new LocalFasterReadOnlyList<ExclusiveGroupStruct>(
FasterReadOnlyList<ExclusiveGroupStruct>.DefaultEmptyList);
groupData = localArray[++startIndex & 3];
localGroups.Intersect(groupData);
if (localGroups.count == 0)
return new LocalFasterReadOnlyList<ExclusiveGroupStruct>(
FasterReadOnlyList<ExclusiveGroupStruct>.DefaultEmptyList);
groupData = localArray[++startIndex & 3];
localGroups.Intersect(groupData);

return new LocalFasterReadOnlyList<ExclusiveGroupStruct>(localGroups.unsafeValues
, (uint) localGroups.count);
}

internal FasterDictionary<ExclusiveGroupStruct, ITypeSafeDictionary> FindGroups_INTERNAL(Type type)
internal FasterDictionary<ExclusiveGroupStruct, ITypeSafeDictionary> FindGroups_INTERNAL(Type type)
{
if (groupsPerEntity.ContainsKey(new RefWrapperType(type)) == false)
return _emptyDictionary;
@@ -134,21 +204,20 @@ namespace Svelto.ECS

struct GroupsList
{
static GroupsList()
{
groups = new FasterList<ExclusiveGroupStruct>();
}
internal FasterDictionary<ExclusiveGroupStruct, ExclusiveGroupStruct> groups;
internal FasterList<FasterDictionary<ExclusiveGroupStruct, ITypeSafeDictionary>> listOfGroups;
public FasterList<ExclusiveGroupStruct> groupArray;
}

static readonly FasterList<ExclusiveGroupStruct> groups;
static readonly ThreadLocal<GroupsList> localgroups = new ThreadLocal<GroupsList>(() =>
{
GroupsList gl = default;

public static implicit operator FasterList<ExclusiveGroupStruct>(in GroupsList list)
{
return list.reference;
}
gl.groups = new FasterDictionary<ExclusiveGroupStruct, ExclusiveGroupStruct>();
gl.listOfGroups = FasterList<FasterDictionary<ExclusiveGroupStruct, ITypeSafeDictionary>>.PreInit(4);
gl.groupArray = new FasterList<ExclusiveGroupStruct>(1);

FasterList<ExclusiveGroupStruct> reference => groups;
}
static readonly ThreadLocal<GroupsList> groups = new ThreadLocal<GroupsList>();
return gl;
});
}
}

+ 75
- 84
Svelto.ECS/Core/EntitiesDB.cs View File

@@ -12,12 +12,14 @@ namespace Svelto.ECS
{
public partial class EntitiesDB
{
internal EntitiesDB(EnginesRoot enginesRoot)
internal EntitiesDB(EnginesRoot enginesRoot, EnginesRoot.LocatorMap entityReferencesMap)
{
_enginesRoot = enginesRoot;
_enginesRoot = enginesRoot;
_entityReferencesMap = entityReferencesMap;
}

EntityCollection<T> InternalQueryEntities<T>(FasterDictionary<RefWrapperType, ITypeSafeDictionary> entitiesInGroupPerType)
EntityCollection<T> InternalQueryEntities<T>
(FasterDictionary<RefWrapperType, ITypeSafeDictionary> entitiesInGroupPerType)
where T : struct, IEntityComponent
{
uint count = 0;
@@ -58,79 +60,85 @@ namespace Svelto.ECS
{
if (groupEntityComponentsDB.TryGetValue(groupStruct, out var entitiesInGroupPerType) == false)
{
return new EntityCollection<T1, T2>(new EntityCollection<T1>(RetrieveEmptyEntityComponentArray<T1>(), 0),
new EntityCollection<T2>(RetrieveEmptyEntityComponentArray<T2>(), 0));
return new EntityCollection<T1, T2>(new EntityCollection<T1>(RetrieveEmptyEntityComponentArray<T1>(), 0)
, new EntityCollection<T2>(
RetrieveEmptyEntityComponentArray<T2>(), 0));
}
var T1entities = InternalQueryEntities<T1>(entitiesInGroupPerType);
var T2entities = InternalQueryEntities<T2>(entitiesInGroupPerType);
#if DEBUG && !PROFILE_SVELTO
if (T1entities.count != T2entities.count)
throw new ECSException("Entity components count do not match in group. Entity 1: ' count: "
.FastConcat(T1entities.count).FastConcat(" ", typeof(T1).ToString())
.FastConcat("'. Entity 2: ' count: ".FastConcat(T2entities.count)
.FastConcat(" ", typeof(T2).ToString())
.FastConcat("' group: ", groupStruct.ToName())));
.FastConcat(T1entities.count).FastConcat(" ", typeof(T1).ToString())
.FastConcat("'. Entity 2: ' count: ".FastConcat(T2entities.count)
.FastConcat(" ", typeof(T2).ToString())
.FastConcat(
"' group: ", groupStruct.ToName())));
#endif

return new EntityCollection<T1, T2>(T1entities, T2entities);
}
public EntityCollection<T1, T2, T3> QueryEntities<T1, T2, T3>(ExclusiveGroupStruct groupStruct)
where T1 : struct, IEntityComponent where T2 : struct, IEntityComponent where T3 : struct, IEntityComponent
{
if (groupEntityComponentsDB.TryGetValue(groupStruct, out var entitiesInGroupPerType) == false)
{
return new EntityCollection<T1, T2, T3>(
new EntityCollection<T1>(RetrieveEmptyEntityComponentArray<T1>(), 0),
new EntityCollection<T2>(RetrieveEmptyEntityComponentArray<T2>(), 0),
new EntityCollection<T3>(RetrieveEmptyEntityComponentArray<T3>(), 0));
new EntityCollection<T1>(RetrieveEmptyEntityComponentArray<T1>(), 0)
, new EntityCollection<T2>(RetrieveEmptyEntityComponentArray<T2>(), 0)
, new EntityCollection<T3>(RetrieveEmptyEntityComponentArray<T3>(), 0));
}
var T1entities = InternalQueryEntities<T1>(entitiesInGroupPerType);
var T2entities = InternalQueryEntities<T2>(entitiesInGroupPerType);
var T3entities = InternalQueryEntities<T3>(entitiesInGroupPerType);
#if DEBUG && !PROFILE_SVELTO
if (T1entities.count != T2entities.count || T2entities.count != T3entities.count)
throw new ECSException("Entity components count do not match in group. Entity 1: "
.FastConcat(typeof(T1).ToString()).FastConcat(" count: ")
.FastConcat(T1entities.count).FastConcat(
" Entity 2: "
.FastConcat(typeof(T2).ToString()).FastConcat(" count: ")
.FastConcat(T2entities.count)
.FastConcat(" Entity 3: ".FastConcat(typeof(T3).ToString()))
.FastConcat(" count: ").FastConcat(T3entities.count)));
.FastConcat(typeof(T1).ToString()).FastConcat(" count: ")
.FastConcat(T1entities.count).FastConcat(
" Entity 2: ".FastConcat(typeof(T2).ToString()).FastConcat(" count: ")
.FastConcat(T2entities.count)
.FastConcat(" Entity 3: ".FastConcat(typeof(T3).ToString()))
.FastConcat(" count: ").FastConcat(T3entities.count)));
#endif

return new EntityCollection<T1, T2, T3>(T1entities, T2entities, T3entities);
}
public EntityCollection<T1, T2, T3, T4> QueryEntities<T1, T2, T3, T4>(ExclusiveGroupStruct groupStruct)
where T1 : struct, IEntityComponent where T2 : struct, IEntityComponent where T3 : struct, IEntityComponent where T4 : struct, IEntityComponent
where T1 : struct, IEntityComponent
where T2 : struct, IEntityComponent
where T3 : struct, IEntityComponent
where T4 : struct, IEntityComponent
{
if (groupEntityComponentsDB.TryGetValue(groupStruct, out var entitiesInGroupPerType) == false)
{
return new EntityCollection<T1, T2, T3, T4>(
new EntityCollection<T1>(RetrieveEmptyEntityComponentArray<T1>(), 0),
new EntityCollection<T2>(RetrieveEmptyEntityComponentArray<T2>(), 0),
new EntityCollection<T3>(RetrieveEmptyEntityComponentArray<T3>(), 0),
new EntityCollection<T4>(RetrieveEmptyEntityComponentArray<T4>(), 0));
new EntityCollection<T1>(RetrieveEmptyEntityComponentArray<T1>(), 0)
, new EntityCollection<T2>(RetrieveEmptyEntityComponentArray<T2>(), 0)
, new EntityCollection<T3>(RetrieveEmptyEntityComponentArray<T3>(), 0)
, new EntityCollection<T4>(RetrieveEmptyEntityComponentArray<T4>(), 0));
}
var T1entities = InternalQueryEntities<T1>(entitiesInGroupPerType);
var T2entities = InternalQueryEntities<T2>(entitiesInGroupPerType);
var T3entities = InternalQueryEntities<T3>(entitiesInGroupPerType);
var T4entities = InternalQueryEntities<T4>(entitiesInGroupPerType);
#if DEBUG && !PROFILE_SVELTO
if (T1entities.count != T2entities.count || T2entities.count != T3entities.count)
if (T1entities.count != T2entities.count || T2entities.count != T3entities.count
|| T3entities.count != T4entities.count)
throw new ECSException("Entity components count do not match in group. Entity 1: "
.FastConcat(typeof(T1).ToString()).FastConcat(" count: ")
.FastConcat(T1entities.count).FastConcat(
" Entity 2: "
.FastConcat(typeof(T2).ToString()).FastConcat(" count: ")
.FastConcat(T2entities.count)
.FastConcat(" Entity 3: ".FastConcat(typeof(T3).ToString()))
.FastConcat(" count: ").FastConcat(T3entities.count)));
.FastConcat(typeof(T1).ToString()).FastConcat(" count: ")
.FastConcat(T1entities.count).FastConcat(
" Entity 2: ".FastConcat(typeof(T2).ToString()).FastConcat(" count: ")
.FastConcat(T2entities.count)
.FastConcat(" Entity 3: ".FastConcat(typeof(T3).ToString()))
.FastConcat(" count: ").FastConcat(T3entities.count)
.FastConcat(" Entity 4: ".FastConcat(typeof(T4).ToString()))
.FastConcat(" count: ").FastConcat(T4entities.count)));
#endif

return new EntityCollection<T1, T2, T3, T4>(T1entities, T2entities, T3entities, T4entities);
@@ -142,13 +150,20 @@ namespace Svelto.ECS
return new GroupsEnumerable<T>(this, groups);
}

/// <summary>
/// Note: Remember that EntityViewComponents are always put at the end of the generic parameters tuple.
/// It won't compile otherwise
/// </summary>
/// <returns></returns>
public GroupsEnumerable<T1, T2> QueryEntities<T1, T2>(in LocalFasterReadOnlyList<ExclusiveGroupStruct> groups)
where T1 : struct, IEntityComponent where T2 : struct, IEntityComponent
{
return new GroupsEnumerable<T1, T2>(this, groups);
}

public GroupsEnumerable<T1, T2, T3> QueryEntities<T1, T2, T3>(in LocalFasterReadOnlyList<ExclusiveGroupStruct> groups)
public GroupsEnumerable<T1, T2, T3> QueryEntities<T1, T2, T3>
(in LocalFasterReadOnlyList<ExclusiveGroupStruct> groups)
where T1 : struct, IEntityComponent where T2 : struct, IEntityComponent where T3 : struct, IEntityComponent
{
return new GroupsEnumerable<T1, T2, T3>(this, groups);
@@ -156,8 +171,10 @@ namespace Svelto.ECS

public GroupsEnumerable<T1, T2, T3, T4> QueryEntities<T1, T2, T3, T4>
(in LocalFasterReadOnlyList<ExclusiveGroupStruct> groups)
where T1 : struct, IEntityComponent where T2 : struct, IEntityComponent
where T3 : struct, IEntityComponent where T4 : struct, IEntityComponent
where T1 : struct, IEntityComponent
where T2 : struct, IEntityComponent
where T3 : struct, IEntityComponent
where T4 : struct, IEntityComponent
{
return new GroupsEnumerable<T1, T2, T3, T4>(this, groups);
}
@@ -167,7 +184,7 @@ namespace Svelto.ECS
where T : struct, IEntityComponent
{
if (SafeQueryEntityDictionary<T>(groupStructId, out var typeSafeDictionary) == false)
throw new EntityGroupNotFoundException(typeof(T) , groupStructId.ToName());
throw new EntityGroupNotFoundException(typeof(T), groupStructId.ToName());

return (typeSafeDictionary as ITypeSafeDictionary<T>).ToEGIDMapper(groupStructId);
}
@@ -239,11 +256,12 @@ namespace Svelto.ECS
public bool IsDisposing => _enginesRoot._isDisposing;

[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal bool SafeQueryEntityDictionary<T>(out ITypeSafeDictionary typeSafeDictionary,
FasterDictionary<RefWrapperType, ITypeSafeDictionary> entitiesInGroupPerType)
where T : IEntityComponent
internal bool SafeQueryEntityDictionary<T>
(out ITypeSafeDictionary typeSafeDictionary
, FasterDictionary<RefWrapperType, ITypeSafeDictionary> entitiesInGroupPerType) where T : IEntityComponent
{
if (entitiesInGroupPerType.TryGetValue(new RefWrapperType(TypeCache<T>.type), out var safeDictionary) == false)
if (entitiesInGroupPerType.TryGetValue(new RefWrapperType(TypeCache<T>.type), out var safeDictionary)
== false)
{
typeSafeDictionary = default;
return false;
@@ -254,10 +272,10 @@ namespace Svelto.ECS

return true;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal bool SafeQueryEntityDictionary<T>(ExclusiveGroupStruct group, out ITypeSafeDictionary typeSafeDictionary)
where T : IEntityComponent
internal bool SafeQueryEntityDictionary<T>
(ExclusiveGroupStruct group, out ITypeSafeDictionary typeSafeDictionary) where T : IEntityComponent
{
if (UnsafeQueryEntityDictionary(group, TypeCache<T>.type, out var safeDictionary) == false)
{
@@ -272,7 +290,8 @@ namespace Svelto.ECS
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal bool UnsafeQueryEntityDictionary(ExclusiveGroupStruct group, Type type, out ITypeSafeDictionary typeSafeDictionary)
internal bool UnsafeQueryEntityDictionary
(ExclusiveGroupStruct group, Type type, out ITypeSafeDictionary typeSafeDictionary)
{
//search for the group
if (groupEntityComponentsDB.TryGetValue(group, out var entitiesInGroupPerType) == false)
@@ -285,36 +304,6 @@ namespace Svelto.ECS
return entitiesInGroupPerType.TryGetValue(new RefWrapperType(type), out typeSafeDictionary);
}

internal bool FindIndex(uint entityID, ExclusiveGroupStruct @group, Type type, out uint index)
{
EGID entityGID = new EGID(entityID, @group);

index = default;
if (UnsafeQueryEntityDictionary(@group, type, out var safeDictionary) == false)
return false;

if (safeDictionary.TryFindIndex(entityGID.entityID, out index) == false)
return false;

return true;
}

internal uint GetIndex(uint entityID, ExclusiveGroupStruct @group, Type type)
{
EGID entityGID = new EGID(entityID, @group);
if (UnsafeQueryEntityDictionary(@group, type, out var safeDictionary) == false)
{
throw new EntityNotFoundException(entityGID, type);
}

if (safeDictionary.TryFindIndex(entityGID.entityID, out var index) == false)
throw new EntityNotFoundException(entityGID, type);

return index;
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
static IBuffer<T> RetrieveEmptyEntityComponentArray<T>() where T : struct, IEntityComponent
{
@@ -353,14 +342,16 @@ namespace Svelto.ECS
//group, then indexable per type, then indexable per EGID. however the TypeSafeDictionary can return an array of
//values directly, that can be iterated over, so that is possible to iterate over all the entity components of
//a specific type inside a specific group.
FasterDictionary<ExclusiveGroupStruct, FasterDictionary<RefWrapperType, ITypeSafeDictionary>>
FasterDictionary<ExclusiveGroupStruct, FasterDictionary<RefWrapperType, ITypeSafeDictionary>>
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
// <EntityComponentType <groupID <entityID, EntityComponent>>>
//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
// <EntityComponentType <groupID <entityID, EntityComponent>>>
FasterDictionary<RefWrapperType, FasterDictionary<ExclusiveGroupStruct, ITypeSafeDictionary>> groupsPerEntity =>
_enginesRoot._groupsPerEntity;

EnginesRoot.LocatorMap _entityReferencesMap;
}
}

+ 1
- 1
Svelto.ECS/Core/EntityCollection.cs View File

@@ -6,7 +6,7 @@ namespace Svelto.ECS
{
public readonly ref struct EntityCollection<T> where T : struct, IEntityComponent
{
static readonly bool IsUnmanaged = TypeSafeDictionary<T>.IsUnmanaged;
static readonly bool IsUnmanaged = TypeSafeDictionary<T>.isUnmanaged;

public EntityCollection(IBuffer<T> buffer, uint count) : this()
{


+ 118
- 57
Svelto.ECS/Core/EntityDescriptor/DynamicEntityDescriptor.cs View File

@@ -7,6 +7,8 @@ namespace Svelto.ECS
/// DynamicEntityDescriptor can be used to add entity components to an existing EntityDescriptor that act as flags,
/// at building time.
/// This method allocates, so it shouldn't be abused
/// TODO:Unit test cases where there could be duplicates of components, especially EntityInfoComponent.
/// Test DynamicED of DynamicED
/// </summary>
/// <typeparam name="TType"></typeparam>
public struct DynamicEntityDescriptor<TType> : IDynamicEntityDescriptor where TType : IEntityDescriptor, new()
@@ -14,87 +16,156 @@ namespace Svelto.ECS
internal DynamicEntityDescriptor(bool isExtendible) : this()
{
var defaultEntities = EntityDescriptorTemplate<TType>.descriptor.componentsToBuild;
var length = defaultEntities.Length;
var length = defaultEntities.Length;

ComponentsToBuild = new IComponentBuilder[length + 1];
Array.Copy(defaultEntities, 0, ComponentsToBuild, 0, length);
if (FetchEntityInfoComponent(defaultEntities) == -1)
{
_componentsToBuild = new IComponentBuilder[length + 1];

//assign it after otherwise the previous copy will overwrite the value in case the item
//is already present
ComponentsToBuild[length] = new ComponentBuilder<EntityInfoComponent>
(
new EntityInfoComponent
Array.Copy(defaultEntities, 0, _componentsToBuild, 0, length);
//assign it after otherwise the previous copy will overwrite the value in case the item
//is already present
_componentsToBuild[length] = new ComponentBuilder<EntityInfoComponent>(new EntityInfoComponent
{
componentsToBuild = ComponentsToBuild
}
);
componentsToBuild = _componentsToBuild
});
}
else
{
_componentsToBuild = new IComponentBuilder[length];

Array.Copy(defaultEntities, 0, _componentsToBuild, 0, length);
}
}

public DynamicEntityDescriptor(IComponentBuilder[] extraEntityBuilders) : this()
public DynamicEntityDescriptor(IComponentBuilder[] extraEntityBuilders) : this(true)
{
var extraEntitiesLength = extraEntityBuilders.Length;

ComponentsToBuild = Construct(extraEntitiesLength, extraEntityBuilders,
EntityDescriptorTemplate<TType>.descriptor.componentsToBuild);
_componentsToBuild = Construct(extraEntitiesLength, extraEntityBuilders);
}

public DynamicEntityDescriptor(FasterList<IComponentBuilder> extraEntityBuilders) : this()
public DynamicEntityDescriptor(FasterList<IComponentBuilder> extraEntityBuilders) : this(true)
{
var extraEntities = extraEntityBuilders.ToArrayFast(out _);
var extraEntities = extraEntityBuilders.ToArrayFast(out _);
var extraEntitiesLength = extraEntityBuilders.count;

ComponentsToBuild = Construct((int) extraEntitiesLength, extraEntities,
EntityDescriptorTemplate<TType>.descriptor.componentsToBuild);
_componentsToBuild = Construct((int)extraEntitiesLength, extraEntities);
}

public void ExtendWith<T>() where T : IEntityDescriptor, new()
{
var newEntitiesToBuild = EntityDescriptorTemplate<T>.descriptor.componentsToBuild;
var extraEntities = EntityDescriptorTemplate<T>.descriptor.componentsToBuild;

ComponentsToBuild = Construct(newEntitiesToBuild.Length, newEntitiesToBuild, ComponentsToBuild);
_componentsToBuild = Construct(extraEntities.Length, extraEntities);
}
public void ExtendWith(IComponentBuilder[] extraEntities)
{
ComponentsToBuild = Construct(extraEntities.Length, extraEntities, ComponentsToBuild);
_componentsToBuild = Construct(extraEntities.Length, extraEntities);
}

public void ExtendWith(FasterList<IComponentBuilder> extraEntities)
{
_componentsToBuild = Construct(extraEntities.count, extraEntities.ToArrayFast(out _));
}

public void Add<T>() where T : struct, IEntityComponent
{
IComponentBuilder[] extraEntities = { new ComponentBuilder<T>() };
_componentsToBuild = Construct(extraEntities.Length, extraEntities);
}

static IComponentBuilder[] Construct(int extraEntitiesLength, IComponentBuilder[] extraEntities,
IComponentBuilder[] startingEntities)
public void Add<T, U>() where T : struct, IEntityComponent where U : struct, IEntityComponent
{
IComponentBuilder[] localEntitiesToBuild;
IComponentBuilder[] extraEntities = { new ComponentBuilder<T>(), new ComponentBuilder<U>() };
_componentsToBuild = Construct(extraEntities.Length, extraEntities);
}

if (extraEntitiesLength == 0)
public void Add<T, U, V>() where T : struct, IEntityComponent
where U : struct, IEntityComponent
where V : struct, IEntityComponent
{
IComponentBuilder[] extraEntities =
{
localEntitiesToBuild = startingEntities;
return localEntitiesToBuild;
}
new ComponentBuilder<T>(), new ComponentBuilder<U>(), new ComponentBuilder<V>()
};
_componentsToBuild = Construct(extraEntities.Length, extraEntities);
}

var defaultEntities = startingEntities;
var index = SetupEntityInfoComponent(defaultEntities, out localEntitiesToBuild, extraEntitiesLength);
/// <summary>
/// Note: unluckily I didn't design the serialization system to be component order independent, so unless
/// I do something about it, this method cannot be optimized, the logic of the component order must stay
/// untouched (no reordering, no use of dictionaries). Components order must stay as it comes, as
/// well as extracomponents order.
/// Speed, however, is not a big issue for this class, as the data is always composed once per entity descriptor
/// at static constructor time
/// </summary>
/// <returns></returns>
IComponentBuilder[] Construct(int extraComponentsLength, IComponentBuilder[] extraComponents)
{
IComponentBuilder[] MergeLists
(IComponentBuilder[] startingComponents, IComponentBuilder[] newComponents, int newComponentsLength)
{
var startComponents =
new FasterDictionary<RefWrapper<IComponentBuilder, ComponentBuilderComparer>, IComponentBuilder>();
var xtraComponents =
new FasterDictionary<RefWrapper<IComponentBuilder, ComponentBuilderComparer>, IComponentBuilder>();

for (uint i = 0; i < startingComponents.Length; i++)
startComponents
[new RefWrapper<IComponentBuilder, ComponentBuilderComparer>(startingComponents[i])] =
startingComponents[i];

for (uint i = 0; i < newComponentsLength; i++)
xtraComponents[new RefWrapper<IComponentBuilder, ComponentBuilderComparer>(newComponents[i])] =
newComponents[i];

Array.Copy(extraEntities, 0, localEntitiesToBuild, defaultEntities.Length, extraEntitiesLength);
xtraComponents.Exclude(startComponents);

//assign it after otherwise the previous copy will overwrite the value in case the item
//is already present
localEntitiesToBuild[index] = new ComponentBuilder<EntityInfoComponent>
(
new EntityInfoComponent
if (newComponentsLength != xtraComponents.count)
{
componentsToBuild = localEntitiesToBuild
newComponentsLength = xtraComponents.count;

uint index = 0;
foreach (var couple in xtraComponents)
newComponents[index++] = couple.Key.value;
}
);

return localEntitiesToBuild;
IComponentBuilder[] componentBuilders =
new IComponentBuilder[newComponentsLength + startingComponents.Length];

Array.Copy(startingComponents, 0, componentBuilders, 0, startingComponents.Length);
Array.Copy(newComponents, 0, componentBuilders, startingComponents.Length, newComponentsLength);

var entityInfoComponentIndex = FetchEntityInfoComponent(componentBuilders);
DBC.ECS.Check.Assert(entityInfoComponentIndex != -1);

componentBuilders[entityInfoComponentIndex] = new ComponentBuilder<EntityInfoComponent>(
new EntityInfoComponent
{
componentsToBuild = componentBuilders
});

return componentBuilders;
}

if (extraComponentsLength == 0)
{
return _componentsToBuild;
}

var safeCopyOfExtraComponents = new IComponentBuilder[extraComponentsLength];
Array.Copy(extraComponents, safeCopyOfExtraComponents, extraComponentsLength);

return MergeLists(_componentsToBuild, safeCopyOfExtraComponents, extraComponentsLength);
}

static int SetupEntityInfoComponent(IComponentBuilder[] defaultEntities, out IComponentBuilder[] componentsToBuild,
int extraLenght)
static int FetchEntityInfoComponent(IComponentBuilder[] defaultEntities)
{
int length = defaultEntities.Length;
int index = -1;
int index = -1;

for (var i = 0; i < length; i++)
{
@@ -106,21 +177,11 @@ namespace Svelto.ECS
}
}

if (index == -1)
{
index = length + extraLenght;
componentsToBuild = new IComponentBuilder[index + 1];
}
else
componentsToBuild = new IComponentBuilder[length + extraLenght];

Array.Copy(defaultEntities, 0, componentsToBuild, 0, length);

return index;
}

public IComponentBuilder[] componentsToBuild => ComponentsToBuild;
public IComponentBuilder[] componentsToBuild => _componentsToBuild;

IComponentBuilder[] ComponentsToBuild;
IComponentBuilder[] _componentsToBuild;
}
}

+ 19
- 5
Svelto.ECS/Core/EntityDescriptor/ExtendibleEntityDescriptor.cs View File

@@ -21,7 +21,7 @@ namespace Svelto.ECS
/// }
/// </summary>
/// <typeparam name="TType"></typeparam>
public class ExtendibleEntityDescriptor<TType> : IDynamicEntityDescriptor where TType : IEntityDescriptor, new()
public abstract class ExtendibleEntityDescriptor<TType> : IDynamicEntityDescriptor where TType : IEntityDescriptor, new()
{
static ExtendibleEntityDescriptor()
{
@@ -30,30 +30,44 @@ namespace Svelto.ECS
$"SerializableEntityDescriptors cannot be used as base entity descriptor: {typeof(TType)}");
}

public ExtendibleEntityDescriptor(IComponentBuilder[] extraEntities)
protected ExtendibleEntityDescriptor(IComponentBuilder[] extraEntities)
{
_dynamicDescriptor = new DynamicEntityDescriptor<TType>(extraEntities);
}

public ExtendibleEntityDescriptor()
protected ExtendibleEntityDescriptor()
{
_dynamicDescriptor = new DynamicEntityDescriptor<TType>(true);
}

public ExtendibleEntityDescriptor<TType> ExtendWith<T>() where T : IEntityDescriptor, new()
protected ExtendibleEntityDescriptor<TType> ExtendWith<T>() where T : IEntityDescriptor, new()
{
_dynamicDescriptor.ExtendWith<T>();

return this;
}

public ExtendibleEntityDescriptor<TType> ExtendWith(IComponentBuilder[] extraEntities)
protected ExtendibleEntityDescriptor<TType> ExtendWith(IComponentBuilder[] extraEntities)
{
_dynamicDescriptor.ExtendWith(extraEntities);

return this;
}


protected void Add<T>() where T : struct, IEntityComponent
{
_dynamicDescriptor.Add<T>();
}
protected void Add<T, U>() where T : struct, IEntityComponent where U : struct, IEntityComponent
{
_dynamicDescriptor.Add<T, U>();
}
protected void Add<T, U, V>() where T : struct, IEntityComponent where U : struct, IEntityComponent where V : struct, IEntityComponent
{
_dynamicDescriptor.Add<T, U, V>();
}

public IComponentBuilder[] componentsToBuild => _dynamicDescriptor.componentsToBuild;

DynamicEntityDescriptor<TType> _dynamicDescriptor;


+ 3
- 3
Svelto.ECS/Core/EntityDescriptor/GenericEntityDescriptor.cs View File

@@ -2,7 +2,7 @@
{
public abstract class GenericEntityDescriptor<T> : IEntityDescriptor where T : struct, IEntityComponent
{
static readonly IComponentBuilder[] _componentBuilders;
internal static readonly IComponentBuilder[] _componentBuilders;
static GenericEntityDescriptor() { _componentBuilders = new IComponentBuilder[] {new ComponentBuilder<T>()}; }

public IComponentBuilder[] componentsToBuild => _componentBuilders;
@@ -11,7 +11,7 @@
public abstract class GenericEntityDescriptor<T, U> : IEntityDescriptor
where T : struct, IEntityComponent where U : struct, IEntityComponent
{
static readonly IComponentBuilder[] _componentBuilders;
internal static readonly IComponentBuilder[] _componentBuilders;

static GenericEntityDescriptor()
{
@@ -24,7 +24,7 @@
public abstract class GenericEntityDescriptor<T, U, V> : IEntityDescriptor
where T : struct, IEntityComponent where U : struct, IEntityComponent where V : struct, IEntityComponent
{
static readonly IComponentBuilder[] _componentBuilders;
internal static readonly IComponentBuilder[] _componentBuilders;

static GenericEntityDescriptor()
{


+ 1
- 1
Svelto.ECS/Core/EntityDescriptorTemplate.cs View File

@@ -2,7 +2,7 @@ using System;

namespace Svelto.ECS
{
static class EntityDescriptorTemplate<TType> where TType : IEntityDescriptor, new()
public static class EntityDescriptorTemplate<TType> where TType : IEntityDescriptor, new()
{
static EntityDescriptorTemplate()
{


+ 39
- 31
Svelto.ECS/Core/EntityFactory.cs View File

@@ -7,76 +7,84 @@ namespace Svelto.ECS.Internal
static class EntityFactory
{
public static FasterDictionary<RefWrapperType, ITypeSafeDictionary> BuildGroupedEntities
(EGID egid, EnginesRoot.DoubleBufferedEntitiesToAdd groupEntitiesToAdd
, IComponentBuilder[] componentsToBuild, IEnumerable<object> implementors, Type implementorType)
(EGID egid, EnginesRoot.DoubleBufferedEntitiesToAdd groupEntitiesToAdd, IComponentBuilder[] componentsToBuild
, IEnumerable<object> implementors
#if DEBUG && !PROFILE_SVELTO
, Type descriptorType
#endif
)
{
var group = FetchEntityGroup(egid.groupID, groupEntitiesToAdd);

BuildEntitiesAndAddToGroup(egid, group, componentsToBuild, implementors, implementorType);
BuildEntitiesAndAddToGroup(egid, group, componentsToBuild, implementors
#if DEBUG && !PROFILE_SVELTO
, descriptorType
#endif
);

return group;
}

static FasterDictionary<RefWrapperType, ITypeSafeDictionary> FetchEntityGroup(ExclusiveGroupStruct groupID,
EnginesRoot.DoubleBufferedEntitiesToAdd groupEntityComponentsByType)
static FasterDictionary<RefWrapperType, ITypeSafeDictionary> FetchEntityGroup
(ExclusiveGroupStruct groupID, EnginesRoot.DoubleBufferedEntitiesToAdd groupEntityComponentsByType)
{
if (groupEntityComponentsByType.current.TryGetValue(groupID, out var group) == false)
if (groupEntityComponentsByType.current.TryGetValue((uint) groupID, out var group) == false)
{
group = new FasterDictionary<RefWrapperType, ITypeSafeDictionary>();
groupEntityComponentsByType.current.Add(groupID, group);
groupEntityComponentsByType.current.Add((uint) groupID, group);
}

if (groupEntityComponentsByType.currentEntitiesCreatedPerGroup.TryGetValue(groupID, out var value) == false)
groupEntityComponentsByType.currentEntitiesCreatedPerGroup[groupID] = 0;
else
groupEntityComponentsByType.currentEntitiesCreatedPerGroup[groupID] = value+1;
//track the number of entities created so far in the group.
groupEntityComponentsByType.IncrementEntityCount(groupID);

return group;
}

static void BuildEntitiesAndAddToGroup
(EGID entityID, FasterDictionary<RefWrapperType, ITypeSafeDictionary> @group
, IComponentBuilder[] componentBuilders, IEnumerable<object> implementors, Type implementorType)
, IComponentBuilder[] componentBuilders, IEnumerable<object> implementors
#if DEBUG && !PROFILE_SVELTO
, Type descriptorType
#endif
)
{
var count = componentBuilders.Length;
#if DEBUG && !PROFILE_SVELTO
DBC.ECS.Check.Require(componentBuilders != null, $"Invalid Entity Descriptor {descriptorType}");
#endif
var numberOfComponents = componentBuilders.Length;

#if DEBUG && !PROFILE_SVELTO
HashSet<Type> types = new HashSet<Type>();

for (var index = 0; index < count; ++index)
for (var index = 0; index < numberOfComponents; ++index)
{
var entityComponentType = componentBuilders[index].GetEntityComponentType();
if (types.Contains(entityComponentType))
{
throw new ECSException($"EntityBuilders must be unique inside an EntityDescriptor. Descriptor Type {implementorType} Component Type: {entityComponentType}");
throw new ECSException(
$"EntityBuilders must be unique inside an EntityDescriptor. Descriptor Type {descriptorType} Component Type: {entityComponentType}");
}

types.Add(entityComponentType);
}
#endif
for (var index = 0; index < count; ++index)
for (var index = 0; index < numberOfComponents; ++index)
{
var entityComponentBuilder = componentBuilders[index];
var entityComponentType = entityComponentBuilder.GetEntityComponentType();

BuildEntity(entityID, @group, entityComponentType, entityComponentBuilder, implementors);
BuildEntity(entityID, @group, entityComponentBuilder, implementors);
}
}

static void BuildEntity(EGID entityID, FasterDictionary<RefWrapperType, ITypeSafeDictionary> group,
Type entityComponentType, IComponentBuilder componentBuilder, IEnumerable<object> implementors)
static void BuildEntity(EGID entityID, FasterDictionary<RefWrapperType, ITypeSafeDictionary> group
, IComponentBuilder componentBuilder, IEnumerable<object> implementors)
{
var entityComponentsPoolWillBeCreated =
group.TryGetValue(new RefWrapperType(entityComponentType), out var safeDictionary) == false;

//passing the undefined entityComponentsByType inside the entityComponentBuilder will allow it to be created with the
//correct type and casted back to the undefined list. that's how the list will be eventually of the target
//type.
componentBuilder.BuildEntityAndAddToList(ref safeDictionary, entityID, implementors);
var entityComponentType = componentBuilder.GetEntityComponentType();
var safeDictionary = group.GetOrCreate(new RefWrapperType(entityComponentType), (ref IComponentBuilder cb) => cb.CreateDictionary(1), ref componentBuilder);

if (entityComponentsPoolWillBeCreated)
group.Add(new RefWrapperType(entityComponentType), safeDictionary);
//if the safeDictionary hasn't been created yet, it will be created inside this method.
componentBuilder.BuildEntityAndAddToList(safeDictionary, entityID, implementors);
}
}
}

+ 21
- 16
Svelto.ECS/Core/EntityInitializer.cs View File

@@ -1,22 +1,27 @@
using Svelto.DataStructures;
using Svelto.ECS.Internal;
using Svelto.ECS.Reference;

namespace Svelto.ECS
{
public readonly ref struct EntityInitializer
{
public EntityInitializer(EGID id, FasterDictionary<RefWrapperType, ITypeSafeDictionary> group)
public EntityInitializer
(EGID id, FasterDictionary<RefWrapperType, ITypeSafeDictionary> group, in EntityReference reference)
{
_group = group;
_ID = id;
_group = group;
_ID = id;
this.reference = reference;
}

public EGID EGID => _ID;
public EGID EGID => _ID;
public readonly EntityReference reference;

public void Init<T>(T initializer) where T : struct, IEntityComponent
{
if (_group.TryGetValue(new RefWrapperType(ComponentBuilder<T>.ENTITY_COMPONENT_TYPE),
out var typeSafeDictionary) == false) return;
if (_group.TryGetValue(new RefWrapperType(ComponentBuilder<T>.ENTITY_COMPONENT_TYPE)
, out var typeSafeDictionary) == false)
return;

var dictionary = (ITypeSafeDictionary<T>) typeSafeDictionary;

@@ -29,23 +34,23 @@ namespace Svelto.ECS

public ref T GetOrCreate<T>() where T : struct, IEntityComponent
{
ref var entityDictionary = ref _group.GetOrCreate(new RefWrapperType(ComponentBuilder<T>.ENTITY_COMPONENT_TYPE)
, TypeSafeDictionaryFactory<T>.Create);
ref var entityDictionary = ref _group.GetOrCreate(
new RefWrapperType(ComponentBuilder<T>.ENTITY_COMPONENT_TYPE), TypeSafeDictionaryFactory<T>.Create);
var dictionary = (ITypeSafeDictionary<T>) entityDictionary;

return ref dictionary.GetOrCreate(_ID.entityID);
}
public ref T Get<T>() where T : struct, IEntityComponent
{
return ref (_group[new RefWrapperType(ComponentBuilder<T>.ENTITY_COMPONENT_TYPE)] as ITypeSafeDictionary<T>)[
_ID.entityID];
return ref (_group[new RefWrapperType(ComponentBuilder<T>.ENTITY_COMPONENT_TYPE)] as ITypeSafeDictionary<T>)
[_ID.entityID];
}
public bool Has<T>() where T : struct, IEntityComponent
{
if (_group.TryGetValue(new RefWrapperType(ComponentBuilder<T>.ENTITY_COMPONENT_TYPE),
out var typeSafeDictionary))
if (_group.TryGetValue(new RefWrapperType(ComponentBuilder<T>.ENTITY_COMPONENT_TYPE)
, out var typeSafeDictionary))
{
var dictionary = (ITypeSafeDictionary<T>) typeSafeDictionary;

@@ -55,8 +60,8 @@ namespace Svelto.ECS

return false;
}
readonly EGID _ID;
readonly EGID _ID;
readonly FasterDictionary<RefWrapperType, ITypeSafeDictionary> _group;
}
}

+ 227
- 0
Svelto.ECS/Core/EntityReference/EnginesRoot.LocatorMap.cs View File

@@ -0,0 +1,227 @@
using System.Runtime.CompilerServices;
using Svelto.Common;
using Svelto.DataStructures;
using Svelto.ECS.DataStructures;
using Svelto.ECS.Reference;

namespace Svelto.ECS
{
// The EntityLocatorMap provides a bidirectional map to help locate entities without using an EGID which might
// change in runtime. The Entity Locator map uses a reusable unique identifier struct called EntityLocator to
// find the last known EGID from last entity submission.
public partial class EnginesRoot
{
public struct LocatorMap
{
internal EntityReference ClaimReference()
{
int tempFreeIndex;
int newFreeIndex;
uint version;

do
{
tempFreeIndex = _nextFreeIndex;
// Check if we need to create a new EntityLocator or whether we can recycle an existing one.
if ((uint)tempFreeIndex >= _entityReferenceMap.count)
{
newFreeIndex = tempFreeIndex + 1;
version = 0;
}
else
{
ref EntityReferenceMapElement element = ref _entityReferenceMap[tempFreeIndex];
// The recycle entities form a linked list, using the egid.entityID to store the next element.
newFreeIndex = (int)element.egid.entityID;
version = element.version;
}
} while (tempFreeIndex != _nextFreeIndex.CompareExchange(newFreeIndex, tempFreeIndex));

#if DEBUG && !PROFILE_SVELTO
// This code should be safe since we own the tempFreeIndex, this allows us to later check that nothing went wrong.
if (tempFreeIndex < _entityReferenceMap.count)
{
_entityReferenceMap[tempFreeIndex] = new EntityReferenceMapElement(new EGID(0, 0), version);
}
#endif

return new EntityReference((uint)tempFreeIndex + 1, version);
}

internal void SetReference(EntityReference reference, EGID egid)
{
// Since references can be claimed in parallel now, it might happen that they are set out of order,
// so we need to resize instead of add.
if (reference.index >= _entityReferenceMap.count)
{
#if DEBUG && !PROFILE_SVELTO //THIS IS TO VALIDATE DATE DBC LIKE
for (var i = _entityReferenceMap.count; i <= reference.index; i++)
{
_entityReferenceMap.Add(new EntityReferenceMapElement(default, 0));
}
#else
_entityReferenceMap.AddAt(reference.index);
#endif
}

#if DEBUG && !PROFILE_SVELTO
// These debug tests should be enough to detect if indices are being used correctly under native factories
if (_entityReferenceMap[reference.index].version != reference.version ||
_entityReferenceMap[reference.index].egid.groupID != ExclusiveGroupStruct.Invalid)
{
throw new ECSException("Entity reference already set. This should never happen, please report it.");
}
#endif

_entityReferenceMap[reference.index] = new EntityReferenceMapElement(egid, reference.version);

// Update reverse map from egid to locator.
var groupMap =
_egidToReferenceMap.GetOrCreate(egid.groupID
, () => new SharedSveltoDictionaryNative<uint, EntityReference>(0));
groupMap[egid.entityID] = reference;
}

internal void UpdateEntityReference(EGID from, EGID to)
{
var reference = FetchAndRemoveReference(@from);

_entityReferenceMap[reference.index].egid = to;

var groupMap =
_egidToReferenceMap.GetOrCreate(
to.groupID, () => new SharedSveltoDictionaryNative<uint, EntityReference>(0));
groupMap[to.entityID] = reference;
}

internal void RemoveEntityReference(EGID egid)
{
var reference = FetchAndRemoveReference(@egid);

// Invalidate the entity locator element by bumping its version and setting the egid to point to a not existing element.
ref var entityReferenceMapElement = ref _entityReferenceMap[reference.index];
entityReferenceMapElement.egid = new EGID((uint)(int)_nextFreeIndex, 0);
entityReferenceMapElement.version++;

// Mark the element as the last element used.
_nextFreeIndex.Set((int)reference.index);
}

EntityReference FetchAndRemoveReference(EGID @from)
{
var egidToReference = _egidToReferenceMap[@from.groupID];
var reference = egidToReference[@from.entityID];
egidToReference.Remove(@from.entityID);

return reference;
}

internal void RemoveAllGroupReferenceLocators(ExclusiveGroupStruct groupId)
{
if (_egidToReferenceMap.TryGetValue(groupId, out var groupMap) == false)
return;

// We need to traverse all entities in the group and remove the locator using the egid.
// RemoveLocator would modify the enumerator so this is why we traverse the dictionary from last to first.
foreach (var item in groupMap)
RemoveEntityReference(new EGID(item.Key, groupId));

_egidToReferenceMap.Remove(groupId);
}

internal void UpdateAllGroupReferenceLocators(ExclusiveGroupStruct fromGroupId, uint toGroupId)
{
if (_egidToReferenceMap.TryGetValue(fromGroupId, out var groupMap) == false)
return;

// We need to traverse all entities in the group and update the locator using the egid.
// UpdateLocator would modify the enumerator so this is why we traverse the dictionary from last to first.
foreach (var item in groupMap)
UpdateEntityReference(new EGID(item.Key, fromGroupId), new EGID(item.Key, toGroupId));

_egidToReferenceMap.Remove(fromGroupId);
}

public EntityReference GetEntityReference(EGID egid)
{
if (_egidToReferenceMap.TryGetValue(egid.groupID, out var groupMap))
{
if (groupMap.TryGetValue(egid.entityID, out var locator))
return locator;
#if DEBUG && !PROFILE_SVELTO
else throw new ECSException($"Entity {egid} does not exist. Are you creating it? Try getting it from initializer.reference.");
#endif
}

return EntityReference.Invalid;
}

public bool TryGetEGID(EntityReference reference, out EGID egid)
{
egid = default;
if (reference == EntityReference.Invalid)
return false;
// Make sure we are querying for the current version of the locator.
// Otherwise the locator is pointing to a removed entity.
if (_entityReferenceMap[reference.index].version == reference.version)
{
egid = _entityReferenceMap[reference.index].egid;
return true;
}

return false;
}

public EGID GetEGID(EntityReference reference)
{
if (reference == EntityReference.Invalid)
throw new ECSException("Invalid Reference");
// Make sure we are querying for the current version of the locator.
// Otherwise the locator is pointing to a removed entity.
if (_entityReferenceMap[reference.index].version != reference.version)
throw new ECSException("outdated Reference");

return _entityReferenceMap[reference.index].egid;
}

internal void PreallocateReferenceMaps(ExclusiveGroupStruct groupID, uint size)
{
_egidToReferenceMap
.GetOrCreate(groupID, () => new SharedSveltoDictionaryNative<uint, EntityReference>(size))
.SetCapacity(size);

_entityReferenceMap.Resize(size);
}

internal void InitEntityReferenceMap()
{
_nextFreeIndex = SharedNativeInt.Create(0, Allocator.Persistent);
_entityReferenceMap =
new NativeDynamicArrayCast<EntityReferenceMapElement>(
NativeDynamicArray.Alloc<EntityReferenceMapElement>());
_egidToReferenceMap =
new SharedSveltoDictionaryNative<ExclusiveGroupStruct,
SharedSveltoDictionaryNative<uint, EntityReference>>(0);
}

internal void DisposeEntityReferenceMap()
{
_nextFreeIndex.Dispose();
_entityReferenceMap.Dispose();

foreach (var element in _egidToReferenceMap)
element.Value.Dispose();
_egidToReferenceMap.Dispose();
}

SharedNativeInt _nextFreeIndex;
NativeDynamicArrayCast<EntityReferenceMapElement> _entityReferenceMap;

SharedSveltoDictionaryNative<ExclusiveGroupStruct, SharedSveltoDictionaryNative<uint, EntityReference>>
_egidToReferenceMap;
}

internal LocatorMap entityLocator => _entityLocator;
LocatorMap _entityLocator;
}
}

+ 32
- 0
Svelto.ECS/Core/EntityReference/EntitiesDB.References.cs View File

@@ -0,0 +1,32 @@
using System.Runtime.CompilerServices;
using Svelto.ECS.Reference;

namespace Svelto.ECS
{
public partial class EntitiesDB
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool TryGetEGID(EntityReference entityReference, out EGID egid)
{
return _entityReferencesMap.TryGetEGID(entityReference, out egid);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public EGID GetEGID(EntityReference entityReference)
{
return _entityReferencesMap.GetEGID(entityReference);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public EnginesRoot.LocatorMap GetEntityLocator()
{
return _entityReferencesMap;
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public EntityReference GetEntityReference(EGID egid)
{
return _entityReferencesMap.GetEntityReference(egid);
}
}
}

+ 78
- 0
Svelto.ECS/Core/EntityReference/EntityReference.cs View File

@@ -0,0 +1,78 @@
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

#pragma warning disable 660,661

namespace Svelto.ECS
{
/// <summary>
/// Todo: EntityReference shouldn't map EGIDs as dictionaries keys but directly the indices in the EntityDB arrays
/// </summary>
[Serialization.DoNotSerialize]
[Serializable]
[StructLayout(LayoutKind.Explicit)]
public struct EntityReference : IEquatable<EntityReference>
{
[FieldOffset(0)] public readonly uint uniqueID;
[FieldOffset(4)] public readonly uint version;
[FieldOffset(0)] readonly ulong _GID;

internal uint index => uniqueID - 1;

public static bool operator ==(EntityReference obj1, EntityReference obj2)
{
return obj1._GID == obj2._GID;
}

public static bool operator !=(EntityReference obj1, EntityReference obj2)
{
return obj1._GID != obj2._GID;
}

public EntityReference(uint uniqueId) : this(uniqueId, 0) {}

public EntityReference(uint uniqueId, uint version) : this()
{
_GID = MAKE_GLOBAL_ID(uniqueId, version);
}

public bool Equals(EntityReference other)
{
return _GID == other._GID;
}

public bool Equals(EntityReference x, EntityReference y)
{
return x._GID == y._GID;
}

public override string ToString()
{
return "id:".FastConcat(uniqueID).FastConcat(" version:").FastConcat(version);
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public EGID ToEGID(EntitiesDB entitiesDB)
{
DBC.ECS.Check.Require(this != Invalid, "Invalid Reference Used");

return entitiesDB.GetEGID(this);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool ToEGID(EntitiesDB entitiesDB, out EGID egid)
{
DBC.ECS.Check.Require(this != Invalid, "Invalid Reference Used");

return entitiesDB.TryGetEGID(this, out egid);
}

static ulong MAKE_GLOBAL_ID(uint uniqueId, uint version)
{
return (ulong)version << 32 | ((ulong)uniqueId & 0xFFFFFFFF);
}

public static EntityReference Invalid => default;
}
}

+ 20
- 0
Svelto.ECS/Core/EntityReference/EntityReferenceMapElement.cs View File

@@ -0,0 +1,20 @@
namespace Svelto.ECS.Reference
{
struct EntityReferenceMapElement
{
internal EGID egid;
internal uint version;

internal EntityReferenceMapElement(EGID egid)
{
this.egid = egid;
version = 0;
}

internal EntityReferenceMapElement(EGID egid, uint version)
{
this.egid = egid;
this.version = version;
}
}
}

+ 0
- 2
Svelto.ECS/Core/EntitySubmissionScheduler.cs View File

@@ -1,5 +1,3 @@
using System.Collections;

namespace Svelto.ECS.Schedulers
{
public abstract class EntitiesSubmissionScheduler


+ 27
- 31
Svelto.ECS/Core/EntityViewUtility.cs View File

@@ -32,7 +32,7 @@ namespace Svelto.ECS
const string NOT_FOUND_EXCEPTION =
"<color=teal>Svelto.ECS</color> Implementor not found for an EntityComponent. ";

public static void FillEntityComponent<T>
public static void SetEntityViewComponentImplementors<T>
(this IComponentBuilder componentBuilder, ref T entityComponent
, FasterList<KeyValuePair<Type, FastInvokeActionCast<T>>> entityComponentBlazingFastReflection
, IEnumerable<object> implementors
@@ -43,49 +43,45 @@ namespace Svelto.ECS
#endif
, Dictionary<Type, Type[]> cachedTypeInterfaces)
{
//efficient way to collect the fields of every EntityComponentType
var setters = FasterList<KeyValuePair<Type, FastInvokeActionCast<T>>>.NoVirt.ToArrayFast(
entityComponentBlazingFastReflection, out var count);
DBC.ECS.Check.Require(implementors != null, NULL_IMPLEMENTOR_ERROR.FastConcat(" entityComponent "
, componentBuilder
.GetEntityComponentType().ToString()));
//todo this should happen once per T, not once per Build<T>
if (implementors != null)
foreach (var implementor in implementors)
{
foreach (var implementor in implementors)
DBC.ECS.Check.Require(implementor != null, "invalid null implementor used to build an entity");
{
if (implementor != null)
{
var type = implementor.GetType();
var type = implementor.GetType();

if (cachedTypeInterfaces.TryGetValue(type, out var interfaces) == false)
interfaces = cachedTypeInterfaces[type] = type.GetInterfacesEx();
//fetch all the interfaces that the implementor implements
if (cachedTypeInterfaces.TryGetValue(type, out var interfaces) == false)
interfaces = cachedTypeInterfaces[type] = type.GetInterfacesEx();

for (var iindex = 0; iindex < interfaces.Length; iindex++)
{
var componentType = interfaces[iindex];
for (var iindex = 0; iindex < interfaces.Length; iindex++)
{
var componentType = interfaces[iindex];
//an implementor can implement multiple interfaces, so for each interface we reference
//the implementation object. Multiple entity view component fields can then be implemented
//by the same implementor
#if DEBUG && !PROFILE_SVELTO
if (implementorsByType.TryGetValue(componentType, out var implementorData))
{
implementorData.numberOfImplementations++;
implementorsByType[componentType] = implementorData;
}
else
implementorsByType[componentType] = new ECSTuple<object, int>(implementor, 1);
if (implementorsByType.TryGetValue(componentType, out var implementorData))
{
implementorData.numberOfImplementations++;
implementorsByType[componentType] = implementorData;
}
else
implementorsByType[componentType] = new ECSTuple<object, int>(implementor, 1);
#else
implementorsByType[componentType] = implementor;
#endif
}
}
#if DEBUG && !PROFILE_SVELTO
else
{
Console.Log(NULL_IMPLEMENTOR_ERROR.FastConcat(" entityComponent "
, componentBuilder
.GetEntityComponentType().ToString()));
}
#endif
}
}

//efficient way to collect the fields of every EntityComponentType
var setters = FasterList<KeyValuePair<Type, FastInvokeActionCast<T>>>.NoVirt.ToArrayFast(
entityComponentBlazingFastReflection, out var count);

for (var i = 0; i < count; i++)
{
var fieldSetter = setters[i];


+ 31
- 32
Svelto.ECS/Core/Filters/EntitiesDB.GroupFilters.cs View File

@@ -14,27 +14,27 @@ namespace Svelto.ECS
public readonly struct Filters
{
public Filters
(FasterDictionary<RefWrapperType, FasterDictionary<ExclusiveGroupStruct, GroupFilters>> filters)
(FasterDictionary<RefWrapperType, FasterDictionary<ExclusiveGroupStruct, GroupFilters>> filters)
{
_filters = filters;
_filters = filters;
}

public ref FilterGroup CreateOrGetFilterForGroup<T>(int filterID, ExclusiveGroupStruct groupID)
where T : struct, IEntityComponent
{
var refWrapper = TypeRefWrapper<T>.wrapper;
return ref CreateOrGetFilterForGroup(filterID, groupID, refWrapper);
}

ref FilterGroup CreateOrGetFilterForGroup(int filterID, ExclusiveGroupStruct groupID, RefWrapperType refWrapper)
internal ref FilterGroup CreateOrGetFilterForGroup
(int filterID, ExclusiveGroupStruct groupID, RefWrapperType refWrapper)
{
var fasterDictionary =
_filters.GetOrCreate(refWrapper, () => new FasterDictionary<ExclusiveGroupStruct, GroupFilters>());

GroupFilters filters =
fasterDictionary.GetOrCreate(
groupID, () => new GroupFilters(new SharedSveltoDictionaryNative<int, FilterGroup>(0), groupID));
GroupFilters filters = fasterDictionary.GetOrCreate(
groupID, () => new GroupFilters(new SharedSveltoDictionaryNative<int, FilterGroup>(0), groupID));

return ref filters.CreateOrGetFilter(filterID);
}
@@ -43,7 +43,7 @@ namespace Svelto.ECS
{
if (_filters.TryGetValue(TypeRefWrapper<T>.wrapper, out var fasterDictionary) == false)
return false;
return fasterDictionary.ContainsKey(groupID);
}

@@ -58,16 +58,17 @@ namespace Svelto.ECS

return false;
}
public ref GroupFilters CreateOrGetFiltersForGroup<T>(ExclusiveGroupStruct groupID)
where T : struct, IEntityComponent
{
var fasterDictionary =
_filters.GetOrCreate(TypeRefWrapper<T>.wrapper, () => new FasterDictionary<ExclusiveGroupStruct, GroupFilters>());
var fasterDictionary = _filters.GetOrCreate(TypeRefWrapper<T>.wrapper
, () =>
new FasterDictionary<ExclusiveGroupStruct,
GroupFilters>());

return ref
fasterDictionary.GetOrCreate(
groupID, () => new GroupFilters(new SharedSveltoDictionaryNative<int, FilterGroup>(0), groupID));
return ref fasterDictionary.GetOrCreate(
groupID, () => new GroupFilters(new SharedSveltoDictionaryNative<int, FilterGroup>(0), groupID));
}

public ref GroupFilters GetFiltersForGroup<T>(ExclusiveGroupStruct groupID)
@@ -75,8 +76,7 @@ namespace Svelto.ECS
{
#if DEBUG && !PROFILE_SVELTO
if (_filters.ContainsKey(TypeRefWrapper<T>.wrapper) == false)
throw new ECSException(
$"trying to fetch not existing filters, type {typeof(T)}");
throw new ECSException($"trying to fetch not existing filters, type {typeof(T)}");
if (_filters[TypeRefWrapper<T>.wrapper].ContainsKey(groupID) == false)
throw new ECSException(
$"trying to fetch not existing filters, type {typeof(T)} group {groupID.ToName()}");
@@ -90,15 +90,14 @@ namespace Svelto.ECS
{
#if DEBUG && !PROFILE_SVELTO
if (_filters.ContainsKey(TypeRefWrapper<T>.wrapper) == false)
throw new ECSException(
$"trying to fetch not existing filters, type {typeof(T)}");
throw new ECSException($"trying to fetch not existing filters, type {typeof(T)}");
if (_filters[TypeRefWrapper<T>.wrapper].ContainsKey(groupID) == false)
throw new ECSException(
$"trying to fetch not existing filters, type {typeof(T)} group {groupID.ToName()}");
#endif
return ref _filters[TypeRefWrapper<T>.wrapper][groupID].GetFilter(filterId);
}
public bool TryGetFilterForGroup<T>(int filterId, ExclusiveGroupStruct groupID, out FilterGroup groupFilter)
where T : struct, IEntityComponent
{
@@ -131,8 +130,9 @@ namespace Svelto.ECS
{
if (_filters.TryGetValue(TypeRefWrapper<T>.wrapper, out var fasterDictionary) == true)
{
DBC.ECS.Check.Require(fasterDictionary.ContainsKey(exclusiveGroupStruct), $"trying to clear filter not present in group {exclusiveGroupStruct}");
DBC.ECS.Check.Require(fasterDictionary.ContainsKey(exclusiveGroupStruct)
, $"trying to clear filter not present in group {exclusiveGroupStruct}");

fasterDictionary[exclusiveGroupStruct].ClearFilter(filterID);
}
}
@@ -174,7 +174,7 @@ namespace Svelto.ECS
}
}

public bool TryRemoveEntityFromFilter<T>(int filtersID, EGID egid) where T : unmanaged, IEntityComponent
public bool TryRemoveEntityFromFilter<T>(int filtersID, EGID egid) where T : struct, IEntityComponent
{
if (TryGetFilterForGroup<T>(filtersID, egid.groupID, out var filter))
{
@@ -184,29 +184,28 @@ namespace Svelto.ECS
return false;
}

public void RemoveEntityFromFilter<T>(int filtersID, EGID egid) where T : unmanaged, IEntityComponent
public void RemoveEntityFromFilter<T>(int filtersID, EGID egid) where T : struct, IEntityComponent
{
ref var filter = ref GetFilterForGroup<T>(filtersID, egid.groupID);

filter.Remove(egid.entityID);
}

public void AddEntityToFilter<N>(int filtersID, EGID egid, N mapper) where N:IEGIDMapper
public bool AddEntityToFilter<N>(int filtersID, EGID egid, N mapper) where N : IEGIDMapper
{
ref var filter = ref CreateOrGetFilterForGroup(filtersID, egid.groupID, new RefWrapperType(mapper.entityType));
ref var filter =
ref CreateOrGetFilterForGroup(filtersID, egid.groupID, new RefWrapperType(mapper.entityType));

filter.Add(egid.entityID, mapper);
return filter.Add(egid.entityID, mapper);
}

readonly FasterDictionary<RefWrapperType, FasterDictionary<ExclusiveGroupStruct, GroupFilters>> _filters;
}

public Filters GetFilters()
{
return new Filters(_filters);
}
public Filters GetFilters() { return new Filters(_filters); }


FasterDictionary<RefWrapperType, FasterDictionary<ExclusiveGroupStruct, GroupFilters>> _filters
=> _enginesRoot._groupFilters;
FasterDictionary<RefWrapperType, FasterDictionary<ExclusiveGroupStruct, GroupFilters>> _filters =>
_enginesRoot._groupFilters;
}
}

+ 43
- 25
Svelto.ECS/Core/Filters/FilterGroup.cs View File

@@ -1,4 +1,5 @@
using Svelto.Common;
using System.Runtime.CompilerServices;
using Svelto.Common;
using Svelto.DataStructures;
using Svelto.ECS.DataStructures;

@@ -23,7 +24,7 @@ namespace Svelto.ECS
//from the index, find the entityID
_reverseEIDs = new NativeDynamicArrayCast<uint>(NativeDynamicArray.Alloc<uint>(Allocator.Persistent));
//from the entityID, find the index
_indexOfEntityInDenseList = new SharedSveltoDictionaryNative<uint, uint>(0, Allocator.Persistent);
_indexOfEntityInDenseList = new SharedSveltoDictionaryNative<uint, uint>(0);
_exclusiveGroupStruct = exclusiveGroupStruct;
_ID = ID;
}
@@ -33,30 +34,15 @@ namespace Svelto.ECS
/// </summary>
public FilteredIndices filteredIndices => new FilteredIndices(_denseListOfIndicesToEntityComponentArray);

public void Add<N>(uint entityID, N mapper) where N:IEGIDMapper
public bool Add<N>(uint entityID, N mapper) where N:IEGIDMapper
{
#if DEBUG && !PROFILE_SVELTO
if (_denseListOfIndicesToEntityComponentArray.isValid == false)
throw new ECSException($"using an invalid filter");
if (_indexOfEntityInDenseList.ContainsKey(entityID) == true)
throw new ECSException(
$"trying to add an existing entity {entityID} to filter {mapper.entityType} - {_ID} with group {mapper.groupID}");
if (mapper.Exists(entityID) == false)
throw new ECSException(
$"trying adding an entity {entityID} to filter {mapper.entityType} - {_ID} with group {mapper.groupID}, but entity is not found! ");
#endif
//Get the index of the Entity in the component array
var indexOfEntityInBufferComponent = mapper.GetIndex(entityID);

//add the index in the list of filtered indices
_denseListOfIndicesToEntityComponentArray.Add(indexOfEntityInBufferComponent);

//inverse map: need to get the entityID from the index. This wouldn't be needed with a real sparseset
var lastIndex = (uint) (_denseListOfIndicesToEntityComponentArray.Count() - 1);
_reverseEIDs.AddAt(lastIndex) = entityID;

//remember the entities indices. This is needed to remove entities from the filter
_indexOfEntityInDenseList.Add(entityID, lastIndex);
return InternalAdd(entityID, mapper.GetIndex(entityID));
}

public void Remove(uint entityID)
@@ -71,6 +57,15 @@ namespace Svelto.ECS
InternalRemove(entityID);
}

public bool Exists(uint entityID)
{
#if DEBUG && !PROFILE_SVELTO
if (_denseListOfIndicesToEntityComponentArray.isValid == false)
throw new ECSException($"invalid Filter");
#endif
return _indexOfEntityInDenseList.ContainsKey(entityID);
}

public bool TryRemove(uint entityID)
{
#if DEBUG && !PROFILE_SVELTO
@@ -99,7 +94,7 @@ namespace Svelto.ECS
/// point to entities that were not the original ones. On structural changes
/// (specifically entities swapped or removed)
/// the filters must then be rebuilt. It would be too slow to add this in the standard flow of Svelto in
/// the current state, so calling this method is a user responsibility.
/// the current state, so calling this method is a user responsibility.
/// </summary>
public void RebuildIndicesOnStructuralChange<N>(N mapper) where N:IEGIDMapper
{
@@ -146,6 +141,29 @@ namespace Svelto.ECS
_reverseEIDs.Dispose();
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal bool InternalAdd(uint entityID, uint indexOfEntityInBufferComponent)
{
#if DEBUG && !PROFILE_SVELTO
if (_denseListOfIndicesToEntityComponentArray.isValid == false)
throw new ECSException($"using an invalid filter");
#endif
if (_indexOfEntityInDenseList.ContainsKey(entityID) == true)
return false;

//add the index in the list of filtered indices
_denseListOfIndicesToEntityComponentArray.Add(indexOfEntityInBufferComponent);

//inverse map: need to get the entityID from the index. This wouldn't be needed with a real sparseset
var lastIndex = (uint) (_denseListOfIndicesToEntityComponentArray.Count() - 1);
_reverseEIDs.AddAt(lastIndex) = entityID;

//remember the entities indices. This is needed to remove entities from the filter
_indexOfEntityInDenseList.Add(entityID, lastIndex);

return true;
}

void InternalRemove(uint entityID)
{
var count = (uint) _denseListOfIndicesToEntityComponentArray.Count();
@@ -157,7 +175,7 @@ namespace Svelto.ECS
var indexInDenseListFromEGID = _indexOfEntityInDenseList[entityID];
//get the entityID of the last entity in the filter array
uint entityIDToMove = _reverseEIDs[count - 1];
//the last index of the last entity is updated to the slot of the deleted entity
if (entityIDToMove != entityID)
{
@@ -165,13 +183,13 @@ namespace Svelto.ECS
//the reverseEGID is updated accordingly
_reverseEIDs[indexInDenseListFromEGID] = entityIDToMove;
}
//
_reverseEIDs.UnorderedRemoveAt(count - 1);

//finally remove the deleted entity from the filters array
_denseListOfIndicesToEntityComponentArray.UnorderedRemoveAt(indexInDenseListFromEGID);
//remove the entity to delete from the tracked Entity
_indexOfEntityInDenseList.Remove(entityID);
}
@@ -188,7 +206,7 @@ namespace Svelto.ECS
NativeDynamicArrayCast<uint> _reverseEIDs; //forced to use this because it's not a real sparse set
SharedSveltoDictionaryNative<uint, uint> _indexOfEntityInDenseList;

readonly ExclusiveGroupStruct _exclusiveGroupStruct;
readonly int _ID;
internal readonly ExclusiveGroupStruct _exclusiveGroupStruct;
internal readonly int _ID;
}
}

+ 2
- 2
Svelto.ECS/Core/Filters/GroupFilters.cs View File

@@ -40,8 +40,8 @@ namespace Svelto.ECS
return filters.TryGetValue(filterIndex, out filter);
}

public SveltoDictionary<int, FilterGroup, NativeStrategy<SveltoDictionaryNode<int>>, NativeStrategy<FilterGroup>
, NativeStrategy<int>>.SveltoDictionaryKeyValueEnumerator GetEnumerator()
public SveltoDictionaryKeyValueEnumerator<int, FilterGroup, NativeStrategy<SveltoDictionaryNode<int>>, NativeStrategy<FilterGroup>
, NativeStrategy<int>> GetEnumerator()
{
return filters.GetEnumerator();
}


+ 7
- 2
Svelto.ECS/Core/GlobalTypeID.cs View File

@@ -23,7 +23,7 @@ namespace Svelto.ECS
{
static Filler()
{
DBC.ECS.Check.Require(TypeCache<T>.IsUnmanaged == true, "invalid type used");
DBC.ECS.Check.Require(TypeCache<T>.isUnmanaged == true, "invalid type used");
}

//it's an internal interface
@@ -52,7 +52,12 @@ namespace Svelto.ECS

static class EntityComponentIDMap
{
static readonly FasterList<IFiller> TYPE_IDS = new FasterList<IFiller>();
static readonly FasterList<IFiller> TYPE_IDS;

static EntityComponentIDMap()
{
TYPE_IDS = new FasterList<IFiller>();
}

internal static void Register<T>(IFiller entityBuilder) where T : struct, IEntityComponent
{


+ 115
- 0
Svelto.ECS/Core/GroupHashMap.cs View File

@@ -0,0 +1,115 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Text;
using Svelto.ECS.Serialization;

namespace Svelto.ECS
{
static class GroupHashMap
{
/// <summary>
/// c# Static constructors are guaranteed to be thread safe
/// </summary>
public static void Init()
{
List<Assembly> assemblies = AssemblyUtility.GetCompatibleAssemblies();
foreach (Assembly assembly in assemblies)
{
try
{
var typeOfExclusiveGroup = typeof(ExclusiveGroup);
var typeOfExclusiveGroupStruct = typeof(ExclusiveGroupStruct);
foreach (Type type in AssemblyUtility.GetTypesSafe(assembly))
{
if (type != null && type.IsClass && type.IsSealed && type.IsAbstract) //this means only static classes
{
var fields = type.GetFields();

foreach (var field in fields)
{
if (field.IsStatic)
{
if (typeOfExclusiveGroup.IsAssignableFrom(field.FieldType))
{
var group = (ExclusiveGroup)field.GetValue(null);
var name = $"{type.FullName}.{field.Name}";
#if DEBUG
GroupNamesMap.idToName[(uint)@group] = $"{name} {(uint)group})";
#endif
RegisterGroup(group, name);
}
else
{
if (typeOfExclusiveGroupStruct.IsAssignableFrom(field.FieldType))
{
var group = (ExclusiveGroupStruct)field.GetValue(null);
var name = $"{type.FullName}.{field.Name}";
#if DEBUG
GroupNamesMap.idToName[(uint)@group] = $"{name} {(uint)group})";
#endif
RegisterGroup(@group, name);
}
}
}
}
}
}
}
catch
{
Console.LogDebugWarning(
"something went wrong while gathering group names on the assembly: ".FastConcat(assembly.FullName));
}
}
}

public static void RegisterGroup(ExclusiveGroupStruct exclusiveGroupStruct, string name)
{
//Group already registered by another field referencing the same group
if (_hashByGroups.ContainsKey(exclusiveGroupStruct))
return;
var nameHash = DesignatedHash.Hash(Encoding.ASCII.GetBytes(name));
if(_groupsByHash.ContainsKey(nameHash))
throw new ECSException($"Group hash collision with {name} and {_groupsByHash[nameHash]}");
Console.LogDebug($"Reigstering group {name} with ID {(uint)exclusiveGroupStruct} to {nameHash}");
_groupsByHash.Add(nameHash, exclusiveGroupStruct);
_hashByGroups.Add(exclusiveGroupStruct, nameHash);
}
public static uint GetHashFromGroup(ExclusiveGroupStruct groupStruct)
{
#if DEBUG
if (_hashByGroups.ContainsKey(groupStruct) == false)
throw new ECSException($"Attempted to get hash from unregistered group {groupStruct}");
#endif
return _hashByGroups[groupStruct];
}
public static ExclusiveGroupStruct GetGroupFromHash(uint groupHash)
{
#if DEBUG
if (_groupsByHash.ContainsKey(groupHash) == false)
throw new ECSException($"Attempted to get group from unregistered hash {groupHash}");
#endif
return _groupsByHash[groupHash];
}

static readonly Dictionary<uint, ExclusiveGroupStruct> _groupsByHash;
static readonly Dictionary<ExclusiveGroupStruct, uint> _hashByGroups;

static GroupHashMap()
{
_groupsByHash = new Dictionary<uint, ExclusiveGroupStruct>();
_hashByGroups = new Dictionary<ExclusiveGroupStruct, uint>();
}
}
}

+ 26
- 0
Svelto.ECS/Core/GroupNamesMap.cs View File

@@ -0,0 +1,26 @@
using System.Collections.Generic;
using Svelto.ECS;

static class GroupNamesMap
{
#if DEBUG
static GroupNamesMap() { idToName = new Dictionary<uint, string>(); }

internal static readonly Dictionary<uint, string> idToName;
#endif
#if DEBUG
public static string ToName(this in ExclusiveGroupStruct group)
{
var idToName = GroupNamesMap.idToName;
if (idToName.TryGetValue((uint)@group, out var name) == false)
name = $"<undefined:{((uint)group).ToString()}>";

return name;
}
#else
public static string ToName(this in ExclusiveGroupStruct group)
{
return ((uint)group).ToString();
}
#endif
}

+ 12
- 2
Svelto.ECS/Core/Groups/ExclusiveBuildGroup.cs View File

@@ -17,9 +17,19 @@ namespace Svelto.ECS
return new ExclusiveBuildGroup(group);
}
public static implicit operator uint(ExclusiveBuildGroup groupStruct)
public static implicit operator ExclusiveGroupStruct(ExclusiveBuildGroup group)
{
return groupStruct.group;
return new ExclusiveGroupStruct(group.group);
}
public static explicit operator uint(ExclusiveBuildGroup groupStruct)
{
return (uint) groupStruct.@group;
}
public override string ToString()
{
return this.group.ToName();
}

internal ExclusiveGroupStruct @group { get; }


+ 25
- 90
Svelto.ECS/Core/Groups/ExclusiveGroup.cs View File

@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;

#pragma warning disable 660,661

@@ -17,20 +18,20 @@ namespace Svelto.ECS
/// public static ExclusiveGroup[] GroupOfGroups = { MyExclusiveGroup1, ...}; //for each on this!
/// }
/// </summary>
///To debug it use in your debug window: Svelto.ECS.Debugger.EGID.GetGroupNameFromId(groupID)
public class ExclusiveGroup
public sealed class ExclusiveGroup
{
public const uint MaxNumberOfExclusiveGroups = 2 << 20;
public ExclusiveGroup()
public const uint MaxNumberOfExclusiveGroups = 2 << 20;
public ExclusiveGroup(ExclusiveGroupBitmask bitmask = 0)
{
_group = ExclusiveGroupStruct.Generate();
_group = ExclusiveGroupStruct.Generate((byte)bitmask);
}

public ExclusiveGroup(string recognizeAs)
public ExclusiveGroup(string recognizeAs, ExclusiveGroupBitmask bitmask = 0)
{
_group = ExclusiveGroupStruct.Generate();
_group = ExclusiveGroupStruct.Generate((byte)bitmask);

_knownGroups.Add(recognizeAs, _group);
}
@@ -43,14 +44,26 @@ namespace Svelto.ECS
#endif
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Disable()
{
_group.Disable();
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Enable()
{
_group.Enable();
}

public static implicit operator ExclusiveGroupStruct(ExclusiveGroup group)
{
return group._group;
}
public static explicit operator uint(ExclusiveGroup group)
{
return group._group;
return (uint) @group._group;
}

public static ExclusiveGroupStruct operator+(ExclusiveGroup a, uint b)
@@ -63,7 +76,7 @@ namespace Svelto.ECS
#endif
return a._group + b;
}
//todo document the use case for this method
public static ExclusiveGroupStruct Search(string holderGroupName)
{
@@ -84,84 +97,6 @@ namespace Svelto.ECS
#if DEBUG
readonly ushort _range;
#endif
readonly ExclusiveGroupStruct _group;
ExclusiveGroupStruct _group;
}
}

#if future
public static void ConstructStaticGroups()
{
Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();

// Assemblies or types aren't guaranteed to be returned in the same order,
// and I couldn't find proof that `GetTypes()` returns them in fixed order either,
// even for builds made with the exact same source code.
// So will sort reflection results by name before constructing groups.
var groupFields = new List<KeyValuePair<string, FieldInfo>>();

foreach (Assembly assembly in assemblies)
{
Type[] types = GetTypesSafe(assembly);

foreach (Type type in types)
{
if (type == null || !type.IsClass)
{
continue;
}

// Groups defined as static members in static classes
if (type.IsSealed && type.IsAbstract)
{
FieldInfo[] fields = type.GetFields();
foreach(var field in fields)
{
if (field.IsStatic && typeof(ExclusiveGroup).IsAssignableFrom(field.FieldType))
{
groupFields.Add(new KeyValuePair<string, FieldInfo>($"{type.FullName}.{field.Name}", field));
}
}
}
// Groups defined as classes
else if (type.BaseType != null
&& type.BaseType.IsGenericType
&& type.BaseType.GetGenericTypeDefinition() == typeof(ExclusiveGroup<>))
{
FieldInfo field = type.GetField("Group",
BindingFlags.Static | BindingFlags.Public | BindingFlags.FlattenHierarchy);

groupFields.Add(new KeyValuePair<string, FieldInfo>(type.FullName, field));
}
}
}

groupFields.Sort((a, b) => string.CompareOrdinal(a.Key, b.Key));

for (int i = 0; i < groupFields.Count; ++i)
{
groupFields[i].Value.GetValue(null);
#if DEBUG
var group = (ExclusiveGroup) groupFields[i].Value.GetValue(null);
groupNames[(uint) group] = groupFields[i].Key;
#endif
}
}

static Type[] GetTypesSafe(Assembly assembly)
{
try
{
Type[] types = assembly.GetTypes();

return types;
}
catch (ReflectionTypeLoadException e)
{
return e.Types;
}
}

#if DEBUG
static string[] groupNames = new string[ExclusiveGroup.MaxNumberOfExclusiveGroups];
#endif
#endif

+ 15
- 0
Svelto.ECS/Core/Groups/ExclusiveGroupBitmask.cs View File

@@ -0,0 +1,15 @@
namespace Svelto.ECS
{
public enum ExclusiveGroupBitmask : byte
{
NONE = 0,
DISABLED_BIT = 0b00000001,
UNUSED_BIT_2 = 0b00000010,
UNUSED_BIT_3 = 0b00000100,
UNUSED_BIT_4 = 0b00001000,
UNUSED_BIT_5 = 0b00010000,
UNUSED_BIT_6 = 0b00100000,
UNUSED_BIT_7 = 0b01000000,
UNUSED_BIT_8 = 0b10000000,
}
}

+ 51
- 32
Svelto.ECS/Core/Groups/ExclusiveGroupStruct.cs View File

@@ -1,51 +1,80 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Svelto.DataStructures;

namespace Svelto.ECS
{
[StructLayout(LayoutKind.Explicit, Size = 4)]
public struct ExclusiveGroupStruct : IEquatable<ExclusiveGroupStruct>, IComparable<ExclusiveGroupStruct>,
IEqualityComparer<ExclusiveGroupStruct>
//the type doesn't implement IEqualityComparer, what implements it is a custom comparer
public struct ExclusiveGroupStruct : IEquatable<ExclusiveGroupStruct>, IComparable<ExclusiveGroupStruct>
{
public static readonly ExclusiveGroupStruct Invalid = default; //must stay here because of Burst

public ExclusiveGroupStruct(byte[] data, uint pos):this()
{
_id = (uint)(
data[pos]
| data[++pos] << 8
| data[++pos] << 16
);
_bytemask = (byte) (data[++pos] << 24);

DBC.ECS.Check.Ensure(_id < _globalId, "Invalid group ID deserialiased");
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override bool Equals(object obj)
{
return obj is ExclusiveGroupStruct other && Equals(other);
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override int GetHashCode()
{
return (int) _id;
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator ==(ExclusiveGroupStruct c1, ExclusiveGroupStruct c2)
{
return c1.Equals(c2);
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator !=(ExclusiveGroupStruct c1, ExclusiveGroupStruct c2)
{
return c1.Equals(c2) == false;
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Equals(ExclusiveGroupStruct other)
{
return other._id == _id;
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public int CompareTo(ExclusiveGroupStruct other)
{
return other._id.CompareTo(_id);
}

public bool Equals(ExclusiveGroupStruct x, ExclusiveGroupStruct y)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly bool IsEnabled()
{
return x._id == y._id;
return (_bytemask & (byte)ExclusiveGroupBitmask.DISABLED_BIT) == 0;
}

public int GetHashCode(ExclusiveGroupStruct obj)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal void Disable()
{
return _id.GetHashCode();
_bytemask |= (byte)ExclusiveGroupBitmask.DISABLED_BIT;
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal void Enable()
{
_bytemask &= (byte)(~ExclusiveGroupBitmask.DISABLED_BIT);
}

public override string ToString()
@@ -53,6 +82,20 @@ namespace Svelto.ECS
return this.ToName();
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static explicit operator uint(ExclusiveGroupStruct groupStruct)
{
return groupStruct._id;
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ExclusiveGroupStruct operator+(ExclusiveGroupStruct a, uint b)
{
var group = new ExclusiveGroupStruct {_id = a._id + b};

return @group;
}

internal static ExclusiveGroupStruct Generate(byte bitmask = 0)
{
ExclusiveGroupStruct groupStruct;
@@ -82,33 +125,9 @@ namespace Svelto.ECS
_id = groupID;
}

public ExclusiveGroupStruct(byte[] data, uint pos):this()
{
_id = (uint)(
data[pos]
| data[++pos] << 8
| data[++pos] << 16
);
_bytemask = (byte) (data[++pos] << 24);

DBC.ECS.Check.Ensure(_id < _globalId, "Invalid group ID deserialiased");
}

public static implicit operator uint(ExclusiveGroupStruct groupStruct)
{
return groupStruct._id;
}
public static ExclusiveGroupStruct operator+(ExclusiveGroupStruct a, uint b)
{
var group = new ExclusiveGroupStruct {_id = a._id + b};

return @group;
}

[FieldOffset(0)] uint _id;
[FieldOffset(3)] byte _bytemask;

static uint _globalId = 1; //it starts from 1 because default EGID is considered not initalized value
static uint _globalId = 1; //it starts from 1 because default EGID is considered not initialized value
}
}

+ 284
- 215
Svelto.ECS/Core/Groups/GroupCompound.cs View File

@@ -1,225 +1,294 @@
using System;
using System.Collections.Generic;
using System.Threading;
using Svelto.DataStructures;

namespace Svelto.ECS
{
public abstract class GroupCompound<G1, G2, G3, G4> where G1 : GroupTag<G1>
where G2 : GroupTag<G2>
where G3 : GroupTag<G3>
where G4 : GroupTag<G4>
{
static readonly FasterList<ExclusiveGroupStruct> _Groups;
static readonly HashSet<ExclusiveGroupStruct> _GroupsHashSet;

public static FasterReadOnlyList<ExclusiveGroupStruct> Groups =>
new FasterReadOnlyList<ExclusiveGroupStruct>(_Groups);

public static ExclusiveBuildGroup BuildGroup => new ExclusiveBuildGroup(_Groups[0]);

static int isInitializing;

static GroupCompound()
{
if (Interlocked.CompareExchange(ref isInitializing, 1, 0) == 0)
{
_Groups = new FasterList<ExclusiveGroupStruct>(1);

var Group = new ExclusiveGroup();
_Groups.Add(Group);
_GroupsHashSet = new HashSet<ExclusiveGroupStruct>(_Groups.ToArrayFast(out _));

GroupCompound<G1, G2, G3>.Add(Group);
GroupCompound<G1, G2, G4>.Add(Group);
GroupCompound<G1, G3, G4>.Add(Group);
GroupCompound<G2, G3, G4>.Add(Group);

GroupCompound<G1, G2>.Add(Group); //<G1/G2> and <G2/G1> must share the same array
GroupCompound<G1, G3>.Add(Group);
GroupCompound<G1, G4>.Add(Group);
GroupCompound<G2, G3>.Add(Group);
GroupCompound<G2, G4>.Add(Group);
GroupCompound<G3, G4>.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<G1>.Add(Group);
GroupTag<G2>.Add(Group);
GroupTag<G3>.Add(Group);
GroupTag<G4>.Add(Group);

#if DEBUG
GroupNamesMap.idToName[(uint) Group] =
$"Compound: {typeof(G1).Name}-{typeof(G2).Name}-{typeof(G3).Name}-{typeof(G4).Name} ID {(uint) Group}";
#endif
GroupHashMap.RegisterGroup(BuildGroup,
$"Compound: {typeof(G1).Name}-{typeof(G2).Name}-{typeof(G3).Name}-{typeof(G4).Name}");

//all the combinations must share the same group and group hashset
GroupCompound<G1, G2, G4, G3>._Groups = _Groups;
GroupCompound<G1, G3, G2, G4>._Groups = _Groups;
GroupCompound<G1, G3, G4, G2>._Groups = _Groups;
GroupCompound<G1, G4, G2, G3>._Groups = _Groups;
GroupCompound<G2, G1, G3, G4>._Groups = _Groups;
GroupCompound<G2, G3, G4, G1>._Groups = _Groups;
GroupCompound<G3, G1, G2, G4>._Groups = _Groups;
GroupCompound<G4, G1, G2, G3>._Groups = _Groups;
GroupCompound<G1, G4, G3, G2>._Groups = _Groups;
GroupCompound<G2, G1, G4, G3>._Groups = _Groups;
GroupCompound<G2, G4, G3, G1>._Groups = _Groups;
GroupCompound<G3, G1, G4, G2>._Groups = _Groups;
GroupCompound<G4, G1, G3, G2>._Groups = _Groups;
GroupCompound<G2, G3, G1, G4>._Groups = _Groups;
GroupCompound<G3, G4, G1, G2>._Groups = _Groups;
GroupCompound<G2, G4, G1, G3>._Groups = _Groups;
GroupCompound<G3, G2, G1, G4>._Groups = _Groups;
GroupCompound<G3, G2, G4, G1>._Groups = _Groups;
GroupCompound<G3, G4, G2, G1>._Groups = _Groups;
GroupCompound<G4, G2, G1, G3>._Groups = _Groups;
GroupCompound<G4, G2, G3, G1>._Groups = _Groups;
GroupCompound<G4, G3, G1, G2>._Groups = _Groups;
GroupCompound<G4, G3, G2, G1>._Groups = _Groups;

GroupCompound<G1, G2, G4, G3>._GroupsHashSet = _GroupsHashSet;
GroupCompound<G1, G3, G2, G4>._GroupsHashSet = _GroupsHashSet;
GroupCompound<G1, G3, G4, G2>._GroupsHashSet = _GroupsHashSet;
GroupCompound<G1, G4, G2, G3>._GroupsHashSet = _GroupsHashSet;
GroupCompound<G2, G1, G3, G4>._GroupsHashSet = _GroupsHashSet;
GroupCompound<G2, G3, G4, G1>._GroupsHashSet = _GroupsHashSet;
GroupCompound<G3, G1, G2, G4>._GroupsHashSet = _GroupsHashSet;
GroupCompound<G4, G1, G2, G3>._GroupsHashSet = _GroupsHashSet;
GroupCompound<G1, G4, G3, G2>._GroupsHashSet = _GroupsHashSet;
GroupCompound<G2, G1, G4, G3>._GroupsHashSet = _GroupsHashSet;
GroupCompound<G2, G4, G3, G1>._GroupsHashSet = _GroupsHashSet;
GroupCompound<G3, G1, G4, G2>._GroupsHashSet = _GroupsHashSet;
GroupCompound<G4, G1, G3, G2>._GroupsHashSet = _GroupsHashSet;
GroupCompound<G2, G3, G1, G4>._GroupsHashSet = _GroupsHashSet;
GroupCompound<G3, G4, G1, G2>._GroupsHashSet = _GroupsHashSet;
GroupCompound<G2, G4, G1, G3>._GroupsHashSet = _GroupsHashSet;
GroupCompound<G3, G2, G1, G4>._GroupsHashSet = _GroupsHashSet;
GroupCompound<G3, G2, G4, G1>._GroupsHashSet = _GroupsHashSet;
GroupCompound<G3, G4, G2, G1>._GroupsHashSet = _GroupsHashSet;
GroupCompound<G4, G2, G1, G3>._GroupsHashSet = _GroupsHashSet;
GroupCompound<G4, G2, G3, G1>._GroupsHashSet = _GroupsHashSet;
GroupCompound<G4, G3, G1, G2>._GroupsHashSet = _GroupsHashSet;
GroupCompound<G4, G3, G2, G1>._GroupsHashSet = _GroupsHashSet;
}
}

internal static void Add(ExclusiveGroupStruct @group)
{
for (int i = 0; i < _Groups.count; ++i)
if (_Groups[i] == group)
throw new Exception("temporary must be transformed in unit test");

_Groups.Add(group);
_GroupsHashSet.Add(group);
}

public static bool Includes(ExclusiveGroupStruct @group) { return _GroupsHashSet.Contains(@group); }
}

public abstract class GroupCompound<G1, G2, G3>
where G1 : GroupTag<G1> where G2 : GroupTag<G2> where G3 : GroupTag<G3>
{
static readonly FasterList<ExclusiveGroupStruct> _Groups;
static readonly HashSet<ExclusiveGroupStruct> _GroupsHashSet;

public static FasterReadOnlyList<ExclusiveGroupStruct> Groups =>
new FasterReadOnlyList<ExclusiveGroupStruct>(_Groups);

public static ExclusiveBuildGroup BuildGroup => new ExclusiveBuildGroup(_Groups[0]);
static int isInitializing;

internal static void Add(ExclusiveGroupStruct group)
{
for (var i = 0; i < _Groups.count; ++i)
if (_Groups[i] == group)
throw new Exception("temporary must be transformed in unit test");

_Groups.Add(group);
_GroupsHashSet.Add(group);
}

public static bool Includes(ExclusiveGroupStruct @group) { return _GroupsHashSet.Contains(@group); }

static GroupCompound()
{
if (Interlocked.CompareExchange(ref isInitializing, 1, 0) == 0)
{
_Groups = new FasterList<ExclusiveGroupStruct>(1);

var Group = new ExclusiveGroup();
_Groups.Add(Group);
_GroupsHashSet = new HashSet<ExclusiveGroupStruct>(_Groups.ToArrayFast(out _));

GroupCompound<G1, G2>.Add(Group); //<G1/G2> and <G2/G1> must share the same array
GroupCompound<G1, G3>.Add(Group);
GroupCompound<G2, G3>.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<G1>.Add(Group);
GroupTag<G2>.Add(Group);
GroupTag<G3>.Add(Group);

#if DEBUG
GroupNamesMap.idToName[(uint) Group] =
$"Compound: {typeof(G1).Name}-{typeof(G2).Name}-{typeof(G3).Name} ID {(uint) Group}";
#endif
GroupHashMap.RegisterGroup(BuildGroup,
$"Compound: {typeof(G1).Name}-{typeof(G2).Name}-{typeof(G3).Name}");
//all the combinations must share the same group and group hashset
GroupCompound<G3, G1, G2>._Groups = _Groups;
GroupCompound<G2, G3, G1>._Groups = _Groups;
GroupCompound<G3, G2, G1>._Groups = _Groups;
GroupCompound<G1, G3, G2>._Groups = _Groups;
GroupCompound<G2, G1, G3>._Groups = _Groups;

GroupCompound<G3, G1, G2>._GroupsHashSet = _GroupsHashSet;
GroupCompound<G2, G3, G1>._GroupsHashSet = _GroupsHashSet;
GroupCompound<G3, G2, G1>._GroupsHashSet = _GroupsHashSet;
GroupCompound<G1, G3, G2>._GroupsHashSet = _GroupsHashSet;
GroupCompound<G2, G1, G3>._GroupsHashSet = _GroupsHashSet;
}
}
}

public abstract class GroupCompound<G1, G2> where G1 : GroupTag<G1> where G2 : GroupTag<G2>
{
static readonly FasterList<ExclusiveGroupStruct> _Groups;
static readonly HashSet<ExclusiveGroupStruct> _GroupsHashSet;

public static FasterReadOnlyList<ExclusiveGroupStruct> Groups =>
new FasterReadOnlyList<ExclusiveGroupStruct>(_Groups);

public static ExclusiveBuildGroup BuildGroup => new ExclusiveBuildGroup(_Groups[0]);
static int isInitializing;

internal static void Add(ExclusiveGroupStruct group)
{
for (var i = 0; i < _Groups.count; ++i)
if (_Groups[i] == group)
throw new Exception("temporary must be transformed in unit test");

_Groups.Add(group);
_GroupsHashSet.Add(group);
}

public static bool Includes(ExclusiveGroupStruct @group) { return _GroupsHashSet.Contains(@group); }

static GroupCompound()
{
if (Interlocked.CompareExchange(ref isInitializing, 1, 0) == 0)
{
var Group = new ExclusiveGroup();

_Groups = new FasterList<ExclusiveGroupStruct>(1);
_Groups.Add(Group);
_GroupsHashSet = new HashSet<ExclusiveGroupStruct>(_Groups.ToArrayFast(out _));

//every abstract group preemptively adds this group, it may or may not be empty in future
GroupTag<G1>.Add(Group);
GroupTag<G2>.Add(Group);

#if DEBUG
GroupNamesMap.idToName[(uint) Group] = $"Compound: {typeof(G1).Name}-{typeof(G2).Name} ID {(uint) Group}";
#endif
GroupHashMap.RegisterGroup(BuildGroup,
$"Compound: {typeof(G1).Name}-{typeof(G2).Name}");

GroupCompound<G2, G1>._Groups = _Groups;
GroupCompound<G2, G1>._GroupsHashSet = _GroupsHashSet;
}
}
}

/// <summary>
/// Very naive fail safe, but at least it's simple to understand and safe
///A Group Tag holds initially just a group, itself. However the number of groups can grow with the number of
///combinations of GroupTags including this one. This because a GroupTag is an adjective and different entities
///can use the same adjective together with other ones. However since I need to be able to iterate over all the
///groups with the same adjective, a group tag needs to hold all the groups sharing it.
/// </summary>
static class GroupCompoundInitializer
/// <typeparam name="T"></typeparam>
public abstract class GroupTag<T> where T : GroupTag<T>
{
internal static readonly ThreadLocal<bool> isInitializing4 = new ThreadLocal<bool>();
internal static readonly ThreadLocal<bool> isInitializing3 = new ThreadLocal<bool>();
internal static readonly ThreadLocal<bool> isInitializing2 = new ThreadLocal<bool>();
}
public abstract class GroupCompound<G1, G2, G3, G4>
where G1 : GroupTag<G1> where G2 : GroupTag<G2> where G3 : GroupTag<G3> where G4 : GroupTag<G4>
{
static readonly FasterList<ExclusiveGroupStruct> _Groups;
public static FasterReadOnlyList<ExclusiveGroupStruct> Groups => new FasterReadOnlyList<ExclusiveGroupStruct>(_Groups);
public static ExclusiveBuildGroup BuildGroup => new ExclusiveBuildGroup(_Groups[0]);
static GroupCompound()
{
if (GroupCompoundInitializer.isInitializing4.Value == false)
{
_Groups = new FasterList<ExclusiveGroupStruct>(1);
var Group = new ExclusiveGroup();
_Groups.Add(Group);
GroupCompound<G1, G2, G3>.Add(Group);
GroupCompound<G1, G2, G4>.Add(Group);
GroupCompound<G1, G3, G4>.Add(Group);
GroupCompound<G2, G3, G4>.Add(Group);
GroupCompound<G1, G2>.Add(Group); //<G1/G2> and <G2/G1> must share the same array
GroupCompound<G1, G3>.Add(Group);
GroupCompound<G1, G4>.Add(Group);
GroupCompound<G2, G3>.Add(Group);
GroupCompound<G2, G4>.Add(Group);
GroupCompound<G3, G4>.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<G1>.Add(Group);
GroupTag<G2>.Add(Group);
GroupTag<G3>.Add(Group);
GroupTag<G4>.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<G1, G2, G4, G3>._Groups = _Groups;
GroupCompound<G1, G3, G2, G4>._Groups = _Groups;
GroupCompound<G1, G3, G4, G2>._Groups = _Groups;
GroupCompound<G1, G4, G2, G3>._Groups = _Groups;
GroupCompound<G2, G1, G3, G4>._Groups = _Groups;
GroupCompound<G2, G3, G4, G1>._Groups = _Groups;
GroupCompound<G3, G1, G2, G4>._Groups = _Groups;
GroupCompound<G4, G1, G2, G3>._Groups = _Groups;
GroupCompound<G1, G4, G3, G2>._Groups = _Groups;
GroupCompound<G2, G1, G4, G3>._Groups = _Groups;
GroupCompound<G2, G4, G3, G1>._Groups = _Groups;
GroupCompound<G3, G1, G4, G2>._Groups = _Groups;
GroupCompound<G4, G1, G3, G2>._Groups = _Groups;
GroupCompound<G2, G3, G1, G4>._Groups = _Groups;
GroupCompound<G3, G4, G1, G2>._Groups = _Groups;
GroupCompound<G2, G4, G1, G3>._Groups = _Groups;
GroupCompound<G3, G2, G1, G4>._Groups = _Groups;
GroupCompound<G3, G2, G4, G1>._Groups = _Groups;
GroupCompound<G3, G4, G2, G1>._Groups = _Groups;
GroupCompound<G4, G2, G1, G3>._Groups = _Groups;
GroupCompound<G4, G2, G3, G1>._Groups = _Groups;
GroupCompound<G4, G3, G1, G2>._Groups = _Groups;
GroupCompound<G4, G3, G2, G1>._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<G1, G2, G3>
where G1 : GroupTag<G1> where G2 : GroupTag<G2> where G3 : GroupTag<G3>
{
static readonly FasterList<ExclusiveGroupStruct> _Groups;
public static FasterReadOnlyList<ExclusiveGroupStruct> Groups =>
new FasterReadOnlyList<ExclusiveGroupStruct>(_Groups);
public static ExclusiveBuildGroup BuildGroup => new ExclusiveBuildGroup(_Groups[0]);
public static void Add(ExclusiveGroupStruct group)
{
for (var i = 0; i < _Groups.count; ++i)
if (_Groups[i] == group)
throw new Exception("temporary must be transformed in unit test");
_Groups.Add(group);
}
static GroupCompound()
{
if (GroupCompoundInitializer.isInitializing3.Value == false)
{
_Groups = new FasterList<ExclusiveGroupStruct>(1);
var Group = new ExclusiveGroup();
_Groups.Add(Group);
GroupCompound<G1, G2>.Add(Group); //<G1/G2> and <G2/G1> must share the same array
GroupCompound<G1, G3>.Add(Group);
GroupCompound<G2, G3>.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<G1>.Add(Group);
GroupTag<G2>.Add(Group);
GroupTag<G3>.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<G3, G1, G2>._Groups = _Groups;
GroupCompound<G2, G3, G1>._Groups = _Groups;
GroupCompound<G3, G2, G1>._Groups = _Groups;
GroupCompound<G1, G3, G2>._Groups = _Groups;
GroupCompound<G2, G1, G3>._Groups = _Groups;
GroupCompoundInitializer.isInitializing3.Value = false;
}
}
}
public abstract class GroupCompound<G1, G2> where G1 : GroupTag<G1> where G2 : GroupTag<G2>
{
static readonly FasterList<ExclusiveGroupStruct> _Groups;
public static FasterReadOnlyList<ExclusiveGroupStruct> Groups =>
new FasterReadOnlyList<ExclusiveGroupStruct>(_Groups);
public static ExclusiveBuildGroup BuildGroup => new ExclusiveBuildGroup(_Groups[0]);
public static void Add(ExclusiveGroupStruct group)
{
for (var i = 0; i < _Groups.count; ++i)
if (_Groups[i] == group)
throw new Exception("temporary must be transformed in unit test");
_Groups.Add(group);
}
static GroupCompound()
{
if (GroupCompoundInitializer.isInitializing2.Value == false)
{
var Group = new ExclusiveGroup();
_Groups = new FasterList<ExclusiveGroupStruct>(1);
_Groups.Add(Group);
//every abstract group preemptively adds this group, it may or may not be empty in future
GroupTag<G1>.Add(Group);
GroupTag<G2>.Add(Group);
#if DEBUG
GroupMap.idToName[(uint) Group] = $"Compound: {typeof(G1).Name}-{typeof(G2).Name} ID {(uint)Group}";
#endif
GroupCompoundInitializer.isInitializing2.Value = true;
GroupCompound<G2, G1>._Groups = _Groups;
GroupCompoundInitializer.isInitializing2.Value = false;
}
}
}
/// <summary>
///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.
/// </summary>
/// <typeparam name="T"></typeparam>
public abstract class GroupTag<T> where T : GroupTag<T>
{
static readonly FasterList<ExclusiveGroupStruct> _Groups = new FasterList<ExclusiveGroupStruct>(1);
public static FasterReadOnlyList<ExclusiveGroupStruct> Groups =>
new FasterReadOnlyList<ExclusiveGroupStruct>(_Groups);
public static ExclusiveBuildGroup BuildGroup => new ExclusiveBuildGroup(_Groups[0]);
static GroupTag()
{
var group = new ExclusiveGroup();
_Groups.Add(group);
#if DEBUG
GroupMap.idToName[(uint) group] = $"Compound: {typeof(T).Name} ID {(uint)group}";
#endif
}
//Each time a new combination of group tags is found a new group is added.
internal static void Add(ExclusiveGroupStruct group)
{
for (var i = 0; i < _Groups.count; ++i)
if (_Groups[i] == group)
throw new Exception("temporary must be transformed in unit test");
_Groups.Add(group);
}
}
static readonly FasterList<ExclusiveGroupStruct> _Groups = new FasterList<ExclusiveGroupStruct>(1);
static readonly HashSet<ExclusiveGroupStruct> _GroupsHashSet;

public static FasterReadOnlyList<ExclusiveGroupStruct> Groups =>
new FasterReadOnlyList<ExclusiveGroupStruct>(_Groups);

public static ExclusiveBuildGroup BuildGroup => new ExclusiveBuildGroup(_Groups[0]);
static int isInitializing;

static GroupTag()
{
if (Interlocked.CompareExchange(ref isInitializing, 1, 0) == 0)
{
var group = new ExclusiveGroup();
_Groups.Add(group);
_GroupsHashSet = new HashSet<ExclusiveGroupStruct>(_Groups.ToArrayFast(out _));

#if DEBUG
var typeInfo = typeof(T);
var typeInfoBaseType = typeInfo.BaseType;
if (typeInfoBaseType.GenericTypeArguments[0] != typeInfo)
throw new ECSException("Invalid Group Tag declared");

GroupNamesMap.idToName[(uint)group] = $"Compound: {typeInfo.Name} ID {(uint)group}";
#endif
GroupHashMap.RegisterGroup(BuildGroup,
$"Compound: {typeof(T).FullName}");
}
}

//Each time a new combination of group tags is found a new group is added.
internal static void Add(ExclusiveGroupStruct group)
{
for (var i = 0; i < _Groups.count; ++i)
if (_Groups[i] == group)
throw new Exception("temporary must be transformed in unit test");

_Groups.Add(group);
_GroupsHashSet.Add(group);
}

public static bool Includes(ExclusiveGroupStruct @group) { return _GroupsHashSet.Contains(@group); }
}
}

+ 2
- 1
Svelto.ECS/Core/Groups/NamedExclusiveGroup.cs View File

@@ -14,8 +14,9 @@ namespace Svelto.ECS
static NamedExclusiveGroup()
{
#if DEBUG
GroupMap.idToName[(uint) Group] = $"{name} ID {(uint)Group}";
GroupNamesMap.idToName[(uint) Group] = $"{name} ID {(uint)Group}";
#endif
GroupHashMap.RegisterGroup(Group, $"{name}");
}
// protected NamedExclusiveGroup(string recognizeAs) : base(recognizeAs) {}
// protected NamedExclusiveGroup(ushort range) : base(range) {}


+ 1
- 1
Svelto.ECS/Core/Hybrid/IEntityDescriptorHolder.cs View File

@@ -1,4 +1,4 @@
namespace Svelto.ECS
namespace Svelto.ECS.Hybrid
{
public interface IEntityDescriptorHolder
{


+ 27
- 3
Svelto.ECS/Core/Hybrid/ValueReference.cs View File

@@ -1,13 +1,37 @@
using System;
using System.Runtime.InteropServices;

namespace Svelto.ECS.Hybrid
{
public struct ValueReference<T> where T:class, IImplementor
/// <summary>
/// an ECS dirty trick to hold the reference to an object. this is component can be used in an engine
/// managing an OOP abstraction layer. It's need is quite rare though! An example is found at
/// https://github.com/sebas77/Svelto.MiniExamples/blob/master/Example6-Unity%20Hybrid-OOP%20Abstraction/Assets/Code/WithEntityViewComponent/Descriptors/TransformImplementor.cs
/// All other uses must be considered an abuse
/// as the object can be casted back to it's real type only by an OOP Abstraction Layer Engine:
/// https://www.sebaslab.com/oop-abstraction-layer-in-a-ecs-centric-application/
/// </summary>
/// <typeparam name="T"></typeparam>
public struct ValueReference<T> : IDisposable where T:class, IImplementor
{
static ValueReference()
{
DBC.ECS.Check.Require(typeof(T).IsInterface == true, "ValueReference type can be only pure interface implementing IImplementor");
}

public ValueReference(T obj) { _pointer = GCHandle.Alloc(obj, GCHandleType.Normal); }

public static explicit operator T(ValueReference<T> t) => (T) t._pointer.Target;
public W Convert<W>(W implementer) where W:T, new()
{
var pointerTarget = _pointer.Target;
return (W)pointerTarget;
}

GCHandle _pointer;
public void Dispose()
{
_pointer.Free();
}
GCHandle _pointer;
}
}

+ 5
- 5
Svelto.ECS/Core/IComponentBuilder.cs View File

@@ -6,11 +6,11 @@ namespace Svelto.ECS
{
public interface IComponentBuilder
{
void BuildEntityAndAddToList(ref ITypeSafeDictionary dictionary, EGID egid,
IEnumerable<object> implementors);
ITypeSafeDictionary Preallocate(ref ITypeSafeDictionary dictionary, uint size);
void BuildEntityAndAddToList(ITypeSafeDictionary dictionary, EGID egid, IEnumerable<object> implementors);
void Preallocate(ITypeSafeDictionary dictionary, uint size);
ITypeSafeDictionary CreateDictionary(uint size);

Type GetEntityComponentType();
bool isUnmanaged { get; }
Type GetEntityComponentType();
bool isUnmanaged { get; }
}
}

+ 7
- 11
Svelto.ECS/Core/IEntityFactory.cs View File

@@ -25,19 +25,14 @@ namespace Svelto.ECS
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="groupStructId"></param>
/// <param name="size"></param>
void PreallocateEntitySpace<T>(ExclusiveGroupStruct groupStructId, uint size)
/// <param name="numberOfEntities"></param>
void PreallocateEntitySpace<T>(ExclusiveGroupStruct groupStructId, uint numberOfEntities)
where T : IEntityDescriptor, new();

/// <summary>
/// The EntityDescriptor doesn't need to be ever instantiated. It just describes the Entity
/// itself in terms of EntityComponents to build. The Implementors are passed to fill the
/// references of the EntityComponents components. Please read the articles on my blog
/// to understand better the terminologies
/// Using this function is like building a normal entity, but the entity components
/// are grouped by groupID to be more efficiently processed inside engines and
/// improve cache locality. Either class entityComponents and struct entityComponents can be
/// grouped.
/// references of the Entity View Components components if present.
/// </summary>
/// <param name="entityID"></param>
/// <param name="groupStructId"></param>
@@ -51,17 +46,18 @@ namespace Svelto.ECS
where T : IEntityDescriptor, new();

EntityInitializer BuildEntity<T>(uint entityID, ExclusiveBuildGroup groupStructId,
T descriptorEntity, IEnumerable<object> implementors = null)
T descriptorEntity, IEnumerable<object> implementors = null)
where T : IEntityDescriptor;

EntityInitializer BuildEntity<T>(EGID egid, T entityDescriptor, IEnumerable<object> implementors = null)
where T : IEntityDescriptor;

//Todo: analyze if this can be internal or just related to serialization
EntityInitializer BuildEntity
(EGID egid, IComponentBuilder[] componentsToBuild, Type type, IEnumerable<object> implementors = null);
(EGID egid, IComponentBuilder[] componentsToBuild, Type descriptorType, IEnumerable<object> implementors = null);

#if UNITY_NATIVE
NativeEntityFactory ToNative<T>(string callerName) where T : IEntityDescriptor, new();
Svelto.ECS.Native.NativeEntityFactory ToNative<T>(string callerName) where T : IEntityDescriptor, new();
#endif
}
}

+ 5
- 7
Svelto.ECS/Core/IEntityFunctions.cs View File

@@ -1,5 +1,3 @@
using System.Runtime.CompilerServices;

namespace Svelto.ECS
{
public interface IEntityFunctions
@@ -7,8 +5,8 @@ namespace Svelto.ECS
//being entity ID globally not unique, the group must be specified when
//an entity is removed. Not specifying the group will attempt to remove
//the entity from the special standard group.
void RemoveEntity<T>(uint entityID, ExclusiveBuildGroup groupID, [CallerMemberName] string memberName = "") where T : IEntityDescriptor, new();
void RemoveEntity<T>(EGID entityegid, [CallerMemberName] string memberName = "") where T : IEntityDescriptor, new();
void RemoveEntity<T>(uint entityID, ExclusiveBuildGroup groupID) where T : IEntityDescriptor, new();
void RemoveEntity<T>(EGID entityegid) where T : IEntityDescriptor, new();
void RemoveEntitiesFromGroup(ExclusiveBuildGroup groupID);

@@ -19,7 +17,7 @@ namespace Svelto.ECS

void SwapEntityGroup<T>(EGID fromID, ExclusiveBuildGroup toGroupID) where T : IEntityDescriptor, new();

void SwapEntityGroup<T>(EGID fromID, ExclusiveBuildGroup toGroupID, ExclusiveBuildGroup mustBeFromGroup)
void SwapEntityGroup<T>(EGID fromID, ExclusiveBuildGroup fromGroup, ExclusiveBuildGroup toGroupID)
where T : IEntityDescriptor, new();

void SwapEntityGroup<T>(EGID fromID, EGID toId) where T : IEntityDescriptor, new();
@@ -27,8 +25,8 @@ namespace Svelto.ECS
void SwapEntityGroup<T>(EGID fromID, EGID toId, ExclusiveBuildGroup mustBeFromGroup)
where T : IEntityDescriptor, new();
#if UNITY_NATIVE
NativeEntityRemove ToNativeRemove<T>(string memberName) where T : IEntityDescriptor, new();
NativeEntitySwap ToNativeSwap<T>(string memberName) where T : IEntityDescriptor, new();
Svelto.ECS.Native.NativeEntityRemove ToNativeRemove<T>(string memberName) where T : IEntityDescriptor, new();
Svelto.ECS.Native.NativeEntitySwap ToNativeSwap<T>(string memberName) where T : IEntityDescriptor, new();
#endif
}
}

+ 3
- 1
Svelto.ECS/Core/INeedEGID.cs View File

@@ -2,10 +2,12 @@ namespace Svelto.ECS
{
/// <summary>
/// use INeedEGID on an IEntityComponent only if you need the EGID. consider using EGIDComponent instead
/// INeedEGID and EGIDComponent will become probably obsolete once QueryEntities will be able to return
/// also the EGIDs to iterate upon
/// </summary>
public interface INeedEGID
{
//The set is used only for the framework, but it must stay there
//The set is used only by the framework, but it must stay there
EGID ID { get; set; }
}
}

+ 15
- 0
Svelto.ECS/Core/INeedEntityReference.cs View File

@@ -0,0 +1,15 @@
using Svelto.ECS.Reference;

namespace Svelto.ECS
{
/// <summary>
/// The use of this is an exception and it's necessary for deprecated design only
/// It currently exist because of the publisher/consumer behavior, but the publisher/consumer must not be
/// considered an ECS pattern.
/// Other uses are invalid.
/// </summary>
public interface INeedEntityReference
{
EntityReference selfReference { get; set; }
}
}

+ 137
- 86
Svelto.ECS/Core/QueryGroups.cs View File

@@ -1,3 +1,4 @@
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Threading;
using Svelto.DataStructures;
@@ -9,145 +10,195 @@ namespace Svelto.ECS.Experimental
static GroupsList()
{
groups = new FasterList<ExclusiveGroupStruct>();
sets = new HashSet<ExclusiveGroupStruct>();
}

static readonly FasterList<ExclusiveGroupStruct> groups;
public FasterList<ExclusiveGroupStruct> reference => groups;
static readonly HashSet<ExclusiveGroupStruct> sets;

public void Reset() { sets.Clear(); }

public void AddRange(ExclusiveGroupStruct[] groupsToAdd, int length)
{
for (int i = 0; i < length; i++)
{
sets.Add(groupsToAdd[i]);
}
}

public void Add(ExclusiveGroupStruct @group) { sets.Add(group); }

public void Exclude(ExclusiveGroupStruct[] groupsToIgnore, int length)
{
for (int i = 0; i < length; i++)
{
sets.Remove(groupsToIgnore[i]);
}
}

public void Exclude(ExclusiveGroupStruct groupsToIgnore) { sets.Remove(groupsToIgnore); }

public void EnsureCapacity(uint preparecount) { groups.EnsureCapacity(preparecount); }

public FasterList<ExclusiveGroupStruct> Evaluate()
{
groups.FastClear();

foreach (var item in sets)
{
groups.Add(item);
}

return groups;
}
}

public ref struct QueryGroups
{
static readonly ThreadLocal<GroupsList> groups = new ThreadLocal<GroupsList>();

public QueryGroups(LocalFasterReadOnlyList<ExclusiveGroupStruct> findGroups)
public QueryGroups(LocalFasterReadOnlyList<ExclusiveGroupStruct> groups)
{
var groupsValue = groups.Value;
var group = groupsValue.reference;
var groupsValue = QueryGroups.groups.Value;

group.FastClear();
for (int i = 0; i < findGroups.count; i++)
group.Add(findGroups[i]);
groupsValue.Reset();
groupsValue.AddRange(groups.ToArrayFast(out var count), count);
}

public QueryGroups(ExclusiveGroupStruct findGroups)
public QueryGroups(ExclusiveGroupStruct @group)
{
var groupsValue = groups.Value;
var group = groupsValue.reference;

group.FastClear();
group.Add(findGroups);
groupsValue.Reset();
groupsValue.Add(@group);
}

public QueryGroups(uint preparecount)
{
var groupsValue = groups.Value;
var group = groupsValue.reference;

group.FastClear();
group.EnsureCapacity(preparecount);
groupsValue.Reset();
groupsValue.EnsureCapacity(preparecount);
}

public QueryResult Except(ExclusiveGroupStruct[] groupsToIgnore)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public QueryGroups Union(ExclusiveGroupStruct group)
{
var group = groups.Value.reference;
var groupsCount = group.count;
var groupsValue = groups.Value;

for (int i = 0; i < groupsToIgnore.Length; i++)
for (int j = 0; j < groupsCount; j++)
{
if (groupsToIgnore[i] == group[j])
{
group.UnorderedRemoveAt(j);
j--;
groupsCount--;
}
}
groupsValue.Add(group);

return new QueryResult(group);
return this;
}
public QueryResult Except(FasterList<ExclusiveGroupStruct> groupsToIgnore)

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public QueryGroups Union(LocalFasterReadOnlyList<ExclusiveGroupStruct> groups)
{
var group = groups.Value.reference;
var groupsCount = group.count;
var groupsValue = QueryGroups.groups.Value;

for (int i = 0; i < groupsToIgnore.count; i++)
for (int j = 0; j < groupsCount; j++)
{
if (groupsToIgnore[i] == group[j])
{
group.UnorderedRemoveAt(j);
j--;
groupsCount--;
}
}
groupsValue.AddRange(groups.ToArrayFast(out var count), count);

return new QueryResult(group);
return this;
}
public QueryResult Except(ExclusiveGroupStruct groupsToIgnore)

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public QueryGroups Except(ExclusiveGroupStruct group)
{
var group = groups.Value.reference;
var groupsCount = group.count;
var groupsValue = groups.Value;

for (int j = 0; j < groupsCount; j++)
if (groupsToIgnore == group[j])
{
group.UnorderedRemoveAt(j);
j--;
groupsCount--;
}
groupsValue.Exclude(group);

return new QueryResult(group);
return this;
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public int Count<T>
(EntitiesDB entitiesDB, in LocalFasterReadOnlyList<ExclusiveGroupStruct> groups) where T : struct, IEntityComponent
public QueryGroups Except(ExclusiveGroupStruct[] groupsToIgnore)
{
int count = 0;
var groupsValue = QueryGroups.groups.Value;

var groupsCount = groups.count;
for (int i = 0; i < groupsCount; ++i)
{
count += entitiesDB.Count<T>(groups[i]);
}
groupsValue.Exclude(groupsToIgnore, groupsToIgnore.Length);

return count;
return this;
}
public QueryResult WithAny<T>(EntitiesDB entitiesDB)
where T : struct, IEntityComponent

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public QueryGroups Except(LocalFasterReadOnlyList<ExclusiveGroupStruct> groupsToIgnore)
{
var group = groups.Value.reference;
var groupsCount = group.count;
var groupsValue = QueryGroups.groups.Value;

for (var i = 0; i < groupsCount; i++)
{
if (entitiesDB.Count<T>(group[i]) == 0)
{
group.UnorderedRemoveAt(i);
i--;
groupsCount--;
}
}
groupsValue.Exclude(groupsToIgnore.ToArrayFast(out var count), count);

return this;
}

return new QueryResult(group);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public QueryGroups Except(FasterList<ExclusiveGroupStruct> groupsToIgnore)
{
var groupsValue = QueryGroups.groups.Value;

groupsValue.Exclude(groupsToIgnore.ToArrayFast(out var count), count);

return this;
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Add(ExclusiveGroupStruct group)
public QueryGroups Except(FasterReadOnlyList<ExclusiveGroupStruct> groupsToIgnore)
{
var groupsValue = QueryGroups.groups.Value;

groupsValue.Exclude(groupsToIgnore.ToArrayFast(out var count), count);

return this;
}

// public QueryGroups WithAny<T>(EntitiesDB entitiesDB)
// where T : struct, IEntityComponent
// {
// var group = groups.Value.reference;
// var groupsCount = group.count;
//
// for (uint i = 0; i < groupsCount; i++)
// {
// if (entitiesDB.Count<T>(group[i]) == 0)
// {
// group.UnorderedRemoveAt(i);
// i--;
// groupsCount--;
// }
// }
//
// return this;
// }

public QueryResult Evaluate()
{
groups.Value.reference.Add(group);
var groupsValue = groups.Value;

return new QueryResult(groupsValue.Evaluate());
}
}

public readonly ref struct QueryResult
{
readonly FasterReadOnlyList<ExclusiveGroupStruct> _group;
public QueryResult(FasterList<ExclusiveGroupStruct> @group) { _group = @group; }
public FasterReadOnlyList<ExclusiveGroupStruct> result => _group;

public LocalFasterReadOnlyList<ExclusiveGroupStruct> result => _group;

readonly FasterReadOnlyList<ExclusiveGroupStruct> _group;

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public int Count<T>(EntitiesDB entitiesDB)
where T : struct, IEntityComponent
{
int count = 0;

var groupsCount = result.count;
for (int i = 0; i < groupsCount; ++i)
{
count += entitiesDB.Count<T>(result[i]);
}

return count;
}
}
}

+ 16
- 0
Svelto.ECS/Core/ReactEngineContainer.cs View File

@@ -0,0 +1,16 @@
using Svelto.ECS.Internal;

namespace Svelto.ECS
{
public readonly struct ReactEngineContainer
{
public readonly string name;
public readonly IReactEngine engine;

public ReactEngineContainer(IReactEngine engine, string name)
{
this.name = name;
this.engine = engine;
}
}
}

+ 31
- 1
Svelto.ECS/Core/SetEGIDWithoutBoxing.cs View File

@@ -1,10 +1,14 @@
using Svelto.ECS.Reference;

namespace Svelto.ECS.Internal
{
delegate void SetEGIDWithoutBoxingActionCast<T>(ref T target, EGID egid) where T : struct, IEntityComponent;
delegate void SetReferenceWithoutBoxingActionCast<T>(ref T target, EntityReference egid) where T : struct, IEntityComponent;

static class SetEGIDWithoutBoxing<T> where T : struct, IEntityComponent
{
public static readonly SetEGIDWithoutBoxingActionCast<T> SetIDWithoutBoxing = MakeSetter();
public static readonly SetEGIDWithoutBoxingActionCast<T> SetIDWithoutBoxing = MakeSetter();
public static readonly SetReferenceWithoutBoxingActionCast<T> SetRefWithoutBoxing = MakeSetterReference();

public static void Warmup() { }

@@ -29,12 +33,38 @@ namespace Svelto.ECS.Internal
return null;
}
static SetReferenceWithoutBoxingActionCast<T> MakeSetterReference()
{
if (ComponentBuilder<T>.HAS_REFERENCE)
{
#if !ENABLE_IL2CPP
var method = typeof(Trick).GetMethod(nameof(Trick.SetEGIDImplRef)).MakeGenericMethod(typeof(T));
return (SetReferenceWithoutBoxingActionCast<T>) System.Delegate.CreateDelegate(
typeof(SetReferenceWithoutBoxingActionCast<T>), method);
#else
return (ref T target, EntityReference reference) =>
{
var needEgid = (target as INeedEntityReference);
needEgid.selfReference = reference;
target = (T) needEgid;
};
#endif
}

return null;
}
static class Trick
{
public static void SetEGIDImpl<U>(ref U target, EGID egid) where U : struct, INeedEGID
{
target.ID = egid;
}
public static void SetEGIDImplRef<U>(ref U target, EntityReference reference) where U : struct, INeedEntityReference
{
target.selfReference = reference;
}
}
}
}

+ 29
- 27
Svelto.ECS/Core/SimpleEntitiesSubmissionScheduler.cs View File

@@ -1,5 +1,5 @@
using System;
using System.Collections;
using System.Collections.Generic;

namespace Svelto.ECS.Schedulers
{
@@ -7,52 +7,54 @@ namespace Svelto.ECS.Schedulers
{
public SimpleEntitiesSubmissionScheduler(uint maxNumberOfOperationsPerFrame = UInt32.MaxValue)
{
_maxNumberOfOperationsPerFrame = maxNumberOfOperationsPerFrame;
_enumerator = SubmitEntitiesAsync(maxNumberOfOperationsPerFrame);
}
public IEnumerator SubmitEntitiesAsync()
{
if (paused == false)
{
var submitEntities = _onTick.Invoke(_maxNumberOfOperationsPerFrame);
while (submitEntities.MoveNext())
yield return null;
}
}
public IEnumerator SubmitEntitiesAsync(uint maxNumberOfOperationsPerFrame)

public IEnumerator<bool> SubmitEntitiesAsync() { return _enumerator; }

public IEnumerator<bool> SubmitEntitiesAsync(uint maxNumberOfOperations)
{
if (paused == false)
EnginesRoot.EntitiesSubmitter entitiesSubmitter = _onTick.Value;
entitiesSubmitter.maxNumberOfOperationsPerFrame = maxNumberOfOperations;

while (true)
{
var submitEntities = _onTick.Invoke(maxNumberOfOperationsPerFrame);
while (submitEntities.MoveNext())
yield return null;
if (paused == false)
{
var entitiesSubmitterSubmitEntities = entitiesSubmitter.submitEntities;

entitiesSubmitterSubmitEntities.MoveNext();

if (entitiesSubmitterSubmitEntities.Current == true)
yield return true;
else
yield return false;
}
}
}

public void SubmitEntities()
{
var enumerator = SubmitEntitiesAsync();
_enumerator.MoveNext();

while (enumerator.MoveNext());
while (_enumerator.Current == true)
_enumerator.MoveNext();
}

public override bool paused { get; set; }
public override bool paused { get; set; }
public override void Dispose() { }

protected internal override EnginesRoot.EntitiesSubmitter onTick
{
set
{
DBC.ECS.Check.Require(_onTick.IsUnused, "a scheduler can be exclusively used by one enginesRoot only");
DBC.ECS.Check.Require(_onTick == null, "a scheduler can be exclusively used by one enginesRoot only");
_onTick = value;
}
}

EnginesRoot.EntitiesSubmitter _onTick;
readonly uint _maxNumberOfOperationsPerFrame;
EnginesRoot.EntitiesSubmitter? _onTick;
readonly IEnumerator<bool> _enumerator;
}
}

+ 33
- 11
Svelto.ECS/Core/Streams/Consumer.cs View File

@@ -15,13 +15,16 @@ namespace Svelto.ECS
#endif
_ringBuffer = new RingBuffer<ValueTuple<T, EGID>>((int) capacity,
#if DEBUG && !PROFILE_SVELTO
_name
_name
#else
string.Empty
#endif
);
mustBeDisposed = MemoryUtilities.Alloc(sizeof(bool), Allocator.Persistent);
mustBeDisposed = MemoryUtilities.Alloc<bool>(1, Allocator.Persistent);
*(bool*) mustBeDisposed = false;

isActive = MemoryUtilities.Alloc<bool>(1, Allocator.Persistent);
*(bool*) isActive = true;
}
}

@@ -33,7 +36,11 @@ namespace Svelto.ECS

internal void Enqueue(in T entity, in EGID egid)
{
_ringBuffer.Enqueue((entity, egid));
unsafe
{
if (*(bool*)isActive)
_ringBuffer.Enqueue((entity, egid));
}
}

public bool TryDequeue(out T entity)
@@ -45,6 +52,9 @@ namespace Svelto.ECS
return tryDequeue;
}

//Note: it is correct to publish the EGID at the moment of the publishing, as the responsibility of
//the publisher consumer is not tracking the real state of the entity in the database at the
//moment of the consumption, but it's instead to store a copy of the entity at the moment of the publishing
public bool TryDequeue(out T entity, out EGID id)
{
var tryDequeue = _ringBuffer.TryDequeue(out var values);
@@ -55,10 +65,7 @@ namespace Svelto.ECS
return tryDequeue;
}

public void Flush()
{
_ringBuffer.Reset();
}
public void Flush() { _ringBuffer.Reset(); }

public void Dispose()
{
@@ -68,20 +75,35 @@ namespace Svelto.ECS
}
}

public uint Count()
{
return (uint) _ringBuffer.Count;
}
public uint Count() { return (uint) _ringBuffer.Count; }

public void Free()
{
MemoryUtilities.Free(mustBeDisposed, Allocator.Persistent);
MemoryUtilities.Free(isActive, Allocator.Persistent);
}

public void Pause()
{
unsafe
{
*(bool*) isActive = false;
}
}

public void Resume()
{
unsafe
{
*(bool*) isActive = true;
}
}

readonly RingBuffer<ValueTuple<T, EGID>> _ringBuffer;

internal readonly ExclusiveGroupStruct group;
internal readonly bool hasGroup;
internal IntPtr isActive;
internal IntPtr mustBeDisposed;

#if DEBUG && !PROFILE_SVELTO


+ 5
- 1
Svelto.ECS/Core/Streams/EnginesRoot.Streams.cs View File

@@ -1,12 +1,16 @@
namespace Svelto.ECS
using System.Runtime.CompilerServices;

namespace Svelto.ECS
{
public partial class EnginesRoot
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal Consumer<T> GenerateConsumer<T>(string name, uint capacity) where T : unmanaged, IEntityComponent
{
return _entityStreams.GenerateConsumer<T>(name, capacity);
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal Consumer<T> GenerateConsumer<T>(ExclusiveGroupStruct group, string name, uint capacity)
where T : unmanaged, IEntityComponent
{


+ 4
- 6
Svelto.ECS/Core/Streams/EntitiesDB.Streams.cs View File

@@ -5,15 +5,13 @@ namespace Svelto.ECS
public partial class EntitiesDB
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
//Todo I should rename this method to reflect it's original intention
public void PublishEntityChange<T>(EGID egid) where T : unmanaged, IEntityComponent
{
//Note: it is correct to publish the EGID at the moment of the publishing, as the responsibility of
//the publisher consumer is not tracking the real state of the entity in the database at the
//moment of the consumption, but it's instead to store a copy of the entity at the moment of the publishing
_entityStream.PublishEntity(ref this.QueryEntity<T>(egid), egid);
}
#if later
public ThreadSafeNativeEntityStream<T> GenerateThreadSafePublisher<T>() where T: unmanaged, IEntityComponent
{
return _entityStream.GenerateThreadSafePublisher<T>(this);
}
#endif
}
}

+ 3
- 13
Svelto.ECS/Core/Streams/EntitiesStreams.cs View File

@@ -14,7 +14,7 @@ namespace Svelto.ECS
/// one only
/// - you want to communicate between EnginesRoots
/// </summary>
internal struct EntitiesStreams : IDisposable
struct EntitiesStreams : IDisposable
{
internal Consumer<T> GenerateConsumer<T>(string name, uint capacity)
where T : unmanaged, IEntityComponent
@@ -34,16 +34,6 @@ namespace Svelto.ECS
var typeSafeStream = (EntityStream<T>) _streams[TypeRefWrapper<T>.wrapper];
return typeSafeStream.GenerateConsumer(group, name, capacity);
}
#if later
public ThreadSafeNativeEntityStream<T> GenerateThreadSafePublisher<T>(EntitiesDB entitiesDB) where T: unmanaged, IEntityComponent
{
var threadSafeNativeEntityStream = new ThreadSafeNativeEntityStream<T>(entitiesDB);
_streams[TypeRefWrapper<T>.wrapper] = threadSafeNativeEntityStream;

return threadSafeNativeEntityStream;
}
#endif

internal void PublishEntity<T>(ref T entity, EGID egid) where T : unmanaged, IEntityComponent
{
@@ -62,11 +52,11 @@ namespace Svelto.ECS
public static EntitiesStreams Create()
{
var stream = new EntitiesStreams();
stream._streams = ManagedSveltoDictionary<RefWrapperType, ITypeSafeStream>.Create();
stream._streams = FasterDictionary<RefWrapperType, ITypeSafeStream>.Construct();

return stream;
}

ManagedSveltoDictionary<RefWrapperType, ITypeSafeStream> _streams;
FasterDictionary<RefWrapperType, ITypeSafeStream> _streams;
}
}

+ 3
- 3
Svelto.ECS/Core/Streams/EntityStream.cs View File

@@ -33,15 +33,15 @@ namespace Svelto.ECS
if (*(bool*) _consumers[i].mustBeDisposed)
{
_consumers[i].Free();
_consumers.UnorderedRemoveAt(i);
_consumers.UnorderedRemoveAt((uint) i);
--i;
continue;
}

if (_consumers[i].hasGroup)
{
if (egid.groupID == _consumers[i].@group)
_consumers[i].Enqueue(entity, egid);
if (egid.groupID == _consumers[i].@group)
_consumers[i].Enqueue(entity, egid);
}
else
{


+ 3
- 0
Svelto.ECS/Core/Streams/GenericentityStreamConsumerFactory.cs View File

@@ -1,3 +1,4 @@
using System.Runtime.CompilerServices;
using Svelto.DataStructures;

namespace Svelto.ECS
@@ -9,12 +10,14 @@ namespace Svelto.ECS
_enginesRoot = new WeakReference<EnginesRoot>(weakReference);
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Consumer<T> GenerateConsumer<T>(string name, uint capacity)
where T : unmanaged, IEntityComponent
{
return _enginesRoot.Target.GenerateConsumer<T>(name, capacity);
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Consumer<T> GenerateConsumer<T>(ExclusiveGroupStruct @group, string name, uint capacity)
where T : unmanaged, IEntityComponent
{


+ 0
- 293
Svelto.ECS/DataStructures/FastTypeSafeDictionary.cs View File

@@ -1,293 +0,0 @@
#if EXPERIMENTAL
using System;
using System.Runtime.CompilerServices;
using Svelto.Common;
using Svelto.DataStructures;

namespace Svelto.ECS.Internal
{
sealed class FastTypeSafeDictionary<TValue> : ITypeSafeDictionary<TValue> 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<TValue>(size); }

public FastTypeSafeDictionary() { _implementation = new SetDictionary<TValue>(1); }

/// <summary>
/// Add entities from external typeSafeDictionary
/// </summary>
/// <param name="entitiesToSubmit"></param>
/// <param name="groupId"></param>
/// <exception cref="TypeSafeDictionaryException"></exception>
public void AddEntitiesFromDictionary(ITypeSafeDictionary entitiesToSubmit, uint groupId)
{
var typeSafeDictionary = entitiesToSubmit as FastTypeSafeDictionary<TValue>;

foreach (var tuple in typeSafeDictionary)
{
try
{
if (_hasEgid)
SetEGIDWithoutBoxing<TValue>.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<TValue>;
ref var entity = ref _implementation.unsafeValues[(int) valueIndex];

if (_hasEgid) SetEGIDWithoutBoxing<TValue>.SetIDWithoutBoxing(ref entity, toEntityID);

toGroupCasted.Add(fromEntityGid.entityID, entity);
}
}

public void AddEntitiesToEngines(FasterDictionary<RefWrapperType, FasterList<IEngine>> entityComponentEnginesDB,
ITypeSafeDictionary realDic,
ExclusiveGroupStruct @group,
in PlatformProfiler profiler)
{
var typeSafeDictionary = realDic as ITypeSafeDictionary<TValue>;

//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<RefWrapperType, FasterList<IEngine>> 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<RefWrapperType, FasterList<IEngine>> 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<TValue>;
var previousGroup = fromEntityGid.groupID;

if (_hasEgid) SetEGIDWithoutBoxing<TValue>.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<TValue>(); }

void AddEntityComponentToEngines(FasterDictionary<RefWrapperType, FasterList<IEngine>> 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<TValue>).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<TValue>).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<RefWrapperType, FasterList<IEngine>> @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<TValue>).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<TValue>).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<TValue>.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<TValue> 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<TValue> _implementation;
}
}
#endif

+ 4
- 4
Svelto.ECS/DataStructures/ITypeSafeDictionary.cs View File

@@ -21,14 +21,14 @@ namespace Svelto.ECS.Internal
ITypeSafeDictionary Create();

//todo: there is something wrong in the design of the execute callback methods. Something to cleanup
void ExecuteEnginesAddOrSwapCallbacks(FasterDictionary<RefWrapperType, FasterList<IReactEngine>> entityComponentEnginesDb,
void ExecuteEnginesAddOrSwapCallbacks(FasterDictionary<RefWrapperType, FasterList<ReactEngineContainer>> entityComponentEnginesDb,
ITypeSafeDictionary realDic, ExclusiveGroupStruct? fromGroup, ExclusiveGroupStruct toGroup, in PlatformProfiler profiler);
void ExecuteEnginesSwapOrRemoveCallbacks(EGID fromEntityGid, EGID? toEntityID, ITypeSafeDictionary toGroup,
FasterDictionary<RefWrapperType, FasterList<IReactEngine>> engines, in PlatformProfiler profiler);
void ExecuteEnginesRemoveCallbacks(FasterDictionary<RefWrapperType, FasterList<IReactEngine>> entityComponentEnginesDB,
FasterDictionary<RefWrapperType, FasterList<ReactEngineContainer>> engines, in PlatformProfiler profiler);
void ExecuteEnginesRemoveCallbacks(FasterDictionary<RefWrapperType, FasterList<ReactEngineContainer>> entityComponentEnginesDB,
in PlatformProfiler profiler, ExclusiveGroupStruct @group);
void AddEntitiesFromDictionary(ITypeSafeDictionary entitiesToSubmit, uint groupId);
void AddEntitiesFromDictionary(ITypeSafeDictionary entitiesToSubmit, uint groupId, EnginesRoot enginesRoot);
void AddEntityToDictionary(EGID fromEntityGid, EGID toEntityID, ITypeSafeDictionary toGroup);
void RemoveEntityFromDictionary(EGID fromEntityGid);


+ 123
- 112
Svelto.ECS/DataStructures/TypeSafeDictionary.cs View File

@@ -8,14 +8,14 @@ namespace Svelto.ECS.Internal
{
sealed class TypeSafeDictionary<TValue> : ITypeSafeDictionary<TValue> where TValue : struct, IEntityComponent
{
static readonly Type _type = typeof(TValue);
static readonly string _typeName = _type.Name;
static readonly bool _hasEgid = typeof(INeedEGID).IsAssignableFrom(_type);
static readonly Type _type = typeof(TValue);
static readonly bool _hasEgid = typeof(INeedEGID).IsAssignableFrom(_type);
static readonly bool _hasReference = typeof(INeedEntityReference).IsAssignableFrom(_type);

internal static readonly bool IsUnmanaged =
internal static readonly bool isUnmanaged =
_type.IsUnmanagedEx() && (typeof(IEntityViewComponent).IsAssignableFrom(_type) == false);

SveltoDictionary<uint, TValue, NativeStrategy<SveltoDictionaryNode<uint>>, ManagedStrategy<TValue>,
internal SveltoDictionary<uint, TValue, ManagedStrategy<SveltoDictionaryNode<uint>>, ManagedStrategy<TValue>,
ManagedStrategy<int>> implMgd;

//used directly by native methods
@@ -24,43 +24,88 @@ namespace Svelto.ECS.Internal

public TypeSafeDictionary(uint size)
{
if (IsUnmanaged)
if (isUnmanaged)
implUnmgd = new SveltoDictionary<uint, TValue, NativeStrategy<SveltoDictionaryNode<uint>>,
NativeStrategy<TValue>, NativeStrategy<int>>(size);
NativeStrategy<TValue>, NativeStrategy<int>>(size, Allocator.Persistent);
else
{
implMgd = new SveltoDictionary<uint, TValue, NativeStrategy<SveltoDictionaryNode<uint>>,
ManagedStrategy<TValue>, ManagedStrategy<int>>(size);
implMgd = new SveltoDictionary<uint, TValue, ManagedStrategy<SveltoDictionaryNode<uint>>,
ManagedStrategy<TValue>, ManagedStrategy<int>>(size, Allocator.Managed);
}
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Add(uint egidEntityId, in TValue entityComponent)
{
if (IsUnmanaged)
if (isUnmanaged)
implUnmgd.Add(egidEntityId, entityComponent);
else
implMgd.Add(egidEntityId, entityComponent);
}
/// todo: Is this really needed, cannot I just use AddEntitiesFromDictionary? Needs to be checked
public void AddEntityToDictionary(EGID fromEntityGid, EGID toEntityID, ITypeSafeDictionary toGroup)
{
if (isUnmanaged)
{
var valueIndex = implUnmgd.GetIndex(fromEntityGid.entityID);

DBC.ECS.Check.Require(toGroup != null
, "Invalid To Group"); //todo check this, if it's right merge GetIndex
{
var toGroupCasted = toGroup as ITypeSafeDictionary<TValue>;
ref var entity = ref implUnmgd.GetDirectValueByRef(valueIndex);

if (_hasEgid)
SetEGIDWithoutBoxing<TValue>.SetIDWithoutBoxing(ref entity, toEntityID);

toGroupCasted.Add(toEntityID.entityID, entity);
}
}
else
{
var valueIndex = implMgd.GetIndex(fromEntityGid.entityID);

DBC.ECS.Check.Require(toGroup != null
, "Invalid To Group"); //todo check this, if it's right merge GetIndex
{
var toGroupCasted = toGroup as ITypeSafeDictionary<TValue>;
ref var entity = ref implMgd.GetDirectValueByRef(valueIndex);

if (_hasEgid)
SetEGIDWithoutBoxing<TValue>.SetIDWithoutBoxing(ref entity, toEntityID);

toGroupCasted.Add(toEntityID.entityID, entity);
}
}
}

/// <summary>
/// Add entities from external typeSafeDictionary
/// Add entities from external typeSafeDictionary (todo: add use case)
/// </summary>
/// <param name="entitiesToSubmit"></param>
/// <param name="groupId"></param>
/// <param name="enginesRoot"></param>
/// <exception cref="TypeSafeDictionaryException"></exception>
public void AddEntitiesFromDictionary(ITypeSafeDictionary entitiesToSubmit, uint groupId)
public void AddEntitiesFromDictionary
(ITypeSafeDictionary entitiesToSubmit, uint groupId, EnginesRoot enginesRoot)
{
if (IsUnmanaged)
var safeDictionary = (entitiesToSubmit as TypeSafeDictionary<TValue>);
if (isUnmanaged)
{
var typeSafeDictionary = (entitiesToSubmit as TypeSafeDictionary<TValue>).implUnmgd;
var typeSafeDictionary = safeDictionary.implUnmgd;

foreach (var tuple in typeSafeDictionary)
try
{
var egid = new EGID(tuple.Key, groupId);
if (_hasEgid)
SetEGIDWithoutBoxing<TValue>.SetIDWithoutBoxing(
ref tuple.Value, new EGID(tuple.Key, groupId));
ref tuple.Value, egid);
if (_hasReference)
SetEGIDWithoutBoxing<TValue>.SetRefWithoutBoxing(
ref tuple.Value, enginesRoot.entityLocator.GetEntityReference(egid));

implUnmgd.Add(tuple.Key, tuple.Value);
}
@@ -74,7 +119,7 @@ namespace Svelto.ECS.Internal
}
else
{
var typeSafeDictionary = (entitiesToSubmit as TypeSafeDictionary<TValue>).implMgd;
var typeSafeDictionary = safeDictionary.implMgd;

foreach (var tuple in typeSafeDictionary)
try
@@ -96,11 +141,11 @@ namespace Svelto.ECS.Internal
}

public void ExecuteEnginesAddOrSwapCallbacks
(FasterDictionary<RefWrapperType, FasterList<IReactEngine>> entityComponentEnginesDB
(FasterDictionary<RefWrapperType, FasterList<ReactEngineContainer>> entityComponentEnginesDB
, ITypeSafeDictionary realDic, ExclusiveGroupStruct? fromGroup, ExclusiveGroupStruct toGroup
, in PlatformProfiler profiler)
{
if (IsUnmanaged)
if (isUnmanaged)
{
var typeSafeDictionary = realDic as ITypeSafeDictionary<TValue>;

@@ -122,46 +167,10 @@ namespace Svelto.ECS.Internal
}
}

public void AddEntityToDictionary(EGID fromEntityGid, EGID toEntityID, ITypeSafeDictionary toGroup)
{
if (IsUnmanaged)
{
var valueIndex = implUnmgd.GetIndex(fromEntityGid.entityID);

DBC.ECS.Check.Require(toGroup != null
, "Invalid To Group"); //todo check this, if it's right merge GetIndex
{
var toGroupCasted = toGroup as ITypeSafeDictionary<TValue>;
ref var entity = ref implUnmgd.GetDirectValueByRef(valueIndex);

if (_hasEgid)
SetEGIDWithoutBoxing<TValue>.SetIDWithoutBoxing(ref entity, toEntityID);

toGroupCasted.Add(toEntityID.entityID, entity);
}
}
else
{
var valueIndex = implMgd.GetIndex(fromEntityGid.entityID);

DBC.ECS.Check.Require(toGroup != null
, "Invalid To Group"); //todo check this, if it's right merge GetIndex
{
var toGroupCasted = toGroup as ITypeSafeDictionary<TValue>;
ref var entity = ref implMgd.GetDirectValueByRef(valueIndex);

if (_hasEgid)
SetEGIDWithoutBoxing<TValue>.SetIDWithoutBoxing(ref entity, toEntityID);

toGroupCasted.Add(toEntityID.entityID, entity);
}
}
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Clear()
{
if (IsUnmanaged)
if (isUnmanaged)
{
implUnmgd.Clear();
}
@@ -174,7 +183,7 @@ namespace Svelto.ECS.Internal
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void FastClear()
{
if (IsUnmanaged)
if (isUnmanaged)
{
implUnmgd.FastClear();
}
@@ -187,7 +196,7 @@ namespace Svelto.ECS.Internal
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool ContainsKey(uint egidEntityId)
{
if (IsUnmanaged)
if (isUnmanaged)
{
return implUnmgd.ContainsKey(egidEntityId);
}
@@ -203,77 +212,77 @@ namespace Svelto.ECS.Internal
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public uint GetIndex(uint valueEntityId)
{
if (IsUnmanaged)
if (isUnmanaged)
{
return this.implUnmgd.GetIndex(valueEntityId);
return implUnmgd.GetIndex(valueEntityId);
}
else
{
return this.implMgd.GetIndex(valueEntityId);
return implMgd.GetIndex(valueEntityId);
}
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ref TValue GetOrCreate(uint idEntityId)
{
if (IsUnmanaged)
if (isUnmanaged)
{
return ref this.implUnmgd.GetOrCreate(idEntityId);
return ref implUnmgd.GetOrCreate(idEntityId);
}
else
{
return ref this.implMgd.GetOrCreate(idEntityId);
return ref implMgd.GetOrCreate(idEntityId);
}
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public IBuffer<TValue> GetValues(out uint count)
{
if (IsUnmanaged)
if (isUnmanaged)
{
return this.implUnmgd.GetValues(out count);
return implUnmgd.GetValues(out count);
}
else
{
return this.implMgd.GetValues(out count);
return implMgd.GetValues(out count);
}
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ref TValue GetDirectValueByRef(uint key)
{
if (IsUnmanaged)
if (isUnmanaged)
{
return ref this.implUnmgd.GetDirectValueByRef(key);
return ref implUnmgd.GetDirectValueByRef(key);
}
else
{
return ref this.implMgd.GetDirectValueByRef(key);
return ref implMgd.GetDirectValueByRef(key);
}
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Has(uint key)
{
if (IsUnmanaged)
if (isUnmanaged)
{
return this.implUnmgd.ContainsKey(key);
return implUnmgd.ContainsKey(key);
}
else
{
return this.implMgd.ContainsKey(key);
return implMgd.ContainsKey(key);
}
}

public void ExecuteEnginesSwapOrRemoveCallbacks
(EGID fromEntityGid, EGID? toEntityID, ITypeSafeDictionary toGroup
, FasterDictionary<RefWrapperType, FasterList<IReactEngine>> engines, in PlatformProfiler profiler)
, FasterDictionary<RefWrapperType, FasterList<ReactEngineContainer>> engines, in PlatformProfiler profiler)
{
if (IsUnmanaged)
if (isUnmanaged)
{
var valueIndex = this.implUnmgd.GetIndex(fromEntityGid.entityID);
var valueIndex = implUnmgd.GetIndex(fromEntityGid.entityID);

ref var entity = ref this.implUnmgd.GetDirectValueByRef(valueIndex);
ref var entity = ref implUnmgd.GetDirectValueByRef(valueIndex);

//move
if (toGroup != null)
@@ -281,6 +290,7 @@ namespace Svelto.ECS.Internal
var toGroupCasted = toGroup as ITypeSafeDictionary<TValue>;
var previousGroup = fromEntityGid.groupID;

//todo: why is setting the EGID if this code just execute callbacks?
if (_hasEgid)
SetEGIDWithoutBoxing<TValue>.SetIDWithoutBoxing(ref entity, toEntityID.Value);

@@ -297,15 +307,16 @@ namespace Svelto.ECS.Internal
}
else
{
var valueIndex = this.implMgd.GetIndex(fromEntityGid.entityID);
var valueIndex = implMgd.GetIndex(fromEntityGid.entityID);

ref var entity = ref this.implMgd.GetDirectValueByRef(valueIndex);
ref var entity = ref implMgd.GetDirectValueByRef(valueIndex);

if (toGroup != null)
{
var toGroupCasted = toGroup as ITypeSafeDictionary<TValue>;
var previousGroup = fromEntityGid.groupID;

//todo: why is setting the EGID if this code just execute callbacks?
if (_hasEgid)
SetEGIDWithoutBoxing<TValue>.SetIDWithoutBoxing(ref entity, toEntityID.Value);

@@ -322,10 +333,10 @@ namespace Svelto.ECS.Internal
}

public void ExecuteEnginesRemoveCallbacks
(FasterDictionary<RefWrapperType, FasterList<IReactEngine>> engines, in PlatformProfiler profiler
(FasterDictionary<RefWrapperType, FasterList<ReactEngineContainer>> engines, in PlatformProfiler profiler
, ExclusiveGroupStruct group)
{
if (IsUnmanaged)
if (isUnmanaged)
{
foreach (var value in implUnmgd)
ExecuteEnginesRemoveCallbackOnSingleEntity(engines, ref implUnmgd.GetValueByRef(value.Key)
@@ -342,46 +353,46 @@ namespace Svelto.ECS.Internal
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void RemoveEntityFromDictionary(EGID fromEntityGid)
{
if (IsUnmanaged)
if (isUnmanaged)
{
this.implUnmgd.Remove(fromEntityGid.entityID);
implUnmgd.Remove(fromEntityGid.entityID);
}
else
{
this.implMgd.Remove(fromEntityGid.entityID);
implMgd.Remove(fromEntityGid.entityID);
}
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SetCapacity(uint size)
{
if (IsUnmanaged)
if (isUnmanaged)
{
this.implUnmgd.SetCapacity(size);
implUnmgd.ExpandTo(size);
}
else
{
this.implMgd.SetCapacity(size);
implMgd.ExpandTo(size);
}
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Trim()
{
if (IsUnmanaged)
if (isUnmanaged)
{
this.implUnmgd.Trim();
implUnmgd.Trim();
}
else
{
this.implMgd.Trim();
implMgd.Trim();
}
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool TryFindIndex(uint entityId, out uint index)
{
if (IsUnmanaged)
if (isUnmanaged)
{
return implUnmgd.TryFindIndex(entityId, out index);
}
@@ -393,7 +404,7 @@ namespace Svelto.ECS.Internal

public void KeysEvaluator(Action<uint> action)
{
if (IsUnmanaged)
if (isUnmanaged)
{
foreach (var key in implUnmgd.keys)
{
@@ -412,13 +423,13 @@ namespace Svelto.ECS.Internal
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool TryGetValue(uint entityId, out TValue item)
{
if (IsUnmanaged)
if (isUnmanaged)
{
return this.implUnmgd.TryGetValue(entityId, out item);
return implUnmgd.TryGetValue(entityId, out item);
}
else
{
return this.implMgd.TryGetValue(entityId, out item);
return implMgd.TryGetValue(entityId, out item);
}
}

@@ -427,13 +438,13 @@ namespace Svelto.ECS.Internal
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
if (IsUnmanaged)
if (isUnmanaged)
{
return (uint) this.implUnmgd.count;
return (uint) implUnmgd.count;
}
else
{
return (uint) this.implMgd.count;
return (uint) implMgd.count;
}
}
}
@@ -443,19 +454,19 @@ namespace Svelto.ECS.Internal
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
if (IsUnmanaged)
if (isUnmanaged)
{
return ref this.implUnmgd.GetValueByRef(idEntityId);
return ref implUnmgd.GetValueByRef(idEntityId);
}
else
{
return ref this.implMgd.GetValueByRef(idEntityId);
return ref implMgd.GetValueByRef(idEntityId);
}
}
}

static void ExecuteEnginesRemoveCallbackOnSingleEntity
(FasterDictionary<RefWrapperType, FasterList<IReactEngine>> engines, ref TValue entity
(FasterDictionary<RefWrapperType, FasterList<ReactEngineContainer>> engines, ref TValue entity
, in PlatformProfiler profiler, EGID egid)
{
if (!engines.TryGetValue(new RefWrapperType(_type), out var entityComponentsEngines))
@@ -464,14 +475,14 @@ namespace Svelto.ECS.Internal
for (var i = 0; i < entityComponentsEngines.count; i++)
try
{
using (profiler.Sample(entityComponentsEngines[i], _typeName))
using (profiler.Sample(entityComponentsEngines[i].name))
{
(entityComponentsEngines[i] as IReactOnAddAndRemove<TValue>).Remove(ref entity, egid);
(entityComponentsEngines[i].engine as IReactOnAddAndRemove<TValue>).Remove(ref entity, egid);
}
}
catch
{
Svelto.Console.LogError(
Console.LogError(
"Code crashed inside Remove callback ".FastConcat(typeof(TValue).ToString()));

throw;
@@ -479,7 +490,7 @@ namespace Svelto.ECS.Internal
}

void ExecuteEnginesAddOrSwapCallbacksOnSingleEntity
(FasterDictionary<RefWrapperType, FasterList<IReactEngine>> engines, ref TValue entity
(FasterDictionary<RefWrapperType, FasterList<ReactEngineContainer>> engines, ref TValue entity
, ExclusiveGroupStruct? previousGroup, in PlatformProfiler profiler, EGID egid)
{
//get all the engines linked to TValue
@@ -491,14 +502,14 @@ namespace Svelto.ECS.Internal
for (var i = 0; i < entityComponentsEngines.count; i++)
try
{
using (profiler.Sample(entityComponentsEngines[i], _typeName))
using (profiler.Sample(entityComponentsEngines[i].name))
{
(entityComponentsEngines[i] as IReactOnAddAndRemove<TValue>).Add(ref entity, egid);
(entityComponentsEngines[i].engine as IReactOnAddAndRemove<TValue>).Add(ref entity, egid);
}
}
catch
{
Svelto.Console.LogError(
Console.LogError(
"Code crashed inside Add callback ".FastConcat(typeof(TValue).ToString()));

throw;
@@ -509,15 +520,15 @@ namespace Svelto.ECS.Internal
for (var i = 0; i < entityComponentsEngines.count; i++)
try
{
using (profiler.Sample(entityComponentsEngines[i], _typeName))
using (profiler.Sample(entityComponentsEngines[i].name))
{
(entityComponentsEngines[i] as IReactOnSwap<TValue>).MovedTo(
(entityComponentsEngines[i].engine as IReactOnSwap<TValue>).MovedTo(
ref entity, previousGroup.Value, egid);
}
}
catch (Exception)
{
Svelto.Console.LogError(
Console.LogError(
"Code crashed inside MoveTo callback ".FastConcat(typeof(TValue).ToString()));

throw;
@@ -527,7 +538,7 @@ namespace Svelto.ECS.Internal

public void Dispose()
{
if (IsUnmanaged)
if (isUnmanaged)
implUnmgd.Dispose();
else
implMgd.Dispose();


+ 13
- 6
Svelto.ECS/DataStructures/Unmanaged/AtomicNativeBags.cs View File

@@ -9,12 +9,6 @@ namespace Svelto.ECS.DataStructures
{
public unsafe struct AtomicNativeBags:IDisposable
{
[global::Unity.Collections.LowLevel.Unsafe.NativeDisableUnsafePtrRestriction]
NativeBag* _data;
readonly Allocator _allocator;
readonly uint _threadsCount;

public uint count => _threadsCount;

public AtomicNativeBags(Allocator allocator)
@@ -42,16 +36,20 @@ namespace Svelto.ECS.DataStructures
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ref NativeBag GetBuffer(int index)
{
#if DEBUG
if (_data == null)
throw new Exception("using invalid AtomicNativeBags");
#endif
return ref MemoryUtilities.ArrayElementAsRef<NativeBag>((IntPtr) _data, index);
}

public void Dispose()
{
#if DEBUG
if (_data == null)
throw new Exception("using invalid AtomicNativeBags");
#endif
for (int i = 0; i < _threadsCount; i++)
{
@@ -63,14 +61,23 @@ namespace Svelto.ECS.DataStructures

public void Clear()
{
#if DEBUG
if (_data == null)
throw new Exception("using invalid AtomicNativeBags");
#endif
for (int i = 0; i < _threadsCount; i++)
{
GetBuffer(i).Clear();
}
}
#if UNITY_COLLECTIONS
[global::Unity.Collections.LowLevel.Unsafe.NativeDisableUnsafePtrRestriction]
#endif
NativeBag* _data;
readonly Allocator _allocator;
readonly uint _threadsCount;
}
}
#endif

+ 5
- 4
Svelto.ECS/DataStructures/Unmanaged/NativeBag.cs View File

@@ -78,8 +78,7 @@ namespace Svelto.ECS.DataStructures
{
unsafe
{
var sizeOf = MemoryUtilities.SizeOf<UnsafeBlob>();
var listData = (UnsafeBlob*) MemoryUtilities.Alloc((uint) sizeOf, allocator);
var listData = (UnsafeBlob*) MemoryUtilities.Alloc<UnsafeBlob>((uint) 1, allocator);

//clear to nullify the pointers
//MemoryUtilities.MemClear((IntPtr) listData, (uint) sizeOf);
@@ -130,7 +129,7 @@ namespace Svelto.ECS.DataStructures
{
#endif
_queue->Dispose();
MemoryUtilities.Free((IntPtr) _queue, _queue->allocator);
MemoryUtilities.Free((IntPtr) _queue, _queue->allocator);
_queue = null;
#if ENABLE_THREAD_SAFE_CHECKS
}
@@ -151,6 +150,7 @@ namespace Svelto.ECS.DataStructures

var sizeOf = MemoryUtilities.SizeOf<T>();
if (_queue->space - sizeOf < 0)
//Todo: NativeBag is very complicated. At the time of writing of this comment I don't remember if the sizeof really needs to be aligned by 4. To check and change this comment
_queue->Realloc((uint) ((_queue->capacity + MemoryUtilities.Align4((uint) sizeOf)) * 2.0f));

#if ENABLE_THREAD_SAFE_CHECKS
@@ -182,6 +182,7 @@ namespace Svelto.ECS.DataStructures
#endif
var sizeOf = MemoryUtilities.SizeOf<T>();
if (_queue->space - sizeOf < 0)
//Todo: NativeBag is very complicated. At the time of writing of this comment I don't remember if the sizeof really needs to be aligned by 4. To check and change this comment
_queue->Realloc((uint) ((_queue->capacity + MemoryUtilities.Align4((uint) sizeOf)) * 2.0f));

_queue->Write(item);
@@ -272,7 +273,7 @@ namespace Svelto.ECS.DataStructures
#if ENABLE_THREAD_SAFE_CHECKS
int _threadSentinel;
#endif
#if UNITY_NATIVE
#if UNITY_COLLECTIONS
[global::Unity.Collections.LowLevel.Unsafe.NativeDisableUnsafePtrRestriction]
#endif
unsafe UnsafeBlob* _queue;


+ 37
- 18
Svelto.ECS/DataStructures/Unmanaged/NativeDynamicArray.cs View File

@@ -34,6 +34,20 @@ namespace Svelto.ECS.DataStructures
return (_list->count / MemoryUtilities.SizeOf<T>());
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public int Size()
{
unsafe
{
#if DEBUG && !PROFILE_SVELTO
if (_list == null)
throw new Exception("NativeDynamicArray: null-access");

#endif
return (_list->count);
}
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public int Capacity<T>() where T : struct
@@ -51,6 +65,11 @@ namespace Svelto.ECS.DataStructures
}
}

public static NativeDynamicArray Alloc<T>(uint newLength = 0) where T : struct
{
return Alloc<T>(Allocator.Persistent, newLength);
}

public static NativeDynamicArray Alloc<T>(Allocator allocator, uint newLength = 0) where T : struct
{
unsafe
@@ -60,16 +79,13 @@ namespace Svelto.ECS.DataStructures
#else
NativeDynamicArray rtnStruc = default;
#endif
var sizeOf = MemoryUtilities.SizeOf<T>();

uint structSize = (uint) MemoryUtilities.SizeOf<UnsafeArray>();
UnsafeArray* listData = (UnsafeArray*) MemoryUtilities.Alloc(structSize, allocator);
UnsafeArray* listData = (UnsafeArray*) MemoryUtilities.Alloc<UnsafeArray>(1, allocator);

//clear to nullify the pointers
//MemoryUtilities.MemClear((IntPtr) listData, structSize);

rtnStruc._allocator = allocator;
listData->Realloc((uint) (newLength * sizeOf), allocator);
listData->Realloc<T>(newLength, allocator);

rtnStruc._list = listData;

@@ -93,6 +109,12 @@ namespace Svelto.ECS.DataStructures
return ref _list->Get<T>(index);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ref T Get<T>(int index) where T : struct
{
return ref Get<T>((uint) index);
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Set<T>(uint index, in T value) where T : struct
@@ -118,6 +140,8 @@ namespace Svelto.ECS.DataStructures
throw new Exception("NativeDynamicArray: null-access");
#endif
_list->Dispose(_allocator);
MemoryUtilities.Free((IntPtr) _list, _allocator);
_list = null;
}

@@ -132,10 +156,10 @@ namespace Svelto.ECS.DataStructures
if (_hashType != TypeHash<T>.hash)
throw new Exception("NativeDynamicArray: not expected type used");
#endif
var structSize = (uint) MemoryUtilities.SizeOf<T>();
if (_list->space - (int) structSize < 0)
_list->Realloc((uint) (((uint) ((Count<T>() + 1) * 1.5f) * (float) structSize)), _allocator);
if (Count<T>() == Capacity<T>())
{
_list->Realloc<T>((uint) ((Capacity<T>() + 1) * 1.5f), _allocator);
}

_list->Add(item);
}
@@ -155,7 +179,7 @@ namespace Svelto.ECS.DataStructures
var structSize = (uint) MemoryUtilities.SizeOf<T>();

if (index >= Capacity<T>())
_list->Realloc((uint) (((index + 1) * 1.5f) * structSize), _allocator);
_list->Realloc<T>((uint) ((index + 1) * 1.5f), _allocator);

var writeIndex = (index + 1) * structSize;
if (_list->count < writeIndex)
@@ -165,7 +189,7 @@ namespace Svelto.ECS.DataStructures
}
}
public void Grow<T>(uint newCapacity) where T : struct
public void Resize<T>(uint newCapacity) where T : struct
{
unsafe
{
@@ -174,13 +198,8 @@ namespace Svelto.ECS.DataStructures
throw new Exception("NativeDynamicArray: null-access");
if (_hashType != TypeHash<T>.hash)
throw new Exception("NativeDynamicArray: not expected type used");
if (newCapacity <= Capacity<T>())
throw new Exception("New capacity must be greater than current one");
#endif
uint structSize = (uint) MemoryUtilities.SizeOf<T>();

uint size = (uint) (newCapacity * structSize);
_list->Realloc((uint) size, _allocator);
_list->Realloc<T>((uint) newCapacity, _allocator);
}
}

@@ -358,7 +377,7 @@ namespace Svelto.ECS.DataStructures
}
}
#if UNITY_NATIVE
#if UNITY_COLLECTIONS
[global::Unity.Burst.NoAlias] [global::Unity.Collections.LowLevel.Unsafe.NativeDisableUnsafePtrRestriction]
#endif
unsafe UnsafeArray* _list;


+ 17
- 0
Svelto.ECS/DataStructures/Unmanaged/NativeDynamicArrayCast.cs View File

@@ -1,9 +1,14 @@
using System.Runtime.CompilerServices;
using Svelto.Common;

namespace Svelto.ECS.DataStructures
{
public struct NativeDynamicArrayCast<T> where T : struct
{
public NativeDynamicArrayCast(uint size, Allocator allocator)
{
_array = NativeDynamicArray.Alloc<T>(allocator, size);
}
public NativeDynamicArrayCast(NativeDynamicArray array) : this() { _array = array; }

[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -14,6 +19,12 @@ namespace Svelto.ECS.DataStructures
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => _array.Count<T>();
}
public int capacity
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => _array.Capacity<T>();
}

public ref T this[int index]
{
@@ -44,6 +55,12 @@ namespace Svelto.ECS.DataStructures

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ref T AddAt(uint lastIndex) { return ref _array.AddAt<T>(lastIndex); }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Resize(uint newSize) { _array.Resize<T>(newSize); }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public NativeDynamicArray ToNativeArray() { return _array; }

public bool isValid => _array.isValid;



+ 1
- 1
Svelto.ECS/DataStructures/Unmanaged/NativeDynamicArrayUnityExtension.cs View File

@@ -1,4 +1,4 @@
#if UNITY_NATIVE
#if UNITY_COLLECTIONS
using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe;



+ 29
- 16
Svelto.ECS/DataStructures/Unmanaged/SharedNativeInt.cs View File

@@ -6,7 +6,7 @@ namespace Svelto.ECS.DataStructures
{
public struct SharedNativeInt: IDisposable
{
#if UNITY_NATIVE
#if UNITY_COLLECTIONS
[global::Unity.Collections.LowLevel.Unsafe.NativeDisableUnsafePtrRestriction]
#endif
unsafe int* data;
@@ -34,15 +34,15 @@ namespace Svelto.ECS.DataStructures
return current;
}
}
public static implicit operator int(SharedNativeInt t)
{
unsafe
{
#if DEBUG && !PROFILE_SVELTO
if (t.data == null)
#if DEBUG && !PROFILE_SVELTO
if (t.data == null)
throw new Exception("using disposed SharedInt");
#endif
#endif
return *t.data;
}
}
@@ -66,12 +66,12 @@ namespace Svelto.ECS.DataStructures
#if DEBUG && !PROFILE_SVELTO
if (data == null)
throw new Exception("null-access");
#endif
#endif
return Interlocked.Decrement(ref *data);
}
}
public int Increment()
{
unsafe
@@ -79,12 +79,12 @@ namespace Svelto.ECS.DataStructures
#if DEBUG && !PROFILE_SVELTO
if (data == null)
throw new Exception("null-access");
#endif
#endif
return Interlocked.Increment(ref *data);
}
}
public int Add(int val)
{
unsafe
@@ -92,12 +92,25 @@ namespace Svelto.ECS.DataStructures
#if DEBUG && !PROFILE_SVELTO
if (data == null)
throw new Exception("null-access");
#endif
#endif
return Interlocked.Add(ref *data, val);
}
}

public int CompareExchange(int value, int compare)
{
unsafe
{
#if DEBUG && !PROFILE_SVELTO
if (data == null)
throw new Exception("null-access");
#endif

return Interlocked.CompareExchange(ref *data, value, compare);
}
}

public void Set(int val)
{
unsafe
@@ -105,8 +118,8 @@ namespace Svelto.ECS.DataStructures
#if DEBUG && !PROFILE_SVELTO
if (data == null)
throw new Exception("null-access");
#endif
#endif
Volatile.Write(ref *data, val);
}
}


+ 1
- 1
Svelto.ECS/DataStructures/Unmanaged/ThreadSafeNativeBag.cs View File

@@ -183,7 +183,7 @@ namespace Svelto.ECS.DataStructures
}
}
#if UNITY_NATIVE
#if UNITY_COLLECTIONS
[global::Unity.Collections.LowLevel.Unsafe.NativeDisableUnsafePtrRestriction]
#endif
unsafe UnsafeBlob* _queue;


+ 14
- 8
Svelto.ECS/DataStructures/Unmanaged/UnsafeArray.cs View File

@@ -88,16 +88,19 @@ namespace Svelto.ECS.DataStructures
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal void Realloc(uint newCapacity, Allocator allocator)
internal void Realloc<T>(uint newCapacity, Allocator allocator) where T : struct
{
unsafe
{
var structSize = (uint) MemoryUtilities.SizeOf<T>();

uint newCapacityInBytes = structSize * newCapacity;
if (_ptr == null)
_ptr = (byte*) MemoryUtilities.Alloc(newCapacity, allocator);
_ptr = (byte*) MemoryUtilities.Alloc(newCapacityInBytes, allocator);
else
_ptr = (byte*) MemoryUtilities.Realloc((IntPtr) _ptr, (uint) count, newCapacity, allocator);
_ptr = (byte*) MemoryUtilities.Realloc((IntPtr) _ptr, newCapacityInBytes, allocator, (uint) count);

_capacity = newCapacity;
_capacity = newCapacityInBytes;
}
}

@@ -106,9 +109,12 @@ namespace Svelto.ECS.DataStructures
{
unsafe
{
if (ptr != null)
MemoryUtilities.Free((IntPtr) ptr, allocator);

#if DEBUG && !PROFILE_SVELTO
if (ptr == null)
throw new Exception("UnsafeArray: try to dispose an already disposed array");
#endif
MemoryUtilities.Free((IntPtr) ptr, allocator);
_ptr = null;
_writeIndex = 0;
_capacity = 0;
@@ -127,7 +133,7 @@ namespace Svelto.ECS.DataStructures
_writeIndex = count;
}
#if UNITY_NATIVE
#if UNITY_COLLECTIONS
[global::Unity.Collections.LowLevel.Unsafe.NativeDisableUnsafePtrRestriction]
#endif
unsafe byte* _ptr;


+ 0
- 70
Svelto.ECS/Debugger/ExclusiveGroupDebugger.cs View File

@@ -1,70 +0,0 @@
using Svelto.ECS;

#if DEBUG
using System;
using System.Collections.Generic;
using System.Reflection;

public static class ExclusiveGroupDebugger
{
static ExclusiveGroupDebugger()
{
Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
foreach (Assembly assembly in assemblies)
{
Type[] types = assembly.GetTypes();

foreach (Type type in types)
{
if (type != null && type.IsClass && type.IsSealed && type.IsAbstract) //this means only static classes
{
var fields = type.GetFields();
foreach (var field in fields)
{
if (field.IsStatic && typeof(ExclusiveGroup).IsAssignableFrom(field.FieldType))
{
var group = (ExclusiveGroup) field.GetValue(null);
string name = $"{type.FullName}.{field.Name} ({(uint)group})";
GroupMap.idToName[(ExclusiveGroupStruct) group] = name;
}

if (field.IsStatic && typeof(ExclusiveGroupStruct).IsAssignableFrom(field.FieldType))
{
var group = (ExclusiveGroupStruct) field.GetValue(null);

string name = $"{type.FullName}.{field.Name} ({(uint)group})";
GroupMap.idToName[@group] = name;
}
}
}
}
}
}
public static string ToName(this in ExclusiveGroupStruct group)
{
if (GroupMap.idToName.TryGetValue(group, out var name) == false)
name = $"<undefined:{((uint)group).ToString()}>";

return name;
}
}

public static class GroupMap
{
static GroupMap()
{
GroupMap.idToName = new Dictionary<uint, string>();
}

internal static readonly Dictionary<uint, string> idToName;
}
#else
public static class ExclusiveGroupDebugger
{
public static string ToName(this in ExclusiveGroupStruct group)
{
return ((uint)group).ToString();
}
}
#endif

+ 0
- 25
Svelto.ECS/Dispatcher/DispatchOnChange.cs View File

@@ -1,25 +0,0 @@
using System;

namespace Svelto.ECS
{
public class DispatchOnChange<T> : DispatchOnSet<T> where T:IEquatable<T>
{
public DispatchOnChange(EGID senderID, T initialValue = default(T)) : base(senderID)
{
_value = initialValue;
}

public DispatchOnChange(EGID senderID, Action<EGID, T> callback) : base(senderID, callback) {}

public new T value
{
set
{
if (value.Equals(_value) == false)
base.value = value;
}

get => _value;
}
}
}

+ 0
- 48
Svelto.ECS/Dispatcher/DispatchOnSet.cs View File

@@ -1,48 +0,0 @@
using System;

namespace Svelto.ECS
{
public class DispatchOnSet<T>
{
public DispatchOnSet(EGID senderID, Action<EGID, T> callback):this(senderID)
{
NotifyOnValueSet(callback);
}
public DispatchOnSet(EGID senderID) { _senderID = senderID; }

public T value
{
set
{
_value = value;

if (_paused == false)
_subscriber(_senderID, value);
}
}

public void NotifyOnValueSet(Action<EGID, T> action)
{
#if DEBUG && !PROFILE_SVELTO
DBC.ECS.Check.Require(_subscriber == null, $"{this.GetType().Name}: listener already registered");
#endif
_subscriber = action;
_paused = false;
}

public void StopNotify()
{
_subscriber = null;
_paused = true;
}

public void PauseNotify() { _paused = true; }
public void ResumeNotify() { _paused = false; }

protected T _value;
readonly EGID _senderID;

Action<EGID, T> _subscriber;
bool _paused;
}
}

+ 97
- 0
Svelto.ECS/Dispatcher/ReactiveValue.cs View File

@@ -0,0 +1,97 @@
using System;
using System.Collections.Generic;

namespace Svelto.ECS
{
/// <summary>
/// Reasons why unfortunately this cannot be a struct:
/// the user must remember to create interface with ref getters
/// ref getters cannot have set, while we sometimes use set to initialise values
/// the struct will be valid even if it has not ever been initialised
///
/// 1 and 3 are possibly solvable, but 2 is a problem
/// </summary>
/// <typeparam name="T"></typeparam>{
public class ReactiveValue<T>
{
public ReactiveValue
(EntityReference senderID, Action<EntityReference, T> callback, T initialValue = default
, bool notifyImmediately = false, ReactiveType notifyOnChange = ReactiveType.ReactOnChange)
{
_subscriber = callback;

if (notifyImmediately)
_subscriber(_senderID, initialValue);

_senderID = senderID;
_value = initialValue;
_notifyOnChange = notifyOnChange;
}

public ReactiveValue(EntityReference senderID, Action<EntityReference, T> callback, ReactiveType notifyOnChange)
{
_subscriber = callback;
_notifyOnChange = notifyOnChange;
_senderID = senderID;
}

public T value
{
set
{
if (_notifyOnChange == ReactiveType.ReactOnSet ||
EqualityComparer<T>.Default.Equals(_value) == false)
{
if (_paused == false)
_subscriber(_senderID, value);

//all the subscribers relies on the actual value not being changed yet, as the second parameter
//is the new value
_value = value;
}
}
get => _value;
}

public void PauseNotify()
{
_paused = true;
}

public void ResumeNotify()
{
_paused = false;
}
public void ForceValue(in T value)
{
if (_paused == false)
_subscriber(_senderID, value);

_value = value;
}

public void SetValueWithoutNotify(in T value)
{
_value = value;
}

public void StopNotify()
{
_subscriber = null;
_paused = true;
}

readonly ReactiveType _notifyOnChange;
readonly EntityReference _senderID;
bool _paused;
Action<EntityReference, T> _subscriber;
T _value;
}

public enum ReactiveType
{
ReactOnSet,
ReactOnChange
}
}

+ 7
- 18
Svelto.ECS/ECSResources/ECSResources.cs View File

@@ -2,12 +2,12 @@ using Svelto.DataStructures;

namespace Svelto.ECS.Experimental
{
public struct ECSResources<T>
{
internal uint id;
public static implicit operator T(ECSResources<T> ecsString) { return ResourcesECSDB<T>.FromECS(ecsString.id); }
}
// struct ECSResources<T>
// {
// internal uint id;
//
// public static implicit operator T(ECSResources<T> ecsString) { return ResourcesECSDB<T>.FromECS(ecsString.id); }
// }
/// <summary>
/// To do. Or we reuse the ID or we need to clear this
@@ -22,7 +22,7 @@ namespace Svelto.ECS.Experimental
return ref _resources[(int) id - 1];
}

internal static uint ToECS(T resource)
internal static uint ToECS(in T resource)
{
_resources.Add(resource);

@@ -37,15 +37,4 @@ namespace Svelto.ECS.Experimental
return default;
}
}

public static class ResourceExtensions
{
public static void Set<T>(ref this ECSResources<T> resource, T newText)
{
if (resource.id != 0)
ResourcesECSDB<T>.resources(resource.id) = newText;
else
resource.id = ResourcesECSDB<T>.ToECS(newText);
}
}
}

+ 3
- 7
Svelto.ECS/ECSResources/ECSString.cs View File

@@ -3,15 +3,11 @@ using System.Runtime.InteropServices;

namespace Svelto.ECS.Experimental
{
/// <summary>
/// Todo: the entityDB should be aware of the ECSString and recycle it on entity removal
/// </summary>
[Serialization.DoNotSerialize]
[StructLayout(LayoutKind.Explicit)]
///
/// Note: I should extend this to reuse unused id
///
//todo ResourcesECSDB<T> 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<ECSString>
{
[FieldOffset(0)] uint _id;


+ 24
- 0
Svelto.ECS/Extensions/DisposeDisposablesEngine.cs View File

@@ -0,0 +1,24 @@
using System;

namespace Svelto.ECS
{
[AllowMultiple]
public class DisposeDisposablesEngine : IEngine, IDisposable
{
public DisposeDisposablesEngine(IDisposable[] disposable)
{
_disposable = disposable;
}
public void Dispose()
{
foreach (var d in _disposable)
{
d.Dispose();
}
}

IDisposable[] _disposable;
}

}

+ 6
- 3
Svelto.ECS/Extensions/Svelto/AllGroupsEnumerable.cs View File

@@ -6,7 +6,7 @@ namespace Svelto.ECS
{
/// <summary>
/// ToDo it would be interesting to have a version of this dedicated to unmanaged, IEntityComponent
/// that can be burstifiable
/// that can be burstifiable
/// </summary>
/// <typeparam name="T1"></typeparam>
public readonly struct AllGroupsEnumerable<T1> where T1 : struct, IEntityComponent
@@ -38,6 +38,9 @@ namespace Svelto.ECS
while (_db.MoveNext() == true)
{
var group = _db.Current;
if (group.Key.IsEnabled() == false)
continue;

ITypeSafeDictionary<T1> typeSafeDictionary = @group.Value as ITypeSafeDictionary<T1>;

if (typeSafeDictionary.count == 0)
@@ -54,9 +57,9 @@ namespace Svelto.ECS

public GroupCollection Current => _array;

SveltoDictionary<ExclusiveGroupStruct, ITypeSafeDictionary,
SveltoDictionaryKeyValueEnumerator<ExclusiveGroupStruct, ITypeSafeDictionary,
ManagedStrategy<SveltoDictionaryNode<ExclusiveGroupStruct>>, ManagedStrategy<ITypeSafeDictionary>,
ManagedStrategy<int>>.SveltoDictionaryKeyValueEnumerator _db;
ManagedStrategy<int>> _db;

GroupCollection _array;
}


+ 149
- 0
Svelto.ECS/Extensions/Svelto/EGIDMultiMapper.cs View File

@@ -0,0 +1,149 @@
using System;
using System.Runtime.CompilerServices;
using Svelto.Common;
using Svelto.DataStructures;
using Svelto.ECS.Hybrid;

namespace Svelto.ECS
{
namespace Native
{
public struct EGIDMultiMapper<T> where T : unmanaged, IEntityComponent
{
public EGIDMultiMapper
(SveltoDictionary<ExclusiveGroupStruct,
SveltoDictionary<uint, T, NativeStrategy<
SveltoDictionaryNode<uint>>, NativeStrategy<T>, NativeStrategy<int>>,
ManagedStrategy<SveltoDictionaryNode<ExclusiveGroupStruct>>,
ManagedStrategy<SveltoDictionary<uint, T, NativeStrategy<SveltoDictionaryNode<uint>>, NativeStrategy<T>
, NativeStrategy<int>>>, NativeStrategy<int>> dictionary)
{
_dic = dictionary;
}

public int count
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => _dic.count;
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ref T Entity(EGID entity)
{
#if DEBUG && !PROFILE_SVELTO
if (Exists(entity) == false)
throw new Exception("NativeEGIDMultiMapper: Entity not found");
#endif
ref var sveltoDictionary = ref _dic.GetValueByRef(entity.groupID);
return ref sveltoDictionary.GetValueByRef(entity.entityID);
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Exists(EGID entity)
{
return _dic.TryFindIndex(entity.groupID, out var index)
&& _dic.GetDirectValueByRef(index).ContainsKey(entity.entityID);
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool TryGetEntity(EGID entity, out T component)
{
component = default;
return _dic.TryFindIndex(entity.groupID, out var index)
&& _dic.GetDirectValueByRef(index).TryGetValue(entity.entityID, out component);
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool FindIndex(ExclusiveGroupStruct group, uint entityID, out uint index)
{
index = 0;
return _dic.TryFindIndex(group, out var groupIndex) &&
_dic.GetDirectValueByRef(groupIndex).TryFindIndex(entityID, out index);
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public uint GetIndex(ExclusiveGroupStruct group, uint entityID)
{
uint groupIndex = _dic.GetIndex(group);
return _dic.GetDirectValueByRef(groupIndex).GetIndex(entityID);
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Exists(ExclusiveGroupStruct group, uint entityID)
{
return _dic.TryFindIndex(group, out var groupIndex) &&
_dic.GetDirectValueByRef(groupIndex).ContainsKey(entityID);
}

public Type entityType => TypeCache<T>.type;

SveltoDictionary<ExclusiveGroupStruct,
SveltoDictionary<uint, T, NativeStrategy<SveltoDictionaryNode<uint>>, NativeStrategy<T>,
NativeStrategy<int>>, ManagedStrategy<SveltoDictionaryNode<ExclusiveGroupStruct>>,
ManagedStrategy<SveltoDictionary<uint, T, NativeStrategy<SveltoDictionaryNode<uint>>, NativeStrategy<T>,
NativeStrategy<int>>>, NativeStrategy<int>> _dic;
}

public interface IEGIDMultiMapper
{
bool FindIndex(ExclusiveGroupStruct group, uint entityID, out uint index);

uint GetIndex(ExclusiveGroupStruct group, uint entityID);

bool Exists(ExclusiveGroupStruct group, uint entityID);

Type entityType { get; }
}
}

public struct EGIDMultiMapper<T> where T : struct, IEntityViewComponent
{
public EGIDMultiMapper
(SveltoDictionary<ExclusiveGroupStruct,
SveltoDictionary<uint, T, ManagedStrategy<SveltoDictionaryNode<uint>>, ManagedStrategy<T>,
ManagedStrategy<int>>, ManagedStrategy<SveltoDictionaryNode<ExclusiveGroupStruct>>,
ManagedStrategy<SveltoDictionary<uint, T, ManagedStrategy<SveltoDictionaryNode<uint>>, ManagedStrategy<T>,
ManagedStrategy<int>>>, ManagedStrategy<int>> dictionary)
{
_dic = dictionary;
}

public int count
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => _dic.count;
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ref T Entity(EGID entity)
{
#if DEBUG && !PROFILE_SVELTO
if (Exists(entity) == false)
throw new Exception("NativeEGIDMultiMapper: Entity not found");
#endif
ref var sveltoDictionary = ref _dic.GetValueByRef(entity.groupID);
return ref sveltoDictionary.GetValueByRef(entity.entityID);
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Exists(EGID entity)
{
return _dic.TryFindIndex(entity.groupID, out var index)
&& _dic.GetDirectValueByRef(index).ContainsKey(entity.entityID);
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool TryGetEntity(EGID entity, out T component)
{
component = default;
return _dic.TryFindIndex(entity.groupID, out var index)
&& _dic.GetDirectValueByRef(index).TryGetValue(entity.entityID, out component);
}

SveltoDictionary<ExclusiveGroupStruct,
SveltoDictionary<uint, T, ManagedStrategy<SveltoDictionaryNode<uint>>, ManagedStrategy<T>,
ManagedStrategy<int>>, ManagedStrategy<SveltoDictionaryNode<ExclusiveGroupStruct>>,
ManagedStrategy<SveltoDictionary<uint, T, ManagedStrategy<SveltoDictionaryNode<uint>>, ManagedStrategy<T>,
ManagedStrategy<int>>>, ManagedStrategy<int>> _dic;
}
}

+ 16
- 0
Svelto.ECS/Extensions/Svelto/EntitiesDBFiltersExtension.cs View File

@@ -0,0 +1,16 @@
using Svelto.DataStructures;
using Svelto.ECS.Native;

namespace Svelto.ECS
{
public static class EntitiesDBFiltersExtension
{
public static bool AddEntityToFilter<N>(this EntitiesDB.Filters filters, int filtersID, EGID egid, N mapper) where N : IEGIDMultiMapper
{
ref var filter =
ref filters.CreateOrGetFilterForGroup(filtersID, egid.groupID, new RefWrapperType(mapper.entityType));

return filter.Add(egid.entityID, mapper);
}
}
}

+ 35
- 16
Svelto.ECS/Extensions/Svelto/EntityManagedDBExtensions.cs View File

@@ -1,4 +1,5 @@
using System.Runtime.CompilerServices;
using Svelto.Common;
using Svelto.DataStructures;
using Svelto.ECS.Hybrid;
using Svelto.ECS.Internal;
@@ -8,7 +9,8 @@ namespace Svelto.ECS
public static class EntityManagedDBExtensions
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static MB<T> QueryEntitiesAndIndex<T>(this EntitiesDB entitiesDb, EGID entityGID, out uint index) where T : struct, IEntityViewComponent
public static MB<T> QueryEntitiesAndIndex<T>
(this EntitiesDB entitiesDb, EGID entityGID, out uint index) where T : struct, IEntityViewComponent
{
if (entitiesDb.QueryEntitiesAndIndexInternal<T>(entityGID, out index, out MB<T> array) == true)
return array;
@@ -17,7 +19,8 @@ namespace Svelto.ECS
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool TryQueryEntitiesAndIndex<T>(this EntitiesDB entitiesDb, EGID entityGID, out uint index, out MB<T> array)
public static bool TryQueryEntitiesAndIndex<T>
(this EntitiesDB entitiesDb, EGID entityGID, out uint index, out MB<T> array)
where T : struct, IEntityViewComponent
{
if (entitiesDb.QueryEntitiesAndIndexInternal<T>(entityGID, out index, out array) == true)
@@ -25,9 +28,10 @@ namespace Svelto.ECS

return false;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool TryQueryEntitiesAndIndex<T>(this EntitiesDB entitiesDb, uint id, ExclusiveGroupStruct group, out uint index, out MB<T> array)
public static bool TryQueryEntitiesAndIndex<T>
(this EntitiesDB entitiesDb, uint id, ExclusiveGroupStruct group, out uint index, out MB<T> array)
where T : struct, IEntityViewComponent
{
if (entitiesDb.QueryEntitiesAndIndexInternal<T>(new EGID(id, group), out index, out array) == true)
@@ -35,9 +39,11 @@ namespace Svelto.ECS

return false;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
static bool QueryEntitiesAndIndexInternal<T>(this EntitiesDB entitiesDb, EGID entityGID, out uint index, out MB<T> buffer) where T : struct, IEntityViewComponent
static bool QueryEntitiesAndIndexInternal<T>
(this EntitiesDB entitiesDb, EGID entityGID, out uint index, out MB<T> buffer)
where T : struct, IEntityViewComponent
{
index = 0;
buffer = default;
@@ -46,28 +52,31 @@ namespace Svelto.ECS

if (safeDictionary.TryFindIndex(entityGID.entityID, out index) == false)
return false;
buffer = (MB<T>) (safeDictionary as ITypeSafeDictionary<T>).GetValues(out _);

return true;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ref T QueryEntity<T>(this EntitiesDB entitiesDb, EGID entityGID) where T : struct, IEntityViewComponent
public static ref T QueryEntity<T>
(this EntitiesDB entitiesDb, EGID entityGID) where T : struct, IEntityViewComponent
{
var array = entitiesDb.QueryEntitiesAndIndex<T>(entityGID, out var index);
return ref array[(int) index];
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ref T QueryEntity<T>(this EntitiesDB entitiesDb, uint id, ExclusiveGroupStruct group) where T : struct, IEntityViewComponent
public static ref T QueryEntity<T>
(this EntitiesDB entitiesDb, uint id, ExclusiveGroupStruct group) where T : struct, IEntityViewComponent
{
return ref entitiesDb.QueryEntity<T>(new EGID(id, group));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ref T QueryUniqueEntity<T>(this EntitiesDB entitiesDb, ExclusiveGroupStruct group) where T : struct, IEntityViewComponent
public static ref T QueryUniqueEntity<T>
(this EntitiesDB entitiesDb, ExclusiveGroupStruct group) where T : struct, IEntityViewComponent
{
var (entities, entitiescount) = entitiesDb.QueryEntities<T>(@group);

@@ -80,9 +89,10 @@ namespace Svelto.ECS
#endif
return ref entities[0];
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static MB<T> GetArrayAndEntityIndex<T>(this EGIDMapper<T> mapper, uint entityID, out uint index) where T : struct, IEntityViewComponent
public static MB<T> GetArrayAndEntityIndex<T>
(this EGIDMapper<T> mapper, uint entityID, out uint index) where T : struct, IEntityViewComponent
{
if (mapper._map.TryFindIndex(entityID, out index))
{
@@ -93,7 +103,9 @@ namespace Svelto.ECS
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool TryGetArrayAndEntityIndex<T>(this EGIDMapper<T> mapper, uint entityID, out uint index, out MB<T> array) where T : struct, IEntityViewComponent
public static bool TryGetArrayAndEntityIndex<T>
(this EGIDMapper<T> mapper, uint entityID, out uint index, out MB<T> array)
where T : struct, IEntityViewComponent
{
index = default;
if (mapper._map != null && mapper._map.TryFindIndex(entityID, out index))
@@ -105,5 +117,12 @@ namespace Svelto.ECS
array = default;
return false;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static AllGroupsEnumerable<T1> QueryEntities<T1>(this EntitiesDB db)
where T1 :struct, IEntityComponent
{
return new AllGroupsEnumerable<T1>(db);
}
}
}

+ 137
- 108
Svelto.ECS/Extensions/Svelto/EntityNativeDBExtensions.cs View File

@@ -1,125 +1,154 @@
using System.Runtime.CompilerServices;
using Svelto.Common;
using Svelto.DataStructures;
using Svelto.ECS.Internal;

//todo: once using native memory for unmanaged struct will be optional, this will need to be moved under the Native namespace
namespace Svelto.ECS
{
public static class EntityNativeDBExtensions
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static AllGroupsEnumerable<T1> QueryEntities<T1>(this EntitiesDB db)
where T1 :struct, IEntityComponent
public static NB<T> QueryEntitiesAndIndex<T>
(this EntitiesDB entitiesDb, EGID entityGID, out uint index) where T : unmanaged, IEntityComponent
{
return new AllGroupsEnumerable<T1>(db);
if (entitiesDb.QueryEntitiesAndIndexInternal(entityGID, out index, out NB<T> array) == true)
return array;

throw new EntityNotFoundException(entityGID, typeof(T));
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static NB<T> QueryEntitiesAndIndex<T>
(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<T> array) == true)
return array;

throw new EntityNotFoundException(entityGID, typeof(T));
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool TryQueryEntitiesAndIndex<T>
(this EntitiesDB entitiesDb, EGID entityGID, out uint index, out NB<T> 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<T>
(this EntitiesDB entitiesDb, uint id, ExclusiveGroupStruct group, out uint index, out NB<T> array)
where T : unmanaged, IEntityComponent
{
if (entitiesDb.QueryEntitiesAndIndexInternal(new EGID(id, group), out index, out array) == true)
return true;

return false;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool TryGetEntity<T>(this EntitiesDB entitiesDb, uint entityID, ExclusiveGroupStruct @group, out T value)
where T : unmanaged, IEntityComponent
{
if (TryQueryEntitiesAndIndex<T>(entitiesDb, entityID, group, out var index, out var array))
{
value = array[index];
return true;
}

value = default;
return false;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static NB<T> QueryEntitiesAndIndex<T>(this EntitiesDB entitiesDb, EGID entityGID, out uint index) where T : unmanaged, IEntityComponent
{
if (entitiesDb.QueryEntitiesAndIndexInternal(entityGID, out index, out NB<T> array) == true)
return array;

throw new EntityNotFoundException(entityGID, typeof(T));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static NB<T> QueryEntitiesAndIndex<T>(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<T> array) == true)
return array;

throw new EntityNotFoundException(entityGID, typeof(T));
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool TryQueryEntitiesAndIndex<T>(this EntitiesDB entitiesDb, EGID entityGID, out uint index, out NB<T> 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<T>(this EntitiesDB entitiesDb, uint id, ExclusiveGroupStruct group, out uint index, out NB<T> 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<T>(this EntitiesDB entitiesDb, EGID entityGID, out uint index, out NB<T> buffer) where T : unmanaged, IEntityComponent
{
index = 0;
buffer = default;
if (entitiesDb.SafeQueryEntityDictionary<T>(entityGID.groupID, out var safeDictionary) == false)
return false;

if (safeDictionary.TryFindIndex(entityGID.entityID, out index) == false)
return false;
buffer = (NB<T>) (safeDictionary as ITypeSafeDictionary<T>).GetValues(out _);

return true;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ref T QueryEntity<T>(this EntitiesDB entitiesDb, EGID entityGID) where T : unmanaged, IEntityComponent
{
var array = entitiesDb.QueryEntitiesAndIndex<T>(entityGID, out var index);
return ref array[(int) index];
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ref T QueryEntity<T>(this EntitiesDB entitiesDb, uint id, ExclusiveGroupStruct group) where T : unmanaged, IEntityComponent
{
return ref entitiesDb.QueryEntity<T>(new EGID(id, group));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ref T QueryUniqueEntity<T>(this EntitiesDB entitiesDb, ExclusiveGroupStruct group) where T : unmanaged, IEntityComponent
{
var (entities, entitiescount) = entitiesDb.QueryEntities<T>(@group);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool TryGetEntity<T>(this EntitiesDB entitiesDb, EGID egid, out T value)
where T : unmanaged, IEntityComponent
{
return TryGetEntity<T>(entitiesDb, egid.entityID, egid.groupID, out value);
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
static bool QueryEntitiesAndIndexInternal<T>
(this EntitiesDB entitiesDb, EGID entityGID, out uint index, out NB<T> buffer)
where T : unmanaged, IEntityComponent
{
index = 0;
buffer = default;
if (entitiesDb.SafeQueryEntityDictionary<T>(entityGID.groupID, out var safeDictionary) == false)
return false;

if (safeDictionary.TryFindIndex(entityGID.entityID, out index) == false)
return false;

buffer = (NB<T>) (safeDictionary as ITypeSafeDictionary<T>).GetValues(out _);

return true;
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ref T QueryEntity<T>
(this EntitiesDB entitiesDb, EGID entityGID) where T : unmanaged, IEntityComponent
{
var array = entitiesDb.QueryEntitiesAndIndex<T>(entityGID, out var index);

return ref array[(int) index];
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ref T QueryEntity<T>
(this EntitiesDB entitiesDb, uint id, ExclusiveGroupStruct group) where T : unmanaged, IEntityComponent
{
return ref entitiesDb.QueryEntity<T>(new EGID(id, group));
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ref T QueryUniqueEntity<T>
(this EntitiesDB entitiesDb, ExclusiveGroupStruct group) where T : unmanaged, IEntityComponent
{
var (entities, entitiescount) = entitiesDb.QueryEntities<T>(@group);

#if DEBUG && !PROFILE_SVELTO
if (entitiescount == 0)
throw new ECSException("Unique entity not found '".FastConcat(typeof(T).ToString()).FastConcat("'"));
if (entitiescount != 1)
throw new ECSException("Unique entities must be unique! '".FastConcat(typeof(T).ToString())
.FastConcat("'"));
if (entitiescount == 0)
throw new ECSException("Unique entity not found '".FastConcat(typeof(T).ToString()).FastConcat("'"));
if (entitiescount != 1)
throw new ECSException("Unique entities must be unique! '".FastConcat(typeof(T).ToString())
.FastConcat("'"));
#endif
return ref entities[0];
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static NB<T> GetArrayAndEntityIndex<T>(this EGIDMapper<T> mapper, uint entityID, out uint index) where T : unmanaged, IEntityComponent
{
if (mapper._map.TryFindIndex(entityID, out index))
{
return (NB<T>) mapper._map.GetValues(out _);
}

throw new ECSException("Entity not found");
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool TryGetArrayAndEntityIndex<T>(this EGIDMapper<T> mapper, uint entityID, out uint index, out NB<T> array) where T : unmanaged, IEntityComponent
{
index = default;
if (mapper._map != null && mapper._map.TryFindIndex(entityID, out index))
{
array = (NB<T>) mapper._map.GetValues(out _);
return true;
}

array = default;
return false;
}
return ref entities[0];
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static NB<T> GetArrayAndEntityIndex<T>
(this EGIDMapper<T> mapper, uint entityID, out uint index) where T : unmanaged, IEntityComponent
{
if (mapper._map.TryFindIndex(entityID, out index))
{
return (NB<T>) mapper._map.GetValues(out _);
}

throw new ECSException("Entity not found");
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool TryGetArrayAndEntityIndex<T>
(this EGIDMapper<T> mapper, uint entityID, out uint index, out NB<T> array)
where T : unmanaged, IEntityComponent
{
index = default;
if (mapper._map != null && mapper._map.TryFindIndex(entityID, out index))
{
array = (NB<T>) mapper._map.GetValues(out _);
return true;
}

array = default;
return false;
}
}
}

+ 19
- 0
Svelto.ECS/Extensions/Svelto/FilterGroupExtensions.cs View File

@@ -0,0 +1,19 @@
using Svelto.ECS.Native;

namespace Svelto.ECS
{
public static class FilterGroupExtensions
{
public static bool Add<N>(this FilterGroup filter, uint entityID, N mapper) where N : IEGIDMultiMapper
{
#if DEBUG && !PROFILE_SVELTO
if (mapper.Exists(filter._exclusiveGroupStruct, entityID) == false)
throw new ECSException(
$"trying adding an entity {entityID} to filter {mapper.entityType} - {filter._ID} with group {filter._exclusiveGroupStruct}, but entity is not found! ");
#endif

return filter.InternalAdd(entityID, mapper.GetIndex(filter._exclusiveGroupStruct, entityID));
}

}
}

+ 27
- 21
Svelto.ECS/Extensions/Svelto/GroupsEnumerable.cs View File

@@ -1,4 +1,3 @@
using DBC.ECS;
using Svelto.DataStructures;

namespace Svelto.ECS
@@ -37,20 +36,15 @@ namespace Svelto.ECS
//attention, the while is necessary to skip empty groups
while (++_indexGroup < _groups.count)
{
var entityCollection1 = _entitiesDB.QueryEntities<T1, T2, T3>(_groups[_indexGroup]);
if (entityCollection1.count == 0)
continue;
var entityCollection2 = _entitiesDB.QueryEntities<T4>(_groups[_indexGroup]);
if (entityCollection2.count == 0)
var exclusiveGroupStruct = _groups[_indexGroup];
if (!exclusiveGroupStruct.IsEnabled())
continue;

Check.Assert(entityCollection1.count == entityCollection2.count
, "congratulation, you found a bug in Svelto, please report it");
var entityCollection1 = _entitiesDB.QueryEntities<T1, T2, T3, T4>(exclusiveGroupStruct);

var array = entityCollection1;
var array2 = entityCollection2;
_buffers = new EntityCollection<T1, T2, T3, T4>(array.buffer1, array.buffer2, array.buffer3
, array2);
, array.buffer4);
break;
}

@@ -79,7 +73,7 @@ namespace Svelto.ECS
readonly EntitiesDB _db;
readonly LocalFasterReadOnlyList<ExclusiveGroupStruct> _groups;

public ref struct RefCurrent
public readonly ref struct RefCurrent
{
public RefCurrent(in EntityCollection<T1, T2, T3, T4> buffers, ExclusiveGroupStruct group)
{
@@ -104,7 +98,6 @@ namespace Svelto.ECS
{
public GroupsEnumerable(EntitiesDB db, in LocalFasterReadOnlyList<ExclusiveGroupStruct> groups)
{
DBC.ECS.Check.Require(groups.count > 0, "can't initialise a query without valid groups");
_db = db;
_groups = groups;
}
@@ -123,7 +116,11 @@ namespace Svelto.ECS
//attention, the while is necessary to skip empty groups
while (++_indexGroup < _groups.count)
{
var entityCollection = _entitiesDB.QueryEntities<T1, T2, T3>(_groups[_indexGroup]);
var exclusiveGroupStruct = _groups[_indexGroup];
if (!exclusiveGroupStruct.IsEnabled())
continue;

var entityCollection = _entitiesDB.QueryEntities<T1, T2, T3>(exclusiveGroupStruct);
if (entityCollection.count == 0)
continue;

@@ -156,7 +153,7 @@ namespace Svelto.ECS
readonly EntitiesDB _db;
readonly LocalFasterReadOnlyList<ExclusiveGroupStruct> _groups;

public ref struct RefCurrent
public readonly ref struct RefCurrent
{
public RefCurrent(in EntityCollection<T1, T2, T3> buffers, ExclusiveGroupStruct group)
{
@@ -198,7 +195,11 @@ namespace Svelto.ECS
//attention, the while is necessary to skip empty groups
while (++_indexGroup < _groups.count)
{
var entityCollection = _db.QueryEntities<T1, T2>(_groups[_indexGroup]);
var exclusiveGroupStruct = _groups[_indexGroup];
if (!exclusiveGroupStruct.IsEnabled())
continue;

var entityCollection = _db.QueryEntities<T1, T2>(exclusiveGroupStruct);
if (entityCollection.count == 0)
continue;

@@ -231,7 +232,7 @@ namespace Svelto.ECS
readonly EntitiesDB _db;
readonly LocalFasterReadOnlyList<ExclusiveGroupStruct> _groups;

public ref struct RefCurrent
public readonly ref struct RefCurrent
{
public RefCurrent(in EntityCollection<T1, T2> buffers, ExclusiveGroupStruct group)
{
@@ -272,14 +273,19 @@ namespace Svelto.ECS
//attention, the while is necessary to skip empty groups
while (++_indexGroup < _groups.count)
{
var entityCollection = _db.QueryEntities<T1>(_groups[_indexGroup]);
var exclusiveGroupStruct = _groups[_indexGroup];
if (!exclusiveGroupStruct.IsEnabled())
continue;

var entityCollection = _db.QueryEntities<T1>(exclusiveGroupStruct);
if (entityCollection.count == 0)
continue;

_buffer = entityCollection;
break;
}
var moveNext = _indexGroup < _groups.count;

if (moveNext == false)
@@ -318,14 +324,14 @@ namespace Svelto.ECS
buffers = _buffers;
group = _group;
}
public void Deconstruct(out EntityCollection<T1> buffers)
{
buffers = _buffers;
}

public readonly EntityCollection<T1> _buffers;
public readonly ExclusiveGroupStruct _group;
internal readonly EntityCollection<T1> _buffers;
internal readonly ExclusiveGroupStruct _group;
}
}
}

+ 3
- 0
Svelto.ECS/Extensions/Unity/DOTS/Jobs/IJobifiedEngine.cs View File

@@ -16,6 +16,9 @@ namespace Svelto.ECS.Extensions.Unity

string name { get; }
}
public interface IJobifiedGroupEngine<T> : IJobifiedEngine<T>
{ }
}

#endif

+ 1
- 1
Svelto.ECS/Extensions/Unity/DOTS/Jobs/SortedJobifiedEnginesGroup.cs View File

@@ -10,7 +10,7 @@ namespace Svelto.ECS.Extensions.Unity
/// </summary>
/// <typeparam name="Interface"></typeparam>
/// <typeparam name="SequenceOrder"></typeparam>
public abstract class SortedJobifiedEnginesGroup<Interface, SequenceOrder>
public abstract class SortedJobifiedEnginesGroup<Interface, SequenceOrder> : IJobifiedEngine
where SequenceOrder : struct, ISequenceOrder where Interface : class, IJobifiedEngine
{
protected SortedJobifiedEnginesGroup(FasterList<Interface> engines)


+ 32
- 6
Svelto.ECS/Extensions/Unity/DOTS/Jobs/UnityJobExtensions.cs View File

@@ -12,33 +12,59 @@ namespace Svelto.ECS
{
return new DisposeJob<T1>(disposable).Schedule(inputDeps);
}
public static JobHandle ScheduleDispose
<T1, T2>(this T1 disposable1, T2 disposable2, JobHandle inputDeps)
<T1, T2>(this T1 disposable1, T2 disposable2, JobHandle inputDeps)
where T1 : struct, IDisposable where T2 : struct, IDisposable
{
return new DisposeJob<T1, T2>(disposable1, disposable2).Schedule(inputDeps);
}
public static JobHandle ScheduleParallel
<JOB>(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
<JOB>(this JOB job, uint iterations, JobHandle inputDeps) where JOB: struct, IJobParallelFor
{
if (iterations == 0)
return inputDeps;
var innerloopBatchCount = ProcessorCount.BatchSize(iterations);
return job.Schedule((int)iterations, innerloopBatchCount, inputDeps);
}

public static JobHandle ScheduleParallelAndCombine
<JOB>(this JOB job, int iterations, JobHandle inputDeps, JobHandle combinedDeps) where JOB: struct, IJobParallelFor
{
if (iterations == 0)
return inputDeps;

var innerloopBatchCount = ProcessorCount.BatchSize((uint)iterations);
var jobDeps = job.Schedule(iterations, innerloopBatchCount, inputDeps);

return JobHandle.CombineDependencies(combinedDeps, jobDeps);
}

public static JobHandle ScheduleAndCombine
<JOB>(this JOB job, JobHandle inputDeps, JobHandle combinedDeps) where JOB : struct, IJob
{
var jobDeps = job.Schedule(inputDeps);
return JobHandle.CombineDependencies(combinedDeps, jobDeps);
}

public static JobHandle ScheduleAndCombine
<JOB>(this JOB job, int arrayLength, JobHandle inputDeps, JobHandle combinedDeps) where JOB : struct, IJobFor
{
var jobDeps = job.Schedule(arrayLength, inputDeps);
return JobHandle.CombineDependencies(combinedDeps, jobDeps);
}
}
}
#endif

Svelto.ECS/Extensions/Unity/DOTS/Jobs/JobifiedEnginesGroup.cs → Svelto.ECS/Extensions/Unity/DOTS/Jobs/UnsortedJobifiedEnginesGroup.cs View File

@@ -5,21 +5,19 @@ using Unity.Jobs;

namespace Svelto.ECS.Extensions.Unity
{
public interface IJobifiedGroupEngine<T> : IJobifiedEngine<T>
{ }
/// <summary>
/// Note unsorted jobs run in parallel
/// </summary>
/// <typeparam name="Interface"></typeparam>
public abstract class JobifiedEnginesGroup<Interface>:IJobifiedEngine where Interface : class, IJobifiedEngine
public abstract class UnsortedJobifiedEnginesGroup<Interface>:IJobifiedEngine where Interface : class, IJobifiedEngine
{
protected JobifiedEnginesGroup(FasterList<Interface> engines)
protected UnsortedJobifiedEnginesGroup(FasterList<Interface> engines)
{
_name = "JobifiedEnginesGroup - "+this.GetType().Name;
_engines = engines;
}
protected JobifiedEnginesGroup()
protected UnsortedJobifiedEnginesGroup()
{
_name = "JobifiedEnginesGroup - "+this.GetType().Name;
_engines = new FasterList<Interface>();
@@ -36,7 +34,7 @@ namespace Svelto.ECS.Extensions.Unity
ref var engine = ref engines[index];
using (profiler.Sample(engine.name))
{
combinedHandles = engine.Execute(inputHandles);
combinedHandles = JobHandle.CombineDependencies(combinedHandles, engine.Execute(inputHandles));
}
}
}
@@ -71,7 +69,8 @@ namespace Svelto.ECS.Extensions.Unity
for (var index = 0; index < engines.count; index++)
{
var engine = engines[index];
using (profiler.Sample(engine.name)) combinedHandles = engine.Execute(combinedHandles, ref _param);
using (profiler.Sample(engine.name))
combinedHandles = JobHandle.CombineDependencies(combinedHandles, engine.Execute(combinedHandles, ref _param));
}
}


+ 25
- 16
Svelto.ECS/Extensions/Unity/DOTS/Native/EnginesRoot.NativeOperation.cs View File

@@ -5,21 +5,17 @@ using Svelto.Common;
using Svelto.DataStructures;
using Svelto.ECS.DataStructures;
using Svelto.ECS.Internal;
using Svelto.ECS.Native;

namespace Svelto.ECS
{
public partial class EnginesRoot
{
//todo: I very likely don't need to create one for each native entity factory, the same can be reused
readonly AtomicNativeBags _nativeAddOperationQueue = new AtomicNativeBags(Common.Allocator.Persistent);
readonly AtomicNativeBags _nativeRemoveOperationQueue = new AtomicNativeBags(Common.Allocator.Persistent);
readonly AtomicNativeBags _nativeSwapOperationQueue = new AtomicNativeBags(Common.Allocator.Persistent);

NativeEntityRemove ProvideNativeEntityRemoveQueue<T>(string memberName) where T : IEntityDescriptor, new()
{
//DBC.ECS.Check.Require(EntityDescriptorTemplate<T>.descriptor.IsUnmanaged(), "can't remove entities with not native types");
//DBC.ECS.Check.Require(EntityDescriptorTemplate<T>.descriptor.isUnmanaged(), "can't remove entities with not native types");
//todo: remove operation array and store entity descriptor hash in the return value
//todo I maybe able to provide a _nativeSwap.SwapEntity<entityDescriptor>
//todo I maybe able to provide a _nativeSwap.SwapEntity<entityDescriptor>
_nativeRemoveOperations.Add(new NativeOperationRemove(
EntityDescriptorTemplate<T>.descriptor.componentsToBuild, TypeCache<T>.type
, memberName));
@@ -29,7 +25,7 @@ namespace Svelto.ECS

NativeEntitySwap ProvideNativeEntitySwapQueue<T>(string memberName) where T : IEntityDescriptor, new()
{
// DBC.ECS.Check.Require(EntityDescriptorTemplate<T>.descriptor.IsUnmanaged(), "can't swap entities with not native types");
// DBC.ECS.Check.Require(EntityDescriptorTemplate<T>.descriptor.isUnmanaged(), "can't swap entities with not native types");
//todo: remove operation array and store entity descriptor hash in the return value
_nativeSwapOperations.Add(new NativeOperationSwap(EntityDescriptorTemplate<T>.descriptor.componentsToBuild
, TypeCache<T>.type, memberName));
@@ -44,14 +40,15 @@ namespace Svelto.ECS
_nativeAddOperations.Add(
new NativeOperationBuild(EntityDescriptorTemplate<T>.descriptor.componentsToBuild, TypeCache<T>.type, memberName));

return new NativeEntityFactory(_nativeAddOperationQueue, _nativeAddOperations.count - 1);
return new NativeEntityFactory(_nativeAddOperationQueue, _nativeAddOperations.count - 1, _entityLocator);
}

void NativeOperationSubmission(in PlatformProfiler profiler)
void FlushNativeOperations(in PlatformProfiler profiler)
{
using (profiler.Sample("Native Remove/Swap Operations"))
{
var removeBuffersCount = _nativeRemoveOperationQueue.count;
//todo, I don't like that this scans all the queues even if they are empty
for (int i = 0; i < removeBuffersCount; i++)
{
ref var buffer = ref _nativeRemoveOperationQueue.GetBuffer(i);
@@ -105,18 +102,26 @@ namespace Svelto.ECS
{
var componentsIndex = buffer.Dequeue<uint>();
var egid = buffer.Dequeue<EGID>();
var reference = buffer.Dequeue<EntityReference>();
var componentCounts = buffer.Dequeue<uint>();

Check.Require((uint)egid.groupID != 0, "invalid group detected, are you using new ExclusiveGroupStruct() instead of new ExclusiveGroup()?");

var componentBuilders = _nativeAddOperations[componentsIndex].components;
#if DEBUG && !PROFILE_SVELTO
var entityDescriptorType = _nativeAddOperations[componentsIndex].entityDescriptorType;
CheckAddEntityID(egid, entityDescriptorType, _nativeAddOperations[componentsIndex].caller);
#endif

Check.Require(egid.groupID != 0, "invalid group detected, are you using new ExclusiveGroupStruct() instead of new ExclusiveGroup()?");

_entityLocator.SetReference(reference, egid);
var dic = EntityFactory.BuildGroupedEntities(egid, _groupedEntityToAdd, componentBuilders
, null, entityDescriptorType);
, null
#if DEBUG && !PROFILE_SVELTO
, entityDescriptorType
#endif
);

var init = new EntityInitializer(egid, dic);
var init = new EntityInitializer(egid, dic, reference);

//only called if Init is called on the initialized (there is something to init)
while (componentCounts > 0)
@@ -126,7 +131,6 @@ namespace Svelto.ECS
var typeID = buffer.Dequeue<uint>();

IFiller entityBuilder = EntityComponentIDMap.GetTypeFromID(typeID);

//after the typeID, I expect the serialized component
entityBuilder.FillFromByteArray(init, buffer);
}
@@ -145,6 +149,11 @@ namespace Svelto.ECS
FasterList<NativeOperationRemove> _nativeRemoveOperations;
FasterList<NativeOperationSwap> _nativeSwapOperations;
FasterList<NativeOperationBuild> _nativeAddOperations;
//todo: I very likely don't need to create one for each native entity factory, the same can be reused
readonly AtomicNativeBags _nativeAddOperationQueue;
readonly AtomicNativeBags _nativeRemoveOperationQueue;
readonly AtomicNativeBags _nativeSwapOperationQueue;
}

readonly struct DoubleEGID


+ 6
- 2
Svelto.ECS/Extensions/Unity/DOTS/Native/NativeEGIDMapper.cs View File

@@ -4,7 +4,7 @@ using System.Runtime.CompilerServices;
using Svelto.Common;
using Svelto.DataStructures;

namespace Svelto.ECS
namespace Svelto.ECS.Native
{
public readonly struct NativeEGIDMapper<T>:IEGIDMapper where T : unmanaged, IEntityComponent
{
@@ -22,7 +22,7 @@ namespace Svelto.ECS
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ref T Entity(uint entityID)
{
#if DEBUG && !PROFILE_SVELTO
#if DEBUG
if (_map.TryFindIndex(entityID, out var findIndex) == false)
throw new Exception("Entity not found in this group ".FastConcat(typeof(T).ToString()));
#else
@@ -56,7 +56,11 @@ namespace Svelto.ECS
return new NB<T>(_map.GetValues(out var count).ToNativeArray(out _), count);
}

#if DEBUG
throw new ECSException("Entity not found");
#else
return default;
#endif
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]


+ 24
- 23
Svelto.ECS/Extensions/Unity/DOTS/Native/NativeEGIDMultiMapper.cs View File

@@ -2,32 +2,16 @@
using System;
using Svelto.DataStructures;

namespace Svelto.ECS
namespace Svelto.ECS.Native
{
public struct NativeEGIDMultiMapper<T>:IDisposable where T : unmanaged, IEntityComponent
public struct NativeEGIDMultiMapper<T> : IDisposable where T : unmanaged, IEntityComponent
{
SveltoDictionary<ExclusiveGroupStruct, SveltoDictionary<uint, T,
NativeStrategy<SveltoDictionaryNode<uint>>,
NativeStrategy<T>,
NativeStrategy<int>>,
NativeStrategy<SveltoDictionaryNode<ExclusiveGroupStruct>>,
NativeStrategy<SveltoDictionary<uint, T,
NativeStrategy<SveltoDictionaryNode<uint>>,
NativeStrategy<T>,
NativeStrategy<int>>>,
NativeStrategy<int>> _dic;

public NativeEGIDMultiMapper
(SveltoDictionary<ExclusiveGroupStruct, SveltoDictionary<uint, T,
NativeStrategy<SveltoDictionaryNode<uint>>,
NativeStrategy<T>,
NativeStrategy<int>>,
NativeStrategy<SveltoDictionaryNode<ExclusiveGroupStruct>>,
NativeStrategy<SveltoDictionary<uint, T,
NativeStrategy<SveltoDictionaryNode<uint>>,
NativeStrategy<T>,
NativeStrategy<int>>>,
NativeStrategy<int>> dictionary)
(SveltoDictionary<ExclusiveGroupStruct,
SveltoDictionary<uint, T, NativeStrategy<SveltoDictionaryNode<uint>>, NativeStrategy<T>,
NativeStrategy<int>>, NativeStrategy<SveltoDictionaryNode<ExclusiveGroupStruct>>,
NativeStrategy<SveltoDictionary<uint, T, NativeStrategy<SveltoDictionaryNode<uint>>, NativeStrategy<T>,
NativeStrategy<int>>>, NativeStrategy<int>> dictionary)
{
_dic = dictionary;
}
@@ -41,6 +25,10 @@ namespace Svelto.ECS

public ref T Entity(EGID entity)
{
#if DEBUG && !PROFILE_SVELTO
if (Exists(entity) == false)
throw new Exception("NativeEGIDMultiMapper: Entity not found");
#endif
ref var sveltoDictionary = ref _dic.GetValueByRef(entity.groupID);
return ref sveltoDictionary.GetValueByRef(entity.entityID);
}
@@ -50,6 +38,19 @@ namespace Svelto.ECS
return _dic.TryFindIndex(entity.groupID, out var index)
&& _dic.GetDirectValueByRef(index).ContainsKey(entity.entityID);
}

public bool TryGetEntity(EGID entity, out T component)
{
component = default;
return _dic.TryFindIndex(entity.groupID, out var index)
&& _dic.GetDirectValueByRef(index).TryGetValue(entity.entityID, out component);
}
SveltoDictionary<ExclusiveGroupStruct,
SveltoDictionary<uint, T, NativeStrategy<SveltoDictionaryNode<uint>>, NativeStrategy<T>,
NativeStrategy<int>>, NativeStrategy<SveltoDictionaryNode<ExclusiveGroupStruct>>,
NativeStrategy<SveltoDictionary<uint, T, NativeStrategy<SveltoDictionaryNode<uint>>, NativeStrategy<T>,
NativeStrategy<int>>>, NativeStrategy<int>> _dic;
}
}
#endif

+ 16
- 8
Svelto.ECS/Extensions/Unity/DOTS/Native/NativeEntityFactory.cs View File

@@ -1,41 +1,49 @@
#if UNITY_NATIVE
using Svelto.ECS.DataStructures;

namespace Svelto.ECS
namespace Svelto.ECS.Native
{
public readonly struct NativeEntityFactory
{
internal NativeEntityFactory(AtomicNativeBags addOperationQueue, int index)
internal NativeEntityFactory(AtomicNativeBags addOperationQueue, int index, EnginesRoot.LocatorMap entityLocator)
{
_index = index;
_addOperationQueue = addOperationQueue;
_entityLocator = entityLocator;
}

public NativeEntityInitializer BuildEntity
(uint eindex, ExclusiveBuildGroup exclusiveBuildGroup, int threadIndex)
{
EntityReference reference = _entityLocator.ClaimReference();

NativeBag unsafeBuffer = _addOperationQueue.GetBuffer(threadIndex + 1);

unsafeBuffer.Enqueue(_index);
unsafeBuffer.Enqueue(new EGID(eindex, exclusiveBuildGroup));
unsafeBuffer.Enqueue(reference);
unsafeBuffer.ReserveEnqueue<uint>(out var index) = 0;

return new NativeEntityInitializer(unsafeBuffer, index);
return new NativeEntityInitializer(unsafeBuffer, index, reference);
}
public NativeEntityInitializer BuildEntity(EGID egid, int threadIndex)
{
EntityReference reference = _entityLocator.ClaimReference();

NativeBag unsafeBuffer = _addOperationQueue.GetBuffer(threadIndex + 1);

unsafeBuffer.Enqueue(_index);
unsafeBuffer.Enqueue(new EGID(egid.entityID, egid.groupID));
unsafeBuffer.Enqueue(reference);
unsafeBuffer.ReserveEnqueue<uint>(out var index) = 0;

return new NativeEntityInitializer(unsafeBuffer, index);
return new NativeEntityInitializer(unsafeBuffer, index, reference);
}
readonly AtomicNativeBags _addOperationQueue;
readonly int _index;

readonly EnginesRoot.LocatorMap _entityLocator;
readonly AtomicNativeBags _addOperationQueue;
readonly int _index;
}
}
#endif

+ 6
- 2
Svelto.ECS/Extensions/Unity/DOTS/Native/NativeEntityInitializer.cs View File

@@ -1,16 +1,18 @@
using Svelto.ECS.DataStructures;

namespace Svelto.ECS
namespace Svelto.ECS.Native
{
public readonly ref struct NativeEntityInitializer
{
readonly NativeBag _unsafeBuffer;
readonly UnsafeArrayIndex _index;
readonly EntityReference _reference;

public NativeEntityInitializer(in NativeBag unsafeBuffer, UnsafeArrayIndex index)
public NativeEntityInitializer(in NativeBag unsafeBuffer, UnsafeArrayIndex index, EntityReference reference)
{
_unsafeBuffer = unsafeBuffer;
_index = index;
_reference = reference;
}

public void Init<T>(in T component) where T : unmanaged, IEntityComponent
@@ -22,5 +24,7 @@ namespace Svelto.ECS
_unsafeBuffer.Enqueue(id);
_unsafeBuffer.Enqueue(component);
}

public EntityReference reference => _reference;
}
}

+ 1
- 1
Svelto.ECS/Extensions/Unity/DOTS/Native/NativeEntityRemove.cs View File

@@ -1,7 +1,7 @@
#if UNITY_NATIVE
using Svelto.ECS.DataStructures;

namespace Svelto.ECS
namespace Svelto.ECS.Native
{
public readonly struct NativeEntityRemove
{


+ 1
- 1
Svelto.ECS/Extensions/Unity/DOTS/Native/NativeEntitySwap.cs View File

@@ -1,7 +1,7 @@
#if UNITY_NATIVE
using Svelto.ECS.DataStructures;

namespace Svelto.ECS
namespace Svelto.ECS.Native
{
public readonly struct NativeEntitySwap
{


Svelto.ECS/Extensions/Unity/DOTS/Native/UnityEntityDBExtensions.cs → Svelto.ECS/Extensions/Unity/DOTS/Native/UnityNativeEntityDBExtensions.cs View File

@@ -4,9 +4,9 @@ using Svelto.Common;
using Svelto.DataStructures;
using Svelto.ECS.Internal;

namespace Svelto.ECS
namespace Svelto.ECS.Native
{
public static class UnityEntityDBExtensions
public static class UnityNativeEntityDBExtensions
{
internal static NativeEGIDMapper<T> ToNativeEGIDMapper<T>(this TypeSafeDictionary<T> dic,
ExclusiveGroupStruct groupStructId) where T : unmanaged, IEntityComponent
@@ -43,21 +43,15 @@ namespace Svelto.ECS

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static NativeEGIDMultiMapper<T> QueryNativeMappedEntities<T>(this EntitiesDB entitiesDb,
LocalFasterReadOnlyList<ExclusiveGroupStruct> groups)
LocalFasterReadOnlyList<ExclusiveGroupStruct> groups, Allocator allocator)
where T : unmanaged, IEntityComponent
{
var dictionary =
new SveltoDictionary<ExclusiveGroupStruct, SveltoDictionary<uint, T,
NativeStrategy<SveltoDictionaryNode<uint>>,
NativeStrategy<T>,
NativeStrategy<int>>,
NativeStrategy<SveltoDictionaryNode<ExclusiveGroupStruct>>,
NativeStrategy<SveltoDictionary<uint, T,
NativeStrategy<SveltoDictionaryNode<uint>>,
NativeStrategy<T>,
NativeStrategy<int>>>,
NativeStrategy<int>>
((uint) groups.count, Allocator.TempJob);
var dictionary = new SveltoDictionary<ExclusiveGroupStruct, //key
SveltoDictionary<uint, T,
NativeStrategy<SveltoDictionaryNode<uint>>, NativeStrategy<T>, NativeStrategy<int>>, //value
NativeStrategy<SveltoDictionaryNode<ExclusiveGroupStruct>>, //strategy to store the key
NativeStrategy<SveltoDictionary<uint, T, NativeStrategy<SveltoDictionaryNode<uint>>, NativeStrategy<T>, NativeStrategy<int>>>, NativeStrategy<int>> //strategy to store the value
((uint) groups.count, allocator);
foreach (var group in groups)
{

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save