My last article on Callback Strategies highlighted some pretty severe performance differences between my Runnable strategy, as3signals by Robert Penner, and Flash’s native Event system. My simple Runnable technique had an artificial advantage though: it was not a proper library but instead a little bit of code built right into the test app. Today I’m introducing TurboSignals as an AS3 library making use of the Runnable technique.

Basics

TurboSignals is a simple library. It includes signal classes for up to ten parameters (Signal0, Signal1, …, Signal10) as well as a class for var args (SignalN). These are paired with slot interfaces (Slot0, Slot1, …, Slot10 and SlotN) that you implement in order to receive the signal’s callback. The purpose of an explicit slot type is to avoid using a Function variable, which is very slow. This was shown in my article on Runnables along with how Runnables are a good strategy for avoiding that slowdown. Some helper classes (FunctionSlot0, FunctionSlot1, …, FunctionSlot10 and FunctionSlotN), however slow, are provided for when you really want to provide an arbitrary function.

Advanced Features

One nicety of TurboSignals is that the dispatch operation is “safe” insomuch as that calls to addSlot, removeSlot, and removeAllSlots will not affect which slots are called. One drawback though is that parameters to dispatch are all untyped (*) and therefore there is no compile-time checking of the parameters and the possibility exists that there will be type errors at runtime. This is true too in the Event/EventDispatcher system as well as as3signals, only the latter explicitly checks for this problem at runtime to give more informative errors.

Usage Example (faster)

This example runs at maximum speed, which is probably not needed for simple button clicks.

import com.jacksondunstan.signals.*;
public class Button extends Sprite
{
	public var clicked:Signal0 = new Signal0();
	public function Button()
	{
		addEventListener(MouseEvent.MOUSE_DOWN, onMouseDown);
	}
	private function onMouseDown(ev:MouseEvent): void
	{
		this.clicked.dispatch();
	}
}
public class MainMenu implements Slot0
{
	public function MainMenu(button:Button)
	{
		button.clicked.addSlot(this);
	}
	public function onSignal0(): void
	{
		trace("button was clicked");
	}
}
Usage Example (slower)

This example allows you to make your callback private and name it as you wish, courtesy of the FunctionSlot0 adapter class.

import com.jacksondunstan.signals.*;
public class Button extends Sprite
{
	public var clicked:Signal0 = new Signal0();
	public function Button()
	{
		addEventListener(MouseEvent.MOUSE_DOWN, onMouseDown);
	}
	private function onMouseDown(ev:MouseEvent): void
	{
		this.clicked.dispatch();
	}
}
public class MainMenu
{
	public function MainMenu(button:Button)
	{
		button.clicked.addSlot(new FunctionSlot0(onButtonClicked));
	}
	private function onButtonClicked(): void
	{
		trace("button was clicked");
	}
}
Usage Example (complex)

This example runs at maximum speed with multiple buttons.

import com.jacksondunstan.signals.*;
public class Button extends Sprite
{
	public var clicked:Signal1 = new Signal1();
	public function Button()
	{
		addEventListener(MouseEvent.MOUSE_DOWN, onMouseDown);
	}
	private function onMouseDown(ev:MouseEvent): void
	{
		this.clicked.dispatch(this);
	}
}
public class MainMenu implements Slot1
{
	private var __button1:Button;
	private var __button2:Button;
	public function MainMenu(button1:Button, button2:Button)
	{
		__button1 = button1;
		__button2 = button2;
		button1.clicked.addSlot(this);
		button2.clicked.addSlot(this);
	}
	public function onSignal1(target:*): void
	{
		if (target == __button1)
		{
			trace("button 1 was clicked");
		}
		else if (target == __button2)
		{
			trace("button 2 was clicked");
		}
	}
}
Parameter Passing Strategy

As you can see from above, functionality from alternative systems can be emulated in TurboSignals by simply adding more event parameters. The Event/EventDispatcher system’s Event.target is attained by simply passing a reference to this as a parameter to dispatch. Likewise, the type field can easily be passed. You may also choose to pass objects like Event that include these too, which may help with speed as multiple arguments slow down the dispatch operation.

Performance Data

The TurboSignals distribution includes a suite of performance tests for TurboSignals as well as as3signals and Event/EventDispatcher. Here are the results for the first version:

TurboSignals – 1 Listener (1000000 dispatches)

Environment 0 1 2 3 4 5 6 7 8 9 10 N
2.2 Ghz Intel Core 2 Duo, 2GB, Mac OS X 10.6 56 91 98 97 101 90 94 107 104 116 119 1917

TurboSignals – 10 Listeners (1000000 dispatches)

Environment 0 1 2 3 4 5 6 7 8 9 10 N
2.2 Ghz Intel Core 2 Duo, 2GB, Mac OS X 10.6 317 600 570 585 567 606 582 562 580 614 628 7679

TurboSignals – 1 Function Listener (1000000 dispatches)

Environment 0 1 2 3 4 5 6 7 8 9 10 N
2.2 Ghz Intel Core 2 Duo, 2GB, Mac OS X 10.6 317 600 570 585 567 606 582 562 580 614 628 7679

TurboSignals – 10 Function Listeners (1000000 dispatches)

Environment 0 1 2 3 4 5 6 7 8 9 10 N
2.2 Ghz Intel Core 2 Duo, 2GB, Mac OS X 10.6 2985 3359 3449 3433 3551 3564 3534 3597 3624 3731 3862 22184

as3signals – 1 Function Listener (1000000 dispatches)

Environment 0 1 2 3 4 5 6 7 8 9 10 N
2.2 Ghz Intel Core 2 Duo, 2GB, Mac OS X 10.6 954 1158 1326 1418 1534 1696 1818 1951 2055 2467 2740 n/a

as3signals – 10 Function Listeners (1000000 dispatches)

Environment 0 1 2 3 4 5 6 7 8 9 10 N
2.2 Ghz Intel Core 2 Duo, 2GB, Mac OS X 10.6 2568 2908 3394 3656 3918 4249 4535 4805 5354 6288 6912 n/a

Event/EventDispatcher – 1 Function Listener (1000000 dispatches)

Environment 0 1 2 3 4 5 6 7 8 9 10 N
2.2 Ghz Intel Core 2 Duo, 2GB, Mac OS X 10.6 n/a 4886 n/a n/a n/a n/a n/a n/a n/a n/a n/a n/a

Event/EventDispatcher – 10 Function Listeners (1000000 dispatches)

Environment 0 1 2 3 4 5 6 7 8 9 10 N
2.2 Ghz Intel Core 2 Duo, 2GB, Mac OS X 10.6 n/a 33755 n/a n/a n/a n/a n/a n/a n/a n/a n/a n/a
Performance Graphs

1 listener

10 listeners

1 function listener

10 function listeners

Performance Analysis

The latest version of as3signals (as of today, 2/15/2010) goes a long way to improve performance of the Event/EventDispatcher system, especially on Mac OS X. TurboSignals goes a lot further though and nearly matches the speed of its inspiration: the simple list of Runnables. TurboSignals manages to dispatch events about 17 times faster than as3signals when implementing the slot directly and about 3 times faster than as3signals when using a Function variable to allow for a the callback to be private, named, or anonymous. That said, as3signals is itself 4-13x faster than the Event/EventDispatcher system. So if you are planning on dispatching frequently or to many listeners, you should definitely take a look at TurboSignals.