99 Commits

Author SHA1 Message Date
Ivan FB
8f3d5cf8c2
fix: reject {.ffi.} calls on an unconstructed ref library
When no {.ffiCtor.} has stored a library, the FFI thread points `myLib` at a
default-valued fallback so handlers always have something to bind. For an
`object` library that is a usable zero value and callers legitimately depend on
it (tests/unit/test_ffi_handle drives a context that never runs a ctor), but for
a `ref` library the default is `nil`: the user body received a nil ref and
faulted on its first field access. A failing ctor is the common way to get
there, since the C entry point hands back a live context before the ctor body
has run on the FFI thread.

Track whether a ctor actually stored a library and, for `ref` library types
only, reject such requests with a clear error instead of dispatching them. The
check sits in the generated handler rather than the C entry point, so it runs
behind any queued constructor — a host that fires a call without awaiting the
create callback still succeeds, as before.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ULz7Md52AF6PmqZeCmh8b7
2026-07-29 11:42:49 -03:00
Ivan FB
11fd49c2dc
fix: reject {.ffi.} calls on an unconstructed ref library
When no {.ffiCtor.} has stored a library, the FFI thread points `myLib` at a
default-valued fallback so handlers always have something to bind, and
`myLibOwned` is what distinguishes that fallback from a real one. For an
`object` library the fallback is a usable zero value and callers legitimately
depend on it, but for a `ref` library it is `nil`: the user body received a nil
ref and faulted on its first field access. A failing ctor is the common way to
get there, since the C entry point hands back a live context before the ctor
body has run on the FFI thread.

Guard on `myLibOwned` for `ref` library types only. The check sits in the
generated handler rather than the C entry point, so it runs behind any queued
constructor — a host that fires a call without awaiting the create callback
still succeeds, as before.

Backport of the same fix on master, where the state had to be added rather than
reused.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ULz7Md52AF6PmqZeCmh8b7
2026-07-29 11:42:49 -03:00
Ivan FB
53c18944cc
feat(ffi): add {.ffiExport.} for simple synchronous C exports
{.ffi.}/{.ffiCtor.} cover the async, context-handle, CBOR-marshaled path.
{.ffiExport.} covers the complementary case: a no-argument lifecycle/health
entry point a host loads with dlopen+dlsym and calls synchronously — no
context, no callback, no CBOR, the return value crossing the ABI directly.

Native Nim return types are bridged to the C ABI (int/bool -> int,
uint64 -> unsigned long long, string -> const char* kept alive in shared
memory; no-return -> void), and initializeLibrary() is injected so the Nim
runtime is up on first call — the host never invokes NimMain itself.

Fills the no-argument scalar case of the declared-but-unimplemented abi=c path.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0138fdSv6Xhv2KjhiyYR4SAu
2026-07-29 11:42:49 -03:00
Ivan FB
80d3f58702
feat: multi-parameter {.ffiEvent.} via synthesised envelope
`{.ffiEvent.}` previously accepted exactly one parameter, forcing every
multi-field event to declare a hand-written {.ffi.} payload type. The macro
now bundles two or more parameters into a synthesised, registered envelope
object named `<WireNamePascalCase>Payload`, whose fields are the parameters,
and dispatches an instance of it. A single parameter still rides the wire
directly (scalar or existing {.ffi.} object), so this is backwards
compatible. Because the envelope is registered like any {.ffi.} type, the
foreign bindings gain it as a first-class struct plus a typed handler.

The timer example gains an `on_job_scheduled(jobId, willRunCount)` event to
exercise the path; the C++ and Rust bindings are regenerated accordingly.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-29 11:42:49 -03:00
Ivan FB
dda79fab34
feat: recycle pooled FFI contexts; compile-time request ids
Two foreign-host concurrency fixes for refc, both needed so a Go host can
hammer the FFI under load without corrupting Nim's per-thread GC.

1. Compile-time request ids. ffiNewReq / the method + ctor wrappers built
   the request id with `$T` at runtime, allocating a Nim GC string on the
   foreign caller's (often transient) thread. Emit a `cstring` literal of
   the type name instead — no allocation on the caller thread.

2. Recycle pooled contexts instead of destroy/recreate. Restores the
   release/v0.1 model that v0.2 dropped: a pool slot's worker + event
   threads and signal fds are built once and reused. The ffiDtor now
   requests a synchronous recycle (drain in-flight handlers, free the lib,
   clear listeners, release the slot) on the FFI thread, keeping the
   threads alive; createFFIContext reuses an initialised slot. Without this
   every create/destroy churned ~6 signal fds, so fd numbers climbed past
   FD_SETSIZE (1024) and ThreadSignalPtr.waitSync's select() failed with
   EINVAL under create/destroy load.

   Adds CtxLifecycle (Active/RecyclePending/Recycling), ctx-level
   inUse/tryClaim/release/markAsActive, requestRecycle (waits on a new
   recycleDoneSignal), freeLib (refc GC_unref / orc =destroy of ctor-owned
   libs), recycleContext, FFIEventRegistry.clearListeners, and roots the
   ctor-stored ref lib under refc (GC_ref, balanced in freeLib).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-29 11:42:49 -03:00
Ivan FB
3352517921
fix: set up foreign-thread GC in event-listener entry points
sds_add_event_listener / sds_remove_event_listener run on the foreign
caller thread and allocate Nim memory ($eventName, the listener registry
Table+seq), but unlike the ctor/dtor they never called initializeLibrary.
A foreign host (Go) migrates goroutines across OS threads, so the thread
running add_event_listener may not be the one that ran a prior entry
point; without setupForeignThreadGc its per-thread allocator region is
uninitialised and the first Nim allocation faults in the allocator.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-29 11:42:45 -03:00
Gabriel Cruz
83f1aae895
fix: non-canonical seq CBOR generation (#141) 2026-07-28 10:04:13 -03:00
Gabriel Cruz
435eebf9e3 chore(release): 0.3.0 v0.3.0-rc.0 2026-07-24 13:10:02 -03:00
Gabriel Cruz
41bb2b36a7
feat(ffi): {.ffiStatic.} for context-independent procs (#138) 2026-07-24 12:35:11 -03:00
Gabriel Cruz
20fe91ae45
feat(c): non-scalar CBOR-free fast path (#137) 2026-07-23 14:46:26 -03:00
Gabriel Cruz
64cb4bfce4
feat(codegen): {.ffiConst.} constants and {.ffi.} enums for C/C++/Rust (#140) 2026-07-23 14:00:24 -03:00
Gabriel Cruz
c58e2bcdd2
feat: propagate comments (#136) 2026-07-22 17:04:35 -03:00
Gabriel Cruz
98fb7da9e2
chore(c): ctx_destroy returns int (#135) 2026-07-22 16:21:18 -03:00
Gabriel Cruz
e60f771be6
feat(codegen): scalar-fast-path bindings for the abi = c header (#124) 2026-07-16 10:58:45 -03:00
Gabriel Cruz
9ed1fedf96
chore: reduce walls of comments (#132) 2026-07-15 11:46:05 -03:00
Gabriel Cruz
4981a1b71e
chore: unify c and c_abi (#130) 2026-07-15 09:54:19 -03:00
Ivan FB
7b028e64c4
feat(ffi): RET_STALE_WARN progress callback replacing handler timeout (#129) 2026-07-14 17:07:33 +02:00
Ivan FB
e0faefd5a0
fix(test): rebuild CBOR libecho in cpp e2e to avoid ABI mismatch (#128) 2026-07-13 11:12:36 -03:00
Gabriel Cruz
7ef58f4bf5
feat: shrink the genbindings incantation (#119) 2026-07-10 17:13:06 -03:00
Gabriel Cruz
c0f6f2e802
feat: main prefix validation (#116) 2026-07-10 16:49:07 -03:00
Gabriel Cruz
62d3c00b61
feat: check_bindings failure message (#117) 2026-07-10 16:33:14 -03:00
Gabriel Cruz
b53f0289a1
docs: complete the README workflow story (#121) 2026-07-10 16:27:23 -03:00
Gabriel Cruz
ea67fb747a
feat(ffi): make the scalar-fast-path binding drop loud (#120) 2026-07-09 16:43:21 -03:00
Gabriel Cruz
3e57751e3a
feat(ffi): async teardown hook for {.ffiDtor.} (#115) 2026-07-09 13:53:21 -03:00
Gabriel Cruz
5eaa799b7d
feat: CBOR-free abi=c C bindings (-d:targetLang=c_abi) (#109) 2026-07-06 13:48:08 -03:00
Gabriel Cruz
c91287ba22
feat: CBOR-free scalar fast path for abi=c procs (#110) 2026-07-06 12:48:07 -03:00
Gabriel Cruz
fdc68ea76d
feat(devx): derive ffiEvent wire name, guard genBindings ordering, real README (#111) 2026-07-06 12:31:49 -03:00
Gabriel Cruz
8dcdcdc76a
feat(ffi): configurable per-request handler timeout with a finite default (#93) (#108) 2026-07-06 11:58:04 -03:00
Gabriel Cruz
fb0a1f1077
feat(ffi): CBOR-free scalar fast path for all-scalar abi = c procs (#106) 2026-07-06 11:57:46 -03:00
Gabriel Cruz
8c1343aaf2
refactor(codegen): shared FFIType IR + single type parser for C/C++/Rust (#104) 2026-07-06 11:43:34 -03:00
Gabriel Cruz
08509cc74f
feat: slab-allocate event payloads (#107) 2026-07-06 10:52:09 -03:00
Gabriel Cruz
7e3fd96e74
feat(codegen): C binding generator (-d:targetLang=c) (#102) 2026-07-01 12:00:47 -03:00
Gabriel Cruz
f1cb110e52
fix: stale echo.hpp (#103) 2026-06-30 11:47:34 -03:00
Ivan FB
5d49ee6b08
feat(ffi): sharded MPSC request ingress (alternative to lock-free #98) (#101)
Co-authored-by: Gabriel Cruz <8129788+gmelodie@users.noreply.github.com>
2026-06-27 19:08:10 +02:00
Gabriel Cruz
3d7b267ef0
chore: bump nim versions to 2.2.6 and stable (#100) 2026-06-26 10:54:21 -03:00
Gabriel Cruz
4bac7a7bc6
test(bench): submit-throughput scaling gate for sendRequestToFFIThread (issue #90) (#97) 2026-06-25 18:02:45 -03:00
Gabriel Cruz
64a332ca8b
feat(ffi): scalar type mappings + seq[byte] byte-string codec (#99) 2026-06-25 17:16:21 -03:00
Gabriel Cruz
7362bfd212
test(bench): cbor vs c (cwire) codec microbenchmark (#86) v0.2.0-rc.1 2026-06-23 13:47:56 -03:00
Gabriel Cruz
f6881274ca
feat(ffi): cwire codec composite fields (#88) 2026-06-23 12:55:07 -03:00
Gabriel Cruz
6c4657ad7e
feat(ffi): cwire codec core (flat scalars + strings) (#87) 2026-06-23 11:49:19 -03:00
Gabriel Cruz
a5ccadd62d
feat(ffi): per-interaction ABI-format annotations + c codec (ffiRaw) (#85) 2026-06-23 11:18:03 -03:00
Gabriel Cruz
021f469041
feat: {.ffiHandle.} — export complex live objects as opaque handles (#81) v0.2.0-rc.0 2026-06-16 14:11:31 -03:00
Gabriel Cruz
16dc1b3573
chore(ci): add nph linting (#77) 2026-06-10 16:30:30 -03:00
Gabriel Cruz
9b3cde7674
feat: move user event code to a dedicated event thread (#69) 2026-06-09 11:35:04 -03:00
Gabriel Cruz
54c41a3e62
refactor(ffi): event thread scaffolding + FFIContext lifecycle split (#71) 2026-06-08 11:46:16 -03:00
Ivan FB
721f244312
docs: finalize 0.2.0 changelog and correct 0.1.4 date
CBOR is the headline 0.2.0 feature, not 0.1.4: at v0.1.4 serial.nim was
still JSON/string-based, so the prior CBOR attribution was wrong. Also
complete the 0.2.0 scope (events, registry, codegen) ahead of tagging.

Date 0.1.4 by its last functional change (#14, 2026-05-13) rather than
the later changelog/version-bump commits, so the version reflects when
its code actually settled.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 09:17:56 +02:00
Ivan FB
11aae0c7b8
add changelog for 0.1.4 (#73) 2026-06-03 23:43:52 +02:00
Ivan FB
f96a5b158a
Remove wildcard event listener; keep per-event dispatch (#70) 2026-06-02 22:22:35 +02:00
Ivan FB
e0bd74232b
Rust event examples (#53)
* rust examples: sync main.rs + tokio main.rs demoing the listener API

Adds two bundled examples to the generated Rust crate:

- examples/main.rs: sync flow using std::sync::mpsc to bridge a typed
  on_echo_fired listener into main + a wildcard add_event_listener
  that uses decode_event_payload::<EchoEvent>(envelope) for the
  matching event id.
- examples/tokio_main.rs: same shape via #[tokio::main] +
  tokio::sync::mpsc.

Bumps generateCargoToml to ship `[dev-dependencies]` with tokio's
`rt-multi-thread` + `macros` features so the bundled examples can use
#[tokio::main] without polluting the library's runtime profile.

Run with `cargo run --example main` (set DYLD_LIBRARY_PATH=<repo> on
macOS until build.rs emits an rpath).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* simplify examples

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-01 13:34:56 -03:00
Ivan FB
c43563f82f
rust codegen: per-event typed add_on_<x>_listener + wildcard add_event_listener (#52) 2026-05-29 20:40:28 +02:00