StrangeIoC

“I really like it! I recognize a well-thought-out API when I see it.”
- Till Schneidereit, Author of Swiftsuspenders, member of the Shumway team

@StrangeIoC I dig the "bind everything" concept.
- Shaun Smith, Author of Robotlegs

StrangeIoC: An introduction for users of Robotlegs

So you know Robotlegs? Come on in, stroll around the place. It should look pretty familiar. Strange of course, but familiar. That’s because StrangeIoC, while not a port of Robotlegs, was heavily inspired by that framework.

If you’ve landed here and don’t understand what I’m talking about, let me sum up: Robotlegs is a spectacularly useful Inversion-of-Control micro-framework for Actionscript 3, the coding language that underpins Adobe Flash. If you still don’t know what I’m on about, you might want to check out today’s XKCD. It probably has nothing to do with anything we’re discussing, but it’s probably very funny.

For those still with me, this article is intended to be a fast-track for those who don’t need an explanation of concepts like Dependency Injection, Inversion-of-Control, MVC, etc. It also assumes you know at least a bit about Unity and programming in C#. If you liked the structure and dependency decoupling Robotlegs offered, you should like Strange (always assuming I’ve done my job). Basically, I want to explain what will look familiar, and what’s different.

Note: with the release of StrangeIoC v.0.6.0, Strange now has Signals. Like other features inspired from our old friends in the ActionScript world, Signals will seem pretty familiar. We don't go into Signals in this document, but we've covered it pretty well in the Big, Strange How-To, so check it out!

Getting Strange

At first glance, Strange looks a lot like Robotlegs. A lot of concepts have survived more-or-less intact. I’ll go through these, explaining where I’ve made changes and why.

ContextView

The top-level GameObject is called the ContextView, just like the top-level Sprite in RL. It’s injectable at various points around the app. Unfortunately, while Flash enforced the principle that everything was attached to the top-level object, Unity has no such mechanism. Unity also doesn’t have Event bubbling, which was a critical element of how Views alerted Contexts to their existence. I’ll talk more about the latter problem under Views & Mediators.

Despite these problems, you can more-or-less think of the ContextView as being the same as the one in Flash. Note that there is a ContextView class — a "Mediator" (a Monobehaviour, actually) — that you manually attach to your GameObject to declare that it is the ContextView. Instantiating the Context from a subclass of ContextView requires just two lines of code, which you probably want in the Awake() method:

context = new MyContext(this, true);
context.Start();

Context

Just like RL, the Context is where you map your dependencies. If your Context is a subclass of MVCSContext (which I highly recommend, at least to start), the cast of characters will seem pretty familiar:

Now the thing that will probably stand out for you here is that one oft-repeated word: ‘Binder’ (even the ones not so-named are Binders). The reason for that word is that Strange starts from a slightly different perspective than other IoC systems, including Robotlegs, in that it sees all forms of decoupling as merely varied types of a collection called Binder. A Binder in Strange is simply a dictionary of Bindings, which in turn are nothing more than a set of three lists with some relationship to each other. These lists are Key, Value and Name. It turns out that every Binding RL did, and a whole lot more besides, can be accomplished with this simple structure. For example, an EventDispatcher is nothing but a Key (the Event) that triggers Values (the callbacks). A Dependency Injector is little more than a Key (the interface or superclass) that resolves to a Value (the concrete or derived class) with the option of a Name (for named injections). All the components in Strange are built on this principle.

Notwithstanding the above difference the effect is very similar to what you’re already used to. There are some syntactic differences, though, because C# supports generics and we want to take that into account. Here’s a list of bindings (mappings) that should look pretty familiar.

Injector Bindings

Basic factory binding, Interface to Concrete class.

injectionBinder.Bind<IMyInterface>().To<MyImplementation>();

Map to Singleton:

injectionBinder.Bind<IMyInterface>()
        .To<MyImplementation>()
        .ToSingleton();

Map to value, note how the value does not use the generic brackets:

injectionBinder.Bind<IMyInterface>().ToValue(new MyImplementation());

Named injection 1. Name with a constant or Enum (non-generic):

injectionBinder.Bind<IMyInterface>()
        .To<MyImplementation>()
        .ToName(MyEnum.ONE);

Named injection 2. Name with a marker class (generic)...do you see how whenever we bind to a class we use generics?

injectionBinder.Bind<IMyInterface>()
        .To<MyImplementation>()
        .To<MyMarkerClass>ToName();

Polymorphous binding, multiple interfaces to a single implementation:

injectionBinder
        .Bind<IMyInterface>()
        .Bind<IOtherInterface>()
        .To<MyImplementation>();

We’ll talk about [Inject] tags and the like a little further down.

EventDispatcher bindings

Just like in RL, we don’t really do a lot of dispatcher mapping inside the context, but I’ll discuss EventDispatcher here since it’s one of the important pieces instantiated in the Context. First, note that, like most of Strange, use of the EventDispatcher is optional. You can subclass Context and set up a framework using a dispatcher you prefer (Unity-Signals anyone?). But the EventDispatcher works well here and will be pretty familiar. Also, it’s probably worth noting that since our Events don’t bubble, this dispatcher isn’t quite as expensive as Flash’s. Let’s look at the mappings.

Add a listener. Anything can be used as the event type. I recommend an Enum (EventMap below might be an Enum or a list of constants):

dispatcher.AddListener(EventMap.MY_EVENT, onSomeEvent);

The handler must take either no arguments or a single argument like so:

private void onSomeEvent(IEvent evt)
{
	//do stuff.
}

A few important points here: IEvent is obviously the Event interface. For maximum decoupling, we use this instead of the concrete TmEvent. IEvent specifies three properties:

Also note that C# won't let you identify an event by the term 'event' since it's a keyword. So use my 'evt' convention or come up with your own.

Removing a listener is just what you’d expect:

dispatcher.RemoveListener(EventMap.MY_EVENT, onSomeEvent);

And we’ve added a boolean convenience function (true to add, false to remove:

dispatcher.UpdateListener(true, Events.MY_EVENT, onSomeEvent);

There are a number of options when dispatching.

Dispatch with null payload.

dispatcher.Dispatch(EventMap.MY_EVENT);

Dispatch with payload.

dispatcher.Dispatch(EventMap.MY_EVENT, 42);

Dispatch by providing the whole TmEvent explicitly:

TmEvent evt = new TmEvent
         (EventMap.MY_EVENT, dispatcher, someVar);
         dispatcher.Dispatch(evt);

I’ve not implemented priorities in dispatching, and, as I rarely used it, probably won’t unless someone tells be it’s desperately important. And as I’ve noted already, bubbling doesn’t really come into play.

A final word before I move on: there are three mappings for EventDispatcher in MVCSContext, each for a particular use:

  1. The context-wide dispatcher, mapped to the name ContextKeys.CONTEXT_DISPATCHER. Use this dispatcher as the event bus throughout your Context, just like RL.

  2. The crossContextDispatcher dispatcher, mapped to the name ContextKeys.CROSS_CONTEXT_DISPATCHER. Use this dispatcher for communicating between contexts.

  3. The local dispatcher, which isn’t mapped to any name. Injecting this is useful for creating a new instance of EventDispatcher for use in highly local communication (as between a View and its Mediator).

Command bindings

Command binding is a lot like the RL commandMap, but sequencing gives us an extra flavor to work with. Let’s begin with binding an event to a Command.

commandBinder.Bind(EventMap.MY_EVENT).To<MyCommand>();

Now we’ll bind an event to several commands that fire in parallel.

commandBinder.Bind(EventMap.MY_EVENT).To<MyFirstCommand>()
         .To<MySecondCommand>()
         .To<MyThirdCommand>()
         .To<MyFourthCommand>();

Note that I’m using the term “in parallel” a tad loosely. It’s just a loop firing one after the other. But the Commands are assumed to be disconnected from one another and will all fire independently.

If you'd like to fire events serially, commandBinder has this capability too. This is useful for setting up guards to stop a Command from firing if certain conditions aren’t met, and for handling complex chains where a process must complete a step before continuing.

To fire a sequence, just mark the binding with InSequence().

commandBinder.Bind(EventMap.MY_EVENT).InSequence()
       .To<MyFirstCommand>()
       .To<MySecondCommand>()
       .To<MyThirdCommand>()
       .To<MyFourthCommand>();

RL’s one-off method is also supported in the form:

commandBinder.Bind(EventMap.MY_EVENT)
         .To<MyCommand>().Once();

Which unmaps the binding as soon as the Command has fired. Note that for sequences the unmapping occurs only after the sequence has successfully completed.

Mediation binding

After everything you’ve read so far, mediation binding will seem a dawdle:

mediationBinder.Bind<MyView>().To<MyMediator>();

Just that simple. The only addition Strange supports is the option to bind multiple mediators. This allows the dynamic adding of Monobehaviours to your gameObjects.

mediationBinder.Bind<MyView>()
         .To<MyMediator>()
         .To<MyOtherMediator>();

The Views in question extend Monobehaviour, as do the Mediators. We’ll discuss all that when we come to the mediation section.

Everything else Context

Those are the components of the Context, but there are few other things to note.

Where to map all those bindings?

There’s a method you can override called mapBindings(). Guess what it’s for? There’s also a postBindings() method in case you want to do anything after binding but before Launch(). MVCSContext uses postBindings() to process any Views which register early (before mapBindings() is called). Another obvious and useful case for this is to call DontDestroyOnLoad(contextView) inside postBindings(), in order to retain the contextView (and the Context!) when you load in a new scene.

Launch()

Launch fires a particular event: ContextEvent.START. Map a command to this event to fire your first Command.

Does Strange support more than one context?

No.

Nah, I’m just messin’ with ya! Of course it does (which you’d know if you’d read carefully above). You can set up as many contexts as you like and they can be in the same Scene or different Scenes. the first Context to register has slightly special properties, though. It defines the crossContextDispatcher (CCD) which it shares with all the other contexts to provide communication. Once a context is listening to the CCD, you pick up CCD events on your own context’s dispatcher. So while you dispatch to the CCD, you can choose to simply listen locally.

The other special thing about firstContext is, er firstContext. This is a static property (Context.firstContext) which is the Context of last resort for lost Views. I’ll explain this more in the Mediation section; for now just know that it’s there to help you.

Anything else?

I added a crazy feature...maybe not a good idea, but it seemed worthwhile, called Context.GetComponent(), which essentially allows you to ask for mappings out of the injectionBinder. This was useful when setting up cross-context communication, so I thought it might have other useful applications.

Injection

Let’s talk about those marvelously injected classes. Unlike RL, which used the Swiftsuspenders injector, Strange has an injector of its own. But if you’re one of those people who feels they should roll their own, never fear; it’s all abstracted so you could do just that.

Marking classes for injection is really straightforward if you’re coming from Robotlegs. Here’s a setter injection:

[Inject]
public IMyDependency myDependency{get;set;}

Notwithstanding a few C# syntactical differences, this is identical to the RL approach. You can inject things using the [Inject] metatag, but note that you must designate setters and getters, even if they’re only stubs. Strange supports Setter injection and constructor injection, but not field injection.

You can also do a named injection. Here’s an example with the name based on an Enum. I like this.

[Inject(SomeEnum.ONE)]
public IMyDependency myDependency{get;set;}

But you could also map a name to a marker class. The syntax ends up a little more convoluted.

[Inject(typeof(MyMarkerClass))]
public IMyDependency myDependency{get;set;}

Strange also supports the metatag [PostConstruct].

[PostConstruct]
public void postConstruct()
{ 
    //do stuff immediately after injection
}

Just like in Swiftsuspenders, you can tag as many PostConstruct methods as you like. I haven’t yet implemented an ordering parameter, but I’ll attempt to add that soon.

Now Constructors are a bit more complicated, especially since in C# (unlike in Actionscript) you can have more than one. because of this, I’ve created a set of rules to determine which Constructor to use. It works like so:

  1. If there’s only one constructor, use it.

  2. If any constructor is marked with the [Construct] metatag, use that method for constructing.

  3. If there are more than one, but none is marked with [Construct], use the one with the fewest parameters.

IMPORTANT SAFETY TIP: You can inject MonoBehaviours, but you can’t Construct them. MonoBehaviours have to go through GameObject.AddComponent() method. If you map one for instantiation as if it were a normal class, Strange will burn your village to the ground and jump up and down on the ashes. Or throw an error. Depends on its mood.

Commands

Ok, that’s injection done and dusted, so let’s talk about Commands. Again, this will look pretty familiar to you from RL. Here’s a basic Command:

using strange.extensions.command.impl;
using com.example.spacebattle.utils;
namespace com.example.spacebattle.controller
{
    class StartGameCommand : EventCommand
    {
        [Inject]
        public ITimer gameTimer{get;set;}
     
        override public void Execute()
        {
            gameTimer.start();
            dispatcher.dispatch(GameEvent.STARTED);
        }
    }
}

In C#, ‘using’ is like AS3 import, and we’ve covered the tiny differences in injection. The rest of it is almost identical. Command automatically injects injectionBinder and commandBinder. EventCommand, which extends Command, automatically injects the context-wide dispatcher, and any IEvent that may have been involved in the Command’s instantiation. One thing notably missing relative to Robotlegs is the injection of the ContextView. The key reason for this is that to do so would introduce a required dependency on Unity outside its proscribed bounds. Essentially, I'm trying to keep Unity out of your code wherever possible, which improves both testability and portability. Of course you can still inject the ContextView yourself wherever you need it:

[Inject(ContextKeys.CONTEXT_VIEW)]
public GameObject contextView{get;set;}

You can also get access to other useful components:

[Inject]
public IMediationBinder mediationBinder{get;set;}
[Inject(ContextKeys.CROSS_CONTEXT_DISPATCHER)]
public IEventDispatcher crossContextDispatcher{get;set;}

And of course anything else you see fit to inject.

By default a Command is garbage collected as soon as Execute() completes, but you can hold it around if necessary for async operations by using Retain()/Release().

using strange.extensions.command.impl;
using com.example.spacebattle.utils;
namespace com.example.spacebattle.controller
{
    class StartGameCommand : EventCommand
    {
        [Inject] public IService server{get;set;}
        
        override public void Execute() 
        { 
            Retain(); 
            server.dispatcher.AddListener
                (ServerEvent.COMPLETE, onComplete); 
            server.call();
        }

        private void onComplete(IEvent evt)
        {
            //remember to remove listeners!!!
            server.dispatcher.RemoveListener
                (ServerEvent.COMPLETE, onComplete);
            //Do something with the result
            Release();
        }
    }
}

Be sure to clean up your listeners and call Release(), or the Command will not be garbage collected.

If you're running a Command as part of a sequence and want the sequence to terminate, you can call Fail().

        override public void Execute() 
        {
            if (server.isAvailable)
            {
                Retain(); 
                server.dispatcher.AddListener
                    (ServerEvent.COMPLETE, onComplete);
                server.call();
            }
            else
            {
                server.retries++;
                if(server.retries > server.maxRetries)
                {
                    dispatcher.Dispatch(ServerEvent.NOT_RESPONDING);
                }
                Fail();
            }
        }

This Command terminates its sequence if the server isn't available. After a specified number of tries, it dispatches an event so that some other system can do something about it. Fail() has no effect if fired in a Command that is not part of a sequence.

Views and Mediation

Views and Mediators both extend MonoBehaviour, which means that when running in the Unity IDE you can actually look at and tweak public properties, even at runtime. The principle of these two classes is much the same as it is in Robotlegs: the View controls the UI, while the Mediator bridges communication between the View and the rest of the app. There are a few important differences to note, however.

Events don’t bubble

I touched briefly on this before. In RL, the bubbling ADDED_TO_STAGE event was used to capture, track and mediate views when they appeared. Unity has no such mechanism (and I deplore the use of SendMessage) which left me looking for another approach. The one I settled on was a sort of “manual bubbling”. Whenever a View fires its Awake() or Start() method, (they’re Monobehaviours, remember) it traverses the Transform tree, looking for a ContextView. If it finds one, it registers with the Context and receives whatever mediation to which it might be mapped.

In this manner, Views should ordinarily find the Context to which they belong.

However, since the top of a Unity app is not a GameObject with a MonoBehaviour attached, it is possible for this bubbling to fail. I.e., if you set the View in the scene, not attached to any GameObject, there’s nothing to bubble to. In this case, the View will look for firstContext (remember where I mentioned that above?). If it finds firstContext, it will add itself to that.

You can inject into Views

Unlike RL, which forced you into a curious workaround to inject into Views, I’ve decided to allow this. RL’s reasoning is perfectly sound (at least as I understand it): Views are meant to be insulated from the rest of the app. Therefore injection might be seen as a bad practice. But a colleague convinced me that there are plenty of perfectly sensible cases where injection into Views makes sense, including a local dispatcher to communicate with the Mediator and injection of pure-view config files. My job, ultimately, is to provide you with tools. I strongly urge you to use those tools wisely, but I’m not your mother.

In fact, I’m sooo not your mother I even provided you with EventView, which injects a local EventDispatcher, just the right size for dispatching to its own Mediator.

Mediators as MonoBehaviours

I initially made Mediators extend Monobehaviour because it created an easy place to store them: on the same GameObject as the View. But having them be actual MonoBehaviours is actually quite useful. For instance, access to gameObject, Update(), FixedUpdate(), Coroutine, etc., give the Mediator a little power to know about the View and about the app at large.

Mediator injects the ContextView, while EventMediator also injects the context-wide dispatcher. You can of course inject other dependencies, and the one you really MUST inject (if your Mediator is to be of any use) is the View itself:

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

override public void OnRegister()
{
    view.doPrettyFunnyExcitingEmotionallyMoistStuff();
}

Views, Mediators and loading external scenes

Probably the trickiest part of most any project is the marshalling of external assets. Using Strange doesn’t change this, but it does present some particular touchpoints you’ll want to be aware of.

First, as we’ve already noted, the display tree in Unity is a little different than the one in Flash. If you load a Scene outside the display hierarchy of the ContextView, you might get into a muddle. The firstContext will of course pick up stragglers, but in a complex application this could get difficult. I strongly advise that you load and attach your scenes with appropriate care. Consider yourself warned!

The other difficulty is timing. Strange is very good at capturing Views as they appear (quite a bit of thought went into fallbacks to try and capture each View and place it in the correct Context), and its timing is very orderly; you know that the Context instantiates early, and you control the order of events, and also when things are placed on stage. But when an external scene loads that doesn’t have a Context of its own, you need to make sure that the Views have appropriate time to register before continuing with other processes. This is generally nothing more than a single frame cycle, so pausing for an Update() after loading should do.

Models and Services

If you’ve read this far, there’s really nothing important to say about Models and Services. You already know how map them, to inject them, and to use them wisely.

Something not at all Robotlegsy

I mentioned at the top that Strange was all based on a collection called Binder. What I didn’t really discuss was how simple and extensible this class is. Once I came to see all forms of decoupling as nothing more than variations on the Binder, a raft of possibilities opened up before me. I’m reasonably convinced that there are a ton of use cases for Binders and that before long Strange developers will start finding great uses for them. I urge you to crawl through the core framework classes (there are only eight of them, and they’re not long or hard to follow) and learn how to build new Binders yourself.

That’s it. You’re now Strange like me.

I can’t tell you how much I appreciate you taking time to look at Strange. It’s my job, but it’s also been a labor of love. I hope you find Strange useful and accessible, and that it provides at least some of the advantages you had with Robotlegs. If you have any ideas, suggestions or corrections, if you want to buy me a whisky or tell me about some great project in which you’re using Strange, please feel free to contact me through GitHub.

I look forward to Strange days ahead.