Create a gist now

Instantly share code, notes, and snippets.

@nadako /Main.hx
Last active Nov 12, 2015

What would you like to do?
Signal builder using new Rest type parameter in Haxe
class Main {
static function main() {
var signal = new Signal<Int,String>();
var conn = signal.connect(function(a, b) {
trace('Well done $a $b');
});
signal.dispatch(10, "lol");
}
}
import haxe.Constraints.Function;
@:genericBuild(SignalMacro.build())
class Signal<Rest> {}
class SignalBase<T:Function> {
var head:SignalConnection<T>;
var tail:SignalConnection<T>;
var toAddHead:SignalConnection<T>;
var toAddTail:SignalConnection<T>;
var dispatching:Bool;
public function new() {
dispatching = false;
}
public function connect(listener:T, once = false):SignalConnection<T> {
var conn = new SignalConnection(this, listener, once);
if (dispatching) {
if (toAddHead == null) {
toAddHead = toAddTail = conn;
} else {
toAddTail.next = conn;
conn.previous = toAddTail;
toAddTail = conn;
}
} else {
if (head == null) {
head = tail = conn;
} else {
tail.next = conn;
conn.previous = tail;
tail = conn;
}
}
return conn;
}
function disconnect(conn:SignalConnection<T>):Void {
if (head == conn)
head = head.next;
if (tail == conn)
tail = tail.previous;
if (toAddHead == conn)
toAddHead = toAddHead.next;
if (toAddTail == conn)
toAddTail = toAddTail.previous;
if (conn.previous != null)
conn.previous.next = conn.next;
if (conn.next != null)
conn.next.previous = conn.previous;
}
inline function startDispatch():Void {
dispatching = true;
}
function endDispatch():Void {
dispatching = false;
if (toAddHead != null) {
if (head == null) {
head = toAddHead;
tail = toAddTail;
} else {
tail.next = toAddHead;
toAddHead.previous = tail;
tail = toAddTail;
}
toAddHead = toAddTail = null;
}
}
}
@:allow(SignalBase)
@:access(SignalBase)
class SignalConnection<T:Function> {
var signal:SignalBase<T>;
var listener:T;
var once:Bool;
var previous:SignalConnection<T>;
var next:SignalConnection<T>;
function new(signal:SignalBase<T>, listener:T, once:Bool) {
this.signal = signal;
this.listener = listener;
this.once = once;
}
public function dispose():Void {
if (signal != null) {
signal.disconnect(this);
signal = null;
}
}
}
import haxe.macro.Context;
import haxe.macro.Expr;
import haxe.macro.Type;
using haxe.macro.Tools;
class SignalMacro {
static var signalTypes = new Map<Int,Bool>();
static function build():ComplexType {
return switch (Context.getLocalType()) {
case TInst(_.get() => {name: "Signal"}, params):
buildSignalClass(params);
default:
throw false;
}
}
static function buildSignalClass(params:Array<Type>):ComplexType {
var numParams = params.length;
var name = 'Signal$numParams';
if (!signalTypes.exists(numParams))
{
var typeParams:Array<TypeParamDecl> = [];
var superClassFunctionArgs:Array<ComplexType> = [];
var dispatchArgs:Array<FunctionArg> = [];
var listenerCallParams:Array<Expr> = [];
for (i in 0...numParams) {
typeParams.push({name: 'T$i'});
superClassFunctionArgs.push(TPath({name: 'T$i', pack: []}));
dispatchArgs.push({name: 'arg$i', type: TPath({name: 'T$i', pack: []})});
listenerCallParams.push(macro $i{'arg$i'});
}
var pos = Context.currentPos();
Context.defineType({
pack: [],
name: name,
pos: pos,
params: typeParams,
kind: TDClass({
pack: [],
name: "Signal",
sub: "SignalBase",
params: [TPType(TFunction(superClassFunctionArgs, macro : Void))]
}),
fields: [
{
name: "dispatch",
access: [APublic],
pos: pos,
kind: FFun({
args: dispatchArgs,
ret: macro : Void,
expr: macro {
startDispatch();
var conn = head;
while (conn != null) {
conn.listener($a{listenerCallParams});
if (conn.once)
conn.dispose();
conn = conn.next;
}
endDispatch();
}
})
}
]
});
signalTypes[numParams] = true;
}
return TPath({pack: [], name: name, params: [for (t in params) TPType(t.toComplexType())]});
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment