#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 _scriptableObjectInstallers = new List(); [FormerlySerializedAs("Installers")] [FormerlySerializedAs("_installers")] [SerializeField] List _monoInstallers = new List(); [SerializeField] List _installerPrefabs = new List(); List _normalInstallers = new List(); List _normalInstallerTypes = new List(); public IEnumerable Installers { get { return _monoInstallers; } set { _monoInstallers.Clear(); _monoInstallers.AddRange(value); } } public IEnumerable InstallerPrefabs { get { return _installerPrefabs; } set { _installerPrefabs.Clear(); _installerPrefabs.AddRange(value); } } public IEnumerable ScriptableObjectInstallers { get { return _scriptableObjectInstallers; } set { _scriptableObjectInstallers.Clear(); _scriptableObjectInstallers.AddRange(value); } } // Unlike other installer types this has to be set through code public IEnumerable NormalInstallerTypes { get { return _normalInstallerTypes; } set { Assert.That(value.All(x => x != null && x.DerivesFrom())); _normalInstallerTypes.Clear(); _normalInstallerTypes.AddRange(value); } } // Unlike other installer types this has to be set through code public IEnumerable NormalInstallers { get { return _normalInstallers; } set { _normalInstallers.Clear(); _normalInstallers.AddRange(value); } } public abstract DiContainer Container { get; } public abstract IEnumerable GetRootGameObjects(); public void AddNormalInstallerType(Type installerType) { Assert.IsNotNull(installerType); Assert.That(installerType.DerivesFrom()); _normalInstallerTypes.Add(installerType); } public void AddNormalInstaller(InstallerBase installer) { _normalInstallers.Add(installer); } void CheckInstallerPrefabTypes(List installers, List 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() != 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 normalInstallers, List normalInstallerTypes, List scriptableObjectInstallers, List installers, List 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() .Concat(scriptableObjectInstallers.Cast()) .Concat(installers.Cast()).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(); 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 injectableMonoBehaviours) { foreach (var binding in injectableMonoBehaviours.OfType()) { if (binding == null) { continue; } if (binding.Context == null || (binding.UseSceneContext && this is SceneContext)) { binding.Context = this; } } // We'd prefer to use GameObject.FindObjectsOfType() 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()) { 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 components); } } #endif