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.

564 lines
28KB

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