// MIT License - Copyright (c) 2025 wallstop // Full license text: https://github.com/wallstop/unity-helpers/blob/main/LICENSE #if ZENJECT_PRESENT namespace WallstopStudios.UnityHelpers.Integrations.Zenject { using System; using System.Collections.Generic; using global::Zenject; using UnityEngine; using UnityEngine.SceneManagement; using WallstopStudios.UnityHelpers.Core.Attributes; using WallstopStudios.UnityHelpers.Tags; using WallstopStudios.UnityHelpers.Utils; /// /// Scene-level entry point that assigns relational component fields immediately after Zenject /// finishes injecting the container. /// /// /// Registered automatically when you add to a /// SceneContext or bind it manually. Uses when /// available to discover component types that contain relational attributes, then hydrates those /// fields across the active scene. /// /// /// /// // In SceneContext, add RelationalComponentsInstaller to enable scene-wide assignment /// [AddComponentMenu("Installers/Game Installer")] /// public sealed class GameInstaller : MonoInstaller /// { /// public override void InstallBindings() /// { /// // Your app bindings here /// } /// } /// /// public sealed class RelationalComponentSceneInitializer : IInitializable { private readonly IRelationalComponentAssigner _assigner; private readonly AttributeMetadataCache _metadataCache; private readonly RelationalSceneAssignmentOptions _options; public RelationalComponentSceneInitializer( IRelationalComponentAssigner assigner, AttributeMetadataCache metadataCache, RelationalSceneAssignmentOptions options ) { _assigner = assigner ?? throw new ArgumentNullException(nameof(assigner)); _metadataCache = metadataCache; _options = options; } public void Initialize() { AttributeMetadataCache cache = _metadataCache ?? AttributeMetadataCache.Instance; if (cache == null) { return; } using PooledResource> pooledTypes = Buffers.List.Get( out List relationalTypes ); cache.CollectRelationalComponentTypes(relationalTypes); if (relationalTypes.Count == 0) { // Fallback: scan all components in the active scene and assign when type has relational fields bool includeInactiveAll = _options.IncludeInactive; Scene active = SceneManager.GetActiveScene(); Component[] allComponents = includeInactiveAll ? UnityEngine.Object.FindObjectsOfType(true) : UnityEngine.Object.FindObjectsOfType(false); for (int i = 0; i < allComponents.Length; i++) { Component c = allComponents[i]; if (c == null || c.gameObject.scene != active) { continue; } if (_assigner.HasRelationalAssignments(c.GetType())) { _assigner.Assign(c); } } return; } bool includeInactive = _options.IncludeInactive; Scene activeScene = SceneManager.GetActiveScene(); if (_options.UseSinglePassScan) { using PooledResource> pooledSet = Buffers.HashSet.Get( out HashSet relationalSet ); foreach (Type relationalType in relationalTypes) { if (relationalType != null) { relationalSet.Add(relationalType); } } Component[] all = includeInactive ? UnityEngine.Object.FindObjectsOfType(true) : UnityEngine.Object.FindObjectsOfType(false); foreach (Component c in all) { if (c == null || c.gameObject.scene != activeScene) { continue; } Type t = c.GetType(); while (t != null && typeof(Component).IsAssignableFrom(t)) { if (relationalSet.Contains(t)) { _assigner.Assign(c); break; } t = t.BaseType; } } } else { foreach (Type componentType in relationalTypes) { if (componentType == null) { continue; } UnityEngine.Object[] located = includeInactive ? UnityEngine.Object.FindObjectsOfType(componentType, true) : UnityEngine.Object.FindObjectsOfType(componentType, false); foreach (UnityEngine.Object candidate in located) { if (candidate is not Component component) { continue; } if (component == null || component.gameObject.scene != activeScene) { continue; } _assigner.Assign(component); } } } // Safety net in Editor/tests: also walk scene roots to ensure coverage #if UNITY_EDITOR if (!Application.isPlaying) { using PooledResource> rootGoBuffer = Buffers.List.Get( out List roots ); activeScene.GetRootGameObjects(roots); foreach (GameObject root in roots) { if (root == null) { continue; } _assigner.AssignHierarchy(root, includeInactive); } } #endif } } } #endif