A stable modding interface between Techblox and mods https://mod.exmods.org/
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

128 lines
5.1KB

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq.Expressions;
  4. using Svelto.DataStructures;
  5. using Svelto.ECS;
  6. using Svelto.ECS.Internal;
  7. using TechbloxModdingAPI.Utility;
  8. namespace TechbloxModdingAPI
  9. {
  10. public abstract class EcsObjectBase
  11. {
  12. public EGID Id { get; }
  13. private static readonly Dictionary<Type, WeakDictionary<EGID, EcsObjectBase>> _instances =
  14. new Dictionary<Type, WeakDictionary<EGID, EcsObjectBase>>();
  15. private static readonly WeakDictionary<EGID, EcsObjectBase> _noInstance =
  16. new WeakDictionary<EGID, EcsObjectBase>();
  17. internal static WeakDictionary<EGID, EcsObjectBase> GetInstances(Type type)
  18. {
  19. return _instances.TryGetValue(type, out var dict) ? dict : null;
  20. }
  21. /// <summary>
  22. /// Returns a cached instance if there's an actively used instance of the object already.
  23. /// Objects still get garbage collected and then they will be removed from the cache.
  24. /// </summary>
  25. /// <param name="egid">The EGID of the entity</param>
  26. /// <param name="constructor">The constructor to construct the object</param>
  27. /// <typeparam name="T">The object type</typeparam>
  28. /// <returns></returns>
  29. internal static T GetInstance<T>(EGID egid, Func<EGID, T> constructor, Type type = null) where T : EcsObjectBase
  30. {
  31. var instances = GetInstances(type ?? typeof(T));
  32. if (instances == null || !instances.TryGetValue(egid, out var instance))
  33. return constructor(egid); // It will be added by the constructor
  34. return (T)instance;
  35. }
  36. protected EcsObjectBase(EGID id)
  37. {
  38. if (!_instances.TryGetValue(GetType(), out var dict))
  39. {
  40. dict = new WeakDictionary<EGID, EcsObjectBase>();
  41. _instances.Add(GetType(), dict);
  42. }
  43. if (!dict.ContainsKey(id)) // Multiple instances may be created
  44. dict.Add(id, this);
  45. Id = id;
  46. }
  47. protected EcsObjectBase(Func<EcsObjectBase, EGID> initializer)
  48. {
  49. if (!_instances.TryGetValue(GetType(), out var dict))
  50. {
  51. dict = new WeakDictionary<EGID, EcsObjectBase>();
  52. _instances.Add(GetType(), dict);
  53. }
  54. var id = initializer(this);
  55. if (!dict.ContainsKey(id)) // Multiple instances may be created
  56. dict.Add(id, this);
  57. else
  58. {
  59. Logging.MetaDebugLog($"An object of this type and ID is already stored: {GetType()} - {id}");
  60. Logging.MetaDebugLog(this);
  61. Logging.MetaDebugLog(dict[id]);
  62. }
  63. Id = id;
  64. }
  65. #region ECS initializer stuff
  66. protected internal EcsInitData InitData;
  67. /// <summary>
  68. /// Holds information needed to construct a component initializer
  69. /// </summary>
  70. protected internal struct EcsInitData
  71. {
  72. private FasterDictionary<RefWrapperType, ITypeSafeDictionary> group;
  73. private EntityReference reference;
  74. public static implicit operator EcsInitData(EntityInitializer initializer) => new EcsInitData
  75. { group = GetInitGroup(initializer), reference = initializer.reference };
  76. public EntityInitializer Initializer(EGID id) => new EntityInitializer(id, group, reference);
  77. public bool Valid => group != null;
  78. }
  79. private delegate FasterDictionary<RefWrapperType, ITypeSafeDictionary> GetInitGroupFunc(
  80. EntityInitializer initializer);
  81. /// <summary>
  82. /// Accesses the group field of the initializer
  83. /// </summary>
  84. private static GetInitGroupFunc GetInitGroup = CreateAccessor<GetInitGroupFunc>("_group");
  85. //https://stackoverflow.com/questions/55878525/unit-testing-ref-structs-with-private-fields-via-reflection
  86. private static TDelegate CreateAccessor<TDelegate>(string memberName) where TDelegate : Delegate
  87. {
  88. var invokeMethod = typeof(TDelegate).GetMethod("Invoke");
  89. if (invokeMethod == null)
  90. throw new InvalidOperationException($"{typeof(TDelegate)} signature could not be determined.");
  91. var delegateParameters = invokeMethod.GetParameters();
  92. if (delegateParameters.Length != 1)
  93. throw new InvalidOperationException("Delegate must have a single parameter.");
  94. var paramType = delegateParameters[0].ParameterType;
  95. var objParam = Expression.Parameter(paramType, "obj");
  96. var memberExpr = Expression.PropertyOrField(objParam, memberName);
  97. Expression returnExpr = memberExpr;
  98. if (invokeMethod.ReturnType != memberExpr.Type)
  99. returnExpr = Expression.ConvertChecked(memberExpr, invokeMethod.ReturnType);
  100. var lambda =
  101. Expression.Lambda<TDelegate>(returnExpr, $"Access{paramType.Name}_{memberName}", new[] { objParam });
  102. return lambda.Compile();
  103. }
  104. #endregion
  105. }
  106. }