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.

450 lines
20KB

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