// 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