Simple & Smart cross-platform useful implementation of Signals without any dependencies.

Build status


Compatible with targets:

  • Flash
  • Java
  • Neko
  • C++
  • PHP
  • JS
  • Python
  • Lua

C# in future plans.


Stable release: haxelib install signal.

Development version:

haxelib hg signal https://bitbucket.org/fzzr/hx.signal

Then add to your hxml: -lib signal.


This is almost classic signals and really simple to use.

// create signal with one argument`:Int`:
var signal = new Signal<Int>();

// add listener:
signal.add( function(value:Int) trace("1: " + value) );

// add "one-shot" `once` listener:
//          there second optional argument is `true`:  ____
signal.add( function(value:Int) trace("2: " + value) , true);

// dispatch - invoke all listeners:
signal.dispatch(42); // traces: "1: 42" , "2: 42"

// dispatch again:
signal.dispatch(42); // traces: "1: 42"
// signals based on any types and its numbers:
// signal without args
new Signal()

// signal on Int
new Signal<Int>()

// signal on any what we want
new Signal<Int, Float, Signal<Bool>, Foo, Bar, ?(?String -> Predicate) -> Bytes>()

And we have neat completion by Haxe compiler!


and type-checks

type checks

See tests for more examples.

Advanced Usage:

// create signal from function-type:
var signal:Signal<Int -> Bool>;
// add listener:
signal.add( function(f:Int -> Bool) f() );
// dispatch:
signal.dispatch( function(v:Int) return v % 2 == 0 );
// create signal from function-type ending with Void:
// it will expanded to Signal<Int>
var signal = new Signal<Int -> Void>();
// create signal from function-type with Optional arguments:
var signal = new Signal<?Int -> ?Bool -> Void>();
signal.add( function(?value:Int, ?is:Bool) trace('$value is $is') );
signal.dispatch();     // trace: " is "
signal.dispatch(42);   // trace: "42 is "
signal.dispatch(true); // trace: " is true"

Standard behavior:

I think I shall not describe in detail the behavior of a standard signal.

In short, we have a signal as a dispatcher. And two kinds of handlers/listeners: ordinary - lives until it is killed; once - one-shot throwaway listener.

We can add listener: ordinary: mySignal.add(myListenerFunction); once: mySignal.add(myListenerFunction, true);

We can get a typed <abbr title="Value Object">VO</abbr> named Slot when doing add the listener:

var slot = mySignal.add(myListenerFunction);

Also we can try to get a slot by the listener:

var slot:Null<Slot> = mySignal.get(myListenerFunction);

Now we can remove listener: mySignal.remove(slot); or slot.dispose();

How to create the signal? or how the macro-generator works for signals

With required arguments:

var s:Signal;
    s = new Signal() // is signal with `dispatch()` without arguments
    s = new Signal<Void>() // same
    s = new Signal<Void, Void>() // too
    s = new Signal<Void -> Void>() // same too (look, function type-param!)

var s1 = new Signal<Foo>() // is signal with `dispatch(arg:Foo)` with one argument
    s1 = new Signal<Foo -> Void>() // same (look, function type again!)

var s5 = new Signal<Foo, Bar, C, D, E>() // is signal with `dispatch` with five argument
    s5 = new Signal<Foo -> Bar -> C -> D -> E -> Void>() // same

With optional arguments - only with one Function-type parameter:

var s1 = new Signal<?Foo -> Void>() // produce `function dispatch(?arg:Foo)`
    s1.dispatch(); // we can omit opt arg

var s2 = new Signal<Foo -> ?Bar -> Void>() // same but two args where second is optional

It works only for single type-parameter passed to Signal< > and type should ending with Void. For example: ?Foo -> Void and not ?Foo -> Bar because method dispatch can't return anything except nothing (Void).

Signal as Monomorph (think "any Signal")

Also works really cool with Monomorph from library "quasix".

Simply add to your hxml: -lib quasix and use cool syntax. It look like a classic var m:Map<String, Float> = new Map() with omitted type-parameters in the right part.

//                 no need params here:  -----.
var s:Signal<Int, Array<String>> = new Signal();
s.dispatch(42, []);


//          .------ no need params there:
var s:Signal = new Signal<Int, Array<String>>();
s.dispatch(42, []);

without casting and any runtime overhead.

Special behaviors:

These things are a little slower, but they provide safety.


  • signal-stoppable enable truth stopPropagation-like behavior for method Signal.stop();
  • signal-safety helps if
    • if its slot is disposed;
    • if next slot is disposed;
    • if this signal is disposed;
    • when "one-shot" listener with sub-call .dispatch() for current signal;
  • prevent-signal-looping throw or trace exception about founded loop.
  • signal-strict more strict typing for Signal's optional TypeParams, e.g. for Signal based on ?Int -> Bool -> Void the first param should be optional in listener if strict is enabled. For example see what types we can use as listeners for Signal<?Int -> Bool -> Void>:
    • in case if -D signal-strict is defined - optional parameter always only optional:
      • ?Int -> Bool -> Void
      • ?Int -> ?Bool -> Void
    • in case if signal-strict is false/0 or not defined - listener can be:
      • Int -> Bool -> Void
      • ?Int -> Bool -> Void
      • Int -> ?Bool -> Void
      • ?Int -> ?Bool -> Void
  • signal-debug TODO: not implemented yet.

For more (complex) examples checkout unit-tests.

no more type hell!