3580 lines
138 KiB
C#
3580 lines
138 KiB
C#
using System;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using ModestTree;
|
|
using ModestTree.Util;
|
|
using Zenject.Internal;
|
|
#if !NOT_UNITY3D
|
|
using UnityEngine;
|
|
#endif
|
|
|
|
namespace Zenject
|
|
{
|
|
public delegate bool BindingCondition(InjectContext c);
|
|
|
|
// Responsibilities:
|
|
// - Expose methods to configure object graph via BindX() methods
|
|
// - Look up bound values via Resolve() method
|
|
// - Instantiate new values via InstantiateX() methods
|
|
[NoReflectionBaking]
|
|
public class DiContainer : IInstantiator
|
|
{
|
|
readonly Dictionary<Type, IDecoratorProvider> _decorators = new Dictionary<Type, IDecoratorProvider>();
|
|
readonly Dictionary<BindingId, List<ProviderInfo>> _providers = new Dictionary<BindingId, List<ProviderInfo>>();
|
|
|
|
readonly DiContainer[][] _containerLookups = new DiContainer[4][];
|
|
|
|
readonly HashSet<LookupId> _resolvesInProgress = new HashSet<LookupId>();
|
|
readonly HashSet<LookupId> _resolvesTwiceInProgress = new HashSet<LookupId>();
|
|
|
|
readonly LazyInstanceInjector _lazyInjector;
|
|
|
|
readonly SingletonMarkRegistry _singletonMarkRegistry = new SingletonMarkRegistry();
|
|
readonly Queue<BindStatement> _currentBindings = new Queue<BindStatement>();
|
|
readonly List<BindStatement> _childBindings = new List<BindStatement>();
|
|
|
|
readonly HashSet<Type> _validatedTypes = new HashSet<Type>();
|
|
readonly List<IValidatable> _validationQueue = new List<IValidatable>();
|
|
|
|
#if !NOT_UNITY3D
|
|
Transform _contextTransform;
|
|
bool _hasLookedUpContextTransform;
|
|
Transform _inheritedDefaultParent;
|
|
Transform _explicitDefaultParent;
|
|
bool _hasExplicitDefaultParent;
|
|
#endif
|
|
|
|
ZenjectSettings _settings;
|
|
|
|
bool _hasResolvedRoots;
|
|
bool _isFinalizingBinding;
|
|
bool _isValidating;
|
|
bool _isInstalling;
|
|
#if DEBUG || UNITY_EDITOR
|
|
bool _hasDisplayedInstallWarning;
|
|
#endif
|
|
|
|
public DiContainer(
|
|
IEnumerable<DiContainer> parentContainersEnumerable, bool isValidating)
|
|
{
|
|
_isValidating = isValidating;
|
|
|
|
_lazyInjector = new LazyInstanceInjector(this);
|
|
|
|
InstallDefaultBindings();
|
|
FlushBindings();
|
|
Assert.That(_currentBindings.Count == 0);
|
|
|
|
_settings = ZenjectSettings.Default;
|
|
|
|
var selfLookup = new[] { this };
|
|
_containerLookups[(int)InjectSources.Local] = selfLookup;
|
|
|
|
var parentContainers = parentContainersEnumerable.ToArray();
|
|
_containerLookups[(int)InjectSources.Parent] = parentContainers;
|
|
|
|
var ancestorContainers = FlattenInheritanceChain().ToArray();
|
|
|
|
_containerLookups[(int)InjectSources.AnyParent] = ancestorContainers;
|
|
_containerLookups[(int)InjectSources.Any] = selfLookup.Concat(ancestorContainers).ToArray();
|
|
|
|
if (!parentContainers.IsEmpty())
|
|
{
|
|
for (int i = 0; i < parentContainers.Length; i++)
|
|
{
|
|
parentContainers[i].FlushBindings();
|
|
}
|
|
|
|
#if !NOT_UNITY3D
|
|
_inheritedDefaultParent = parentContainers.First().DefaultParent;
|
|
#endif
|
|
|
|
// Make sure to avoid duplicates which could happen if a parent container
|
|
// appears multiple times in the inheritance chain
|
|
foreach (var ancestorContainer in ancestorContainers.Distinct())
|
|
{
|
|
foreach (var binding in ancestorContainer._childBindings)
|
|
{
|
|
if (ShouldInheritBinding(binding, ancestorContainer))
|
|
{
|
|
FinalizeBinding(binding);
|
|
}
|
|
}
|
|
}
|
|
|
|
Assert.That(_currentBindings.Count == 0);
|
|
Assert.That(_childBindings.Count == 0);
|
|
}
|
|
|
|
// Assumed to be configured in a parent container
|
|
var settings = TryResolve<ZenjectSettings>();
|
|
|
|
if (settings != null)
|
|
{
|
|
_settings = settings;
|
|
}
|
|
}
|
|
|
|
public DiContainer(bool isValidating)
|
|
: this(Enumerable.Empty<DiContainer>(), isValidating)
|
|
{
|
|
}
|
|
|
|
public DiContainer()
|
|
: this(Enumerable.Empty<DiContainer>(), false)
|
|
{
|
|
}
|
|
|
|
public DiContainer(DiContainer parentContainer, bool isValidating)
|
|
: this(new [] { parentContainer }, isValidating)
|
|
{
|
|
}
|
|
|
|
public DiContainer(DiContainer parentContainer)
|
|
: this(new [] { parentContainer }, false)
|
|
{
|
|
}
|
|
|
|
public DiContainer(IEnumerable<DiContainer> parentContainers)
|
|
: this(parentContainers, false)
|
|
{
|
|
}
|
|
|
|
// By default the settings will be inherited from parent containers, but can be
|
|
// set explicitly here as well which is useful in particular in unit tests
|
|
// Note however that if you want child containers to use this same value you have
|
|
// to bind it as well
|
|
public ZenjectSettings Settings
|
|
{
|
|
get { return _settings; }
|
|
set
|
|
{
|
|
_settings = value;
|
|
Rebind<ZenjectSettings>().FromInstance(value);
|
|
}
|
|
}
|
|
|
|
internal SingletonMarkRegistry SingletonMarkRegistry
|
|
{
|
|
get { return _singletonMarkRegistry; }
|
|
}
|
|
|
|
public IEnumerable<IProvider> AllProviders
|
|
{
|
|
// Distinct is necessary since the same providers can be used with multiple contracts
|
|
get { return _providers.Values.SelectMany(x => x).Select(x => x.Provider).Distinct(); }
|
|
}
|
|
|
|
void InstallDefaultBindings()
|
|
{
|
|
Bind(typeof(DiContainer), typeof(IInstantiator)).FromInstance(this);
|
|
Bind(typeof(LazyInject<>)).FromMethodUntyped(CreateLazyBinding).Lazy();
|
|
}
|
|
|
|
object CreateLazyBinding(InjectContext context)
|
|
{
|
|
// By cloning it this also means that Ids, optional, etc. are forwarded properly
|
|
var newContext = context.Clone();
|
|
newContext.MemberType = context.MemberType.GenericArguments().Single();
|
|
|
|
var result = Activator.CreateInstance(
|
|
typeof(LazyInject<>)
|
|
.MakeGenericType(newContext.MemberType), this, newContext);
|
|
|
|
if (_isValidating)
|
|
{
|
|
QueueForValidate((IValidatable)result);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
public void QueueForValidate(IValidatable validatable)
|
|
{
|
|
// Don't bother adding to queue if the initial resolve is already completed
|
|
if (!_hasResolvedRoots)
|
|
{
|
|
var concreteType = validatable.GetType();
|
|
|
|
if (!_validatedTypes.Contains(concreteType))
|
|
{
|
|
_validatedTypes.Add(concreteType);
|
|
_validationQueue.Add(validatable);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool ShouldInheritBinding(BindStatement binding, DiContainer ancestorContainer)
|
|
{
|
|
if (binding.BindingInheritanceMethod == BindingInheritanceMethods.CopyIntoAll
|
|
|| binding.BindingInheritanceMethod == BindingInheritanceMethods.MoveIntoAll)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if ((binding.BindingInheritanceMethod == BindingInheritanceMethods.CopyDirectOnly
|
|
|| binding.BindingInheritanceMethod == BindingInheritanceMethods.MoveDirectOnly)
|
|
&& ParentContainers.Contains(ancestorContainer))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
#if !NOT_UNITY3D
|
|
// This might be null in some rare cases like when used in ZenjectUnitTestFixture
|
|
Transform ContextTransform
|
|
{
|
|
get
|
|
{
|
|
if (!_hasLookedUpContextTransform)
|
|
{
|
|
_hasLookedUpContextTransform = true;
|
|
|
|
var context = TryResolve<Context>();
|
|
|
|
if (context != null)
|
|
{
|
|
_contextTransform = context.transform;
|
|
}
|
|
}
|
|
|
|
return _contextTransform;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
// When true, this will throw exceptions whenever we create new game objects
|
|
// This is helpful when used in places like EditorWindowKernel where we can't
|
|
// assume that there is a "scene" to place objects
|
|
public bool AssertOnNewGameObjects
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
#if !NOT_UNITY3D
|
|
|
|
public Transform InheritedDefaultParent
|
|
{
|
|
get { return _inheritedDefaultParent; }
|
|
}
|
|
|
|
public Transform DefaultParent
|
|
{
|
|
get { return _explicitDefaultParent; }
|
|
set
|
|
{
|
|
_explicitDefaultParent = value;
|
|
// Need to use a flag because null is a valid explicit default parent
|
|
_hasExplicitDefaultParent = true;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
public DiContainer[] ParentContainers
|
|
{
|
|
get { return _containerLookups[(int)InjectSources.Parent]; }
|
|
}
|
|
|
|
public DiContainer[] AncestorContainers
|
|
{
|
|
get { return _containerLookups[(int)InjectSources.AnyParent]; }
|
|
}
|
|
|
|
public bool ChecksForCircularDependencies
|
|
{
|
|
get
|
|
{
|
|
#if ZEN_MULTITHREADING
|
|
// When multithreading is supported we can't use a static field to track the lookup
|
|
// TODO: We could look at the inject context though
|
|
return false;
|
|
#else
|
|
return true;
|
|
#endif
|
|
}
|
|
}
|
|
|
|
public bool IsValidating
|
|
{
|
|
get { return _isValidating; }
|
|
}
|
|
|
|
// When this is true, it will log warnings when Resolve or Instantiate
|
|
// methods are called
|
|
// Used to ensure that Resolve and Instantiate methods are not called
|
|
// during bind phase. This is important since Resolve and Instantiate
|
|
// make use of the bindings, so if the bindings are not complete then
|
|
// unexpected behaviour can occur
|
|
public bool IsInstalling
|
|
{
|
|
get { return _isInstalling; }
|
|
set { _isInstalling = value; }
|
|
}
|
|
|
|
public IEnumerable<BindingId> AllContracts
|
|
{
|
|
get
|
|
{
|
|
FlushBindings();
|
|
return _providers.Keys;
|
|
}
|
|
}
|
|
|
|
public void ResolveRoots()
|
|
{
|
|
Assert.That(!_hasResolvedRoots);
|
|
|
|
FlushBindings();
|
|
|
|
ResolveDependencyRoots();
|
|
#if DEBUG
|
|
if (IsValidating && _settings.ValidationRootResolveMethod == RootResolveMethods.All)
|
|
{
|
|
ValidateFullResolve();
|
|
}
|
|
#endif
|
|
|
|
_lazyInjector.LazyInjectAll();
|
|
|
|
if (IsValidating)
|
|
{
|
|
FlushValidationQueue();
|
|
}
|
|
|
|
Assert.That(!_hasResolvedRoots);
|
|
_hasResolvedRoots = true;
|
|
}
|
|
|
|
void ResolveDependencyRoots()
|
|
{
|
|
var rootBindings = new List<BindingId>();
|
|
var rootProviders = new List<ProviderInfo>();
|
|
|
|
foreach (var bindingPair in _providers)
|
|
{
|
|
foreach (var provider in bindingPair.Value)
|
|
{
|
|
if (provider.NonLazy)
|
|
{
|
|
// Save them to a list instead of resolving for them here to account
|
|
// for the rare case where one of the resolves does another binding
|
|
// and therefore changes _providers, causing an exception.
|
|
rootBindings.Add(bindingPair.Key);
|
|
rootProviders.Add(provider);
|
|
}
|
|
}
|
|
}
|
|
|
|
Assert.IsEqual(rootProviders.Count, rootBindings.Count);
|
|
|
|
var instances = ZenPools.SpawnList<object>();
|
|
|
|
try
|
|
{
|
|
for (int i = 0; i < rootProviders.Count; i++)
|
|
{
|
|
var bindId = rootBindings[i];
|
|
var providerInfo = rootProviders[i];
|
|
|
|
using (var context = ZenPools.SpawnInjectContext(this, bindId.Type))
|
|
{
|
|
context.Identifier = bindId.Identifier;
|
|
context.SourceType = InjectSources.Local;
|
|
|
|
// Should this be true? Are there cases where you are ok that NonLazy matches
|
|
// zero providers?
|
|
// Probably better to be false to catch mistakes
|
|
context.Optional = false;
|
|
|
|
instances.Clear();
|
|
|
|
#if ZEN_INTERNAL_PROFILING
|
|
using (ProfileTimers.CreateTimedBlock("DiContainer.Resolve"))
|
|
#endif
|
|
{
|
|
SafeGetInstances(providerInfo, context, instances);
|
|
}
|
|
|
|
// Zero matches might actually be valid in some cases
|
|
//Assert.That(matches.Any());
|
|
}
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
ZenPools.DespawnList(instances);
|
|
}
|
|
}
|
|
|
|
void ValidateFullResolve()
|
|
{
|
|
Assert.That(!_hasResolvedRoots);
|
|
Assert.That(IsValidating);
|
|
|
|
foreach (var bindingId in _providers.Keys.ToList())
|
|
{
|
|
if (!bindingId.Type.IsOpenGenericType())
|
|
{
|
|
using (var context = ZenPools.SpawnInjectContext(this, bindingId.Type))
|
|
{
|
|
context.Identifier = bindingId.Identifier;
|
|
context.SourceType = InjectSources.Local;
|
|
context.Optional = true;
|
|
|
|
ResolveAll(context);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void FlushValidationQueue()
|
|
{
|
|
Assert.That(!_hasResolvedRoots);
|
|
Assert.That(IsValidating);
|
|
|
|
var validatables = new List<IValidatable>();
|
|
|
|
// Repeatedly flush the validation queue until it's empty, to account for
|
|
// cases where calls to Validate() add more objects to the queue
|
|
while (_validationQueue.Any())
|
|
{
|
|
validatables.Clear();
|
|
validatables.AllocFreeAddRange(_validationQueue);
|
|
_validationQueue.Clear();
|
|
|
|
for (int i = 0; i < validatables.Count; i++)
|
|
{
|
|
validatables[i].Validate();
|
|
}
|
|
}
|
|
}
|
|
|
|
public DiContainer CreateSubContainer()
|
|
{
|
|
return CreateSubContainer(_isValidating);
|
|
}
|
|
|
|
public void QueueForInject(object instance)
|
|
{
|
|
_lazyInjector.AddInstance(instance);
|
|
}
|
|
|
|
// Note: this only does anything useful during the injection phase
|
|
// It will inject on the given instance if it hasn't already been injected, but only
|
|
// if the given instance has been queued for inject already by calling QueueForInject
|
|
// In some rare cases this can be useful - for example if you want to add a binding in a
|
|
// a higher level container to a resolve inside a lower level game object context container
|
|
// since in this case you need the game object context to be injected so you can access its
|
|
// Container property
|
|
public T LazyInject<T>(T instance)
|
|
{
|
|
_lazyInjector.LazyInject(instance);
|
|
return instance;
|
|
}
|
|
|
|
DiContainer CreateSubContainer(bool isValidating)
|
|
{
|
|
return new DiContainer(new[] { this }, isValidating);
|
|
}
|
|
|
|
public void RegisterProvider(
|
|
BindingId bindingId, BindingCondition condition, IProvider provider, bool nonLazy)
|
|
{
|
|
var info = new ProviderInfo(provider, condition, nonLazy, this);
|
|
|
|
List<ProviderInfo> providerInfos;
|
|
|
|
if (!_providers.TryGetValue(bindingId, out providerInfos))
|
|
{
|
|
providerInfos = new List<ProviderInfo>();
|
|
_providers.Add(bindingId, providerInfos);
|
|
}
|
|
|
|
providerInfos.Add(info);
|
|
}
|
|
|
|
void GetProviderMatches(
|
|
InjectContext context, List<ProviderInfo> buffer)
|
|
{
|
|
Assert.IsNotNull(context);
|
|
Assert.That(buffer.Count == 0);
|
|
|
|
var allMatches = ZenPools.SpawnList<ProviderInfo>();
|
|
|
|
try
|
|
{
|
|
GetProvidersForContract(
|
|
context.BindingId, context.SourceType, allMatches);
|
|
|
|
for (int i = 0; i < allMatches.Count; i++)
|
|
{
|
|
var match = allMatches[i];
|
|
|
|
if (match.Condition == null || match.Condition(context))
|
|
{
|
|
buffer.Add(match);
|
|
}
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
ZenPools.DespawnList(allMatches);
|
|
}
|
|
}
|
|
|
|
ProviderInfo TryGetUniqueProvider(InjectContext context)
|
|
{
|
|
Assert.IsNotNull(context);
|
|
|
|
var bindingId = context.BindingId;
|
|
var sourceType = context.SourceType;
|
|
|
|
var containerLookups = _containerLookups[(int)sourceType];
|
|
|
|
for (int i = 0; i < containerLookups.Length; i++)
|
|
{
|
|
containerLookups[i].FlushBindings();
|
|
}
|
|
|
|
var localProviders = ZenPools.SpawnList<ProviderInfo>();
|
|
|
|
try
|
|
{
|
|
ProviderInfo selected = null;
|
|
int selectedDistance = Int32.MaxValue;
|
|
bool selectedHasCondition = false;
|
|
bool ambiguousSelection = false;
|
|
|
|
for (int i = 0; i < containerLookups.Length; i++)
|
|
{
|
|
var container = containerLookups[i];
|
|
|
|
int curDistance = GetContainerHeirarchyDistance(container);
|
|
|
|
if (curDistance > selectedDistance)
|
|
{
|
|
// If matching provider was already found lower in the hierarchy => don't search for a new one,
|
|
// because there can't be a better or equal provider in this container.
|
|
continue;
|
|
}
|
|
|
|
localProviders.Clear();
|
|
container.GetLocalProviders(bindingId, localProviders);
|
|
|
|
for (int k = 0; k < localProviders.Count; k++)
|
|
{
|
|
var provider = localProviders[k];
|
|
|
|
bool curHasCondition = provider.Condition != null;
|
|
|
|
if (curHasCondition && !provider.Condition(context))
|
|
{
|
|
// The condition is not satisfied.
|
|
continue;
|
|
}
|
|
|
|
// The distance can't decrease becuase we are iterating over the containers with increasing distance.
|
|
// The distance can't increase because we skip the container if the distance is greater than selected.
|
|
// So the distances are equal and only the condition can help resolving the amiguity.
|
|
Assert.That(selected == null || selectedDistance == curDistance);
|
|
|
|
if (curHasCondition)
|
|
{
|
|
if (selectedHasCondition)
|
|
{
|
|
// Both providers have condition and are on equal depth.
|
|
ambiguousSelection = true;
|
|
}
|
|
else
|
|
{
|
|
// Ambiguity is resolved because a provider with condition was found.
|
|
ambiguousSelection = false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (selectedHasCondition)
|
|
{
|
|
// Selected provider is better because it has condition.
|
|
continue;
|
|
}
|
|
if (selected != null)
|
|
{
|
|
// Both providers don't have a condition and are on equal depth.
|
|
ambiguousSelection = true;
|
|
}
|
|
}
|
|
|
|
if (ambiguousSelection)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
selectedDistance = curDistance;
|
|
selectedHasCondition = curHasCondition;
|
|
selected = provider;
|
|
}
|
|
}
|
|
|
|
if (ambiguousSelection)
|
|
{
|
|
throw Assert.CreateException(
|
|
"Found multiple matches when only one was expected for type '{0}'{1}. Object graph:\n {2}",
|
|
context.MemberType,
|
|
(context.ObjectType == null
|
|
? ""
|
|
: " while building object with type '{0}'".Fmt(context.ObjectType)),
|
|
context.GetObjectGraphString());
|
|
}
|
|
|
|
return selected;
|
|
}
|
|
finally
|
|
{
|
|
ZenPools.DespawnList(localProviders);
|
|
}
|
|
}
|
|
|
|
// Get the full list of ancestor Di Containers, making sure to avoid
|
|
// duplicates and also order them in a breadth-first way
|
|
List<DiContainer> FlattenInheritanceChain()
|
|
{
|
|
var processed = new List<DiContainer>();
|
|
|
|
var containerQueue = new Queue<DiContainer>();
|
|
containerQueue.Enqueue(this);
|
|
|
|
while (containerQueue.Count > 0)
|
|
{
|
|
var current = containerQueue.Dequeue();
|
|
|
|
foreach (var parent in current.ParentContainers)
|
|
{
|
|
if (!processed.Contains(parent))
|
|
{
|
|
processed.Add(parent);
|
|
containerQueue.Enqueue(parent);
|
|
}
|
|
}
|
|
}
|
|
|
|
return processed;
|
|
}
|
|
|
|
void GetLocalProviders(BindingId bindingId, List<ProviderInfo> buffer)
|
|
{
|
|
List<ProviderInfo> localProviders;
|
|
|
|
if (_providers.TryGetValue(bindingId, out localProviders))
|
|
{
|
|
buffer.AllocFreeAddRange(localProviders);
|
|
return;
|
|
}
|
|
|
|
// If we are asking for a List<int>, we should also match for any localProviders that are bound to the open generic type List<>
|
|
// Currently it only matches one and not the other - not totally sure if this is better than returning both
|
|
if (bindingId.Type.IsGenericType() && _providers.TryGetValue(new BindingId(bindingId.Type.GetGenericTypeDefinition(), bindingId.Identifier), out localProviders))
|
|
{
|
|
buffer.AllocFreeAddRange(localProviders);
|
|
}
|
|
|
|
// None found
|
|
}
|
|
|
|
void GetProvidersForContract(
|
|
BindingId bindingId, InjectSources sourceType, List<ProviderInfo> buffer)
|
|
{
|
|
var containerLookups = _containerLookups[(int)sourceType];
|
|
|
|
for (int i = 0; i < containerLookups.Length; i++)
|
|
{
|
|
containerLookups[i].FlushBindings();
|
|
}
|
|
|
|
for (int i = 0; i < containerLookups.Length; i++)
|
|
{
|
|
containerLookups[i].GetLocalProviders(bindingId, buffer);
|
|
}
|
|
}
|
|
|
|
public void Install<TInstaller>()
|
|
where TInstaller : Installer
|
|
{
|
|
Instantiate<TInstaller>().InstallBindings();
|
|
}
|
|
|
|
// Note: You might want to use Installer<> as your base class instead to allow
|
|
// for strongly typed parameters
|
|
public void Install<TInstaller>(object[] extraArgs)
|
|
where TInstaller : Installer
|
|
{
|
|
Instantiate<TInstaller>(extraArgs).InstallBindings();
|
|
}
|
|
|
|
public IList ResolveAll(InjectContext context)
|
|
{
|
|
var buffer = ZenPools.SpawnList<object>();
|
|
|
|
try
|
|
{
|
|
ResolveAll(context, buffer);
|
|
return ReflectionUtil.CreateGenericList(context.MemberType, buffer);
|
|
}
|
|
finally
|
|
{
|
|
ZenPools.DespawnList(buffer);
|
|
}
|
|
}
|
|
|
|
public void ResolveAll(InjectContext context, List<object> buffer)
|
|
{
|
|
#if ZEN_INTERNAL_PROFILING
|
|
using (ProfileTimers.CreateTimedBlock("DiContainer.Resolve"))
|
|
#endif
|
|
{
|
|
Assert.IsNotNull(context);
|
|
// Note that different types can map to the same provider (eg. a base type to a concrete class and a concrete class to itself)
|
|
|
|
FlushBindings();
|
|
CheckForInstallWarning(context);
|
|
|
|
var matches = ZenPools.SpawnList<ProviderInfo>();
|
|
|
|
try
|
|
{
|
|
GetProviderMatches(context, matches);
|
|
|
|
if (matches.Count == 0)
|
|
{
|
|
if (!context.Optional)
|
|
{
|
|
throw Assert.CreateException(
|
|
"Could not find required dependency with type '{0}' Object graph:\n {1}", context.MemberType, context.GetObjectGraphString());
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
var instances = ZenPools.SpawnList<object>();
|
|
var allInstances = ZenPools.SpawnList<object>();
|
|
|
|
try
|
|
{
|
|
for (int i = 0; i < matches.Count; i++)
|
|
{
|
|
var match = matches[i];
|
|
|
|
instances.Clear();
|
|
SafeGetInstances(match, context, instances);
|
|
|
|
for (int k = 0; k < instances.Count; k++)
|
|
{
|
|
allInstances.Add(instances[k]);
|
|
}
|
|
}
|
|
|
|
if (allInstances.Count == 0 && !context.Optional)
|
|
{
|
|
throw Assert.CreateException(
|
|
"Could not find required dependency with type '{0}'. Found providers but they returned zero results!", context.MemberType);
|
|
}
|
|
|
|
if (IsValidating)
|
|
{
|
|
for (int i = 0; i < allInstances.Count; i++)
|
|
{
|
|
var instance = allInstances[i];
|
|
|
|
if (instance is ValidationMarker)
|
|
{
|
|
allInstances[i] = context.MemberType.GetDefaultValue();
|
|
}
|
|
}
|
|
}
|
|
|
|
buffer.AllocFreeAddRange(allInstances);
|
|
}
|
|
finally
|
|
{
|
|
ZenPools.DespawnList(instances);
|
|
ZenPools.DespawnList(allInstances);
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
ZenPools.DespawnList(matches);
|
|
}
|
|
}
|
|
}
|
|
|
|
void CheckForInstallWarning(InjectContext context)
|
|
{
|
|
if (!_settings.DisplayWarningWhenResolvingDuringInstall)
|
|
{
|
|
return;
|
|
}
|
|
|
|
Assert.IsNotNull(context);
|
|
|
|
#if DEBUG || UNITY_EDITOR
|
|
if (!_isInstalling)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (_hasDisplayedInstallWarning)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (context == null)
|
|
{
|
|
// No way to tell whether this is ok or not so just assume ok
|
|
return;
|
|
}
|
|
|
|
#if UNITY_EDITOR
|
|
if (context.MemberType.DerivesFrom<Context>())
|
|
{
|
|
// This happens when getting default transform parent so ok
|
|
return;
|
|
}
|
|
#endif
|
|
if (IsValidating && TypeAnalyzer.ShouldAllowDuringValidation(context.MemberType))
|
|
{
|
|
return;
|
|
}
|
|
|
|
var rootContext = context.ParentContextsAndSelf.Last();
|
|
|
|
if (rootContext.MemberType.DerivesFrom<IInstaller>())
|
|
{
|
|
// Resolving/instantiating/injecting installers is valid during install phase
|
|
return;
|
|
}
|
|
|
|
_hasDisplayedInstallWarning = true;
|
|
|
|
// Feel free to comment this out if you are comfortable with this practice
|
|
Log.Warn("Zenject Warning: It is bad practice to call Inject/Resolve/Instantiate before all the Installers have completed! This is important to ensure that all bindings have properly been installed in case they are needed when injecting/instantiating/resolving. Detected when operating on type '{0}'. If you don't care about this, you can disable this warning by setting flag 'ZenjectSettings.DisplayWarningWhenResolvingDuringInstall' to false (see docs for details on ZenjectSettings).", rootContext.MemberType);
|
|
#endif
|
|
}
|
|
|
|
// Returns the concrete type that would be returned with Resolve<T>
|
|
// without actually instantiating it
|
|
// This is safe to use within installers
|
|
public Type ResolveType<T>()
|
|
{
|
|
return ResolveType(typeof(T));
|
|
}
|
|
|
|
// Returns the concrete type that would be returned with Resolve(type)
|
|
// without actually instantiating it
|
|
// This is safe to use within installers
|
|
public Type ResolveType(Type type)
|
|
{
|
|
using (var context = ZenPools.SpawnInjectContext(this, type))
|
|
{
|
|
return ResolveType(context);
|
|
}
|
|
}
|
|
|
|
// Returns the concrete type that would be returned with Resolve(context)
|
|
// without actually instantiating it
|
|
// This is safe to use within installers
|
|
public Type ResolveType(InjectContext context)
|
|
{
|
|
Assert.IsNotNull(context);
|
|
|
|
FlushBindings();
|
|
|
|
var providerInfo = TryGetUniqueProvider(context);
|
|
|
|
if (providerInfo == null)
|
|
{
|
|
throw Assert.CreateException(
|
|
"Unable to resolve {0}{1}. Object graph:\n{2}", context.BindingId,
|
|
(context.ObjectType == null ? "" : " while building object with type '{0}'".Fmt(context.ObjectType)),
|
|
context.GetObjectGraphString());
|
|
}
|
|
|
|
return providerInfo.Provider.GetInstanceType(context);
|
|
}
|
|
|
|
public List<Type> ResolveTypeAll(Type type)
|
|
{
|
|
return ResolveTypeAll(type, null);
|
|
}
|
|
|
|
public List<Type> ResolveTypeAll(Type type, object identifier)
|
|
{
|
|
using (var context = ZenPools.SpawnInjectContext(this, type))
|
|
{
|
|
context.Identifier = identifier;
|
|
return ResolveTypeAll(context);
|
|
}
|
|
}
|
|
|
|
// Returns all the types that would be returned if ResolveAll was called with the given values
|
|
public List<Type> ResolveTypeAll(InjectContext context)
|
|
{
|
|
Assert.IsNotNull(context);
|
|
|
|
FlushBindings();
|
|
|
|
var matches = ZenPools.SpawnList<ProviderInfo>();
|
|
|
|
try
|
|
{
|
|
GetProviderMatches(context, matches);
|
|
|
|
if (matches.Count > 0 )
|
|
{
|
|
return matches.Select(
|
|
x => x.Provider.GetInstanceType(context))
|
|
.Where(x => x != null).ToList();
|
|
}
|
|
|
|
return new List<Type>();
|
|
}
|
|
finally
|
|
{
|
|
ZenPools.DespawnList(matches);
|
|
}
|
|
}
|
|
|
|
public object Resolve(BindingId id)
|
|
{
|
|
using (var context = ZenPools.SpawnInjectContext(this, id.Type))
|
|
{
|
|
context.Identifier = id.Identifier;
|
|
return Resolve(context);
|
|
}
|
|
}
|
|
|
|
public object Resolve(InjectContext context)
|
|
{
|
|
#if ZEN_INTERNAL_PROFILING
|
|
using (ProfileTimers.CreateTimedBlock("DiContainer.Resolve"))
|
|
#endif
|
|
{
|
|
// Note: context.Container is not necessarily equal to this, since
|
|
// you can have some lookups recurse to parent containers
|
|
Assert.IsNotNull(context);
|
|
|
|
var memberType = context.MemberType;
|
|
|
|
FlushBindings();
|
|
CheckForInstallWarning(context);
|
|
|
|
var lookupContext = context;
|
|
|
|
// The context used for lookups is always the same as the given context EXCEPT for LazyInject<>
|
|
// In CreateLazyBinding above, we forward the context to a new instance of LazyInject<>
|
|
// The problem is, we want the binding for Bind(typeof(LazyInject<>)) to always match even
|
|
// for members that are marked for a specific ID, so we need to discard the identifier
|
|
// for this one particular case
|
|
if (memberType.IsGenericType() && memberType.GetGenericTypeDefinition() == typeof(LazyInject<>))
|
|
{
|
|
lookupContext = context.Clone();
|
|
lookupContext.Identifier = null;
|
|
lookupContext.SourceType = InjectSources.Local;
|
|
lookupContext.Optional = false;
|
|
}
|
|
|
|
var providerInfo = TryGetUniqueProvider(lookupContext);
|
|
|
|
if (providerInfo == null)
|
|
{
|
|
// If it's an array try matching to multiple values using its array type
|
|
if (memberType.IsArray && memberType.GetArrayRank() == 1)
|
|
{
|
|
var subType = memberType.GetElementType();
|
|
|
|
var subContext = context.Clone();
|
|
subContext.MemberType = subType;
|
|
// By making this optional this means that all injected fields of type T[]
|
|
// will pass validation, which could be error prone, but I think this is better
|
|
// than always requiring that they explicitly mark their array types as optional
|
|
subContext.Optional = true;
|
|
|
|
var results = ZenPools.SpawnList<object>();
|
|
|
|
try
|
|
{
|
|
ResolveAll(subContext, results);
|
|
return ReflectionUtil.CreateArray(subContext.MemberType, results);
|
|
}
|
|
finally
|
|
{
|
|
ZenPools.DespawnList(results);
|
|
}
|
|
}
|
|
|
|
// If it's a generic list then try matching multiple instances to its generic type
|
|
if (memberType.IsGenericType()
|
|
&& (memberType.GetGenericTypeDefinition() == typeof(List<>)
|
|
|| memberType.GetGenericTypeDefinition() == typeof(IList<>)
|
|
#if NET_4_6
|
|
|| memberType.GetGenericTypeDefinition() == typeof(IReadOnlyList<>)
|
|
#endif
|
|
|| memberType.GetGenericTypeDefinition() == typeof(IEnumerable<>)))
|
|
{
|
|
var subType = memberType.GenericArguments().Single();
|
|
|
|
var subContext = context.Clone();
|
|
subContext.MemberType = subType;
|
|
// By making this optional this means that all injected fields of type List<>
|
|
// will pass validation, which could be error prone, but I think this is better
|
|
// than always requiring that they explicitly mark their list types as optional
|
|
subContext.Optional = true;
|
|
|
|
return ResolveAll(subContext);
|
|
}
|
|
|
|
if (context.Optional)
|
|
{
|
|
return context.FallBackValue;
|
|
}
|
|
|
|
throw Assert.CreateException("Unable to resolve '{0}'{1}. Object graph:\n{2}", context.BindingId,
|
|
(context.ObjectType == null ? "" : " while building object with type '{0}'".Fmt(context.ObjectType)),
|
|
context.GetObjectGraphString());
|
|
}
|
|
|
|
var instances = ZenPools.SpawnList<object>();
|
|
|
|
try
|
|
{
|
|
SafeGetInstances(providerInfo, context, instances);
|
|
|
|
if (instances.Count == 0)
|
|
{
|
|
if (context.Optional)
|
|
{
|
|
return context.FallBackValue;
|
|
}
|
|
|
|
throw Assert.CreateException(
|
|
"Unable to resolve '{0}'{1}. Object graph:\n{2}", context.BindingId,
|
|
(context.ObjectType == null
|
|
? ""
|
|
: " while building object with type '{0}'".Fmt(context.ObjectType)),
|
|
context.GetObjectGraphString());
|
|
}
|
|
|
|
if (instances.Count() > 1)
|
|
{
|
|
throw Assert.CreateException(
|
|
"Provider returned multiple instances when only one was expected! While resolving '{0}'{1}. Object graph:\n{2}", context.BindingId,
|
|
(context.ObjectType == null
|
|
? ""
|
|
: " while building object with type '{0}'".Fmt(context.ObjectType)),
|
|
context.GetObjectGraphString());
|
|
}
|
|
|
|
return instances.First();
|
|
}
|
|
finally
|
|
{
|
|
ZenPools.DespawnList(instances);
|
|
}
|
|
}
|
|
}
|
|
|
|
void SafeGetInstances(ProviderInfo providerInfo, InjectContext context, List<object> instances)
|
|
{
|
|
Assert.IsNotNull(context);
|
|
|
|
var provider = providerInfo.Provider;
|
|
|
|
if (ChecksForCircularDependencies)
|
|
{
|
|
var lookupId = ZenPools.SpawnLookupId(provider, context.BindingId);
|
|
|
|
try
|
|
{
|
|
// Use the container associated with the provider to address some rare cases
|
|
// which would otherwise result in an infinite loop. Like this:
|
|
// Container.Bind<ICharacter>().FromComponentInNewPrefab(Prefab).AsTransient()
|
|
// With the prefab being a GameObjectContext containing a script that has a
|
|
// ICharacter dependency. In this case, we would otherwise use the _resolvesInProgress
|
|
// associated with the GameObjectContext container, which will allow the recursive
|
|
// lookup, which will trigger another GameObjectContext and container (since it is
|
|
// transient) and the process continues indefinitely
|
|
var providerContainer = providerInfo.Container;
|
|
|
|
if (providerContainer._resolvesTwiceInProgress.Contains(lookupId))
|
|
{
|
|
// Allow one before giving up so that you can do circular dependencies via postinject or fields
|
|
throw Assert.CreateException(
|
|
"Circular dependency detected! Object graph:\n {0}", context.GetObjectGraphString());
|
|
}
|
|
|
|
bool twice = false;
|
|
if (!providerContainer._resolvesInProgress.Add(lookupId))
|
|
{
|
|
bool added = providerContainer._resolvesTwiceInProgress.Add(lookupId);
|
|
Assert.That(added);
|
|
twice = true;
|
|
}
|
|
|
|
try
|
|
{
|
|
GetDecoratedInstances(provider, context, instances);
|
|
}
|
|
finally
|
|
{
|
|
if (twice)
|
|
{
|
|
bool removed = providerContainer._resolvesTwiceInProgress.Remove(lookupId);
|
|
Assert.That(removed);
|
|
}
|
|
else
|
|
{
|
|
bool removed = providerContainer._resolvesInProgress.Remove(lookupId);
|
|
Assert.That(removed);
|
|
}
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
ZenPools.DespawnLookupId(lookupId);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
GetDecoratedInstances(provider, context, instances);
|
|
}
|
|
}
|
|
|
|
public DecoratorToChoiceFromBinder<TContract> Decorate<TContract>()
|
|
{
|
|
var bindStatement = StartBinding();
|
|
var bindInfo = bindStatement.SpawnBindInfo();
|
|
|
|
bindInfo.ContractTypes.Add(typeof(IFactory<TContract, TContract>));
|
|
|
|
var factoryBindInfo = new FactoryBindInfo(
|
|
typeof(PlaceholderFactory<TContract, TContract>));
|
|
|
|
bindStatement.SetFinalizer(
|
|
new PlaceholderFactoryBindingFinalizer<TContract>(
|
|
bindInfo, factoryBindInfo));
|
|
|
|
var bindId = Guid.NewGuid();
|
|
|
|
bindInfo.Identifier = bindId;
|
|
|
|
IDecoratorProvider decoratorProvider;
|
|
|
|
if (!_decorators.TryGetValue(typeof(TContract), out decoratorProvider))
|
|
{
|
|
decoratorProvider = new DecoratorProvider<TContract>(this);
|
|
_decorators.Add(typeof(TContract), decoratorProvider);
|
|
}
|
|
|
|
((DecoratorProvider<TContract>)decoratorProvider).AddFactoryId(bindId);
|
|
|
|
return new DecoratorToChoiceFromBinder<TContract>(
|
|
this, bindInfo, factoryBindInfo);
|
|
}
|
|
|
|
void GetDecoratedInstances(
|
|
IProvider provider, InjectContext context, List<object> buffer)
|
|
{
|
|
// TODO: This is flawed since it doesn't allow binding new decorators in subcontainers
|
|
var decoratorProvider = TryGetDecoratorProvider(context.BindingId.Type);
|
|
|
|
if (decoratorProvider != null)
|
|
{
|
|
decoratorProvider.GetAllInstances(provider, context, buffer);
|
|
return;
|
|
}
|
|
|
|
provider.GetAllInstances(context, buffer);
|
|
}
|
|
|
|
IDecoratorProvider TryGetDecoratorProvider(Type contractType)
|
|
{
|
|
IDecoratorProvider decoratorProvider;
|
|
|
|
if (_decorators.TryGetValue(contractType, out decoratorProvider))
|
|
{
|
|
return decoratorProvider;
|
|
}
|
|
|
|
var ancestorContainers = AncestorContainers;
|
|
|
|
for (int i = 0; i < ancestorContainers.Length; i++)
|
|
{
|
|
if (ancestorContainers[i]._decorators.TryGetValue(contractType, out decoratorProvider))
|
|
{
|
|
return decoratorProvider;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
int GetContainerHeirarchyDistance(DiContainer container)
|
|
{
|
|
return GetContainerHeirarchyDistance(container, 0).Value;
|
|
}
|
|
|
|
int? GetContainerHeirarchyDistance(DiContainer container, int depth)
|
|
{
|
|
if (container == this)
|
|
{
|
|
return depth;
|
|
}
|
|
|
|
int? result = null;
|
|
|
|
var parentContainers = ParentContainers;
|
|
|
|
for (int i = 0; i < parentContainers.Length; i++)
|
|
{
|
|
var parent = parentContainers[i];
|
|
|
|
var distance = parent.GetContainerHeirarchyDistance(container, depth + 1);
|
|
|
|
if (distance.HasValue && (!result.HasValue || distance.Value < result.Value))
|
|
{
|
|
result = distance;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
public IEnumerable<Type> GetDependencyContracts<TContract>()
|
|
{
|
|
return GetDependencyContracts(typeof(TContract));
|
|
}
|
|
|
|
public IEnumerable<Type> GetDependencyContracts(Type contract)
|
|
{
|
|
FlushBindings();
|
|
|
|
var info = TypeAnalyzer.TryGetInfo(contract);
|
|
|
|
if (info != null)
|
|
{
|
|
foreach (var injectMember in info.AllInjectables)
|
|
{
|
|
yield return injectMember.MemberType;
|
|
}
|
|
}
|
|
}
|
|
|
|
object InstantiateInternal(
|
|
Type concreteType, bool autoInject, List<TypeValuePair> extraArgs, InjectContext context, object concreteIdentifier)
|
|
{
|
|
#if !NOT_UNITY3D
|
|
Assert.That(!concreteType.DerivesFrom<Component>(),
|
|
"Error occurred while instantiating object of type '{0}'. Instantiator should not be used to create new mono behaviours. Must use InstantiatePrefabForComponent, InstantiatePrefab, or InstantiateComponent.", concreteType);
|
|
#endif
|
|
|
|
Assert.That(!concreteType.IsAbstract(), "Expected type '{0}' to be non-abstract", concreteType);
|
|
|
|
FlushBindings();
|
|
CheckForInstallWarning(context);
|
|
|
|
var typeInfo = TypeAnalyzer.TryGetInfo(concreteType);
|
|
|
|
Assert.IsNotNull(typeInfo, "Tried to create type '{0}' but could not find type information", concreteType);
|
|
|
|
bool allowDuringValidation = IsValidating && TypeAnalyzer.ShouldAllowDuringValidation(concreteType);
|
|
|
|
object newObj;
|
|
|
|
#if !NOT_UNITY3D
|
|
if (concreteType.DerivesFrom<ScriptableObject>())
|
|
{
|
|
Assert.That(typeInfo.InjectConstructor.Parameters.Length == 0,
|
|
"Found constructor parameters on ScriptableObject type '{0}'. This is not allowed. Use an [Inject] method or fields instead.");
|
|
|
|
if (!IsValidating || allowDuringValidation)
|
|
{
|
|
newObj = ScriptableObject.CreateInstance(concreteType);
|
|
}
|
|
else
|
|
{
|
|
newObj = new ValidationMarker(concreteType);
|
|
}
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
Assert.IsNotNull(typeInfo.InjectConstructor.Factory,
|
|
"More than one (or zero) constructors found for type '{0}' when creating dependencies. Use one [Inject] attribute to specify which to use.", concreteType);
|
|
|
|
// Make a copy since we remove from it below
|
|
var paramValues = ZenPools.SpawnArray<object>(typeInfo.InjectConstructor.Parameters.Length);
|
|
|
|
try
|
|
{
|
|
for (int i = 0; i < typeInfo.InjectConstructor.Parameters.Length; i++)
|
|
{
|
|
var injectInfo = typeInfo.InjectConstructor.Parameters[i];
|
|
|
|
object value;
|
|
|
|
if (!InjectUtil.PopValueWithType(
|
|
extraArgs, injectInfo.MemberType, out value))
|
|
{
|
|
using (var subContext = ZenPools.SpawnInjectContext(
|
|
this, injectInfo, context, null, concreteType, concreteIdentifier))
|
|
{
|
|
value = Resolve(subContext);
|
|
}
|
|
}
|
|
|
|
if (value == null || value is ValidationMarker)
|
|
{
|
|
paramValues[i] = injectInfo.MemberType.GetDefaultValue();
|
|
}
|
|
else
|
|
{
|
|
paramValues[i] = value;
|
|
}
|
|
}
|
|
|
|
if (!IsValidating || allowDuringValidation)
|
|
{
|
|
//ModestTree.Log.Debug("Zenject: Instantiating type '{0}'", concreteType);
|
|
try
|
|
{
|
|
#if ZEN_INTERNAL_PROFILING
|
|
using (ProfileTimers.CreateTimedBlock("User Code"))
|
|
#endif
|
|
#if UNITY_EDITOR
|
|
using (ProfileBlock.Start("{0}.{1}()", concreteType, concreteType.Name))
|
|
#endif
|
|
{
|
|
newObj = typeInfo.InjectConstructor.Factory(paramValues);
|
|
}
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
throw Assert.CreateException(
|
|
e, "Error occurred while instantiating object with type '{0}'", concreteType);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
newObj = new ValidationMarker(concreteType);
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
ZenPools.DespawnArray(paramValues);
|
|
}
|
|
}
|
|
|
|
if (autoInject)
|
|
{
|
|
InjectExplicit(newObj, concreteType, extraArgs, context, concreteIdentifier);
|
|
|
|
if (extraArgs.Count > 0 && !(newObj is ValidationMarker))
|
|
{
|
|
throw Assert.CreateException(
|
|
"Passed unnecessary parameters when injecting into type '{0}'. \nExtra Parameters: {1}\nObject graph:\n{2}",
|
|
newObj.GetType(), String.Join(",", extraArgs.Select(x => x.Type.PrettyName()).ToArray()), context.GetObjectGraphString());
|
|
}
|
|
}
|
|
|
|
#if DEBUG
|
|
if (IsValidating && newObj is IValidatable)
|
|
{
|
|
QueueForValidate((IValidatable)newObj);
|
|
}
|
|
#endif
|
|
|
|
return newObj;
|
|
}
|
|
|
|
// InjectExplicit is only necessary when you want to inject null values into your object
|
|
// otherwise you can just use Inject()
|
|
// Note: Any arguments that are used will be removed from extraArgMap
|
|
public void InjectExplicit(object injectable, List<TypeValuePair> extraArgs)
|
|
{
|
|
Type injectableType;
|
|
|
|
if (injectable is ValidationMarker)
|
|
{
|
|
injectableType = ((ValidationMarker)injectable).MarkedType;
|
|
}
|
|
else
|
|
{
|
|
injectableType = injectable.GetType();
|
|
}
|
|
|
|
InjectExplicit(
|
|
injectable,
|
|
injectableType,
|
|
extraArgs,
|
|
new InjectContext(this, injectableType, null),
|
|
null);
|
|
}
|
|
|
|
public void InjectExplicit(
|
|
object injectable, Type injectableType,
|
|
List<TypeValuePair> extraArgs, InjectContext context, object concreteIdentifier)
|
|
{
|
|
#if ZEN_INTERNAL_PROFILING
|
|
using (ProfileTimers.CreateTimedBlock("DiContainer.Inject"))
|
|
#endif
|
|
{
|
|
if (IsValidating)
|
|
{
|
|
var marker = injectable as ValidationMarker;
|
|
|
|
if (marker != null && marker.InstantiateFailed)
|
|
{
|
|
// Do nothing in this case because it already failed and so there
|
|
// could be many knock-on errors that aren't related to the user
|
|
return;
|
|
}
|
|
|
|
if (_settings.ValidationErrorResponse == ValidationErrorResponses.Throw)
|
|
{
|
|
InjectExplicitInternal(
|
|
injectable, injectableType, extraArgs, context, concreteIdentifier);
|
|
}
|
|
else
|
|
{
|
|
// In this case, just log it and continue to print out multiple validation errors
|
|
// at once
|
|
try
|
|
{
|
|
InjectExplicitInternal(injectable, injectableType, extraArgs, context, concreteIdentifier);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Log.ErrorException(e);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
InjectExplicitInternal(injectable, injectableType, extraArgs, context, concreteIdentifier);
|
|
}
|
|
}
|
|
}
|
|
|
|
void CallInjectMethodsTopDown(
|
|
object injectable, Type injectableType,
|
|
InjectTypeInfo typeInfo, List<TypeValuePair> extraArgs,
|
|
InjectContext context, object concreteIdentifier, bool isDryRun)
|
|
{
|
|
if (typeInfo.BaseTypeInfo != null)
|
|
{
|
|
CallInjectMethodsTopDown(
|
|
injectable, injectableType, typeInfo.BaseTypeInfo, extraArgs,
|
|
context, concreteIdentifier, isDryRun);
|
|
}
|
|
|
|
for (int i = 0; i < typeInfo.InjectMethods.Length; i++)
|
|
{
|
|
var method = typeInfo.InjectMethods[i];
|
|
var paramValues = ZenPools.SpawnArray<object>(method.Parameters.Length);
|
|
|
|
try
|
|
{
|
|
for (int k = 0; k < method.Parameters.Length; k++)
|
|
{
|
|
var injectInfo = method.Parameters[k];
|
|
|
|
object value;
|
|
|
|
if (!InjectUtil.PopValueWithType(extraArgs, injectInfo.MemberType, out value))
|
|
{
|
|
using (var subContext = ZenPools.SpawnInjectContext(
|
|
this, injectInfo, context, injectable, injectableType, concreteIdentifier))
|
|
{
|
|
value = Resolve(subContext);
|
|
}
|
|
}
|
|
|
|
if (value is ValidationMarker)
|
|
{
|
|
Assert.That(IsValidating);
|
|
|
|
paramValues[k] = injectInfo.MemberType.GetDefaultValue();
|
|
}
|
|
else
|
|
{
|
|
paramValues[k] = value;
|
|
}
|
|
}
|
|
|
|
if (!isDryRun)
|
|
{
|
|
#if ZEN_INTERNAL_PROFILING
|
|
using (ProfileTimers.CreateTimedBlock("User Code"))
|
|
#endif
|
|
#if UNITY_EDITOR
|
|
using (ProfileBlock.Start("{0}.{1}()", typeInfo.Type, method.Name))
|
|
#endif
|
|
{
|
|
method.Action(injectable, paramValues);
|
|
}
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
ZenPools.DespawnArray(paramValues);
|
|
}
|
|
}
|
|
}
|
|
|
|
void InjectMembersTopDown(
|
|
object injectable, Type injectableType,
|
|
InjectTypeInfo typeInfo, List<TypeValuePair> extraArgs,
|
|
InjectContext context, object concreteIdentifier, bool isDryRun)
|
|
{
|
|
if (typeInfo.BaseTypeInfo != null)
|
|
{
|
|
InjectMembersTopDown(
|
|
injectable, injectableType, typeInfo.BaseTypeInfo, extraArgs,
|
|
context, concreteIdentifier, isDryRun);
|
|
}
|
|
|
|
for (int i = 0; i < typeInfo.InjectMembers.Length; i++)
|
|
{
|
|
var injectInfo = typeInfo.InjectMembers[i].Info;
|
|
var setterMethod = typeInfo.InjectMembers[i].Setter;
|
|
|
|
object value;
|
|
|
|
if (InjectUtil.PopValueWithType(extraArgs, injectInfo.MemberType, out value))
|
|
{
|
|
if (!isDryRun)
|
|
{
|
|
if (value is ValidationMarker)
|
|
{
|
|
Assert.That(IsValidating);
|
|
}
|
|
else
|
|
{
|
|
setterMethod(injectable, value);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
using (var subContext = ZenPools.SpawnInjectContext(
|
|
this, injectInfo, context, injectable, injectableType, concreteIdentifier))
|
|
{
|
|
value = Resolve(subContext);
|
|
}
|
|
|
|
if (injectInfo.Optional && value == null)
|
|
{
|
|
// Do not override in this case so it retains the hard-coded value
|
|
}
|
|
else
|
|
{
|
|
if (!isDryRun)
|
|
{
|
|
if (value is ValidationMarker)
|
|
{
|
|
Assert.That(IsValidating);
|
|
}
|
|
else
|
|
{
|
|
setterMethod(injectable, value);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void InjectExplicitInternal(
|
|
object injectable, Type injectableType, List<TypeValuePair> extraArgs,
|
|
InjectContext context, object concreteIdentifier)
|
|
{
|
|
Assert.That(injectable != null);
|
|
|
|
var typeInfo = TypeAnalyzer.TryGetInfo(injectableType);
|
|
|
|
if (typeInfo == null)
|
|
{
|
|
Assert.That(extraArgs.IsEmpty());
|
|
return;
|
|
}
|
|
|
|
var allowDuringValidation = IsValidating && TypeAnalyzer.ShouldAllowDuringValidation(injectableType);
|
|
|
|
// Installers are the only things that we instantiate/inject on during validation
|
|
var isDryRun = IsValidating && !allowDuringValidation;
|
|
|
|
if (!isDryRun)
|
|
{
|
|
Assert.IsEqual(injectable.GetType(), injectableType);
|
|
}
|
|
|
|
#if !NOT_UNITY3D
|
|
if (injectableType == typeof(GameObject))
|
|
{
|
|
Assert.CreateException(
|
|
"Use InjectGameObject to Inject game objects instead of Inject method. Object graph: {0}", context.GetObjectGraphString());
|
|
}
|
|
#endif
|
|
|
|
FlushBindings();
|
|
CheckForInstallWarning(context);
|
|
|
|
InjectMembersTopDown(
|
|
injectable, injectableType, typeInfo, extraArgs, context, concreteIdentifier, isDryRun);
|
|
|
|
CallInjectMethodsTopDown(
|
|
injectable, injectableType, typeInfo, extraArgs, context, concreteIdentifier, isDryRun);
|
|
|
|
if (extraArgs.Count > 0)
|
|
{
|
|
throw Assert.CreateException(
|
|
"Passed unnecessary parameters when injecting into type '{0}'. \nExtra Parameters: {1}\nObject graph:\n{2}",
|
|
injectableType, String.Join(",", extraArgs.Select(x => x.Type.PrettyName()).ToArray()), context.GetObjectGraphString());
|
|
}
|
|
}
|
|
|
|
#if !NOT_UNITY3D
|
|
|
|
// Don't use this unless you know what you're doing
|
|
// You probably want to use InstantiatePrefab instead
|
|
// This one will only create the prefab and will not inject into it
|
|
// Also, this will always return the new game object as disabled, so that injection can occur before Awake / OnEnable / Start
|
|
internal GameObject CreateAndParentPrefabResource(
|
|
string resourcePath, GameObjectCreationParameters gameObjectBindInfo, InjectContext context, out bool shouldMakeActive)
|
|
{
|
|
var prefab = (GameObject)Resources.Load(resourcePath);
|
|
|
|
Assert.IsNotNull(prefab,
|
|
"Could not find prefab at resource location '{0}'".Fmt(resourcePath));
|
|
|
|
return CreateAndParentPrefab(prefab, gameObjectBindInfo, context, out shouldMakeActive);
|
|
}
|
|
|
|
GameObject GetPrefabAsGameObject(UnityEngine.Object prefab)
|
|
{
|
|
if (prefab is GameObject)
|
|
{
|
|
return (GameObject)prefab;
|
|
}
|
|
|
|
Assert.That(prefab is Component, "Invalid type given for prefab. Given object name: '{0}'", prefab.name);
|
|
return ((Component)prefab).gameObject;
|
|
}
|
|
|
|
// Don't use this unless you know what you're doing
|
|
// You probably want to use InstantiatePrefab instead
|
|
// This one will only create the prefab and will not inject into it
|
|
internal GameObject CreateAndParentPrefab(
|
|
UnityEngine.Object prefab, GameObjectCreationParameters gameObjectBindInfo,
|
|
InjectContext context, out bool shouldMakeActive)
|
|
{
|
|
Assert.That(prefab != null, "Null prefab found when instantiating game object");
|
|
|
|
Assert.That(!AssertOnNewGameObjects,
|
|
"Given DiContainer does not support creating new game objects");
|
|
|
|
FlushBindings();
|
|
|
|
var prefabAsGameObject = GetPrefabAsGameObject(prefab);
|
|
|
|
var prefabWasActive = prefabAsGameObject.activeSelf;
|
|
|
|
shouldMakeActive = prefabWasActive;
|
|
|
|
var parent = GetTransformGroup(gameObjectBindInfo, context);
|
|
|
|
Transform initialParent;
|
|
#if !UNITY_EDITOR
|
|
if (prefabWasActive)
|
|
{
|
|
prefabAsGameObject.SetActive(false);
|
|
}
|
|
#else
|
|
if (prefabWasActive)
|
|
{
|
|
initialParent = ZenUtilInternal.GetOrCreateInactivePrefabParent();
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
if (parent != null)
|
|
{
|
|
initialParent = parent;
|
|
}
|
|
else
|
|
{
|
|
// This ensures it gets added to the right scene instead of just the active scene
|
|
initialParent = ContextTransform;
|
|
}
|
|
}
|
|
|
|
bool positionAndRotationWereSet;
|
|
GameObject gameObj;
|
|
|
|
#if ZEN_INTERNAL_PROFILING
|
|
using (ProfileTimers.CreateTimedBlock("GameObject.Instantiate"))
|
|
#endif
|
|
{
|
|
if (gameObjectBindInfo.Position.HasValue && gameObjectBindInfo.Rotation.HasValue)
|
|
{
|
|
gameObj = GameObject.Instantiate(
|
|
prefabAsGameObject, gameObjectBindInfo.Position.Value, gameObjectBindInfo.Rotation.Value, initialParent);
|
|
positionAndRotationWereSet = true;
|
|
}
|
|
else if (gameObjectBindInfo.Position.HasValue)
|
|
{
|
|
gameObj = GameObject.Instantiate(
|
|
prefabAsGameObject, gameObjectBindInfo.Position.Value, prefabAsGameObject.transform.rotation, initialParent);
|
|
positionAndRotationWereSet = true;
|
|
}
|
|
else if (gameObjectBindInfo.Rotation.HasValue)
|
|
{
|
|
gameObj = GameObject.Instantiate(
|
|
prefabAsGameObject, prefabAsGameObject.transform.position, gameObjectBindInfo.Rotation.Value, initialParent);
|
|
positionAndRotationWereSet = true;
|
|
}
|
|
else
|
|
{
|
|
gameObj = GameObject.Instantiate(prefabAsGameObject, initialParent);
|
|
positionAndRotationWereSet = false;
|
|
}
|
|
}
|
|
|
|
#if !UNITY_EDITOR
|
|
if (prefabWasActive)
|
|
{
|
|
prefabAsGameObject.SetActive(true);
|
|
}
|
|
#else
|
|
if (prefabWasActive)
|
|
{
|
|
gameObj.SetActive(false);
|
|
|
|
if (parent == null)
|
|
{
|
|
gameObj.transform.SetParent(ContextTransform, positionAndRotationWereSet);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
if (gameObj.transform.parent != parent)
|
|
{
|
|
gameObj.transform.SetParent(parent, positionAndRotationWereSet);
|
|
}
|
|
|
|
if (gameObjectBindInfo.Name != null)
|
|
{
|
|
gameObj.name = gameObjectBindInfo.Name;
|
|
}
|
|
|
|
return gameObj;
|
|
}
|
|
|
|
public GameObject CreateEmptyGameObject(string name)
|
|
{
|
|
return CreateEmptyGameObject(new GameObjectCreationParameters { Name = name }, null);
|
|
}
|
|
|
|
public GameObject CreateEmptyGameObject(
|
|
GameObjectCreationParameters gameObjectBindInfo, InjectContext context)
|
|
{
|
|
Assert.That(!AssertOnNewGameObjects,
|
|
"Given DiContainer does not support creating new game objects");
|
|
|
|
FlushBindings();
|
|
|
|
var gameObj = new GameObject(gameObjectBindInfo.Name ?? "GameObject");
|
|
var parent = GetTransformGroup(gameObjectBindInfo, context);
|
|
|
|
if (parent == null)
|
|
{
|
|
// This ensures it gets added to the right scene instead of just the active scene
|
|
gameObj.transform.SetParent(ContextTransform, false);
|
|
gameObj.transform.SetParent(null, false);
|
|
}
|
|
else
|
|
{
|
|
gameObj.transform.SetParent(parent, false);
|
|
}
|
|
|
|
return gameObj;
|
|
}
|
|
|
|
Transform GetTransformGroup(
|
|
GameObjectCreationParameters gameObjectBindInfo, InjectContext context)
|
|
{
|
|
Assert.That(!AssertOnNewGameObjects,
|
|
"Given DiContainer does not support creating new game objects");
|
|
|
|
if (gameObjectBindInfo.ParentTransform != null)
|
|
{
|
|
Assert.IsNull(gameObjectBindInfo.GroupName);
|
|
Assert.IsNull(gameObjectBindInfo.ParentTransformGetter);
|
|
|
|
return gameObjectBindInfo.ParentTransform;
|
|
}
|
|
|
|
// Don't execute the ParentTransformGetter method during validation
|
|
// since it might do a resolve etc.
|
|
if (gameObjectBindInfo.ParentTransformGetter != null && !IsValidating)
|
|
{
|
|
Assert.IsNull(gameObjectBindInfo.GroupName);
|
|
|
|
if (context == null)
|
|
{
|
|
context = new InjectContext
|
|
{
|
|
// This is the only information we can supply in this case
|
|
Container = this
|
|
};
|
|
}
|
|
|
|
// NOTE: Null is fine here, will just be a root game object in that case
|
|
return gameObjectBindInfo.ParentTransformGetter(context);
|
|
}
|
|
|
|
var groupName = gameObjectBindInfo.GroupName;
|
|
|
|
// Only use the inherited parent if is not set locally
|
|
var defaultParent = _hasExplicitDefaultParent ? _explicitDefaultParent : _inheritedDefaultParent;
|
|
|
|
if (defaultParent == null)
|
|
{
|
|
if (groupName == null)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
return (GameObject.Find("/" + groupName) ?? CreateTransformGroup(groupName)).transform;
|
|
}
|
|
|
|
if (groupName == null)
|
|
{
|
|
return defaultParent;
|
|
}
|
|
|
|
foreach (Transform child in defaultParent)
|
|
{
|
|
if (child.name == groupName)
|
|
{
|
|
return child;
|
|
}
|
|
}
|
|
|
|
var group = new GameObject(groupName).transform;
|
|
group.SetParent(defaultParent, false);
|
|
return group;
|
|
}
|
|
|
|
GameObject CreateTransformGroup(string groupName)
|
|
{
|
|
var gameObj = new GameObject(groupName);
|
|
gameObj.transform.SetParent(ContextTransform, false);
|
|
gameObj.transform.SetParent(null, false);
|
|
return gameObj;
|
|
}
|
|
|
|
#endif
|
|
|
|
// Use this method to create any non-monobehaviour
|
|
// Any fields marked [Inject] will be set using the bindings on the container
|
|
// Any methods marked with a [Inject] will be called
|
|
// Any constructor parameters will be filled in with values from the container
|
|
public T Instantiate<T>()
|
|
{
|
|
return Instantiate<T>(new object[0]);
|
|
}
|
|
|
|
// Note: For IL2CPP platforms make sure to use new object[] instead of new [] when creating
|
|
// the argument list to avoid errors converting to IEnumerable<object>
|
|
public T Instantiate<T>(IEnumerable<object> extraArgs)
|
|
{
|
|
var result = Instantiate(typeof(T), extraArgs);
|
|
|
|
if (IsValidating && !(result is T))
|
|
{
|
|
Assert.That(result is ValidationMarker);
|
|
return default(T);
|
|
}
|
|
|
|
return (T)result;
|
|
}
|
|
|
|
public object Instantiate(Type concreteType)
|
|
{
|
|
return Instantiate(concreteType, new object[0]);
|
|
}
|
|
|
|
// Note: For IL2CPP platforms make sure to use new object[] instead of new [] when creating
|
|
// the argument list to avoid errors converting to IEnumerable<object>
|
|
public object Instantiate(
|
|
Type concreteType, IEnumerable<object> extraArgs)
|
|
{
|
|
Assert.That(!extraArgs.ContainsItem(null),
|
|
"Null value given to factory constructor arguments when instantiating object with type '{0}'. In order to use null use InstantiateExplicit", concreteType);
|
|
|
|
return InstantiateExplicit(
|
|
concreteType, InjectUtil.CreateArgList(extraArgs));
|
|
}
|
|
|
|
#if !NOT_UNITY3D
|
|
// Add new component to existing game object and fill in its dependencies
|
|
// This is the same as AddComponent except the [Inject] fields will be filled in
|
|
// NOTE: Gameobject here is not a prefab prototype, it is an instance
|
|
public TContract InstantiateComponent<TContract>(GameObject gameObject)
|
|
where TContract : Component
|
|
{
|
|
return InstantiateComponent<TContract>(gameObject, new object[0]);
|
|
}
|
|
|
|
// Add new component to existing game object and fill in its dependencies
|
|
// This is the same as AddComponent except the [Inject] fields will be filled in
|
|
// NOTE: Gameobject here is not a prefab prototype, it is an instance
|
|
// Note: For IL2CPP platforms make sure to use new object[] instead of new [] when creating
|
|
// the argument list to avoid errors converting to IEnumerable<object>
|
|
public TContract InstantiateComponent<TContract>(
|
|
GameObject gameObject, IEnumerable<object> extraArgs)
|
|
where TContract : Component
|
|
{
|
|
return (TContract)InstantiateComponent(typeof(TContract), gameObject, extraArgs);
|
|
}
|
|
|
|
// Add new component to existing game object and fill in its dependencies
|
|
// This is the same as AddComponent except the [Inject] fields will be filled in
|
|
// NOTE: Gameobject here is not a prefab prototype, it is an instance
|
|
public Component InstantiateComponent(
|
|
Type componentType, GameObject gameObject)
|
|
{
|
|
return InstantiateComponent(componentType, gameObject, new object[0]);
|
|
}
|
|
|
|
// Add new component to existing game object and fill in its dependencies
|
|
// This is the same as AddComponent except the [Inject] fields will be filled in
|
|
// NOTE: Gameobject here is not a prefab prototype, it is an instance
|
|
// Note: For IL2CPP platforms make sure to use new object[] instead of new [] when creating
|
|
// the argument list to avoid errors converting to IEnumerable<object>
|
|
public Component InstantiateComponent(
|
|
Type componentType, GameObject gameObject, IEnumerable<object> extraArgs)
|
|
{
|
|
return InstantiateComponentExplicit(
|
|
componentType, gameObject, InjectUtil.CreateArgList(extraArgs));
|
|
}
|
|
|
|
public T InstantiateComponentOnNewGameObject<T>()
|
|
where T : Component
|
|
{
|
|
return InstantiateComponentOnNewGameObject<T>(typeof(T).Name);
|
|
}
|
|
|
|
// Note: For IL2CPP platforms make sure to use new object[] instead of new [] when creating
|
|
// the argument list to avoid errors converting to IEnumerable<object>
|
|
public T InstantiateComponentOnNewGameObject<T>(IEnumerable<object> extraArgs)
|
|
where T : Component
|
|
{
|
|
return InstantiateComponentOnNewGameObject<T>(typeof(T).Name, extraArgs);
|
|
}
|
|
|
|
public T InstantiateComponentOnNewGameObject<T>(string gameObjectName)
|
|
where T : Component
|
|
{
|
|
return InstantiateComponentOnNewGameObject<T>(gameObjectName, new object[0]);
|
|
}
|
|
|
|
// Note: For IL2CPP platforms make sure to use new object[] instead of new [] when creating
|
|
// the argument list to avoid errors converting to IEnumerable<object>
|
|
public T InstantiateComponentOnNewGameObject<T>(
|
|
string gameObjectName, IEnumerable<object> extraArgs)
|
|
where T : Component
|
|
{
|
|
return InstantiateComponent<T>(
|
|
CreateEmptyGameObject(gameObjectName),
|
|
extraArgs);
|
|
}
|
|
|
|
// Create a new game object from a prefab and fill in dependencies for all children
|
|
public GameObject InstantiatePrefab(UnityEngine.Object prefab)
|
|
{
|
|
return InstantiatePrefab(
|
|
prefab, GameObjectCreationParameters.Default);
|
|
}
|
|
|
|
// Create a new game object from a prefab and fill in dependencies for all children
|
|
public GameObject InstantiatePrefab(UnityEngine.Object prefab, Transform parentTransform)
|
|
{
|
|
return InstantiatePrefab(
|
|
prefab, new GameObjectCreationParameters { ParentTransform = parentTransform });
|
|
}
|
|
|
|
// Create a new game object from a prefab and fill in dependencies for all children
|
|
public GameObject InstantiatePrefab(
|
|
UnityEngine.Object prefab, Vector3 position, Quaternion rotation, Transform parentTransform)
|
|
{
|
|
return InstantiatePrefab(
|
|
prefab, new GameObjectCreationParameters
|
|
{
|
|
ParentTransform = parentTransform,
|
|
Position = position,
|
|
Rotation = rotation
|
|
});
|
|
}
|
|
|
|
// Create a new game object from a prefab and fill in dependencies for all children
|
|
public GameObject InstantiatePrefab(
|
|
UnityEngine.Object prefab, GameObjectCreationParameters gameObjectBindInfo)
|
|
{
|
|
FlushBindings();
|
|
|
|
bool shouldMakeActive;
|
|
var gameObj = CreateAndParentPrefab(
|
|
prefab, gameObjectBindInfo, null, out shouldMakeActive);
|
|
|
|
InjectGameObject(gameObj);
|
|
|
|
if (shouldMakeActive && !IsValidating)
|
|
{
|
|
#if ZEN_INTERNAL_PROFILING
|
|
using (ProfileTimers.CreateTimedBlock("User Code"))
|
|
#endif
|
|
{
|
|
gameObj.SetActive(true);
|
|
}
|
|
}
|
|
|
|
return gameObj;
|
|
}
|
|
|
|
// Create a new game object from a resource path and fill in dependencies for all children
|
|
public GameObject InstantiatePrefabResource(string resourcePath)
|
|
{
|
|
return InstantiatePrefabResource(resourcePath, GameObjectCreationParameters.Default);
|
|
}
|
|
|
|
// Create a new game object from a resource path and fill in dependencies for all children
|
|
public GameObject InstantiatePrefabResource(string resourcePath, Transform parentTransform)
|
|
{
|
|
return InstantiatePrefabResource(resourcePath, new GameObjectCreationParameters { ParentTransform = parentTransform });
|
|
}
|
|
|
|
public GameObject InstantiatePrefabResource(
|
|
string resourcePath, Vector3 position, Quaternion rotation, Transform parentTransform)
|
|
{
|
|
return InstantiatePrefabResource(
|
|
resourcePath, new GameObjectCreationParameters
|
|
{
|
|
ParentTransform = parentTransform,
|
|
Position = position,
|
|
Rotation = rotation
|
|
});
|
|
}
|
|
|
|
// Create a new game object from a resource path and fill in dependencies for all children
|
|
public GameObject InstantiatePrefabResource(
|
|
string resourcePath, GameObjectCreationParameters creationInfo)
|
|
{
|
|
var prefab = (GameObject)Resources.Load(resourcePath);
|
|
|
|
Assert.IsNotNull(prefab,
|
|
"Could not find prefab at resource location '{0}'".Fmt(resourcePath));
|
|
|
|
return InstantiatePrefab(prefab, creationInfo);
|
|
}
|
|
|
|
// Same as InstantiatePrefab but returns a component after it's initialized
|
|
// and optionally allows extra arguments for the given component type
|
|
public T InstantiatePrefabForComponent<T>(UnityEngine.Object prefab)
|
|
{
|
|
return (T)InstantiatePrefabForComponent(
|
|
typeof(T), prefab, null, new object[0]);
|
|
}
|
|
|
|
// Same as InstantiatePrefab but returns a component after it's initialized
|
|
// and optionally allows extra arguments for the given component type
|
|
// Note: For IL2CPP platforms make sure to use new object[] instead of new [] when creating
|
|
// the argument list to avoid errors converting to IEnumerable<object>
|
|
public T InstantiatePrefabForComponent<T>(
|
|
UnityEngine.Object prefab, IEnumerable<object> extraArgs)
|
|
{
|
|
return (T)InstantiatePrefabForComponent(
|
|
typeof(T), prefab, null, extraArgs);
|
|
}
|
|
|
|
public T InstantiatePrefabForComponent<T>(
|
|
UnityEngine.Object prefab, Transform parentTransform)
|
|
{
|
|
return (T)InstantiatePrefabForComponent(
|
|
typeof(T), prefab, parentTransform, new object[0]);
|
|
}
|
|
|
|
// Note: For IL2CPP platforms make sure to use new object[] instead of new [] when creating
|
|
// the argument list to avoid errors converting to IEnumerable<object>
|
|
public T InstantiatePrefabForComponent<T>(
|
|
UnityEngine.Object prefab, Transform parentTransform, IEnumerable<object> extraArgs)
|
|
{
|
|
return (T)InstantiatePrefabForComponent(
|
|
typeof(T), prefab, parentTransform, extraArgs);
|
|
}
|
|
|
|
public T InstantiatePrefabForComponent<T>(
|
|
UnityEngine.Object prefab, Vector3 position, Quaternion rotation, Transform parentTransform)
|
|
{
|
|
return (T)InstantiatePrefabForComponent(
|
|
typeof(T), prefab, new object[0], new GameObjectCreationParameters
|
|
{
|
|
ParentTransform = parentTransform,
|
|
Position = position,
|
|
Rotation = rotation
|
|
});
|
|
}
|
|
|
|
public T InstantiatePrefabForComponent<T>(
|
|
UnityEngine.Object prefab, Vector3 position, Quaternion rotation, Transform parentTransform, IEnumerable<object> extraArgs)
|
|
{
|
|
return (T)InstantiatePrefabForComponent(
|
|
typeof(T), prefab, extraArgs, new GameObjectCreationParameters
|
|
{
|
|
ParentTransform = parentTransform,
|
|
Position = position,
|
|
Rotation = rotation
|
|
});
|
|
}
|
|
|
|
// Same as InstantiatePrefab but returns a component after it's initialized
|
|
// and optionally allows extra arguments for the given component type
|
|
// Note: For IL2CPP platforms make sure to use new object[] instead of new [] when creating
|
|
// the argument list to avoid errors converting to IEnumerable<object>
|
|
public object InstantiatePrefabForComponent(
|
|
Type concreteType, UnityEngine.Object prefab,
|
|
Transform parentTransform, IEnumerable<object> extraArgs)
|
|
{
|
|
return InstantiatePrefabForComponent(
|
|
concreteType, prefab, extraArgs,
|
|
new GameObjectCreationParameters { ParentTransform = parentTransform });
|
|
}
|
|
|
|
// Note: For IL2CPP platforms make sure to use new object[] instead of new [] when creating
|
|
// the argument list to avoid errors converting to IEnumerable<object>
|
|
public object InstantiatePrefabForComponent(
|
|
Type concreteType, UnityEngine.Object prefab,
|
|
IEnumerable<object> extraArgs, GameObjectCreationParameters creationInfo)
|
|
{
|
|
return InstantiatePrefabForComponentExplicit(
|
|
concreteType, prefab,
|
|
InjectUtil.CreateArgList(extraArgs), creationInfo);
|
|
}
|
|
|
|
// Same as InstantiatePrefabResource but returns a component after it's initialized
|
|
// and optionally allows extra arguments for the given component type
|
|
public T InstantiatePrefabResourceForComponent<T>(string resourcePath)
|
|
{
|
|
return (T)InstantiatePrefabResourceForComponent(
|
|
typeof(T), resourcePath, null, new object[0]);
|
|
}
|
|
|
|
// Same as InstantiatePrefabResource but returns a component after it's initialized
|
|
// and optionally allows extra arguments for the given component type
|
|
// Note: For IL2CPP platforms make sure to use new object[] instead of new [] when creating
|
|
// the argument list to avoid errors converting to IEnumerable<object>
|
|
public T InstantiatePrefabResourceForComponent<T>(
|
|
string resourcePath, IEnumerable<object> extraArgs)
|
|
{
|
|
return (T)InstantiatePrefabResourceForComponent(
|
|
typeof(T), resourcePath, null, extraArgs);
|
|
}
|
|
|
|
public T InstantiatePrefabResourceForComponent<T>(
|
|
string resourcePath, Transform parentTransform)
|
|
{
|
|
return (T)InstantiatePrefabResourceForComponent(
|
|
typeof(T), resourcePath, parentTransform, new object[0]);
|
|
}
|
|
|
|
// Note: For IL2CPP platforms make sure to use new object[] instead of new [] when creating
|
|
// the argument list to avoid errors converting to IEnumerable<object>
|
|
public T InstantiatePrefabResourceForComponent<T>(
|
|
string resourcePath, Transform parentTransform, IEnumerable<object> extraArgs)
|
|
{
|
|
return (T)InstantiatePrefabResourceForComponent(
|
|
typeof(T), resourcePath, parentTransform, extraArgs);
|
|
}
|
|
|
|
public T InstantiatePrefabResourceForComponent<T>(
|
|
string resourcePath, Vector3 position, Quaternion rotation, Transform parentTransform)
|
|
{
|
|
return InstantiatePrefabResourceForComponent<T>(resourcePath, position, rotation, parentTransform, new object[0]);
|
|
}
|
|
|
|
public T InstantiatePrefabResourceForComponent<T>(
|
|
string resourcePath, Vector3 position, Quaternion rotation, Transform parentTransform, IEnumerable<object> extraArgs)
|
|
{
|
|
var argsList = InjectUtil.CreateArgList(extraArgs);
|
|
var creationParameters = new GameObjectCreationParameters
|
|
{
|
|
ParentTransform = parentTransform,
|
|
Position = position,
|
|
Rotation = rotation
|
|
};
|
|
return (T)InstantiatePrefabResourceForComponentExplicit(
|
|
typeof(T), resourcePath, argsList, creationParameters);
|
|
}
|
|
|
|
// Same as InstantiatePrefabResource but returns a component after it's initialized
|
|
// and optionally allows extra arguments for the given component type
|
|
// Note: For IL2CPP platforms make sure to use new object[] instead of new [] when creating
|
|
// the argument list to avoid errors converting to IEnumerable<object>
|
|
public object InstantiatePrefabResourceForComponent(
|
|
Type concreteType, string resourcePath, Transform parentTransform,
|
|
IEnumerable<object> extraArgs)
|
|
{
|
|
Assert.That(!extraArgs.ContainsItem(null),
|
|
"Null value given to factory constructor arguments when instantiating object with type '{0}'. In order to use null use InstantiatePrefabForComponentExplicit", concreteType);
|
|
|
|
return InstantiatePrefabResourceForComponentExplicit(
|
|
concreteType, resourcePath,
|
|
InjectUtil.CreateArgList(extraArgs),
|
|
new GameObjectCreationParameters { ParentTransform = parentTransform });
|
|
}
|
|
|
|
public T InstantiateScriptableObjectResource<T>(string resourcePath)
|
|
where T : ScriptableObject
|
|
{
|
|
return InstantiateScriptableObjectResource<T>(resourcePath, new object[0]);
|
|
}
|
|
|
|
// Note: For IL2CPP platforms make sure to use new object[] instead of new [] when creating
|
|
// the argument list to avoid errors converting to IEnumerable<object>
|
|
public T InstantiateScriptableObjectResource<T>(
|
|
string resourcePath, IEnumerable<object> extraArgs)
|
|
where T : ScriptableObject
|
|
{
|
|
return (T)InstantiateScriptableObjectResource(
|
|
typeof(T), resourcePath, extraArgs);
|
|
}
|
|
|
|
public object InstantiateScriptableObjectResource(
|
|
Type scriptableObjectType, string resourcePath)
|
|
{
|
|
return InstantiateScriptableObjectResource(
|
|
scriptableObjectType, resourcePath, new object[0]);
|
|
}
|
|
|
|
// Note: For IL2CPP platforms make sure to use new object[] instead of new [] when creating
|
|
// the argument list to avoid errors converting to IEnumerable<object>
|
|
public object InstantiateScriptableObjectResource(
|
|
Type scriptableObjectType, string resourcePath, IEnumerable<object> extraArgs)
|
|
{
|
|
Assert.DerivesFromOrEqual<ScriptableObject>(scriptableObjectType);
|
|
return InstantiateScriptableObjectResourceExplicit(
|
|
scriptableObjectType, resourcePath, InjectUtil.CreateArgList(extraArgs));
|
|
}
|
|
|
|
// Inject dependencies into any and all child components on the given game object
|
|
public void InjectGameObject(GameObject gameObject)
|
|
{
|
|
FlushBindings();
|
|
|
|
ZenUtilInternal.AddStateMachineBehaviourAutoInjectersUnderGameObject(gameObject);
|
|
|
|
var monoBehaviours = ZenPools.SpawnList<MonoBehaviour>();
|
|
try
|
|
{
|
|
ZenUtilInternal.GetInjectableMonoBehavioursUnderGameObject(gameObject, monoBehaviours);
|
|
|
|
for (int i = 0; i < monoBehaviours.Count; i++)
|
|
{
|
|
Inject(monoBehaviours[i]);
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
ZenPools.DespawnList(monoBehaviours);
|
|
}
|
|
}
|
|
|
|
// Same as InjectGameObject except it will also search the game object for the
|
|
// given component, and also optionally allow passing extra inject arguments into the
|
|
// given component
|
|
public T InjectGameObjectForComponent<T>(GameObject gameObject)
|
|
where T : Component
|
|
{
|
|
return InjectGameObjectForComponent<T>(gameObject, new object[0]);
|
|
}
|
|
|
|
// Same as InjectGameObject except it will also search the game object for the
|
|
// given component, and also optionally allow passing extra inject arguments into the
|
|
// given component
|
|
// Note: For IL2CPP platforms make sure to use new object[] instead of new [] when creating
|
|
// the argument list to avoid errors converting to IEnumerable<object>
|
|
public T InjectGameObjectForComponent<T>(
|
|
GameObject gameObject, IEnumerable<object> extraArgs)
|
|
where T : Component
|
|
{
|
|
return (T)InjectGameObjectForComponent(gameObject, typeof(T), extraArgs);
|
|
}
|
|
|
|
// Same as InjectGameObject except it will also search the game object for the
|
|
// given component, and also optionally allow passing extra inject arguments into the
|
|
// given component
|
|
// Note: For IL2CPP platforms make sure to use new object[] instead of new [] when creating
|
|
// the argument list to avoid errors converting to IEnumerable<object>
|
|
public object InjectGameObjectForComponent(
|
|
GameObject gameObject, Type componentType, IEnumerable<object> extraArgs)
|
|
{
|
|
return InjectGameObjectForComponentExplicit(
|
|
gameObject, componentType, InjectUtil.CreateArgList(extraArgs), new InjectContext(this, componentType, null), null);
|
|
}
|
|
|
|
// Same as InjectGameObjectForComponent except allows null values
|
|
// to be included in the argument list. Also see InjectUtil.CreateArgList
|
|
public Component InjectGameObjectForComponentExplicit(
|
|
GameObject gameObject, Type componentType, List<TypeValuePair> extraArgs, InjectContext context, object concreteIdentifier)
|
|
{
|
|
if (!componentType.DerivesFrom<MonoBehaviour>() && extraArgs.Count > 0)
|
|
{
|
|
throw Assert.CreateException(
|
|
"Cannot inject into non-monobehaviours! Argument list must be zero length");
|
|
}
|
|
|
|
ZenUtilInternal.AddStateMachineBehaviourAutoInjectersUnderGameObject(gameObject);
|
|
|
|
var injectableMonoBehaviours = ZenPools.SpawnList<MonoBehaviour>();
|
|
try
|
|
{
|
|
|
|
ZenUtilInternal.GetInjectableMonoBehavioursUnderGameObject(gameObject, injectableMonoBehaviours);
|
|
|
|
for (int i = 0; i < injectableMonoBehaviours.Count; i++)
|
|
{
|
|
var monoBehaviour = injectableMonoBehaviours[i];
|
|
if (monoBehaviour.GetType().DerivesFromOrEqual(componentType))
|
|
{
|
|
InjectExplicit(monoBehaviour, monoBehaviour.GetType(), extraArgs, context, concreteIdentifier);
|
|
}
|
|
else
|
|
{
|
|
Inject(monoBehaviour);
|
|
}
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
ZenPools.DespawnList(injectableMonoBehaviours);
|
|
}
|
|
|
|
var matches = gameObject.GetComponentsInChildren(componentType, true);
|
|
|
|
Assert.That(matches.Length > 0,
|
|
"Expected to find component with type '{0}' when injecting into game object '{1}'", componentType, gameObject.name);
|
|
|
|
Assert.That(matches.Length == 1,
|
|
"Found multiple component with type '{0}' when injecting into game object '{1}'", componentType, gameObject.name);
|
|
|
|
return matches[0];
|
|
}
|
|
#endif
|
|
|
|
// When you call any of these Inject methods
|
|
// Any fields marked [Inject] will be set using the bindings on the container
|
|
// Any methods marked with a [Inject] will be called
|
|
// Any constructor parameters will be filled in with values from the container
|
|
public void Inject(object injectable)
|
|
{
|
|
Inject(injectable, new object[0]);
|
|
}
|
|
|
|
// Same as Inject(injectable) except allows adding extra values to be injected
|
|
// Note: For IL2CPP platforms make sure to use new object[] instead of new [] when creating
|
|
// the argument list to avoid errors converting to IEnumerable<object>
|
|
public void Inject(object injectable, IEnumerable<object> extraArgs)
|
|
{
|
|
InjectExplicit(
|
|
injectable, InjectUtil.CreateArgList(extraArgs));
|
|
}
|
|
|
|
// Resolve<> - Lookup a value in the container.
|
|
//
|
|
// Note that this may result in a new object being created (for transient bindings) or it
|
|
// may return an already created object (for FromInstance or ToSingle, etc. bindings)
|
|
//
|
|
// If a single unique value for the given type cannot be found, an exception is thrown.
|
|
//
|
|
public TContract Resolve<TContract>()
|
|
{
|
|
return (TContract)Resolve(typeof(TContract));
|
|
}
|
|
|
|
public object Resolve(Type contractType)
|
|
{
|
|
return ResolveId(contractType, null);
|
|
}
|
|
|
|
public TContract ResolveId<TContract>(object identifier)
|
|
{
|
|
return (TContract)ResolveId(typeof(TContract), identifier);
|
|
}
|
|
|
|
public object ResolveId(Type contractType, object identifier)
|
|
{
|
|
using (var context = ZenPools.SpawnInjectContext(this, contractType))
|
|
{
|
|
context.Identifier = identifier;
|
|
return Resolve(context);
|
|
}
|
|
}
|
|
|
|
// Same as Resolve<> except it will return null if a value for the given type cannot
|
|
// be found.
|
|
public TContract TryResolve<TContract>()
|
|
where TContract : class
|
|
{
|
|
return (TContract)TryResolve(typeof(TContract));
|
|
}
|
|
|
|
public object TryResolve(Type contractType)
|
|
{
|
|
return TryResolveId(contractType, null);
|
|
}
|
|
|
|
public TContract TryResolveId<TContract>(object identifier)
|
|
where TContract : class
|
|
{
|
|
return (TContract)TryResolveId(
|
|
typeof(TContract), identifier);
|
|
}
|
|
|
|
public object TryResolveId(Type contractType, object identifier)
|
|
{
|
|
using (var context = ZenPools.SpawnInjectContext(this, contractType))
|
|
{
|
|
context.Identifier = identifier;
|
|
context.Optional = true;
|
|
return Resolve(context);
|
|
}
|
|
}
|
|
|
|
// Same as Resolve<> except it will return all bindings that are associated with the given type
|
|
public List<TContract> ResolveAll<TContract>()
|
|
{
|
|
return (List<TContract>)ResolveAll(typeof(TContract));
|
|
}
|
|
|
|
public IList ResolveAll(Type contractType)
|
|
{
|
|
return ResolveIdAll(contractType, null);
|
|
}
|
|
|
|
public List<TContract> ResolveIdAll<TContract>(object identifier)
|
|
{
|
|
return (List<TContract>)ResolveIdAll(typeof(TContract), identifier);
|
|
}
|
|
|
|
public IList ResolveIdAll(Type contractType, object identifier)
|
|
{
|
|
using (var context = ZenPools.SpawnInjectContext(this, contractType))
|
|
{
|
|
context.Identifier = identifier;
|
|
context.Optional = true;
|
|
return ResolveAll(context);
|
|
}
|
|
}
|
|
|
|
// Removes all bindings
|
|
public void UnbindAll()
|
|
{
|
|
FlushBindings();
|
|
_providers.Clear();
|
|
}
|
|
|
|
// Remove all bindings bound to the given contract type
|
|
public bool Unbind<TContract>()
|
|
{
|
|
return Unbind(typeof(TContract));
|
|
}
|
|
|
|
public bool Unbind(Type contractType)
|
|
{
|
|
return UnbindId(contractType, null);
|
|
}
|
|
|
|
public bool UnbindId<TContract>(object identifier)
|
|
{
|
|
return UnbindId(typeof(TContract), identifier);
|
|
}
|
|
|
|
public bool UnbindId(Type contractType, object identifier)
|
|
{
|
|
FlushBindings();
|
|
|
|
var bindingId = new BindingId(contractType, identifier);
|
|
|
|
return _providers.Remove(bindingId);
|
|
}
|
|
|
|
public void UnbindInterfacesTo<TConcrete>()
|
|
{
|
|
UnbindInterfacesTo(typeof(TConcrete));
|
|
}
|
|
|
|
public void UnbindInterfacesTo(Type concreteType)
|
|
{
|
|
foreach (var i in concreteType.Interfaces())
|
|
{
|
|
Unbind(i, concreteType);
|
|
}
|
|
}
|
|
|
|
public bool Unbind<TContract, TConcrete>()
|
|
{
|
|
return Unbind(typeof(TContract), typeof(TConcrete));
|
|
}
|
|
|
|
public bool Unbind(Type contractType, Type concreteType)
|
|
{
|
|
return UnbindId(contractType, concreteType, null);
|
|
}
|
|
|
|
public bool UnbindId<TContract, TConcrete>(object identifier)
|
|
{
|
|
return UnbindId(typeof(TContract), typeof(TConcrete), identifier);
|
|
}
|
|
|
|
public bool UnbindId(Type contractType, Type concreteType, object identifier)
|
|
{
|
|
FlushBindings();
|
|
|
|
var bindingId = new BindingId(contractType, identifier);
|
|
|
|
List<ProviderInfo> providers;
|
|
|
|
if (!_providers.TryGetValue(bindingId, out providers))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
var matches = providers.Where(x => x.Provider.GetInstanceType(new InjectContext(this, contractType, identifier)).DerivesFromOrEqual(concreteType)).ToList();
|
|
|
|
if (matches.Count == 0)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
foreach (var info in matches)
|
|
{
|
|
bool success = providers.Remove(info);
|
|
Assert.That(success);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// Returns true if the given type is bound to something in the container
|
|
public bool HasBinding<TContract>()
|
|
{
|
|
return HasBinding(typeof(TContract));
|
|
}
|
|
|
|
public bool HasBinding(Type contractType)
|
|
{
|
|
return HasBindingId(contractType, null);
|
|
}
|
|
|
|
public bool HasBindingId<TContract>(object identifier)
|
|
{
|
|
return HasBindingId(typeof(TContract), identifier);
|
|
}
|
|
|
|
public bool HasBindingId(Type contractType, object identifier)
|
|
{
|
|
return HasBindingId(contractType, identifier, InjectSources.Any);
|
|
}
|
|
|
|
public bool HasBindingId(Type contractType, object identifier, InjectSources sourceType)
|
|
{
|
|
using (var ctx = ZenPools.SpawnInjectContext(this, contractType))
|
|
{
|
|
ctx.Identifier = identifier;
|
|
ctx.SourceType = sourceType;
|
|
return HasBinding(ctx);
|
|
}
|
|
}
|
|
|
|
// You shouldn't need to use this
|
|
public bool HasBinding(InjectContext context)
|
|
{
|
|
Assert.IsNotNull(context);
|
|
|
|
FlushBindings();
|
|
|
|
var matches = ZenPools.SpawnList<ProviderInfo>();
|
|
|
|
try
|
|
{
|
|
GetProviderMatches(context, matches);
|
|
return matches.Count > 0;
|
|
}
|
|
finally
|
|
{
|
|
ZenPools.DespawnList(matches);
|
|
}
|
|
}
|
|
|
|
// You shouldn't need to use this
|
|
public void FlushBindings()
|
|
{
|
|
while (_currentBindings.Count > 0)
|
|
{
|
|
var binding = _currentBindings.Dequeue();
|
|
|
|
if (binding.BindingInheritanceMethod != BindingInheritanceMethods.MoveDirectOnly
|
|
&& binding.BindingInheritanceMethod != BindingInheritanceMethods.MoveIntoAll)
|
|
{
|
|
FinalizeBinding(binding);
|
|
}
|
|
|
|
if (binding.BindingInheritanceMethod != BindingInheritanceMethods.None)
|
|
{
|
|
_childBindings.Add(binding);
|
|
}
|
|
else
|
|
{
|
|
binding.Dispose();
|
|
}
|
|
}
|
|
}
|
|
|
|
void FinalizeBinding(BindStatement binding)
|
|
{
|
|
_isFinalizingBinding = true;
|
|
|
|
try
|
|
{
|
|
binding.FinalizeBinding(this);
|
|
}
|
|
finally
|
|
{
|
|
_isFinalizingBinding = false;
|
|
}
|
|
}
|
|
|
|
// Don't use this method
|
|
public BindStatement StartBinding(bool flush = true)
|
|
{
|
|
Assert.That(!_isFinalizingBinding,
|
|
"Attempted to start a binding during a binding finalizer. This is not allowed, since binding finalizers should directly use AddProvider instead, to allow for bindings to be inherited properly without duplicates");
|
|
|
|
if (flush)
|
|
{
|
|
FlushBindings();
|
|
}
|
|
|
|
var bindStatement = ZenPools.SpawnStatement();
|
|
_currentBindings.Enqueue(bindStatement);
|
|
return bindStatement;
|
|
}
|
|
|
|
public ConcreteBinderGeneric<TContract> Rebind<TContract>()
|
|
{
|
|
return RebindId<TContract>(null);
|
|
}
|
|
|
|
public ConcreteBinderGeneric<TContract> RebindId<TContract>(object identifier)
|
|
{
|
|
UnbindId<TContract>(identifier);
|
|
return Bind<TContract>().WithId(identifier);
|
|
}
|
|
|
|
public ConcreteBinderNonGeneric Rebind(Type contractType)
|
|
{
|
|
return RebindId(contractType, null);
|
|
}
|
|
|
|
public ConcreteBinderNonGeneric RebindId(Type contractType, object identifier)
|
|
{
|
|
UnbindId(contractType, identifier);
|
|
return Bind(contractType).WithId(identifier);
|
|
}
|
|
|
|
// Map the given type to a way of obtaining it
|
|
// Note that this can include open generic types as well such as List<>
|
|
public ConcreteIdBinderGeneric<TContract> Bind<TContract>()
|
|
{
|
|
return Bind<TContract>(StartBinding());
|
|
}
|
|
|
|
// This is only useful for complex cases where you want to add multiple bindings
|
|
// at the same time and can be ignored by 99% of users
|
|
public ConcreteIdBinderGeneric<TContract> BindNoFlush<TContract>()
|
|
{
|
|
return Bind<TContract>(StartBinding(false));
|
|
}
|
|
|
|
ConcreteIdBinderGeneric<TContract> Bind<TContract>(
|
|
BindStatement bindStatement)
|
|
{
|
|
var bindInfo = bindStatement.SpawnBindInfo();
|
|
|
|
Assert.That(!typeof(TContract).DerivesFrom<IPlaceholderFactory>(),
|
|
"You should not use Container.Bind for factory classes. Use Container.BindFactory instead.");
|
|
|
|
Assert.That(!bindInfo.ContractTypes.Contains(typeof(TContract)));
|
|
bindInfo.ContractTypes.Add(typeof(TContract));
|
|
|
|
return new ConcreteIdBinderGeneric<TContract>(
|
|
this, bindInfo, bindStatement);
|
|
}
|
|
|
|
// Non-generic version of Bind<> for cases where you only have the runtime type
|
|
// Note that this can include open generic types as well such as List<>
|
|
public ConcreteIdBinderNonGeneric Bind(params Type[] contractTypes)
|
|
{
|
|
var statement = StartBinding();
|
|
var bindInfo = statement.SpawnBindInfo();
|
|
bindInfo.ContractTypes.AllocFreeAddRange(contractTypes);
|
|
return BindInternal(bindInfo, statement);
|
|
}
|
|
|
|
public ConcreteIdBinderNonGeneric Bind(IEnumerable<Type> contractTypes)
|
|
{
|
|
var statement = StartBinding();
|
|
var bindInfo = statement.SpawnBindInfo();
|
|
bindInfo.ContractTypes.AddRange(contractTypes);
|
|
return BindInternal(bindInfo, statement);
|
|
}
|
|
|
|
ConcreteIdBinderNonGeneric BindInternal(
|
|
BindInfo bindInfo, BindStatement bindingFinalizer)
|
|
{
|
|
#if ZEN_INTERNAL_PROFILING
|
|
using (ProfileTimers.CreateTimedBlock("DiContainer.Bind"))
|
|
#endif
|
|
{
|
|
Assert.That(bindInfo.ContractTypes.All(x => !x.DerivesFrom<IPlaceholderFactory>()),
|
|
"You should not use Container.Bind for factory classes. Use Container.BindFactory instead.");
|
|
|
|
return new ConcreteIdBinderNonGeneric(this, bindInfo, bindingFinalizer);
|
|
}
|
|
}
|
|
|
|
#if !(UNITY_WSA && ENABLE_DOTNET)
|
|
public ConcreteIdBinderNonGeneric Bind(
|
|
Action<ConventionSelectTypesBinder> generator)
|
|
{
|
|
var conventionBindInfo = new ConventionBindInfo();
|
|
generator(new ConventionSelectTypesBinder(conventionBindInfo));
|
|
|
|
var contractTypesList = conventionBindInfo.ResolveTypes();
|
|
|
|
Assert.That(contractTypesList.All(x => !x.DerivesFrom<IPlaceholderFactory>()),
|
|
"You should not use Container.Bind for factory classes. Use Container.BindFactory instead.");
|
|
|
|
var statement = StartBinding();
|
|
var bindInfo = statement.SpawnBindInfo();
|
|
bindInfo.ContractTypes.AllocFreeAddRange(contractTypesList);
|
|
|
|
// This is nice because it allows us to do things like Bind(all interfaces).To<Foo>()
|
|
// (though of course it would be more efficient to use BindInterfacesTo in this case)
|
|
bindInfo.InvalidBindResponse = InvalidBindResponses.Skip;
|
|
|
|
return new ConcreteIdBinderNonGeneric(this, bindInfo, statement);
|
|
}
|
|
#endif
|
|
|
|
// Bind all the interfaces for the given type to the same thing.
|
|
//
|
|
// Example:
|
|
//
|
|
// public class Foo : ITickable, IInitializable
|
|
// {
|
|
// }
|
|
//
|
|
// Container.BindInterfacesTo<Foo>().AsSingle();
|
|
//
|
|
// This line above is equivalent to the following:
|
|
//
|
|
// Container.Bind<ITickable>().ToSingle<Foo>();
|
|
// Container.Bind<IInitializable>().ToSingle<Foo>();
|
|
//
|
|
// Note here that we do not bind Foo to itself. For that, use BindInterfacesAndSelfTo
|
|
public FromBinderNonGeneric BindInterfacesTo<T>()
|
|
{
|
|
return BindInterfacesTo(typeof(T));
|
|
}
|
|
|
|
public FromBinderNonGeneric BindInterfacesTo(Type type)
|
|
{
|
|
var statement = StartBinding();
|
|
var bindInfo = statement.SpawnBindInfo();
|
|
|
|
var interfaces = type.Interfaces();
|
|
|
|
if (interfaces.Length == 0)
|
|
{
|
|
Log.Warn("Called BindInterfacesTo for type {0} but no interfaces were found", type);
|
|
}
|
|
|
|
bindInfo.ContractTypes.AllocFreeAddRange(interfaces);
|
|
bindInfo.SetContextInfo("BindInterfacesTo({0})".Fmt(type));
|
|
|
|
// Almost always, you don't want to use the default AsTransient so make them type it
|
|
bindInfo.RequireExplicitScope = true;
|
|
return BindInternal(bindInfo, statement).To(type);
|
|
}
|
|
|
|
// Same as BindInterfaces except also binds to self
|
|
public FromBinderNonGeneric BindInterfacesAndSelfTo<T>()
|
|
{
|
|
return BindInterfacesAndSelfTo(typeof(T));
|
|
}
|
|
|
|
public FromBinderNonGeneric BindInterfacesAndSelfTo(Type type)
|
|
{
|
|
var statement = StartBinding();
|
|
var bindInfo = statement.SpawnBindInfo();
|
|
|
|
bindInfo.ContractTypes.AllocFreeAddRange(type.Interfaces());
|
|
bindInfo.ContractTypes.Add(type);
|
|
|
|
bindInfo.SetContextInfo("BindInterfacesAndSelfTo({0})".Fmt(type));
|
|
|
|
// Almost always, you don't want to use the default AsTransient so make them type it
|
|
bindInfo.RequireExplicitScope = true;
|
|
return BindInternal(bindInfo, statement).To(type);
|
|
}
|
|
|
|
// This is simply a shortcut to using the FromInstance method.
|
|
//
|
|
// Example:
|
|
// Container.BindInstance(new Foo());
|
|
//
|
|
// This line above is equivalent to the following:
|
|
//
|
|
// Container.Bind<Foo>().FromInstance(new Foo());
|
|
//
|
|
public IdScopeConcreteIdArgConditionCopyNonLazyBinder BindInstance<TContract>(TContract instance)
|
|
{
|
|
var statement = StartBinding();
|
|
var bindInfo = statement.SpawnBindInfo();
|
|
bindInfo.ContractTypes.Add(typeof(TContract));
|
|
|
|
statement.SetFinalizer(
|
|
new ScopableBindingFinalizer(
|
|
bindInfo,
|
|
(container, type) => new InstanceProvider(type, instance, container, bindInfo.InstantiatedCallback)));
|
|
|
|
return new IdScopeConcreteIdArgConditionCopyNonLazyBinder(bindInfo);
|
|
}
|
|
|
|
// Unfortunately we can't support setting scope / condition / etc. here since all the
|
|
// bindings are finalized one at a time
|
|
public void BindInstances(params object[] instances)
|
|
{
|
|
for (int i = 0; i < instances.Length; i++)
|
|
{
|
|
var instance = instances[i];
|
|
|
|
Assert.That(!ZenUtilInternal.IsNull(instance),
|
|
"Found null instance provided to BindInstances method");
|
|
|
|
Bind(instance.GetType()).FromInstance(instance);
|
|
}
|
|
}
|
|
|
|
FactoryToChoiceIdBinder<TContract> BindFactoryInternal<TContract, TFactoryContract, TFactoryConcrete>()
|
|
where TFactoryConcrete : TFactoryContract, IFactory
|
|
where TFactoryContract : IFactory
|
|
{
|
|
var statement = StartBinding();
|
|
var bindInfo = statement.SpawnBindInfo();
|
|
bindInfo.ContractTypes.Add(typeof(TFactoryContract));
|
|
|
|
var factoryBindInfo = new FactoryBindInfo(typeof(TFactoryConcrete));
|
|
|
|
statement.SetFinalizer(
|
|
new PlaceholderFactoryBindingFinalizer<TContract>(
|
|
bindInfo, factoryBindInfo));
|
|
|
|
return new FactoryToChoiceIdBinder<TContract>(
|
|
this, bindInfo, factoryBindInfo);
|
|
}
|
|
|
|
public FactoryToChoiceIdBinder<TContract> BindIFactory<TContract>()
|
|
{
|
|
return BindFactoryInternal<TContract, IFactory<TContract>, PlaceholderFactory<TContract>>();
|
|
}
|
|
|
|
public FactoryToChoiceIdBinder<TContract> BindFactory<TContract, TFactory>()
|
|
where TFactory : PlaceholderFactory<TContract>
|
|
{
|
|
return BindFactoryInternal<TContract, TFactory, TFactory>();
|
|
}
|
|
|
|
public FactoryToChoiceIdBinder<TContract> BindFactoryCustomInterface<TContract, TFactoryConcrete, TFactoryContract>()
|
|
where TFactoryConcrete : PlaceholderFactory<TContract>, TFactoryContract
|
|
where TFactoryContract : IFactory
|
|
{
|
|
return BindFactoryInternal<TContract, TFactoryContract, TFactoryConcrete>();
|
|
}
|
|
|
|
public MemoryPoolIdInitialSizeMaxSizeBinder<TItemContract> BindMemoryPool<TItemContract>()
|
|
{
|
|
return BindMemoryPool<TItemContract, MemoryPool<TItemContract>>();
|
|
}
|
|
|
|
public MemoryPoolIdInitialSizeMaxSizeBinder<TItemContract> BindMemoryPool<TItemContract, TPool>()
|
|
where TPool : IMemoryPool
|
|
{
|
|
return BindMemoryPoolCustomInterface<TItemContract, TPool, TPool>();
|
|
}
|
|
|
|
public MemoryPoolIdInitialSizeMaxSizeBinder<TItemContract> BindMemoryPoolCustomInterface<TItemContract, TPoolConcrete, TPoolContract>(bool includeConcreteType = false)
|
|
where TPoolConcrete : TPoolContract, IMemoryPool
|
|
where TPoolContract : IMemoryPool
|
|
{
|
|
return BindMemoryPoolCustomInterfaceInternal<TItemContract, TPoolConcrete, TPoolContract>(includeConcreteType, StartBinding());
|
|
}
|
|
|
|
internal MemoryPoolIdInitialSizeMaxSizeBinder<TItemContract> BindMemoryPoolCustomInterfaceNoFlush<TItemContract, TPoolConcrete, TPoolContract>(bool includeConcreteType = false)
|
|
where TPoolConcrete : TPoolContract, IMemoryPool
|
|
where TPoolContract : IMemoryPool
|
|
{
|
|
return BindMemoryPoolCustomInterfaceInternal<TItemContract, TPoolConcrete, TPoolContract>(includeConcreteType, StartBinding(false));
|
|
}
|
|
|
|
MemoryPoolIdInitialSizeMaxSizeBinder<TItemContract> BindMemoryPoolCustomInterfaceInternal<TItemContract, TPoolConcrete, TPoolContract>(
|
|
bool includeConcreteType, BindStatement statement)
|
|
where TPoolConcrete : TPoolContract, IMemoryPool
|
|
where TPoolContract : IMemoryPool
|
|
{
|
|
var contractTypes = new List<Type> { typeof(IDisposable), typeof(TPoolContract) };
|
|
|
|
if (includeConcreteType)
|
|
{
|
|
contractTypes.Add(typeof(TPoolConcrete));
|
|
}
|
|
|
|
var bindInfo = statement.SpawnBindInfo();
|
|
|
|
bindInfo.ContractTypes.AllocFreeAddRange(contractTypes);
|
|
|
|
// This interface is used in the optional class PoolCleanupChecker
|
|
// And also allow people to manually call DespawnAll() for all IMemoryPool
|
|
// if they want
|
|
bindInfo.ContractTypes.Add(typeof(IMemoryPool));
|
|
|
|
var factoryBindInfo = new FactoryBindInfo(typeof(TPoolConcrete));
|
|
var poolBindInfo = new MemoryPoolBindInfo();
|
|
|
|
statement.SetFinalizer(
|
|
new MemoryPoolBindingFinalizer<TItemContract>(
|
|
bindInfo, factoryBindInfo, poolBindInfo));
|
|
|
|
return new MemoryPoolIdInitialSizeMaxSizeBinder<TItemContract>(
|
|
this, bindInfo, factoryBindInfo, poolBindInfo);
|
|
}
|
|
|
|
FactoryToChoiceIdBinder<TParam1, TContract> BindFactoryInternal<TParam1, TContract, TFactoryContract, TFactoryConcrete>()
|
|
where TFactoryConcrete : TFactoryContract, IFactory
|
|
where TFactoryContract : IFactory
|
|
{
|
|
var statement = StartBinding();
|
|
var bindInfo = statement.SpawnBindInfo();
|
|
|
|
bindInfo.ContractTypes.Add(typeof(TFactoryContract));
|
|
|
|
var factoryBindInfo = new FactoryBindInfo(typeof(TFactoryConcrete));
|
|
|
|
statement.SetFinalizer(
|
|
new PlaceholderFactoryBindingFinalizer<TContract>(
|
|
bindInfo, factoryBindInfo));
|
|
|
|
return new FactoryToChoiceIdBinder<TParam1, TContract>(
|
|
this, bindInfo, factoryBindInfo);
|
|
}
|
|
|
|
public FactoryToChoiceIdBinder<TParam1, TContract> BindIFactory<TParam1, TContract>()
|
|
{
|
|
return BindFactoryInternal<
|
|
TParam1, TContract, IFactory<TParam1, TContract>, PlaceholderFactory<TParam1, TContract>>();
|
|
}
|
|
|
|
public FactoryToChoiceIdBinder<TParam1, TContract> BindFactory<TParam1, TContract, TFactory>()
|
|
where TFactory : PlaceholderFactory<TParam1, TContract>
|
|
{
|
|
return BindFactoryInternal<
|
|
TParam1, TContract, TFactory, TFactory>();
|
|
}
|
|
|
|
public FactoryToChoiceIdBinder<TParam1, TContract> BindFactoryCustomInterface<TParam1, TContract, TFactoryConcrete, TFactoryContract>()
|
|
where TFactoryConcrete : PlaceholderFactory<TParam1, TContract>, TFactoryContract
|
|
where TFactoryContract : IFactory
|
|
{
|
|
return BindFactoryInternal<TParam1, TContract, TFactoryContract, TFactoryConcrete>();
|
|
}
|
|
|
|
FactoryToChoiceIdBinder<TParam1, TParam2, TContract> BindFactoryInternal<TParam1, TParam2, TContract, TFactoryContract, TFactoryConcrete>()
|
|
where TFactoryConcrete : TFactoryContract, IFactory
|
|
where TFactoryContract : IFactory
|
|
{
|
|
var statement = StartBinding();
|
|
var bindInfo = statement.SpawnBindInfo();
|
|
|
|
bindInfo.ContractTypes.Add(typeof(TFactoryContract));
|
|
|
|
var factoryBindInfo = new FactoryBindInfo(typeof(TFactoryConcrete));
|
|
|
|
statement.SetFinalizer(
|
|
new PlaceholderFactoryBindingFinalizer<TContract>(
|
|
bindInfo, factoryBindInfo));
|
|
|
|
return new FactoryToChoiceIdBinder<TParam1, TParam2, TContract>(
|
|
this, bindInfo, factoryBindInfo);
|
|
}
|
|
|
|
public FactoryToChoiceIdBinder<TParam1, TParam2, TContract> BindIFactory<TParam1, TParam2, TContract>()
|
|
{
|
|
return BindFactoryInternal<
|
|
TParam1, TParam2, TContract, IFactory<TParam1, TParam2, TContract>, PlaceholderFactory<TParam1, TParam2, TContract>>();
|
|
}
|
|
|
|
public FactoryToChoiceIdBinder<TParam1, TParam2, TContract> BindFactory<TParam1, TParam2, TContract, TFactory>()
|
|
where TFactory : PlaceholderFactory<TParam1, TParam2, TContract>
|
|
{
|
|
return BindFactoryInternal<
|
|
TParam1, TParam2, TContract, TFactory, TFactory>();
|
|
}
|
|
|
|
public FactoryToChoiceIdBinder<TParam1, TParam2, TContract> BindFactoryCustomInterface<TParam1, TParam2, TContract, TFactoryConcrete, TFactoryContract>()
|
|
where TFactoryConcrete : PlaceholderFactory<TParam1, TParam2, TContract>, TFactoryContract
|
|
where TFactoryContract : IFactory
|
|
{
|
|
return BindFactoryInternal<TParam1, TParam2, TContract, TFactoryContract, TFactoryConcrete>();
|
|
}
|
|
|
|
FactoryToChoiceIdBinder<TParam1, TParam2, TParam3, TContract> BindFactoryInternal<TParam1, TParam2, TParam3, TContract, TFactoryContract, TFactoryConcrete>()
|
|
where TFactoryConcrete : TFactoryContract, IFactory
|
|
where TFactoryContract : IFactory
|
|
{
|
|
var statement = StartBinding();
|
|
var bindInfo = statement.SpawnBindInfo();
|
|
|
|
bindInfo.ContractTypes.Add(typeof(TFactoryContract));
|
|
|
|
var factoryBindInfo = new FactoryBindInfo(typeof(TFactoryConcrete));
|
|
|
|
statement.SetFinalizer(
|
|
new PlaceholderFactoryBindingFinalizer<TContract>(
|
|
bindInfo, factoryBindInfo));
|
|
|
|
return new FactoryToChoiceIdBinder<TParam1, TParam2, TParam3, TContract>(
|
|
this, bindInfo, factoryBindInfo);
|
|
}
|
|
|
|
public FactoryToChoiceIdBinder<TParam1, TParam2, TParam3, TContract> BindIFactory<TParam1, TParam2, TParam3, TContract>()
|
|
{
|
|
return BindFactoryInternal<
|
|
TParam1, TParam2, TParam3, TContract, IFactory<TParam1, TParam2, TParam3, TContract>, PlaceholderFactory<TParam1, TParam2, TParam3, TContract>>();
|
|
}
|
|
|
|
public FactoryToChoiceIdBinder<TParam1, TParam2, TParam3, TContract> BindFactory<TParam1, TParam2, TParam3, TContract, TFactory>()
|
|
where TFactory : PlaceholderFactory<TParam1, TParam2, TParam3, TContract>
|
|
{
|
|
return BindFactoryInternal<
|
|
TParam1, TParam2, TParam3, TContract, TFactory, TFactory>();
|
|
}
|
|
|
|
public FactoryToChoiceIdBinder<TParam1, TParam2, TParam3, TContract> BindFactoryCustomInterface<TParam1, TParam2, TParam3, TContract, TFactoryConcrete, TFactoryContract>()
|
|
where TFactoryConcrete : PlaceholderFactory<TParam1, TParam2, TParam3, TContract>, TFactoryContract
|
|
where TFactoryContract : IFactory
|
|
{
|
|
return BindFactoryInternal<TParam1, TParam2, TParam3, TContract, TFactoryContract, TFactoryConcrete>();
|
|
}
|
|
|
|
FactoryToChoiceIdBinder<TParam1, TParam2, TParam3, TParam4, TContract> BindFactoryInternal<TParam1, TParam2, TParam3, TParam4, TContract, TFactoryContract, TFactoryConcrete>()
|
|
where TFactoryConcrete : TFactoryContract, IFactory
|
|
where TFactoryContract : IFactory
|
|
{
|
|
var statement = StartBinding();
|
|
var bindInfo = statement.SpawnBindInfo();
|
|
|
|
bindInfo.ContractTypes.Add(typeof(TFactoryContract));
|
|
|
|
var factoryBindInfo = new FactoryBindInfo(typeof(TFactoryConcrete));
|
|
|
|
statement.SetFinalizer(
|
|
new PlaceholderFactoryBindingFinalizer<TContract>(
|
|
bindInfo, factoryBindInfo));
|
|
|
|
return new FactoryToChoiceIdBinder<TParam1, TParam2, TParam3, TParam4, TContract>(
|
|
this, bindInfo, factoryBindInfo);
|
|
}
|
|
|
|
public FactoryToChoiceIdBinder<TParam1, TParam2, TParam3, TParam4, TContract> BindIFactory<TParam1, TParam2, TParam3, TParam4, TContract>()
|
|
{
|
|
return BindFactoryInternal<
|
|
TParam1, TParam2, TParam3, TParam4, TContract, IFactory<TParam1, TParam2, TParam3, TParam4, TContract>, PlaceholderFactory<TParam1, TParam2, TParam3, TParam4, TContract>>();
|
|
}
|
|
|
|
public FactoryToChoiceIdBinder<TParam1, TParam2, TParam3, TParam4, TContract> BindFactory<TParam1, TParam2, TParam3, TParam4, TContract, TFactory>()
|
|
where TFactory : PlaceholderFactory<TParam1, TParam2, TParam3, TParam4, TContract>
|
|
{
|
|
return BindFactoryInternal<
|
|
TParam1, TParam2, TParam3, TParam4, TContract, TFactory, TFactory>();
|
|
}
|
|
|
|
public FactoryToChoiceIdBinder<TParam1, TParam2, TParam3, TParam4, TContract> BindFactoryCustomInterface<TParam1, TParam2, TParam3, TParam4, TContract, TFactoryConcrete, TFactoryContract>()
|
|
where TFactoryConcrete : PlaceholderFactory<TParam1, TParam2, TParam3, TParam4, TContract>, TFactoryContract
|
|
where TFactoryContract : IFactory
|
|
{
|
|
return BindFactoryInternal<TParam1, TParam2, TParam3, TParam4, TContract, TFactoryContract, TFactoryConcrete>();
|
|
}
|
|
|
|
FactoryToChoiceIdBinder<TParam1, TParam2, TParam3, TParam4, TParam5, TContract> BindFactoryInternal<TParam1, TParam2, TParam3, TParam4, TParam5, TContract, TFactoryContract, TFactoryConcrete>()
|
|
where TFactoryConcrete : TFactoryContract, IFactory
|
|
where TFactoryContract : IFactory
|
|
{
|
|
var statement = StartBinding();
|
|
var bindInfo = statement.SpawnBindInfo();
|
|
|
|
bindInfo.ContractTypes.Add(typeof(TFactoryContract));
|
|
|
|
var factoryBindInfo = new FactoryBindInfo(typeof(TFactoryConcrete));
|
|
|
|
statement.SetFinalizer(
|
|
new PlaceholderFactoryBindingFinalizer<TContract>(
|
|
bindInfo, factoryBindInfo));
|
|
|
|
return new FactoryToChoiceIdBinder<TParam1, TParam2, TParam3, TParam4, TParam5, TContract>(
|
|
this, bindInfo, factoryBindInfo);
|
|
}
|
|
|
|
public FactoryToChoiceIdBinder<TParam1, TParam2, TParam3, TParam4, TParam5, TContract> BindIFactory<TParam1, TParam2, TParam3, TParam4, TParam5, TContract>()
|
|
{
|
|
return BindFactoryInternal<
|
|
TParam1, TParam2, TParam3, TParam4, TParam5, TContract, IFactory<TParam1, TParam2, TParam3, TParam4, TParam5, TContract>, PlaceholderFactory<TParam1, TParam2, TParam3, TParam4, TParam5, TContract>>();
|
|
}
|
|
|
|
public FactoryToChoiceIdBinder<TParam1, TParam2, TParam3, TParam4, TParam5, TContract> BindFactory<TParam1, TParam2, TParam3, TParam4, TParam5, TContract, TFactory>()
|
|
where TFactory : PlaceholderFactory<TParam1, TParam2, TParam3, TParam4, TParam5, TContract>
|
|
{
|
|
return BindFactoryInternal<
|
|
TParam1, TParam2, TParam3, TParam4, TParam5, TContract, TFactory, TFactory>();
|
|
}
|
|
|
|
public FactoryToChoiceIdBinder<TParam1, TParam2, TParam3, TParam4, TParam5, TContract> BindFactoryCustomInterface<TParam1, TParam2, TParam3, TParam4, TParam5, TContract, TFactoryConcrete, TFactoryContract>()
|
|
where TFactoryConcrete : PlaceholderFactory<TParam1, TParam2, TParam3, TParam4, TParam5, TContract>, TFactoryContract
|
|
where TFactoryContract : IFactory
|
|
{
|
|
return BindFactoryInternal<TParam1, TParam2, TParam3, TParam4, TParam5, TContract, TFactoryContract, TFactoryConcrete>();
|
|
}
|
|
|
|
FactoryToChoiceIdBinder<TParam1, TParam2, TParam3, TParam4, TParam5, TParam6, TContract> BindFactoryInternal<TParam1, TParam2, TParam3, TParam4, TParam5, TParam6, TContract, TFactoryContract, TFactoryConcrete>()
|
|
where TFactoryConcrete : TFactoryContract, IFactory
|
|
where TFactoryContract : IFactory
|
|
{
|
|
var statement = StartBinding();
|
|
var bindInfo = statement.SpawnBindInfo();
|
|
|
|
bindInfo.ContractTypes.Add(typeof(TFactoryContract));
|
|
|
|
var factoryBindInfo = new FactoryBindInfo(typeof(TFactoryConcrete));
|
|
|
|
statement.SetFinalizer(
|
|
new PlaceholderFactoryBindingFinalizer<TContract>(
|
|
bindInfo, factoryBindInfo));
|
|
|
|
return new FactoryToChoiceIdBinder<TParam1, TParam2, TParam3, TParam4, TParam5, TParam6, TContract>(
|
|
this, bindInfo, factoryBindInfo);
|
|
}
|
|
|
|
public FactoryToChoiceIdBinder<TParam1, TParam2, TParam3, TParam4, TParam5, TParam6, TContract> BindIFactory<TParam1, TParam2, TParam3, TParam4, TParam5, TParam6, TContract>()
|
|
{
|
|
return BindFactoryInternal<
|
|
TParam1, TParam2, TParam3, TParam4, TParam5, TParam6, TContract, IFactory<TParam1, TParam2, TParam3, TParam4, TParam5, TParam6, TContract>, PlaceholderFactory<TParam1, TParam2, TParam3, TParam4, TParam5, TParam6, TContract>>();
|
|
}
|
|
|
|
public FactoryToChoiceIdBinder<TParam1, TParam2, TParam3, TParam4, TParam5, TParam6, TContract> BindFactory<TParam1, TParam2, TParam3, TParam4, TParam5, TParam6, TContract, TFactory>()
|
|
where TFactory : PlaceholderFactory<TParam1, TParam2, TParam3, TParam4, TParam5, TParam6, TContract>
|
|
{
|
|
return BindFactoryInternal<
|
|
TParam1, TParam2, TParam3, TParam4, TParam5, TParam6, TContract, TFactory, TFactory>();
|
|
}
|
|
|
|
public FactoryToChoiceIdBinder<TParam1, TParam2, TParam3, TParam4, TParam5, TParam6, TContract> BindFactoryCustomInterface<TParam1, TParam2, TParam3, TParam4, TParam5, TParam6, TContract, TFactoryConcrete, TFactoryContract>()
|
|
where TFactoryConcrete : PlaceholderFactory<TParam1, TParam2, TParam3, TParam4, TParam5, TParam6, TContract>, TFactoryContract
|
|
where TFactoryContract : IFactory
|
|
{
|
|
return BindFactoryInternal<TParam1, TParam2, TParam3, TParam4, TParam5, TParam6, TContract, TFactoryContract, TFactoryConcrete>();
|
|
}
|
|
|
|
FactoryToChoiceIdBinder<TParam1, TParam2, TParam3, TParam4, TParam5, TParam6, TParam7, TParam8, TParam9, TParam10, TContract> BindFactoryInternal<TParam1, TParam2, TParam3, TParam4, TParam5, TParam6, TParam7, TParam8, TParam9, TParam10, TContract, TFactoryContract, TFactoryConcrete>()
|
|
where TFactoryConcrete : TFactoryContract, IFactory
|
|
where TFactoryContract : IFactory
|
|
{
|
|
var statement = StartBinding();
|
|
var bindInfo = statement.SpawnBindInfo();
|
|
|
|
bindInfo.ContractTypes.Add(typeof(TFactoryContract));
|
|
|
|
var factoryBindInfo = new FactoryBindInfo(typeof(TFactoryConcrete));
|
|
|
|
statement.SetFinalizer(
|
|
new PlaceholderFactoryBindingFinalizer<TContract>(
|
|
bindInfo, factoryBindInfo));
|
|
|
|
return new FactoryToChoiceIdBinder<TParam1, TParam2, TParam3, TParam4, TParam5, TParam6, TParam7, TParam8, TParam9, TParam10, TContract>(
|
|
this, bindInfo, factoryBindInfo);
|
|
}
|
|
|
|
public FactoryToChoiceIdBinder<TParam1, TParam2, TParam3, TParam4, TParam5, TParam6, TParam7, TParam8, TParam9, TParam10, TContract> BindIFactory<TParam1, TParam2, TParam3, TParam4, TParam5, TParam6, TParam7, TParam8, TParam9, TParam10, TContract>()
|
|
{
|
|
return BindFactoryInternal<
|
|
TParam1, TParam2, TParam3, TParam4, TParam5, TParam6, TParam7, TParam8, TParam9, TParam10, TContract, IFactory<TParam1, TParam2, TParam3, TParam4, TParam5, TParam6, TParam7, TParam8, TParam9, TParam10, TContract>, PlaceholderFactory<TParam1, TParam2, TParam3, TParam4, TParam5, TParam6, TParam7, TParam8, TParam9, TParam10, TContract>>();
|
|
}
|
|
|
|
public FactoryToChoiceIdBinder<TParam1, TParam2, TParam3, TParam4, TParam5, TParam6, TParam7, TParam8, TParam9, TParam10, TContract> BindFactory<TParam1, TParam2, TParam3, TParam4, TParam5, TParam6, TParam7, TParam8, TParam9, TParam10, TContract, TFactory>()
|
|
where TFactory : PlaceholderFactory<TParam1, TParam2, TParam3, TParam4, TParam5, TParam6, TParam7, TParam8, TParam9, TParam10, TContract>
|
|
{
|
|
return BindFactoryInternal<
|
|
TParam1, TParam2, TParam3, TParam4, TParam5, TParam6, TParam7, TParam8, TParam9, TParam10, TContract, TFactory, TFactory>();
|
|
}
|
|
|
|
public FactoryToChoiceIdBinder<TParam1, TParam2, TParam3, TParam4, TParam5, TParam6, TParam7, TParam8, TParam9, TParam10, TContract> BindFactoryCustomInterface<TParam1, TParam2, TParam3, TParam4, TParam5, TParam6, TParam7, TParam8, TParam9, TParam10, TContract, TFactoryConcrete, TFactoryContract>()
|
|
where TFactoryConcrete : PlaceholderFactory<TParam1, TParam2, TParam3, TParam4, TParam5, TParam6, TParam7, TParam8, TParam9, TParam10, TContract>, TFactoryContract
|
|
where TFactoryContract : IFactory
|
|
{
|
|
return BindFactoryInternal<TParam1, TParam2, TParam3, TParam4, TParam5, TParam6, TParam7, TParam8, TParam9, TParam10, TContract, TFactoryContract, TFactoryConcrete>();
|
|
}
|
|
|
|
public T InstantiateExplicit<T>(List<TypeValuePair> extraArgs)
|
|
{
|
|
return (T)InstantiateExplicit(typeof(T), extraArgs);
|
|
}
|
|
|
|
public object InstantiateExplicit(Type concreteType, List<TypeValuePair> extraArgs)
|
|
{
|
|
bool autoInject = true;
|
|
|
|
return InstantiateExplicit(
|
|
concreteType,
|
|
autoInject,
|
|
extraArgs,
|
|
new InjectContext(this, concreteType, null),
|
|
null);
|
|
}
|
|
|
|
public object InstantiateExplicit(Type concreteType, bool autoInject, List<TypeValuePair> extraArgs, InjectContext context, object concreteIdentifier)
|
|
{
|
|
#if ZEN_INTERNAL_PROFILING
|
|
using (ProfileTimers.CreateTimedBlock("DiContainer.Instantiate"))
|
|
#endif
|
|
{
|
|
if (IsValidating)
|
|
{
|
|
if (_settings.ValidationErrorResponse == ValidationErrorResponses.Throw)
|
|
{
|
|
return InstantiateInternal(concreteType, autoInject, extraArgs, context, concreteIdentifier);
|
|
}
|
|
|
|
// In this case, just log it and continue to print out multiple validation errors
|
|
// at once
|
|
try
|
|
{
|
|
return InstantiateInternal(concreteType, autoInject, extraArgs, context, concreteIdentifier);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Log.ErrorException(e);
|
|
return new ValidationMarker(concreteType, true);
|
|
}
|
|
}
|
|
|
|
return InstantiateInternal(concreteType, autoInject, extraArgs, context, concreteIdentifier);
|
|
}
|
|
}
|
|
|
|
#if !NOT_UNITY3D
|
|
public Component InstantiateComponentExplicit(
|
|
Type componentType, GameObject gameObject, List<TypeValuePair> extraArgs)
|
|
{
|
|
Assert.That(componentType.DerivesFrom<Component>());
|
|
|
|
FlushBindings();
|
|
|
|
var monoBehaviour = gameObject.AddComponent(componentType);
|
|
InjectExplicit(monoBehaviour, extraArgs);
|
|
return monoBehaviour;
|
|
}
|
|
|
|
public object InstantiateScriptableObjectResourceExplicit(
|
|
Type scriptableObjectType, string resourcePath, List<TypeValuePair> extraArgs)
|
|
{
|
|
var objects = Resources.LoadAll(resourcePath, scriptableObjectType);
|
|
|
|
Assert.That(objects.Length > 0,
|
|
"Could not find resource at path '{0}' with type '{1}'", resourcePath, scriptableObjectType);
|
|
|
|
Assert.That(objects.Length == 1,
|
|
"Found multiple scriptable objects at path '{0}' when only 1 was expected with type '{1}'", resourcePath, scriptableObjectType);
|
|
|
|
var newObj = ScriptableObject.Instantiate(objects.Single());
|
|
|
|
InjectExplicit(newObj, extraArgs);
|
|
|
|
return newObj;
|
|
}
|
|
|
|
// Same as InstantiatePrefabResourceForComponent except allows null values
|
|
// to be included in the argument list. Also see InjectUtil.CreateArgList
|
|
public object InstantiatePrefabResourceForComponentExplicit(
|
|
Type componentType, string resourcePath, List<TypeValuePair> extraArgs,
|
|
GameObjectCreationParameters creationInfo)
|
|
{
|
|
return InstantiatePrefabResourceForComponentExplicit(
|
|
componentType, resourcePath, extraArgs, new InjectContext(this, componentType, null), null, creationInfo);
|
|
}
|
|
|
|
public object InstantiatePrefabResourceForComponentExplicit(
|
|
Type componentType, string resourcePath, List<TypeValuePair> extraArgs, InjectContext context, object concreteIdentifier,
|
|
GameObjectCreationParameters creationInfo)
|
|
{
|
|
var prefab = (GameObject)Resources.Load(resourcePath);
|
|
Assert.IsNotNull(prefab,
|
|
"Could not find prefab at resource location '{0}'".Fmt(resourcePath));
|
|
return InstantiatePrefabForComponentExplicit(
|
|
componentType, prefab, extraArgs, context, concreteIdentifier, creationInfo);
|
|
}
|
|
|
|
public object InstantiatePrefabForComponentExplicit(
|
|
Type componentType, UnityEngine.Object prefab,
|
|
List<TypeValuePair> extraArgs)
|
|
{
|
|
return InstantiatePrefabForComponentExplicit(
|
|
componentType, prefab, extraArgs, GameObjectCreationParameters.Default);
|
|
}
|
|
|
|
public object InstantiatePrefabForComponentExplicit(
|
|
Type componentType, UnityEngine.Object prefab,
|
|
List<TypeValuePair> extraArgs, GameObjectCreationParameters gameObjectBindInfo)
|
|
{
|
|
return InstantiatePrefabForComponentExplicit(
|
|
componentType, prefab, extraArgs, new InjectContext(this, componentType, null), null, gameObjectBindInfo);
|
|
}
|
|
|
|
// Same as InstantiatePrefabForComponent except allows null values
|
|
// to be included in the argument list. Also see InjectUtil.CreateArgList
|
|
public object InstantiatePrefabForComponentExplicit(
|
|
Type componentType, UnityEngine.Object prefab,
|
|
List<TypeValuePair> extraArgs, InjectContext context, object concreteIdentifier, GameObjectCreationParameters gameObjectBindInfo)
|
|
{
|
|
Assert.That(!AssertOnNewGameObjects,
|
|
"Given DiContainer does not support creating new game objects");
|
|
|
|
FlushBindings();
|
|
|
|
Assert.That(componentType.IsInterface() || componentType.DerivesFrom<Component>(),
|
|
"Expected type '{0}' to derive from UnityEngine.Component", componentType);
|
|
|
|
bool shouldMakeActive;
|
|
var gameObj = CreateAndParentPrefab(prefab, gameObjectBindInfo, context, out shouldMakeActive);
|
|
|
|
var component = InjectGameObjectForComponentExplicit(
|
|
gameObj, componentType, extraArgs, context, concreteIdentifier);
|
|
|
|
if (shouldMakeActive && !IsValidating)
|
|
{
|
|
#if ZEN_INTERNAL_PROFILING
|
|
using (ProfileTimers.CreateTimedBlock("User Code"))
|
|
#endif
|
|
{
|
|
gameObj.SetActive(true);
|
|
}
|
|
}
|
|
|
|
return component;
|
|
}
|
|
#endif
|
|
|
|
////////////// Execution order ////////////////
|
|
|
|
public void BindExecutionOrder<T>(int order)
|
|
{
|
|
BindExecutionOrder(typeof(T), order);
|
|
}
|
|
|
|
public void BindExecutionOrder(Type type, int order)
|
|
{
|
|
Assert.That(type.DerivesFrom<ITickable>() || type.DerivesFrom<IInitializable>() || type.DerivesFrom<IDisposable>() || type.DerivesFrom<ILateDisposable>() || type.DerivesFrom<IFixedTickable>() || type.DerivesFrom<ILateTickable>() || type.DerivesFrom<IPoolable>(),
|
|
"Expected type '{0}' to derive from one or more of the following interfaces: ITickable, IInitializable, ILateTickable, IFixedTickable, IDisposable, ILateDisposable", type);
|
|
|
|
if (type.DerivesFrom<ITickable>())
|
|
{
|
|
BindTickableExecutionOrder(type, order);
|
|
}
|
|
|
|
if (type.DerivesFrom<IInitializable>())
|
|
{
|
|
BindInitializableExecutionOrder(type, order);
|
|
}
|
|
|
|
if (type.DerivesFrom<IDisposable>())
|
|
{
|
|
BindDisposableExecutionOrder(type, order);
|
|
}
|
|
|
|
if (type.DerivesFrom<ILateDisposable>())
|
|
{
|
|
BindLateDisposableExecutionOrder(type, order);
|
|
}
|
|
|
|
if (type.DerivesFrom<IFixedTickable>())
|
|
{
|
|
BindFixedTickableExecutionOrder(type, order);
|
|
}
|
|
|
|
if (type.DerivesFrom<ILateTickable>())
|
|
{
|
|
BindLateTickableExecutionOrder(type, order);
|
|
}
|
|
|
|
if (type.DerivesFrom<IPoolable>())
|
|
{
|
|
BindPoolableExecutionOrder(type, order);
|
|
}
|
|
}
|
|
|
|
public CopyNonLazyBinder BindTickableExecutionOrder<T>(int order)
|
|
where T : ITickable
|
|
{
|
|
return BindTickableExecutionOrder(typeof(T), order);
|
|
}
|
|
|
|
public CopyNonLazyBinder BindTickableExecutionOrder(Type type, int order)
|
|
{
|
|
Assert.That(type.DerivesFrom<ITickable>(),
|
|
"Expected type '{0}' to derive from ITickable", type);
|
|
|
|
return BindInstance(
|
|
ValuePair.New(type, order)).WhenInjectedInto<TickableManager>();
|
|
}
|
|
|
|
public CopyNonLazyBinder BindInitializableExecutionOrder<T>(int order)
|
|
where T : IInitializable
|
|
{
|
|
return BindInitializableExecutionOrder(typeof(T), order);
|
|
}
|
|
|
|
public CopyNonLazyBinder BindInitializableExecutionOrder(Type type, int order)
|
|
{
|
|
Assert.That(type.DerivesFrom<IInitializable>(),
|
|
"Expected type '{0}' to derive from IInitializable", type);
|
|
|
|
return BindInstance(
|
|
ValuePair.New(type, order)).WhenInjectedInto<InitializableManager>();
|
|
}
|
|
|
|
public CopyNonLazyBinder BindDisposableExecutionOrder<T>(int order)
|
|
where T : IDisposable
|
|
{
|
|
return BindDisposableExecutionOrder(typeof(T), order);
|
|
}
|
|
|
|
public CopyNonLazyBinder BindLateDisposableExecutionOrder<T>(int order)
|
|
where T : ILateDisposable
|
|
{
|
|
return BindLateDisposableExecutionOrder(typeof(T), order);
|
|
}
|
|
|
|
public CopyNonLazyBinder BindDisposableExecutionOrder(Type type, int order)
|
|
{
|
|
Assert.That(type.DerivesFrom<IDisposable>(),
|
|
"Expected type '{0}' to derive from IDisposable", type);
|
|
|
|
return BindInstance(
|
|
ValuePair.New(type, order)).WhenInjectedInto<DisposableManager>();
|
|
}
|
|
|
|
public CopyNonLazyBinder BindLateDisposableExecutionOrder(Type type, int order)
|
|
{
|
|
Assert.That(type.DerivesFrom<ILateDisposable>(),
|
|
"Expected type '{0}' to derive from ILateDisposable", type);
|
|
|
|
return BindInstance(
|
|
ValuePair.New(type, order)).WithId("Late").WhenInjectedInto<DisposableManager>();
|
|
}
|
|
|
|
public CopyNonLazyBinder BindFixedTickableExecutionOrder<T>(int order)
|
|
where T : IFixedTickable
|
|
{
|
|
return BindFixedTickableExecutionOrder(typeof(T), order);
|
|
}
|
|
|
|
public CopyNonLazyBinder BindFixedTickableExecutionOrder(Type type, int order)
|
|
{
|
|
Assert.That(type.DerivesFrom<IFixedTickable>(),
|
|
"Expected type '{0}' to derive from IFixedTickable", type);
|
|
|
|
return Bind<ValuePair<Type, int>>().WithId("Fixed")
|
|
.FromInstance(ValuePair.New(type, order)).WhenInjectedInto<TickableManager>();
|
|
}
|
|
|
|
public CopyNonLazyBinder BindLateTickableExecutionOrder<T>(int order)
|
|
where T : ILateTickable
|
|
{
|
|
return BindLateTickableExecutionOrder(typeof(T), order);
|
|
}
|
|
|
|
public CopyNonLazyBinder BindLateTickableExecutionOrder(Type type, int order)
|
|
{
|
|
Assert.That(type.DerivesFrom<ILateTickable>(),
|
|
"Expected type '{0}' to derive from ILateTickable", type);
|
|
|
|
return Bind<ValuePair<Type, int>>().WithId("Late")
|
|
.FromInstance(ValuePair.New(type, order)).WhenInjectedInto<TickableManager>();
|
|
}
|
|
|
|
public CopyNonLazyBinder BindPoolableExecutionOrder<T>(int order)
|
|
where T : IPoolable
|
|
{
|
|
return BindPoolableExecutionOrder(typeof(T), order);
|
|
}
|
|
|
|
public CopyNonLazyBinder BindPoolableExecutionOrder(Type type, int order)
|
|
{
|
|
Assert.That(type.DerivesFrom<IPoolable>(),
|
|
"Expected type '{0}' to derive from IPoolable", type);
|
|
|
|
return Bind<ValuePair<Type, int>>()
|
|
.FromInstance(ValuePair.New(type, order)).WhenInjectedInto<PoolableManager>();
|
|
}
|
|
|
|
class ProviderInfo
|
|
{
|
|
public ProviderInfo(
|
|
IProvider provider, BindingCondition condition, bool nonLazy, DiContainer container)
|
|
{
|
|
Provider = provider;
|
|
Condition = condition;
|
|
NonLazy = nonLazy;
|
|
Container = container;
|
|
}
|
|
|
|
public readonly DiContainer Container;
|
|
public readonly bool NonLazy;
|
|
public readonly IProvider Provider;
|
|
public readonly BindingCondition Condition;
|
|
}
|
|
}
|
|
}
|