|
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407 |
- using System.Collections.Generic;
- using System.Threading;
- using Svelto.DataStructures;
-
- namespace Svelto.ECS
- {
- /// <summary>
- /// 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)
- /// </summary>
- static class GroupCompoundInitializer
- {
- internal static readonly ThreadLocal<bool> skipStaticCompoundConstructorsWith4Tags = new ThreadLocal<bool>();
- internal static readonly ThreadLocal<bool> skipStaticCompoundConstructorsWith3Tags = new ThreadLocal<bool>();
- internal static readonly ThreadLocal<bool> skipStaticCompoundConstructorsWith2Tags = new ThreadLocal<bool>();
- }
-
- interface ITouchedByReflection { }
-
- public abstract class GroupCompound<G1, G2, G3, G4>: ITouchedByReflection
- where G1 : GroupTag<G1>
- where G2 : GroupTag<G2>
- where G3 : GroupTag<G3>
- where G4 : GroupTag<G4>
- {
- static GroupCompound()
- {
- //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!
- /// c# Static constructors are guaranteed to be thread safe
- /// 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,
- /// the static constructor is always executed one time. To get a better understanding how this works, it helps to know what purpose it serves.
- if (Interlocked.CompareExchange(ref isInitializing, 1, 0) == 0 &&
- GroupCompoundInitializer.skipStaticCompoundConstructorsWith4Tags.Value == false)
- {
- System.Runtime.CompilerServices.RuntimeHelpers.RunClassConstructor(
- typeof(GroupCompound<G1, G2, G3, G4>).TypeHandle);
- var group = new ExclusiveGroup(
- GroupTag<G1>.bitmask | GroupTag<G2>.bitmask | GroupTag<G3>.bitmask | GroupTag<G4>.bitmask
- | bitmask);
-
- _Groups = new FasterList<ExclusiveGroupStruct>(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<G1, G2, G3, G4>).FullName);
-
- //ToArrayFast is theoretically not correct, but since multiple 0s are ignored and we don't care if we
- //add one, we avoid an allocation
- _GroupsHashSet = new HashSet<ExclusiveGroupStruct>(_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<G1, G2, G4, G3>._Groups = _Groups;
- GroupCompound<G1, G3, G2, G4>._Groups = _Groups;
- GroupCompound<G1, G3, G4, G2>._Groups = _Groups;
- GroupCompound<G1, G4, G2, G3>._Groups = _Groups;
- GroupCompound<G2, G1, G3, G4>._Groups = _Groups;
- GroupCompound<G2, G3, G4, G1>._Groups = _Groups;
- GroupCompound<G3, G1, G2, G4>._Groups = _Groups;
- GroupCompound<G4, G1, G2, G3>._Groups = _Groups;
- GroupCompound<G1, G4, G3, G2>._Groups = _Groups;
- GroupCompound<G2, G1, G4, G3>._Groups = _Groups;
- GroupCompound<G2, G4, G3, G1>._Groups = _Groups;
- GroupCompound<G3, G1, G4, G2>._Groups = _Groups;
- GroupCompound<G4, G1, G3, G2>._Groups = _Groups;
- GroupCompound<G2, G3, G1, G4>._Groups = _Groups;
- GroupCompound<G3, G4, G1, G2>._Groups = _Groups;
- GroupCompound<G2, G4, G1, G3>._Groups = _Groups;
- GroupCompound<G3, G2, G1, G4>._Groups = _Groups;
- GroupCompound<G3, G2, G4, G1>._Groups = _Groups;
- GroupCompound<G3, G4, G2, G1>._Groups = _Groups;
- GroupCompound<G4, G2, G1, G3>._Groups = _Groups;
- GroupCompound<G4, G2, G3, G1>._Groups = _Groups;
- GroupCompound<G4, G3, G1, G2>._Groups = _Groups;
- GroupCompound<G4, G3, G2, G1>._Groups = _Groups;
-
- //all the permutations are warmed up now
- GroupCompoundInitializer.skipStaticCompoundConstructorsWith4Tags.Value = false;
-
- GroupCompound<G1, G2, G4, G3>._GroupsHashSet = _GroupsHashSet;
- GroupCompound<G1, G3, G2, G4>._GroupsHashSet = _GroupsHashSet;
- GroupCompound<G1, G3, G4, G2>._GroupsHashSet = _GroupsHashSet;
- GroupCompound<G1, G4, G2, G3>._GroupsHashSet = _GroupsHashSet;
- GroupCompound<G2, G1, G3, G4>._GroupsHashSet = _GroupsHashSet;
- GroupCompound<G2, G3, G4, G1>._GroupsHashSet = _GroupsHashSet;
- GroupCompound<G3, G1, G2, G4>._GroupsHashSet = _GroupsHashSet;
- GroupCompound<G4, G1, G2, G3>._GroupsHashSet = _GroupsHashSet;
- GroupCompound<G1, G4, G3, G2>._GroupsHashSet = _GroupsHashSet;
- GroupCompound<G2, G1, G4, G3>._GroupsHashSet = _GroupsHashSet;
- GroupCompound<G2, G4, G3, G1>._GroupsHashSet = _GroupsHashSet;
- GroupCompound<G3, G1, G4, G2>._GroupsHashSet = _GroupsHashSet;
- GroupCompound<G4, G1, G3, G2>._GroupsHashSet = _GroupsHashSet;
- GroupCompound<G2, G3, G1, G4>._GroupsHashSet = _GroupsHashSet;
- GroupCompound<G3, G4, G1, G2>._GroupsHashSet = _GroupsHashSet;
- GroupCompound<G2, G4, G1, G3>._GroupsHashSet = _GroupsHashSet;
- GroupCompound<G3, G2, G1, G4>._GroupsHashSet = _GroupsHashSet;
- GroupCompound<G3, G2, G4, G1>._GroupsHashSet = _GroupsHashSet;
- GroupCompound<G3, G4, G2, G1>._GroupsHashSet = _GroupsHashSet;
- GroupCompound<G4, G2, G1, G3>._GroupsHashSet = _GroupsHashSet;
- GroupCompound<G4, G2, G3, G1>._GroupsHashSet = _GroupsHashSet;
- GroupCompound<G4, G3, G1, G2>._GroupsHashSet = _GroupsHashSet;
- GroupCompound<G4, G3, G2, G1>._GroupsHashSet = _GroupsHashSet;
-
- GroupCompound<G1, G2, G3>.Add(group);
- GroupCompound<G1, G2, G4>.Add(group);
- GroupCompound<G1, G3, G4>.Add(group);
- GroupCompound<G2, G3, G4>.Add(group);
-
- GroupCompound<G1, G2>.Add(group); //<G1/G2> and <G2/G1> must share the same array
- GroupCompound<G1, G3>.Add(group);
- GroupCompound<G1, G4>.Add(group);
- GroupCompound<G2, G3>.Add(group);
- GroupCompound<G2, G4>.Add(group);
- GroupCompound<G3, G4>.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<G1>.Add(group);
- GroupTag<G2>.Add(group);
- GroupTag<G3>.Add(group);
- GroupTag<G4>.Add(group);
- }
- }
-
- public static FasterReadOnlyList<ExclusiveGroupStruct> Groups =>
- new FasterReadOnlyList<ExclusiveGroupStruct>(_Groups);
-
- public static ExclusiveBuildGroup BuildGroup => new ExclusiveBuildGroup(_Groups[0]);
-
- public static bool Includes(ExclusiveGroupStruct group)
- {
- DBC.ECS.Check.Require(group != ExclusiveGroupStruct.Invalid, "invalid group passed");
-
- 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 System.Exception("this test must be transformed in unit test");
- #endif
-
- _Groups.Add(group);
- _GroupsHashSet.Add(group);
- }
-
- static readonly FasterList<ExclusiveGroupStruct> _Groups;
- static readonly HashSet<ExclusiveGroupStruct> _GroupsHashSet;
-
- //we are changing this with Interlocked, so it cannot be readonly
- static int isInitializing;
- protected internal static ExclusiveGroupBitmask bitmask;
- }
-
- public abstract class GroupCompound<G1, G2, G3>: ITouchedByReflection
- where G1 : GroupTag<G1>
- where G2 : GroupTag<G2>
- where G3 : GroupTag<G3>
- {
- static GroupCompound()
- {
- if (Interlocked.CompareExchange(ref isInitializing, 1, 0) == 0 &&
- GroupCompoundInitializer.skipStaticCompoundConstructorsWith3Tags.Value == false)
- {
- System.Runtime.CompilerServices.RuntimeHelpers.RunClassConstructor(
- typeof(GroupCompound<G1, G2, G3>).TypeHandle);
-
- var group = new ExclusiveGroup(
- GroupTag<G1>.bitmask | GroupTag<G2>.bitmask | GroupTag<G3>.bitmask | bitmask);
-
- _Groups = new FasterList<ExclusiveGroupStruct>(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<G1, G2, G3>).FullName);
-
- _GroupsHashSet = new HashSet<ExclusiveGroupStruct>(_Groups.ToArrayFast(out _));
-
- GroupCompoundInitializer.skipStaticCompoundConstructorsWith3Tags.Value = true;
-
- //all the combinations must share the same group and group hashset
- GroupCompound<G3, G1, G2>._Groups = _Groups;
- GroupCompound<G2, G3, G1>._Groups = _Groups;
- GroupCompound<G3, G2, G1>._Groups = _Groups;
- GroupCompound<G1, G3, G2>._Groups = _Groups;
- GroupCompound<G2, G1, G3>._Groups = _Groups;
-
- //all the constructor have been called now
- GroupCompoundInitializer.skipStaticCompoundConstructorsWith3Tags.Value = false;
-
- GroupCompound<G3, G1, G2>._GroupsHashSet = _GroupsHashSet;
- GroupCompound<G2, G3, G1>._GroupsHashSet = _GroupsHashSet;
- GroupCompound<G3, G2, G1>._GroupsHashSet = _GroupsHashSet;
- GroupCompound<G1, G3, G2>._GroupsHashSet = _GroupsHashSet;
- GroupCompound<G2, G1, G3>._GroupsHashSet = _GroupsHashSet;
-
- GroupCompound<G1, G2>.Add(group); //<G1/G2> and <G2/G1> must share the same array
- GroupCompound<G1, G3>.Add(group);
- GroupCompound<G2, G3>.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<G1>.Add(group);
- GroupTag<G2>.Add(group);
- GroupTag<G3>.Add(group);
- }
- }
-
- public static FasterReadOnlyList<ExclusiveGroupStruct> Groups =>
- new FasterReadOnlyList<ExclusiveGroupStruct>(_Groups);
-
- public static ExclusiveBuildGroup BuildGroup => new ExclusiveBuildGroup(_Groups[0]);
-
- public static bool Includes(ExclusiveGroupStruct group)
- {
- DBC.ECS.Check.Require(group != ExclusiveGroupStruct.Invalid, "invalid group passed");
-
- 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 System.Exception("this test must be transformed in unit test");
- #endif
-
- _Groups.Add(group);
- _GroupsHashSet.Add(group);
- }
-
- static readonly FasterList<ExclusiveGroupStruct> _Groups;
- static readonly HashSet<ExclusiveGroupStruct> _GroupsHashSet;
-
- //we are changing this with Interlocked, so it cannot be readonly
- static int isInitializing;
- protected internal static ExclusiveGroupBitmask bitmask;
- }
-
- public abstract class GroupCompound<G1, G2>: ITouchedByReflection
- where G1 : GroupTag<G1>
- where G2 : GroupTag<G2>
- {
- static GroupCompound()
- {
- if (Interlocked.CompareExchange(ref isInitializing, 1, 0) == 0 &&
- GroupCompoundInitializer.skipStaticCompoundConstructorsWith2Tags.Value == false)
- {
- System.Runtime.CompilerServices.RuntimeHelpers.RunClassConstructor(
- typeof(GroupCompound<G1, G2>).TypeHandle);
-
- var group = new ExclusiveGroup(GroupTag<G1>.bitmask | GroupTag<G2>.bitmask | bitmask);
-
- _Groups = new FasterList<ExclusiveGroupStruct>(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<G1, G2>).FullName);
-
- _GroupsHashSet = new HashSet<ExclusiveGroupStruct>(_Groups.ToArrayFast(out _));
-
- GroupCompoundInitializer.skipStaticCompoundConstructorsWith2Tags.Value = true;
- GroupCompound<G2, G1>._Groups = _Groups;
- GroupCompoundInitializer.skipStaticCompoundConstructorsWith2Tags.Value = false;
-
- GroupCompound<G2, G1>._GroupsHashSet = _GroupsHashSet;
-
- //every abstract group preemptively adds this group, it may or may not be empty in future
- GroupTag<G1>.Add(group);
- GroupTag<G2>.Add(group);
- }
- }
-
- public static FasterReadOnlyList<ExclusiveGroupStruct> Groups =>
- new FasterReadOnlyList<ExclusiveGroupStruct>(_Groups);
-
- public static ExclusiveBuildGroup BuildGroup => new ExclusiveBuildGroup(_Groups[0]);
-
- //TODO there is an overlap between this method and ExclusiveGroupExtensions FoundIn
- public static bool Includes(ExclusiveGroupStruct group)
- {
- DBC.ECS.Check.Require(group != ExclusiveGroupStruct.Invalid, "invalid group passed");
-
- 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 System.Exception("this test must be transformed in unit test");
- #endif
-
- _Groups.Add(group);
- _GroupsHashSet.Add(group);
- }
-
- static readonly FasterList<ExclusiveGroupStruct> _Groups;
- static readonly HashSet<ExclusiveGroupStruct> _GroupsHashSet;
-
- static int isInitializing;
- protected internal static ExclusiveGroupBitmask bitmask;
- }
-
- /// <summary>
- /// 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.
- /// </summary>
- /// <typeparam name="T"></typeparam>
- public abstract class GroupTag<T>: ITouchedByReflection
- where T : GroupTag<T>
- {
- static GroupTag()
- {
- if (Interlocked.CompareExchange(ref isInitializing, 1, 0) == 0)
- {
- //Allow to call GroupTag static constructors like
- // public class Dead: GroupTag<Dead>
- // {
- // static Dead()
- // {
- // bitmask = ExclusiveGroupBitmask.DISABLED_BIT;
- // }
- // };
- System.Runtime.CompilerServices.RuntimeHelpers.RunClassConstructor(typeof(T).TypeHandle);
-
- var group = new ExclusiveGroup(bitmask);
- _Groups.Add(group);
-
- #if DEBUG
- var typeInfo = typeof(T);
- var name = $"Compound: {typeInfo.Name} ID {(uint)group.id}";
- #if !PROFILE_SVELTO
- var typeInfoBaseType = typeInfo.BaseType;
- 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
- throw new ECSException("Invalid Group Tag declared");
- #endif
-
- 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<T>).FullName);
-
- _GroupsHashSet = new HashSet<ExclusiveGroupStruct>(_Groups.ToArrayFast(out _));
- }
- }
-
- public static FasterReadOnlyList<ExclusiveGroupStruct> Groups =>
- new FasterReadOnlyList<ExclusiveGroupStruct>(_Groups);
-
- public static ExclusiveBuildGroup BuildGroup => new ExclusiveBuildGroup(_Groups[0]);
-
- public static bool Includes(ExclusiveGroupStruct group)
- {
- DBC.ECS.Check.Require(group != ExclusiveGroupStruct.Invalid, "invalid group passed");
-
- 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 System.Exception("this test must be transformed in unit test");
- #endif
-
- _Groups.Add(group);
- _GroupsHashSet.Add(group);
- }
-
- static readonly FasterList<ExclusiveGroupStruct> _Groups = new FasterList<ExclusiveGroupStruct>(1);
- static readonly HashSet<ExclusiveGroupStruct> _GroupsHashSet;
-
- //we are changing this with Interlocked, so it cannot be readonly
- static int isInitializing;
- protected internal static ExclusiveGroupBitmask bitmask;
- }
- }
|