diff --git a/examples/timer/go_bindings/example/main.go b/examples/timer/go_bindings/example/main.go index d6ce723..f3ed126 100644 --- a/examples/timer/go_bindings/example/main.go +++ b/examples/timer/go_bindings/example/main.go @@ -31,12 +31,23 @@ func main() { fmt.Printf("version: %s\n", v) } + // Native typed event: Echo fires onEchoFired(EchoEvent) inside the library. + // Register a typed handler — the payload arrives as a Go struct, no CBOR. + events := make(chan timer.EchoEvent, 4) + node.OnEchoFired(func(e timer.EchoEvent) { events <- e }) + // Struct param + typed struct return: EchoResponse { Echoed; TimerName }. if resp, err := node.Echo(timer.EchoRequest{Message: "hello from Go", DelayMs: 5}); err != nil { log.Printf("echo: %v", err) } else { fmt.Printf("echo: echoed=%q timerName=%q\n", resp.Echoed, resp.TimerName) } + select { + case e := <-events: + fmt.Printf("event OnEchoFired: message=%q echoCount=%d\n", e.Message, e.EchoCount) + default: + fmt.Println("event OnEchoFired: (none received)") + } // Deeply nested param + typed return: slice of structs, slice of strings, // two optionals in; ComplexResponse { Summary; ItemCount; HasNote } out. diff --git a/examples/timer/go_bindings/my_timer.go b/examples/timer/go_bindings/my_timer.go index 25ba745..0af13dc 100644 --- a/examples/timer/go_bindings/my_timer.go +++ b/examples/timer/go_bindings/my_timer.go @@ -14,6 +14,7 @@ extern uint64_t my_timer_add_event_listener_cbor(void* ctx, const char* eventNam extern void my_timerResultEcho(int ret, char* msg, size_t len, void* ud); extern void my_timerResultComplex(int ret, char* msg, size_t len, void* ud); extern void my_timerResultSchedule(int ret, char* msg, size_t len, void* ud); +extern void my_timerEvtOnEchoFired(int ret, char* msg, size_t len, void* ud); typedef struct { int ret; char* msg; size_t len; int done; @@ -474,6 +475,28 @@ func my_timerGoEvent(ret C.int, msg *C.char, length C.size_t, userData unsafe.Po } } +var evtOnEchoFiredHandler func(EchoEvent) + +// OnEchoFired installs the native typed handler for the "on_echo_fired" event. +func (n *My_timerNode) OnEchoFired(h func(EchoEvent)) { + eventMu.Lock() + evtOnEchoFiredHandler = h + eventMu.Unlock() + cn := C.CString("on_echo_fired") + defer C.free(unsafe.Pointer(cn)) + C.my_timer_add_event_listener(n.ctx, cn, C.FFICallBack(C.my_timerEvtOnEchoFired), n.ctx) +} + +//export my_timerEvtOnEchoFired +func my_timerEvtOnEchoFired(ret C.int, msg *C.char, length C.size_t, ud unsafe.Pointer) { + eventMu.Lock() + h := evtOnEchoFiredHandler + eventMu.Unlock() + if h != nil && ret == C.RET_OK { + h(EchoEventFromC((*C.EchoEvent)(unsafe.Pointer(msg)))) + } +} + func NewMy_timer(config TimerConfig) (*My_timerNode, error) { c_config, free_config := config.toC() defer func() { diff --git a/ffi/codegen/go.nim b/ffi/codegen/go.nim index 789da35..e292a08 100644 --- a/ffi/codegen/go.nim +++ b/ffi/codegen/go.nim @@ -385,6 +385,14 @@ proc generateGoFile*( "extern void " & libName & "Result" & methodName(p.procName, libName) & "(int ret, char* msg, size_t len, void* ud);" ) + # One exported Go callback per event (native typed delivery): msg is a typed + # `const *` that the callback reads into a Go value. + for e in events: + if isFFIStruct(e.payloadTypeName, types): + L.add( + "extern void " & libName & "Evt" & snakeToPascalCase(e.wireName) & + "(int ret, char* msg, size_t len, void* ud);" + ) L.add("") L.add("typedef struct {") L.add(" int ret; char* msg; size_t len; int done;") @@ -565,6 +573,47 @@ proc generateGoFile*( L.add("}") L.add("") + # ---- per-event NATIVE typed handlers ------------------------------------- + # `On(h)` registers a native listener; the library delivers the typed + # `` POD, which the exported callback reads into a Go value and hands + # to `h`. No CBOR — this is the same-process path (cf. SetEventHandler). + for e in events: + if not isFFIStruct(e.payloadTypeName, types): + continue + let pascal = snakeToPascalCase(e.wireName) + let goType = e.payloadTypeName + let handlerVar = "evt" & pascal & "Handler" + let cbName = libName & "Evt" & pascal + L.add("var " & handlerVar & " func(" & goType & ")") + L.add("") + L.add("// " & pascal & " installs the native typed handler for the \"" & + e.wireName & "\" event.") + L.add("func (n *" & nodeType & ") " & pascal & "(h func(" & goType & ")) {") + L.add("\teventMu.Lock()") + L.add("\t" & handlerVar & " = h") + L.add("\teventMu.Unlock()") + L.add("\tcn := C.CString(\"" & e.wireName & "\")") + L.add("\tdefer C.free(unsafe.Pointer(cn))") + L.add( + "\tC." & libName & "_add_event_listener(n.ctx, cn, C.FFICallBack(C." & cbName & + "), n.ctx)" + ) + L.add("}") + L.add("") + L.add("//export " & cbName) + L.add( + "func " & cbName & + "(ret C.int, msg *C.char, length C.size_t, ud unsafe.Pointer) {" + ) + L.add("\teventMu.Lock()") + L.add("\th := " & handlerVar) + L.add("\teventMu.Unlock()") + L.add("\tif h != nil && ret == C.RET_OK {") + L.add("\t\th(" & goType & "FromC((*C." & goType & ")(unsafe.Pointer(msg))))") + L.add("\t}") + L.add("}") + L.add("") + # ---- constructor --------------------------------------------------------- if haveCtor: let (goParams, conv, callArgs) = goParamConv(ctor.extraParams, types)