using System; using System.Collections.Generic; using System.Linq.Expressions; using Svelto.DataStructures; using Svelto.ECS; using Svelto.ECS.Internal; using TechbloxModdingAPI.Utility; namespace TechbloxModdingAPI { public abstract class EcsObjectBase { public abstract EGID Id { get; } //Abstract to support the 'place' Block constructor private static readonly Dictionary> _instances = new Dictionary>(); private static readonly WeakDictionary _noInstance = new WeakDictionary(); internal static WeakDictionary GetInstances(Type type) { return _instances.TryGetValue(type, out var dict) ? dict : null; } /// /// Returns a cached instance if there's an actively used instance of the object already. /// Objects still get garbage collected and then they will be removed from the cache. /// /// The EGID of the entity /// The constructor to construct the object /// The object type /// internal static T GetInstance(EGID egid, Func constructor, Type type = null) where T : EcsObjectBase { var instances = GetInstances(type ?? typeof(T)); if (instances == null || !instances.TryGetValue(egid, out var instance)) return constructor(egid); // It will be added by the constructor return (T)instance; } protected EcsObjectBase() { if (!_instances.TryGetValue(GetType(), out var dict)) { dict = new WeakDictionary(); _instances.Add(GetType(), dict); } // ReSharper disable VirtualMemberCallInConstructor // The ID should not depend on the constructor if (!dict.ContainsKey(Id)) // Multiple instances may be created dict.Add(Id, this); // ReSharper enable VirtualMemberCallInConstructor } #region ECS initializer stuff protected internal EcsInitData InitData; /// /// Holds information needed to construct a component initializer /// protected internal struct EcsInitData { private FasterDictionary group; private EntityReference reference; public static implicit operator EcsInitData(EntityInitializer initializer) => new EcsInitData { group = GetInitGroup(initializer), reference = initializer.reference }; public EntityInitializer Initializer(EGID id) => new EntityInitializer(id, group, reference); public bool Valid => group != null; } private delegate FasterDictionary GetInitGroupFunc( EntityInitializer initializer); /// /// Accesses the group field of the initializer /// private static GetInitGroupFunc GetInitGroup = CreateAccessor("_group"); //https://stackoverflow.com/questions/55878525/unit-testing-ref-structs-with-private-fields-via-reflection private static TDelegate CreateAccessor(string memberName) where TDelegate : Delegate { var invokeMethod = typeof(TDelegate).GetMethod("Invoke"); if (invokeMethod == null) throw new InvalidOperationException($"{typeof(TDelegate)} signature could not be determined."); var delegateParameters = invokeMethod.GetParameters(); if (delegateParameters.Length != 1) throw new InvalidOperationException("Delegate must have a single parameter."); var paramType = delegateParameters[0].ParameterType; var objParam = Expression.Parameter(paramType, "obj"); var memberExpr = Expression.PropertyOrField(objParam, memberName); Expression returnExpr = memberExpr; if (invokeMethod.ReturnType != memberExpr.Type) returnExpr = Expression.ConvertChecked(memberExpr, invokeMethod.ReturnType); var lambda = Expression.Lambda(returnExpr, $"Access{paramType.Name}_{memberName}", new[] { objParam }); return lambda.Compile(); } #endregion } }