|
- 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 EGID Id { get; }
-
- private static readonly Dictionary<Type, WeakDictionary<EGID, EcsObjectBase>> _instances =
- new Dictionary<Type, WeakDictionary<EGID, EcsObjectBase>>();
-
- private static readonly WeakDictionary<EGID, EcsObjectBase> _noInstance =
- new WeakDictionary<EGID, EcsObjectBase>();
-
- internal static WeakDictionary<EGID, EcsObjectBase> GetInstances(Type type)
- {
- return _instances.TryGetValue(type, out var dict) ? dict : null;
- }
-
- /// <summary>
- /// 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.
- /// </summary>
- /// <param name="egid">The EGID of the entity</param>
- /// <param name="constructor">The constructor to construct the object</param>
- /// <typeparam name="T">The object type</typeparam>
- /// <returns></returns>
- internal static T GetInstance<T>(EGID egid, Func<EGID, T> 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(EGID id)
- {
- if (!_instances.TryGetValue(GetType(), out var dict))
- {
- dict = new WeakDictionary<EGID, EcsObjectBase>();
- _instances.Add(GetType(), dict);
- }
- if (!dict.ContainsKey(id)) // Multiple instances may be created
- dict.Add(id, this);
- Id = id;
- }
-
- protected EcsObjectBase(Func<EcsObjectBase, EGID> initializer)
- {
- if (!_instances.TryGetValue(GetType(), out var dict))
- {
- dict = new WeakDictionary<EGID, EcsObjectBase>();
- _instances.Add(GetType(), dict);
- }
-
- var id = initializer(this);
- if (!dict.ContainsKey(id)) // Multiple instances may be created
- dict.Add(id, this);
- else
- {
- Logging.MetaDebugLog($"An object of this type and ID is already stored: {GetType()} - {id}");
- Logging.MetaDebugLog(this);
- Logging.MetaDebugLog(dict[id]);
- }
-
- Id = id;
- }
-
- #region ECS initializer stuff
-
- protected internal EcsInitData InitData;
-
- /// <summary>
- /// Holds information needed to construct a component initializer
- /// </summary>
- protected internal struct EcsInitData
- {
- private FasterDictionary<RefWrapperType, ITypeSafeDictionary> 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<RefWrapperType, ITypeSafeDictionary> GetInitGroupFunc(
- EntityInitializer initializer);
-
- /// <summary>
- /// Accesses the group field of the initializer
- /// </summary>
- private static GetInitGroupFunc GetInitGroup = CreateAccessor<GetInitGroupFunc>("_group");
-
- //https://stackoverflow.com/questions/55878525/unit-testing-ref-structs-with-private-fields-via-reflection
- private static TDelegate CreateAccessor<TDelegate>(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<TDelegate>(returnExpr, $"Access{paramType.Name}_{memberName}", new[] { objParam });
- return lambda.Compile();
- }
-
- #endregion
- }
- }
|