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.

527 lines
27KB

  1. using System;
  2. using System.Runtime.CompilerServices;
  3. using Svelto.Common;
  4. using Svelto.DataStructures;
  5. using Svelto.DataStructures.Native;
  6. using Svelto.ECS.Internal;
  7. namespace Svelto.ECS
  8. {
  9. public partial class EnginesRoot
  10. {
  11. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  12. void SingleSubmission(PlatformProfiler profiler)
  13. {
  14. //clear the data checks before the submission. We want to allow structural changes inside the callbacks
  15. ClearChecksForMultipleOperationsOnTheSameEgid();
  16. _entitiesOperations.ExecuteRemoveAndSwappingOperations(
  17. _swapEntities,
  18. _removeEntities,
  19. _removeGroup,
  20. _swapGroup,
  21. this);
  22. AddEntities(profiler);
  23. //clear the data checks after the submission, so if structural changes happened inside the callback, the debug structure is reset for the next frame operations
  24. ClearChecksForMultipleOperationsOnTheSameEgid();
  25. }
  26. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  27. static void RemoveGroup(ExclusiveGroupStruct groupID, EnginesRoot enginesRoot)
  28. {
  29. using (var sampler = new PlatformProfiler("remove whole group"))
  30. {
  31. enginesRoot.RemoveEntitiesFromGroup(groupID, sampler);
  32. }
  33. }
  34. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  35. static void SwapGroup(ExclusiveGroupStruct fromGroupID, ExclusiveGroupStruct toGroupID, EnginesRoot enginesRoot)
  36. {
  37. using (var sampler = new PlatformProfiler("swap whole group"))
  38. {
  39. enginesRoot.SwapEntitiesBetweenGroups(fromGroupID, toGroupID, sampler);
  40. }
  41. }
  42. static void RemoveEntities(FasterDictionary<ExclusiveGroupStruct, FasterDictionary<ComponentID, FasterList<(uint, string)>>>
  43. removeOperations, FasterList<EGID> entitiesRemoved, EnginesRoot enginesRoot)
  44. {
  45. using (var sampler = new PlatformProfiler("remove Entities"))
  46. {
  47. using (sampler.Sample("Execute obsolete remove callbacks"))
  48. {
  49. foreach (var entitiesToRemove in removeOperations)
  50. {
  51. ExclusiveGroupStruct group = entitiesToRemove.key;
  52. var fromGroupDictionary = enginesRoot.GetDBGroup(group);
  53. foreach (var groupedEntitiesToRemove in entitiesToRemove.value)
  54. {
  55. var componentType = groupedEntitiesToRemove.key;
  56. ITypeSafeDictionary fromComponentsDictionary = fromGroupDictionary[componentType];
  57. FasterList<(uint, string)> infosToProcess = groupedEntitiesToRemove.value;
  58. fromComponentsDictionary.ExecuteEnginesRemoveCallbacks(
  59. infosToProcess,
  60. enginesRoot._reactiveEnginesRemove,
  61. group,
  62. in sampler);
  63. }
  64. }
  65. }
  66. using (sampler.Sample("Remove Entities"))
  67. {
  68. enginesRoot._cachedRangeOfSubmittedIndices.Clear();
  69. foreach (var entitiesToRemove in removeOperations)
  70. {
  71. ExclusiveGroupStruct fromGroup = entitiesToRemove.key;
  72. var fromGroupDictionary = enginesRoot.GetDBGroup(fromGroup);
  73. foreach (var groupedEntitiesToRemove in entitiesToRemove.value)
  74. {
  75. ComponentID componentType = groupedEntitiesToRemove.key;
  76. ITypeSafeDictionary fromComponentsDictionary = fromGroupDictionary[componentType];
  77. FasterList<(uint, string)> entityIDsToRemove = groupedEntitiesToRemove.value;
  78. enginesRoot._transientEntityIDsAffectedByRemoveAtSwapBack.Clear();
  79. fromComponentsDictionary.RemoveEntitiesFromDictionary(
  80. entityIDsToRemove,
  81. enginesRoot._transientEntityIDsAffectedByRemoveAtSwapBack);
  82. //important: remove from the filter must happen after remove from the dictionary
  83. //as we need to read the new indices linked to entities after the removal
  84. enginesRoot.RemoveEntitiesFromPersistentFilters(
  85. entityIDsToRemove,
  86. fromGroup,
  87. componentType,
  88. enginesRoot._transientEntityIDsAffectedByRemoveAtSwapBack);
  89. //store new database count after the entities are removed from the datatabase, plus the number of entities removed
  90. enginesRoot._cachedRangeOfSubmittedIndices.Add(
  91. ((uint, uint))(fromComponentsDictionary.count,
  92. fromComponentsDictionary.count + entityIDsToRemove.count));
  93. }
  94. }
  95. }
  96. var rangeEnumerator = enginesRoot._cachedRangeOfSubmittedIndices.GetEnumerator();
  97. //Note, very important: This is exploiting a trick of the removal operation (RemoveEntitiesFromDictionary)
  98. //You may wonder: how can the remove callbacks iterate entities that have been just removed
  99. //from the database? This works just because during a remove, entities are put at the end of the
  100. //array and not actually removed. The entities are not iterated anymore in future just because
  101. //the count of the array decreases. This means that at the end of the array, after the remove
  102. //operations, we will find the collection of entities just removed. The remove callbacks are
  103. //going to iterate the array from the new count to the new count + the number of entities removed
  104. using (sampler.Sample("Execute remove Callbacks Fast"))
  105. {
  106. foreach (var entitiesToRemove in removeOperations)
  107. {
  108. ExclusiveGroupStruct group = entitiesToRemove.key;
  109. var fromGroupDictionary = enginesRoot.GetDBGroup(group);
  110. foreach (var groupedEntitiesToRemove in entitiesToRemove.value)
  111. {
  112. rangeEnumerator.MoveNext();
  113. var componentType = groupedEntitiesToRemove.key;
  114. ITypeSafeDictionary fromComponentsDictionary = fromGroupDictionary[componentType];
  115. //get all the engines linked to TValue
  116. if (!enginesRoot._reactiveEnginesRemoveEx.TryGetValue(
  117. componentType,
  118. out var entityComponentsEngines))
  119. continue;
  120. fromComponentsDictionary.ExecuteEnginesRemoveCallbacksFast(
  121. entityComponentsEngines,
  122. group,
  123. rangeEnumerator.Current,
  124. in sampler);
  125. }
  126. }
  127. }
  128. //doing this at the end to be able to use EGID.ToEntityReference inside callbacks
  129. using (sampler.Sample("Remove Entity References"))
  130. {
  131. var count = entitiesRemoved.count;
  132. for (int i = 0; i < count; i++)
  133. {
  134. enginesRoot._entityLocator.RemoveEntityReference(entitiesRemoved[i]);
  135. }
  136. }
  137. }
  138. }
  139. static void SwapEntities(FasterDictionary<ExclusiveGroupStruct, FasterDictionary<ComponentID,
  140. FasterDictionary<ExclusiveGroupStruct, FasterDictionary<uint, SwapInfo>>>> swapEntitiesOperations,
  141. FasterDictionary<EGID, (EGID, EGID)> entitiesIDSwaps, EnginesRoot enginesRoot)
  142. {
  143. using (var sampler = new PlatformProfiler("Swap entities between groups"))
  144. {
  145. using (sampler.Sample("Update Entity References"))
  146. {
  147. var count = entitiesIDSwaps.count;
  148. var entitiesIDSwapsValues = entitiesIDSwaps.unsafeValues;
  149. for (int i = 0; i < count; i++)
  150. {
  151. var (fromEntityGid, toEntityGid) = entitiesIDSwapsValues[i];
  152. enginesRoot._entityLocator.UpdateEntityReference(fromEntityGid, toEntityGid);
  153. }
  154. }
  155. using (sampler.Sample("Swap Entities"))
  156. {
  157. //Entities to swap are organised in order to minimise the amount of dictionary lookups.
  158. //swapEntitiesOperations iterations happen in the following order:
  159. //for each fromGroup, get all the entities to swap for each component type.
  160. //then get the list of IDs for each ToGroup.
  161. //now swap the set of FromGroup -> ToGroup entities per ID.
  162. foreach (var entitiesToSwap in swapEntitiesOperations) //each operation is a component to swap
  163. {
  164. enginesRoot._cachedRangeOfSubmittedIndices.Clear();
  165. ExclusiveGroupStruct fromGroup = entitiesToSwap.key;
  166. FasterDictionary<ComponentID, ITypeSafeDictionary> fromGroupDictionary = enginesRoot.GetDBGroup(fromGroup);
  167. //iterate all the fromgroups
  168. foreach (var groupedEntitiesToSwap in entitiesToSwap.value)
  169. {
  170. ComponentID componentType = groupedEntitiesToSwap.key;
  171. ITypeSafeDictionary fromComponentsDictionaryDB = fromGroupDictionary[componentType];
  172. //for each fromgroup get the subset of togroups (entities that move from fromgroup to any togroup)
  173. foreach (var entitiesInfoToSwap in groupedEntitiesToSwap.value)
  174. {
  175. ExclusiveGroupStruct toGroup = entitiesInfoToSwap.key;
  176. ITypeSafeDictionary toComponentsDictionaryDB = enginesRoot.GetOrAddTypeSafeDictionary(
  177. toGroup, enginesRoot.GetOrAddDBGroup(toGroup), componentType, fromComponentsDictionaryDB);
  178. DBC.ECS.Check.Assert(toComponentsDictionaryDB != null, "something went wrong with the creation of dictionaries");
  179. //entities moving from fromgroup to this togroup
  180. FasterDictionary<uint, SwapInfo> fromEntityToEntityIDs = entitiesInfoToSwap.value;
  181. DBC.ECS.Check.Assert(fromEntityToEntityIDs.count > 0, "something went wrong, no entities to swap");
  182. //ensure that to dictionary has enough room to store the new entities
  183. var newBufferSize = (uint)(toComponentsDictionaryDB.count + fromEntityToEntityIDs.count);
  184. toComponentsDictionaryDB.EnsureCapacity(newBufferSize);
  185. //fortunately swap adds the swapped entities at the end of the buffer
  186. //so we can just iterate the list using the indices ranges added in the cachedIndices
  187. enginesRoot._cachedRangeOfSubmittedIndices.Add(((uint, uint))(toComponentsDictionaryDB.count, newBufferSize));
  188. enginesRoot._transientEntityIDsAffectedByRemoveAtSwapBack.Clear();
  189. fromComponentsDictionaryDB.SwapEntitiesBetweenDictionaries(
  190. fromEntityToEntityIDs, fromGroup, toGroup, toComponentsDictionaryDB,
  191. enginesRoot._transientEntityIDsAffectedByRemoveAtSwapBack);
  192. //important: this must happen after the entities are swapped in the database
  193. enginesRoot.SwapEntityBetweenPersistentFilters(
  194. fromEntityToEntityIDs, fromGroup, toGroup, componentType,
  195. enginesRoot._transientEntityIDsAffectedByRemoveAtSwapBack);
  196. }
  197. }
  198. var rangeEnumerator = enginesRoot._cachedRangeOfSubmittedIndices.GetEnumerator();
  199. using (sampler.Sample("Execute Swap Callbacks Fast"))
  200. {
  201. foreach (var groupedEntitiesToSwap in entitiesToSwap.value)
  202. {
  203. var componentType = groupedEntitiesToSwap.key;
  204. foreach (var entitiesInfoToSwap in groupedEntitiesToSwap.value)
  205. {
  206. rangeEnumerator.MoveNext();
  207. //get all the engines linked to TValue
  208. if (!enginesRoot._reactiveEnginesSwapEx.TryGetValue(componentType, out var entityComponentsEngines))
  209. continue;
  210. ExclusiveGroupStruct toGroup = entitiesInfoToSwap.key;
  211. ITypeSafeDictionary toComponentsDictionary = GetTypeSafeDictionary(
  212. toGroup, enginesRoot.GetDBGroup(toGroup), componentType);
  213. toComponentsDictionary.ExecuteEnginesSwapCallbacksFast(
  214. entityComponentsEngines, fromGroup, toGroup, rangeEnumerator.Current, in sampler);
  215. }
  216. }
  217. }
  218. }
  219. }
  220. using (sampler.Sample("Execute Swap Callbacks"))
  221. {
  222. foreach (var entitiesToSwap in swapEntitiesOperations)
  223. {
  224. ExclusiveGroupStruct fromGroup = entitiesToSwap.key;
  225. foreach (var groupedEntitiesToSwap in entitiesToSwap.value)
  226. {
  227. var componentType = groupedEntitiesToSwap.key;
  228. //get all the engines linked to TValue
  229. if (enginesRoot._reactiveEnginesSwap.TryGetValue(componentType, out var entityComponentsEngines) == false)
  230. continue;
  231. foreach (var entitiesInfoToSwap in groupedEntitiesToSwap.value)
  232. {
  233. ExclusiveGroupStruct toGroup = entitiesInfoToSwap.key;
  234. ITypeSafeDictionary toComponentsDictionary = GetTypeSafeDictionary(
  235. toGroup, enginesRoot.GetDBGroup(toGroup), componentType);
  236. var infosToProcess = entitiesInfoToSwap.value;
  237. toComponentsDictionary.ExecuteEnginesSwapCallbacks(
  238. infosToProcess, entityComponentsEngines,
  239. fromGroup, toGroup, in sampler);
  240. }
  241. }
  242. }
  243. }
  244. }
  245. }
  246. void AddEntities(PlatformProfiler sampler)
  247. {
  248. //current buffer becomes other, and other becomes current
  249. _groupedEntityToAdd.Swap();
  250. //I need to iterate the previous current, which is now other
  251. if (_groupedEntityToAdd.AnyPreviousEntityCreated())
  252. {
  253. _cachedRangeOfSubmittedIndices.Clear();
  254. using (sampler.Sample("Add operations"))
  255. {
  256. try
  257. {
  258. using (sampler.Sample("Add entities to database"))
  259. {
  260. //each group is indexed by entity view type. for each type there is a dictionary indexed
  261. //by entityID
  262. foreach (var groupToSubmit in _groupedEntityToAdd)
  263. {
  264. var groupID = groupToSubmit.@group;
  265. var groupDB = GetOrAddDBGroup(groupID);
  266. //add the entityComponents in the group
  267. foreach (var entityComponentsToSubmit in groupToSubmit.components)
  268. {
  269. var type = entityComponentsToSubmit.key;
  270. var fromDictionary = entityComponentsToSubmit.value;
  271. var toDictionary = GetOrAddTypeSafeDictionary(groupID, groupDB, type, fromDictionary);
  272. //all the new entities are added at the end of each dictionary list, so we can
  273. //just iterate the list using the indices ranges added in the _cachedIndices
  274. _cachedRangeOfSubmittedIndices.Add(((uint, uint))(toDictionary.count, toDictionary.count + fromDictionary.count));
  275. //Fill the DB with the entity components generated this frame.
  276. fromDictionary.AddEntitiesToDictionary(
  277. toDictionary, groupID
  278. #if SLOW_SVELTO_SUBMISSION
  279. , entityLocator
  280. #endif
  281. );
  282. }
  283. }
  284. }
  285. //then submit everything in the engines, so that the DB is up to date with all the entity components
  286. //created by the entity built
  287. var enumerator = _cachedRangeOfSubmittedIndices.GetEnumerator();
  288. using (sampler.Sample("Add entities to engines fast"))
  289. {
  290. foreach (GroupInfo groupToSubmit in _groupedEntityToAdd)
  291. {
  292. var groupID = groupToSubmit.@group;
  293. var groupDB = GetDBGroup(groupID);
  294. foreach (var entityComponentsToSubmit in groupToSubmit.components)
  295. {
  296. var type = entityComponentsToSubmit.key;
  297. var toDictionary = GetTypeSafeDictionary(groupID, groupDB, type);
  298. enumerator.MoveNext();
  299. toDictionary.ExecuteEnginesAddEntityCallbacksFast(_reactiveEnginesAddEx, groupID, enumerator.Current, in sampler);
  300. }
  301. }
  302. }
  303. //then submit everything in the engines, so that the DB is up to date with all the entity components
  304. //created by the entity built
  305. using (sampler.Sample("Add entities to engines"))
  306. {
  307. foreach (GroupInfo groupToSubmit in _groupedEntityToAdd)
  308. {
  309. var groupID = groupToSubmit.@group;
  310. var groupDB = GetDBGroup(groupID);
  311. //This loop iterates again all the entity components that have been just submitted to call
  312. //the Add Callbacks on them. Note that I am iterating the transient buffer of the just
  313. //added components, but calling the callback on the entities just added in the real buffer
  314. //Note: it's OK to add new entities while this happens because of the double buffer
  315. //design of the transient buffer of added entities.
  316. foreach (var entityComponentsToSubmit in groupToSubmit.components)
  317. {
  318. var type = entityComponentsToSubmit.key;
  319. var fromDictionary = entityComponentsToSubmit.value;
  320. //this contains the total number of components ever submitted in the DB
  321. ITypeSafeDictionary toDictionary = GetTypeSafeDictionary(groupID, groupDB, type);
  322. fromDictionary.ExecuteEnginesAddCallbacks(_reactiveEnginesAdd, toDictionary, groupID, in sampler);
  323. }
  324. }
  325. }
  326. }
  327. finally
  328. {
  329. using (sampler.Sample("clear double buffering"))
  330. {
  331. //other can be cleared now, but let's avoid deleting the dictionary every time
  332. _groupedEntityToAdd.ClearLastAddOperations();
  333. }
  334. }
  335. }
  336. }
  337. }
  338. bool HasMadeNewStructuralChangesInThisIteration()
  339. {
  340. return _groupedEntityToAdd.AnyEntityCreated() || _entitiesOperations.AnyOperationQueued();
  341. }
  342. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  343. void RemoveEntitiesFromGroup(ExclusiveGroupStruct groupID, in PlatformProfiler profiler)
  344. {
  345. _entityLocator.RemoveAllGroupReferenceLocators(groupID);
  346. if (_groupEntityComponentsDB.TryGetValue(groupID, out var dictionariesOfEntities))
  347. {
  348. foreach (var dictionaryOfEntities in dictionariesOfEntities)
  349. {
  350. //RemoveEX happens inside
  351. dictionaryOfEntities.value.ExecuteEnginesRemoveCallbacks_Group(
  352. _reactiveEnginesRemove, _reactiveEnginesRemoveEx, groupID, profiler);
  353. }
  354. foreach (var dictionaryOfEntities in dictionariesOfEntities)
  355. {
  356. dictionaryOfEntities.value.Clear();
  357. _groupsPerEntity[dictionaryOfEntities.key][groupID].Clear();
  358. }
  359. }
  360. }
  361. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  362. void SwapEntitiesBetweenGroups(ExclusiveGroupStruct fromGroupId, ExclusiveGroupStruct toGroupId, PlatformProfiler platformProfiler)
  363. {
  364. FasterDictionary<ComponentID, ITypeSafeDictionary> fromGroup = GetDBGroup(fromGroupId);
  365. FasterDictionary<ComponentID, ITypeSafeDictionary> toGroup = GetOrAddDBGroup(toGroupId);
  366. _entityLocator.UpdateAllGroupReferenceLocators(fromGroupId, toGroupId);
  367. //remove entities from dictionaries
  368. foreach (var dictionaryOfEntities in fromGroup)
  369. {
  370. var refWrapperType = dictionaryOfEntities.key;
  371. ITypeSafeDictionary fromDictionary = dictionaryOfEntities.value;
  372. ITypeSafeDictionary toDictionary = GetOrAddTypeSafeDictionary(toGroupId, toGroup, refWrapperType, fromDictionary);
  373. fromDictionary.AddEntitiesToDictionary(
  374. toDictionary, toGroupId
  375. #if SLOW_SVELTO_SUBMISSION
  376. , entityLocator
  377. #endif
  378. );
  379. }
  380. //Call all the callbacks
  381. foreach (var dictionaryOfEntities in fromGroup)
  382. {
  383. var refWrapperType = dictionaryOfEntities.key;
  384. ITypeSafeDictionary fromDictionary = dictionaryOfEntities.value;
  385. ITypeSafeDictionary toDictionary = GetTypeSafeDictionary(toGroupId, toGroup, refWrapperType);
  386. //SwapEX happens inside
  387. fromDictionary.ExecuteEnginesSwapCallbacks_Group(
  388. _reactiveEnginesSwap, _reactiveEnginesSwapEx, toDictionary,
  389. fromGroupId, toGroupId, platformProfiler);
  390. }
  391. //remove entities from dictionaries
  392. foreach (var dictionaryOfEntities in fromGroup)
  393. {
  394. dictionaryOfEntities.value.Clear();
  395. _groupsPerEntity[dictionaryOfEntities.key][fromGroupId].Clear();
  396. }
  397. }
  398. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  399. ITypeSafeDictionary GetOrAddTypeSafeDictionary(ExclusiveGroupStruct groupId,
  400. FasterDictionary<ComponentID, ITypeSafeDictionary> groupPerComponentType, ComponentID typeID,
  401. ITypeSafeDictionary fromDictionary)
  402. {
  403. //be sure that the TypeSafeDictionary for the entity Type exists
  404. if (groupPerComponentType.TryGetValue(typeID, out ITypeSafeDictionary toEntitiesDictionary) == false)
  405. {
  406. toEntitiesDictionary = fromDictionary.Create();
  407. groupPerComponentType.Add(typeID, toEntitiesDictionary);
  408. }
  409. {
  410. //update GroupsPerEntity
  411. if (_groupsPerEntity.TryGetValue(typeID, out var groupedGroup) == false)
  412. groupedGroup = _groupsPerEntity[typeID] =
  413. new FasterDictionary<ExclusiveGroupStruct, ITypeSafeDictionary>();
  414. groupedGroup[groupId] = toEntitiesDictionary;
  415. }
  416. return toEntitiesDictionary;
  417. }
  418. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  419. static ITypeSafeDictionary GetTypeSafeDictionary(ExclusiveGroupStruct groupID,
  420. FasterDictionary<ComponentID, ITypeSafeDictionary> @group, ComponentID refWrapper)
  421. {
  422. if (@group.TryGetValue(refWrapper, out ITypeSafeDictionary fromTypeSafeDictionary) == false)
  423. {
  424. throw new ECSException("no group found: ".FastConcat(groupID.ToName()));
  425. }
  426. return fromTypeSafeDictionary;
  427. }
  428. readonly DoubleBufferedEntitiesToAdd _groupedEntityToAdd;
  429. readonly EntitiesOperations _entitiesOperations;
  430. //transient caches>>>>>>>>>>>>>>>>>>>>>
  431. readonly FasterList<(uint, uint)> _cachedRangeOfSubmittedIndices;
  432. readonly FasterDictionary<uint, uint> _transientEntityIDsAffectedByRemoveAtSwapBack;
  433. //<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
  434. static readonly
  435. Action<FasterDictionary<ExclusiveGroupStruct, FasterDictionary<ComponentID,
  436. FasterDictionary<ExclusiveGroupStruct, FasterDictionary<uint, SwapInfo>>>>, FasterDictionary<EGID, (EGID, EGID)>,
  437. EnginesRoot> _swapEntities;
  438. static readonly Action<
  439. FasterDictionary<ExclusiveGroupStruct, FasterDictionary<ComponentID, FasterList<(uint, string)>>>,
  440. FasterList<EGID>, EnginesRoot> _removeEntities;
  441. static readonly Action<ExclusiveGroupStruct, EnginesRoot> _removeGroup;
  442. static readonly Action<ExclusiveGroupStruct, ExclusiveGroupStruct, EnginesRoot> _swapGroup;
  443. }
  444. }