internal ComponentType[] GetQueryTypes() { var types = new HashSet <ComponentType>(); for (var i = 0; i < m_GroupData->ArchetypeQueryCount; ++i) { for (var j = 0; j < m_GroupData->ArchetypeQuery[i].AnyCount; ++j) { types.Add(TypeManager.GetType(m_GroupData->ArchetypeQuery[i].Any[j])); } for (var j = 0; j < m_GroupData->ArchetypeQuery[i].AllCount; ++j) { types.Add(TypeManager.GetType(m_GroupData->ArchetypeQuery[i].All[j])); } for (var j = 0; j < m_GroupData->ArchetypeQuery[i].NoneCount; ++j) { types.Add(ComponentType.Subtractive(TypeManager.GetType(m_GroupData->ArchetypeQuery[i].None[j]))); } } var array = new ComponentType[types.Count]; var t = 0; foreach (var type in types) { array[t++] = type; } return(array); }
static ComponentType[] GetComponentTypes(Type jobType, Type interfaceType, out int processCount, out ComponentType[] changedFilter) { var genericArgs = interfaceType.GetGenericArguments(); var executeMethodParameters = jobType.GetMethod("Execute").GetParameters(); var componentTypes = new List <ComponentType>(); var changedFilterTypes = new List <ComponentType>(); // void Execute(Entity entity, int index, ref T0 data0, ref T1 data1, ref T2 data2); // First two parameters are optional, depending on the interface name used. var methodParameterOffset = genericArgs.Length != executeMethodParameters.Length ? 2 : 0; for (var i = 0; i < genericArgs.Length; i++) { var isReadonly = executeMethodParameters[i + methodParameterOffset].GetCustomAttribute(typeof(ReadOnlyAttribute)) != null; var type = new ComponentType(genericArgs[i], isReadonly ? ComponentType.AccessMode.ReadOnly : ComponentType.AccessMode.ReadWrite); componentTypes.Add(type); var isChangedFilter = executeMethodParameters[i + methodParameterOffset].GetCustomAttribute(typeof(ChangedFilterAttribute)) != null; if (isChangedFilter) { changedFilterTypes.Add(type); } } var subtractive = jobType.GetCustomAttribute <RequireSubtractiveComponentAttribute>(); if (subtractive != null) { foreach (var type in subtractive.SubtractiveComponents) { componentTypes.Add(ComponentType.Subtractive(type)); } } var requiredTags = jobType.GetCustomAttribute <RequireComponentTagAttribute>(); if (requiredTags != null) { foreach (var type in requiredTags.TagComponents) { componentTypes.Add(ComponentType.ReadOnly(type)); } } processCount = genericArgs.Length; changedFilter = changedFilterTypes.ToArray(); return(componentTypes.ToArray()); }
private static ComponentType[] GetComponentTypes(Type jobType, Type interfaceType, out int processCount, out ComponentType[] changedFilter) { var genericArgs = interfaceType.GetGenericArguments(); var executeMethodParameters = jobType.GetMethod("Execute").GetParameters(); var componentTypes = new List <ComponentType>(); var changedFilterTypes = new List <ComponentType>(); for (var i = 0; i < genericArgs.Length; i++) { var isReadonly = executeMethodParameters[i].GetCustomAttribute(typeof(ReadOnlyAttribute)) != null; var type = new ComponentType(genericArgs[i], isReadonly ? ComponentType.AccessMode.ReadOnly : ComponentType.AccessMode.ReadWrite); componentTypes.Add(type); var isChangedFilter = executeMethodParameters[i].GetCustomAttribute(typeof(ChangedFilterAttribute)) != null; if (isChangedFilter) { changedFilterTypes.Add(type); } } var subtractive = jobType.GetCustomAttribute <RequireSubtractiveComponentAttribute>(); if (subtractive != null) { foreach (var type in subtractive.SubtractiveComponents) { componentTypes.Add(ComponentType.Subtractive(type)); } } var requiredTags = jobType.GetCustomAttribute <RequireComponentTagAttribute>(); if (requiredTags != null) { foreach (var type in requiredTags.TagComponents) { componentTypes.Add(ComponentType.ReadOnly(type)); } } processCount = genericArgs.Length; changedFilter = changedFilterTypes.ToArray(); return(componentTypes.ToArray()); }
private static unsafe ComponentType[] GetComponentTypes(Type jobType, Type interfaceType, out int processCount, out ComponentType[] changedFilter) { Type[] genericArguments = interfaceType.GetGenericArguments(); ParameterInfo[] parameters = jobType.GetMethod("Execute").GetParameters(); List <ComponentType> list = new List <ComponentType>(); List <ComponentType> list2 = new List <ComponentType>(); int num = (genericArguments.Length != parameters.Length) ? 2 : 0; int index = 0; while (true) { ComponentType type; if (index >= genericArguments.Length) { RequireSubtractiveComponentAttribute customAttribute = jobType.GetCustomAttribute <RequireSubtractiveComponentAttribute>(); if (customAttribute != null) { foreach (Type type2 in customAttribute.SubtractiveComponents) { list.Add(ComponentType.Subtractive(type2)); } } RequireComponentTagAttribute attribute2 = jobType.GetCustomAttribute <RequireComponentTagAttribute>(); if (attribute2 != null) { foreach (Type type3 in attribute2.TagComponents) { list.Add(ComponentType.ReadOnly(type3)); } } processCount = genericArguments.Length; changedFilter = list2.ToArray(); return(list.ToArray()); } bool flag = parameters[index + num].GetCustomAttribute(typeof(ReadOnlyAttribute)) != null; ComponentType *typePtr1 = (ComponentType *)new ComponentType(genericArguments[index], flag ? ComponentType.AccessMode.ReadOnly : ComponentType.AccessMode.ReadWrite); typePtr1 = (ComponentType *)ref type; list.Add(type); if (parameters[index + num].GetCustomAttribute(typeof(ChangedFilterAttribute)) != null) { list2.Add(type); } index++; } }
static ComponentType[] GetComponentTypes(Type jobType, Type interfaceType, out int processCount) { var genericArgs = interfaceType.GetGenericArguments(); var executeMethodParameters = jobType.GetMethod("Execute").GetParameters(); var componentTypes = new List <ComponentType>(); for (var i = 0; i < genericArgs.Length; i++) { var isReadonly = executeMethodParameters[i].GetCustomAttribute(typeof(Unity.Collections.ReadOnlyAttribute)) != null || executeMethodParameters[i].GetCustomAttribute(typeof(System.Runtime.CompilerServices.IsReadOnlyAttribute)) != null; componentTypes.Add(new ComponentType(genericArgs[i], isReadonly ? ComponentType.AccessMode.ReadOnly : ComponentType.AccessMode.ReadWrite)); } var subtractive = jobType.GetCustomAttribute <RequireSubtractiveComponentAttribute>(); if (subtractive != null) { foreach (var type in subtractive.SubtractiveComponents) { componentTypes.Add(ComponentType.Subtractive(type)); } } var requiredTags = jobType.GetCustomAttribute <RequireComponentTagAttribute>(); if (requiredTags != null) { //@TODO: Add Special component type which doesn't capture job dependencies... foreach (var type in requiredTags.TagComponents) { componentTypes.Add(ComponentType.ReadOnly(type)); } } processCount = genericArgs.Length; return(componentTypes.ToArray()); }
/// <summary> /// Gets array of all ComponentTypes in this ComponentGroup's ArchetypeQueries. /// </summary> /// <returns>Array of ComponentTypes</returns> internal ComponentType[] GetQueryTypes() { #if !UNITY_CSHARP_TINY var types = new HashSet <ComponentType>(); #else var types = new SlowListSet <ComponentType>(); #endif for (var i = 0; i < m_GroupData->ArchetypeQueryCount; ++i) { for (var j = 0; j < m_GroupData->ArchetypeQuery[i].AnyCount; ++j) { types.Add(TypeManager.GetType(m_GroupData->ArchetypeQuery[i].Any[j])); } for (var j = 0; j < m_GroupData->ArchetypeQuery[i].AllCount; ++j) { types.Add(TypeManager.GetType(m_GroupData->ArchetypeQuery[i].All[j])); } for (var j = 0; j < m_GroupData->ArchetypeQuery[i].NoneCount; ++j) { types.Add(ComponentType.Subtractive(TypeManager.GetType(m_GroupData->ArchetypeQuery[i].None[j]))); } } #if !UNITY_CSHARP_TINY var array = new ComponentType[types.Count]; var t = 0; foreach (var type in types) { array[t++] = type; } return(array); #else return(types.ToArray()); #endif }
public ComponentGroupArrayStaticCache(Type type, EntityManager entityManager, ComponentSystemBase system) { var fields = type.GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public); var componentFieldOffsetsBuilder = new List <int>(); var componentTypesBuilder = new List <ComponentType>(); var componentDataFieldOffsetsBuilder = new List <int>(); var componentDataTypesBuilder = new List <ComponentType>(); var subtractiveComponentTypesBuilder = new List <ComponentType>(); foreach (var field in fields) { var fieldType = field.FieldType; var offset = UnsafeUtility.GetFieldOffset(field); if (fieldType.IsPointer) { var isReadOnly = field.GetCustomAttributes(typeof(ReadOnlyAttribute), true).Length != 0; var accessMode = isReadOnly ? ComponentType.AccessMode.ReadOnly : ComponentType.AccessMode.ReadWrite; var elementType = fieldType.GetElementType(); #if ENABLE_UNITY_COLLECTIONS_CHECKS if (!typeof(IComponentData).IsAssignableFrom(elementType) && elementType != typeof(Entity)) { throw new ArgumentException( $"{type}.{field.Name} is a pointer type but not a IComponentData. Only IComponentData or Entity may be a pointer type for enumeration."); } #endif componentDataFieldOffsetsBuilder.Add(offset); componentDataTypesBuilder.Add(new ComponentType(elementType, accessMode)); } else if (TypeManager.UnityEngineComponentType?.IsAssignableFrom(fieldType) ?? false) { componentFieldOffsetsBuilder.Add(offset); componentTypesBuilder.Add(fieldType); } else if (fieldType.IsGenericType && fieldType.GetGenericTypeDefinition() == typeof(SubtractiveComponent <>)) { subtractiveComponentTypesBuilder.Add(ComponentType.Subtractive(fieldType.GetGenericArguments()[0])); } #if ENABLE_UNITY_COLLECTIONS_CHECKS else if (typeof(IComponentData).IsAssignableFrom(fieldType)) { throw new ArgumentException( $"{type}.{field.Name} must be an unsafe pointer to the {fieldType}. Like this: {fieldType}* {field.Name};"); } else { throw new ArgumentException($"{type}.{field.Name} can not be used in a component enumerator"); } #endif } #if ENABLE_UNITY_COLLECTIONS_CHECKS if (componentTypesBuilder.Count + componentDataTypesBuilder.Count > ComponentGroupArrayData.kMaxStream) { throw new ArgumentException( $"{type} has too many component references. A ComponentGroup Array can have up to {ComponentGroupArrayData.kMaxStream}."); } #endif ComponentDataCount = componentDataTypesBuilder.Count; ComponentCount = componentTypesBuilder.Count; componentDataTypesBuilder.AddRange(componentTypesBuilder); componentDataTypesBuilder.AddRange(subtractiveComponentTypesBuilder); ComponentTypes = componentDataTypesBuilder.ToArray(); componentDataFieldOffsetsBuilder.AddRange(componentFieldOffsetsBuilder); ComponentFieldOffsets = componentDataFieldOffsetsBuilder.ToArray(); ComponentGroup = system.GetComponentGroupInternal(ComponentTypes); SafetyManager = entityManager.ComponentJobSafetyManager; CachedType = type; }
private static string CollectInjectedGroup(ComponentSystemBase system, FieldInfo groupField, Type injectedGroupType, out FieldInfo entityArrayField, out FieldInfo indexFromEntityField, InjectionContext injectionContext, out FieldInfo lengthField, out FieldInfo componentGroupIndexField, ISet <ComponentType> componentRequirements, ICollection <InjectionData> componentDataInjections, ICollection <InjectionData> bufferDataInjections, ICollection <InjectionData> sharedComponentInjections) { //@TODO: Improve error messages... var fields = injectedGroupType.GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public); entityArrayField = null; indexFromEntityField = null; lengthField = null; componentGroupIndexField = null; foreach (var field in fields) { var isReadOnly = field.GetCustomAttributes(typeof(ReadOnlyAttribute), true).Length != 0; //@TODO: Prevent using GameObjectEntity, it will never show up. Point to GameObjectArray instead... if (field.FieldType.IsGenericType && field.FieldType.GetGenericTypeDefinition() == typeof(ComponentDataArray <>)) { var injection = new InjectionData(field, field.FieldType.GetGenericArguments()[0], isReadOnly); componentDataInjections.Add(injection); componentRequirements.Add(injection.ComponentType); } else if (field.FieldType.IsGenericType && field.FieldType.GetGenericTypeDefinition() == typeof(SubtractiveComponent <>)) { componentRequirements.Add(ComponentType.Subtractive(field.FieldType.GetGenericArguments()[0])); } else if (field.FieldType.IsGenericType && field.FieldType.GetGenericTypeDefinition() == typeof(BufferArray <>)) { var injection = new InjectionData(field, field.FieldType.GetGenericArguments()[0], isReadOnly); bufferDataInjections.Add(injection); componentRequirements.Add(injection.ComponentType); } else if (field.FieldType.IsGenericType && field.FieldType.GetGenericTypeDefinition() == typeof(SharedComponentDataArray <>)) { if (!isReadOnly) { return ($"{system.GetType().Name}:{groupField.Name} SharedComponentDataArray<> must always be injected as [ReadOnly]"); } var injection = new InjectionData(field, field.FieldType.GetGenericArguments()[0], true); sharedComponentInjections.Add(injection); componentRequirements.Add(injection.ComponentType); } else if (field.FieldType == typeof(EntityArray)) { // Error on multiple EntityArray if (entityArrayField != null) { return ($"{system.GetType().Name}:{groupField.Name} An [Inject] struct, may only contain a single EntityArray"); } entityArrayField = field; } else if (field.FieldType == typeof(int)) { if (field.Name != "Length" && field.Name != "GroupIndex") { return ($"{system.GetType().Name}:{groupField.Name} Int in an [Inject] struct should be named \"Length\" (group length) or \"GroupIndex\" (index in ComponentGroup[])"); } if (!field.IsInitOnly) { return ($"{system.GetType().Name}:{groupField.Name} {field.Name} must use the \"readonly\" keyword"); } if (field.Name == "Length") { lengthField = field; } if (field.Name == "GroupIndex") { componentGroupIndexField = field; } } else { var hook = InjectionHookSupport.HookFor(field); if (hook == null) { return ($"{system.GetType().Name}:{groupField.Name} [Inject] may only be used on ComponentDataArray<>, ComponentArray<>, TransformAccessArray, EntityArray, {string.Join(",", InjectionHookSupport.Hooks.Select(h => h.FieldTypeOfInterest.Name))} and int Length."); } var error = hook.ValidateField(field, isReadOnly, injectionContext); if (error != null) { return(error); } injectionContext.AddEntry(hook.CreateInjectionInfoFor(field, isReadOnly)); } } if (injectionContext.HasComponentRequirements) { foreach (var requirement in injectionContext.ComponentRequirements) { componentRequirements.Add(requirement); } } return(null); }
static string CollectInjectedGroup(Type injectedGroupType, out FieldInfo entityArrayField, out FieldInfo indexFromEntityField, InjectionContext injectionContext, out FieldInfo lengthField, ISet <ComponentType> componentRequirements, ICollection <InjectionData> componentDataInjections, ICollection <InjectionData> fixedArrayInjections, ICollection <InjectionData> sharedComponentInjections) { //@TODO: Improve error messages... var fields = injectedGroupType.GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public); entityArrayField = null; indexFromEntityField = null; lengthField = null; foreach (var field in fields) { var isReadOnly = field.GetCustomAttributes(typeof(ReadOnlyAttribute), true).Length != 0; //@TODO: Prevent using GameObjectEntity, it will never show up. Point to GameObjectArray instead... if (field.FieldType.IsGenericType && field.FieldType.GetGenericTypeDefinition() == typeof(ComponentDataArray <>)) { var injection = new InjectionData(field, field.FieldType.GetGenericArguments()[0], isReadOnly); componentDataInjections.Add(injection); componentRequirements.Add(injection.ComponentType); } else if (field.FieldType.IsGenericType && field.FieldType.GetGenericTypeDefinition() == typeof(SubtractiveComponent <>)) { componentRequirements.Add(ComponentType.Subtractive(field.FieldType.GetGenericArguments()[0])); } else if (field.FieldType.IsGenericType && field.FieldType.GetGenericTypeDefinition() == typeof(FixedArrayArray <>)) { var injection = new InjectionData(field, field.FieldType.GetGenericArguments()[0], isReadOnly); fixedArrayInjections.Add(injection); componentRequirements.Add(injection.ComponentType); } else if (field.FieldType.IsGenericType && field.FieldType.GetGenericTypeDefinition() == typeof(SharedComponentDataArray <>)) { if (!isReadOnly) { return("SharedComponentDataArray<> must always be injected as [ReadOnly]"); } var injection = new InjectionData(field, field.FieldType.GetGenericArguments()[0], true); sharedComponentInjections.Add(injection); componentRequirements.Add(injection.ComponentType); } else if (field.FieldType == typeof(EntityArray)) { // Error on multiple EntityArray if (entityArrayField != null) { return("A [Inject] struct, may only contain a single EntityArray"); } entityArrayField = field; } else if (field.FieldType == typeof(IndexFromEntity)) { // Error on multiple IndexFromEntity if (indexFromEntityField != null) { return("A [Inject] struct, may only contain a single IndexFromEntity"); } indexFromEntityField = field; } else if (field.FieldType == typeof(int)) { // Error on multiple EntityArray if (field.Name != "Length") { return("A [Inject] struct, supports only a specialized int storing the length of the group. (\"int Length;\")"); } lengthField = field; } else { var hook = InjectionHookSupport.HookFor(field); if (hook == null) { return ($"[Inject] may only be used on ComponentDataArray<>, ComponentArray<>, TransformAccessArray, EntityArray, {string.Join(",", InjectionHookSupport.Hooks.Select(h => h.FieldTypeOfInterest.Name))} and int Length."); } var error = hook.ValidateField(field, isReadOnly, injectionContext); if (error != null) { return(error); } injectionContext.AddEntry(hook.CreateInjectionInfoFor(field, isReadOnly)); } } if (injectionContext.HasComponentRequirements) { foreach (var requirement in injectionContext.ComponentRequirements) { componentRequirements.Add(requirement); } } return(null); }
public ComponentGroupArrayStaticCache(Type type, EntityManager entityManager, ComponentSystemBase system) { List <int> collection = new List <int>(); List <ComponentType> list2 = new List <ComponentType>(); List <int> list3 = new List <int>(); List <ComponentType> list4 = new List <ComponentType>(); List <ComponentType> list5 = new List <ComponentType>(); foreach (FieldInfo info in type.GetFields(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance)) { Type fieldType = info.FieldType; int fieldOffset = UnsafeUtility.GetFieldOffset(info); if (fieldType.IsPointer) { ComponentType.AccessMode accessModeType = (info.GetCustomAttributes(typeof(ReadOnlyAttribute), true).Length != 0) ? ComponentType.AccessMode.ReadOnly : ComponentType.AccessMode.ReadWrite; Type elementType = fieldType.GetElementType(); if (!typeof(IComponentData).IsAssignableFrom(elementType) && (elementType != typeof(Entity))) { throw new ArgumentException($"{type}.{info.Name} is a pointer type but not a IComponentData. Only IComponentData or Entity may be a pointer type for enumeration."); } list3.Add(fieldOffset); list4.Add(new ComponentType(elementType, accessModeType)); } else { bool flag1; if (TypeManager.UnityEngineComponentType != null) { flag1 = TypeManager.UnityEngineComponentType.IsAssignableFrom(fieldType); } else { object unityEngineComponentType = TypeManager.UnityEngineComponentType; flag1 = false; } if (flag1) { collection.Add(fieldOffset); list2.Add(fieldType); } else { if (!(fieldType.IsGenericType && (fieldType.GetGenericTypeDefinition() == typeof(SubtractiveComponent <>)))) { if (!typeof(IComponentData).IsAssignableFrom(fieldType)) { throw new ArgumentException($"{type}.{info.Name} can not be used in a component enumerator"); } throw new ArgumentException($"{type}.{info.Name} must be an unsafe pointer to the {fieldType}. Like this: {fieldType}* {info.Name};"); } list5.Add(ComponentType.Subtractive(fieldType.GetGenericArguments()[0])); } } } if ((list2.Count + list4.Count) > 6) { throw new ArgumentException($"{type} has too many component references. A ComponentGroup Array can have up to {6}."); } this.ComponentDataCount = list4.Count; this.ComponentCount = list2.Count; list4.AddRange(list2); list4.AddRange(list5); this.ComponentTypes = list4.ToArray(); list3.AddRange(collection); this.ComponentFieldOffsets = list3.ToArray(); this.ComponentGroup = system.GetComponentGroupInternal(this.ComponentTypes); this.SafetyManager = entityManager.ComponentJobSafetyManager; this.CachedType = type; }
private static string CollectInjectedGroup(ComponentSystemBase system, FieldInfo groupField, Type injectedGroupType, out FieldInfo entityArrayField, out FieldInfo indexFromEntityField, InjectionContext injectionContext, out FieldInfo lengthField, out FieldInfo componentGroupIndexField, ISet <ComponentType> componentRequirements, ICollection <InjectionData> componentDataInjections, ICollection <InjectionData> bufferDataInjections, ICollection <InjectionData> sharedComponentInjections) { string str; FieldInfo[] fields = injectedGroupType.GetFields(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance); entityArrayField = null; indexFromEntityField = null; lengthField = null; componentGroupIndexField = null; FieldInfo[] infoArray2 = fields; int index = 0; while (true) { if (index >= infoArray2.Length) { if (injectionContext.HasComponentRequirements) { foreach (ComponentType type in injectionContext.ComponentRequirements) { componentRequirements.Add(type); } } str = null; break; } FieldInfo field = infoArray2[index]; bool isReadOnly = field.GetCustomAttributes(typeof(ReadOnlyAttribute), true).Length != 0; if (field.FieldType.IsGenericType && (field.FieldType.GetGenericTypeDefinition() == typeof(ComponentDataArray <>))) { InjectionData item = new InjectionData(field, field.FieldType.GetGenericArguments()[0], isReadOnly); componentDataInjections.Add(item); componentRequirements.Add(item.ComponentType); } else if (field.FieldType.IsGenericType && (field.FieldType.GetGenericTypeDefinition() == typeof(SubtractiveComponent <>))) { componentRequirements.Add(ComponentType.Subtractive(field.FieldType.GetGenericArguments()[0])); } else if (field.FieldType.IsGenericType && (field.FieldType.GetGenericTypeDefinition() == typeof(BufferArray <>))) { InjectionData item = new InjectionData(field, field.FieldType.GetGenericArguments()[0], isReadOnly); bufferDataInjections.Add(item); componentRequirements.Add(item.ComponentType); } else if (field.FieldType.IsGenericType && (field.FieldType.GetGenericTypeDefinition() == typeof(SharedComponentDataArray <>))) { if (!isReadOnly) { str = $"{system.GetType().Name}:{groupField.Name} SharedComponentDataArray<> must always be injected as [ReadOnly]"; break; } InjectionData item = new InjectionData(field, field.FieldType.GetGenericArguments()[0], true); sharedComponentInjections.Add(item); componentRequirements.Add(item.ComponentType); } else if (field.FieldType == typeof(EntityArray)) { if (entityArrayField != null) { str = $"{system.GetType().Name}:{groupField.Name} An [Inject] struct, may only contain a single EntityArray"; break; } entityArrayField = field; } else if (!(field.FieldType == typeof(int))) { InjectionHook hook = InjectionHookSupport.HookFor(field); if (hook == null) { str = $"{system.GetType().Name}:{groupField.Name} [Inject] may only be used on ComponentDataArray<>, ComponentArray<>, TransformAccessArray, EntityArray, {string.Join(",", (IEnumerable<string>) (from h in InjectionHookSupport.Hooks select h.FieldTypeOfInterest.Name))} and int Length."; break; } string str2 = hook.ValidateField(field, isReadOnly, injectionContext); if (str2 != null) { str = str2; break; } injectionContext.AddEntry(hook.CreateInjectionInfoFor(field, isReadOnly)); } else { if ((field.Name != "Length") && (field.Name != "GroupIndex")) { str = $"{system.GetType().Name}:{groupField.Name} Int in an [Inject] struct should be named " Length " (group length) or " GroupIndex " (index in ComponentGroup[])"; break; } if (!field.IsInitOnly) { str = $"{system.GetType().Name}:{groupField.Name} {field.Name} must use the " readonly " keyword";