mirror of
https://github.com/logos-messaging/nim-ffi.git
synced 2026-06-20 16:29:31 +00:00
feat(codegen): Go per-event typed native event handlers
Adds the ergonomic native event surface for Go: `node.On<Event>(func(<Payload>))` registers a native listener for that event and the library delivers the typed `<Payload>` POD, which an exported Go callback reads into a Go struct (reusing the generated `fromC`) and hands to the user's handler — no CBOR parsing. Sits beside the existing CBOR `SetEventHandler` (wildcard / inter-process). The example registers `OnEchoFired` and receives a typed `EchoEvent` when Echo fires it. Verified end-to-end with `go run -race`. C/C++/Rust get the same per-event typed handler + router next. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
918dd72390
commit
ceccc7bef3
@ -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.
|
||||
|
||||
@ -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() {
|
||||
|
||||
@ -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 <Payload>*` 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<Event>(h)` registers a native listener; the library delivers the typed
|
||||
# `<Payload>` 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)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user