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.

92 lines
3.6KB

  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 abstract EGID Id { get; } //Abstract to support the 'place' Block constructor
  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. protected EcsObjectBase()
  22. {
  23. if (!_instances.TryGetValue(GetType(), out var dict))
  24. {
  25. dict = new WeakDictionary<EGID, EcsObjectBase>();
  26. _instances.Add(GetType(), dict);
  27. }
  28. // ReSharper disable once VirtualMemberCallInConstructor
  29. // The ID should not depend on the constructor
  30. dict.Add(Id, this);
  31. }
  32. #region ECS initializer stuff
  33. protected internal EcsInitData InitData;
  34. /// <summary>
  35. /// Holds information needed to construct a component initializer
  36. /// </summary>
  37. protected internal struct EcsInitData
  38. {
  39. private FasterDictionary<RefWrapperType, ITypeSafeDictionary> group;
  40. private EntityReference reference;
  41. public static implicit operator EcsInitData(EntityInitializer initializer) => new EcsInitData
  42. { group = GetInitGroup(initializer), reference = initializer.reference };
  43. public EntityInitializer Initializer(EGID id) => new EntityInitializer(id, group, reference);
  44. public bool Valid => group != null;
  45. }
  46. private delegate FasterDictionary<RefWrapperType, ITypeSafeDictionary> GetInitGroupFunc(
  47. EntityInitializer initializer);
  48. /// <summary>
  49. /// Accesses the group field of the initializer
  50. /// </summary>
  51. private static GetInitGroupFunc GetInitGroup = CreateAccessor<GetInitGroupFunc>("_group");
  52. //https://stackoverflow.com/questions/55878525/unit-testing-ref-structs-with-private-fields-via-reflection
  53. private static TDelegate CreateAccessor<TDelegate>(string memberName) where TDelegate : Delegate
  54. {
  55. var invokeMethod = typeof(TDelegate).GetMethod("Invoke");
  56. if (invokeMethod == null)
  57. throw new InvalidOperationException($"{typeof(TDelegate)} signature could not be determined.");
  58. var delegateParameters = invokeMethod.GetParameters();
  59. if (delegateParameters.Length != 1)
  60. throw new InvalidOperationException("Delegate must have a single parameter.");
  61. var paramType = delegateParameters[0].ParameterType;
  62. var objParam = Expression.Parameter(paramType, "obj");
  63. var memberExpr = Expression.PropertyOrField(objParam, memberName);
  64. Expression returnExpr = memberExpr;
  65. if (invokeMethod.ReturnType != memberExpr.Type)
  66. returnExpr = Expression.ConvertChecked(memberExpr, invokeMethod.ReturnType);
  67. var lambda =
  68. Expression.Lambda<TDelegate>(returnExpr, $"Access{paramType.Name}_{memberName}", new[] { objParam });
  69. return lambda.Compile();
  70. }
  71. #endregion
  72. }
  73. }