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.

407 lines
19KB

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