introduce callback state into ffi context

This commit is contained in:
Ivan FB 2026-05-08 17:16:29 +02:00
parent 729801b999
commit 5c31d2f313
No known key found for this signature in database
GPG Key ID: DF0C67A04C543270
2 changed files with 33 additions and 28 deletions

View File

@ -6,6 +6,12 @@ import std/[options, atomics, os, net, locks, json, tables]
import chronicles, chronos, chronos/threadsync, taskpools/channels_spsc_single, results
import ./ffi_types, ./ffi_thread_request, ./internal/ffi_macro, ./logging
type FFICallbackState* = object
## Holds the C event callback and its associated user-data pointer.
## Embedded in FFIContext and referenced from the FFI thread via a thread-local.
callback*: pointer
userData*: pointer
type FFIContext*[T] = object
myLib*: ptr T
# main library object (e.g., Waku, LibP2P, SDS, the one to be exposed as a library)
@ -19,51 +25,54 @@ type FFIContext*[T] = object
reqReceivedSignal: ThreadSignalPtr
# to signal main thread, interfacing with the FFI thread, that FFI thread received the request
userData*: pointer
eventCallback*: pointer
eventUserdata*: pointer
callbackState*: FFICallbackState
running: Atomic[bool] # To control when the threads are running
registeredRequests: ptr Table[cstring, FFIRequestProc]
# Pointer to with the registered requests at compile time
var ffiCurrentCallbackState* {.threadvar.}: ptr FFICallbackState
## Set by ffiThreadBody at thread startup; read by dispatchFfiEvent.
const git_version* {.strdefine.} = "n/a"
template callEventCallback*(ctx: ptr FFIContext, eventName: string, body: untyped) =
if isNil(ctx[].eventCallback):
if isNil(ctx[].callbackState.callback):
chronicles.error eventName & " - eventCallback is nil"
return
foreignThreadGc:
try:
let event = body
cast[FFICallBack](ctx[].eventCallback)(
RET_OK, unsafeAddr event[0], cast[csize_t](len(event)), ctx[].eventUserData
cast[FFICallBack](ctx[].callbackState.callback)(
RET_OK, unsafeAddr event[0], cast[csize_t](len(event)), ctx[].callbackState.userData
)
except Exception, CatchableError:
let msg =
"Exception " & eventName & " when calling 'eventCallBack': " &
getCurrentExceptionMsg()
cast[FFICallBack](ctx[].eventCallback)(
RET_ERR, unsafeAddr msg[0], cast[csize_t](len(msg)), ctx[].eventUserData
cast[FFICallBack](ctx[].callbackState.callback)(
RET_ERR, unsafeAddr msg[0], cast[csize_t](len(msg)), ctx[].callbackState.userData
)
template dispatchFfiEvent*(w: typed, eventName: string, body: untyped) =
## Fires an FFI event via the library object's event callback fields.
## Works with any type that has ffiEventCallback and ffiEventUserData fields.
if w.ffiEventCallback.isNil():
chronicles.error eventName & " - ffiEventCallback is nil"
template dispatchFfiEvent*(eventName: string, body: untyped) =
## Dispatches an FFI event to the callback registered via `{libName}_set_event_callback`.
## `body` is evaluated lazily — only when a callback is registered.
## Valid only on the FFI thread (i.e., inside {.ffi.} proc bodies and their async closures).
let ffiState = ffiCurrentCallbackState
if isNil(ffiState) or isNil(ffiState[].callback):
chronicles.error eventName & " - event callback not set"
return
foreignThreadGc:
try:
let event = body
cast[FFICallBack](w.ffiEventCallback)(
RET_OK, unsafeAddr event[0], cast[csize_t](len(event)), w.ffiEventUserData
cast[FFICallBack](ffiState[].callback)(
RET_OK, unsafeAddr event[0], cast[csize_t](len(event)), ffiState[].userData
)
except Exception, CatchableError:
let msg =
"Exception " & eventName & " when calling 'ffiEventCallback': " &
getCurrentExceptionMsg()
cast[FFICallBack](w.ffiEventCallback)(
RET_ERR, unsafeAddr msg[0], cast[csize_t](len(msg)), w.ffiEventUserData
"Exception dispatching " & eventName & ": " & getCurrentExceptionMsg()
cast[FFICallBack](ffiState[].callback)(
RET_ERR, unsafeAddr msg[0], cast[csize_t](len(msg)), ffiState[].userData
)
proc sendRequestToFFIThread*(
@ -196,6 +205,7 @@ proc processRequest[T](
proc ffiThreadBody[T](ctx: ptr FFIContext[T]) {.thread.} =
## FFI thread body that attends library user API requests
ffiCurrentCallbackState = addr ctx[].callbackState
logging.setupLog(logging.LogLevel.DEBUG, logging.LogFormat.TEXT)

View File

@ -93,13 +93,12 @@ macro declareLibraryBase(libraryName: static[string]): untyped =
macro declareLibrary*(libraryName: static[string], libType: untyped): untyped =
## Declares a library with the given name and automatically generates
## `{libraryName}_set_event_callback`, a C-exported function that lets callers
## register an event callback on both the FFIContext and the library object.
## `{libraryName}_set_event_callback`, a C-exported function that stores the
## caller's event callback on the FFIContext.
##
## `libType` is the Nim type of the main library object (e.g. `Waku`). It is used
## to type the `ctx: ptr FFIContext[libType]` parameter of the generated
## `{libraryName}_set_event_callback` proc, and to conditionally propagate the
## callback to `ctx.myLib[].ffiEventCallback` when that field exists on `libType`.
## `{libraryName}_set_event_callback` proc.
result = newStmtList()
# Emit the base bootstrap (pragmas, linker flags, NimMain, initializeLibrary)
@ -117,12 +116,8 @@ macro declareLibrary*(libraryName: static[string], libType: untyped): untyped =
if isNil(ctx):
echo `errorMsg`
return
ctx[].eventCallback = cast[pointer](callback)
ctx[].eventUserData = userData
when compiles(ctx.myLib[].ffiEventCallback):
if not isNil(ctx.myLib) and not isNil(ctx.myLib[]):
ctx.myLib[].ffiEventCallback = cast[pointer](callback)
ctx.myLib[].ffiEventUserData = userData
ctx[].callbackState.callback = cast[pointer](callback)
ctx[].callbackState.userData = userData
let procNode = newProc(
name = funcIdent,