using System; using System.Runtime.CompilerServices; using Svelto.Common; using Svelto.DataStructures; using Svelto.ECS.Hybrid; namespace Svelto.ECS.Internal { sealed class TypeSafeDictionary : ITypeSafeDictionary where TValue : struct, IEntityComponent { static readonly Type _type = typeof(TValue); static readonly bool _hasEgid = typeof(INeedEGID).IsAssignableFrom(_type); static readonly bool _hasReference = typeof(INeedEntityReference).IsAssignableFrom(_type); internal static readonly bool isUnmanaged = _type.IsUnmanagedEx() && (typeof(IEntityViewComponent).IsAssignableFrom(_type) == false); public TypeSafeDictionary(uint size) { if (isUnmanaged) implUnmgd = new SveltoDictionary>, NativeStrategy, NativeStrategy>(size, Allocator.Persistent); else { implMgd = new SveltoDictionary>, ManagedStrategy, ManagedStrategy>(size, Allocator.Managed); } } [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Add(uint egidEntityId, in TValue entityComponent) { if (isUnmanaged) implUnmgd.Add(egidEntityId, entityComponent); else implMgd.Add(egidEntityId, entityComponent); } /// todo: Is this really needed, cannot I just use AddEntitiesFromDictionary? Needs to be checked public void AddEntityToDictionary(EGID fromEntityGid, EGID toEntityID, ITypeSafeDictionary toGroup) { if (isUnmanaged) { var valueIndex = implUnmgd.GetIndex(fromEntityGid.entityID); DBC.ECS.Check.Require(toGroup != null , "Invalid To Group"); //todo check this, if it's right merge GetIndex { var toGroupCasted = toGroup as ITypeSafeDictionary; ref var entity = ref implUnmgd.GetDirectValueByRef(valueIndex); if (_hasEgid) SetEGIDWithoutBoxing.SetIDWithoutBoxing(ref entity, toEntityID); toGroupCasted.Add(toEntityID.entityID, entity); } } else { var valueIndex = implMgd.GetIndex(fromEntityGid.entityID); DBC.ECS.Check.Require(toGroup != null , "Invalid To Group"); //todo check this, if it's right merge GetIndex { var toGroupCasted = toGroup as ITypeSafeDictionary; ref var entity = ref implMgd.GetDirectValueByRef(valueIndex); if (_hasEgid) SetEGIDWithoutBoxing.SetIDWithoutBoxing(ref entity, toEntityID); toGroupCasted.Add(toEntityID.entityID, entity); } } } /// /// Add entities from external typeSafeDictionary (todo: add use case) /// /// /// /// /// public void AddEntitiesFromDictionary (ITypeSafeDictionary entitiesToSubmit, ExclusiveGroupStruct groupId, EnginesRoot enginesRoot) { var safeDictionary = (entitiesToSubmit as TypeSafeDictionary); if (isUnmanaged) { var typeSafeDictionary = safeDictionary.implUnmgd; foreach (var tuple in typeSafeDictionary) try { var egid = new EGID(tuple.Key, groupId); if (_hasEgid) SetEGIDWithoutBoxing.SetIDWithoutBoxing(ref tuple.Value, egid); if (_hasReference) SetEGIDWithoutBoxing.SetRefWithoutBoxing( ref tuple.Value, enginesRoot.entityLocator.GetEntityReference(egid)); 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.ToName()).FastConcat(", id ").FastConcat(tuple.Key)); throw; } } else { var typeSafeDictionary = safeDictionary.implMgd; foreach (var tuple in typeSafeDictionary) try { if (_hasEgid) SetEGIDWithoutBoxing.SetIDWithoutBoxing( ref tuple.Value, new EGID(tuple.Key, groupId)); implMgd.Add(tuple.Key, tuple.Value); } catch (Exception e) { Console.LogException( e, "trying to add an EntityComponent with the same ID more than once Entity: ".FastConcat(typeof(TValue).ToString()).FastConcat(", group ").FastConcat(groupId.ToName()).FastConcat(", id ").FastConcat(tuple.Key)); throw; } } } public void ExecuteEnginesAddOrSwapCallbacks (FasterDictionary> entityComponentEnginesDB , ITypeSafeDictionary realDic, ExclusiveGroupStruct? fromGroup, ExclusiveGroupStruct toGroup , in PlatformProfiler profiler) { if (isUnmanaged) { var typeSafeDictionary = realDic as ITypeSafeDictionary; //this can be optimized, should pass all the entities and not restart the process for each one foreach (var value in implUnmgd) ExecuteEnginesAddOrSwapCallbacksOnSingleEntity(entityComponentEnginesDB , ref typeSafeDictionary[value.Key], fromGroup , in profiler, new EGID(value.Key, toGroup)); } else { var typeSafeDictionary = realDic as ITypeSafeDictionary; //this can be optimized, should pass all the entities and not restart the process for each one foreach (var value in implMgd) ExecuteEnginesAddOrSwapCallbacksOnSingleEntity(entityComponentEnginesDB , ref typeSafeDictionary[value.Key], fromGroup , in profiler, new EGID(value.Key, toGroup)); } } [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Clear() { if (isUnmanaged) { implUnmgd.Clear(); } else { implMgd.Clear(); } } [MethodImpl(MethodImplOptions.AggressiveInlining)] public void FastClear() { if (isUnmanaged) { implUnmgd.FastClear(); } else { implMgd.FastClear(); } } [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool ContainsKey(uint egidEntityId) { if (isUnmanaged) { return implUnmgd.ContainsKey(egidEntityId); } else { return implMgd.ContainsKey(egidEntityId); } } [MethodImpl(MethodImplOptions.AggressiveInlining)] public ITypeSafeDictionary Create() { return TypeSafeDictionaryFactory.Create(1); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public uint GetIndex(uint valueEntityId) { if (isUnmanaged) { return implUnmgd.GetIndex(valueEntityId); } else { return implMgd.GetIndex(valueEntityId); } } [MethodImpl(MethodImplOptions.AggressiveInlining)] public ref TValue GetOrCreate(uint idEntityId) { if (isUnmanaged) { return ref implUnmgd.GetOrCreate(idEntityId); } else { return ref implMgd.GetOrCreate(idEntityId); } } [MethodImpl(MethodImplOptions.AggressiveInlining)] public IBuffer GetValues(out uint count) { if (isUnmanaged) { return implUnmgd.GetValues(out count); } else { return implMgd.GetValues(out count); } } [MethodImpl(MethodImplOptions.AggressiveInlining)] public ref TValue GetDirectValueByRef(uint key) { if (isUnmanaged) { return ref implUnmgd.GetDirectValueByRef(key); } else { return ref implMgd.GetDirectValueByRef(key); } } [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool Has(uint key) { if (isUnmanaged) { return implUnmgd.ContainsKey(key); } else { return implMgd.ContainsKey(key); } } public void ExecuteEnginesSwapOrRemoveCallbacks (EGID fromEntityGid, EGID? toEntityID, ITypeSafeDictionary toGroup , FasterDictionary> engines, in PlatformProfiler profiler) { if (isUnmanaged) { var valueIndex = implUnmgd.GetIndex(fromEntityGid.entityID); ref var entity = ref implUnmgd.GetDirectValueByRef(valueIndex); //move if (toGroup != null) { var toGroupCasted = toGroup as ITypeSafeDictionary; var previousGroup = fromEntityGid.groupID; //todo: why is setting the EGID if this code just execute callbacks? if (_hasEgid) SetEGIDWithoutBoxing.SetIDWithoutBoxing(ref entity, toEntityID.Value); var index = toGroupCasted.GetIndex(toEntityID.Value.entityID); ExecuteEnginesAddOrSwapCallbacksOnSingleEntity(engines, ref toGroupCasted.GetDirectValueByRef(index) , previousGroup, in profiler, toEntityID.Value); } //remove else { ExecuteEnginesRemoveCallbackOnSingleEntity(engines, ref entity, in profiler, fromEntityGid); } } else { var valueIndex = implMgd.GetIndex(fromEntityGid.entityID); ref var entity = ref implMgd.GetDirectValueByRef(valueIndex); if (toGroup != null) { var toGroupCasted = toGroup as ITypeSafeDictionary; var previousGroup = fromEntityGid.groupID; //todo: why is setting the EGID if this code just execute callbacks? if (_hasEgid) SetEGIDWithoutBoxing.SetIDWithoutBoxing(ref entity, toEntityID.Value); var index = toGroupCasted.GetIndex(toEntityID.Value.entityID); ExecuteEnginesAddOrSwapCallbacksOnSingleEntity(engines, ref toGroupCasted.GetDirectValueByRef(index) , previousGroup, in profiler, toEntityID.Value); } else { ExecuteEnginesRemoveCallbackOnSingleEntity(engines, ref entity, in profiler, fromEntityGid); } } } public void ExecuteEnginesRemoveCallbacks (FasterDictionary> engines, in PlatformProfiler profiler , ExclusiveGroupStruct group) { if (isUnmanaged) { foreach (var value in implUnmgd) ExecuteEnginesRemoveCallbackOnSingleEntity(engines, ref implUnmgd.GetValueByRef(value.Key) , in profiler, new EGID(value.Key, group)); } else { foreach (var value in implMgd) ExecuteEnginesRemoveCallbackOnSingleEntity(engines, ref implMgd.GetValueByRef(value.Key) , in profiler, new EGID(value.Key, group)); } } [MethodImpl(MethodImplOptions.AggressiveInlining)] public void RemoveEntityFromDictionary(EGID fromEntityGid) { if (isUnmanaged) { implUnmgd.Remove(fromEntityGid.entityID); } else { implMgd.Remove(fromEntityGid.entityID); } } [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ResizeTo(uint size) { if (isUnmanaged) { implUnmgd.ResizeTo(size); } else { implMgd.ResizeTo(size); } } [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Trim() { if (isUnmanaged) { implUnmgd.Trim(); } else { implMgd.Trim(); } } [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool TryFindIndex(uint entityId, out uint index) { if (isUnmanaged) { return implUnmgd.TryFindIndex(entityId, out index); } else { return implMgd.TryFindIndex(entityId, out index); } } public void KeysEvaluator(Action action) { if (isUnmanaged) { foreach (var key in implUnmgd.keys) { action(key); } } else { foreach (var key in implMgd.keys) { action(key); } } } [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool TryGetValue(uint entityId, out TValue item) { if (isUnmanaged) { return implUnmgd.TryGetValue(entityId, out item); } else { return implMgd.TryGetValue(entityId, out item); } } public uint count { [MethodImpl(MethodImplOptions.AggressiveInlining)] get { if (isUnmanaged) { return (uint) implUnmgd.count; } else { return (uint) implMgd.count; } } } public ref TValue this[uint idEntityId] { [MethodImpl(MethodImplOptions.AggressiveInlining)] get { if (isUnmanaged) { return ref implUnmgd.GetValueByRef(idEntityId); } else { return ref implMgd.GetValueByRef(idEntityId); } } } public void Dispose() { if (isUnmanaged) implUnmgd.Dispose(); else implMgd.Dispose(); GC.SuppressFinalize(this); } void ExecuteEnginesAddOrSwapCallbacksOnSingleEntity (FasterDictionary> engines, ref TValue entity , ExclusiveGroupStruct? previousGroup, in PlatformProfiler profiler, EGID egid) { //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 < entityComponentsEngines.count; i++) try { using (profiler.Sample(entityComponentsEngines[i].name)) { (entityComponentsEngines[i].engine as IReactOnAddAndRemove).Add(ref entity, egid); } } catch { Console.LogError("Code crashed inside Add callback ".FastConcat(typeof(TValue).ToString())); throw; } } else { for (var i = 0; i < entityComponentsEngines.count; i++) try { using (profiler.Sample(entityComponentsEngines[i].name)) { (entityComponentsEngines[i].engine as IReactOnSwap).MovedTo( ref entity, previousGroup.Value, egid); } } catch (Exception) { Console.LogError("Code crashed inside MoveTo callback ".FastConcat(typeof(TValue).ToString())); throw; } } } static void ExecuteEnginesRemoveCallbackOnSingleEntity (FasterDictionary> engines, ref TValue entity , in PlatformProfiler profiler, EGID egid) { if (!engines.TryGetValue(new RefWrapperType(_type), out var entityComponentsEngines)) return; for (var i = 0; i < entityComponentsEngines.count; i++) try { using (profiler.Sample(entityComponentsEngines[i].name)) { (entityComponentsEngines[i].engine as IReactOnAddAndRemove).Remove(ref entity, egid); } } catch { Console.LogError("Code crashed inside Remove callback ".FastConcat(typeof(TValue).ToString())); throw; } } internal SveltoDictionary>, ManagedStrategy, ManagedStrategy> implMgd; //used directly by native methods internal SveltoDictionary>, NativeStrategy, NativeStrategy> implUnmgd; } }