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.

338 lines
15KB

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