Getting Started with Strange IoC

I haven’t been posting for a while because I’ve been busy implementing the persistence feature using SimpleSQL and Strange. It’s not finished yet, thus this non development post of helping you getting a head start on using Strange.

I decided to use Strange because I want to explore this framework. Strange is an Inversion of Control (IoC) framework for Unity. IoC is another way of decoupling classes, and for me, any decoupling technique is worth studying. What is IoC? Read here. IoC frameworks have been a staple in enterprise and other non game software. It has matured in those areas tremendously. I’m betting on its promise that it makes games more testable and improves collaboration. I’m curious on how it can really help game development.

Using Strange is very “strange” indeed. I found it hard to set-up something like a Hello World for it. It has a how-to page but doesn’t really have a step by step guide on how to do it. This post is a step by step guide to a Strange Hello World.

Some notes:

  • Read the Strange how-to page first before heading back here for the guide on how to put them all together.
  • This guide uses Signals instead of Events (because I like Signals more than Events)
  • I won’t give a Unity project for this so that you’ll have to set it up on your own. You’ll probably learn more.
  • The Hello World sample merely gives a simple usage of injection binding, command binding, and mediation binding.

Start Signal

Start with an empty project. Download and extract the Strange framework in the Assets folder. The folder structure should look like this:

Assets
    StrangeIoC
        examples
        scripts
        StrangeIoCTemplate

Create a “Game” folder under Assets. This will the folder for our Hello World sample.

Assets
    Game
        Scenes
        Scripts

Create a script named HelloWorldSignals.cs under Scripts. This class will contain all signals used in the sample. Open up Mono. We begin coding.

using System;

using strange.extensions.signal.impl;

namespace Game {

    public class StartSignal : Signal {}

}

Signals are more like events in an observer pattern. They are implemented as named classes that extends from Signal. We’ll see later on how they are used.

Strange employs the concept of “contexts” to identify different problem domains or sub modules. In a real game project, you can have multiple contexts like game logic, asset sources, persistence, analytics, social features, etc. We only have one context in this guide for simplicity.

A pre-made context that is available in Strange is class called MVCSContext. However, MVCSContext uses events by default. We create another base context class that uses signals. This class can be reused in other contexts.

Create a script named SignalContext.cs under Scripts.

using System;

using UnityEngine;

using strange.extensions.context.impl;
using strange.extensions.command.api;
using strange.extensions.command.impl;
using strange.extensions.signal.impl;

namespace Game {
    public class SignalContext : MVCSContext {

        /**
         * Constructor
         */
        public SignalContext (MonoBehaviour contextView) : base(contextView) {
        }

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

            // bind signal command binder
            injectionBinder.Unbind<ICommandBinder>();
            injectionBinder.Bind<ICommandBinder>().To<SignalCommandBinder>().ToSingleton();
        }

        public override void Launch() {
            base.Launch();
            Signal startSignal = injectionBinder.GetInstance<StartSignal>();
            startSignal.Dispatch();
        }

    }
}

Create a new folder named “Controller” under “Scripts”. It’s pretty clear that we are heading towards an MVC pattern. The author of Strange suggests that we implement controllers as Command classes. This folder will contain all Command classes. For now, we create a command that will be executed when StartSignal is dispatched. Create a class named HelloWorldStartCommand  under Controller.

using System;

using UnityEngine;

using strange.extensions.context.api;
using strange.extensions.command.impl;

namespace Game {
    public class HelloWorldStartCommand : Command {

        public override void Execute() {
            // perform all game start setup here
            Debug.Log("Hello World");
        }

    }
}

Now we create the custom context class for the Hello World sample. Create a class named HelloWorldContext under Scripts.

using System;

using UnityEngine;

using strange.extensions.context.impl;

namespace Game {
    public class HelloWorldContext : SignalContext {

        /**
         * Constructor
         */
        public HelloWorldContext(MonoBehaviour contextView) : base(contextView) {
        }

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

            // we bind a command to StartSignal since it is invoked by SignalContext (the parent class) on Launch()
            commandBinder.Bind<StartSignal>().To<HelloWorldStartCommand>().Once();
        }

    }
}

In here, we bind StartSignal to HelloWorldStartCommand which means that HelloWorldStartCommand gets instantiated and executed whenever an instance of StartSignal is dispatched. Note that in our sample, StartSignal is dispatched in SignalContext.Launch().

The final step is to create the MonoBehaviour that will host the context. Create a class named HelloWorldBootstrap.cs under Scripts.

using System;

using UnityEngine;

using strange.extensions.context.impl;

namespace Game {
    public class HelloWorldBootstrap : ContextView {

        void Awake() {
            this.context = new HelloWorldContext(this);
        }

    }
}

Components that hosts a Strange context is usually called a “Bootstrap”. It’s just a suggestion but it can be anything. The only thing to note here is that it extends ContextView which is a MonoBehaviour. In Awake(), it assigns an instance of the custom context that we have implemented to a special inherited variable “context”.

Create a new empty scene named “HelloStrange” under Scenes folder. Create a new GameObject named “Bootstrap”. Add the HelloWorldBootstrap to it. Hit play. “Hello World” should be printed on the console.

Strange Hello World
Strange Hello World

Injection in Mediator

So much code, we only logged “Hello World”, what’s the fuzz with Strange then? Let me say that what we have done until now is pretty cool! We now have a working context. From here on, we can now add views and their corresponding mediators. We can also use the injection binder to map an instance to some interface which can be injected into controllers/commands and mediators without them knowing where the instance came from. Follow through the rest of the guide to see some magic.

When we make games, we usually use singleton managers like EnemyManager, AsteroidManager, CombatManager, etc. There are many ways to resolve an instance to any one manager. We may use GameObject.Find() or add a GetInstance() static method. Let’s create a hypothetical manager and see how instance resolution works in Strange. Create an interface named ISomeManager.cs under Scripts folder.

namespace Game {
    public interface ISomeManager {

        /**
         * Perform some management
         */
        void DoManagement();

    }
}

This will be the interface for our sample manager. The author of Strange suggests to always use an interface and map it to an actual implementation using the injectionBinder, although the mapping would still work without using one. Let’s create a concrete implementation. Create a class named ManagerAsNormalClass.cs under Scripts.

using System;

using UnityEngine;

namespace Game {
    public class ManagerAsNormalClass : ISomeManager {

        public ManagerAsNormalClass() {
        }

        #region ISomeManager implementation
        public void DoManagement() {
            Debug.Log("Manager implemented as a normal class");
        }
        #endregion

    }
}

This is a non MonoBehaviour version of the manager. I’ll show you later how to bind a MonoBehaviour one.

Let’s create a simple UI where there’s a button that when clicked, it invokes ISomeManager.DoManagement() without using a reference to ISomeManager. In an MVC pattern, this is ideal. Views should only say, “Hey I’m clicked/used” and it should not know what happens next.

Create a folder named View under Scripts. This folder will contain the View and Mediator classes.

Game
    Controller
    View

Create a file named HelloWorldView.cs under View folder.

using System;

using UnityEngine;

using strange.extensions.mediation.impl;
using strange.extensions.signal.impl;

namespace Game {
    public class HelloWorldView : View {

        public Signal buttonClicked = new Signal();

        private Rect buttonRect = new Rect(0, 0, 200, 50);

        public void OnGUI() {
            if(GUI.Button(buttonRect, "Manage")) {
                buttonClicked.Dispatch();
            }
        }

    }
}

View is a MonoBehaviour class that is included in Strange. All views that should work within a Strange context should extend this class. HelloWorldView just renders a “Manage” button that dispatches a generic signal when clicked.

The author of Strange recommends that each view should have a corresponding mediator. A mediator is a thin layer that lets a view communicate with the rest of the application. The mediation binder is used to map a view to its corresponding mediator. Let’s create the mediator to HelloWorldView. Create a file named HelloWorldMediator.cs under View folder.

using System;

using UnityEngine;

using strange.extensions.mediation.impl;

namespace Game {
    public class HelloWorldMediator : Mediator {

        [Inject]
        public HelloWorldView view {get; set;}

        [Inject]
        public ISomeManager manager {get; set;}

        public override void OnRegister() {
            view.buttonClicked.AddListener(delegate() {
                manager.DoManagement();
            });
        }

    }
}

Now we see the magical Inject attribute here. This attribute only works with properties. When a property has this attribute, it means that Strange will automatically assign/inject an instance to it based on the mappings in the context. In the perspective of the class above, it knows that it can get an instance for its properties view and manager without knowing where it came from.

OnRegister() is a method that can be overridden to mark that the injection of instances has been done and they are ready for use. What is done here is mostly initialization or preparation routines. In the class above, we add a listener to the HellowWorldView.buttonClicked signal using a closure that invokes ISomeManager.DoManagement().

To put them all together, we will map the binding in the Strange context. Open HelloWorldContext and set the following lines inside mapBindings() method:

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

            // we bind a command to StartSignal since it is invoked by SignalContext (the parent class) during on Launch()
            commandBinder.Bind<StartSignal>().To<HelloWorldStartCommand>().Once();

            // bind our view to its mediator
            mediationBinder.Bind<HelloWorldView>().To<HelloWorldMediator>();

            // bind our interface to a concrete implementation
            injectionBinder.Bind<ISomeManager>().To<ManagerAsNormalClass>().ToSingleton();
        }

In HelloStrange scene, create a new GameObject named “View” and add the HelloWorldView script to it. Play the scene. You should be able to see a Manage button that prints “Manager implemented as a normal class” when clicked.

ISomeManager in action
Strange in action

Note also that Strange automatically adds the mediator component when the scene is played. Remember that we didn’t add the HelloWorldMediator to the View scene object.

Where did the mediator came from?
Where did the mediator came from?

MonoBehaviour Manager

Almost always, we want our managers to be implemented as a MonoBehaviour so we can inherit its advantages like coroutines, and serialization. How do we bind a MonoBehaviour manager instance in Strange?

Let’s create our MonoBehaviour manager. Create a file named ManagerAsMonoBehaviour.cs under Scripts folder.

using System;

using UnityEngine;

namespace Game {
    public class ManagerAsMonoBehaviour : MonoBehaviour, ISomeManager {

        #region ISomeManager implementation
        public void DoManagement() {
            Debug.Log("Manager implemented as MonoBehaviour");
        }
        #endregion

    }
}

In HelloStrange scene, create a new scene object named “Manager” then add the ManagerAsMonoBehaviour script to it.

Edit mapBindings() method of HelloWorldContext to the following:

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

            // we bind a command to StartSignal since it is invoked by SignalContext (the parent class) during on Launch()
            commandBinder.Bind<StartSignal>().To<HelloWorldStartCommand>().Once();

            // bind our view to its mediator
            mediationBinder.Bind<HelloWorldView>().To<HelloWorldMediator>();

            // REMOVED!!!
            //injectionBinder.Bind<ISomeManager>().To<ManagerAsNormalClass>().ToSingleton();

            // bind the manager implemented as a MonoBehaviour
            ManagerAsMonoBehaviour manager = GameObject.Find("Manager").GetComponent<ManagerAsMonoBehaviour>();
            injectionBinder.Bind<ISomeManager>().ToValue(manager);
        }

Instead of mapping ISomeManager to a type, we map it with an instance value. Play the scene again and click Manage, you should see something like this:

Manager is now a MonoBehaviour
Manager is now a MonoBehaviour

Injection in Command

What we did so far is we injected an ISomeManager instance in HelloWorldMediator and use that directly. That is actually not ideal. A mediator should only be a thin layer between its view and controllers. As much as it can be, it should not know about which part of the code performs the complex processing like managers. It can, however, know about signals that is mapped to commands.

Edit HelloWorldSignals.cs and add a DoManagementSignal:

using System;

using strange.extensions.signal.impl;

namespace Game {

    public class StartSignal : Signal {}

    public class DoManagementSignal : Signal {} // A new signal!

}

Let’s create the command that is mapped to that signal. Add a file named DoManagementCommand.cs under Controller folder.

using System;

using UnityEngine;

using strange.extensions.context.api;
using strange.extensions.command.impl;

namespace Game {
    public class DoManagementCommand : Command {

        [Inject]
        public ISomeManager manager {get; set;}

        public override void Execute() {
            manager.DoManagement();
        }

    }
}

In here, we inject ISomeManager to the command class and invoke ISomeManager.DoManagement() in its Execute() method.

Edit HelloWorldMediator to the following:

using System;

using UnityEngine;

using strange.extensions.mediation.impl;

namespace Game {
    public class HelloWorldMediator : Mediator {

        [Inject]
        public HelloWorldView view {get; set;}

        [Inject]
        public DoManagementSignal doManagement {get; set;}

        public override void OnRegister() {
            view.buttonClicked.AddListener(doManagement.Dispatch);
        }

    }
}

We now removed any references to ISomeManager in the mediator. We get an instance of DoManagementSignal instead and dispatch it whenever the button is clicked. In a way, HelloWorldMediator now doesn’t know about any managers. It just knows signals.

Finally, we add a signal-command mapping in HelloWorldContext.mapBindings():

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

            // we bind a command to StartSignal since it is invoked by SignalContext (the parent class) during on Launch()
            commandBinder.Bind<StartSignal>().To<HelloWorldStartCommand>().Once();
            commandBinder.Bind<DoManagementSignal>().To<DoManagementCommand>().Pooled(); // THIS IS THE NEW MAPPING!!!

            // bind our view to its mediator
            mediationBinder.Bind<HelloWorldView>().To<HelloWorldMediator>();

            // bind the manager implemented as a MonoBehaviour
            ManagerAsMonoBehaviour manager = GameObject.Find("Manager").GetComponent<ManagerAsMonoBehaviour>();
            injectionBinder.Bind<ISomeManager>().ToValue(manager);
        }

Play the scene. It should still work like the previous one.

Last Words

I always mention “the author of Strange recommends/suggests…”. That’s because his suggestions are optional. For example, you can inject instances directly in your views. You may not use a mediator at all. What I’m trying to say is, you can choose another pattern of using Strange depending on your needs. I just chose to follow his recommendations because it works for me.

If you made it this far, then you are probably strange, too. You might be asking “why should I add this complexity and extra layer of indirection to my project?” I’m currently looking for the answer to that question by applying it to my current game. All I can say right now is Strange forces me to keep things separate. Like for now, I have this separate scene that handles persistence using SimpleSQL within a Strange context. Database transactions are executed in commands. The existing game logic, which is non Strange, communicates with this module through signals.

We are also using it in another RPG project. I hope that it does help with respect to code collaboration as it promised. I can’t say how good it is yet as we are still starting the project but so far it is working for us. We are working on multiple contexts. Each context is owned by only one programmer. We communicate with other contexts using signals.

Advertisements

20 thoughts on “Getting Started with Strange IoC

  1. I am getting this warning and error after adding the code to implement the manager as a MonoBehaviour:

    Warning: You are trying to create a MonoBehaviour using the ‘new’ keyword. This is not allowed. MonoBehaviours can only be added using AddComponent()

    Error: NullReferenceException: Object reference not set to an instance of an object

    This error occurs after clicking the Manage button.

    Anybody else having this problem?

    Like

    1. Did you change the mapping in mapBindings()?

      // REMOVED!!!
      //injectionBinder.Bind().To().ToSingleton();

      // bind the manager implemented as a MonoBehaviour
      ManagerAsMonoBehaviour manager = GameObject.Find(“Manager”).GetComponent();
      injectionBinder.Bind().ToValue(manager);

      Like

  2. Wouldn’t it be better to use singelton pattern for MonoBehaviour manager insted of using find?

    create public stactic field instance, and in awake method add this to instance if it is null
    void Awake() {
    if(instance==null)
    instance = this;

    } else if(instance !=this) {
    Destroy(gameObject);
    }
    }

    Like

  3. Went through the whole tutorial with ease(after reading the how-to page, of course). No errors or such, but I’m still wrapping my head around it.

    Before reading further, I may be asking about something really basic. I’ve been a heavy Singleton/Static and Global Manager user for few years. (*thinking* Oh, not another newbie! :D)

    Few concepts I found difficult(better yet, strange :P) :

    1.
    The Bind command has different meanings for:
    commandBinder.Bind().To().Once();
    mediationBinder.Bind().To();
    injectionBinder.Bind().To().ToSingleton();

    1a.
    Command binding binds a command to a signal. From what I understand, when a signal is a property with [Inject] attribute, that particular signal(in this case, StartSignal), creates a new instance of the command(HelloWorldStartCommand). Now a method can call dispatch.
    Calling Once() makes the signal create a Command object every time we dispatch
    Pooled() makes it a pool of Command objects with initial size of zero. What happens after the command finishes execution? Does it reset its state and get pooled? Didn’t quite understand this.

    1b.
    Mediation binding binds a mediator to a view. From what I understand, this enables injection of the view into the mediator, nothing more than this.
    Like you said, even though the author recommends against it, you can technically inject some other class inside the view. But wouldn’t that require you to write a new Binder?

    1c.
    Injection binding binds an interface class to a concrete class. Few questions:
    So, whenever we inject a property like so
    [Inject]
    public ISomeManager manager {get; set;}
    the implementation is always resolved to ManagerAsNormalClass
    What if I had more managers such as AnotherCoolManager, UIManager etc. that implements the interface ISomeManager?
    We can use only one manager at a time. I could use another layer of inheritance like so:
    IManager – base manger interface
    IManagerAsNormalClass, IAnotherCoolManager, IUIManager – manager interfaces
    ManagerAsNormalClass, AnotherCoolManager, UIManager – manager concrete classes implements respective interface
    But this is useless, I might as well get rid of all those interfaces, and bind a concrete class like so
    injectionBinder.Bind();

    2.
    How Signals are created is a bit confusing.
    You used this in the Launch method of the SignalsContext class
    Signal startSignal = injectionBinder.GetInstance();

    And this inside a mediator class.
    [Inject]
    public DoManagementSignal doManagement {get; set;}

    I didn’t get this.
    In the mediator class can I instead do this?
    Signal doManagementSignal = injectionBinder.GetInstance();

    3.
    Doing a one-one correlation to the Model View Controller design pattern.
    What I understand:
    View is a termed correctly and relates to the ‘View’ in MVC pattern. Throw in the Mediator in there too.
    Controller consists of signals bound to commands.
    Models are… wait… Where are the models here? *scratching head*

    For anyone’s response, thanks in advance. 🙂

    Like

  4. Hey George,

    I’ll answer your questions as best as I can.

    1a. I don’t know the internal workings of Strange myself. I choose not to, that’s how I see it when you use frameworks. But I happen to know that when it is pooled, the states of the command is assigned to their default values. I discovered this while working with asynchronous calls through WWW. I consider this a limitation of Strange because I can’t use a command as an asynchronous callback.

    1b. Given that you have used the injection binder to bind instances, there’s no additional binder needed to inject instances in views.

    1c. I believe injection binding is one to one. It injects the instance that is mapped to a certain interface as long as it satisfies the interface. You can use named injection if you have multiple instances that satisfies a single interface. Binding without the interface also works. I think the reason the author suggested using interfaces is for you to switch another instance say for testing.

    2. I just copied the implementation of SignalContext from one of the authors sample projects. I don’t know why he didn’t use [Inject] in it. In mediator class, yes, you can certainly choose to do that. I think that’s how the [Inject] attribute works internally.

    3. Yeah, models aren’t that clear in Strange. It’s not like in uFrame (another MVC/MVVM framework for Unity) where it forces you to use models. What I did is managers handle model instances. So whenever a mediator is created, I can request or resolve a model through the manager. But I use a signal to get the model instance. I do this by using a utility class like ResultWrapper that has Get()/Set() methods. Then in the command, I set the requested model instance. After the command executes, the mediator now has that instance through its Get().

    Like

    1. First, Thank you marnel.estrada for you response.

      1a. Got it. Thanks 🙂 I believe you got the WWW calls to work using Retain() and Release() to make it asynchronous?

      1b. Right, so in the context mapBindings() I can do something like:

      injectionBinder.Bind().ToSingleton();

      and inside the View class

      [Inject]
      public ModelValueObject modelValue {get; set;}

      (I understand that giving the view class access to the model is terrible and breaks the MVC pattern, but I’m just curious to know, that’s all 😛 )

      1c. My bad, I forgot about named instances while binding. It works, but I still find the use of Enums/Strings while binding, create a dependency. It means that all classes injecting this property, has to know about the Enum/String.

      2. Oh, really? Nice!
      I find injectionBinder.GetInstance() useful because I get the instance without associating it with a property. Also, if I’m not mistaken, the property needs to be public for injection to happen, not ideal imho.
      Instead, can I do something like this?

      private StartSignal startSignal{ get; set; }
      public override void OnRegister ()
      {
      startSignal = injectionBinder.GetInstance();
      }

      Is there something wrong with this? I’ve been using Unity’s own GetComponent() for years, and the author of strange mentioned in the ‘how-to’ that GetComponent is actually a type of Dependancy Injection.

      3. One of my colleague suggested using several value objects as models. This way he was using them as payload for the signals for communicating with the view mediator.
      I like what you said about uFrame, “it forces you to use models”. Do you have something in mind to enforce it in Strange?

      Like

      1. 1a. Haven’t tried that but thanks for the idea.

        1b. Yes. This way, you’re merely using Strange as a Dependency Injection (DI) system. Some devs are just into the DI and doesn’t want the whole MVC.

        1c. Yes it does create dependency. The author has warned about it. A soft patch then is to design your managers or algorithms such that named injections won’t be needed. I rarely used shared interfaces, at least in my games. Most of the time, I’ll just bind using the concrete classes.

        2. That’s actually a good idea. I cringe at public properties myself. Some devs argue that it doesn’t break encapsulation but you still give public access to those variables.

        3. In uFrame, a view always has a model attached to it whenever you create one whether prebaked in scene or instantiated at runtime. But you’re not supposed to mutate any of its values. Only controllers can change it.

        What I replied previously somehow simulates it already with some more work needed. You could create a base component like BaseMediator<T> which already has signals prepared to query the model instance T. You could put various options in this class to say resolve an existing named instance or resolve a new one.

        Like

  5. Thanks marnel for your responses. I’m glad you helped me out(and I gave you some ideas in the process).
    Can I have your email-id? We can stay in touch.

    Like

  6. I am getting weired problems please help me.
    Working on Unity 5.
    Machine – Macbook Pro 15
    OS – MacOS X

    At first I was getting problem with HelloWorldContext.cs
    public override void mapBindings() was giving some problems regarding accessmodifires so I changed it to protected so it worked
    Now
    Game.SignalContext.cs is giving some problems it was giving some constructor problems to I changed the constructor to just public SignalContext (MonoBehaviour contextView){}
    Now it’s saying –
    Assets/Scripts/HelloWorldContext.cs(12,24): error CS1729: The type `Game.SignalContext’ does not contain a constructor that takes `0′ arguments

    Please help.

    Like

      1. @marnel.estrada

        It was giving problem with
        public SignalContext (MonoBehaviour contextView) : base (contextView){
        }
        that was the reason I tried changing it to
        public SignalContext(MonoBehaviour contextView){
        }
        It is giving problems with both of them.
        BTW, Thanks for Replying..

        Like

    1. Hey, Thanks a lot.
      It finally worked I don’t know what was the problem last time but it worked this time.
      Last time I tried like 2-3 times but was stucking on the same problem and couldn’t proceed further but this time it worked and hoping to proceed further.

      Like

  7. Hi,
    After implementing changes form “Injection in Command” it still works but button prints only once… after first click doesn’t hang or prints errors to console – looks ok but doesn’t prints log message…
    Thanks,
    Thomas

    Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s