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.

Advertisements

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

  1. Pingback: Signals vs. Events — A Strange Smackdown (Part 2 of 2) | StrangeIoC

  2. Pingback: A Strange Camera System in Unity: Part I // Unity Reverie

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 )

Google photo

You are commenting using your Google 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 )

Connecting to %s