Unit Testing with StrangeIoC

This isn’t actually my post. I just wanted to draw everyone’s attention to this post over here, where Brett Fowle explains in some depth how you can use StrangeIoC to assist in the unit testing of MonoBehaviours, such as views and mediators. It’s worth taking some time to read, since it takes my claim of some of what Strange is good for and shows you how to put it into practice.

Advertisements

Signals vs. Events — A Strange Smackdown (Part 2 of 2)

This is the second of two articles comparing/contrasting StrangeIoC’s new Signals package with the original EventDispatcher system. The article is targeted at those already familiar with/using StrangeIoC, and describes a new feature set. If you’re interested in Strange (including the general topics of IoC, Dependency Injection and Pub/Sub systems in Unity3D) and how it might improve your development practices, check out the StrangeIoC website:

http://thirdmotion.github.io/strangeioc/

In the first of these two posts, I described the pros and cons of the existing EventDispatcher. A key problem with that system is the lack of type-safety. That is, an Event payload can be of any Type, and it’s rather easy to cause an error by sending the wrong payload to a callback.

Signals is an alternate communication system new with v0.6.0. This article explains some important pros and cons of Signals and goes into some depth on how type-safety in Signals can make your code more robust.

Signals: the newer, safer approach

Signals is a pub/sub system, built by developer Will Corwin and modelled on similar systems like AS3Signals and QTSignals. Like EventDispatcher, Signals can be injected anywhere, can dispatch within a context, between contexts, or locally. Like EventDispatcher, a Signal can trigger a Command. Unlike EventDispatcher, Signals are type-safe. That is, each Signal knows what Type(s) it dispatches. Listeners to the Signal must match their signatures to those Type(s). Any failure to match Signal Types to callback Types throws a compiler Exception. This can greatly improve the reliability of your code.

Here are two example signals:

Signal<string> signalOne = new Signal<string>();
Signal<uint, Transform> signalTwo = new Signal<uint, Transform>();

Notice how each Signal has its Type signature built right in. signalOne expects to send a string. signalTwo expects to send a uint and a Transform, in that order. Now, let’s see what happens when we add listeners.

void callbackOne(string value)
{
    //do something with the value
}
signalOne.AddListener(callbackOne);
signalOne.Dispatch(“test message”);
void callbackTwo(uint id, Transform trans)
{
    //do something with the values
}
signalTwo.AddListener(callbackTwo);
signalTwo.Dispatch(42, gameObject.transform);

Observe how in each case the listening method matches the signature originally baked into the Signal. Now let’s see what would happen if we accidentally swapped the listeners:

signalTwo.AddListener(callbackOne);
//The above line produces compile-time errors, including:
//MyClass.cs(14,14): Error CS0123: A method or delegate
//`MyClass.callbackOne(string)' parameters do not match delegate
//`System.Action<uint, UnityEngine.Transform>(uint)' parameters

The result is that it’s essentially impossible to mix up data types between dispatchers and receivers. In any system where objects are largely decoupled (as Strange intends), this security is a HUGE advantage.

Advantages of Signals

We’ve pretty much covered the big win that type-safety provides. Another advantage of Signals is that while EventDispatcher limits you to a single payload object, Signals actually permit you to send multiple payloads in the form of zero to four parameters (the number of parameters is limited by Unity’s implementation of the underlying Action class). These parameters can be of any type, including multiples of the same type, and the order is preserved between dispatcher and callback.

Also, because Signals do not create objects — they simply pass references — they are a better choice than EventDispatcher for avoiding the rapid creation and destruction of objects which can lead to frequent garbage collection and subsequent performance hits.

Disadvantages of Signals

There are a few pitfalls to watch out for with Signals. They probably don’t cancel out the big wins, but you want to be aware of them.

I’ll start by pointing out that Signals are arguably more difficult to use, since you need to understand why type-safety is useful and how the wiring supports it. A little education will get your team past that.

Second, note that while EventDispatcher is monolithic, each Signal is its own class. This means that you’ll need to inject each Signal you want to use, which means more code and more concrete dependencies. For example, if a class listens for ShipDestroyedSignal and dispatches AddToScoreSignal, both signals would require injection, as compared to just a single injection for IEventDispatcher. Although you could theoretically map/inject abstract Types (e.g., Signal<uint> or Signal<string, Transform>), this becomes impractical if any two signals have the same signature.

//Injecting a signal into a Command.
//Note the addition of a concrete dependency.
[Inject]
public ShipDestroyedSignal shipDestroyedSignal {get; set;}

public override void Execute()
{
    shipDestroyedSignal.Dispatch();
}

Finally, there’s the matter of using Signals with Commands. Signals absolutely support mapping to Commands. In some ways, using Signals with Commands is better than using EventDispatcher, since there’s no need to extend EventCommand (you can simply extend Command) and you don’t need to unravel the data property of an Event. But there is one gotcha, which I’ll go over in the next section.

Setting up Strange to use Signals and Commands

If you want your Context to be able to bind Signals to Commands (a very good idea) you need to make one small plumbing change. In your Context, add this override:

protected override void addCoreComponents()
{
    base.addCoreComponents();
    injectionBinder.Unbind<ICommandBinder>();
    injectionBinder.Bind<ICommandBinder>().To<SignalCommandBinder>().ToSingleton();
}

Also, note that by default Strange kicks off with a START Event. To change this to work with Signals, we want to override the Launch method:

protected override void Launch()
{
    base.Launch();
    //Make sure you've mapped this to a StartCommand!
    StartSignal startSignal = (StartSignal)injectionBinder.GetInstance<StartSignal>(); 
    startSignal.Dispatch();
}

This tells Strange that you want Command bindings to map to the SignalCommandBinder, not the default CommandBinder. Once you’ve done this, the bindings themselves are just:

commandBinder.Bind<SomeSignal>().To<SomeCommand>();

Doing this also creates an injection binding, so you can inject that Signal anywhere you like. When you dispatch the Signal, a new instance of the bound Command will be instantiated and the parameters of the Signal will get injected. You need to specify the injections in the Command, so if you have a Signal that looks like this…

Signal<uint, string> mySignal = new Signal<uint, string>();

…the matching Command should look like this…

namespace mynamespace
{
    //Note how we extend Command, not EventCommand
    public class SomeCommand : Command
    {
        [Inject]
        public uint id{ get; set;}
        [Inject]
        public string message{ get; set;
        public override void Execute ()
        {
            //Do something command-like
        }
    }
}

You can of course inject other things into the Command, but you must at least inject the required elements of the Signal. Note that unlike other Signal listeners, Command mappings are performed at run-time not compile-time, so you won’t get compiler warnings with a badly-written Command in the same way that you would with a badly written callback.

But I mentioned an important pitfall of Signals related to Commands and here it is: Signals are capable of dispatching two parameters of the same Type. For example, this is perfectly legal:

Signal<uint, uint> mySignal = new Signal<uint, uint>();

That Signal would map to a callback:

void myCallback(uint value1, uint value2).

But because injections are keyed to Types, that Signal cannot be mapped to a Command. Doing so will raise a run-time Exception when the SignalCommandBinder realizes it can’t satisfy the mapping. So mind this when creating your Signal-to-Command mappings.

Conclusion

Both Signals and EventDispatcher provide you with powerful tools to improve your codebase. We think Signals — with the addition of type-safety — is a big step forward, but pick the system that suits your work style best. As ever, keep in touch and let us know about the amazing stuff you’re building with Strange.

Right, I’m off for an Ardbeg.

Signals vs. Events — A Strange Smackdown (Part 1 of 2)

This article is targeted at those already familiar with/using StrangeIoC, and describes a new feature set. If you’re interested in Strange (including the general topics of IoC, Dependency Injection and Pub/Sub systems in Unity3D) and how it might improve your development practices, check out the StrangeIoC website:

http://thirdmotion.github.io/strangeioc/

A key new feature in StrangeIoC v0.6.0 is Signals, which serves as an alternate communication system to EventDispatcher, already included in the framework. Both systems have advantages and both will continue to be supported for the foreseeable future, so it’s in your interest to understand what each one brings and why you might want to work with one or the other.

This article explains some important pros and cons of the current EventDispatcher system. A second article will do the same for Signals. At the end of these two articles you’ll have a solid grasp of the similarities and differences between the two systems. Importantly, you’ll also walk away understanding the importance of type-safety and why it can improve your code.

Introduction: Communication Patterns

Both Signals and EventDispatcher are publish/subscribe systems — which is a means of communicating around your application. Such systems are sometimes referred to as the Observer Pattern, the Event Dispatcher Pattern, or just Pub/Sub. The principle: an object sends a message, knowing little/nothing about any possible recipient. Observers (or ‘listeners’) receive the message and do something with it. The key is that neither the sender nor the receiver know nothing anything about each other. Each does its job without becoming entangled by the other.

By the way, SendMessage, Unity’s built-in pub/sub, is driven by matching strings to MonoBehaviour methods. It’s slow, inherently unsafe, limited, and prone to breakage. Signals and EventDispatcher are both better choices, even if you use no other aspect of Strange.

EventDispatcher: the classic approach

EventDispatcher is Strange’s original communication system. Whenever it dispatches, it creates a new TmEvent object with an optional, untyped payload (TmEvent is entirely internal to Strange; you should never need to reference it. Rather, type callbacks to IEvent, the interface TmEvent satisfies). The basic use of EventDispatcher is simple:

//instantiate the dispatcher
IEventDispatcher dispatcher = new EventDispatcher();

//define a listening method
void listenerMethod(IEvent evt)
{
    string data = evt.data as string;
    //do something with the data
}
//add the listener to the dispatcher
dispatcher.AddListener(SomeEvent.EVENT_NAME, listenerMethod);
//dispatch
dispatch.Dispatch(SomeEvent.EVENT_NAME, “test message”);

The dispatcher fires a key (SomeEvent.EVENT_NAME) and a value (“test message”). The listening method receives an IEvent and extracts the value out of IEvent’s ‘data’ property. The key can a string, an Enum, whatever. The value can be any payload you want to deliver to that Event’s recipients.

When using Strange as an MVCS framework, we map and inject IEventDispatcher in three ways:

  • As a context-wide dispatcher (contextDispatcher). The contextDispatcher is available for injection everywhere within a context. If can be injected into MonoBehaviours, such as Mediators, but also into Commands, Models and Services. This means that virtually every point in your Context can function as either a publisher or a subscriber to specific Events.

  • As a dispatcher across contexts (crossContextDispatcher). When an application gets sufficiently complex, it makes sense to break it into multiple contexts, which improves modularity and portability. The crossContextDispatcher allows you to keep each context isolated, but still permit some chatter across the context boundary. As of v0.6.0, there is no longer any need to directly reference the crossContextDispatcher. Individual end-points don’t need to know that they’re dispatching between contexts. Rather, Event mappings dictate whether any specific dispatch should cross that boundary.

  • As a factory. If you just want a dispatcher for local communication, as between a View and its Mediator, you can inject an instance of IEventDispatcher, privately shared between a dispatching instance and one or more listeners.

Advantages of EventDispatcher

EventDispatcher is simple. You want access to the context-wide event bus? Inject the contextDispatcher and listen to it or dispatch from it. In this regard, it’s “monolithic”; that is, your classes need only ever have a reference to IEventDispatcher and you’re done. There need be no other references working their way into your code. Every injection of the (context-)dispatcher is linked to every other injection, so if you want to add a listener, just AddListener on the already-injected dispatcher instance.

Problems with EventDispatcher

The biggest problem with EventDispatcher — the problem which led to the creation of Signals — is an issue called “type safety”. The IEvent.data property is untyped: it can contain any Type of object, be it string, uint, MonoBehaviour or anything else. As you’ve seen in the example above, we coerce it to a Type when it arrives at the listener, but this means there’s a little black hole in your code. Dispatch a ByteArray. In the callback, coerce IEvent.data to a ByteArray. Mostly this works fine. But if there’s something broken in this agreement (for example, if you accidentally map the wrong key to a listener), the error won’t show up until the app is running, and this can lead to unexpected errors. A type-safe solution would throw an error at compile-time.

Another disadvantage is that every time the dispatcher fires, an object (TmEvent) is created. Occasional creation of objects is fine, but continual object creation probably makes the EventDispatcher unsuitable for rapid, repeated use. For example, we don’t recommend firing Events every frame, since all those new objects would require a lot of garbage collection, which might cause slowdowns.

Finally, EventDispatcher forces you to to maintain an Event key-list. This functions a little like a static, inserting a dependency to the key-list/map wherever you fire an Event.

Conclusion

EventDispatcher is a pretty good pub/sub system, and it’s worked for Strange up to this point. But the lack of type-safety has been a cause for concern. In part two of this post, we’ll see how Signals solves this problem.

Crossing Contexts with StrangeIoC

This article is targeted at those already familiar with/using StrangeIoC, and describes a new feature set. If you’re interested in Strange (including the general topic of IoC/Dependency Injection in Unity3D) and how it might improve your development practices, check out the introductory article here:
http://thirdmotion.github.io/strangeioc/exec.html

StrangeIoC v0.6.0 adds some great features for working across and communicating between contexts. This article helps you learn how to use these features.

Introduction: Explaining Contexts

Let’s take a moment to understand what a context is and why you might want more than one. Think of a context as being like a ‘module’. Ideally a module can stand on its own without the rest of its application, but it has inputs and outputs so that it can integrate with other modules.

In Strange, each Context is defined by a set of mappings inside an extension of the Context Class (or more likely, an extension of MVCSContext). You could map your entire app inside a single, giant Context, but then the app becomes monolithic, i.e., it’s non-modular, harder to build, harder to debug, harder to integrate, harder to re-use. You can create separate contexts easily by creating multiple scenes, and attaching a ContextView and Context to those scenes.

Adding a Context

This particular feature hasn’t changed in v0.6.0; if you did it before, you know how to do it now. Just fire a Command with the following line in it:

Application.LoadLevelAdditive(“path-to-the-scene”);

You may find that a frame needs to elapse before you can do certain things, since the child Context needs to register.

Removing a Context

Removing a context has become slightly more automated in Strange v0.6.0. In fact, you probably already know how to do it…even if you don’t know that you know! In older versions it was necessary to explicitly remove the Context, and then destroy the ContextView GameObject. Something like:

Context.firstContext.RemoveContext(context);

GameObject.Destroy(contextView);

In the new version, we leverage the OnDestroy() method of MonoBehaviour to automate the process, so all you need to do is:

GameObject.Destroy(contextView);

The Context will automatically be removed for you.

Mapping Injections Across Contexts

Sometimes you want to share a mapping between contexts. For example, you might have a model — the holder for the game score or user data, say — which more than one context needs to know about. Strange’s old way of handling this straight-up sucked. You either had to dispatch the object you wanted to share using the crossContextDispatcher, then map it on arrival, or you had to call Context.GetComponent(), then re-map that “inherited” object in the child Context. Klunky, non-intuitive, high in polysaturated fat.

The new approach couldn’t be simpler. Observe. If you dare:

injectionBinder.Bind<ISpaceship>().To<Prometheus>().ToSingleton().CrossContext();

Boom. The mapping is now shared to other contexts. All the mappings you’re used to work this way: Singletons, value-mappings, named injections, factories.

Two quick caveats. First, while this mapping can appear anywhere in your app, we recommend that you default to placing it in your firstContext to avoid confusion. Second, note that if you re-map an identical key (or name/key pair) in a child context, the child mapping will override the cross-context mapping. This allows you to make exceptions where necessary.

Cross-Context Dispatching

For communicating between contexts (and presuming you’re not ready to switch to Signals, see below), Strange has the CrossContextDispatcher. While the dispatcher itself works just fine, it wasn’t right that you had to inject it into and dispatch it from various endpoints around your app. Implicit in nearly every piece of Strange is the idea that each Class know no more than strictly necessary; allowing a Mediator or Command to know that an event is intended to be consumed by another context violates this principle.

In delicious, new Strange v0.6.0, we fix this by having you bind your cross-context Events in the Context, using the new CrossContextBridge:

crossContextBridge.Bind(GameEvent.DESTROY_SHIP);

Having done this, GameEvent.DESTROY_SHIP will now be sent out across the CrossContextDispatcher and mappable by any other Context. The CrossContextBridge is shared by all Contexts, but as with cross-context injection bindings, we recommend that you default to entering your cross-context Event bindings in your firstContext’s mapBindings method.

Mapping Cross-Context Signals

In addition to these cross-context improvements, the big new feature in Strange v0.6.0 is Signals. Signals are a type-safe dispatch system. I’m not going to explain them here (there’s a section for this in the Big, Strange How-To), but if you’re starting to use them you might be wondering about mapping them across contexts.

Guess what? If you’ve read this far you already know how to do this. A signal is an object, no different from any other injected instance, so if you want to share one across contexts, simply map it cross-context:

injectionBinder.Bind<ShipDestroySignal>().ToSingleton().CrossContext();

The signal is now mappable to a Command or injectable inside any Context.

So those are the new cross-context features in v0.6.0. Hope they help you get more out of Strange. As always, please let me know what you’re doing with Strange, especially if you’re about to release something cool.

That’s me done. I’m off for some whisky.