// MIT License - Copyright (c) 2025 wallstop // Full license text: https://github.com/wallstop/unity-helpers/blob/main/LICENSE #if VCONTAINER_PRESENT namespace WallstopStudios.UnityHelpers.Integrations.VContainer { using System; using System.Collections.Generic; using global::VContainer; using global::VContainer.Unity; using UnityEngine; using WallstopStudios.UnityHelpers.Core.Attributes; using WallstopStudios.UnityHelpers.Utils; /// /// Convenience extensions that bridge VContainer resolution/injection with relational component /// assignment. /// /// /// These helpers are safe to call whether or not the integration is registered with the /// container. If an binding is available, it is used /// to hydrate attributed fields; otherwise the call falls back to /// /// so you never end up with null references. /// /// /// /// // In a LifetimeScope /// using VContainer; /// using VContainer.Unity; /// using WallstopStudios.UnityHelpers.Integrations.VContainer; /// /// public sealed class GameLifetimeScope : LifetimeScope /// { /// protected override void Configure(IContainerBuilder builder) /// { /// // Registers IRelationalComponentAssigner and the scene entry point /// builder.RegisterRelationalComponents(); /// } /// } /// /// // In a MonoBehaviour that is created at runtime /// using UnityEngine; /// using VContainer; /// using WallstopStudios.UnityHelpers.Integrations.VContainer; /// /// public sealed class Spawner : MonoBehaviour /// { /// [Inject] private IObjectResolver _resolver; /// /// [SerializeField] private Enemy _enemyPrefab; /// /// public Enemy Spawn(Transform parent) /// { /// Enemy instance = Instantiate(_enemyPrefab, parent); /// // Ensures DI injection then relational assignment for fields like [Child], [Sibling], etc. /// return _resolver.BuildUpWithRelations(instance); /// } /// } /// /// public static class ObjectResolverRelationalExtensions { /// /// Injects with VContainer and assigns its relational fields. /// /// The component type. /// The VContainer object resolver. /// The component instance to inject and hydrate. /// The same component instance. public static T InjectWithRelations(this IObjectResolver resolver, T component) where T : Component { if (component == null) { return null; } // Use Inject for compatibility with VContainer 1.16.x resolver?.Inject(component); resolver.AssignRelationalComponents(component); return component; } /// /// Assigns all relational fields on a component using the container's registered /// if present, with a safe fallback to the /// non-DI assignment path. /// /// The VContainer object resolver (may be null). /// The component to hydrate. /// /// Use this after calling resolver.Inject(component) if the component wasn't created /// by VContainer. When the assigner is not bound, the call uses /// component.AssignRelationalComponents() so behavior remains consistent. /// /// /// /// _resolver.Inject(controller); /// _resolver.AssignRelationalComponents(controller); /// /// public static void AssignRelationalComponents( this IObjectResolver resolver, Component component ) { if (component == null) { return; } if (TryResolveAssigner(resolver, out IRelationalComponentAssigner assigner)) { assigner.Assign(component); } else { component.AssignRelationalComponents(); } } /// /// Assigns relational fields for all components in a hierarchy. /// /// The VContainer object resolver. /// The root GameObject to scan. /// /// Include inactive objects when scanning children. Defaults to true. /// /// /// If is bound, it is used; otherwise the /// method iterates the hierarchy and calls /// AssignRelationalComponents() on each component. /// /// /// /// // After building a dynamic hierarchy /// _resolver.AssignRelationalHierarchy(parentGameObject, includeInactiveChildren: false); /// /// public static void AssignRelationalHierarchy( this IObjectResolver resolver, GameObject root, bool includeInactiveChildren = true ) { if (root == null) { return; } if (TryResolveAssigner(resolver, out IRelationalComponentAssigner assigner)) { assigner.AssignHierarchy(root, includeInactiveChildren); return; } using PooledResource> componentBuffer = Buffers.List.Get( out List components ); root.GetComponentsInChildren(includeInactiveChildren, components); for (int i = 0; i < components.Count; i++) { components[i].AssignRelationalComponents(); } } /// /// Injects a component with VContainer and then hydrates its relational fields. /// /// The component type. /// The VContainer object resolver. /// The component instance to build up. /// The same component instance for fluent usage. /// /// /// Enemy enemy = Instantiate(enemyPrefab); /// enemy = _resolver.BuildUpWithRelations(enemy); /// /// public static T BuildUpWithRelations(this IObjectResolver resolver, T component) where T : Component { if (component == null) { return null; } // Use Inject for compatibility with VContainer 1.16.x resolver?.Inject(component); resolver.AssignRelationalComponents(component); return component; } /// /// Instantiates a prefab that has component on the root, injects it /// using VContainer and assigns relational fields. /// /// Component type on the prefab root. /// The VContainer object resolver. /// Prefab that contains . /// Optional parent transform for the new instance. /// The instantiated component with DI and relational fields populated. public static T InstantiateComponentWithRelations( this IObjectResolver resolver, T prefab, Transform parent = null ) where T : Component { if (prefab == null) { throw new ArgumentNullException(nameof(prefab)); } T instance = UnityEngine.Object.Instantiate(prefab, parent); return resolver.BuildUpWithRelations(instance); } /// /// Instantiates a GameObject prefab, injects its hierarchy with VContainer, then assigns /// relational fields for all components beneath the root. /// /// The VContainer object resolver. /// GameObject prefab to instantiate. /// Optional parent transform. /// Whether to include inactive children when assigning. /// The instantiated GameObject. public static GameObject InstantiateGameObjectWithRelations( this IObjectResolver resolver, GameObject prefab, Transform parent = null, bool includeInactiveChildren = true ) { if (prefab == null) { throw new ArgumentNullException(nameof(prefab)); } GameObject instance = UnityEngine.Object.Instantiate(prefab, parent); resolver.InjectGameObjectWithRelations(instance, includeInactiveChildren); return instance; } /// /// Injects all components on and its children, then assigns /// relational fields for the hierarchy. /// /// The VContainer object resolver. /// Root GameObject to inject and hydrate. /// Whether to include inactive children when assigning. public static void InjectGameObjectWithRelations( this IObjectResolver resolver, GameObject root, bool includeInactiveChildren = true ) { if (root == null) { return; } resolver?.InjectGameObject(root); resolver.AssignRelationalHierarchy(root, includeInactiveChildren); } private static bool TryResolveAssigner( IObjectResolver resolver, out IRelationalComponentAssigner assigner ) { if (resolver != null && resolver.TryResolve(out assigner)) { return assigner != null; } assigner = null; return false; } } } #endif