@@ -1 +1 @@ | |||
Subproject commit 9ebffd823db96acb9c29095ab4b9e78fd1fdd56b | |||
Subproject commit db2e49147d988c06f5fa2de93d38ad1a3be99b9d |
@@ -1,6 +1,19 @@ | |||
# Changelog | |||
All notable changes to this project will be documented in this file. Changes are listed in random order of importance. | |||
## [3.3.0] - 04-2022 | |||
* INeedEGID and INeedEntityReference interfaces are not deprecated, but still available for backwards compatibility through the define SLOW_SVELTO_SUBMISSION | |||
* There are some minor breaking changes, you may need to rename a bunch of methods calls | |||
* Drastically improved Submission phase performance | |||
* All the IReactOn interfaces are now replaced by much faster IReacOn*Ex interfaces. Use those~~~~ | |||
* QueryEntities methods now optionally return also an array of Entity IDs that you can reference like a component (this supersedes INeedEGID) | |||
* Completely reworked and way more powerful filter API. The old one has been renamed to Legacy and left for backward compatibility | |||
* NativeEGIDMultiMapper doesn't need to be created every submission anymore. It can be created permanently and disposed when not used anymore (some caveats with it) | |||
* Improved Serialization system | |||
* Improved SveltoOnDots system | |||
* Tons of other improvements and bug fixes | |||
~~~~ | |||
## [3.2.5] | |||
* refactor and improved NativeBag and UnsafeBlob. This fix a previously known crash with Unity IL2CPP | |||
@@ -1,7 +1,9 @@ | |||
#if SLOW_SVELTO_SUBMISSION | |||
namespace Svelto.ECS | |||
{ | |||
public struct EGIDComponent:IEntityComponent, INeedEGID | |||
{ | |||
public EGID ID { get; set; } | |||
} | |||
} | |||
} | |||
#endif |
@@ -1,11 +0,0 @@ | |||
namespace Svelto.ECS | |||
{ | |||
public struct EntityHierarchyComponent: IEntityComponent, INeedEGID | |||
{ | |||
public readonly ExclusiveGroupStruct parentGroup; | |||
public EntityHierarchyComponent(ExclusiveGroup group): this() { parentGroup = group; } | |||
public EGID ID { get; set; } | |||
} | |||
} |
@@ -1,11 +0,0 @@ | |||
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; } | |||
} | |||
} |
@@ -1,7 +0,0 @@ | |||
namespace Svelto.ECS | |||
{ | |||
public struct LinkedEntityComponent : IEntityComponent | |||
{ | |||
public EGID linkedEntity; | |||
} | |||
} |
@@ -4,7 +4,6 @@ using System.Diagnostics; | |||
#endif | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Runtime.CompilerServices; | |||
using Svelto.DataStructures; | |||
namespace Svelto.ECS | |||
@@ -16,9 +15,9 @@ namespace Svelto.ECS | |||
public partial class EnginesRoot | |||
{ | |||
#if DONT_USE | |||
[Conditional("CHECK_ALL")] | |||
[Conditional("MEANINGLESS")] | |||
#endif | |||
void CheckRemoveEntityID(EGID egid, Type entityDescriptorType, [CallerMemberName] string caller = null) | |||
void CheckRemoveEntityID(EGID egid, Type entityDescriptorType, string caller) | |||
{ | |||
if (_multipleOperationOnSameEGIDChecker.ContainsKey(egid) == true) | |||
throw new ECSException( | |||
@@ -30,23 +29,29 @@ namespace Svelto.ECS | |||
.FastConcat(_multipleOperationOnSameEGIDChecker[egid] == 1 ? "add" : "remove")); | |||
if (_idChecker.TryGetValue(egid.groupID, out var hash)) | |||
{ | |||
if (hash.Contains(egid.entityID) == false) | |||
throw new ECSException("Trying to remove an Entity never submitted in the database " | |||
.FastConcat(" caller: ", caller, " ").FastConcat(egid.entityID) | |||
.FastConcat(" groupid: ").FastConcat(egid.groupID.ToName()) | |||
.FastConcat(" type: ") | |||
.FastConcat(entityDescriptorType != null | |||
? entityDescriptorType.Name | |||
: "not available")); | |||
else | |||
hash.Remove(egid.entityID); | |||
throw new ECSException("Trying to remove an Entity not present in the database " | |||
.FastConcat(" caller: ", caller, " entityID ").FastConcat(egid.entityID).FastConcat(" groupid: ") | |||
.FastConcat(egid.groupID.ToName()).FastConcat(" type: ") | |||
.FastConcat(entityDescriptorType != null ? entityDescriptorType.Name : "not available")); | |||
} | |||
else | |||
{ | |||
throw new ECSException("Trying to remove an Entity with a group never used so far " | |||
.FastConcat(" caller: ", caller, " entityID ").FastConcat(egid.entityID).FastConcat(" groupid: ") | |||
.FastConcat(egid.groupID.ToName()).FastConcat(" type: ") | |||
.FastConcat(entityDescriptorType != null ? entityDescriptorType.Name : "not available")); | |||
} | |||
hash.Remove(egid.entityID); | |||
_multipleOperationOnSameEGIDChecker.Add(egid, 0); | |||
} | |||
#if DONT_USE | |||
[Conditional("CHECK_ALL")] | |||
[Conditional("MEANINGLESS")] | |||
#endif | |||
void CheckAddEntityID(EGID egid, Type entityDescriptorType, [CallerMemberName] string caller = null) | |||
void CheckAddEntityID(EGID egid, Type entityDescriptorType, string caller) | |||
{ | |||
if (_multipleOperationOnSameEGIDChecker.ContainsKey(egid) == true) | |||
throw new ECSException( | |||
@@ -57,27 +62,31 @@ namespace Svelto.ECS | |||
.FastConcat(" previous operation was: ") | |||
.FastConcat(_multipleOperationOnSameEGIDChecker[egid] == 1 ? "add" : "remove")); | |||
var hash = _idChecker.GetOrCreate(egid.groupID, () => new HashSet<uint>()); | |||
var hash = _idChecker.GetOrAdd(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) | |||
throw new ECSException("Trying to add an Entity already present in the database " | |||
.FastConcat(" caller: ", caller, " entityID ").FastConcat(egid.entityID) | |||
.FastConcat(" groupid: ").FastConcat(egid.groupID.ToName()).FastConcat(" type: ") | |||
.FastConcat(entityDescriptorType != null | |||
? entityDescriptorType.Name | |||
: "not available")); | |||
hash.Add(egid.entityID); | |||
_multipleOperationOnSameEGIDChecker.Add(egid, 1); | |||
} | |||
#if DONT_USE | |||
[Conditional("CHECK_ALL")] | |||
[Conditional("MEANINGLESS")] | |||
#endif | |||
void RemoveGroupID(ExclusiveBuildGroup groupID) { _idChecker.Remove(groupID); } | |||
void RemoveGroupID(ExclusiveBuildGroup groupID) | |||
{ | |||
_idChecker.Remove(groupID); | |||
} | |||
#if DONT_USE | |||
[Conditional("CHECK_ALL")] | |||
[Conditional("MEANINGLESS")] | |||
#endif | |||
void ClearChecks() { _multipleOperationOnSameEGIDChecker.FastClear(); } | |||
void ClearDebugChecks() { _multipleOperationOnSameEGIDChecker.FastClear(); } | |||
readonly FasterDictionary<EGID, uint> _multipleOperationOnSameEGIDChecker; | |||
readonly FasterDictionary<ExclusiveGroupStruct, HashSet<uint>> _idChecker; | |||
@@ -4,10 +4,11 @@ using System.Diagnostics; | |||
#endif | |||
using System; | |||
using System.Reflection; | |||
using Svelto.ECS.Hybrid; | |||
namespace Svelto.ECS | |||
{ | |||
static class ComponentBuilderUtilities | |||
public static class ComponentBuilderUtilities | |||
{ | |||
const string MSG = "Entity Components and Entity View Components fields cannot hold managed fields outside the Svelto rules."; | |||
@@ -63,16 +64,28 @@ namespace Svelto.ECS | |||
for (int j = properties.Length - 1; j >= 0; --j) | |||
{ | |||
if (properties[j].PropertyType.IsGenericType) | |||
Type propertyType = properties[j].PropertyType; | |||
if (propertyType.IsGenericType) | |||
{ | |||
Type genericTypeDefinition = properties[j].PropertyType.GetGenericTypeDefinition(); | |||
Type genericTypeDefinition = propertyType.GetGenericTypeDefinition(); | |||
if (genericTypeDefinition == RECATIVEVALUETYPE) | |||
{ | |||
continue; | |||
} | |||
} | |||
Type propertyType = properties[j].PropertyType; | |||
// Special rules for ValueReference<T> | |||
if (IsValueReference(propertyType)) | |||
{ | |||
// Getters of ValueReference must be refs, which would cause a failure on the common check. | |||
if (properties[j].CanRead == true && propertyType.IsByRef == false) | |||
{ | |||
ProcessError(MSG, entityComponentType, propertyType); | |||
} | |||
continue; | |||
} | |||
if (propertyType != STRINGTYPE) | |||
{ | |||
//for EntityComponentStructs, component fields that are structs that hold strings | |||
@@ -89,6 +102,12 @@ namespace Svelto.ECS | |||
return type == STRINGTYPE || type == STRINGBUILDERTYPE; | |||
} | |||
static bool IsValueReference(Type type) | |||
{ | |||
var interfaces = type.GetInterfaces(); | |||
return interfaces.Length == 1 && interfaces[0] == VALUE_REF_TYPE; | |||
} | |||
/// <summary> | |||
/// This method checks the fields if it's an IEntityComponent, but checks all the properties if it's | |||
/// IEntityViewComponent | |||
@@ -99,8 +118,9 @@ namespace Svelto.ECS | |||
static void SubCheckFields(Type fieldType, Type entityComponentType, bool isStringAllowed = false) | |||
{ | |||
//pass if it's Primitive or C# 8 unmanaged, or it's a string and string are allowed | |||
//this check must allow pointers are they are unmanaged types | |||
if ((isStringAllowed == true && IsString(fieldType) == true) || fieldType.IsValueTypeEx() == true) | |||
//this check must allow pointers as they are unmanaged types | |||
if ((isStringAllowed == true && IsString(fieldType) == true) || | |||
fieldType.IsValueTypeEx() == true) | |||
{ | |||
//if it's a struct we have to check the fields recursively | |||
if (IsString(fieldType) == false) | |||
@@ -110,7 +130,7 @@ namespace Svelto.ECS | |||
return; | |||
} | |||
ProcessError(MSG, entityComponentType, fieldType); | |||
} | |||
@@ -124,7 +144,8 @@ namespace Svelto.ECS | |||
throw new ECSException(message, entityComponentType); | |||
} | |||
static readonly Type RECATIVEVALUETYPE = typeof(ReactiveValue<>); | |||
static readonly Type RECATIVEVALUETYPE = typeof(ReactiveValue<>); | |||
static readonly Type VALUE_REF_TYPE = typeof(IValueReferenceInternal); | |||
static readonly Type EGIDType = typeof(EGID); | |||
static readonly Type EXCLUSIVEGROUPSTRUCTTYPE = typeof(ExclusiveGroupStruct); | |||
static readonly Type SERIALIZABLE_ENTITY_STRUCT = typeof(SerializableEntityComponent); | |||
@@ -1,6 +1,7 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Reflection; | |||
using System.Threading; | |||
using DBC.ECS; | |||
using Svelto.Common; | |||
using Svelto.DataStructures; | |||
@@ -22,33 +23,59 @@ namespace Svelto.ECS | |||
return obj.GetEntityComponentType().GetHashCode(); | |||
} | |||
} | |||
public static class BurstCompatibleCounter | |||
{ | |||
public static int counter; | |||
} | |||
public class ComponentBuilder<T> : IComponentBuilder | |||
where T : struct, IEntityComponent | |||
public class ComponentID<T> where T : struct, IEntityComponent | |||
{ | |||
public static readonly SharedStaticWrapper<int, ComponentID<T>> id; | |||
#if UNITY_BURST | |||
[Unity.Burst.BurstDiscard] | |||
//SharedStatic values must be initialized from not burstified code | |||
#endif | |||
public static void Init() | |||
{ | |||
id.Data = Interlocked.Increment(ref BurstCompatibleCounter.counter); | |||
DBC.ECS.Check.Ensure(id.Data < ushort.MaxValue, "too many types registered, HOW :)"); | |||
} | |||
} | |||
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; | |||
static readonly T DEFAULT_IT; | |||
static readonly string ENTITY_COMPONENT_NAME; | |||
static readonly bool IS_UNMANAGED; | |||
public static bool HAS_REFERENCE; | |||
#if SLOW_SVELTO_SUBMISSION | |||
public static readonly bool HAS_EGID; | |||
public static readonly bool HAS_REFERENCE; | |||
#endif | |||
static ComponentBuilder() | |||
{ | |||
ENTITY_COMPONENT_TYPE = typeof(T); | |||
DEFAULT_IT = default; | |||
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(); | |||
#if SLOW_SVELTO_SUBMISSION | |||
HAS_EGID = typeof(INeedEGID).IsAssignableFrom(ENTITY_COMPONENT_TYPE); | |||
HAS_REFERENCE = typeof(INeedEntityReference).IsAssignableFrom(ENTITY_COMPONENT_TYPE); | |||
SetEGIDWithoutBoxing<T>.Warmup(); | |||
#endif | |||
ComponentID<T>.Init(); | |||
ENTITY_COMPONENT_NAME = ENTITY_COMPONENT_TYPE.ToString(); | |||
IS_UNMANAGED = TypeType.isUnmanaged<T>(); //attention this is important as it serves as warm up for Type<T> | |||
#if UNITY_NATIVE | |||
if (IS_UNMANAGED) | |||
EntityComponentIDMap.Register<T>(new Filler<T>()); | |||
SetEGIDWithoutBoxing<T>.Warmup(); | |||
#endif | |||
ComponentBuilderUtilities.CheckFields(ENTITY_COMPONENT_TYPE, IS_ENTITY_VIEW_COMPONENT); | |||
@@ -58,16 +85,22 @@ namespace Svelto.ECS | |||
} | |||
else | |||
{ | |||
if (ENTITY_COMPONENT_TYPE != ComponentBuilderUtilities.ENTITY_INFO_COMPONENT | |||
&& ENTITY_COMPONENT_TYPE.IsUnmanagedEx() == false) | |||
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}"); | |||
} | |||
} | |||
public ComponentBuilder() { _initializer = DEFAULT_IT; } | |||
public ComponentBuilder() | |||
{ | |||
_initializer = DEFAULT_IT; | |||
} | |||
public ComponentBuilder(in T initializer) : this() { _initializer = initializer; } | |||
public ComponentBuilder(in T initializer) : this() | |||
{ | |||
_initializer = initializer; | |||
} | |||
public bool isUnmanaged => IS_UNMANAGED; | |||
@@ -75,37 +108,51 @@ namespace Svelto.ECS | |||
{ | |||
var castedDic = dictionary as ITypeSafeDictionary<T>; | |||
T entityComponent = default; | |||
if (IS_ENTITY_VIEW_COMPONENT) | |||
{ | |||
Check.Require(castedDic.ContainsKey(egid.entityID) == false | |||
, $"building an entity with already used entity id! id: '{(ulong) egid}', {ENTITY_COMPONENT_NAME}"); | |||
T entityComponent = default; | |||
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); | |||
this.SetEntityViewComponentImplementors(ref entityComponent, EntityViewComponentCache.cachedFields, | |||
implementors, EntityViewComponentCache.implementorsByType, EntityViewComponentCache.cachedTypes); | |||
castedDic.Add(egid.entityID, entityComponent); | |||
} | |||
else | |||
{ | |||
Check.Require(!castedDic.ContainsKey(egid.entityID) | |||
, $"building an entity with already used entity id! id: '{egid.entityID}'"); | |||
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); } | |||
void IComponentBuilder.Preallocate(ITypeSafeDictionary dictionary, uint size) | |||
{ | |||
Preallocate(dictionary, size); | |||
} | |||
public ITypeSafeDictionary CreateDictionary(uint size) { return TypeSafeDictionaryFactory<T>.Create(size); } | |||
public ITypeSafeDictionary CreateDictionary(uint size) | |||
{ | |||
return TypeSafeDictionaryFactory<T>.Create(size); | |||
} | |||
public Type GetEntityComponentType() { return ENTITY_COMPONENT_TYPE; } | |||
public Type GetEntityComponentType() | |||
{ | |||
return ENTITY_COMPONENT_TYPE; | |||
} | |||
public override int GetHashCode() { return _initializer.GetHashCode(); } | |||
public override int GetHashCode() | |||
{ | |||
return _initializer.GetHashCode(); | |||
} | |||
static void Preallocate(ITypeSafeDictionary dictionary, uint size) { dictionary.ResizeTo(size); } | |||
static void Preallocate(ITypeSafeDictionary dictionary, uint size) | |||
{ | |||
dictionary.EnsureCapacity(size); | |||
} | |||
readonly T _initializer; | |||
@@ -156,7 +203,9 @@ namespace Svelto.ECS | |||
#endif | |||
} | |||
internal static void InitCache() { } | |||
internal static void InitCache() | |||
{ | |||
} | |||
} | |||
} | |||
} |
@@ -55,7 +55,7 @@ namespace DBC.ECS | |||
/// | |||
static class Check | |||
{ | |||
#region Interface | |||
#region Interface | |||
/// <summary> | |||
/// Precondition check. | |||
@@ -65,15 +65,8 @@ namespace DBC.ECS | |||
#endif | |||
public static void Require(bool assertion, string message) | |||
{ | |||
if (UseExceptions) | |||
{ | |||
if (!assertion) | |||
throw new PreconditionException(message); | |||
} | |||
else | |||
{ | |||
Trace.Assert(assertion, "Precondition: " + message); | |||
} | |||
if (!assertion) | |||
throw new PreconditionException(message); | |||
} | |||
/// <summary> | |||
@@ -85,15 +78,8 @@ namespace DBC.ECS | |||
#endif | |||
public static void Require(bool assertion, string message, Exception inner) | |||
{ | |||
if (UseExceptions) | |||
{ | |||
if (!assertion) | |||
throw new PreconditionException(message, inner); | |||
} | |||
else | |||
{ | |||
Trace.Assert(assertion, "Precondition: " + message); | |||
} | |||
if (!assertion) | |||
throw new PreconditionException(message, inner); | |||
} | |||
/// <summary> | |||
@@ -105,15 +91,8 @@ namespace DBC.ECS | |||
#endif | |||
public static void Require(bool assertion) | |||
{ | |||
if (UseExceptions) | |||
{ | |||
if (!assertion) | |||
throw new PreconditionException("Precondition failed."); | |||
} | |||
else | |||
{ | |||
Trace.Assert(assertion, "Precondition failed."); | |||
} | |||
if (!assertion) | |||
throw new PreconditionException("Precondition failed."); | |||
} | |||
/// <summary> | |||
@@ -125,15 +104,8 @@ namespace DBC.ECS | |||
#endif | |||
public static void Ensure(bool assertion, string message) | |||
{ | |||
if (UseExceptions) | |||
{ | |||
if (!assertion) | |||
throw new PostconditionException(message); | |||
} | |||
else | |||
{ | |||
Trace.Assert(assertion, "Postcondition: " + message); | |||
} | |||
if (!assertion) | |||
throw new PostconditionException(message); | |||
} | |||
/// <summary> | |||
@@ -145,15 +117,8 @@ namespace DBC.ECS | |||
#endif | |||
public static void Ensure(bool assertion, string message, Exception inner) | |||
{ | |||
if (UseExceptions) | |||
{ | |||
if (!assertion) | |||
throw new PostconditionException(message, inner); | |||
} | |||
else | |||
{ | |||
Trace.Assert(assertion, "Postcondition: " + message); | |||
} | |||
if (!assertion) | |||
throw new PostconditionException(message, inner); | |||
} | |||
/// <summary> | |||
@@ -165,15 +130,8 @@ namespace DBC.ECS | |||
#endif | |||
public static void Ensure(bool assertion) | |||
{ | |||
if (UseExceptions) | |||
{ | |||
if (!assertion) | |||
throw new PostconditionException("Postcondition failed."); | |||
} | |||
else | |||
{ | |||
Trace.Assert(assertion, "Postcondition failed."); | |||
} | |||
if (!assertion) | |||
throw new PostconditionException("Postcondition failed."); | |||
} | |||
/// <summary> | |||
@@ -185,15 +143,8 @@ namespace DBC.ECS | |||
#endif | |||
public static void Invariant(bool assertion, string message) | |||
{ | |||
if (UseExceptions) | |||
{ | |||
if (!assertion) | |||
throw new InvariantException(message); | |||
} | |||
else | |||
{ | |||
Trace.Assert(assertion, "Invariant: " + message); | |||
} | |||
if (!assertion) | |||
throw new InvariantException(message); | |||
} | |||
/// <summary> | |||
@@ -205,15 +156,8 @@ namespace DBC.ECS | |||
#endif | |||
public static void Invariant(bool assertion, string message, Exception inner) | |||
{ | |||
if (UseExceptions) | |||
{ | |||
if (!assertion) | |||
throw new InvariantException(message, inner); | |||
} | |||
else | |||
{ | |||
Trace.Assert(assertion, "Invariant: " + message); | |||
} | |||
if (!assertion) | |||
throw new InvariantException(message, inner); | |||
} | |||
/// <summary> | |||
@@ -225,15 +169,8 @@ namespace DBC.ECS | |||
#endif | |||
public static void Invariant(bool assertion) | |||
{ | |||
if (UseExceptions) | |||
{ | |||
if (!assertion) | |||
throw new InvariantException("Invariant failed."); | |||
} | |||
else | |||
{ | |||
Trace.Assert(assertion, "Invariant failed."); | |||
} | |||
if (!assertion) | |||
throw new InvariantException("Invariant failed."); | |||
} | |||
/// <summary> | |||
@@ -244,15 +181,8 @@ namespace DBC.ECS | |||
#endif | |||
public static void Assert(bool assertion, string message) | |||
{ | |||
if (UseExceptions) | |||
{ | |||
if (!assertion) | |||
throw new AssertionException(message); | |||
} | |||
else | |||
{ | |||
Trace.Assert(assertion, "Assertion: " + message); | |||
} | |||
if (!assertion) | |||
throw new AssertionException(message); | |||
} | |||
/// <summary> | |||
@@ -264,15 +194,8 @@ namespace DBC.ECS | |||
#endif | |||
public static void Assert(bool assertion, string message, Exception inner) | |||
{ | |||
if (UseExceptions) | |||
{ | |||
if (!assertion) | |||
throw new AssertionException(message, inner); | |||
} | |||
else | |||
{ | |||
Trace.Assert(assertion, "Assertion: " + message); | |||
} | |||
if (!assertion) | |||
throw new AssertionException(message, inner); | |||
} | |||
/// <summary> | |||
@@ -284,59 +207,22 @@ namespace DBC.ECS | |||
#endif | |||
public static void Assert(bool assertion) | |||
{ | |||
if (UseExceptions) | |||
{ | |||
if (!assertion) | |||
throw new AssertionException("Assertion failed."); | |||
} | |||
else | |||
{ | |||
Trace.Assert(assertion, "Assertion failed."); | |||
} | |||
if (!assertion) | |||
throw new AssertionException("Assertion failed."); | |||
} | |||
/// <summary> | |||
/// Set this if you wish to use Trace Assert statements | |||
/// instead of exception handling. | |||
/// (The Check class uses exception handling by default.) | |||
/// </summary> | |||
public static bool UseAssertions | |||
{ | |||
#endregion // Interface | |||
get | |||
{ | |||
return useAssertions; | |||
} | |||
set | |||
{ | |||
useAssertions = value; | |||
} | |||
} | |||
#endregion // Interface | |||
#region Implementation | |||
#region Implementation | |||
// No creation | |||
/// <summary> | |||
/// Is exception handling being used? | |||
/// </summary> | |||
static bool UseExceptions | |||
{ | |||
get | |||
{ | |||
return !useAssertions; | |||
} | |||
} | |||
// Are trace assertion statements being used? | |||
// Default is to use exception handling. | |||
static bool useAssertions; | |||
#endregion // Implementation | |||
} // End Check | |||
#endregion // Implementation | |||
} // End Check | |||
internal class Trace | |||
{ | |||
@@ -350,7 +236,7 @@ namespace DBC.ECS | |||
} | |||
} | |||
#region Exceptions | |||
#region Exceptions | |||
/// <summary> | |||
/// Exception raised when a contract is broken. | |||
@@ -360,9 +246,17 @@ namespace DBC.ECS | |||
/// </summary> | |||
public class DesignByContractException : Exception | |||
{ | |||
protected DesignByContractException() {} | |||
protected DesignByContractException(string message) : base(message) {} | |||
protected DesignByContractException(string message, Exception inner) : base(message, inner) {} | |||
protected DesignByContractException() | |||
{ | |||
} | |||
protected DesignByContractException(string message) : base(message) | |||
{ | |||
} | |||
protected DesignByContractException(string message, Exception inner) : base(message, inner) | |||
{ | |||
} | |||
} | |||
/// <summary> | |||
@@ -373,15 +267,23 @@ namespace DBC.ECS | |||
/// <summary> | |||
/// Precondition Exception. | |||
/// </summary> | |||
public PreconditionException() {} | |||
public PreconditionException() | |||
{ | |||
} | |||
/// <summary> | |||
/// Precondition Exception. | |||
/// </summary> | |||
public PreconditionException(string message) : base(message) {} | |||
public PreconditionException(string message) : base(message) | |||
{ | |||
} | |||
/// <summary> | |||
/// Precondition Exception. | |||
/// </summary> | |||
public PreconditionException(string message, Exception inner) : base(message, inner) {} | |||
public PreconditionException(string message, Exception inner) : base(message, inner) | |||
{ | |||
} | |||
} | |||
/// <summary> | |||
@@ -392,15 +294,23 @@ namespace DBC.ECS | |||
/// <summary> | |||
/// Postcondition Exception. | |||
/// </summary> | |||
public PostconditionException() {} | |||
public PostconditionException() | |||
{ | |||
} | |||
/// <summary> | |||
/// Postcondition Exception. | |||
/// </summary> | |||
public PostconditionException(string message) : base(message) {} | |||
public PostconditionException(string message) : base(message) | |||
{ | |||
} | |||
/// <summary> | |||
/// Postcondition Exception. | |||
/// </summary> | |||
public PostconditionException(string message, Exception inner) : base(message, inner) {} | |||
public PostconditionException(string message, Exception inner) : base(message, inner) | |||
{ | |||
} | |||
} | |||
/// <summary> | |||
@@ -411,15 +321,23 @@ namespace DBC.ECS | |||
/// <summary> | |||
/// Invariant Exception. | |||
/// </summary> | |||
public InvariantException() {} | |||
public InvariantException() | |||
{ | |||
} | |||
/// <summary> | |||
/// Invariant Exception. | |||
/// </summary> | |||
public InvariantException(string message) : base(message) {} | |||
public InvariantException(string message) : base(message) | |||
{ | |||
} | |||
/// <summary> | |||
/// Invariant Exception. | |||
/// </summary> | |||
public InvariantException(string message, Exception inner) : base(message, inner) {} | |||
public InvariantException(string message, Exception inner) : base(message, inner) | |||
{ | |||
} | |||
} | |||
/// <summary> | |||
@@ -430,17 +348,24 @@ namespace DBC.ECS | |||
/// <summary> | |||
/// Assertion Exception. | |||
/// </summary> | |||
public AssertionException() {} | |||
public AssertionException() | |||
{ | |||
} | |||
/// <summary> | |||
/// Assertion Exception. | |||
/// </summary> | |||
public AssertionException(string message) : base(message) {} | |||
public AssertionException(string message) : base(message) | |||
{ | |||
} | |||
/// <summary> | |||
/// Assertion Exception. | |||
/// </summary> | |||
public AssertionException(string message, Exception inner) : base(message, inner) {} | |||
public AssertionException(string message, Exception inner) : base(message, inner) | |||
{ | |||
} | |||
} | |||
#endregion // Exception classes | |||
} // End Design By Contract | |||
#endregion // Exception classes | |||
} // End Design By Contract |
@@ -8,16 +8,16 @@ namespace Svelto.ECS | |||
/// <summary> | |||
/// </summary> | |||
/// <typeparam name="T"></typeparam> | |||
public readonly struct EGIDMapper<T>: IEGIDMapper where T : struct, IEntityComponent | |||
public readonly struct EGIDMapper<T> : IEGIDMapper where T : struct, IEntityComponent | |||
{ | |||
public uint length => _map.count; | |||
public ExclusiveGroupStruct groupID { get; } | |||
public Type entityType => TypeCache<T>.type; | |||
public int count => _map.count; | |||
public ExclusiveGroupStruct groupID { get; } | |||
public Type entityType => TypeCache<T>.type; | |||
internal EGIDMapper(ExclusiveGroupStruct groupStructId, ITypeSafeDictionary<T> dic) : this() | |||
{ | |||
groupID = groupStructId; | |||
_map = dic; | |||
_map = dic; | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
@@ -25,9 +25,10 @@ namespace Svelto.ECS | |||
{ | |||
#if DEBUG && !PROFILE_SVELTO | |||
if (_map == null) | |||
throw new System.Exception("Not initialized EGIDMapper in this group ".FastConcat(typeof(T).ToString())); | |||
if (_map.TryFindIndex(entityID, out var findIndex) == false) | |||
throw new System.Exception("Entity not found in this group ".FastConcat(typeof(T).ToString())); | |||
throw new System.Exception( | |||
"Not initialized EGIDMapper in this group ".FastConcat(typeof(T).ToString())); | |||
if (_map.TryFindIndex(entityID, out var findIndex) == false) | |||
throw new System.Exception("Entity not found in this group ".FastConcat(typeof(T).ToString())); | |||
#else | |||
_map.TryFindIndex(entityID, out var findIndex); | |||
#endif | |||
@@ -72,8 +73,8 @@ namespace Svelto.ECS | |||
bool FindIndex(uint valueKey, out uint index); | |||
uint GetIndex(uint entityID); | |||
bool Exists(uint idEntityId); | |||
ExclusiveGroupStruct groupID { get; } | |||
Type entityType { get; } | |||
ExclusiveGroupStruct groupID { get; } | |||
Type entityType { get; } | |||
} | |||
} |
@@ -11,7 +11,6 @@ namespace Svelto.ECS | |||
///{ | |||
/// WiresTimeRunningGroup, | |||
/// WiresPreInitTimeRunningGroup, | |||
/// WiresInitTimeStoppedGroup, | |||
/// WiresInitTimeRunningGroup | |||
///} | |||
/// | |||
@@ -7,77 +7,98 @@ namespace Svelto.ECS | |||
{ | |||
internal class DoubleBufferedEntitiesToAdd | |||
{ | |||
const int MAX_NUMBER_OF_ITEMS_PER_FRAME_BEFORE_TO_CLEAR = 100; | |||
//while caching is good to avoid over creating dictionaries that may be reused, the side effect | |||
//is that I have to iterate every time up to 100 dictionaries during the flushing of the build entities | |||
//even if there are 0 entities inside. | |||
const int MAX_NUMBER_OF_GROUPS_TO_CACHE = 100; | |||
const int MAX_NUMBER_OF_TYPES_PER_GROUP_TO_CACHE = 100; | |||
public DoubleBufferedEntitiesToAdd() | |||
{ | |||
_currentEntitiesCreatedPerGroup = _entitiesCreatedPerGroupA; | |||
_otherEntitiesCreatedPerGroup = _entitiesCreatedPerGroupB; | |||
var entitiesCreatedPerGroupA = new FasterDictionary<ExclusiveGroupStruct, uint>(); | |||
var entitiesCreatedPerGroupB = new FasterDictionary<ExclusiveGroupStruct, uint>(); | |||
var entityComponentsToAddBufferA = | |||
new FasterDictionary<ExclusiveGroupStruct, FasterDictionary<RefWrapperType, ITypeSafeDictionary>>(); | |||
var entityComponentsToAddBufferB = | |||
new FasterDictionary<ExclusiveGroupStruct, FasterDictionary<RefWrapperType, ITypeSafeDictionary>>(); | |||
current = _entityComponentsToAddBufferA; | |||
other = _entityComponentsToAddBufferB; | |||
_currentNumberEntitiesCreatedPerGroup = entitiesCreatedPerGroupA; | |||
_lastNumberEntitiesCreatedPerGroup = entitiesCreatedPerGroupB; | |||
currentComponentsToAddPerGroup = entityComponentsToAddBufferA; | |||
lastComponentsToAddPerGroup = entityComponentsToAddBufferB; | |||
} | |||
public void ClearOther() | |||
public void ClearLastAddOperations() | |||
{ | |||
//do not clear the groups created so far, they will be reused, unless they are too many! | |||
var otherCount = other.count; | |||
if (otherCount > MAX_NUMBER_OF_ITEMS_PER_FRAME_BEFORE_TO_CLEAR) | |||
var numberOfGroupsAddedSoFar = lastComponentsToAddPerGroup.count; | |||
var componentDictionariesPerType = lastComponentsToAddPerGroup.unsafeValues; | |||
//TODO: rewrite the caching logic with the new RecycleOrAdd dictionary functionality | |||
//I still do not want to cache too many groups | |||
//If we didn't create too many groups, we keep them alive, so we avoid the cost of creating new dictionaries | |||
//during future submissions, otherwise we clean up everything | |||
if (numberOfGroupsAddedSoFar > MAX_NUMBER_OF_GROUPS_TO_CACHE) | |||
{ | |||
var otherValuesArray = other.unsafeValues; | |||
for (var i = 0; i < otherCount; ++i) | |||
for (var i = 0; i < numberOfGroupsAddedSoFar; ++i) | |||
{ | |||
var safeDictionariesCount = otherValuesArray[i].count; | |||
var safeDictionaries = otherValuesArray[i].unsafeValues; | |||
var componentTypesCount = componentDictionariesPerType[i].count; | |||
var componentTypesDictionary = componentDictionariesPerType[i].unsafeValues; | |||
{ | |||
for (var j = 0; j < safeDictionariesCount; ++j) | |||
//clear the dictionary of entities create do far (it won't allocate though) | |||
safeDictionaries[j].Dispose(); | |||
for (var j = 0; j < componentTypesCount; ++j) | |||
//dictionaries of components may be native so they need to be disposed | |||
//before the references are GCed | |||
componentTypesDictionary[j].Dispose(); | |||
} | |||
} | |||
//reset the number of entities created so far | |||
_otherEntitiesCreatedPerGroup.FastClear(); | |||
other.FastClear(); | |||
_lastNumberEntitiesCreatedPerGroup.FastClear(); | |||
lastComponentsToAddPerGroup.FastClear(); | |||
return; | |||
} | |||
for (var i = 0; i < numberOfGroupsAddedSoFar; ++i) | |||
{ | |||
var otherValuesArray = other.unsafeValues; | |||
for (var i = 0; i < otherCount; ++i) | |||
var componentTypesCount = componentDictionariesPerType[i].count; | |||
ITypeSafeDictionary[] componentTypesDictionary = componentDictionariesPerType[i].unsafeValues; | |||
for (var j = 0; j < componentTypesCount; ++j) | |||
//clear the dictionary of entities created so far (it won't allocate though) | |||
componentTypesDictionary[j].Clear(); | |||
//if we didn't create too many component for this group, I reuse the component arrays | |||
if (componentTypesCount <= MAX_NUMBER_OF_TYPES_PER_GROUP_TO_CACHE) | |||
{ | |||
var safeDictionariesCount = otherValuesArray[i].count; | |||
var safeDictionaries = otherValuesArray[i].unsafeValues; | |||
//do not remove the dictionaries of entities per type created so far, they will be reused | |||
if (safeDictionariesCount <= MAX_NUMBER_OF_ITEMS_PER_FRAME_BEFORE_TO_CLEAR) | |||
{ | |||
for (var j = 0; j < safeDictionariesCount; ++j) | |||
//clear the dictionary of entities create do far (it won't allocate though) | |||
safeDictionaries[j].FastClear(); | |||
} | |||
else | |||
{ | |||
for (var j = 0; j < safeDictionariesCount; ++j) | |||
//clear the dictionary of entities create do far (it won't allocate though) | |||
safeDictionaries[j].Dispose(); | |||
otherValuesArray[i].FastClear(); | |||
} | |||
for (var j = 0; j < componentTypesCount; ++j) | |||
componentTypesDictionary[j].Clear(); | |||
} | |||
else | |||
{ | |||
//here I have to dispose, because I am actually clearing the reference of the dictionary | |||
//with the next line. | |||
for (var j = 0; j < componentTypesCount; ++j) | |||
componentTypesDictionary[j].Dispose(); | |||
componentDictionariesPerType[i].FastClear(); | |||
} | |||
//reset the number of entities created so far | |||
_otherEntitiesCreatedPerGroup.FastClear(); | |||
} | |||
//reset the number of entities created so far | |||
_lastNumberEntitiesCreatedPerGroup.FastClear(); | |||
// _totalEntitiesToAdd = 0; | |||
} | |||
public void Dispose() | |||
{ | |||
{ | |||
var otherValuesArray = other.unsafeValues; | |||
for (var i = 0; i < other.count; ++i) | |||
var otherValuesArray = lastComponentsToAddPerGroup.unsafeValues; | |||
for (var i = 0; i < lastComponentsToAddPerGroup.count; ++i) | |||
{ | |||
var safeDictionariesCount = otherValuesArray[i].count; | |||
var safeDictionaries = otherValuesArray[i].unsafeValues; | |||
int safeDictionariesCount = otherValuesArray[i].count; | |||
ITypeSafeDictionary[] safeDictionaries = otherValuesArray[i].unsafeValues; | |||
//do not remove the dictionaries of entities per type created so far, they will be reused | |||
for (var j = 0; j < safeDictionariesCount; ++j) | |||
//clear the dictionary of entities create do far (it won't allocate though) | |||
@@ -85,98 +106,158 @@ namespace Svelto.ECS | |||
} | |||
} | |||
{ | |||
var currentValuesArray = current.unsafeValues; | |||
for (var i = 0; i < current.count; ++i) | |||
var currentValuesArray = currentComponentsToAddPerGroup.unsafeValues; | |||
for (var i = 0; i < currentComponentsToAddPerGroup.count; ++i) | |||
{ | |||
var safeDictionariesCount = currentValuesArray[i].count; | |||
var safeDictionaries = currentValuesArray[i].unsafeValues; | |||
int safeDictionariesCount = currentValuesArray[i].count; | |||
ITypeSafeDictionary[] safeDictionaries = currentValuesArray[i].unsafeValues; | |||
//do not remove the dictionaries of entities per type created so far, they will be reused | |||
for (var j = 0; j < safeDictionariesCount; ++j) | |||
//clear the dictionary of entities create do far (it won't allocate though) | |||
safeDictionaries[j].Dispose(); | |||
} | |||
} | |||
_currentNumberEntitiesCreatedPerGroup = null; | |||
_lastNumberEntitiesCreatedPerGroup = null; | |||
lastComponentsToAddPerGroup = null; | |||
currentComponentsToAddPerGroup = null; | |||
} | |||
internal bool AnyEntityCreated() | |||
{ | |||
return _currentEntitiesCreatedPerGroup.count > 0; | |||
return _currentNumberEntitiesCreatedPerGroup.count > 0; | |||
} | |||
internal bool AnyOtherEntityCreated() | |||
internal bool AnyPreviousEntityCreated() | |||
{ | |||
return _otherEntitiesCreatedPerGroup.count > 0; | |||
return _lastNumberEntitiesCreatedPerGroup.count > 0; | |||
} | |||
internal void IncrementEntityCount(ExclusiveGroupStruct groupID) | |||
{ | |||
_currentEntitiesCreatedPerGroup.GetOrCreate(groupID)++; | |||
_currentNumberEntitiesCreatedPerGroup.GetOrAdd(groupID)++; | |||
// _totalEntitiesToAdd++; | |||
} | |||
// public uint NumberOfEntitiesToAdd() | |||
// { | |||
// return _totalEntitiesToAdd; | |||
// } | |||
internal void Preallocate | |||
(ExclusiveGroupStruct groupID, uint numberOfEntities, IComponentBuilder[] entityComponentsToBuild) | |||
{ | |||
void PreallocateDictionaries | |||
(FasterDictionary<ExclusiveGroupStruct, FasterDictionary<RefWrapperType, ITypeSafeDictionary>> dic) | |||
{ | |||
var group = dic.GetOrCreate(groupID, () => new FasterDictionary<RefWrapperType, | |||
ITypeSafeDictionary>()); | |||
var group = dic.GetOrAdd( | |||
groupID, () => new FasterDictionary<RefWrapperType, ITypeSafeDictionary>()); | |||
foreach (var componentBuilder in entityComponentsToBuild) | |||
{ | |||
var entityComponentType = componentBuilder.GetEntityComponentType(); | |||
var safeDictionary = group.GetOrCreate(new RefWrapperType(entityComponentType) | |||
var safeDictionary = group.GetOrAdd(new RefWrapperType(entityComponentType) | |||
, () => componentBuilder | |||
.CreateDictionary(numberOfEntities)); | |||
componentBuilder.Preallocate(safeDictionary, numberOfEntities); | |||
} | |||
} | |||
PreallocateDictionaries(current); | |||
PreallocateDictionaries(other); | |||
PreallocateDictionaries(currentComponentsToAddPerGroup); | |||
PreallocateDictionaries(lastComponentsToAddPerGroup); | |||
_currentEntitiesCreatedPerGroup.GetOrCreate(groupID); | |||
_otherEntitiesCreatedPerGroup.GetOrCreate(groupID); | |||
_currentNumberEntitiesCreatedPerGroup.GetOrAdd(groupID); | |||
_lastNumberEntitiesCreatedPerGroup.GetOrAdd(groupID); | |||
} | |||
internal void Swap() | |||
{ | |||
Swap(ref current, ref other); | |||
Swap(ref _currentEntitiesCreatedPerGroup, ref _otherEntitiesCreatedPerGroup); | |||
Swap(ref currentComponentsToAddPerGroup, ref lastComponentsToAddPerGroup); | |||
Swap(ref _currentNumberEntitiesCreatedPerGroup, ref _lastNumberEntitiesCreatedPerGroup); | |||
} | |||
void Swap<T>(ref T item1, ref T item2) | |||
static void Swap<T>(ref T item1, ref T item2) | |||
{ | |||
var toSwap = item2; | |||
item2 = item1; | |||
item1 = toSwap; | |||
(item2, item1) = (item1, item2); | |||
} | |||
public OtherComponentsToAddPerGroupEnumerator GetEnumerator() | |||
{ | |||
return new OtherComponentsToAddPerGroupEnumerator(lastComponentsToAddPerGroup | |||
, _lastNumberEntitiesCreatedPerGroup); | |||
} | |||
//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. Sparseset needs | |||
//entities to be created sequentially (the index cannot be managed externally) | |||
internal FasterDictionary<ExclusiveGroupStruct, FasterDictionary<RefWrapperType, ITypeSafeDictionary>> current; | |||
internal FasterDictionary<ExclusiveGroupStruct, FasterDictionary<RefWrapperType, ITypeSafeDictionary>> other; | |||
readonly FasterDictionary<ExclusiveGroupStruct, uint> _entitiesCreatedPerGroupA = | |||
new FasterDictionary<ExclusiveGroupStruct, uint>(); | |||
readonly FasterDictionary<ExclusiveGroupStruct, uint> _entitiesCreatedPerGroupB = | |||
new FasterDictionary<ExclusiveGroupStruct, uint>(); | |||
readonly FasterDictionary<ExclusiveGroupStruct, FasterDictionary<RefWrapperType, ITypeSafeDictionary>> _entityComponentsToAddBufferA = | |||
new FasterDictionary<ExclusiveGroupStruct, FasterDictionary<RefWrapperType, ITypeSafeDictionary>>(); | |||
internal FasterDictionary<ExclusiveGroupStruct, FasterDictionary<RefWrapperType, ITypeSafeDictionary>> | |||
currentComponentsToAddPerGroup; | |||
readonly FasterDictionary<ExclusiveGroupStruct, FasterDictionary<RefWrapperType, ITypeSafeDictionary>> _entityComponentsToAddBufferB = | |||
new FasterDictionary<ExclusiveGroupStruct, FasterDictionary<RefWrapperType, ITypeSafeDictionary>>(); | |||
FasterDictionary<ExclusiveGroupStruct, FasterDictionary<RefWrapperType, ITypeSafeDictionary>> | |||
lastComponentsToAddPerGroup; | |||
/// <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; | |||
FasterDictionary<ExclusiveGroupStruct, uint> _currentNumberEntitiesCreatedPerGroup; | |||
FasterDictionary<ExclusiveGroupStruct, uint> _lastNumberEntitiesCreatedPerGroup; | |||
//uint _totalEntitiesToAdd; | |||
} | |||
} | |||
struct OtherComponentsToAddPerGroupEnumerator | |||
{ | |||
public OtherComponentsToAddPerGroupEnumerator | |||
(FasterDictionary<ExclusiveGroupStruct, FasterDictionary<RefWrapperType, ITypeSafeDictionary>> | |||
lastComponentsToAddPerGroup | |||
, FasterDictionary<ExclusiveGroupStruct, uint> otherNumberEntitiesCreatedPerGroup) | |||
{ | |||
_lastComponentsToAddPerGroup = lastComponentsToAddPerGroup; | |||
_lastNumberEntitiesCreatedPerGroup = otherNumberEntitiesCreatedPerGroup.GetEnumerator(); | |||
Current = default; | |||
} | |||
public bool MoveNext() | |||
{ | |||
while (_lastNumberEntitiesCreatedPerGroup.MoveNext()) | |||
{ | |||
var current = _lastNumberEntitiesCreatedPerGroup.Current; | |||
if (current.value > 0) //there are entities in this group | |||
{ | |||
var value = _lastComponentsToAddPerGroup[current.key]; | |||
Current = new GroupInfo() | |||
{ | |||
group = current.key | |||
, components = value | |||
}; | |||
return true; | |||
} | |||
} | |||
return false; | |||
} | |||
public GroupInfo Current { get; private set; } | |||
//cannot be read only as they will be modified by MoveNext | |||
readonly FasterDictionary<ExclusiveGroupStruct, FasterDictionary<RefWrapperType, ITypeSafeDictionary>> | |||
_lastComponentsToAddPerGroup; | |||
SveltoDictionaryKeyValueEnumerator<ExclusiveGroupStruct, uint, | |||
ManagedStrategy<SveltoDictionaryNode<ExclusiveGroupStruct>>, ManagedStrategy<uint>, | |||
ManagedStrategy<int>> | |||
_lastNumberEntitiesCreatedPerGroup; | |||
} | |||
struct GroupInfo | |||
{ | |||
public ExclusiveGroupStruct group; | |||
public FasterDictionary<RefWrapperType, ITypeSafeDictionary> components; | |||
} | |||
} |
@@ -1,19 +1,33 @@ | |||
#if PROFILE_SVELTO && DEBUG | |||
#warning the global define PROFILE_SVELTO should be used only when it's necessary to profile in order to reduce the overhead of debug code. Normally remove this define to get insights when errors happen | |||
#endif | |||
using System; | |||
using System.Collections.Generic; | |||
using DBC.ECS; | |||
using Svelto.Common; | |||
using Svelto.Common.DataStructures; | |||
using Svelto.DataStructures; | |||
using Svelto.ECS.Internal; | |||
using Svelto.ECS.Schedulers; | |||
namespace Svelto.ECS | |||
{ | |||
public sealed partial class EnginesRoot | |||
public partial class EnginesRoot | |||
{ | |||
static EnginesRoot() | |||
{ | |||
GroupHashMap.Init(); | |||
SharedDictonary.Init(); | |||
SerializationDescriptorMap.Init(); | |||
_swapEntities = SwapEntities; | |||
_removeEntities = RemoveEntities; | |||
_removeGroup = RemoveGroup; | |||
_swapGroup = SwapGroup; | |||
} | |||
/// <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 | |||
@@ -24,36 +38,51 @@ namespace Svelto.ECS | |||
/// </summary> | |||
public EnginesRoot(EntitiesSubmissionScheduler entitiesComponentScheduler) | |||
{ | |||
_entitiesOperations = new FasterDictionary<ulong, EntitySubmitOperation>(); | |||
_idChecker = new FasterDictionary<ExclusiveGroupStruct, HashSet<uint>>(); | |||
_multipleOperationOnSameEGIDChecker = new FasterDictionary<EGID, uint>(); | |||
#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>(); | |||
_entitiesOperations = new EntitiesOperations(); | |||
_idChecker = new FasterDictionary<ExclusiveGroupStruct, HashSet<uint>>(); | |||
_cachedRangeOfSubmittedIndices = new FasterList<(uint, uint)>(); | |||
_cachedIndicesToSwapBeforeSubmissionForFilters = new FasterDictionary<uint, uint>(); | |||
_multipleOperationOnSameEGIDChecker = new FasterDictionary<EGID, uint>(); | |||
#if UNITY_NATIVE //because of the thread count, ATM this is only for unity | |||
_nativeSwapOperationQueue = new Svelto.ECS.DataStructures.AtomicNativeBags(Allocator.Persistent); | |||
_nativeRemoveOperationQueue = new Svelto.ECS.DataStructures.AtomicNativeBags(Allocator.Persistent); | |||
_nativeAddOperationQueue = new Svelto.ECS.DataStructures.AtomicNativeBags(Allocator.Persistent); | |||
#endif | |||
_serializationDescriptorMap = new SerializationDescriptorMap(); | |||
_reactiveEnginesAdd = new FasterDictionary<RefWrapperType, FasterList<ReactEngineContainer<IReactOnAdd>>>(); | |||
_reactiveEnginesAddEx = | |||
new FasterDictionary<RefWrapperType, FasterList<ReactEngineContainer<IReactOnAddEx>>>(); | |||
_reactiveEnginesRemove = | |||
new FasterDictionary<RefWrapperType, FasterList<ReactEngineContainer<IReactOnRemove>>>(); | |||
_reactiveEnginesRemoveEx = | |||
new FasterDictionary<RefWrapperType, FasterList<ReactEngineContainer<IReactOnRemoveEx>>>(); | |||
_reactiveEnginesSwap = | |||
new FasterDictionary<RefWrapperType, FasterList<ReactEngineContainer<IReactOnSwap>>>(); | |||
_reactiveEnginesSwapEx = | |||
new FasterDictionary<RefWrapperType, FasterList<ReactEngineContainer<IReactOnSwapEx>>>(); | |||
_reactiveEnginesDispose = | |||
new FasterDictionary<RefWrapperType, FasterList<ReactEngineContainer<IReactOnDispose>>>(); | |||
_reactiveEnginesSubmission = new FasterList<IReactOnSubmission>(); | |||
_enginesSet = new FasterList<IEngine>(); | |||
_enginesTypeSet = new HashSet<Type>(); | |||
_disposableEngines = new FasterList<IDisposable>(); | |||
_groupEntityComponentsDB = | |||
new FasterDictionary<ExclusiveGroupStruct, FasterDictionary<RefWrapperType, ITypeSafeDictionary>>(); | |||
_groupsPerEntity = | |||
new FasterDictionary<RefWrapperType, FasterDictionary<ExclusiveGroupStruct, ITypeSafeDictionary>>(); | |||
_groupedEntityToAdd = new DoubleBufferedEntitiesToAdd(); | |||
_entityStreams = EntitiesStreams.Create(); | |||
_entityStreams = EntitiesStreams.Create(); | |||
_groupFilters = | |||
new FasterDictionary<RefWrapperType, FasterDictionary<ExclusiveGroupStruct, GroupFilters>>(); | |||
new FasterDictionary<RefWrapperType, FasterDictionary<ExclusiveGroupStruct, LegacyGroupFilters>>(); | |||
_entityLocator.InitEntityReferenceMap(); | |||
_entitiesDB = new EntitiesDB(this,_entityLocator); | |||
_entitiesDB = new EntitiesDB(this, _entityLocator); | |||
InitFilters(); | |||
scheduler = entitiesComponentScheduler; | |||
scheduler.onTick = new EntitiesSubmitter(this); | |||
@@ -62,11 +91,10 @@ namespace Svelto.ECS | |||
#endif | |||
} | |||
public EnginesRoot | |||
(EntitiesSubmissionScheduler entitiesComponentScheduler, bool isDeserializationOnly) : | |||
this(entitiesComponentScheduler) | |||
protected EnginesRoot(EntitiesSubmissionScheduler entitiesComponentScheduler, | |||
EnginesReadyOption enginesWaitForReady) : this(entitiesComponentScheduler) | |||
{ | |||
_isDeserializationOnly = isDeserializationOnly; | |||
_enginesWaitForReady = enginesWaitForReady; | |||
} | |||
public EntitiesSubmissionScheduler scheduler { get; } | |||
@@ -78,7 +106,117 @@ namespace Svelto.ECS | |||
/// </summary> | |||
public void Dispose() | |||
{ | |||
_isDisposing = true; | |||
Dispose(true); | |||
GC.SuppressFinalize(this); | |||
} | |||
public void AddEngine(IEngine engine) | |||
{ | |||
var type = engine.GetType(); | |||
var refWrapper = new RefWrapperType(type); | |||
Check.Require(engine != null, "Engine to add is invalid or null"); | |||
Check.Require( | |||
_enginesTypeSet.Contains(refWrapper) == false || | |||
type.ContainsCustomAttribute(typeof(AllowMultipleAttribute)), | |||
"The same engine has been added more than once, if intentional, use [AllowMultiple] class attribute " | |||
.FastConcat(engine.ToString())); | |||
try | |||
{ | |||
if (engine is IReactOnAdd viewEngineAdd) | |||
CheckReactEngineComponents(typeof(IReactOnAdd<>), viewEngineAdd, _reactiveEnginesAdd, type.Name); | |||
if (engine is IReactOnAddEx viewEngineAddEx) | |||
CheckReactEngineComponents(typeof(IReactOnAddEx<>), viewEngineAddEx, _reactiveEnginesAddEx, type.Name); | |||
if (engine is IReactOnRemove viewEngineRemove) | |||
CheckReactEngineComponents(typeof(IReactOnRemove<>), viewEngineRemove, _reactiveEnginesRemove, type.Name); | |||
if (engine is IReactOnRemoveEx viewEngineRemoveEx) | |||
CheckReactEngineComponents(typeof(IReactOnRemoveEx<>), viewEngineRemoveEx, _reactiveEnginesRemoveEx, type.Name); | |||
if (engine is IReactOnDispose viewEngineDispose) | |||
CheckReactEngineComponents(typeof(IReactOnDispose<>), viewEngineDispose, _reactiveEnginesDispose, type.Name); | |||
if (engine is IReactOnSwap viewEngineSwap) | |||
CheckReactEngineComponents(typeof(IReactOnSwap<>), viewEngineSwap, _reactiveEnginesSwap, type.Name); | |||
if (engine is IReactOnSwapEx viewEngineSwapEx) | |||
CheckReactEngineComponents(typeof(IReactOnSwapEx<>), viewEngineSwapEx, _reactiveEnginesSwapEx, type.Name); | |||
if (engine is IReactOnSubmission submissionEngine) | |||
_reactiveEnginesSubmission.Add(submissionEngine); | |||
_enginesTypeSet.Add(refWrapper); | |||
_enginesSet.Add(engine); | |||
if (engine is IDisposable) | |||
_disposableEngines.Add(engine as IDisposable); | |||
if (engine is IQueryingEntitiesEngine queryableEntityComponentEngine) | |||
queryableEntityComponentEngine.entitiesDB = _entitiesDB; | |||
if (_enginesWaitForReady == EnginesReadyOption.ReadyAsAdded && engine is IGetReadyEngine getReadyEngine) | |||
getReadyEngine.Ready(); | |||
} | |||
catch (Exception e) | |||
{ | |||
throw new ECSException("Code crashed while adding engine ".FastConcat(engine.GetType().ToString(), " "), | |||
e); | |||
} | |||
} | |||
public void Ready() | |||
{ | |||
Check.Require(_enginesWaitForReady == EnginesReadyOption.WaitForReady, | |||
"The engine has not been initialise to wait for an external ready trigger"); | |||
foreach (var engine in _enginesSet) | |||
if (engine is IGetReadyEngine getReadyEngine) | |||
getReadyEngine.Ready(); | |||
} | |||
static void AddEngineToList<T>(T engine, Type[] entityComponentTypes, | |||
FasterDictionary<RefWrapperType, FasterList<ReactEngineContainer<T>>> engines, string typeName) | |||
where T : class, IReactEngine | |||
{ | |||
for (var i = 0; i < entityComponentTypes.Length; i++) | |||
{ | |||
var type = entityComponentTypes[i]; | |||
if (engines.TryGetValue(new RefWrapperType(type), out var list) == false) | |||
{ | |||
list = new FasterList<ReactEngineContainer<T>>(); | |||
engines.Add(new RefWrapperType(type), list); | |||
} | |||
list.Add(new ReactEngineContainer<T>(engine, typeName)); | |||
} | |||
} | |||
void CheckReactEngineComponents<T>(Type genericDefinition, T engine, | |||
FasterDictionary<RefWrapperType, FasterList<ReactEngineContainer<T>>> engines, string typeName) | |||
where T : class, IReactEngine | |||
{ | |||
var interfaces = engine.GetType().GetInterfaces(); | |||
foreach (var interf in interfaces) | |||
{ | |||
if (interf.IsGenericTypeEx() && interf.GetGenericTypeDefinition() == genericDefinition) | |||
{ | |||
var genericArguments = interf.GetGenericArgumentsEx(); | |||
AddEngineToList(engine, genericArguments, engines, typeName); | |||
} | |||
} | |||
} | |||
void Dispose(bool disposing) | |||
{ | |||
_isDisposing = disposing; | |||
if (disposing == false) | |||
return; | |||
using (var profiler = new PlatformProfiler("Final Dispose")) | |||
{ | |||
@@ -99,11 +237,13 @@ namespace Svelto.ECS | |||
} | |||
foreach (var groups in _groupEntityComponentsDB) | |||
foreach (var entityList in groups.Value) | |||
foreach (var entityList in groups.value) | |||
try | |||
{ | |||
entityList.Value.ExecuteEnginesRemoveCallbacks(_reactiveEnginesAddRemoveOnDispose, profiler | |||
, groups.Key); | |||
ITypeSafeDictionary typeSafeDictionary = entityList.value; | |||
typeSafeDictionary.ExecuteEnginesDisposeCallbacks_Group(_reactiveEnginesDispose, groups.key, | |||
profiler); | |||
} | |||
catch (Exception e) | |||
{ | |||
@@ -111,15 +251,17 @@ namespace Svelto.ECS | |||
} | |||
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(); | |||
DisposeFilters(); | |||
#if UNITY_NATIVE | |||
_nativeAddOperationQueue.Dispose(); | |||
_nativeRemoveOperationQueue.Dispose(); | |||
@@ -132,207 +274,128 @@ namespace Svelto.ECS | |||
_enginesSet.Clear(); | |||
_enginesTypeSet.Clear(); | |||
_reactiveEnginesSwap.Clear(); | |||
_reactiveEnginesAddRemove.Clear(); | |||
_reactiveEnginesAddRemoveOnDispose.Clear(); | |||
_reactiveEnginesAdd.Clear(); | |||
_reactiveEnginesRemove.Clear(); | |||
_reactiveEnginesDispose.Clear(); | |||
_reactiveEnginesSubmission.Clear(); | |||
_entitiesOperations.Clear(); | |||
_transientEntitiesOperations.Clear(); | |||
_groupedEntityToAdd.Dispose(); | |||
_entityLocator.DisposeEntityReferenceMap(); | |||
_entityStreams.Dispose(); | |||
scheduler.Dispose(); | |||
} | |||
} | |||
GC.SuppressFinalize(this); | |||
void NotifyReactiveEnginesOnSubmission() | |||
{ | |||
var enginesCount = _reactiveEnginesSubmission.count; | |||
for (var i = 0; i < enginesCount; i++) | |||
_reactiveEnginesSubmission[i].EntitiesSubmitted(); | |||
} | |||
public void AddEngine(IEngine engine) | |||
public readonly struct EntitiesSubmitter | |||
{ | |||
var type = engine.GetType(); | |||
var refWrapper = new RefWrapperType(type); | |||
DBC.ECS.Check.Require(engine != null, "Engine to add is invalid or null"); | |||
DBC.ECS.Check.Require( | |||
_enginesTypeSet.Contains(refWrapper) == false | |||
|| type.ContainsCustomAttribute(typeof(AllowMultipleAttribute)) == true | |||
, "The same engine has been added more than once, if intentional, use [AllowMultiple] class attribute " | |||
.FastConcat(engine.ToString())); | |||
try | |||
public EntitiesSubmitter(EnginesRoot enginesRoot) : this() | |||
{ | |||
if (engine is IReactOnAddAndRemove viewEngine) | |||
CheckReactEngineComponents(viewEngine, _reactiveEnginesAddRemove, type.Name); | |||
_enginesRoot = new Svelto.DataStructures.WeakReference<EnginesRoot>(enginesRoot); | |||
} | |||
if (engine is IReactOnDispose viewEngineDispose) | |||
CheckReactEngineComponents(viewEngineDispose, _reactiveEnginesAddRemoveOnDispose, type.Name); | |||
internal void SubmitEntities() | |||
{ | |||
Check.Require(_enginesRoot.IsValid, "ticking an GCed engines root?"); | |||
if (engine is IReactOnSwap viewEngineSwap) | |||
CheckReactEngineComponents(viewEngineSwap, _reactiveEnginesSwap, type.Name); | |||
var enginesRootTarget = _enginesRoot.Target; | |||
var entitiesSubmissionScheduler = enginesRootTarget.scheduler; | |||
if (engine is IReactOnSubmission submissionEngine) | |||
_reactiveEnginesSubmission.Add(submissionEngine); | |||
if (entitiesSubmissionScheduler.paused == false) | |||
{ | |||
Check.Require(entitiesSubmissionScheduler.isRunning == false, | |||
"A submission started while the previous one was still flushing"); | |||
entitiesSubmissionScheduler.isRunning = true; | |||
_enginesTypeSet.Add(refWrapper); | |||
_enginesSet.Add(engine); | |||
using (var profiler = new PlatformProfiler("Svelto.ECS - Entities Submission")) | |||
{ | |||
var iterations = 0; | |||
var hasEverSubmitted = false; | |||
if (engine is IDisposable) | |||
_disposableEngines.Add(engine as IDisposable); | |||
// We need to clear transient filters before processing callbacks since the callbacks may add | |||
// new entities to these filters. | |||
enginesRootTarget.ClearTransientFilters(); | |||
if (engine is IQueryingEntitiesEngine queryableEntityComponentEngine) | |||
{ | |||
queryableEntityComponentEngine.entitiesDB = _entitiesDB; | |||
queryableEntityComponentEngine.Ready(); | |||
#if UNITY_NATIVE | |||
enginesRootTarget.FlushNativeOperations(profiler); | |||
#endif | |||
//todo: proper unit test structural changes made as result of add/remove callbacks | |||
while (enginesRootTarget.HasMadeNewStructuralChangesInThisIteration() | |||
&& iterations++ < MAX_SUBMISSION_ITERATIONS) | |||
{ | |||
hasEverSubmitted = true; | |||
_enginesRoot.Target.SingleSubmission(profiler); | |||
#if UNITY_NATIVE | |||
if (enginesRootTarget.HasMadeNewStructuralChangesInThisIteration()) | |||
enginesRootTarget.FlushNativeOperations(profiler); | |||
#endif | |||
} | |||
#if DEBUG && !PROFILE_SVELTO | |||
if (iterations == MAX_SUBMISSION_ITERATIONS) | |||
throw new ECSException("possible circular submission detected"); | |||
#endif | |||
if (hasEverSubmitted) | |||
enginesRootTarget.NotifyReactiveEnginesOnSubmission(); | |||
} | |||
entitiesSubmissionScheduler.isRunning = false; | |||
++entitiesSubmissionScheduler.iteration; | |||
} | |||
} | |||
catch (Exception e) | |||
{ | |||
throw new ECSException("Code crashed while adding engine ".FastConcat(engine.GetType().ToString(), " ") | |||
, e); | |||
} | |||
} | |||
void NotifyReactiveEnginesOnSubmission() | |||
{ | |||
var enginesCount = _reactiveEnginesSubmission.count; | |||
for (var i = 0; i < enginesCount; i++) | |||
_reactiveEnginesSubmission[i].EntitiesSubmitted(); | |||
readonly Svelto.DataStructures.WeakReference<EnginesRoot> _enginesRoot; | |||
} | |||
~EnginesRoot() | |||
{ | |||
Console.LogWarning("Engines Root has been garbage collected, don't forget to call Dispose()!"); | |||
Dispose(); | |||
Dispose(false); | |||
} | |||
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, typeName); | |||
} | |||
} | |||
static void AddEngineToList<T> | |||
(T engine, Type[] entityComponentTypes | |||
, FasterDictionary<RefWrapperType, FasterList<ReactEngineContainer>> engines, string typeName) | |||
where T : class, IReactEngine | |||
{ | |||
for (var i = 0; i < entityComponentTypes.Length; i++) | |||
{ | |||
var type = entityComponentTypes[i]; | |||
if (engines.TryGetValue(new RefWrapperType(type), out var list) == false) | |||
{ | |||
list = new FasterList<ReactEngineContainer>(); | |||
engines.Add(new RefWrapperType(type), list); | |||
} | |||
list.Add(new ReactEngineContainer(engine, typeName)); | |||
} | |||
} | |||
const int MAX_SUBMISSION_ITERATIONS = 10; | |||
internal bool _isDisposing; | |||
readonly FasterList<IDisposable> _disposableEngines; | |||
readonly FasterList<IEngine> _enginesSet; | |||
readonly HashSet<Type> _enginesTypeSet; | |||
readonly EnginesReadyOption _enginesWaitForReady; | |||
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?"); | |||
readonly FasterDictionary<RefWrapperType, FasterList<ReactEngineContainer<IReactOnAdd>>> _reactiveEnginesAdd; | |||
var enginesRootTarget = _enginesRoot.Target; | |||
var entitiesSubmissionScheduler = enginesRootTarget.scheduler; | |||
readonly FasterDictionary<RefWrapperType, FasterList<ReactEngineContainer<IReactOnAddEx>>> | |||
_reactiveEnginesAddEx; | |||
if (entitiesSubmissionScheduler.paused == false) | |||
{ | |||
DBC.ECS.Check.Require(entitiesSubmissionScheduler.isRunning == false | |||
, "A submission started while the previous one was still flushing"); | |||
entitiesSubmissionScheduler.isRunning = true; | |||
readonly FasterDictionary<RefWrapperType, FasterList<ReactEngineContainer<IReactOnRemove>>> | |||
_reactiveEnginesRemove; | |||
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 | |||
} | |||
readonly FasterDictionary<RefWrapperType, FasterList<ReactEngineContainer<IReactOnRemoveEx>>> | |||
_reactiveEnginesRemoveEx; | |||
#if DEBUG && !PROFILE_SVELTO | |||
if (iterations == 5) | |||
throw new ECSException("possible circular submission detected"); | |||
#endif | |||
if (hasEverSubmitted) | |||
enginesRootTarget.NotifyReactiveEnginesOnSubmission(); | |||
} | |||
readonly FasterDictionary<RefWrapperType, FasterList<ReactEngineContainer<IReactOnSwap>>> _reactiveEnginesSwap; | |||
entitiesSubmissionScheduler.isRunning = false; | |||
++entitiesSubmissionScheduler.iteration; | |||
} | |||
readonly FasterDictionary<RefWrapperType, FasterList<ReactEngineContainer<IReactOnSwapEx>>> | |||
_reactiveEnginesSwapEx; | |||
yield return false; | |||
} | |||
} | |||
readonly FasterDictionary<RefWrapperType, FasterList<ReactEngineContainer<IReactOnDispose>>> | |||
_reactiveEnginesDispose; | |||
public uint maxNumberOfOperationsPerFrame | |||
{ | |||
set => _enginesRoot.Target._maxNumberOfOperationsPerFrame = value; | |||
} | |||
readonly Svelto.DataStructures.WeakReference<EnginesRoot> _enginesRoot; | |||
readonly FasterList<IReactOnSubmission> _reactiveEnginesSubmission; | |||
} | |||
internal readonly IEnumerator<bool> submitEntities; | |||
readonly IEnumerator<bool> _privateSubmitEntities; | |||
} | |||
public enum EnginesReadyOption | |||
{ | |||
ReadyAsAdded, | |||
WaitForReady | |||
} | |||
} |
@@ -1,7 +1,8 @@ | |||
using System; | |||
//#define PARANOID_CHECK | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Runtime.CompilerServices; | |||
using Svelto.Common; | |||
using Svelto.DataStructures; | |||
using Svelto.ECS.Internal; | |||
@@ -16,27 +17,34 @@ 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, string caller) | |||
{ | |||
CheckAddEntityID(entityID, descriptorType); | |||
CheckAddEntityID(entityID, descriptorType, caller); | |||
DBC.ECS.Check.Require(entityID.groupID.isInvalid == false | |||
, "invalid group detected, are you using new ExclusiveGroupStruct() instead of new ExclusiveGroup()?"); | |||
DBC.ECS.Check.Require(entityID.groupID.isInvalid == false, | |||
"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 | |||
, descriptorType | |||
#endif | |||
); | |||
); | |||
return new EntityInitializer(entityID, dic, reference); | |||
} | |||
@@ -44,17 +52,17 @@ namespace Svelto.ECS | |||
/// <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) | |||
void Preallocate(ExclusiveGroupStruct groupID, uint size, IComponentBuilder[] entityComponentsToBuild) | |||
{ | |||
void PreallocateEntitiesToAdd() | |||
{ | |||
_groupedEntityToAdd.Preallocate(groupID, numberOfEntities, entityComponentsToBuild); | |||
_groupedEntityToAdd.Preallocate(groupID, size, entityComponentsToBuild); | |||
} | |||
void PreallocateDBGroup() | |||
{ | |||
var numberOfEntityComponents = entityComponentsToBuild.Length; | |||
FasterDictionary<RefWrapperType, ITypeSafeDictionary> group = GetOrCreateDBGroup(groupID); | |||
FasterDictionary<RefWrapperType, ITypeSafeDictionary> group = GetOrAddDBGroup(groupID); | |||
for (var index = 0; index < numberOfEntityComponents; index++) | |||
{ | |||
@@ -62,8 +70,8 @@ namespace Svelto.ECS | |||
var entityComponentType = entityComponentBuilder.GetEntityComponentType(); | |||
var refWrapper = new RefWrapperType(entityComponentType); | |||
var dbList = group.GetOrCreate(refWrapper, ()=>entityComponentBuilder.CreateDictionary(numberOfEntities)); | |||
entityComponentBuilder.Preallocate(dbList, numberOfEntities); | |||
var dbList = group.GetOrAdd(refWrapper, () => entityComponentBuilder.CreateDictionary(size)); | |||
entityComponentBuilder.Preallocate(dbList, size); | |||
if (_groupsPerEntity.TryGetValue(refWrapper, out var groupedGroup) == false) | |||
groupedGroup = _groupsPerEntity[refWrapper] = | |||
@@ -75,245 +83,82 @@ namespace Svelto.ECS | |||
PreallocateDBGroup(); | |||
PreallocateEntitiesToAdd(); | |||
_entityLocator.PreallocateReferenceMaps(groupID, numberOfEntities); | |||
} | |||
///-------------------------------------------- | |||
/// | |||
void MoveEntityFromAndToEngines(IComponentBuilder[] componentBuilders, EGID fromEntityGID, EGID? toEntityGID) | |||
{ | |||
using (var sampler = new PlatformProfiler("Move Entity From Engines")) | |||
{ | |||
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) | |||
&& ((ITypeSafeDictionary<EntityInfoComponent>) entityInfoDic).TryGetValue( | |||
fromEntityGID.entityID, out var entityInfo)) | |||
SwapOrRemoveEntityComponents(fromEntityGID, toEntityGID, entityInfo.componentsToBuild, fromGroup | |||
, 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) | |||
{ | |||
using (sampler.Sample("MoveEntityComponents")) | |||
{ | |||
var length = entitiesToMove.Length; | |||
FasterDictionary<RefWrapperType, ITypeSafeDictionary> toGroup = null; | |||
//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.HasValue) | |||
{ | |||
var entityGid = toEntityGID.Value; | |||
_entityLocator.UpdateEntityReference(fromEntityGID, entityGid); | |||
var toGroupID = entityGid.groupID; | |||
toGroup = GetOrCreateDBGroup(toGroupID); | |||
//Add all the entities to the dictionary | |||
for (var i = 0; i < length; i++) | |||
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); | |||
//then remove all the entities from the dictionary | |||
for (var i = 0; i < length; i++) | |||
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) | |||
{ | |||
using (sampler.Sample("CopyEntityToDictionary")) | |||
{ | |||
var wrapper = new RefWrapperType(entityComponentType); | |||
ITypeSafeDictionary fromTypeSafeDictionary = | |||
GetTypeSafeDictionary(entityGID.groupID, fromGroup, wrapper); | |||
#if DEBUG && !PROFILE_SVELTO | |||
if (fromTypeSafeDictionary.Has(entityGID.entityID) == false) | |||
{ | |||
throw new EntityNotFoundException(entityGID, entityComponentType); | |||
} | |||
#endif | |||
ITypeSafeDictionary toEntitiesDictionary = | |||
GetOrCreateTypeSafeDictionary(toEntityGID.groupID, toGroup, wrapper, fromTypeSafeDictionary); | |||
fromTypeSafeDictionary.AddEntityToDictionary(entityGID, toEntityGID, toEntitiesDictionary); | |||
} | |||
} | |||
void ExecuteEnginesSwapOrRemoveCallbacks | |||
(EGID entityGID, EGID? toEntityGID, FasterDictionary<RefWrapperType, ITypeSafeDictionary> fromGroup | |||
, 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); | |||
ITypeSafeDictionary toEntitiesDictionary = null; | |||
if (toGroup != null) | |||
toEntitiesDictionary = toGroup[refWrapper]; //this is guaranteed to exist by AddEntityToDictionary | |||
#if DEBUG && !PROFILE_SVELTO | |||
if (fromTypeSafeDictionary.Has(entityGID.entityID) == false) | |||
throw new EntityNotFoundException(entityGID, entityComponentType); | |||
#endif | |||
fromTypeSafeDictionary.ExecuteEnginesSwapOrRemoveCallbacks(entityGID, toEntityGID, toEntitiesDictionary | |||
, toEntityGID == null | |||
? _reactiveEnginesAddRemove | |||
: _reactiveEnginesSwap, in profiler); | |||
} | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
void RemoveEntityFromDictionary | |||
(EGID entityGID, FasterDictionary<RefWrapperType, ITypeSafeDictionary> fromGroup, Type entityComponentType | |||
, in PlatformProfiler sampler) | |||
{ | |||
using (sampler.Sample("RemoveEntityFromDictionary")) | |||
{ | |||
var refWrapper = new RefWrapperType(entityComponentType); | |||
var fromTypeSafeDictionary = GetTypeSafeDictionary(entityGID.groupID, fromGroup, refWrapper); | |||
fromTypeSafeDictionary.RemoveEntityFromDictionary(entityGID); | |||
} | |||
} | |||
/// <summary> | |||
/// Swap all the entities from one group to another | |||
/// | |||
/// TODO: write unit test that also tests that this calls MoveTo callbacks and not Add or Remove. | |||
/// also that the passing EGID is the same of a component with EGID | |||
/// </summary> | |||
/// <param name="fromIdGroupId"></param> | |||
/// <param name="toGroupId"></param> | |||
/// <param name="profiler"></param> | |||
void SwapEntitiesBetweenGroups | |||
(ExclusiveGroupStruct fromIdGroupId, ExclusiveGroupStruct toGroupId, in PlatformProfiler profiler) | |||
{ | |||
using (profiler.Sample("SwapEntitiesBetweenGroups")) | |||
{ | |||
FasterDictionary<RefWrapperType, ITypeSafeDictionary> fromGroup = GetDBGroup(fromIdGroupId); | |||
FasterDictionary<RefWrapperType, ITypeSafeDictionary> toGroup = GetOrCreateDBGroup(toGroupId); | |||
_entityLocator.UpdateAllGroupReferenceLocators(fromIdGroupId, toGroupId); | |||
foreach (var dictionaryOfEntities in fromGroup) | |||
{ | |||
ITypeSafeDictionary toEntitiesDictionary = | |||
GetOrCreateTypeSafeDictionary(toGroupId, toGroup, dictionaryOfEntities.Key | |||
, dictionaryOfEntities.Value); | |||
var groupsOfEntityType = _groupsPerEntity[dictionaryOfEntities.Key]; | |||
var groupOfEntitiesToCopyAndClear = groupsOfEntityType[fromIdGroupId]; | |||
toEntitiesDictionary.AddEntitiesFromDictionary(groupOfEntitiesToCopyAndClear, toGroupId, this); | |||
//call all the MoveTo callbacks | |||
dictionaryOfEntities.Value.ExecuteEnginesAddOrSwapCallbacks(_reactiveEnginesSwap | |||
, dictionaryOfEntities.Value, fromIdGroupId, toGroupId, profiler); | |||
//todo: if it's unmanaged, I can use fastclear | |||
groupOfEntitiesToCopyAndClear.Clear(); | |||
} | |||
} | |||
_entityLocator.PreallocateReferenceMaps(groupID, size); | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
FasterDictionary<RefWrapperType, ITypeSafeDictionary> GetDBGroup(ExclusiveGroupStruct fromIdGroupId) | |||
{ | |||
if (_groupEntityComponentsDB.TryGetValue(fromIdGroupId | |||
, out FasterDictionary<RefWrapperType, ITypeSafeDictionary> | |||
fromGroup) == false) | |||
if (_groupEntityComponentsDB.TryGetValue(fromIdGroupId, | |||
out FasterDictionary<RefWrapperType, ITypeSafeDictionary> fromGroup) == false) | |||
throw new ECSException("Group doesn't exist: ".FastConcat(fromIdGroupId.ToName())); | |||
return fromGroup; | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
FasterDictionary<RefWrapperType, ITypeSafeDictionary> GetOrCreateDBGroup | |||
(ExclusiveGroupStruct toGroupId) | |||
FasterDictionary<RefWrapperType, ITypeSafeDictionary> GetOrAddDBGroup(ExclusiveGroupStruct toGroupId) | |||
{ | |||
return _groupEntityComponentsDB.GetOrCreate( | |||
toGroupId, () => new FasterDictionary<RefWrapperType, ITypeSafeDictionary>()); | |||
return _groupEntityComponentsDB.GetOrAdd(toGroupId, | |||
() => new FasterDictionary<RefWrapperType, ITypeSafeDictionary>()); | |||
} | |||
ITypeSafeDictionary GetOrCreateTypeSafeDictionary | |||
(ExclusiveGroupStruct groupId, FasterDictionary<RefWrapperType, ITypeSafeDictionary> toGroup | |||
, RefWrapperType type, ITypeSafeDictionary fromTypeSafeDictionary) | |||
IComponentBuilder[] FindRealComponents<T>(EGID fromEntityGID) where T : IEntityDescriptor, new() | |||
{ | |||
//be sure that the TypeSafeDictionary for the entity Type exists | |||
if (toGroup.TryGetValue(type, out ITypeSafeDictionary toEntitiesDictionary) == false) | |||
{ | |||
toEntitiesDictionary = fromTypeSafeDictionary.Create(); | |||
toGroup.Add(type, toEntitiesDictionary); | |||
} | |||
var fromGroup = GetDBGroup(fromEntityGID.groupID); | |||
//update GroupsPerEntity | |||
if (_groupsPerEntity.TryGetValue(type, out var groupedGroup) == false) | |||
groupedGroup = _groupsPerEntity[type] = | |||
new FasterDictionary<ExclusiveGroupStruct, ITypeSafeDictionary>(); | |||
if (fromGroup.TryGetValue(new RefWrapperType(ComponentBuilderUtilities.ENTITY_INFO_COMPONENT), | |||
out var entityInfoDic) //<entity ID, EntityInfoComponent> | |||
&& ((ITypeSafeDictionary<EntityInfoComponent>)entityInfoDic).TryGetValue(fromEntityGID.entityID, | |||
out var entityInfo)) //there could be multiple entity descriptors registered in the same group, so it's necessary to check if the entity registered in the group has entityInfoComponent | |||
{ | |||
#if PARANOID_CHECK | |||
var hash = new HashSet<IComponentBuilder>(entityInfo.componentsToBuild, | |||
default(ComponentBuilderComparer)); | |||
groupedGroup[groupId] = toEntitiesDictionary; | |||
return toEntitiesDictionary; | |||
} | |||
foreach (var component in EntityDescriptorTemplate<T>.descriptor.componentsToBuild) | |||
{ | |||
if (hash.Contains(component) == false) | |||
throw new Exception( | |||
$"entityInfo.componentsToBuild must contain all the base components {fromEntityGID}," + | |||
$" missing component {component}"); | |||
static ITypeSafeDictionary GetTypeSafeDictionary | |||
(ExclusiveGroupStruct groupID, FasterDictionary<RefWrapperType, ITypeSafeDictionary> @group, RefWrapperType refWrapper) | |||
{ | |||
if (@group.TryGetValue(refWrapper, out ITypeSafeDictionary fromTypeSafeDictionary) == false) | |||
{ | |||
throw new ECSException("no group found: ".FastConcat(groupID.ToName())); | |||
hash.Remove(component); | |||
} | |||
#endif | |||
return entityInfo.componentsToBuild; | |||
} | |||
return fromTypeSafeDictionary; | |||
return EntityDescriptorTemplate<T>.descriptor.componentsToBuild; | |||
} | |||
void RemoveEntitiesFromGroup(ExclusiveGroupStruct groupID, in PlatformProfiler profiler) | |||
IComponentBuilder[] FindRealComponents(EGID fromEntityGID, IComponentBuilder[] baseComponents) | |||
{ | |||
_entityLocator.RemoveAllGroupReferenceLocators(groupID); | |||
var fromGroup = GetDBGroup(fromEntityGID.groupID); | |||
if (_groupEntityComponentsDB.TryGetValue(groupID, out var dictionariesOfEntities)) | |||
if (fromGroup.TryGetValue(new RefWrapperType(ComponentBuilderUtilities.ENTITY_INFO_COMPONENT), | |||
out var entityInfoDic) //<entity ID, EntityInfoComponent> | |||
&& ((ITypeSafeDictionary<EntityInfoComponent>)entityInfoDic).TryGetValue(fromEntityGID.entityID, | |||
out var entityInfo)) //there could be multiple entity descriptors registered in the same group, so it's necessary to check if the entity registered in the group has entityInfoComponent | |||
{ | |||
foreach (var dictionaryOfEntities in dictionariesOfEntities) | |||
#if PARANOID_CHECK | |||
var hash = new HashSet<IComponentBuilder>(entityInfo.componentsToBuild, | |||
default(ComponentBuilderComparer)); | |||
foreach (var component in baseComponents) | |||
{ | |||
dictionaryOfEntities.Value.ExecuteEnginesRemoveCallbacks(_reactiveEnginesAddRemove, profiler | |||
, groupID); | |||
dictionaryOfEntities.Value.FastClear(); | |||
if (hash.Contains(component) == false) | |||
throw new Exception( | |||
$"entityInfo.componentsToBuild must contain all the base components {fromEntityGID}," + | |||
$" missing component {component}"); | |||
var groupsOfEntityType = _groupsPerEntity[dictionaryOfEntities.Key]; | |||
groupsOfEntityType[groupID].FastClear(); | |||
hash.Remove(component); | |||
} | |||
#endif | |||
return entityInfo.componentsToBuild; | |||
} | |||
return baseComponents; | |||
} | |||
//one datastructure rule them all: | |||
@@ -322,7 +167,7 @@ namespace Svelto.ECS | |||
//to the FasterDictionary capabilities OR it's possible to get a specific entityComponent indexed by | |||
//ID. This ID doesn't need to be the EGID, it can be just the entityID | |||
//for each group id, save a dictionary indexed by entity type of entities indexed by id | |||
// group EntityComponentType entityID, EntityComponent | |||
// group EntityComponentType entityID, EntityComponent | |||
internal readonly FasterDictionary<ExclusiveGroupStruct, FasterDictionary<RefWrapperType, ITypeSafeDictionary>> | |||
_groupEntityComponentsDB; | |||
@@ -334,7 +179,7 @@ namespace Svelto.ECS | |||
_groupsPerEntity; | |||
//The filters stored for each component and group | |||
internal readonly FasterDictionary<RefWrapperType, FasterDictionary<ExclusiveGroupStruct, GroupFilters>> | |||
internal readonly FasterDictionary<RefWrapperType, FasterDictionary<ExclusiveGroupStruct, LegacyGroupFilters>> | |||
_groupFilters; | |||
readonly EntitiesDB _entitiesDB; | |||
@@ -1,8 +1,9 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Runtime.CompilerServices; | |||
using Svelto.Common; | |||
namespace Svelto.ECS | |||
namespace Svelto.ECS | |||
{ | |||
public partial class EnginesRoot | |||
{ | |||
@@ -14,52 +15,61 @@ using Svelto.Common; | |||
} | |||
public EntityInitializer BuildEntity<T> | |||
(uint entityID, ExclusiveBuildGroup groupStructId, IEnumerable<object> implementors = null) | |||
where T : IEntityDescriptor, new() | |||
(uint entityID, ExclusiveBuildGroup groupStructId, IEnumerable<object> implementors = null | |||
, [CallerMemberName] string caller = null) where T : IEntityDescriptor, new() | |||
{ | |||
return _enginesRoot.Target.BuildEntity(new EGID(entityID, groupStructId) | |||
, EntityDescriptorTemplate<T>.descriptor.componentsToBuild | |||
, TypeCache<T>.type, implementors); | |||
, TypeCache<T>.type, implementors, caller); | |||
} | |||
public EntityInitializer BuildEntity<T>(EGID egid, IEnumerable<object> implementors = null) | |||
where T : IEntityDescriptor, new() | |||
public EntityInitializer BuildEntity<T> | |||
(EGID egid, IEnumerable<object> implementors = null | |||
, [CallerMemberName] string caller = null) where T : IEntityDescriptor, new() | |||
{ | |||
return _enginesRoot.Target.BuildEntity( | |||
egid, EntityDescriptorTemplate<T>.descriptor.componentsToBuild, TypeCache<T>.type, implementors); | |||
return _enginesRoot.Target.BuildEntity(egid, EntityDescriptorTemplate<T>.descriptor.componentsToBuild | |||
, TypeCache<T>.type, implementors, caller); | |||
} | |||
public EntityInitializer BuildEntity<T> | |||
(EGID egid, T entityDescriptor, IEnumerable<object> implementors) where T : IEntityDescriptor | |||
(EGID egid, T entityDescriptor, IEnumerable<object> implementors | |||
, [CallerMemberName] string caller = null) where T : IEntityDescriptor | |||
{ | |||
return _enginesRoot.Target.BuildEntity(egid, entityDescriptor.componentsToBuild, TypeCache<T>.type, implementors); | |||
return _enginesRoot.Target.BuildEntity(egid, entityDescriptor.componentsToBuild, TypeCache<T>.type | |||
, implementors, caller); | |||
} | |||
public EntityInitializer BuildEntity<T> | |||
(uint entityID, ExclusiveBuildGroup groupStructId, T descriptorEntity, IEnumerable<object> implementors) | |||
where T : IEntityDescriptor | |||
(uint entityID, ExclusiveBuildGroup groupStructId, T descriptorEntity, IEnumerable<object> implementors | |||
, [CallerMemberName] string caller = null) where T : IEntityDescriptor | |||
{ | |||
return _enginesRoot.Target.BuildEntity(new EGID(entityID, groupStructId) | |||
, descriptorEntity.componentsToBuild, TypeCache<T>.type, implementors); | |||
, descriptorEntity.componentsToBuild, TypeCache<T>.type | |||
, implementors, caller); | |||
} | |||
public void PreallocateEntitySpace<T>(ExclusiveGroupStruct groupStructId, uint numberOfEntities) | |||
where T : IEntityDescriptor, new() | |||
{ | |||
_enginesRoot.Target.Preallocate(groupStructId, numberOfEntities, EntityDescriptorTemplate<T>.descriptor.componentsToBuild); | |||
_enginesRoot.Target.Preallocate(groupStructId, numberOfEntities | |||
, EntityDescriptorTemplate<T>.descriptor.componentsToBuild); | |||
} | |||
public EntityInitializer BuildEntity(EGID egid, IComponentBuilder[] componentsToBuild, Type type, IEnumerable<object> implementors = null) | |||
public EntityInitializer BuildEntity | |||
(EGID egid, IComponentBuilder[] componentsToBuild, Type type, IEnumerable<object> implementors = null | |||
, [CallerMemberName] string caller = null) | |||
{ | |||
return _enginesRoot.Target.BuildEntity(egid, componentsToBuild, type, implementors); | |||
return _enginesRoot.Target.BuildEntity(egid, componentsToBuild, type, implementors, caller); | |||
} | |||
#if UNITY_NATIVE | |||
public Svelto.ECS.Native.NativeEntityFactory ToNative<T>(string callerName) where T : IEntityDescriptor, new() | |||
public Native.NativeEntityFactory ToNative<T> | |||
([CallerMemberName] string caller = null) | |||
where T : IEntityDescriptor, new() | |||
{ | |||
return _enginesRoot.Target.ProvideNativeEntityFactoryQueue<T>(callerName); | |||
return _enginesRoot.Target.ProvideNativeEntityFactoryQueue<T>(caller); | |||
} | |||
#endif | |||
#endif | |||
//enginesRoot is a weakreference because GenericEntityStreamConsumerFactory can be injected inside | |||
//engines of other enginesRoot | |||
@@ -7,193 +7,146 @@ namespace Svelto.ECS | |||
{ | |||
public partial class EnginesRoot | |||
{ | |||
/// <summary> | |||
/// todo: EnginesRoot was a weakreference to give the change to inject | |||
/// entity functions from other engines root. It probably should be reverted | |||
/// </summary> | |||
class GenericEntityFunctions : IEntityFunctions | |||
{ | |||
internal GenericEntityFunctions(EnginesRoot weakReference) | |||
{ | |||
_enginesRoot = new Svelto.DataStructures.WeakReference<EnginesRoot>(weakReference); | |||
_enginesRoot = new WeakReference<EnginesRoot>(weakReference); | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public void RemoveEntity<T>(uint entityID, ExclusiveBuildGroup groupID) where T : | |||
IEntityDescriptor, new() | |||
public void RemoveEntity<T> | |||
(uint entityID, ExclusiveBuildGroup groupID, [CallerMemberName] string caller = null) | |||
where T : IEntityDescriptor, new() | |||
{ | |||
RemoveEntity<T>(new EGID(entityID, groupID)); | |||
RemoveEntity<T>(new EGID(entityID, groupID), caller); | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public void RemoveEntity<T>(EGID entityEGID) where T : IEntityDescriptor, new() | |||
public void RemoveEntity<T>(EGID entityEGID, [CallerMemberName] string caller = null) | |||
where T : IEntityDescriptor, new() | |||
{ | |||
DBC.ECS.Check.Require(entityEGID.groupID.isInvalid == false, "invalid group detected"); | |||
var descriptorComponentsToBuild = EntityDescriptorTemplate<T>.descriptor.componentsToBuild; | |||
_enginesRoot.Target.CheckRemoveEntityID(entityEGID, TypeCache<T>.type); | |||
_enginesRoot.Target.CheckRemoveEntityID(entityEGID, TypeCache<T>.type, caller); | |||
_enginesRoot.Target.QueueEntitySubmitOperation<T>( | |||
new EntitySubmitOperation(EntitySubmitOperationType.Remove, entityEGID, entityEGID, | |||
descriptorComponentsToBuild)); | |||
_enginesRoot.Target.QueueRemoveEntityOperation( | |||
entityEGID, _enginesRoot.Target.FindRealComponents<T>(entityEGID), caller); | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public void RemoveEntitiesFromGroup(ExclusiveBuildGroup groupID) | |||
public void RemoveEntitiesFromGroup(ExclusiveBuildGroup groupID, [CallerMemberName] string caller = null) | |||
{ | |||
DBC.ECS.Check.Require(groupID.isInvalid == false, "invalid group detected"); | |||
_enginesRoot.Target.RemoveGroupID(groupID); | |||
_enginesRoot.Target.QueueEntitySubmitOperation( | |||
new EntitySubmitOperation(EntitySubmitOperationType.RemoveGroup, new EGID(0, groupID), new EGID())); | |||
_enginesRoot.Target.QueueRemoveGroupOperation(groupID, caller); | |||
} | |||
// [MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
// void RemoveAllEntities<D, S>(ExclusiveGroup group) | |||
// where D : IEntityDescriptor, new() where S : unmanaged, IEntityComponent | |||
// { | |||
// var targetEntitiesDB = _enginesRoot.Target._entitiesDB; | |||
// var (buffer, count) = targetEntitiesDB.QueryEntities<S>(@group); | |||
// for (uint i = 0; i < count; ++i) | |||
// { | |||
// RemoveEntity<D>(new EGID(i, group)); | |||
// } | |||
// } | |||
// | |||
// [MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
// void RemoveAllEntities<D, S>() | |||
// where D : IEntityDescriptor, new() where S : unmanaged, IEntityComponent | |||
// { | |||
// var targetEntitiesDB = _enginesRoot.Target._entitiesDB; | |||
// foreach (var ((buffer, count), exclusiveGroupStruct) in targetEntitiesDB.QueryEntities<S>()) | |||
// for (uint i = 0; i < count; ++i) | |||
// { | |||
// RemoveEntity<D>(new EGID(i, exclusiveGroupStruct)); | |||
// } | |||
// } | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public void SwapEntitiesInGroup<T>(ExclusiveBuildGroup fromGroupID, ExclusiveBuildGroup toGroupID) | |||
where T : IEntityDescriptor, new() | |||
public void SwapEntitiesInGroup | |||
(ExclusiveBuildGroup fromGroupID, ExclusiveBuildGroup toGroupID, [CallerMemberName] string caller = null) | |||
{ | |||
if (_enginesRoot.Target._groupEntityComponentsDB.TryGetValue( | |||
fromGroupID.group, out FasterDictionary<RefWrapperType, ITypeSafeDictionary> entitiesInGroupPerType) | |||
== true) | |||
fromGroupID.group | |||
, out FasterDictionary<RefWrapperType, ITypeSafeDictionary> entitiesInGroupPerType) == true) | |||
{ | |||
#if DEBUG && !PROFILE_SVELTO | |||
IComponentBuilder[] components = EntityDescriptorTemplate<T>.descriptor.componentsToBuild; | |||
var dictionary = entitiesInGroupPerType[new RefWrapperType(components[0].GetEntityComponentType())]; | |||
ITypeSafeDictionary dictionary = entitiesInGroupPerType.unsafeValues[0]; | |||
dictionary.KeysEvaluator((key) => | |||
{ | |||
_enginesRoot.Target.CheckRemoveEntityID(new EGID(key, fromGroupID), TypeCache<T>.type); | |||
_enginesRoot.Target.CheckAddEntityID(new EGID(key, toGroupID), TypeCache<T>.type); | |||
_enginesRoot.Target.CheckRemoveEntityID(new EGID(key, fromGroupID), null, caller); | |||
_enginesRoot.Target.CheckAddEntityID(new EGID(key, toGroupID), null, caller); | |||
}); | |||
#endif | |||
_enginesRoot.Target.QueueEntitySubmitOperation( | |||
new EntitySubmitOperation(EntitySubmitOperationType.SwapGroup, new EGID(0, fromGroupID) | |||
, new EGID(0, toGroupID))); | |||
_enginesRoot.Target.QueueSwapGroupOperation(fromGroupID, toGroupID, caller); | |||
} | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public void SwapEntityGroup<T>(uint entityID, ExclusiveBuildGroup fromGroupID, | |||
ExclusiveBuildGroup toGroupID) | |||
where T : IEntityDescriptor, new() | |||
public void SwapEntityGroup<T> | |||
(uint entityID, ExclusiveBuildGroup fromGroupID, ExclusiveBuildGroup toGroupID | |||
, [CallerMemberName] string caller = null) where T : IEntityDescriptor, new() | |||
{ | |||
SwapEntityGroup<T>(new EGID(entityID, fromGroupID), toGroupID); | |||
SwapEntityGroup<T>(new EGID(entityID, fromGroupID), toGroupID, caller); | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public void SwapEntityGroup<T>(EGID fromID, ExclusiveBuildGroup toGroupID) | |||
public void SwapEntityGroup<T> | |||
(EGID fromEGID, ExclusiveBuildGroup toGroupID, [CallerMemberName] string caller = null) | |||
where T : IEntityDescriptor, new() | |||
{ | |||
SwapEntityGroup<T>(fromID, new EGID(fromID.entityID, toGroupID)); | |||
SwapEntityGroup<T>(fromEGID, new EGID(fromEGID.entityID, toGroupID), caller); | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public void SwapEntityGroup<T>(EGID fromID, ExclusiveBuildGroup mustBeFromGroup, ExclusiveBuildGroup toGroupID) | |||
public void SwapEntityGroup<T> | |||
(EGID fromEGID, EGID toEGID, ExclusiveBuildGroup mustBeFromGroup, [CallerMemberName] string caller = null) | |||
where T : IEntityDescriptor, new() | |||
{ | |||
if (fromID.groupID != mustBeFromGroup) | |||
throw new ECSException($"Entity is not coming from the expected group. Expected {mustBeFromGroup} is {fromID.groupID}"); | |||
if (fromEGID.groupID != mustBeFromGroup) | |||
throw new ECSException( | |||
$"Entity is not coming from the expected group Expected {mustBeFromGroup} is {fromEGID.groupID}"); | |||
SwapEntityGroup<T>(fromID, toGroupID); | |||
SwapEntityGroup<T>(fromEGID, toEGID, caller); | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public void SwapEntityGroup<T>(EGID fromID, EGID toID, ExclusiveBuildGroup mustBeFromGroup) | |||
public void SwapEntityGroup<T>(EGID fromEGID, EGID toEGID, [CallerMemberName] string caller = null) | |||
where T : IEntityDescriptor, new() | |||
{ | |||
if (fromID.groupID != mustBeFromGroup) | |||
throw new ECSException($"Entity is not coming from the expected group Expected {mustBeFromGroup} is {fromID.groupID}"); | |||
DBC.ECS.Check.Require(fromEGID.groupID.isInvalid == false, "invalid group detected"); | |||
DBC.ECS.Check.Require(toEGID.groupID.isInvalid == false, "invalid group detected"); | |||
var enginesRootTarget = _enginesRoot.Target; | |||
SwapEntityGroup<T>(fromID, toID); | |||
enginesRootTarget.CheckRemoveEntityID(fromEGID, TypeCache<T>.type, caller); | |||
enginesRootTarget.CheckAddEntityID(toEGID, TypeCache<T>.type, caller); | |||
enginesRootTarget.QueueSwapEntityOperation(fromEGID, toEGID | |||
, this._enginesRoot.Target.FindRealComponents<T>(fromEGID) | |||
, caller); | |||
} | |||
#if UNITY_NATIVE | |||
public Svelto.ECS.Native.NativeEntityRemove ToNativeRemove<T>(string memberName) where T : IEntityDescriptor, new() | |||
public Native.NativeEntityRemove ToNativeRemove<T>(string memberName) where T : IEntityDescriptor, new() | |||
{ | |||
return _enginesRoot.Target.ProvideNativeEntityRemoveQueue<T>(memberName); | |||
} | |||
public Svelto.ECS.Native.NativeEntitySwap ToNativeSwap<T>(string memberName) where T : IEntityDescriptor, new() | |||
public Native.NativeEntitySwap ToNativeSwap<T>(string memberName) where T : IEntityDescriptor, new() | |||
{ | |||
return _enginesRoot.Target.ProvideNativeEntitySwapQueue<T>(memberName); | |||
} | |||
#endif | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public void SwapEntityGroup<T>(EGID fromID, EGID toID) | |||
where T : IEntityDescriptor, new() | |||
{ | |||
DBC.ECS.Check.Require(fromID.groupID.isInvalid == false, "invalid group detected"); | |||
DBC.ECS.Check.Require(toID.groupID.isInvalid == false, "invalid group detected"); | |||
var enginesRootTarget = _enginesRoot.Target; | |||
var descriptorComponentsToBuild = EntityDescriptorTemplate<T>.descriptor.componentsToBuild; | |||
enginesRootTarget.CheckRemoveEntityID(fromID, TypeCache<T>.type); | |||
enginesRootTarget.CheckAddEntityID(toID, TypeCache<T>.type); | |||
enginesRootTarget.QueueEntitySubmitOperation<T>( | |||
new EntitySubmitOperation(EntitySubmitOperationType.Swap, | |||
fromID, toID, descriptorComponentsToBuild)); | |||
} | |||
//enginesRoot is a weakreference because GenericEntityStreamConsumerFactory can be injected inside | |||
//engines of other enginesRoot | |||
readonly Svelto.DataStructures.WeakReference<EnginesRoot> _enginesRoot; | |||
readonly WeakReference<EnginesRoot> _enginesRoot; | |||
} | |||
void QueueEntitySubmitOperation(EntitySubmitOperation entitySubmitOperation) | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
void QueueRemoveGroupOperation(ExclusiveBuildGroup groupID, string caller) | |||
{ | |||
#if DEBUG && !PROFILE_SVELTO | |||
entitySubmitOperation.trace = new System.Diagnostics.StackFrame(1, true); | |||
#endif | |||
_entitiesOperations.Add((ulong) entitySubmitOperation.fromID, entitySubmitOperation); | |||
_entitiesOperations.AddRemoveGroupOperation(groupID, caller); | |||
} | |||
void QueueEntitySubmitOperation<T>(EntitySubmitOperation entitySubmitOperation) where T : IEntityDescriptor | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
void QueueSwapGroupOperation(ExclusiveBuildGroup fromGroupID, ExclusiveBuildGroup toGroupID, string caller) | |||
{ | |||
#if DEBUG && !PROFILE_SVELTO | |||
entitySubmitOperation.trace = new System.Diagnostics.StackFrame(1, true); | |||
_entitiesOperations.AddSwapGroupOperation(fromGroupID, toGroupID, caller); | |||
} | |||
if (_entitiesOperations.TryGetValue((ulong) entitySubmitOperation.fromID, out var entitySubmitedOperation)) | |||
{ | |||
if (entitySubmitedOperation != entitySubmitOperation) | |||
throw new ECSException("Only one entity operation per submission is allowed" | |||
.FastConcat(" entityComponentType: ") | |||
.FastConcat(typeof(T).Name) | |||
.FastConcat(" submission type ", entitySubmitOperation.type.ToString(), | |||
" from ID: ", entitySubmitOperation.fromID.entityID.ToString()) | |||
.FastConcat(" previous operation type: ", | |||
_entitiesOperations[(ulong) entitySubmitOperation.fromID].type | |||
.ToString())); | |||
} | |||
else | |||
#endif | |||
_entitiesOperations[(ulong) entitySubmitOperation.fromID] = entitySubmitOperation; | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
void QueueSwapEntityOperation | |||
(EGID fromID, EGID toID, IComponentBuilder[] componentBuilders, string caller) | |||
{ | |||
_entitiesOperations.AddSwapOperation(fromID, toID, componentBuilders, caller); | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
void QueueRemoveEntityOperation(EGID entityEGID, IComponentBuilder[] componentBuilders, string caller) | |||
{ | |||
_entitiesOperations.AddRemoveOperation(entityEGID, componentBuilders, caller); | |||
} | |||
} | |||
} |
@@ -1,177 +1,515 @@ | |||
using System.Collections.Generic; | |||
using System; | |||
using System.Runtime.CompilerServices; | |||
using Svelto.Common; | |||
using Svelto.DataStructures; | |||
using Svelto.ECS.DataStructures; | |||
using Svelto.ECS.Internal; | |||
namespace Svelto.ECS | |||
{ | |||
public partial class EnginesRoot | |||
{ | |||
/// <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<bool> SingleSubmission(PlatformProfiler profiler) | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
void SingleSubmission(PlatformProfiler profiler) | |||
{ | |||
while (true) | |||
ClearDebugChecks(); //this must be done first as I need the carry the last states after the submission | |||
_entitiesOperations.ExecuteRemoveAndSwappingOperations(_swapEntities, _removeEntities, _removeGroup, | |||
_swapGroup, this); | |||
AddEntities(profiler); | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
static void RemoveGroup(ExclusiveGroupStruct groupID, EnginesRoot enginesRoot) | |||
{ | |||
using (var sampler = new PlatformProfiler("remove whole group")) | |||
{ | |||
enginesRoot.RemoveEntitiesFromGroup(groupID, sampler); | |||
} | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
static void SwapGroup(ExclusiveGroupStruct fromGroupID, ExclusiveGroupStruct toGroupID, EnginesRoot enginesRoot) | |||
{ | |||
using (var sampler = new PlatformProfiler("swap whole group")) | |||
{ | |||
enginesRoot.SwapEntitiesBetweenGroups(fromGroupID, toGroupID, sampler); | |||
} | |||
} | |||
static void RemoveEntities( | |||
FasterDictionary<ExclusiveGroupStruct, FasterDictionary<RefWrapperType, FasterList<(uint, string)>>> | |||
removeOperations, FasterList<EGID> entitiesRemoved, EnginesRoot enginesRoot) | |||
{ | |||
using (var sampler = new PlatformProfiler("remove Entities")) | |||
{ | |||
using (sampler.Sample("Remove Entity References")) | |||
{ | |||
var count = entitiesRemoved.count; | |||
for (int i = 0; i < count; i++) | |||
{ | |||
enginesRoot._entityLocator.RemoveEntityReference(entitiesRemoved[i]); | |||
} | |||
} | |||
using (sampler.Sample("Execute remove callbacks and remove entities")) | |||
{ | |||
foreach (var entitiesToRemove in removeOperations) | |||
{ | |||
ExclusiveGroupStruct group = entitiesToRemove.key; | |||
var fromGroupDictionary = enginesRoot.GetDBGroup(group); | |||
foreach (var groupedEntitiesToRemove in entitiesToRemove.value) | |||
{ | |||
var componentType = groupedEntitiesToRemove.key; | |||
ITypeSafeDictionary fromComponentsDictionary = fromGroupDictionary[componentType]; | |||
FasterList<(uint, string)> infosToProcess = groupedEntitiesToRemove.value; | |||
fromComponentsDictionary.ExecuteEnginesRemoveCallbacks(infosToProcess, | |||
enginesRoot._reactiveEnginesRemove, group, in sampler); | |||
} | |||
} | |||
} | |||
using (sampler.Sample("Remove Entities")) | |||
{ | |||
foreach (var entitiesToRemove in removeOperations) | |||
{ | |||
ExclusiveGroupStruct fromGroup = entitiesToRemove.key; | |||
var fromGroupDictionary = enginesRoot.GetDBGroup(fromGroup); | |||
foreach (var groupedEntitiesToRemove in entitiesToRemove.value) | |||
{ | |||
RefWrapperType componentType = groupedEntitiesToRemove.key; | |||
ITypeSafeDictionary fromComponentsDictionary = fromGroupDictionary[componentType]; | |||
FasterList<(uint, string)> entityIDsToRemove = groupedEntitiesToRemove.value; | |||
enginesRoot.RemoveEntityFromPersistentFilters(entityIDsToRemove, fromGroup, | |||
componentType, fromComponentsDictionary); | |||
fromComponentsDictionary.RemoveEntitiesFromDictionary(entityIDsToRemove); | |||
//store new count after the entities are removed, plus the number of entities removed | |||
enginesRoot._cachedRangeOfSubmittedIndices.Add(((uint, uint))( | |||
fromComponentsDictionary.count, | |||
fromComponentsDictionary.count + entityIDsToRemove.count)); | |||
} | |||
} | |||
} | |||
var rangeEnumerator = enginesRoot._cachedRangeOfSubmittedIndices.GetEnumerator(); | |||
using (sampler.Sample("Execute remove Callbacks Fast")) | |||
{ | |||
foreach (var entitiesToRemove in removeOperations) | |||
{ | |||
ExclusiveGroupStruct group = entitiesToRemove.key; | |||
var fromGroupDictionary = enginesRoot.GetDBGroup(group); | |||
foreach (var groupedEntitiesToRemove in entitiesToRemove.value) | |||
{ | |||
rangeEnumerator.MoveNext(); | |||
var componentType = groupedEntitiesToRemove.key; | |||
ITypeSafeDictionary fromComponentsDictionary = fromGroupDictionary[componentType]; | |||
//get all the engines linked to TValue | |||
if (!enginesRoot._reactiveEnginesRemoveEx.TryGetValue(new RefWrapperType(componentType), | |||
out var entityComponentsEngines)) | |||
continue; | |||
fromComponentsDictionary.ExecuteEnginesRemoveCallbacksFast(entityComponentsEngines, | |||
group, rangeEnumerator.Current, in sampler); | |||
} | |||
} | |||
} | |||
} | |||
} | |||
static void SwapEntities( | |||
FasterDictionary<ExclusiveGroupStruct, FasterDictionary<RefWrapperType, | |||
FasterDictionary<ExclusiveGroupStruct, FasterList<(uint, uint, string)>>>> swapEntitiesOperations, | |||
FasterList<(EGID, EGID)> entitiesIDSwaps, EnginesRoot enginesRoot) | |||
{ | |||
using (var sampler = new PlatformProfiler("Swap entities between groups")) | |||
{ | |||
DBC.ECS.Check.Require(_maxNumberOfOperationsPerFrame > 0); | |||
ClearChecks(); | |||
using (sampler.Sample("Update Entity References")) | |||
{ | |||
var count = entitiesIDSwaps.count; | |||
for (int i = 0; i < count; i++) | |||
{ | |||
var (fromEntityGid, toEntityGid) = entitiesIDSwaps[i]; | |||
uint numberOfOperations = 0; | |||
enginesRoot._entityLocator.UpdateEntityReference(fromEntityGid, toEntityGid); | |||
} | |||
} | |||
if (_entitiesOperations.count > 0) | |||
using (sampler.Sample("Swap Entities")) | |||
{ | |||
using (var sample = profiler.Sample("Remove and Swap operations")) | |||
enginesRoot._cachedRangeOfSubmittedIndices.FastClear(); | |||
//Entities to swap are organised in order to minimise the amount of dictionary lookups. | |||
//swapEntitiesOperations iterations happen in the following order: | |||
//for each fromGroup, get all the entities to swap for each component type. | |||
//then get the list of IDs for each ToGroup. | |||
//now swap the set of FromGroup -> ToGroup entities per ID. | |||
foreach (var entitiesToSwap in swapEntitiesOperations) | |||
{ | |||
_transientEntitiesOperations.FastClear(); | |||
_entitiesOperations.CopyValuesTo(_transientEntitiesOperations); | |||
_entitiesOperations.FastClear(); | |||
EntitySubmitOperation[] entitiesOperations = | |||
_transientEntitiesOperations.ToArrayFast(out var count); | |||
for (var i = 0; i < count; i++) | |||
ExclusiveGroupStruct fromGroup = entitiesToSwap.key; | |||
var fromGroupDictionary = enginesRoot.GetDBGroup(fromGroup); | |||
//iterate all the fromgroups | |||
foreach (var groupedEntitiesToSwap in entitiesToSwap.value) | |||
{ | |||
try | |||
var componentType = groupedEntitiesToSwap.key; | |||
ITypeSafeDictionary fromComponentsDictionary = fromGroupDictionary[componentType]; | |||
//get the subset of togroups that come from from group | |||
foreach (var entitiesInfoToSwap in groupedEntitiesToSwap.value) | |||
{ | |||
switch (entitiesOperations[i].type) | |||
ExclusiveGroupStruct toGroup = entitiesInfoToSwap.key; | |||
ITypeSafeDictionary toComponentsDictionary = | |||
enginesRoot.GetOrAddTypeSafeDictionary(toGroup, | |||
enginesRoot.GetOrAddDBGroup(toGroup), componentType, fromComponentsDictionary); | |||
DBC.ECS.Check.Assert(toComponentsDictionary != null, | |||
"something went wrong with the creation of dictionaries"); | |||
//this list represents the set of entities that come from fromGroup and need | |||
//to be swapped to toGroup. Most of the times will be 1 of few units. | |||
FasterList<(uint, uint, string)> fromEntityToEntityIDs = entitiesInfoToSwap.value; | |||
int fromDictionaryCountBeforeSubmission = -1; | |||
if (enginesRoot._indicesOfPersistentFiltersUsedByThisComponent.TryGetValue( | |||
new NativeRefWrapperType(componentType), | |||
out NativeDynamicArrayCast<int> listOfFilters)) | |||
{ | |||
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; | |||
enginesRoot._cachedIndicesToSwapBeforeSubmissionForFilters.FastClear(); | |||
fromDictionaryCountBeforeSubmission = fromComponentsDictionary.count - 1; | |||
//add the index of the entities in the component array for each entityID | |||
//BEFORE the submission, as after that the ID will be different | |||
foreach (var (fromEntityID, _, _) in fromEntityToEntityIDs) | |||
enginesRoot._cachedIndicesToSwapBeforeSubmissionForFilters.Add(fromEntityID, | |||
fromComponentsDictionary.GetIndex(fromEntityID)); | |||
} | |||
//ensure that to dictionary has enough room to store the new entities` | |||
toComponentsDictionary.EnsureCapacity((uint)(toComponentsDictionary.count + | |||
(uint)fromEntityToEntityIDs.count)); | |||
//fortunately swap means that entities are added at the end of each destination | |||
//dictionary list, so we can just iterate the list using the indices ranges added in the | |||
//_cachedIndices | |||
enginesRoot._cachedRangeOfSubmittedIndices.Add(((uint, uint))( | |||
toComponentsDictionary.count, | |||
toComponentsDictionary.count + fromEntityToEntityIDs.count)); | |||
fromComponentsDictionary.SwapEntitiesBetweenDictionaries(fromEntityToEntityIDs, | |||
fromGroup, toGroup, toComponentsDictionary); | |||
if (fromDictionaryCountBeforeSubmission != -1) //this if skips the swap if there are no filters linked to the component | |||
enginesRoot.SwapEntityBetweenPersistentFilters(fromEntityToEntityIDs, | |||
enginesRoot._cachedIndicesToSwapBeforeSubmissionForFilters, | |||
toComponentsDictionary, fromGroup, toGroup, | |||
(uint)fromDictionaryCountBeforeSubmission, listOfFilters); | |||
} | |||
catch | |||
} | |||
} | |||
} | |||
using (sampler.Sample("Execute Swap Callbacks")) | |||
{ | |||
foreach (var entitiesToSwap in swapEntitiesOperations) | |||
{ | |||
ExclusiveGroupStruct fromGroup = entitiesToSwap.key; | |||
foreach (var groupedEntitiesToSwap in entitiesToSwap.value) | |||
{ | |||
var componentType = groupedEntitiesToSwap.key; | |||
//get all the engines linked to TValue | |||
if (!enginesRoot._reactiveEnginesSwap.TryGetValue(new RefWrapperType(componentType), | |||
out var entityComponentsEngines)) | |||
continue; | |||
foreach (var entitiesInfoToSwap in groupedEntitiesToSwap.value) | |||
{ | |||
var str = "Crash while executing Entity Operation ".FastConcat( | |||
entitiesOperations[i].type.ToString()); | |||
ExclusiveGroupStruct toGroup = entitiesInfoToSwap.key; | |||
ITypeSafeDictionary toComponentsDictionary = GetTypeSafeDictionary(toGroup, | |||
enginesRoot.GetDBGroup(toGroup), componentType); | |||
Svelto.Console.LogError(str.FastConcat(" ") | |||
#if DEBUG && !PROFILE_SVELTO | |||
.FastConcat(entitiesOperations[i].trace.ToString()) | |||
#endif | |||
); | |||
var infosToProcess = entitiesInfoToSwap.value; | |||
throw; | |||
toComponentsDictionary.ExecuteEnginesSwapCallbacks(infosToProcess, | |||
entityComponentsEngines, fromGroup, toGroup, in sampler); | |||
} | |||
} | |||
} | |||
} | |||
++numberOfOperations; | |||
var rangeEnumerator = enginesRoot._cachedRangeOfSubmittedIndices.GetEnumerator(); | |||
using (sampler.Sample("Execute Swap Callbacks Fast")) | |||
{ | |||
foreach (var entitiesToSwap in swapEntitiesOperations) | |||
{ | |||
ExclusiveGroupStruct fromGroup = entitiesToSwap.key; | |||
foreach (var groupedEntitiesToSwap in entitiesToSwap.value) | |||
{ | |||
var componentType = groupedEntitiesToSwap.key; | |||
if ((uint) numberOfOperations >= (uint) _maxNumberOfOperationsPerFrame) | |||
foreach (var entitiesInfoToSwap in groupedEntitiesToSwap.value) | |||
{ | |||
using (sample.Yield()) | |||
yield return true; | |||
rangeEnumerator.MoveNext(); | |||
numberOfOperations = 0; | |||
//get all the engines linked to TValue | |||
if (!enginesRoot._reactiveEnginesSwapEx.TryGetValue(new RefWrapperType(componentType), | |||
out var entityComponentsEngines)) | |||
continue; | |||
ExclusiveGroupStruct toGroup = entitiesInfoToSwap.key; | |||
ITypeSafeDictionary toComponentsDictionary = GetTypeSafeDictionary(toGroup, | |||
enginesRoot.GetDBGroup(toGroup), componentType); | |||
toComponentsDictionary.ExecuteEnginesSwapCallbacksFast(entityComponentsEngines, | |||
fromGroup, toGroup, rangeEnumerator.Current, in sampler); | |||
} | |||
} | |||
} | |||
} | |||
} | |||
} | |||
_groupedEntityToAdd.Swap(); | |||
void AddEntities(PlatformProfiler sampler) | |||
{ | |||
//current buffer becomes other, and other becomes current | |||
_groupedEntityToAdd.Swap(); | |||
if (_groupedEntityToAdd.AnyOtherEntityCreated()) | |||
//I need to iterate the previous current, which is now other | |||
if (_groupedEntityToAdd.AnyPreviousEntityCreated()) | |||
{ | |||
_cachedRangeOfSubmittedIndices.FastClear(); | |||
using (sampler.Sample("Add operations")) | |||
{ | |||
using (var outerSampler = profiler.Sample("Add operations")) | |||
try | |||
{ | |||
try | |||
using (sampler.Sample("Add entities to database")) | |||
{ | |||
using (profiler.Sample("Add entities to database")) | |||
//each group is indexed by entity view type. for each type there is a dictionary indexed | |||
//by entityID | |||
foreach (var groupToSubmit in _groupedEntityToAdd) | |||
{ | |||
//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 groupID = groupToSubmit.@group; | |||
var groupDB = GetOrAddDBGroup(groupID); | |||
//add the entityComponents in the group | |||
foreach (var entityComponentsToSubmit in groupToSubmit.components) | |||
{ | |||
var groupID = groupToSubmit.Key; | |||
var groupDB = GetOrCreateDBGroup(groupID); | |||
//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 generated this frame. | |||
dbDic.AddEntitiesFromDictionary(targetTypeSafeDictionary, groupID, this); | |||
} | |||
var type = entityComponentsToSubmit.key; | |||
var fromDictionary = entityComponentsToSubmit.value; | |||
var wrapper = new RefWrapperType(type); | |||
var toDictionary = | |||
GetOrAddTypeSafeDictionary(groupID, groupDB, wrapper, fromDictionary); | |||
//all the new entities are added at the end of each dictionary list, so we can | |||
//just iterate the list using the indices ranges added in the _cachedIndices | |||
_cachedRangeOfSubmittedIndices.Add(((uint, uint))(toDictionary.count, | |||
toDictionary.count + fromDictionary.count)); | |||
//Fill the DB with the entity components generated this frame. | |||
fromDictionary.AddEntitiesToDictionary(toDictionary, groupID, entityLocator); | |||
} | |||
} | |||
} | |||
//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")) | |||
//then submit everything in the engines, so that the DB is up to date with all the entity components | |||
//created by the entity built | |||
var enumerator = _cachedRangeOfSubmittedIndices.GetEnumerator(); | |||
using (sampler.Sample("Add entities to engines fast")) | |||
{ | |||
foreach (GroupInfo groupToSubmit in _groupedEntityToAdd) | |||
{ | |||
foreach (var groupToSubmit in _groupedEntityToAdd.other) | |||
var groupID = groupToSubmit.@group; | |||
var groupDB = GetDBGroup(groupID); | |||
foreach (var entityComponentsToSubmit in groupToSubmit.components) | |||
{ | |||
var groupID = 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 groupToSubmit.Value) | |||
{ | |||
var realDic = groupDB[new RefWrapperType(entityComponentsToSubmit.Key)]; | |||
entityComponentsToSubmit.Value.ExecuteEnginesAddOrSwapCallbacks( | |||
_reactiveEnginesAddRemove, realDic, null, groupID, in profiler); | |||
numberOfOperations += entityComponentsToSubmit.Value.count; | |||
if (numberOfOperations >= _maxNumberOfOperationsPerFrame) | |||
{ | |||
using (outerSampler.Yield()) | |||
using (sampler.Yield()) | |||
{ | |||
yield return true; | |||
} | |||
numberOfOperations = 0; | |||
} | |||
} | |||
var type = entityComponentsToSubmit.key; | |||
var wrapper = new RefWrapperType(type); | |||
var toDictionary = GetTypeSafeDictionary(groupID, groupDB, wrapper); | |||
enumerator.MoveNext(); | |||
toDictionary.ExecuteEnginesAddEntityCallbacksFast(_reactiveEnginesAddEx, groupID, | |||
enumerator.Current, in sampler); | |||
} | |||
} | |||
} | |||
finally | |||
//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 (sampler.Sample("Add entities to engines")) | |||
{ | |||
using (profiler.Sample("clear double buffering")) | |||
foreach (GroupInfo groupToSubmit in _groupedEntityToAdd) | |||
{ | |||
//other can be cleared now, but let's avoid deleting the dictionary every time | |||
_groupedEntityToAdd.ClearOther(); | |||
var groupID = groupToSubmit.@group; | |||
var groupDB = GetDBGroup(groupID); | |||
//This loop iterates again all the entity components that have been just submitted to call | |||
//the Add Callbacks on them. Note that I am iterating the transient buffer of the just | |||
//added components, but calling the callback on the entities just added in the real buffer | |||
//Note: it's OK to add new entities while this happens because of the double buffer | |||
//design of the transient buffer of added entities. | |||
foreach (var entityComponentsToSubmit in groupToSubmit.components) | |||
{ | |||
var type = entityComponentsToSubmit.key; | |||
var fromDictionary = entityComponentsToSubmit.value; | |||
//this contains the total number of components ever submitted in the DB | |||
ITypeSafeDictionary toDictionary = GetTypeSafeDictionary(groupID, groupDB, type); | |||
fromDictionary.ExecuteEnginesAddCallbacks(_reactiveEnginesAdd, toDictionary, | |||
groupID, in sampler); | |||
} | |||
} | |||
} | |||
} | |||
finally | |||
{ | |||
using (sampler.Sample("clear double buffering")) | |||
{ | |||
//other can be cleared now, but let's avoid deleting the dictionary every time | |||
_groupedEntityToAdd.ClearLastAddOperations(); | |||
} | |||
} | |||
} | |||
yield return false; | |||
} | |||
} | |||
bool HasMadeNewStructuralChangesInThisIteration() | |||
{ | |||
return _groupedEntityToAdd.AnyEntityCreated() || _entitiesOperations.count > 0; | |||
return _groupedEntityToAdd.AnyEntityCreated() || _entitiesOperations.AnyOperationQueued(); | |||
} | |||
readonly DoubleBufferedEntitiesToAdd _groupedEntityToAdd; | |||
readonly FasterDictionary<ulong, EntitySubmitOperation> _entitiesOperations; | |||
readonly FasterList<EntitySubmitOperation> _transientEntitiesOperations; | |||
uint _maxNumberOfOperationsPerFrame; | |||
void RemoveEntitiesFromGroup(ExclusiveGroupStruct groupID, in PlatformProfiler profiler) | |||
{ | |||
_entityLocator.RemoveAllGroupReferenceLocators(groupID); | |||
if (_groupEntityComponentsDB.TryGetValue(groupID, out var dictionariesOfEntities)) | |||
{ | |||
foreach (var dictionaryOfEntities in dictionariesOfEntities) | |||
{ | |||
//RemoveEX happens inside | |||
dictionaryOfEntities.value.ExecuteEnginesRemoveCallbacks_Group(_reactiveEnginesRemove, | |||
_reactiveEnginesRemoveEx, groupID, profiler); | |||
} | |||
foreach (var dictionaryOfEntities in dictionariesOfEntities) | |||
{ | |||
dictionaryOfEntities.value.Clear(); | |||
_groupsPerEntity[dictionaryOfEntities.key][groupID].Clear(); | |||
} | |||
} | |||
} | |||
void SwapEntitiesBetweenGroups(ExclusiveGroupStruct fromGroupId, ExclusiveGroupStruct toGroupId, | |||
PlatformProfiler platformProfiler) | |||
{ | |||
FasterDictionary<RefWrapperType, ITypeSafeDictionary> fromGroup = GetDBGroup(fromGroupId); | |||
FasterDictionary<RefWrapperType, ITypeSafeDictionary> toGroup = GetOrAddDBGroup(toGroupId); | |||
_entityLocator.UpdateAllGroupReferenceLocators(fromGroupId, toGroupId); | |||
//remove entities from dictionaries | |||
foreach (var dictionaryOfEntities in fromGroup) | |||
{ | |||
RefWrapperType refWrapperType = dictionaryOfEntities.key; | |||
ITypeSafeDictionary fromDictionary = dictionaryOfEntities.value; | |||
ITypeSafeDictionary toDictionary = | |||
GetOrAddTypeSafeDictionary(toGroupId, toGroup, refWrapperType, fromDictionary); | |||
fromDictionary.AddEntitiesToDictionary(toDictionary, toGroupId, this.entityLocator); | |||
} | |||
//Call all the callbacks | |||
foreach (var dictionaryOfEntities in fromGroup) | |||
{ | |||
RefWrapperType refWrapperType = dictionaryOfEntities.key; | |||
ITypeSafeDictionary fromDictionary = dictionaryOfEntities.value; | |||
ITypeSafeDictionary toDictionary = GetTypeSafeDictionary(toGroupId, toGroup, refWrapperType); | |||
//SwapEX happens inside | |||
fromDictionary.ExecuteEnginesSwapCallbacks_Group(_reactiveEnginesSwap, | |||
_reactiveEnginesSwapEx, toDictionary, fromGroupId, toGroupId, platformProfiler); | |||
} | |||
//remove entities from dictionaries | |||
foreach (var dictionaryOfEntities in fromGroup) | |||
{ | |||
dictionaryOfEntities.value.Clear(); | |||
_groupsPerEntity[dictionaryOfEntities.key][fromGroupId].Clear(); | |||
} | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
ITypeSafeDictionary GetOrAddTypeSafeDictionary(ExclusiveGroupStruct groupId, | |||
FasterDictionary<RefWrapperType, ITypeSafeDictionary> groupPerComponentType, RefWrapperType type, | |||
ITypeSafeDictionary fromDictionary) | |||
{ | |||
//be sure that the TypeSafeDictionary for the entity Type exists | |||
if (groupPerComponentType.TryGetValue(type, out ITypeSafeDictionary toEntitiesDictionary) == false) | |||
{ | |||
toEntitiesDictionary = fromDictionary.Create(); | |||
groupPerComponentType.Add(type, toEntitiesDictionary); | |||
} | |||
{ | |||
//update GroupsPerEntity | |||
if (_groupsPerEntity.TryGetValue(type, out var groupedGroup) == false) | |||
groupedGroup = _groupsPerEntity[type] = | |||
new FasterDictionary<ExclusiveGroupStruct, ITypeSafeDictionary>(); | |||
groupedGroup[groupId] = toEntitiesDictionary; | |||
} | |||
return toEntitiesDictionary; | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
static ITypeSafeDictionary GetTypeSafeDictionary(ExclusiveGroupStruct groupID, | |||
FasterDictionary<RefWrapperType, ITypeSafeDictionary> @group, RefWrapperType refWrapper) | |||
{ | |||
if (@group.TryGetValue(refWrapper, out ITypeSafeDictionary fromTypeSafeDictionary) == false) | |||
{ | |||
throw new ECSException("no group found: ".FastConcat(groupID.ToName())); | |||
} | |||
return fromTypeSafeDictionary; | |||
} | |||
readonly DoubleBufferedEntitiesToAdd _groupedEntityToAdd; | |||
readonly EntitiesOperations _entitiesOperations; | |||
readonly FasterList<(uint, uint)> _cachedRangeOfSubmittedIndices; | |||
readonly FasterDictionary<uint, uint> _cachedIndicesToSwapBeforeSubmissionForFilters; | |||
static readonly | |||
Action<FasterDictionary<ExclusiveGroupStruct, FasterDictionary<RefWrapperType, | |||
FasterDictionary<ExclusiveGroupStruct, FasterList<(uint, uint, string)>>>>, FasterList<(EGID, EGID)> | |||
, | |||
EnginesRoot> _swapEntities; | |||
static readonly Action< | |||
FasterDictionary<ExclusiveGroupStruct, FasterDictionary<RefWrapperType, FasterList<(uint, string)>>>, | |||
FasterList<EGID>, EnginesRoot> _removeEntities; | |||
static readonly Action<ExclusiveGroupStruct, EnginesRoot> _removeGroup; | |||
static readonly Action<ExclusiveGroupStruct, ExclusiveGroupStruct, EnginesRoot> _swapGroup; | |||
} | |||
} |
@@ -1,5 +1,4 @@ | |||
using System; | |||
using System.Runtime.CompilerServices; | |||
using System.Threading; | |||
using Svelto.DataStructures; | |||
using Svelto.ECS.Internal; | |||
@@ -115,9 +114,9 @@ namespace Svelto.ECS | |||
foreach (var value in localArray[startIndex]) | |||
{ | |||
if (value.Key.IsEnabled()) | |||
if (value.key.IsEnabled()) | |||
{ | |||
localGroups.Add(value.Key, value.Key); | |||
localGroups.Add(value.key, value.key); | |||
} | |||
} | |||
@@ -171,9 +170,9 @@ namespace Svelto.ECS | |||
foreach (var value in localArray[startIndex]) | |||
{ | |||
if (value.Key.IsEnabled()) | |||
if (value.key.IsEnabled()) | |||
{ | |||
localGroups.Add(value.Key, value.Key); | |||
localGroups.Add(value.key, value.key); | |||
} | |||
} | |||
@@ -6,14 +6,13 @@ using System; | |||
using System.Runtime.CompilerServices; | |||
using Svelto.Common; | |||
using Svelto.DataStructures; | |||
using Svelto.DataStructures.Native; | |||
using Svelto.ECS.Internal; | |||
namespace Svelto.ECS | |||
{ | |||
public partial class EntitiesDB | |||
{ | |||
internal EntitiesDB(EnginesRoot enginesRoot, EnginesRoot.LocatorMap entityReferencesMap) | |||
internal EntitiesDB(EnginesRoot enginesRoot, EnginesRoot.EntityReferenceMap entityReferencesMap) | |||
{ | |||
_enginesRoot = enginesRoot; | |||
_entityReferencesMap = entityReferencesMap; | |||
@@ -25,15 +24,17 @@ namespace Svelto.ECS | |||
{ | |||
uint count = 0; | |||
IBuffer<T> buffer; | |||
EntityIDs ids = default; | |||
if (SafeQueryEntityDictionary<T>(out var typeSafeDictionary, entitiesInGroupPerType) == false) | |||
buffer = RetrieveEmptyEntityComponentArray<T>(); | |||
else | |||
{ | |||
var safeDictionary = (typeSafeDictionary as ITypeSafeDictionary<T>); | |||
ITypeSafeDictionary<T> safeDictionary = (typeSafeDictionary as ITypeSafeDictionary<T>); | |||
buffer = safeDictionary.GetValues(out count); | |||
ids = safeDictionary.entityIDs; | |||
} | |||
return new EntityCollection<T>(buffer, count); | |||
return new EntityCollection<T>(buffer, count, ids); | |||
} | |||
/// <summary> | |||
@@ -257,7 +258,7 @@ namespace Svelto.ECS | |||
public bool IsDisposing => _enginesRoot._isDisposing; | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
internal bool SafeQueryEntityDictionary<T> | |||
bool SafeQueryEntityDictionary<T> | |||
(out ITypeSafeDictionary typeSafeDictionary | |||
, FasterDictionary<RefWrapperType, ITypeSafeDictionary> entitiesInGroupPerType) where T : IEntityComponent | |||
{ | |||
@@ -353,6 +354,6 @@ namespace Svelto.ECS | |||
FasterDictionary<RefWrapperType, FasterDictionary<ExclusiveGroupStruct, ITypeSafeDictionary>> groupsPerEntity => | |||
_enginesRoot._groupsPerEntity; | |||
EnginesRoot.LocatorMap _entityReferencesMap; | |||
EnginesRoot.EntityReferenceMap _entityReferencesMap; | |||
} | |||
} |
@@ -0,0 +1,191 @@ | |||
using System; | |||
using Svelto.DataStructures; | |||
namespace Svelto.ECS | |||
{ | |||
class EntitiesOperations | |||
{ | |||
public EntitiesOperations() | |||
{ | |||
_thisSubmissionInfo.Init(); | |||
_lastSubmittedInfo.Init(); | |||
} | |||
public void AddSwapOperation(EGID fromID, EGID toID, IComponentBuilder[] componentBuilders, string caller) | |||
{ | |||
_thisSubmissionInfo._entitiesSwapped.Add((fromID, toID)); | |||
//todo: limit the number of dictionaries that can be cached | |||
//recycle or create dictionaries of components per group | |||
var swappedComponentsPerType = _thisSubmissionInfo._currentSwapEntitiesOperations.RecycleOrAdd( | |||
fromID.groupID, | |||
() => new FasterDictionary<RefWrapperType, | |||
FasterDictionary<ExclusiveGroupStruct, FasterList<(uint, uint, string)>>>(), | |||
(ref FasterDictionary<RefWrapperType, FasterDictionary<ExclusiveGroupStruct, FasterList<(uint, uint, string)>>> recycled) => | |||
recycled.FastClear()); | |||
foreach (IComponentBuilder operation in componentBuilders) | |||
{ | |||
swappedComponentsPerType | |||
//recycle or create dictionaries per component type | |||
.RecycleOrAdd(new RefWrapperType(operation.GetEntityComponentType()), | |||
() => new FasterDictionary<ExclusiveGroupStruct, FasterList<(uint, uint, string)>>(), | |||
(ref FasterDictionary<ExclusiveGroupStruct, FasterList<(uint, uint, string)>> target) => | |||
target.FastClear()) | |||
//recycle or create list of entities to swap | |||
.RecycleOrAdd(toID.groupID, () => new FasterList<(uint, uint, string)>(), | |||
(ref FasterList<(uint, uint, string)> target) => target.FastClear()) | |||
//add entity to swap | |||
.Add((fromID.entityID, toID.entityID, caller)); | |||
} | |||
} | |||
public void AddRemoveOperation(EGID entityEgid, IComponentBuilder[] componentBuilders, string caller) | |||
{ | |||
_thisSubmissionInfo._entitiesRemoved.Add(entityEgid); | |||
//todo: limit the number of dictionaries that can be cached | |||
//recycle or create dictionaries of components per group | |||
var removedComponentsPerType = _thisSubmissionInfo._currentRemoveEntitiesOperations.RecycleOrAdd( | |||
entityEgid.groupID, () => new FasterDictionary<RefWrapperType, FasterList<(uint, string)>>(), | |||
(ref FasterDictionary<RefWrapperType, FasterList<(uint, string)>> recycled) => recycled.FastClear()); | |||
foreach (IComponentBuilder operation in componentBuilders) | |||
{ | |||
removedComponentsPerType | |||
//recycle or create dictionaries per component type | |||
.RecycleOrAdd(new RefWrapperType(operation.GetEntityComponentType()), | |||
() => new FasterList<(uint, string)>(), | |||
(ref FasterList<(uint, string)> target) => target.FastClear()) | |||
//add entity to swap | |||
.Add((entityEgid.entityID, caller)); | |||
} | |||
} | |||
public void AddRemoveGroupOperation(ExclusiveBuildGroup groupID, string caller) | |||
{ | |||
_thisSubmissionInfo._groupsToRemove.Add((groupID, caller)); | |||
} | |||
public void AddSwapGroupOperation(ExclusiveBuildGroup fromGroupID, ExclusiveBuildGroup toGroupID, string caller) | |||
{ | |||
_thisSubmissionInfo._groupsToSwap.Add((fromGroupID, toGroupID, caller)); | |||
} | |||
public void ExecuteRemoveAndSwappingOperations( | |||
Action<FasterDictionary<ExclusiveGroupStruct, FasterDictionary<RefWrapperType, | |||
FasterDictionary<ExclusiveGroupStruct, FasterList<(uint, uint, string)>>>>, FasterList<(EGID, EGID)> | |||
, | |||
EnginesRoot> swapEntities, | |||
Action<FasterDictionary<ExclusiveGroupStruct, FasterDictionary<RefWrapperType, FasterList<(uint, string)>>>, | |||
FasterList<EGID>, EnginesRoot> removeEntities, Action<ExclusiveGroupStruct, EnginesRoot> removeGroup, | |||
Action<ExclusiveGroupStruct, ExclusiveGroupStruct, EnginesRoot> swapGroup, EnginesRoot enginesRoot) | |||
{ | |||
(_thisSubmissionInfo, _lastSubmittedInfo) = (_lastSubmittedInfo, _thisSubmissionInfo); | |||
///todo: we found a case where entities with reference to other entities were removed | |||
/// in the same frame where the referenced entities are remove too. | |||
/// the callback of the referencing entities were assuming that the reference at that point | |||
/// would be invalid. However since the callbacks were called before the groups are removed | |||
/// the reference were still valid, which was not expected. | |||
/// If the referenced entities were removed one by one instead that with the group, by chance | |||
/// it instead worked because the entities were removed before the callbacks were called. | |||
/// this is why RemoveGroup is happeing before RemoveEntities, however the real fix | |||
/// should be to update all the references before removing the entities from the dictionaries | |||
/// and call the callbacks | |||
foreach (var (group, caller) in _lastSubmittedInfo._groupsToRemove) | |||
{ | |||
try | |||
{ | |||
removeGroup(group, enginesRoot); | |||
} | |||
catch | |||
{ | |||
var str = "Crash while removing a whole group on ".FastConcat(group.ToString()) | |||
.FastConcat(" from : ", caller); | |||
Console.LogError(str); | |||
throw; | |||
} | |||
} | |||
foreach (var (fromGroup, toGroup, caller) in _lastSubmittedInfo._groupsToSwap) | |||
{ | |||
try | |||
{ | |||
swapGroup(fromGroup, toGroup, enginesRoot); | |||
} | |||
catch | |||
{ | |||
var str = "Crash while swapping a whole group on " | |||
.FastConcat(fromGroup.ToString(), " ", toGroup.ToString()).FastConcat(" from : ", caller); | |||
Console.LogError(str); | |||
throw; | |||
} | |||
} | |||
if (_lastSubmittedInfo._entitiesSwapped.count > 0) | |||
swapEntities(_lastSubmittedInfo._currentSwapEntitiesOperations, _lastSubmittedInfo._entitiesSwapped, | |||
enginesRoot); | |||
if (_lastSubmittedInfo._entitiesRemoved.count > 0) | |||
removeEntities(_lastSubmittedInfo._currentRemoveEntitiesOperations, _lastSubmittedInfo._entitiesRemoved, | |||
enginesRoot); | |||
_lastSubmittedInfo.Clear(); | |||
} | |||
public bool AnyOperationQueued() => _thisSubmissionInfo.AnyOperationQueued(); | |||
struct Info | |||
{ | |||
//from group //actual component type | |||
internal FasterDictionary<ExclusiveGroupStruct, FasterDictionary<RefWrapperType, | |||
// to group ID //entityIDs , debugInfo | |||
FasterDictionary<ExclusiveGroupStruct, FasterList<(uint, uint, string)>>>> | |||
_currentSwapEntitiesOperations; | |||
internal FasterDictionary<ExclusiveGroupStruct, | |||
FasterDictionary<RefWrapperType, FasterList<(uint, string)>>> _currentRemoveEntitiesOperations; | |||
internal FasterList<(EGID, EGID)> _entitiesSwapped; | |||
internal FasterList<EGID> _entitiesRemoved; | |||
public FasterList<(ExclusiveBuildGroup, ExclusiveBuildGroup, string)> _groupsToSwap; | |||
public FasterList<(ExclusiveBuildGroup, string)> _groupsToRemove; | |||
internal bool AnyOperationQueued() => | |||
_entitiesSwapped.count > 0 || _entitiesRemoved.count > 0 || _groupsToSwap.count > 0 || | |||
_groupsToRemove.count > 0; | |||
internal void Clear() | |||
{ | |||
_currentSwapEntitiesOperations.FastClear(); | |||
_currentRemoveEntitiesOperations.FastClear(); | |||
_entitiesSwapped.FastClear(); | |||
_entitiesRemoved.FastClear(); | |||
_groupsToRemove.FastClear(); | |||
_groupsToSwap.FastClear(); | |||
} | |||
internal void Init() | |||
{ | |||
_entitiesSwapped = new FasterList<(EGID, EGID)>(); | |||
_entitiesRemoved = new FasterList<EGID>(); | |||
_groupsToRemove = new FasterList<(ExclusiveBuildGroup, string)>(); | |||
_groupsToSwap = new FasterList<(ExclusiveBuildGroup, ExclusiveBuildGroup, string)>(); | |||
_currentSwapEntitiesOperations = | |||
new FasterDictionary<ExclusiveGroupStruct, FasterDictionary<RefWrapperType, | |||
FasterDictionary<ExclusiveGroupStruct, FasterList<(uint, uint, string)>>>>(); | |||
_currentRemoveEntitiesOperations = | |||
new FasterDictionary<ExclusiveGroupStruct, | |||
FasterDictionary<RefWrapperType, FasterList<(uint, string)>>>(); | |||
} | |||
} | |||
Info _thisSubmissionInfo; | |||
Info _lastSubmittedInfo; | |||
} | |||
} |
@@ -1,6 +1,5 @@ | |||
using System.Runtime.CompilerServices; | |||
using Svelto.DataStructures; | |||
using Svelto.DataStructures.Native; | |||
using Svelto.ECS.Internal; | |||
namespace Svelto.ECS | |||
@@ -9,134 +8,139 @@ namespace Svelto.ECS | |||
{ | |||
static readonly bool IsUnmanaged = TypeSafeDictionary<T>.isUnmanaged; | |||
public EntityCollection(IBuffer<T> buffer, uint count, EntityIDs entityIDs) : this() | |||
{ | |||
DBC.ECS.Check.Require(count == 0 || buffer.isValid, "Buffer is found in impossible state"); | |||
if (IsUnmanaged) | |||
{ | |||
_nativedBuffer = (NB<T>)buffer; | |||
_nativedIndices = entityIDs.nativeIDs; | |||
} | |||
else | |||
{ | |||
_managedBuffer = (MB<T>)buffer; | |||
_managedIndices = entityIDs.managedIDs; | |||
} | |||
this.count = count; | |||
} | |||
public EntityCollection(IBuffer<T> buffer, uint count) : this() | |||
{ | |||
DBC.ECS.Check.Require(count == 0 || buffer.isValid, "Buffer is found in impossible state"); | |||
if (IsUnmanaged) | |||
_nativedBuffer = (NB<T>) buffer; | |||
_nativedBuffer = (NB<T>)buffer; | |||
else | |||
_managedBuffer = (MB<T>) buffer; | |||
_managedBuffer = (MB<T>)buffer; | |||
_count = count; | |||
this.count = count; | |||
} | |||
public uint count => _count; | |||
public uint count { get; } | |||
internal readonly MB<T> _managedBuffer; | |||
internal readonly NB<T> _nativedBuffer; | |||
readonly uint _count; | |||
internal readonly NativeEntityIDs _nativedIndices; | |||
internal readonly ManagedEntityIDs _managedIndices; | |||
} | |||
public readonly ref struct EntityCollection<T1, T2> | |||
where T1 : struct, IEntityComponent where T2 : struct, IEntityComponent | |||
public readonly ref struct EntityCollection<T1, T2> where T1 : struct, IEntityComponent | |||
where T2 : struct, IEntityComponent | |||
{ | |||
internal EntityCollection(in EntityCollection<T1> array1, in EntityCollection<T2> array2) | |||
{ | |||
_array1 = array1; | |||
_array2 = array2; | |||
buffer1 = array1; | |||
buffer2 = array2; | |||
} | |||
public uint count => _array1.count; | |||
public int count => (int)buffer1.count; | |||
internal EntityCollection<T2> buffer2 | |||
{ | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
get => _array2; | |||
get; | |||
} | |||
internal EntityCollection<T1> buffer1 | |||
{ | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
get => _array1; | |||
get; | |||
} | |||
readonly EntityCollection<T1> _array1; | |||
readonly EntityCollection<T2> _array2; | |||
} | |||
public readonly ref struct EntityCollection<T1, T2, T3> where T3 : struct, IEntityComponent | |||
where T2 : struct, IEntityComponent | |||
where T1 : struct, IEntityComponent | |||
where T2 : struct, IEntityComponent | |||
where T1 : struct, IEntityComponent | |||
{ | |||
internal EntityCollection | |||
(in EntityCollection<T1> array1, in EntityCollection<T2> array2, in EntityCollection<T3> array3) | |||
internal EntityCollection(in EntityCollection<T1> array1, in EntityCollection<T2> array2, | |||
in EntityCollection<T3> array3) | |||
{ | |||
_array1 = array1; | |||
_array2 = array2; | |||
_array3 = array3; | |||
buffer1 = array1; | |||
buffer2 = array2; | |||
buffer3 = array3; | |||
} | |||
internal EntityCollection<T1> buffer1 | |||
{ | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
get => _array1; | |||
get; | |||
} | |||
internal EntityCollection<T2> buffer2 | |||
{ | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
get => _array2; | |||
get; | |||
} | |||
internal EntityCollection<T3> buffer3 | |||
{ | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
get => _array3; | |||
get; | |||
} | |||
internal uint count => buffer1.count; | |||
readonly EntityCollection<T1> _array1; | |||
readonly EntityCollection<T2> _array2; | |||
readonly EntityCollection<T3> _array3; | |||
internal int count => (int)buffer1.count; | |||
} | |||
public readonly ref struct EntityCollection<T1, T2, T3, T4> where T1 : struct, IEntityComponent | |||
where T2 : struct, IEntityComponent | |||
where T3 : struct, IEntityComponent | |||
where T4 : struct, IEntityComponent | |||
where T2 : struct, IEntityComponent | |||
where T3 : struct, IEntityComponent | |||
where T4 : struct, IEntityComponent | |||
{ | |||
internal EntityCollection | |||
(in EntityCollection<T1> array1, in EntityCollection<T2> array2, in EntityCollection<T3> array3 | |||
, in EntityCollection<T4> array4) | |||
internal EntityCollection(in EntityCollection<T1> array1, in EntityCollection<T2> array2, | |||
in EntityCollection<T3> array3, in EntityCollection<T4> array4) | |||
{ | |||
_array1 = array1; | |||
_array2 = array2; | |||
_array3 = array3; | |||
_array4 = array4; | |||
buffer1 = array1; | |||
buffer2 = array2; | |||
buffer3 = array3; | |||
buffer4 = array4; | |||
} | |||
internal EntityCollection<T1> buffer1 | |||
{ | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
get => _array1; | |||
get; | |||
} | |||
internal EntityCollection<T2> buffer2 | |||
{ | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
get => _array2; | |||
get; | |||
} | |||
internal EntityCollection<T3> buffer3 | |||
{ | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
get => _array3; | |||
get; | |||
} | |||
internal EntityCollection<T4> buffer4 | |||
{ | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
get => _array4; | |||
get; | |||
} | |||
internal uint count => _array1.count; | |||
readonly EntityCollection<T1> _array1; | |||
readonly EntityCollection<T2> _array2; | |||
readonly EntityCollection<T3> _array3; | |||
readonly EntityCollection<T4> _array4; | |||
internal int count => (int)buffer1.count; | |||
} | |||
public readonly struct BT<BufferT1, BufferT2, BufferT3, BufferT4> | |||
@@ -147,13 +151,20 @@ namespace Svelto.ECS | |||
public readonly BufferT4 buffer4; | |||
public readonly int count; | |||
public BT(BufferT1 bufferT1, BufferT2 bufferT2, BufferT3 bufferT3, BufferT4 bufferT4, uint count) : this() | |||
BT(in (BufferT1 bufferT1, BufferT2 bufferT2, BufferT3 bufferT3, BufferT4 bufferT4, int count) buffer) : | |||
this() | |||
{ | |||
buffer1 = buffer.bufferT1; | |||
buffer2 = buffer.bufferT2; | |||
buffer3 = buffer.bufferT3; | |||
buffer4 = buffer.bufferT4; | |||
count = buffer.count; | |||
} | |||
public static implicit operator BT<BufferT1, BufferT2, BufferT3, BufferT4>( | |||
in (BufferT1 bufferT1, BufferT2 bufferT2, BufferT3 bufferT3, BufferT4 bufferT4, int count) buffer) | |||
{ | |||
this.buffer1 = bufferT1; | |||
this.buffer2 = bufferT2; | |||
this.buffer3 = bufferT3; | |||
this.buffer4 = bufferT4; | |||
this.count = (int) count; | |||
return new BT<BufferT1, BufferT2, BufferT3, BufferT4>(buffer); | |||
} | |||
} | |||
@@ -164,20 +175,18 @@ namespace Svelto.ECS | |||
public readonly BufferT3 buffer3; | |||
public readonly int count; | |||
public BT(BufferT1 bufferT1, BufferT2 bufferT2, BufferT3 bufferT3, uint count) : this() | |||
BT(in (BufferT1 bufferT1, BufferT2 bufferT2, BufferT3 bufferT3, int count) buffer) : this() | |||
{ | |||
this.buffer1 = bufferT1; | |||
this.buffer2 = bufferT2; | |||
this.buffer3 = bufferT3; | |||
this.count = (int) count; | |||
buffer1 = buffer.bufferT1; | |||
buffer2 = buffer.bufferT2; | |||
buffer3 = buffer.bufferT3; | |||
count = buffer.count; | |||
} | |||
public void Deconstruct(out BufferT1 bufferT1, out BufferT2 bufferT2, out BufferT3 bufferT3, out int count) | |||
public static implicit operator BT<BufferT1, BufferT2, BufferT3>( | |||
in (BufferT1 bufferT1, BufferT2 bufferT2, BufferT3 bufferT3, int count) buffer) | |||
{ | |||
bufferT1 = buffer1; | |||
bufferT2 = buffer2; | |||
bufferT3 = buffer3; | |||
count = this.count; | |||
return new BT<BufferT1, BufferT2, BufferT3>(buffer); | |||
} | |||
} | |||
@@ -186,16 +195,15 @@ namespace Svelto.ECS | |||
public readonly BufferT1 buffer; | |||
public readonly int count; | |||
public BT(BufferT1 bufferT1, uint count) : this() | |||
BT(in (BufferT1 bufferT1, int count) buffer) : this() | |||
{ | |||
this.buffer = bufferT1; | |||
this.count = (int) count; | |||
this.buffer = buffer.bufferT1; | |||
count = buffer.count; | |||
} | |||
public void Deconstruct(out BufferT1 bufferT1, out int count) | |||
public static implicit operator BT<BufferT1>(in (BufferT1 bufferT1, int count) buffer) | |||
{ | |||
bufferT1 = buffer; | |||
count = this.count; | |||
return new BT<BufferT1>(buffer); | |||
} | |||
public static implicit operator BufferT1(BT<BufferT1> t) => t.buffer; | |||
@@ -207,18 +215,17 @@ namespace Svelto.ECS | |||
public readonly BufferT2 buffer2; | |||
public readonly int count; | |||
public BT(BufferT1 bufferT1, BufferT2 bufferT2, uint count) : this() | |||
BT(in (BufferT1 bufferT1, BufferT2 bufferT2, int count) buffer) : this() | |||
{ | |||
this.buffer1 = bufferT1; | |||
this.buffer2 = bufferT2; | |||
this.count = (int) count; | |||
buffer1 = buffer.bufferT1; | |||
buffer2 = buffer.bufferT2; | |||
count = buffer.count; | |||
} | |||
public void Deconstruct(out BufferT1 bufferT1, out BufferT2 bufferT2, out int count) | |||
public static implicit operator BT<BufferT1, BufferT2>( | |||
in (BufferT1 bufferT1, BufferT2 bufferT2, int count) buffer) | |||
{ | |||
bufferT1 = buffer1; | |||
bufferT2 = buffer2; | |||
count = this.count; | |||
return new BT<BufferT1, BufferT2>(buffer); | |||
} | |||
} | |||
} |
@@ -129,7 +129,7 @@ namespace Svelto.ECS | |||
uint index = 0; | |||
foreach (var couple in xtraComponents) | |||
newComponents[index++] = couple.Key.value; | |||
newComponents[index++] = couple.key.type; | |||
} | |||
IComponentBuilder[] componentBuilders = | |||
@@ -4,8 +4,8 @@ namespace Svelto.ECS | |||
{ | |||
public static bool IsUnmanaged(this IEntityDescriptor descriptor) | |||
{ | |||
foreach (var component in descriptor.componentsToBuild) | |||
if (component.isUnmanaged == false) | |||
foreach (IComponentBuilder component in descriptor.componentsToBuild) | |||
if (component.GetEntityComponentType() != typeof(EntityInfoComponent) && component.isUnmanaged == false) | |||
return false; | |||
return true; | |||
@@ -1,6 +1,3 @@ | |||
using System; | |||
using Svelto.ECS.Serialization; | |||
namespace Svelto.ECS | |||
{ | |||
/// <summary> | |||
@@ -25,9 +22,11 @@ namespace Svelto.ECS | |||
{ | |||
static ExtendibleEntityDescriptor() | |||
{ | |||
if (typeof(ISerializableEntityDescriptor).IsAssignableFrom(typeof(TType))) | |||
throw new Exception( | |||
$"SerializableEntityDescriptors cannot be used as base entity descriptor: {typeof(TType)}"); | |||
//I am removing this check because in reality there is not a strong reason to forbid it and | |||
//furthermore it's already possible to extend a SerializableEntityDescriptor through DynamicEntityDescriptor | |||
// if (typeof(ISerializableEntityDescriptor).IsAssignableFrom(typeof(TType))) | |||
// throw new Exception( | |||
// $"SerializableEntityDescriptors cannot be used as base entity descriptor: {typeof(TType)}"); | |||
} | |||
protected ExtendibleEntityDescriptor(IComponentBuilder[] extraEntities) | |||
@@ -54,7 +53,6 @@ namespace Svelto.ECS | |||
return this; | |||
} | |||
protected void Add<T>() where T : struct, IEntityComponent | |||
{ | |||
_dynamicDescriptor.Add<T>(); | |||
@@ -2,7 +2,7 @@ | |||
{ | |||
public abstract class GenericEntityDescriptor<T> : IEntityDescriptor where T : struct, IEntityComponent | |||
{ | |||
internal static readonly IComponentBuilder[] _componentBuilders; | |||
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 | |||
{ | |||
internal static readonly IComponentBuilder[] _componentBuilders; | |||
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 | |||
{ | |||
internal static readonly IComponentBuilder[] _componentBuilders; | |||
static readonly IComponentBuilder[] _componentBuilders; | |||
static GenericEntityDescriptor() | |||
{ | |||
@@ -1,4 +1,3 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using Svelto.DataStructures; | |||
@@ -10,11 +9,15 @@ namespace Svelto.ECS.Internal | |||
(EGID egid, EnginesRoot.DoubleBufferedEntitiesToAdd groupEntitiesToAdd, IComponentBuilder[] componentsToBuild | |||
, IEnumerable<object> implementors | |||
#if DEBUG && !PROFILE_SVELTO | |||
, Type descriptorType | |||
, System.Type descriptorType | |||
#endif | |||
) | |||
{ | |||
var group = FetchEntityGroup(egid.groupID, groupEntitiesToAdd); | |||
var group = groupEntitiesToAdd.currentComponentsToAddPerGroup.GetOrAdd( | |||
egid.groupID, () => new FasterDictionary<RefWrapperType, ITypeSafeDictionary>()); | |||
//track the number of entities created so far in the group. | |||
groupEntitiesToAdd.IncrementEntityCount(egid.groupID); | |||
BuildEntitiesAndAddToGroup(egid, group, componentsToBuild, implementors | |||
#if DEBUG && !PROFILE_SVELTO | |||
@@ -25,23 +28,11 @@ namespace Svelto.ECS.Internal | |||
return group; | |||
} | |||
static FasterDictionary<RefWrapperType, ITypeSafeDictionary> FetchEntityGroup | |||
(ExclusiveGroupStruct groupID, EnginesRoot.DoubleBufferedEntitiesToAdd groupEntityComponentsByType) | |||
{ | |||
var group = groupEntityComponentsByType.current.GetOrCreate( | |||
groupID, () => new FasterDictionary<RefWrapperType, ITypeSafeDictionary>()); | |||
//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 | |||
#if DEBUG && !PROFILE_SVELTO | |||
, Type descriptorType | |||
, System.Type descriptorType | |||
#endif | |||
) | |||
{ | |||
@@ -51,7 +42,7 @@ namespace Svelto.ECS.Internal | |||
var numberOfComponents = componentBuilders.Length; | |||
#if DEBUG && !PROFILE_SVELTO | |||
HashSet<Type> types = new HashSet<Type>(); | |||
var types = new HashSet<System.Type>(); | |||
for (var index = 0; index < numberOfComponents; ++index) | |||
{ | |||
@@ -78,7 +69,7 @@ namespace Svelto.ECS.Internal | |||
, IComponentBuilder componentBuilder, IEnumerable<object> implementors) | |||
{ | |||
var entityComponentType = componentBuilder.GetEntityComponentType(); | |||
var safeDictionary = group.GetOrCreate(new RefWrapperType(entityComponentType) | |||
ITypeSafeDictionary safeDictionary = group.GetOrAdd(new RefWrapperType(entityComponentType) | |||
, (ref IComponentBuilder cb) => cb.CreateDictionary(1) | |||
, ref componentBuilder); | |||
@@ -1,58 +1,58 @@ | |||
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, in EntityReference reference) | |||
public EntityInitializer(EGID id, FasterDictionary<RefWrapperType, ITypeSafeDictionary> group, | |||
in EntityReference reference) | |||
{ | |||
_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) | |||
if (_group.TryGetValue(new RefWrapperType(ComponentBuilder<T>.ENTITY_COMPONENT_TYPE), | |||
out var typeSafeDictionary) == false) | |||
return; | |||
var dictionary = (ITypeSafeDictionary<T>) typeSafeDictionary; | |||
var dictionary = (ITypeSafeDictionary<T>)typeSafeDictionary; | |||
#if SLOW_SVELTO_SUBMISSION | |||
if (ComponentBuilder<T>.HAS_EGID) | |||
SetEGIDWithoutBoxing<T>.SetIDWithoutBoxing(ref initializer, _ID); | |||
#endif | |||
if (dictionary.TryFindIndex(_ID.entityID, out var findElementIndex)) | |||
dictionary.GetDirectValueByRef(findElementIndex) = initializer; | |||
} | |||
public ref T GetOrCreate<T>() where T : struct, IEntityComponent | |||
public ref T GetOrAdd<T>() where T : struct, IEntityComponent | |||
{ | |||
ref var entityDictionary = ref _group.GetOrCreate( | |||
ref var entityDictionary = ref _group.GetOrAdd( | |||
new RefWrapperType(ComponentBuilder<T>.ENTITY_COMPONENT_TYPE), TypeSafeDictionaryFactory<T>.Create); | |||
var dictionary = (ITypeSafeDictionary<T>) entityDictionary; | |||
var dictionary = (ITypeSafeDictionary<T>)entityDictionary; | |||
return ref dictionary.GetOrCreate(_ID.entityID); | |||
return ref dictionary.GetOrAdd(_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]; | |||
.GetValueByRef(_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; | |||
var dictionary = (ITypeSafeDictionary<T>)typeSafeDictionary; | |||
if (dictionary.ContainsKey(_ID.entityID)) | |||
return true; | |||
@@ -1,6 +1,5 @@ | |||
using System.Runtime.CompilerServices; | |||
using Svelto.Common; | |||
using Svelto.DataStructures; | |||
using Svelto.DataStructures.Native; | |||
using Svelto.ECS.DataStructures; | |||
using Svelto.ECS.Reference; | |||
@@ -12,7 +11,7 @@ namespace Svelto.ECS | |||
// find the last known EGID from last entity submission. | |||
public partial class EnginesRoot | |||
{ | |||
public struct LocatorMap | |||
public struct EntityReferenceMap | |||
{ | |||
internal EntityReference ClaimReference() | |||
{ | |||
@@ -78,11 +77,12 @@ namespace Svelto.ECS | |||
// Update reverse map from egid to locator. | |||
var groupMap = | |||
_egidToReferenceMap.GetOrCreate(egid.groupID | |||
_egidToReferenceMap.GetOrAdd(egid.groupID | |||
, () => new SharedSveltoDictionaryNative<uint, EntityReference>(0)); | |||
groupMap[egid.entityID] = reference; | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
internal void UpdateEntityReference(EGID from, EGID to) | |||
{ | |||
var reference = FetchAndRemoveReference(@from); | |||
@@ -90,11 +90,12 @@ namespace Svelto.ECS | |||
_entityReferenceMap[reference.index].egid = to; | |||
var groupMap = | |||
_egidToReferenceMap.GetOrCreate( | |||
_egidToReferenceMap.GetOrAdd( | |||
to.groupID, () => new SharedSveltoDictionaryNative<uint, EntityReference>(0)); | |||
groupMap[to.entityID] = reference; | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
internal void RemoveEntityReference(EGID egid) | |||
{ | |||
var reference = FetchAndRemoveReference(@egid); | |||
@@ -108,6 +109,7 @@ namespace Svelto.ECS | |||
_nextFreeIndex.Set((int)reference.index); | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
EntityReference FetchAndRemoveReference(EGID @from) | |||
{ | |||
var egidToReference = _egidToReferenceMap[@from.groupID]; | |||
@@ -117,6 +119,7 @@ namespace Svelto.ECS | |||
return reference; | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
internal void RemoveAllGroupReferenceLocators(ExclusiveGroupStruct groupId) | |||
{ | |||
if (_egidToReferenceMap.TryGetValue(groupId, out var groupMap) == false) | |||
@@ -125,11 +128,12 @@ namespace Svelto.ECS | |||
// 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)); | |||
RemoveEntityReference(new EGID(item.key, groupId)); | |||
_egidToReferenceMap.Remove(groupId); | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
internal void UpdateAllGroupReferenceLocators(ExclusiveGroupStruct fromGroupId, ExclusiveGroupStruct toGroupId) | |||
{ | |||
if (_egidToReferenceMap.TryGetValue(fromGroupId, out var groupMap) == false) | |||
@@ -138,7 +142,7 @@ namespace Svelto.ECS | |||
// 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)); | |||
UpdateEntityReference(new EGID(item.key, fromGroupId), new EGID(item.key, toGroupId)); | |||
_egidToReferenceMap.Remove(fromGroupId); | |||
} | |||
@@ -188,8 +192,8 @@ namespace Svelto.ECS | |||
internal void PreallocateReferenceMaps(ExclusiveGroupStruct groupID, uint size) | |||
{ | |||
_egidToReferenceMap | |||
.GetOrCreate(groupID, () => new SharedSveltoDictionaryNative<uint, EntityReference>(size)) | |||
.ResizeTo(size); | |||
.GetOrAdd(groupID, () => new SharedSveltoDictionaryNative<uint, EntityReference>(size)) | |||
.EnsureCapacity(size); | |||
_entityReferenceMap.Resize(size); | |||
} | |||
@@ -211,7 +215,7 @@ namespace Svelto.ECS | |||
_entityReferenceMap.Dispose(); | |||
foreach (var element in _egidToReferenceMap) | |||
element.Value.Dispose(); | |||
element.value.Dispose(); | |||
_egidToReferenceMap.Dispose(); | |||
} | |||
@@ -222,8 +226,8 @@ namespace Svelto.ECS | |||
_egidToReferenceMap; | |||
} | |||
internal LocatorMap entityLocator => _entityLocator; | |||
EntityReferenceMap entityLocator => _entityLocator; | |||
LocatorMap _entityLocator; | |||
EntityReferenceMap _entityLocator; | |||
} | |||
} |
@@ -1,5 +1,4 @@ | |||
using System.Runtime.CompilerServices; | |||
using Svelto.ECS.Reference; | |||
namespace Svelto.ECS | |||
{ | |||
@@ -18,7 +17,7 @@ namespace Svelto.ECS | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public EnginesRoot.LocatorMap GetEntityLocator() | |||
public EnginesRoot.EntityReferenceMap GetEntityReferenceMap() | |||
{ | |||
return _entityReferencesMap; | |||
} | |||
@@ -30,6 +30,8 @@ namespace Svelto.ECS | |||
return obj1._GID != obj2._GID; | |||
} | |||
public override int GetHashCode() { return _GID.GetHashCode(); } | |||
public EntityReference(uint uniqueId) : this(uniqueId, 0) {} | |||
public EntityReference(uint uniqueId, uint version) : this() | |||
@@ -59,7 +61,7 @@ namespace Svelto.ECS | |||
return entitiesDB.GetEGID(this); | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public bool ToEGID(EntitiesDB entitiesDB, out EGID egid) | |||
{ | |||
@@ -6,9 +6,9 @@ namespace Svelto.ECS.Schedulers | |||
public abstract void Dispose(); | |||
public abstract bool paused { get; set; } | |||
public uint iteration { get; protected internal set; } | |||
public bool paused { get; set; } | |||
public uint iteration { get; protected internal set; } | |||
internal bool isRunning; | |||
} | |||
} |
@@ -1,65 +1,10 @@ | |||
using System; | |||
namespace Svelto.ECS | |||
namespace Svelto.ECS | |||
{ | |||
#pragma warning disable 660,661 | |||
struct EntitySubmitOperation | |||
#pragma warning restore 660,661 | |||
: IEquatable<EntitySubmitOperation> | |||
{ | |||
public readonly EntitySubmitOperationType type; | |||
public readonly IComponentBuilder[] builders; | |||
public readonly EGID fromID; | |||
public readonly EGID toID; | |||
#if DEBUG && !PROFILE_SVELTO | |||
public System.Diagnostics.StackFrame trace; | |||
#endif | |||
public EntitySubmitOperation(EntitySubmitOperationType operation, EGID from, EGID to, | |||
IComponentBuilder[] builders = null) | |||
{ | |||
type = operation; | |||
this.builders = builders; | |||
fromID = from; | |||
toID = to; | |||
#if DEBUG && !PROFILE_SVELTO | |||
trace = default; | |||
#endif | |||
} | |||
public EntitySubmitOperation | |||
(EntitySubmitOperationType operation, ExclusiveGroupStruct @group | |||
, IComponentBuilder[] descriptorComponentsToBuild):this() | |||
{ | |||
type = operation; | |||
this.builders = descriptorComponentsToBuild; | |||
fromID = new EGID(0, group); | |||
#if DEBUG && !PROFILE_SVELTO | |||
trace = default; | |||
#endif | |||
} | |||
public static bool operator ==(EntitySubmitOperation obj1, EntitySubmitOperation obj2) | |||
{ | |||
return obj1.Equals(obj2); | |||
} | |||
public static bool operator !=(EntitySubmitOperation obj1, EntitySubmitOperation obj2) | |||
{ | |||
return obj1.Equals(obj2) == false; | |||
} | |||
public bool Equals(EntitySubmitOperation other) | |||
{ | |||
return type == other.type && fromID == other.fromID && toID == other.toID; | |||
} | |||
} | |||
enum EntitySubmitOperationType | |||
enum EntitySubmitOperationType | |||
{ | |||
Swap, | |||
Remove, | |||
RemoveGroup, | |||
SwapGroup | |||
SwapGroup | |||
} | |||
} |
@@ -0,0 +1,160 @@ | |||
using Svelto.DataStructures; | |||
using Svelto.DataStructures.Native; | |||
using Svelto.ECS.DataStructures; | |||
using Svelto.ECS.Internal; | |||
namespace Svelto.ECS | |||
{ | |||
public partial class EnginesRoot | |||
{ | |||
void InitFilters() | |||
{ | |||
_transientEntityFilters = new SharedSveltoDictionaryNative<long, EntityFilterCollection>(0); | |||
_persistentEntityFilters = new SharedSveltoDictionaryNative<long, EntityFilterCollection>(0); | |||
_indicesOfPersistentFiltersUsedByThisComponent = | |||
new SharedSveltoDictionaryNative<NativeRefWrapperType, NativeDynamicArrayCast<int>>(0); | |||
} | |||
void DisposeFilters() | |||
{ | |||
foreach (var filter in _transientEntityFilters) | |||
{ | |||
filter.value.Dispose(); | |||
} | |||
foreach (var filter in _persistentEntityFilters) | |||
{ | |||
filter.value.Dispose(); | |||
} | |||
foreach (var filter in _indicesOfPersistentFiltersUsedByThisComponent) | |||
{ | |||
filter.value.Dispose(); | |||
} | |||
_transientEntityFilters.Dispose(); | |||
_persistentEntityFilters.Dispose(); | |||
_indicesOfPersistentFiltersUsedByThisComponent.Dispose(); | |||
} | |||
void ClearTransientFilters() | |||
{ | |||
foreach (var filter in _transientEntityFilters) | |||
{ | |||
filter.value.Clear(); | |||
} | |||
} | |||
void RemoveEntityFromPersistentFilters(FasterList<(uint, string)> entityIDs, ExclusiveGroupStruct fromGroup, | |||
RefWrapperType refWrapperType, ITypeSafeDictionary fromDic) | |||
{ | |||
//is there any filter used by this component? | |||
if (_indicesOfPersistentFiltersUsedByThisComponent.TryGetValue(new NativeRefWrapperType(refWrapperType), | |||
out NativeDynamicArrayCast<int> listOfFilters)) | |||
{ | |||
var numberOfFilters = listOfFilters.count; | |||
for (int filterIndex = 0; filterIndex < numberOfFilters; ++filterIndex) | |||
{ | |||
//we are going to remove multiple entities, this means that the dictionary count would decrease | |||
//for each entity removed from each filter | |||
//we need to keep a copy to reset to the original count for each filter | |||
var currentLastIndex = (uint)fromDic.count - 1; | |||
var filters = _persistentEntityFilters.unsafeValues; | |||
var persistentFilter = filters[listOfFilters[filterIndex]]._filtersPerGroup; | |||
if (persistentFilter.TryGetValue(fromGroup, out var groupFilter)) | |||
{ | |||
var entitiesCount = entityIDs.count; | |||
for (int entityIndex = 0; entityIndex < entitiesCount; ++entityIndex) | |||
{ | |||
uint fromentityID = entityIDs[entityIndex].Item1; | |||
var fromIndex = fromDic.GetIndex(fromentityID); | |||
groupFilter.RemoveWithSwapBack(fromentityID, fromIndex, currentLastIndex--); | |||
} | |||
} | |||
} | |||
} | |||
} | |||
//this method is called by the framework only if listOfFilters.count > 0 | |||
void SwapEntityBetweenPersistentFilters(FasterList<(uint, uint, string)> fromEntityToEntityIDs, | |||
FasterDictionary<uint, uint> beforeSubmissionFromIDs, ITypeSafeDictionary toComponentsDictionary, | |||
ExclusiveGroupStruct fromGroup, ExclusiveGroupStruct toGroup, uint fromDictionaryCount, | |||
NativeDynamicArrayCast<int> listOfFilters) | |||
{ | |||
DBC.ECS.Check.Require(listOfFilters.count > 0, "why are you calling this with an empty list?"); | |||
var numberOfFilters = listOfFilters.count; | |||
/// fromEntityToEntityIDs are the ID of the entities to swap from the from group to the to group. | |||
/// for this component type. for each component type, there is only one set of fromEntityToEntityIDs | |||
/// per from/to group. | |||
/// The complexity of this function is that the ToDictionary is already updated, so the toIndex | |||
/// is actually correct and guaranteed to be valid. However the beforeSubmissionFromIDs are the | |||
/// indices of the entities in the FromDictionary BEFORE the submission happens, so before the | |||
/// entities are actually removed from the dictionary. | |||
for (int filterIndex = 0; filterIndex < numberOfFilters; ++filterIndex) | |||
{ | |||
//we are going to remove multiple entities, this means that the dictionary count would decrease | |||
//for each entity removed from each filter | |||
//we need to keep a copy to reset to the original count for each filter | |||
var currentLastIndex = fromDictionaryCount; | |||
//if the group has a filter linked: | |||
EntityFilterCollection persistentFilter = | |||
_persistentEntityFilters.unsafeValues[listOfFilters[filterIndex]]; | |||
if (persistentFilter._filtersPerGroup.TryGetValue(fromGroup, out var fromGroupFilter)) | |||
{ | |||
EntityFilterCollection.GroupFilters groupFilterTo = default; | |||
foreach (var (fromEntityID, toEntityID, _) in fromEntityToEntityIDs) | |||
{ | |||
//if there is an entity, it must be moved to the to filter | |||
if (fromGroupFilter.Exists(fromEntityID) == true) | |||
{ | |||
var toIndex = toComponentsDictionary.GetIndex(toEntityID); | |||
if (groupFilterTo.isValid == false) | |||
groupFilterTo = persistentFilter.GetGroupFilter(toGroup); | |||
groupFilterTo.Add(toEntityID, toIndex); | |||
} | |||
} | |||
foreach (var (fromEntityID, _, _) in fromEntityToEntityIDs) | |||
{ | |||
//fromIndex is the same of the index in the filter if the entity is in the filter, but | |||
//we need to update the entity index of the last entity swapped from the dictionary even | |||
//in the case when the fromEntity is not present in the filter. | |||
uint fromIndex; //index in the from dictionary | |||
if (fromGroupFilter.Exists(fromEntityID)) | |||
fromIndex = fromGroupFilter._entityIDToDenseIndex[fromEntityID]; | |||
else | |||
fromIndex = beforeSubmissionFromIDs[fromEntityID]; | |||
//Removing an entity from the dictionary may affect the index of the last entity in the | |||
//values dictionary array, so we need to to update the indices of the affected entities. | |||
//must be outside because from may not be present in the filter, but last index is | |||
//for each entity removed from the from group, I have to update it's index in the | |||
//from filter. An entity removed from the DB is always swapped back, which means | |||
//it's current position is taken by the last entity in the dictionary array. | |||
//this means that the index of the last entity will change to the index of the | |||
//replaced entity | |||
fromGroupFilter.RemoveWithSwapBack(fromEntityID, fromIndex, currentLastIndex--); | |||
} | |||
} | |||
} | |||
} | |||
internal SharedSveltoDictionaryNative<long, EntityFilterCollection> _transientEntityFilters; | |||
internal SharedSveltoDictionaryNative<long, EntityFilterCollection> _persistentEntityFilters; | |||
internal SharedSveltoDictionaryNative<NativeRefWrapperType, NativeDynamicArrayCast<int>> | |||
_indicesOfPersistentFiltersUsedByThisComponent; | |||
} | |||
} |
@@ -0,0 +1,349 @@ | |||
using System; | |||
using System.Threading; | |||
using Svelto.Common; | |||
using Svelto.DataStructures; | |||
using Svelto.DataStructures.Native; | |||
using Svelto.ECS.DataStructures; | |||
namespace Svelto.ECS | |||
{ | |||
public struct FilterContextID | |||
{ | |||
public readonly uint id; | |||
internal FilterContextID(uint id) | |||
{ | |||
DBC.ECS.Check.Require(id < ushort.MaxValue, "too many types registered, HOW :)"); | |||
this.id = id; | |||
} | |||
} | |||
public readonly struct CombinedFilterID | |||
{ | |||
internal readonly long id; | |||
public FilterContextID contextID => new FilterContextID((uint)((id & 0xFFFF0000) >> 16)); | |||
public uint filterID => (uint)(id >> 32); | |||
public CombinedFilterID(int filterID, FilterContextID contextID) | |||
{ | |||
id = (long)filterID << 32 | (uint)contextID.id << 16; | |||
} | |||
public static implicit operator CombinedFilterID((int filterID, FilterContextID contextID) data) | |||
{ | |||
return new CombinedFilterID(data.filterID, data.contextID); | |||
} | |||
} | |||
//this cannot be inside EntitiesDB otherwise it will cause hashing of reference in Burst | |||
public class Internal_FilterHelper | |||
{ | |||
//since the user can choose their own filterID, in order to avoid collisions between | |||
//filters of the same type, the FilterContext is provided. The type is identified through | |||
//TypeCounter | |||
public static long CombineFilterIDs<T>(CombinedFilterID combinedFilterID) where T: struct, IEntityComponent | |||
{ | |||
var id = (uint)ComponentID<T>.id.Data; | |||
var combineFilterIDs = (long)combinedFilterID.id | id; | |||
return combineFilterIDs; | |||
} | |||
} | |||
public partial class EntitiesDB | |||
{ | |||
public SveltoFilters GetFilters() | |||
{ | |||
return new SveltoFilters(_enginesRoot._persistentEntityFilters, | |||
_enginesRoot._indicesOfPersistentFiltersUsedByThisComponent, _enginesRoot._transientEntityFilters); | |||
} | |||
/// <summary> | |||
/// this whole structure is usable inside DOTS JOBS and BURST | |||
/// </summary> | |||
public readonly struct SveltoFilters | |||
{ | |||
static readonly SharedStaticWrapper<int, Internal_FilterHelper> uniqueContextID; | |||
#if UNITY_BURST | |||
[Unity.Burst.BurstDiscard] | |||
//SharedStatic values must be initialized from not burstified code | |||
#endif | |||
public static FilterContextID GetNewContextID() | |||
{ | |||
return new FilterContextID((uint)Interlocked.Increment(ref uniqueContextID.Data)); | |||
} | |||
public SveltoFilters(SharedSveltoDictionaryNative<long, EntityFilterCollection> persistentEntityFilters, | |||
SharedSveltoDictionaryNative<NativeRefWrapperType, NativeDynamicArrayCast<int>> | |||
indicesOfPersistentFiltersUsedByThisComponent, | |||
SharedSveltoDictionaryNative<long, EntityFilterCollection> transientEntityFilters) | |||
{ | |||
_persistentEntityFilters = persistentEntityFilters; | |||
_indicesOfPersistentFiltersUsedByThisComponent = indicesOfPersistentFiltersUsedByThisComponent; | |||
_transientEntityFilters = transientEntityFilters; | |||
} | |||
#if UNITY_BURST | |||
public ref EntityFilterCollection GetOrCreatePersistentFilter<T>(int filterID, | |||
FilterContextID filterContextId, NativeRefWrapperType typeRef) where T : unmanaged, IEntityComponent | |||
{ | |||
return ref GetOrCreatePersistentFilter<T>(new CombinedFilterID(filterID, filterContextId), typeRef); | |||
} | |||
public ref EntityFilterCollection GetOrCreatePersistentFilter<T>(CombinedFilterID filterID, | |||
NativeRefWrapperType typeRef) where T : unmanaged, IEntityComponent | |||
{ | |||
long combineFilterIDs = Internal_FilterHelper.CombineFilterIDs<T>(filterID); | |||
if (_persistentEntityFilters.TryFindIndex(combineFilterIDs, out var index) == true) | |||
return ref _persistentEntityFilters.GetDirectValueByRef(index); | |||
_persistentEntityFilters.Add(combineFilterIDs, new EntityFilterCollection(filterID)); | |||
var lastIndex = _persistentEntityFilters.count - 1; | |||
if (_indicesOfPersistentFiltersUsedByThisComponent.TryFindIndex(typeRef, out var getIndex) == false) | |||
{ | |||
var newArray = new NativeDynamicArrayCast<int>(1, Allocator.Persistent); | |||
newArray.Add(lastIndex); | |||
_indicesOfPersistentFiltersUsedByThisComponent.Add(typeRef, newArray); | |||
} | |||
else | |||
{ | |||
ref var array = ref _indicesOfPersistentFiltersUsedByThisComponent.GetDirectValueByRef(getIndex); | |||
array.Add(lastIndex); | |||
} | |||
return ref _persistentEntityFilters.GetDirectValueByRef((uint)lastIndex); | |||
} | |||
#endif | |||
/// <summary> | |||
/// Create a persistent filter. Persistent filters are not deleted after each submission, | |||
/// however they have a maintenance cost that must be taken into account and will affect | |||
/// entities submission performance. | |||
/// </summary> | |||
/// <typeparam name="T"></typeparam> | |||
/// <returns></returns> | |||
#if UNITY_BURST && UNITY_COLLECTIONS | |||
[Unity.Collections.NotBurstCompatible] | |||
#endif | |||
public ref EntityFilterCollection GetOrCreatePersistentFilter<T>(int filterID, FilterContextID filterContextId) | |||
where T : unmanaged, IEntityComponent | |||
{ | |||
return ref GetOrCreatePersistentFilter<T>(new CombinedFilterID(filterID, filterContextId)); | |||
} | |||
#if UNITY_BURST && UNITY_COLLECTIONS | |||
[Unity.Collections.NotBurstCompatible] | |||
#endif | |||
public ref EntityFilterCollection GetOrCreatePersistentFilter<T>(CombinedFilterID filterID) | |||
where T : unmanaged, IEntityComponent | |||
{ | |||
long combineFilterIDs = Internal_FilterHelper.CombineFilterIDs<T>(filterID); | |||
if (_persistentEntityFilters.TryFindIndex(combineFilterIDs, out var index) == true) | |||
return ref _persistentEntityFilters.GetDirectValueByRef(index); | |||
var typeRef = TypeRefWrapper<T>.wrapper; | |||
var filterCollection = new EntityFilterCollection(filterID); | |||
_persistentEntityFilters.Add(combineFilterIDs, filterCollection); | |||
var lastIndex = _persistentEntityFilters.count - 1; | |||
_indicesOfPersistentFiltersUsedByThisComponent.GetOrAdd(new NativeRefWrapperType(typeRef), | |||
() => new NativeDynamicArrayCast<int>(1, Svelto.Common.Allocator.Persistent)).Add(lastIndex); | |||
return ref _persistentEntityFilters.GetDirectValueByRef((uint)lastIndex); | |||
} | |||
public ref EntityFilterCollection GetPersistentFilter<T>(int filterID, FilterContextID filterContextId) | |||
where T : unmanaged, IEntityComponent | |||
{ | |||
return ref GetPersistentFilter<T>(new CombinedFilterID(filterID, filterContextId)); | |||
} | |||
public ref EntityFilterCollection GetPersistentFilter<T>(CombinedFilterID filterID) | |||
where T : unmanaged, IEntityComponent | |||
{ | |||
long combineFilterIDs = Internal_FilterHelper.CombineFilterIDs<T>(filterID); | |||
if (_persistentEntityFilters.TryFindIndex(combineFilterIDs, out var index) == true) | |||
return ref _persistentEntityFilters.GetDirectValueByRef(index); | |||
throw new Exception("filter not found"); | |||
} | |||
public bool TryGetPersistentFilter<T>(CombinedFilterID combinedFilterID, out EntityFilterCollection entityCollection) | |||
where T : unmanaged, IEntityComponent | |||
{ | |||
long combineFilterIDs = Internal_FilterHelper.CombineFilterIDs<T>(combinedFilterID); | |||
if (_persistentEntityFilters.TryFindIndex(combineFilterIDs, out var index) == true) | |||
{ | |||
entityCollection = _persistentEntityFilters.GetDirectValueByRef(index); | |||
return true; | |||
} | |||
entityCollection = default; | |||
return false; | |||
} | |||
public EntityFilterCollectionsEnumerator GetPersistentFilters<T>() where T : unmanaged, IEntityComponent | |||
{ | |||
if (_indicesOfPersistentFiltersUsedByThisComponent.TryFindIndex( | |||
new NativeRefWrapperType(new RefWrapperType(typeof(T))), out var index) == true) | |||
return new EntityFilterCollectionsEnumerator( | |||
_indicesOfPersistentFiltersUsedByThisComponent.GetDirectValueByRef(index), | |||
_persistentEntityFilters); | |||
throw new Exception($"no filters associated with the type {TypeCache<T>.name}"); | |||
} | |||
public EntityFilterCollectionsWithContextEnumerator GetPersistentFilters<T>(FilterContextID filterContextId) | |||
{ | |||
if (_indicesOfPersistentFiltersUsedByThisComponent.TryFindIndex( | |||
new NativeRefWrapperType(new RefWrapperType(typeof(T))), out var index) == true) | |||
return new EntityFilterCollectionsWithContextEnumerator( | |||
_indicesOfPersistentFiltersUsedByThisComponent.GetDirectValueByRef(index), | |||
_persistentEntityFilters, filterContextId); | |||
throw new Exception($"no filters associated with the type {TypeCache<T>.name}"); | |||
} | |||
public bool TryGetPersistentFilters<T>(FilterContextID filterContextId, out EntityFilterCollectionsWithContextEnumerator enumerator) | |||
{ | |||
if (_indicesOfPersistentFiltersUsedByThisComponent.TryFindIndex( | |||
new NativeRefWrapperType(new RefWrapperType(typeof(T))), out var index) == true) | |||
{ | |||
enumerator = new EntityFilterCollectionsWithContextEnumerator( | |||
_indicesOfPersistentFiltersUsedByThisComponent.GetDirectValueByRef(index), | |||
_persistentEntityFilters, filterContextId); | |||
return true; | |||
} | |||
enumerator = default; | |||
return false; | |||
} | |||
public struct EntityFilterCollectionsEnumerator | |||
{ | |||
public EntityFilterCollectionsEnumerator(NativeDynamicArrayCast<int> getDirectValueByRef, | |||
SharedSveltoDictionaryNative<long, EntityFilterCollection> sharedSveltoDictionaryNative) : this() | |||
{ | |||
_getDirectValueByRef = getDirectValueByRef; | |||
_sharedSveltoDictionaryNative = sharedSveltoDictionaryNative; | |||
} | |||
public EntityFilterCollectionsEnumerator GetEnumerator() | |||
{ | |||
return this; | |||
} | |||
public bool MoveNext() | |||
{ | |||
if (_currentIndex < _getDirectValueByRef.count) | |||
{ | |||
_currentIndex++; | |||
return true; | |||
} | |||
return false; | |||
} | |||
public ref EntityFilterCollection Current => | |||
ref _sharedSveltoDictionaryNative.GetDirectValueByRef((uint)_currentIndex - 1); | |||
readonly NativeDynamicArrayCast<int> _getDirectValueByRef; | |||
readonly SharedSveltoDictionaryNative<long, EntityFilterCollection> _sharedSveltoDictionaryNative; | |||
int _currentIndex; | |||
} | |||
public struct EntityFilterCollectionsWithContextEnumerator | |||
{ | |||
public EntityFilterCollectionsWithContextEnumerator(NativeDynamicArrayCast<int> getDirectValueByRef, | |||
SharedSveltoDictionaryNative<long, EntityFilterCollection> sharedSveltoDictionaryNative, | |||
FilterContextID filterContextId) : this() | |||
{ | |||
_getDirectValueByRef = getDirectValueByRef; | |||
_sharedSveltoDictionaryNative = sharedSveltoDictionaryNative; | |||
_filterContextId = filterContextId; | |||
} | |||
public EntityFilterCollectionsWithContextEnumerator GetEnumerator() | |||
{ | |||
return this; | |||
} | |||
public bool MoveNext() | |||
{ | |||
while (_currentIndex++ < _getDirectValueByRef.count && | |||
_sharedSveltoDictionaryNative.GetDirectValueByRef((uint)_currentIndex - 1).combinedFilterID | |||
.contextID.id != _filterContextId.id) ; | |||
if (_currentIndex - 1 < _getDirectValueByRef.count) | |||
return true; | |||
return false; | |||
} | |||
public ref EntityFilterCollection Current => | |||
ref _sharedSveltoDictionaryNative.GetDirectValueByRef((uint)_currentIndex - 1); | |||
readonly NativeDynamicArrayCast<int> _getDirectValueByRef; | |||
readonly SharedSveltoDictionaryNative<long, EntityFilterCollection> _sharedSveltoDictionaryNative; | |||
readonly FilterContextID _filterContextId; | |||
int _currentIndex; | |||
} | |||
/// <summary> | |||
/// Creates a transient filter. Transient filters are deleted after each submission | |||
/// </summary> | |||
/// <typeparam name="T"></typeparam> | |||
/// <returns></returns> | |||
public ref EntityFilterCollection GetOrCreateTransientFilter<T>(CombinedFilterID filterID) | |||
where T : unmanaged, IEntityComponent | |||
{ | |||
var combineFilterIDs = Internal_FilterHelper.CombineFilterIDs<T>(filterID); | |||
if (_transientEntityFilters.TryFindIndex(combineFilterIDs, out var index)) | |||
return ref _transientEntityFilters.GetDirectValueByRef(index); | |||
var filterCollection = new EntityFilterCollection(filterID); | |||
_transientEntityFilters.Add(combineFilterIDs, filterCollection); | |||
return ref _transientEntityFilters.GetDirectValueByRef((uint)(_transientEntityFilters.count - 1)); | |||
} | |||
public bool TryGetTransientFilter<T>(CombinedFilterID filterID, out EntityFilterCollection entityCollection) | |||
where T : unmanaged, IEntityComponent | |||
{ | |||
var combineFilterIDs = Internal_FilterHelper.CombineFilterIDs<T>(filterID); | |||
if (_transientEntityFilters.TryFindIndex(combineFilterIDs, out var index)) | |||
{ | |||
entityCollection = _transientEntityFilters.GetDirectValueByRef(index); | |||
return true; | |||
} | |||
entityCollection = default; | |||
return false; | |||
} | |||
readonly SharedSveltoDictionaryNative<long, EntityFilterCollection> _persistentEntityFilters; | |||
readonly SharedSveltoDictionaryNative<NativeRefWrapperType, NativeDynamicArrayCast<int>> | |||
_indicesOfPersistentFiltersUsedByThisComponent; | |||
readonly SharedSveltoDictionaryNative<long, EntityFilterCollection> _transientEntityFilters; | |||
} | |||
} | |||
} |
@@ -1,10 +1,12 @@ | |||
using DBC.ECS; | |||
using Svelto.DataStructures; | |||
using Svelto.DataStructures.Native; | |||
namespace Svelto.ECS | |||
{ | |||
/// <summary> | |||
/// This feature must be eventually tied to the new ExclusiveGroup that won't allow the use of custom EntitiesID | |||
/// This feature must be eventually tied to the new ExclusiveGroup that won't allow the use of | |||
/// custom EntitiesID | |||
/// The filters could be updated when entities buffer changes during the submission, while now this process | |||
/// is completely manual. | |||
/// Making this working properly is not in my priorities right now, as I will need to add the new group type | |||
@@ -12,15 +14,24 @@ namespace Svelto.ECS | |||
/// </summary> | |||
public partial class EntitiesDB | |||
{ | |||
public readonly struct Filters | |||
FasterDictionary<RefWrapperType, FasterDictionary<ExclusiveGroupStruct, LegacyGroupFilters>> _filters => | |||
_enginesRoot._groupFilters; | |||
public LegacyFilters GetLegacyFilters() | |||
{ | |||
public Filters | |||
(FasterDictionary<RefWrapperType, FasterDictionary<ExclusiveGroupStruct, GroupFilters>> filters) | |||
return new LegacyFilters(_filters); | |||
} | |||
public readonly struct LegacyFilters | |||
{ | |||
public LegacyFilters( | |||
FasterDictionary<RefWrapperType, FasterDictionary<ExclusiveGroupStruct, LegacyGroupFilters>> | |||
filtersLegacy) | |||
{ | |||
_filters = filters; | |||
_filtersLegacy = filtersLegacy; | |||
} | |||
public ref FilterGroup CreateOrGetFilterForGroup<T>(int filterID, ExclusiveGroupStruct groupID) | |||
public ref LegacyFilterGroup CreateOrGetFilterForGroup<T>(int filterID, ExclusiveGroupStruct groupID) | |||
where T : struct, IEntityComponent | |||
{ | |||
var refWrapper = TypeRefWrapper<T>.wrapper; | |||
@@ -28,21 +39,9 @@ namespace Svelto.ECS | |||
return ref CreateOrGetFilterForGroup(filterID, groupID, 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)); | |||
return ref filters.CreateOrGetFilter(filterID); | |||
} | |||
public bool HasFiltersForGroup<T>(ExclusiveGroupStruct groupID) where T : struct, IEntityComponent | |||
{ | |||
if (_filters.TryGetValue(TypeRefWrapper<T>.wrapper, out var fasterDictionary) == false) | |||
if (_filtersLegacy.TryGetValue(TypeRefWrapper<T>.wrapper, out var fasterDictionary) == false) | |||
return false; | |||
return fasterDictionary.ContainsKey(groupID); | |||
@@ -51,7 +50,7 @@ namespace Svelto.ECS | |||
public bool HasFilterForGroup<T>(int filterID, ExclusiveGroupStruct groupID) | |||
where T : struct, IEntityComponent | |||
{ | |||
if (_filters.TryGetValue(TypeRefWrapper<T>.wrapper, out var fasterDictionary) == false) | |||
if (_filtersLegacy.TryGetValue(TypeRefWrapper<T>.wrapper, out var fasterDictionary) == false) | |||
return false; | |||
if (fasterDictionary.TryGetValue(groupID, out var result)) | |||
@@ -60,79 +59,77 @@ namespace Svelto.ECS | |||
return false; | |||
} | |||
public ref GroupFilters CreateOrGetFiltersForGroup<T>(ExclusiveGroupStruct groupID) | |||
public ref LegacyGroupFilters CreateOrGetFiltersForGroup<T>(ExclusiveGroupStruct groupID) | |||
where T : struct, IEntityComponent | |||
{ | |||
var fasterDictionary = _filters.GetOrCreate(TypeRefWrapper<T>.wrapper | |||
, () => | |||
new FasterDictionary<ExclusiveGroupStruct, | |||
GroupFilters>()); | |||
var fasterDictionary = _filtersLegacy.GetOrAdd(TypeRefWrapper<T>.wrapper, | |||
() => new FasterDictionary<ExclusiveGroupStruct, LegacyGroupFilters>()); | |||
return ref fasterDictionary.GetOrCreate( | |||
groupID, () => new GroupFilters(new SharedSveltoDictionaryNative<int, FilterGroup>(0), groupID)); | |||
return ref fasterDictionary.GetOrAdd(groupID, | |||
() => new LegacyGroupFilters(new SharedSveltoDictionaryNative<int, LegacyFilterGroup>(0), groupID)); | |||
} | |||
public ref GroupFilters GetFiltersForGroup<T>(ExclusiveGroupStruct groupID) | |||
public ref LegacyGroupFilters GetFiltersForGroup<T>(ExclusiveGroupStruct groupID) | |||
where T : struct, IEntityComponent | |||
{ | |||
#if DEBUG && !PROFILE_SVELTO | |||
if (_filters.ContainsKey(TypeRefWrapper<T>.wrapper) == false) | |||
if (_filtersLegacy.ContainsKey(TypeRefWrapper<T>.wrapper) == false) | |||
throw new ECSException($"trying to fetch not existing filters, type {typeof(T)}"); | |||
if (_filters[TypeRefWrapper<T>.wrapper].ContainsKey(groupID) == false) | |||
if (_filtersLegacy[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].GetValueByRef(groupID); | |||
return ref _filtersLegacy[TypeRefWrapper<T>.wrapper].GetValueByRef(groupID); | |||
} | |||
public ref FilterGroup GetFilterForGroup<T>(int filterId, ExclusiveGroupStruct groupID) | |||
public ref LegacyFilterGroup GetFilterForGroup<T>(int filterId, ExclusiveGroupStruct groupID) | |||
where T : struct, IEntityComponent | |||
{ | |||
#if DEBUG && !PROFILE_SVELTO | |||
if (_filters.ContainsKey(TypeRefWrapper<T>.wrapper) == false) | |||
if (_filtersLegacy.ContainsKey(TypeRefWrapper<T>.wrapper) == false) | |||
throw new ECSException($"trying to fetch not existing filters, type {typeof(T)}"); | |||
if (_filters[TypeRefWrapper<T>.wrapper].ContainsKey(groupID) == false) | |||
if (_filtersLegacy[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); | |||
return ref _filtersLegacy[TypeRefWrapper<T>.wrapper][groupID].GetFilter(filterId); | |||
} | |||
public bool TryGetFilterForGroup<T>(int filterId, ExclusiveGroupStruct groupID, out FilterGroup groupFilter) | |||
where T : struct, IEntityComponent | |||
public bool TryGetFilterForGroup<T>(int filterId, ExclusiveGroupStruct groupID, | |||
out LegacyFilterGroup groupLegacyFilter) where T : struct, IEntityComponent | |||
{ | |||
groupFilter = default; | |||
groupLegacyFilter = default; | |||
if (_filters.TryGetValue(TypeRefWrapper<T>.wrapper, out var fasterDictionary) == false) | |||
if (_filtersLegacy.TryGetValue(TypeRefWrapper<T>.wrapper, out var fasterDictionary) == false) | |||
return false; | |||
if (fasterDictionary.TryGetValue(groupID, out var groupFilters) == false) | |||
return false; | |||
if (groupFilters.TryGetFilter(filterId, out groupFilter) == false) | |||
if (groupFilters.TryGetFilter(filterId, out groupLegacyFilter) == false) | |||
return false; | |||
return true; | |||
} | |||
public bool TryGetFiltersForGroup<T>(ExclusiveGroupStruct groupID, out GroupFilters groupFilters) | |||
where T : struct, IEntityComponent | |||
public bool TryGetFiltersForGroup<T>(ExclusiveGroupStruct groupID, | |||
out LegacyGroupFilters legacyGroupFilters) where T : struct, IEntityComponent | |||
{ | |||
groupFilters = default; | |||
legacyGroupFilters = default; | |||
if (_filters.TryGetValue(TypeRefWrapper<T>.wrapper, out var fasterDictionary) == false) | |||
if (_filtersLegacy.TryGetValue(TypeRefWrapper<T>.wrapper, out var fasterDictionary) == false) | |||
return false; | |||
return fasterDictionary.TryGetValue(groupID, out groupFilters); | |||
return fasterDictionary.TryGetValue(groupID, out legacyGroupFilters); | |||
} | |||
public void ClearFilter<T>(int filterID, ExclusiveGroupStruct exclusiveGroupStruct) | |||
{ | |||
if (_filters.TryGetValue(TypeRefWrapper<T>.wrapper, out var fasterDictionary) == true) | |||
if (_filtersLegacy.TryGetValue(TypeRefWrapper<T>.wrapper, out var fasterDictionary)) | |||
{ | |||
DBC.ECS.Check.Require(fasterDictionary.ContainsKey(exclusiveGroupStruct) | |||
, $"trying to clear filter not present in group {exclusiveGroupStruct}"); | |||
Check.Require(fasterDictionary.ContainsKey(exclusiveGroupStruct), | |||
$"trying to clear filter not present in group {exclusiveGroupStruct}"); | |||
fasterDictionary[exclusiveGroupStruct].ClearFilter(filterID); | |||
} | |||
@@ -140,16 +137,14 @@ namespace Svelto.ECS | |||
public void ClearFilters<T>(int filterID) | |||
{ | |||
if (_filters.TryGetValue(TypeRefWrapper<T>.wrapper, out var fasterDictionary) == true) | |||
{ | |||
if (_filtersLegacy.TryGetValue(TypeRefWrapper<T>.wrapper, out var fasterDictionary)) | |||
foreach (var filtersPerGroup in fasterDictionary) | |||
filtersPerGroup.Value.ClearFilter(filterID); | |||
} | |||
filtersPerGroup.value.ClearFilter(filterID); | |||
} | |||
public void DisposeFilters<T>(ExclusiveGroupStruct exclusiveGroupStruct) | |||
{ | |||
if (_filters.TryGetValue(TypeRefWrapper<T>.wrapper, out var fasterDictionary) == true) | |||
if (_filtersLegacy.TryGetValue(TypeRefWrapper<T>.wrapper, out var fasterDictionary)) | |||
{ | |||
fasterDictionary[exclusiveGroupStruct].DisposeFilters(); | |||
fasterDictionary.Remove(exclusiveGroupStruct); | |||
@@ -158,29 +153,23 @@ namespace Svelto.ECS | |||
public void DisposeFilters<T>() | |||
{ | |||
if (_filters.TryGetValue(TypeRefWrapper<T>.wrapper, out var fasterDictionary) == true) | |||
{ | |||
if (_filtersLegacy.TryGetValue(TypeRefWrapper<T>.wrapper, out var fasterDictionary)) | |||
foreach (var filtersPerGroup in fasterDictionary) | |||
filtersPerGroup.Value.DisposeFilters(); | |||
} | |||
filtersPerGroup.value.DisposeFilters(); | |||
_filters.Remove(TypeRefWrapper<T>.wrapper); | |||
_filtersLegacy.Remove(TypeRefWrapper<T>.wrapper); | |||
} | |||
public void DisposeFilterForGroup<T>(int resetFilterID, ExclusiveGroupStruct @group) | |||
public void DisposeFilterForGroup<T>(int resetFilterID, ExclusiveGroupStruct group) | |||
{ | |||
if (_filters.TryGetValue(TypeRefWrapper<T>.wrapper, out var fasterDictionary) == true) | |||
{ | |||
fasterDictionary[group].DisposeFilter(resetFilterID); | |||
} | |||
if (_filtersLegacy.TryGetValue(TypeRefWrapper<T>.wrapper, out var fasterDictionary)) | |||
fasterDictionary[@group].DisposeFilter(resetFilterID); | |||
} | |||
public bool TryRemoveEntityFromFilter<T>(int filtersID, EGID egid) where T : struct, IEntityComponent | |||
{ | |||
if (TryGetFilterForGroup<T>(filtersID, egid.groupID, out var filter)) | |||
{ | |||
return filter.TryRemove(egid.entityID); | |||
} | |||
return false; | |||
} | |||
@@ -200,13 +189,22 @@ namespace Svelto.ECS | |||
return filter.Add(egid.entityID, mapper); | |||
} | |||
readonly FasterDictionary<RefWrapperType, FasterDictionary<ExclusiveGroupStruct, GroupFilters>> _filters; | |||
} | |||
internal ref LegacyFilterGroup CreateOrGetFilterForGroup(int filterID, ExclusiveGroupStruct groupID, | |||
RefWrapperType refWrapper) | |||
{ | |||
var fasterDictionary = _filtersLegacy.GetOrAdd(refWrapper, | |||
() => new FasterDictionary<ExclusiveGroupStruct, LegacyGroupFilters>()); | |||
public Filters GetFilters() { return new Filters(_filters); } | |||
var filters = fasterDictionary.GetOrAdd(groupID, | |||
(ref ExclusiveGroupStruct gid) => | |||
new LegacyGroupFilters(new SharedSveltoDictionaryNative<int, LegacyFilterGroup>(0), gid), | |||
ref groupID); | |||
return ref filters.CreateOrGetFilter(filterID); | |||
} | |||
FasterDictionary<RefWrapperType, FasterDictionary<ExclusiveGroupStruct, GroupFilters>> _filters => | |||
_enginesRoot._groupFilters; | |||
readonly FasterDictionary<RefWrapperType, FasterDictionary<ExclusiveGroupStruct, LegacyGroupFilters>> | |||
_filtersLegacy; | |||
} | |||
} | |||
} |
@@ -0,0 +1,198 @@ | |||
using System.Runtime.CompilerServices; | |||
using Svelto.Common; | |||
using Svelto.DataStructures.Native; | |||
using Svelto.ECS.Native; | |||
namespace Svelto.ECS | |||
{ | |||
public readonly struct EntityFilterCollection | |||
{ | |||
internal EntityFilterCollection(CombinedFilterID combinedFilterId, | |||
Allocator allocatorStrategy = Allocator.Persistent) | |||
{ | |||
_filtersPerGroup = | |||
SharedSveltoDictionaryNative<ExclusiveGroupStruct, GroupFilters>.Create(allocatorStrategy); | |||
combinedFilterID = combinedFilterId; | |||
} | |||
public CombinedFilterID combinedFilterID { get; } | |||
public EntityFilterIterator GetEnumerator() => new EntityFilterIterator(this); | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public bool Add<T>(EGID egid, NativeEGIDMapper<T> mmap) where T : unmanaged, IEntityComponent | |||
{ | |||
DBC.ECS.Check.Require(mmap.groupID == egid.groupID, "not compatible NativeEgidMapper used"); | |||
return Add(egid, mmap.GetIndex(egid.entityID)); | |||
} | |||
public bool Add<T>(EGID egid, NativeEGIDMultiMapper<T> mmap) where T : unmanaged, IEntityComponent | |||
{ | |||
return Add(egid, mmap.GetIndex(egid)); | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public bool Add(EGID egid, uint toIndex) | |||
{ | |||
return GetGroupFilter(egid.groupID).Add(egid.entityID, toIndex); | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public void Add(uint entityID, ExclusiveGroupStruct groupId, uint index) | |||
{ | |||
Add(new EGID(entityID, groupId), index); | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public void Remove(EGID egid) | |||
{ | |||
_filtersPerGroup[egid.groupID].Remove(egid.entityID); | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public bool Exists(EGID egid) | |||
{ | |||
return GetGroupFilter(egid.groupID).Exists(egid.entityID); | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public GroupFilters GetGroupFilter(ExclusiveGroupStruct group) | |||
{ | |||
if (_filtersPerGroup.TryGetValue(group, out var groupFilter) == false) | |||
{ | |||
groupFilter = new GroupFilters(group); | |||
_filtersPerGroup.Add(group, groupFilter); | |||
} | |||
return groupFilter; | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public void Clear() | |||
{ | |||
var filterSets = _filtersPerGroup.GetValues(out var count); | |||
for (var i = 0; i < count; i++) | |||
{ | |||
filterSets[i].Clear(); | |||
} | |||
} | |||
internal int groupCount => _filtersPerGroup.count; | |||
public void ComputeFinalCount(out int count) | |||
{ | |||
count = 0; | |||
for (int i = 0; i < _filtersPerGroup.count; i++) | |||
{ | |||
count += (int)GetGroup(i).count; | |||
} | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
internal GroupFilters GetGroup(int indexGroup) | |||
{ | |||
DBC.ECS.Check.Require(indexGroup < _filtersPerGroup.count); | |||
return _filtersPerGroup.GetValues(out _)[indexGroup]; | |||
} | |||
public void Dispose() | |||
{ | |||
var filterSets = _filtersPerGroup.GetValues(out var count); | |||
for (var i = 0; i < count; i++) | |||
{ | |||
filterSets[i].Dispose(); | |||
} | |||
_filtersPerGroup.Dispose(); | |||
} | |||
internal readonly SharedSveltoDictionaryNative<ExclusiveGroupStruct, GroupFilters> _filtersPerGroup; | |||
public struct GroupFilters | |||
{ | |||
internal GroupFilters(ExclusiveGroupStruct group) : this() | |||
{ | |||
_entityIDToDenseIndex = new SharedSveltoDictionaryNative<uint, uint>(1); | |||
_indexToEntityId = new SharedSveltoDictionaryNative<uint, uint>(1); | |||
_group = group; | |||
} | |||
public bool Add(uint entityId, uint entityIndex) | |||
{ | |||
//TODO: when sentinels are finished, we need to add AsWriter here | |||
if (_entityIDToDenseIndex.TryAdd(entityId, entityIndex, out _)) | |||
{ | |||
_indexToEntityId[entityIndex] = entityId; | |||
return true; | |||
} | |||
return false; | |||
} | |||
public bool Exists(uint entityId) => _entityIDToDenseIndex.ContainsKey(entityId); | |||
public void Remove(uint entityId) | |||
{ | |||
_indexToEntityId.Remove(_entityIDToDenseIndex[entityId]); | |||
_entityIDToDenseIndex.Remove(entityId); | |||
} | |||
public EntityFilterIndices indices | |||
{ | |||
get | |||
{ | |||
var values = _entityIDToDenseIndex.GetValues(out var count); | |||
return new EntityFilterIndices(values, count); | |||
} | |||
} | |||
public int count => _entityIDToDenseIndex.count; | |||
public bool isValid => _entityIDToDenseIndex.isValid; | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
internal void RemoveWithSwapBack(uint entityId, uint entityIndex, uint lastIndex) | |||
{ | |||
// Check if the last index is part of the filter as an entity, in that case | |||
//we need to update the filter | |||
if (entityIndex != lastIndex && _indexToEntityId.TryGetValue(lastIndex, out var lastEntityID)) | |||
{ | |||
_entityIDToDenseIndex[lastEntityID] = entityIndex; | |||
_indexToEntityId[entityIndex] = lastEntityID; | |||
_indexToEntityId.Remove(lastIndex); | |||
} | |||
else | |||
{ | |||
// We don't need to check if the entityIndex is a part of the dictionary. | |||
// The Remove function will check for us. | |||
_indexToEntityId.Remove(entityIndex); | |||
} | |||
// We don't need to check if the entityID is part of the dictionary. | |||
// The Remove function will check for us. | |||
_entityIDToDenseIndex.Remove(entityId); | |||
} | |||
internal void Clear() | |||
{ | |||
_indexToEntityId.FastClear(); | |||
_entityIDToDenseIndex.FastClear(); | |||
} | |||
internal void Dispose() | |||
{ | |||
_entityIDToDenseIndex.Dispose(); | |||
_indexToEntityId.Dispose(); | |||
} | |||
internal ExclusiveGroupStruct group => _group; | |||
SharedSveltoDictionaryNative<uint, uint> _indexToEntityId; | |||
internal SharedSveltoDictionaryNative<uint, uint> _entityIDToDenseIndex; | |||
readonly ExclusiveGroupStruct _group; | |||
} | |||
} | |||
} |
@@ -0,0 +1,32 @@ | |||
using System; | |||
using Svelto.DataStructures; | |||
namespace Svelto.ECS | |||
{ | |||
// This is just an opaque struct to identify filter collections. | |||
public struct EntityFilterID : IEquatable<EntityFilterID> | |||
{ | |||
internal EntityFilterID(uint filterID, RefWrapperType componentType) | |||
{ | |||
_filterID = filterID; | |||
_componentType = componentType; | |||
_hashCode = (int)filterID + (int)filterID ^ componentType.GetHashCode(); | |||
} | |||
public bool Equals(EntityFilterID other) | |||
{ | |||
return _filterID == other._filterID && _componentType.Equals(other._componentType); | |||
} | |||
public override bool Equals(object obj) | |||
{ | |||
throw new NotImplementedException(); | |||
} | |||
public override int GetHashCode() => _hashCode; | |||
readonly uint _filterID; | |||
readonly RefWrapperType _componentType; | |||
readonly int _hashCode; | |||
} | |||
} |
@@ -0,0 +1,47 @@ | |||
using System.Runtime.CompilerServices; | |||
using System.Threading; | |||
using Svelto.DataStructures; | |||
namespace Svelto.ECS | |||
{ | |||
public struct EntityFilterIndices | |||
{ | |||
public EntityFilterIndices(NB<uint> indices, uint count) | |||
{ | |||
_indices = indices; | |||
_count = count; | |||
_index = 0; | |||
} | |||
public uint count | |||
{ | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
get => _count; | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public uint Get(uint index) => _indices[index]; | |||
public uint this[uint index] | |||
{ | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
get => _indices[index]; | |||
} | |||
public uint this[int index] | |||
{ | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
get => _indices[index]; | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public uint Next() | |||
{ | |||
return _indices[Interlocked.Increment(ref _index) - 1]; | |||
} | |||
readonly NB<uint> _indices; | |||
readonly uint _count; | |||
int _index; | |||
} | |||
} |
@@ -0,0 +1,51 @@ | |||
namespace Svelto.ECS | |||
{ | |||
public ref struct EntityFilterIterator | |||
{ | |||
internal EntityFilterIterator(EntityFilterCollection filter) | |||
{ | |||
_filter = filter; | |||
_indexGroup = -1; | |||
_current = default; | |||
} | |||
public bool MoveNext() | |||
{ | |||
while (++_indexGroup < _filter.groupCount) | |||
{ | |||
_current = _filter.GetGroup(_indexGroup); | |||
if (_current.count > 0) break; | |||
} | |||
return _indexGroup < _filter.groupCount; | |||
} | |||
public void Reset() | |||
{ | |||
_indexGroup = -1; | |||
} | |||
public RefCurrent Current => new RefCurrent(_current); | |||
int _indexGroup; | |||
readonly EntityFilterCollection _filter; | |||
EntityFilterCollection.GroupFilters _current; | |||
public readonly ref struct RefCurrent | |||
{ | |||
internal RefCurrent(EntityFilterCollection.GroupFilters filter) | |||
{ | |||
_filter = filter; | |||
} | |||
public void Deconstruct(out EntityFilterIndices indices, out ExclusiveGroupStruct group) | |||
{ | |||
indices = _filter.indices; | |||
group = _filter.group; | |||
} | |||
readonly EntityFilterCollection.GroupFilters _filter; | |||
} | |||
} | |||
} |
@@ -1,104 +0,0 @@ | |||
using Svelto.DataStructures; | |||
using Svelto.DataStructures.Native; | |||
namespace Svelto.ECS | |||
{ | |||
public struct GroupFilters | |||
{ | |||
internal GroupFilters(SharedSveltoDictionaryNative<int, FilterGroup> filters, ExclusiveGroupStruct group) | |||
{ | |||
this.filters = filters; | |||
_group = @group; | |||
} | |||
public ref FilterGroup GetFilter(int filterIndex) | |||
{ | |||
#if DEBUG && !PROFILE_SVELTO | |||
if (filters.isValid == false) | |||
throw new ECSException($"trying to fetch not existing filters {filterIndex} group {_group.ToName()}"); | |||
if (filters.ContainsKey(filterIndex) == false) | |||
throw new ECSException($"trying to fetch not existing filters {filterIndex} group {_group.ToName()}"); | |||
#endif | |||
return ref filters.GetValueByRef(filterIndex); | |||
} | |||
public bool HasFilter(int filterIndex) { return filters.ContainsKey(filterIndex); } | |||
public void ClearFilter(int filterIndex) | |||
{ | |||
if (filters.TryFindIndex(filterIndex, out var index)) | |||
filters.GetValues(out _)[index].Clear(); | |||
} | |||
public void ClearFilters() | |||
{ | |||
foreach (var filter in filters) | |||
filter.Value.Clear(); | |||
} | |||
public bool TryGetFilter(int filterIndex, out FilterGroup filter) | |||
{ | |||
return filters.TryGetValue(filterIndex, out filter); | |||
} | |||
public SveltoDictionaryKeyValueEnumerator<int, FilterGroup, NativeStrategy<SveltoDictionaryNode<int>>, NativeStrategy<FilterGroup> | |||
, NativeStrategy<int>> GetEnumerator() | |||
{ | |||
return filters.GetEnumerator(); | |||
} | |||
//Note the following methods are internal because I was pondering the idea to be able to return | |||
//the list of GroupFilters linked to a specific filter ID. However this would mean to be able to | |||
//maintain a revers map which at this moment seems too much and also would need the following | |||
//method to be for ever internal (at this point in time I am not sure it's a good idea) | |||
internal void DisposeFilter(int filterIndex) | |||
{ | |||
if (filters.TryFindIndex(filterIndex, out var index)) | |||
{ | |||
ref var filterGroup = ref filters.GetValues(out _)[index]; | |||
filterGroup.Dispose(); | |||
filters.Remove(filterIndex); | |||
} | |||
} | |||
internal void DisposeFilters() | |||
{ | |||
//must release the native buffers! | |||
foreach (var filter in filters) | |||
filter.Value.Dispose(); | |||
filters.FastClear(); | |||
} | |||
internal ref FilterGroup CreateOrGetFilter(int filterID) | |||
{ | |||
if (filters.TryFindIndex(filterID, out var index) == false) | |||
{ | |||
var orGetFilterForGroup = new FilterGroup(_group, filterID); | |||
filters[filterID] = orGetFilterForGroup; | |||
return ref filters.GetValueByRef(filterID); | |||
} | |||
return ref filters.GetValues(out _)[index]; | |||
} | |||
internal void Dispose() | |||
{ | |||
foreach (var filter in filters) | |||
{ | |||
filter.Value.Dispose(); | |||
} | |||
filters.Dispose(); | |||
} | |||
readonly ExclusiveGroupStruct _group; | |||
//filterID, filter | |||
SharedSveltoDictionaryNative<int, FilterGroup> filters; | |||
} | |||
} |
@@ -1,6 +1,5 @@ | |||
using System.Runtime.CompilerServices; | |||
using Svelto.Common; | |||
using Svelto.DataStructures; | |||
using Svelto.DataStructures.Native; | |||
using Svelto.ECS.DataStructures; | |||
@@ -16,9 +15,9 @@ namespace Svelto.ECS | |||
/// sparse[0] = position in the dense list of the entity 0 | |||
/// dense[index] = entity ID but also index in the sparse list of the same entity ID | |||
/// </summary> | |||
public struct FilterGroup | |||
public struct LegacyFilterGroup | |||
{ | |||
internal FilterGroup(ExclusiveGroupStruct exclusiveGroupStruct, int ID) | |||
internal LegacyFilterGroup(ExclusiveGroupStruct exclusiveGroupStruct, int ID) | |||
{ | |||
_denseListOfIndicesToEntityComponentArray = | |||
new NativeDynamicArrayCast<uint>(NativeDynamicArray.Alloc<uint>(Allocator.Persistent)); | |||
@@ -33,7 +32,7 @@ namespace Svelto.ECS | |||
/// <summary> | |||
/// Todo: how to detect if the indices are still pointing to valid entities? | |||
/// </summary> | |||
public FilteredIndices filteredIndices => new FilteredIndices(_denseListOfIndicesToEntityComponentArray); | |||
public LegacyFilteredIndices filteredIndices => new LegacyFilteredIndices(_denseListOfIndicesToEntityComponentArray); | |||
public bool Add<N>(uint entityID, N mapper) where N:IEGIDMapper | |||
{ | |||
@@ -85,11 +84,11 @@ namespace Svelto.ECS | |||
/// Filters were initially designed to be used for tagging operations within submissions of entities. | |||
/// They were designed as a fast tagging mechanism to be used within the submission frame. However I then | |||
/// extended it, but the extension includes a drawback: | |||
///If filters are not in sync with the operations of remove and swap, filters may end up pointing to | |||
///invalid indices. I need to put in place a way to be able to recognised an invalid filter. | |||
///This is currently a disadvantage of the filters. The filters are not updated by the framework | |||
///but they must be updated by the user. | |||
///When to use this method: Add and Removed should be used to add and remove entities in the filters. This is | |||
/// If filters are not in sync with the operations of remove and swap, filters may end up pointing to | |||
/// invalid indices. I need to put in place a way to be able to recognised an invalid filter. | |||
/// This is currently a disadvantage of the filters. The filters are not updated by the framework | |||
/// but they must be updated by the user. | |||
/// When to use this method: Add and Removed should be used to add and remove entities in the filters. This is | |||
/// valid as long as no structural changes happen in the group of entities involved. | |||
/// IF structural changes happen, the indices stored in the filters won't be valid anymore as they will possibly | |||
/// point to entities that were not the original ones. On structural changes | |||
@@ -107,11 +106,11 @@ namespace Svelto.ECS | |||
_reverseEIDs.Clear(); | |||
foreach (var value in _indexOfEntityInDenseList) | |||
if (mapper.FindIndex(value.Key, out var indexOfEntityInBufferComponent) == true) | |||
if (mapper.FindIndex(value.key, out var indexOfEntityInBufferComponent) == true) | |||
{ | |||
_denseListOfIndicesToEntityComponentArray.Add(indexOfEntityInBufferComponent); | |||
var lastIndex = (uint) (_denseListOfIndicesToEntityComponentArray.Count() - 1); | |||
_reverseEIDs.AddAt(lastIndex) = value.Key; | |||
_reverseEIDs.AddAt(lastIndex) = value.key; | |||
} | |||
_indexOfEntityInDenseList.Clear(); |
@@ -3,16 +3,19 @@ using Svelto.ECS.DataStructures; | |||
namespace Svelto.ECS | |||
{ | |||
public readonly struct FilteredIndices | |||
public readonly struct LegacyFilteredIndices | |||
{ | |||
public FilteredIndices(NativeDynamicArrayCast<uint> denseListOfIndicesToEntityComponentArray) | |||
public LegacyFilteredIndices(NativeDynamicArrayCast<uint> denseListOfIndicesToEntityComponentArray) | |||
{ | |||
_denseListOfIndicesToEntityComponentArray = denseListOfIndicesToEntityComponentArray; | |||
_count = _denseListOfIndicesToEntityComponentArray.count; | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public int Count() => _count; | |||
public int count | |||
{ | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
get { return _count; } | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public uint Get(uint index) => _denseListOfIndicesToEntityComponentArray[index]; |
@@ -0,0 +1,104 @@ | |||
using Svelto.DataStructures; | |||
using Svelto.DataStructures.Native; | |||
namespace Svelto.ECS | |||
{ | |||
public struct LegacyGroupFilters | |||
{ | |||
internal LegacyGroupFilters(SharedSveltoDictionaryNative<int, LegacyFilterGroup> legacyFilters, ExclusiveGroupStruct group) | |||
{ | |||
this._legacyFilters = legacyFilters; | |||
_group = @group; | |||
} | |||
public ref LegacyFilterGroup GetFilter(int filterIndex) | |||
{ | |||
#if DEBUG && !PROFILE_SVELTO | |||
if (_legacyFilters.isValid == false) | |||
throw new ECSException($"trying to fetch not existing filters {filterIndex} group {_group.ToName()}"); | |||
if (_legacyFilters.ContainsKey(filterIndex) == false) | |||
throw new ECSException($"trying to fetch not existing filters {filterIndex} group {_group.ToName()}"); | |||
#endif | |||
return ref _legacyFilters.GetValueByRef(filterIndex); | |||
} | |||
public bool HasFilter(int filterIndex) { return _legacyFilters.ContainsKey(filterIndex); } | |||
public void ClearFilter(int filterIndex) | |||
{ | |||
if (_legacyFilters.TryFindIndex(filterIndex, out var index)) | |||
_legacyFilters.GetValues(out _)[index].Clear(); | |||
} | |||
public void ClearFilters() | |||
{ | |||
foreach (var filter in _legacyFilters) | |||
filter.value.Clear(); | |||
} | |||
public bool TryGetFilter(int filterIndex, out LegacyFilterGroup legacyFilter) | |||
{ | |||
return _legacyFilters.TryGetValue(filterIndex, out legacyFilter); | |||
} | |||
public SveltoDictionaryKeyValueEnumerator<int, LegacyFilterGroup, NativeStrategy<SveltoDictionaryNode<int>>, NativeStrategy<LegacyFilterGroup> | |||
, NativeStrategy<int>> GetEnumerator() | |||
{ | |||
return _legacyFilters.GetEnumerator(); | |||
} | |||
//Note the following methods are internal because I was pondering the idea to be able to return | |||
//the list of LegacyGroupFilters linked to a specific filter ID. However this would mean to be able to | |||
//maintain a revers map which at this moment seems too much and also would need the following | |||
//method to be for ever internal (at this point in time I am not sure it's a good idea) | |||
internal void DisposeFilter(int filterIndex) | |||
{ | |||
if (_legacyFilters.TryFindIndex(filterIndex, out var index)) | |||
{ | |||
ref var filterGroup = ref _legacyFilters.GetValues(out _)[index]; | |||
filterGroup.Dispose(); | |||
_legacyFilters.Remove(filterIndex); | |||
} | |||
} | |||
internal void DisposeFilters() | |||
{ | |||
//must release the native buffers! | |||
foreach (var filter in _legacyFilters) | |||
filter.value.Dispose(); | |||
_legacyFilters.FastClear(); | |||
} | |||
internal ref LegacyFilterGroup CreateOrGetFilter(int filterID) | |||
{ | |||
if (_legacyFilters.TryFindIndex(filterID, out var index) == false) | |||
{ | |||
var orGetFilterForGroup = new LegacyFilterGroup(_group, filterID); | |||
_legacyFilters[filterID] = orGetFilterForGroup; | |||
return ref _legacyFilters.GetValueByRef(filterID); | |||
} | |||
return ref _legacyFilters.GetValues(out _)[index]; | |||
} | |||
internal void Dispose() | |||
{ | |||
foreach (var filter in _legacyFilters) | |||
{ | |||
filter.value.Dispose(); | |||
} | |||
_legacyFilters.Dispose(); | |||
} | |||
readonly ExclusiveGroupStruct _group; | |||
//filterID, filter | |||
SharedSveltoDictionaryNative<int, LegacyFilterGroup> _legacyFilters; | |||
} | |||
} |
@@ -0,0 +1,148 @@ | |||
using System.Runtime.CompilerServices; | |||
using Svelto.DataStructures.Native; | |||
using Svelto.ECS.Native; | |||
namespace Svelto.ECS | |||
{ | |||
public struct NativeEntityFilterCollection<T> where T : unmanaged, IEntityComponent | |||
{ | |||
internal NativeEntityFilterCollection(NativeEGIDMultiMapper<T> mmap) | |||
{ | |||
_mmap = mmap; | |||
_filtersPerGroup = new SharedSveltoDictionaryNative<ExclusiveGroupStruct, GroupFilters>(); | |||
} | |||
public NativeEntityFilterIterator<T> iterator => new NativeEntityFilterIterator<T>(this); | |||
public void AddEntity(EGID egid) | |||
{ | |||
AddEntity(egid, _mmap.GetIndex(egid)); | |||
} | |||
public void RemoveEntity(EGID egid) | |||
{ | |||
_filtersPerGroup[egid.groupID].Remove(egid.entityID); | |||
} | |||
public void Clear() | |||
{ | |||
var filterSets = _filtersPerGroup.GetValues(out var count); | |||
for (var i = 0; i < count; i++) | |||
{ | |||
filterSets[i].Clear(); | |||
} | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
void AddEntity(EGID egid, uint toIndex) | |||
{ | |||
if (_filtersPerGroup.TryGetValue(egid.groupID, out var groupFilter) == false) | |||
{ | |||
groupFilter = new GroupFilters(32, egid.groupID); | |||
_filtersPerGroup[egid.groupID] = groupFilter; | |||
} | |||
groupFilter.Add(egid.entityID, toIndex); | |||
} | |||
internal int groupCount => _filtersPerGroup.count; | |||
internal GroupFilters GetGroup(int indexGroup) | |||
{ | |||
DBC.ECS.Check.Require(indexGroup < _filtersPerGroup.count); | |||
return _filtersPerGroup.GetValues(out _)[indexGroup]; | |||
} | |||
internal void Dispose() | |||
{ | |||
var filterSets = _filtersPerGroup.GetValues(out var count); | |||
for (var i = 0; i < count; i++) | |||
{ | |||
filterSets[i].Dispose(); | |||
} | |||
} | |||
readonly NativeEGIDMultiMapper<T> _mmap; | |||
//double check if this needs to be shared | |||
SharedSveltoDictionaryNative<ExclusiveGroupStruct, GroupFilters> _filtersPerGroup; | |||
internal struct GroupFilters | |||
{ | |||
internal GroupFilters(uint size, ExclusiveGroupStruct group) | |||
{ | |||
_entityIDToDenseIndex = new SharedSveltoDictionaryNative<uint, uint>(size); | |||
_indexToEntityId = new SharedSveltoDictionaryNative<uint, uint>(size); | |||
_group = group; | |||
} | |||
internal void Add(uint entityId, uint entityIndex) | |||
{ | |||
_entityIDToDenseIndex.Add(entityId, entityIndex); | |||
_indexToEntityId.Add(entityIndex, entityId); | |||
} | |||
internal void Remove(uint entityId) | |||
{ | |||
_indexToEntityId.Remove(_entityIDToDenseIndex[entityId]); | |||
_entityIDToDenseIndex.Remove(entityId); | |||
} | |||
internal void RemoveWithSwapBack(uint entityId, uint entityIndex, uint lastIndex) | |||
{ | |||
// Check if the last index is part of the filter as an entity, in that case | |||
//we need to update the filter | |||
if (entityIndex != lastIndex && _indexToEntityId.ContainsKey(lastIndex)) | |||
{ | |||
uint lastEntityID = _indexToEntityId[lastIndex]; | |||
_entityIDToDenseIndex[lastEntityID] = entityIndex; | |||
_indexToEntityId[entityIndex] = lastEntityID; | |||
_indexToEntityId.Remove(lastIndex); | |||
} | |||
else | |||
{ | |||
// We don't need to check if the entityIndex is a part of the dictionary. | |||
// The Remove function will check for us. | |||
_indexToEntityId.Remove(entityIndex); | |||
} | |||
// We don't need to check if the entityID is part of the dictionary. | |||
// The Remove function will check for us. | |||
_entityIDToDenseIndex.Remove(entityId); | |||
} | |||
internal void Clear() | |||
{ | |||
_indexToEntityId.FastClear(); | |||
_entityIDToDenseIndex.FastClear(); | |||
} | |||
internal bool HasEntity(uint entityId) => _entityIDToDenseIndex.ContainsKey(entityId); | |||
internal void Dispose() | |||
{ | |||
_entityIDToDenseIndex.Dispose(); | |||
_indexToEntityId.Dispose(); | |||
} | |||
internal EntityFilterIndices indices | |||
{ | |||
get | |||
{ | |||
var values = _entityIDToDenseIndex.GetValues(out var count); | |||
return new EntityFilterIndices(values, count); | |||
} | |||
} | |||
internal uint count => (uint)_entityIDToDenseIndex.count; | |||
internal ExclusiveGroupStruct group => _group; | |||
//double check if these need to be shared | |||
SharedSveltoDictionaryNative<uint, uint> _indexToEntityId; | |||
SharedSveltoDictionaryNative<uint, uint> _entityIDToDenseIndex; | |||
readonly ExclusiveGroupStruct _group; | |||
} | |||
} | |||
} |
@@ -0,0 +1,63 @@ | |||
namespace Svelto.ECS | |||
{ | |||
public readonly ref struct NativeEntityFilterIterator<T> where T : unmanaged, IEntityComponent | |||
{ | |||
internal NativeEntityFilterIterator(NativeEntityFilterCollection<T> filter) | |||
{ | |||
_filter = filter; | |||
} | |||
public Iterator GetEnumerator() => new Iterator(_filter); | |||
readonly NativeEntityFilterCollection<T> _filter; | |||
public ref struct Iterator | |||
{ | |||
internal Iterator(NativeEntityFilterCollection<T> filter) | |||
{ | |||
_filter = filter; | |||
_indexGroup = -1; | |||
_current = default; | |||
} | |||
public bool MoveNext() | |||
{ | |||
while (++_indexGroup < _filter.groupCount) | |||
{ | |||
_current = _filter.GetGroup(_indexGroup); | |||
if (_current.count > 0) break; | |||
} | |||
return _indexGroup < _filter.groupCount; | |||
} | |||
public void Reset() | |||
{ | |||
_indexGroup = -1; | |||
} | |||
public RefCurrent Current => new RefCurrent(_current); | |||
int _indexGroup; | |||
readonly NativeEntityFilterCollection<T> _filter; | |||
NativeEntityFilterCollection<T>.GroupFilters _current; | |||
} | |||
public readonly ref struct RefCurrent | |||
{ | |||
internal RefCurrent(NativeEntityFilterCollection<T>.GroupFilters filter) | |||
{ | |||
_filter = filter; | |||
} | |||
public void Deconstruct(out EntityFilterIndices indices, out ExclusiveGroupStruct group) | |||
{ | |||
indices = _filter.indices; | |||
group = _filter.group; | |||
} | |||
readonly NativeEntityFilterCollection<T>.GroupFilters _filter; | |||
} | |||
} | |||
} |
@@ -1,6 +1,5 @@ | |||
using System.Threading; | |||
using Svelto.Common; | |||
using Svelto.DataStructures; | |||
using Svelto.ECS.DataStructures; | |||
namespace Svelto.ECS | |||
@@ -23,7 +22,7 @@ namespace Svelto.ECS | |||
{ | |||
static Filler() | |||
{ | |||
DBC.ECS.Check.Require(TypeCache<T>.isUnmanaged == true, "invalid type used"); | |||
DBC.ECS.Check.Require(TypeType.isUnmanaged<T>() == true, "invalid type used"); | |||
} | |||
//it's an internal interface | |||
@@ -35,28 +34,20 @@ namespace Svelto.ECS | |||
} | |||
} | |||
#if UNITY_NATIVE //at the moment I am still considering NativeOperations useful only for Unity | |||
static class EntityComponentID<T> | |||
{ | |||
#if UNITY_NATIVE | |||
internal static readonly Unity.Burst.SharedStatic<uint> ID = | |||
Unity.Burst.SharedStatic<uint>.GetOrCreate<GlobalTypeID, T>(); | |||
#else | |||
internal struct SharedStatic | |||
{ | |||
public uint Data; | |||
} | |||
internal static SharedStatic ID; | |||
#endif | |||
} | |||
static class EntityComponentIDMap | |||
{ | |||
static readonly FasterList<IFiller> TYPE_IDS; | |||
static readonly Svelto.DataStructures.FasterList<IFiller> TYPE_IDS; | |||
static EntityComponentIDMap() | |||
{ | |||
TYPE_IDS = new FasterList<IFiller>(); | |||
TYPE_IDS = new Svelto.DataStructures.FasterList<IFiller>(); | |||
} | |||
internal static void Register<T>(IFiller entityBuilder) where T : struct, IEntityComponent | |||
@@ -67,4 +58,5 @@ namespace Svelto.ECS | |||
internal static IFiller GetTypeFromID(uint typeId) { return TYPE_IDS[typeId]; } | |||
} | |||
#endif | |||
} |
@@ -6,12 +6,12 @@ using Svelto.ECS.Serialization; | |||
namespace Svelto.ECS | |||
{ | |||
static class GroupHashMap | |||
public static class GroupHashMap | |||
{ | |||
/// <summary> | |||
/// c# Static constructors are guaranteed to be thread safe | |||
/// </summary> | |||
public static void Init() | |||
internal static void Init() | |||
{ | |||
List<Assembly> assemblies = AssemblyUtility.GetCompatibleAssemblies(); | |||
foreach (Assembly assembly in assemblies) | |||
@@ -20,41 +20,61 @@ namespace Svelto.ECS | |||
{ | |||
var typeOfExclusiveGroup = typeof(ExclusiveGroup); | |||
var typeOfExclusiveGroupStruct = typeof(ExclusiveGroupStruct); | |||
var typeOfExclusiveBuildGroup = typeof(ExclusiveBuildGroup); | |||
foreach (Type type in AssemblyUtility.GetTypesSafe(assembly)) | |||
{ | |||
if (type != null && type.IsClass && type.IsSealed | |||
&& type.IsAbstract) //this means only static classes | |||
CheckForGroupCompounds(type); | |||
if (type != null && type.IsClass && type.IsSealed && | |||
type.IsAbstract) //IsClass and IsSealed and IsAbstract means only static classes | |||
{ | |||
var subClasses = type.GetNestedTypes(); | |||
foreach (var subclass in subClasses) | |||
{ | |||
CheckForGroupCompounds(subclass); | |||
} | |||
var fields = type.GetFields(); | |||
foreach (var field in fields) | |||
{ | |||
if (field.IsStatic) | |||
if (field.IsStatic | |||
&& (typeOfExclusiveGroup.IsAssignableFrom(field.FieldType) | |||
|| typeOfExclusiveGroupStruct.IsAssignableFrom(field.FieldType) | |||
|| typeOfExclusiveBuildGroup.IsAssignableFrom(field.FieldType))) | |||
{ | |||
uint groupID; | |||
if (typeOfExclusiveGroup.IsAssignableFrom(field.FieldType)) | |||
{ | |||
var group = (ExclusiveGroup) field.GetValue(null); | |||
#if DEBUG | |||
GroupNamesMap.idToName[@group] = | |||
$"{$"{type.FullName}.{field.Name}"} {group.id})"; | |||
#endif | |||
//The hashname is independent from the actual group ID. this is fundamental because it is want | |||
//guarantees the hash to be the same across different machines | |||
RegisterGroup(group, $"{type.FullName}.{field.Name}"); | |||
var group = (ExclusiveGroup)field.GetValue(null); | |||
groupID = ((ExclusiveGroupStruct)@group).id; | |||
} | |||
else | |||
if (typeOfExclusiveGroupStruct.IsAssignableFrom(field.FieldType)) | |||
{ | |||
var group = (ExclusiveGroupStruct)field.GetValue(null); | |||
groupID = @group.id; | |||
} | |||
else | |||
if (typeOfExclusiveGroupStruct.IsAssignableFrom(field.FieldType)) | |||
{ | |||
var group = (ExclusiveGroupStruct) field.GetValue(null); | |||
#if DEBUG | |||
{ | |||
var group = (ExclusiveBuildGroup)field.GetValue(null); | |||
groupID = ((ExclusiveGroupStruct)@group).id; | |||
} | |||
{ | |||
ExclusiveGroupStruct group = new ExclusiveGroupStruct(groupID); | |||
#if DEBUG && !PROFILE_SVELTO | |||
if (GroupNamesMap.idToName.ContainsKey(@group) == false) | |||
GroupNamesMap.idToName[@group] = | |||
$"{$"{type.FullName}.{field.Name}"} {group.id})"; | |||
$"{type.FullName}.{field.Name} {@group.id})"; | |||
#endif | |||
//The hashname is independent from the actual group ID. this is fundamental because it is want | |||
//guarantees the hash to be the same across different machines | |||
RegisterGroup(@group, $"{type.FullName}.{field.Name}"); | |||
} | |||
//The hashname is independent from the actual group ID. this is fundamental because it is want | |||
//guarantees the hash to be the same across different machines | |||
RegisterGroup(@group, $"{type.FullName}.{field.Name}"); | |||
} | |||
} | |||
} | |||
} | |||
@@ -69,6 +89,16 @@ namespace Svelto.ECS | |||
} | |||
} | |||
static void CheckForGroupCompounds(Type type) | |||
{ | |||
if (typeof(ITouchedByReflection).IsAssignableFrom(type)) | |||
{ | |||
//this wil call the static constructor, but only once. Static constructors won't be called | |||
//more than once with this | |||
System.Runtime.CompilerServices.RuntimeHelpers.RunClassConstructor(type.BaseType.TypeHandle); | |||
} | |||
} | |||
/// <summary> | |||
/// The hashname is independent from the actual group ID. this is fundamental because it is want | |||
/// guarantees the hash to be the same across different machines | |||
@@ -78,7 +108,8 @@ namespace Svelto.ECS | |||
/// <exception cref="ECSException"></exception> | |||
public static void RegisterGroup(ExclusiveGroupStruct exclusiveGroupStruct, string name) | |||
{ | |||
//Group already registered by another field referencing the same group | |||
//Group already registered by another field referencing the same group, can happen because | |||
//the group poked is a group compound which static constructor is already been called at this point | |||
if (_hashByGroups.ContainsKey(exclusiveGroupStruct)) | |||
return; | |||
@@ -93,9 +124,9 @@ namespace Svelto.ECS | |||
_hashByGroups.Add(exclusiveGroupStruct, nameHash); | |||
} | |||
public static uint GetHashFromGroup(ExclusiveGroupStruct groupStruct) | |||
internal static uint GetHashFromGroup(ExclusiveGroupStruct groupStruct) | |||
{ | |||
#if DEBUG | |||
#if DEBUG && !PROFILE_SVELTO | |||
if (_hashByGroups.ContainsKey(groupStruct) == false) | |||
throw new ECSException($"Attempted to get hash from unregistered group {groupStruct}"); | |||
#endif | |||
@@ -103,9 +134,9 @@ namespace Svelto.ECS | |||
return _hashByGroups[groupStruct]; | |||
} | |||
public static ExclusiveGroupStruct GetGroupFromHash(uint groupHash) | |||
internal static ExclusiveGroupStruct GetGroupFromHash(uint groupHash) | |||
{ | |||
#if DEBUG | |||
#if DEBUG && !PROFILE_SVELTO | |||
if (_groupsByHash.ContainsKey(groupHash) == false) | |||
throw new ECSException($"Attempted to get group from unregistered hash {groupHash}"); | |||
#endif | |||
@@ -3,12 +3,12 @@ using Svelto.ECS; | |||
static class GroupNamesMap | |||
{ | |||
#if DEBUG | |||
#if DEBUG && !PROFILE_SVELTO | |||
static GroupNamesMap() { idToName = new Dictionary<ExclusiveGroupStruct, string>(); } | |||
internal static readonly Dictionary<ExclusiveGroupStruct, string> idToName; | |||
#endif | |||
#if DEBUG | |||
#if DEBUG && !PROFILE_SVELTO | |||
public static string ToName(this in ExclusiveGroupStruct group) | |||
{ | |||
Dictionary<ExclusiveGroupStruct, string> idToName = GroupNamesMap.idToName; | |||
@@ -16,7 +16,7 @@ namespace Svelto.ECS | |||
{ | |||
return new ExclusiveBuildGroup(group); | |||
} | |||
public static implicit operator ExclusiveGroupStruct(ExclusiveBuildGroup group) | |||
{ | |||
return group.group; | |||
@@ -1,6 +1,5 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Runtime.CompilerServices; | |||
#pragma warning disable 660,661 | |||
@@ -43,7 +42,7 @@ namespace Svelto.ECS | |||
public ExclusiveGroup(ushort range) | |||
{ | |||
_group = ExclusiveGroupStruct.GenerateWithRange(range); | |||
#if DEBUG | |||
#if DEBUG && !PROFILE_SVELTO | |||
_range = range; | |||
#endif | |||
} | |||
@@ -55,7 +54,7 @@ namespace Svelto.ECS | |||
public static ExclusiveGroupStruct operator+(ExclusiveGroup @group, uint b) | |||
{ | |||
#if DEBUG | |||
#if DEBUG && !PROFILE_SVELTO | |||
if (@group._range == 0) | |||
throw new ECSException($"Adding values to a not ranged ExclusiveGroup: {@group.id}"); | |||
if (b >= @group._range) | |||
@@ -83,7 +82,7 @@ namespace Svelto.ECS | |||
static readonly Dictionary<string, ExclusiveGroupStruct> _knownGroups = | |||
new Dictionary<string, ExclusiveGroupStruct>(); | |||
#if DEBUG | |||
#if DEBUG && !PROFILE_SVELTO | |||
readonly ushort _range; | |||
#endif | |||
readonly ExclusiveGroupStruct _group; | |||
@@ -1,10 +1,12 @@ | |||
using System; | |||
using System.Diagnostics; | |||
using System.Runtime.CompilerServices; | |||
using System.Runtime.InteropServices; | |||
using System.Threading; | |||
namespace Svelto.ECS | |||
{ | |||
[DebuggerDisplay("{ToString()}")] | |||
[StructLayout(LayoutKind.Explicit, Size = 4)] | |||
//the type doesn't implement IEqualityComparer, what implements it is a custom comparer | |||
public readonly struct ExclusiveGroupStruct : IEquatable<ExclusiveGroupStruct>, IComparable<ExclusiveGroupStruct> | |||
@@ -1,4 +1,3 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Threading; | |||
using Svelto.DataStructures; | |||
@@ -18,25 +17,31 @@ namespace Svelto.ECS | |||
internal static readonly ThreadLocal<bool> skipStaticCompoundConstructorsWith2Tags = 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> | |||
interface ITouchedByReflection | |||
{ | |||
} | |||
public abstract class GroupCompound<G1, G2, G3, G4> : ITouchedByReflection where G1 : GroupTag<G1> | |||
where G2 : GroupTag<G2> | |||
where G3 : GroupTag<G3> | |||
where G4 : GroupTag<G4> | |||
{ | |||
static GroupCompound() | |||
{ | |||
//avoid race conditions if compounds are using on multiple thread | |||
if (Interlocked.CompareExchange(ref isInitializing, 1, 0) == 0 | |||
&& GroupCompoundInitializer.skipStaticCompoundConstructorsWith4Tags.Value == false) | |||
if (Interlocked.CompareExchange(ref isInitializing, 1, 0) == 0 && | |||
GroupCompoundInitializer.skipStaticCompoundConstructorsWith4Tags.Value == false) | |||
{ | |||
var group = new ExclusiveGroup(); //todo: it's a bit of a waste to create a class here even if this is a static constructor | |||
var | |||
group = | |||
new ExclusiveGroup(); //todo: it's a bit of a waste to create a class here even if this is a static constructor | |||
_Groups = new FasterList<ExclusiveGroupStruct>(1); | |||
_Groups.Add(group); | |||
#if DEBUG | |||
#if DEBUG && !PROFILE_SVELTO | |||
var name = | |||
$"Compound: {typeof(G1).Name}-{typeof(G2).Name}-{typeof(G3).Name}-{typeof(G4).Name} ID {(uint) group.id}"; | |||
$"Compound: {typeof(G1).Name}-{typeof(G2).Name}-{typeof(G3).Name}-{typeof(G4).Name} ID {(uint)group.id}"; | |||
GroupNamesMap.idToName[group] = name; | |||
#endif | |||
//The hashname is independent from the actual group ID. this is fundamental because it is want | |||
@@ -133,16 +138,16 @@ namespace Svelto.ECS | |||
internal static void Add(ExclusiveGroupStruct group) | |||
{ | |||
#if DEBUG && !PROFILE_SVELTO | |||
#if DEBUG && !PROFILE_SVELTO | |||
for (var i = 0; i < _Groups.count; ++i) | |||
if (_Groups[i] == group) | |||
throw new Exception("this test must be transformed in unit test"); | |||
throw new System.Exception("this test must be transformed in unit test"); | |||
#endif | |||
_Groups.Add(group); | |||
_GroupsHashSet.Add(group); | |||
} | |||
static readonly FasterList<ExclusiveGroupStruct> _Groups; | |||
static readonly HashSet<ExclusiveGroupStruct> _GroupsHashSet; | |||
@@ -150,21 +155,24 @@ namespace Svelto.ECS | |||
static int isInitializing; | |||
} | |||
public abstract class GroupCompound<G1, G2, G3> | |||
where G1 : GroupTag<G1> where G2 : GroupTag<G2> where G3 : GroupTag<G3> | |||
public abstract class GroupCompound<G1, G2, G3> : ITouchedByReflection where G1 : GroupTag<G1> | |||
where G2 : GroupTag<G2> | |||
where G3 : GroupTag<G3> | |||
{ | |||
static GroupCompound() | |||
{ | |||
if (Interlocked.CompareExchange(ref isInitializing, 1, 0) == 0 | |||
&& GroupCompoundInitializer.skipStaticCompoundConstructorsWith3Tags.Value == false) | |||
if (Interlocked.CompareExchange(ref isInitializing, 1, 0) == 0 && | |||
GroupCompoundInitializer.skipStaticCompoundConstructorsWith3Tags.Value == false) | |||
{ | |||
var group = new ExclusiveGroup(); //todo: it's a bit of a waste to create a class here even if this is a static constructor | |||
var | |||
group = | |||
new ExclusiveGroup(); //todo: it's a bit of a waste to create a class here even if this is a static constructor | |||
_Groups = new FasterList<ExclusiveGroupStruct>(1); | |||
_Groups.Add(group); | |||
#if DEBUG | |||
var name = $"Compound: {typeof(G1).Name}-{typeof(G2).Name}-{typeof(G3).Name} ID {(uint) group.id}"; | |||
#if DEBUG && !PROFILE_SVELTO | |||
var name = $"Compound: {typeof(G1).Name}-{typeof(G2).Name}-{typeof(G3).Name} ID {(uint)group.id}"; | |||
GroupNamesMap.idToName[group] = name; | |||
#endif | |||
//The hashname is independent from the actual group ID. this is fundamental because it is want | |||
@@ -215,16 +223,16 @@ namespace Svelto.ECS | |||
internal static void Add(ExclusiveGroupStruct group) | |||
{ | |||
#if DEBUG && !PROFILE_SVELTO | |||
#if DEBUG && !PROFILE_SVELTO | |||
for (var i = 0; i < _Groups.count; ++i) | |||
if (_Groups[i] == group) | |||
throw new Exception("this test must be transformed in unit test"); | |||
throw new System.Exception("this test must be transformed in unit test"); | |||
#endif | |||
_Groups.Add(group); | |||
_GroupsHashSet.Add(group); | |||
} | |||
static readonly FasterList<ExclusiveGroupStruct> _Groups; | |||
static readonly HashSet<ExclusiveGroupStruct> _GroupsHashSet; | |||
@@ -232,19 +240,21 @@ namespace Svelto.ECS | |||
static int isInitializing; | |||
} | |||
public abstract class GroupCompound<G1, G2> where G1 : GroupTag<G1> where G2 : GroupTag<G2> | |||
public abstract class GroupCompound<G1, G2> : ITouchedByReflection where G1 : GroupTag<G1> where G2 : GroupTag<G2> | |||
{ | |||
static GroupCompound() | |||
{ | |||
if (Interlocked.CompareExchange(ref isInitializing, 1, 0) == 0 | |||
&& GroupCompoundInitializer.skipStaticCompoundConstructorsWith2Tags.Value == false) | |||
if (Interlocked.CompareExchange(ref isInitializing, 1, 0) == 0 && | |||
GroupCompoundInitializer.skipStaticCompoundConstructorsWith2Tags.Value == false) | |||
{ | |||
var group = new ExclusiveGroup(); //todo: it's a bit of a waste to create a class here even if this is a static constructor | |||
var | |||
group = | |||
new ExclusiveGroup(); //todo: it's a bit of a waste to create a class here even if this is a static constructor | |||
_Groups = new FasterList<ExclusiveGroupStruct>(1); | |||
_Groups.Add(group); | |||
#if DEBUG | |||
#if DEBUG && !PROFILE_SVELTO | |||
GroupNamesMap.idToName[group] = $"Compound: {typeof(G1).Name}-{typeof(G2).Name} ID {group.id}"; | |||
#endif | |||
//The hashname is independent from the actual group ID. this is fundamental because it is want | |||
@@ -276,16 +286,16 @@ namespace Svelto.ECS | |||
internal static void Add(ExclusiveGroupStruct group) | |||
{ | |||
#if DEBUG && !PROFILE_SVELTO | |||
#if DEBUG && !PROFILE_SVELTO | |||
for (var i = 0; i < _Groups.count; ++i) | |||
if (_Groups[i] == group) | |||
throw new Exception("this test must be transformed in unit test"); | |||
#endif | |||
throw new System.Exception("this test must be transformed in unit test"); | |||
#endif | |||
_Groups.Add(group); | |||
_GroupsHashSet.Add(group); | |||
} | |||
static readonly FasterList<ExclusiveGroupStruct> _Groups; | |||
static readonly HashSet<ExclusiveGroupStruct> _GroupsHashSet; | |||
@@ -299,7 +309,7 @@ namespace Svelto.ECS | |||
/// 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> | |||
public abstract class GroupTag<T> : ITouchedByReflection where T : GroupTag<T> | |||
{ | |||
static GroupTag() | |||
{ | |||
@@ -308,12 +318,13 @@ namespace Svelto.ECS | |||
var group = new ExclusiveGroup(); | |||
_Groups.Add(group); | |||
#if DEBUG | |||
var typeInfo = typeof(T); | |||
var name = $"Compound: {typeInfo.Name} ID {(uint) group.id}"; | |||
#if DEBUG && !PROFILE_SVELTO | |||
var typeInfo = typeof(T); | |||
var name = $"Compound: {typeInfo.Name} ID {(uint)group.id}"; | |||
var typeInfoBaseType = typeInfo.BaseType; | |||
if (typeInfoBaseType.GenericTypeArguments[0] != typeInfo) //todo: this should shield from using a pattern different than public class GROUP_NAME : GroupTag<GROUP_NAME> {} however I am not sure it's working | |||
if (typeInfoBaseType.GenericTypeArguments[0] != | |||
typeInfo) //todo: this should shield from using a pattern different than public class GROUP_NAME : GroupTag<GROUP_NAME> {} however I am not sure it's working | |||
throw new ECSException("Invalid Group Tag declared"); | |||
GroupNamesMap.idToName[group] = name; | |||
@@ -339,16 +350,16 @@ namespace Svelto.ECS | |||
//Each time a new combination of group tags is found a new group is added. | |||
internal static void Add(ExclusiveGroupStruct group) | |||
{ | |||
#if DEBUG && !PROFILE_SVELTO | |||
#if DEBUG && !PROFILE_SVELTO | |||
for (var i = 0; i < _Groups.count; ++i) | |||
if (_Groups[i] == group) | |||
throw new Exception("this test must be transformed in unit test"); | |||
throw new System.Exception("this test must be transformed in unit test"); | |||
#endif | |||
_Groups.Add(group); | |||
_GroupsHashSet.Add(group); | |||
} | |||
static readonly FasterList<ExclusiveGroupStruct> _Groups = new FasterList<ExclusiveGroupStruct>(1); | |||
static readonly HashSet<ExclusiveGroupStruct> _GroupsHashSet; | |||
@@ -13,7 +13,7 @@ namespace Svelto.ECS | |||
static NamedExclusiveGroup() | |||
{ | |||
#if DEBUG | |||
#if DEBUG && !PROFILE_SVELTO | |||
GroupNamesMap.idToName[Group] = $"{name} ID {Group.id}"; | |||
#endif | |||
//The hashname is independent from the actual group ID. this is fundamental because it is want | |||
@@ -3,7 +3,10 @@ namespace Svelto.ECS.Hybrid | |||
public interface IManagedComponent:IEntityComponent | |||
{} | |||
public interface IEntityViewComponent:IManagedComponent, INeedEGID | |||
public interface IEntityViewComponent:IManagedComponent | |||
#if SLOW_SVELTO_SUBMISSION | |||
,INeedEGID | |||
#endif | |||
{} | |||
} | |||
@@ -1,37 +1,29 @@ | |||
using System; | |||
using System.Runtime.InteropServices; | |||
namespace Svelto.ECS.Hybrid | |||
{ | |||
/// <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/ | |||
/// ValueReference is the only way to store a reference inside an Implementor. To stop any abuse | |||
/// the reference must be an implementor and converted back to an implementor. | |||
/// The OOP abstraction layer that knows about the implementor than can cast it to the real type | |||
/// </summary> | |||
/// <typeparam name="T"></typeparam> | |||
public struct ValueReference<T> : IDisposable where T:class, IImplementor | |||
public struct ValueReference<T> : IValueReferenceInternal where T:class | |||
{ | |||
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 W Convert<W>(W implementer) where W:T | |||
public T ConvertAndDispose<W>(W implementer) where W:IImplementor | |||
{ | |||
var pointerTarget = _pointer.Target; | |||
return (W)pointerTarget; | |||
} | |||
public void Dispose() | |||
{ | |||
_pointer.Free(); | |||
return (T)pointerTarget; | |||
} | |||
public bool isDefault => _pointer.IsAllocated == false; | |||
GCHandle _pointer; | |||
} | |||
// Used to validate the use of this struct on the component builder check fields. | |||
internal interface IValueReferenceInternal {} | |||
} |
@@ -2,41 +2,128 @@ using Svelto.ECS.Internal; | |||
namespace Svelto.ECS.Internal | |||
{ | |||
public interface IReactEngine: IEngine | |||
{} | |||
public interface IReactEngine : IEngine | |||
{ | |||
} | |||
#region legacy interfaces | |||
/// <summary> | |||
/// This is now considered legacy and it will be deprecated in future | |||
/// </summary> | |||
public interface IReactOnAdd : IReactEngine | |||
{ | |||
} | |||
public interface IReactOnAddAndRemove : IReactEngine | |||
{} | |||
/// <summary> | |||
/// This is now considered legacy and it will be deprecated in future | |||
/// </summary> | |||
public interface IReactOnRemove : IReactEngine | |||
{ | |||
} | |||
public interface IReactOnDispose : IReactEngine | |||
{} | |||
/// <summary> | |||
/// This is now considered legacy and it will be deprecated in future | |||
/// </summary> | |||
public interface IReactOnSwap : IReactEngine | |||
{} | |||
{ | |||
} | |||
#endregion | |||
public interface IReactOnAddEx : IReactEngine | |||
{ | |||
} | |||
public interface IReactOnRemoveEx : IReactEngine | |||
{ | |||
} | |||
public interface IReactOnSwapEx : IReactEngine | |||
{ | |||
} | |||
public interface IReactOnDispose : IReactEngine | |||
{ | |||
} | |||
} | |||
namespace Svelto.ECS | |||
{ | |||
public interface IEngine | |||
{} | |||
public interface IReactOnAddAndRemove<T> : IReactOnAddAndRemove where T : IEntityComponent | |||
{ | |||
} | |||
public interface IGetReadyEngine : IEngine | |||
{ | |||
void Ready(); | |||
} | |||
public interface IQueryingEntitiesEngine : IGetReadyEngine | |||
{ | |||
EntitiesDB entitiesDB { set; } | |||
} | |||
/// <summary> | |||
/// Interface to mark an Engine as reacting on entities added | |||
/// </summary> | |||
/// <typeparam name="T"></typeparam> | |||
public interface IReactOnAdd<T> : IReactOnAdd where T : IEntityComponent | |||
{ | |||
void Add(ref T entityComponent, EGID egid); | |||
} | |||
public interface IReactOnAddEx<T> : IReactOnAddEx where T : struct, IEntityComponent | |||
{ | |||
void Add((uint start, uint end) rangeOfEntities, in EntityCollection<T> collection, | |||
ExclusiveGroupStruct groupID); | |||
} | |||
/// <summary> | |||
/// Interface to mark an Engine as reacting on entities removed | |||
/// </summary> | |||
/// <typeparam name="T"></typeparam> | |||
public interface IReactOnRemove<T> : IReactOnRemove where T : IEntityComponent | |||
{ | |||
void Remove(ref T entityComponent, EGID egid); | |||
} | |||
public interface IReactOnRemoveEx<T> : IReactOnRemoveEx where T : struct, IEntityComponent | |||
{ | |||
void Remove((uint start, uint end) rangeOfEntities, in EntityCollection<T> collection, | |||
ExclusiveGroupStruct groupID); | |||
} | |||
public interface IReactOnAddAndRemove<T> : IReactOnAdd<T>, IReactOnRemove<T> where T : IEntityComponent | |||
{ | |||
} | |||
/// <summary> | |||
/// Interface to mark an Engine as reacting on engines root disposed. | |||
/// It can work together with IReactOnRemove which normally is not called on enginesroot disposed | |||
/// </summary> | |||
/// <typeparam name="T"></typeparam> | |||
public interface IReactOnDispose<T> : IReactOnDispose where T : IEntityComponent | |||
{ | |||
void Remove(ref T entityComponent, EGID egid); | |||
} | |||
/// <summary> | |||
/// Interface to mark an Engine as reacting to entities swapping group | |||
/// </summary> | |||
/// <typeparam name="T"></typeparam> | |||
public interface IReactOnSwap<T> : IReactOnSwap where T : IEntityComponent | |||
{ | |||
void MovedTo(ref T entityComponent, ExclusiveGroupStruct previousGroup, EGID egid); | |||
} | |||
public interface IReactOnSubmission:IReactEngine | |||
public interface IReactOnSwapEx<T> : IReactOnSwapEx where T : struct, IEntityComponent | |||
{ | |||
void MovedTo((uint start, uint end) rangeOfEntities, in EntityCollection<T> collection, | |||
ExclusiveGroupStruct fromGroup, ExclusiveGroupStruct toGroup); | |||
} | |||
/// <summary> | |||
/// Interface to mark an Engine as reacting after each entities submission phase | |||
/// </summary> | |||
public interface IReactOnSubmission : IReactEngine | |||
{ | |||
void EntitiesSubmitted(); | |||
} |
@@ -39,25 +39,27 @@ namespace Svelto.ECS | |||
/// <param name="ed"></param> | |||
/// <param name="implementors"></param> | |||
EntityInitializer BuildEntity<T>(uint entityID, ExclusiveBuildGroup groupStructId, | |||
IEnumerable<object> implementors = null) | |||
where T : IEntityDescriptor, new(); | |||
IEnumerable<object> implementors = null, | |||
[System.Runtime.CompilerServices.CallerMemberName] string caller = null) where T : IEntityDescriptor, new(); | |||
EntityInitializer BuildEntity<T>(EGID egid, IEnumerable<object> implementors = null) | |||
where T : IEntityDescriptor, new(); | |||
EntityInitializer BuildEntity<T>(EGID egid, IEnumerable<object> implementors = null, | |||
[System.Runtime.CompilerServices.CallerMemberName] string caller = null) where T : IEntityDescriptor, new(); | |||
EntityInitializer BuildEntity<T>(uint entityID, ExclusiveBuildGroup groupStructId, | |||
T descriptorEntity, IEnumerable<object> implementors = null) | |||
where T : IEntityDescriptor; | |||
EntityInitializer BuildEntity<T>(uint entityID, ExclusiveBuildGroup groupStructId, T descriptorEntity, | |||
IEnumerable<object> implementors = null, | |||
[System.Runtime.CompilerServices.CallerMemberName] string caller = null) where T : IEntityDescriptor; | |||
EntityInitializer BuildEntity<T>(EGID egid, T entityDescriptor, IEnumerable<object> implementors = null) | |||
where T : IEntityDescriptor; | |||
EntityInitializer BuildEntity<T>(EGID egid, T entityDescriptor, IEnumerable<object> implementors = null, | |||
[System.Runtime.CompilerServices.CallerMemberName] string caller = null) where T : IEntityDescriptor; | |||
//Todo: analyze if this can be internal or just related to serialization | |||
EntityInitializer BuildEntity | |||
(EGID egid, IComponentBuilder[] componentsToBuild, Type descriptorType, IEnumerable<object> implementors = null); | |||
EntityInitializer BuildEntity(EGID egid, IComponentBuilder[] componentsToBuild, Type descriptorType, | |||
IEnumerable<object> implementors = null, | |||
[System.Runtime.CompilerServices.CallerMemberName] string caller = null); | |||
#if UNITY_NATIVE | |||
Svelto.ECS.Native.NativeEntityFactory ToNative<T>(string callerName) where T : IEntityDescriptor, new(); | |||
#endif | |||
Svelto.ECS.Native.NativeEntityFactory ToNative<T>([System.Runtime.CompilerServices.CallerMemberName] string callerName | |||
= null) where T : IEntityDescriptor, new(); | |||
#endif | |||
} | |||
} |
@@ -1,3 +1,5 @@ | |||
using System.Runtime.CompilerServices; | |||
namespace Svelto.ECS | |||
{ | |||
public interface IEntityFunctions | |||
@@ -5,28 +7,19 @@ 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) where T : IEntityDescriptor, new(); | |||
void RemoveEntity<T>(EGID entityegid) where T : IEntityDescriptor, new(); | |||
void RemoveEntity<T>(uint entityID, ExclusiveBuildGroup groupID , [CallerMemberName] string caller = null) where T : IEntityDescriptor, new(); | |||
void RemoveEntity<T>(EGID entityegid , [CallerMemberName] string caller = null) where T : IEntityDescriptor, new(); | |||
void RemoveEntitiesFromGroup(ExclusiveBuildGroup groupID); | |||
void SwapEntitiesInGroup<T>(ExclusiveBuildGroup fromGroupID, ExclusiveBuildGroup toGroupID) where T : IEntityDescriptor, new(); | |||
void SwapEntityGroup<T>(uint entityID, ExclusiveBuildGroup fromGroupID, ExclusiveBuildGroup toGroupID) | |||
where T : IEntityDescriptor, new(); | |||
void SwapEntityGroup<T>(EGID fromID, ExclusiveBuildGroup toGroupID) where T : IEntityDescriptor, new(); | |||
void SwapEntityGroup<T>(EGID fromID, ExclusiveBuildGroup fromGroup, ExclusiveBuildGroup toGroupID) | |||
where T : IEntityDescriptor, new(); | |||
void SwapEntityGroup<T>(EGID fromID, EGID toId) where T : IEntityDescriptor, new(); | |||
void RemoveEntitiesFromGroup(ExclusiveBuildGroup groupID , [CallerMemberName] string caller = null); | |||
void SwapEntitiesInGroup(ExclusiveBuildGroup fromGroupID, ExclusiveBuildGroup toGroupID, [CallerMemberName] string caller = null); | |||
void SwapEntityGroup<T>(EGID fromID, EGID toId, ExclusiveBuildGroup mustBeFromGroup) | |||
where T : IEntityDescriptor, new(); | |||
void SwapEntityGroup<T>(uint entityID, ExclusiveBuildGroup fromGroupID, ExclusiveBuildGroup toGroupID, [CallerMemberName] string caller = null) where T : IEntityDescriptor, new(); | |||
void SwapEntityGroup<T>(EGID fromEGID, ExclusiveBuildGroup toGroupID, [CallerMemberName] string caller = null) where T : IEntityDescriptor, new(); | |||
void SwapEntityGroup<T>(EGID fromEGID, EGID toEGID, [CallerMemberName] string caller = null)where T : IEntityDescriptor, new(); | |||
void SwapEntityGroup<T>(EGID fromEGID, EGID toEGID, ExclusiveBuildGroup mustBeFromGroup, [CallerMemberName] string caller = null) where T : IEntityDescriptor, new(); | |||
#if UNITY_NATIVE | |||
Svelto.ECS.Native.NativeEntityRemove ToNativeRemove<T>(string memberName) where T : IEntityDescriptor, new(); | |||
Svelto.ECS.Native.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 | |||
} | |||
} |
@@ -1,13 +1,16 @@ | |||
#if SLOW_SVELTO_SUBMISSION | |||
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 | |||
/// This is set to become obsolete at a given point | |||
/// </summary> | |||
public interface INeedEGID | |||
{ | |||
//The set is used only by the framework, but it must stay there | |||
EGID ID { get; set; } | |||
} | |||
} | |||
} | |||
#endif |
@@ -1,5 +1,4 @@ | |||
using Svelto.ECS.Reference; | |||
#if SLOW_SVELTO_SUBMISSION | |||
namespace Svelto.ECS | |||
{ | |||
/// <summary> | |||
@@ -7,9 +6,11 @@ namespace Svelto.ECS | |||
/// It currently exist because of the publisher/consumer behavior, but the publisher/consumer must not be | |||
/// considered an ECS pattern. | |||
/// Other uses are invalid. | |||
/// It will become obsolete over the time | |||
/// </summary> | |||
public interface INeedEntityReference | |||
{ | |||
EntityReference selfReference { get; set; } | |||
} | |||
} | |||
} | |||
#endif |
@@ -1,9 +0,0 @@ | |||
namespace Svelto.ECS | |||
{ | |||
public interface IQueryingEntitiesEngine : IEngine | |||
{ | |||
EntitiesDB entitiesDB { set; } | |||
void Ready(); | |||
} | |||
} |
@@ -5,72 +5,85 @@ using Svelto.DataStructures; | |||
namespace Svelto.ECS.Experimental | |||
{ | |||
struct GroupsList | |||
internal struct GroupsList | |||
{ | |||
static GroupsList() | |||
public static GroupsList Init() | |||
{ | |||
groups = new FasterList<ExclusiveGroupStruct>(); | |||
sets = new HashSet<ExclusiveGroupStruct>(); | |||
} | |||
var group = new GroupsList(); | |||
group._groups = new FasterList<ExclusiveGroupStruct>(); | |||
group._sets = new HashSet<ExclusiveGroupStruct>(); | |||
static readonly FasterList<ExclusiveGroupStruct> groups; | |||
static readonly HashSet<ExclusiveGroupStruct> sets; | |||
return group; | |||
} | |||
public void Reset() { sets.Clear(); } | |||
public void Reset() | |||
{ | |||
_sets.Clear(); | |||
} | |||
public void AddRange(ExclusiveGroupStruct[] groupsToAdd, int length) | |||
{ | |||
for (int i = 0; i < length; i++) | |||
{ | |||
sets.Add(groupsToAdd[i]); | |||
} | |||
for (var i = 0; i < length; i++) _sets.Add(groupsToAdd[i]); | |||
} | |||
public void Add(ExclusiveGroupStruct @group) { sets.Add(group); } | |||
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]); | |||
} | |||
for (var i = 0; i < length; i++) _sets.Remove(groupsToIgnore[i]); | |||
} | |||
public void Exclude(ExclusiveGroupStruct groupsToIgnore) { sets.Remove(groupsToIgnore); } | |||
public void Exclude(ExclusiveGroupStruct groupsToIgnore) | |||
{ | |||
_sets.Remove(groupsToIgnore); | |||
} | |||
public void EnsureCapacity(uint preparecount) { groups.EnsureCapacity(preparecount); } | |||
public void Resize(uint preparecount) | |||
{ | |||
_groups.Resize(preparecount); | |||
} | |||
public FasterList<ExclusiveGroupStruct> Evaluate() | |||
{ | |||
groups.FastClear(); | |||
_groups.FastClear(); | |||
foreach (var item in sets) | |||
{ | |||
groups.Add(item); | |||
} | |||
foreach (var item in _sets) _groups.Add(item); | |||
return groups; | |||
return _groups; | |||
} | |||
FasterList<ExclusiveGroupStruct> _groups; | |||
HashSet<ExclusiveGroupStruct> _sets; | |||
} | |||
//I am not 100% sure why I made this thread-safe since it cannot be used inside jobs. | |||
public ref struct QueryGroups | |||
{ | |||
static readonly ThreadLocal<GroupsList> groups = new ThreadLocal<GroupsList>(); | |||
static readonly ThreadLocal<GroupsList> groups; | |||
static QueryGroups() | |||
{ | |||
groups = new ThreadLocal<GroupsList>(GroupsList.Init); | |||
} | |||
public QueryGroups(LocalFasterReadOnlyList<ExclusiveGroupStruct> groups) | |||
{ | |||
var groupsValue = QueryGroups.groups.Value; | |||
groupsValue.Reset(); | |||
groupsValue.AddRange(groups.ToArrayFast(out var count), count); | |||
groupsValue.AddRange(groups.ToArrayFast(out var count), count); | |||
} | |||
public QueryGroups(ExclusiveGroupStruct @group) | |||
public QueryGroups(ExclusiveGroupStruct group) | |||
{ | |||
var groupsValue = groups.Value; | |||
groupsValue.Reset(); | |||
groupsValue.Add(@group); | |||
groupsValue.Add(group); | |||
} | |||
public QueryGroups(uint preparecount) | |||
@@ -78,7 +91,7 @@ namespace Svelto.ECS.Experimental | |||
var groupsValue = groups.Value; | |||
groupsValue.Reset(); | |||
groupsValue.EnsureCapacity(preparecount); | |||
groupsValue.Resize(preparecount); | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
@@ -114,7 +127,7 @@ namespace Svelto.ECS.Experimental | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public QueryGroups Except(ExclusiveGroupStruct[] groupsToIgnore) | |||
{ | |||
var groupsValue = QueryGroups.groups.Value; | |||
var groupsValue = groups.Value; | |||
groupsValue.Exclude(groupsToIgnore, groupsToIgnore.Length); | |||
@@ -124,7 +137,7 @@ namespace Svelto.ECS.Experimental | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public QueryGroups Except(LocalFasterReadOnlyList<ExclusiveGroupStruct> groupsToIgnore) | |||
{ | |||
var groupsValue = QueryGroups.groups.Value; | |||
var groupsValue = groups.Value; | |||
groupsValue.Exclude(groupsToIgnore.ToArrayFast(out var count), count); | |||
@@ -134,7 +147,7 @@ namespace Svelto.ECS.Experimental | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public QueryGroups Except(FasterList<ExclusiveGroupStruct> groupsToIgnore) | |||
{ | |||
var groupsValue = QueryGroups.groups.Value; | |||
var groupsValue = groups.Value; | |||
groupsValue.Exclude(groupsToIgnore.ToArrayFast(out var count), count); | |||
@@ -144,7 +157,7 @@ namespace Svelto.ECS.Experimental | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public QueryGroups Except(FasterReadOnlyList<ExclusiveGroupStruct> groupsToIgnore) | |||
{ | |||
var groupsValue = QueryGroups.groups.Value; | |||
var groupsValue = groups.Value; | |||
groupsValue.Exclude(groupsToIgnore.ToArrayFast(out var count), count); | |||
@@ -176,29 +189,49 @@ namespace Svelto.ECS.Experimental | |||
return new QueryResult(groupsValue.Evaluate()); | |||
} | |||
public void Evaluate(FasterList<ExclusiveGroupStruct> group) | |||
{ | |||
var groupsValue = groups.Value; | |||
groupsValue.Evaluate().CopyTo(group.ToArrayFast(out var count), count); | |||
} | |||
} | |||
public readonly ref struct QueryResult | |||
{ | |||
public QueryResult(FasterList<ExclusiveGroupStruct> @group) { _group = @group; } | |||
public QueryResult(FasterList<ExclusiveGroupStruct> group) | |||
{ | |||
_group = group; | |||
} | |||
public LocalFasterReadOnlyList<ExclusiveGroupStruct> result => _group; | |||
readonly FasterReadOnlyList<ExclusiveGroupStruct> _group; | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public int Count<T>(EntitiesDB entitiesDB) | |||
where T : struct, IEntityComponent | |||
public int Count<T>(EntitiesDB entitiesDB) where T : struct, IEntityComponent | |||
{ | |||
var count = 0; | |||
var groupsCount = result.count; | |||
for (var i = 0; i < groupsCount; ++i) count += entitiesDB.Count<T>(result[i]); | |||
return count; | |||
} | |||
public int Max<T>(EntitiesDB entitiesDB) where T : struct, IEntityComponent | |||
{ | |||
int count = 0; | |||
var max = 0; | |||
var groupsCount = result.count; | |||
for (int i = 0; i < groupsCount; ++i) | |||
var groupsCount = result.count; | |||
for (var i = 0; i < groupsCount; ++i) | |||
{ | |||
count += entitiesDB.Count<T>(result[i]); | |||
var count = entitiesDB.Count<T>(result[i]); | |||
if (count > max) max = count; | |||
} | |||
return count; | |||
return max; | |||
} | |||
} | |||
} |
@@ -2,12 +2,12 @@ using Svelto.ECS.Internal; | |||
namespace Svelto.ECS | |||
{ | |||
public readonly struct ReactEngineContainer | |||
public readonly struct ReactEngineContainer<T> where T:IReactEngine | |||
{ | |||
public readonly string name; | |||
public readonly IReactEngine engine; | |||
public readonly T engine; | |||
public ReactEngineContainer(IReactEngine engine, string name) | |||
public ReactEngineContainer(T engine, string name) | |||
{ | |||
this.name = name; | |||
this.engine = engine; | |||
@@ -1,5 +1,4 @@ | |||
using Svelto.ECS.Reference; | |||
#if SLOW_SVELTO_SUBMISSION | |||
namespace Svelto.ECS.Internal | |||
{ | |||
delegate void SetEGIDWithoutBoxingActionCast<T>(ref T target, EGID egid) where T : struct, IEntityComponent; | |||
@@ -67,4 +66,5 @@ namespace Svelto.ECS.Internal | |||
} | |||
} | |||
} | |||
} | |||
} | |||
#endif |
@@ -1,60 +1,33 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
namespace Svelto.ECS.Schedulers | |||
namespace Svelto.ECS.Schedulers | |||
{ | |||
public sealed class SimpleEntitiesSubmissionScheduler : EntitiesSubmissionScheduler | |||
{ | |||
public SimpleEntitiesSubmissionScheduler(uint maxNumberOfOperationsPerFrame = UInt32.MaxValue) | |||
{ | |||
_enumerator = SubmitEntitiesAsync(maxNumberOfOperationsPerFrame); | |||
} | |||
public IEnumerator<bool> SubmitEntitiesAsync() | |||
{ | |||
return _enumerator; | |||
} | |||
public IEnumerator<bool> SubmitEntitiesAsync(uint maxNumberOfOperations) | |||
protected internal override EnginesRoot.EntitiesSubmitter onTick | |||
{ | |||
EnginesRoot.EntitiesSubmitter entitiesSubmitter = _onTick.Value; | |||
entitiesSubmitter.maxNumberOfOperationsPerFrame = maxNumberOfOperations; | |||
while (true) | |||
set | |||
{ | |||
if (paused == false) | |||
{ | |||
var entitiesSubmitterSubmitEntities = entitiesSubmitter.submitEntities; | |||
entitiesSubmitterSubmitEntities.MoveNext(); | |||
yield return entitiesSubmitterSubmitEntities.Current == true; | |||
} | |||
} | |||
} | |||
DBC.ECS.Check.Require(_entitiesSubmitter == null, "a scheduler can be exclusively used by one enginesRoot only"); | |||
public void SubmitEntities() | |||
{ | |||
do | |||
{ | |||
_enumerator.MoveNext(); | |||
} while (_enumerator.Current == true); | |||
_entitiesSubmitter = value; | |||
} | |||
} | |||
public override bool paused { get; set; } | |||
public override void Dispose() { } | |||
protected internal override EnginesRoot.EntitiesSubmitter onTick | |||
public void SubmitEntities() | |||
{ | |||
set | |||
try | |||
{ | |||
DBC.ECS.Check.Require(_onTick == null, "a scheduler can be exclusively used by one enginesRoot only"); | |||
_onTick = value; | |||
_entitiesSubmitter.Value.SubmitEntities(); | |||
} | |||
catch | |||
{ | |||
paused = true; | |||
throw; | |||
} | |||
} | |||
EnginesRoot.EntitiesSubmitter? _onTick; | |||
readonly IEnumerator<bool> _enumerator; | |||
EnginesRoot.EntitiesSubmitter? _entitiesSubmitter; | |||
} | |||
} |
@@ -7,9 +7,9 @@ namespace Svelto.ECS | |||
/// <summary> | |||
/// Enumerator that yields until the next Entities Submission | |||
/// </summary> | |||
public struct WaitForSubmissionEnumerator : IEnumerator | |||
public class WaitForSubmissionEnumerator : IEnumerator | |||
{ | |||
public WaitForSubmissionEnumerator(EntitiesSubmissionScheduler scheduler):this() | |||
public WaitForSubmissionEnumerator(EntitiesSubmissionScheduler scheduler) | |||
{ | |||
_scheduler = scheduler; | |||
} | |||
@@ -4,15 +4,13 @@ using Svelto.DataStructures; | |||
namespace Svelto.ECS | |||
{ | |||
/// <summary> | |||
/// I eventually realised that, with the ECS design, no form of communication other than polling entity components can | |||
/// exist. | |||
/// Using groups, you can have always an optimal set of entity components to poll. However EntityStreams | |||
/// can be useful if: | |||
/// - you need to react on seldom entity changes, usually due to user events | |||
/// - you want engines to be able to track entity changes | |||
/// - you want a thread-safe way to read entity states, which includes all the state changes and not the last | |||
/// one only | |||
/// - you want to communicate between EnginesRoots | |||
/// I eventually realised that, with the ECS design, no form of engines (systems) communication other | |||
/// than polling entity components is effective. | |||
/// The only purpose of this publisher/consumer model is to let two enginesroots communicate with each other | |||
/// through a thread safe ring buffer. | |||
/// The engines root A publishes entities. | |||
/// The engines root B can consume those entities at any time, as they will be a copy of the original | |||
/// entities and won't point directly to the database of the engines root A | |||
/// </summary> | |||
struct EntitiesStreams : IDisposable | |||
{ | |||
@@ -46,7 +44,7 @@ namespace Svelto.ECS | |||
public void Dispose() | |||
{ | |||
foreach (var stream in _streams) | |||
stream.Value.Dispose(); | |||
stream.value.Dispose(); | |||
} | |||
public static EntitiesStreams Create() | |||
@@ -1,31 +0,0 @@ | |||
namespace Svelto.ECS | |||
{ | |||
/// <summary> | |||
/// This EntityStream can be used in parallel jobs, but does NOT guarantee order. | |||
/// </summary> | |||
/// <typeparam name="T"></typeparam> | |||
public struct ThreadSafeNativeEntityStream<T> : ITypeSafeStream | |||
{ | |||
public ThreadSafeNativeEntityStream(EntitiesDB entitiesDB) | |||
{ | |||
} | |||
public void Dispose() | |||
{ | |||
} | |||
/// <summary> | |||
/// I am thinking to pass the component and do the queryEntity only as a validation | |||
/// </summary> | |||
/// <param name="entityComponent"></param> | |||
/// <param name="id"></param> | |||
public void PublishEntityChange(in T entityComponent, EGID id) | |||
{ | |||
#if DEBUG && !PROFILE_SVELTO | |||
#endif | |||
} | |||
} | |||
} |
@@ -7,37 +7,80 @@ namespace Svelto.ECS.Internal | |||
public interface ITypeSafeDictionary<TValue> : ITypeSafeDictionary where TValue : IEntityComponent | |||
{ | |||
void Add(uint egidEntityId, in TValue entityComponent); | |||
ref TValue this[uint idEntityId] { get; } | |||
bool TryGetValue(uint entityId, out TValue item); | |||
ref TValue GetOrCreate(uint idEntityId); | |||
bool TryGetValue(uint entityId, out TValue item); | |||
ref TValue GetOrAdd(uint idEntityId); | |||
IBuffer<TValue> GetValues(out uint count); | |||
ref TValue GetDirectValueByRef(uint key); | |||
ref TValue GetDirectValueByRef(uint key); | |||
ref TValue GetValueByRef(uint key); | |||
EntityIDs entityIDs { get; } | |||
} | |||
public interface ITypeSafeDictionary:IDisposable | |||
public interface ITypeSafeDictionary : IDisposable | |||
{ | |||
uint count { get; } | |||
int count { get; } | |||
ITypeSafeDictionary Create(); | |||
//todo: there is something wrong in the design of the execute callback methods. Something to cleanup | |||
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<ReactEngineContainer>> engines, in PlatformProfiler profiler); | |||
void ExecuteEnginesRemoveCallbacks(FasterDictionary<RefWrapperType, FasterList<ReactEngineContainer>> entityComponentEnginesDB, | |||
in PlatformProfiler profiler, ExclusiveGroupStruct @group); | |||
void AddEntitiesToDictionary | |||
(ITypeSafeDictionary toDictionary, ExclusiveGroupStruct groupId, in EnginesRoot.EntityReferenceMap entityLocator); | |||
void RemoveEntitiesFromDictionary(FasterList<(uint, string)> infosToProcess); | |||
void SwapEntitiesBetweenDictionaries(FasterList<(uint, uint, string)> infosToProcess, | |||
ExclusiveGroupStruct fromGroup, ExclusiveGroupStruct toGroup, ITypeSafeDictionary toComponentsDictionary); | |||
//------------ | |||
//This is now obsolete, but I cannot mark it as such because it's heavily used by legacy projects | |||
void ExecuteEnginesAddCallbacks | |||
(FasterDictionary<RefWrapperType, FasterList<ReactEngineContainer<IReactOnAdd>>> entityComponentEnginesDb | |||
, ITypeSafeDictionary destinationDatabase, ExclusiveGroupStruct toGroup, in PlatformProfiler profiler); | |||
//Version to use | |||
void ExecuteEnginesAddEntityCallbacksFast( | |||
FasterDictionary<RefWrapperType, FasterList<ReactEngineContainer<IReactOnAddEx>>> reactiveEnginesAdd, | |||
ExclusiveGroupStruct groupID, (uint, uint) rangeOfSubmittedEntitiesIndicies, in PlatformProfiler profiler); | |||
//------------ | |||
//This is now obsolete, but I cannot mark it as such because it's heavily used by legacy projects | |||
void ExecuteEnginesSwapCallbacks(FasterList<(uint, uint, string)> infosToProcess, | |||
FasterList<ReactEngineContainer<IReactOnSwap>> reactiveEnginesSwap, ExclusiveGroupStruct fromGroup, | |||
ExclusiveGroupStruct toGroup, in PlatformProfiler sampler); | |||
//Version to use | |||
void ExecuteEnginesSwapCallbacksFast(FasterList<ReactEngineContainer<IReactOnSwapEx>> reactiveEnginesSwap, | |||
ExclusiveGroupStruct fromGroup, ExclusiveGroupStruct toGroup, (uint, uint) rangeOfSubmittedEntitiesIndicies, | |||
in PlatformProfiler sampler); | |||
void AddEntitiesFromDictionary | |||
(ITypeSafeDictionary entitiesToSubmit, ExclusiveGroupStruct groupId, EnginesRoot enginesRoot); | |||
//------------ | |||
void AddEntityToDictionary(EGID fromEntityGid, EGID toEntityID, ITypeSafeDictionary toGroup); | |||
void RemoveEntityFromDictionary(EGID fromEntityGid); | |||
//This is now obsolete, but I cannot mark it as such because it's heavily used by legacy projects | |||
void ExecuteEnginesRemoveCallbacks(FasterList<(uint, string)> infosToProcess, | |||
FasterDictionary<RefWrapperType, FasterList<ReactEngineContainer<IReactOnRemove>>> reactiveEnginesRemove, | |||
ExclusiveGroupStruct fromGroup, in PlatformProfiler sampler); | |||
//Version to use | |||
void ExecuteEnginesRemoveCallbacksFast(FasterList<ReactEngineContainer<IReactOnRemoveEx>> reactiveEnginesRemoveEx, | |||
ExclusiveGroupStruct fromGroup, (uint, uint) rangeOfSubmittedEntitiesIndicies, | |||
in PlatformProfiler sampler); | |||
//------------ | |||
void ExecuteEnginesSwapCallbacks_Group( | |||
FasterDictionary<RefWrapperType, FasterList<ReactEngineContainer<IReactOnSwap>>> reactiveEnginesSwap, | |||
FasterDictionary<RefWrapperType, FasterList<ReactEngineContainer<IReactOnSwapEx>>> reactiveEnginesSwapEx, | |||
ITypeSafeDictionary toEntitiesDictionary, ExclusiveGroupStruct fromGroupId, ExclusiveGroupStruct toGroupId, | |||
in PlatformProfiler platformProfiler); | |||
void ExecuteEnginesRemoveCallbacks_Group( | |||
FasterDictionary<RefWrapperType, FasterList<ReactEngineContainer<IReactOnRemove>>> engines, | |||
FasterDictionary<RefWrapperType, FasterList<ReactEngineContainer<IReactOnRemoveEx>>> reactiveEnginesRemoveEx, | |||
ExclusiveGroupStruct @group, in PlatformProfiler profiler); | |||
void ExecuteEnginesDisposeCallbacks_Group | |||
(FasterDictionary<RefWrapperType, FasterList<ReactEngineContainer<IReactOnDispose>>> engines | |||
, ExclusiveGroupStruct group, in PlatformProfiler profiler); | |||
void ResizeTo(uint size); | |||
void IncreaseCapacityBy(uint size); | |||
void EnsureCapacity(uint size); | |||
void Trim(); | |||
void Clear(); | |||
void FastClear(); | |||
bool Has(uint key); | |||
bool ContainsKey(uint egidEntityId); | |||
uint GetIndex(uint valueEntityId); | |||
@@ -75,7 +75,7 @@ namespace Svelto.ECS.DataStructures | |||
readonly Allocator _allocator; | |||
readonly uint _threadsCount; | |||
#if UNITY_COLLECTIONS || UNITY_JOBS || UNITY_BURST | |||
#if UNITY_COLLECTIONS || UNITY_JOBS || UNITY_BURST | |||
#if UNITY_BURST | |||
[Unity.Burst.NoAlias] | |||
#endif | |||
@@ -1,15 +1,11 @@ | |||
#if DEBUG && !PROFILE_SVELTO | |||
#define ENABLE_DEBUG_CHECKS | |||
#endif | |||
#if DEBUG && !PROFILE_SVELTO | |||
//#define ENABLE_THREAD_SAFE_CHECKS | |||
#endif | |||
using System; | |||
using System.Diagnostics; | |||
using System.Runtime.CompilerServices; | |||
using Svelto.Common; | |||
using Svelto.Common.DataStructures; | |||
namespace Svelto.ECS.DataStructures | |||
{ | |||
@@ -34,18 +30,11 @@ namespace Svelto.ECS.DataStructures | |||
unsafe | |||
{ | |||
BasicTests(); | |||
#if ENABLE_THREAD_SAFE_CHECKS | |||
try | |||
using (_threadSentinel.TestThreadSafety()) | |||
{ | |||
#endif | |||
return _queue->size; | |||
#if ENABLE_THREAD_SAFE_CHECKS | |||
} | |||
finally | |||
{ | |||
Volatile.Write(ref _threadSentinel, 0); | |||
return _queue->size; | |||
} | |||
#endif | |||
} | |||
} | |||
} | |||
@@ -58,35 +47,23 @@ namespace Svelto.ECS.DataStructures | |||
unsafe | |||
{ | |||
BasicTests(); | |||
#if ENABLE_THREAD_SAFE_CHECKS | |||
try | |||
using (_threadSentinel.TestThreadSafety()) | |||
{ | |||
#endif | |||
return _queue->capacity; | |||
#if ENABLE_THREAD_SAFE_CHECKS | |||
} | |||
finally | |||
{ | |||
Volatile.Write(ref _threadSentinel, 0); | |||
return _queue->capacity; | |||
} | |||
#endif | |||
} | |||
} | |||
} | |||
public NativeBag(Allocator allocator) | |||
public NativeBag(Allocator allocator):this() | |||
{ | |||
unsafe | |||
{ | |||
var listData = (UnsafeBlob*) MemoryUtilities.Alloc<UnsafeBlob>((uint) 1, allocator); | |||
var listData = (UnsafeBlob*)MemoryUtilities.Alloc<UnsafeBlob>((uint)1, allocator); | |||
//clear to nullify the pointers | |||
//MemoryUtilities.MemClear((IntPtr) listData, (uint) sizeOf); | |||
listData->allocator = allocator; | |||
_queue = listData; | |||
#if ENABLE_THREAD_SAFE_CHECKS | |||
_threadSentinel = 0; | |||
#endif | |||
} | |||
} | |||
@@ -96,19 +73,12 @@ namespace Svelto.ECS.DataStructures | |||
unsafe | |||
{ | |||
BasicTests(); | |||
#if ENABLE_THREAD_SAFE_CHECKS | |||
try | |||
{ | |||
#endif | |||
if (_queue == null || _queue->ptr == null) | |||
return true; | |||
#if ENABLE_THREAD_SAFE_CHECKS | |||
} | |||
finally | |||
using (_threadSentinel.TestThreadSafety()) | |||
{ | |||
Volatile.Write(ref _threadSentinel, 0); | |||
if (_queue == null || _queue->ptr == null) | |||
return true; | |||
} | |||
#endif | |||
} | |||
return count == 0; | |||
@@ -119,84 +89,58 @@ namespace Svelto.ECS.DataStructures | |||
{ | |||
if (_queue != null) | |||
{ | |||
#if ENABLE_THREAD_SAFE_CHECKS | |||
//todo: this must be unit tested | |||
if (Interlocked.CompareExchange(ref _threadSentinel, 1, 0) != 0) | |||
throw new Exception("NativeBag is not thread safe, reading and writing operations can happen" + | |||
"on different threads, but not simultaneously"); | |||
BasicTests(); | |||
try | |||
using (_threadSentinel.TestThreadSafety()) | |||
{ | |||
#endif | |||
_queue->Dispose(); | |||
MemoryUtilities.Free((IntPtr) _queue, _queue->allocator); | |||
_queue = null; | |||
#if ENABLE_THREAD_SAFE_CHECKS | |||
} | |||
finally | |||
{ | |||
Volatile.Write(ref _threadSentinel, 0); | |||
_queue->Dispose(); | |||
MemoryUtilities.Free((IntPtr)_queue, _queue->allocator); | |||
_queue = null; | |||
} | |||
#endif | |||
} | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public ref T ReserveEnqueue<T>(out UnsafeArrayIndex index) where T : struct | |||
public ref T ReserveEnqueue<T> | |||
(out UnsafeArrayIndex index) | |||
where T : struct //should be unmanaged, but it's not due to Svelto.ECS constraints. | |||
{ | |||
unsafe | |||
{ | |||
BasicTests(); | |||
var sizeOf = MemoryUtilities.SizeOf<T>(); | |||
if (_queue->availableSpace - sizeOf < 0) | |||
using (_threadSentinel.TestThreadSafety()) | |||
{ | |||
_queue->Realloc((_queue->capacity + (uint)sizeOf) << 1); | |||
} | |||
#if ENABLE_THREAD_SAFE_CHECKS | |||
try | |||
{ | |||
#endif | |||
if (_queue->availableSpace - sizeOf < 0) | |||
{ | |||
_queue->Grow<T>(); | |||
} | |||
return ref _queue->Reserve<T>(out index); | |||
#if ENABLE_THREAD_SAFE_CHECKS | |||
return ref _queue->Reserve<T>(out index); | |||
} | |||
finally | |||
{ | |||
Volatile.Write(ref _threadSentinel, 0); | |||
} | |||
#endif | |||
} | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public void Enqueue<T>(in T item) where T : struct | |||
public void Enqueue<T> | |||
(in T item) where T : struct //should be unmanaged, but it's not due to Svelto.ECS constraints. | |||
{ | |||
unsafe | |||
{ | |||
BasicTests(); | |||
#if ENABLE_THREAD_SAFE_CHECKS | |||
try | |||
{ | |||
#endif | |||
var sizeOf = MemoryUtilities.SizeOf<T>(); | |||
if (_queue->availableSpace - sizeOf < 0) | |||
using (_threadSentinel.TestThreadSafety()) | |||
{ | |||
var capacityInBytes = (_queue->capacity + (uint)sizeOf); | |||
_queue->Realloc(capacityInBytes << 1); | |||
} | |||
var sizeOf = MemoryUtilities.SizeOf<T>(); | |||
if (_queue->availableSpace - sizeOf < 0) | |||
{ | |||
_queue->Grow<T>(); | |||
} | |||
_queue->Enqueue(item); | |||
#if ENABLE_THREAD_SAFE_CHECKS | |||
} | |||
finally | |||
{ | |||
Volatile.Write(ref _threadSentinel, 0); | |||
_queue->Enqueue(item); | |||
} | |||
#endif | |||
} | |||
} | |||
@@ -206,58 +150,37 @@ namespace Svelto.ECS.DataStructures | |||
unsafe | |||
{ | |||
BasicTests(); | |||
#if ENABLE_THREAD_SAFE_CHECKS | |||
try | |||
{ | |||
#endif | |||
_queue->Clear(); | |||
#if ENABLE_THREAD_SAFE_CHECKS | |||
} | |||
finally | |||
using (_threadSentinel.TestThreadSafety()) | |||
{ | |||
Volatile.Write(ref _threadSentinel, 0); | |||
_queue->Clear(); | |||
} | |||
#endif | |||
} | |||
} | |||
public T Dequeue<T>() where T : struct | |||
public T Dequeue<T>() where T : struct //should be unmanaged, but it's not due to Svelto.ECS constraints. | |||
{ | |||
unsafe | |||
{ | |||
BasicTests(); | |||
#if ENABLE_THREAD_SAFE_CHECKS | |||
try | |||
{ | |||
#endif | |||
return _queue->Dequeue<T>(); | |||
#if ENABLE_THREAD_SAFE_CHECKS | |||
} | |||
finally | |||
using (_threadSentinel.TestThreadSafety()) | |||
{ | |||
Volatile.Write(ref _threadSentinel, 0); | |||
return _queue->Dequeue<T>(); | |||
} | |||
#endif | |||
} | |||
} | |||
public ref T AccessReserved<T>(UnsafeArrayIndex reservedIndex) where T : struct | |||
public ref T AccessReserved<T>(UnsafeArrayIndex reservedIndex) where T : struct //should be unmanaged, but it's not due to Svelto.ECS constraints. | |||
{ | |||
unsafe | |||
{ | |||
BasicTests(); | |||
#if ENABLE_THREAD_SAFE_CHECKS | |||
try | |||
{ | |||
#endif | |||
return ref _queue->AccessReserved<T>(reservedIndex); | |||
#if ENABLE_THREAD_SAFE_CHECKS | |||
} | |||
finally | |||
using (_threadSentinel.TestThreadSafety()) | |||
{ | |||
Volatile.Write(ref _threadSentinel, 0); | |||
return ref _queue->AccessReserved<T>(reservedIndex); | |||
} | |||
#endif | |||
} | |||
} | |||
@@ -266,17 +189,10 @@ namespace Svelto.ECS.DataStructures | |||
{ | |||
if (_queue == null) | |||
throw new Exception("SimpleNativeArray: null-access"); | |||
#if ENABLE_THREAD_SAFE_CHECKS | |||
todo: this must be unit tested | |||
if (Interlocked.CompareExchange(ref _threadSentinel, 1, 0) != 0) | |||
throw new Exception("NativeBag is not thread safe, reading and writing operations can happen" | |||
+ "on different threads, but not simultaneously"); | |||
#endif | |||
} | |||
readonly Sentinel _threadSentinel; | |||
#if ENABLE_THREAD_SAFE_CHECKS | |||
int _threadSentinel; | |||
#endif | |||
#if UNITY_COLLECTIONS || UNITY_JOBS || UNITY_BURST | |||
#if UNITY_BURST | |||
[Unity.Burst.NoAlias] | |||
@@ -1,6 +1,11 @@ | |||
#if DEBUG && !PROFILE_SVELTO | |||
#define ENABLE_DEBUG_CHECKS | |||
#endif | |||
using System; | |||
using System.Runtime.CompilerServices; | |||
using Svelto.Common; | |||
using Svelto.Common.DataStructures; | |||
using Allocator = Svelto.Common.Allocator; | |||
namespace Svelto.ECS.DataStructures | |||
@@ -24,7 +29,7 @@ namespace Svelto.ECS.DataStructures | |||
{ | |||
unsafe | |||
{ | |||
#if DEBUG && !PROFILE_SVELTO | |||
#if ENABLE_DEBUG_CHECKS | |||
if (_list == null) | |||
throw new Exception("NativeDynamicArray: null-access"); | |||
if (_hashType != TypeHash<T>.hash) | |||
@@ -34,13 +39,13 @@ namespace Svelto.ECS.DataStructures | |||
return (_list->count / MemoryUtilities.SizeOf<T>()); | |||
} | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public int Size() | |||
public int SizeInBytes() | |||
{ | |||
unsafe | |||
{ | |||
#if DEBUG && !PROFILE_SVELTO | |||
#if ENABLE_DEBUG_CHECKS | |||
if (_list == null) | |||
throw new Exception("NativeDynamicArray: null-access"); | |||
@@ -54,7 +59,7 @@ namespace Svelto.ECS.DataStructures | |||
{ | |||
unsafe | |||
{ | |||
#if DEBUG && !PROFILE_SVELTO | |||
#if ENABLE_DEBUG_CHECKS | |||
if (_list == null) | |||
throw new Exception("NativeDynamicArray: null-access"); | |||
if (_hashType != TypeHash<T>.hash) | |||
@@ -74,16 +79,19 @@ namespace Svelto.ECS.DataStructures | |||
{ | |||
unsafe | |||
{ | |||
#if DEBUG && !PROFILE_SVELTO | |||
var rtnStruc = new NativeDynamicArray {_hashType = TypeHash<T>.hash}; | |||
#else | |||
#if ENABLE_DEBUG_CHECKS | |||
var rtnStruc = new NativeDynamicArray | |||
{ | |||
_hashType = TypeHash<T>.hash, | |||
}; | |||
#else | |||
NativeDynamicArray rtnStruc = default; | |||
#endif | |||
UnsafeArray* listData = (UnsafeArray*) MemoryUtilities.Alloc<UnsafeArray>(1, allocator); | |||
UnsafeArray* listData = (UnsafeArray*)MemoryUtilities.Alloc<UnsafeArray>(1, allocator); | |||
//clear to nullify the pointers | |||
//MemoryUtilities.MemClear((IntPtr) listData, structSize); | |||
rtnStruc._allocator = allocator; | |||
listData->Realloc<T>(newLength, allocator); | |||
@@ -98,7 +106,7 @@ namespace Svelto.ECS.DataStructures | |||
{ | |||
unsafe | |||
{ | |||
#if DEBUG && !PROFILE_SVELTO | |||
#if ENABLE_DEBUG_CHECKS | |||
if (_list == null) | |||
throw new Exception("NativeDynamicArray: null-access"); | |||
if (_hashType != TypeHash<T>.hash) | |||
@@ -106,14 +114,22 @@ namespace Svelto.ECS.DataStructures | |||
if (index >= Count<T>()) | |||
throw new Exception($"NativeDynamicArray: out of bound access, index {index} count {Count<T>()}"); | |||
#endif | |||
return ref _list->Get<T>(index); | |||
#if ENABLE_DEBUG_CHECKS | |||
using (_threadSentinel.TestThreadSafety()) | |||
{ | |||
#endif | |||
return ref _list->Get<T>(index); | |||
#if ENABLE_DEBUG_CHECKS | |||
} | |||
#endif | |||
} | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public ref T Get<T>(int index) where T : struct | |||
{ | |||
return ref Get<T>((uint) index); | |||
return ref Get<T>((uint)index); | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
@@ -121,27 +137,45 @@ namespace Svelto.ECS.DataStructures | |||
{ | |||
unsafe | |||
{ | |||
#if DEBUG && !PROFILE_SVELTO | |||
#if ENABLE_DEBUG_CHECKS | |||
if (_list == null) | |||
throw new Exception("NativeDynamicArray: null-access"); | |||
if (_hashType != TypeHash<T>.hash) | |||
throw new Exception("NativeDynamicArray: not expected type used"); | |||
if (index >= Capacity<T>()) | |||
throw new Exception($"NativeDynamicArray: out of bound access, index {index} capacity {Capacity<T>()}"); | |||
throw new Exception( | |||
$"NativeDynamicArray: out of bound access, index {index} capacity {Capacity<T>()}"); | |||
#endif | |||
#if ENABLE_DEBUG_CHECKS | |||
using (_threadSentinel.TestThreadSafety()) | |||
{ | |||
#endif | |||
_list->Set(index, value); | |||
#if ENABLE_DEBUG_CHECKS | |||
} | |||
#endif | |||
_list->Set(index, value); | |||
} | |||
} | |||
public unsafe void Dispose() | |||
{ | |||
#if DEBUG && !PROFILE_SVELTO | |||
#if ENABLE_DEBUG_CHECKS | |||
if (_list == null) | |||
throw new Exception("NativeDynamicArray: null-access"); | |||
#endif | |||
_list->Dispose(_allocator); | |||
MemoryUtilities.Free((IntPtr) _list, _allocator); | |||
#if ENABLE_DEBUG_CHECKS | |||
using (_threadSentinel.TestThreadSafety()) | |||
{ | |||
#endif | |||
_list->Dispose(_allocator); | |||
MemoryUtilities.Free((IntPtr)_list, _allocator); | |||
#if ENABLE_DEBUG_CHECKS | |||
} | |||
#endif | |||
_list = null; | |||
} | |||
@@ -150,56 +184,80 @@ namespace Svelto.ECS.DataStructures | |||
{ | |||
unsafe | |||
{ | |||
#if DEBUG && !PROFILE_SVELTO | |||
#if ENABLE_DEBUG_CHECKS | |||
if (_list == null) | |||
throw new Exception("NativeDynamicArray: null-access"); | |||
if (_hashType != TypeHash<T>.hash) | |||
throw new Exception("NativeDynamicArray: not expected type used"); | |||
#endif | |||
if (Count<T>() == Capacity<T>()) | |||
#if ENABLE_DEBUG_CHECKS | |||
using (_threadSentinel.TestThreadSafety()) | |||
{ | |||
_list->Realloc<T>((uint) ((Capacity<T>() + 1) * 1.5f), _allocator); | |||
} | |||
#endif | |||
if (Count<T>() == Capacity<T>()) | |||
{ | |||
_list->Realloc<T>((uint)((Capacity<T>() + 1) * 1.5f), _allocator); | |||
} | |||
_list->Add(item); | |||
_list->Add(item); | |||
#if ENABLE_DEBUG_CHECKS | |||
} | |||
#endif | |||
} | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public ref T AddAt<T>(uint index) where T : struct | |||
{ | |||
unsafe | |||
{ | |||
#if DEBUG && !PROFILE_SVELTO | |||
#if ENABLE_DEBUG_CHECKS | |||
if (_list == null) | |||
throw new Exception("NativeDynamicArray: null-access"); | |||
if (_hashType != TypeHash<T>.hash) | |||
throw new Exception("NativeDynamicArray: not expected type used"); | |||
#endif | |||
var structSize = (uint) MemoryUtilities.SizeOf<T>(); | |||
var structSize = (uint)MemoryUtilities.SizeOf<T>(); | |||
if (index >= Capacity<T>()) | |||
_list->Realloc<T>((uint) ((index + 1) * 1.5f), _allocator); | |||
#if ENABLE_DEBUG_CHECKS | |||
using (_threadSentinel.TestThreadSafety()) | |||
{ | |||
#endif | |||
if (index >= Capacity<T>()) | |||
_list->Realloc<T>((uint)((index + 1) * 1.5f), _allocator); | |||
var writeIndex = (index + 1) * structSize; | |||
if (_list->count < writeIndex) | |||
_list->SetCountTo(writeIndex); | |||
var writeIndex = (index + 1) * structSize; | |||
if (_list->count < writeIndex) | |||
_list->SetCountTo(writeIndex); | |||
return ref _list->Get<T>(index); | |||
return ref _list->Get<T>(index); | |||
#if ENABLE_DEBUG_CHECKS | |||
} | |||
#endif | |||
} | |||
} | |||
public void Resize<T>(uint newCapacity) where T : struct | |||
{ | |||
unsafe | |||
{ | |||
#if DEBUG && !PROFILE_SVELTO | |||
#if ENABLE_DEBUG_CHECKS | |||
if (_list == null) | |||
throw new Exception("NativeDynamicArray: null-access"); | |||
if (_hashType != TypeHash<T>.hash) | |||
throw new Exception("NativeDynamicArray: not expected type used"); | |||
#endif | |||
_list->Realloc<T>((uint) newCapacity, _allocator); | |||
#if ENABLE_DEBUG_CHECKS | |||
using (_threadSentinel.TestThreadSafety()) | |||
{ | |||
#endif | |||
_list->Realloc<T>((uint)newCapacity, _allocator); | |||
#if ENABLE_DEBUG_CHECKS | |||
} | |||
#endif | |||
} | |||
} | |||
@@ -207,16 +265,24 @@ namespace Svelto.ECS.DataStructures | |||
{ | |||
unsafe | |||
{ | |||
#if DEBUG && !PROFILE_SVELTO | |||
#if ENABLE_DEBUG_CHECKS | |||
if (_list == null) | |||
throw new Exception("NativeDynamicArray: null-access"); | |||
if (_hashType != TypeHash<T>.hash) | |||
throw new Exception("NativeDynamicArray: not expected type used"); | |||
#endif | |||
uint structSize = (uint) MemoryUtilities.SizeOf<T>(); | |||
uint size = (uint) (count * structSize); | |||
uint structSize = (uint)MemoryUtilities.SizeOf<T>(); | |||
uint size = (uint)(count * structSize); | |||
_list->SetCountTo((uint) size); | |||
#if ENABLE_DEBUG_CHECKS | |||
using (_threadSentinel.TestThreadSafety()) | |||
{ | |||
#endif | |||
_list->SetCountTo((uint)size); | |||
#if ENABLE_DEBUG_CHECKS | |||
} | |||
#endif | |||
} | |||
} | |||
@@ -225,18 +291,27 @@ namespace Svelto.ECS.DataStructures | |||
{ | |||
unsafe | |||
{ | |||
#if DEBUG && !PROFILE_SVELTO | |||
#if ENABLE_DEBUG_CHECKS | |||
if (_list == null) | |||
throw new Exception("NativeDynamicArray: null-access"); | |||
if (_hashType != TypeHash<T>.hash) | |||
throw new Exception("NativeDynamicArray: not expected type used"); | |||
var structSize = (uint) MemoryUtilities.SizeOf<T>(); | |||
var structSize = (uint)MemoryUtilities.SizeOf<T>(); | |||
if (_list->space - (int)structSize < 0) | |||
throw new Exception("NativeDynamicArray: no writing authorized"); | |||
#endif | |||
_list->Add(item); | |||
#if ENABLE_DEBUG_CHECKS | |||
using (_threadSentinel.TestThreadSafety()) | |||
{ | |||
#endif | |||
_list->Add(item); | |||
#if ENABLE_DEBUG_CHECKS | |||
} | |||
#endif | |||
} | |||
} | |||
@@ -245,7 +320,7 @@ namespace Svelto.ECS.DataStructures | |||
{ | |||
unsafe | |||
{ | |||
#if DEBUG && !PROFILE_SVELTO | |||
#if ENABLE_DEBUG_CHECKS | |||
if (_list == null) | |||
throw new Exception("NativeDynamicArray: null-access"); | |||
if (_hashType != TypeHash<T>.hash) | |||
@@ -253,13 +328,22 @@ namespace Svelto.ECS.DataStructures | |||
if (Count<T>() == 0) | |||
throw new Exception("NativeDynamicArray: empty array invalid operation"); | |||
#endif | |||
var indexToMove = Count<T>() - 1; | |||
if (index < indexToMove) | |||
#if ENABLE_DEBUG_CHECKS | |||
using (_threadSentinel.TestThreadSafety()) | |||
{ | |||
Set<T>(index, Get<T>((uint) indexToMove)); | |||
#endif | |||
var indexToMove = Count<T>() - 1; | |||
if (index < indexToMove) | |||
{ | |||
Set<T>(index, Get<T>((uint)indexToMove)); | |||
} | |||
_list->Pop<T>(); | |||
#if ENABLE_DEBUG_CHECKS | |||
} | |||
_list->Pop<T>(); | |||
#endif | |||
} | |||
} | |||
@@ -268,38 +352,47 @@ namespace Svelto.ECS.DataStructures | |||
{ | |||
unsafe | |||
{ | |||
#if DEBUG && !PROFILE_SVELTO | |||
#if ENABLE_DEBUG_CHECKS | |||
if (_list == null) | |||
throw new Exception("NativeDynamicArray: null-access"); | |||
#endif | |||
_list->Clear(); | |||
#if ENABLE_DEBUG_CHECKS | |||
using (_threadSentinel.TestThreadSafety()) | |||
{ | |||
#endif | |||
_list->Clear(); | |||
#if ENABLE_DEBUG_CHECKS | |||
} | |||
#endif | |||
} | |||
} | |||
public unsafe T* ToPTR<T>() where T : unmanaged | |||
{ | |||
#if DEBUG && !PROFILE_SVELTO | |||
#if ENABLE_DEBUG_CHECKS | |||
if (_list == null) | |||
throw new Exception("NativeDynamicArray: null-access"); | |||
if (_hashType != TypeHash<T>.hash) | |||
throw new Exception("NativeDynamicArray: not expected type used"); | |||
#endif | |||
return (T*) _list->ptr; | |||
return (T*)_list->ptr; | |||
} | |||
public IntPtr ToIntPTR<T>() where T : struct | |||
{ | |||
unsafe | |||
{ | |||
#if DEBUG && !PROFILE_SVELTO | |||
if (_list == null) | |||
throw new Exception("NativeDynamicArray: null-access"); | |||
if (_hashType != TypeHash<T>.hash) | |||
throw new Exception("NativeDynamicArray: not expected type used"); | |||
#if ENABLE_DEBUG_CHECKS | |||
if (_list == null) | |||
throw new Exception("NativeDynamicArray: null-access"); | |||
if (_hashType != TypeHash<T>.hash) | |||
throw new Exception("NativeDynamicArray: not expected type used"); | |||
#endif | |||
return (IntPtr) _list->ptr; | |||
return (IntPtr)_list->ptr; | |||
} | |||
} | |||
@@ -307,7 +400,7 @@ namespace Svelto.ECS.DataStructures | |||
{ | |||
unsafe | |||
{ | |||
#if DEBUG && !PROFILE_SVELTO | |||
#if ENABLE_DEBUG_CHECKS | |||
if (_list == null) | |||
throw new Exception("NativeDynamicArray: null-access"); | |||
if (_hashType != TypeHash<T>.hash) | |||
@@ -318,12 +411,20 @@ namespace Svelto.ECS.DataStructures | |||
var ret = new T[count]; | |||
var lengthToCopyInBytes = count * MemoryUtilities.SizeOf<T>(); | |||
fixed (void* handle = ret) | |||
#if ENABLE_DEBUG_CHECKS | |||
using (_threadSentinel.TestThreadSafety()) | |||
{ | |||
Unsafe.CopyBlock(handle, _list->ptr, (uint) lengthToCopyInBytes); | |||
#endif | |||
fixed (void* handle = ret) | |||
{ | |||
Unsafe.CopyBlock(handle, _list->ptr, (uint)lengthToCopyInBytes); | |||
} | |||
return ret; | |||
#if ENABLE_DEBUG_CHECKS | |||
} | |||
return ret; | |||
#endif | |||
} | |||
} | |||
@@ -331,20 +432,27 @@ namespace Svelto.ECS.DataStructures | |||
{ | |||
unsafe | |||
{ | |||
#if DEBUG && !PROFILE_SVELTO | |||
#if ENABLE_DEBUG_CHECKS | |||
if (_list == null) | |||
throw new Exception("NativeDynamicArray: null-access"); | |||
if (_hashType != TypeHash<T>.hash) | |||
throw new Exception("NativeDynamicArray: not expected type used"); | |||
#endif | |||
var capacity = Capacity<T>(); | |||
var lengthToCopyInBytes = capacity * MemoryUtilities.SizeOf<T>(); | |||
var ret = new T[capacity]; | |||
var capacity = Capacity<T>(); | |||
var ret = new T[capacity]; | |||
fixed (void* handle = ret) | |||
#if ENABLE_DEBUG_CHECKS | |||
using (_threadSentinel.TestThreadSafety()) | |||
{ | |||
Unsafe.CopyBlock(handle, _list->ptr, (uint) lengthToCopyInBytes); | |||
#endif | |||
fixed (void* handle = ret) | |||
{ | |||
MemoryUtilities.MemCpy<T>((IntPtr)_list->ptr, 0, (IntPtr)handle, 0, (uint)capacity); | |||
} | |||
#if ENABLE_DEBUG_CHECKS | |||
} | |||
#endif | |||
return ret; | |||
} | |||
@@ -354,18 +462,24 @@ namespace Svelto.ECS.DataStructures | |||
{ | |||
unsafe | |||
{ | |||
#if DEBUG && !PROFILE_SVELTO | |||
#if ENABLE_DEBUG_CHECKS | |||
if (_list == null) | |||
throw new Exception("NativeDynamicArray: null-access"); | |||
if (_hashType != TypeHash<T>.hash) | |||
throw new Exception("NativeDynamicArray: not expected type used"); | |||
#endif | |||
var sizeOf = MemoryUtilities.SizeOf<T>(); | |||
//Unsafe.CopyBlock may not be memory overlapping safe (memcpy vs memmove) | |||
Buffer.MemoryCopy(_list->ptr + (index + 1) * sizeOf, _list->ptr + index * sizeOf, _list->count | |||
, (uint) ((Count<T>() - (index + 1)) * sizeOf)); | |||
_list->Pop<T>(); | |||
#if ENABLE_DEBUG_CHECKS | |||
using (_threadSentinel.TestThreadSafety()) | |||
{ | |||
#endif | |||
MemoryUtilities.MemMove<T>((IntPtr)_list->ptr, index + 1, index, (uint)(Count<T>() - (index + 1))); | |||
_list->Pop<T>(); | |||
#if ENABLE_DEBUG_CHECKS | |||
} | |||
#endif | |||
} | |||
} | |||
@@ -373,11 +487,18 @@ namespace Svelto.ECS.DataStructures | |||
{ | |||
unsafe | |||
{ | |||
MemoryUtilities.MemClear((IntPtr) _list->ptr, (uint) _list->capacity); | |||
#if ENABLE_DEBUG_CHECKS | |||
using (_threadSentinel.TestThreadSafety()) | |||
{ | |||
#endif | |||
MemoryUtilities.MemClear((IntPtr)_list->ptr, (uint)_list->capacity); | |||
#if ENABLE_DEBUG_CHECKS | |||
} | |||
#endif | |||
} | |||
} | |||
#if UNITY_COLLECTIONS || UNITY_JOBS || UNITY_BURST | |||
#if UNITY_COLLECTIONS || UNITY_JOBS || UNITY_BURST | |||
#if UNITY_BURST | |||
[Unity.Burst.NoAlias] | |||
#endif | |||
@@ -387,6 +508,9 @@ namespace Svelto.ECS.DataStructures | |||
#if DEBUG && !PROFILE_SVELTO | |||
int _hashType; | |||
#endif | |||
Sentinel _threadSentinel; | |||
Allocator _allocator; | |||
} | |||
} |
@@ -1,9 +1,10 @@ | |||
using System.Runtime.CompilerServices; | |||
using System; | |||
using System.Runtime.CompilerServices; | |||
using Svelto.Common; | |||
namespace Svelto.ECS.DataStructures | |||
{ | |||
public struct NativeDynamicArrayCast<T> where T : struct | |||
public struct NativeDynamicArrayCast<T>:IDisposable where T : struct | |||
{ | |||
public NativeDynamicArrayCast(uint size, Allocator allocator) | |||
{ | |||
@@ -61,6 +62,18 @@ namespace Svelto.ECS.DataStructures | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public NativeDynamicArray ToNativeArray() { return _array; } | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public void Set(uint index, in T value) | |||
{ | |||
_array.Set(index, value); | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public void Set(int index, in T value) | |||
{ | |||
_array.Set((uint)index, value); | |||
} | |||
public bool isValid => _array.isValid; | |||
@@ -1,12 +1,54 @@ | |||
using System; | |||
using System.Runtime.CompilerServices; | |||
using System.Threading; | |||
using Svelto.Common; | |||
namespace Svelto.ECS.DataStructures | |||
{ | |||
public struct SharedNative<T> : IDisposable where T : struct, IDisposable | |||
{ | |||
#if UNITY_COLLECTIONS || (UNITY_JOBS || UNITY_BURST) | |||
[global::Unity.Collections.LowLevel.Unsafe.NativeDisableUnsafePtrRestriction] | |||
#endif | |||
unsafe IntPtr ptr; | |||
public SharedNative(in T value) | |||
{ | |||
unsafe | |||
{ | |||
ptr = MemoryUtilities.Alloc<T>(1, Allocator.Persistent); | |||
Unsafe.Write((void*)ptr, value); | |||
} | |||
} | |||
public void Dispose() | |||
{ | |||
unsafe | |||
{ | |||
Unsafe.AsRef<T>((void*)ptr).Dispose(); | |||
MemoryUtilities.Free((IntPtr)ptr, Allocator.Persistent); | |||
ptr = IntPtr.Zero; | |||
} | |||
} | |||
public ref T value | |||
{ | |||
get | |||
{ | |||
unsafe | |||
{ | |||
DBC.ECS.Check.Require(ptr != null, "SharedNative has not been initialized"); | |||
return ref Unsafe.AsRef<T>((void*)ptr); | |||
} | |||
} | |||
} | |||
} | |||
public struct SharedNativeInt: IDisposable | |||
{ | |||
#if UNITY_COLLECTIONS || UNITY_JOBS || UNITY_BURST | |||
#if UNITY_COLLECTIONS || (UNITY_JOBS || UNITY_BURST) | |||
[global::Unity.Collections.LowLevel.Unsafe.NativeDisableUnsafePtrRestriction] | |||
#endif | |||
unsafe int* data; | |||
@@ -14,7 +14,7 @@ namespace Svelto.ECS.DataStructures | |||
/// Note: this must work inside burst, so it must follow burst restrictions | |||
/// It's a typeless native queue based on a ring-buffer model. This means that the writing head and the | |||
/// reading head always advance independently. If there is enough space left by dequeued elements, | |||
/// the writing head will wrap around if it reaches the end of the array. The writing head cannot ever surpass the reading head. | |||
/// the writing head will wrap around. The writing head cannot ever surpass the reading head. | |||
/// | |||
/// </summary> | |||
struct UnsafeBlob : IDisposable | |||
@@ -47,7 +47,7 @@ namespace Svelto.ECS.DataStructures | |||
internal Allocator allocator; | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
internal void Enqueue<T>(in T item) where T : struct | |||
internal void Enqueue<T>(in T item) where T : struct //should be unmanaged, but it's not due to Svelto.ECS constraints. | |||
{ | |||
unsafe | |||
{ | |||
@@ -93,7 +93,7 @@ namespace Svelto.ECS.DataStructures | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
//The index returned is the index of the unwrapped ring. It must be wrapped again before to be used | |||
internal ref T Reserve<T>(out UnsafeArrayIndex index) where T : struct | |||
internal ref T Reserve<T>(out UnsafeArrayIndex index) where T : struct //should be unmanaged, but it's not due to Svelto.ECS constraints. | |||
{ | |||
unsafe | |||
{ | |||
@@ -119,7 +119,7 @@ namespace Svelto.ECS.DataStructures | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
internal ref T AccessReserved<T>(UnsafeArrayIndex index) where T : struct | |||
internal ref T AccessReserved<T>(UnsafeArrayIndex index) where T : struct //should be unmanaged, but it's not due to Svelto.ECS constraints. | |||
{ | |||
unsafe | |||
{ | |||
@@ -133,7 +133,7 @@ namespace Svelto.ECS.DataStructures | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
internal T Dequeue<T>() where T : struct | |||
internal T Dequeue<T>() where T : struct //should be unmanaged, but it's not due to Svelto.ECS constraints. | |||
{ | |||
unsafe | |||
{ | |||
@@ -176,36 +176,39 @@ namespace Svelto.ECS.DataStructures | |||
return item; | |||
} | |||
} | |||
/// <summary> | |||
/// This version of Realloc unwraps a queue, but doesn't change the unwrapped index of existing elements. | |||
/// In this way the previously indices will remain valid | |||
/// This code unwraps the queue and resizes the array, but doesn't change the unwrapped index of existing elements. | |||
/// In this way the previously reserved indices will remain valid | |||
/// </summary> | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
internal void Realloc(uint newCapacity) | |||
internal void Grow<T>() where T : struct //should be unmanaged, but it's not due to Svelto.ECS constraints. | |||
{ | |||
unsafe | |||
{ | |||
//be sure it's multiple of 4. Assuming that what we write is aligned to 4, then we will always have aligned wrapped heads. | |||
var sizeOf = MemoryUtilities.SizeOf<T>(); | |||
var oldCapacity = capacity; | |||
uint newCapacity = (uint) ((oldCapacity + sizeOf) << 1); | |||
//be sure it's multiple of 4. Assuming that what we write is aligned to 4, then we will always have aligned wrapped heads | |||
//the reading and writing head always increment in multiple of 4 | |||
newCapacity += MemoryUtilities.Pad4(newCapacity); | |||
byte* newPointer = null; | |||
#if DEBUG && !PROFILE_SVELTO | |||
if (newCapacity <= capacity) | |||
throw new Exception("new capacity must be bigger than current"); | |||
#endif | |||
newPointer = (byte*) MemoryUtilities.Alloc(newCapacity, allocator); | |||
//copy wrapped content if there is any | |||
var currentSize = _writeIndex - _readIndex; | |||
if (currentSize > 0) | |||
{ | |||
var oldReaderHead = _readIndex % capacity; | |||
var writerHead = _writeIndex % capacity; | |||
var oldReaderHead = _readIndex % oldCapacity; | |||
var oldWriterHead = _writeIndex % oldCapacity; | |||
//there was no wrapping | |||
if (oldReaderHead < writerHead) | |||
//Remembering that the unwrapped reader cannot ever surpass the unwrapped writer, if the reader is behind the writer | |||
//it means that the writer didn't wrap. It's the natural position so the data can be copied with | |||
//a single memcpy | |||
if (oldReaderHead < oldWriterHead) | |||
{ | |||
var newReaderHead = _readIndex % newCapacity; | |||
@@ -213,15 +216,21 @@ namespace Svelto.ECS.DataStructures | |||
} | |||
else | |||
{ | |||
var byteCountToEnd = capacity - oldReaderHead; | |||
var newReaderHead = _readIndex % newCapacity; | |||
//if the wrapped writer is behind the wrapped reader, it means the writer wrapped. Therefore | |||
//I need to copy the data from the current wrapped reader to the end and then from the | |||
//begin of the array to the current wrapped writer. | |||
var byteCountToEnd = oldCapacity - oldReaderHead; //bytes to copy from the reader to the end | |||
var newReaderHead = _readIndex % newCapacity; | |||
#if DEBUG && !PROFILE_SVELTO | |||
if (newReaderHead + byteCountToEnd + writerHead > newCapacity) | |||
if (newReaderHead + byteCountToEnd + oldWriterHead > newCapacity) //basically the test is the old size must be less than the new capacity. | |||
throw new Exception("something is wrong with my previous assumptions"); | |||
#endif | |||
//I am leaving on purpose gap at the begin of the new array if there is any, it will be | |||
//anyway used once it's time to wrap. | |||
Unsafe.CopyBlock(newPointer + newReaderHead, ptr + oldReaderHead, byteCountToEnd); //from the old reader head to the end of the old array | |||
Unsafe.CopyBlock(newPointer + newReaderHead + byteCountToEnd, ptr + 0, (uint) writerHead); //from the begin of the old array to the old writer head (rember the writerHead wrapped) | |||
Unsafe.CopyBlock(newPointer + newReaderHead + byteCountToEnd, ptr + 0, (uint) oldWriterHead); //from the begin of the old array to the old writer head (rember the writerHead wrapped) | |||
} | |||
} | |||
@@ -231,7 +240,7 @@ namespace Svelto.ECS.DataStructures | |||
ptr = newPointer; | |||
capacity = newCapacity; | |||
//_readIndex = 0; readIndex won't change to keep the previous reserved indices valid | |||
//_readIndex = 0; the idea is that the old readIndex should remain unchanged. Remember this is the unwrapped index. | |||
_writeIndex = _readIndex + currentSize; | |||
} | |||
} | |||
@@ -260,4 +269,4 @@ namespace Svelto.ECS.DataStructures | |||
uint _writeIndex; | |||
uint _readIndex; | |||
} | |||
} | |||
} |
@@ -21,7 +21,7 @@ namespace Svelto.ECS | |||
_subscriber = callback; | |||
if (notifyImmediately) | |||
_subscriber(_senderID, initialValue); | |||
_subscriber(senderID, initialValue); | |||
_senderID = senderID; | |||
_value = initialValue; | |||
@@ -16,6 +16,7 @@ namespace Svelto.ECS | |||
//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 make this work with base descriptors too | |||
_nativeRemoveOperations.Add(new NativeOperationRemove( | |||
EntityDescriptorTemplate<T>.descriptor.componentsToBuild, TypeCache<T>.type | |||
, memberName)); | |||
@@ -25,7 +26,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)); | |||
@@ -33,19 +34,21 @@ namespace Svelto.ECS | |||
return new NativeEntitySwap(_nativeSwapOperationQueue, _nativeSwapOperations.count - 1); | |||
} | |||
NativeEntityFactory ProvideNativeEntityFactoryQueue<T>(string memberName) where T : IEntityDescriptor, new() | |||
NativeEntityFactory ProvideNativeEntityFactoryQueue<T>(string caller) where T : IEntityDescriptor, new() | |||
{ | |||
DBC.ECS.Check.Require(EntityDescriptorTemplate<T>.descriptor.IsUnmanaged(), "can't build entities with not native types"); | |||
DBC.ECS.Check.Require(EntityDescriptorTemplate<T>.descriptor.IsUnmanaged() | |||
, "can't build entities with not native types"); | |||
DBC.ECS.Check.Require(string.IsNullOrEmpty(caller) == false, "an invalid caller has been provided"); | |||
//todo: remove operation array and store entity descriptor hash in the return value | |||
_nativeAddOperations.Add( | |||
new NativeOperationBuild(EntityDescriptorTemplate<T>.descriptor.componentsToBuild, TypeCache<T>.type, memberName)); | |||
_nativeAddOperations.Add(new NativeOperationBuild(EntityDescriptorTemplate<T>.descriptor.componentsToBuild | |||
, TypeCache<T>.type, caller)); | |||
return new NativeEntityFactory(_nativeAddOperationQueue, _nativeAddOperations.count - 1, _entityLocator); | |||
} | |||
void FlushNativeOperations(in PlatformProfiler profiler) | |||
{ | |||
using (profiler.Sample("Native Remove/Swap Operations")) | |||
using (profiler.Sample("Native Remove Operations")) | |||
{ | |||
var removeBuffersCount = _nativeRemoveOperationQueue.count; | |||
//todo, I don't like that this scans all the queues even if they are empty | |||
@@ -55,17 +58,23 @@ namespace Svelto.ECS | |||
while (buffer.IsEmpty() == false) | |||
{ | |||
var componentsIndex = buffer.Dequeue<uint>(); | |||
var entityEGID = buffer.Dequeue<EGID>(); | |||
NativeOperationRemove nativeRemoveOperation = _nativeRemoveOperations[componentsIndex]; | |||
var componentsIndex = buffer.Dequeue<uint>(); | |||
var entityEGID = buffer.Dequeue<EGID>(); | |||
ref NativeOperationRemove nativeRemoveOperation = ref _nativeRemoveOperations[componentsIndex]; | |||
CheckRemoveEntityID(entityEGID, nativeRemoveOperation.entityDescriptorType | |||
, nativeRemoveOperation.caller); | |||
QueueEntitySubmitOperation(new EntitySubmitOperation( | |||
EntitySubmitOperationType.Remove, entityEGID, entityEGID | |||
, nativeRemoveOperation.components)); | |||
QueueRemoveEntityOperation( | |||
entityEGID, FindRealComponents(entityEGID, nativeRemoveOperation.components) | |||
, nativeRemoveOperation.caller); | |||
} | |||
} | |||
} | |||
using (profiler.Sample("Native Swap Operations")) | |||
{ | |||
var swapBuffersCount = _nativeSwapOperationQueue.count; | |||
for (int i = 0; i < swapBuffersCount; i++) | |||
{ | |||
@@ -76,28 +85,28 @@ namespace Svelto.ECS | |||
var componentsIndex = buffer.Dequeue<uint>(); | |||
var entityEGID = buffer.Dequeue<DoubleEGID>(); | |||
var componentBuilders = _nativeSwapOperations[componentsIndex].components; | |||
ref var nativeSwapOperation = ref _nativeSwapOperations[componentsIndex]; | |||
CheckRemoveEntityID(entityEGID.@from | |||
, _nativeSwapOperations[componentsIndex].entityDescriptorType | |||
, _nativeSwapOperations[componentsIndex].caller); | |||
CheckAddEntityID(entityEGID.to, _nativeSwapOperations[componentsIndex].entityDescriptorType | |||
, _nativeSwapOperations[componentsIndex].caller); | |||
CheckRemoveEntityID(entityEGID.@from, nativeSwapOperation.entityDescriptorType | |||
, nativeSwapOperation.caller); | |||
CheckAddEntityID(entityEGID.to, nativeSwapOperation.entityDescriptorType | |||
, nativeSwapOperation.caller); | |||
QueueEntitySubmitOperation(new EntitySubmitOperation( | |||
EntitySubmitOperationType.Swap, entityEGID.@from, entityEGID.to | |||
, componentBuilders)); | |||
QueueSwapEntityOperation(entityEGID.@from, entityEGID.to | |||
, FindRealComponents(entityEGID.@from, nativeSwapOperation.components) | |||
, nativeSwapOperation.caller); | |||
} | |||
} | |||
} | |||
//todo: it feels weird that these builds in the transient entities database while it could build directly to the final one | |||
using (profiler.Sample("Native Add Operations")) | |||
{ | |||
var addBuffersCount = _nativeAddOperationQueue.count; | |||
for (int i = 0; i < addBuffersCount; i++) | |||
{ | |||
ref var buffer = ref _nativeAddOperationQueue.GetBuffer(i); | |||
//todo: I don't like to iterate a constant number of buffer and skip the empty ones | |||
while (buffer.IsEmpty() == false) | |||
{ | |||
var componentsIndex = buffer.Dequeue<uint>(); | |||
@@ -105,21 +114,24 @@ namespace Svelto.ECS | |||
var reference = buffer.Dequeue<EntityReference>(); | |||
var componentCounts = buffer.Dequeue<uint>(); | |||
Check.Assert(egid.groupID.isInvalid == false, "invalid group detected, are you using new ExclusiveGroupStruct() instead of new ExclusiveGroup()?"); | |||
Check.Assert(egid.groupID.isInvalid == false | |||
, "invalid group detected, are you using new ExclusiveGroupStruct() instead of new ExclusiveGroup()?"); | |||
var componentBuilders = _nativeAddOperations[componentsIndex].components; | |||
ref var nativeOperation = ref _nativeAddOperations[componentsIndex]; | |||
#if DEBUG && !PROFILE_SVELTO | |||
var entityDescriptorType = _nativeAddOperations[componentsIndex].entityDescriptorType; | |||
CheckAddEntityID(egid, entityDescriptorType, _nativeAddOperations[componentsIndex].caller); | |||
var entityDescriptorType = nativeOperation.entityDescriptorType; | |||
CheckAddEntityID(egid, entityDescriptorType, nativeOperation.caller); | |||
#endif | |||
_entityLocator.SetReference(reference, egid); | |||
var dic = EntityFactory.BuildGroupedEntities(egid, _groupedEntityToAdd, componentBuilders | |||
, null | |||
//todo: I reckon is not necessary to carry the components array in the native operation, it's enough to know the descriptor type | |||
//however I guess this can work only if the type is hashed, which could be done with the burst type hash | |||
var dic = EntityFactory.BuildGroupedEntities(egid, _groupedEntityToAdd | |||
, nativeOperation.components, null | |||
#if DEBUG && !PROFILE_SVELTO | |||
, entityDescriptorType | |||
#endif | |||
); | |||
); | |||
var init = new EntityInitializer(egid, dic, reference); | |||
@@ -149,7 +161,7 @@ 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; |
@@ -1,7 +1,5 @@ | |||
using System.Runtime.CompilerServices; | |||
using Svelto.Common; | |||
using Svelto.DataStructures; | |||
using Svelto.DataStructures.Native; | |||
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 | |||
@@ -52,7 +50,6 @@ namespace Svelto.ECS | |||
return false; | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public static bool TryGetEntity<T>(this EntitiesDB entitiesDb, uint entityID, ExclusiveGroupStruct @group, out T value) | |||
where T : unmanaged, IEntityComponent | |||
@@ -66,14 +63,13 @@ namespace Svelto.ECS | |||
value = default; | |||
return false; | |||
} | |||
[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) |
@@ -0,0 +1,117 @@ | |||
using System; | |||
using System.Runtime.CompilerServices; | |||
using Svelto.Common; | |||
using Svelto.DataStructures; | |||
using Svelto.DataStructures.Native; | |||
using Svelto.ECS.DataStructures; | |||
namespace Svelto.ECS.Native | |||
{ | |||
/// <summary> | |||
/// Note: this class should really be ref struct by design. It holds the reference of a dictionary that can become | |||
/// invalid. Unfortunately it can be a ref struct, because Jobs needs to hold if by paramater. So the deal is | |||
/// that a job can use it as long as nothing else is modifying the entities database and the NativeEGIDMapper | |||
/// is disposed right after the use. | |||
/// </summary> | |||
public readonly struct NativeEGIDMapper<T> : IEGIDMapper where T : unmanaged, IEntityComponent | |||
{ | |||
public static readonly NativeEGIDMapper<T> empty = new NativeEGIDMapper<T> | |||
(default, new SharedNative<SveltoDictionary<uint, T, NativeStrategy<SveltoDictionaryNode<uint>>, | |||
NativeStrategy<T>, NativeStrategy<int>>>( | |||
new SveltoDictionary<uint, T, NativeStrategy<SveltoDictionaryNode<uint>>, | |||
NativeStrategy<T>, NativeStrategy<int>>(0, Allocator.Persistent))); | |||
public NativeEGIDMapper(ExclusiveGroupStruct groupStructId, | |||
in SharedNative<SveltoDictionary<uint, T, NativeStrategy<SveltoDictionaryNode<uint>>, NativeStrategy<T>, | |||
NativeStrategy<int>>> toNative) : this() | |||
{ | |||
groupID = groupStructId; | |||
_map = toNative; | |||
} | |||
public int count => _map.value.count; | |||
public Type entityType => TypeCache<T>.type; | |||
public ExclusiveGroupStruct groupID { get; } | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public ref T Entity(uint entityID) | |||
{ | |||
var sveltoDictionary = _map.value; | |||
#if DEBUG && !PROFILE_SVELTO | |||
if (sveltoDictionary.TryFindIndex(entityID, out var findIndex) == false) | |||
throw new Exception($"Entity {entityID} not found in this group {groupID} - {typeof(T).Name}"); | |||
#else | |||
sveltoDictionary.TryFindIndex(entityID, out var findIndex); | |||
#endif | |||
return ref sveltoDictionary.GetDirectValueByRef(findIndex); | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public bool TryGetEntity(uint entityID, out T value) | |||
{ | |||
var sveltoDictionary = _map.value; | |||
if (sveltoDictionary.count > 0 && sveltoDictionary.TryFindIndex(entityID, out var index)) | |||
{ | |||
var values = sveltoDictionary.unsafeValues.ToRealBuffer(); | |||
value = values[index]; | |||
return true; | |||
} | |||
value = default; | |||
return false; | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public NB<T> GetArrayAndEntityIndex(uint entityID, out uint index) | |||
{ | |||
var sveltoDictionary = _map.value; | |||
if (sveltoDictionary.TryFindIndex(entityID, out index)) | |||
return sveltoDictionary.unsafeValues.ToRealBuffer(); | |||
#if DEBUG && !PROFILE_SVELTO | |||
throw new ECSException("Entity not found"); | |||
#else | |||
return default; | |||
#endif | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public bool TryGetArrayAndEntityIndex(uint entityID, out uint index, out NB<T> array) | |||
{ | |||
index = 0; | |||
var sveltoDictionary = _map.value; | |||
if (sveltoDictionary.count > 0 && sveltoDictionary.TryFindIndex(entityID, out index)) | |||
{ | |||
array = sveltoDictionary.unsafeValues.ToRealBuffer(); | |||
return true; | |||
} | |||
array = default; | |||
return false; | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public bool Exists(uint idEntityId) | |||
{ | |||
var sveltoDictionary = _map.value; | |||
return sveltoDictionary.count > 0 && sveltoDictionary.TryFindIndex(idEntityId, out _); | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public uint GetIndex(uint entityID) | |||
{ | |||
return _map.value.GetIndex(entityID); | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public bool FindIndex(uint valueKey, out uint index) | |||
{ | |||
return _map.value.TryFindIndex(valueKey, out index); | |||
} | |||
readonly SharedNative<SveltoDictionary<uint, T, NativeStrategy<SveltoDictionaryNode<uint>>, NativeStrategy<T>, | |||
NativeStrategy<int>>> _map; | |||
} | |||
} |
@@ -0,0 +1,78 @@ | |||
using System; | |||
using Svelto.DataStructures; | |||
using Svelto.DataStructures.Native; | |||
using Svelto.ECS.DataStructures; | |||
namespace Svelto.ECS.Native | |||
{ | |||
/// <summary> | |||
/// Note: this class should really be ref struct by design. It holds the reference of a dictionary that can become | |||
/// invalid. Unfortunately it can be a ref struct, because Jobs needs to hold if by paramater. So the deal is | |||
/// that a job can use it as long as nothing else is modifying the entities database and the NativeEGIDMultiMapper | |||
/// is disposed right after the use. | |||
/// | |||
///WARNING: REMEMBER THIS MUST BE DISPOSED OF, AS IT USES NATIVE MEMORY. IT WILL LEAK MEMORY OTHERWISE | |||
/// | |||
/// </summary> | |||
public struct NativeEGIDMultiMapper<T> : IDisposable where T : unmanaged, IEntityComponent | |||
{ | |||
public NativeEGIDMultiMapper(in SveltoDictionary< | |||
/*key */ExclusiveGroupStruct, | |||
/*value*/ | |||
SharedNative<SveltoDictionary<uint, T, NativeStrategy<SveltoDictionaryNode<uint>>, NativeStrategy<T>, | |||
NativeStrategy<int>>>, | |||
/*strategy to store the key*/ NativeStrategy<SveltoDictionaryNode<ExclusiveGroupStruct>>, | |||
/*strategy to store the value*/ | |||
NativeStrategy<SharedNative<SveltoDictionary<uint, T, NativeStrategy<SveltoDictionaryNode<uint>>, | |||
NativeStrategy<T>, NativeStrategy<int>>>>, NativeStrategy<int>> dictionary) | |||
{ | |||
_dic = dictionary; | |||
} | |||
public int count => (int)_dic.count; | |||
public void Dispose() | |||
{ | |||
_dic.Dispose(); | |||
} | |||
public ref T Entity(EGID entity) | |||
{ | |||
#if DEBUG && !PROFILE_SVELTO | |||
if (Exists(entity) == false) | |||
throw new Exception($"NativeEGIDMultiMapper: Entity not found {entity}"); | |||
#endif | |||
ref var sveltoDictionary = ref _dic.GetValueByRef(entity.groupID); | |||
return ref sveltoDictionary.value.GetValueByRef(entity.entityID); | |||
} | |||
public uint GetIndex(EGID entity) | |||
{ | |||
#if DEBUG && !PROFILE_SVELTO | |||
if (Exists(entity) == false) | |||
throw new Exception($"NativeEGIDMultiMapper: Entity not found {entity}"); | |||
#endif | |||
ref var sveltoDictionary = ref _dic.GetValueByRef(entity.groupID); | |||
return sveltoDictionary.value.GetIndex(entity.entityID); | |||
} | |||
public bool Exists(EGID entity) | |||
{ | |||
return _dic.TryFindIndex(entity.groupID, out var index) && | |||
_dic.GetDirectValueByRef(index).value.ContainsKey(entity.entityID); | |||
} | |||
public bool TryGetEntity(EGID entity, out T component) | |||
{ | |||
component = default; | |||
return _dic.TryFindIndex(entity.groupID, out var index) && | |||
_dic.GetDirectValueByRef(index).value.TryGetValue(entity.entityID, out component); | |||
} | |||
SveltoDictionary<ExclusiveGroupStruct, | |||
SharedNative<SveltoDictionary<uint, T, NativeStrategy<SveltoDictionaryNode<uint>>, NativeStrategy<T>, | |||
NativeStrategy<int>>>, NativeStrategy<SveltoDictionaryNode<ExclusiveGroupStruct>>, NativeStrategy< | |||
SharedNative<SveltoDictionary<uint, T, NativeStrategy<SveltoDictionaryNode<uint>>, NativeStrategy<T>, | |||
NativeStrategy<int>>>>, NativeStrategy<int>> _dic; | |||
} | |||
} |
@@ -5,7 +5,7 @@ namespace Svelto.ECS.Native | |||
{ | |||
public readonly struct NativeEntityFactory | |||
{ | |||
internal NativeEntityFactory(AtomicNativeBags addOperationQueue, int index, EnginesRoot.LocatorMap entityLocator) | |||
internal NativeEntityFactory(AtomicNativeBags addOperationQueue, int index, EnginesRoot.EntityReferenceMap entityLocator) | |||
{ | |||
_index = index; | |||
_addOperationQueue = addOperationQueue; | |||
@@ -16,17 +16,18 @@ namespace Svelto.ECS.Native | |||
(uint eindex, ExclusiveBuildGroup exclusiveBuildGroup, int threadIndex) | |||
{ | |||
EntityReference reference = _entityLocator.ClaimReference(); | |||
NativeBag unsafeBuffer = _addOperationQueue.GetBuffer(threadIndex + 1); | |||
NativeBag bagPerEntityPerThread = _addOperationQueue.GetBuffer(threadIndex + 1); | |||
unsafeBuffer.Enqueue(_index); //each native ECS native operation is stored in an array, each request to perform a native operation in a queue. _index is the index of the operation in the array that will be dequeued later | |||
unsafeBuffer.Enqueue(new EGID(eindex, exclusiveBuildGroup)); | |||
unsafeBuffer.Enqueue(reference); | |||
bagPerEntityPerThread.Enqueue(_index); //each native ECS native operation is stored in an array, each request to perform a native operation in a queue. _index is the index of the operation in the array that will be dequeued later | |||
bagPerEntityPerThread.Enqueue(new EGID(eindex, exclusiveBuildGroup)); | |||
bagPerEntityPerThread.Enqueue(reference); | |||
//NativeEntityInitializer is quite a complex beast. It holds the starting values of the component set by the user. These components must be later dequeued and in order to know how many components | |||
//must be dequeued, a count must be used. The space to hold the count is then reserved in the queue and index will be used access the count later on through NativeEntityInitializer so it can increment it. | |||
unsafeBuffer.ReserveEnqueue<uint>(out var index) = 0; | |||
//index is not the number of components of the entity, it's just the number of components that the user decide to initialise | |||
bagPerEntityPerThread.ReserveEnqueue<uint>(out var index) = 0; | |||
return new NativeEntityInitializer(unsafeBuffer, index, reference); | |||
return new NativeEntityInitializer(bagPerEntityPerThread, index, reference); | |||
} | |||
public NativeEntityInitializer BuildEntity(EGID egid, int threadIndex) | |||
@@ -34,7 +35,7 @@ namespace Svelto.ECS.Native | |||
return BuildEntity(egid.entityID, egid.groupID, threadIndex); | |||
} | |||
readonly EnginesRoot.LocatorMap _entityLocator; | |||
readonly EnginesRoot.EntityReferenceMap _entityLocator; | |||
readonly AtomicNativeBags _addOperationQueue; | |||
readonly int _index; | |||
} |
@@ -1,3 +1,4 @@ | |||
#if UNITY_NATIVE //at the moment I am still considering NativeOperations useful only for Unity | |||
using Svelto.ECS.DataStructures; | |||
namespace Svelto.ECS.Native | |||
@@ -19,12 +20,15 @@ namespace Svelto.ECS.Native | |||
{ | |||
uint id = EntityComponentID<T>.ID.Data; | |||
_unsafeBuffer.AccessReserved<uint>(_index)++; | |||
_unsafeBuffer.AccessReserved<uint>(_index)++; //number of components added so far | |||
//Since NativeEntityInitializer is a ref struct, it guarantees that I am enqueueing components of the | |||
//last entity built | |||
_unsafeBuffer.Enqueue(id); | |||
_unsafeBuffer.Enqueue(component); | |||
} | |||
public EntityReference reference => _reference; | |||
} | |||
} | |||
} | |||
#endif |
@@ -1,15 +1,15 @@ | |||
#if UNITY_NATIVE | |||
using System.Runtime.CompilerServices; | |||
using Svelto.Common; | |||
using Svelto.DataStructures; | |||
using Svelto.DataStructures.Native; | |||
using Svelto.ECS.DataStructures; | |||
using Svelto.ECS.Internal; | |||
namespace Svelto.ECS.Native | |||
{ | |||
public static class UnityNativeEntityDBExtensions | |||
{ | |||
internal static NativeEGIDMapper<T> ToNativeEGIDMapper<T>(this TypeSafeDictionary<T> dic, | |||
static NativeEGIDMapper<T> ToNativeEGIDMapper<T>(this TypeSafeDictionary<T> dic, | |||
ExclusiveGroupStruct groupStructId) where T : unmanaged, IEntityComponent | |||
{ | |||
var mapper = new NativeEGIDMapper<T>(groupStructId, dic.implUnmgd); | |||
@@ -32,7 +32,7 @@ namespace Svelto.ECS.Native | |||
out NativeEGIDMapper<T> mapper) | |||
where T : unmanaged, IEntityComponent | |||
{ | |||
mapper = default; | |||
mapper = NativeEGIDMapper<T>.empty; | |||
if (entitiesDb.SafeQueryEntityDictionary<T>(groupStructId, out var typeSafeDictionary) == false || | |||
typeSafeDictionary.count == 0) | |||
return false; | |||
@@ -43,21 +43,25 @@ namespace Svelto.ECS.Native | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
///Note: if I use a SharedNativeSveltoDictionary for implUnmg, I may be able to cache NativeEGIDMultiMapper | |||
/// and reuse it | |||
public static NativeEGIDMultiMapper<T> QueryNativeMappedEntities<T>(this EntitiesDB entitiesDb, | |||
LocalFasterReadOnlyList<ExclusiveGroupStruct> groups, Allocator allocator) | |||
LocalFasterReadOnlyList<ExclusiveGroupStruct> groups, Allocator allocator) | |||
where T : unmanaged, IEntityComponent | |||
{ | |||
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 | |||
var dictionary = new SveltoDictionary< | |||
/*key */ExclusiveGroupStruct, | |||
/*value*/SharedNative<SveltoDictionary<uint, T, NativeStrategy<SveltoDictionaryNode<uint>>, NativeStrategy<T>, NativeStrategy<int>>>, | |||
/*strategy to store the key*/ NativeStrategy<SveltoDictionaryNode<ExclusiveGroupStruct>>, | |||
/*strategy to store the value*/NativeStrategy< | |||
SharedNative<SveltoDictionary<uint, T, NativeStrategy<SveltoDictionaryNode<uint>>, NativeStrategy<T>, NativeStrategy<int>>>> | |||
, NativeStrategy<int>> | |||
((uint) groups.count, allocator); | |||
foreach (var group in groups) | |||
{ | |||
if (entitiesDb.SafeQueryEntityDictionary<T>(group, out var typeSafeDictionary) == true) | |||
if (typeSafeDictionary.count > 0) | |||
//if (typeSafeDictionary.count > 0) | |||
dictionary.Add(group, ((TypeSafeDictionary<T>)typeSafeDictionary).implUnmgd); | |||
} | |||
@@ -65,4 +69,3 @@ namespace Svelto.ECS.Native | |||
} | |||
} | |||
} | |||
#endif |
@@ -38,17 +38,16 @@ namespace Svelto.ECS | |||
while (_db.MoveNext() == true) | |||
{ | |||
var group = _db.Current; | |||
if (group.Key.IsEnabled() == false) | |||
if (group.key.IsEnabled() == false) | |||
continue; | |||
ITypeSafeDictionary<T1> typeSafeDictionary = @group.Value as ITypeSafeDictionary<T1>; | |||
ITypeSafeDictionary<T1> typeSafeDictionary = @group.value as ITypeSafeDictionary<T1>; | |||
if (typeSafeDictionary.count == 0) | |||
continue; | |||
_array.collection = new EntityCollection<T1>(typeSafeDictionary.GetValues(out var count), count); | |||
_array.@group = group.Key; | |||
_array.collection = new EntityCollection<T1>(typeSafeDictionary.GetValues(out var count), count, | |||
typeSafeDictionary.entityIDs); | |||
_array.@group = group.key; | |||
return true; | |||
} | |||
@@ -5,10 +5,10 @@ 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 | |||
public static bool AddEntityToFilter<N>(this EntitiesDB.LegacyFilters legacyFilters, int filtersID, EGID egid, N mapper) where N : IEGIDMultiMapper | |||
{ | |||
ref var filter = | |||
ref filters.CreateOrGetFilterForGroup(filtersID, egid.groupID, new RefWrapperType(mapper.entityType)); | |||
ref legacyFilters.CreateOrGetFilterForGroup(filtersID, egid.groupID, new RefWrapperType(mapper.entityType)); | |||
return filter.Add(egid.entityID, mapper); | |||
} | |||
@@ -1,251 +1,242 @@ | |||
using System.Runtime.CompilerServices; | |||
using Svelto.DataStructures; | |||
using Svelto.DataStructures.Native; | |||
using Svelto.ECS.Hybrid; | |||
using Svelto.ECS.Internal; | |||
namespace Svelto.ECS | |||
{ | |||
public static class EntityCollectionExtension | |||
{ | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public static void Deconstruct<T1> | |||
(in this EntityCollection<T1> ec, out NB<T1> buffer, out int count) where T1 : unmanaged, IEntityComponent | |||
public static void Deconstruct<T1>(in this EntityCollection<T1> ec, out NB<T1> buffer, out int count) | |||
where T1 : unmanaged, IEntityComponent | |||
{ | |||
buffer = ec._nativedBuffer; | |||
count = (int) ec.count; | |||
count = (int)ec.count; | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public static void Deconstruct<T1, T2> | |||
(in this EntityCollection<T1, T2> ec, out NB<T1> buffer1, out NB<T2> buffer2, out int count) | |||
where T1 : unmanaged, IEntityComponent where T2 : unmanaged, IEntityComponent | |||
public static void Deconstruct<T1>(in this EntityCollection<T1> ec, out NB<T1> buffer, | |||
out NativeEntityIDs entityIDs, out int count) where T1 : unmanaged, IEntityComponent | |||
{ | |||
buffer1 = ec.buffer1._nativedBuffer; | |||
buffer2 = ec.buffer2._nativedBuffer; | |||
count = (int) ec.count; | |||
buffer = ec._nativedBuffer; | |||
count = (int)ec.count; | |||
entityIDs = ec._nativedIndices; | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public static void Deconstruct<T1, T2>(in this EntityCollection<T1, T2> ec, out NB<T1> buffer1, | |||
out NB<T2> buffer2, out NativeEntityIDs entityIDs, out int count) where T1 : unmanaged, IEntityComponent | |||
where T2 : unmanaged, IEntityComponent | |||
{ | |||
buffer1 = ec.buffer1._nativedBuffer; | |||
buffer2 = ec.buffer2._nativedBuffer; | |||
count = ec.count; | |||
entityIDs = ec.buffer1._nativedIndices; | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public static void Deconstruct<T1, T2, T3> | |||
(in this EntityCollection<T1, T2, T3> ec, out NB<T1> buffer1, out NB<T2> buffer2, out NB<T3> buffer3 | |||
, out int count) where T1 : unmanaged, IEntityComponent | |||
where T2 : unmanaged, IEntityComponent | |||
where T3 : unmanaged, IEntityComponent | |||
public static void Deconstruct<T1, T2>(in this EntityCollection<T1, T2> ec, out NB<T1> buffer1, | |||
out NB<T2> buffer2, out int count) where T1 : unmanaged, IEntityComponent | |||
where T2 : unmanaged, IEntityComponent | |||
{ | |||
buffer1 = ec.buffer1._nativedBuffer; | |||
buffer2 = ec.buffer2._nativedBuffer; | |||
buffer3 = ec.buffer3._nativedBuffer; | |||
count = (int) ec.count; | |||
count = (int)ec.count; | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public static void Deconstruct<T1, T2, T3, T4> | |||
(in this EntityCollection<T1, T2, T3, T4> ec, out NB<T1> buffer1, out NB<T2> buffer2, out NB<T3> buffer3 | |||
, out NB<T4> buffer4, out int count) where T1 : unmanaged, IEntityComponent | |||
where T2 : unmanaged, IEntityComponent | |||
where T3 : unmanaged, IEntityComponent | |||
where T4 : unmanaged, IEntityComponent | |||
public static void Deconstruct<T1, T2, T3>(in this EntityCollection<T1, T2, T3> ec, out NB<T1> buffer1, | |||
out NB<T2> buffer2, out NB<T3> buffer3, out int count) where T1 : unmanaged, IEntityComponent | |||
where T2 : unmanaged, IEntityComponent | |||
where T3 : unmanaged, IEntityComponent | |||
{ | |||
buffer1 = ec.buffer1._nativedBuffer; | |||
buffer2 = ec.buffer2._nativedBuffer; | |||
buffer3 = ec.buffer3._nativedBuffer; | |||
buffer4 = ec.buffer4._nativedBuffer; | |||
count = (int) ec.count; | |||
count = (int)ec.count; | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public static BT<NB<T1>> ToBuffer<T1>(in this EntityCollection<T1> ec) where T1 : unmanaged, IEntityComponent | |||
public static void Deconstruct<T1, T2, T3>(in this EntityCollection<T1, T2, T3> ec, out NB<T1> buffer1, | |||
out NB<T2> buffer2, out NB<T3> buffer3, out NativeEntityIDs entityIDs, out int count) | |||
where T1 : unmanaged, IEntityComponent | |||
where T2 : unmanaged, IEntityComponent | |||
where T3 : unmanaged, IEntityComponent | |||
{ | |||
return new BT<NB<T1>>(ec._nativedBuffer, ec.count); | |||
buffer1 = ec.buffer1._nativedBuffer; | |||
buffer2 = ec.buffer2._nativedBuffer; | |||
buffer3 = ec.buffer3._nativedBuffer; | |||
count = (int)ec.count; | |||
entityIDs = ec.buffer1._nativedIndices; | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public static BT<NB<T1>, NB<T2>> ToBuffers<T1, T2> | |||
(in this EntityCollection<T1, T2> ec) | |||
where T2 : unmanaged, IEntityComponent where T1 : unmanaged, IEntityComponent | |||
public static void Deconstruct<T1, T2, T3, T4>(in this EntityCollection<T1, T2, T3, T4> ec, out NB<T1> buffer1, | |||
out NB<T2> buffer2, out NB<T3> buffer3, out NB<T4> buffer4, out int count) | |||
where T1 : unmanaged, IEntityComponent | |||
where T2 : unmanaged, IEntityComponent | |||
where T3 : unmanaged, IEntityComponent | |||
where T4 : unmanaged, IEntityComponent | |||
{ | |||
return new BT<NB<T1>, NB<T2>>(ec.buffer1._nativedBuffer, ec.buffer2._nativedBuffer, ec.count); | |||
buffer1 = ec.buffer1._nativedBuffer; | |||
buffer2 = ec.buffer2._nativedBuffer; | |||
buffer3 = ec.buffer3._nativedBuffer; | |||
buffer4 = ec.buffer4._nativedBuffer; | |||
count = (int)ec.count; | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public static BT<NB<T1>, NB<T2>, NB<T3>> ToBuffers<T1, T2, T3> | |||
(in this EntityCollection<T1, T2, T3> ec) | |||
where T2 : unmanaged, IEntityComponent | |||
public static void Deconstruct<T1, T2, T3, T4>(in this EntityCollection<T1, T2, T3, T4> ec, out NB<T1> buffer1, | |||
out NB<T2> buffer2, out NB<T3> buffer3, out NB<T4> buffer4, out NativeEntityIDs entityIDs, out int count) | |||
where T1 : unmanaged, IEntityComponent | |||
where T2 : unmanaged, IEntityComponent | |||
where T3 : unmanaged, IEntityComponent | |||
where T4 : unmanaged, IEntityComponent | |||
{ | |||
return new BT<NB<T1>, NB<T2>, NB<T3>>(ec.buffer1._nativedBuffer, ec.buffer2._nativedBuffer | |||
, ec.buffer3._nativedBuffer, ec.count); | |||
buffer1 = ec.buffer1._nativedBuffer; | |||
buffer2 = ec.buffer2._nativedBuffer; | |||
buffer3 = ec.buffer3._nativedBuffer; | |||
buffer4 = ec.buffer4._nativedBuffer; | |||
entityIDs = ec.buffer1._nativedIndices; | |||
count = (int)ec.count; | |||
} | |||
} | |||
public static class EntityCollectionExtensionB | |||
{ | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public static void Deconstruct<T1> | |||
(in this EntityCollection<T1> ec, out MB<T1> buffer, out int count) where T1 : struct, IEntityViewComponent | |||
public static void Deconstruct<T1>(in this EntityCollection<T1> ec, out MB<T1> buffer, out int count) | |||
where T1 : struct, IEntityViewComponent | |||
{ | |||
buffer = ec._managedBuffer; | |||
count = (int) ec.count; | |||
count = (int)ec.count; | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public static BT<MB<T1>> ToBuffer<T1>(in this EntityCollection<T1> ec) where T1 : struct, IEntityViewComponent | |||
public static void Deconstruct<T1>(in this EntityCollection<T1> ec, out MB<T1> buffer, | |||
out ManagedEntityIDs entityIDs, out int count) where T1 : struct, IEntityViewComponent | |||
{ | |||
return new BT<MB<T1>>(ec._managedBuffer, ec.count); | |||
buffer = ec._managedBuffer; | |||
count = (int)ec.count; | |||
entityIDs = ec._managedIndices; | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public static void Deconstruct<T1, T2> | |||
(in this EntityCollection<T1, T2> ec, out MB<T1> buffer1, out MB<T2> buffer2, out int count) | |||
where T1 : struct, IEntityViewComponent where T2 : struct, IEntityViewComponent | |||
public static void Deconstruct<T1, T2>(in this EntityCollection<T1, T2> ec, out MB<T1> buffer1, | |||
out MB<T2> buffer2, out int count) where T1 : struct, IEntityViewComponent | |||
where T2 : struct, IEntityViewComponent | |||
{ | |||
buffer1 = ec.buffer1._managedBuffer; | |||
buffer2 = ec.buffer2._managedBuffer; | |||
count = (int) ec.count; | |||
count = (int)ec.count; | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public static (MB<T1> buffer1, MB<T2> buffer2, uint count) ToBuffers<T1, T2> | |||
(in this EntityCollection<T1, T2> ec) | |||
where T2 : struct, IEntityViewComponent where T1 : struct, IEntityViewComponent | |||
public static void Deconstruct<T1, T2>(in this EntityCollection<T1, T2> ec, out MB<T1> buffer1, | |||
out MB<T2> buffer2, out ManagedEntityIDs entityIDs, out int count) where T1 : struct, IEntityViewComponent | |||
where T2 : struct, IEntityViewComponent | |||
{ | |||
return (ec.buffer1._managedBuffer, ec.buffer2._managedBuffer, ec.count); | |||
buffer1 = ec.buffer1._managedBuffer; | |||
buffer2 = ec.buffer2._managedBuffer; | |||
count = (int)ec.count; | |||
entityIDs = ec.buffer1._managedIndices; | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public static void Deconstruct<T1, T2, T3> | |||
(in this EntityCollection<T1, T2, T3> ec, out MB<T1> buffer1, out MB<T2> buffer2, out MB<T3> buffer3 | |||
, out int count) where T1 : struct, IEntityViewComponent | |||
where T2 : struct, IEntityViewComponent | |||
where T3 : struct, IEntityViewComponent | |||
public static void Deconstruct<T1, T2, T3>(in this EntityCollection<T1, T2, T3> ec, out MB<T1> buffer1, | |||
out MB<T2> buffer2, out MB<T3> buffer3, out int count) where T1 : struct, IEntityViewComponent | |||
where T2 : struct, IEntityViewComponent | |||
where T3 : struct, IEntityViewComponent | |||
{ | |||
buffer1 = ec.buffer1._managedBuffer; | |||
buffer2 = ec.buffer2._managedBuffer; | |||
buffer3 = ec.buffer3._managedBuffer; | |||
count = (int) ec.count; | |||
count = (int)ec.count; | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public static (MB<T1> buffer1, MB<T2> buffer2, MB<T3> buffer3, uint count) ToBuffers<T1, T2, T3> | |||
(in this EntityCollection<T1, T2, T3> ec) | |||
where T2 : struct, IEntityViewComponent | |||
public static void Deconstruct<T1, T2, T3>(in this EntityCollection<T1, T2, T3> ec, out MB<T1> buffer1, | |||
out MB<T2> buffer2, out MB<T3> buffer3, out ManagedEntityIDs entityIDs, out int count) | |||
where T1 : struct, IEntityViewComponent | |||
where T2 : struct, IEntityViewComponent | |||
where T3 : struct, IEntityViewComponent | |||
{ | |||
return (ec.buffer1._managedBuffer, ec.buffer2._managedBuffer, ec.buffer3._managedBuffer, ec.count); | |||
buffer1 = ec.buffer1._managedBuffer; | |||
buffer2 = ec.buffer2._managedBuffer; | |||
buffer3 = ec.buffer3._managedBuffer; | |||
count = (int)ec.count; | |||
entityIDs = ec.buffer1._managedIndices; | |||
} | |||
} | |||
public static class EntityCollectionExtensionC | |||
{ | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public static (NB<T1> buffer1, MB<T2> buffer2, uint count) ToBuffers<T1, T2> | |||
(in this EntityCollection<T1, T2> ec) | |||
where T1 : unmanaged, IEntityComponent where T2 : struct, IEntityViewComponent | |||
{ | |||
return (ec.buffer1._nativedBuffer, ec.buffer2._managedBuffer, ec.count); | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public static (NB<T1> buffer1, MB<T2> buffer2, MB<T3> buffer3, uint count) ToBuffers<T1, T2, T3> | |||
(in this EntityCollection<T1, T2, T3> ec) | |||
where T1 : unmanaged, IEntityComponent | |||
public static void Deconstruct<T1, T2>(in this EntityCollection<T1, T2> ec, out NB<T1> buffer1, | |||
out MB<T2> buffer2, out int count) where T1 : unmanaged, IEntityComponent | |||
where T2 : struct, IEntityViewComponent | |||
where T3 : struct, IEntityViewComponent | |||
{ | |||
return (ec.buffer1._nativedBuffer, ec.buffer2._managedBuffer, ec.buffer3._managedBuffer, ec.count); | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public static void Deconstruct<T1, T2> | |||
(in this EntityCollection<T1, T2> ec, out NB<T1> buffer1, out MB<T2> buffer2, out int count) | |||
where T1 : unmanaged, IEntityComponent where T2 : struct, IEntityViewComponent | |||
{ | |||
buffer1 = ec.buffer1._nativedBuffer; | |||
buffer2 = ec.buffer2._managedBuffer; | |||
count = (int) ec.count; | |||
count = (int)ec.count; | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public static void Deconstruct<T1, T2, T3> | |||
(in this EntityCollection<T1, T2, T3> ec, out NB<T1> buffer1, out MB<T2> buffer2, out MB<T3> buffer3, out int count) | |||
where T1 : unmanaged, IEntityComponent | |||
public static void Deconstruct<T1, T2, T3>(in this EntityCollection<T1, T2, T3> ec, out NB<T1> buffer1, | |||
out MB<T2> buffer2, out MB<T3> buffer3, out int count) where T1 : unmanaged, IEntityComponent | |||
where T2 : struct, IEntityViewComponent | |||
where T3 : struct, IEntityViewComponent | |||
{ | |||
buffer1 = ec.buffer1._nativedBuffer; | |||
buffer2 = ec.buffer2._managedBuffer; | |||
buffer3 = ec.buffer3._managedBuffer; | |||
count = (int) ec.count; | |||
count = (int)ec.count; | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public static void Deconstruct<T1, T2, T3, T4> | |||
(in this EntityCollection<T1, T2, T3, T4> ec, out NB<T1> buffer1, out NB<T2> buffer2, out NB<T3> buffer3 | |||
, out MB<T4> buffer4, out int count) where T1 : unmanaged, IEntityComponent | |||
where T2 : unmanaged, IEntityComponent | |||
where T3 : unmanaged, IEntityComponent | |||
where T4 : struct, IEntityViewComponent | |||
public static void Deconstruct<T1, T2, T3, T4>(in this EntityCollection<T1, T2, T3, T4> ec, out NB<T1> buffer1, | |||
out NB<T2> buffer2, out NB<T3> buffer3, out MB<T4> buffer4, out int count) | |||
where T1 : unmanaged, IEntityComponent | |||
where T2 : unmanaged, IEntityComponent | |||
where T3 : unmanaged, IEntityComponent | |||
where T4 : struct, IEntityViewComponent | |||
{ | |||
buffer1 = ec.buffer1._nativedBuffer; | |||
buffer2 = ec.buffer2._nativedBuffer; | |||
buffer3 = ec.buffer3._nativedBuffer; | |||
buffer4 = ec.buffer4._managedBuffer; | |||
count = (int) ec.count; | |||
count = (int)ec.count; | |||
} | |||
} | |||
public static class EntityCollectionExtensionD | |||
{ | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public static void Deconstruct<T1, T2, T3> | |||
(in this EntityCollection<T1, T2, T3> ec, out NB<T1> buffer1, out NB<T2> buffer2, out MB<T3> buffer3 | |||
, out int count) where T1 : unmanaged, IEntityComponent | |||
where T2 : unmanaged, IEntityComponent | |||
where T3 : struct, IEntityViewComponent | |||
public static void Deconstruct<T1, T2, T3>(in this EntityCollection<T1, T2, T3> ec, out NB<T1> buffer1, | |||
out NB<T2> buffer2, out MB<T3> buffer3, out int count) where T1 : unmanaged, IEntityComponent | |||
where T2 : unmanaged, IEntityComponent | |||
where T3 : struct, IEntityViewComponent | |||
{ | |||
buffer1 = ec.buffer1._nativedBuffer; | |||
buffer2 = ec.buffer2._nativedBuffer; | |||
buffer3 = ec.buffer3._managedBuffer; | |||
count = (int) ec.count; | |||
count = (int)ec.count; | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public static (NB<T1> buffer1, NB<T2> buffer2, MB<T3> buffer3, uint count) ToBuffers<T1, T2, T3> | |||
(in this EntityCollection<T1, T2, T3> ec) | |||
public static void Deconstruct<T1, T2, T3, T4>(in this EntityCollection<T1, T2, T3, T4> ec, out NB<T1> buffer1, | |||
out NB<T2> buffer2, out MB<T3> buffer3, out MB<T4> buffer4, out int count) | |||
where T1 : unmanaged, IEntityComponent | |||
where T2 : unmanaged, IEntityComponent | |||
where T3 : struct, IEntityViewComponent | |||
{ | |||
return (ec.buffer1._nativedBuffer, ec.buffer2._nativedBuffer, ec.buffer3._managedBuffer, ec.count); | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public static BT<NB<T1>, NB<T2>, NB<T3>, NB<T4>> ToBuffers<T1, T2, T3, T4> | |||
(in this EntityCollection<T1, T2, T3, T4> ec) | |||
where T2 : unmanaged, IEntityComponent | |||
where T1 : unmanaged, IEntityComponent | |||
where T3 : unmanaged, IEntityComponent | |||
where T4 : unmanaged, IEntityComponent | |||
{ | |||
return new BT<NB<T1>, NB<T2>, NB<T3>, NB<T4>>(ec.buffer1._nativedBuffer, ec.buffer2._nativedBuffer | |||
, ec.buffer3._nativedBuffer, ec.buffer4._nativedBuffer, ec.count); | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public static void Deconstruct<T1, T2, T3, T4> | |||
(in this EntityCollection<T1, T2, T3, T4> ec, out NB<T1> buffer1, out NB<T2> buffer2, out MB<T3> buffer3 | |||
, out MB<T4> buffer4, out int count) where T1 : unmanaged, IEntityComponent | |||
where T2 : unmanaged, IEntityComponent | |||
where T3 : struct, IEntityViewComponent | |||
where T4 : struct, IEntityViewComponent | |||
where T4 : struct, IEntityViewComponent | |||
{ | |||
buffer1 = ec.buffer1._nativedBuffer; | |||
buffer2 = ec.buffer2._nativedBuffer; | |||
buffer3 = ec.buffer3._managedBuffer; | |||
buffer4 = ec.buffer4._managedBuffer; | |||
count = (int) ec.count; | |||
count = (int)ec.count; | |||
} | |||
} | |||
} |
@@ -1,5 +1,4 @@ | |||
using System.Runtime.CompilerServices; | |||
using Svelto.Common; | |||
using Svelto.DataStructures; | |||
using Svelto.ECS.Hybrid; | |||
using Svelto.ECS.Internal; | |||
@@ -4,15 +4,15 @@ namespace Svelto.ECS | |||
{ | |||
public static class FilterGroupExtensions | |||
{ | |||
public static bool Add<N>(this FilterGroup filter, uint entityID, N mapper) where N : IEGIDMultiMapper | |||
public static bool Add<N>(this LegacyFilterGroup legacyFilter, uint entityID, N mapper) where N : IEGIDMultiMapper | |||
{ | |||
#if DEBUG && !PROFILE_SVELTO | |||
if (mapper.Exists(filter._exclusiveGroupStruct, entityID) == false) | |||
if (mapper.Exists(legacyFilter._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! "); | |||
$"trying adding an entity {entityID} to filter {mapper.entityType} - {legacyFilter._ID} with group {legacyFilter._exclusiveGroupStruct}, but entity is not found! "); | |||
#endif | |||
return filter.InternalAdd(entityID, mapper.GetIndex(filter._exclusiveGroupStruct, entityID)); | |||
return legacyFilter.InternalAdd(entityID, mapper.GetIndex(legacyFilter._exclusiveGroupStruct, entityID)); | |||
} | |||
} |
@@ -40,11 +40,11 @@ namespace Svelto.ECS | |||
if (!exclusiveGroupStruct.IsEnabled()) | |||
continue; | |||
var entityCollection1 = _entitiesDB.QueryEntities<T1, T2, T3, T4>(exclusiveGroupStruct); | |||
var entityCollection = _entitiesDB.QueryEntities<T1, T2, T3, T4>(exclusiveGroupStruct); | |||
if (entityCollection.count == 0) | |||
continue; | |||
var array = entityCollection1; | |||
_buffers = new EntityCollection<T1, T2, T3, T4>(array.buffer1, array.buffer2, array.buffer3 | |||
, array.buffer4); | |||
_buffers = entityCollection; | |||
break; | |||
} | |||
@@ -120,7 +120,7 @@ namespace Svelto.ECS | |||
if (!exclusiveGroupStruct.IsEnabled()) | |||
continue; | |||
var entityCollection = _entitiesDB.QueryEntities<T1, T2, T3>(exclusiveGroupStruct); | |||
EntityCollection<T1, T2, T3> entityCollection = _entitiesDB.QueryEntities<T1, T2, T3>(exclusiveGroupStruct); | |||
if (entityCollection.count == 0) | |||
continue; | |||
@@ -2,7 +2,7 @@ | |||
using System; | |||
using Unity.Jobs; | |||
namespace Svelto.ECS.Extensions.Unity | |||
namespace Svelto.ECS.SveltoOnDOTS | |||
{ | |||
public struct DisposeJob<T>:IJob where T:struct,IDisposable | |||
{ | |||
@@ -1,7 +1,7 @@ | |||
#if UNITY_JOBS | |||
using Unity.Jobs; | |||
namespace Svelto.ECS.Extensions.Unity | |||
namespace Svelto.ECS.SveltoOnDOTS | |||
{ | |||
public interface IJobifiedEngine<T> : IEngine | |||
{ | |||
@@ -3,7 +3,7 @@ using Svelto.DataStructures; | |||
using Unity.Jobs; | |||
using Svelto.Common; | |||
namespace Svelto.ECS.Extensions.Unity | |||
namespace Svelto.ECS.SveltoOnDOTS | |||
{ | |||
/// <summary> | |||
/// Note sorted jobs run in serial | |||
@@ -15,38 +15,40 @@ namespace Svelto.ECS.Extensions.Unity | |||
{ | |||
protected SortedJobifiedEnginesGroup(FasterList<Interface> engines) | |||
{ | |||
_name = "SortedJobifiedEnginesGroup - "+this.GetType().Name; | |||
_name = "SortedJobifiedEnginesGroup - " + this.GetType().Name; | |||
_instancedSequence = new Sequence<Interface, SequenceOrder>(engines); | |||
} | |||
public JobHandle Execute(JobHandle inputHandles) | |||
{ | |||
var sequenceItems = _instancedSequence.items; | |||
var sequenceItems = _instancedSequence.items; | |||
JobHandle combinedHandles = inputHandles; | |||
using (var profiler = new PlatformProfiler(_name)) | |||
{ | |||
for (var index = 0; index < sequenceItems.count; index++) | |||
{ | |||
var engine = sequenceItems[index]; | |||
using (profiler.Sample(engine.name)) combinedHandles = engine.Execute(combinedHandles); | |||
using (profiler.Sample(engine.name)) | |||
combinedHandles = engine.Execute(combinedHandles); | |||
} | |||
} | |||
return combinedHandles; | |||
return combinedHandles; | |||
} | |||
public string name => _name; | |||
readonly string _name; | |||
readonly string _name; | |||
readonly Sequence<Interface, SequenceOrder> _instancedSequence; | |||
} | |||
public abstract class SortedJobifiedEnginesGroup<Interface, Parameter, SequenceOrder>: IJobifiedGroupEngine<Parameter> | |||
} | |||
public abstract class | |||
SortedJobifiedEnginesGroup<Interface, Parameter, SequenceOrder> : IJobifiedGroupEngine<Parameter> | |||
where SequenceOrder : struct, ISequenceOrder where Interface : class, IJobifiedEngine<Parameter> | |||
{ | |||
protected SortedJobifiedEnginesGroup(FasterList<Interface> engines) | |||
{ | |||
_name = "SortedJobifiedEnginesGroup - "+this.GetType().Name; | |||
_name = "SortedJobifiedEnginesGroup - " + this.GetType().Name; | |||
_instancedSequence = new Sequence<Interface, SequenceOrder>(engines); | |||
} | |||
@@ -58,7 +60,8 @@ namespace Svelto.ECS.Extensions.Unity | |||
for (var index = 0; index < sequenceItems.count; index++) | |||
{ | |||
var engine = sequenceItems[index]; | |||
using (profiler.Sample(engine.name)) combinedHandles = engine.Execute(combinedHandles, ref param); | |||
using (profiler.Sample(engine.name)) | |||
combinedHandles = engine.Execute(combinedHandles, ref param); | |||
} | |||
} | |||
@@ -66,8 +69,8 @@ namespace Svelto.ECS.Extensions.Unity | |||
} | |||
public string name => _name; | |||
readonly string _name; | |||
readonly string _name; | |||
readonly Sequence<Interface, SequenceOrder> _instancedSequence; | |||
} | |||
} |
@@ -1,11 +1,11 @@ | |||
#if UNITY_JOBS | |||
using System; | |||
using Svelto.ECS.Extensions.Unity; | |||
using Svelto.ECS.SveltoOnDOTS; | |||
using Unity.Jobs; | |||
namespace Svelto.ECS | |||
{ | |||
public static class UnityJobExtensions2 | |||
public static class UnityJobExtensions | |||
{ | |||
public static JobHandle ScheduleDispose | |||
<T1>(this T1 disposable, JobHandle inputDeps) where T1 : struct, IDisposable | |||
@@ -3,29 +3,30 @@ using Svelto.Common; | |||
using Svelto.DataStructures; | |||
using Unity.Jobs; | |||
namespace Svelto.ECS.Extensions.Unity | |||
namespace Svelto.ECS.SveltoOnDOTS | |||
{ | |||
/// <summary> | |||
/// Note unsorted jobs run in parallel | |||
/// </summary> | |||
/// <typeparam name="Interface"></typeparam> | |||
public abstract class UnsortedJobifiedEnginesGroup<Interface>:IJobifiedEngine where Interface : class, IJobifiedEngine | |||
public abstract class UnsortedJobifiedEnginesGroup<Interface> : IJobifiedEngine | |||
where Interface : class, IJobifiedEngine | |||
{ | |||
protected UnsortedJobifiedEnginesGroup(FasterList<Interface> engines) | |||
{ | |||
_name = "JobifiedEnginesGroup - "+this.GetType().Name; | |||
_engines = engines; | |||
_name = "JobifiedEnginesGroup - " + this.GetType().Name; | |||
_engines = engines; | |||
} | |||
protected UnsortedJobifiedEnginesGroup() | |||
{ | |||
_name = "JobifiedEnginesGroup - "+this.GetType().Name; | |||
_name = "JobifiedEnginesGroup - " + this.GetType().Name; | |||
_engines = new FasterList<Interface>(); | |||
} | |||
public JobHandle Execute(JobHandle inputHandles) | |||
{ | |||
var engines = _engines; | |||
var engines = _engines; | |||
JobHandle combinedHandles = inputHandles; | |||
using (var profiler = new PlatformProfiler(_name)) | |||
{ | |||
@@ -52,13 +53,14 @@ namespace Svelto.ECS.Extensions.Unity | |||
protected readonly FasterList<Interface> _engines; | |||
readonly string _name; | |||
} | |||
public abstract class JobifiedEnginesGroup<Interface, Param>: IJobifiedGroupEngine<Param> where Interface : class, IJobifiedEngine<Param> | |||
public abstract class UnsortedJobifiedEnginesGroup<Interface, Param> : IJobifiedGroupEngine<Param> | |||
where Interface : class, IJobifiedEngine<Param> | |||
{ | |||
protected JobifiedEnginesGroup(FasterList<Interface> engines) | |||
protected UnsortedJobifiedEnginesGroup(FasterList<Interface> engines) | |||
{ | |||
_name = "JobifiedEnginesGroup - "+this.GetType().Name; | |||
_engines = engines; | |||
_name = "JobifiedEnginesGroup - " + this.GetType().Name; | |||
_engines = engines; | |||
} | |||
public JobHandle Execute(JobHandle combinedHandles, ref Param _param) | |||
@@ -70,15 +72,16 @@ namespace Svelto.ECS.Extensions.Unity | |||
{ | |||
var engine = engines[index]; | |||
using (profiler.Sample(engine.name)) | |||
combinedHandles = JobHandle.CombineDependencies(combinedHandles, engine.Execute(combinedHandles, ref _param)); | |||
combinedHandles = JobHandle.CombineDependencies(combinedHandles, | |||
engine.Execute(combinedHandles, ref _param)); | |||
} | |||
} | |||
return combinedHandles; | |||
} | |||
public string name => _name; | |||
readonly string _name; | |||
readonly FasterReadOnlyList<Interface> _engines; | |||
@@ -1,108 +0,0 @@ | |||
#if UNITY_NATIVE | |||
using System; | |||
using System.Runtime.CompilerServices; | |||
using Svelto.Common; | |||
using Svelto.DataStructures; | |||
using Svelto.DataStructures.Native; | |||
namespace Svelto.ECS.Native | |||
{ | |||
/// <summary> | |||
/// Note: this class should really be ref struct by design. It holds the reference of a dictionary that can become | |||
/// invalid. Unfortunately it can be a ref struct, because Jobs needs to hold if by paramater. So the deal is | |||
/// that a job can use it as long as nothing else is modifying the entities database and the NativeEGIDMapper | |||
/// is disposed right after the use. | |||
/// </summary> | |||
public readonly struct NativeEGIDMapper<T> : IEGIDMapper where T : unmanaged, IEntityComponent | |||
{ | |||
public NativeEGIDMapper | |||
(ExclusiveGroupStruct groupStructId | |||
, SveltoDictionary<uint, T, NativeStrategy<SveltoDictionaryNode<uint>>, NativeStrategy<T>, NativeStrategy<int>> | |||
toNative) : this() | |||
{ | |||
groupID = groupStructId; | |||
_map = new SveltoDictionaryNative<uint, T>(); | |||
_map.UnsafeCast(toNative); | |||
} | |||
public int count => _map.count; | |||
public Type entityType => TypeCache<T>.type; | |||
public ExclusiveGroupStruct groupID { get; } | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public ref T Entity(uint entityID) | |||
{ | |||
#if DEBUG | |||
if (_map.TryFindIndex(entityID, out var findIndex) == false) | |||
throw new Exception("Entity not found in this group ".FastConcat(typeof(T).ToString())); | |||
#else | |||
_map.TryFindIndex(entityID, out var findIndex); | |||
#endif | |||
return ref _map.GetDirectValueByRef(findIndex); | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public bool TryGetEntity(uint entityID, out T value) | |||
{ | |||
if (_map.count > 0 && _map.TryFindIndex(entityID, out var index)) | |||
unsafe | |||
{ | |||
value = Unsafe.AsRef<T>(Unsafe.Add<T>((void*) _map.GetValues(out _).ToNativeArray(out _) | |||
, (int) index)); | |||
return true; | |||
} | |||
value = default; | |||
return false; | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public NB<T> GetArrayAndEntityIndex(uint entityID, out uint index) | |||
{ | |||
if (_map.TryFindIndex(entityID, out index)) | |||
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)] | |||
public bool TryGetArrayAndEntityIndex(uint entityID, out uint index, out NB<T> array) | |||
{ | |||
index = 0; | |||
if (_map.count > 0 && _map.TryFindIndex(entityID, out index)) | |||
{ | |||
array = new NB<T>(_map.GetValues(out var count).ToNativeArray(out _), count); | |||
return true; | |||
} | |||
array = default; | |||
return false; | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public bool Exists(uint idEntityId) | |||
{ | |||
return _map.count > 0 && _map.TryFindIndex(idEntityId, out _); | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public uint GetIndex(uint entityID) | |||
{ | |||
return _map.GetIndex(entityID); | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public bool FindIndex(uint valueKey, out uint index) | |||
{ | |||
return _map.TryFindIndex(valueKey, out index); | |||
} | |||
readonly SveltoDictionaryNative<uint, T> _map; | |||
} | |||
} | |||
#endif |
@@ -1,63 +0,0 @@ | |||
#if UNITY_NATIVE | |||
using System; | |||
using Svelto.DataStructures; | |||
using Svelto.DataStructures.Native; | |||
namespace Svelto.ECS.Native | |||
{ | |||
/// <summary> | |||
/// Note: this class should really be ref struct by design. It holds the reference of a dictionary that can become | |||
/// invalid. Unfortunately it can be a ref struct, because Jobs needs to hold if by paramater. So the deal is | |||
/// that a job can use it as long as nothing else is modifying the entities database and the NativeEGIDMultiMapper | |||
/// is disposed right after the use. | |||
/// </summary> | |||
public struct NativeEGIDMultiMapper<T> : IDisposable where T : unmanaged, IEntityComponent | |||
{ | |||
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) | |||
{ | |||
_dic = dictionary; | |||
} | |||
public int count => (int) _dic.count; | |||
public void Dispose() | |||
{ | |||
_dic.Dispose(); | |||
} | |||
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); | |||
} | |||
public bool Exists(EGID entity) | |||
{ | |||
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 |