370 lines
12 KiB
C#

using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
using VContainer.Diagnostics;
using VContainer.Internal;
namespace VContainer.Unity
{
[DefaultExecutionOrder(-5000)]
public partial class LifetimeScope : MonoBehaviour, IDisposable
{
public readonly struct ParentOverrideScope : IDisposable
{
public ParentOverrideScope(LifetimeScope nextParent)
{
lock (SyncRoot)
{
GlobalOverrideParents.Push(nextParent);
}
}
public void Dispose()
{
lock (SyncRoot)
{
GlobalOverrideParents.Pop();
}
}
}
public readonly struct ExtraInstallationScope : IDisposable
{
public ExtraInstallationScope(IInstaller installer)
{
lock (SyncRoot)
{
GlobalExtraInstallers.Push(installer);
}
}
void IDisposable.Dispose()
{
lock (SyncRoot)
{
GlobalExtraInstallers.Pop();
}
}
}
[SerializeField]
public ParentReference parentReference;
[SerializeField]
public bool autoRun = true;
[SerializeField]
protected List<GameObject> autoInjectGameObjects;
string scopeName;
static readonly Stack<LifetimeScope> GlobalOverrideParents = new Stack<LifetimeScope>();
static readonly Stack<IInstaller> GlobalExtraInstallers = new Stack<IInstaller>();
static readonly object SyncRoot = new object();
public static LifetimeScope Create(IInstaller installer = null, string name = null)
{
var gameObject = new GameObject(name ?? "LifetimeScope");
gameObject.SetActive(false);
var newScope = gameObject.AddComponent<LifetimeScope>();
if (installer != null)
{
newScope.localExtraInstallers.Add(installer);
}
gameObject.SetActive(true);
return newScope;
}
public static LifetimeScope Create(Action<IContainerBuilder> configuration, string name = null)
=> Create(new ActionInstaller(configuration), name);
public static ParentOverrideScope EnqueueParent(LifetimeScope parent)
=> new ParentOverrideScope(parent);
public static ExtraInstallationScope Enqueue(Action<IContainerBuilder> installing)
=> new ExtraInstallationScope(new ActionInstaller(installing));
public static ExtraInstallationScope Enqueue(IInstaller installer)
=> new ExtraInstallationScope(installer);
[Obsolete("LifetimeScope.PushParent is obsolete. Use LifetimeScope.EnqueueParent instead.", false)]
public static ParentOverrideScope PushParent(LifetimeScope parent) => new ParentOverrideScope(parent);
[Obsolete("LifetimeScope.Push is obsolete. Use LifetimeScope.Enqueue instead.", false)]
public static ExtraInstallationScope Push(Action<IContainerBuilder> installing) => Enqueue(installing);
[Obsolete("LifetimeScope.Push is obsolete. Use LifetimeScope.Enqueue instead.", false)]
public static ExtraInstallationScope Push(IInstaller installer) => Enqueue(installer);
public static LifetimeScope Find<T>(Scene scene) where T : LifetimeScope => Find(typeof(T), scene);
public static LifetimeScope Find<T>() where T : LifetimeScope => Find(typeof(T));
static LifetimeScope Find(Type type, Scene scene)
{
using (ListPool<GameObject>.Get(out var buffer))
{
scene.GetRootGameObjects(buffer);
foreach (var gameObject in buffer)
{
var found = gameObject.GetComponentInChildren(type) as LifetimeScope;
if (found != null)
return found;
}
}
return null;
}
static LifetimeScope Find(Type type)
{
#if UNITY_2022_1_OR_NEWER
return (LifetimeScope)FindAnyObjectByType(type);
#else
return (LifetimeScope)FindObjectOfType(type);
#endif
}
public IObjectResolver Container { get; private set; }
public LifetimeScope Parent { get; private set; }
public bool IsRoot => VContainerSettings.Instance != null &&
VContainerSettings.Instance.IsRootLifetimeScopeInstance(this);
readonly List<IInstaller> localExtraInstallers = new List<IInstaller>();
protected virtual void Awake()
{
if (VContainerSettings.DiagnosticsEnabled && string.IsNullOrEmpty(scopeName))
{
scopeName = $"{name} ({gameObject.GetInstanceID()})";
}
try
{
if (autoRun)
{
Build();
}
}
catch (VContainerParentTypeReferenceNotFound) when(!IsRoot)
{
if (WaitingList.Contains(this))
{
throw;
}
EnqueueAwake(this);
}
}
protected virtual void OnDestroy()
{
DisposeCore();
}
protected virtual void Configure(IContainerBuilder builder) { }
public void Dispose()
{
DisposeCore();
if (this != null)
{
Destroy(gameObject);
}
}
public void DisposeCore()
{
Container?.Dispose();
Container = null;
CancelAwake(this);
if (VContainerSettings.DiagnosticsEnabled)
{
DiagnositcsContext.RemoveCollector(scopeName);
}
}
public void Build()
{
if (Parent == null)
Parent = GetRuntimeParent();
if (Parent != null)
{
if (VContainerSettings.Instance != null && Parent.IsRoot)
{
if (Parent.Container == null)
Parent.Build();
}
// ReSharper disable once PossibleNullReferenceException
Parent.Container.CreateScope(builder =>
{
builder.RegisterBuildCallback(SetContainer);
builder.ApplicationOrigin = this;
builder.Diagnostics = VContainerSettings.DiagnosticsEnabled ? DiagnositcsContext.GetCollector(scopeName) : null;
InstallTo(builder);
});
}
else
{
var builder = new ContainerBuilder
{
ApplicationOrigin = this,
Diagnostics = VContainerSettings.DiagnosticsEnabled ? DiagnositcsContext.GetCollector(scopeName) : null,
};
builder.RegisterBuildCallback(SetContainer);
InstallTo(builder);
builder.Build();
}
AwakeWaitingChildren(this);
}
void SetContainer(IObjectResolver container)
{
Container = container;
AutoInjectAll();
}
public TScope CreateChild<TScope>(IInstaller installer = null, string childScopeName = null)
where TScope : LifetimeScope
{
var childGameObject = new GameObject(childScopeName ?? "LifetimeScope (Child)");
childGameObject.SetActive(false);
childGameObject.transform.SetParent(transform, false);
var child = childGameObject.AddComponent<TScope>();
if (installer != null)
{
child.localExtraInstallers.Add(installer);
}
child.parentReference.Object = this;
childGameObject.SetActive(true);
return child;
}
public LifetimeScope CreateChild(IInstaller installer = null, string childScopeName = null)
=> CreateChild<LifetimeScope>(installer, childScopeName);
public TScope CreateChild<TScope>(Action<IContainerBuilder> installation, string childScopeName = null)
where TScope : LifetimeScope
=> CreateChild<TScope>(new ActionInstaller(installation), childScopeName);
public LifetimeScope CreateChild(Action<IContainerBuilder> installation, string childScopeName = null)
=> CreateChild<LifetimeScope>(new ActionInstaller(installation), childScopeName);
public TScope CreateChildFromPrefab<TScope>(TScope prefab, IInstaller installer = null)
where TScope : LifetimeScope
{
var wasActive = prefab.gameObject.activeSelf;
using (new ObjectResolverUnityExtensions.PrefabDirtyScope(prefab.gameObject))
{
if (wasActive)
{
prefab.gameObject.SetActive(false);
}
var child = Instantiate(prefab, transform, false);
if (installer != null)
{
child.localExtraInstallers.Add(installer);
}
child.parentReference.Object = this;
if (wasActive)
{
prefab.gameObject.SetActive(true);
child.gameObject.SetActive(true);
}
return child;
}
}
public TScope CreateChildFromPrefab<TScope>(TScope prefab, Action<IContainerBuilder> installation)
where TScope : LifetimeScope
=> CreateChildFromPrefab(prefab, new ActionInstaller(installation));
void InstallTo(IContainerBuilder builder)
{
Configure(builder);
foreach (var installer in localExtraInstallers)
{
installer.Install(builder);
}
localExtraInstallers.Clear();
lock (SyncRoot)
{
foreach (var installer in GlobalExtraInstallers)
{
installer.Install(builder);
}
}
builder.RegisterInstance<LifetimeScope>(this).AsSelf();
EntryPointsBuilder.EnsureDispatcherRegistered(builder);
}
protected virtual LifetimeScope FindParent() => null;
LifetimeScope GetRuntimeParent()
{
if (IsRoot) return null;
if (parentReference.Object != null)
return parentReference.Object;
// Find via implementation
var implParent = FindParent();
if (implParent != null)
{
if (parentReference.Type != null && parentReference.Type != implParent.GetType()) {
UnityEngine.Debug.LogWarning($"FindParent returned {implParent.GetType()} but parent reference type is {parentReference.Type}. This may be unintentional.");
}
return implParent;
}
// Find in scene via type
if (parentReference.Type != null && parentReference.Type != GetType())
{
var found = Find(parentReference.Type);
if (found != null && found.Container != null)
{
return found;
}
throw new VContainerParentTypeReferenceNotFound(
parentReference.Type,
$"{name} could not found parent reference of type : {parentReference.Type}");
}
lock (SyncRoot)
{
if (GlobalOverrideParents.Count > 0)
{
return GlobalOverrideParents.Peek();
}
}
// Find root from settings
if (VContainerSettings.Instance != null)
{
return VContainerSettings.Instance.GetOrCreateRootLifetimeScopeInstance();
}
return null;
}
void AutoInjectAll()
{
if (autoInjectGameObjects == null)
return;
foreach (var target in autoInjectGameObjects)
{
if (target != null) // Check missing reference
{
Container.InjectGameObject(target);
}
}
}
}
}