Mirror of Svelto.ECS because we're a fan of it
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.

401 lines
17KB

  1. #if PROFILE_SVELTO && DEBUG
  2. #warning the global define PROFILE_SVELTO should be used only when it's necessary to profile in order to reduce the overhead of debug code. Normally remove this define to get insights when errors happen
  3. #endif
  4. using System;
  5. using System.Collections.Generic;
  6. using DBC.ECS;
  7. using Svelto.Common;
  8. using Svelto.Common.DataStructures;
  9. using Svelto.DataStructures;
  10. using Svelto.ECS.Internal;
  11. using Svelto.ECS.Schedulers;
  12. namespace Svelto.ECS
  13. {
  14. public partial class EnginesRoot
  15. {
  16. static EnginesRoot()
  17. {
  18. GroupHashMap.Init();
  19. SharedDictonary.Init();
  20. SerializationDescriptorMap.Init();
  21. _swapEntities = SwapEntities;
  22. _removeEntities = RemoveEntities;
  23. _removeGroup = RemoveGroup;
  24. _swapGroup = SwapGroup;
  25. }
  26. /// <summary>
  27. /// Engines root contextualize your engines and entities. You don't need to limit yourself to one EngineRoot
  28. /// as multiple engines root could promote separation of scopes. The EntitySubmissionScheduler checks
  29. /// periodically if new entity must be submitted to the database and the engines. It's an external
  30. /// dependencies to be independent by the running platform as the user can define it.
  31. /// The EntitySubmissionScheduler cannot hold an EnginesRoot reference, that's why
  32. /// it must receive a weak reference of the EnginesRoot callback.
  33. /// </summary>
  34. public EnginesRoot(EntitiesSubmissionScheduler entitiesComponentScheduler)
  35. {
  36. _entitiesOperations = new EntitiesOperations();
  37. _idChecker = new FasterDictionary<ExclusiveGroupStruct, HashSet<uint>>();
  38. _cachedRangeOfSubmittedIndices = new FasterList<(uint, uint)>();
  39. _cachedIndicesToSwapBeforeSubmissionForFilters = new FasterDictionary<uint, uint>();
  40. _multipleOperationOnSameEGIDChecker = new FasterDictionary<EGID, uint>();
  41. #if UNITY_NATIVE //because of the thread count, ATM this is only for unity
  42. _nativeSwapOperationQueue = new Svelto.ECS.DataStructures.AtomicNativeBags(Allocator.Persistent);
  43. _nativeRemoveOperationQueue = new Svelto.ECS.DataStructures.AtomicNativeBags(Allocator.Persistent);
  44. _nativeAddOperationQueue = new Svelto.ECS.DataStructures.AtomicNativeBags(Allocator.Persistent);
  45. #endif
  46. _serializationDescriptorMap = new SerializationDescriptorMap();
  47. _reactiveEnginesAdd = new FasterDictionary<RefWrapperType, FasterList<ReactEngineContainer<IReactOnAdd>>>();
  48. _reactiveEnginesAddEx =
  49. new FasterDictionary<RefWrapperType, FasterList<ReactEngineContainer<IReactOnAddEx>>>();
  50. _reactiveEnginesRemove =
  51. new FasterDictionary<RefWrapperType, FasterList<ReactEngineContainer<IReactOnRemove>>>();
  52. _reactiveEnginesRemoveEx =
  53. new FasterDictionary<RefWrapperType, FasterList<ReactEngineContainer<IReactOnRemoveEx>>>();
  54. _reactiveEnginesSwap =
  55. new FasterDictionary<RefWrapperType, FasterList<ReactEngineContainer<IReactOnSwap>>>();
  56. _reactiveEnginesSwapEx =
  57. new FasterDictionary<RefWrapperType, FasterList<ReactEngineContainer<IReactOnSwapEx>>>();
  58. _reactiveEnginesDispose =
  59. new FasterDictionary<RefWrapperType, FasterList<ReactEngineContainer<IReactOnDispose>>>();
  60. _reactiveEnginesSubmission = new FasterList<IReactOnSubmission>();
  61. _enginesSet = new FasterList<IEngine>();
  62. _enginesTypeSet = new HashSet<Type>();
  63. _disposableEngines = new FasterList<IDisposable>();
  64. _groupEntityComponentsDB =
  65. new FasterDictionary<ExclusiveGroupStruct, FasterDictionary<RefWrapperType, ITypeSafeDictionary>>();
  66. _groupsPerEntity =
  67. new FasterDictionary<RefWrapperType, FasterDictionary<ExclusiveGroupStruct, ITypeSafeDictionary>>();
  68. _groupedEntityToAdd = new DoubleBufferedEntitiesToAdd();
  69. _entityStreams = EntitiesStreams.Create();
  70. _groupFilters =
  71. new FasterDictionary<RefWrapperType, FasterDictionary<ExclusiveGroupStruct, LegacyGroupFilters>>();
  72. _entityLocator.InitEntityReferenceMap();
  73. _entitiesDB = new EntitiesDB(this, _entityLocator);
  74. InitFilters();
  75. scheduler = entitiesComponentScheduler;
  76. scheduler.onTick = new EntitiesSubmitter(this);
  77. #if UNITY_NATIVE
  78. AllocateNativeOperations();
  79. #endif
  80. }
  81. protected EnginesRoot(EntitiesSubmissionScheduler entitiesComponentScheduler,
  82. EnginesReadyOption enginesWaitForReady) : this(entitiesComponentScheduler)
  83. {
  84. _enginesWaitForReady = enginesWaitForReady;
  85. }
  86. public EntitiesSubmissionScheduler scheduler { get; }
  87. /// <summary>
  88. /// Dispose an EngineRoot once not used anymore, so that all the
  89. /// engines are notified with the entities removed.
  90. /// It's a clean up process.
  91. /// </summary>
  92. public void Dispose()
  93. {
  94. Dispose(true);
  95. GC.SuppressFinalize(this);
  96. }
  97. public void AddEngine(IEngine engine)
  98. {
  99. var type = engine.GetType();
  100. var refWrapper = new RefWrapperType(type);
  101. Check.Require(engine != null, "Engine to add is invalid or null");
  102. Check.Require(
  103. _enginesTypeSet.Contains(refWrapper) == false ||
  104. type.ContainsCustomAttribute(typeof(AllowMultipleAttribute)),
  105. "The same engine has been added more than once, if intentional, use [AllowMultiple] class attribute "
  106. .FastConcat(engine.ToString()));
  107. try
  108. {
  109. if (engine is IReactOnAdd viewEngineAdd)
  110. CheckReactEngineComponents(typeof(IReactOnAdd<>), viewEngineAdd, _reactiveEnginesAdd, type.Name);
  111. if (engine is IReactOnAddEx viewEngineAddEx)
  112. CheckReactEngineComponents(typeof(IReactOnAddEx<>), viewEngineAddEx, _reactiveEnginesAddEx, type.Name);
  113. if (engine is IReactOnRemove viewEngineRemove)
  114. CheckReactEngineComponents(typeof(IReactOnRemove<>), viewEngineRemove, _reactiveEnginesRemove, type.Name);
  115. if (engine is IReactOnRemoveEx viewEngineRemoveEx)
  116. CheckReactEngineComponents(typeof(IReactOnRemoveEx<>), viewEngineRemoveEx, _reactiveEnginesRemoveEx, type.Name);
  117. if (engine is IReactOnDispose viewEngineDispose)
  118. CheckReactEngineComponents(typeof(IReactOnDispose<>), viewEngineDispose, _reactiveEnginesDispose, type.Name);
  119. if (engine is IReactOnSwap viewEngineSwap)
  120. CheckReactEngineComponents(typeof(IReactOnSwap<>), viewEngineSwap, _reactiveEnginesSwap, type.Name);
  121. if (engine is IReactOnSwapEx viewEngineSwapEx)
  122. CheckReactEngineComponents(typeof(IReactOnSwapEx<>), viewEngineSwapEx, _reactiveEnginesSwapEx, type.Name);
  123. if (engine is IReactOnSubmission submissionEngine)
  124. _reactiveEnginesSubmission.Add(submissionEngine);
  125. _enginesTypeSet.Add(refWrapper);
  126. _enginesSet.Add(engine);
  127. if (engine is IDisposable)
  128. _disposableEngines.Add(engine as IDisposable);
  129. if (engine is IQueryingEntitiesEngine queryableEntityComponentEngine)
  130. queryableEntityComponentEngine.entitiesDB = _entitiesDB;
  131. if (_enginesWaitForReady == EnginesReadyOption.ReadyAsAdded && engine is IGetReadyEngine getReadyEngine)
  132. getReadyEngine.Ready();
  133. }
  134. catch (Exception e)
  135. {
  136. throw new ECSException("Code crashed while adding engine ".FastConcat(engine.GetType().ToString(), " "),
  137. e);
  138. }
  139. }
  140. public void Ready()
  141. {
  142. Check.Require(_enginesWaitForReady == EnginesReadyOption.WaitForReady,
  143. "The engine has not been initialise to wait for an external ready trigger");
  144. foreach (var engine in _enginesSet)
  145. if (engine is IGetReadyEngine getReadyEngine)
  146. getReadyEngine.Ready();
  147. }
  148. static void AddEngineToList<T>(T engine, Type[] entityComponentTypes,
  149. FasterDictionary<RefWrapperType, FasterList<ReactEngineContainer<T>>> engines, string typeName)
  150. where T : class, IReactEngine
  151. {
  152. for (var i = 0; i < entityComponentTypes.Length; i++)
  153. {
  154. var type = entityComponentTypes[i];
  155. if (engines.TryGetValue(new RefWrapperType(type), out var list) == false)
  156. {
  157. list = new FasterList<ReactEngineContainer<T>>();
  158. engines.Add(new RefWrapperType(type), list);
  159. }
  160. list.Add(new ReactEngineContainer<T>(engine, typeName));
  161. }
  162. }
  163. void CheckReactEngineComponents<T>(Type genericDefinition, T engine,
  164. FasterDictionary<RefWrapperType, FasterList<ReactEngineContainer<T>>> engines, string typeName)
  165. where T : class, IReactEngine
  166. {
  167. var interfaces = engine.GetType().GetInterfaces();
  168. foreach (var interf in interfaces)
  169. {
  170. if (interf.IsGenericTypeEx() && interf.GetGenericTypeDefinition() == genericDefinition)
  171. {
  172. var genericArguments = interf.GetGenericArgumentsEx();
  173. AddEngineToList(engine, genericArguments, engines, typeName);
  174. }
  175. }
  176. }
  177. void Dispose(bool disposing)
  178. {
  179. _isDisposing = disposing;
  180. if (disposing == false)
  181. return;
  182. using (var profiler = new PlatformProfiler("Final Dispose"))
  183. {
  184. //Note: The engines are disposed before the the remove callback to give the chance to behave
  185. //differently if a remove happens as a consequence of a dispose
  186. //The pattern is to implement the IDisposable interface and set a flag in the engine. The
  187. //remove callback will then behave differently according the flag.
  188. foreach (var engine in _disposableEngines)
  189. try
  190. {
  191. if (engine is IDisposingEngine dengine)
  192. dengine.isDisposing = true;
  193. engine.Dispose();
  194. }
  195. catch (Exception e)
  196. {
  197. Console.LogException(e);
  198. }
  199. foreach (var groups in _groupEntityComponentsDB)
  200. foreach (var entityList in groups.value)
  201. try
  202. {
  203. ITypeSafeDictionary typeSafeDictionary = entityList.value;
  204. typeSafeDictionary.ExecuteEnginesDisposeCallbacks_Group(_reactiveEnginesDispose, groups.key,
  205. profiler);
  206. }
  207. catch (Exception e)
  208. {
  209. Console.LogException(e);
  210. }
  211. foreach (var groups in _groupEntityComponentsDB)
  212. foreach (var entityList in groups.value)
  213. entityList.value.Dispose();
  214. foreach (var type in _groupFilters)
  215. foreach (var group in type.value)
  216. group.value.Dispose();
  217. _groupFilters.Clear();
  218. DisposeFilters();
  219. #if UNITY_NATIVE
  220. _nativeAddOperationQueue.Dispose();
  221. _nativeRemoveOperationQueue.Dispose();
  222. _nativeSwapOperationQueue.Dispose();
  223. #endif
  224. _groupEntityComponentsDB.Clear();
  225. _groupsPerEntity.Clear();
  226. _disposableEngines.Clear();
  227. _enginesSet.Clear();
  228. _enginesTypeSet.Clear();
  229. _reactiveEnginesSwap.Clear();
  230. _reactiveEnginesAdd.Clear();
  231. _reactiveEnginesRemove.Clear();
  232. _reactiveEnginesDispose.Clear();
  233. _reactiveEnginesSubmission.Clear();
  234. _groupedEntityToAdd.Dispose();
  235. _entityLocator.DisposeEntityReferenceMap();
  236. _entityStreams.Dispose();
  237. scheduler.Dispose();
  238. }
  239. }
  240. void NotifyReactiveEnginesOnSubmission()
  241. {
  242. var enginesCount = _reactiveEnginesSubmission.count;
  243. for (var i = 0; i < enginesCount; i++)
  244. _reactiveEnginesSubmission[i].EntitiesSubmitted();
  245. }
  246. public readonly struct EntitiesSubmitter
  247. {
  248. public EntitiesSubmitter(EnginesRoot enginesRoot) : this()
  249. {
  250. _enginesRoot = new Svelto.DataStructures.WeakReference<EnginesRoot>(enginesRoot);
  251. }
  252. internal void SubmitEntities()
  253. {
  254. Check.Require(_enginesRoot.IsValid, "ticking an GCed engines root?");
  255. var enginesRootTarget = _enginesRoot.Target;
  256. var entitiesSubmissionScheduler = enginesRootTarget.scheduler;
  257. if (entitiesSubmissionScheduler.paused == false)
  258. {
  259. Check.Require(entitiesSubmissionScheduler.isRunning == false,
  260. "A submission started while the previous one was still flushing");
  261. entitiesSubmissionScheduler.isRunning = true;
  262. using (var profiler = new PlatformProfiler("Svelto.ECS - Entities Submission"))
  263. {
  264. var iterations = 0;
  265. var hasEverSubmitted = false;
  266. // We need to clear transient filters before processing callbacks since the callbacks may add
  267. // new entities to these filters.
  268. enginesRootTarget.ClearTransientFilters();
  269. #if UNITY_NATIVE
  270. enginesRootTarget.FlushNativeOperations(profiler);
  271. #endif
  272. //todo: proper unit test structural changes made as result of add/remove callbacks
  273. while (enginesRootTarget.HasMadeNewStructuralChangesInThisIteration()
  274. && iterations++ < MAX_SUBMISSION_ITERATIONS)
  275. {
  276. hasEverSubmitted = true;
  277. _enginesRoot.Target.SingleSubmission(profiler);
  278. #if UNITY_NATIVE
  279. if (enginesRootTarget.HasMadeNewStructuralChangesInThisIteration())
  280. enginesRootTarget.FlushNativeOperations(profiler);
  281. #endif
  282. }
  283. #if DEBUG && !PROFILE_SVELTO
  284. if (iterations == MAX_SUBMISSION_ITERATIONS)
  285. throw new ECSException("possible circular submission detected");
  286. #endif
  287. if (hasEverSubmitted)
  288. enginesRootTarget.NotifyReactiveEnginesOnSubmission();
  289. }
  290. entitiesSubmissionScheduler.isRunning = false;
  291. ++entitiesSubmissionScheduler.iteration;
  292. }
  293. }
  294. readonly Svelto.DataStructures.WeakReference<EnginesRoot> _enginesRoot;
  295. }
  296. ~EnginesRoot()
  297. {
  298. Console.LogWarning("Engines Root has been garbage collected, don't forget to call Dispose()!");
  299. Dispose(false);
  300. }
  301. const int MAX_SUBMISSION_ITERATIONS = 10;
  302. internal bool _isDisposing;
  303. readonly FasterList<IDisposable> _disposableEngines;
  304. readonly FasterList<IEngine> _enginesSet;
  305. readonly HashSet<Type> _enginesTypeSet;
  306. readonly EnginesReadyOption _enginesWaitForReady;
  307. readonly FasterDictionary<RefWrapperType, FasterList<ReactEngineContainer<IReactOnAdd>>> _reactiveEnginesAdd;
  308. readonly FasterDictionary<RefWrapperType, FasterList<ReactEngineContainer<IReactOnAddEx>>>
  309. _reactiveEnginesAddEx;
  310. readonly FasterDictionary<RefWrapperType, FasterList<ReactEngineContainer<IReactOnRemove>>>
  311. _reactiveEnginesRemove;
  312. readonly FasterDictionary<RefWrapperType, FasterList<ReactEngineContainer<IReactOnRemoveEx>>>
  313. _reactiveEnginesRemoveEx;
  314. readonly FasterDictionary<RefWrapperType, FasterList<ReactEngineContainer<IReactOnSwap>>> _reactiveEnginesSwap;
  315. readonly FasterDictionary<RefWrapperType, FasterList<ReactEngineContainer<IReactOnSwapEx>>>
  316. _reactiveEnginesSwapEx;
  317. readonly FasterDictionary<RefWrapperType, FasterList<ReactEngineContainer<IReactOnDispose>>>
  318. _reactiveEnginesDispose;
  319. readonly FasterList<IReactOnSubmission> _reactiveEnginesSubmission;
  320. }
  321. public enum EnginesReadyOption
  322. {
  323. ReadyAsAdded,
  324. WaitForReady
  325. }
  326. }