Mirror of Svelto.ECS because we're a fan of it
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

165 lines
6.7KB

  1. #if !DEBUG || PROFILE_SVELTO
  2. #define DISABLE_CHECKS
  3. using System.Diagnostics;
  4. #endif
  5. using System;
  6. using System.Reflection;
  7. using System.Runtime.CompilerServices;
  8. using Svelto.ECS.Hybrid;
  9. using Svelto.ECS.Serialization;
  10. namespace Svelto.ECS
  11. {
  12. public static class ComponentBuilderUtilities
  13. {
  14. const string MSG = "Entity Components and Entity View Components fields cannot hold managed fields outside the Svelto rules.";
  15. #if DISABLE_CHECKS
  16. [Conditional("_CHECKS_DISABLED")]
  17. #endif
  18. public static void CheckFields(Type entityComponentType, bool needsReflection, bool isStringAllowed = false)
  19. {
  20. if (entityComponentType == ENTITY_INFO_COMPONENT || entityComponentType == EGIDType ||
  21. entityComponentType == EXCLUSIVEGROUPSTRUCTTYPE || entityComponentType == SERIALIZABLE_ENTITY_STRUCT)
  22. {
  23. return;
  24. }
  25. if (needsReflection == false)
  26. {
  27. if (entityComponentType.IsClass)
  28. {
  29. throw new ECSException("EntityComponents must be structs.", entityComponentType);
  30. }
  31. FieldInfo[] fields = entityComponentType.GetFields(BindingFlags.Public | BindingFlags.Instance);
  32. for (var i = fields.Length - 1; i >= 0; --i)
  33. {
  34. FieldInfo fieldInfo = fields[i];
  35. Type fieldType = fieldInfo.FieldType;
  36. SubCheckFields(fieldType, entityComponentType, isStringAllowed);
  37. }
  38. }
  39. else
  40. {
  41. FieldInfo[] fields = entityComponentType.GetFields(BindingFlags.Public | BindingFlags.Instance);
  42. if (fields.Length < 1)
  43. {
  44. ProcessError("Entity View Components must have at least one interface declared as public field and not property", entityComponentType);
  45. }
  46. for (int i = fields.Length - 1; i >= 0; --i)
  47. {
  48. FieldInfo fieldInfo = fields[i];
  49. if (fieldInfo.FieldType.IsInterfaceEx() == false)
  50. {
  51. ProcessError("Entity View Components must hold only entity components interfaces."
  52. , entityComponentType);
  53. }
  54. PropertyInfo[] properties = fieldInfo.FieldType.GetProperties(
  55. BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly);
  56. for (int j = properties.Length - 1; j >= 0; --j)
  57. {
  58. Type propertyType = properties[j].PropertyType;
  59. if (propertyType.IsGenericType)
  60. {
  61. Type genericTypeDefinition = propertyType.GetGenericTypeDefinition();
  62. if (genericTypeDefinition == RECATIVEVALUETYPE)
  63. {
  64. continue;
  65. }
  66. }
  67. // Special rules for ValueReference<T>
  68. if (IsValueReference(propertyType))
  69. {
  70. // Getters of ValueReference must be refs, which would cause a failure on the common check.
  71. if (properties[j].CanRead == true && propertyType.IsByRef == false)
  72. {
  73. ProcessError($"{MSG} Getters of ValueReference must be byref", entityComponentType, propertyType);
  74. }
  75. continue;
  76. }
  77. if (propertyType != STRINGTYPE)
  78. {
  79. //for EntityComponentStructs, component fields that are structs that hold strings
  80. //are allowed
  81. SubCheckFields(propertyType, entityComponentType, isStringAllowed: true);
  82. }
  83. }
  84. }
  85. }
  86. }
  87. static bool IsString(Type type)
  88. {
  89. return type == STRINGTYPE || type == STRINGBUILDERTYPE;
  90. }
  91. static bool IsValueReference(Type type)
  92. {
  93. var interfaces = type.GetInterfaces();
  94. return interfaces.Length == 1 && interfaces[0] == VALUE_REF_TYPE;
  95. }
  96. /// <summary>
  97. /// This method checks the fields if it's an IEntityComponent, but checks all the properties if it's
  98. /// IEntityViewComponent
  99. /// </summary>
  100. /// <param name="fieldType"></param>
  101. /// <param name="entityComponentType"></param>
  102. /// <param name="isStringAllowed"></param>
  103. static void SubCheckFields(Type fieldType, Type entityComponentType, bool isStringAllowed = false)
  104. {
  105. //pass if it's Primitive or C# 8 unmanaged, or it's a string and string are allowed
  106. //this check must allow pointers as they are unmanaged types
  107. if ((isStringAllowed == true && IsString(fieldType) == true) ||
  108. fieldType.IsValueTypeEx() == true)
  109. {
  110. //if it's a struct we have to check the fields recursively
  111. if (IsString(fieldType) == false)
  112. {
  113. CheckFields(fieldType, false, isStringAllowed);
  114. }
  115. return;
  116. }
  117. ProcessError(MSG, entityComponentType, fieldType);
  118. }
  119. static void ProcessError(string message, Type entityComponentType, Type fieldType = null)
  120. {
  121. if (fieldType != null)
  122. {
  123. throw new ECSException(message, entityComponentType, fieldType);
  124. }
  125. throw new ECSException(message, entityComponentType);
  126. }
  127. static readonly Type RECATIVEVALUETYPE = typeof(ReactiveValue<>);
  128. static readonly Type VALUE_REF_TYPE = typeof(IValueReferenceInternal);
  129. static readonly Type EGIDType = typeof(EGID);
  130. static readonly Type EXCLUSIVEGROUPSTRUCTTYPE = typeof(ExclusiveGroupStruct);
  131. static readonly Type SERIALIZABLE_ENTITY_STRUCT = typeof(SerializableEntityComponent);
  132. static readonly Type STRINGTYPE = typeof(string);
  133. static readonly Type STRINGBUILDERTYPE = typeof(System.Text.StringBuilder);
  134. internal static readonly Type ENTITY_INFO_COMPONENT = typeof(EntityInfoComponent);
  135. public static ComponentID ENTITY_INFO_COMPONENT_ID
  136. {
  137. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  138. get => ComponentTypeID<EntityInfoComponent>.id;
  139. }
  140. }
  141. }