using System; using System.Collections.Generic; using System.Threading; using Svelto.DataStructures; namespace Svelto.ECS { /// /// This mechanism is not for thread-safety but to be sure that all the permutations of group tags always /// point to the same group ID. /// A group compound can generate several permutation of tags, so that the order of the tag doesn't matter, /// but for this to work, each permutation must be identified by the same ID (generated by the unique combination) /// static class GroupCompoundInitializer { internal static readonly ThreadLocal skipStaticCompoundConstructorsWith4Tags = new ThreadLocal(); internal static readonly ThreadLocal skipStaticCompoundConstructorsWith3Tags = new ThreadLocal(); internal static readonly ThreadLocal skipStaticCompoundConstructorsWith2Tags = new ThreadLocal(); } public abstract class GroupCompound where G1 : GroupTag where G2 : GroupTag where G3 : GroupTag where G4 : GroupTag { static GroupCompound() { //avoid race conditions if compounds are using on multiple thread if (Interlocked.CompareExchange(ref isInitializing, 1, 0) == 0 && GroupCompoundInitializer.skipStaticCompoundConstructorsWith4Tags.Value == false) { var group = new ExclusiveGroup(); //todo: it's a bit of a waste to create a class here even if this is a static constructor _Groups = new FasterList(1); _Groups.Add(group); #if DEBUG var name = $"Compound: {typeof(G1).Name}-{typeof(G2).Name}-{typeof(G3).Name}-{typeof(G4).Name} ID {(uint) group.id}"; GroupNamesMap.idToName[group] = name; #endif //The hashname is independent from the actual group ID. this is fundamental because it is want //guarantees the hash to be the same across different machines GroupHashMap.RegisterGroup(group, typeof(GroupCompound).FullName); _GroupsHashSet = new HashSet(_Groups.ToArrayFast(out _)); GroupCompoundInitializer.skipStaticCompoundConstructorsWith4Tags.Value = true; //all the permutations must share the same group and group hashset. Warm them up, avoid call the //constructors again, set the desired value GroupCompound._Groups = _Groups; GroupCompound._Groups = _Groups; GroupCompound._Groups = _Groups; GroupCompound._Groups = _Groups; GroupCompound._Groups = _Groups; GroupCompound._Groups = _Groups; GroupCompound._Groups = _Groups; GroupCompound._Groups = _Groups; GroupCompound._Groups = _Groups; GroupCompound._Groups = _Groups; GroupCompound._Groups = _Groups; GroupCompound._Groups = _Groups; GroupCompound._Groups = _Groups; GroupCompound._Groups = _Groups; GroupCompound._Groups = _Groups; GroupCompound._Groups = _Groups; GroupCompound._Groups = _Groups; GroupCompound._Groups = _Groups; GroupCompound._Groups = _Groups; GroupCompound._Groups = _Groups; GroupCompound._Groups = _Groups; GroupCompound._Groups = _Groups; GroupCompound._Groups = _Groups; //all the permutations are warmed up now GroupCompoundInitializer.skipStaticCompoundConstructorsWith4Tags.Value = false; GroupCompound._GroupsHashSet = _GroupsHashSet; GroupCompound._GroupsHashSet = _GroupsHashSet; GroupCompound._GroupsHashSet = _GroupsHashSet; GroupCompound._GroupsHashSet = _GroupsHashSet; GroupCompound._GroupsHashSet = _GroupsHashSet; GroupCompound._GroupsHashSet = _GroupsHashSet; GroupCompound._GroupsHashSet = _GroupsHashSet; GroupCompound._GroupsHashSet = _GroupsHashSet; GroupCompound._GroupsHashSet = _GroupsHashSet; GroupCompound._GroupsHashSet = _GroupsHashSet; GroupCompound._GroupsHashSet = _GroupsHashSet; GroupCompound._GroupsHashSet = _GroupsHashSet; GroupCompound._GroupsHashSet = _GroupsHashSet; GroupCompound._GroupsHashSet = _GroupsHashSet; GroupCompound._GroupsHashSet = _GroupsHashSet; GroupCompound._GroupsHashSet = _GroupsHashSet; GroupCompound._GroupsHashSet = _GroupsHashSet; GroupCompound._GroupsHashSet = _GroupsHashSet; GroupCompound._GroupsHashSet = _GroupsHashSet; GroupCompound._GroupsHashSet = _GroupsHashSet; GroupCompound._GroupsHashSet = _GroupsHashSet; GroupCompound._GroupsHashSet = _GroupsHashSet; GroupCompound._GroupsHashSet = _GroupsHashSet; GroupCompound.Add(group); GroupCompound.Add(group); GroupCompound.Add(group); GroupCompound.Add(group); GroupCompound.Add(group); // and must share the same array GroupCompound.Add(group); GroupCompound.Add(group); GroupCompound.Add(group); GroupCompound.Add(group); GroupCompound.Add(group); //This is done here to be sure that the group is added once per group tag //(if done inside the previous group compound it would be added multiple times) GroupTag.Add(group); GroupTag.Add(group); GroupTag.Add(group); GroupTag.Add(group); } } public static FasterReadOnlyList Groups => new FasterReadOnlyList(_Groups); public static ExclusiveBuildGroup BuildGroup => new ExclusiveBuildGroup(_Groups[0]); public static bool Includes(ExclusiveGroupStruct group) { return _GroupsHashSet.Contains(group); } internal static void Add(ExclusiveGroupStruct group) { #if DEBUG && !PROFILE_SVELTO for (var i = 0; i < _Groups.count; ++i) if (_Groups[i] == group) throw new Exception("this test must be transformed in unit test"); #endif _Groups.Add(group); _GroupsHashSet.Add(group); } static readonly FasterList _Groups; static readonly HashSet _GroupsHashSet; //we are changing this with Interlocked, so it cannot be readonly static int isInitializing; } public abstract class GroupCompound where G1 : GroupTag where G2 : GroupTag where G3 : GroupTag { static GroupCompound() { if (Interlocked.CompareExchange(ref isInitializing, 1, 0) == 0 && GroupCompoundInitializer.skipStaticCompoundConstructorsWith3Tags.Value == false) { var group = new ExclusiveGroup(); //todo: it's a bit of a waste to create a class here even if this is a static constructor _Groups = new FasterList(1); _Groups.Add(group); #if DEBUG var name = $"Compound: {typeof(G1).Name}-{typeof(G2).Name}-{typeof(G3).Name} ID {(uint) group.id}"; GroupNamesMap.idToName[group] = name; #endif //The hashname is independent from the actual group ID. this is fundamental because it is want //guarantees the hash to be the same across different machines GroupHashMap.RegisterGroup(group, typeof(GroupCompound).FullName); _GroupsHashSet = new HashSet(_Groups.ToArrayFast(out _)); GroupCompoundInitializer.skipStaticCompoundConstructorsWith3Tags.Value = true; //all the combinations must share the same group and group hashset GroupCompound._Groups = _Groups; GroupCompound._Groups = _Groups; GroupCompound._Groups = _Groups; GroupCompound._Groups = _Groups; GroupCompound._Groups = _Groups; //all the constructor have been called now GroupCompoundInitializer.skipStaticCompoundConstructorsWith3Tags.Value = false; GroupCompound._GroupsHashSet = _GroupsHashSet; GroupCompound._GroupsHashSet = _GroupsHashSet; GroupCompound._GroupsHashSet = _GroupsHashSet; GroupCompound._GroupsHashSet = _GroupsHashSet; GroupCompound._GroupsHashSet = _GroupsHashSet; GroupCompound.Add(group); // and must share the same array GroupCompound.Add(group); GroupCompound.Add(group); //This is done here to be sure that the group is added once per group tag //(if done inside the previous group compound it would be added multiple times) GroupTag.Add(group); GroupTag.Add(group); GroupTag.Add(group); } } public static FasterReadOnlyList Groups => new FasterReadOnlyList(_Groups); public static ExclusiveBuildGroup BuildGroup => new ExclusiveBuildGroup(_Groups[0]); public static bool Includes(ExclusiveGroupStruct group) { return _GroupsHashSet.Contains(group); } internal static void Add(ExclusiveGroupStruct group) { #if DEBUG && !PROFILE_SVELTO for (var i = 0; i < _Groups.count; ++i) if (_Groups[i] == group) throw new Exception("this test must be transformed in unit test"); #endif _Groups.Add(group); _GroupsHashSet.Add(group); } static readonly FasterList _Groups; static readonly HashSet _GroupsHashSet; //we are changing this with Interlocked, so it cannot be readonly static int isInitializing; } public abstract class GroupCompound where G1 : GroupTag where G2 : GroupTag { static GroupCompound() { if (Interlocked.CompareExchange(ref isInitializing, 1, 0) == 0 && GroupCompoundInitializer.skipStaticCompoundConstructorsWith2Tags.Value == false) { var group = new ExclusiveGroup(); //todo: it's a bit of a waste to create a class here even if this is a static constructor _Groups = new FasterList(1); _Groups.Add(group); #if DEBUG GroupNamesMap.idToName[group] = $"Compound: {typeof(G1).Name}-{typeof(G2).Name} ID {group.id}"; #endif //The hashname is independent from the actual group ID. this is fundamental because it is want //guarantees the hash to be the same across different machines GroupHashMap.RegisterGroup(group, typeof(GroupCompound).FullName); _GroupsHashSet = new HashSet(_Groups.ToArrayFast(out _)); GroupCompoundInitializer.skipStaticCompoundConstructorsWith2Tags.Value = true; GroupCompound._Groups = _Groups; GroupCompoundInitializer.skipStaticCompoundConstructorsWith2Tags.Value = false; GroupCompound._GroupsHashSet = _GroupsHashSet; //every abstract group preemptively adds this group, it may or may not be empty in future GroupTag.Add(group); GroupTag.Add(group); } } public static FasterReadOnlyList Groups => new FasterReadOnlyList(_Groups); public static ExclusiveBuildGroup BuildGroup => new ExclusiveBuildGroup(_Groups[0]); public static bool Includes(ExclusiveGroupStruct group) { return _GroupsHashSet.Contains(group); } internal static void Add(ExclusiveGroupStruct group) { #if DEBUG && !PROFILE_SVELTO for (var i = 0; i < _Groups.count; ++i) if (_Groups[i] == group) throw new Exception("this test must be transformed in unit test"); #endif _Groups.Add(group); _GroupsHashSet.Add(group); } static readonly FasterList _Groups; static readonly HashSet _GroupsHashSet; static int isInitializing; } /// /// A Group Tag holds initially just a group, itself. However the number of groups can grow with the number of /// combinations of GroupTags including this one. This because a GroupTag is an adjective and different entities /// can use the same adjective together with other ones. However since I need to be able to iterate over all the /// groups with the same adjective, a group tag needs to hold all the groups sharing it. /// /// public abstract class GroupTag where T : GroupTag { static GroupTag() { if (Interlocked.CompareExchange(ref isInitializing, 1, 0) == 0) { var group = new ExclusiveGroup(); _Groups.Add(group); #if DEBUG var typeInfo = typeof(T); var name = $"Compound: {typeInfo.Name} ID {(uint) group.id}"; var typeInfoBaseType = typeInfo.BaseType; if (typeInfoBaseType.GenericTypeArguments[0] != typeInfo) //todo: this should shield from using a pattern different than public class GROUP_NAME : GroupTag {} however I am not sure it's working throw new ECSException("Invalid Group Tag declared"); GroupNamesMap.idToName[group] = name; #endif //The hashname is independent from the actual group ID. this is fundamental because it is want //guarantees the hash to be the same across different machines GroupHashMap.RegisterGroup(group, typeof(GroupTag).FullName); _GroupsHashSet = new HashSet(_Groups.ToArrayFast(out _)); } } public static FasterReadOnlyList Groups => new FasterReadOnlyList(_Groups); public static ExclusiveBuildGroup BuildGroup => new ExclusiveBuildGroup(_Groups[0]); public static bool Includes(ExclusiveGroupStruct group) { return _GroupsHashSet.Contains(group); } //Each time a new combination of group tags is found a new group is added. internal static void Add(ExclusiveGroupStruct group) { #if DEBUG && !PROFILE_SVELTO for (var i = 0; i < _Groups.count; ++i) if (_Groups[i] == group) throw new Exception("this test must be transformed in unit test"); #endif _Groups.Add(group); _GroupsHashSet.Add(group); } static readonly FasterList _Groups = new FasterList(1); static readonly HashSet _GroupsHashSet; //we are changing this with Interlocked, so it cannot be readonly static int isInitializing; } }