Initial Commit
This commit is contained in:
@@ -0,0 +1,334 @@
|
||||
#if !NOT_UNITY3D
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using ModestTree;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Serialization;
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor;
|
||||
#endif
|
||||
|
||||
namespace Zenject
|
||||
{
|
||||
public abstract class Context : MonoBehaviour
|
||||
{
|
||||
[SerializeField]
|
||||
List<ScriptableObjectInstaller> _scriptableObjectInstallers = new List<ScriptableObjectInstaller>();
|
||||
|
||||
[FormerlySerializedAs("Installers")]
|
||||
[FormerlySerializedAs("_installers")]
|
||||
[SerializeField]
|
||||
List<MonoInstaller> _monoInstallers = new List<MonoInstaller>();
|
||||
|
||||
[SerializeField]
|
||||
List<MonoInstaller> _installerPrefabs = new List<MonoInstaller>();
|
||||
|
||||
List<InstallerBase> _normalInstallers = new List<InstallerBase>();
|
||||
List<Type> _normalInstallerTypes = new List<Type>();
|
||||
|
||||
public IEnumerable<MonoInstaller> Installers
|
||||
{
|
||||
get { return _monoInstallers; }
|
||||
set
|
||||
{
|
||||
_monoInstallers.Clear();
|
||||
_monoInstallers.AddRange(value);
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<MonoInstaller> InstallerPrefabs
|
||||
{
|
||||
get { return _installerPrefabs; }
|
||||
set
|
||||
{
|
||||
_installerPrefabs.Clear();
|
||||
_installerPrefabs.AddRange(value);
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<ScriptableObjectInstaller> ScriptableObjectInstallers
|
||||
{
|
||||
get { return _scriptableObjectInstallers; }
|
||||
set
|
||||
{
|
||||
_scriptableObjectInstallers.Clear();
|
||||
_scriptableObjectInstallers.AddRange(value);
|
||||
}
|
||||
}
|
||||
|
||||
// Unlike other installer types this has to be set through code
|
||||
public IEnumerable<Type> NormalInstallerTypes
|
||||
{
|
||||
get { return _normalInstallerTypes; }
|
||||
set
|
||||
{
|
||||
Assert.That(value.All(x => x != null && x.DerivesFrom<InstallerBase>()));
|
||||
|
||||
_normalInstallerTypes.Clear();
|
||||
_normalInstallerTypes.AddRange(value);
|
||||
}
|
||||
}
|
||||
|
||||
// Unlike other installer types this has to be set through code
|
||||
public IEnumerable<InstallerBase> NormalInstallers
|
||||
{
|
||||
get { return _normalInstallers; }
|
||||
set
|
||||
{
|
||||
_normalInstallers.Clear();
|
||||
_normalInstallers.AddRange(value);
|
||||
}
|
||||
}
|
||||
|
||||
public abstract DiContainer Container
|
||||
{
|
||||
get;
|
||||
}
|
||||
public abstract IEnumerable<GameObject> GetRootGameObjects();
|
||||
|
||||
|
||||
public void AddNormalInstallerType(Type installerType)
|
||||
{
|
||||
Assert.IsNotNull(installerType);
|
||||
Assert.That(installerType.DerivesFrom<InstallerBase>());
|
||||
|
||||
_normalInstallerTypes.Add(installerType);
|
||||
}
|
||||
|
||||
public void AddNormalInstaller(InstallerBase installer)
|
||||
{
|
||||
_normalInstallers.Add(installer);
|
||||
}
|
||||
|
||||
void CheckInstallerPrefabTypes(List<MonoInstaller> installers, List<MonoInstaller> installerPrefabs)
|
||||
{
|
||||
foreach (var installer in installers)
|
||||
{
|
||||
Assert.IsNotNull(installer, "Found null installer in Context '{0}'", name);
|
||||
|
||||
#if UNITY_EDITOR
|
||||
#if UNITY_2018_3_OR_NEWER
|
||||
Assert.That(!PrefabUtility.IsPartOfPrefabAsset(installer.gameObject),
|
||||
#else
|
||||
Assert.That(PrefabUtility.GetPrefabType(installer.gameObject) != PrefabType.Prefab,
|
||||
#endif
|
||||
"Found prefab with name '{0}' in the Installer property of Context '{1}'. You should use the property 'InstallerPrefabs' for this instead.", installer.name, name);
|
||||
#endif
|
||||
}
|
||||
|
||||
foreach (var installerPrefab in installerPrefabs)
|
||||
{
|
||||
Assert.IsNotNull(installerPrefab, "Found null prefab in Context");
|
||||
|
||||
// We'd like to do this but this is actually a valid case sometimes
|
||||
// (eg. loading an asset bundle with a scene containing a scene context when inside unity editor)
|
||||
//#if UNITY_EDITOR
|
||||
//Assert.That(PrefabUtility.GetPrefabType(installerPrefab.gameObject) == PrefabType.Prefab,
|
||||
//"Found non-prefab with name '{0}' in the InstallerPrefabs property of Context '{1}'. You should use the property 'Installer' for this instead",
|
||||
//installerPrefab.name, this.name);
|
||||
//#endif
|
||||
Assert.That(installerPrefab.GetComponent<MonoInstaller>() != null,
|
||||
"Expected to find component with type 'MonoInstaller' on given installer prefab '{0}'", installerPrefab.name);
|
||||
}
|
||||
}
|
||||
|
||||
protected void InstallInstallers()
|
||||
{
|
||||
InstallInstallers(
|
||||
_normalInstallers, _normalInstallerTypes, _scriptableObjectInstallers, _monoInstallers, _installerPrefabs);
|
||||
}
|
||||
|
||||
protected void InstallInstallers(
|
||||
List<InstallerBase> normalInstallers,
|
||||
List<Type> normalInstallerTypes,
|
||||
List<ScriptableObjectInstaller> scriptableObjectInstallers,
|
||||
List<MonoInstaller> installers,
|
||||
List<MonoInstaller> installerPrefabs)
|
||||
{
|
||||
CheckInstallerPrefabTypes(installers, installerPrefabs);
|
||||
|
||||
// Ideally we would just have one flat list of all the installers
|
||||
// since that way the user has complete control over the order, but
|
||||
// that's not possible since Unity does not allow serializing lists of interfaces
|
||||
// (and it has to be an inteface since the scriptable object installers only share
|
||||
// the interface)
|
||||
//
|
||||
// So the best we can do is have a hard-coded order in terms of the installer type
|
||||
//
|
||||
// The order is:
|
||||
// - Normal installers given directly via code
|
||||
// - ScriptableObject installers
|
||||
// - MonoInstallers in the scene
|
||||
// - Prefab Installers
|
||||
//
|
||||
// We put ScriptableObject installers before the MonoInstallers because
|
||||
// ScriptableObjectInstallers are often used for settings (including settings
|
||||
// that are injected into other installers like MonoInstallers)
|
||||
|
||||
var allInstallers = normalInstallers.Cast<IInstaller>()
|
||||
.Concat(scriptableObjectInstallers.Cast<IInstaller>())
|
||||
.Concat(installers.Cast<IInstaller>()).ToList();
|
||||
|
||||
foreach (var installerPrefab in installerPrefabs)
|
||||
{
|
||||
Assert.IsNotNull(installerPrefab, "Found null installer prefab in '{0}'", GetType());
|
||||
|
||||
GameObject installerGameObject;
|
||||
|
||||
#if ZEN_INTERNAL_PROFILING
|
||||
using (ProfileTimers.CreateTimedBlock("GameObject.Instantiate"))
|
||||
#endif
|
||||
{
|
||||
installerGameObject = GameObject.Instantiate(installerPrefab.gameObject);
|
||||
}
|
||||
|
||||
installerGameObject.transform.SetParent(transform, false);
|
||||
var installer = installerGameObject.GetComponent<MonoInstaller>();
|
||||
|
||||
Assert.IsNotNull(installer, "Could not find installer component on prefab '{0}'", installerPrefab.name);
|
||||
|
||||
allInstallers.Add(installer);
|
||||
}
|
||||
|
||||
foreach (var installerType in normalInstallerTypes)
|
||||
{
|
||||
var installer = (InstallerBase)Container.Instantiate(installerType);
|
||||
|
||||
#if ZEN_INTERNAL_PROFILING
|
||||
using (ProfileTimers.CreateTimedBlock("User Code"))
|
||||
#endif
|
||||
{
|
||||
installer.InstallBindings();
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var installer in allInstallers)
|
||||
{
|
||||
Assert.IsNotNull(installer,
|
||||
"Found null installer in '{0}'", GetType());
|
||||
|
||||
Container.Inject(installer);
|
||||
|
||||
#if ZEN_INTERNAL_PROFILING
|
||||
using (ProfileTimers.CreateTimedBlock("User Code"))
|
||||
#endif
|
||||
{
|
||||
installer.InstallBindings();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void InstallSceneBindings(List<MonoBehaviour> injectableMonoBehaviours)
|
||||
{
|
||||
foreach (var binding in injectableMonoBehaviours.OfType<ZenjectBinding>())
|
||||
{
|
||||
if (binding == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (binding.Context == null || (binding.UseSceneContext && this is SceneContext))
|
||||
{
|
||||
binding.Context = this;
|
||||
}
|
||||
}
|
||||
|
||||
// We'd prefer to use GameObject.FindObjectsOfType<ZenjectBinding>() here
|
||||
// instead but that doesn't find inactive gameobjects
|
||||
// TODO: Consider changing this
|
||||
// Maybe ZenjectBinding could add itself to a registry class on Awake/OnEnable
|
||||
// then we could avoid calling the slow Resources.FindObjectsOfTypeAll here
|
||||
foreach (var binding in Resources.FindObjectsOfTypeAll<ZenjectBinding>())
|
||||
{
|
||||
if (binding == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// This is necessary for cases where the ZenjectBinding is inside a GameObjectContext
|
||||
// since it won't be caught in the other loop above
|
||||
if (this is SceneContext)
|
||||
{
|
||||
if (binding.Context == null && binding.UseSceneContext
|
||||
&& binding.gameObject.scene == gameObject.scene)
|
||||
{
|
||||
binding.Context = this;
|
||||
}
|
||||
}
|
||||
|
||||
if (binding.Context == this)
|
||||
{
|
||||
InstallZenjectBinding(binding);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void InstallZenjectBinding(ZenjectBinding binding)
|
||||
{
|
||||
if (!binding.enabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (binding.Components == null || binding.Components.IsEmpty())
|
||||
{
|
||||
Log.Warn("Found empty list of components on ZenjectBinding on object '{0}'", binding.name);
|
||||
return;
|
||||
}
|
||||
|
||||
string identifier = null;
|
||||
|
||||
if (binding.Identifier.Trim().Length > 0)
|
||||
{
|
||||
identifier = binding.Identifier;
|
||||
}
|
||||
|
||||
foreach (var component in binding.Components)
|
||||
{
|
||||
var bindType = binding.BindType;
|
||||
|
||||
if (component == null)
|
||||
{
|
||||
Log.Warn("Found null component in ZenjectBinding on object '{0}'", binding.name);
|
||||
continue;
|
||||
}
|
||||
|
||||
var componentType = component.GetType();
|
||||
|
||||
switch (bindType)
|
||||
{
|
||||
case ZenjectBinding.BindTypes.Self:
|
||||
{
|
||||
Container.Bind(componentType).WithId(identifier).FromInstance(component);
|
||||
break;
|
||||
}
|
||||
case ZenjectBinding.BindTypes.BaseType:
|
||||
{
|
||||
Container.Bind(componentType.BaseType()).WithId(identifier).FromInstance(component);
|
||||
break;
|
||||
}
|
||||
case ZenjectBinding.BindTypes.AllInterfaces:
|
||||
{
|
||||
Container.Bind(componentType.Interfaces()).WithId(identifier).FromInstance(component);
|
||||
break;
|
||||
}
|
||||
case ZenjectBinding.BindTypes.AllInterfacesAndSelf:
|
||||
{
|
||||
Container.Bind(componentType.Interfaces().Concat(new[] { componentType }).ToArray()).WithId(identifier).FromInstance(component);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
throw Assert.CreateException();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract void GetInjectableMonoBehaviours(List<MonoBehaviour> components);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user