@@ -1,81 +1,85 @@ | |||
#if DEBUG && !PROFILER | |||
#if !DEBUG || PROFILE_SVELTO | |||
#define DONT_USE | |||
#endif | |||
using System; | |||
using System.Collections.Generic; | |||
using Svelto.DataStructures; | |||
#else | |||
using System.Diagnostics; | |||
#endif | |||
using Svelto.DataStructures; | |||
namespace Svelto.ECS | |||
{ | |||
/// <summary> | |||
/// Note: this check doesn't catch the case when an add and remove is done on the same entity before the nextI am | |||
/// submission. Two operations on the same entity are not allowed between submissions. | |||
/// </summary> | |||
public partial class EnginesRoot | |||
{ | |||
#if DEBUG && !PROFILER | |||
void CheckRemoveEntityID(EGID egid) | |||
#if DONT_USE | |||
[Conditional("CHECK_ALL")] | |||
#endif | |||
void CheckRemoveEntityID(EGID egid, Type entityDescriptorType, string caller = "") | |||
{ | |||
// Console.LogError("<color=orange>removed</color>".FastConcat(egid.ToString())); | |||
if (_idCheckers.TryGetValue(egid.groupID, out var hash)) | |||
{ | |||
if (hash.Contains(egid.entityID) == false) | |||
throw new ECSException("Entity with not found ID is about to be removed: id: " | |||
.FastConcat(egid.entityID) | |||
.FastConcat(" groupid: ") | |||
.FastConcat(egid.groupID)); | |||
if (_multipleOperationOnSameEGIDChecker.ContainsKey(egid) == true) | |||
throw new ECSException( | |||
"Executing multiple structural changes in one submission on the same entity is not supported " | |||
.FastConcat(" caller: ", caller, " ").FastConcat(egid.entityID).FastConcat(" groupid: ") | |||
.FastConcat(egid.groupID.ToName()).FastConcat(" type: ") | |||
.FastConcat(entityDescriptorType != null ? entityDescriptorType.Name : "not available") | |||
.FastConcat(" operation was: ") | |||
.FastConcat(_multipleOperationOnSameEGIDChecker[egid] == 1 ? "add" : "remove")); | |||
hash.Remove(egid.entityID); | |||
if (_idChecker.TryGetValue(egid.groupID, out var hash)) | |||
if (hash.Contains(egid.entityID) == false) | |||
throw new ECSException("Trying to remove an Entity never submitted in the database " | |||
.FastConcat(" caller: ", caller, " ").FastConcat(egid.entityID) | |||
.FastConcat(" groupid: ").FastConcat(egid.groupID.ToName()) | |||
.FastConcat(" type: ") | |||
.FastConcat(entityDescriptorType != null | |||
? entityDescriptorType.Name | |||
: "not available")); | |||
else | |||
hash.Remove(egid.entityID); | |||
if (hash.Count == 0) | |||
_idCheckers.Remove(egid.groupID); | |||
} | |||
else | |||
{ | |||
throw new ECSException("Entity with not found ID is about to be removed: id: " | |||
.FastConcat(egid.entityID) | |||
.FastConcat(" groupid: ") | |||
.FastConcat(egid.groupID)); | |||
} | |||
_multipleOperationOnSameEGIDChecker.Add(egid, 0); | |||
} | |||
void CheckAddEntityID(EGID egid) | |||
#if DONT_USE | |||
[Conditional("CHECK_ALL")] | |||
#endif | |||
void CheckAddEntityID(EGID egid, Type entityDescriptorType, string caller = "") | |||
{ | |||
// Console.LogError("<color=orange>added</color> ".FastConcat(egid.ToString())); | |||
if (_idCheckers.TryGetValue(egid.groupID, out var hash) == false) | |||
hash = _idCheckers[egid.groupID] = new HashSet<uint>(); | |||
else | |||
{ | |||
if (hash.Contains(egid.entityID)) | |||
throw new ECSException("Entity with used ID is about to be built: '" | |||
.FastConcat("' id: '") | |||
.FastConcat(egid.entityID) | |||
.FastConcat("' groupid: '") | |||
.FastConcat(egid.groupID) | |||
.FastConcat("'")); | |||
} | |||
if (_multipleOperationOnSameEGIDChecker.ContainsKey(egid) == true) | |||
throw new ECSException( | |||
"Executing multiple structural changes in one submission on the same entity is not supported " | |||
.FastConcat(" caller: ", caller, " ").FastConcat(egid.entityID).FastConcat(" groupid: ") | |||
.FastConcat(egid.groupID.ToName()).FastConcat(" type: ") | |||
.FastConcat(entityDescriptorType != null ? entityDescriptorType.Name : "not available") | |||
.FastConcat(" operation was: ") | |||
.FastConcat(_multipleOperationOnSameEGIDChecker[egid] == 1 ? "add" : "remove")); | |||
var hash = _idChecker.GetOrCreate(egid.groupID, () => new HashSet<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) | |||
.FastConcat(" groupid: ").FastConcat(egid.groupID.ToName()).FastConcat(" type: ") | |||
.FastConcat(entityDescriptorType != null | |||
? entityDescriptorType.Name | |||
: "not available")); | |||
hash.Add(egid.entityID); | |||
} | |||
void RemoveGroupID(ExclusiveGroup.ExclusiveGroupStruct groupID) | |||
{ | |||
_idCheckers.Remove(groupID); | |||
_multipleOperationOnSameEGIDChecker.Add(egid, 1); | |||
} | |||
readonly FasterDictionary<uint, HashSet<uint>> _idCheckers = new FasterDictionary<uint, HashSet<uint>>(); | |||
#else | |||
[Conditional("_CHECKS_DISABLED")] | |||
void CheckRemoveEntityID(EGID egid) | |||
{ | |||
} | |||
#if DONT_USE | |||
[Conditional("CHECK_ALL")] | |||
#endif | |||
void RemoveGroupID(BuildGroup groupID) { _idChecker.Remove(groupID); } | |||
[Conditional("_CHECKS_DISABLED")] | |||
void CheckAddEntityID(EGID egid) | |||
{ | |||
} | |||
[Conditional("_CHECKS_DISABLED")] | |||
void RemoveGroupID(ExclusiveGroup.ExclusiveGroupStruct groupID) | |||
{ | |||
} | |||
#if DONT_USE | |||
[Conditional("CHECK_ALL")] | |||
#endif | |||
void ClearChecks() { _multipleOperationOnSameEGIDChecker.FastClear(); } | |||
readonly FasterDictionary<EGID, uint> _multipleOperationOnSameEGIDChecker = new FasterDictionary<EGID, uint>(); | |||
readonly FasterDictionary<uint, HashSet<uint>> _idChecker = new FasterDictionary<uint, HashSet<uint>>(); | |||
} | |||
} | |||
} |
@@ -0,0 +1,142 @@ | |||
#if !DEBUG || PROFILE_SVELTO | |||
#define DISABLE_CHECKS | |||
using System.Diagnostics; | |||
#endif | |||
using System; | |||
using System.Reflection; | |||
using Svelto.Common; | |||
namespace Svelto.ECS | |||
{ | |||
static class ComponentBuilderUtilities | |||
{ | |||
const string MSG = "Entity Components and Entity View Components fields cannot hold managed fields outside the Svelto rules."; | |||
#if DISABLE_CHECKS | |||
[Conditional("_CHECKS_DISABLED")] | |||
#endif | |||
public static void CheckFields(Type entityComponentType, bool needsReflection, bool isStringAllowed = false) | |||
{ | |||
if (entityComponentType == ENTITY_INFO_COMPONENT || entityComponentType == EGIDType || | |||
entityComponentType == EXCLUSIVEGROUPSTRUCTTYPE || entityComponentType == SERIALIZABLE_ENTITY_STRUCT) | |||
{ | |||
return; | |||
} | |||
if (needsReflection == false) | |||
{ | |||
if (entityComponentType.IsClass) | |||
{ | |||
throw new ECSException("EntityComponents must be structs.", entityComponentType); | |||
} | |||
FieldInfo[] fields = entityComponentType.GetFields(BindingFlags.Public | BindingFlags.Instance); | |||
for (var i = fields.Length - 1; i >= 0; --i) | |||
{ | |||
FieldInfo fieldInfo = fields[i]; | |||
Type fieldType = fieldInfo.FieldType; | |||
SubCheckFields(fieldType, entityComponentType, isStringAllowed); | |||
} | |||
} | |||
else | |||
{ | |||
FieldInfo[] fields = entityComponentType.GetFields(BindingFlags.Public | BindingFlags.Instance); | |||
if (fields.Length < 1) | |||
{ | |||
ProcessError("No valid fields found in Entity View Components", entityComponentType); | |||
} | |||
for (int i = fields.Length - 1; i >= 0; --i) | |||
{ | |||
FieldInfo fieldInfo = fields[i]; | |||
if (fieldInfo.FieldType.IsInterfaceEx() == true) | |||
{ | |||
PropertyInfo[] properties = fieldInfo.FieldType.GetProperties( | |||
BindingFlags.Public | BindingFlags.Instance | |||
| BindingFlags.DeclaredOnly); | |||
for (int j = properties.Length - 1; j >= 0; --j) | |||
{ | |||
if (properties[j].PropertyType.IsGenericType) | |||
{ | |||
Type genericTypeDefinition = properties[j].PropertyType.GetGenericTypeDefinition(); | |||
if (genericTypeDefinition == DISPATCHONSETTYPE | |||
|| genericTypeDefinition == DISPATCHONCHANGETYPE) | |||
{ | |||
continue; | |||
} | |||
} | |||
Type propertyType = properties[j].PropertyType; | |||
//for EntityComponentStructs, component fields that are structs that hold strings | |||
//are allowed | |||
SubCheckFields(propertyType, entityComponentType, isStringAllowed: true); | |||
} | |||
} | |||
else | |||
if (fieldInfo.FieldType.IsUnmanagedEx() == false) | |||
{ | |||
ProcessError("Entity View Components must hold only public interfaces, strings or unmanaged type fields.", | |||
entityComponentType); | |||
} | |||
} | |||
} | |||
} | |||
static bool IsString(Type type) | |||
{ | |||
return type == STRINGTYPE || type == STRINGBUILDERTYPE; | |||
} | |||
/// <summary> | |||
/// This method checks the fields if it's an IEntityComponent, but checks all the properties if it's | |||
/// IEntityViewComponent | |||
/// </summary> | |||
/// <param name="fieldType"></param> | |||
/// <param name="entityComponentType"></param> | |||
/// <param name="isStringAllowed"></param> | |||
static void SubCheckFields(Type fieldType, Type entityComponentType, bool isStringAllowed = false) | |||
{ | |||
//pass if it's Primitive or C# 8 unmanaged, or it's a string and string are allowed | |||
//this check must allow pointers are they are unmanaged types | |||
if ((isStringAllowed == true && IsString(fieldType) == true) || fieldType.IsValueTypeEx() == true) | |||
{ | |||
//if it's a struct we have to check the fields recursively | |||
if (IsString(fieldType) == false) | |||
{ | |||
CheckFields(fieldType, false, isStringAllowed); | |||
} | |||
return; | |||
} | |||
ProcessError(MSG, entityComponentType, fieldType); | |||
} | |||
static void ProcessError(string message, Type entityComponentType, Type fieldType = null) | |||
{ | |||
if (fieldType != null) | |||
{ | |||
throw new ECSException(message, entityComponentType, fieldType); | |||
} | |||
throw new ECSException(message, entityComponentType); | |||
} | |||
static readonly Type DISPATCHONCHANGETYPE = typeof(DispatchOnChange<>); | |||
static readonly Type DISPATCHONSETTYPE = typeof(DispatchOnSet<>); | |||
static readonly Type EGIDType = typeof(EGID); | |||
static readonly Type EXCLUSIVEGROUPSTRUCTTYPE = typeof(ExclusiveGroupStruct); | |||
static readonly Type SERIALIZABLE_ENTITY_STRUCT = typeof(SerializableEntityComponent); | |||
static readonly Type STRINGTYPE = typeof(string); | |||
static readonly Type STRINGBUILDERTYPE = typeof(System.Text.StringBuilder); | |||
internal static readonly Type ENTITY_INFO_COMPONENT = typeof(EntityInfoComponent); | |||
} | |||
} |
@@ -1,5 +1,5 @@ | |||
fileFormatVersion: 2 | |||
guid: ea4e6d9818ba3189beab2cf40d7e8e15 | |||
guid: b8801fb2bdee37a6aa48c7ab61badd55 | |||
MonoImporter: | |||
externalObjects: {} | |||
serializedVersion: 2 |
@@ -0,0 +1,155 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Reflection; | |||
using Svelto.Common; | |||
using Svelto.DataStructures; | |||
using Svelto.ECS.Hybrid; | |||
using Svelto.ECS.Internal; | |||
using Svelto.Utilities; | |||
namespace Svelto.ECS | |||
{ | |||
public class ComponentBuilder<T> : IComponentBuilder where T : struct, IEntityComponent | |||
{ | |||
public ComponentBuilder() | |||
{ | |||
_initializer = DEFAULT_IT; | |||
} | |||
public ComponentBuilder(in T initializer) : this() | |||
{ | |||
_initializer = initializer; | |||
} | |||
public void BuildEntityAndAddToList(ref ITypeSafeDictionary dictionary, EGID egid, | |||
IEnumerable<object> implementors) | |||
{ | |||
if (dictionary == null) | |||
dictionary = TypeSafeDictionaryFactory<T>.Create(); | |||
var castedDic = dictionary as ITypeSafeDictionary<T>; | |||
T entityComponent = default; | |||
if (IS_ENTITY_VIEW_COMPONENT) | |||
{ | |||
DBC.ECS.Check.Require(castedDic.ContainsKey(egid.entityID) == false, | |||
$"building an entity with already used entity id! id: '{(ulong) egid}', {ENTITY_COMPONENT_NAME}"); | |||
this.FillEntityComponent(ref entityComponent, EntityViewComponentCache.cachedFields, implementors, | |||
EntityViewComponentCache.implementorsByType, EntityViewComponentCache.cachedTypes); | |||
castedDic.Add(egid.entityID, entityComponent); | |||
} | |||
else | |||
{ | |||
DBC.ECS.Check.Require(!castedDic.ContainsKey(egid.entityID), | |||
$"building an entity with already used entity id! id: '{egid.entityID}'"); | |||
castedDic.Add(egid.entityID, _initializer); | |||
} | |||
} | |||
ITypeSafeDictionary IComponentBuilder.Preallocate(ref ITypeSafeDictionary dictionary, uint size) | |||
{ | |||
return Preallocate(ref dictionary, size); | |||
} | |||
static ITypeSafeDictionary Preallocate(ref ITypeSafeDictionary dictionary, uint size) | |||
{ | |||
if (dictionary == null) | |||
dictionary = TypeSafeDictionaryFactory<T>.Create(size); | |||
else | |||
dictionary.SetCapacity(size); | |||
return dictionary; | |||
} | |||
public Type GetEntityComponentType() | |||
{ | |||
return ENTITY_COMPONENT_TYPE; | |||
} | |||
static ComponentBuilder() | |||
{ | |||
ENTITY_COMPONENT_TYPE = typeof(T); | |||
DEFAULT_IT = default; | |||
IS_ENTITY_VIEW_COMPONENT = typeof(IEntityViewComponent).IsAssignableFrom(ENTITY_COMPONENT_TYPE); | |||
HAS_EGID = typeof(INeedEGID).IsAssignableFrom(ENTITY_COMPONENT_TYPE); | |||
ENTITY_COMPONENT_NAME = ENTITY_COMPONENT_TYPE.ToString(); | |||
var IS_UNMANAGED = ENTITY_COMPONENT_TYPE.IsUnmanagedEx(); | |||
if (IS_UNMANAGED) | |||
EntityComponentIDMap.Register<T>(new Filler<T>()); | |||
SetEGIDWithoutBoxing<T>.Warmup(); | |||
ComponentBuilderUtilities.CheckFields(ENTITY_COMPONENT_TYPE, IS_ENTITY_VIEW_COMPONENT); | |||
if (IS_ENTITY_VIEW_COMPONENT) | |||
EntityViewComponentCache.InitCache(); | |||
else | |||
{ | |||
if (ENTITY_COMPONENT_TYPE != ComponentBuilderUtilities.ENTITY_INFO_COMPONENT && ENTITY_COMPONENT_TYPE.IsUnmanagedEx() == false) | |||
throw new Exception($"Entity Component check failed, unexpected struct type (must be unmanaged) {ENTITY_COMPONENT_TYPE}"); | |||
} | |||
} | |||
readonly T _initializer; | |||
internal static readonly Type ENTITY_COMPONENT_TYPE; | |||
public static readonly bool HAS_EGID; | |||
internal static readonly bool IS_ENTITY_VIEW_COMPONENT; | |||
static readonly T DEFAULT_IT; | |||
static readonly string ENTITY_COMPONENT_NAME; | |||
/// <summary> | |||
/// Note: this static class will hold forever the references of the entities implementors. These references | |||
/// are not even cleared when the engines root is destroyed, as they are static references. | |||
/// It must be seen as an application-wide cache system. Honestly, I am not sure if this may cause leaking | |||
/// issues in some kind of applications. To remember. | |||
/// </summary> | |||
static class EntityViewComponentCache | |||
{ | |||
internal static readonly FasterList<KeyValuePair<Type, FastInvokeActionCast<T>>> cachedFields; | |||
internal static readonly Dictionary<Type, Type[]> cachedTypes; | |||
#if DEBUG && !PROFILE_SVELTO | |||
internal static readonly Dictionary<Type, ECSTuple<object, int>> implementorsByType; | |||
#else | |||
internal static readonly Dictionary<Type, object> implementorsByType; | |||
#endif | |||
static EntityViewComponentCache() | |||
{ | |||
cachedFields = new FasterList<KeyValuePair<Type, FastInvokeActionCast<T>>>(); | |||
var type = typeof(T); | |||
var fields = type.GetFields(BindingFlags.Public | BindingFlags.Instance); | |||
for (var i = fields.Length - 1; i >= 0; --i) | |||
{ | |||
var field = fields[i]; | |||
if (field.FieldType.IsInterface == true) | |||
{ | |||
var setter = FastInvoke<T>.MakeSetter(field); | |||
//for each interface, cache the setter for this type | |||
cachedFields.Add(new KeyValuePair<Type, FastInvokeActionCast<T>>(field.FieldType, setter)); | |||
} | |||
} | |||
cachedTypes = new Dictionary<Type, Type[]>(); | |||
#if DEBUG && !PROFILE_SVELTO | |||
implementorsByType = new Dictionary<Type, ECSTuple<object, int>>(); | |||
#else | |||
implementorsByType = new Dictionary<Type, object>(); | |||
#endif | |||
} | |||
internal static void InitCache() | |||
{} | |||
} | |||
} | |||
} |
@@ -1,5 +1,5 @@ | |||
fileFormatVersion: 2 | |||
guid: cc541a5ea51e37898f2e56854ab8c4fb | |||
guid: cf16c7aee929396a99cb63c9d8242a91 | |||
MonoImporter: | |||
externalObjects: {} | |||
serializedVersion: 2 |
@@ -1,5 +1,6 @@ | |||
fileFormatVersion: 2 | |||
guid: 7f991729576f3f0fa1771f61c9f77c15 | |||
guid: 7163f266434d335e810da967a4c4b3ce | |||
folderAsset: yes | |||
DefaultImporter: | |||
externalObjects: {} | |||
userData: |
@@ -0,0 +1,7 @@ | |||
namespace Svelto.ECS | |||
{ | |||
public struct EGIDComponent:IEntityComponent, INeedEGID | |||
{ | |||
public EGID ID { get; set; } | |||
} | |||
} |
@@ -1,5 +1,5 @@ | |||
fileFormatVersion: 2 | |||
guid: 31949720b3a734b4a94de73465ef307f | |||
guid: 486ed173f6753a56b9f8b9ec44c7bfc3 | |||
MonoImporter: | |||
externalObjects: {} | |||
serializedVersion: 2 |
@@ -0,0 +1,11 @@ | |||
namespace Svelto.ECS | |||
{ | |||
public struct EntityHierarchyComponent: IEntityComponent, INeedEGID | |||
{ | |||
public readonly ExclusiveGroupStruct parentGroup; | |||
public EntityHierarchyComponent(ExclusiveGroup group): this() { parentGroup = group; } | |||
public EGID ID { get; set; } | |||
} | |||
} |
@@ -0,0 +1,11 @@ | |||
fileFormatVersion: 2 | |||
guid: b278b4ce4e7f34f0a2434b7de79b7cbb | |||
MonoImporter: | |||
externalObjects: {} | |||
serializedVersion: 2 | |||
defaultReferences: [] | |||
executionOrder: 0 | |||
icon: {instanceID: 0} | |||
userData: | |||
assetBundleName: | |||
assetBundleVariant: |
@@ -0,0 +1,7 @@ | |||
namespace Svelto.ECS | |||
{ | |||
public struct LinkedEntityComponent : IEntityComponent | |||
{ | |||
public EGID linkedEntity; | |||
} | |||
} |
@@ -0,0 +1,11 @@ | |||
fileFormatVersion: 2 | |||
guid: b2536af786e0381aa68a66f6569358bf | |||
MonoImporter: | |||
externalObjects: {} | |||
serializedVersion: 2 | |||
defaultReferences: [] | |||
executionOrder: 0 | |||
icon: {instanceID: 0} | |||
userData: | |||
assetBundleName: | |||
assetBundleVariant: |
@@ -1,4 +1,4 @@ | |||
#if DISABLE_DBC || !DEBUG || PROFILER | |||
#if DISABLE_DBC || !DEBUG || PROFILE_SVELTO | |||
#define DISABLE_CHECKS | |||
using System.Diagnostics; | |||
#endif | |||
@@ -0,0 +1,293 @@ | |||
#if EXPERIMENTAL | |||
using System; | |||
using System.Runtime.CompilerServices; | |||
using Svelto.Common; | |||
using Svelto.DataStructures; | |||
namespace Svelto.ECS.Internal | |||
{ | |||
sealed class FastTypeSafeDictionary<TValue> : ITypeSafeDictionary<TValue> where TValue : struct, IEntityComponent | |||
{ | |||
static readonly Type _type = typeof(TValue); | |||
static readonly string _typeName = _type.Name; | |||
static readonly bool _hasEgid = typeof(INeedEGID).IsAssignableFrom(_type); | |||
public FastTypeSafeDictionary(uint size) { _implementation = new SetDictionary<TValue>(size); } | |||
public FastTypeSafeDictionary() { _implementation = new SetDictionary<TValue>(1); } | |||
/// <summary> | |||
/// Add entities from external typeSafeDictionary | |||
/// </summary> | |||
/// <param name="entitiesToSubmit"></param> | |||
/// <param name="groupId"></param> | |||
/// <exception cref="TypeSafeDictionaryException"></exception> | |||
public void AddEntitiesFromDictionary(ITypeSafeDictionary entitiesToSubmit, uint groupId) | |||
{ | |||
var typeSafeDictionary = entitiesToSubmit as FastTypeSafeDictionary<TValue>; | |||
foreach (var tuple in typeSafeDictionary) | |||
{ | |||
try | |||
{ | |||
if (_hasEgid) | |||
SetEGIDWithoutBoxing<TValue>.SetIDWithoutBoxing(ref typeSafeDictionary.unsafeValues[tuple.Key], | |||
new EGID(tuple.Key, groupId)); | |||
_implementation.Add(tuple.Value); | |||
} | |||
catch (Exception e) | |||
{ | |||
throw new | |||
TypeSafeDictionaryException("trying to add an EntityComponent with the same ID more than once Entity: ". | |||
FastConcat(typeof(TValue).ToString()).FastConcat(", group "). | |||
FastConcat(groupId).FastConcat(", id ").FastConcat(tuple.Key), e); | |||
} | |||
} | |||
} | |||
public void AddEntityToDictionary(EGID fromEntityGid, EGID toEntityID, ITypeSafeDictionary toGroup) | |||
{ | |||
var valueIndex = _implementation.GetIndex(fromEntityGid.entityID); | |||
if (toGroup != null) | |||
{ | |||
var toGroupCasted = toGroup as ITypeSafeDictionary<TValue>; | |||
ref var entity = ref _implementation.unsafeValues[(int) valueIndex]; | |||
if (_hasEgid) SetEGIDWithoutBoxing<TValue>.SetIDWithoutBoxing(ref entity, toEntityID); | |||
toGroupCasted.Add(fromEntityGid.entityID, entity); | |||
} | |||
} | |||
public void AddEntitiesToEngines(FasterDictionary<RefWrapperType, FasterList<IEngine>> entityComponentEnginesDB, | |||
ITypeSafeDictionary realDic, | |||
ExclusiveGroupStruct @group, | |||
in PlatformProfiler profiler) | |||
{ | |||
var typeSafeDictionary = realDic as ITypeSafeDictionary<TValue>; | |||
//this can be optimized, should pass all the entities and not restart the process for each one | |||
foreach (var value in _implementation) | |||
AddEntityComponentToEngines(entityComponentEnginesDB, ref typeSafeDictionary.GetValueByRef(value.Key), null, | |||
in profiler, new EGID(value.Key, group)); | |||
} | |||
public void RemoveEntitiesFromEngines( | |||
FasterDictionary<RefWrapperType, FasterList<IEngine>> entityComponentEnginesDB, in PlatformProfiler profiler, | |||
ExclusiveGroupStruct @group) | |||
{ | |||
foreach (var value in _implementation) | |||
RemoveEntityComponentFromEngines(entityComponentEnginesDB, ref _implementation.GetValueByRef(value.Key), null, | |||
in profiler, new EGID(value.Key, group)); | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public void FastClear() { _implementation.FastClear(); } | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public bool Has(uint key) { return _implementation.ContainsKey(key); } | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public void RemoveEntityFromDictionary(EGID fromEntityGid) | |||
{ | |||
_implementation.Remove(fromEntityGid.entityID); | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public void SetCapacity(uint size) { throw new NotImplementedException(); } | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public void Trim() { _implementation.Trim(); } | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public void Clear() { _implementation.Clear(); } | |||
public void MoveEntityFromEngines(EGID fromEntityGid, EGID? toEntityID, ITypeSafeDictionary toGroup, | |||
FasterDictionary<RefWrapperType, FasterList<IEngine>> engines, | |||
in PlatformProfiler profiler) | |||
{ | |||
var valueIndex = _implementation.GetIndex(fromEntityGid.entityID); | |||
ref var entity = ref _implementation.unsafeValues[(int) valueIndex]; | |||
if (toGroup != null) | |||
{ | |||
RemoveEntityComponentFromEngines(engines, ref entity, fromEntityGid.groupID, in profiler, fromEntityGid); | |||
var toGroupCasted = toGroup as ITypeSafeDictionary<TValue>; | |||
var previousGroup = fromEntityGid.groupID; | |||
if (_hasEgid) SetEGIDWithoutBoxing<TValue>.SetIDWithoutBoxing(ref entity, toEntityID.Value); | |||
var index = toGroupCasted.GetIndex(toEntityID.Value.entityID); | |||
AddEntityComponentToEngines(engines, ref toGroupCasted.unsafeValues[(int) index], previousGroup, in profiler, | |||
toEntityID.Value); | |||
} | |||
else | |||
RemoveEntityComponentFromEngines(engines, ref entity, null, in profiler, fromEntityGid); | |||
} | |||
public uint Count | |||
{ | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
get => _implementation.count; | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public ITypeSafeDictionary Create() { return new FastTypeSafeDictionary<TValue>(); } | |||
void AddEntityComponentToEngines(FasterDictionary<RefWrapperType, FasterList<IEngine>> entityComponentEnginesDB, | |||
ref TValue entity, | |||
ExclusiveGroupStruct? previousGroup, | |||
in PlatformProfiler profiler, | |||
EGID egid) | |||
{ | |||
//get all the engines linked to TValue | |||
if (!entityComponentEnginesDB.TryGetValue(new RefWrapperType(_type), out var entityComponentsEngines)) return; | |||
if (previousGroup == null) | |||
{ | |||
for (var i = 0; i < entityComponentsEngines.count; i++) | |||
try | |||
{ | |||
using (profiler.Sample(entityComponentsEngines[i], _typeName)) | |||
{ | |||
(entityComponentsEngines[i] as IReactOnAddAndRemove<TValue>).Add(ref entity, egid); | |||
} | |||
} | |||
catch (Exception e) | |||
{ | |||
throw new | |||
ECSException("Code crashed inside Add callback ".FastConcat(typeof(TValue).ToString()), e); | |||
} | |||
} | |||
else | |||
{ | |||
for (var i = 0; i < entityComponentsEngines.count; i++) | |||
try | |||
{ | |||
using (profiler.Sample(entityComponentsEngines[i], _typeName)) | |||
{ | |||
(entityComponentsEngines[i] as IReactOnSwap<TValue>).MovedTo(ref entity, previousGroup.Value, | |||
egid); | |||
} | |||
} | |||
catch (Exception e) | |||
{ | |||
throw new | |||
ECSException("Code crashed inside MovedTo callback ".FastConcat(typeof(TValue).ToString()), | |||
e); | |||
} | |||
} | |||
} | |||
static void RemoveEntityComponentFromEngines(FasterDictionary<RefWrapperType, FasterList<IEngine>> @group, | |||
ref TValue entity, | |||
ExclusiveGroupStruct? previousGroup, | |||
in PlatformProfiler profiler, | |||
EGID egid) | |||
{ | |||
if (!@group.TryGetValue(new RefWrapperType(_type), out var entityComponentsEngines)) return; | |||
if (previousGroup == null) | |||
{ | |||
for (var i = 0; i < entityComponentsEngines.count; i++) | |||
try | |||
{ | |||
using (profiler.Sample(entityComponentsEngines[i], _typeName)) | |||
(entityComponentsEngines[i] as IReactOnAddAndRemove<TValue>).Remove(ref entity, egid); | |||
} | |||
catch (Exception e) | |||
{ | |||
throw new | |||
ECSException("Code crashed inside Remove callback ".FastConcat(typeof(TValue).ToString()), | |||
e); | |||
} | |||
} | |||
#if SEEMS_UNNECESSARY | |||
else | |||
{ | |||
for (var i = 0; i < entityComponentsEngines.Count; i++) | |||
try | |||
{ | |||
using (profiler.Sample(entityComponentsEngines[i], _typeName)) | |||
(entityComponentsEngines[i] as IReactOnSwap<TValue>).MovedFrom(ref entity, egid); | |||
} | |||
catch (Exception e) | |||
{ | |||
throw new ECSException( | |||
"Code crashed inside Remove callback ".FastConcat(typeof(TValue).ToString()), e); | |||
} | |||
} | |||
#endif | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public TValue[] GetValuesArray(out uint count) | |||
{ | |||
var managedBuffer = _implementation.GetValuesArray(out count); | |||
return managedBuffer; | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public bool ContainsKey(uint egidEntityId) { return _implementation.ContainsKey(egidEntityId); } | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public void Add(uint egidEntityId, in TValue entityComponent) { _implementation.Add(entityComponent); } | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public SetDictionary<TValue>.SetDictionaryKeyValueEnumerator GetEnumerator() | |||
{ | |||
return _implementation.GetEnumerator(); | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public ref TValue GetValueByRef(uint key) { return ref _implementation.GetValueByRef(key); } | |||
public TValue this[uint idEntityId] | |||
{ | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
get => _implementation[idEntityId]; | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
set => _implementation[idEntityId] = value; | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public uint GetIndex(uint valueEntityId) { return _implementation.GetIndex(valueEntityId); } | |||
public TValue[] unsafeValues | |||
{ | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
get => _implementation.unsafeValues; | |||
} | |||
public SetDictionary<TValue> implementation => _implementation; | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public bool TryGetValue(uint entityId, out TValue item) | |||
{ | |||
return _implementation.TryGetValue(entityId, out item); | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public ref TValue GetOrCreate(uint idEntityId) { throw new NotImplementedException(); } | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public bool TryFindIndex(uint entityId, out uint index) | |||
{ | |||
return _implementation.TryFindIndex(entityId, out index); | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public ref TValue GetDirectValue(uint findElementIndex) | |||
{ | |||
return ref _implementation.GetDirectValue(findElementIndex); | |||
} | |||
readonly SetDictionary<TValue> _implementation; | |||
} | |||
} | |||
#endif |
@@ -0,0 +1,11 @@ | |||
fileFormatVersion: 2 | |||
guid: 5aab8c4d657d3850b22b7a8d7b0038b6 | |||
MonoImporter: | |||
externalObjects: {} | |||
serializedVersion: 2 | |||
defaultReferences: [] | |||
executionOrder: 0 | |||
icon: {instanceID: 0} | |||
userData: | |||
assetBundleName: | |||
assetBundleVariant: |
@@ -0,0 +1,47 @@ | |||
using System; | |||
using Svelto.Common; | |||
using Svelto.DataStructures; | |||
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); | |||
IBuffer<TValue> GetValues(out uint count); | |||
ref TValue GetDirectValueByRef(uint key); | |||
} | |||
public interface ITypeSafeDictionary:IDisposable | |||
{ | |||
uint count { get; } | |||
ITypeSafeDictionary Create(); | |||
//todo: there is something wrong in the design of the execute callback methods. Something to cleanup | |||
void ExecuteEnginesAddOrSwapCallbacks(FasterDictionary<RefWrapperType, FasterList<IReactEngine>> entityComponentEnginesDb, | |||
ITypeSafeDictionary realDic, ExclusiveGroupStruct? fromGroup, ExclusiveGroupStruct toGroup, in PlatformProfiler profiler); | |||
void ExecuteEnginesSwapOrRemoveCallbacks(EGID fromEntityGid, EGID? toEntityID, ITypeSafeDictionary toGroup, | |||
FasterDictionary<RefWrapperType, FasterList<IReactEngine>> engines, in PlatformProfiler profiler); | |||
void ExecuteEnginesRemoveCallbacks(FasterDictionary<RefWrapperType, FasterList<IReactEngine>> entityComponentEnginesDB, | |||
in PlatformProfiler profiler, ExclusiveGroupStruct @group); | |||
void AddEntitiesFromDictionary(ITypeSafeDictionary entitiesToSubmit, uint groupId); | |||
void AddEntityToDictionary(EGID fromEntityGid, EGID toEntityID, ITypeSafeDictionary toGroup); | |||
void RemoveEntityFromDictionary(EGID fromEntityGid); | |||
void SetCapacity(uint size); | |||
void Trim(); | |||
void Clear(); | |||
void FastClear(); | |||
bool Has(uint key); | |||
bool ContainsKey(uint egidEntityId); | |||
uint GetIndex(uint valueEntityId); | |||
bool TryFindIndex(uint entityGidEntityId, out uint index); | |||
void KeysEvaluator(System.Action<uint> action); | |||
} | |||
} |
@@ -0,0 +1,11 @@ | |||
fileFormatVersion: 2 | |||
guid: 186bc7d192ae3700b0cbd86c710b6630 | |||
MonoImporter: | |||
externalObjects: {} | |||
serializedVersion: 2 | |||
defaultReferences: [] | |||
executionOrder: 0 | |||
icon: {instanceID: 0} | |||
userData: | |||
assetBundleName: | |||
assetBundleVariant: |
@@ -1,46 +0,0 @@ | |||
using System; | |||
using System.Linq.Expressions; | |||
using System.Reflection; | |||
namespace Svelto.ECS.Internal | |||
{ | |||
static class SetEGIDWithoutBoxing<T> where T : struct, IEntityStruct | |||
{ | |||
internal delegate void ActionCast(ref T target, EGID egid); | |||
public static readonly ActionCast SetIDWithoutBoxing = MakeSetter(); | |||
static ActionCast MakeSetter() | |||
{ | |||
if (EntityBuilder<T>.HAS_EGID) | |||
{ | |||
#if !ENABLE_IL2CPP | |||
Type myTypeA = typeof(T); | |||
PropertyInfo myFieldInfo = myTypeA.GetProperty("ID"); | |||
ParameterExpression targetExp = Expression.Parameter(typeof(T).MakeByRefType(), "target"); | |||
ParameterExpression valueExp = Expression.Parameter(typeof(EGID), "value"); | |||
MemberExpression fieldExp = Expression.Property(targetExp, myFieldInfo); | |||
BinaryExpression assignExp = Expression.Assign(fieldExp, valueExp); | |||
var setter = Expression.Lambda<ActionCast>(assignExp, targetExp, valueExp).Compile(); | |||
return setter; | |||
#else | |||
return (ref T target, EGID value) => | |||
{ | |||
var needEgid = (target as INeedEGID); | |||
needEgid.ID = value; | |||
target = (T) needEgid; | |||
}; | |||
#endif | |||
} | |||
return null; | |||
} | |||
public static void Warmup() | |||
{ | |||
} | |||
} | |||
} |
@@ -0,0 +1,74 @@ | |||
// using System.Threading.Tasks; | |||
// using NUnit.Framework; | |||
// using Svelto.Common; | |||
// using Svelto.ECS.DataStructures; | |||
// | |||
// namespace Svelto.ECS.Tests.Common.DataStructures | |||
// { | |||
// [TestFixture] | |||
// public class ThreadSafeNativeBagTest | |||
// { | |||
// [Test] | |||
// public void TestByteReallocWorks() | |||
// { | |||
// var threadNativeBag = new ThreadSafeNativeBag(Allocator.Persistent); | |||
// | |||
// Parallel.Invoke(() => | |||
// { | |||
// for (int i = 0; i < 100; i++) | |||
// { | |||
// threadNativeBag.Enqueue((int)1); | |||
// } | |||
// } | |||
// , // close first Action | |||
// () => | |||
// { | |||
// for (int i = 0; i < 100; i++) | |||
// { | |||
// threadNativeBag.Enqueue((int)2); | |||
// } | |||
// } | |||
// , //close second Action | |||
// | |||
// () => | |||
// { | |||
// for (int i = 0; i < 100; i++) | |||
// { | |||
// threadNativeBag.Enqueue(3); | |||
// } | |||
// } //close third Action | |||
// ); //close parallel.invoke | |||
// | |||
// // for (int i = 0; i < 100; i++) | |||
// // { | |||
// // threadNativeBag.Enqueue(1); | |||
// // } | |||
// | |||
// int oneCount = 0, twoCount = 0, threeCount = 0; | |||
// | |||
// while (threadNativeBag.count > 0) | |||
// { | |||
// var value = threadNativeBag.Dequeue<int>(); | |||
// | |||
// switch (value) | |||
// { | |||
// case 1: | |||
// oneCount++; | |||
// break; | |||
// case 2: | |||
// twoCount++; | |||
// break; | |||
// case 3: | |||
// threeCount++; | |||
// break; | |||
// } | |||
// } | |||
// | |||
// Assert.That(oneCount, Is.EqualTo(100)); | |||
// Assert.That(twoCount, Is.EqualTo(100)); | |||
// Assert.That(threeCount, Is.EqualTo(100)); | |||
// | |||
// threadNativeBag.Dispose(); | |||
// } | |||
// } | |||
// } |
@@ -0,0 +1,11 @@ | |||
fileFormatVersion: 2 | |||
guid: 6783b8d49c5935fd8f863f6d78e003ef | |||
MonoImporter: | |||
externalObjects: {} | |||
serializedVersion: 2 | |||
defaultReferences: [] | |||
executionOrder: 0 | |||
icon: {instanceID: 0} | |||
userData: | |||
assetBundleName: | |||
assetBundleVariant: |
@@ -1,222 +1,536 @@ | |||
using System; | |||
using System.Runtime.CompilerServices; | |||
using Svelto.Common; | |||
using Svelto.DataStructures; | |||
using Svelto.ECS.Hybrid; | |||
namespace Svelto.ECS.Internal | |||
{ | |||
public interface ITypeSafeDictionary | |||
sealed class TypeSafeDictionary<TValue> : ITypeSafeDictionary<TValue> where TValue : struct, IEntityComponent | |||
{ | |||
int Count { get; } | |||
ITypeSafeDictionary Create(); | |||
static readonly Type _type = typeof(TValue); | |||
static readonly string _typeName = _type.Name; | |||
static readonly bool _hasEgid = typeof(INeedEGID).IsAssignableFrom(_type); | |||
void AddEntitiesToEngines( | |||
FasterDictionary<RefWrapper<Type>, FasterList<IEngine>> entityViewEnginesDb, | |||
ITypeSafeDictionary realDic, in PlatformProfiler profiler, ExclusiveGroup.ExclusiveGroupStruct @group); | |||
internal static readonly bool IsUnmanaged = | |||
_type.IsUnmanagedEx() && (typeof(IEntityViewComponent).IsAssignableFrom(_type) == false); | |||
void RemoveEntitiesFromEngines(FasterDictionary<RefWrapper<Type>, FasterList<IEngine>> entityViewEnginesDB, | |||
in PlatformProfiler profiler, ExclusiveGroup.ExclusiveGroupStruct @group); | |||
SveltoDictionary<uint, TValue, NativeStrategy<FasterDictionaryNode<uint>>, ManagedStrategy<TValue>, | |||
ManagedStrategy<int>> implMgd; | |||
void AddEntitiesFromDictionary(ITypeSafeDictionary entitiesToSubmit, uint groupId); | |||
//used directly by native methods | |||
internal SveltoDictionary<uint, TValue, NativeStrategy<FasterDictionaryNode<uint>>, NativeStrategy<TValue>, | |||
NativeStrategy<int>> implUnmgd; | |||
void MoveEntityFromEngines(EGID fromEntityGid, EGID? toEntityID, ITypeSafeDictionary toGroup, | |||
FasterDictionary<RefWrapper<Type>, FasterList<IEngine>> engines, in PlatformProfiler profiler); | |||
public TypeSafeDictionary(uint size) | |||
{ | |||
if (IsUnmanaged) | |||
implUnmgd = new SveltoDictionary<uint, TValue, NativeStrategy<FasterDictionaryNode<uint>>, | |||
NativeStrategy<TValue>, NativeStrategy<int>>(size); | |||
else | |||
{ | |||
implMgd = new SveltoDictionary<uint, TValue, NativeStrategy<FasterDictionaryNode<uint>>, | |||
ManagedStrategy<TValue>, ManagedStrategy<int>>(size); | |||
} | |||
} | |||
void AddEntityToDictionary(EGID fromEntityGid, EGID toEntityID, ITypeSafeDictionary toGroup); | |||
void RemoveEntityFromDictionary(EGID fromEntityGid, in PlatformProfiler profiler); | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public void Add(uint egidEntityId, in TValue entityComponent) | |||
{ | |||
if (IsUnmanaged) | |||
implUnmgd.Add(egidEntityId, entityComponent); | |||
else | |||
implMgd.Add(egidEntityId, entityComponent); | |||
} | |||
void SetCapacity(uint size); | |||
void Trim(); | |||
void Clear(); | |||
void FastClear(); | |||
bool Has(uint entityIdEntityId); | |||
} | |||
/// <summary> | |||
/// Add entities from external typeSafeDictionary | |||
/// </summary> | |||
/// <param name="entitiesToSubmit"></param> | |||
/// <param name="groupId"></param> | |||
/// <exception cref="TypeSafeDictionaryException"></exception> | |||
public void AddEntitiesFromDictionary(ITypeSafeDictionary entitiesToSubmit, uint groupId) | |||
{ | |||
if (IsUnmanaged) | |||
{ | |||
var typeSafeDictionary = (entitiesToSubmit as TypeSafeDictionary<TValue>).implUnmgd; | |||
class TypeSafeDictionary<TValue> : FasterDictionary<uint, TValue>, | |||
ITypeSafeDictionary where TValue : struct, IEntityStruct | |||
{ | |||
static readonly Type _type = typeof(TValue); | |||
static readonly string _typeName = _type.Name; | |||
static readonly bool _hasEgid = typeof(INeedEGID).IsAssignableFrom(_type); | |||
foreach (var tuple in typeSafeDictionary) | |||
try | |||
{ | |||
if (_hasEgid) | |||
SetEGIDWithoutBoxing<TValue>.SetIDWithoutBoxing( | |||
ref tuple.Value, new EGID(tuple.Key, groupId)); | |||
public TypeSafeDictionary(uint size) : base(size) {} | |||
public TypeSafeDictionary() {} | |||
implUnmgd.Add(tuple.Key, tuple.Value); | |||
} | |||
catch (Exception e) | |||
{ | |||
Console.LogException( | |||
e, "trying to add an EntityComponent with the same ID more than once Entity: ".FastConcat(typeof(TValue).ToString()).FastConcat(", group ").FastConcat(groupId).FastConcat(", id ").FastConcat(tuple.Key)); | |||
public void AddEntitiesFromDictionary(ITypeSafeDictionary entitiesToSubmit, uint groupId) | |||
throw; | |||
} | |||
} | |||
else | |||
{ | |||
var typeSafeDictionary = (entitiesToSubmit as TypeSafeDictionary<TValue>).implMgd; | |||
foreach (var tuple in typeSafeDictionary) | |||
try | |||
{ | |||
if (_hasEgid) | |||
SetEGIDWithoutBoxing<TValue>.SetIDWithoutBoxing( | |||
ref tuple.Value, new EGID(tuple.Key, groupId)); | |||
implMgd.Add(tuple.Key, tuple.Value); | |||
} | |||
catch (Exception e) | |||
{ | |||
Console.LogException( | |||
e, "trying to add an EntityComponent with the same ID more than once Entity: ".FastConcat(typeof(TValue).ToString()).FastConcat(", group ").FastConcat(groupId).FastConcat(", id ").FastConcat(tuple.Key)); | |||
throw; | |||
} | |||
} | |||
} | |||
public void ExecuteEnginesAddOrSwapCallbacks | |||
(FasterDictionary<RefWrapperType, FasterList<IReactEngine>> entityComponentEnginesDB | |||
, ITypeSafeDictionary realDic, ExclusiveGroupStruct? fromGroup, ExclusiveGroupStruct toGroup | |||
, in PlatformProfiler profiler) | |||
{ | |||
var typeSafeDictionary = entitiesToSubmit as TypeSafeDictionary<TValue>; | |||
if (IsUnmanaged) | |||
{ | |||
var typeSafeDictionary = realDic as ITypeSafeDictionary<TValue>; | |||
foreach (var tuple in typeSafeDictionary) | |||
//this can be optimized, should pass all the entities and not restart the process for each one | |||
foreach (var value in implUnmgd) | |||
ExecuteEnginesAddOrSwapCallbacksOnSingleEntity(entityComponentEnginesDB, ref typeSafeDictionary[value.Key] | |||
, fromGroup, in profiler, new EGID(value.Key, toGroup)); | |||
} | |||
else | |||
{ | |||
try | |||
var typeSafeDictionary = realDic as ITypeSafeDictionary<TValue>; | |||
//this can be optimized, should pass all the entities and not restart the process for each one | |||
foreach (var value in implMgd) | |||
ExecuteEnginesAddOrSwapCallbacksOnSingleEntity(entityComponentEnginesDB, ref typeSafeDictionary[value.Key] | |||
, fromGroup, in profiler, new EGID(value.Key, toGroup)); | |||
} | |||
} | |||
public void AddEntityToDictionary(EGID fromEntityGid, EGID toEntityID, ITypeSafeDictionary toGroup) | |||
{ | |||
if (IsUnmanaged) | |||
{ | |||
var valueIndex = implUnmgd.GetIndex(fromEntityGid.entityID); | |||
DBC.ECS.Check.Require(toGroup != null | |||
, "Invalid To Group"); //todo check this, if it's right merge GetIndex | |||
{ | |||
if (_hasEgid) SetEGIDWithoutBoxing<TValue>.SetIDWithoutBoxing(ref tuple.Value, new EGID(tuple.Key, groupId)); | |||
var toGroupCasted = toGroup as ITypeSafeDictionary<TValue>; | |||
ref var entity = ref implUnmgd.GetDirectValueByRef(valueIndex); | |||
Add(tuple.Key, tuple.Value); | |||
if (_hasEgid) | |||
SetEGIDWithoutBoxing<TValue>.SetIDWithoutBoxing(ref entity, toEntityID); | |||
toGroupCasted.Add(toEntityID.entityID, entity); | |||
} | |||
catch (Exception e) | |||
} | |||
else | |||
{ | |||
var valueIndex = implMgd.GetIndex(fromEntityGid.entityID); | |||
DBC.ECS.Check.Require(toGroup != null | |||
, "Invalid To Group"); //todo check this, if it's right merge GetIndex | |||
{ | |||
throw new TypeSafeDictionaryException( | |||
"trying to add an EntityView with the same ID more than once Entity: " | |||
.FastConcat(typeof(TValue).ToString()).FastConcat(", group ").FastConcat(groupId).FastConcat(", id ").FastConcat(tuple.Key), e); | |||
var toGroupCasted = toGroup as ITypeSafeDictionary<TValue>; | |||
ref var entity = ref implMgd.GetDirectValueByRef(valueIndex); | |||
if (_hasEgid) | |||
SetEGIDWithoutBoxing<TValue>.SetIDWithoutBoxing(ref entity, toEntityID); | |||
toGroupCasted.Add(toEntityID.entityID, entity); | |||
} | |||
} | |||
} | |||
public void AddEntitiesToEngines( | |||
FasterDictionary<RefWrapper<Type>, FasterList<IEngine>> entityViewEnginesDB, | |||
ITypeSafeDictionary realDic, in PlatformProfiler profiler, ExclusiveGroup.ExclusiveGroupStruct @group) | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public void Clear() | |||
{ | |||
var typeSafeDictionary = realDic as TypeSafeDictionary<TValue>; | |||
//this can be optimized, should pass all the entities and not restart the process for each one | |||
foreach (var value in this) | |||
AddEntityViewToEngines(entityViewEnginesDB, ref typeSafeDictionary.GetValueByRef(value.Key), null, | |||
in profiler, new EGID(value.Key, group)); | |||
if (IsUnmanaged) | |||
{ | |||
implUnmgd.Clear(); | |||
} | |||
else | |||
{ | |||
implMgd.Clear(); | |||
} | |||
} | |||
public void RemoveEntitiesFromEngines( | |||
FasterDictionary<RefWrapper<Type>, FasterList<IEngine>> entityViewEnginesDB, | |||
in PlatformProfiler profiler, ExclusiveGroup.ExclusiveGroupStruct @group) | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public void FastClear() | |||
{ | |||
foreach (var value in this) | |||
RemoveEntityViewFromEngines(entityViewEnginesDB, ref GetValueByRef(value.Key), null, in profiler, | |||
new EGID(value.Key, group)); | |||
if (IsUnmanaged) | |||
{ | |||
implUnmgd.FastClear(); | |||
} | |||
else | |||
{ | |||
implMgd.FastClear(); | |||
} | |||
} | |||
public bool Has(uint entityIdEntityId) | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public bool ContainsKey(uint egidEntityId) | |||
{ | |||
return ContainsKey(entityIdEntityId); | |||
if (IsUnmanaged) | |||
{ | |||
return implUnmgd.ContainsKey(egidEntityId); | |||
} | |||
else | |||
{ | |||
return implMgd.ContainsKey(egidEntityId); | |||
} | |||
} | |||
public void RemoveEntityFromDictionary(EGID fromEntityGid, in PlatformProfiler profiler) | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public ITypeSafeDictionary Create() { return TypeSafeDictionaryFactory<TValue>.Create(1); } | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public uint GetIndex(uint valueEntityId) | |||
{ | |||
Remove(fromEntityGid.entityID); | |||
if (IsUnmanaged) | |||
{ | |||
return this.implUnmgd.GetIndex(valueEntityId); | |||
} | |||
else | |||
{ | |||
return this.implMgd.GetIndex(valueEntityId); | |||
} | |||
} | |||
public void AddEntityToDictionary(EGID fromEntityGid, EGID toEntityID, ITypeSafeDictionary toGroup) | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public ref TValue GetOrCreate(uint idEntityId) | |||
{ | |||
var valueIndex = GetIndex(fromEntityGid.entityID); | |||
if (IsUnmanaged) | |||
{ | |||
return ref this.implUnmgd.GetOrCreate(idEntityId); | |||
} | |||
else | |||
{ | |||
return ref this.implMgd.GetOrCreate(idEntityId); | |||
} | |||
} | |||
if (toGroup != null) | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public IBuffer<TValue> GetValues(out uint count) | |||
{ | |||
if (IsUnmanaged) | |||
{ | |||
var toGroupCasted = toGroup as TypeSafeDictionary<TValue>; | |||
ref var entity = ref valuesArray[valueIndex]; | |||
return this.implUnmgd.GetValues(out count); | |||
} | |||
else | |||
{ | |||
return this.implMgd.GetValues(out count); | |||
} | |||
} | |||
if (_hasEgid) SetEGIDWithoutBoxing<TValue>.SetIDWithoutBoxing(ref entity, toEntityID); | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public ref TValue GetDirectValueByRef(uint key) | |||
{ | |||
if (IsUnmanaged) | |||
{ | |||
return ref this.implUnmgd.GetDirectValueByRef(key); | |||
} | |||
else | |||
{ | |||
return ref this.implMgd.GetDirectValueByRef(key); | |||
} | |||
} | |||
toGroupCasted.Add(fromEntityGid.entityID, entity); | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public bool Has(uint key) | |||
{ | |||
if (IsUnmanaged) | |||
{ | |||
return this.implUnmgd.ContainsKey(key); | |||
} | |||
else | |||
{ | |||
return this.implMgd.ContainsKey(key); | |||
} | |||
} | |||
public void MoveEntityFromEngines(EGID fromEntityGid, EGID? toEntityID, ITypeSafeDictionary toGroup, | |||
FasterDictionary<RefWrapper<Type>, FasterList<IEngine>> engines, in PlatformProfiler profiler) | |||
public void ExecuteEnginesSwapOrRemoveCallbacks | |||
(EGID fromEntityGid, EGID? toEntityID, ITypeSafeDictionary toGroup | |||
, FasterDictionary<RefWrapperType, FasterList<IReactEngine>> engines, in PlatformProfiler profiler) | |||
{ | |||
var valueIndex = GetIndex(fromEntityGid.entityID); | |||
ref var entity = ref valuesArray[valueIndex]; | |||
if (IsUnmanaged) | |||
{ | |||
var valueIndex = this.implUnmgd.GetIndex(fromEntityGid.entityID); | |||
if (toGroup != null) | |||
ref var entity = ref this.implUnmgd.GetDirectValueByRef(valueIndex); | |||
//move | |||
if (toGroup != null) | |||
{ | |||
var toGroupCasted = toGroup as ITypeSafeDictionary<TValue>; | |||
var previousGroup = fromEntityGid.groupID; | |||
if (_hasEgid) | |||
SetEGIDWithoutBoxing<TValue>.SetIDWithoutBoxing(ref entity, toEntityID.Value); | |||
var index = toGroupCasted.GetIndex(toEntityID.Value.entityID); | |||
ExecuteEnginesAddOrSwapCallbacksOnSingleEntity(engines, ref toGroupCasted.GetDirectValueByRef(index) | |||
, previousGroup, in profiler, toEntityID.Value); | |||
} | |||
//remove | |||
else | |||
{ | |||
ExecuteEnginesRemoveCallbackOnSingleEntity(engines, ref entity, in profiler, fromEntityGid); | |||
} | |||
} | |||
else | |||
{ | |||
RemoveEntityViewFromEngines(engines, ref entity, fromEntityGid.groupID, in profiler, | |||
fromEntityGid); | |||
var valueIndex = this.implMgd.GetIndex(fromEntityGid.entityID); | |||
ref var entity = ref this.implMgd.GetDirectValueByRef(valueIndex); | |||
var toGroupCasted = toGroup as TypeSafeDictionary<TValue>; | |||
var previousGroup = fromEntityGid.groupID; | |||
if (toGroup != null) | |||
{ | |||
var toGroupCasted = toGroup as ITypeSafeDictionary<TValue>; | |||
var previousGroup = fromEntityGid.groupID; | |||
if (_hasEgid) SetEGIDWithoutBoxing<TValue>.SetIDWithoutBoxing(ref entity, toEntityID.Value); | |||
if (_hasEgid) | |||
SetEGIDWithoutBoxing<TValue>.SetIDWithoutBoxing(ref entity, toEntityID.Value); | |||
var index = toGroupCasted.GetIndex(toEntityID.Value.entityID); | |||
var index = toGroupCasted.GetIndex(toEntityID.Value.entityID); | |||
AddEntityViewToEngines(engines, ref toGroupCasted.valuesArray[index], previousGroup, | |||
in profiler, toEntityID.Value); | |||
ExecuteEnginesAddOrSwapCallbacksOnSingleEntity(engines, ref toGroupCasted.GetDirectValueByRef(index) | |||
, previousGroup, in profiler, toEntityID.Value); | |||
} | |||
else | |||
{ | |||
ExecuteEnginesRemoveCallbackOnSingleEntity(engines, ref entity, in profiler, fromEntityGid); | |||
} | |||
} | |||
} | |||
public void ExecuteEnginesRemoveCallbacks | |||
(FasterDictionary<RefWrapperType, FasterList<IReactEngine>> engines, in PlatformProfiler profiler | |||
, ExclusiveGroupStruct group) | |||
{ | |||
if (IsUnmanaged) | |||
{ | |||
foreach (var value in implUnmgd) | |||
ExecuteEnginesRemoveCallbackOnSingleEntity(engines, ref implUnmgd.GetValueByRef(value.Key) | |||
, in profiler, new EGID(value.Key, group)); | |||
} | |||
else | |||
RemoveEntityViewFromEngines(engines, ref entity, null, in profiler, fromEntityGid); | |||
{ | |||
foreach (var value in implMgd) | |||
ExecuteEnginesRemoveCallbackOnSingleEntity(engines, ref implMgd.GetValueByRef(value.Key) | |||
, in profiler, new EGID(value.Key, group)); | |||
} | |||
} | |||
public ITypeSafeDictionary Create() | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public void RemoveEntityFromDictionary(EGID fromEntityGid) | |||
{ | |||
return new TypeSafeDictionary<TValue>(); | |||
if (IsUnmanaged) | |||
{ | |||
this.implUnmgd.Remove(fromEntityGid.entityID); | |||
} | |||
else | |||
{ | |||
this.implMgd.Remove(fromEntityGid.entityID); | |||
} | |||
} | |||
void AddEntityViewToEngines(FasterDictionary<RefWrapper<Type>, FasterList<IEngine>> entityViewEnginesDB, | |||
ref TValue entity, ExclusiveGroup.ExclusiveGroupStruct? previousGroup, | |||
in PlatformProfiler profiler, EGID egid) | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public void SetCapacity(uint size) | |||
{ | |||
//get all the engines linked to TValue | |||
if (!entityViewEnginesDB.TryGetValue(new RefWrapper<Type>(_type), out var entityViewsEngines)) return; | |||
if (IsUnmanaged) | |||
{ | |||
this.implUnmgd.SetCapacity(size); | |||
} | |||
else | |||
{ | |||
this.implMgd.SetCapacity(size); | |||
} | |||
} | |||
if (previousGroup == null) | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public void Trim() | |||
{ | |||
if (IsUnmanaged) | |||
{ | |||
for (var i = 0; i < entityViewsEngines.Count; i++) | |||
try | |||
{ | |||
using (profiler.Sample(entityViewsEngines[i], _typeName)) | |||
{ | |||
(entityViewsEngines[i] as IReactOnAddAndRemove<TValue>).Add(ref entity, egid); | |||
} | |||
} | |||
catch (Exception e) | |||
{ | |||
throw new ECSException( | |||
"Code crashed inside Add callback ".FastConcat(typeof(TValue).ToString()), e); | |||
} | |||
this.implUnmgd.Trim(); | |||
} | |||
else | |||
{ | |||
for (var i = 0; i < entityViewsEngines.Count; i++) | |||
try | |||
{ | |||
using (profiler.Sample(entityViewsEngines[i], _typeName)) | |||
{ | |||
(entityViewsEngines[i] as IReactOnSwap<TValue>).MovedTo(ref entity, previousGroup.Value, | |||
egid); | |||
} | |||
} | |||
catch (Exception e) | |||
this.implMgd.Trim(); | |||
} | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public bool TryFindIndex(uint entityId, out uint index) | |||
{ | |||
if (IsUnmanaged) | |||
{ | |||
return implUnmgd.TryFindIndex(entityId, out index); | |||
} | |||
else | |||
{ | |||
return implMgd.TryFindIndex(entityId, out index); | |||
} | |||
} | |||
public void KeysEvaluator(Action<uint> action) | |||
{ | |||
if (IsUnmanaged) | |||
{ | |||
foreach (var key in implUnmgd.keys) | |||
{ | |||
action(key); | |||
} | |||
} | |||
else | |||
{ | |||
foreach (var key in implMgd.keys) | |||
{ | |||
action(key); | |||
} | |||
} | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public bool TryGetValue(uint entityId, out TValue item) | |||
{ | |||
if (IsUnmanaged) | |||
{ | |||
return this.implUnmgd.TryGetValue(entityId, out item); | |||
} | |||
else | |||
{ | |||
return this.implMgd.TryGetValue(entityId, out item); | |||
} | |||
} | |||
public uint count | |||
{ | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
get | |||
{ | |||
if (IsUnmanaged) | |||
{ | |||
return (uint) this.implUnmgd.count; | |||
} | |||
else | |||
{ | |||
return (uint) this.implMgd.count; | |||
} | |||
} | |||
} | |||
public ref TValue this[uint idEntityId] | |||
{ | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
get | |||
{ | |||
if (IsUnmanaged) | |||
{ | |||
return ref this.implUnmgd.GetValueByRef(idEntityId); | |||
} | |||
else | |||
{ | |||
return ref this.implMgd.GetValueByRef(idEntityId); | |||
} | |||
} | |||
} | |||
static void ExecuteEnginesRemoveCallbackOnSingleEntity | |||
(FasterDictionary<RefWrapperType, FasterList<IReactEngine>> engines, ref TValue entity | |||
, in PlatformProfiler profiler, EGID egid) | |||
{ | |||
if (!engines.TryGetValue(new RefWrapperType(_type), out var entityComponentsEngines)) | |||
return; | |||
for (var i = 0; i < entityComponentsEngines.count; i++) | |||
try | |||
{ | |||
using (profiler.Sample(entityComponentsEngines[i], _typeName)) | |||
{ | |||
throw new ECSException( | |||
"Code crashed inside MovedTo callback ".FastConcat(typeof(TValue).ToString()), e); | |||
(entityComponentsEngines[i] as IReactOnAddAndRemove<TValue>).Remove(ref entity, egid); | |||
} | |||
} | |||
} | |||
catch | |||
{ | |||
Svelto.Console.LogError( | |||
"Code crashed inside Remove callback ".FastConcat(typeof(TValue).ToString())); | |||
throw; | |||
} | |||
} | |||
static void RemoveEntityViewFromEngines( | |||
FasterDictionary<RefWrapper<Type>, FasterList<IEngine>> @group, ref TValue entity, | |||
ExclusiveGroup.ExclusiveGroupStruct? previousGroup, in PlatformProfiler profiler, EGID egid) | |||
void ExecuteEnginesAddOrSwapCallbacksOnSingleEntity | |||
(FasterDictionary<RefWrapperType, FasterList<IReactEngine>> engines, ref TValue entity | |||
, ExclusiveGroupStruct? previousGroup, in PlatformProfiler profiler, EGID egid) | |||
{ | |||
if (!@group.TryGetValue(new RefWrapper<Type>(_type), out var entityViewsEngines)) return; | |||
//get all the engines linked to TValue | |||
if (!engines.TryGetValue(new RefWrapperType(_type), out var entityComponentsEngines)) | |||
return; | |||
if (previousGroup == null) | |||
{ | |||
for (var i = 0; i < entityViewsEngines.Count; i++) | |||
for (var i = 0; i < entityComponentsEngines.count; i++) | |||
try | |||
{ | |||
using (profiler.Sample(entityViewsEngines[i], _typeName)) | |||
(entityViewsEngines[i] as IReactOnAddAndRemove<TValue>).Remove(ref entity, egid); | |||
using (profiler.Sample(entityComponentsEngines[i], _typeName)) | |||
{ | |||
(entityComponentsEngines[i] as IReactOnAddAndRemove<TValue>).Add(ref entity, egid); | |||
} | |||
} | |||
catch (Exception e) | |||
catch | |||
{ | |||
throw new ECSException( | |||
"Code crashed inside Remove callback ".FastConcat(typeof(TValue).ToString()), e); | |||
Svelto.Console.LogError( | |||
"Code crashed inside Add callback ".FastConcat(typeof(TValue).ToString())); | |||
throw; | |||
} | |||
} | |||
#if SEEMS_UNNECESSARY | |||
else | |||
{ | |||
for (var i = 0; i < entityViewsEngines.Count; i++) | |||
for (var i = 0; i < entityComponentsEngines.count; i++) | |||
try | |||
{ | |||
using (profiler.Sample(entityViewsEngines[i], _typeName)) | |||
(entityViewsEngines[i] as IReactOnSwap<TValue>).MovedFrom(ref entity, egid); | |||
using (profiler.Sample(entityComponentsEngines[i], _typeName)) | |||
{ | |||
(entityComponentsEngines[i] as IReactOnSwap<TValue>).MovedTo( | |||
ref entity, previousGroup.Value, egid); | |||
} | |||
} | |||
catch (Exception e) | |||
catch (Exception) | |||
{ | |||
throw new ECSException( | |||
"Code crashed inside Remove callback ".FastConcat(typeof(TValue).ToString()), e); | |||
Svelto.Console.LogError( | |||
"Code crashed inside MoveTo callback ".FastConcat(typeof(TValue).ToString())); | |||
throw; | |||
} | |||
} | |||
#endif | |||
} | |||
public void Dispose() | |||
{ | |||
if (IsUnmanaged) | |||
implUnmgd.Dispose(); | |||
else | |||
implMgd.Dispose(); | |||
GC.SuppressFinalize(this); | |||
} | |||
} | |||
} |
@@ -0,0 +1,13 @@ | |||
namespace Svelto.ECS.Internal | |||
{ | |||
static class TypeSafeDictionaryUtilities | |||
{ | |||
internal static EGIDMapper<T> ToEGIDMapper<T>(this ITypeSafeDictionary<T> dic, | |||
ExclusiveGroupStruct groupStructId) where T:struct, IEntityComponent | |||
{ | |||
var mapper = new EGIDMapper<T>(groupStructId, dic); | |||
return mapper; | |||
} | |||
} | |||
} |
@@ -0,0 +1,11 @@ | |||
fileFormatVersion: 2 | |||
guid: a2196c9108d435e6ac91b30c0aed8644 | |||
MonoImporter: | |||
externalObjects: {} | |||
serializedVersion: 2 | |||
defaultReferences: [] | |||
executionOrder: 0 | |||
icon: {instanceID: 0} | |||
userData: | |||
assetBundleName: | |||
assetBundleVariant: |
@@ -0,0 +1,8 @@ | |||
fileFormatVersion: 2 | |||
guid: c887c28f847537e58b00adf544344895 | |||
folderAsset: yes | |||
DefaultImporter: | |||
externalObjects: {} | |||
userData: | |||
assetBundleName: | |||
assetBundleVariant: |
@@ -0,0 +1,76 @@ | |||
#if UNITY_NATIVE //because of the thread count, ATM this is only for unity | |||
using System; | |||
using System.Runtime.CompilerServices; | |||
using Svelto.Common; | |||
using Unity.Jobs.LowLevel.Unsafe; | |||
using Allocator = Svelto.Common.Allocator; | |||
namespace Svelto.ECS.DataStructures | |||
{ | |||
public unsafe struct AtomicNativeBags:IDisposable | |||
{ | |||
[global::Unity.Collections.LowLevel.Unsafe.NativeDisableUnsafePtrRestriction] | |||
NativeBag* _data; | |||
readonly Allocator _allocator; | |||
readonly uint _threadsCount; | |||
public uint count => _threadsCount; | |||
public AtomicNativeBags(Allocator allocator) | |||
{ | |||
_allocator = allocator; | |||
_threadsCount = JobsUtility.MaxJobThreadCount + 1; | |||
var bufferSize = MemoryUtilities.SizeOf<NativeBag>(); | |||
var bufferCount = _threadsCount; | |||
var allocationSize = bufferSize * bufferCount; | |||
var ptr = (byte*)MemoryUtilities.Alloc((uint) allocationSize, allocator); | |||
// MemoryUtilities.MemClear((IntPtr) ptr, (uint) allocationSize); | |||
for (int i = 0; i < bufferCount; i++) | |||
{ | |||
var bufferPtr = (NativeBag*)(ptr + bufferSize * i); | |||
var buffer = new NativeBag(allocator); | |||
MemoryUtilities.CopyStructureToPtr(ref buffer, (IntPtr) bufferPtr); | |||
} | |||
_data = (NativeBag*)ptr; | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public ref NativeBag GetBuffer(int index) | |||
{ | |||
if (_data == null) | |||
throw new Exception("using invalid AtomicNativeBags"); | |||
return ref MemoryUtilities.ArrayElementAsRef<NativeBag>((IntPtr) _data, index); | |||
} | |||
public void Dispose() | |||
{ | |||
if (_data == null) | |||
throw new Exception("using invalid AtomicNativeBags"); | |||
for (int i = 0; i < _threadsCount; i++) | |||
{ | |||
GetBuffer(i).Dispose(); | |||
} | |||
MemoryUtilities.Free((IntPtr) _data, _allocator); | |||
_data = null; | |||
} | |||
public void Clear() | |||
{ | |||
if (_data == null) | |||
throw new Exception("using invalid AtomicNativeBags"); | |||
for (int i = 0; i < _threadsCount; i++) | |||
{ | |||
GetBuffer(i).Clear(); | |||
} | |||
} | |||
} | |||
} | |||
#endif |
@@ -0,0 +1,11 @@ | |||
fileFormatVersion: 2 | |||
guid: 9903f36f7416334c99d89982bf84cf2a | |||
MonoImporter: | |||
externalObjects: {} | |||
serializedVersion: 2 | |||
defaultReferences: [] | |||
executionOrder: 0 | |||
icon: {instanceID: 0} | |||
userData: | |||
assetBundleName: | |||
assetBundleVariant: |
@@ -0,0 +1,281 @@ | |||
#if DEBUG && !PROFILE_SVELTO | |||
#define ENABLE_DEBUG_CHECKS | |||
#endif | |||
#if DEBUG && !PROFILE_SVELTO | |||
//#define ENABLE_THREAD_SAFE_CHECKS | |||
#endif | |||
using System; | |||
using System.Diagnostics; | |||
using System.Runtime.CompilerServices; | |||
using System.Threading; | |||
using Svelto.Common; | |||
namespace Svelto.ECS.DataStructures | |||
{ | |||
/// <summary> | |||
/// Burst friendly RingBuffer on steroid: | |||
/// it can: Enqueue/Dequeue, it wraps if there is enough space after dequeuing | |||
/// It resizes if there isn't enough space left. | |||
/// It's a "bag", you can queue and dequeue any T. Just be sure that you dequeue what you queue! No check on type | |||
/// is done. | |||
/// You can reserve a position in the queue to update it later. | |||
/// The datastructure is a struct and it's "copyable" | |||
/// I eventually decided to call it NativeBag and not NativeBag because it can also be used as | |||
/// a preallocated memory pool where any kind of T can be stored as long as T is unmanaged | |||
/// </summary> | |||
public struct NativeBag : IDisposable | |||
{ | |||
public uint count | |||
{ | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
get | |||
{ | |||
unsafe | |||
{ | |||
BasicTests(); | |||
#if ENABLE_THREAD_SAFE_CHECKS | |||
try | |||
{ | |||
#endif | |||
return _queue->size; | |||
#if ENABLE_THREAD_SAFE_CHECKS | |||
} | |||
finally | |||
{ | |||
Volatile.Write(ref _threadSentinel, 0); | |||
} | |||
#endif | |||
} | |||
} | |||
} | |||
public uint capacity | |||
{ | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
get | |||
{ | |||
unsafe | |||
{ | |||
BasicTests(); | |||
#if ENABLE_THREAD_SAFE_CHECKS | |||
try | |||
{ | |||
#endif | |||
return _queue->capacity; | |||
#if ENABLE_THREAD_SAFE_CHECKS | |||
} | |||
finally | |||
{ | |||
Volatile.Write(ref _threadSentinel, 0); | |||
} | |||
#endif | |||
} | |||
} | |||
} | |||
public NativeBag(Allocator allocator) | |||
{ | |||
unsafe | |||
{ | |||
var sizeOf = MemoryUtilities.SizeOf<UnsafeBlob>(); | |||
var listData = (UnsafeBlob*) MemoryUtilities.Alloc((uint) sizeOf, allocator); | |||
//clear to nullify the pointers | |||
//MemoryUtilities.MemClear((IntPtr) listData, (uint) sizeOf); | |||
listData->allocator = allocator; | |||
_queue = listData; | |||
#if ENABLE_THREAD_SAFE_CHECKS | |||
_threadSentinel = 0; | |||
#endif | |||
} | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public bool IsEmpty() | |||
{ | |||
unsafe | |||
{ | |||
BasicTests(); | |||
#if ENABLE_THREAD_SAFE_CHECKS | |||
try | |||
{ | |||
#endif | |||
if (_queue == null || _queue->ptr == null) | |||
return true; | |||
#if ENABLE_THREAD_SAFE_CHECKS | |||
} | |||
finally | |||
{ | |||
Volatile.Write(ref _threadSentinel, 0); | |||
} | |||
#endif | |||
} | |||
return count == 0; | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public unsafe void Dispose() | |||
{ | |||
if (_queue != null) | |||
{ | |||
#if ENABLE_THREAD_SAFE_CHECKS | |||
//todo: this must be unit tested | |||
if (Interlocked.CompareExchange(ref _threadSentinel, 1, 0) != 0) | |||
throw new Exception("NativeBag is not thread safe, reading and writing operations can happen" + | |||
"on different threads, but not simultaneously"); | |||
try | |||
{ | |||
#endif | |||
_queue->Dispose(); | |||
MemoryUtilities.Free((IntPtr) _queue, _queue->allocator); | |||
_queue = null; | |||
#if ENABLE_THREAD_SAFE_CHECKS | |||
} | |||
finally | |||
{ | |||
Volatile.Write(ref _threadSentinel, 0); | |||
} | |||
#endif | |||
} | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public ref T ReserveEnqueue<T>(out UnsafeArrayIndex index) where T : struct | |||
{ | |||
unsafe | |||
{ | |||
BasicTests(); | |||
var sizeOf = MemoryUtilities.SizeOf<T>(); | |||
if (_queue->space - sizeOf < 0) | |||
_queue->Realloc((uint) ((_queue->capacity + MemoryUtilities.Align4((uint) sizeOf)) * 2.0f)); | |||
#if ENABLE_THREAD_SAFE_CHECKS | |||
try | |||
{ | |||
#endif | |||
return ref _queue->Reserve<T>(out index); | |||
#if ENABLE_THREAD_SAFE_CHECKS | |||
} | |||
finally | |||
{ | |||
Volatile.Write(ref _threadSentinel, 0); | |||
} | |||
#endif | |||
} | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public void Enqueue<T>(in T item) where T : struct | |||
{ | |||
unsafe | |||
{ | |||
BasicTests(); | |||
#if ENABLE_THREAD_SAFE_CHECKS | |||
try | |||
{ | |||
#endif | |||
var sizeOf = MemoryUtilities.SizeOf<T>(); | |||
if (_queue->space - sizeOf < 0) | |||
_queue->Realloc((uint) ((_queue->capacity + MemoryUtilities.Align4((uint) sizeOf)) * 2.0f)); | |||
_queue->Write(item); | |||
#if ENABLE_THREAD_SAFE_CHECKS | |||
} | |||
finally | |||
{ | |||
Volatile.Write(ref _threadSentinel, 0); | |||
} | |||
#endif | |||
} | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public void Clear() | |||
{ | |||
unsafe | |||
{ | |||
BasicTests(); | |||
#if ENABLE_THREAD_SAFE_CHECKS | |||
try | |||
{ | |||
#endif | |||
_queue->Clear(); | |||
#if ENABLE_THREAD_SAFE_CHECKS | |||
} | |||
finally | |||
{ | |||
Volatile.Write(ref _threadSentinel, 0); | |||
} | |||
#endif | |||
} | |||
} | |||
public T Dequeue<T>() where T : struct | |||
{ | |||
unsafe | |||
{ | |||
BasicTests(); | |||
#if ENABLE_THREAD_SAFE_CHECKS | |||
try | |||
{ | |||
#endif | |||
return _queue->Read<T>(); | |||
#if ENABLE_THREAD_SAFE_CHECKS | |||
} | |||
finally | |||
{ | |||
Volatile.Write(ref _threadSentinel, 0); | |||
} | |||
#endif | |||
} | |||
} | |||
internal ref T AccessReserved<T>(UnsafeArrayIndex reserverIndex) where T : struct | |||
{ | |||
unsafe | |||
{ | |||
BasicTests(); | |||
#if ENABLE_THREAD_SAFE_CHECKS | |||
try | |||
{ | |||
#endif | |||
return ref _queue->AccessReserved<T>(reserverIndex); | |||
#if ENABLE_THREAD_SAFE_CHECKS | |||
} | |||
finally | |||
{ | |||
Volatile.Write(ref _threadSentinel, 0); | |||
} | |||
#endif | |||
} | |||
} | |||
[Conditional("ENABLE_DEBUG_CHECKS")] | |||
unsafe void BasicTests() | |||
{ | |||
if (_queue == null) | |||
throw new Exception("SimpleNativeArray: null-access"); | |||
#if ENABLE_THREAD_SAFE_CHECKS | |||
todo: this must be unit tested | |||
if (Interlocked.CompareExchange(ref _threadSentinel, 1, 0) != 0) | |||
throw new Exception("NativeBag is not thread safe, reading and writing operations can happen" | |||
+ "on different threads, but not simultaneously"); | |||
#endif | |||
} | |||
#if ENABLE_THREAD_SAFE_CHECKS | |||
int _threadSentinel; | |||
#endif | |||
#if UNITY_NATIVE | |||
[global::Unity.Collections.LowLevel.Unsafe.NativeDisableUnsafePtrRestriction] | |||
#endif | |||
unsafe UnsafeBlob* _queue; | |||
} | |||
} |
@@ -0,0 +1,11 @@ | |||
fileFormatVersion: 2 | |||
guid: 5775773e479e3b1db95b31e996594fb8 | |||
MonoImporter: | |||
externalObjects: {} | |||
serializedVersion: 2 | |||
defaultReferences: [] | |||
executionOrder: 0 | |||
icon: {instanceID: 0} | |||
userData: | |||
assetBundleName: | |||
assetBundleVariant: |
@@ -0,0 +1,370 @@ | |||
using System; | |||
using System.Runtime.CompilerServices; | |||
using Svelto.Common; | |||
using Allocator = Svelto.Common.Allocator; | |||
namespace Svelto.ECS.DataStructures | |||
{ | |||
public struct NativeDynamicArray : IDisposable | |||
{ | |||
public bool isValid | |||
{ | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
get | |||
{ | |||
unsafe | |||
{ | |||
return _list != null; | |||
} | |||
} | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public int Count<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"); | |||
#endif | |||
return (_list->count / MemoryUtilities.SizeOf<T>()); | |||
} | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public int Capacity<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"); | |||
#endif | |||
return (_list->capacity / MemoryUtilities.SizeOf<T>()); | |||
} | |||
} | |||
public static NativeDynamicArray Alloc<T>(Allocator allocator, uint newLength = 0) where T : struct | |||
{ | |||
unsafe | |||
{ | |||
#if DEBUG && !PROFILE_SVELTO | |||
var rtnStruc = new NativeDynamicArray {_hashType = TypeHash<T>.hash}; | |||
#else | |||
NativeDynamicArray rtnStruc = default; | |||
#endif | |||
var sizeOf = MemoryUtilities.SizeOf<T>(); | |||
uint structSize = (uint) MemoryUtilities.SizeOf<UnsafeArray>(); | |||
UnsafeArray* listData = (UnsafeArray*) MemoryUtilities.Alloc(structSize, allocator); | |||
//clear to nullify the pointers | |||
//MemoryUtilities.MemClear((IntPtr) listData, structSize); | |||
rtnStruc._allocator = allocator; | |||
listData->Realloc((uint) (newLength * sizeOf), allocator); | |||
rtnStruc._list = listData; | |||
return rtnStruc; | |||
} | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public ref T Get<T>(uint index) 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 (index >= Count<T>()) | |||
throw new Exception($"NativeDynamicArray: out of bound access, index {index} count {Count<T>()}"); | |||
#endif | |||
return ref _list->Get<T>(index); | |||
} | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public void Set<T>(uint index, in T value) where T : struct | |||
{ | |||
unsafe | |||
{ | |||
#if DEBUG && !PROFILE_SVELTO | |||
if (_list == null) | |||
throw new Exception("NativeDynamicArray: null-access"); | |||
if (_hashType != TypeHash<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>()}"); | |||
#endif | |||
_list->Set(index, value); | |||
} | |||
} | |||
public unsafe void Dispose() | |||
{ | |||
#if DEBUG && !PROFILE_SVELTO | |||
if (_list == null) | |||
throw new Exception("NativeDynamicArray: null-access"); | |||
#endif | |||
_list->Dispose(_allocator); | |||
_list = null; | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public void Add<T>(in T item) 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"); | |||
#endif | |||
var structSize = (uint) MemoryUtilities.SizeOf<T>(); | |||
if (_list->space - (int) structSize < 0) | |||
_list->Realloc((uint) (((uint) ((Count<T>() + 1) * 1.5f) * (float) structSize)), _allocator); | |||
_list->Add(item); | |||
} | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public ref T AddAt<T>(uint index) 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"); | |||
#endif | |||
var structSize = (uint) MemoryUtilities.SizeOf<T>(); | |||
if (index >= Capacity<T>()) | |||
_list->Realloc((uint) (((index + 1) * 1.5f) * structSize), _allocator); | |||
var writeIndex = (index + 1) * structSize; | |||
if (_list->count < writeIndex) | |||
_list->SetCountTo(writeIndex); | |||
return ref _list->Get<T>(index); | |||
} | |||
} | |||
public void Grow<T>(uint newCapacity) 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 (newCapacity <= Capacity<T>()) | |||
throw new Exception("New capacity must be greater than current one"); | |||
#endif | |||
uint structSize = (uint) MemoryUtilities.SizeOf<T>(); | |||
uint size = (uint) (newCapacity * structSize); | |||
_list->Realloc((uint) size, _allocator); | |||
} | |||
} | |||
public void SetCount<T>(uint count) 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"); | |||
#endif | |||
uint structSize = (uint) MemoryUtilities.SizeOf<T>(); | |||
uint size = (uint) (count * structSize); | |||
_list->SetCountTo((uint) size); | |||
} | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public void AddWithoutGrow<T>(in T item) 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"); | |||
var structSize = (uint) MemoryUtilities.SizeOf<T>(); | |||
if (_list->space - (int)structSize < 0) | |||
throw new Exception("NativeDynamicArray: no writing authorized"); | |||
#endif | |||
_list->Add(item); | |||
} | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public void UnorderedRemoveAt<T>(uint index) 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 (Count<T>() == 0) | |||
throw new Exception("NativeDynamicArray: empty array invalid operation"); | |||
#endif | |||
var indexToMove = Count<T>() - 1; | |||
if (index < indexToMove) | |||
{ | |||
Set<T>(index, Get<T>((uint) indexToMove)); | |||
} | |||
_list->Pop<T>(); | |||
} | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public void FastClear() | |||
{ | |||
unsafe | |||
{ | |||
#if DEBUG && !PROFILE_SVELTO | |||
if (_list == null) | |||
throw new Exception("NativeDynamicArray: null-access"); | |||
#endif | |||
_list->Clear(); | |||
} | |||
} | |||
public unsafe T* ToPTR<T>() where T : unmanaged | |||
{ | |||
#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"); | |||
#endif | |||
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"); | |||
#endif | |||
return (IntPtr) _list->ptr; | |||
} | |||
} | |||
public T[] ToManagedArray<T>() where T : unmanaged | |||
{ | |||
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"); | |||
#endif | |||
var count = Count<T>(); | |||
var ret = new T[count]; | |||
var lengthToCopyInBytes = count * MemoryUtilities.SizeOf<T>(); | |||
fixed (void* handle = ret) | |||
{ | |||
Unsafe.CopyBlock(handle, _list->ptr, (uint) lengthToCopyInBytes); | |||
} | |||
return ret; | |||
} | |||
} | |||
public T[] ToManagedArrayUntrimmed<T>() where T : unmanaged | |||
{ | |||
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"); | |||
#endif | |||
var capacity = Capacity<T>(); | |||
var lengthToCopyInBytes = capacity * MemoryUtilities.SizeOf<T>(); | |||
var ret = new T[capacity]; | |||
fixed (void* handle = ret) | |||
{ | |||
Unsafe.CopyBlock(handle, _list->ptr, (uint) lengthToCopyInBytes); | |||
} | |||
return ret; | |||
} | |||
} | |||
public void RemoveAt<T>(uint index) 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"); | |||
#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>(); | |||
} | |||
} | |||
public void MemClear() | |||
{ | |||
unsafe | |||
{ | |||
MemoryUtilities.MemClear((IntPtr) _list->ptr, (uint) _list->capacity); | |||
} | |||
} | |||
#if UNITY_NATIVE | |||
[global::Unity.Burst.NoAlias] [global::Unity.Collections.LowLevel.Unsafe.NativeDisableUnsafePtrRestriction] | |||
#endif | |||
unsafe UnsafeArray* _list; | |||
#if DEBUG && !PROFILE_SVELTO | |||
int _hashType; | |||
#endif | |||
Allocator _allocator; | |||
} | |||
} |
@@ -0,0 +1,11 @@ | |||
fileFormatVersion: 2 | |||
guid: 711d4a28132031fca4870da7c8dae575 | |||
MonoImporter: | |||
externalObjects: {} | |||
serializedVersion: 2 | |||
defaultReferences: [] | |||
executionOrder: 0 | |||
icon: {instanceID: 0} | |||
userData: | |||
assetBundleName: | |||
assetBundleVariant: |
@@ -0,0 +1,48 @@ | |||
using System.Runtime.CompilerServices; | |||
namespace Svelto.ECS.DataStructures | |||
{ | |||
public struct NativeDynamicArrayCast<T> where T : struct | |||
{ | |||
public NativeDynamicArrayCast(NativeDynamicArray array) : this() { _array = array; } | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public int Count() => _array.Count<T>(); | |||
public int count => _array.Count<T>(); | |||
public ref T this[int index] | |||
{ | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
get => ref _array.Get<T>((uint) index); | |||
} | |||
public ref T this[uint index] | |||
{ | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
get => ref _array.Get<T>(index); | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public void Add(in T id) { _array.Add(id); } | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public void UnorderedRemoveAt(uint index) { _array.UnorderedRemoveAt<T>(index); } | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public void RemoveAt(uint index) { _array.RemoveAt<T>(index); } | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public void Clear() { _array.FastClear(); } | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public void Dispose() { _array.Dispose(); } | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public ref T AddAt(uint lastIndex) { return ref _array.AddAt<T>(lastIndex); } | |||
public bool isValid => _array.isValid; | |||
NativeDynamicArray _array; | |||
} | |||
} |
@@ -0,0 +1,11 @@ | |||
fileFormatVersion: 2 | |||
guid: 60ca1e40bef23e8db37576d0f3dbc631 | |||
MonoImporter: | |||
externalObjects: {} | |||
serializedVersion: 2 | |||
defaultReferences: [] | |||
executionOrder: 0 | |||
icon: {instanceID: 0} | |||
userData: | |||
assetBundleName: | |||
assetBundleVariant: |
@@ -0,0 +1,23 @@ | |||
#if UNITY_NATIVE | |||
using Unity.Collections; | |||
using Unity.Collections.LowLevel.Unsafe; | |||
namespace Svelto.ECS.DataStructures | |||
{ | |||
public static class NativeDynamicArrayUnityExtension | |||
{ | |||
public static NativeArray<T> ToNativeArray<T>(this NativeDynamicArray array) where T : struct | |||
{ | |||
unsafe | |||
{ | |||
var nativeArray = NativeArrayUnsafeUtility.ConvertExistingDataToNativeArray<T>( | |||
(void*) array.ToIntPTR<T>(), (int) array.Count<T>(), Allocator.None); | |||
#if ENABLE_UNITY_COLLECTIONS_CHECKS | |||
NativeArrayUnsafeUtility.SetAtomicSafetyHandle(ref nativeArray, AtomicSafetyHandle.Create()); | |||
#endif | |||
return nativeArray; | |||
} | |||
} | |||
} | |||
} | |||
#endif |
@@ -0,0 +1,11 @@ | |||
fileFormatVersion: 2 | |||
guid: 668917ff718433479e84c1f270e88377 | |||
MonoImporter: | |||
externalObjects: {} | |||
serializedVersion: 2 | |||
defaultReferences: [] | |||
executionOrder: 0 | |||
icon: {instanceID: 0} | |||
userData: | |||
assetBundleName: | |||
assetBundleVariant: |
@@ -0,0 +1,115 @@ | |||
using System; | |||
using System.Runtime.InteropServices; | |||
using System.Threading; | |||
using Svelto.Common; | |||
namespace Svelto.ECS.DataStructures | |||
{ | |||
public struct SharedNativeInt: IDisposable | |||
{ | |||
#if UNITY_NATIVE | |||
[global::Unity.Collections.LowLevel.Unsafe.NativeDisableUnsafePtrRestriction] | |||
#endif | |||
unsafe int* data; | |||
Allocator _allocator; | |||
public SharedNativeInt(Allocator allocator) | |||
{ | |||
unsafe | |||
{ | |||
_allocator = allocator; | |||
data = (int*) MemoryUtilities.Alloc(sizeof(int), allocator); | |||
} | |||
} | |||
public static SharedNativeInt Create(int t, Allocator allocator) | |||
{ | |||
unsafe | |||
{ | |||
var current = new SharedNativeInt(); | |||
current._allocator = allocator; | |||
current.data = (int*) MemoryUtilities.Alloc(sizeof(int), allocator); | |||
*current.data = t; | |||
return current; | |||
} | |||
} | |||
public static implicit operator int(SharedNativeInt t) | |||
{ | |||
unsafe | |||
{ | |||
#if DEBUG && !PROFILE_SVELTO | |||
if (t.data == null) | |||
throw new Exception("using disposed SharedInt"); | |||
#endif | |||
return *t.data; | |||
} | |||
} | |||
public void Dispose() | |||
{ | |||
unsafe | |||
{ | |||
if (data != null) | |||
{ | |||
MemoryUtilities.Free((IntPtr) data, _allocator); | |||
data = null; | |||
} | |||
} | |||
} | |||
public int Decrement() | |||
{ | |||
unsafe | |||
{ | |||
#if DEBUG && !PROFILE_SVELTO | |||
if (data == null) | |||
throw new Exception("null-access"); | |||
#endif | |||
return Interlocked.Decrement(ref *data); | |||
} | |||
} | |||
public int Increment() | |||
{ | |||
unsafe | |||
{ | |||
#if DEBUG && !PROFILE_SVELTO | |||
if (data == null) | |||
throw new Exception("null-access"); | |||
#endif | |||
return Interlocked.Increment(ref *data); | |||
} | |||
} | |||
public int Add(int val) | |||
{ | |||
unsafe | |||
{ | |||
#if DEBUG && !PROFILE_SVELTO | |||
if (data == null) | |||
throw new Exception("null-access"); | |||
#endif | |||
return Interlocked.Add(ref *data, val); | |||
} | |||
} | |||
public void Set(int val) | |||
{ | |||
unsafe | |||
{ | |||
#if DEBUG && !PROFILE_SVELTO | |||
if (data == null) | |||
throw new Exception("null-access"); | |||
#endif | |||
Volatile.Write(ref *data, val); | |||
} | |||
} | |||
} | |||
} |
@@ -0,0 +1,11 @@ | |||
fileFormatVersion: 2 | |||
guid: 109ecc8b65fa3a55a287c25fe70ef472 | |||
MonoImporter: | |||
externalObjects: {} | |||
serializedVersion: 2 | |||
defaultReferences: [] | |||
executionOrder: 0 | |||
icon: {instanceID: 0} | |||
userData: | |||
assetBundleName: | |||
assetBundleVariant: |
@@ -0,0 +1,194 @@ | |||
#if later | |||
using System; | |||
using System.Runtime.CompilerServices; | |||
using System.Threading; | |||
using Svelto.Common; | |||
using Svelto.Utilities; | |||
namespace Svelto.ECS.DataStructures | |||
{ | |||
/// <summary> | |||
/// Burst friendly Ring Buffer on steroid: | |||
/// it can: Enqueue/Dequeue, it wraps if there is enough space after dequeuing | |||
/// It resizes if there isn't enough space left. | |||
/// It's a "bag", you can queue and dequeue any T. Just be sure that you dequeue what you queue! No check on type | |||
/// is done. | |||
/// You can reserve a position in the queue to update it later. | |||
/// The datastructure is a struct and it's "copyable" | |||
/// I eventually decided to call it NativeBag and not NativeBag because it can also be used as | |||
/// a preallocated memory pool where any kind of T can be stored as long as T is unmanaged | |||
/// </summary> | |||
public struct ThreadSafeNativeBag : IDisposable | |||
{ | |||
public uint count | |||
{ | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
get | |||
{ | |||
unsafe | |||
{ | |||
#if DEBUG && !PROFILE_SVELTO | |||
if (_queue == null) | |||
throw new Exception("SimpleNativeArray: null-access"); | |||
#endif | |||
return _queue->size; | |||
} | |||
} | |||
} | |||
public uint capacity | |||
{ | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
get | |||
{ | |||
unsafe | |||
{ | |||
#if DEBUG && !PROFILE_SVELTO | |||
if (_queue == null) | |||
throw new Exception("SimpleNativeArray: null-access"); | |||
#endif | |||
return _queue->capacity; | |||
} | |||
} | |||
} | |||
public ThreadSafeNativeBag(Allocator allocator) | |||
{ | |||
unsafe | |||
{ | |||
var sizeOf = MemoryUtilities.SizeOf<UnsafeBlob>(); | |||
var listData = (UnsafeBlob*) MemoryUtilities.Alloc((uint) sizeOf, allocator); | |||
//clear to nullify the pointers | |||
//MemoryUtilities.MemClear((IntPtr) listData, (uint) sizeOf); | |||
listData->allocator = allocator; | |||
_queue = listData; | |||
} | |||
_writingGuard = 0; | |||
} | |||
public ThreadSafeNativeBag(Allocator allocator, uint capacity) | |||
{ | |||
unsafe | |||
{ | |||
var sizeOf = MemoryUtilities.SizeOf<UnsafeBlob>(); | |||
var listData = (UnsafeBlob*) MemoryUtilities.Alloc((uint) sizeOf, allocator); | |||
//clear to nullify the pointers | |||
//MemoryUtilities.MemClear((IntPtr) listData, (uint) sizeOf); | |||
listData->allocator = allocator; | |||
_queue = listData; | |||
_queue->Realloc(capacity); | |||
} | |||
_writingGuard = 0; | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public bool IsEmpty() | |||
{ | |||
unsafe | |||
{ | |||
if (_queue == null || _queue->ptr == null) | |||
return true; | |||
} | |||
return count == 0; | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public unsafe void Dispose() | |||
{ | |||
if (_queue != null) | |||
{ | |||
_queue->Dispose(); | |||
MemoryUtilities.Free((IntPtr) _queue, _queue->allocator); | |||
_queue = null; | |||
} | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public void Enqueue<T>(in T item) where T : struct | |||
{ | |||
unsafe | |||
{ | |||
#if DEBUG && !PROFILE_SVELTO | |||
if (_queue == null) | |||
throw new Exception("SimpleNativeArray: null-access"); | |||
#endif | |||
var sizeOf = MemoryUtilities.SizeOf<T>(); | |||
var alignedSize = (uint) MemoryUtilities.SizeOfAligned<T>(); | |||
Interlocked.MemoryBarrier(); | |||
Reset: | |||
var oldCapacity = _queue->capacity; | |||
var spaceleft = oldCapacity - (_queue->_writeIndex - _queue->_readIndex) - sizeOf; | |||
while (spaceleft < 0) | |||
{ | |||
//if _writingGuard is not equal to 0, it means that another thread increased the | |||
//value so it's possible the reallocing is already happening OR it means that | |||
//writing are still in progress and we must be sure that are all flushed first | |||
if (Interlocked.CompareExchange(ref _writingGuard, 1, 0) != 0) | |||
{ | |||
ThreadUtility.Yield(); | |||
goto Reset; | |||
} | |||
var newCapacity = (uint) ((oldCapacity + alignedSize) * 2.0f); | |||
Svelto.Console.Log($"realloc {newCapacity}"); | |||
_queue->Realloc(newCapacity); | |||
Volatile.Write(ref _writingGuard, 0); | |||
} | |||
int writeIndex; | |||
//look for the first available slot to write in | |||
writeIndex = _queue->_writeIndex; | |||
if (Interlocked.CompareExchange(ref _queue->_writeIndex, (int) (writeIndex + alignedSize) | |||
, writeIndex) != writeIndex) | |||
{ | |||
ThreadUtility.Yield(); | |||
goto Reset; | |||
} | |||
Interlocked.Increment(ref _writingGuard); | |||
_queue->Write(item, (uint) writeIndex); | |||
Interlocked.Decrement(ref _writingGuard); | |||
} | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public void Clear() | |||
{ | |||
unsafe | |||
{ | |||
#if DEBUG && !PROFILE_SVELTO | |||
if (_queue == null) | |||
throw new Exception("SimpleNativeArray: null-access"); | |||
#endif | |||
_queue->Clear(); | |||
} | |||
} | |||
public T Dequeue<T>() where T : struct | |||
{ | |||
unsafe | |||
{ | |||
return _queue->Read<T>(); | |||
} | |||
} | |||
#if UNITY_NATIVE | |||
[global::Unity.Collections.LowLevel.Unsafe.NativeDisableUnsafePtrRestriction] | |||
#endif | |||
unsafe UnsafeBlob* _queue; | |||
int _writingGuard; | |||
} | |||
} | |||
#endif |
@@ -0,0 +1,11 @@ | |||
fileFormatVersion: 2 | |||
guid: ecdfbc4967aa30eebd81d08e327f9857 | |||
MonoImporter: | |||
externalObjects: {} | |||
serializedVersion: 2 | |||
defaultReferences: [] | |||
executionOrder: 0 | |||
icon: {instanceID: 0} | |||
userData: | |||
assetBundleName: | |||
assetBundleVariant: |
@@ -0,0 +1,138 @@ | |||
using System; | |||
using System.Runtime.CompilerServices; | |||
using Svelto.Common; | |||
namespace Svelto.ECS.DataStructures | |||
{ | |||
struct UnsafeArray | |||
{ | |||
internal unsafe byte* ptr => _ptr; | |||
//expressed in bytes | |||
internal int capacity => (int) _capacity; | |||
//expressed in bytes | |||
internal int count => (int) _writeIndex; | |||
//expressed in bytes | |||
internal int space => capacity - count; | |||
#if DEBUG && !PROFILE_SVELTO | |||
#pragma warning disable 649 | |||
internal uint id; | |||
#pragma warning restore 649 | |||
#endif | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public ref T Get<T>(uint index) where T : struct | |||
{ | |||
unsafe | |||
{ | |||
#if DEBUG && !PROFILE_SVELTO | |||
uint sizeOf = (uint) MemoryUtilities.SizeOf<T>(); | |||
if (index + sizeOf > _writeIndex) | |||
throw new Exception("no reading authorized"); | |||
#endif | |||
return ref Unsafe.AsRef<T>(Unsafe.Add<T>(ptr, (int) index)); | |||
} | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public void Set<T>(uint index, in T value) where T : struct | |||
{ | |||
unsafe | |||
{ | |||
uint sizeOf = (uint) MemoryUtilities.SizeOf<T>(); | |||
uint writeIndex = (uint) (index * sizeOf); | |||
#if DEBUG && !PROFILE_SVELTO | |||
if (_capacity < writeIndex + sizeOf) | |||
throw new Exception("no writing authorized"); | |||
#endif | |||
Unsafe.AsRef<T>(Unsafe.Add<T>(_ptr, (int) index)) = value; | |||
if (_writeIndex < writeIndex + sizeOf) | |||
_writeIndex = (uint) (writeIndex + sizeOf); | |||
} | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public void Add<T>(in T value) where T : struct | |||
{ | |||
unsafe | |||
{ | |||
var structSize = MemoryUtilities.SizeOf<T>(); | |||
#if DEBUG && !PROFILE_SVELTO | |||
if (space - structSize < 0) | |||
throw new Exception("no writing authorized"); | |||
#endif | |||
Unsafe.Write(ptr + _writeIndex, value); | |||
_writeIndex += (uint)structSize; | |||
} | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public ref T Pop<T>() where T : struct | |||
{ | |||
unsafe | |||
{ | |||
var structSize = MemoryUtilities.SizeOf<T>(); | |||
_writeIndex -= (uint)structSize; | |||
return ref Unsafe.AsRef<T>(ptr + _writeIndex); | |||
} | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
internal void Realloc(uint newCapacity, Allocator allocator) | |||
{ | |||
unsafe | |||
{ | |||
if (_ptr == null) | |||
_ptr = (byte*) MemoryUtilities.Alloc(newCapacity, allocator); | |||
else | |||
_ptr = (byte*) MemoryUtilities.Realloc((IntPtr) _ptr, (uint) count, newCapacity, allocator); | |||
_capacity = newCapacity; | |||
} | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public void Dispose(Allocator allocator) | |||
{ | |||
unsafe | |||
{ | |||
if (ptr != null) | |||
MemoryUtilities.Free((IntPtr) ptr, allocator); | |||
_ptr = null; | |||
_writeIndex = 0; | |||
_capacity = 0; | |||
} | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public void Clear() | |||
{ | |||
_writeIndex = 0; | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public void SetCountTo(uint count) | |||
{ | |||
_writeIndex = count; | |||
} | |||
#if UNITY_NATIVE | |||
[global::Unity.Collections.LowLevel.Unsafe.NativeDisableUnsafePtrRestriction] | |||
#endif | |||
unsafe byte* _ptr; | |||
uint _writeIndex; | |||
uint _capacity; | |||
} | |||
} |
@@ -0,0 +1,11 @@ | |||
fileFormatVersion: 2 | |||
guid: 0acd30efabd337da835fcfda3b966aee | |||
MonoImporter: | |||
externalObjects: {} | |||
serializedVersion: 2 | |||
defaultReferences: [] | |||
executionOrder: 0 | |||
icon: {instanceID: 0} | |||
userData: | |||
assetBundleName: | |||
assetBundleVariant: |
@@ -0,0 +1,302 @@ | |||
using System; | |||
using System.Runtime.CompilerServices; | |||
using Svelto.Common; | |||
namespace Svelto.ECS.DataStructures | |||
{ | |||
//ToDO to complete in future version of svelto, maybe removed | |||
public struct UnsafeArrayIndex | |||
{ | |||
internal uint index; | |||
internal uint capacity; | |||
} | |||
/// <summary> | |||
/// Note: this must work inside burst, so it must follow burst restrictions | |||
/// Note: All the svelto native structures | |||
/// </summary> | |||
struct UnsafeBlob : IDisposable | |||
{ | |||
internal unsafe byte* ptr { get; set; } | |||
//expressed in bytes | |||
internal uint capacity { get; private set; } | |||
//expressed in bytes | |||
internal uint size => (uint)_writeIndex - _readIndex; | |||
//expressed in bytes | |||
internal uint space => capacity - size; | |||
/// <summary> | |||
/// </summary> | |||
internal Allocator allocator; | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
internal void Write<T>(in T item) where T : struct | |||
{ | |||
unsafe | |||
{ | |||
var structSize = (uint) MemoryUtilities.SizeOf<T>(); | |||
//the idea is, considering the wrap, a read pointer must always be behind a writer pointer | |||
#if DEBUG && !PROFILE_SVELTO | |||
if (space - (int) structSize < 0) | |||
throw new Exception("no writing authorized"); | |||
#endif | |||
var writeHead = _writeIndex % capacity; | |||
if (writeHead + structSize <= capacity) | |||
{ | |||
Unsafe.Write(ptr + writeHead, item); | |||
} | |||
else | |||
//copy with wrap, will start to copy and wrap for the reminder | |||
{ | |||
var byteCountToEnd = capacity - writeHead; | |||
var localCopyToAvoidGcIssues = item; | |||
//read and copy the first portion of Item until the end of the stream | |||
Unsafe.CopyBlock(ptr + writeHead, Unsafe.AsPointer(ref localCopyToAvoidGcIssues), (uint)byteCountToEnd); | |||
var restCount = structSize - byteCountToEnd; | |||
//read and copy the remainder | |||
Unsafe.CopyBlock(ptr, (byte*) Unsafe.AsPointer(ref localCopyToAvoidGcIssues) + byteCountToEnd | |||
, (uint)restCount); | |||
} | |||
//this is may seems a waste if you are going to use an unsafeBlob just for bytes, but it's necessary for mixed types. | |||
//it's still possible to use WriteUnaligned though | |||
int paddedStructSize = (int) MemoryUtilities.Align4(structSize); | |||
_writeIndex += paddedStructSize; | |||
} | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
internal void Write<T>(in T item, uint writeIndex) where T : struct | |||
{ | |||
unsafe | |||
{ | |||
var structSize = (uint) MemoryUtilities.SizeOf<T>(); | |||
//the idea is, considering the wrap, a read pointer must always be behind a writer pointer | |||
var writeHead = writeIndex % capacity; | |||
if (writeHead + structSize <= capacity) | |||
{ | |||
Unsafe.Write(ptr + writeHead, item); | |||
} | |||
else //copy with wrap, will start to copy and wrap for the reminder | |||
{ | |||
var byteCountToEnd = capacity - writeHead; | |||
var localCopyToAvoidGcIssues = item; | |||
//read and copy the first portion of Item until the end of the stream | |||
Unsafe.CopyBlock(ptr + writeHead, Unsafe.AsPointer(ref localCopyToAvoidGcIssues), byteCountToEnd); | |||
var restCount = structSize - byteCountToEnd; | |||
//read and copy the remainder | |||
Unsafe.CopyBlock(ptr, (byte*) Unsafe.AsPointer(ref localCopyToAvoidGcIssues) + byteCountToEnd | |||
, restCount); | |||
} | |||
} | |||
} | |||
// [MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
// //ToDo: remove this and create an UnsafeBlobUnaligned, used on NativeRingBuffer where T cannot change | |||
// internal void WriteUnaligned<T>(in T item) where T : struct | |||
// { | |||
// unsafe | |||
// { | |||
// var structSize = (uint) MemoryUtilities.SizeOf<T>(); | |||
// | |||
// //the idea is, considering the wrap, a read pointer must always be behind a writer pointer | |||
// #if DEBUG && !PROFILE_SVELTO | |||
// if (space - (int) structSize < 0) | |||
// throw new Exception("no writing authorized"); | |||
// #endif | |||
// var pointer = _writeIndex % capacity; | |||
// | |||
// if (pointer + structSize <= capacity) | |||
// { | |||
// Unsafe.Write(ptr + pointer, item); | |||
// } | |||
// else | |||
// { | |||
// var byteCount = capacity - pointer; | |||
// | |||
// var localCopyToAvoidGCIssues = item; | |||
// | |||
// Unsafe.CopyBlockUnaligned(ptr + pointer, Unsafe.AsPointer(ref localCopyToAvoidGCIssues), byteCount); | |||
// | |||
// var restCount = structSize - byteCount; | |||
// Unsafe.CopyBlockUnaligned(ptr, (byte*) Unsafe.AsPointer(ref localCopyToAvoidGCIssues) + byteCount | |||
// , restCount); | |||
// } | |||
// | |||
// _writeIndex += structSize; | |||
// } | |||
// } | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
internal T Read<T>() where T : struct | |||
{ | |||
unsafe | |||
{ | |||
var structSize = (uint) MemoryUtilities.SizeOf<T>(); | |||
#if DEBUG && !PROFILE_SVELTO | |||
if (size < structSize) //are there enough bytes to read? | |||
throw new Exception("dequeuing empty queue or unexpected type dequeued"); | |||
if (_readIndex > _writeIndex) | |||
throw new Exception("unexpected read"); | |||
#endif | |||
var head = _readIndex % capacity; | |||
var paddedStructSize = MemoryUtilities.Align4(structSize); | |||
_readIndex += paddedStructSize; | |||
if (_readIndex == _writeIndex) | |||
{ | |||
//resetting the Indices has the benefit to let the Reserve work in more occasions and | |||
//the rapping happening less often. If the _readIndex reached the _writeIndex, it means | |||
//that there is no data left to read, so we can start to write again from the begin of the memory | |||
_writeIndex = 0; | |||
_readIndex = 0; | |||
} | |||
if (head + paddedStructSize <= capacity) | |||
return Unsafe.Read<T>(ptr + head); | |||
T item = default; | |||
var byteCountToEnd = capacity - head; | |||
Unsafe.CopyBlock(Unsafe.AsPointer(ref item), ptr + head, byteCountToEnd); | |||
var restCount = structSize - byteCountToEnd; | |||
Unsafe.CopyBlock((byte*) Unsafe.AsPointer(ref item) + byteCountToEnd, ptr, restCount); | |||
return item; | |||
} | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
internal ref T Reserve<T>(out UnsafeArrayIndex index) where T : struct | |||
{ | |||
unsafe | |||
{ | |||
var sizeOf = (uint) MemoryUtilities.SizeOf<T>(); | |||
ref var buffer = ref Unsafe.AsRef<T>(ptr + _writeIndex); | |||
#if DEBUG && !PROFILE_SVELTO | |||
if (_writeIndex > capacity) | |||
throw new Exception( | |||
$"can't reserve if the writeIndex wrapped around the capacity, writeIndex {_writeIndex} capacity {capacity}"); | |||
if (_writeIndex + sizeOf > capacity) | |||
throw new Exception("out of bound reserving"); | |||
#endif | |||
index = new UnsafeArrayIndex | |||
{ | |||
capacity = capacity | |||
, index = (uint)_writeIndex | |||
}; | |||
int align4 = (int) MemoryUtilities.Align4(sizeOf); | |||
_writeIndex += align4; | |||
return ref buffer; | |||
} | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
internal ref T AccessReserved<T>(UnsafeArrayIndex index) where T : struct | |||
{ | |||
unsafe | |||
{ | |||
#if DEBUG && !PROFILE_SVELTO | |||
var size = MemoryUtilities.SizeOf<T>(); | |||
if (index.index + size > capacity) | |||
throw new Exception($"out of bound access, index {index.index} size {size} capacity {capacity}"); | |||
#endif | |||
return ref Unsafe.AsRef<T>(ptr + index.index); | |||
} | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
internal void Realloc(uint newCapacity) | |||
{ | |||
unsafe | |||
{ | |||
//be sure it's multiple of 4. Assuming that what we write is aligned to 4, then we will always have aligned wrapped heads | |||
newCapacity = MemoryUtilities.Align4(newCapacity); | |||
byte* newPointer = null; | |||
#if DEBUG && !PROFILE_SVELTO | |||
if (newCapacity <= capacity) | |||
throw new Exception("new capacity must be bigger than current"); | |||
#endif | |||
if (newCapacity > 0) | |||
{ | |||
newPointer = (byte*) MemoryUtilities.Alloc(newCapacity, allocator); | |||
if (size > 0) | |||
{ | |||
var readerHead = _readIndex % capacity; | |||
var writerHead = _writeIndex % capacity; | |||
if (readerHead < writerHead) | |||
{ | |||
//copy to the new pointer, from th reader position | |||
var currentSize = _writeIndex - _readIndex; | |||
Unsafe.CopyBlock(newPointer, ptr + readerHead, (uint)currentSize); | |||
} | |||
//the assumption is that if size > 0 (so readerPointer and writerPointer are not the same) | |||
//writerHead wrapped and reached readerHead. so I have to copy from readerHead to the end | |||
//and from the start to writerHead (which is the same position of readerHead) | |||
else | |||
{ | |||
var byteCountToEnd = capacity - readerHead; | |||
Unsafe.CopyBlock(newPointer, ptr + readerHead, byteCountToEnd); | |||
Unsafe.CopyBlock(newPointer + byteCountToEnd, ptr, (uint)writerHead); | |||
} | |||
} | |||
} | |||
if (ptr != null) | |||
MemoryUtilities.Free((IntPtr) ptr, allocator); | |||
ptr = newPointer; | |||
capacity = newCapacity; | |||
_readIndex = 0; | |||
_writeIndex = (int)size; | |||
} | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public void Dispose() | |||
{ | |||
unsafe | |||
{ | |||
if (ptr != null) | |||
MemoryUtilities.Free((IntPtr) ptr, allocator); | |||
ptr = null; | |||
_writeIndex = 0; | |||
capacity = 0; | |||
} | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public void Clear() | |||
{ | |||
_writeIndex = 0; | |||
_readIndex = 0; | |||
} | |||
internal int _writeIndex; | |||
internal uint _readIndex; | |||
} | |||
} |
@@ -0,0 +1,11 @@ | |||
fileFormatVersion: 2 | |||
guid: 62fedd45acd134729d5be903fc2d5b26 | |||
MonoImporter: | |||
externalObjects: {} | |||
serializedVersion: 2 | |||
defaultReferences: [] | |||
executionOrder: 0 | |||
icon: {instanceID: 0} | |||
userData: | |||
assetBundleName: | |||
assetBundleVariant: |
@@ -0,0 +1,8 @@ | |||
fileFormatVersion: 2 | |||
guid: d2793f2ae73e357f9773b68721bbe468 | |||
folderAsset: yes | |||
DefaultImporter: | |||
externalObjects: {} | |||
userData: | |||
assetBundleName: | |||
assetBundleVariant: |
@@ -0,0 +1,70 @@ | |||
using Svelto.ECS; | |||
#if DEBUG | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Reflection; | |||
public static class ExclusiveGroupDebugger | |||
{ | |||
static ExclusiveGroupDebugger() | |||
{ | |||
Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); | |||
foreach (Assembly assembly in assemblies) | |||
{ | |||
Type[] types = assembly.GetTypes(); | |||
foreach (Type type in types) | |||
{ | |||
if (type != null && type.IsClass && type.IsSealed && type.IsAbstract) //this means only static classes | |||
{ | |||
var fields = type.GetFields(); | |||
foreach (var field in fields) | |||
{ | |||
if (field.IsStatic && typeof(ExclusiveGroup).IsAssignableFrom(field.FieldType)) | |||
{ | |||
var group = (ExclusiveGroup) field.GetValue(null); | |||
string name = $"{type.FullName}.{field.Name} ({(uint)group})"; | |||
GroupMap.idToName[(ExclusiveGroupStruct) group] = name; | |||
} | |||
if (field.IsStatic && typeof(ExclusiveGroupStruct).IsAssignableFrom(field.FieldType)) | |||
{ | |||
var group = (ExclusiveGroupStruct) field.GetValue(null); | |||
string name = $"{type.FullName}.{field.Name} ({(uint)group})"; | |||
GroupMap.idToName[@group] = name; | |||
} | |||
} | |||
} | |||
} | |||
} | |||
} | |||
public static string ToName(this in ExclusiveGroupStruct group) | |||
{ | |||
if (GroupMap.idToName.TryGetValue(group, out var name) == false) | |||
name = $"<undefined:{((uint)group).ToString()}>"; | |||
return name; | |||
} | |||
} | |||
public static class GroupMap | |||
{ | |||
static GroupMap() | |||
{ | |||
GroupMap.idToName = new Dictionary<uint, string>(); | |||
} | |||
internal static readonly Dictionary<uint, string> idToName; | |||
} | |||
#else | |||
public static class ExclusiveGroupDebugger | |||
{ | |||
public static string ToName(this in ExclusiveGroupStruct group) | |||
{ | |||
return ((uint)group).ToString(); | |||
} | |||
} | |||
#endif |
@@ -0,0 +1,11 @@ | |||
fileFormatVersion: 2 | |||
guid: fd73f27c31073381b9b694f3f32941f3 | |||
MonoImporter: | |||
externalObjects: {} | |||
serializedVersion: 2 | |||
defaultReferences: [] | |||
executionOrder: 0 | |||
icon: {instanceID: 0} | |||
userData: | |||
assetBundleName: | |||
assetBundleVariant: |
@@ -4,8 +4,10 @@ namespace Svelto.ECS | |||
{ | |||
public class DispatchOnChange<T> : DispatchOnSet<T> where T:IEquatable<T> | |||
{ | |||
public DispatchOnChange(EGID senderID) : base(senderID) | |||
{ } | |||
public DispatchOnChange(EGID senderID, T initialValue = default(T)) : base(senderID) | |||
{ | |||
_value = initialValue; | |||
} | |||
public new T value | |||
{ | |||
@@ -16,18 +16,23 @@ namespace Svelto.ECS | |||
_value = value; | |||
if (_paused == false) | |||
_subscribers(_senderID, value); | |||
_subscriber(_senderID, value); | |||
} | |||
} | |||
public void NotifyOnValueSet(Action<EGID, T> action) | |||
{ | |||
_subscribers += action; | |||
#if DEBUG && !PROFILE_SVELTO | |||
DBC.ECS.Check.Require(_subscriber == null, $"{this.GetType().Name}: listener already registered"); | |||
#endif | |||
_subscriber = action; | |||
_paused = false; | |||
} | |||
public void StopNotify(Action<EGID, T> action) | |||
public void StopNotify() | |||
{ | |||
_subscribers -= action; | |||
_subscriber = null; | |||
_paused = true; | |||
} | |||
public void PauseNotify() { _paused = true; } | |||
@@ -36,7 +41,7 @@ namespace Svelto.ECS | |||
protected T _value; | |||
readonly EGID _senderID; | |||
Action<EGID, T> _subscribers; | |||
Action<EGID, T> _subscriber; | |||
bool _paused; | |||
} | |||
} |
@@ -4,66 +4,66 @@ using Svelto.DataStructures; | |||
namespace Svelto.ECS | |||
{ | |||
/// <summary> | |||
/// DynamicEntityDescriptor can be used to add entity views to an existing EntityDescriptor that act as flags, | |||
/// DynamicEntityDescriptor can be used to add entity components to an existing EntityDescriptor that act as flags, | |||
/// at building time. | |||
/// This method allocates, so it shouldn't be abused | |||
/// </summary> | |||
/// <typeparam name="TType"></typeparam> | |||
public struct DynamicEntityDescriptor<TType> : IEntityDescriptor where TType : IEntityDescriptor, new() | |||
public struct DynamicEntityDescriptor<TType> : IDynamicEntityDescriptor where TType : IEntityDescriptor, new() | |||
{ | |||
internal DynamicEntityDescriptor(bool isExtendible) : this() | |||
{ | |||
var defaultEntities = EntityDescriptorTemplate<TType>.descriptor.entitiesToBuild; | |||
var defaultEntities = EntityDescriptorTemplate<TType>.descriptor.componentsToBuild; | |||
var length = defaultEntities.Length; | |||
_entitiesToBuild = new IEntityBuilder[length + 1]; | |||
ComponentsToBuild = new IComponentBuilder[length + 1]; | |||
Array.Copy(defaultEntities, 0, _entitiesToBuild, 0, length); | |||
Array.Copy(defaultEntities, 0, ComponentsToBuild, 0, length); | |||
//assign it after otherwise the previous copy will overwrite the value in case the item | |||
//is already present | |||
_entitiesToBuild[length] = new EntityBuilder<EntityStructInfoView> | |||
ComponentsToBuild[length] = new ComponentBuilder<EntityInfoComponent> | |||
( | |||
new EntityStructInfoView | |||
new EntityInfoComponent | |||
{ | |||
entitiesToBuild = _entitiesToBuild | |||
componentsToBuild = ComponentsToBuild | |||
} | |||
); | |||
} | |||
public DynamicEntityDescriptor(IEntityBuilder[] extraEntityBuilders) : this() | |||
public DynamicEntityDescriptor(IComponentBuilder[] extraEntityBuilders) : this() | |||
{ | |||
var extraEntitiesLength = extraEntityBuilders.Length; | |||
_entitiesToBuild = Construct(extraEntitiesLength, extraEntityBuilders, | |||
EntityDescriptorTemplate<TType>.descriptor.entitiesToBuild); | |||
ComponentsToBuild = Construct(extraEntitiesLength, extraEntityBuilders, | |||
EntityDescriptorTemplate<TType>.descriptor.componentsToBuild); | |||
} | |||
public DynamicEntityDescriptor(FasterList<IEntityBuilder> extraEntityBuilders) : this() | |||
public DynamicEntityDescriptor(FasterList<IComponentBuilder> extraEntityBuilders) : this() | |||
{ | |||
var extraEntities = extraEntityBuilders.ToArrayFast(); | |||
var extraEntitiesLength = extraEntityBuilders.Count; | |||
var extraEntities = extraEntityBuilders.ToArrayFast(out _); | |||
var extraEntitiesLength = extraEntityBuilders.count; | |||
_entitiesToBuild = Construct(extraEntitiesLength, extraEntities, | |||
EntityDescriptorTemplate<TType>.descriptor.entitiesToBuild); | |||
ComponentsToBuild = Construct((int) extraEntitiesLength, extraEntities, | |||
EntityDescriptorTemplate<TType>.descriptor.componentsToBuild); | |||
} | |||
public void ExtendWith<T>() where T : IEntityDescriptor, new() | |||
{ | |||
var newEntitiesToBuild = EntityDescriptorTemplate<T>.descriptor.entitiesToBuild; | |||
var newEntitiesToBuild = EntityDescriptorTemplate<T>.descriptor.componentsToBuild; | |||
_entitiesToBuild = Construct(newEntitiesToBuild.Length, newEntitiesToBuild, _entitiesToBuild); | |||
ComponentsToBuild = Construct(newEntitiesToBuild.Length, newEntitiesToBuild, ComponentsToBuild); | |||
} | |||
public void ExtendWith(IEntityBuilder[] extraEntities) | |||
public void ExtendWith(IComponentBuilder[] extraEntities) | |||
{ | |||
_entitiesToBuild = Construct(extraEntities.Length, extraEntities, _entitiesToBuild); | |||
ComponentsToBuild = Construct(extraEntities.Length, extraEntities, ComponentsToBuild); | |||
} | |||
static IEntityBuilder[] Construct(int extraEntitiesLength, IEntityBuilder[] extraEntities, | |||
IEntityBuilder[] startingEntities) | |||
static IComponentBuilder[] Construct(int extraEntitiesLength, IComponentBuilder[] extraEntities, | |||
IComponentBuilder[] startingEntities) | |||
{ | |||
IEntityBuilder[] localEntitiesToBuild; | |||
IComponentBuilder[] localEntitiesToBuild; | |||
if (extraEntitiesLength == 0) | |||
{ | |||
@@ -72,26 +72,25 @@ namespace Svelto.ECS | |||
} | |||
var defaultEntities = startingEntities; | |||
var length = defaultEntities.Length; | |||
var index = SetupSpecialEntityStruct(defaultEntities, out localEntitiesToBuild, extraEntitiesLength); | |||
var index = SetupEntityInfoComponent(defaultEntities, out localEntitiesToBuild, extraEntitiesLength); | |||
Array.Copy(extraEntities, 0, localEntitiesToBuild, length, extraEntitiesLength); | |||
Array.Copy(extraEntities, 0, localEntitiesToBuild, defaultEntities.Length, extraEntitiesLength); | |||
//assign it after otherwise the previous copy will overwrite the value in case the item | |||
//is already present | |||
localEntitiesToBuild[index] = new EntityBuilder<EntityStructInfoView> | |||
localEntitiesToBuild[index] = new ComponentBuilder<EntityInfoComponent> | |||
( | |||
new EntityStructInfoView | |||
new EntityInfoComponent | |||
{ | |||
entitiesToBuild = localEntitiesToBuild | |||
componentsToBuild = localEntitiesToBuild | |||
} | |||
); | |||
return localEntitiesToBuild; | |||
} | |||
static int SetupSpecialEntityStruct(IEntityBuilder[] defaultEntities, out IEntityBuilder[] entitiesToBuild, | |||
static int SetupEntityInfoComponent(IComponentBuilder[] defaultEntities, out IComponentBuilder[] componentsToBuild, | |||
int extraLenght) | |||
{ | |||
int length = defaultEntities.Length; | |||
@@ -100,7 +99,7 @@ namespace Svelto.ECS | |||
for (var i = 0; i < length; i++) | |||
{ | |||
//the special entity already exists | |||
if (defaultEntities[i].GetEntityType() == EntityBuilderUtilities.ENTITY_STRUCT_INFO_VIEW) | |||
if (defaultEntities[i].GetEntityComponentType() == ComponentBuilderUtilities.ENTITY_INFO_COMPONENT) | |||
{ | |||
index = i; | |||
break; | |||
@@ -110,19 +109,18 @@ namespace Svelto.ECS | |||
if (index == -1) | |||
{ | |||
index = length + extraLenght; | |||
entitiesToBuild = new IEntityBuilder[index + 1]; | |||
componentsToBuild = new IComponentBuilder[index + 1]; | |||
} | |||
else | |||
entitiesToBuild = new IEntityBuilder[length + extraLenght]; | |||
componentsToBuild = new IComponentBuilder[length + extraLenght]; | |||
Array.Copy(defaultEntities, 0, entitiesToBuild, 0, length); | |||
Array.Copy(defaultEntities, 0, componentsToBuild, 0, length); | |||
return index; | |||
} | |||
public IComponentBuilder[] componentsToBuild => ComponentsToBuild; | |||
public IEntityBuilder[] entitiesToBuild => _entitiesToBuild; | |||
IEntityBuilder[] _entitiesToBuild; | |||
IComponentBuilder[] ComponentsToBuild; | |||
} | |||
} |
@@ -9,5 +9,15 @@ namespace Svelto.ECS | |||
public ECSException(string message, Exception innerE):base("<color=red>".FastConcat(message, "</color>"), innerE) | |||
{} | |||
public ECSException(string message, Type entityComponentType, Type type) : | |||
base(message.FastConcat(" entity view: '", entityComponentType.Name, "', field: '", type.Name)) | |||
{ | |||
} | |||
public ECSException(string message, Type entityComponentType) : | |||
base(message.FastConcat(" entity view: ", entityComponentType.Name)) | |||
{ | |||
} | |||
} | |||
} |
@@ -9,6 +9,10 @@ namespace Svelto.ECS.Experimental | |||
public static implicit operator T(ECSResources<T> ecsString) { return ResourcesECSDB<T>.FromECS(ecsString.id); } | |||
} | |||
/// <summary> | |||
/// To do. Or we reuse the ID or we need to clear this | |||
/// </summary> | |||
/// <typeparam name="T"></typeparam> | |||
static class ResourcesECSDB<T> | |||
{ | |||
static readonly FasterList<T> _resources = new FasterList<T>(); | |||
@@ -22,12 +26,12 @@ namespace Svelto.ECS.Experimental | |||
{ | |||
_resources.Add(resource); | |||
return (uint)_resources.Count; | |||
return (uint)_resources.count; | |||
} | |||
public static T FromECS(uint id) | |||
{ | |||
if (id - 1 < _resources.Count) | |||
if (id - 1 < _resources.count) | |||
return _resources[(int) id - 1]; | |||
return default; | |||
@@ -1,38 +1,96 @@ | |||
using System; | |||
using System.Runtime.InteropServices; | |||
namespace Svelto.ECS.Experimental | |||
{ | |||
[Serialization.DoNotSerialize] | |||
[StructLayout(LayoutKind.Explicit)] | |||
/// | |||
/// Note: I should extend this to reuse unused id | |||
/// | |||
//todo ResourcesECSDB<T> must be used only inside entity components. Same for ECSString. | |||
//what I could do is that if the component is removed from the database, a reference counter to the object | |||
//will be modified. If 0 is reached, the ID should be recycled. | |||
public struct ECSString:IEquatable<ECSString> | |||
{ | |||
uint id; | |||
[FieldOffset(0)] uint _id; | |||
[FieldOffset(4)] uint _versioning; | |||
[FieldOffset(0)] long _realID; | |||
public ECSString(string newText) | |||
public ECSString(string newText):this() | |||
{ | |||
id = ResourcesECSDB<string>.ToECS(newText); | |||
_id = ResourcesECSDB<string>.ToECS(newText); | |||
} | |||
ECSString(uint id):this() | |||
{ | |||
_id = id; | |||
} | |||
public static implicit operator string(ECSString ecsString) | |||
{ | |||
return ResourcesECSDB<string>.FromECS(ecsString.id); | |||
return ResourcesECSDB<string>.FromECS(ecsString._id); | |||
} | |||
/// <summary> | |||
/// Note: Setting null String could be a good way to signal a disposing of the ID so that | |||
/// it can be recycled. | |||
/// Zero id must be a null string | |||
/// </summary> | |||
/// <param name="newText"></param> | |||
public void Set(string newText) | |||
{ | |||
if (id != 0) | |||
ResourcesECSDB<string>.resources(id) = newText; | |||
if (_id != 0) | |||
{ | |||
if (ResourcesECSDB<string>.resources(_id).Equals(newText) == false) | |||
{ | |||
ResourcesECSDB<string>.resources(_id) = newText; | |||
_versioning++; | |||
} | |||
} | |||
else | |||
id = ResourcesECSDB<string>.ToECS(newText); | |||
_id = ResourcesECSDB<string>.ToECS(newText); | |||
} | |||
public bool Equals(ECSString other) | |||
public ECSString Copy() | |||
{ | |||
return other.id == id; | |||
DBC.ECS.Check.Require(_id != 0, "copying not initialized string"); | |||
var id = ResourcesECSDB<string>.ToECS(ResourcesECSDB<string>.resources(_id)); | |||
return new ECSString(id); | |||
} | |||
public override string ToString() | |||
{ | |||
return ResourcesECSDB<string>.FromECS(id); | |||
return ResourcesECSDB<string>.FromECS(_id); | |||
} | |||
public bool Equals(ECSString other) | |||
{ | |||
return _realID == other._realID; | |||
} | |||
public static bool operator==(ECSString options1, ECSString options2) | |||
{ | |||
return options1._realID == options2._realID; | |||
} | |||
public static bool operator!=(ECSString options1, ECSString options2) | |||
{ | |||
return options1._realID != options2._realID; | |||
} | |||
public override bool Equals(object obj) | |||
{ | |||
throw new NotSupportedException(); //this is on purpose | |||
} | |||
public override int GetHashCode() | |||
{ | |||
return _realID.GetHashCode(); | |||
} | |||
} | |||
} |
@@ -1,19 +1,21 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Runtime.InteropServices; | |||
#pragma warning disable 660,661 | |||
namespace Svelto.ECS | |||
{ | |||
//todo: add debug map | |||
[Serialization.DoNotSerialize] | |||
[Serializable] | |||
public struct EGID:IEquatable<EGID>,IEqualityComparer<EGID>,IComparable<EGID> | |||
[StructLayout(LayoutKind.Explicit)] | |||
public struct EGID:IEquatable<EGID>,IComparable<EGID> | |||
{ | |||
public uint entityID => (uint) (_GID & 0xFFFFFFFF); | |||
public ExclusiveGroup.ExclusiveGroupStruct groupID => new ExclusiveGroup.ExclusiveGroupStruct((uint) (_GID >> 32)); | |||
[FieldOffset(0)] public readonly uint entityID; | |||
[FieldOffset(4)] public readonly ExclusiveGroupStruct groupID; | |||
[FieldOffset(0)] readonly ulong _GID; | |||
public static readonly EGID Empty = new EGID(); | |||
public static bool operator ==(EGID obj1, EGID obj2) | |||
{ | |||
return obj1._GID == obj2._GID; | |||
@@ -24,10 +26,15 @@ namespace Svelto.ECS | |||
return obj1._GID != obj2._GID; | |||
} | |||
public EGID(uint entityID, ExclusiveGroup.ExclusiveGroupStruct groupID) : this() | |||
public EGID(uint entityID, ExclusiveGroupStruct groupID) : this() | |||
{ | |||
_GID = MAKE_GLOBAL_ID(entityID, groupID); | |||
} | |||
public EGID(uint entityID, BuildGroup groupID) : this() | |||
{ | |||
_GID = MAKE_GLOBAL_ID(entityID, groupID.group); | |||
} | |||
static ulong MAKE_GLOBAL_ID(uint entityId, uint groupId) | |||
{ | |||
@@ -52,11 +59,16 @@ namespace Svelto.ECS | |||
return x == y; | |||
} | |||
public int GetHashCode(EGID obj) | |||
public override int GetHashCode() | |||
{ | |||
return _GID.GetHashCode(); | |||
} | |||
public int GetHashCode(EGID egid) | |||
{ | |||
return egid.GetHashCode(); | |||
} | |||
public int CompareTo(EGID other) | |||
{ | |||
return _GID.CompareTo(other._GID); | |||
@@ -69,9 +81,8 @@ namespace Svelto.ECS | |||
public override string ToString() | |||
{ | |||
return "id ".FastConcat(entityID).FastConcat(" group ").FastConcat(groupID); | |||
var value = groupID.ToName(); | |||
return "id ".FastConcat(entityID).FastConcat(" group ").FastConcat(value); | |||
} | |||
readonly ulong _GID; | |||
} | |||
} |
@@ -1,37 +1,82 @@ | |||
using System; | |||
using System.Runtime.CompilerServices; | |||
using System.Runtime.InteropServices; | |||
using Svelto.Common; | |||
using Svelto.DataStructures; | |||
using Svelto.ECS.Internal; | |||
namespace Svelto.ECS | |||
{ | |||
public struct EGIDMapper<T> where T : struct, IEntityStruct | |||
/// <summary> | |||
/// Note: does mono devirtualize sealed classes? If so it could be worth to use TypeSafeDictionary instead of | |||
/// the interface | |||
/// </summary> | |||
/// <typeparam name="T"></typeparam> | |||
public readonly struct EGIDMapper<T>: IEGIDMapper where T : struct, IEntityComponent | |||
{ | |||
internal FasterDictionary<uint, T> map; | |||
public uint length => _map.count; | |||
public ExclusiveGroupStruct groupID { get; } | |||
public Type entityType => TypeCache<T>.type; | |||
internal EGIDMapper(ExclusiveGroupStruct groupStructId, ITypeSafeDictionary<T> dic) : this() | |||
{ | |||
groupID = groupStructId; | |||
_map = dic; | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public ref T Entity(uint entityID) | |||
{ | |||
#if DEBUG && !PROFILER | |||
if (map.TryFindIndex(entityID, out var findIndex) == false) | |||
throw new Exception("Entity not found in this group ".FastConcat(typeof(T).ToString())); | |||
#if DEBUG && !PROFILE_SVELTO | |||
if (_map == null) | |||
throw new System.Exception("Not initialized EGIDMapper in this group ".FastConcat(typeof(T).ToString())); | |||
if (_map.TryFindIndex(entityID, out var findIndex) == false) | |||
throw new System.Exception("Entity not found in this group ".FastConcat(typeof(T).ToString())); | |||
#else | |||
map.TryFindIndex(entityID, out var findIndex); | |||
_map.TryFindIndex(entityID, out var findIndex); | |||
#endif | |||
return ref map.valuesArray[findIndex]; | |||
return ref _map.GetDirectValueByRef(findIndex); | |||
} | |||
public bool TryGetEntity(uint entityID, out T value) | |||
{ | |||
if (map.TryFindIndex(entityID, out var index)) | |||
if (_map != null && _map.TryFindIndex(entityID, out var index)) | |||
{ | |||
value = map.GetDirectValue(index); | |||
value = _map.GetDirectValueByRef(index); | |||
return true; | |||
} | |||
value = default; | |||
return false; | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public bool Exists(uint idEntityId) | |||
{ | |||
return _map.count > 0 && _map.TryFindIndex(idEntityId, out _); | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public uint GetIndex(uint entityID) | |||
{ | |||
return _map.GetIndex(entityID); | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public bool FindIndex(uint valueKey, out uint index) | |||
{ | |||
return _map.TryFindIndex(valueKey, out index); | |||
} | |||
internal readonly ITypeSafeDictionary<T> _map; | |||
} | |||
} | |||
public interface IEGIDMapper | |||
{ | |||
bool FindIndex(uint valueKey, out uint index); | |||
uint GetIndex(uint entityID); | |||
bool Exists(uint idEntityId); | |||
ExclusiveGroupStruct groupID { get; } | |||
Type entityType { get; } | |||
} | |||
} |
@@ -0,0 +1,148 @@ | |||
using System; | |||
using Svelto.DataStructures; | |||
using Svelto.ECS.Internal; | |||
namespace Svelto.ECS | |||
{ | |||
public partial class EnginesRoot | |||
{ | |||
internal class DoubleBufferedEntitiesToAdd | |||
{ | |||
const int MaximumNumberOfItemsPerFrameBeforeToClear = 100; | |||
internal void Swap() | |||
{ | |||
Swap(ref current, ref other); | |||
Swap(ref currentEntitiesCreatedPerGroup, ref otherEntitiesCreatedPerGroup); | |||
} | |||
void Swap<T>(ref T item1, ref T item2) | |||
{ | |||
var toSwap = item2; | |||
item2 = item1; | |||
item1 = toSwap; | |||
} | |||
public void ClearOther() | |||
{ | |||
//do not clear the groups created so far, they will be reused, unless they are too many! | |||
var otherCount = other.count; | |||
if (otherCount > MaximumNumberOfItemsPerFrameBeforeToClear) | |||
{ | |||
FasterDictionary<RefWrapperType, ITypeSafeDictionary>[] otherValuesArray = other.unsafeValues; | |||
for (int i = 0; i < otherCount; ++i) | |||
{ | |||
var safeDictionariesCount = otherValuesArray[i].count; | |||
ITypeSafeDictionary[] safeDictionaries = otherValuesArray[i].unsafeValues; | |||
{ | |||
for (int j = 0; j < safeDictionariesCount; ++j) | |||
{ | |||
//clear the dictionary of entities create do far (it won't allocate though) | |||
safeDictionaries[j].Dispose(); | |||
} | |||
} | |||
} | |||
otherEntitiesCreatedPerGroup.FastClear(); | |||
other.FastClear(); | |||
return; | |||
} | |||
{ | |||
FasterDictionary<RefWrapperType, ITypeSafeDictionary>[] otherValuesArray = other.unsafeValues; | |||
for (int i = 0; i < otherCount; ++i) | |||
{ | |||
var safeDictionariesCount = otherValuesArray[i].count; | |||
ITypeSafeDictionary[] safeDictionaries = otherValuesArray[i].unsafeValues; | |||
//do not remove the dictionaries of entities per type created so far, they will be reused | |||
if (safeDictionariesCount <= MaximumNumberOfItemsPerFrameBeforeToClear) | |||
{ | |||
for (int j = 0; j < safeDictionariesCount; ++j) | |||
{ | |||
//clear the dictionary of entities create do far (it won't allocate though) | |||
safeDictionaries[j].FastClear(); | |||
} | |||
} | |||
else | |||
{ | |||
for (int j = 0; j < safeDictionariesCount; ++j) | |||
{ | |||
//clear the dictionary of entities create do far (it won't allocate though) | |||
safeDictionaries[j].Dispose(); | |||
} | |||
otherValuesArray[i].FastClear(); | |||
} | |||
} | |||
otherEntitiesCreatedPerGroup.FastClear(); | |||
} | |||
} | |||
/// <summary> | |||
/// To avoid extra allocation, I don't clear the dictionaries, so I need an extra data structure | |||
/// to keep count of the number of entities submitted this frame | |||
/// </summary> | |||
internal FasterDictionary<ExclusiveGroupStruct, uint> currentEntitiesCreatedPerGroup; | |||
internal FasterDictionary<ExclusiveGroupStruct, uint> otherEntitiesCreatedPerGroup; | |||
//Before I tried for the third time to use a SparseSet instead of FasterDictionary, remember that | |||
//while group indices are sequential, they may not be used in a sequential order. Sparaset needs | |||
//entities to be created sequentially (the index cannot be managed externally) | |||
internal FasterDictionary<uint, FasterDictionary<RefWrapperType, ITypeSafeDictionary>> current; | |||
internal FasterDictionary<uint, FasterDictionary<RefWrapperType, ITypeSafeDictionary>> other; | |||
readonly FasterDictionary<uint, FasterDictionary<RefWrapperType, ITypeSafeDictionary>> | |||
_entityComponentsToAddBufferA = | |||
new FasterDictionary<uint, FasterDictionary<RefWrapperType, ITypeSafeDictionary>>(); | |||
readonly FasterDictionary<uint, FasterDictionary<RefWrapperType, ITypeSafeDictionary>> | |||
_entityComponentsToAddBufferB = | |||
new FasterDictionary<uint, FasterDictionary<RefWrapperType, ITypeSafeDictionary>>(); | |||
readonly FasterDictionary<ExclusiveGroupStruct, uint> _entitiesCreatedPerGroupA = new FasterDictionary<ExclusiveGroupStruct, uint>(); | |||
readonly FasterDictionary<ExclusiveGroupStruct, uint> _entitiesCreatedPerGroupB = new FasterDictionary<ExclusiveGroupStruct, uint>(); | |||
public DoubleBufferedEntitiesToAdd() | |||
{ | |||
currentEntitiesCreatedPerGroup = _entitiesCreatedPerGroupA; | |||
otherEntitiesCreatedPerGroup = _entitiesCreatedPerGroupB; | |||
current = _entityComponentsToAddBufferA; | |||
other = _entityComponentsToAddBufferB; | |||
} | |||
public void Dispose() | |||
{ | |||
{ | |||
var otherValuesArray = other.unsafeValues; | |||
for (int i = 0; i < other.count; ++i) | |||
{ | |||
var safeDictionariesCount = otherValuesArray[i].count; | |||
var safeDictionaries = otherValuesArray[i].unsafeValues; | |||
//do not remove the dictionaries of entities per type created so far, they will be reused | |||
for (int j = 0; j < safeDictionariesCount; ++j) | |||
{ | |||
//clear the dictionary of entities create do far (it won't allocate though) | |||
safeDictionaries[j].Dispose(); | |||
} | |||
} | |||
} | |||
{ | |||
var currentValuesArray = current.unsafeValues; | |||
for (int i = 0; i < current.count; ++i) | |||
{ | |||
var safeDictionariesCount = currentValuesArray[i].count; | |||
var safeDictionaries = currentValuesArray[i].unsafeValues; | |||
//do not remove the dictionaries of entities per type created so far, they will be reused | |||
for (int j = 0; j < safeDictionariesCount; ++j) | |||
{ | |||
//clear the dictionary of entities create do far (it won't allocate though) | |||
safeDictionaries[j].Dispose(); | |||
} | |||
} | |||
} | |||
} | |||
} | |||
} | |||
} |
@@ -0,0 +1,11 @@ | |||
fileFormatVersion: 2 | |||
guid: 5bf312fc57853c4d8368dcb99141a1e9 | |||
MonoImporter: | |||
externalObjects: {} | |||
serializedVersion: 2 | |||
defaultReferences: [] | |||
executionOrder: 0 | |||
icon: {instanceID: 0} | |||
userData: | |||
assetBundleName: | |||
assetBundleVariant: |
@@ -1,86 +0,0 @@ | |||
using System; | |||
using Svelto.DataStructures; | |||
using Svelto.ECS.Internal; | |||
namespace Svelto.ECS | |||
{ | |||
public partial class EnginesRoot | |||
{ | |||
internal class DoubleBufferedEntitiesToAdd | |||
{ | |||
const int MaximumNumberOfItemsPerFrameBeforeToClear = 100; | |||
internal void Swap() | |||
{ | |||
Swap(ref current, ref other); | |||
Swap(ref currentEntitiesCreatedPerGroup, ref otherEntitiesCreatedPerGroup); | |||
} | |||
void Swap<T>(ref T item1, ref T item2) | |||
{ | |||
var toSwap = item2; | |||
item2 = item1; | |||
item1 = toSwap; | |||
} | |||
public void ClearOther() | |||
{ | |||
//do not clear the groups created so far, they will be reused, unless they are too many! | |||
var otherCount = other.Count; | |||
if (otherCount > MaximumNumberOfItemsPerFrameBeforeToClear) | |||
{ | |||
otherEntitiesCreatedPerGroup.FastClear(); | |||
other.FastClear(); | |||
return; | |||
} | |||
var otherValuesArray = other.valuesArray; | |||
for (int i = 0; i < otherCount; ++i) | |||
{ | |||
var safeDictionariesCount = otherValuesArray[i].Count; | |||
var safeDictionaries = otherValuesArray[i].valuesArray; | |||
//do not remove the dictionaries of entities per type created so far, they will be reused | |||
if (safeDictionariesCount <= MaximumNumberOfItemsPerFrameBeforeToClear) | |||
{ | |||
for (int j = 0; j < safeDictionariesCount; ++j) | |||
{ | |||
//clear the dictionary of entities create do far (it won't allocate though) | |||
safeDictionaries[j].FastClear(); | |||
} | |||
} | |||
else | |||
{ | |||
otherValuesArray[i].FastClear(); | |||
} | |||
} | |||
otherEntitiesCreatedPerGroup.FastClear(); | |||
} | |||
internal FasterDictionary<uint, uint> currentEntitiesCreatedPerGroup; | |||
internal FasterDictionary<uint, uint> otherEntitiesCreatedPerGroup; | |||
internal FasterDictionary<uint, FasterDictionary<RefWrapper<Type>, ITypeSafeDictionary>> current; | |||
internal FasterDictionary<uint, FasterDictionary<RefWrapper<Type>, ITypeSafeDictionary>> other; | |||
readonly FasterDictionary<uint, FasterDictionary<RefWrapper<Type>, ITypeSafeDictionary>> | |||
_entityViewsToAddBufferA = | |||
new FasterDictionary<uint, FasterDictionary<RefWrapper<Type>, ITypeSafeDictionary>>(); | |||
readonly FasterDictionary<uint, FasterDictionary<RefWrapper<Type>, ITypeSafeDictionary>> | |||
_entityViewsToAddBufferB = | |||
new FasterDictionary<uint, FasterDictionary<RefWrapper<Type>, ITypeSafeDictionary>>(); | |||
readonly FasterDictionary<uint, uint> _entitiesCreatedPerGroupA = new FasterDictionary<uint, uint>(); | |||
readonly FasterDictionary<uint, uint> _entitiesCreatedPerGroupB = new FasterDictionary<uint, uint>(); | |||
public DoubleBufferedEntitiesToAdd() | |||
{ | |||
currentEntitiesCreatedPerGroup = _entitiesCreatedPerGroupA; | |||
otherEntitiesCreatedPerGroup = _entitiesCreatedPerGroupB; | |||
current = _entityViewsToAddBufferA; | |||
other = _entityViewsToAddBufferB; | |||
} | |||
} | |||
} | |||
} |
@@ -1,28 +1,35 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using Svelto.Common; | |||
using Svelto.DataStructures; | |||
using Svelto.ECS.Internal; | |||
using Svelto.ECS.Schedulers; | |||
namespace Svelto.ECS | |||
{ | |||
public partial class EnginesRoot | |||
public sealed partial class EnginesRoot | |||
{ | |||
public struct EntitiesSubmitter | |||
public readonly struct EntitiesSubmitter | |||
{ | |||
public EntitiesSubmitter(EnginesRoot enginesRoot) | |||
{ | |||
_weakReference = new DataStructures.WeakReference<EnginesRoot>(enginesRoot); | |||
_weakReference = new Svelto.DataStructures.WeakReference<EnginesRoot>(enginesRoot); | |||
} | |||
public bool IsUnused => _weakReference.IsValid == false; | |||
public void Invoke() | |||
{ | |||
if (_weakReference.IsValid) | |||
_weakReference.Target.SubmitEntityViews(); | |||
_weakReference.Target.SubmitEntityComponents(); | |||
} | |||
readonly DataStructures.WeakReference<EnginesRoot> _weakReference; | |||
readonly Svelto.DataStructures.WeakReference<EnginesRoot> _weakReference; | |||
} | |||
readonly EntitiesSubmissionScheduler _scheduler; | |||
public IEntitiesSubmissionScheduler scheduler => _scheduler; | |||
/// <summary> | |||
/// Engines root contextualize your engines and entities. You don't need to limit yourself to one EngineRoot | |||
/// as multiple engines root could promote separation of scopes. The EntitySubmissionScheduler checks | |||
@@ -31,49 +38,156 @@ namespace Svelto.ECS | |||
/// The EntitySubmissionScheduler cannot hold an EnginesRoot reference, that's why | |||
/// it must receive a weak reference of the EnginesRoot callback. | |||
/// </summary> | |||
public EnginesRoot(IEntitySubmissionScheduler entityViewScheduler) | |||
public EnginesRoot(EntitiesSubmissionScheduler entitiesComponentScheduler) | |||
{ | |||
_entitiesOperations = new FasterDictionary<ulong, EntitySubmitOperation>(); | |||
serializationDescriptorMap = new SerializationDescriptorMap(); | |||
_reactiveEnginesAddRemove = new FasterDictionary<RefWrapper<Type>, FasterList<IEngine>>(); | |||
_reactiveEnginesSwap = new FasterDictionary<RefWrapper<Type>, FasterList<IEngine>>(); | |||
_enginesSet = new FasterList<IEngine>(); | |||
_enginesTypeSet = new HashSet<Type>(); | |||
_disposableEngines = new FasterList<IDisposable>(); | |||
_entitiesOperations = new ThreadSafeDictionary<ulong, EntitySubmitOperation>(); | |||
serializationDescriptorMap = new SerializationDescriptorMap(); | |||
_reactiveEnginesAddRemove = new FasterDictionary<RefWrapperType, FasterList<IReactEngine>>(); | |||
_reactiveEnginesSwap = new FasterDictionary<RefWrapperType, FasterList<IReactEngine>>(); | |||
_reactiveEnginesSubmission = new FasterList<IReactOnSubmission>(); | |||
_enginesSet = new FasterList<IEngine>(); | |||
_enginesTypeSet = new HashSet<Type>(); | |||
_disposableEngines = new FasterList<IDisposable>(); | |||
_transientEntitiesOperations = new FasterList<EntitySubmitOperation>(); | |||
_groupEntityViewsDB = new FasterDictionary<uint, FasterDictionary<RefWrapper<Type>, ITypeSafeDictionary>>(); | |||
_groupsPerEntity = new FasterDictionary<RefWrapper<Type>, FasterDictionary<uint, ITypeSafeDictionary>>(); | |||
_groupEntityComponentsDB = | |||
new FasterDictionary<ExclusiveGroupStruct, FasterDictionary<RefWrapperType, ITypeSafeDictionary>>(); | |||
_groupsPerEntity = | |||
new FasterDictionary<RefWrapperType, FasterDictionary<ExclusiveGroupStruct, ITypeSafeDictionary>>(); | |||
_groupedEntityToAdd = new DoubleBufferedEntitiesToAdd(); | |||
_entitiesStream = new EntitiesStream(); | |||
_entitiesDB = new EntitiesDB(_groupEntityViewsDB, _groupsPerEntity, _entitiesStream); | |||
_entityStreams = EntitiesStreams.Create(); | |||
_groupFilters = | |||
new FasterDictionary<RefWrapperType, FasterDictionary<ExclusiveGroupStruct, GroupFilters>>(); | |||
_entitiesDB = new EntitiesDB(this); | |||
_scheduler = entityViewScheduler; | |||
_scheduler = entitiesComponentScheduler; | |||
_scheduler.onTick = new EntitiesSubmitter(this); | |||
#if UNITY_NATIVE | |||
AllocateNativeOperations(); | |||
#endif | |||
} | |||
public EnginesRoot(IEntitySubmissionScheduler entityViewScheduler, bool isDeserializationOnly):this(entityViewScheduler) | |||
public EnginesRoot | |||
(EntitiesSubmissionScheduler entitiesComponentScheduler, bool isDeserializationOnly) : | |||
this(entitiesComponentScheduler) | |||
{ | |||
_isDeserializationOnly = isDeserializationOnly; | |||
} | |||
/// <summary> | |||
/// Dispose an EngineRoot once not used anymore, so that all the | |||
/// engines are notified with the entities removed. | |||
/// It's a clean up process. | |||
/// </summary> | |||
public void Dispose() | |||
{ | |||
_isDisposing = true; | |||
using (var profiler = new PlatformProfiler("Final Dispose")) | |||
{ | |||
//Note: The engines are disposed before the the remove callback to give the chance to behave | |||
//differently if a remove happens as a consequence of a dispose | |||
//The pattern is to implement the IDisposable interface and set a flag in the engine. The | |||
//remove callback will then behave differently according the flag. | |||
foreach (var engine in _disposableEngines) | |||
{ | |||
try | |||
{ | |||
if (engine is IDisposingEngine dengine) | |||
dengine.isDisposing = true; | |||
engine.Dispose(); | |||
} | |||
catch (Exception e) | |||
{ | |||
Svelto.Console.LogException(e); | |||
} | |||
} | |||
foreach (FasterDictionary<ExclusiveGroupStruct, FasterDictionary<RefWrapperType, ITypeSafeDictionary>>. | |||
KeyValuePairFast groups in _groupEntityComponentsDB) | |||
{ | |||
foreach (FasterDictionary<RefWrapperType, ITypeSafeDictionary>.KeyValuePairFast entityList in groups | |||
.Value) | |||
try | |||
{ | |||
entityList.Value.ExecuteEnginesRemoveCallbacks(_reactiveEnginesAddRemove, profiler | |||
, new ExclusiveGroupStruct(groups.Key)); | |||
} | |||
catch (Exception e) | |||
{ | |||
Svelto.Console.LogException(e); | |||
} | |||
} | |||
foreach (FasterDictionary<ExclusiveGroupStruct, FasterDictionary<RefWrapperType, ITypeSafeDictionary>>. | |||
KeyValuePairFast groups in _groupEntityComponentsDB) | |||
{ | |||
foreach (FasterDictionary<RefWrapperType, ITypeSafeDictionary>.KeyValuePairFast entityList in groups | |||
.Value) | |||
entityList.Value.Dispose(); | |||
} | |||
foreach (FasterDictionary<RefWrapperType, FasterDictionary<ExclusiveGroupStruct, GroupFilters>>. | |||
KeyValuePairFast type in _groupFilters) | |||
foreach (FasterDictionary<ExclusiveGroupStruct, GroupFilters>.KeyValuePairFast group in type.Value) | |||
group.Value.Dispose(); | |||
_groupFilters.Clear(); | |||
#if UNITY_NATIVE | |||
_addOperationQueue.Dispose(); | |||
_removeOperationQueue.Dispose(); | |||
_swapOperationQueue.Dispose(); | |||
#endif | |||
_groupEntityComponentsDB.Clear(); | |||
_groupsPerEntity.Clear(); | |||
_disposableEngines.Clear(); | |||
_enginesSet.Clear(); | |||
_enginesTypeSet.Clear(); | |||
_reactiveEnginesSwap.Clear(); | |||
_reactiveEnginesAddRemove.Clear(); | |||
_reactiveEnginesSubmission.Clear(); | |||
_entitiesOperations.Clear(); | |||
_transientEntitiesOperations.Clear(); | |||
_groupedEntityToAdd.Dispose(); | |||
_entityStreams.Dispose(); | |||
scheduler.Dispose(); | |||
} | |||
GC.SuppressFinalize(this); | |||
} | |||
~EnginesRoot() | |||
{ | |||
Console.LogWarning("Engines Root has been garbage collected, don't forget to call Dispose()!"); | |||
Dispose(); | |||
} | |||
public void AddEngine(IEngine engine) | |||
{ | |||
var type = engine.GetType(); | |||
var refWrapper = new RefWrapper<Type>(type); | |||
var type = engine.GetType(); | |||
var refWrapper = new RefWrapperType(type); | |||
DBC.ECS.Check.Require(engine != null, "Engine to add is invalid or null"); | |||
DBC.ECS.Check.Require( | |||
_enginesTypeSet.Contains(refWrapper) == false || | |||
type.ContainsCustomAttribute(typeof(AllowMultipleAttribute)) == true, | |||
"The same engine has been added more than once, if intentional, use [AllowMultiple] class attribute " | |||
.FastConcat(engine.ToString())); | |||
_enginesTypeSet.Contains(refWrapper) == false | |||
|| type.ContainsCustomAttribute(typeof(AllowMultipleAttribute)) == true | |||
, "The same engine has been added more than once, if intentional, use [AllowMultiple] class attribute " | |||
.FastConcat(engine.ToString())); | |||
try | |||
{ | |||
if (engine is IReactOnAddAndRemove viewEngine) | |||
CheckEntityViewsEngine(viewEngine, _reactiveEnginesAddRemove); | |||
CheckReactEngineComponents(viewEngine, _reactiveEnginesAddRemove); | |||
if (engine is IReactOnSwap viewEngineSwap) | |||
CheckEntityViewsEngine(viewEngineSwap, _reactiveEnginesSwap); | |||
CheckReactEngineComponents(viewEngineSwap, _reactiveEnginesSwap); | |||
if (engine is IReactOnSubmission submissionEngine) | |||
_reactiveEnginesSubmission.Add(submissionEngine); | |||
_enginesTypeSet.Add(refWrapper); | |||
_enginesSet.Add(engine); | |||
@@ -81,20 +195,21 @@ namespace Svelto.ECS | |||
if (engine is IDisposable) | |||
_disposableEngines.Add(engine as IDisposable); | |||
if (engine is IQueryingEntitiesEngine queryableEntityViewEngine) | |||
if (engine is IQueryingEntitiesEngine queryableEntityComponentEngine) | |||
{ | |||
queryableEntityViewEngine.entitiesDB = _entitiesDB; | |||
queryableEntityViewEngine.Ready(); | |||
queryableEntityComponentEngine.entitiesDB = _entitiesDB; | |||
queryableEntityComponentEngine.Ready(); | |||
} | |||
} | |||
catch (Exception e) | |||
{ | |||
throw new ECSException("Code crashed while adding engine ".FastConcat(engine.GetType().ToString(), " "), e); | |||
throw new ECSException("Code crashed while adding engine ".FastConcat(engine.GetType().ToString(), " ") | |||
, e); | |||
} | |||
} | |||
void CheckEntityViewsEngine<T>(T engine, FasterDictionary<RefWrapper<Type>, FasterList<IEngine>> engines) | |||
where T : class, IEngine | |||
void CheckReactEngineComponents<T>(T engine, FasterDictionary<RefWrapperType, FasterList<IReactEngine>> engines) | |||
where T : class, IReactEngine | |||
{ | |||
var interfaces = engine.GetType().GetInterfaces(); | |||
@@ -109,36 +224,37 @@ namespace Svelto.ECS | |||
} | |||
} | |||
static void AddEngine<T>(T engine, Type[] entityViewTypes, | |||
FasterDictionary<RefWrapper<Type>, FasterList<IEngine>> engines) | |||
where T : class, IEngine | |||
static void AddEngine<T> | |||
(T engine, Type[] entityComponentTypes, FasterDictionary<RefWrapperType, FasterList<IReactEngine>> engines) | |||
where T : class, IReactEngine | |||
{ | |||
for (var i = 0; i < entityViewTypes.Length; i++) | |||
for (var i = 0; i < entityComponentTypes.Length; i++) | |||
{ | |||
var type = entityViewTypes[i]; | |||
var type = entityComponentTypes[i]; | |||
AddEngine(engine, engines, type); | |||
} | |||
} | |||
static void AddEngine<T>(T engine, FasterDictionary<RefWrapper<Type>, FasterList<IEngine>> engines, Type type) | |||
where T : class, IEngine | |||
static void AddEngine<T>(T engine, FasterDictionary<RefWrapperType, FasterList<IReactEngine>> engines, Type type) | |||
where T : class, IReactEngine | |||
{ | |||
if (engines.TryGetValue(new RefWrapper<Type>(type), out var list) == false) | |||
if (engines.TryGetValue(new RefWrapperType(type), out var list) == false) | |||
{ | |||
list = new FasterList<IEngine>(); | |||
list = new FasterList<IReactEngine>(); | |||
engines.Add(new RefWrapper<Type>(type), list); | |||
engines.Add(new RefWrapperType(type), list); | |||
} | |||
list.Add(engine); | |||
} | |||
readonly FasterDictionary<RefWrapper<Type>, FasterList<IEngine>> _reactiveEnginesAddRemove; | |||
readonly FasterDictionary<RefWrapper<Type>, FasterList<IEngine>> _reactiveEnginesSwap; | |||
readonly FasterList<IDisposable> _disposableEngines; | |||
readonly FasterList<IEngine> _enginesSet; | |||
readonly HashSet<Type> _enginesTypeSet; | |||
readonly FasterDictionary<RefWrapperType, FasterList<IReactEngine>> _reactiveEnginesAddRemove; | |||
readonly FasterDictionary<RefWrapperType, FasterList<IReactEngine>> _reactiveEnginesSwap; | |||
readonly FasterList<IReactOnSubmission> _reactiveEnginesSubmission; | |||
readonly FasterList<IDisposable> _disposableEngines; | |||
readonly FasterList<IEngine> _enginesSet; | |||
readonly HashSet<Type> _enginesTypeSet; | |||
internal bool _isDisposing; | |||
} | |||
} |
@@ -1,65 +1,15 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Runtime.CompilerServices; | |||
using DBC.ECS; | |||
using Svelto.Common; | |||
using Svelto.DataStructures; | |||
using Svelto.ECS.Internal; | |||
namespace Svelto.ECS | |||
{ | |||
public partial class EnginesRoot : IDisposable | |||
public partial class EnginesRoot : IDisposable, IUnitTestingInterface | |||
{ | |||
/// <summary> | |||
/// Dispose an EngineRoot once not used anymore, so that all the | |||
/// engines are notified with the entities removed. | |||
/// It's a clean up process. | |||
/// </summary> | |||
public void Dispose() | |||
{ | |||
using (var profiler = new PlatformProfiler("Final Dispose")) | |||
{ | |||
foreach (var groups in _groupEntityViewsDB) | |||
{ | |||
foreach (var entityList in groups.Value) | |||
{ | |||
entityList.Value.RemoveEntitiesFromEngines(_reactiveEnginesAddRemove, | |||
profiler, new ExclusiveGroup.ExclusiveGroupStruct(groups.Key)); | |||
} | |||
} | |||
_groupEntityViewsDB.Clear(); | |||
_groupsPerEntity.Clear(); | |||
foreach (var engine in _disposableEngines) | |||
engine.Dispose(); | |||
_disposableEngines.Clear(); | |||
_enginesSet.Clear(); | |||
_enginesTypeSet.Clear(); | |||
_reactiveEnginesSwap.Clear(); | |||
_reactiveEnginesAddRemove.Clear(); | |||
_entitiesOperations.Clear(); | |||
_transientEntitiesOperations.Clear(); | |||
_scheduler.Dispose(); | |||
#if DEBUG && !PROFILER | |||
_idCheckers.Clear(); | |||
#endif | |||
_groupedEntityToAdd = null; | |||
_entitiesStream.Dispose(); | |||
} | |||
GC.SuppressFinalize(this); | |||
} | |||
~EnginesRoot() | |||
{ | |||
Console.LogWarning("Engines Root has been garbage collected, don't forget to call Dispose()!"); | |||
Dispose(); | |||
} | |||
///-------------------------------------------- | |||
/// | |||
public IEntityStreamConsumerFactory GenerateConsumerFactory() | |||
@@ -79,227 +29,304 @@ namespace Svelto.ECS | |||
///-------------------------------------------- | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
EntityStructInitializer BuildEntity(EGID entityID, IEntityBuilder[] entitiesToBuild, | |||
EntityComponentInitializer BuildEntity | |||
(EGID entityID, IComponentBuilder[] componentsToBuild, Type descriptorType, | |||
IEnumerable<object> implementors = null) | |||
{ | |||
CheckAddEntityID(entityID); | |||
CheckAddEntityID(entityID, descriptorType); | |||
Check.Require(entityID.groupID != 0, "invalid group detected, are you using new ExclusiveGroupStruct() instead of new ExclusiveGroup()?"); | |||
var dic = EntityFactory.BuildGroupedEntities(entityID, _groupedEntityToAdd, | |||
entitiesToBuild, implementors); | |||
var dic = EntityFactory.BuildGroupedEntities(entityID, _groupedEntityToAdd, componentsToBuild | |||
, implementors, descriptorType); | |||
return new EntityStructInitializer(entityID, dic); | |||
return new EntityComponentInitializer(entityID, dic); | |||
} | |||
///-------------------------------------------- | |||
void Preallocate<T>(uint groupID, uint size) where T : IEntityDescriptor, new() | |||
void Preallocate<T>(ExclusiveGroupStruct groupID, uint size) where T : IEntityDescriptor, new() | |||
{ | |||
var entityViewsToBuild = EntityDescriptorTemplate<T>.descriptor.entitiesToBuild; | |||
var numberOfEntityViews = entityViewsToBuild.Length; | |||
using (var profiler = new PlatformProfiler("Preallocate")) | |||
{ | |||
var entityComponentsToBuild = EntityDescriptorTemplate<T>.descriptor.componentsToBuild; | |||
var numberOfEntityComponents = entityComponentsToBuild.Length; | |||
//reserve space in the database | |||
if (_groupEntityViewsDB.TryGetValue(groupID, out var group) == false) | |||
group = _groupEntityViewsDB[groupID] = new FasterDictionary<RefWrapper<Type>, ITypeSafeDictionary>(); | |||
FasterDictionary<RefWrapperType, ITypeSafeDictionary> group = GetOrCreateGroup(groupID, profiler); | |||
for (var index = 0; index < numberOfEntityViews; index++) | |||
{ | |||
var entityViewBuilder = entityViewsToBuild[index]; | |||
var entityViewType = entityViewBuilder.GetEntityType(); | |||
for (var index = 0; index < numberOfEntityComponents; index++) | |||
{ | |||
var entityComponentBuilder = entityComponentsToBuild[index]; | |||
var entityComponentType = entityComponentBuilder.GetEntityComponentType(); | |||
var refWrapper = new RefWrapper<Type>(entityViewType); | |||
if (group.TryGetValue(refWrapper, out var dbList) == false) | |||
group[refWrapper] = entityViewBuilder.Preallocate(ref dbList, size); | |||
else | |||
dbList.SetCapacity(size); | |||
var refWrapper = new RefWrapperType(entityComponentType); | |||
if (group.TryGetValue(refWrapper, out var dbList) == false) | |||
group[refWrapper] = entityComponentBuilder.Preallocate(ref dbList, size); | |||
else | |||
dbList.SetCapacity(size); | |||
if (_groupsPerEntity.TryGetValue(refWrapper, out var groupedGroup) == false) | |||
groupedGroup = _groupsPerEntity[refWrapper] = | |||
new FasterDictionary<uint, ITypeSafeDictionary>(); | |||
if (_groupsPerEntity.TryGetValue(refWrapper, out var groupedGroup) == false) | |||
groupedGroup = _groupsPerEntity[refWrapper] = new FasterDictionary<ExclusiveGroupStruct, ITypeSafeDictionary>(); | |||
groupedGroup[groupID] = dbList; | |||
groupedGroup[groupID] = dbList; | |||
} | |||
} | |||
} | |||
///-------------------------------------------- | |||
/// | |||
void MoveEntityFromAndToEngines(IEntityBuilder[] entityBuilders, EGID fromEntityGID, EGID? toEntityGID) | |||
void MoveEntityFromAndToEngines(IComponentBuilder[] componentBuilders, EGID fromEntityGID, EGID? toEntityGID) | |||
{ | |||
using (var sampler = new PlatformProfiler("Move Entity From Engines")) | |||
{ | |||
//for each entity view generated by the entity descriptor | |||
if (_groupEntityViewsDB.TryGetValue(fromEntityGID.groupID, out var fromGroup) == false) | |||
throw new ECSException("from group not found eid: ".FastConcat(fromEntityGID.entityID) | |||
.FastConcat(" group: ").FastConcat(fromEntityGID.groupID)); | |||
//Check if there is an EntityInfoView linked to this entity, if so it's a DynamicEntityDescriptor! | |||
if (fromGroup.TryGetValue(new RefWrapper<Type>(EntityBuilderUtilities.ENTITY_STRUCT_INFO_VIEW), | |||
out var entityInfoViewDic) && | |||
(entityInfoViewDic as TypeSafeDictionary<EntityStructInfoView>).TryGetValue( | |||
fromEntityGID.entityID, out var entityInfoView)) | |||
MoveEntities(fromEntityGID, toEntityGID, entityInfoView.entitiesToBuild, fromGroup, sampler); | |||
var fromGroup = GetGroup(fromEntityGID.groupID); | |||
//Check if there is an EntityInfo linked to this entity, if so it's a DynamicEntityDescriptor! | |||
if (fromGroup.TryGetValue(new RefWrapperType(ComponentBuilderUtilities.ENTITY_INFO_COMPONENT) | |||
, out var entityInfoDic) | |||
&& (entityInfoDic as ITypeSafeDictionary<EntityInfoComponent>).TryGetValue( | |||
fromEntityGID.entityID, out var entityInfo)) | |||
SwapOrRemoveEntityComponents(fromEntityGID, toEntityGID, entityInfo.componentsToBuild, fromGroup | |||
, sampler); | |||
//otherwise it's a normal static entity descriptor | |||
else | |||
MoveEntities(fromEntityGID, toEntityGID, entityBuilders, fromGroup, sampler); | |||
SwapOrRemoveEntityComponents(fromEntityGID, toEntityGID, componentBuilders, fromGroup, sampler); | |||
} | |||
} | |||
void MoveEntities(EGID fromEntityGID, EGID? toEntityGID, IEntityBuilder[] entitiesToMove, | |||
FasterDictionary<RefWrapper<Type>, ITypeSafeDictionary> fromGroup, PlatformProfiler sampler) | |||
void SwapOrRemoveEntityComponents(EGID fromEntityGID, EGID? toEntityGID, IComponentBuilder[] entitiesToMove | |||
, FasterDictionary<RefWrapperType, ITypeSafeDictionary> fromGroup, in PlatformProfiler sampler) | |||
{ | |||
FasterDictionary<RefWrapper<Type>, ITypeSafeDictionary> toGroup = null; | |||
if (toEntityGID != null) | |||
using (sampler.Sample("MoveEntityComponents")) | |||
{ | |||
var toGroupID = toEntityGID.Value.groupID; | |||
var length = entitiesToMove.Length; | |||
if (_groupEntityViewsDB.TryGetValue(toGroupID, out toGroup) == false) | |||
toGroup = _groupEntityViewsDB[toGroupID] = new FasterDictionary<RefWrapper<Type>, ITypeSafeDictionary>(); | |||
FasterDictionary<RefWrapperType, ITypeSafeDictionary> toGroup = null; | |||
//Add all the entities to the dictionary | |||
for (var i = 0; i < entitiesToMove.Length; i++) | |||
CopyEntityToDictionary(fromEntityGID, toEntityGID.Value, fromGroup, toGroup, | |||
entitiesToMove[i].GetEntityType()); | |||
} | |||
//Swap is not like adding a new entity. While adding new entities happen at the end of submission | |||
//Adding an entity to a group due to a swap of groups happens now. | |||
if (toEntityGID != null) | |||
{ | |||
var toGroupID = toEntityGID.Value.groupID; | |||
//call all the callbacks | |||
for (var i = 0; i < entitiesToMove.Length; i++) | |||
MoveEntityViewFromAndToEngines(fromEntityGID, toEntityGID, fromGroup, toGroup, | |||
entitiesToMove[i].GetEntityType(), sampler); | |||
toGroup = GetOrCreateGroup(toGroupID, sampler); | |||
//then remove all the entities from the dictionary | |||
for (var i = 0; i < entitiesToMove.Length; i++) | |||
RemoveEntityFromDictionary(fromEntityGID, fromGroup, entitiesToMove[i].GetEntityType(), sampler); | |||
} | |||
//Add all the entities to the dictionary | |||
for (var i = 0; i < length; i++) | |||
CopyEntityToDictionary(fromEntityGID, toEntityGID.Value, fromGroup, toGroup | |||
, entitiesToMove[i].GetEntityComponentType(), sampler); | |||
} | |||
void CopyEntityToDictionary(EGID entityGID, EGID toEntityGID, | |||
FasterDictionary<RefWrapper<Type>, ITypeSafeDictionary> fromGroup, | |||
FasterDictionary<RefWrapper<Type>, ITypeSafeDictionary> toGroup, Type entityViewType) | |||
{ | |||
var wrapper = new RefWrapper<Type>(entityViewType); | |||
//call all the callbacks | |||
for (var i = 0; i < length; i++) | |||
ExecuteEnginesSwapOrRemoveCallbacks(fromEntityGID, toEntityGID, fromGroup, toGroup | |||
, entitiesToMove[i].GetEntityComponentType(), sampler); | |||
if (fromGroup.TryGetValue(wrapper, out var fromTypeSafeDictionary) == false) | |||
{ | |||
throw new ECSException("no entities in from group eid: ".FastConcat(entityGID.entityID) | |||
.FastConcat(" group: ").FastConcat(entityGID.groupID)); | |||
//then remove all the entities from the dictionary | |||
for (var i = 0; i < length; i++) | |||
RemoveEntityFromDictionary(fromEntityGID, fromGroup, entitiesToMove[i].GetEntityComponentType(), | |||
sampler); | |||
} | |||
} | |||
#if DEBUG && !PROFILER | |||
if (fromTypeSafeDictionary.Has(entityGID.entityID) == false) | |||
{ | |||
throw new EntityNotFoundException(entityGID, entityViewType); | |||
} | |||
#endif | |||
if (toGroup.TryGetValue(wrapper, out var toEntitiesDictionary) == false) | |||
void CopyEntityToDictionary | |||
(EGID entityGID, EGID toEntityGID, FasterDictionary<RefWrapperType, ITypeSafeDictionary> fromGroup | |||
, FasterDictionary<RefWrapperType, ITypeSafeDictionary> toGroup, Type entityComponentType, | |||
in PlatformProfiler sampler) | |||
{ | |||
using (sampler.Sample("CopyEntityToDictionary")) | |||
{ | |||
toEntitiesDictionary = fromTypeSafeDictionary.Create(); | |||
toGroup.Add(wrapper, toEntitiesDictionary); | |||
} | |||
var wrapper = new RefWrapperType(entityComponentType); | |||
//todo: this must be unit tested properly | |||
if (_groupsPerEntity.TryGetValue(wrapper, out var groupedGroup) == false) | |||
groupedGroup = _groupsPerEntity[wrapper] = | |||
new FasterDictionary<uint, ITypeSafeDictionary>(); | |||
ITypeSafeDictionary fromTypeSafeDictionary = | |||
GetTypeSafeDictionary(entityGID.groupID, fromGroup, wrapper); | |||
groupedGroup[toEntityGID.groupID] = toEntitiesDictionary; | |||
#if DEBUG && !PROFILE_SVELTO | |||
if (fromTypeSafeDictionary.Has(entityGID.entityID) == false) | |||
{ | |||
throw new EntityNotFoundException(entityGID, entityComponentType); | |||
} | |||
#endif | |||
ITypeSafeDictionary toEntitiesDictionary = | |||
GetOrCreateTypeSafeDictionary(toEntityGID.groupID, toGroup, wrapper, fromTypeSafeDictionary); | |||
fromTypeSafeDictionary.AddEntityToDictionary(entityGID, toEntityGID, toEntitiesDictionary); | |||
fromTypeSafeDictionary.AddEntityToDictionary(entityGID, toEntityGID, toEntitiesDictionary); | |||
} | |||
} | |||
void MoveEntityViewFromAndToEngines(EGID entityGID, EGID? toEntityGID, | |||
FasterDictionary<RefWrapper<Type>, ITypeSafeDictionary> fromGroup, | |||
FasterDictionary<RefWrapper<Type>, ITypeSafeDictionary> toGroup, Type entityViewType, | |||
in PlatformProfiler profiler) | |||
void ExecuteEnginesSwapOrRemoveCallbacks | |||
(EGID entityGID, EGID? toEntityGID, FasterDictionary<RefWrapperType, ITypeSafeDictionary> fromGroup | |||
, FasterDictionary<RefWrapperType, ITypeSafeDictionary> toGroup, Type entityComponentType | |||
, in PlatformProfiler profiler) | |||
{ | |||
//add all the entities | |||
var refWrapper = new RefWrapper<Type>(entityViewType); | |||
if (fromGroup.TryGetValue(refWrapper, out var fromTypeSafeDictionary) == false) | |||
using (profiler.Sample("MoveEntityComponentFromAndToEngines")) | |||
{ | |||
throw new ECSException("no entities in from group eid: ".FastConcat(entityGID.entityID) | |||
.FastConcat(" group: ").FastConcat(entityGID.groupID)); | |||
} | |||
//add all the entities | |||
var refWrapper = new RefWrapperType(entityComponentType); | |||
var fromTypeSafeDictionary = GetTypeSafeDictionary(entityGID.groupID, fromGroup, refWrapper); | |||
ITypeSafeDictionary toEntitiesDictionary = null; | |||
if (toGroup != null) | |||
toEntitiesDictionary = toGroup[refWrapper]; //this is guaranteed to exist by AddEntityToDictionary | |||
ITypeSafeDictionary toEntitiesDictionary = null; | |||
if (toGroup != null) | |||
toEntitiesDictionary = toGroup[refWrapper]; //this is guaranteed to exist by AddEntityToDictionary | |||
#if DEBUG && !PROFILER | |||
if (fromTypeSafeDictionary.Has(entityGID.entityID) == false) | |||
throw new EntityNotFoundException(entityGID, entityViewType); | |||
#if DEBUG && !PROFILE_SVELTO | |||
if (fromTypeSafeDictionary.Has(entityGID.entityID) == false) | |||
throw new EntityNotFoundException(entityGID, entityComponentType); | |||
#endif | |||
fromTypeSafeDictionary.MoveEntityFromEngines(entityGID, toEntityGID, | |||
toEntitiesDictionary, toEntityGID == null ? _reactiveEnginesAddRemove : _reactiveEnginesSwap, | |||
in profiler); | |||
fromTypeSafeDictionary.ExecuteEnginesSwapOrRemoveCallbacks(entityGID, toEntityGID, toEntitiesDictionary | |||
, toEntityGID == null ? _reactiveEnginesAddRemove : _reactiveEnginesSwap, in profiler); | |||
} | |||
} | |||
void RemoveEntityFromDictionary(EGID entityGID, | |||
FasterDictionary<RefWrapper<Type>, ITypeSafeDictionary> fromGroup, Type entityViewType, | |||
in PlatformProfiler profiler) | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
void RemoveEntityFromDictionary | |||
(EGID entityGID, FasterDictionary<RefWrapperType, ITypeSafeDictionary> fromGroup, Type entityComponentType | |||
, in PlatformProfiler sampler) | |||
{ | |||
var refWrapper = new RefWrapper<Type>(entityViewType); | |||
if (fromGroup.TryGetValue(refWrapper, out var fromTypeSafeDictionary) == false) | |||
using (sampler.Sample("RemoveEntityFromDictionary")) | |||
{ | |||
throw new ECSException("no entities in from group eid: ".FastConcat(entityGID.entityID) | |||
.FastConcat(" group: ").FastConcat(entityGID.groupID)); | |||
} | |||
fromTypeSafeDictionary.RemoveEntityFromDictionary(entityGID, profiler); | |||
var refWrapper = new RefWrapperType(entityComponentType); | |||
var fromTypeSafeDictionary = GetTypeSafeDictionary(entityGID.groupID, fromGroup, refWrapper); | |||
if (fromTypeSafeDictionary.Count == 0) //clean up | |||
{ | |||
//todo: this must be unit tested properly | |||
_groupsPerEntity[refWrapper].Remove(entityGID.groupID); | |||
//I don't remove the group if empty on purpose, in case it needs to be reused | |||
fromTypeSafeDictionary.RemoveEntityFromDictionary(entityGID); | |||
} | |||
} | |||
/// <summary> | |||
/// Todo: I should keep the group, but I need to mark the group as deleted for the Exist function to work | |||
/// Swap all the entities from one group to another | |||
/// | |||
/// TODO: write unit test that also tests that this calls MoveTo callbacks and not Add or Remove. | |||
/// also that the passing EGID is the same of a component with EGID | |||
/// </summary> | |||
/// <param name="groupID"></param> | |||
/// <param name="fromIdGroupId"></param> | |||
/// <param name="toGroupId"></param> | |||
/// <param name="profiler"></param> | |||
void RemoveGroupAndEntitiesFromDB(uint groupID, in PlatformProfiler profiler) | |||
void SwapEntitiesBetweenGroups(ExclusiveGroupStruct fromIdGroupId, ExclusiveGroupStruct toGroupId, in PlatformProfiler profiler) | |||
{ | |||
var dictionariesOfEntities = _groupEntityViewsDB[groupID]; | |||
foreach (var dictionaryOfEntities in dictionariesOfEntities) | |||
using (profiler.Sample("SwapEntitiesBetweenGroups")) | |||
{ | |||
dictionaryOfEntities.Value.RemoveEntitiesFromEngines(_reactiveEnginesAddRemove, profiler, | |||
new ExclusiveGroup.ExclusiveGroupStruct(groupID)); | |||
var groupedGroupOfEntities = _groupsPerEntity[dictionaryOfEntities.Key]; | |||
groupedGroupOfEntities.Remove(groupID); | |||
FasterDictionary<RefWrapperType, ITypeSafeDictionary> fromGroup = GetGroup(fromIdGroupId); | |||
FasterDictionary<RefWrapperType, ITypeSafeDictionary> toGroup = GetOrCreateGroup(toGroupId, profiler); | |||
foreach (var dictionaryOfEntities in fromGroup) | |||
{ | |||
ITypeSafeDictionary toEntitiesDictionary = | |||
GetOrCreateTypeSafeDictionary(toGroupId, toGroup, dictionaryOfEntities.Key | |||
, dictionaryOfEntities.Value); | |||
var groupsOfEntityType = _groupsPerEntity[dictionaryOfEntities.Key]; | |||
var groupOfEntitiesToCopyAndClear = groupsOfEntityType[fromIdGroupId]; | |||
toEntitiesDictionary.AddEntitiesFromDictionary(groupOfEntitiesToCopyAndClear, toGroupId); | |||
//call all the MoveTo callbacks | |||
dictionaryOfEntities.Value.ExecuteEnginesAddOrSwapCallbacks(_reactiveEnginesSwap | |||
, dictionaryOfEntities.Value, new ExclusiveGroupStruct(fromIdGroupId), new ExclusiveGroupStruct(toGroupId), profiler); | |||
//todo: if it's unmanaged, I can use fastclear | |||
groupOfEntitiesToCopyAndClear.Clear(); | |||
} | |||
} | |||
} | |||
FasterDictionary<RefWrapperType, ITypeSafeDictionary> GetGroup(ExclusiveGroupStruct fromIdGroupId) | |||
{ | |||
if (_groupEntityComponentsDB.TryGetValue(fromIdGroupId | |||
, out FasterDictionary<RefWrapperType, ITypeSafeDictionary> | |||
fromGroup) == false) | |||
throw new ECSException("Group doesn't exist: ".FastConcat(fromIdGroupId)); | |||
return fromGroup; | |||
} | |||
FasterDictionary<RefWrapperType, ITypeSafeDictionary> GetOrCreateGroup(ExclusiveGroupStruct toGroupId, | |||
in PlatformProfiler profiler) | |||
{ | |||
using (profiler.Sample("GetOrCreateGroup")) | |||
{ | |||
if (_groupEntityComponentsDB.TryGetValue( | |||
toGroupId, out FasterDictionary<RefWrapperType, ITypeSafeDictionary> toGroup) == false) | |||
toGroup = _groupEntityComponentsDB[toGroupId] = | |||
new FasterDictionary<RefWrapperType, ITypeSafeDictionary>(); | |||
//careful, in this case I assume you really don't want to use this group anymore | |||
//so I remove it from the database | |||
_groupEntityViewsDB.Remove(groupID); | |||
return toGroup; | |||
} | |||
} | |||
internal Consumer<T> GenerateConsumer<T>(string name, uint capacity) where T : unmanaged, IEntityStruct | |||
ITypeSafeDictionary GetOrCreateTypeSafeDictionary | |||
(ExclusiveGroupStruct groupId, FasterDictionary<RefWrapperType, ITypeSafeDictionary> toGroup, RefWrapperType type | |||
, ITypeSafeDictionary fromTypeSafeDictionary) | |||
{ | |||
return _entitiesStream.GenerateConsumer<T>(name, capacity); | |||
//be sure that the TypeSafeDictionary for the entity Type exists | |||
if (toGroup.TryGetValue(type, out ITypeSafeDictionary toEntitiesDictionary) == false) | |||
{ | |||
toEntitiesDictionary = fromTypeSafeDictionary.Create(); | |||
toGroup.Add(type, toEntitiesDictionary); | |||
} | |||
//update GroupsPerEntity | |||
if (_groupsPerEntity.TryGetValue(type, out var groupedGroup) == false) | |||
groupedGroup = _groupsPerEntity[type] = new FasterDictionary<ExclusiveGroupStruct, ITypeSafeDictionary>(); | |||
groupedGroup[groupId] = toEntitiesDictionary; | |||
return toEntitiesDictionary; | |||
} | |||
static ITypeSafeDictionary GetTypeSafeDictionary | |||
(uint groupID, FasterDictionary<RefWrapperType, ITypeSafeDictionary> @group, RefWrapperType refWrapper) | |||
{ | |||
if (@group.TryGetValue(refWrapper, out ITypeSafeDictionary fromTypeSafeDictionary) == false) | |||
{ | |||
throw new ECSException("no group found: ".FastConcat(groupID)); | |||
} | |||
return fromTypeSafeDictionary; | |||
} | |||
public Consumer<T> GenerateConsumer<T>(ExclusiveGroup group, string name, uint capacity) where T : unmanaged, | |||
IEntityStruct | |||
void RemoveEntitiesFromGroup(ExclusiveGroupStruct groupID, in PlatformProfiler profiler) | |||
{ | |||
return _entitiesStream.GenerateConsumer<T>(group, name, capacity); | |||
if (_groupEntityComponentsDB.TryGetValue(groupID, out var dictionariesOfEntities)) | |||
{ | |||
foreach (FasterDictionary<RefWrapperType, ITypeSafeDictionary>.KeyValuePairFast dictionaryOfEntities | |||
in dictionariesOfEntities) | |||
{ | |||
dictionaryOfEntities.Value.ExecuteEnginesRemoveCallbacks(_reactiveEnginesAddRemove, profiler | |||
, new ExclusiveGroupStruct(groupID)); | |||
dictionaryOfEntities.Value.FastClear(); | |||
var groupsOfEntityType = | |||
_groupsPerEntity[dictionaryOfEntities.Key]; | |||
groupsOfEntityType[groupID].FastClear(); | |||
} | |||
} | |||
} | |||
//one datastructure rule them all: | |||
//split by group | |||
//split by type per group. It's possible to get all the entities of a give type T per group thanks | |||
//to the FasterDictionary capabilities OR it's possible to get a specific entityView indexed by | |||
//to the FasterDictionary capabilities OR it's possible to get a specific entityComponent indexed by | |||
//ID. This ID doesn't need to be the EGID, it can be just the entityID | |||
//for each group id, save a dictionary indexed by entity type of entities indexed by id | |||
//ITypeSafeDictionary = Key = entityID, Value = EntityStruct | |||
readonly FasterDictionary<uint, FasterDictionary<RefWrapper<Type>, ITypeSafeDictionary>> _groupEntityViewsDB; | |||
// group EntityComponentType entityID, EntityComponent | |||
internal readonly FasterDictionary<ExclusiveGroupStruct, FasterDictionary<RefWrapperType, ITypeSafeDictionary>> | |||
_groupEntityComponentsDB; | |||
//for each entity view type, return the groups (dictionary of entities indexed by entity id) where they are | |||
//found indexed by group id | |||
//EntityViewType //groupID //entityID, EntityStruct | |||
readonly FasterDictionary<RefWrapper<Type>, FasterDictionary<uint, ITypeSafeDictionary>> _groupsPerEntity; | |||
//found indexed by group id. TypeSafeDictionary are never created, they instead point to the ones hold | |||
//by _groupEntityComponentsDB | |||
// <EntityComponentType <groupID <entityID, EntityComponent>>> | |||
internal readonly FasterDictionary<RefWrapperType, FasterDictionary<ExclusiveGroupStruct, ITypeSafeDictionary>> | |||
_groupsPerEntity; | |||
readonly EntitiesDB _entitiesDB; | |||
readonly EntitiesStream _entitiesStream; | |||
//The filters stored for each component and group | |||
internal readonly FasterDictionary<RefWrapperType, FasterDictionary<ExclusiveGroupStruct, GroupFilters>> | |||
_groupFilters; | |||
readonly EntitiesDB _entitiesDB; | |||
EntitiesDB IUnitTestingInterface.entitiesForTesting => _entitiesDB; | |||
} | |||
public interface IUnitTestingInterface | |||
{ | |||
EntitiesDB entitiesForTesting { get; } | |||
} | |||
} |
@@ -1,5 +1,6 @@ | |||
using System.Collections.Generic; | |||
using Svelto.DataStructures; | |||
using System; | |||
using System.Collections.Generic; | |||
using Svelto.Common; | |||
namespace Svelto.ECS | |||
{ | |||
@@ -9,49 +10,58 @@ namespace Svelto.ECS | |||
{ | |||
public GenericEntityFactory(EnginesRoot weakReference) | |||
{ | |||
_enginesRoot = new WeakReference<EnginesRoot>(weakReference); | |||
_enginesRoot = new Svelto.DataStructures.WeakReference<EnginesRoot>(weakReference); | |||
} | |||
public EntityStructInitializer BuildEntity<T>(uint entityID, | |||
ExclusiveGroup.ExclusiveGroupStruct groupStructId, IEnumerable<object> implementors = null) | |||
public EntityComponentInitializer BuildEntity<T> | |||
(uint entityID, BuildGroup groupStructId, IEnumerable<object> implementors = null) | |||
where T : IEntityDescriptor, new() | |||
{ | |||
return _enginesRoot.Target.BuildEntity(new EGID(entityID, groupStructId), | |||
EntityDescriptorTemplate<T>.descriptor.entitiesToBuild, implementors); | |||
return _enginesRoot.Target.BuildEntity(new EGID(entityID, groupStructId) | |||
, EntityDescriptorTemplate<T>.descriptor.componentsToBuild | |||
, TypeCache<T>.type, implementors); | |||
} | |||
public EntityStructInitializer BuildEntity<T>(EGID egid, IEnumerable<object> implementors = null) | |||
public EntityComponentInitializer BuildEntity<T>(EGID egid, IEnumerable<object> implementors = null) | |||
where T : IEntityDescriptor, new() | |||
{ | |||
return _enginesRoot.Target.BuildEntity(egid, | |||
EntityDescriptorTemplate<T>.descriptor.entitiesToBuild, implementors); | |||
return _enginesRoot.Target.BuildEntity( | |||
egid, EntityDescriptorTemplate<T>.descriptor.componentsToBuild, TypeCache<T>.type, implementors); | |||
} | |||
public EntityStructInitializer BuildEntity<T>(EGID egid, T entityDescriptor, | |||
IEnumerable<object> implementors) | |||
where T : IEntityDescriptor | |||
public EntityComponentInitializer BuildEntity<T> | |||
(EGID egid, T entityDescriptor, IEnumerable<object> implementors) where T : IEntityDescriptor | |||
{ | |||
return _enginesRoot.Target.BuildEntity(egid, entityDescriptor.entitiesToBuild, implementors); | |||
return _enginesRoot.Target.BuildEntity(egid, entityDescriptor.componentsToBuild, TypeCache<T>.type, implementors); | |||
} | |||
public EntityStructInitializer BuildEntity<T>(uint entityID, | |||
ExclusiveGroup.ExclusiveGroupStruct groupStructId, T descriptorEntity, IEnumerable<object> implementors) | |||
#if UNITY_NATIVE | |||
public NativeEntityFactory ToNative<T>(string memberName) where T : IEntityDescriptor, new() | |||
{ | |||
return _enginesRoot.Target.ProvideNativeEntityFactoryQueue<T>(memberName); | |||
} | |||
#endif | |||
public EntityComponentInitializer BuildEntity<T> | |||
(uint entityID, BuildGroup groupStructId, T descriptorEntity, IEnumerable<object> implementors) | |||
where T : IEntityDescriptor | |||
{ | |||
return _enginesRoot.Target.BuildEntity(new EGID(entityID, groupStructId), | |||
descriptorEntity.entitiesToBuild, | |||
implementors); | |||
return _enginesRoot.Target.BuildEntity(new EGID(entityID, groupStructId) | |||
, descriptorEntity.componentsToBuild, TypeCache<T>.type, implementors); | |||
} | |||
public void PreallocateEntitySpace<T>(ExclusiveGroup.ExclusiveGroupStruct groupStructId, uint size) | |||
public void PreallocateEntitySpace<T>(ExclusiveGroupStruct groupStructId, uint size) | |||
where T : IEntityDescriptor, new() | |||
{ | |||
_enginesRoot.Target.Preallocate<T>(groupStructId, size); | |||
} | |||
public EntityComponentInitializer BuildEntity(EGID egid, IComponentBuilder[] componentsToBuild, Type type, IEnumerable<object> implementors = null) | |||
{ | |||
return _enginesRoot.Target.BuildEntity(egid, componentsToBuild, type, implementors); | |||
} | |||
//enginesRoot is a weakreference because GenericEntityStreamConsumerFactory can be injected inside | |||
//engines of other enginesRoot | |||
readonly WeakReference<EnginesRoot> _enginesRoot; | |||
//engines of other enginesRoot | |||
readonly Svelto.DataStructures.WeakReference<EnginesRoot> _enginesRoot; | |||
} | |||
} | |||
} |
@@ -1,6 +1,8 @@ | |||
using System.Diagnostics; | |||
using System; | |||
using System.Runtime.CompilerServices; | |||
using Svelto.Common; | |||
using Svelto.DataStructures; | |||
using Svelto.ECS.Internal; | |||
namespace Svelto.ECS | |||
{ | |||
@@ -8,17 +10,17 @@ namespace Svelto.ECS | |||
{ | |||
/// <summary> | |||
/// todo: EnginesRoot was a weakreference to give the change to inject | |||
/// entityfunctions from other engines root. It probably should be reverted | |||
/// entity functions from other engines root. It probably should be reverted | |||
/// </summary> | |||
sealed class GenericEntityFunctions : IEntityFunctions | |||
class GenericEntityFunctions : IEntityFunctions | |||
{ | |||
internal GenericEntityFunctions(EnginesRoot weakReference) | |||
{ | |||
_enginesRoot = new WeakReference<EnginesRoot>(weakReference); | |||
_enginesRoot = new Svelto.DataStructures.WeakReference<EnginesRoot>(weakReference); | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public void RemoveEntity<T>(uint entityID, ExclusiveGroup.ExclusiveGroupStruct groupID) where T : | |||
public void RemoveEntity<T>(uint entityID, BuildGroup groupID) where T : | |||
IEntityDescriptor, new() | |||
{ | |||
RemoveEntity<T>(new EGID(entityID, groupID)); | |||
@@ -27,40 +29,92 @@ namespace Svelto.ECS | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public void RemoveEntity<T>(EGID entityEGID) where T : IEntityDescriptor, new() | |||
{ | |||
_enginesRoot.Target.CheckRemoveEntityID(entityEGID); | |||
DBC.ECS.Check.Require(entityEGID.groupID != 0, "invalid group detected"); | |||
var descriptorComponentsToBuild = EntityDescriptorTemplate<T>.descriptor.componentsToBuild; | |||
_enginesRoot.Target.CheckRemoveEntityID(entityEGID, TypeCache<T>.type); | |||
_enginesRoot.Target.QueueEntitySubmitOperation<T>( | |||
new EntitySubmitOperation(EntitySubmitOperationType.Remove, entityEGID, entityEGID, | |||
EntityDescriptorTemplate<T>.descriptor.entitiesToBuild)); | |||
descriptorComponentsToBuild)); | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public void RemoveGroupAndEntities(ExclusiveGroup.ExclusiveGroupStruct groupID) | |||
public void RemoveEntitiesFromGroup(BuildGroup groupID) | |||
{ | |||
DBC.ECS.Check.Require(groupID != 0, "invalid group detected"); | |||
_enginesRoot.Target.RemoveGroupID(groupID); | |||
_enginesRoot.Target.QueueEntitySubmitOperation( | |||
new EntitySubmitOperation(EntitySubmitOperationType.RemoveGroup, new EGID(0, groupID), new EGID())); | |||
} | |||
// [MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
// void RemoveAllEntities<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 SwapEntityGroup<T>(uint entityID, ExclusiveGroup.ExclusiveGroupStruct fromGroupID, | |||
ExclusiveGroup.ExclusiveGroupStruct toGroupID) | |||
public void SwapEntitiesInGroup<T>(BuildGroup fromGroupID, BuildGroup toGroupID) | |||
where T : IEntityDescriptor, new() | |||
{ | |||
if (_enginesRoot.Target._groupEntityComponentsDB.TryGetValue( | |||
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())]; | |||
dictionary.KeysEvaluator((key) => | |||
{ | |||
_enginesRoot.Target.CheckRemoveEntityID(new EGID(key, fromGroupID), TypeCache<T>.type); | |||
_enginesRoot.Target.CheckAddEntityID(new EGID(key, toGroupID), TypeCache<T>.type); | |||
}); | |||
#endif | |||
_enginesRoot.Target.QueueEntitySubmitOperation( | |||
new EntitySubmitOperation(EntitySubmitOperationType.SwapGroup, new EGID(0, fromGroupID) | |||
, new EGID(0, toGroupID))); | |||
} | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public void SwapEntityGroup<T>(uint entityID, BuildGroup fromGroupID, | |||
BuildGroup toGroupID) | |||
where T : IEntityDescriptor, new() | |||
{ | |||
SwapEntityGroup<T>(new EGID(entityID, fromGroupID), toGroupID); | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public void SwapEntityGroup<T>(EGID fromID, ExclusiveGroup.ExclusiveGroupStruct toGroupID) | |||
public void SwapEntityGroup<T>(EGID fromID, BuildGroup toGroupID) | |||
where T : IEntityDescriptor, new() | |||
{ | |||
SwapEntityGroup<T>(fromID, new EGID(fromID.entityID, (uint) toGroupID)); | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public void SwapEntityGroup<T>(EGID fromID, ExclusiveGroup.ExclusiveGroupStruct toGroupID | |||
, ExclusiveGroup.ExclusiveGroupStruct mustBeFromGroup) | |||
public void SwapEntityGroup<T>(EGID fromID, BuildGroup toGroupID | |||
, BuildGroup mustBeFromGroup) | |||
where T : IEntityDescriptor, new() | |||
{ | |||
if (fromID.groupID != mustBeFromGroup) | |||
@@ -71,7 +125,7 @@ namespace Svelto.ECS | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public void SwapEntityGroup<T>(EGID fromID, EGID toID | |||
, ExclusiveGroup.ExclusiveGroupStruct mustBeFromGroup) | |||
, BuildGroup mustBeFromGroup) | |||
where T : IEntityDescriptor, new() | |||
{ | |||
if (fromID.groupID != mustBeFromGroup) | |||
@@ -80,47 +134,65 @@ namespace Svelto.ECS | |||
SwapEntityGroup<T>(fromID, toID); | |||
} | |||
#if UNITY_NATIVE | |||
public NativeEntityRemove ToNativeRemove<T>(string memberName) where T : IEntityDescriptor, new() | |||
{ | |||
return _enginesRoot.Target.ProvideNativeEntityRemoveQueue<T>(memberName); | |||
} | |||
public NativeEntitySwap ToNativeSwap<T>(string memberName) where T : IEntityDescriptor, new() | |||
{ | |||
return _enginesRoot.Target.ProvideNativeEntitySwapQueue<T>(memberName); | |||
} | |||
#endif | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public void SwapEntityGroup<T>(EGID fromID, EGID toID) | |||
where T : IEntityDescriptor, new() | |||
{ | |||
_enginesRoot.Target.CheckRemoveEntityID(fromID); | |||
_enginesRoot.Target.CheckAddEntityID(toID); | |||
DBC.ECS.Check.Require(fromID.groupID != 0, "invalid group detected"); | |||
DBC.ECS.Check.Require(toID.groupID != 0, "invalid group detected"); | |||
_enginesRoot.Target.QueueEntitySubmitOperation<T>( | |||
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, EntityDescriptorTemplate<T>.descriptor.entitiesToBuild)); | |||
fromID, toID, descriptorComponentsToBuild)); | |||
} | |||
//enginesRoot is a weakreference because GenericEntityStreamConsumerFactory can be injected inside | |||
//engines of other enginesRoot | |||
readonly WeakReference<EnginesRoot> _enginesRoot; | |||
//engines of other enginesRoot | |||
readonly Svelto.DataStructures.WeakReference<EnginesRoot> _enginesRoot; | |||
} | |||
void QueueEntitySubmitOperation(EntitySubmitOperation entitySubmitOperation) | |||
{ | |||
#if DEBUG && !PROFILER | |||
entitySubmitOperation.trace = new StackFrame(1, true); | |||
#if DEBUG && !PROFILE_SVELTO | |||
entitySubmitOperation.trace = new System.Diagnostics.StackFrame(1, true); | |||
#endif | |||
_entitiesOperations.Add((ulong) entitySubmitOperation.fromID, entitySubmitOperation); | |||
} | |||
void QueueEntitySubmitOperation<T>(EntitySubmitOperation entitySubmitOperation) where T : IEntityDescriptor | |||
{ | |||
#if DEBUG && !PROFILER | |||
entitySubmitOperation.trace = new StackFrame(1, true); | |||
#if DEBUG && !PROFILE_SVELTO | |||
entitySubmitOperation.trace = new System.Diagnostics.StackFrame(1, true); | |||
if (_entitiesOperations.TryGetValue((ulong) entitySubmitOperation.fromID, out var entitySubmitedOperation)) | |||
{ | |||
if (entitySubmitedOperation != entitySubmitOperation) | |||
throw new ECSException("Only one entity operation per submission is allowed" | |||
.FastConcat(" entityViewType: ") | |||
.FastConcat(typeof(T).Name) | |||
.FastConcat(" submission type ", entitySubmitOperation.type.ToString(), | |||
.FastConcat(" entityComponentType: ") | |||
.FastConcat(typeof(T).Name) | |||
.FastConcat(" submission type ", entitySubmitOperation.type.ToString(), | |||
" from ID: ", entitySubmitOperation.fromID.entityID.ToString()) | |||
.FastConcat(" previous operation type: ", | |||
.FastConcat(" previous operation type: ", | |||
_entitiesOperations[(ulong) entitySubmitOperation.fromID].type | |||
.ToString())); | |||
.ToString())); | |||
} | |||
else | |||
#endif | |||
@@ -2,7 +2,6 @@ | |||
using Svelto.Common; | |||
using Svelto.DataStructures; | |||
using Svelto.ECS.Internal; | |||
using Svelto.ECS.Schedulers; | |||
namespace Svelto.ECS | |||
{ | |||
@@ -10,7 +9,7 @@ namespace Svelto.ECS | |||
{ | |||
readonly FasterList<EntitySubmitOperation> _transientEntitiesOperations; | |||
void SubmitEntityViews() | |||
void SubmitEntityComponents() | |||
{ | |||
using (var profiler = new PlatformProfiler("Svelto.ECS - Entities Submission")) | |||
{ | |||
@@ -18,29 +17,40 @@ namespace Svelto.ECS | |||
do | |||
{ | |||
SingleSubmission(profiler); | |||
} while ((_groupedEntityToAdd.currentEntitiesCreatedPerGroup.Count > 0 || | |||
} while ((_groupedEntityToAdd.currentEntitiesCreatedPerGroup.count > 0 || | |||
_entitiesOperations.Count > 0) && ++iterations < 5); | |||
#if DEBUG && !PROFILER | |||
#if DEBUG && !PROFILE_SVELTO | |||
if (iterations == 5) | |||
throw new ECSException("possible circular submission detected"); | |||
#endif | |||
} | |||
} | |||
/// <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> | |||
void SingleSubmission(in PlatformProfiler profiler) | |||
{ | |||
#if UNITY_NATIVE | |||
NativeOperationSubmission(profiler); | |||
#endif | |||
ClearChecks(); | |||
bool entitiesAreSubmitted = false; | |||
if (_entitiesOperations.Count > 0) | |||
{ | |||
using (profiler.Sample("Remove and Swap operations")) | |||
{ | |||
_transientEntitiesOperations.FastClear(); | |||
var entitySubmitOperations = _entitiesOperations.GetValuesArray(out var count); | |||
_transientEntitiesOperations.AddRange(entitySubmitOperations, count); | |||
_entitiesOperations.CopyValuesTo(_transientEntitiesOperations); | |||
_entitiesOperations.FastClear(); | |||
var entitiesOperations = _transientEntitiesOperations.ToArrayFast(); | |||
for (var i = 0; i < _transientEntitiesOperations.Count; i++) | |||
EntitySubmitOperation[] entitiesOperations = _transientEntitiesOperations.ToArrayFast(out var count); | |||
for (var i = 0; i < count; i++) | |||
{ | |||
try | |||
{ | |||
@@ -48,57 +58,74 @@ namespace Svelto.ECS | |||
{ | |||
case EntitySubmitOperationType.Swap: | |||
MoveEntityFromAndToEngines(entitiesOperations[i].builders, | |||
entitiesOperations[i].fromID, | |||
entitiesOperations[i].toID); | |||
entitiesOperations[i].fromID, entitiesOperations[i].toID); | |||
break; | |||
case EntitySubmitOperationType.Remove: | |||
MoveEntityFromAndToEngines(entitiesOperations[i].builders, | |||
entitiesOperations[i].fromID, null); | |||
break; | |||
case EntitySubmitOperationType.RemoveGroup: | |||
RemoveGroupAndEntitiesFromDB( | |||
RemoveEntitiesFromGroup( | |||
entitiesOperations[i].fromID.groupID, profiler); | |||
break; | |||
case EntitySubmitOperationType.SwapGroup: | |||
SwapEntitiesBetweenGroups(entitiesOperations[i].fromID.groupID, | |||
entitiesOperations[i].toID.groupID, profiler); | |||
break; | |||
} | |||
} | |||
catch (Exception e) | |||
catch | |||
{ | |||
var str = "Crash while executing Entity Operation " | |||
.FastConcat(entitiesOperations[i].type.ToString()); | |||
throw new ECSException(str.FastConcat(" ") | |||
#if DEBUG && !PROFILER | |||
.FastConcat(entitiesOperations[i].trace.ToString()) | |||
Svelto.Console.LogError(str.FastConcat(" ") | |||
#if DEBUG && !PROFILE_SVELTO | |||
.FastConcat(entitiesOperations[i].trace.ToString()) | |||
#endif | |||
, e); | |||
); | |||
throw; | |||
} | |||
} | |||
} | |||
entitiesAreSubmitted = true; | |||
} | |||
_groupedEntityToAdd.Swap(); | |||
if (_groupedEntityToAdd.otherEntitiesCreatedPerGroup.Count > 0) | |||
if (_groupedEntityToAdd.otherEntitiesCreatedPerGroup.count > 0) | |||
{ | |||
using (profiler.Sample("Add operations")) | |||
{ | |||
try | |||
{ | |||
AddEntityViewsToTheDBAndSuitableEngines(profiler); | |||
AddEntityComponentsToTheDBAndSuitableEngines(profiler); | |||
} | |||
finally | |||
{ | |||
using (profiler.Sample("clear operates double buffering")) | |||
using (profiler.Sample("clear 6operates double buffering")) | |||
{ | |||
//other can be cleared now, but let's avoid deleting the dictionary every time | |||
_groupedEntityToAdd.ClearOther(); | |||
} | |||
} | |||
} | |||
entitiesAreSubmitted = true; | |||
} | |||
if (entitiesAreSubmitted) | |||
{ | |||
var enginesCount = _reactiveEnginesSubmission.count; | |||
for (int i = 0; i < enginesCount; i++) | |||
_reactiveEnginesSubmission[i].EntitiesSubmitted(); | |||
} | |||
} | |||
void AddEntityViewsToTheDBAndSuitableEngines(in PlatformProfiler profiler) | |||
void AddEntityComponentsToTheDBAndSuitableEngines(in PlatformProfiler profiler) | |||
{ | |||
using (profiler.Sample("Add entities to database")) | |||
{ | |||
@@ -106,57 +133,46 @@ namespace Svelto.ECS | |||
foreach (var groupOfEntitiesToSubmit in _groupedEntityToAdd.otherEntitiesCreatedPerGroup) | |||
{ | |||
var groupID = groupOfEntitiesToSubmit.Key; | |||
var groupDB = GetOrCreateGroup(groupID, profiler); | |||
//if the group doesn't exist in the current DB let's create it first | |||
if (_groupEntityViewsDB.TryGetValue(groupID, out var groupDB) == false) | |||
groupDB = _groupEntityViewsDB[groupID] = | |||
new FasterDictionary<RefWrapper<Type>, ITypeSafeDictionary>(); | |||
//add the entityViews in the group | |||
foreach (var entityViewsToSubmit in _groupedEntityToAdd.other[groupID]) | |||
//add the entityComponents in the group | |||
foreach (var entityComponentsToSubmit in _groupedEntityToAdd.other[groupID]) | |||
{ | |||
var type = entityViewsToSubmit.Key; | |||
var typeSafeDictionary = entityViewsToSubmit.Value; | |||
var type = entityComponentsToSubmit.Key; | |||
var targetTypeSafeDictionary = entityComponentsToSubmit.Value; | |||
var wrapper = new RefWrapperType(type); | |||
var wrapper = new RefWrapper<Type>(type); | |||
if (groupDB.TryGetValue(wrapper, out var dbDic) == false) | |||
dbDic = groupDB[wrapper] = typeSafeDictionary.Create(); | |||
ITypeSafeDictionary dbDic = GetOrCreateTypeSafeDictionary(groupID, groupDB, wrapper, | |||
targetTypeSafeDictionary); | |||
//Fill the DB with the entity views generate this frame. | |||
dbDic.AddEntitiesFromDictionary(typeSafeDictionary, groupID); | |||
if (_groupsPerEntity.TryGetValue(wrapper, out var groupedGroup) == false) | |||
groupedGroup = _groupsPerEntity[wrapper] = | |||
new FasterDictionary<uint, ITypeSafeDictionary>(); | |||
groupedGroup[groupID] = dbDic; | |||
//Fill the DB with the entity components generate this frame. | |||
dbDic.AddEntitiesFromDictionary(targetTypeSafeDictionary, groupID); | |||
} | |||
} | |||
} | |||
//then submit everything in the engines, so that the DB is up to date with all the entity views and struct | |||
//then submit everything in the engines, so that the DB is up to date with all the entity components | |||
//created by the entity built | |||
using (profiler.Sample("Add entities to engines")) | |||
{ | |||
foreach (var groupToSubmit in _groupedEntityToAdd.otherEntitiesCreatedPerGroup) | |||
{ | |||
var groupID = groupToSubmit.Key; | |||
var groupDB = _groupEntityComponentsDB[groupID]; | |||
var groupDB = _groupEntityViewsDB[groupID]; | |||
foreach (var entityViewsToSubmit in _groupedEntityToAdd.other[groupID]) | |||
foreach (var entityComponentsToSubmit in _groupedEntityToAdd.other[groupID]) | |||
{ | |||
var realDic = groupDB[new RefWrapper<Type>(entityViewsToSubmit.Key)]; | |||
var realDic = groupDB[new RefWrapperType(entityComponentsToSubmit.Key)]; | |||
entityViewsToSubmit.Value.AddEntitiesToEngines(_reactiveEnginesAddRemove, realDic, in profiler, | |||
new ExclusiveGroup.ExclusiveGroupStruct(groupToSubmit.Key)); | |||
entityComponentsToSubmit.Value.ExecuteEnginesAddOrSwapCallbacks(_reactiveEnginesAddRemove, realDic, | |||
null, new ExclusiveGroupStruct(groupToSubmit.Key), in profiler); | |||
} | |||
} | |||
} | |||
} | |||
DoubleBufferedEntitiesToAdd _groupedEntityToAdd; | |||
readonly IEntitySubmissionScheduler _scheduler; | |||
readonly FasterDictionary<ulong, EntitySubmitOperation> _entitiesOperations; | |||
readonly DoubleBufferedEntitiesToAdd _groupedEntityToAdd; | |||
readonly ThreadSafeDictionary<ulong, EntitySubmitOperation> _entitiesOperations; | |||
} | |||
} |
@@ -0,0 +1,154 @@ | |||
using System; | |||
using System.Threading; | |||
using Svelto.DataStructures; | |||
using Svelto.ECS.Internal; | |||
namespace Svelto.ECS | |||
{ | |||
public partial class EntitiesDB | |||
{ | |||
public LocalFasterReadOnlyList<ExclusiveGroupStruct> FindGroups<T1>() where T1 : IEntityComponent | |||
{ | |||
FasterList<ExclusiveGroupStruct> result = groups.Value; | |||
result.FastClear(); | |||
if (groupsPerEntity.TryGetValue(TypeRefWrapper<T1>.wrapper | |||
, out FasterDictionary<ExclusiveGroupStruct, ITypeSafeDictionary> result1) == false) | |||
return result; | |||
var result1Count = result1.count; | |||
var fasterDictionaryNodes1 = result1.unsafeKeys; | |||
for (int j = 0; j < result1Count; j++) | |||
{ | |||
result.Add(new ExclusiveGroupStruct(fasterDictionaryNodes1[j].key)); | |||
} | |||
return result; | |||
} | |||
public LocalFasterReadOnlyList<ExclusiveGroupStruct> FindGroups<T1, T2>() where T1 : IEntityComponent where T2 : IEntityComponent | |||
{ | |||
FasterList<ExclusiveGroupStruct> result = groups.Value; | |||
result.FastClear(); | |||
if (groupsPerEntity.TryGetValue(TypeRefWrapper<T1>.wrapper | |||
, out FasterDictionary<ExclusiveGroupStruct, ITypeSafeDictionary> result1) == false) | |||
return result; | |||
if (groupsPerEntity.TryGetValue(TypeRefWrapper<T2>.wrapper | |||
, out FasterDictionary<ExclusiveGroupStruct, ITypeSafeDictionary> result2) == false) | |||
return result; | |||
var result1Count = result1.count; | |||
var result2Count = result2.count; | |||
var fasterDictionaryNodes1 = result1.unsafeKeys; | |||
var fasterDictionaryNodes2 = result2.unsafeKeys; | |||
for (int i = 0; i < result1Count; i++) | |||
{ | |||
var groupID = fasterDictionaryNodes1[i].key; | |||
for (int j = 0; j < result2Count; j++) | |||
{ | |||
//if the same group is found used with both T1 and T2 | |||
if (groupID == fasterDictionaryNodes2[j].key) | |||
{ | |||
result.Add(new ExclusiveGroupStruct(groupID)); | |||
break; | |||
} | |||
} | |||
} | |||
return result; | |||
} | |||
/// <summary> | |||
/// Remember that this operation os O(N*M*P) where N,M,P are the number of groups where each component | |||
/// is found. | |||
/// </summary> | |||
/// <typeparam name="T1"></typeparam> | |||
/// <typeparam name="T2"></typeparam> | |||
/// <typeparam name="T3"></typeparam> | |||
/// <returns></returns> | |||
public LocalFasterReadOnlyList<ExclusiveGroupStruct> FindGroups<T1, T2, T3>() | |||
where T1 : IEntityComponent where T2 : IEntityComponent where T3 : IEntityComponent | |||
{ | |||
FasterList<ExclusiveGroupStruct> result = groups.Value; | |||
result.FastClear(); | |||
if (groupsPerEntity.TryGetValue(TypeRefWrapper<T1>.wrapper | |||
, out FasterDictionary<ExclusiveGroupStruct, ITypeSafeDictionary> groupOfEntities1) == false) | |||
return result; | |||
if (groupsPerEntity.TryGetValue(TypeRefWrapper<T2>.wrapper | |||
, out FasterDictionary<ExclusiveGroupStruct, ITypeSafeDictionary> groupOfEntities2) == false) | |||
return result; | |||
if (groupsPerEntity.TryGetValue(TypeRefWrapper<T3>.wrapper | |||
, out FasterDictionary<ExclusiveGroupStruct, ITypeSafeDictionary> groupOfEntities3) == false) | |||
return result; | |||
var result1Count = groupOfEntities1.count; | |||
var result2Count = groupOfEntities2.count; | |||
var result3Count = groupOfEntities3.count; | |||
var fasterDictionaryNodes1 = groupOfEntities1.unsafeKeys; | |||
var fasterDictionaryNodes2 = groupOfEntities2.unsafeKeys; | |||
var fasterDictionaryNodes3 = groupOfEntities3.unsafeKeys; | |||
// | |||
//TODO: I have to find once for ever a solution to be sure that the entities in the groups match | |||
//Currently this returns group where the entities are found, but the entities may not match in these | |||
//groups. | |||
//Checking the size of the entities is an early check, needed, but not sufficient, as entities components may | |||
//coincidentally match in number but not from which entities they are generated | |||
//foreach group where T1 is found | |||
for (int i = 0; i < result1Count; i++) | |||
{ | |||
var groupT1 = fasterDictionaryNodes1[i].key; | |||
//foreach group where T2 is found | |||
for (int j = 0; j < result2Count; ++j) | |||
{ | |||
if (groupT1 == fasterDictionaryNodes2[j].key) | |||
{ | |||
//foreach group where T3 is found | |||
for (int k = 0; k < result3Count; ++k) | |||
{ | |||
if (groupT1 == fasterDictionaryNodes3[k].key) | |||
{ | |||
result.Add(new ExclusiveGroupStruct(groupT1)); | |||
break; | |||
} | |||
} | |||
break; | |||
} | |||
} | |||
} | |||
return result; | |||
} | |||
internal FasterDictionary<ExclusiveGroupStruct, ITypeSafeDictionary> FindGroups_INTERNAL(Type type) | |||
{ | |||
if (groupsPerEntity.ContainsKey(new RefWrapperType(type)) == false) | |||
return _emptyDictionary; | |||
return groupsPerEntity[new RefWrapperType(type)]; | |||
} | |||
struct GroupsList | |||
{ | |||
static GroupsList() | |||
{ | |||
groups = new FasterList<ExclusiveGroupStruct>(); | |||
} | |||
static readonly FasterList<ExclusiveGroupStruct> groups; | |||
public static implicit operator FasterList<ExclusiveGroupStruct>(in GroupsList list) | |||
{ | |||
return list.reference; | |||
} | |||
FasterList<ExclusiveGroupStruct> reference => groups; | |||
} | |||
static readonly ThreadLocal<GroupsList> groups = new ThreadLocal<GroupsList>(); | |||
} | |||
} |
@@ -0,0 +1,11 @@ | |||
fileFormatVersion: 2 | |||
guid: 11e0317f53f0374d9924cae6235eacdb | |||
MonoImporter: | |||
externalObjects: {} | |||
serializedVersion: 2 | |||
defaultReferences: [] | |||
executionOrder: 0 | |||
icon: {instanceID: 0} | |||
userData: | |||
assetBundleName: | |||
assetBundleVariant: |
@@ -1,254 +1,263 @@ | |||
#if DEBUG && !PROFILER | |||
#if DEBUG && !PROFILE_SVELTO | |||
#define ENABLE_DEBUG_FUNC | |||
#endif | |||
using System; | |||
using System.Runtime.CompilerServices; | |||
using Svelto.Common; | |||
using Svelto.DataStructures; | |||
using Svelto.ECS.Internal; | |||
namespace Svelto.ECS.Internal | |||
namespace Svelto.ECS | |||
{ | |||
partial class EntitiesDB : IEntitiesDB | |||
public partial class EntitiesDB | |||
{ | |||
internal EntitiesDB( | |||
FasterDictionary<uint, FasterDictionary<RefWrapper<Type>, ITypeSafeDictionary>> groupEntityViewsDB, | |||
FasterDictionary<RefWrapper<Type>, FasterDictionary<uint, ITypeSafeDictionary>> groupsPerEntity, | |||
EntitiesStream entityStream) | |||
internal EntitiesDB(EnginesRoot enginesRoot) | |||
{ | |||
_groupEntityViewsDB = groupEntityViewsDB; | |||
_groupsPerEntity = groupsPerEntity; | |||
_entityStream = entityStream; | |||
_enginesRoot = enginesRoot; | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public ref T QueryUniqueEntity<T>(ExclusiveGroup.ExclusiveGroupStruct group) where T : struct, IEntityStruct | |||
{ | |||
var entities = QueryEntities<T>(group, out var count); | |||
if (count == 0) | |||
throw new ECSException("Unique entity not found '".FastConcat(typeof(T).ToString()).FastConcat("'")); | |||
if (count != 1) | |||
throw new ECSException("Unique entities must be unique! '".FastConcat(typeof(T).ToString()) | |||
.FastConcat("'")); | |||
return ref entities[0]; | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public ref T QueryEntity<T>(EGID entityGID) where T : struct, IEntityStruct | |||
EntityCollection<T> InternalQueryEntities<T>(FasterDictionary<RefWrapperType, ITypeSafeDictionary> entitiesInGroupPerType) | |||
where T : struct, IEntityComponent | |||
{ | |||
T[] array; | |||
if ((array = QueryEntitiesAndIndexInternal<T>(entityGID, out var index)) != null) | |||
return ref array[index]; | |||
uint count = 0; | |||
IBuffer<T> buffer; | |||
if (SafeQueryEntityDictionary<T>(out var typeSafeDictionary, entitiesInGroupPerType) == false) | |||
buffer = RetrieveEmptyEntityComponentArray<T>(); | |||
else | |||
{ | |||
var safeDictionary = (typeSafeDictionary as ITypeSafeDictionary<T>); | |||
buffer = safeDictionary.GetValues(out count); | |||
} | |||
throw new EntityNotFoundException(entityGID, typeof(T)); | |||
return new EntityCollection<T>(buffer, count); | |||
} | |||
public ref T QueryEntity<T>(uint id, ExclusiveGroup.ExclusiveGroupStruct group) where T : struct, IEntityStruct | |||
/// <summary> | |||
/// The QueryEntities<T> follows the rule that entities could always be iterated regardless if they | |||
/// are 0, 1 or N. In case of 0 it returns an empty array. This allows to use the same for iteration | |||
/// regardless the number of entities built. | |||
/// </summary> | |||
/// <param name="groupStructId"></param> | |||
/// <typeparam name="T"></typeparam> | |||
/// <returns></returns> | |||
public EntityCollection<T> QueryEntities<T>(ExclusiveGroupStruct groupStructId) | |||
where T : struct, IEntityComponent | |||
{ | |||
return ref QueryEntity<T>(new EGID(id, group)); | |||
if (groupEntityComponentsDB.TryGetValue(groupStructId, out var entitiesInGroupPerType) == false) | |||
{ | |||
var buffer = RetrieveEmptyEntityComponentArray<T>(); | |||
return new EntityCollection<T>(buffer, 0); | |||
} | |||
return InternalQueryEntities<T>(entitiesInGroupPerType); | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public T[] QueryEntities<T>(ExclusiveGroup.ExclusiveGroupStruct groupStruct, out uint count) | |||
where T : struct, IEntityStruct | |||
public EntityCollection<T1, T2> QueryEntities<T1, T2>(ExclusiveGroupStruct groupStruct) | |||
where T1 : struct, IEntityComponent where T2 : struct, IEntityComponent | |||
{ | |||
uint group = groupStruct; | |||
count = 0; | |||
if (SafeQueryEntityDictionary(group, out TypeSafeDictionary<T> typeSafeDictionary) == false) | |||
return RetrieveEmptyEntityViewArray<T>(); | |||
if (groupEntityComponentsDB.TryGetValue(groupStruct, out var entitiesInGroupPerType) == false) | |||
{ | |||
return new EntityCollection<T1, T2>(new EntityCollection<T1>(RetrieveEmptyEntityComponentArray<T1>(), 0), | |||
new EntityCollection<T2>(RetrieveEmptyEntityComponentArray<T2>(), 0)); | |||
} | |||
var T1entities = InternalQueryEntities<T1>(entitiesInGroupPerType); | |||
var T2entities = InternalQueryEntities<T2>(entitiesInGroupPerType); | |||
#if DEBUG && !PROFILE_SVELTO | |||
if (T1entities.count != T2entities.count) | |||
throw new ECSException("Entity components count do not match in group. Entity 1: ' count: " | |||
.FastConcat(T1entities.count).FastConcat(" ", typeof(T1).ToString()) | |||
.FastConcat("'. Entity 2: ' count: ".FastConcat(T2entities.count) | |||
.FastConcat(" ", typeof(T2).ToString()) | |||
.FastConcat("' group: ", groupStruct.ToName()))); | |||
#endif | |||
return typeSafeDictionary.GetValuesArray(out count); | |||
return new EntityCollection<T1, T2>(T1entities, T2entities); | |||
} | |||
public EntityCollection<T> QueryEntities<T>(ExclusiveGroup.ExclusiveGroupStruct groupStruct) | |||
where T : struct, IEntityStruct | |||
public EntityCollection<T1, T2, T3> QueryEntities<T1, T2, T3>(ExclusiveGroupStruct groupStruct) | |||
where T1 : struct, IEntityComponent where T2 : struct, IEntityComponent where T3 : struct, IEntityComponent | |||
{ | |||
return new EntityCollection<T>(QueryEntities<T>(groupStruct, out var count), count); | |||
} | |||
if (groupEntityComponentsDB.TryGetValue(groupStruct, out var entitiesInGroupPerType) == false) | |||
{ | |||
return new EntityCollection<T1, T2, T3>( | |||
new EntityCollection<T1>(RetrieveEmptyEntityComponentArray<T1>(), 0), | |||
new EntityCollection<T2>(RetrieveEmptyEntityComponentArray<T2>(), 0), | |||
new EntityCollection<T3>(RetrieveEmptyEntityComponentArray<T3>(), 0)); | |||
} | |||
var T1entities = InternalQueryEntities<T1>(entitiesInGroupPerType); | |||
var T2entities = InternalQueryEntities<T2>(entitiesInGroupPerType); | |||
var T3entities = InternalQueryEntities<T3>(entitiesInGroupPerType); | |||
#if DEBUG && !PROFILE_SVELTO | |||
if (T1entities.count != T2entities.count || T2entities.count != T3entities.count) | |||
throw new ECSException("Entity components count do not match in group. Entity 1: " | |||
.FastConcat(typeof(T1).ToString()).FastConcat(" count: ") | |||
.FastConcat(T1entities.count).FastConcat( | |||
" Entity 2: " | |||
.FastConcat(typeof(T2).ToString()).FastConcat(" count: ") | |||
.FastConcat(T2entities.count) | |||
.FastConcat(" Entity 3: ".FastConcat(typeof(T3).ToString())) | |||
.FastConcat(" count: ").FastConcat(T3entities.count))); | |||
#endif | |||
public EntityCollection<T1, T2> QueryEntities<T1, T2>(ExclusiveGroup.ExclusiveGroupStruct groupStruct) | |||
where T1 : struct, IEntityStruct where T2 : struct, IEntityStruct | |||
{ | |||
return new EntityCollection<T1, T2>(QueryEntities<T1, T2>(groupStruct, out var count), count); | |||
return new EntityCollection<T1, T2, T3>(T1entities, T2entities, T3entities); | |||
} | |||
public EntityCollection<T1, T2, T3> QueryEntities<T1, T2, T3>(ExclusiveGroup.ExclusiveGroupStruct groupStruct) | |||
where T1 : struct, IEntityStruct where T2 : struct, IEntityStruct where T3 : struct, IEntityStruct | |||
public EntityCollection<T1, T2, T3, T4> QueryEntities<T1, T2, T3, T4>(ExclusiveGroupStruct groupStruct) | |||
where T1 : struct, IEntityComponent where T2 : struct, IEntityComponent where T3 : struct, IEntityComponent where T4 : struct, IEntityComponent | |||
{ | |||
return new EntityCollection<T1, T2, T3>(QueryEntities<T1, T2, T3>(groupStruct, out var count), count); | |||
if (groupEntityComponentsDB.TryGetValue(groupStruct, out var entitiesInGroupPerType) == false) | |||
{ | |||
return new EntityCollection<T1, T2, T3, T4>( | |||
new EntityCollection<T1>(RetrieveEmptyEntityComponentArray<T1>(), 0), | |||
new EntityCollection<T2>(RetrieveEmptyEntityComponentArray<T2>(), 0), | |||
new EntityCollection<T3>(RetrieveEmptyEntityComponentArray<T3>(), 0), | |||
new EntityCollection<T4>(RetrieveEmptyEntityComponentArray<T4>(), 0)); | |||
} | |||
var T1entities = InternalQueryEntities<T1>(entitiesInGroupPerType); | |||
var T2entities = InternalQueryEntities<T2>(entitiesInGroupPerType); | |||
var T3entities = InternalQueryEntities<T3>(entitiesInGroupPerType); | |||
var T4entities = InternalQueryEntities<T4>(entitiesInGroupPerType); | |||
#if DEBUG && !PROFILE_SVELTO | |||
if (T1entities.count != T2entities.count || T2entities.count != T3entities.count) | |||
throw new ECSException("Entity components count do not match in group. Entity 1: " | |||
.FastConcat(typeof(T1).ToString()).FastConcat(" count: ") | |||
.FastConcat(T1entities.count).FastConcat( | |||
" Entity 2: " | |||
.FastConcat(typeof(T2).ToString()).FastConcat(" count: ") | |||
.FastConcat(T2entities.count) | |||
.FastConcat(" Entity 3: ".FastConcat(typeof(T3).ToString())) | |||
.FastConcat(" count: ").FastConcat(T3entities.count))); | |||
#endif | |||
return new EntityCollection<T1, T2, T3, T4>(T1entities, T2entities, T3entities, T4entities); | |||
} | |||
public EntityCollections<T> QueryEntities<T>(ExclusiveGroup[] groups) where T : struct, IEntityStruct | |||
public GroupsEnumerable<T> QueryEntities<T> | |||
(in LocalFasterReadOnlyList<ExclusiveGroupStruct> groups) where T : struct, IEntityComponent | |||
{ | |||
return new EntityCollections<T>(this, groups); | |||
return new GroupsEnumerable<T>(this, groups); | |||
} | |||
public EntityCollections<T1, T2> QueryEntities<T1, T2>(ExclusiveGroup[] groups) | |||
where T1 : struct, IEntityStruct where T2 : struct, IEntityStruct | |||
public GroupsEnumerable<T1, T2> QueryEntities<T1, T2>(in LocalFasterReadOnlyList<ExclusiveGroupStruct> groups) | |||
where T1 : struct, IEntityComponent where T2 : struct, IEntityComponent | |||
{ | |||
return new EntityCollections<T1, T2>(this, groups); | |||
return new GroupsEnumerable<T1, T2>(this, groups); | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public (T1[], T2[]) QueryEntities<T1, T2>(ExclusiveGroup.ExclusiveGroupStruct groupStruct, out uint count) | |||
where T1 : struct, IEntityStruct | |||
where T2 : struct, IEntityStruct | |||
public GroupsEnumerable<T1, T2, T3> QueryEntities<T1, T2, T3>(in LocalFasterReadOnlyList<ExclusiveGroupStruct> groups) | |||
where T1 : struct, IEntityComponent where T2 : struct, IEntityComponent where T3 : struct, IEntityComponent | |||
{ | |||
var T1entities = QueryEntities<T1>(groupStruct, out var countCheck); | |||
var T2entities = QueryEntities<T2>(groupStruct, out count); | |||
if (count != countCheck) | |||
{ | |||
throw new ECSException("Entity views count do not match in group. Entity 1: ' count: " | |||
.FastConcat(countCheck) | |||
.FastConcat(typeof(T1).ToString()) | |||
.FastConcat("'. Entity 2: ' count: ".FastConcat(count) | |||
.FastConcat(typeof(T2).ToString()) | |||
.FastConcat("'"))); | |||
} | |||
return (T1entities, T2entities); | |||
return new GroupsEnumerable<T1, T2, T3>(this, groups); | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public (T1[], T2[], T3[]) QueryEntities | |||
<T1, T2, T3>(ExclusiveGroup.ExclusiveGroupStruct groupStruct, out uint count) | |||
where T1 : struct, IEntityStruct where T2 : struct, IEntityStruct where T3 : struct, IEntityStruct | |||
public GroupsEnumerable<T1, T2, T3, T4> QueryEntities<T1, T2, T3, T4> | |||
(in LocalFasterReadOnlyList<ExclusiveGroupStruct> groups) | |||
where T1 : struct, IEntityComponent where T2 : struct, IEntityComponent | |||
where T3 : struct, IEntityComponent where T4 : struct, IEntityComponent | |||
{ | |||
var T1entities = QueryEntities<T1>(groupStruct, out var countCheck1); | |||
var T2entities = QueryEntities<T2>(groupStruct, out var countCheck2); | |||
var T3entities = QueryEntities<T3>(groupStruct, out count); | |||
if (count != countCheck1 || count != countCheck2) | |||
throw new ECSException("Entity views count do not match in group. Entity 1: " | |||
.FastConcat(typeof(T1).ToString()).FastConcat(" count: ").FastConcat(countCheck1).FastConcat( | |||
" Entity 2: ".FastConcat(typeof(T2).ToString()) | |||
.FastConcat(" count: ").FastConcat(countCheck2) | |||
.FastConcat(" Entity 3: ".FastConcat(typeof(T3).ToString())).FastConcat(" count: ") | |||
.FastConcat(count))); | |||
return (T1entities, T2entities, T3entities); | |||
return new GroupsEnumerable<T1, T2, T3, T4>(this, groups); | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public EGIDMapper<T> QueryMappedEntities<T>(ExclusiveGroup.ExclusiveGroupStruct groupStructId) | |||
where T : struct, IEntityStruct | |||
public EGIDMapper<T> QueryMappedEntities<T>(ExclusiveGroupStruct groupStructId) | |||
where T : struct, IEntityComponent | |||
{ | |||
if (SafeQueryEntityDictionary(groupStructId, out TypeSafeDictionary<T> typeSafeDictionary) == false) | |||
throw new EntityGroupNotFoundException(groupStructId, typeof(T)); | |||
if (SafeQueryEntityDictionary<T>(groupStructId, out var typeSafeDictionary) == false) | |||
throw new EntityGroupNotFoundException(typeof(T) , groupStructId.ToName()); | |||
EGIDMapper<T> mapper; | |||
mapper.map = typeSafeDictionary; | |||
return mapper; | |||
return (typeSafeDictionary as ITypeSafeDictionary<T>).ToEGIDMapper(groupStructId); | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public bool TryQueryMappedEntities<T>(ExclusiveGroup.ExclusiveGroupStruct groupStructId, | |||
out EGIDMapper<T> mapper) | |||
where T : struct, IEntityStruct | |||
public bool TryQueryMappedEntities<T> | |||
(ExclusiveGroupStruct groupStructId, out EGIDMapper<T> mapper) where T : struct, IEntityComponent | |||
{ | |||
mapper = default; | |||
if (SafeQueryEntityDictionary(groupStructId, out TypeSafeDictionary<T> typeSafeDictionary) == false) | |||
if (SafeQueryEntityDictionary<T>(groupStructId, out var typeSafeDictionary) == false | |||
|| typeSafeDictionary.count == 0) | |||
return false; | |||
mapper.map = typeSafeDictionary; | |||
mapper = (typeSafeDictionary as ITypeSafeDictionary<T>).ToEGIDMapper(groupStructId); | |||
return true; | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public T[] QueryEntitiesAndIndex<T>(EGID entityGID, out uint index) where T : struct, IEntityStruct | |||
public bool Exists<T>(EGID entityGID) where T : struct, IEntityComponent | |||
{ | |||
T[] array; | |||
if ((array = QueryEntitiesAndIndexInternal<T>(entityGID, out index)) != null) | |||
return array; | |||
if (SafeQueryEntityDictionary<T>(entityGID.groupID, out var casted) == false) | |||
return false; | |||
throw new EntityNotFoundException(entityGID, typeof(T)); | |||
return casted != null && casted.ContainsKey(entityGID.entityID); | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public bool TryQueryEntitiesAndIndex<T>(EGID entityGid, out uint index, out T[] array) | |||
where T : struct, IEntityStruct | |||
public bool Exists<T>(uint id, ExclusiveGroupStruct group) where T : struct, IEntityComponent | |||
{ | |||
if ((array = QueryEntitiesAndIndexInternal<T>(entityGid, out index)) != null) | |||
return true; | |||
if (SafeQueryEntityDictionary<T>(group, out var casted) == false) | |||
return false; | |||
return false; | |||
return casted != null && casted.ContainsKey(id); | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public T[] QueryEntitiesAndIndex<T>(uint id, ExclusiveGroup.ExclusiveGroupStruct group, out uint index) | |||
where T : struct, IEntityStruct | |||
public bool ExistsAndIsNotEmpty(ExclusiveGroupStruct gid) | |||
{ | |||
return QueryEntitiesAndIndex<T>(new EGID(id, group), out index); | |||
} | |||
if (groupEntityComponentsDB.TryGetValue( | |||
gid, out FasterDictionary<RefWrapperType, ITypeSafeDictionary> group) == true) | |||
{ | |||
return group.count > 0; | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public bool TryQueryEntitiesAndIndex<T>(uint id, ExclusiveGroup.ExclusiveGroupStruct group, out uint index, | |||
out T[] array) where T : struct, IEntityStruct | |||
{ | |||
return TryQueryEntitiesAndIndex(new EGID(id, group), out index, out array); | |||
return false; | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public bool Exists<T>(EGID entityGID) where T : struct, IEntityStruct | |||
public bool HasAny<T>(ExclusiveGroupStruct groupStruct) where T : struct, IEntityComponent | |||
{ | |||
if (SafeQueryEntityDictionary(entityGID.groupID, out TypeSafeDictionary<T> casted) == false) return false; | |||
return casted != null && casted.ContainsKey(entityGID.entityID); | |||
return Count<T>(groupStruct) > 0; | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public bool Exists<T>(uint id, ExclusiveGroup.ExclusiveGroupStruct group) where T : struct, IEntityStruct | |||
public int Count<T>(ExclusiveGroupStruct groupStruct) where T : struct, IEntityComponent | |||
{ | |||
if (SafeQueryEntityDictionary(group, out TypeSafeDictionary<T> casted) == false) return false; | |||
if (SafeQueryEntityDictionary<T>(groupStruct, out var typeSafeDictionary) == false) | |||
return 0; | |||
return casted != null && casted.ContainsKey(id); | |||
return (int) typeSafeDictionary.count; | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public bool Exists(ExclusiveGroup.ExclusiveGroupStruct gid) | |||
public bool FoundInGroups<T1>() where T1 : IEntityComponent | |||
{ | |||
return _groupEntityViewsDB.ContainsKey(gid); | |||
return groupsPerEntity.ContainsKey(TypeRefWrapper<T1>.wrapper); | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public bool HasAny<T>(ExclusiveGroup.ExclusiveGroupStruct groupStruct) where T : struct, IEntityStruct | |||
{ | |||
QueryEntities<T>(groupStruct, out var count); | |||
return count > 0; | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public uint Count<T>(ExclusiveGroup.ExclusiveGroupStruct groupStruct) where T : struct, IEntityStruct | |||
{ | |||
QueryEntities<T>(groupStruct, out var count); | |||
return count; | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public void PublishEntityChange<T>(EGID egid) where T : unmanaged, IEntityStruct | |||
{ | |||
_entityStream.PublishEntity(ref QueryEntity<T>(egid), egid); | |||
} | |||
public bool IsDisposing => _enginesRoot._isDisposing; | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
T[] QueryEntitiesAndIndexInternal<T>(EGID entityGID, out uint index) where T : struct, IEntityStruct | |||
internal bool SafeQueryEntityDictionary<T>(out ITypeSafeDictionary typeSafeDictionary, | |||
FasterDictionary<RefWrapperType, ITypeSafeDictionary> entitiesInGroupPerType) | |||
where T : IEntityComponent | |||
{ | |||
index = 0; | |||
if (SafeQueryEntityDictionary(entityGID.groupID, out TypeSafeDictionary<T> safeDictionary) == false) | |||
return null; | |||
if (entitiesInGroupPerType.TryGetValue(new RefWrapperType(TypeCache<T>.type), out var safeDictionary) == false) | |||
{ | |||
typeSafeDictionary = default; | |||
return false; | |||
} | |||
if (safeDictionary.TryFindIndex(entityGID.entityID, out index) == false) | |||
return null; | |||
//return the indexes entities if they exist | |||
typeSafeDictionary = safeDictionary; | |||
return safeDictionary.GetValuesArray(out _); | |||
return true; | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
bool SafeQueryEntityDictionary<T>(uint group, out TypeSafeDictionary<T> typeSafeDictionary) | |||
where T : struct, IEntityStruct | |||
internal bool SafeQueryEntityDictionary<T>(ExclusiveGroupStruct group, out ITypeSafeDictionary typeSafeDictionary) | |||
where T : IEntityComponent | |||
{ | |||
if (UnsafeQueryEntityDictionary(group, TypeCache<T>.type, out var safeDictionary) == false) | |||
{ | |||
@@ -257,45 +266,101 @@ namespace Svelto.ECS.Internal | |||
} | |||
//return the indexes entities if they exist | |||
typeSafeDictionary = safeDictionary as TypeSafeDictionary<T>; | |||
typeSafeDictionary = safeDictionary; | |||
return true; | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
internal bool UnsafeQueryEntityDictionary(uint group, Type type, out ITypeSafeDictionary typeSafeDictionary) | |||
internal bool UnsafeQueryEntityDictionary(ExclusiveGroupStruct group, Type type, out ITypeSafeDictionary typeSafeDictionary) | |||
{ | |||
//search for the group | |||
if (_groupEntityViewsDB.TryGetValue(group, out var entitiesInGroupPerType) == false) | |||
if (groupEntityComponentsDB.TryGetValue(group, out var entitiesInGroupPerType) == false) | |||
{ | |||
typeSafeDictionary = null; | |||
return false; | |||
} | |||
//search for the indexed entities in the group | |||
return entitiesInGroupPerType.TryGetValue(new RefWrapper<Type>(type), out typeSafeDictionary); | |||
return entitiesInGroupPerType.TryGetValue(new RefWrapperType(type), out typeSafeDictionary); | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
static T[] RetrieveEmptyEntityViewArray<T>() | |||
internal bool FindIndex(uint entityID, ExclusiveGroupStruct @group, Type type, out uint index) | |||
{ | |||
return EmptyList<T>.emptyArray; | |||
EGID entityGID = new EGID(entityID, @group); | |||
index = default; | |||
if (UnsafeQueryEntityDictionary(@group, type, out var safeDictionary) == false) | |||
return false; | |||
if (safeDictionary.TryFindIndex(entityGID.entityID, out index) == false) | |||
return false; | |||
return true; | |||
} | |||
//grouped set of entity views, this is the standard way to handle entity views entity views are grouped per | |||
//group, then indexable per type, then indexable per EGID. however the TypeSafeDictionary can return an array of | |||
//values directly, that can be iterated over, so that is possible to iterate over all the entity views of | |||
//a specific type inside a specific group. | |||
readonly FasterDictionary<uint, FasterDictionary<RefWrapper<Type>, ITypeSafeDictionary>> _groupEntityViewsDB; | |||
internal uint GetIndex(uint entityID, ExclusiveGroupStruct @group, Type type) | |||
{ | |||
EGID entityGID = new EGID(entityID, @group); | |||
if (UnsafeQueryEntityDictionary(@group, type, out var safeDictionary) == false) | |||
{ | |||
throw new EntityNotFoundException(entityGID, type); | |||
} | |||
if (safeDictionary.TryFindIndex(entityGID.entityID, out var index) == false) | |||
throw new EntityNotFoundException(entityGID, type); | |||
//needed to be able to iterate over all the entities of the same type regardless the group | |||
//may change in future | |||
readonly FasterDictionary<RefWrapper<Type>, FasterDictionary<uint, ITypeSafeDictionary>> _groupsPerEntity; | |||
readonly EntitiesStream _entityStream; | |||
return index; | |||
} | |||
static class EmptyList<T> | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
static IBuffer<T> RetrieveEmptyEntityComponentArray<T>() where T : struct, IEntityComponent | |||
{ | |||
internal static readonly T[] emptyArray = new T[0]; | |||
return EmptyList<T>.emptyArray; | |||
} | |||
static class EmptyList<T> where T : struct, IEntityComponent | |||
{ | |||
internal static readonly IBuffer<T> emptyArray; | |||
static EmptyList() | |||
{ | |||
if (ComponentBuilder<T>.IS_ENTITY_VIEW_COMPONENT) | |||
{ | |||
MB<T> b = default; | |||
emptyArray = b; | |||
} | |||
else | |||
{ | |||
NB<T> b = default; | |||
emptyArray = b; | |||
} | |||
} | |||
} | |||
static readonly FasterDictionary<ExclusiveGroupStruct, ITypeSafeDictionary> _emptyDictionary = | |||
new FasterDictionary<ExclusiveGroupStruct, ITypeSafeDictionary>(); | |||
readonly EnginesRoot _enginesRoot; | |||
EntitiesStreams _entityStream => _enginesRoot._entityStreams; | |||
//grouped set of entity components, this is the standard way to handle entity components are grouped per | |||
//group, then indexable per type, then indexable per EGID. however the TypeSafeDictionary can return an array of | |||
//values directly, that can be iterated over, so that is possible to iterate over all the entity components of | |||
//a specific type inside a specific group. | |||
FasterDictionary<ExclusiveGroupStruct, FasterDictionary<RefWrapperType, ITypeSafeDictionary>> | |||
groupEntityComponentsDB => _enginesRoot._groupEntityComponentsDB; | |||
//for each entity view type, return the groups (dictionary of entities indexed by entity id) where they are | |||
//found indexed by group id. TypeSafeDictionary are never created, they instead point to the ones hold | |||
//by _groupEntityComponentsDB | |||
// <EntityComponentType <groupID <entityID, EntityComponent>>> | |||
FasterDictionary<RefWrapperType, FasterDictionary<ExclusiveGroupStruct, ITypeSafeDictionary>> groupsPerEntity => | |||
_enginesRoot._groupsPerEntity; | |||
} | |||
} |
@@ -1,138 +0,0 @@ | |||
#if !DEBUG || PROFILER | |||
#define DISABLE_CHECKS | |||
using System.Diagnostics; | |||
#endif | |||
using System; | |||
using System.Reflection; | |||
namespace Svelto.ECS | |||
{ | |||
internal static class EntityBuilderUtilities | |||
{ | |||
const string MSG = "Entity Structs field and Entity View Struct components must hold value types."; | |||
#if DISABLE_CHECKS | |||
[Conditional("_CHECKS_DISABLED")] | |||
#endif | |||
public static void CheckFields(Type entityStructType, bool needsReflection, bool isStringAllowed = false) | |||
{ | |||
if (entityStructType == ENTITY_STRUCT_INFO_VIEW || | |||
entityStructType == EGIDType || | |||
entityStructType == EXCLUSIVEGROUPSTRUCTTYPE || | |||
entityStructType == SERIALIZABLE_ENTITY_STRUCT) | |||
{ | |||
return; | |||
} | |||
if (needsReflection == false) | |||
{ | |||
if (entityStructType.IsClass) | |||
{ | |||
throw new EntityStructException("EntityStructs must be structs.", entityStructType); | |||
} | |||
FieldInfo[] fields = entityStructType.GetFields(BindingFlags.Public | BindingFlags.Instance); | |||
for (var i = fields.Length - 1; i >= 0; --i) | |||
{ | |||
FieldInfo fieldInfo = fields[i]; | |||
Type fieldType = fieldInfo.FieldType; | |||
SubCheckFields(fieldType, entityStructType, isStringAllowed); | |||
} | |||
} | |||
else | |||
{ | |||
FieldInfo[] fields = entityStructType.GetFields(BindingFlags.Public | BindingFlags.Instance); | |||
if (fields.Length < 1) | |||
{ | |||
ProcessError("Entity View Structs must hold only entity components interfaces.", entityStructType); | |||
} | |||
for (int i = fields.Length - 1; i >= 0; --i) | |||
{ | |||
FieldInfo fieldInfo = fields[i]; | |||
if (fieldInfo.FieldType.IsInterfaceEx() == false) | |||
{ | |||
ProcessError("Entity View Structs must hold only entity components interfaces.", | |||
entityStructType); | |||
} | |||
PropertyInfo[] properties = fieldInfo.FieldType.GetProperties( | |||
BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly); | |||
for (int j = properties.Length - 1; j >= 0; --j) | |||
{ | |||
if (properties[j].PropertyType.IsGenericType) | |||
{ | |||
Type genericTypeDefinition = properties[j].PropertyType.GetGenericTypeDefinition(); | |||
if (genericTypeDefinition == DISPATCHONSETTYPE || | |||
genericTypeDefinition == DISPATCHONCHANGETYPE) | |||
{ | |||
continue; | |||
} | |||
} | |||
Type propertyType = properties[j].PropertyType; | |||
if (propertyType != STRINGTYPE) | |||
{ | |||
//for EntityViewStructs, component fields that are structs that hold strings | |||
//are allowed | |||
SubCheckFields(propertyType, entityStructType, isStringAllowed: true); | |||
} | |||
} | |||
} | |||
} | |||
} | |||
static void SubCheckFields(Type fieldType, Type entityStructType, bool isStringAllowed = false) | |||
{ | |||
if (fieldType.IsPrimitive || fieldType.IsValueType || (isStringAllowed == true && fieldType == STRINGTYPE)) | |||
{ | |||
if (fieldType.IsValueType && !fieldType.IsEnum && fieldType.IsPrimitive == false) | |||
{ | |||
CheckFields(fieldType, false, isStringAllowed); | |||
} | |||
return; | |||
} | |||
ProcessError(MSG, entityStructType, fieldType); | |||
} | |||
static void ProcessError(string message, Type entityViewType, Type fieldType = null) | |||
{ | |||
if (fieldType != null) | |||
{ | |||
throw new EntityStructException(message, entityViewType, fieldType); | |||
} | |||
throw new EntityStructException(message, entityViewType); | |||
} | |||
static readonly Type DISPATCHONCHANGETYPE = typeof(DispatchOnChange<>); | |||
static readonly Type DISPATCHONSETTYPE = typeof(DispatchOnSet<>); | |||
static readonly Type EGIDType = typeof(EGID); | |||
static readonly Type EXCLUSIVEGROUPSTRUCTTYPE = typeof(ExclusiveGroup.ExclusiveGroupStruct); | |||
static readonly Type SERIALIZABLE_ENTITY_STRUCT = typeof(SerializableEntityStruct); | |||
static readonly Type STRINGTYPE = typeof(string); | |||
internal static readonly Type ENTITY_STRUCT_INFO_VIEW = typeof(EntityStructInfoView); | |||
} | |||
public class EntityStructException : Exception | |||
{ | |||
public EntityStructException(string message, Type entityViewType, Type type) : | |||
base(message.FastConcat(" entity view: '", entityViewType.ToString(), "', field: '", type.ToString())) | |||
{ | |||
} | |||
public EntityStructException(string message, Type entityViewType) : | |||
base(message.FastConcat(" entity view: ", entityViewType.ToString())) | |||
{ | |||
} | |||
} | |||
} |
@@ -1,146 +0,0 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Reflection; | |||
using Svelto.DataStructures; | |||
using Svelto.ECS.Hybrid; | |||
using Svelto.ECS.Internal; | |||
using Svelto.Utilities; | |||
namespace Svelto.ECS | |||
{ | |||
public class EntityBuilder<T> : IEntityBuilder where T : struct, IEntityStruct | |||
{ | |||
static class EntityView | |||
{ | |||
internal static readonly FasterList<KeyValuePair<Type, ActionCast<T>>> cachedFields; | |||
internal static readonly Dictionary<Type, Type[]> cachedTypes; | |||
#if DEBUG && !PROFILER | |||
internal static readonly Dictionary<Type, ECSTuple<object, int>> implementorsByType; | |||
#else | |||
internal static readonly Dictionary<Type, object> implementorsByType; | |||
#endif | |||
static EntityView() | |||
{ | |||
cachedFields = new FasterList<KeyValuePair<Type, ActionCast<T>>>(); | |||
var type = typeof(T); | |||
var fields = type.GetFields(BindingFlags.Public | | |||
BindingFlags.Instance); | |||
for (var i = fields.Length - 1; i >= 0; --i) | |||
{ | |||
var field = fields[i]; | |||
var setter = FastInvoke<T>.MakeSetter(field); | |||
cachedFields.Add(new KeyValuePair<Type, ActionCast<T>>(field.FieldType, setter)); | |||
} | |||
cachedTypes = new Dictionary<Type, Type[]>(); | |||
#if DEBUG && !PROFILER | |||
implementorsByType = new Dictionary<Type, ECSTuple<object, int>>(); | |||
#else | |||
implementorsByType = new Dictionary<Type, object>(); | |||
#endif | |||
} | |||
internal static void InitCache() | |||
{} | |||
internal static void BuildEntityView(out T entityView) | |||
{ | |||
entityView = new T(); | |||
} | |||
} | |||
public EntityBuilder() | |||
{ | |||
_initializer = DEFAULT_IT; | |||
EntityBuilderUtilities.CheckFields(ENTITY_VIEW_TYPE, NEEDS_REFLECTION); | |||
if (NEEDS_REFLECTION) | |||
EntityView.InitCache(); | |||
} | |||
public EntityBuilder(in T initializer) : this() | |||
{ | |||
_initializer = initializer; | |||
} | |||
public void BuildEntityAndAddToList(ref ITypeSafeDictionary dictionary, EGID egid, | |||
IEnumerable<object> implementors) | |||
{ | |||
if (dictionary == null) | |||
dictionary = new TypeSafeDictionary<T>(); | |||
var castedDic = dictionary as TypeSafeDictionary<T>; | |||
if (NEEDS_REFLECTION) | |||
{ | |||
DBC.ECS.Check.Require(implementors != null, | |||
$"Implementors not found while building an EntityView `{typeof(T)}`"); | |||
DBC.ECS.Check.Require(castedDic.ContainsKey(egid.entityID) == false, | |||
$"building an entity with already used entity id! id: '{(ulong) egid}', {ENTITY_VIEW_NAME}"); | |||
EntityView.BuildEntityView(out var entityView); | |||
this.FillEntityView(ref entityView, entityViewBlazingFastReflection, implementors, | |||
EntityView.implementorsByType, EntityView.cachedTypes); | |||
castedDic.Add(egid.entityID, entityView); | |||
} | |||
else | |||
{ | |||
DBC.ECS.Check.Require(!castedDic.ContainsKey(egid.entityID), | |||
$"building an entity with already used entity id! id: '{egid.entityID}'"); | |||
castedDic.Add(egid.entityID, _initializer); | |||
} | |||
} | |||
ITypeSafeDictionary IEntityBuilder.Preallocate(ref ITypeSafeDictionary dictionary, uint size) | |||
{ | |||
return Preallocate(ref dictionary, size); | |||
} | |||
static ITypeSafeDictionary Preallocate(ref ITypeSafeDictionary dictionary, uint size) | |||
{ | |||
if (dictionary == null) | |||
dictionary = new TypeSafeDictionary<T>(size); | |||
else | |||
dictionary.SetCapacity(size); | |||
return dictionary; | |||
} | |||
public Type GetEntityType() | |||
{ | |||
return ENTITY_VIEW_TYPE; | |||
} | |||
static EntityBuilder() | |||
{ | |||
ENTITY_VIEW_TYPE = typeof(T); | |||
DEFAULT_IT = default; | |||
NEEDS_REFLECTION = typeof(IEntityViewStruct).IsAssignableFrom(ENTITY_VIEW_TYPE); | |||
HAS_EGID = typeof(INeedEGID).IsAssignableFrom(ENTITY_VIEW_TYPE); | |||
ENTITY_VIEW_NAME = ENTITY_VIEW_TYPE.ToString(); | |||
SetEGIDWithoutBoxing<T>.Warmup(); | |||
} | |||
readonly T _initializer; | |||
static FasterList<KeyValuePair<Type, ActionCast<T>>> entityViewBlazingFastReflection => | |||
EntityView.cachedFields; | |||
internal static readonly Type ENTITY_VIEW_TYPE; | |||
public static readonly bool HAS_EGID; | |||
static readonly T DEFAULT_IT; | |||
static readonly bool NEEDS_REFLECTION; | |||
static readonly string ENTITY_VIEW_NAME; | |||
} | |||
} |
@@ -1,8 +0,0 @@ | |||
diff a/Assets/Svelto/Svelto.ECS/EntityBuilder.cs b/Assets/Svelto/Svelto.ECS/EntityBuilder.cs (rejected hunks) | |||
@@ -1,5 +1,6 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
+using DBC.ECS; | |||
using Svelto.DataStructures; | |||
using Svelto.ECS.Hybrid; | |||
using Svelto.ECS.Internal; |
@@ -1,326 +1,221 @@ | |||
using System; | |||
using System.Collections; | |||
using System.Collections.Generic; | |||
using System.Runtime.CompilerServices; | |||
using Svelto.DataStructures; | |||
using Svelto.ECS.Internal; | |||
namespace Svelto.ECS | |||
{ | |||
public struct EntityCollection<T> | |||
public readonly ref struct EntityCollection<T> where T : struct, IEntityComponent | |||
{ | |||
public EntityCollection(T[] array, uint count) | |||
static readonly bool IsUnmanaged = TypeSafeDictionary<T>.IsUnmanaged; | |||
public EntityCollection(IBuffer<T> buffer, uint count):this() | |||
{ | |||
_array = array; | |||
_count = count; | |||
if (IsUnmanaged) | |||
_nativedBuffer = (NB<T>) buffer; | |||
else | |||
_managedBuffer = (MB<T>) buffer; | |||
_count = count; | |||
} | |||
public EntityIterator GetEnumerator() | |||
{ | |||
return new EntityIterator(_array, _count); | |||
} | |||
readonly T[] _array; | |||
readonly uint _count; | |||
public struct EntityIterator : IEnumerator<T> | |||
{ | |||
public EntityIterator(T[] array, uint count) : this() | |||
{ | |||
_array = array; | |||
_count = count; | |||
_index = -1; | |||
} | |||
public bool MoveNext() | |||
{ | |||
return ++_index < _count; | |||
} | |||
public void Reset() | |||
{ | |||
_index = -1; | |||
} | |||
public ref T Current => ref _array[_index]; | |||
public uint count => _count; | |||
T IEnumerator<T>.Current => throw new NotImplementedException(); | |||
object IEnumerator.Current => throw new NotImplementedException(); | |||
internal readonly MB<T> _managedBuffer; | |||
internal readonly NB<T> _nativedBuffer; | |||
public void Dispose() {} | |||
readonly T[] _array; | |||
readonly uint _count; | |||
int _index; | |||
} | |||
readonly uint _count; | |||
} | |||
public struct EntityCollection<T1, T2> | |||
public readonly ref struct EntityCollection<T1, T2> where T1 : struct, IEntityComponent where T2 : struct, IEntityComponent | |||
{ | |||
public EntityCollection(in (T1[], T2[]) array, uint count) | |||
internal EntityCollection(in EntityCollection<T1> array1, in EntityCollection<T2> array2) | |||
{ | |||
_array = array; | |||
_count = count; | |||
_array1 = array1; | |||
_array2 = array2; | |||
} | |||
public EntityIterator GetEnumerator() | |||
public uint count => _array1.count; | |||
internal EntityCollection<T2> buffer2 | |||
{ | |||
return new EntityIterator(_array, _count); | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
get => _array2; | |||
} | |||
readonly (T1[], T2[]) _array; | |||
readonly uint _count; | |||
public struct EntityIterator : IEnumerator<ValueRef<T1, T2>> | |||
internal EntityCollection<T1> buffer1 | |||
{ | |||
public EntityIterator((T1[], T2[]) array, uint count) : this() | |||
{ | |||
_array = array; | |||
_count = count; | |||
_index = -1; | |||
} | |||
public bool MoveNext() | |||
{ | |||
return ++_index < _count; | |||
} | |||
public void Reset() | |||
{ | |||
_index = -1; | |||
} | |||
public ValueRef<T1, T2> Current => new ValueRef<T1, T2>(_array, (uint) _index); | |||
ValueRef<T1, T2> IEnumerator<ValueRef<T1, T2>>. Current => throw new NotImplementedException(); | |||
object IEnumerator.Current => throw new NotImplementedException(); | |||
public void Dispose() {} | |||
readonly (T1[], T2[]) _array; | |||
readonly uint _count; | |||
int _index; | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
get => _array1; | |||
} | |||
readonly EntityCollection<T1> _array1; | |||
readonly EntityCollection<T2> _array2; | |||
} | |||
public struct EntityCollection<T1, T2, T3> | |||
public readonly ref struct EntityCollection<T1, T2, T3> where T3 : struct, IEntityComponent | |||
where T2 : struct, IEntityComponent | |||
where T1 : struct, IEntityComponent | |||
{ | |||
public EntityCollection(in (T1[], T2[], T3[]) array, uint count) | |||
internal EntityCollection | |||
(in EntityCollection<T1> array1, in EntityCollection<T2> array2, in EntityCollection<T3> array3) | |||
{ | |||
_array = array; | |||
_count = count; | |||
_array1 = array1; | |||
_array2 = array2; | |||
_array3 = array3; | |||
} | |||
public EntityIterator GetEnumerator() | |||
internal EntityCollection<T1> buffer1 | |||
{ | |||
return new EntityIterator(_array, _count); | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
get => _array1; | |||
} | |||
readonly (T1[], T2[], T3[]) _array; | |||
readonly uint _count; | |||
public struct EntityIterator : IEnumerator<ValueRef<T1, T2, T3>> | |||
internal EntityCollection<T2> buffer2 | |||
{ | |||
public EntityIterator((T1[], T2[], T3[]) array, uint count) : this() | |||
{ | |||
_array = array; | |||
_count = count; | |||
_index = -1; | |||
} | |||
public bool MoveNext() | |||
{ | |||
return ++_index < _count; | |||
} | |||
public void Reset() | |||
{ | |||
_index = -1; | |||
} | |||
public ValueRef<T1, T2, T3> Current => new ValueRef<T1, T2, T3>(_array, (uint) _index); | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
get => _array2; | |||
} | |||
ValueRef<T1, T2, T3> IEnumerator<ValueRef<T1, T2, T3>>.Current => throw new NotImplementedException(); | |||
object IEnumerator. Current => throw new NotImplementedException(); | |||
internal EntityCollection<T3> buffer3 | |||
{ | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
get => _array3; | |||
} | |||
public void Dispose() {} | |||
internal uint count => buffer1.count; | |||
readonly (T1[], T2[], T3[]) _array; | |||
readonly uint _count; | |||
int _index; | |||
} | |||
readonly EntityCollection<T1> _array1; | |||
readonly EntityCollection<T2> _array2; | |||
readonly EntityCollection<T3> _array3; | |||
} | |||
public struct EntityCollections<T> where T : struct, IEntityStruct | |||
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 | |||
{ | |||
public EntityCollections(IEntitiesDB db, ExclusiveGroup[] groups) : this() | |||
internal EntityCollection | |||
(in EntityCollection<T1> array1, in EntityCollection<T2> array2, in EntityCollection<T3> array3, in EntityCollection<T4> array4) | |||
{ | |||
_db = db; | |||
_groups = groups; | |||
_array1 = array1; | |||
_array2 = array2; | |||
_array3 = array3; | |||
_array4 = array4; | |||
} | |||
public EntityGroupsIterator GetEnumerator() | |||
internal EntityCollection<T1> Item1 | |||
{ | |||
return new EntityGroupsIterator(_db, _groups); | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
get => _array1; | |||
} | |||
readonly IEntitiesDB _db; | |||
readonly ExclusiveGroup[] _groups; | |||
public struct EntityGroupsIterator : IEnumerator<T> | |||
internal EntityCollection<T2> Item2 | |||
{ | |||
public EntityGroupsIterator(IEntitiesDB db, ExclusiveGroup[] groups) : this() | |||
{ | |||
_db = db; | |||
_groups = groups; | |||
_indexGroup = -1; | |||
_index = -1; | |||
} | |||
public bool MoveNext() | |||
{ | |||
while (_index + 1 >= _count && ++_indexGroup < _groups.Length) | |||
{ | |||
_index = -1; | |||
_array = _db.QueryEntities<T>(_groups[_indexGroup], out _count); | |||
} | |||
return ++_index < _count; | |||
} | |||
public void Reset() | |||
{ | |||
_index = -1; | |||
_indexGroup = -1; | |||
_count = 0; | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
get => _array2; | |||
} | |||
public ref T Current => ref _array[_index]; | |||
internal EntityCollection<T3> Item3 | |||
{ | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
get => _array3; | |||
} | |||
internal EntityCollection<T4> Item4 | |||
{ | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
get => _array4; | |||
} | |||
T IEnumerator<T>.Current => throw new NotImplementedException(); | |||
object IEnumerator.Current => throw new NotImplementedException(); | |||
internal uint count => _array1.count; | |||
public void Dispose() {} | |||
readonly EntityCollection<T1> _array1; | |||
readonly EntityCollection<T2> _array2; | |||
readonly EntityCollection<T3> _array3; | |||
readonly EntityCollection<T4> _array4; | |||
} | |||
readonly IEntitiesDB _db; | |||
readonly ExclusiveGroup[] _groups; | |||
public readonly struct BT<BufferT1, BufferT2, BufferT3, BufferT4> | |||
{ | |||
public readonly BufferT1 buffer1; | |||
public readonly BufferT2 buffer2; | |||
public readonly BufferT3 buffer3; | |||
public readonly BufferT4 buffer4; | |||
public readonly int count; | |||
T[] _array; | |||
uint _count; | |||
int _index; | |||
int _indexGroup; | |||
public BT(BufferT1 bufferT1, BufferT2 bufferT2, BufferT3 bufferT3, BufferT4 bufferT4, uint count) : this() | |||
{ | |||
this.buffer1 = bufferT1; | |||
this.buffer2 = bufferT2; | |||
this.buffer3 = bufferT3; | |||
this.buffer4 = bufferT4; | |||
this.count = (int) count; | |||
} | |||
} | |||
public struct EntityCollections<T1, T2> where T1 : struct, IEntityStruct where T2 : struct, IEntityStruct | |||
public readonly struct BT<BufferT1, BufferT2, BufferT3> | |||
{ | |||
public EntityCollections(IEntitiesDB db, ExclusiveGroup[] groups) : this() | |||
{ | |||
_db = db; | |||
_groups = groups; | |||
} | |||
public readonly BufferT1 buffer1; | |||
public readonly BufferT2 buffer2; | |||
public readonly BufferT3 buffer3; | |||
public readonly int count; | |||
public EntityGroupsIterator GetEnumerator() | |||
public BT(BufferT1 bufferT1, BufferT2 bufferT2, BufferT3 bufferT3, uint count) : this() | |||
{ | |||
return new EntityGroupsIterator(_db, _groups); | |||
this.buffer1 = bufferT1; | |||
this.buffer2 = bufferT2; | |||
this.buffer3 = bufferT3; | |||
this.count = (int) count; | |||
} | |||
readonly IEntitiesDB _db; | |||
readonly ExclusiveGroup[] _groups; | |||
public struct EntityGroupsIterator : IEnumerator<ValueRef<T1, T2>> | |||
public void Deconstruct(out BufferT1 bufferT1, out BufferT2 bufferT2, out BufferT3 bufferT3, out int count) | |||
{ | |||
public EntityGroupsIterator(IEntitiesDB db, ExclusiveGroup[] groups) : this() | |||
{ | |||
_db = db; | |||
_groups = groups; | |||
_indexGroup = -1; | |||
_index = -1; | |||
} | |||
public bool MoveNext() | |||
{ | |||
while (_index + 1 >= _count && ++_indexGroup < _groups.Length) | |||
{ | |||
_index = -1; | |||
var array1 = _db.QueryEntities<T1>(_groups[_indexGroup], out _count); | |||
var array2 = _db.QueryEntities<T2>(_groups[_indexGroup], out var count1); | |||
_array = (array1, array2); | |||
#if DEBUG && !PROFILER | |||
if (_count != count1) | |||
throw new ECSException("number of entities in group doesn't match"); | |||
#endif | |||
} | |||
return ++_index < _count; | |||
} | |||
public void Reset() | |||
{ | |||
_index = -1; | |||
_indexGroup = -1; | |||
var array1 = _db.QueryEntities<T1>(_groups[0], out _count); | |||
var array2 = _db.QueryEntities<T2>(_groups[0], out var count1); | |||
_array = (array1, array2); | |||
#if DEBUG && !PROFILER | |||
if (_count != count1) | |||
throw new ECSException("number of entities in group doesn't match"); | |||
#endif | |||
} | |||
public ValueRef<T1, T2> Current | |||
{ | |||
get | |||
{ | |||
var valueRef = new ValueRef<T1, T2>(_array, (uint) _index); | |||
return valueRef; | |||
} | |||
} | |||
ValueRef<T1, T2> IEnumerator<ValueRef<T1, T2>>.Current => throw new NotImplementedException(); | |||
object IEnumerator.Current => throw new NotImplementedException(); | |||
public void Dispose() {} | |||
readonly IEntitiesDB _db; | |||
readonly ExclusiveGroup[] _groups; | |||
uint _count; | |||
int _index; | |||
int _indexGroup; | |||
(T1[], T2[]) _array; | |||
bufferT1 = buffer1; | |||
bufferT2 = buffer2; | |||
bufferT3 = buffer3; | |||
count = this.count; | |||
} | |||
} | |||
public struct ValueRef<T1, T2> | |||
{ | |||
readonly (T1[], T2[]) array; | |||
readonly uint index; | |||
public readonly struct BT<BufferT1> | |||
{ | |||
public readonly BufferT1 buffer; | |||
public readonly int count; | |||
public ValueRef(in (T1[], T2[]) entity1, uint i) | |||
public BT(BufferT1 bufferT1, uint count) : this() | |||
{ | |||
array = entity1; | |||
index = i; | |||
this.buffer = bufferT1; | |||
this.count = (int) count; | |||
} | |||
public ref T1 entityStructA => ref array.Item1[index]; | |||
public ref T2 entityStructB => ref array.Item2[index]; | |||
public void Deconstruct(out BufferT1 bufferT1, out int count) | |||
{ | |||
bufferT1 = buffer; | |||
count = this.count; | |||
} | |||
public static implicit operator BufferT1(BT<BufferT1> t) => t.buffer; | |||
} | |||
public struct ValueRef<T1, T2, T3> | |||
{ | |||
readonly (T1[], T2[], T3[]) array; | |||
readonly uint index; | |||
public readonly struct BT<BufferT1, BufferT2> | |||
{ | |||
public readonly BufferT1 buffer1; | |||
public readonly BufferT2 buffer2; | |||
public readonly int count; | |||
public ValueRef(in (T1[], T2[], T3[]) entity1, uint i) | |||
public BT(BufferT1 bufferT1, BufferT2 bufferT2, uint count) : this() | |||
{ | |||
array = entity1; | |||
index = i; | |||
this.buffer1 = bufferT1; | |||
this.buffer2 = bufferT2; | |||
this.count = (int) count; | |||
} | |||
public void Deconstruct(out BufferT1 bufferT1, out BufferT2 bufferT2, out int count) | |||
{ | |||
bufferT1 = buffer1; | |||
bufferT2 = buffer2; | |||
count = this.count; | |||
} | |||
public ref T1 entityStructA => ref array.Item1[index]; | |||
public ref T2 entityStructB => ref array.Item2[index]; | |||
public ref T3 entityStructC => ref array.Item3[index]; | |||
} | |||
} |
@@ -0,0 +1,63 @@ | |||
using System; | |||
using Svelto.DataStructures; | |||
using Svelto.ECS.Internal; | |||
namespace Svelto.ECS | |||
{ | |||
public readonly ref struct EntityComponentInitializer | |||
{ | |||
public EntityComponentInitializer(EGID id, FasterDictionary<RefWrapperType, ITypeSafeDictionary> group) | |||
{ | |||
_group = group; | |||
_ID = id; | |||
} | |||
public EGID EGID => _ID; | |||
public void Init<T>(T initializer) where T : struct, IEntityComponent | |||
{ | |||
if (_group.TryGetValue(new RefWrapperType(ComponentBuilder<T>.ENTITY_COMPONENT_TYPE), | |||
out var typeSafeDictionary) == false) return; | |||
var dictionary = (ITypeSafeDictionary<T>) typeSafeDictionary; | |||
if (ComponentBuilder<T>.HAS_EGID) | |||
SetEGIDWithoutBoxing<T>.SetIDWithoutBoxing(ref initializer, _ID); | |||
if (dictionary.TryFindIndex(_ID.entityID, out var findElementIndex)) | |||
dictionary.GetDirectValueByRef(findElementIndex) = initializer; | |||
} | |||
public ref T GetOrCreate<T>() where T : struct, IEntityComponent | |||
{ | |||
ref var entityDictionary = ref _group.GetOrCreate(new RefWrapperType(ComponentBuilder<T>.ENTITY_COMPONENT_TYPE) | |||
, TypeSafeDictionaryFactory<T>.Create); | |||
var dictionary = (ITypeSafeDictionary<T>) entityDictionary; | |||
return ref dictionary.GetOrCreate(_ID.entityID); | |||
} | |||
public ref T Get<T>() where T : struct, IEntityComponent | |||
{ | |||
return ref (_group[new RefWrapperType(ComponentBuilder<T>.ENTITY_COMPONENT_TYPE)] as ITypeSafeDictionary<T>)[ | |||
_ID.entityID]; | |||
} | |||
public bool Has<T>() where T : struct, IEntityComponent | |||
{ | |||
if (_group.TryGetValue(new RefWrapperType(ComponentBuilder<T>.ENTITY_COMPONENT_TYPE), | |||
out var typeSafeDictionary)) | |||
{ | |||
var dictionary = (ITypeSafeDictionary<T>) typeSafeDictionary; | |||
if (dictionary.ContainsKey(_ID.entityID)) | |||
return true; | |||
} | |||
return false; | |||
} | |||
readonly EGID _ID; | |||
readonly FasterDictionary<RefWrapperType, ITypeSafeDictionary> _group; | |||
} | |||
} |
@@ -0,0 +1,11 @@ | |||
fileFormatVersion: 2 | |||
guid: 005da4cbd47736e1bc2fb1676cb30190 | |||
MonoImporter: | |||
externalObjects: {} | |||
serializedVersion: 2 | |||
defaultReferences: [] | |||
executionOrder: 0 | |||
icon: {instanceID: 0} | |||
userData: | |||
assetBundleName: | |||
assetBundleVariant: |
@@ -1,17 +1,26 @@ | |||
using System; | |||
namespace Svelto.ECS | |||
{ | |||
public interface IEntityDescriptor | |||
{ | |||
IEntityBuilder[] entitiesToBuild { get; } | |||
IComponentBuilder[] componentsToBuild { get; } | |||
} | |||
public interface IDynamicEntityDescriptor: IEntityDescriptor | |||
{ | |||
} | |||
static class EntityDescriptorTemplate<TType> where TType : IEntityDescriptor, new() | |||
{ | |||
static EntityDescriptorTemplate() | |||
{ | |||
descriptor = new TType(); | |||
realDescriptor = new TType(); | |||
descriptor = realDescriptor; | |||
} | |||
public static IEntityDescriptor descriptor { get; } | |||
public static TType realDescriptor { get; } | |||
public static Type type => typeof(TType); | |||
public static IEntityDescriptor descriptor { get; } | |||
} | |||
} |
@@ -6,92 +6,77 @@ namespace Svelto.ECS.Internal | |||
{ | |||
static class EntityFactory | |||
{ | |||
public static FasterDictionary<RefWrapper<Type>, ITypeSafeDictionary> BuildGroupedEntities(EGID egid, | |||
EnginesRoot.DoubleBufferedEntitiesToAdd groupEntitiesToAdd, | |||
IEntityBuilder[] entitiesToBuild, | |||
IEnumerable<object> implementors) | |||
public static FasterDictionary<RefWrapperType, ITypeSafeDictionary> BuildGroupedEntities | |||
(EGID egid, EnginesRoot.DoubleBufferedEntitiesToAdd groupEntitiesToAdd | |||
, IComponentBuilder[] componentsToBuild, IEnumerable<object> implementors, Type implementorType) | |||
{ | |||
var group = FetchEntityGroup(egid.groupID, groupEntitiesToAdd); | |||
BuildEntitiesAndAddToGroup(egid, group, entitiesToBuild, implementors); | |||
BuildEntitiesAndAddToGroup(egid, group, componentsToBuild, implementors, implementorType); | |||
return group; | |||
} | |||
static FasterDictionary<RefWrapper<Type>, ITypeSafeDictionary> FetchEntityGroup(uint groupID, | |||
EnginesRoot.DoubleBufferedEntitiesToAdd groupEntityViewsByType) | |||
static FasterDictionary<RefWrapperType, ITypeSafeDictionary> FetchEntityGroup(ExclusiveGroupStruct groupID, | |||
EnginesRoot.DoubleBufferedEntitiesToAdd groupEntityComponentsByType) | |||
{ | |||
if (groupEntityViewsByType.current.TryGetValue(groupID, out var group) == false) | |||
if (groupEntityComponentsByType.current.TryGetValue(groupID, out var group) == false) | |||
{ | |||
group = new FasterDictionary<RefWrapper<Type>, ITypeSafeDictionary>(); | |||
group = new FasterDictionary<RefWrapperType, ITypeSafeDictionary>(); | |||
groupEntityViewsByType.current.Add(groupID, group); | |||
groupEntityComponentsByType.current.Add(groupID, group); | |||
} | |||
if (groupEntityViewsByType.currentEntitiesCreatedPerGroup.TryGetValue(groupID, out var value) == false) | |||
groupEntityViewsByType.currentEntitiesCreatedPerGroup[groupID] = 0; | |||
if (groupEntityComponentsByType.currentEntitiesCreatedPerGroup.TryGetValue(groupID, out var value) == false) | |||
groupEntityComponentsByType.currentEntitiesCreatedPerGroup[groupID] = 0; | |||
else | |||
groupEntityViewsByType.currentEntitiesCreatedPerGroup[groupID] = value+1; | |||
groupEntityComponentsByType.currentEntitiesCreatedPerGroup[groupID] = value+1; | |||
return group; | |||
} | |||
static void BuildEntitiesAndAddToGroup(EGID entityID, | |||
FasterDictionary<RefWrapper<Type>, ITypeSafeDictionary> group, | |||
IEntityBuilder[] entityBuilders, IEnumerable<object> implementors) | |||
static void BuildEntitiesAndAddToGroup | |||
(EGID entityID, FasterDictionary<RefWrapperType, ITypeSafeDictionary> @group | |||
, IComponentBuilder[] componentBuilders, IEnumerable<object> implementors, Type implementorType) | |||
{ | |||
#if DEBUG && !PROFILER | |||
var count = componentBuilders.Length; | |||
#if DEBUG && !PROFILE_SVELTO | |||
HashSet<Type> types = new HashSet<Type>(); | |||
#endif | |||
InternalBuild(entityID, group, entityBuilders, implementors | |||
#if DEBUG && !PROFILER | |||
, types | |||
#endif | |||
); | |||
} | |||
static void InternalBuild(EGID entityID, FasterDictionary<RefWrapper<Type>, ITypeSafeDictionary> group, | |||
IEntityBuilder[] entityBuilders, IEnumerable<object> implementors | |||
#if DEBUG && !PROFILER | |||
, HashSet<Type> types | |||
#endif | |||
) | |||
{ | |||
var count = entityBuilders.Length; | |||
#if DEBUG && !PROFILER | |||
for (var index = 0; index < count; ++index) | |||
{ | |||
var entityViewType = entityBuilders[index].GetEntityType(); | |||
if (types.Contains(entityViewType)) | |||
var entityComponentType = componentBuilders[index].GetEntityComponentType(); | |||
if (types.Contains(entityComponentType)) | |||
{ | |||
throw new ECSException("EntityBuilders must be unique inside an EntityDescriptor"); | |||
throw new ECSException($"EntityBuilders must be unique inside an EntityDescriptor. Descriptor Type {implementorType} Component Type: {entityComponentType}"); | |||
} | |||
types.Add(entityViewType); | |||
types.Add(entityComponentType); | |||
} | |||
#endif | |||
for (var index = 0; index < count; ++index) | |||
{ | |||
var entityStructBuilder = entityBuilders[index]; | |||
var entityViewType = entityStructBuilder.GetEntityType(); | |||
var entityComponentBuilder = componentBuilders[index]; | |||
var entityComponentType = entityComponentBuilder.GetEntityComponentType(); | |||
BuildEntity(entityID, group, entityViewType, entityStructBuilder, implementors); | |||
BuildEntity(entityID, @group, entityComponentType, entityComponentBuilder, implementors); | |||
} | |||
} | |||
static void BuildEntity(EGID entityID, FasterDictionary<RefWrapper<Type>, ITypeSafeDictionary> group, | |||
Type entityViewType, IEntityBuilder entityBuilder, IEnumerable<object> implementors) | |||
static void BuildEntity(EGID entityID, FasterDictionary<RefWrapperType, ITypeSafeDictionary> group, | |||
Type entityComponentType, IComponentBuilder componentBuilder, IEnumerable<object> implementors) | |||
{ | |||
var entityViewsPoolWillBeCreated = | |||
group.TryGetValue(new RefWrapper<Type>(entityViewType), out var safeDictionary) == false; | |||
var entityComponentsPoolWillBeCreated = | |||
group.TryGetValue(new RefWrapperType(entityComponentType), out var safeDictionary) == false; | |||
//passing the undefined entityViewsByType inside the entityViewBuilder will allow it to be created with the | |||
//passing the undefined entityComponentsByType inside the entityComponentBuilder will allow it to be created with the | |||
//correct type and casted back to the undefined list. that's how the list will be eventually of the target | |||
//type. | |||
entityBuilder.BuildEntityAndAddToList(ref safeDictionary, entityID, implementors); | |||
componentBuilder.BuildEntityAndAddToList(ref safeDictionary, entityID, implementors); | |||
if (entityViewsPoolWillBeCreated) | |||
group.Add(new RefWrapper<Type>(entityViewType), safeDictionary); | |||
if (entityComponentsPoolWillBeCreated) | |||
group.Add(new RefWrapperType(entityComponentType), safeDictionary); | |||
} | |||
} | |||
} |
@@ -4,8 +4,8 @@ namespace Svelto.ECS.Internal | |||
{ | |||
class EntityGroupNotFoundException : Exception | |||
{ | |||
public EntityGroupNotFoundException(uint groupId, Type type) | |||
: base("entity group not found ".FastConcat(type.ToString())) | |||
public EntityGroupNotFoundException(Type type, string toName) | |||
: base($"entity group {toName} not used for component type ".FastConcat(type.ToString())) | |||
{ | |||
} | |||
} |
@@ -1,11 +0,0 @@ | |||
namespace Svelto.ECS | |||
{ | |||
public struct EntityHierarchyStruct: IEntityStruct, INeedEGID | |||
{ | |||
public readonly ExclusiveGroup.ExclusiveGroupStruct parentGroup; | |||
public EntityHierarchyStruct(ExclusiveGroup group): this() { parentGroup = group; } | |||
public EGID ID { get; set; } | |||
} | |||
} |
@@ -1,11 +0,0 @@ | |||
fileFormatVersion: 2 | |||
guid: dd25991b7ae539ff89f73ae33b98106f | |||
MonoImporter: | |||
externalObjects: {} | |||
serializedVersion: 2 | |||
defaultReferences: [] | |||
executionOrder: 0 | |||
icon: {instanceID: 0} | |||
userData: | |||
assetBundleName: | |||
assetBundleVariant: |
@@ -1,7 +1,7 @@ | |||
namespace Svelto.ECS | |||
{ | |||
struct EntityStructInfoView: IEntityStruct | |||
struct EntityInfoComponent: IEntityComponent | |||
{ | |||
public IEntityBuilder[] entitiesToBuild; | |||
public IComponentBuilder[] componentsToBuild; | |||
} | |||
} |
@@ -5,7 +5,7 @@ namespace Svelto.ECS | |||
public class EntityNotFoundException : Exception | |||
{ | |||
public EntityNotFoundException(EGID entityEGID, Type entityType) : base( | |||
$"entity of type '{entityType}' with ID '{entityEGID.entityID}', group '{(uint) entityEGID.groupID}' not found!") | |||
$"entity of type '{entityType}' with ID '{entityEGID.entityID}', group '{entityEGID.groupID.ToName()}' not found!") | |||
{ | |||
} | |||
} |
@@ -1,163 +0,0 @@ | |||
using System; | |||
using Svelto.DataStructures; | |||
namespace Svelto.ECS | |||
{ | |||
/// <summary> | |||
/// Do not use this class in place of a normal polling. | |||
/// I eventually realised than in ECS no form of communication other than polling entity components can exist. | |||
/// Using groups, you can have always an optimal set of entity components to poll, so EntityStreams must be used | |||
/// only if: | |||
/// - you want to polling engine to be able to track all the entity changes happening in between polls and not | |||
/// just the current state | |||
/// - you want a thread-safe way to read entity states, which includes all the state changes and not the last | |||
/// one only | |||
/// - you want to communicate between EnginesRoots | |||
/// </summary> | |||
class EntitiesStream : IDisposable | |||
{ | |||
internal Consumer<T> GenerateConsumer<T>(string name, uint capacity) where T : unmanaged, IEntityStruct | |||
{ | |||
if (_streams.ContainsKey(TypeRefWrapper<T>.wrapper) == false) _streams[TypeRefWrapper<T>.wrapper] = new EntityStream<T>(); | |||
return (_streams[TypeRefWrapper<T>.wrapper] as EntityStream<T>).GenerateConsumer(name, capacity); | |||
} | |||
public Consumer<T> GenerateConsumer<T>(ExclusiveGroup group, string name, uint capacity) | |||
where T : unmanaged, IEntityStruct | |||
{ | |||
if (_streams.ContainsKey(TypeRefWrapper<T>.wrapper) == false) _streams[TypeRefWrapper<T>.wrapper] = new EntityStream<T>(); | |||
return (_streams[TypeRefWrapper<T>.wrapper] as EntityStream<T>).GenerateConsumer(group, name, capacity); | |||
} | |||
internal void PublishEntity<T>(ref T entity, EGID egid) where T : unmanaged, IEntityStruct | |||
{ | |||
if (_streams.TryGetValue(TypeRefWrapper<T>.wrapper, out var typeSafeStream)) | |||
(typeSafeStream as EntityStream<T>).PublishEntity(ref entity, egid); | |||
else | |||
Console.LogDebug("No Consumers are waiting for this entity to change ", typeof(T)); | |||
} | |||
readonly ThreadSafeDictionary<RefWrapper<Type>, ITypeSafeStream> _streams = | |||
new ThreadSafeDictionary<RefWrapper<Type>, ITypeSafeStream>(); | |||
public void Dispose() | |||
{ | |||
_streams.Clear(); | |||
} | |||
} | |||
interface ITypeSafeStream | |||
{} | |||
class EntityStream<T> : ITypeSafeStream where T : unmanaged, IEntityStruct | |||
{ | |||
public void PublishEntity(ref T entity, EGID egid) | |||
{ | |||
for (int i = 0; i < _consumers.Count; i++) | |||
{ | |||
if (_consumers[i]._hasGroup) | |||
{ | |||
if (egid.groupID == _consumers[i]._group) | |||
{ | |||
_consumers[i].Enqueue(entity, egid); | |||
} | |||
} | |||
else | |||
{ | |||
_consumers[i].Enqueue(entity, egid); | |||
} | |||
} | |||
} | |||
public Consumer<T> GenerateConsumer(string name, uint capacity) | |||
{ | |||
var consumer = new Consumer<T>(name, capacity, this); | |||
_consumers.Add(consumer); | |||
return consumer; | |||
} | |||
public Consumer<T> GenerateConsumer(ExclusiveGroup group, string name, uint capacity) | |||
{ | |||
var consumer = new Consumer<T>(group, name, capacity, this); | |||
_consumers.Add(consumer); | |||
return consumer; | |||
} | |||
public void RemoveConsumer(Consumer<T> consumer) | |||
{ | |||
_consumers.UnorderedRemove(consumer); | |||
} | |||
readonly FasterListThreadSafe<Consumer<T>> _consumers = new FasterListThreadSafe<Consumer<T>>(); | |||
} | |||
public struct Consumer<T> : IDisposable where T : unmanaged, IEntityStruct | |||
{ | |||
internal Consumer(string name, uint capacity, EntityStream<T> stream):this() | |||
{ | |||
#if DEBUG && !PROFILER | |||
_name = name; | |||
#endif | |||
_ringBuffer = new RingBuffer<ValueTuple<T, EGID>>((int) capacity, | |||
#if DEBUG && !PROFILER | |||
_name | |||
#else | |||
string.Empty | |||
#endif | |||
); | |||
_stream = stream; | |||
} | |||
internal Consumer(ExclusiveGroup group, string name, uint capacity, EntityStream<T> stream) : this(name, | |||
capacity, stream) | |||
{ | |||
_group = group; | |||
_hasGroup = true; | |||
} | |||
internal void Enqueue(in T entity, in EGID egid) | |||
{ | |||
_ringBuffer.Enqueue((entity, egid)); | |||
} | |||
public bool TryDequeue(out T entity) | |||
{ | |||
var tryDequeue = _ringBuffer.TryDequeue(out var values); | |||
entity = values.Item1; | |||
return tryDequeue; | |||
} | |||
public bool TryDequeue(out T entity, out EGID id) | |||
{ | |||
var tryDequeue = _ringBuffer.TryDequeue(out var values); | |||
entity = values.Item1; | |||
id = values.Item2; | |||
return tryDequeue; | |||
} | |||
public void Flush() { _ringBuffer.Reset(); } | |||
public void Dispose() { _stream.RemoveConsumer(this); } | |||
public uint Count() { return (uint) _ringBuffer.Count; } | |||
readonly RingBuffer<ValueTuple<T, EGID>> _ringBuffer; | |||
readonly EntityStream<T> _stream; | |||
internal readonly ExclusiveGroup _group; | |||
internal readonly bool _hasGroup; | |||
#if DEBUG && !PROFILER | |||
readonly string _name; | |||
#endif | |||
} | |||
} |
@@ -1,75 +0,0 @@ | |||
using System; | |||
using Svelto.DataStructures; | |||
using Svelto.ECS.Internal; | |||
namespace Svelto.ECS | |||
{ | |||
public ref struct EntityStructInitializer | |||
{ | |||
public EntityStructInitializer(EGID id, FasterDictionary<RefWrapper<Type>, ITypeSafeDictionary> group) | |||
{ | |||
_group = group; | |||
_ID = id; | |||
} | |||
public void Init<T>(T initializer) where T : struct, IEntityStruct | |||
{ | |||
if (_group.TryGetValue(new RefWrapper<Type>(EntityBuilder<T>.ENTITY_VIEW_TYPE), | |||
out var typeSafeDictionary) == false) return; | |||
var dictionary = (TypeSafeDictionary<T>) typeSafeDictionary; | |||
if (EntityBuilder<T>.HAS_EGID) | |||
SetEGIDWithoutBoxing<T>.SetIDWithoutBoxing(ref initializer, _ID); | |||
if (dictionary.TryFindIndex(_ID.entityID, out var findElementIndex)) | |||
dictionary.GetDirectValue(findElementIndex) = initializer; | |||
} | |||
public void CopyFrom<T>(T initializer) where T : struct, IEntityStruct | |||
{ | |||
var dictionary = (TypeSafeDictionary<T>) _group[new RefWrapper<Type>(EntityBuilder<T>.ENTITY_VIEW_TYPE)]; | |||
if (EntityBuilder<T>.HAS_EGID) | |||
SetEGIDWithoutBoxing<T>.SetIDWithoutBoxing(ref initializer, _ID); | |||
dictionary[_ID.entityID] = initializer; | |||
} | |||
public ref T GetOrCreate<T>() where T : struct, IEntityStruct | |||
{ | |||
ref var entityDictionary = ref _group.GetOrCreate(new RefWrapper<Type>(EntityBuilder<T>.ENTITY_VIEW_TYPE) | |||
, () => new TypeSafeDictionary<T>()); | |||
var dictionary = (TypeSafeDictionary<T>) entityDictionary; | |||
return ref dictionary.GetOrCreate(_ID.entityID); | |||
} | |||
public T Get<T>() where T : struct, IEntityStruct | |||
{ | |||
return (_group[new RefWrapper<Type>(EntityBuilder<T>.ENTITY_VIEW_TYPE)] as TypeSafeDictionary<T>)[_ID.entityID]; | |||
} | |||
public bool Has<T>() where T : struct, IEntityStruct | |||
{ | |||
if (_group.TryGetValue(new RefWrapper<Type>(EntityBuilder<T>.ENTITY_VIEW_TYPE), | |||
out var typeSafeDictionary)) | |||
{ | |||
var dictionary = (TypeSafeDictionary<T>) typeSafeDictionary; | |||
if (dictionary.ContainsKey(_ID.entityID)) | |||
return true; | |||
} | |||
return false; | |||
} | |||
public static EntityStructInitializer CreateEmptyInitializer() | |||
{ | |||
return new EntityStructInitializer(new EGID(), new FasterDictionary<RefWrapper<Type>, ITypeSafeDictionary>()); | |||
} | |||
readonly EGID _ID; | |||
readonly FasterDictionary<RefWrapper<Type>, ITypeSafeDictionary> _group; | |||
} | |||
} |
@@ -1,11 +0,0 @@ | |||
fileFormatVersion: 2 | |||
guid: fb6bf1ad9be534e8a24b96e680030bb8 | |||
MonoImporter: | |||
externalObjects: {} | |||
serializedVersion: 2 | |||
defaultReferences: [] | |||
executionOrder: 0 | |||
icon: {instanceID: 0} | |||
userData: | |||
assetBundleName: | |||
assetBundleVariant: |
@@ -2,8 +2,20 @@ using System; | |||
namespace Svelto.ECS.Schedulers | |||
{ | |||
public interface IEntitySubmissionScheduler: IDisposable | |||
public interface IEntitiesSubmissionScheduler: IDisposable | |||
{ | |||
EnginesRoot.EntitiesSubmitter onTick { set; } | |||
bool paused { get; set; } | |||
} | |||
public abstract class EntitiesSubmissionScheduler: IEntitiesSubmissionScheduler | |||
{ | |||
protected internal abstract EnginesRoot.EntitiesSubmitter onTick { set; } | |||
public abstract void Dispose(); | |||
public abstract bool paused { get; set; } | |||
} | |||
public abstract class ISimpleEntitiesSubmissionScheduler: EntitiesSubmissionScheduler | |||
{ | |||
public abstract void SubmitEntities(); | |||
} | |||
} |
@@ -1,5 +1,4 @@ | |||
using System; | |||
using System.Diagnostics; | |||
namespace Svelto.ECS | |||
{ | |||
@@ -9,25 +8,37 @@ namespace Svelto.ECS | |||
: IEquatable<EntitySubmitOperation> | |||
{ | |||
public readonly EntitySubmitOperationType type; | |||
public readonly IEntityBuilder[] builders; | |||
public readonly IComponentBuilder[] builders; | |||
public readonly EGID fromID; | |||
public readonly EGID toID; | |||
#if DEBUG && !PROFILER | |||
public StackFrame trace; | |||
#if DEBUG && !PROFILE_SVELTO | |||
public System.Diagnostics.StackFrame trace; | |||
#endif | |||
public EntitySubmitOperation(EntitySubmitOperationType operation, EGID from, EGID to, | |||
IEntityBuilder[] builders = null) | |||
IComponentBuilder[] builders = null) | |||
{ | |||
type = operation; | |||
this.builders = builders; | |||
fromID = from; | |||
toID = to; | |||
#if DEBUG && !PROFILER | |||
#if DEBUG && !PROFILE_SVELTO | |||
trace = default; | |||
#endif | |||
} | |||
public EntitySubmitOperation | |||
(EntitySubmitOperationType operation, ExclusiveGroupStruct @group | |||
, IComponentBuilder[] descriptorComponentsToBuild):this() | |||
{ | |||
type = operation; | |||
this.builders = descriptorComponentsToBuild; | |||
fromID = new EGID(0, group); | |||
#if DEBUG && !PROFILE_SVELTO | |||
trace = default; | |||
#endif | |||
} | |||
public static bool operator ==(EntitySubmitOperation obj1, EntitySubmitOperation obj2) | |||
{ | |||
return obj1.Equals(obj2); | |||
@@ -48,6 +59,7 @@ namespace Svelto.ECS | |||
{ | |||
Swap, | |||
Remove, | |||
RemoveGroup | |||
RemoveGroup, | |||
SwapGroup | |||
} | |||
} |
@@ -1,77 +1,89 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using Svelto.DataStructures; | |||
using Svelto.ECS.Internal; | |||
using Svelto.Utilities; | |||
namespace Svelto.ECS | |||
{ | |||
#if DEBUG && !PROFILER | |||
#if DEBUG && !PROFILE_SVELTO | |||
struct ECSTuple<T1, T2> | |||
{ | |||
public readonly T1 implementorType; | |||
public readonly T1 instance; | |||
public T2 numberOfImplementations; | |||
public ECSTuple(T1 implementor, T2 v) | |||
{ | |||
implementorType = implementor; | |||
instance = implementor; | |||
numberOfImplementations = v; | |||
} | |||
} | |||
#endif | |||
static class EntityViewUtility | |||
static class EntityComponentUtility | |||
{ | |||
const string DUPLICATE_IMPLEMENTOR_ERROR = | |||
"<color=teal>Svelto.ECS</color> the same component is implemented with more than one implementor. This is " | |||
+ "considered an error and MUST be fixed. "; | |||
const string NULL_IMPLEMENTOR_ERROR = | |||
"<color=teal>Svelto.ECS</color> Null implementor, please be careful about the implementors passed to avoid " | |||
+ "performance loss "; | |||
public static void FillEntityView<T>(this IEntityBuilder entityBuilder | |||
, ref T entityView | |||
, FasterList<KeyValuePair<Type, ActionCast<T>>> | |||
entityViewBlazingFastReflection | |||
, IEnumerable<object> implementors, | |||
#if DEBUG && !PROFILER | |||
Dictionary<Type, ECSTuple<object, int>> implementorsByType | |||
const string NOT_FOUND_EXCEPTION = | |||
"<color=teal>Svelto.ECS</color> Implementor not found for an EntityComponent. "; | |||
public static void FillEntityComponent<T> | |||
(this IComponentBuilder componentBuilder, ref T entityComponent | |||
, FasterList<KeyValuePair<Type, FastInvokeActionCast<T>>> entityComponentBlazingFastReflection | |||
, IEnumerable<object> implementors | |||
#if DEBUG && !PROFILE_SVELTO | |||
,Dictionary<Type, ECSTuple<object, int>> implementorsByType | |||
#else | |||
Dictionary<Type, object> implementorsByType | |||
, Dictionary<Type, object> implementorsByType | |||
#endif | |||
, Dictionary<Type, Type[]> cachedTypes | |||
) | |||
, Dictionary<Type, Type[]> cachedTypeInterfaces) | |||
{ | |||
//efficient way to collect the fields of every EntityViewType | |||
var setters = | |||
FasterList<KeyValuePair<Type, ActionCast<T>>>.NoVirt.ToArrayFast(entityViewBlazingFastReflection, out var count); | |||
foreach (var implementor in implementors) | |||
//efficient way to collect the fields of every EntityComponentType | |||
var setters = FasterList<KeyValuePair<Type, FastInvokeActionCast<T>>>.NoVirt.ToArrayFast( | |||
entityComponentBlazingFastReflection, out var count); | |||
//todo this should happen once per T, not once per Build<T> | |||
if (implementors != null) | |||
{ | |||
if (implementor != null) | |||
foreach (var implementor in implementors) | |||
{ | |||
var type = implementor.GetType(); | |||
if (implementor != null) | |||
{ | |||
var type = implementor.GetType(); | |||
if (cachedTypes.TryGetValue(type, out var interfaces) == false) | |||
interfaces = cachedTypes[type] = type.GetInterfacesEx(); | |||
if (cachedTypeInterfaces.TryGetValue(type, out var interfaces) == false) | |||
interfaces = cachedTypeInterfaces[type] = type.GetInterfacesEx(); | |||
for (var iindex = 0; iindex < interfaces.Length; iindex++) | |||
{ | |||
var componentType = interfaces[iindex]; | |||
#if DEBUG && !PROFILER | |||
if (implementorsByType.TryGetValue(componentType, out var implementorData)) | |||
for (var iindex = 0; iindex < interfaces.Length; iindex++) | |||
{ | |||
implementorData.numberOfImplementations++; | |||
implementorsByType[componentType] = implementorData; | |||
} | |||
else | |||
implementorsByType[componentType] = new ECSTuple<object, int>(implementor, 1); | |||
var componentType = interfaces[iindex]; | |||
#if DEBUG && !PROFILE_SVELTO | |||
if (implementorsByType.TryGetValue(componentType, out var implementorData)) | |||
{ | |||
implementorData.numberOfImplementations++; | |||
implementorsByType[componentType] = implementorData; | |||
} | |||
else | |||
implementorsByType[componentType] = new ECSTuple<object, int>(implementor, 1); | |||
#else | |||
implementorsByType[componentType] = implementor; | |||
#endif | |||
} | |||
} | |||
#if DEBUG && !PROFILE_SVELTO | |||
else | |||
{ | |||
Console.Log(NULL_IMPLEMENTOR_ERROR.FastConcat(" entityComponent " | |||
, componentBuilder | |||
.GetEntityComponentType().ToString())); | |||
} | |||
} | |||
#if DEBUG && !PROFILER | |||
else | |||
{ | |||
Console.Log(NULL_IMPLEMENTOR_ERROR.FastConcat(" entityView ", | |||
entityBuilder.GetEntityType().ToString())); | |||
} | |||
#endif | |||
} | |||
} | |||
for (var i = 0; i < count; i++) | |||
@@ -79,47 +91,33 @@ namespace Svelto.ECS | |||
var fieldSetter = setters[i]; | |||
var fieldType = fieldSetter.Key; | |||
#if DEBUG && !PROFILER | |||
ECSTuple<object, int> component; | |||
#if DEBUG && !PROFILE_SVELTO | |||
ECSTuple<object, int> implementor; | |||
#else | |||
object component; | |||
object implementor; | |||
#endif | |||
if (implementorsByType.TryGetValue(fieldType, out component) == false) | |||
if (implementorsByType.TryGetValue(fieldType, out implementor) == false) | |||
{ | |||
var e = new ECSException(NOT_FOUND_EXCEPTION + " Component Type: " + fieldType.Name + | |||
" - EntityView: " + entityBuilder.GetEntityType().Name); | |||
var e = new ECSException(NOT_FOUND_EXCEPTION + " Component Type: " + fieldType.Name | |||
+ " - EntityComponent: " + componentBuilder.GetEntityComponentType().Name); | |||
throw e; | |||
} | |||
#if DEBUG && !PROFILER | |||
if (component.numberOfImplementations > 1) | |||
#if DEBUG && !PROFILE_SVELTO | |||
if (implementor.numberOfImplementations > 1) | |||
throw new ECSException(DUPLICATE_IMPLEMENTOR_ERROR.FastConcat( | |||
"Component Type: ", fieldType.Name, | |||
" implementor: ", | |||
component.implementorType | |||
.ToString()) + | |||
" - EntityView: " + | |||
entityBuilder.GetEntityType().Name); | |||
"Component Type: ", fieldType.Name, " implementor: ", implementor.instance.ToString()) + | |||
" - EntityComponent: " + componentBuilder.GetEntityComponentType().Name); | |||
#endif | |||
#if DEBUG && !PROFILER | |||
fieldSetter.Value(ref entityView, component.implementorType); | |||
#if DEBUG && !PROFILE_SVELTO | |||
fieldSetter.Value(ref entityComponent, implementor.instance); | |||
#else | |||
fieldSetter.Value(ref entityView, component); | |||
fieldSetter.Value(ref entityComponent, implementor); | |||
#endif | |||
} | |||
implementorsByType.Clear(); | |||
} | |||
const string DUPLICATE_IMPLEMENTOR_ERROR = | |||
"<color=teal>Svelto.ECS</color> the same component is implemented with more than one implementor. This is " + | |||
"considered an error and MUST be fixed. "; | |||
const string NULL_IMPLEMENTOR_ERROR = | |||
"<color=teal>Svelto.ECS</color> Null implementor, please be careful about the implementors passed to avoid " + | |||
"performance loss "; | |||
const string NOT_FOUND_EXCEPTION = "<color=teal>Svelto.ECS</color> Implementor not found for an EntityView. "; | |||
} | |||
} |
@@ -1,5 +1,6 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Runtime.CompilerServices; | |||
#pragma warning disable 660,661 | |||
@@ -17,26 +18,12 @@ namespace Svelto.ECS | |||
/// public static ExclusiveGroup[] GroupOfGroups = { MyExclusiveGroup1, ...}; //for each on this! | |||
/// } | |||
/// </summary> | |||
/// | |||
///use this like: | |||
/// public class TriggersGroup : ExclusiveGroup<TriggersGroup> {} | |||
public abstract class NamedExclusiveGroup<T>:ExclusiveGroup | |||
{ | |||
public static ExclusiveGroup Group = new ExclusiveGroup(); | |||
public static string name = typeof(T).FullName; | |||
public NamedExclusiveGroup() { } | |||
public NamedExclusiveGroup(string recognizeAs) : base(recognizeAs) | |||
{} | |||
public NamedExclusiveGroup(ushort range) : base(range) | |||
{} | |||
} | |||
///To debug it use in your debug window: Svelto.ECS.Debugger.EGID.GetGroupNameFromId(groupID) | |||
public class ExclusiveGroup | |||
{ | |||
public const uint MaxNumberOfExclusiveGroups = 2 << 20; | |||
public ExclusiveGroup() | |||
{ | |||
_group = ExclusiveGroupStruct.Generate(); | |||
@@ -46,7 +33,7 @@ namespace Svelto.ECS | |||
{ | |||
_group = ExclusiveGroupStruct.Generate(); | |||
_serialisedGroups.Add(recognizeAs, _group); | |||
_knownGroups.Add(recognizeAs, _group); | |||
} | |||
public ExclusiveGroup(ushort range) | |||
@@ -61,7 +48,7 @@ namespace Svelto.ECS | |||
{ | |||
return group._group; | |||
} | |||
public static explicit operator uint(ExclusiveGroup group) | |||
{ | |||
return group._group; | |||
@@ -71,118 +58,34 @@ namespace Svelto.ECS | |||
{ | |||
#if DEBUG | |||
if (a._range == 0) | |||
throw new ECSException("adding values to a not ranged ExclusiveGroup"); | |||
throw new ECSException($"Adding values to a not ranged ExclusiveGroup: {(uint)a}"); | |||
if (b >= a._range) | |||
throw new ECSException("Using out of range group"); | |||
#endif | |||
throw new ECSException($"Using out of range group: {(uint)a} + {b}"); | |||
#endif | |||
return a._group + b; | |||
} | |||
readonly ExclusiveGroupStruct _group; | |||
//I use this as parameter because it must not be possible to pass null Exclusive Groups. | |||
public struct ExclusiveGroupStruct : IEquatable<ExclusiveGroupStruct>, IComparable<ExclusiveGroupStruct>, | |||
IEqualityComparer<ExclusiveGroupStruct> | |||
//todo document the use case for this method | |||
public static ExclusiveGroupStruct Search(string holderGroupName) | |||
{ | |||
public static bool operator ==(ExclusiveGroupStruct c1, ExclusiveGroupStruct c2) | |||
{ | |||
return c1.Equals(c2); | |||
} | |||
public static bool operator !=(ExclusiveGroupStruct c1, ExclusiveGroupStruct c2) | |||
{ | |||
return c1.Equals(c2) == false; | |||
} | |||
public bool Equals(ExclusiveGroupStruct other) | |||
{ | |||
return other._id == _id; | |||
} | |||
public int CompareTo(ExclusiveGroupStruct other) | |||
{ | |||
return other._id.CompareTo(_id); | |||
} | |||
public bool Equals(ExclusiveGroupStruct x, ExclusiveGroupStruct y) | |||
{ | |||
return x._id == y._id; | |||
} | |||
public int GetHashCode(ExclusiveGroupStruct obj) | |||
{ | |||
return _id.GetHashCode(); | |||
} | |||
internal static ExclusiveGroupStruct Generate() | |||
{ | |||
ExclusiveGroupStruct groupStruct; | |||
groupStruct._id = _globalId; | |||
DBC.ECS.Check.Require(_globalId + 1 < ushort.MaxValue, "too many exclusive groups created"); | |||
_globalId++; | |||
return groupStruct; | |||
} | |||
/// <summary> | |||
/// Use this constructor to reserve N groups | |||
/// </summary> | |||
internal ExclusiveGroupStruct(ushort range) | |||
{ | |||
_id = _globalId; | |||
DBC.ECS.Check.Require(_globalId + range < ushort.MaxValue, "too many exclusive groups created"); | |||
_globalId += range; | |||
} | |||
internal ExclusiveGroupStruct(uint groupID) | |||
{ | |||
_id = groupID; | |||
} | |||
public ExclusiveGroupStruct(byte[] data, uint pos) | |||
{ | |||
_id = (uint)( | |||
data[pos++] | |||
| data[pos++] << 8 | |||
| data[pos++] << 16 | |||
| data[pos++] << 24 | |||
); | |||
DBC.ECS.Check.Ensure(_id < _globalId, "Invalid group ID deserialiased"); | |||
} | |||
public static implicit operator uint(ExclusiveGroupStruct groupStruct) | |||
{ | |||
return groupStruct._id; | |||
} | |||
public static ExclusiveGroupStruct operator+(ExclusiveGroupStruct a, uint b) | |||
{ | |||
var group = new ExclusiveGroupStruct(); | |||
group._id = a._id + b; | |||
return group; | |||
} | |||
if (_knownGroups.ContainsKey(holderGroupName) == false) | |||
throw new Exception("Named Group Not Found ".FastConcat(holderGroupName)); | |||
uint _id; | |||
static uint _globalId; | |||
return _knownGroups[holderGroupName]; | |||
} | |||
public static ExclusiveGroupStruct Search(string holderGroupName) | |||
public override string ToString() | |||
{ | |||
if (_serialisedGroups.ContainsKey(holderGroupName) == false) | |||
throw new Exception("Named Group Not Found ".FastConcat(holderGroupName)); | |||
return _serialisedGroups[holderGroupName]; | |||
return _group.ToString(); | |||
} | |||
static readonly Dictionary<string, ExclusiveGroupStruct> _serialisedGroups = new Dictionary<string, | |||
static readonly Dictionary<string, ExclusiveGroupStruct> _knownGroups = new Dictionary<string, | |||
ExclusiveGroupStruct>(); | |||
#if DEBUG | |||
readonly ushort _range; | |||
#endif | |||
#endif | |||
readonly ExclusiveGroupStruct _group; | |||
} | |||
} | |||
@@ -260,6 +163,6 @@ namespace Svelto.ECS | |||
} | |||
#if DEBUG | |||
static string[] groupNames = new string[ushort.MaxValue]; | |||
static string[] groupNames = new string[ExclusiveGroup.MaxNumberOfExclusiveGroups]; | |||
#endif | |||
#endif |
@@ -0,0 +1,139 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Runtime.InteropServices; | |||
namespace Svelto.ECS | |||
{ | |||
public readonly struct BuildGroup | |||
{ | |||
internal BuildGroup(ExclusiveGroupStruct group) | |||
{ | |||
this.group = group; | |||
} | |||
public static implicit operator BuildGroup(ExclusiveGroupStruct group) | |||
{ | |||
return new BuildGroup(group); | |||
} | |||
public static implicit operator BuildGroup(ExclusiveGroup group) | |||
{ | |||
return new BuildGroup(group); | |||
} | |||
public static implicit operator uint(BuildGroup groupStruct) | |||
{ | |||
return groupStruct.group; | |||
} | |||
internal ExclusiveGroupStruct @group { get; } | |||
} | |||
[StructLayout(LayoutKind.Explicit, Size = 4)] | |||
public struct ExclusiveGroupStruct : IEquatable<ExclusiveGroupStruct>, IComparable<ExclusiveGroupStruct>, | |||
IEqualityComparer<ExclusiveGroupStruct> | |||
{ | |||
public override bool Equals(object obj) | |||
{ | |||
return obj is ExclusiveGroupStruct other && Equals(other); | |||
} | |||
public override int GetHashCode() | |||
{ | |||
return (int) _id; | |||
} | |||
public static bool operator ==(ExclusiveGroupStruct c1, ExclusiveGroupStruct c2) | |||
{ | |||
return c1.Equals(c2); | |||
} | |||
public static bool operator !=(ExclusiveGroupStruct c1, ExclusiveGroupStruct c2) | |||
{ | |||
return c1.Equals(c2) == false; | |||
} | |||
public bool Equals(ExclusiveGroupStruct other) | |||
{ | |||
return other._id == _id; | |||
} | |||
public int CompareTo(ExclusiveGroupStruct other) | |||
{ | |||
return other._id.CompareTo(_id); | |||
} | |||
public bool Equals(ExclusiveGroupStruct x, ExclusiveGroupStruct y) | |||
{ | |||
return x._id == y._id; | |||
} | |||
public int GetHashCode(ExclusiveGroupStruct obj) | |||
{ | |||
return _id.GetHashCode(); | |||
} | |||
public override string ToString() | |||
{ | |||
return this.ToName(); | |||
} | |||
internal static ExclusiveGroupStruct Generate(byte bitmask = 0) | |||
{ | |||
ExclusiveGroupStruct groupStruct; | |||
groupStruct._id = _globalId; | |||
groupStruct._bytemask = bitmask; | |||
DBC.ECS.Check.Require(_globalId + 1 < ExclusiveGroup.MaxNumberOfExclusiveGroups, "too many exclusive groups created"); | |||
_globalId++; | |||
return groupStruct; | |||
} | |||
internal ExclusiveGroupStruct(ExclusiveGroupStruct @group):this() { this = group; } | |||
/// <summary> | |||
/// Use this constructor to reserve N groups | |||
/// </summary> | |||
internal ExclusiveGroupStruct(ushort range):this() | |||
{ | |||
_id = _globalId; | |||
DBC.ECS.Check.Require(_globalId + range < ExclusiveGroup.MaxNumberOfExclusiveGroups, "too many exclusive groups created"); | |||
_globalId += range; | |||
} | |||
internal ExclusiveGroupStruct(uint groupID):this() | |||
{ | |||
_id = groupID; | |||
} | |||
public ExclusiveGroupStruct(byte[] data, uint pos):this() | |||
{ | |||
_id = (uint)( | |||
data[pos] | |||
| data[++pos] << 8 | |||
| data[++pos] << 16 | |||
); | |||
_bytemask = (byte) (data[++pos] << 24); | |||
DBC.ECS.Check.Ensure(_id < _globalId, "Invalid group ID deserialiased"); | |||
} | |||
public static implicit operator uint(ExclusiveGroupStruct groupStruct) | |||
{ | |||
return groupStruct._id; | |||
} | |||
public static ExclusiveGroupStruct operator+(ExclusiveGroupStruct a, uint b) | |||
{ | |||
var group = new ExclusiveGroupStruct {_id = a._id + b}; | |||
return @group; | |||
} | |||
[FieldOffset(0)] uint _id; | |||
[FieldOffset(3)] byte _bytemask; | |||
static uint _globalId = 1; //it starts from 1 because default EGID is considered not initalized value | |||
} | |||
} |
@@ -0,0 +1,11 @@ | |||
fileFormatVersion: 2 | |||
guid: 67dde70e73a3374a87d0e9b93385405a | |||
MonoImporter: | |||
externalObjects: {} | |||
serializedVersion: 2 | |||
defaultReferences: [] | |||
executionOrder: 0 | |||
icon: {instanceID: 0} | |||
userData: | |||
assetBundleName: | |||
assetBundleVariant: |
@@ -1,61 +0,0 @@ | |||
using System; | |||
using Svelto.DataStructures; | |||
namespace Svelto.ECS.Internal | |||
{ | |||
partial class EntitiesDB | |||
{ | |||
public void ExecuteOnAllEntities<T>(Action<T[], ExclusiveGroup.ExclusiveGroupStruct, uint, IEntitiesDB> action) | |||
where T : struct, IEntityStruct | |||
{ | |||
var type = typeof(T); | |||
if (_groupsPerEntity.TryGetValue(new RefWrapper<Type>(type), out var dictionary)) | |||
{ | |||
foreach (var pair in dictionary) | |||
{ | |||
var entities = (pair.Value as TypeSafeDictionary<T>).GetValuesArray(out var innerCount); | |||
if (innerCount > 0) | |||
action(entities, new ExclusiveGroup.ExclusiveGroupStruct(pair.Key), innerCount, this); | |||
} | |||
} | |||
} | |||
public void ExecuteOnAllEntities | |||
<T, W>(W value, Action<T[], ExclusiveGroup.ExclusiveGroupStruct, uint, IEntitiesDB, W> action) | |||
where T : struct, IEntityStruct | |||
{ | |||
var type = typeof(T); | |||
if (_groupsPerEntity.TryGetValue(new RefWrapper<Type>(type), out var dic)) | |||
{ | |||
foreach (var pair in dic) | |||
{ | |||
var entities = (pair.Value as TypeSafeDictionary<T>).GetValuesArray(out var innerCount); | |||
if (innerCount > 0) | |||
action(entities, new ExclusiveGroup.ExclusiveGroupStruct(pair.Key), innerCount, this, value); | |||
} | |||
} | |||
} | |||
public void ExecuteOnAllEntities | |||
<T, W>(ref W value, ExecuteOnAllEntitiesAction<T, W> action) | |||
where T : struct, IEntityStruct | |||
{ | |||
var type = typeof(T); | |||
if (_groupsPerEntity.TryGetValue(new RefWrapper<Type>(type), out var dic)) | |||
{ | |||
foreach (var pair in dic) | |||
{ | |||
var entities = (pair.Value as TypeSafeDictionary<T>).GetValuesArray(out var innerCount); | |||
if (innerCount > 0) | |||
action(entities, new ExclusiveGroup.ExclusiveGroupStruct(pair.Key), innerCount, this, ref value); | |||
} | |||
} | |||
} | |||
} | |||
} |
@@ -1,11 +0,0 @@ | |||
fileFormatVersion: 2 | |||
guid: 43acb9dc60f93889a5814ea34494169b | |||
MonoImporter: | |||
externalObjects: {} | |||
serializedVersion: 2 | |||
defaultReferences: [] | |||
executionOrder: 0 | |||
icon: {instanceID: 0} | |||
userData: | |||
assetBundleName: | |||
assetBundleVariant: |
@@ -8,7 +8,7 @@ namespace Svelto.ECS | |||
/// to swap and remove specialized entities from abstract engines | |||
/// </summary> | |||
/// <typeparam name="TType"></typeparam> | |||
public class ExtendibleEntityDescriptor<TType> : IEntityDescriptor where TType : IEntityDescriptor, new() | |||
public class ExtendibleEntityDescriptor<TType> : IDynamicEntityDescriptor where TType : IEntityDescriptor, new() | |||
{ | |||
static ExtendibleEntityDescriptor() | |||
{ | |||
@@ -17,7 +17,7 @@ namespace Svelto.ECS | |||
$"SerializableEntityDescriptors cannot be used as base entity descriptor: {typeof(TType)}"); | |||
} | |||
public ExtendibleEntityDescriptor(IEntityBuilder[] extraEntities) | |||
public ExtendibleEntityDescriptor(IComponentBuilder[] extraEntities) | |||
{ | |||
_dynamicDescriptor = new DynamicEntityDescriptor<TType>(extraEntities); | |||
} | |||
@@ -34,14 +34,14 @@ namespace Svelto.ECS | |||
return this; | |||
} | |||
public ExtendibleEntityDescriptor<TType> ExtendWith(IEntityBuilder[] extraEntities) | |||
public ExtendibleEntityDescriptor<TType> ExtendWith(IComponentBuilder[] extraEntities) | |||
{ | |||
_dynamicDescriptor.ExtendWith(extraEntities); | |||
return this; | |||
} | |||
public IEntityBuilder[] entitiesToBuild => _dynamicDescriptor.entitiesToBuild; | |||
public IComponentBuilder[] componentsToBuild => _dynamicDescriptor.componentsToBuild; | |||
DynamicEntityDescriptor<TType> _dynamicDescriptor; | |||
} |
@@ -0,0 +1,19 @@ | |||
using System; | |||
namespace Svelto.ECS | |||
{ | |||
internal static class ProcessorCount | |||
{ | |||
public static readonly int processorCount = Environment.ProcessorCount; | |||
public static int BatchSize(uint totalIterations) | |||
{ | |||
var iterationsPerBatch = totalIterations / processorCount; | |||
if (iterationsPerBatch < 32) | |||
return 32; | |||
return (int) iterationsPerBatch; | |||
} | |||
} | |||
} |
@@ -0,0 +1,11 @@ | |||
fileFormatVersion: 2 | |||
guid: ce4c5807b1b43a598f0b8d7820575611 | |||
MonoImporter: | |||
externalObjects: {} | |||
serializedVersion: 2 | |||
defaultReferences: [] | |||
executionOrder: 0 | |||
icon: {instanceID: 0} | |||
userData: | |||
assetBundleName: | |||
assetBundleVariant: |
@@ -0,0 +1,8 @@ | |||
fileFormatVersion: 2 | |||
guid: 30192a9315e83c87a194215a241c9fa1 | |||
folderAsset: yes | |||
DefaultImporter: | |||
externalObjects: {} | |||
userData: | |||
assetBundleName: | |||
assetBundleVariant: |
@@ -0,0 +1,70 @@ | |||
using Svelto.Common; | |||
using Svelto.DataStructures; | |||
using Svelto.ECS.Internal; | |||
namespace Svelto.ECS | |||
{ | |||
/// <summary> | |||
/// ToDo it would be interesting to have a version of this dedicated to unmanaged, IEntityComponent | |||
/// that can be burstifiable | |||
/// </summary> | |||
/// <typeparam name="T1"></typeparam> | |||
public readonly struct AllGroupsEnumerable<T1> where T1 : struct, IEntityComponent | |||
{ | |||
public ref struct GroupCollection | |||
{ | |||
internal EntityCollection<T1> collection; | |||
internal ExclusiveGroupStruct group; | |||
public void Deconstruct(out EntityCollection<T1> collection, out ExclusiveGroupStruct group) | |||
{ | |||
collection = this.collection; | |||
group = this.@group; | |||
} | |||
} | |||
public AllGroupsEnumerable(EntitiesDB db) | |||
{ | |||
_db = db; | |||
} | |||
public ref struct GroupsIterator | |||
{ | |||
public GroupsIterator(EntitiesDB db) : this() | |||
{ | |||
_db = db.FindGroups_INTERNAL(TypeCache<T1>.type).GetEnumerator(); | |||
} | |||
public bool MoveNext() | |||
{ | |||
//attention, the while is necessary to skip empty groups | |||
while (_db.MoveNext() == true) | |||
{ | |||
FasterDictionary<ExclusiveGroupStruct, ITypeSafeDictionary>.KeyValuePairFast group = _db.Current; | |||
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 = new ExclusiveGroupStruct(group.Key); | |||
return true; | |||
} | |||
return false; | |||
} | |||
public GroupCollection Current => _array; | |||
FasterDictionary<ExclusiveGroupStruct, ITypeSafeDictionary>.FasterDictionaryKeyValueEnumerator _db; | |||
GroupCollection _array; | |||
} | |||
public GroupsIterator GetEnumerator() | |||
{ | |||
return new GroupsIterator(_db); | |||
} | |||
readonly EntitiesDB _db; | |||
} | |||
} |
@@ -0,0 +1,11 @@ | |||
fileFormatVersion: 2 | |||
guid: 6a6569334364384ea95a60579864448c | |||
MonoImporter: | |||
externalObjects: {} | |||
serializedVersion: 2 | |||
defaultReferences: [] | |||
executionOrder: 0 | |||
icon: {instanceID: 0} | |||
userData: | |||
assetBundleName: | |||
assetBundleVariant: |
@@ -0,0 +1,251 @@ | |||
using System; | |||
using System.Runtime.CompilerServices; | |||
using Svelto.DataStructures; | |||
using Svelto.ECS.Hybrid; | |||
namespace Svelto.ECS | |||
{ | |||
public static class EntityCollectionExtension | |||
{ | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public static void Deconstruct<T1> | |||
(in this EntityCollection<T1> ec, out NB<T1> buffer, out int count) where T1 : unmanaged, IEntityComponent | |||
{ | |||
buffer = ec._nativedBuffer; | |||
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 | |||
{ | |||
buffer1 = ec.buffer1._nativedBuffer; | |||
buffer2 = ec.buffer2._nativedBuffer; | |||
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 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; | |||
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 | |||
{ | |||
buffer1 = ec.Item1._nativedBuffer; | |||
buffer2 = ec.Item2._nativedBuffer; | |||
buffer3 = ec.Item3._nativedBuffer; | |||
buffer4 = ec.Item4._nativedBuffer; | |||
count = (int) ec.count; | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public static BT<NB<T1>> ToBuffer<T1>(in this EntityCollection<T1> ec) where T1 : unmanaged, IEntityComponent | |||
{ | |||
return new BT<NB<T1>>(ec._nativedBuffer, ec.count); | |||
} | |||
[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 | |||
{ | |||
return new BT<NB<T1>, NB<T2>>(ec.buffer1._nativedBuffer, ec.buffer2._nativedBuffer, 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 | |||
where T1 : unmanaged, IEntityComponent | |||
where T3 : unmanaged, IEntityComponent | |||
{ | |||
return new BT<NB<T1>, NB<T2>, NB<T3>>(ec.buffer1._nativedBuffer, ec.buffer2._nativedBuffer | |||
, ec.buffer3._nativedBuffer, 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 | |||
{ | |||
buffer = ec._managedBuffer; | |||
count = (int) ec.count; | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public static BT<MB<T1>> ToBuffer<T1>(in this EntityCollection<T1> ec) where T1 : struct, IEntityViewComponent | |||
{ | |||
return new BT<MB<T1>>(ec._managedBuffer, ec.count); | |||
} | |||
[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 | |||
{ | |||
buffer1 = ec.buffer1._managedBuffer; | |||
buffer2 = ec.buffer2._managedBuffer; | |||
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 | |||
{ | |||
return (ec.buffer1._managedBuffer, ec.buffer2._managedBuffer, ec.count); | |||
} | |||
[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 | |||
{ | |||
buffer1 = ec.buffer1._managedBuffer; | |||
buffer2 = ec.buffer2._managedBuffer; | |||
buffer3 = ec.buffer3._managedBuffer; | |||
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 | |||
where T1 : struct, IEntityViewComponent | |||
where T3 : struct, IEntityViewComponent | |||
{ | |||
return (ec.buffer1._managedBuffer, ec.buffer2._managedBuffer, ec.buffer3._managedBuffer, ec.count); | |||
} | |||
} | |||
public static class EntityCollectionExtensionC | |||
{ | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public static (NB<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 | |||
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; | |||
} | |||
[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 | |||
where T2 : struct, IEntityViewComponent | |||
where T3 : struct, IEntityViewComponent | |||
{ | |||
buffer1 = ec.buffer1._nativedBuffer; | |||
buffer2 = ec.buffer2._managedBuffer; | |||
buffer3 = ec.buffer3._managedBuffer; | |||
count = (int) ec.count; | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public static void Deconstruct<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.Item1._nativedBuffer; | |||
buffer2 = ec.Item2._nativedBuffer; | |||
buffer3 = ec.Item3._nativedBuffer; | |||
buffer4 = ec.Item4._managedBuffer; | |||
count = (int) ec.count; | |||
} | |||
} | |||
public static class EntityCollectionExtensionD | |||
{ | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public static void Deconstruct<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; | |||
} | |||
[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) | |||
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.Item1._nativedBuffer, ec.Item2._nativedBuffer | |||
, ec.Item3._nativedBuffer, ec.Item4._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 | |||
{ | |||
buffer1 = ec.Item1._nativedBuffer; | |||
buffer2 = ec.Item2._nativedBuffer; | |||
buffer3 = ec.Item3._managedBuffer; | |||
buffer4 = ec.Item4._managedBuffer; | |||
count = (int) ec.count; | |||
} | |||
} | |||
} |
@@ -0,0 +1,11 @@ | |||
fileFormatVersion: 2 | |||
guid: 27ea6afd09113d0ebdfba19bde433004 | |||
MonoImporter: | |||
externalObjects: {} | |||
serializedVersion: 2 | |||
defaultReferences: [] | |||
executionOrder: 0 | |||
icon: {instanceID: 0} | |||
userData: | |||
assetBundleName: | |||
assetBundleVariant: |
@@ -0,0 +1,109 @@ | |||
using System.Runtime.CompilerServices; | |||
using Svelto.DataStructures; | |||
using Svelto.ECS.Hybrid; | |||
using Svelto.ECS.Internal; | |||
namespace Svelto.ECS | |||
{ | |||
public static class EntityManagedDBExtensions | |||
{ | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public static MB<T> QueryEntitiesAndIndex<T>(this EntitiesDB entitiesDb, EGID entityGID, out uint index) where T : struct, IEntityViewComponent | |||
{ | |||
if (entitiesDb.QueryEntitiesAndIndexInternal<T>(entityGID, out index, out MB<T> array) == true) | |||
return array; | |||
throw new EntityNotFoundException(entityGID, typeof(T)); | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public static bool TryQueryEntitiesAndIndex<T>(this EntitiesDB entitiesDb, EGID entityGID, out uint index, out MB<T> array) | |||
where T : struct, IEntityViewComponent | |||
{ | |||
if (entitiesDb.QueryEntitiesAndIndexInternal<T>(entityGID, out index, out array) == true) | |||
return true; | |||
return false; | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public static bool TryQueryEntitiesAndIndex<T>(this EntitiesDB entitiesDb, uint id, ExclusiveGroupStruct group, out uint index, out MB<T> array) | |||
where T : struct, IEntityViewComponent | |||
{ | |||
if (entitiesDb.QueryEntitiesAndIndexInternal<T>(new EGID(id, group), out index, out array) == true) | |||
return true; | |||
return false; | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
static bool QueryEntitiesAndIndexInternal<T>(this EntitiesDB entitiesDb, EGID entityGID, out uint index, out MB<T> buffer) where T : struct, IEntityViewComponent | |||
{ | |||
index = 0; | |||
buffer = default; | |||
if (entitiesDb.SafeQueryEntityDictionary<T>(entityGID.groupID, out var safeDictionary) == false) | |||
return false; | |||
if (safeDictionary.TryFindIndex(entityGID.entityID, out index) == false) | |||
return false; | |||
buffer = (MB<T>) (safeDictionary as ITypeSafeDictionary<T>).GetValues(out _); | |||
return true; | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public static ref T QueryEntity<T>(this EntitiesDB entitiesDb, EGID entityGID) where T : struct, IEntityViewComponent | |||
{ | |||
var array = entitiesDb.QueryEntitiesAndIndex<T>(entityGID, out var index); | |||
return ref array[(int) index]; | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public static ref T QueryEntity<T>(this EntitiesDB entitiesDb, uint id, ExclusiveGroupStruct group) where T : struct, IEntityViewComponent | |||
{ | |||
return ref entitiesDb.QueryEntity<T>(new EGID(id, group)); | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public static ref T QueryUniqueEntity<T>(this EntitiesDB entitiesDb, ExclusiveGroupStruct group) where T : struct, IEntityViewComponent | |||
{ | |||
var (entities, entitiescount) = entitiesDb.QueryEntities<T>(@group); | |||
#if DEBUG && !PROFILE_SVELTO | |||
if (entitiescount == 0) | |||
throw new ECSException("Unique entity not found '".FastConcat(typeof(T).ToString()).FastConcat("'")); | |||
if (entitiescount != 1) | |||
throw new ECSException("Unique entities must be unique! '".FastConcat(typeof(T).ToString()) | |||
.FastConcat("'")); | |||
#endif | |||
return ref entities[0]; | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public static MB<T> GetArrayAndEntityIndex<T>(this EGIDMapper<T> mapper, uint entityID, out uint index) where T : struct, IEntityViewComponent | |||
{ | |||
if (mapper._map.TryFindIndex(entityID, out index)) | |||
{ | |||
return (MB<T>) mapper._map.GetValues(out _); | |||
} | |||
throw new ECSException("Entity not found"); | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public static bool TryGetArrayAndEntityIndex<T>(this EGIDMapper<T> mapper, uint entityID, out uint index, out MB<T> array) where T : struct, IEntityViewComponent | |||
{ | |||
index = default; | |||
if (mapper._map != null && mapper._map.TryFindIndex(entityID, out index)) | |||
{ | |||
array = (MB<T>) mapper._map.GetValues(out _); | |||
return true; | |||
} | |||
array = default; | |||
return false; | |||
} | |||
} | |||
} |
@@ -0,0 +1,11 @@ | |||
fileFormatVersion: 2 | |||
guid: 45c4aaf99be337d0b1599b27d7da352f | |||
MonoImporter: | |||
externalObjects: {} | |||
serializedVersion: 2 | |||
defaultReferences: [] | |||
executionOrder: 0 | |||
icon: {instanceID: 0} | |||
userData: | |||
assetBundleName: | |||
assetBundleVariant: |
@@ -0,0 +1,125 @@ | |||
using System.Runtime.CompilerServices; | |||
using Svelto.DataStructures; | |||
using Svelto.ECS.Internal; | |||
namespace Svelto.ECS | |||
{ | |||
public static class EntityNativeDBExtensions | |||
{ | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public static AllGroupsEnumerable<T1> QueryEntities<T1>(this EntitiesDB db) | |||
where T1 :struct, IEntityComponent | |||
{ | |||
return new AllGroupsEnumerable<T1>(db); | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public static NB<T> QueryEntitiesAndIndex<T>(this EntitiesDB entitiesDb, EGID entityGID, out uint index) where T : unmanaged, IEntityComponent | |||
{ | |||
if (entitiesDb.QueryEntitiesAndIndexInternal(entityGID, out index, out NB<T> array) == true) | |||
return array; | |||
throw new EntityNotFoundException(entityGID, typeof(T)); | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public static NB<T> QueryEntitiesAndIndex<T>(this EntitiesDB entitiesDb, uint id, ExclusiveGroupStruct group, out uint index) where T : unmanaged, IEntityComponent | |||
{ | |||
EGID entityGID = new EGID(id, group); | |||
if (entitiesDb.QueryEntitiesAndIndexInternal(entityGID, out index, out NB<T> array) == true) | |||
return array; | |||
throw new EntityNotFoundException(entityGID, typeof(T)); | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public static bool TryQueryEntitiesAndIndex<T>(this EntitiesDB entitiesDb, EGID entityGID, out uint index, out NB<T> array) | |||
where T : unmanaged, IEntityComponent | |||
{ | |||
if (entitiesDb.QueryEntitiesAndIndexInternal(entityGID, out index, out array) == true) | |||
return true; | |||
return false; | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public static bool TryQueryEntitiesAndIndex<T>(this EntitiesDB entitiesDb, uint id, ExclusiveGroupStruct group, out uint index, out NB<T> array) | |||
where T : unmanaged, IEntityComponent | |||
{ | |||
if (entitiesDb.QueryEntitiesAndIndexInternal(new EGID(id, group), out index, out array) == true) | |||
return true; | |||
return false; | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
static bool QueryEntitiesAndIndexInternal<T>(this EntitiesDB entitiesDb, EGID entityGID, out uint index, out NB<T> buffer) where T : unmanaged, IEntityComponent | |||
{ | |||
index = 0; | |||
buffer = default; | |||
if (entitiesDb.SafeQueryEntityDictionary<T>(entityGID.groupID, out var safeDictionary) == false) | |||
return false; | |||
if (safeDictionary.TryFindIndex(entityGID.entityID, out index) == false) | |||
return false; | |||
buffer = (NB<T>) (safeDictionary as ITypeSafeDictionary<T>).GetValues(out _); | |||
return true; | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public static ref T QueryEntity<T>(this EntitiesDB entitiesDb, EGID entityGID) where T : unmanaged, IEntityComponent | |||
{ | |||
var array = entitiesDb.QueryEntitiesAndIndex<T>(entityGID, out var index); | |||
return ref array[(int) index]; | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public static ref T QueryEntity<T>(this EntitiesDB entitiesDb, uint id, ExclusiveGroupStruct group) where T : unmanaged, IEntityComponent | |||
{ | |||
return ref entitiesDb.QueryEntity<T>(new EGID(id, group)); | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public static ref T QueryUniqueEntity<T>(this EntitiesDB entitiesDb, ExclusiveGroupStruct group) where T : unmanaged, IEntityComponent | |||
{ | |||
var (entities, entitiescount) = entitiesDb.QueryEntities<T>(@group); | |||
#if DEBUG && !PROFILE_SVELTO | |||
if (entitiescount == 0) | |||
throw new ECSException("Unique entity not found '".FastConcat(typeof(T).ToString()).FastConcat("'")); | |||
if (entitiescount != 1) | |||
throw new ECSException("Unique entities must be unique! '".FastConcat(typeof(T).ToString()) | |||
.FastConcat("'")); | |||
#endif | |||
return ref entities[0]; | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public static NB<T> GetArrayAndEntityIndex<T>(this EGIDMapper<T> mapper, uint entityID, out uint index) where T : unmanaged, IEntityComponent | |||
{ | |||
if (mapper._map.TryFindIndex(entityID, out index)) | |||
{ | |||
return (NB<T>) mapper._map.GetValues(out _); | |||
} | |||
throw new ECSException("Entity not found"); | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public static bool TryGetArrayAndEntityIndex<T>(this EGIDMapper<T> mapper, uint entityID, out uint index, out NB<T> array) where T : unmanaged, IEntityComponent | |||
{ | |||
index = default; | |||
if (mapper._map != null && mapper._map.TryFindIndex(entityID, out index)) | |||
{ | |||
array = (NB<T>) mapper._map.GetValues(out _); | |||
return true; | |||
} | |||
array = default; | |||
return false; | |||
} | |||
} | |||
} |
@@ -0,0 +1,11 @@ | |||
fileFormatVersion: 2 | |||
guid: 319a6a0269bf3d1bbca89f52ea1d0aeb | |||
MonoImporter: | |||
externalObjects: {} | |||
serializedVersion: 2 | |||
defaultReferences: [] | |||
executionOrder: 0 | |||
icon: {instanceID: 0} | |||
userData: | |||
assetBundleName: | |||
assetBundleVariant: |