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.

358 lines
17KB

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Threading;
  4. using Svelto.DataStructures;
  5. namespace Svelto.ECS
  6. {
  7. /// <summary>
  8. /// This mechanism is not for thread-safety but to be sure that all the permutations of group tags always
  9. /// point to the same group ID.
  10. /// A group compound can generate several permutation of tags, so that the order of the tag doesn't matter,
  11. /// but for this to work, each permutation must be identified by the same ID (generated by the unique combination)
  12. /// </summary>
  13. static class GroupCompoundInitializer
  14. {
  15. internal static readonly ThreadLocal<bool> skipStaticCompoundConstructorsWith4Tags = new ThreadLocal<bool>();
  16. internal static readonly ThreadLocal<bool> skipStaticCompoundConstructorsWith3Tags = new ThreadLocal<bool>();
  17. internal static readonly ThreadLocal<bool> skipStaticCompoundConstructorsWith2Tags = new ThreadLocal<bool>();
  18. }
  19. public abstract class GroupCompound<G1, G2, G3, G4> where G1 : GroupTag<G1>
  20. where G2 : GroupTag<G2>
  21. where G3 : GroupTag<G3>
  22. where G4 : GroupTag<G4>
  23. {
  24. static GroupCompound()
  25. {
  26. //avoid race conditions if compounds are using on multiple thread
  27. if (Interlocked.CompareExchange(ref isInitializing, 1, 0) == 0
  28. && GroupCompoundInitializer.skipStaticCompoundConstructorsWith4Tags.Value == false)
  29. {
  30. var group = new ExclusiveGroup(); //todo: it's a bit of a waste to create a class here even if this is a static constructor
  31. _Groups = new FasterList<ExclusiveGroupStruct>(1);
  32. _Groups.Add(group);
  33. #if DEBUG
  34. var name =
  35. $"Compound: {typeof(G1).Name}-{typeof(G2).Name}-{typeof(G3).Name}-{typeof(G4).Name} ID {(uint) group.id}";
  36. GroupNamesMap.idToName[group] = name;
  37. #endif
  38. //The hashname is independent from the actual group ID. this is fundamental because it is want
  39. //guarantees the hash to be the same across different machines
  40. GroupHashMap.RegisterGroup(group, typeof(GroupCompound<G1, G2, G3, G4>).FullName);
  41. _GroupsHashSet = new HashSet<ExclusiveGroupStruct>(_Groups.ToArrayFast(out _));
  42. GroupCompoundInitializer.skipStaticCompoundConstructorsWith4Tags.Value = true;
  43. //all the permutations must share the same group and group hashset. Warm them up, avoid call the
  44. //constructors again, set the desired value
  45. GroupCompound<G1, G2, G4, G3>._Groups = _Groups;
  46. GroupCompound<G1, G3, G2, G4>._Groups = _Groups;
  47. GroupCompound<G1, G3, G4, G2>._Groups = _Groups;
  48. GroupCompound<G1, G4, G2, G3>._Groups = _Groups;
  49. GroupCompound<G2, G1, G3, G4>._Groups = _Groups;
  50. GroupCompound<G2, G3, G4, G1>._Groups = _Groups;
  51. GroupCompound<G3, G1, G2, G4>._Groups = _Groups;
  52. GroupCompound<G4, G1, G2, G3>._Groups = _Groups;
  53. GroupCompound<G1, G4, G3, G2>._Groups = _Groups;
  54. GroupCompound<G2, G1, G4, G3>._Groups = _Groups;
  55. GroupCompound<G2, G4, G3, G1>._Groups = _Groups;
  56. GroupCompound<G3, G1, G4, G2>._Groups = _Groups;
  57. GroupCompound<G4, G1, G3, G2>._Groups = _Groups;
  58. GroupCompound<G2, G3, G1, G4>._Groups = _Groups;
  59. GroupCompound<G3, G4, G1, G2>._Groups = _Groups;
  60. GroupCompound<G2, G4, G1, G3>._Groups = _Groups;
  61. GroupCompound<G3, G2, G1, G4>._Groups = _Groups;
  62. GroupCompound<G3, G2, G4, G1>._Groups = _Groups;
  63. GroupCompound<G3, G4, G2, G1>._Groups = _Groups;
  64. GroupCompound<G4, G2, G1, G3>._Groups = _Groups;
  65. GroupCompound<G4, G2, G3, G1>._Groups = _Groups;
  66. GroupCompound<G4, G3, G1, G2>._Groups = _Groups;
  67. GroupCompound<G4, G3, G2, G1>._Groups = _Groups;
  68. //all the permutations are warmed up now
  69. GroupCompoundInitializer.skipStaticCompoundConstructorsWith4Tags.Value = false;
  70. GroupCompound<G1, G2, G4, G3>._GroupsHashSet = _GroupsHashSet;
  71. GroupCompound<G1, G3, G2, G4>._GroupsHashSet = _GroupsHashSet;
  72. GroupCompound<G1, G3, G4, G2>._GroupsHashSet = _GroupsHashSet;
  73. GroupCompound<G1, G4, G2, G3>._GroupsHashSet = _GroupsHashSet;
  74. GroupCompound<G2, G1, G3, G4>._GroupsHashSet = _GroupsHashSet;
  75. GroupCompound<G2, G3, G4, G1>._GroupsHashSet = _GroupsHashSet;
  76. GroupCompound<G3, G1, G2, G4>._GroupsHashSet = _GroupsHashSet;
  77. GroupCompound<G4, G1, G2, G3>._GroupsHashSet = _GroupsHashSet;
  78. GroupCompound<G1, G4, G3, G2>._GroupsHashSet = _GroupsHashSet;
  79. GroupCompound<G2, G1, G4, G3>._GroupsHashSet = _GroupsHashSet;
  80. GroupCompound<G2, G4, G3, G1>._GroupsHashSet = _GroupsHashSet;
  81. GroupCompound<G3, G1, G4, G2>._GroupsHashSet = _GroupsHashSet;
  82. GroupCompound<G4, G1, G3, G2>._GroupsHashSet = _GroupsHashSet;
  83. GroupCompound<G2, G3, G1, G4>._GroupsHashSet = _GroupsHashSet;
  84. GroupCompound<G3, G4, G1, G2>._GroupsHashSet = _GroupsHashSet;
  85. GroupCompound<G2, G4, G1, G3>._GroupsHashSet = _GroupsHashSet;
  86. GroupCompound<G3, G2, G1, G4>._GroupsHashSet = _GroupsHashSet;
  87. GroupCompound<G3, G2, G4, G1>._GroupsHashSet = _GroupsHashSet;
  88. GroupCompound<G3, G4, G2, G1>._GroupsHashSet = _GroupsHashSet;
  89. GroupCompound<G4, G2, G1, G3>._GroupsHashSet = _GroupsHashSet;
  90. GroupCompound<G4, G2, G3, G1>._GroupsHashSet = _GroupsHashSet;
  91. GroupCompound<G4, G3, G1, G2>._GroupsHashSet = _GroupsHashSet;
  92. GroupCompound<G4, G3, G2, G1>._GroupsHashSet = _GroupsHashSet;
  93. GroupCompound<G1, G2, G3>.Add(group);
  94. GroupCompound<G1, G2, G4>.Add(group);
  95. GroupCompound<G1, G3, G4>.Add(group);
  96. GroupCompound<G2, G3, G4>.Add(group);
  97. GroupCompound<G1, G2>.Add(group); //<G1/G2> and <G2/G1> must share the same array
  98. GroupCompound<G1, G3>.Add(group);
  99. GroupCompound<G1, G4>.Add(group);
  100. GroupCompound<G2, G3>.Add(group);
  101. GroupCompound<G2, G4>.Add(group);
  102. GroupCompound<G3, G4>.Add(group);
  103. //This is done here to be sure that the group is added once per group tag
  104. //(if done inside the previous group compound it would be added multiple times)
  105. GroupTag<G1>.Add(group);
  106. GroupTag<G2>.Add(group);
  107. GroupTag<G3>.Add(group);
  108. GroupTag<G4>.Add(group);
  109. }
  110. }
  111. public static FasterReadOnlyList<ExclusiveGroupStruct> Groups =>
  112. new FasterReadOnlyList<ExclusiveGroupStruct>(_Groups);
  113. public static ExclusiveBuildGroup BuildGroup => new ExclusiveBuildGroup(_Groups[0]);
  114. public static bool Includes(ExclusiveGroupStruct group)
  115. {
  116. return _GroupsHashSet.Contains(group);
  117. }
  118. internal static void Add(ExclusiveGroupStruct group)
  119. {
  120. #if DEBUG && !PROFILE_SVELTO
  121. for (var i = 0; i < _Groups.count; ++i)
  122. if (_Groups[i] == group)
  123. throw new Exception("this test must be transformed in unit test");
  124. #endif
  125. _Groups.Add(group);
  126. _GroupsHashSet.Add(group);
  127. }
  128. static readonly FasterList<ExclusiveGroupStruct> _Groups;
  129. static readonly HashSet<ExclusiveGroupStruct> _GroupsHashSet;
  130. //we are changing this with Interlocked, so it cannot be readonly
  131. static int isInitializing;
  132. }
  133. public abstract class GroupCompound<G1, G2, G3>
  134. where G1 : GroupTag<G1> where G2 : GroupTag<G2> where G3 : GroupTag<G3>
  135. {
  136. static GroupCompound()
  137. {
  138. if (Interlocked.CompareExchange(ref isInitializing, 1, 0) == 0
  139. && GroupCompoundInitializer.skipStaticCompoundConstructorsWith3Tags.Value == false)
  140. {
  141. var group = new ExclusiveGroup(); //todo: it's a bit of a waste to create a class here even if this is a static constructor
  142. _Groups = new FasterList<ExclusiveGroupStruct>(1);
  143. _Groups.Add(group);
  144. #if DEBUG
  145. var name = $"Compound: {typeof(G1).Name}-{typeof(G2).Name}-{typeof(G3).Name} ID {(uint) group.id}";
  146. GroupNamesMap.idToName[group] = name;
  147. #endif
  148. //The hashname is independent from the actual group ID. this is fundamental because it is want
  149. //guarantees the hash to be the same across different machines
  150. GroupHashMap.RegisterGroup(group, typeof(GroupCompound<G1, G2, G3>).FullName);
  151. _GroupsHashSet = new HashSet<ExclusiveGroupStruct>(_Groups.ToArrayFast(out _));
  152. GroupCompoundInitializer.skipStaticCompoundConstructorsWith3Tags.Value = true;
  153. //all the combinations must share the same group and group hashset
  154. GroupCompound<G3, G1, G2>._Groups = _Groups;
  155. GroupCompound<G2, G3, G1>._Groups = _Groups;
  156. GroupCompound<G3, G2, G1>._Groups = _Groups;
  157. GroupCompound<G1, G3, G2>._Groups = _Groups;
  158. GroupCompound<G2, G1, G3>._Groups = _Groups;
  159. //all the constructor have been called now
  160. GroupCompoundInitializer.skipStaticCompoundConstructorsWith3Tags.Value = false;
  161. GroupCompound<G3, G1, G2>._GroupsHashSet = _GroupsHashSet;
  162. GroupCompound<G2, G3, G1>._GroupsHashSet = _GroupsHashSet;
  163. GroupCompound<G3, G2, G1>._GroupsHashSet = _GroupsHashSet;
  164. GroupCompound<G1, G3, G2>._GroupsHashSet = _GroupsHashSet;
  165. GroupCompound<G2, G1, G3>._GroupsHashSet = _GroupsHashSet;
  166. GroupCompound<G1, G2>.Add(group); //<G1/G2> and <G2/G1> must share the same array
  167. GroupCompound<G1, G3>.Add(group);
  168. GroupCompound<G2, G3>.Add(group);
  169. //This is done here to be sure that the group is added once per group tag
  170. //(if done inside the previous group compound it would be added multiple times)
  171. GroupTag<G1>.Add(group);
  172. GroupTag<G2>.Add(group);
  173. GroupTag<G3>.Add(group);
  174. }
  175. }
  176. public static FasterReadOnlyList<ExclusiveGroupStruct> Groups =>
  177. new FasterReadOnlyList<ExclusiveGroupStruct>(_Groups);
  178. public static ExclusiveBuildGroup BuildGroup => new ExclusiveBuildGroup(_Groups[0]);
  179. public static bool Includes(ExclusiveGroupStruct group)
  180. {
  181. return _GroupsHashSet.Contains(group);
  182. }
  183. internal static void Add(ExclusiveGroupStruct group)
  184. {
  185. #if DEBUG && !PROFILE_SVELTO
  186. for (var i = 0; i < _Groups.count; ++i)
  187. if (_Groups[i] == group)
  188. throw new Exception("this test must be transformed in unit test");
  189. #endif
  190. _Groups.Add(group);
  191. _GroupsHashSet.Add(group);
  192. }
  193. static readonly FasterList<ExclusiveGroupStruct> _Groups;
  194. static readonly HashSet<ExclusiveGroupStruct> _GroupsHashSet;
  195. //we are changing this with Interlocked, so it cannot be readonly
  196. static int isInitializing;
  197. }
  198. public abstract class GroupCompound<G1, G2> where G1 : GroupTag<G1> where G2 : GroupTag<G2>
  199. {
  200. static GroupCompound()
  201. {
  202. if (Interlocked.CompareExchange(ref isInitializing, 1, 0) == 0
  203. && GroupCompoundInitializer.skipStaticCompoundConstructorsWith2Tags.Value == false)
  204. {
  205. var group = new ExclusiveGroup(); //todo: it's a bit of a waste to create a class here even if this is a static constructor
  206. _Groups = new FasterList<ExclusiveGroupStruct>(1);
  207. _Groups.Add(group);
  208. #if DEBUG
  209. GroupNamesMap.idToName[group] = $"Compound: {typeof(G1).Name}-{typeof(G2).Name} ID {group.id}";
  210. #endif
  211. //The hashname is independent from the actual group ID. this is fundamental because it is want
  212. //guarantees the hash to be the same across different machines
  213. GroupHashMap.RegisterGroup(group, typeof(GroupCompound<G1, G2>).FullName);
  214. _GroupsHashSet = new HashSet<ExclusiveGroupStruct>(_Groups.ToArrayFast(out _));
  215. GroupCompoundInitializer.skipStaticCompoundConstructorsWith2Tags.Value = true;
  216. GroupCompound<G2, G1>._Groups = _Groups;
  217. GroupCompoundInitializer.skipStaticCompoundConstructorsWith2Tags.Value = false;
  218. GroupCompound<G2, G1>._GroupsHashSet = _GroupsHashSet;
  219. //every abstract group preemptively adds this group, it may or may not be empty in future
  220. GroupTag<G1>.Add(group);
  221. GroupTag<G2>.Add(group);
  222. }
  223. }
  224. public static FasterReadOnlyList<ExclusiveGroupStruct> Groups =>
  225. new FasterReadOnlyList<ExclusiveGroupStruct>(_Groups);
  226. public static ExclusiveBuildGroup BuildGroup => new ExclusiveBuildGroup(_Groups[0]);
  227. public static bool Includes(ExclusiveGroupStruct group)
  228. {
  229. return _GroupsHashSet.Contains(group);
  230. }
  231. internal static void Add(ExclusiveGroupStruct group)
  232. {
  233. #if DEBUG && !PROFILE_SVELTO
  234. for (var i = 0; i < _Groups.count; ++i)
  235. if (_Groups[i] == group)
  236. throw new Exception("this test must be transformed in unit test");
  237. #endif
  238. _Groups.Add(group);
  239. _GroupsHashSet.Add(group);
  240. }
  241. static readonly FasterList<ExclusiveGroupStruct> _Groups;
  242. static readonly HashSet<ExclusiveGroupStruct> _GroupsHashSet;
  243. static int isInitializing;
  244. }
  245. /// <summary>
  246. /// A Group Tag holds initially just a group, itself. However the number of groups can grow with the number of
  247. /// combinations of GroupTags including this one. This because a GroupTag is an adjective and different entities
  248. /// can use the same adjective together with other ones. However since I need to be able to iterate over all the
  249. /// groups with the same adjective, a group tag needs to hold all the groups sharing it.
  250. /// </summary>
  251. /// <typeparam name="T"></typeparam>
  252. public abstract class GroupTag<T> where T : GroupTag<T>
  253. {
  254. static GroupTag()
  255. {
  256. if (Interlocked.CompareExchange(ref isInitializing, 1, 0) == 0)
  257. {
  258. var group = new ExclusiveGroup();
  259. _Groups.Add(group);
  260. #if DEBUG
  261. var typeInfo = typeof(T);
  262. var name = $"Compound: {typeInfo.Name} ID {(uint) group.id}";
  263. var typeInfoBaseType = typeInfo.BaseType;
  264. if (typeInfoBaseType.GenericTypeArguments[0] != typeInfo) //todo: this should shield from using a pattern different than public class GROUP_NAME : GroupTag<GROUP_NAME> {} however I am not sure it's working
  265. throw new ECSException("Invalid Group Tag declared");
  266. GroupNamesMap.idToName[group] = name;
  267. #endif
  268. //The hashname is independent from the actual group ID. this is fundamental because it is want
  269. //guarantees the hash to be the same across different machines
  270. GroupHashMap.RegisterGroup(group, typeof(GroupTag<T>).FullName);
  271. _GroupsHashSet = new HashSet<ExclusiveGroupStruct>(_Groups.ToArrayFast(out _));
  272. }
  273. }
  274. public static FasterReadOnlyList<ExclusiveGroupStruct> Groups =>
  275. new FasterReadOnlyList<ExclusiveGroupStruct>(_Groups);
  276. public static ExclusiveBuildGroup BuildGroup => new ExclusiveBuildGroup(_Groups[0]);
  277. public static bool Includes(ExclusiveGroupStruct group)
  278. {
  279. return _GroupsHashSet.Contains(group);
  280. }
  281. //Each time a new combination of group tags is found a new group is added.
  282. internal static void Add(ExclusiveGroupStruct group)
  283. {
  284. #if DEBUG && !PROFILE_SVELTO
  285. for (var i = 0; i < _Groups.count; ++i)
  286. if (_Groups[i] == group)
  287. throw new Exception("this test must be transformed in unit test");
  288. #endif
  289. _Groups.Add(group);
  290. _GroupsHashSet.Add(group);
  291. }
  292. static readonly FasterList<ExclusiveGroupStruct> _Groups = new FasterList<ExclusiveGroupStruct>(1);
  293. static readonly HashSet<ExclusiveGroupStruct> _GroupsHashSet;
  294. //we are changing this with Interlocked, so it cannot be readonly
  295. static int isInitializing;
  296. }
  297. }