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.

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