using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text;
using Svelto.ECS.Serialization;
namespace Svelto.ECS
{
public static class GroupHashMap
{
///
/// 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.
///
/// Warmup the group hash map. This will call all the static constructors of the group types
///
internal static void WarmUp()
{
List assemblies = AssemblyUtility.GetCompatibleAssemblies();
foreach (Assembly assembly in assemblies)
{
var typeOfExclusiveGroup = typeof(ExclusiveGroup);
var typeOfExclusiveGroupStruct = typeof(ExclusiveGroupStruct);
var typeOfExclusiveBuildGroup = typeof(ExclusiveBuildGroup);
var typesSafe = AssemblyUtility.GetTypesSafe(assembly);
foreach (Type type in typesSafe)
{
CheckForGroupCompounds(type);
//Search inside static types
if (type != null && type.IsClass && type.IsSealed
&& type.IsAbstract) //IsClass and IsSealed and IsAbstract means only static classes
{
var subClasses = type.GetNestedTypes();
foreach (var subclass in subClasses)
{
CheckForGroupCompounds(subclass);
}
var fields = type.GetFields(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
foreach (var field in fields)
{
if ((typeOfExclusiveGroup.IsAssignableFrom(field.FieldType)
|| typeOfExclusiveGroupStruct.IsAssignableFrom(field.FieldType)
|| typeOfExclusiveBuildGroup.IsAssignableFrom(field.FieldType)))
{
uint groupIDAndBitMask;
if (typeOfExclusiveGroup.IsAssignableFrom(field.FieldType))
{
var group = (ExclusiveGroup)field.GetValue(null);
groupIDAndBitMask = ((ExclusiveGroupStruct)@group).ToIDAndBitmask();
}
else if (typeOfExclusiveGroupStruct.IsAssignableFrom(field.FieldType))
{
var group = (ExclusiveGroupStruct)field.GetValue(null);
groupIDAndBitMask = @group.ToIDAndBitmask();
}
else
{
var group = (ExclusiveBuildGroup)field.GetValue(null);
groupIDAndBitMask = ((ExclusiveGroupStruct)@group).ToIDAndBitmask();
}
{
var bitMask = (byte)(groupIDAndBitMask >> 24);
var groupID = groupIDAndBitMask & 0xFFFFFF;
ExclusiveGroupStruct group = new ExclusiveGroupStruct(groupID, bitMask);
#if DEBUG && !PROFILE_SVELTO
if (GroupNamesMap.idToName.ContainsKey(@group) == false)
GroupNamesMap.idToName[@group] =
$"{type.FullName}.{field.Name} {@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
RegisterGroup(@group, $"{type.FullName}.{field.Name}");
}
}
}
}
}
}
}
static void CheckForGroupCompounds(Type type)
{
if (typeof(ITouchedByReflection).IsAssignableFrom(type))
{
//this calls the static constructor, but only once. Static constructors won't be called
//more than once with this
CallStaticConstructorsRecursively(type);
}
static void CallStaticConstructorsRecursively(Type type)
{
// Check if the current type has a static constructor
// type.TypeInitializer.Invoke(null, null); //calling Invoke will force the static constructor to be called even if already called, this is a problem because GroupTag and Compound throw an exception if called multiple times
RuntimeHelpers.RunClassConstructor(type.TypeHandle); //this will call the static constructor only once
#if DEBUG && !PROFILE_SVELTO
if (type.GetInterfaces().Contains(type) == false)
{
if (type.IsSealed == false)
Svelto.Console.LogWarning(
$"Group compound/tag {type} is not sealed. GroupCompounds and Tags cannot be inherited, consider marking it sealed");
}
#endif
// Recursively check the base types
Type baseType = type.BaseType;
if (baseType != null && baseType != typeof(object)) //second if means we got the the end of the hierarchy
{
CallStaticConstructorsRecursively(baseType);
}
}
}
///
/// 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
///
///
///
///
internal static void RegisterGroup(ExclusiveGroupStruct exclusiveGroupStruct, string name)
{
//Group already registered by another field referencing the same group, can happen because
//the group poked is a group compound which static constructor is already been called at this point
if (_hashByGroups.ContainsKey(exclusiveGroupStruct))
return;
var nameHash = DesignatedHash.Hash(Encoding.ASCII.GetBytes(name));
if (_groupsByHash.ContainsKey(nameHash))
throw new ECSException($"Group hash collision with {name} and {_groupsByHash[nameHash]}");
Console.LogDebug($"Registering group {name} with ID {exclusiveGroupStruct.id} to {nameHash}");
_groupsByHash.Add(nameHash, exclusiveGroupStruct);
_hashByGroups.Add(exclusiveGroupStruct, nameHash);
}
public static uint GetHashFromGroup(ExclusiveGroupStruct groupStruct)
{
#if DEBUG && !PROFILE_SVELTO
if (_hashByGroups.ContainsKey(groupStruct) == false)
throw new ECSException($"Attempted to get hash from unregistered group {groupStruct}");
#endif
return _hashByGroups[groupStruct];
}
public static ExclusiveGroupStruct GetGroupFromHash(uint groupHash)
{
#if DEBUG && !PROFILE_SVELTO
if (_groupsByHash.ContainsKey(groupHash) == false)
throw new ECSException($"Attempted to get group from unregistered hash {groupHash}");
#endif
return _groupsByHash[groupHash];
}
static readonly Dictionary _groupsByHash;
static readonly Dictionary _hashByGroups;
static GroupHashMap()
{
_groupsByHash = new Dictionary();
_hashByGroups = new Dictionary();
}
}
}