Last active
March 2, 2025 17:22
-
-
Save nadako/b086569b9fffb759a1b5 to your computer and use it in GitHub Desktop.
Signal builder using new Rest type parameter in Haxe
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#if macro | |
import haxe.macro.Context; | |
import haxe.macro.Expr; | |
import haxe.macro.Type; | |
using haxe.macro.Tools; | |
class SignalMacro { | |
static function build():ComplexType { | |
return switch (Context.getLocalType()) { | |
case TInst(_.get() => {name: "Signal"}, params): | |
buildSignalClass(params); | |
default: | |
throw "assert"; | |
} | |
} | |
static function buildSignalClass(params:Array<Type>):ComplexType { | |
var numParams = params.length; | |
var name = 'Signal$numParams'; | |
var typeExists = try { Context.getType(name); true; } catch (_:Any) false; | |
if (!typeExists) { | |
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(); | |
} | |
}) | |
} | |
] | |
}); | |
} | |
return TPath({pack: [], name: name, params: [for (t in params) TPType(t.toComplexType())]}); | |
} | |
} | |
#end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Awesome! I was actually messing with
Context.getType
to fix it myself, so i’ll update if i get something working before you’re able to.Edit: I added in this method to
SignalMarco.hx
:and replaced line 23 with:
This seems to work! I ran into an issue with Hashlink (every other build it would error with
JIT ERROR 0 (jit.c line 3527)
), but I think that's due to something going on with Hashlink, not specific to this macro.On another note, have you thought about submitting this to haxelib? this is the best signal implementation i’ve found for haxe, but i was only able to find it because someone (Gama11 😄) linked it to me.