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