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.

177 lines
7.1KB

  1. using System;
  2. using System.Collections.Generic;
  3. using Svelto.DataStructures;
  4. using Svelto.ECS;
  5. using Svelto.ECS.Hybrid;
  6. using Svelto.ECS.Internal;
  7. using TechbloxModdingAPI.Common.Engines;
  8. using TechbloxModdingAPI.Common.Utils;
  9. using TechbloxModdingAPI.Utility;
  10. namespace TechbloxModdingAPI.Common;
  11. public abstract class EcsObjectBase<TDescriptor> : EcsObjectBase where TDescriptor : IEntityDescriptor, new()
  12. {
  13. protected EcsObjectBase(EGID id) : base(id, typeof(TDescriptor))
  14. {
  15. }
  16. protected EcsObjectBase(EntityReference reference) : base(reference, typeof(TDescriptor))
  17. {
  18. }
  19. protected bool RemoveEntity()
  20. {
  21. if (!Exists) return false;
  22. _engine.Functions.RemoveEntity<TDescriptor>(Id);
  23. return true;
  24. }
  25. }
  26. public abstract class EcsObjectBase {
  27. public EGID Id => _engine.GetEgid(Reference);
  28. /// <summary>
  29. /// A reference to a specific entity that persists through group swaps and such.
  30. /// May be an invalid reference, in that case operations do not have any effect.
  31. /// </summary>
  32. public EntityReference Reference { get; }
  33. /// <summary>
  34. /// Whether the entity reference is still valid. Returns false if this object no longer exists.
  35. /// </summary>
  36. public bool Exists => Id != default; // TODO: Might need extra code to support IDs during init
  37. public readonly Type EntityDescriptorType;
  38. public readonly Type[] AllowedEntityComponents;
  39. private static readonly Dictionary<Type, WeakDictionary<EntityReference, EcsObjectBase>> _instances = new();
  40. internal static readonly EcsObjectBaseEngine _engine = new();
  41. private static WeakDictionary<EntityReference, EcsObjectBase> GetInstances(Type type)
  42. {
  43. return _instances.TryGetValue(type, out var dict) ? dict : null;
  44. }
  45. /// <summary>
  46. /// Returns a cached instance if there's an actively used instance of the object already.
  47. /// Objects still get garbage collected and then they will be removed from the cache.<br />
  48. /// <b>Only use for existing entities!</b> Use the other overload for newly created entities.
  49. /// </summary>
  50. /// <param name="egid">The EGID of the entity</param>
  51. /// <param name="constructor">The constructor to construct the object</param>
  52. /// <typeparam name="T">The object type</typeparam>
  53. /// <returns></returns>
  54. internal static T GetInstanceExisting<T>(EGID egid, Func<EGID, T> constructor, Type type = null) where T : EcsObjectBase
  55. {
  56. var instances = GetInstances(type ?? typeof(T));
  57. if (instances == null || !instances.TryGetValue(_engine.GetEntityReference(egid), out var instance))
  58. return constructor(egid); // It will be added by the constructor
  59. return (T)instance;
  60. }
  61. /// <summary>
  62. /// Returns a cached instance if there's an actively used instance of the object already.
  63. /// Objects still get garbage collected and then they will be removed from the cache.<br />
  64. /// <b>Only use for newly created entities!</b> Use the other overload for existing entities.
  65. /// </summary>
  66. /// <param name="egid">The EGID of the entity</param>
  67. /// <param name="constructor">The constructor to construct the object</param>
  68. /// <typeparam name="T">The object type</typeparam>
  69. /// <returns></returns>
  70. internal static T GetInstanceNew<T>(EcsInitData initData, Func<EGID, T> constructor, Type type = null) where T : EcsObjectBase
  71. {
  72. var instances = GetInstances(type ?? typeof(T));
  73. if (instances == null || !instances.TryGetValue(initData.Reference, out var instance))
  74. {
  75. var ret = constructor(initData.EGID);
  76. ret.InitData = initData;
  77. return ret; // It will be added by the constructor
  78. }
  79. return (T)instance;
  80. }
  81. protected static V CreateEntity<U, V>(EGID egid, Func<EGID, V> constructor, Type type = null) where U : IEntityDescriptor, new() where V : EcsObjectBase<U>
  82. {
  83. return GetInstanceNew(_engine.Factory.BuildEntity<U>(egid), constructor, type);
  84. }
  85. protected EcsObjectBase(EGID id, Type entityDescriptorType) : this(_engine.GetEntityReference(id), entityDescriptorType)
  86. {
  87. }
  88. protected EcsObjectBase(EntityReference reference, Type entityDescriptorType)
  89. {
  90. if (!_instances.TryGetValue(GetType(), out var dict))
  91. {
  92. dict = new();
  93. _instances.Add(GetType(), dict);
  94. }
  95. if (!dict.ContainsKey(reference)) // Multiple instances may be created
  96. dict.Add(reference, this);
  97. Reference = reference;
  98. EntityDescriptorType = entityDescriptorType;
  99. AllowedEntityComponents = EcsUtils.GetValidEntityComponents(entityDescriptorType);
  100. // Remove init data once the entity gets submitted so that it won't be used again once the entity is removed
  101. if (InitData != default) _engine.TrackNewEntity(this, obj => obj.InitData = default);
  102. }
  103. protected internal OptionalRef<T> GetComponentOptional<T>() where T : unmanaged, IEntityComponent
  104. {
  105. return _engine.GetComponentOptional<T>(this);
  106. }
  107. protected internal ref T GetComponent<T>() where T : unmanaged, IEntityComponent
  108. {
  109. return ref _engine.GetComponent<T>(this);
  110. }
  111. protected internal ref T GetViewComponent<T>() where T : struct, IEntityViewComponent
  112. {
  113. return ref _engine.GetViewComponent<T>(this);
  114. }
  115. protected internal object GetComponent(Type type, string name)
  116. {
  117. return _engine.GetComponent(this, type, name);
  118. }
  119. protected internal void SetComponent(Type type, string name, object value)
  120. {
  121. _engine.SetComponent(this, type, name, value);
  122. }
  123. #region ECS initializer stuff
  124. internal EcsInitData InitData { get; private set; }
  125. /// <summary>
  126. /// Holds information needed to construct a component initializer.
  127. /// Necessary because the initializer is a ref struct which cannot be assigned to a field.
  128. /// </summary>
  129. protected internal readonly record struct EcsInitData(FasterDictionary<RefWrapperType, ITypeSafeDictionary> group, EntityReference Reference, EGID EGID)
  130. {
  131. public static implicit operator EcsInitData(EntityInitializer initializer) => new(GetInitGroup(initializer), initializer.reference, initializer.EGID);
  132. private readonly FasterDictionary<RefWrapperType, ITypeSafeDictionary> group = group;
  133. public readonly EntityReference Reference = Reference;
  134. public readonly EGID EGID = EGID;
  135. public EntityInitializer Initializer(EGID id = default) => new(id == default ? EGID : id, group, Reference);
  136. public bool Valid => group != null;
  137. }
  138. private delegate FasterDictionary<RefWrapperType, ITypeSafeDictionary> GetInitGroupFunc(
  139. EntityInitializer initializer);
  140. /// <summary>
  141. /// Accesses the group field of the initializer
  142. /// </summary>
  143. private static readonly GetInitGroupFunc GetInitGroup = Reflections.CreateAccessor<GetInitGroupFunc>("_group");
  144. #endregion
  145. public static void Init()
  146. {
  147. EngineManager.AddEngine(_engine, ApiEngineType.Build, ApiEngineType.Menu, ApiEngineType.PlayClient, ApiEngineType.PlayServer);
  148. }
  149. }