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.

146 lines
7.2KB

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Reflection;
  4. using HarmonyLib;
  5. using Svelto.DataStructures;
  6. using Svelto.ECS;
  7. using Svelto.Tasks;
  8. using Svelto.Tasks.Lean;
  9. using TechbloxModdingAPI.Tasks;
  10. namespace TechbloxModdingAPI.Utility
  11. {
  12. public static class NativeApiExtensions
  13. {
  14. /// <summary>
  15. /// Attempts to query an entity and returns an optional that contains the result if succeeded.
  16. /// <b>This overload does not take initializer data into account.</b>
  17. /// </summary>
  18. /// <param name="entitiesDB">The entities DB</param>
  19. /// <param name="egid">The EGID to query</param>
  20. /// <typeparam name="T">The component type to query</typeparam>
  21. /// <returns>An optional that contains the result on success or is empty if not found</returns>
  22. public static OptionalRef<T> QueryEntityOptional<T>(this EntitiesDB entitiesDB, EGID egid)
  23. where T : unmanaged, IEntityComponent
  24. {
  25. return entitiesDB.TryQueryEntitiesAndIndex<T>(egid, out uint index, out var array)
  26. ? new OptionalRef<T>(array, index)
  27. : new OptionalRef<T>();
  28. }
  29. /// <summary>
  30. /// Attempts to query an entity and returns the result in an optional reference.
  31. /// </summary>
  32. /// <param name="entitiesDB">The entities DB to query from</param>
  33. /// <param name="obj">The ECS object to query</param>
  34. /// <param name="group">The group of the entity if the object can have multiple</param>
  35. /// <typeparam name="T">The component to query</typeparam>
  36. /// <returns>A reference to the component or a dummy value</returns>
  37. public static OptionalRef<T> QueryEntityOptional<T>(this EntitiesDB entitiesDB, EcsObjectBase obj,
  38. ExclusiveGroupStruct group = default)
  39. where T : unmanaged, IEntityComponent
  40. {
  41. EGID id = group == ExclusiveGroupStruct.Invalid ? obj.Id : new EGID(obj.Id.entityID, group);
  42. var opt = QueryEntityOptional<T>(entitiesDB, id);
  43. return opt ? opt : new OptionalRef<T>(obj, true);
  44. }
  45. /// <summary>
  46. /// Attempts to query an entity and returns the result or a dummy value that can be modified.
  47. /// </summary>
  48. /// <param name="entitiesDB">The entities DB to query from</param>
  49. /// <param name="obj">The ECS object to query</param>
  50. /// <param name="group">The group of the entity if the object can have multiple</param>
  51. /// <typeparam name="T">The component to query</typeparam>
  52. /// <returns>A reference to the component or a dummy value</returns>
  53. public static ref T QueryEntityOrDefault<T>(this EntitiesDB entitiesDB, EcsObjectBase obj,
  54. ExclusiveGroupStruct group = default)
  55. where T : unmanaged, IEntityComponent
  56. {
  57. EGID id = group == ExclusiveGroupStruct.Invalid ? obj.Id : new EGID(obj.Id.entityID, group);
  58. var opt = QueryEntityOptional<T>(entitiesDB, id);
  59. if (opt) return ref opt.Get();
  60. if (obj.InitData.Valid) return ref obj.InitData.Initializer(id).GetOrAdd<T>();
  61. /*if (!obj.InitData.Valid) return ref opt.Get(); //Default value
  62. var init = obj.InitData.Initializer(id);
  63. // Do not create the component if missing, as that can trigger Add() listeners that, in some cases, may be
  64. // invalid if (ab)using the classes in an unusual way - TODO: Check entity descriptor or something
  65. if (init.Has<T>()) return ref init.Get<T>();*/
  66. return ref opt.Get(); //Default value
  67. }
  68. private static readonly Dictionary<Type, (int PublishedCount, HashSet<EGID> Changes)> ChangesToPublish = new();
  69. /// <summary>
  70. /// Publishes an entity change, ignoring duplicate publishes and delaying changes as necessary.
  71. /// It will only publish in the next frame.
  72. /// </summary>
  73. /// <param name="entitiesDB">The entities DB to publish to</param>
  74. /// <param name="id">The ECS object that got changed</param>
  75. /// <param name="limit">Limits how many changes to publish - should be no more than the consumers' capacity that process this component</param>
  76. /// <typeparam name="T">The component that changed</typeparam>
  77. public static void PublishEntityChangeDelayed<T>(this EntitiesDB entitiesDB, EGID id, int limit = 80)
  78. where T : unmanaged, IEntityComponent
  79. {
  80. if (!ChangesToPublish.ContainsKey(typeof(T)))
  81. ChangesToPublish.Add(typeof(T), (0, new HashSet<EGID>()));
  82. var changes = ChangesToPublish[typeof(T)].Changes;
  83. if (changes.Contains(id)) return;
  84. changes.Add(id);
  85. PublishChanges<T>(entitiesDB, id, limit).RunOn(Scheduler.leanRunner);
  86. }
  87. private static IEnumerator<TaskContract> PublishChanges<T>(EntitiesDB entitiesDB, EGID id, int limit)
  88. where T : unmanaged, IEntityComponent
  89. {
  90. yield return Yield.It;
  91. while (ChangesToPublish[typeof(T)].PublishedCount >= limit)
  92. yield return Yield.It;
  93. if (!entitiesDB._entityStream._streams.TryGetValue(TypeRefWrapper<T>.wrapper, out var result))
  94. yield break; // There is no entity stream for this type
  95. var consumers = (result as EntityStream<T>)?._consumers;
  96. if (consumers == null)
  97. {
  98. Console.WriteLine("Consumers is null");
  99. yield break;
  100. }
  101. bool waitForConsumers;
  102. do
  103. {
  104. waitForConsumers = false;
  105. for (int i = 0; i < consumers.count; i++)
  106. {
  107. var buffer = consumers[i]._ringBuffer;
  108. if (buffer.Count + 1 <= buffer.Capacity) continue;
  109. waitForConsumers = true;
  110. Console.WriteLine($"Gonna have to wait for a consumer (capacity: {buffer.Capacity} count: {buffer.Count}");
  111. break;
  112. }
  113. if (waitForConsumers) yield return Yield.It;
  114. } while (waitForConsumers);
  115. entitiesDB.PublishEntityChange<T>(id);
  116. var (count, changes) = ChangesToPublish[typeof(T)];
  117. changes.Remove(id);
  118. ChangesToPublish[typeof(T)] = (count + 1, changes);
  119. yield return Yield.It;
  120. ChangesToPublish[typeof(T)] = (Math.Max(ChangesToPublish[typeof(T)].PublishedCount - 1, 0), changes);
  121. }
  122. /// <summary>
  123. /// Query entities as OptionalRefs. The elements always exist, it's just a nice way to encapsulate the data.
  124. /// </summary>
  125. /// <param name="entitiesDB"></param>
  126. /// <param name="group"></param>
  127. /// <param name="select"></param>
  128. /// <typeparam name="T"></typeparam>
  129. /// <typeparam name="TR"></typeparam>
  130. /// <returns></returns>
  131. public static RefCollection<T> QueryEntitiesOptional<T>(this EntitiesDB entitiesDB, ExclusiveGroupStruct group) where T : unmanaged, IEntityComponent
  132. {
  133. var (buffer, ids, count) = entitiesDB.QueryEntities<T>(group);
  134. return new RefCollection<T>(count, buffer, ids, group);
  135. }
  136. }
  137. }