Skip to content

Components

This page contains information on all the components that make up the Dependency Container and how they interact.

Overview

The following diagram denotes the top-level relationship between different components of the IdleKit Dependency Container system. Relationship

IContext

The IContext hosts the IContainer and IInstaller. It usually lives as a MonoBehaviour component on a root GameObject containing all the child GameObjects in an Unity scene. All scripts under the root GameObject would be able to utilize the IContainer and have their dependencies injected. Please refer to MonoContext.cs for a concrete implementation of IContext.

IInstaller

The IInstaller is the location where the static bindings are established and registered to the IBinder. In IdleKit, it is where the registrations of all the service level bindings take place such as the IServices and IActions.

Registering (binding) the IServices and IActions

protected virtual void BindServices()
{
    ...
    _container.Bind<ICurrencyService>().To<CurrencyService>().AsSingleton().Conclude();
    _container.Bind<IEconomyService>().To<EconomyService>().AsSingleton().Conclude();
    _container.Bind<IEntityLoaderService>().To<EntityLoaderService>().AsSingleton().Conclude();
    ...
}

protected virtual void BindActions()
{
    ...
    _container.Bind<AdvanceStageStateAction>().AsSingleton().Conclude();
    _container.Bind<AscensionCompleteStateAction>().AsSingleton().Conclude();
    _container.Bind<AutomateGeneratorStateAction>().AsSingleton().Conclude();
    ...
}

Note

You can override the IInstaller to provide different implementations of services or actions, as shown below.

protected override void BindServices()
{                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         
    ...
    base.BindServices();

    //Replace the implementation for the contract type ICurrencyService.
    _container.Unbind<ICurrencyService>();
    _container.Bind<ICurrencyService>().To<SpecialCurrencyService>().AsSingleton().Conclude();
}

Implementations of IInstaller

These are the existing implementations of IInstaller in IdleKit: * MonoInstaller - An abstract MonoBehaviour based implementation of IInstaller * IKInstaller - The main installer in IdleKit that includes all static dependencies required. This class can be extended and overriden to specify additional non-MonoBehaviour based IBindings. * IKMonoInstaller - Contains IKInstaller as a component and implements MonoInstaller so it can be attached to a GameObject * StandardInstaller - Overrides IKInstaller for a TPB style idle game * StandardMonoInstaller - Similar to the IKMonoInstaller but uses StandardInstaller as its component * SimpleInstaller - Extends StandardMonoInstaller and overrides functionality to bind PlotPoint related dependencies

IContainer

The IContainer contains the main logic of the container. It is made up of the IResolver and IBinder interfaces. No class other then the IContext should ever cache a reference to an IContainer. Instead, they should cache the IResolver or IBinder for the sake of interface segregation. Please refer to Container.cs for a concrete implementation of IContainer.

IBinder

The IBinder is used to register bindings that describe the relationship between the classes. It allows bindings to be created, registered, or removed from the container. Once an IBinding is bound, a record of the IBinding is stored in the IBinder.

Warning

Conclude() must be called at the end of each Bind statement.

Using IBinder, usually in an IInstaller

    //Register binding with contract type Foo to concrete type Bar with a null id.
    _binder.Bind<Foo>().To<Bar>().Conclude();

    //Register binding with contract type Foo to concrete type Bar with id "foo".
    _binder.Bind<Foo>().To<Bar>().ToId("foo").Conclude();

Note

There are other ways to establish an IBinding. Please check the Cheat Sheet for more information.

IResolver

The IResolver is responsible for finding or creating an object and resolving its dependency according to the respective bindings that we registered in the IInstaller using the IBinder. The IResolver enables an instance of a contract type (with an optional id) to be retrieved as long as the corresponding IBinding exists.

Note

In the case that the corresponding IBinding doesn't exist (has not been bound), a BindingException would be thrown.

When Resolve is called with a type information and an optional id, the IResolver looks into the IBinding registry created by the IBinder. If an IBinding matching the type and id are found, the IProvider associated with the IBinding provides an instance for the IResolver to return.

Using IResolver to look for the IBinding that is associated with the type Foo and id.

    //Gets an instance based on the binding with type = Foo and id = null.
    Foo bar = _resolver.Resolve<Foo>();

    //Gets an instance based on the binding with type = Foo and id = "foo".
    Foo foo = _resolver.Resolve<Foo>("foo");

Note

Before the IResolver returns the instance, the IContainer utilizes the IInjector to inject all dependencies into the instance if the instance implements the IInjectable. However, it is valid to bind objects which are not IInjectable.

IInjector

IInjector is a component that lives in the IContainer that is responsible for injecting dependencies recursively into the IInjectable. It is not visible to the users. Please see the example below of the IResolver being injected into the Stage.cs, which is an IInjectable.

Note

This is how all Entities within IdleKit are dynamically bound at initialization of the game.

IInjectable

When an instance is being resolved in the container by the IResolver, it would have its dependency injected via the void Inject(IResolver resolver) method if the instance implements the IInjectable interface.

Warning

It is important that the resolver passed into the Inject method is never cached so it can only be used in the scope of the method. In addition, void Inject(IResolver) should only be used to inject the dependencies and absolutely no logic should be performed in the method.

//The IActionService is first bound to the container in the IKInstaller.
public class IKInstaller : IInstaller
{
    ...
    protected virtual void BindServices()
    {
        _binder.Bind<IActionService>().To<ActionService>().AsSingleton().Conclude();
        ...
    }
}

//When the Stage is loaded, IActionService is injected into the Stage with the IResolver.
public class Stage : IStage, IInjectable
{
    ...
    public virtual void Inject(IResolver resolver)
    {
        _actionService = resolver.Resolve<IActionService>();
        ...
    }
}

Warning

void Inject(IResolver) is only called if the IInjectable is obtained using the IResolver. You should only resolve using the IResolver and never directly invoke the constructors while working with the dependency container.

    IStage stage = new Stage("instanceId", "staticId"); // No injection will be done.
    IStage stage = _resolver.Resolve<IStage>("instanceId"); // Injection will be done before the IStage is returned.

Note

For more information on how an IEntity such as IStage is loaded, please visit the Understanding Entities page.

IBinding

The IBinding makes up the fundamentals of the container. An IBinding allows mapping (binding) of a specific contract type (interface, class) to an IProvider that provides the instance for the type. When an object of a specific type is requested, the IResolver uses the information stored in the IBinding and the IProvider to find or create, and then return an instance of the requested type.

The IBinding also contains an optional id field that is used for identification between different IBinding of the same contract type. Please check the Cheat Sheet for more information.

Fulfilling Contract

There are four ways to fulfill the contract (binding) type in IdleKit.

public interface IContract { }
public class TContract : IContract { }

//The user does not have to specify anything if the binding type is concrete.
_binder.Bind<TContract>().Conclude();

//Fulfilling the contract type with a concrete implementation of its derived type.
_binder.Bind<IContract>().To<TContract>().Conclude();

//Fulfilling the contract type with an instance of its derived type.
_binder.Bind<IContract>().ToInstance(new TContract()).Conclude();

//Fulfilling the contract type with a method producing an instance of its derived type.
_binder.Bind<IContract>().FromMethod(() => new TContract()).Conclude();

//Fulfilling the contract type with information provided by another provider.
IBinding binding = _binder.Bind<IContract>().To<TContract>().Conclude();
_binder.Bind<IContract>().FromProvider(binding.provider).Conclude();

Lifestyle

The lifestyle property of the IBinding denotes the scope of the instance returned by the IProvider in the IBinding. There are currently three types of lifestyle a IBinding can have:

Transient

Binding as Transient means that the TContract returns a new instance of a dependency with a contract type TContract every time it is resolved. This means that a Transient IBinding should always be bound by TContract instead of by ID, as the IDs will always be ignored and a new instance is always returned when resolving.

Cached

Binding as Cached means that the TContract returns the cached instance of a dependency with a contract type TContract when the TContract is resolved. This means that a Cached IBinding should always be both bound and resolved based on a unique ID. Resolving by TContract without specifying an ID returns an IBinding with an ID of null.

Singleton

A Singleton is a specific type of Cached IBinding which only allows a single instance of type TContract to be bound to the container. This means that a Singleton IBinding should always be bound and resolved by type TContract to retrieve the correct instance. A Singleton IBinding has an ID of null, and binding a new Singleton IBinding with type TContract invalidates all previous IBindings of the same contract type TContract.

The following diagram shows how to decide what type of lifestyle your TContract should be bound as.

binding_lifestyles

The following table shows how each type of binding should be bound and resolved.

Binding Type Bind as... Resolve as...
Transient TContract TContract
Cached TContract + ID TContract + ID
Singleton TContract TContract

IProvider

When properly configured, each IBinding has an associated IProvider. The primary function of IProvider is to provide an instance of the contract type for the IBinding that is associated with this IProvider. There are currently four concrete implementations of IProvider: * CachedProvider - Requires a provider (of the types listed below). Invoking GetInstance() returns an instance using that provider. It also caches the object so that the same object is returned when GetInstance() is called again, instead of re-injecting the object. * InstanceProvider - Requires a contract type and object. Invoking GetInstance() returns the object. A second call to GetInstance() returns the same object but re-injects it again. * MethodProvider - Requires a contract type and function. Invoking GetInstance() returns a new instance using the function * TransientProvider - Requires a contract type and concrete type. Invoking GetInstance() returns a new instance of the concrete type

Note

For examples on bindings with different lifestyles using different IProviders, please refer to the Cheat Sheet.