#if !DEBUG || PROFILE_SVELTO #define DISABLE_CHECKS using System.Diagnostics; #endif using System; using System.Reflection; using System.Runtime.CompilerServices; using Svelto.ECS.Hybrid; namespace Svelto.ECS { public static class ComponentBuilderUtilities { const string MSG = "Entity Components and Entity View Components fields cannot hold managed fields outside the Svelto rules."; #if DISABLE_CHECKS [Conditional("_CHECKS_DISABLED")] #endif public static void CheckFields(Type entityComponentType, bool needsReflection, bool isStringAllowed = false) { if (entityComponentType == ENTITY_INFO_COMPONENT || entityComponentType == EGIDType || entityComponentType == EXCLUSIVEGROUPSTRUCTTYPE || entityComponentType == SERIALIZABLE_ENTITY_STRUCT) { return; } if (needsReflection == false) { if (entityComponentType.IsClass) { throw new ECSException("EntityComponents must be structs.", entityComponentType); } FieldInfo[] fields = entityComponentType.GetFields(BindingFlags.Public | BindingFlags.Instance); for (var i = fields.Length - 1; i >= 0; --i) { FieldInfo fieldInfo = fields[i]; Type fieldType = fieldInfo.FieldType; SubCheckFields(fieldType, entityComponentType, isStringAllowed); } } else { FieldInfo[] fields = entityComponentType.GetFields(BindingFlags.Public | BindingFlags.Instance); if (fields.Length < 1) { ProcessError("Entity View Components must have at least one interface declared as public field and not property", entityComponentType); } for (int i = fields.Length - 1; i >= 0; --i) { FieldInfo fieldInfo = fields[i]; if (fieldInfo.FieldType.IsInterfaceEx() == false) { ProcessError("Entity View Components must hold only entity components interfaces." , entityComponentType); } PropertyInfo[] properties = fieldInfo.FieldType.GetProperties( BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly); for (int j = properties.Length - 1; j >= 0; --j) { Type propertyType = properties[j].PropertyType; if (propertyType.IsGenericType) { Type genericTypeDefinition = propertyType.GetGenericTypeDefinition(); if (genericTypeDefinition == RECATIVEVALUETYPE) { continue; } } // Special rules for ValueReference if (IsValueReference(propertyType)) { // Getters of ValueReference must be refs, which would cause a failure on the common check. if (properties[j].CanRead == true && propertyType.IsByRef == false) { ProcessError($"{MSG} Getters of ValueReference must be byref", entityComponentType, propertyType); } continue; } if (propertyType != STRINGTYPE) { //for EntityComponentStructs, component fields that are structs that hold strings //are allowed SubCheckFields(propertyType, entityComponentType, isStringAllowed: true); } } } } } static bool IsString(Type type) { return type == STRINGTYPE || type == STRINGBUILDERTYPE; } static bool IsValueReference(Type type) { var interfaces = type.GetInterfaces(); return interfaces.Length == 1 && interfaces[0] == VALUE_REF_TYPE; } /// /// This method checks the fields if it's an IEntityComponent, but checks all the properties if it's /// IEntityViewComponent /// /// /// /// static void SubCheckFields(Type fieldType, Type entityComponentType, bool isStringAllowed = false) { //pass if it's Primitive or C# 8 unmanaged, or it's a string and string are allowed //this check must allow pointers as they are unmanaged types if ((isStringAllowed == true && IsString(fieldType) == true) || fieldType.IsValueTypeEx() == true) { //if it's a struct we have to check the fields recursively if (IsString(fieldType) == false) { CheckFields(fieldType, false, isStringAllowed); } return; } ProcessError(MSG, entityComponentType, fieldType); } static void ProcessError(string message, Type entityComponentType, Type fieldType = null) { if (fieldType != null) { throw new ECSException(message, entityComponentType, fieldType); } throw new ECSException(message, entityComponentType); } static readonly Type RECATIVEVALUETYPE = typeof(ReactiveValue<>); static readonly Type VALUE_REF_TYPE = typeof(IValueReferenceInternal); static readonly Type EGIDType = typeof(EGID); static readonly Type EXCLUSIVEGROUPSTRUCTTYPE = typeof(ExclusiveGroupStruct); static readonly Type SERIALIZABLE_ENTITY_STRUCT = typeof(SerializableEntityComponent); static readonly Type STRINGTYPE = typeof(string); static readonly Type STRINGBUILDERTYPE = typeof(System.Text.StringBuilder); internal static readonly Type ENTITY_INFO_COMPONENT = typeof(EntityInfoComponent); public static ComponentID ENTITY_INFO_COMPONENT_ID { [MethodImpl(MethodImplOptions.AggressiveInlining)] get { return ComponentTypeID.id; } } } }