// MIT License - Copyright (c) 2025 wallstop // Full license text: https://github.com/wallstop/unity-helpers/blob/main/LICENSE namespace WallstopStudios.UnityHelpers.Core.Attributes { using System; using System.Collections.Generic; using System.Reflection; using UnityEngine; using WallstopStudios.UnityHelpers.Core.Helper; using WallstopStudios.UnityHelpers.Tags; using WallstopStudios.UnityHelpers.Utils; /// /// Explicit initializer for Relational Components. /// Pre-warms ReflectionHelpers caches (field getters/setters and collection creators) /// for all fields marked with relational component attributes across the provided types /// or, if none are provided, across all loaded types. /// /// /// - Uses when available to avoid heavy reflection. /// - Falls back to reflection-based discovery when a type is not present in the cache. /// - Logs warnings and continues when individual fields or element types cannot be resolved. /// - Call during a loading screen or bootstrap to eliminate first-use stalls. /// public static class RelationalComponentInitializer { /// /// Report with summary statistics for a pre-warm run. /// public sealed class Report { /// Total types visited (either provided or discovered). public int TypesVisited { get; internal set; } /// Types that had at least one relational field warmed. public int TypesWarmed { get; internal set; } /// Total number of relational fields warmed. public int FieldsWarmed { get; internal set; } /// Non-fatal issues (e.g., missing fields/types) encountered. public int Warnings { get; internal set; } /// Fatal errors encountered while processing a type. public int Errors { get; internal set; } /// /// Optional per-type breakdown for diagnostics. Not serialized. /// public readonly Dictionary WarmedFieldsPerType = new(); internal void AddTypeResult(Type type, int warmedCount) { if (warmedCount <= 0) { return; } TypesWarmed++; FieldsWarmed += warmedCount; WarmedFieldsPerType[type] = warmedCount; } } /// /// Pre-warms caches for relational attributes on the provided . /// If is null, scans all loaded types. /// /// Specific types to pre-warm, or null to scan all loaded Component types. /// When true, logs a summary to the Unity Console. /// Summary of work performed. public static Report Initialize(IEnumerable types = null, bool logSummary = true) { Report report = new(); AttributeMetadataCache cache = AttributeMetadataCache.Instance; IEnumerable targetTypes = types ?? EnumerateAllComponentTypes(); foreach (Type type in targetTypes) { if (type == null) { continue; } report.TypesVisited++; try { int warmed = WarmType(type, cache, report); report.AddTypeResult(type, warmed); } catch (Exception e) { report.Errors++; Debug.LogError( $"RelationalComponents.Initialize: Error pre-warming type '{type.FullName}': {e.Message}\n{e}" ); } } if (logSummary) { Debug.Log( $"RelationalComponents.Initialize: Done. TypesVisited={report.TypesVisited}, TypesWarmed={report.TypesWarmed}, FieldsWarmed={report.FieldsWarmed}, Warnings={report.Warnings}, Errors={report.Errors}." ); } return report; } private static IEnumerable EnumerateAllComponentTypes() { foreach (Type t in ReflectionHelpers.GetAllLoadedTypes()) { if (t != null && typeof(Component).IsAssignableFrom(t)) { yield return t; } } } private static int WarmType(Type componentType, AttributeMetadataCache cache, Report report) { AttributeMetadataCache.ResolvedRelationalFieldMetadata[] resolved = null; bool usedCache = cache != null && cache.TryGetResolvedRelationalFields(componentType, out resolved); if (!usedCache) { // Fallback: discover relational fields via reflection resolved = DiscoverRelationalFieldsViaReflection(componentType); } if (resolved == null || resolved.Length == 0) { return 0; } int warmed = 0; const BindingFlags flags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic; for (int i = 0; i < resolved.Length; i++) { AttributeMetadataCache.ResolvedRelationalFieldMetadata fieldMeta = resolved[i]; FieldInfo field = componentType.GetField(fieldMeta.FieldName, flags); if (field == null) { report.Warnings++; Debug.LogWarning( $"RelationalComponents.Initialize: Field '{fieldMeta.FieldName}' not found on '{componentType.FullName}'." ); continue; } try { // Force-create field accessors _ = ReflectionHelpers.GetFieldGetter(field); _ = ReflectionHelpers.GetFieldSetter(field); // Determine the element type and prewarm collection creators where applicable Type elementType = fieldMeta.ElementType ?? InferElementType(field.FieldType); PrewarmCollectionCreators(fieldMeta.FieldKind, elementType); warmed++; } catch (Exception e) { report.Errors++; Debug.LogError( $"RelationalComponents.Initialize: Error warming field '{componentType.FullName}.{fieldMeta.FieldName}': {e.Message}\n{e}" ); } } return warmed; } private static void PrewarmCollectionCreators( AttributeMetadataCache.FieldKind kind, Type elementType ) { if (elementType == null) { return; } switch (kind) { case AttributeMetadataCache.FieldKind.Array: _ = ReflectionHelpers.GetArrayCreator(elementType); break; case AttributeMetadataCache.FieldKind.List: _ = ReflectionHelpers.GetListWithCapacityCreator(elementType); break; case AttributeMetadataCache.FieldKind.HashSet: _ = ReflectionHelpers.GetHashSetWithCapacityCreator(elementType); _ = ReflectionHelpers.GetHashSetAdder(elementType); break; } } private static Type InferElementType(Type fieldType) { if (fieldType == null) { return null; } if (fieldType.IsArray) { return fieldType.GetElementType(); } if (fieldType.IsGenericType) { Type g = fieldType.GetGenericTypeDefinition(); if (g == typeof(List<>)) { return fieldType.GenericTypeArguments[0]; } if (g == typeof(HashSet<>)) { return fieldType.GenericTypeArguments[0]; } } return fieldType; } private static AttributeMetadataCache.ResolvedRelationalFieldMetadata[] DiscoverRelationalFieldsViaReflection( Type componentType ) { BindingFlags flags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic; FieldInfo[] fields; try { fields = componentType.GetFields(flags); } catch { return Array.Empty(); } PooledResource< List > resultLease = default; List result = null; for (int i = 0; i < fields.Length; i++) { FieldInfo f = fields[i]; if (f == null) { continue; } AttributeMetadataCache.RelationalAttributeKind kindMeta; if (ReflectionHelpers.HasAttributeSafe(f, inherit: false)) { kindMeta = AttributeMetadataCache.RelationalAttributeKind.Parent; } else if ( ReflectionHelpers.HasAttributeSafe(f, inherit: false) ) { kindMeta = AttributeMetadataCache.RelationalAttributeKind.Child; } else if ( ReflectionHelpers.HasAttributeSafe(f, inherit: false) ) { kindMeta = AttributeMetadataCache.RelationalAttributeKind.Sibling; } else { continue; } AttributeMetadataCache.FieldKind fieldKindMeta = AttributeMetadataCache .FieldKind .Single; Type elementType = null; Type ft = f.FieldType; if (ft.IsArray) { fieldKindMeta = AttributeMetadataCache.FieldKind.Array; elementType = ft.GetElementType(); } else if (ft.IsGenericType) { Type g = ft.GetGenericTypeDefinition(); if (g == typeof(List<>)) { fieldKindMeta = AttributeMetadataCache.FieldKind.List; elementType = ft.GenericTypeArguments[0]; } else if (g == typeof(HashSet<>)) { fieldKindMeta = AttributeMetadataCache.FieldKind.HashSet; elementType = ft.GenericTypeArguments[0]; } else { elementType = ft; } } else { elementType = ft; } bool isInterface = elementType != null && ( elementType.IsInterface || (!elementType.IsSealed && elementType != typeof(Component)) ); if (result == null) { resultLease = Buffers.List.Get( out result ); } result.Add( new AttributeMetadataCache.ResolvedRelationalFieldMetadata( f.Name, kindMeta, fieldKindMeta, elementType, isInterface ) ); } if (result == null) { return Array.Empty(); } AttributeMetadataCache.ResolvedRelationalFieldMetadata[] array = result.ToArray(); resultLease.Dispose(); return array; } } }