// 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.Unity;
using UnityEngine;
using UnityEngine.SceneManagement;
using WallstopStudios.UnityHelpers.Core.Attributes;
using WallstopStudios.UnityHelpers.Tags;
using WallstopStudios.UnityHelpers.Utils;
using Object = UnityEngine.Object;
///
/// Entry point registered with VContainer to hydrate relational attributes once the container is
/// fully built.
///
///
/// This type is registered automatically by calling
/// ,
/// and runs once on startup to assign relational fields across the active scene. It uses
/// (when present) to quickly locate components that have
/// relational attributes.
///
///
///
/// // LifetimeScope that enables relational assignments at scene start
/// public sealed class GameLifetimeScope : LifetimeScope
/// {
/// protected override void Configure(IContainerBuilder builder)
/// {
/// builder.RegisterRelationalComponents(
/// new RelationalSceneAssignmentOptions(includeInactive: true)
/// );
/// }
/// }
///
///
public sealed class RelationalComponentEntryPoint : IInitializable
{
private readonly IRelationalComponentAssigner _assigner;
private readonly AttributeMetadataCache _metadataCache;
private readonly RelationalSceneAssignmentOptions _options;
public RelationalComponentEntryPoint(
IRelationalComponentAssigner assigner,
AttributeMetadataCache metadataCache,
RelationalSceneAssignmentOptions options
)
{
_assigner = assigner ?? throw new ArgumentNullException(nameof(assigner));
_metadataCache = metadataCache;
_options = options;
}
public void Initialize()
{
AttributeMetadataCache cache = _metadataCache;
if (cache == null)
{
cache = 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 once and assign when type has relational fields
bool includeInactiveAll = _options.IncludeInactive;
Component[] allComponents = includeInactiveAll
? Object.FindObjectsOfType(true)
: Object.FindObjectsOfType(false);
for (int i = 0; i < allComponents.Length; i++)
{
Component c = allComponents[i];
if (c == null || c.gameObject.scene != SceneManager.GetActiveScene())
{
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
);
for (int i = 0; i < relationalTypes.Count; i++)
{
Type t = relationalTypes[i];
if (t != null)
{
relationalSet.Add(t);
}
}
Component[] all = includeInactive
? Object.FindObjectsOfType(true)
: Object.FindObjectsOfType(false);
for (int i = 0; i < all.Length; i++)
{
Component c = all[i];
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;
}
Object[] located = includeInactive
? Object.FindObjectsOfType(componentType, true)
: Object.FindObjectsOfType(componentType, false);
foreach (Object t in located)
{
if (t is not Component component)
{
continue;
}
if (component.gameObject.scene != activeScene)
{
continue;
}
_assigner.Assign(component);
}
}
}
// Safety net in Editor/tests: also walk scene roots to catch any missed
#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