// 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 WallstopStudios.UnityHelpers.Core.Attributes; using WallstopStudios.UnityHelpers.Utils; /// /// Helper extensions that bridge Zenject container operations with relational component /// assignment. /// /// /// These helpers prefer a container-registered when /// available (via or a manual binding). If no /// registration exists, they safely fall back to the non-DI assignment path so fields still get /// populated. /// /// /// /// // SceneContext: add RelationalComponentsInstaller to bind assigner and run scene initialization /// public sealed class GameInstaller : MonoInstaller /// { /// public override void InstallBindings() /// { /// // Your bindings... /// } /// } /// /// // Prefab instantiation with DI + relational assignment /// public sealed class Spawner /// { /// readonly DiContainer _container; /// readonly Enemy _enemyPrefab; /// /// public Spawner(DiContainer container, Enemy enemyPrefab) /// { /// _container = container; /// _enemyPrefab = enemyPrefab; /// } /// /// public Enemy Spawn(Transform parent) /// { /// return _container.InstantiateComponentWithRelations(_enemyPrefab, parent); /// } /// } /// /// public static class DiContainerRelationalExtensions { /// /// Injects with Zenject and assigns its relational fields. /// /// The component type. /// The Zenject container. /// The component instance to inject and hydrate. /// The same component instance. public static T InjectWithRelations(this DiContainer container, T component) where T : Component { if (component == null) { return null; } container?.Inject(component); container.AssignRelationalComponents(component); return component; } /// /// Assigns all relational fields on a component using the container-registered /// , with a safe fallback to the non-DI path. /// /// The Zenject container. /// The component to hydrate. /// /// Call this after container.Inject(component) if the instance was not created by /// Zenject, or use /// to combine instantiation and assignment. /// /// /// /// container.Inject(controller); /// container.AssignRelationalComponents(controller); /// /// public static void AssignRelationalComponents( this DiContainer container, Component component ) { if (component == null) { return; } IRelationalComponentAssigner assigner = ResolveAssigner(container); if (assigner != null) { assigner.Assign(component); return; } component.AssignRelationalComponents(); } /// /// Assigns relational fields for all components in a hierarchy. /// /// The Zenject container. /// Root GameObject to scan. /// Whether to include inactive children (default true). /// /// If an assigner binding exists it is used to recursively process the hierarchy; otherwise /// the method iterates components and calls the non-DI assignment path for each. /// /// /// /// container.AssignRelationalHierarchy(root, includeInactiveChildren: false); /// /// public static void AssignRelationalHierarchy( this DiContainer container, GameObject root, bool includeInactiveChildren = true ) { if (root == null) { return; } IRelationalComponentAssigner assigner = ResolveAssigner(container); if (assigner != null) { assigner.AssignHierarchy(root, includeInactiveChildren); return; } using PooledResource> componentBuffer = Buffers.List.Get( out List components ); root.GetComponentsInChildren(includeInactiveChildren, components); foreach (Component component in components) { component.AssignRelationalComponents(); } } /// /// Instantiates a prefab, runs Zenject injection, and assigns relational component fields on /// the returned component. /// /// The component type present on the prefab root. /// The Zenject container. /// Prefab that contains the component of type . /// Optional parent transform. /// The instantiated component with DI and relational fields populated. /// /// /// var enemy = container.InstantiateComponentWithRelations(enemyPrefab, parent); /// /// public static T InstantiateComponentWithRelations( this DiContainer container, T prefab, Transform parent = null ) where T : Component { if (prefab == null) { throw new ArgumentNullException(nameof(prefab)); } T instance = container.InstantiatePrefabForComponent(prefab, parent); container.AssignRelationalComponents(instance); return instance; } /// /// Instantiates a GameObject prefab with Zenject, injects the hierarchy and assigns /// relational fields across the new instance. /// /// The Zenject container. /// GameObject prefab. /// Optional parent transform. /// Include inactive children when assigning. /// The instantiated GameObject. public static GameObject InstantiateGameObjectWithRelations( this DiContainer container, GameObject prefab, Transform parent = null, bool includeInactiveChildren = true ) { if (prefab == null) { throw new ArgumentNullException(nameof(prefab)); } GameObject instance = container.InstantiatePrefab(prefab, parent); container.InjectGameObjectWithRelations(instance, includeInactiveChildren); return instance; } /// /// Injects all components on and children with Zenject, then assigns /// relational fields across the hierarchy. /// /// The Zenject container. /// Root GameObject. /// Include inactive children when assigning. public static void InjectGameObjectWithRelations( this DiContainer container, GameObject root, bool includeInactiveChildren = true ) { if (root == null) { return; } container?.InjectGameObject(root); container.AssignRelationalHierarchy(root, includeInactiveChildren); } private static IRelationalComponentAssigner ResolveAssigner(DiContainer container) { if (container == null) { return null; } if (container.HasBinding(typeof(IRelationalComponentAssigner))) { return container.Resolve(); } return null; } } } #endif