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();
}
interface ITouchedByReflection { }
public abstract class GroupCompound: ITouchedByReflection
where G1 : GroupTag
where G2 : GroupTag
where G3 : GroupTag
where G4 : GroupTag
{
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).TypeHandle);
var group = new ExclusiveGroup(
GroupTag.bitmask | GroupTag.bitmask | GroupTag.bitmask | GroupTag.bitmask
| bitmask);
_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);
//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(_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)
{
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 _Groups;
static readonly HashSet _GroupsHashSet;
//we are changing this with Interlocked, so it cannot be readonly
static int isInitializing;
protected internal static ExclusiveGroupBitmask bitmask;
}
public abstract class GroupCompound: ITouchedByReflection
where G1 : GroupTag
where G2 : GroupTag
where G3 : GroupTag
{
static GroupCompound()
{
if (Interlocked.CompareExchange(ref isInitializing, 1, 0) == 0 &&
GroupCompoundInitializer.skipStaticCompoundConstructorsWith3Tags.Value == false)
{
System.Runtime.CompilerServices.RuntimeHelpers.RunClassConstructor(
typeof(GroupCompound).TypeHandle);
var group = new ExclusiveGroup(
GroupTag.bitmask | GroupTag.bitmask | GroupTag.bitmask | bitmask);
_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)
{
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 _Groups;
static readonly HashSet _GroupsHashSet;
//we are changing this with Interlocked, so it cannot be readonly
static int isInitializing;
protected internal static ExclusiveGroupBitmask bitmask;
}
public abstract class GroupCompound: ITouchedByReflection
where G1 : GroupTag
where G2 : GroupTag
{
static GroupCompound()
{
if (Interlocked.CompareExchange(ref isInitializing, 1, 0) == 0 &&
GroupCompoundInitializer.skipStaticCompoundConstructorsWith2Tags.Value == false)
{
System.Runtime.CompilerServices.RuntimeHelpers.RunClassConstructor(
typeof(GroupCompound).TypeHandle);
var group = new ExclusiveGroup(GroupTag.bitmask | GroupTag.bitmask | bitmask);
_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]);
//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 _Groups;
static readonly HashSet _GroupsHashSet;
static int isInitializing;
protected internal static ExclusiveGroupBitmask bitmask;
}
///
/// 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: ITouchedByReflection
where T : GroupTag
{
static GroupTag()
{
if (Interlocked.CompareExchange(ref isInitializing, 1, 0) == 0)
{
//Allow to call GroupTag static constructors like
// public class Dead: GroupTag
// {
// 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 {} 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).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)
{
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 _Groups = new FasterList(1);
static readonly HashSet _GroupsHashSet;
//we are changing this with Interlocked, so it cannot be readonly
static int isInitializing;
protected internal static ExclusiveGroupBitmask bitmask;
}
}