Files

382 lines
12 KiB
C#

#if !NOT_UNITY3D
using System;
using System.Collections.Generic;
using System.Linq;
using ModestTree;
using ModestTree.Util;
using UnityEngine;
using UnityEngine.Serialization;
using Zenject.Internal;
using UnityEngine.Events;
namespace Zenject
{
public class SceneContext : RunnableContext
{
public event Action PreInstall;
public event Action PostInstall;
public event Action PreResolve;
public event Action PostResolve;
public UnityEvent OnPreInstall;
public UnityEvent OnPostInstall;
public UnityEvent OnPreResolve;
public UnityEvent OnPostResolve;
public static Action<DiContainer> ExtraBindingsInstallMethod;
public static Action<DiContainer> ExtraBindingsLateInstallMethod;
public static IEnumerable<DiContainer> ParentContainers;
[FormerlySerializedAs("ParentNewObjectsUnderRoot")]
[FormerlySerializedAs("_parentNewObjectsUnderRoot")]
[Tooltip("When true, objects that are created at runtime will be parented to the SceneContext")]
[SerializeField]
bool _parentNewObjectsUnderSceneContext;
[Tooltip("Optional contract names for this SceneContext, allowing contexts in subsequently loaded scenes to depend on it and be parented to it, and also for previously loaded decorators to be included")]
[SerializeField]
List<string> _contractNames = new List<string>();
[Tooltip("Optional contract names of SceneContexts in previously loaded scenes that this context depends on and to which it should be parented")]
[SerializeField]
List<string> _parentContractNames = new List<string>();
DiContainer _container;
readonly List<SceneDecoratorContext> _decoratorContexts = new List<SceneDecoratorContext>();
bool _hasInstalled;
bool _hasResolved;
public override DiContainer Container
{
get { return _container; }
}
public bool HasResolved
{
get { return _hasResolved; }
}
public bool HasInstalled
{
get { return _hasInstalled; }
}
public bool IsValidating
{
get
{
return ProjectContext.Instance.Container.IsValidating;
}
}
public IEnumerable<string> ContractNames
{
get { return _contractNames; }
set
{
_contractNames.Clear();
_contractNames.AddRange(value);
}
}
public IEnumerable<string> ParentContractNames
{
get
{
var result = new List<string>();
result.AddRange(_parentContractNames);
return result;
}
set
{
_parentContractNames = value.ToList();
}
}
public bool ParentNewObjectsUnderSceneContext
{
get { return _parentNewObjectsUnderSceneContext; }
set { _parentNewObjectsUnderSceneContext = value; }
}
public void Awake()
{
#if ZEN_INTERNAL_PROFILING
ProfileTimers.ResetAll();
using (ProfileTimers.CreateTimedBlock("Other"))
#endif
{
Initialize();
}
}
public void Validate()
{
Assert.That(IsValidating);
Install();
Resolve();
}
protected override void RunInternal()
{
// We always want to initialize ProjectContext as early as possible
ProjectContext.Instance.EnsureIsInitialized();
#if UNITY_EDITOR
using (ProfileBlock.Start("Zenject.SceneContext.Install"))
#endif
{
Install();
}
#if UNITY_EDITOR
using (ProfileBlock.Start("Zenject.SceneContext.Resolve"))
#endif
{
Resolve();
}
}
public override IEnumerable<GameObject> GetRootGameObjects()
{
return ZenUtilInternal.GetRootGameObjects(gameObject.scene);
}
IEnumerable<DiContainer> GetParentContainers()
{
var parentContractNames = ParentContractNames;
if (parentContractNames.IsEmpty())
{
if (ParentContainers != null)
{
var tempParentContainer = ParentContainers;
// Always reset after using it - it is only used to pass the reference
// between scenes via ZenjectSceneLoader
ParentContainers = null;
return tempParentContainer;
}
return new[] { ProjectContext.Instance.Container };
}
Assert.IsNull(ParentContainers,
"Scene cannot have both a parent scene context name set and also an explicit parent container given");
var parentContainers = UnityUtil.AllLoadedScenes
.Except(gameObject.scene)
.SelectMany(scene => scene.GetRootGameObjects())
.SelectMany(root => root.GetComponentsInChildren<SceneContext>())
.Where(sceneContext => sceneContext.ContractNames.Where(x => parentContractNames.Contains(x)).Any())
.Select(x => x.Container)
.ToList();
if (!parentContainers.Any())
{
throw Assert.CreateException(
"SceneContext on object {0} of scene {1} requires at least one of contracts '{2}', but none of the loaded SceneContexts implements that contract.",
gameObject.name,
gameObject.scene.name,
parentContractNames.Join(", "));
}
return parentContainers;
}
List<SceneDecoratorContext> LookupDecoratorContexts()
{
if (_contractNames.IsEmpty())
{
return new List<SceneDecoratorContext>();
}
return UnityUtil.AllLoadedScenes
.Except(gameObject.scene)
.SelectMany(scene => scene.GetRootGameObjects())
.SelectMany(root => root.GetComponentsInChildren<SceneDecoratorContext>())
.Where(decoratorContext => _contractNames.Contains(decoratorContext.DecoratedContractName))
.ToList();
}
public void Install()
{
Assert.That(!_hasInstalled);
_hasInstalled = true;
Assert.IsNull(_container);
var parents = GetParentContainers();
Assert.That(!parents.IsEmpty());
Assert.That(parents.All(x => x.IsValidating == parents.First().IsValidating));
_container = new DiContainer(parents, parents.First().IsValidating);
// Do this after creating DiContainer in case it's needed by the pre install logic
if (PreInstall != null)
{
PreInstall();
}
if (OnPreInstall != null)
{
OnPreInstall.Invoke();
}
Assert.That(_decoratorContexts.IsEmpty());
_decoratorContexts.AddRange(LookupDecoratorContexts());
if (_parentNewObjectsUnderSceneContext)
{
_container.DefaultParent = transform;
}
else
{
_container.DefaultParent = null;
}
// Record all the injectable components in the scene BEFORE installing the installers
// This is nice for cases where the user calls InstantiatePrefab<>, etc. in their installer
// so that it doesn't inject on the game object twice
// InitialComponentsInjecter will also guarantee that any component that is injected into
// another component has itself been injected
var injectableMonoBehaviours = new List<MonoBehaviour>();
GetInjectableMonoBehaviours(injectableMonoBehaviours);
foreach (var instance in injectableMonoBehaviours)
{
_container.QueueForInject(instance);
}
foreach (var decoratorContext in _decoratorContexts)
{
decoratorContext.Initialize(_container);
}
_container.IsInstalling = true;
try
{
InstallBindings(injectableMonoBehaviours);
}
finally
{
_container.IsInstalling = false;
}
if (PostInstall != null)
{
PostInstall();
}
if (OnPostInstall != null)
{
OnPostInstall.Invoke();
}
}
public void Resolve()
{
if (PreResolve != null)
{
PreResolve();
}
if (OnPreResolve != null)
{
OnPreResolve.Invoke();
}
Assert.That(_hasInstalled);
Assert.That(!_hasResolved);
_hasResolved = true;
_container.ResolveRoots();
if (PostResolve != null)
{
PostResolve();
}
if (OnPostResolve != null)
{
OnPostResolve.Invoke();
}
}
void InstallBindings(List<MonoBehaviour> injectableMonoBehaviours)
{
_container.Bind(typeof(Context), typeof(SceneContext)).To<SceneContext>().FromInstance(this);
_container.BindInterfacesTo<SceneContextRegistryAdderAndRemover>().AsSingle();
// Add to registry first and remove from registry last
_container.BindExecutionOrder<SceneContextRegistryAdderAndRemover>(-1);
foreach (var decoratorContext in _decoratorContexts)
{
decoratorContext.InstallDecoratorSceneBindings();
}
InstallSceneBindings(injectableMonoBehaviours);
_container.Bind(typeof(SceneKernel), typeof(MonoKernel))
.To<SceneKernel>().FromNewComponentOn(gameObject).AsSingle().NonLazy();
_container.Bind<ZenjectSceneLoader>().AsSingle();
if (ExtraBindingsInstallMethod != null)
{
ExtraBindingsInstallMethod(_container);
// Reset extra bindings for next time we change scenes
ExtraBindingsInstallMethod = null;
}
// Always install the installers last so they can be injected with
// everything above
foreach (var decoratorContext in _decoratorContexts)
{
decoratorContext.InstallDecoratorInstallers();
}
InstallInstallers();
foreach (var decoratorContext in _decoratorContexts)
{
decoratorContext.InstallLateDecoratorInstallers();
}
if (ExtraBindingsLateInstallMethod != null)
{
ExtraBindingsLateInstallMethod(_container);
// Reset extra bindings for next time we change scenes
ExtraBindingsLateInstallMethod = null;
}
}
protected override void GetInjectableMonoBehaviours(List<MonoBehaviour> monoBehaviours)
{
var scene = gameObject.scene;
ZenUtilInternal.AddStateMachineBehaviourAutoInjectersInScene(scene);
ZenUtilInternal.GetInjectableMonoBehavioursInScene(scene, monoBehaviours);
}
// These methods can be used for cases where you need to create the SceneContext entirely in code
// Note that if you use these methods that you have to call Run() yourself
// This is useful because it allows you to create a SceneContext and configure it how you want
// and add what installers you want before kicking off the Install/Resolve
public static SceneContext Create()
{
return CreateComponent<SceneContext>(
new GameObject("SceneContext"));
}
}
}
#endif