mirror of
https://github.com/logos-messaging/nim-ffi.git
synced 2026-06-20 08:19:55 +00:00
Adds native (zero-CBOR) event support to the Rust generator, mirroring the cpp_native / Go event path: a per-event `add_<wire>_listener` registrar takes a closure, boxes it, and registers it through the bare `<lib>_add_event_listener` (native) entry point. The extern "C" trampoline reads the payload as the raw C-POD struct and hands the consumer a borrowed idiomatic value via from_c — no serialization on the hot path. The node owns the boxed closures in a Mutex<HashMap<id, Box<dyn Any>>> keyed by listener id so they outlive the call, and `remove_event_listener` drops them and calls the bare remove entry point. Event externs are only emitted when the library declares events, so event-free crates stay minimal. Verified end-to-end: the demo registers a listener, echo fires on_echo_fired inline, the typed EchoEvent reaches the closure, and removal returns true. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
462 lines
21 KiB
Nim
462 lines
21 KiB
Nim
## Native (zero-serialization) Rust binding generator.
|
|
##
|
|
## Emits a `<lib>_native` crate that wraps the *native* C ABI (the `<name>`
|
|
## entry points + flat C-POD structs) — no CBOR. Each `{.ffi.}` type is a
|
|
## `#[repr(C)]` mirror (`ffi`) plus an idiomatic Rust struct with `to_c`/`from_c`
|
|
## (`types`), and `<Lib>Node` marshals typed args in / reads typed struct
|
|
## returns out (`api`). Counterpart of the CBOR generator in `rust_cbor.nim`, and
|
|
## the Rust analogue of `cpp_native.nim`.
|
|
##
|
|
## Commit 1 covers scalar / string / bool / float / nested-struct fields and the
|
|
## procs that use only those (the timer's create / version / echo). Sequences,
|
|
## optionals and native events are layered on next.
|
|
|
|
import std/[os, strutils]
|
|
import ./meta, ./string_helpers
|
|
|
|
proc isSeqT(t: string): bool =
|
|
t.strip().startsWith("seq[") and t.strip().endsWith("]")
|
|
|
|
proc isOptT(t: string): bool =
|
|
let s = t.strip()
|
|
(s.startsWith("Option[") or s.startsWith("Maybe[")) and s.endsWith("]")
|
|
|
|
proc isStringT(t: string): bool =
|
|
t.strip() in ["string", "cstring"]
|
|
|
|
proc isStructT(t: string, types: seq[FFITypeMeta]): bool =
|
|
for ty in types:
|
|
if ty.name == t.strip():
|
|
return true
|
|
false
|
|
|
|
proc seqElem(t: string): string =
|
|
t.strip()["seq[".len .. ^2].strip()
|
|
|
|
proc optElem(t: string): string =
|
|
let s = t.strip()
|
|
let p = if s.startsWith("Maybe["): "Maybe[".len else: "Option[".len
|
|
s[p .. ^2].strip()
|
|
|
|
proc rustScalar(t: string): string =
|
|
case t.strip()
|
|
of "int", "int64", "clong": "i64"
|
|
of "int32", "cint": "i32"
|
|
of "int16": "i16"
|
|
of "int8": "i8"
|
|
of "uint", "uint64", "csize_t": "u64"
|
|
of "uint32", "cuint": "u32"
|
|
of "uint16": "u16"
|
|
of "uint8", "byte": "u8"
|
|
of "bool": "bool"
|
|
of "float", "float32": "f32"
|
|
of "float64": "f64"
|
|
else: capitalizeFirstLetter(t.strip()) # nested struct -> its idiomatic name
|
|
|
|
proc rustIdiomatic(t: string): string =
|
|
let s = t.strip()
|
|
if isSeqT(s): "Vec<" & rustIdiomatic(seqElem(s)) & ">"
|
|
elif isOptT(s): "Option<" & rustIdiomatic(optElem(s)) & ">"
|
|
elif isStringT(s): "String"
|
|
else: rustScalar(s)
|
|
|
|
proc rustCElem(t: string, types: seq[FFITypeMeta], qualify = false): string =
|
|
## repr(C) type for one seq element / option payload (matches emitCStructs).
|
|
## `qualify` prefixes struct names with `ffi::` (needed outside the ffi module).
|
|
let s = t.strip()
|
|
if isStringT(s): "*const c_char"
|
|
elif s == "bool": "c_int"
|
|
elif isStructT(s, types): (if qualify: "ffi::" else: "") & capitalizeFirstLetter(s)
|
|
else: rustScalar(s)
|
|
|
|
proc procSimple(p: FFIProcMeta, types: seq[FFITypeMeta]): bool =
|
|
## All scalar / string / struct params are supported (structs may carry
|
|
## seq/Option fields). Bare seq/Option top-level params and raw pointers are
|
|
## not (the native ABI doesn't expose them either).
|
|
for ep in p.extraParams:
|
|
let t = ep.typeName.strip()
|
|
if ep.isPtr or isSeqT(t) or isOptT(t):
|
|
return false
|
|
true
|
|
|
|
# ── ffi.rs ──────────────────────────────────────────────────────────────────
|
|
proc emitFfiRs(
|
|
procs: seq[FFIProcMeta], types: seq[FFITypeMeta], libName: string,
|
|
events: seq[FFIEventMeta] = @[]
|
|
): string =
|
|
var L: seq[string] = @[]
|
|
L.add("// Generated by nim-ffi native Rust codegen. Do not edit by hand.")
|
|
L.add("#![allow(non_snake_case, dead_code)]")
|
|
L.add("use std::os::raw::{c_char, c_int, c_void};")
|
|
L.add("")
|
|
L.add("pub type FFICallback =")
|
|
L.add(" unsafe extern \"C\" fn(ret: c_int, msg: *const c_char, len: usize, user_data: *mut c_void);")
|
|
L.add("")
|
|
# repr(C) POD mirrors — field layout mirrors codegen/c.emitCStructs.
|
|
for t in types:
|
|
L.add("#[repr(C)]")
|
|
L.add("#[derive(Clone, Copy)]")
|
|
L.add("pub struct " & capitalizeFirstLetter(t.name) & " {")
|
|
for f in t.fields:
|
|
let snake = camelToSnakeCase(f.name)
|
|
let ft = f.typeName.strip()
|
|
if isSeqT(ft):
|
|
L.add(" pub " & snake & ": *const " & rustCElem(seqElem(ft), types) & ",")
|
|
L.add(" pub " & snake & "_len: usize,")
|
|
elif isOptT(ft):
|
|
L.add(" pub " & snake & "_present: c_int,")
|
|
L.add(" pub " & snake & ": " & rustCElem(optElem(ft), types) & ",")
|
|
else:
|
|
L.add(" pub " & snake & ": " & rustCElem(ft, types) & ",")
|
|
L.add("}")
|
|
L.add("")
|
|
L.add("#[link(name = \"" & libName & "\")]")
|
|
L.add("extern \"C\" {")
|
|
for p in procs:
|
|
case p.kind
|
|
of FFIKind.CTOR:
|
|
var ps: seq[string] = @[]
|
|
for ep in p.extraParams:
|
|
ps.add(camelToSnakeCase(ep.name) & ": " & rustCElem(ep.typeName, types))
|
|
ps.add("callback: FFICallback")
|
|
ps.add("user_data: *mut c_void")
|
|
L.add(" pub fn " & p.procName & "(" & ps.join(", ") & ") -> *mut c_void;")
|
|
of FFIKind.FFI:
|
|
if not procSimple(p, types):
|
|
continue
|
|
var ps = @["ctx: *mut c_void", "callback: FFICallback", "user_data: *mut c_void"]
|
|
for ep in p.extraParams:
|
|
ps.add(camelToSnakeCase(ep.name) & ": " & rustCElem(ep.typeName, types))
|
|
L.add(" pub fn " & p.procName & "(" & ps.join(", ") & ") -> c_int;")
|
|
of FFIKind.DTOR:
|
|
L.add(" pub fn " & p.procName & "(ctx: *mut c_void) -> c_int;")
|
|
if events.len > 0:
|
|
# Native event registration: the bare (non-_cbor) listener delivers the
|
|
# payload as the raw C-POD struct, so the trampoline reads it directly.
|
|
L.add(" pub fn " & libName & "_add_event_listener(ctx: *mut c_void, event_name: *const c_char, callback: FFICallback, user_data: *mut c_void) -> u64;")
|
|
L.add(" pub fn " & libName & "_remove_event_listener(ctx: *mut c_void, listener_id: u64) -> c_int;")
|
|
L.add("}")
|
|
return L.join("\n")
|
|
|
|
# ── types.rs (idiomatic structs + to_c / from_c) ─────────────────────────────
|
|
proc emitTypesRs(types: seq[FFITypeMeta]): string =
|
|
var L: seq[string] = @[]
|
|
L.add("// Generated by nim-ffi native Rust codegen. Do not edit by hand.")
|
|
L.add("#![allow(dead_code)]")
|
|
L.add("use std::ffi::{CStr, CString};")
|
|
L.add("use std::os::raw::c_char;")
|
|
L.add("use super::ffi;")
|
|
L.add("")
|
|
L.add("fn cstr(p: *const c_char) -> String {")
|
|
L.add(" if p.is_null() { String::new() }")
|
|
L.add(" else { unsafe { CStr::from_ptr(p) }.to_string_lossy().into_owned() }")
|
|
L.add("}")
|
|
L.add("")
|
|
for t in types:
|
|
let rn = capitalizeFirstLetter(t.name)
|
|
let hn = rn & "C"
|
|
# idiomatic struct
|
|
L.add("#[derive(Clone, Debug, Default)]")
|
|
L.add("pub struct " & rn & " {")
|
|
for f in t.fields:
|
|
L.add(" pub " & camelToSnakeCase(f.name) & ": " & rustIdiomatic(f.typeName) & ",")
|
|
L.add("}")
|
|
L.add("")
|
|
# holder: owns the C struct + all heap backing (CStrings, arrays, nested
|
|
# holders). Returned by value; the backing lives on the heap, so the C
|
|
# struct's raw pointers stay valid across the move and for the call.
|
|
L.add("pub struct " & hn & " {")
|
|
L.add(" pub c: ffi::" & rn & ",")
|
|
L.add(" _s: Vec<CString>,")
|
|
for f in t.fields:
|
|
let s = camelToSnakeCase(f.name)
|
|
let ft = f.typeName.strip()
|
|
if isSeqT(ft):
|
|
L.add(" _a_" & s & ": Vec<" & rustCElem(seqElem(ft), types, qualify = true) & ">,")
|
|
if isStructT(seqElem(ft), types):
|
|
L.add(" _h_" & s & ": Vec<" & capitalizeFirstLetter(seqElem(ft)) & "C>,")
|
|
elif isOptT(ft) and isStructT(optElem(ft), types):
|
|
L.add(" _h_" & s & ": Vec<" & capitalizeFirstLetter(optElem(ft)) & "C>,")
|
|
elif isStructT(ft, types):
|
|
L.add(" _h_" & s & ": Vec<" & capitalizeFirstLetter(ft) & "C>,")
|
|
L.add("}")
|
|
L.add("")
|
|
L.add("impl " & rn & " {")
|
|
L.add(" pub fn to_c(&self) -> " & hn & " {")
|
|
L.add(" #[allow(unused_mut)]")
|
|
L.add(" let mut h = " & hn & " { c: unsafe { std::mem::zeroed() }, _s: Vec::new(),")
|
|
for f in t.fields:
|
|
let s = camelToSnakeCase(f.name)
|
|
let ft = f.typeName.strip()
|
|
if isSeqT(ft):
|
|
L.add(" _a_" & s & ": Vec::new(),")
|
|
if isStructT(seqElem(ft), types):
|
|
L.add(" _h_" & s & ": Vec::new(),")
|
|
elif (isOptT(ft) and isStructT(optElem(ft), types)) or isStructT(ft, types):
|
|
L.add(" _h_" & s & ": Vec::new(),")
|
|
L.add(" };")
|
|
for f in t.fields:
|
|
let s = camelToSnakeCase(f.name)
|
|
let ft = f.typeName.strip()
|
|
if isSeqT(ft):
|
|
let e = seqElem(ft)
|
|
L.add(" for x in &self." & s & " {")
|
|
if isStringT(e):
|
|
L.add(" let cs = CString::new(x.as_str()).unwrap_or_default();")
|
|
L.add(" h._a_" & s & ".push(cs.as_ptr());")
|
|
L.add(" h._s.push(cs);")
|
|
elif isStructT(e, types):
|
|
L.add(" let eh = x.to_c();")
|
|
L.add(" h._a_" & s & ".push(eh.c);")
|
|
L.add(" h._h_" & s & ".push(eh);")
|
|
elif e == "bool":
|
|
L.add(" h._a_" & s & ".push(if *x { 1 } else { 0 });")
|
|
else:
|
|
L.add(" h._a_" & s & ".push(*x);")
|
|
L.add(" }")
|
|
L.add(" h.c." & s & " = if h._a_" & s & ".is_empty() { std::ptr::null() } else { h._a_" & s & ".as_ptr() };")
|
|
L.add(" h.c." & s & "_len = h._a_" & s & ".len();")
|
|
elif isOptT(ft):
|
|
let e = optElem(ft)
|
|
if isStringT(e):
|
|
L.add(" if let Some(x) = &self." & s & " {")
|
|
L.add(" let cs = CString::new(x.as_str()).unwrap_or_default();")
|
|
L.add(" h.c." & s & " = cs.as_ptr(); h.c." & s & "_present = 1; h._s.push(cs);")
|
|
L.add(" }")
|
|
elif isStructT(e, types):
|
|
L.add(" if let Some(x) = &self." & s & " {")
|
|
L.add(" let eh = x.to_c(); h.c." & s & " = eh.c; h.c." & s & "_present = 1; h._h_" & s & ".push(eh);")
|
|
L.add(" }")
|
|
elif e == "bool":
|
|
L.add(" if let Some(v) = self." & s & " { h.c." & s & "_present = 1; h.c." & s & " = if v { 1 } else { 0 }; }")
|
|
else:
|
|
L.add(" if let Some(v) = self." & s & " { h.c." & s & "_present = 1; h.c." & s & " = v; }")
|
|
elif isStringT(ft):
|
|
L.add(" { let cs = CString::new(self." & s & ".as_str()).unwrap_or_default(); h.c." & s & " = cs.as_ptr(); h._s.push(cs); }")
|
|
elif ft == "bool":
|
|
L.add(" h.c." & s & " = if self." & s & " { 1 } else { 0 };")
|
|
elif isStructT(ft, types):
|
|
L.add(" h._h_" & s & ".push(self." & s & ".to_c()); h.c." & s & " = h._h_" & s & "[0].c;")
|
|
else:
|
|
L.add(" h.c." & s & " = self." & s & ";")
|
|
L.add(" h")
|
|
L.add(" }")
|
|
# from_c
|
|
L.add(" pub fn from_c(c: &ffi::" & rn & ") -> Self {")
|
|
L.add(" " & rn & " {")
|
|
for f in t.fields:
|
|
let s = camelToSnakeCase(f.name)
|
|
let ft = f.typeName.strip()
|
|
if isSeqT(ft):
|
|
let e = seqElem(ft)
|
|
var conv = "|&v| v"
|
|
if isStringT(e): conv = "|&p| cstr(p)"
|
|
elif isStructT(e, types): conv = "|e| " & capitalizeFirstLetter(e) & "::from_c(e)"
|
|
elif e == "bool": conv = "|&v| v != 0"
|
|
L.add(" " & s & ": if c." & s & ".is_null() { Vec::new() } else { unsafe { std::slice::from_raw_parts(c." & s & ", c." & s & "_len) }.iter().map(" & conv & ").collect() },")
|
|
elif isOptT(ft):
|
|
let e = optElem(ft)
|
|
var val = "c." & s
|
|
if isStringT(e): val = "cstr(c." & s & ")"
|
|
elif isStructT(e, types): val = capitalizeFirstLetter(e) & "::from_c(&c." & s & ")"
|
|
elif e == "bool": val = "c." & s & " != 0"
|
|
L.add(" " & s & ": if c." & s & "_present != 0 { Some(" & val & ") } else { None },")
|
|
elif isStringT(ft):
|
|
L.add(" " & s & ": cstr(c." & s & "),")
|
|
elif ft == "bool":
|
|
L.add(" " & s & ": c." & s & " != 0,")
|
|
elif isStructT(ft, types):
|
|
L.add(" " & s & ": " & capitalizeFirstLetter(ft) & "::from_c(&c." & s & "),")
|
|
else:
|
|
L.add(" " & s & ": c." & s & ",")
|
|
L.add(" }")
|
|
L.add(" }")
|
|
L.add("}")
|
|
L.add("")
|
|
return L.join("\n")
|
|
|
|
# ── api.rs (Node + blocking calls) ───────────────────────────────────────────
|
|
proc rustMethod(procName, libName: string): string =
|
|
let prefix = libName & "_"
|
|
let bare =
|
|
if procName.startsWith(prefix): procName[prefix.len .. ^1] else: procName
|
|
camelToSnakeCase(bare)
|
|
|
|
proc emitApiRs(
|
|
procs: seq[FFIProcMeta], types: seq[FFITypeMeta], libName: string,
|
|
events: seq[FFIEventMeta] = @[]
|
|
): string =
|
|
let nodeT = snakeToPascalCase(libName) & "Node" # idiomatic Rust: MyTimerNode
|
|
let hasEvents = events.len > 0
|
|
var L: seq[string] = @[]
|
|
L.add("// Generated by nim-ffi native Rust codegen. Do not edit by hand.")
|
|
L.add("#![allow(dead_code)]")
|
|
L.add("use std::os::raw::{c_char, c_int, c_void};")
|
|
L.add("use std::sync::mpsc::{sync_channel, SyncSender};")
|
|
L.add("use super::ffi;")
|
|
L.add("use super::types::*;")
|
|
L.add("")
|
|
L.add("const RET_OK: c_int = 0;")
|
|
L.add("")
|
|
L.add("unsafe fn err_text(msg: *const c_char, len: usize) -> String {")
|
|
L.add(" if msg.is_null() || len == 0 { return String::from(\"error\"); }")
|
|
L.add(" String::from_utf8_lossy(std::slice::from_raw_parts(msg as *const u8, len)).into_owned()")
|
|
L.add("}")
|
|
L.add("")
|
|
L.add("unsafe extern \"C\" fn ack_cb(ret: c_int, msg: *const c_char, len: usize, ud: *mut c_void) {")
|
|
L.add(" let tx = Box::from_raw(ud as *mut SyncSender<Result<(), String>>);")
|
|
L.add(" let _ = tx.send(if ret == RET_OK { Ok(()) } else { Err(err_text(msg, len)) });")
|
|
L.add("}")
|
|
L.add("unsafe extern \"C\" fn str_cb(ret: c_int, msg: *const c_char, len: usize, ud: *mut c_void) {")
|
|
L.add(" let tx = Box::from_raw(ud as *mut SyncSender<Result<String, String>>);")
|
|
L.add(" let r = if ret == RET_OK {")
|
|
L.add(" let b = if msg.is_null() || len == 0 { Vec::new() } else { std::slice::from_raw_parts(msg as *const u8, len).to_vec() };")
|
|
L.add(" Ok(String::from_utf8_lossy(&b).into_owned())")
|
|
L.add(" } else { Err(err_text(msg, len)) };")
|
|
L.add(" let _ = tx.send(r);")
|
|
L.add("}")
|
|
# Per-struct-return callbacks.
|
|
for p in procs:
|
|
if p.kind != FFIKind.FFI or not procSimple(p, types): continue
|
|
if not isStructT(p.returnTypeName, types): continue
|
|
let rt = capitalizeFirstLetter(p.returnTypeName)
|
|
L.add("unsafe extern \"C\" fn cb_" & p.procName & "(ret: c_int, msg: *const c_char, len: usize, ud: *mut c_void) {")
|
|
L.add(" let tx = Box::from_raw(ud as *mut SyncSender<Result<" & rt & ", String>>);")
|
|
L.add(" let r = if ret == RET_OK && !msg.is_null() {")
|
|
L.add(" Ok(" & rt & "::from_c(&*(msg as *const ffi::" & rt & ")))")
|
|
L.add(" } else { Err(err_text(msg, len)) };")
|
|
L.add(" let _ = tx.send(r);")
|
|
L.add("}")
|
|
# Per-event handler boxes + native trampolines (read the raw C-POD payload).
|
|
for e in events:
|
|
let pt = capitalizeFirstLetter(e.payloadTypeName)
|
|
let hs = snakeToPascalCase(e.wireName) & "Handler"
|
|
L.add("")
|
|
L.add("struct " & hs & " { f: Box<dyn Fn(&" & pt & ") + Send + Sync> }")
|
|
L.add("unsafe extern \"C\" fn " & e.wireName & "_trampoline(ret: c_int, msg: *const c_char, _len: usize, ud: *mut c_void) {")
|
|
L.add(" if ud.is_null() || ret != RET_OK || msg.is_null() { return; }")
|
|
L.add(" let h = &*(ud as *const " & hs & ");")
|
|
L.add(" (h.f)(&" & pt & "::from_c(&*(msg as *const ffi::" & pt & ")));")
|
|
L.add("}")
|
|
if hasEvents:
|
|
L.add("")
|
|
L.add("#[derive(Clone, Copy)]")
|
|
L.add("pub struct ListenerHandle { pub id: u64 }")
|
|
L.add("")
|
|
if hasEvents:
|
|
L.add("pub struct " & nodeT & " {")
|
|
L.add(" ctx: *mut c_void,")
|
|
L.add(" listeners: std::sync::Mutex<std::collections::HashMap<u64, Box<dyn std::any::Any + Send>>>,")
|
|
L.add("}")
|
|
else:
|
|
L.add("pub struct " & nodeT & " { ctx: *mut c_void }")
|
|
L.add("unsafe impl Send for " & nodeT & " {}")
|
|
L.add("unsafe impl Sync for " & nodeT & " {}")
|
|
L.add("")
|
|
L.add("impl " & nodeT & " {")
|
|
|
|
# ctor
|
|
for p in procs:
|
|
if p.kind != FFIKind.CTOR: continue
|
|
var params: seq[string] = @[]
|
|
for ep in p.extraParams:
|
|
params.add(camelToSnakeCase(ep.name) & ": " & capitalizeFirstLetter(ep.typeName))
|
|
L.add(" pub fn new(" & params.join(", ") & ") -> Result<Self, String> {")
|
|
var args: seq[string] = @[]
|
|
for ep in p.extraParams:
|
|
# The holder must outlive the call (it owns the CStrings/arrays the C
|
|
# struct points into); the native ctor deep-copies before returning.
|
|
L.add(" let c_" & camelToSnakeCase(ep.name) & " = " & camelToSnakeCase(ep.name) & ".to_c();")
|
|
args.add("c_" & camelToSnakeCase(ep.name) & ".c")
|
|
L.add(" let (tx, rx) = sync_channel::<Result<(), String>>(1);")
|
|
L.add(" let raw = Box::into_raw(Box::new(tx)) as *mut c_void;")
|
|
let cargs = (args & @["ack_cb", "raw"]).join(", ")
|
|
L.add(" let ctx = unsafe { ffi::" & p.procName & "(" & cargs & ") };")
|
|
L.add(" let res = rx.recv().map_err(|_| String::from(\"callback channel closed\"))?;")
|
|
L.add(" res?;")
|
|
L.add(" if ctx.is_null() { return Err(String::from(\"" & p.procName & " returned null\")); }")
|
|
if hasEvents:
|
|
L.add(" Ok(" & nodeT & " { ctx, listeners: std::sync::Mutex::new(std::collections::HashMap::new()) })")
|
|
else:
|
|
L.add(" Ok(" & nodeT & " { ctx })")
|
|
L.add(" }")
|
|
L.add("")
|
|
|
|
# methods
|
|
for p in procs:
|
|
if p.kind != FFIKind.FFI: continue
|
|
if not procSimple(p, types):
|
|
L.add(" // SKIPPED " & p.procName & ": seq/Option params not yet supported by native Rust codegen")
|
|
continue
|
|
let structRet = isStructT(p.returnTypeName, types)
|
|
let retT = if structRet: capitalizeFirstLetter(p.returnTypeName) else: "String"
|
|
let cbName = if structRet: "cb_" & p.procName else: "str_cb"
|
|
var params: seq[string] = @[]
|
|
var conv: seq[string] = @[]
|
|
var callArgs: seq[string] = @["self.ctx", cbName, "raw"]
|
|
for ep in p.extraParams:
|
|
params.add(camelToSnakeCase(ep.name) & ": " & capitalizeFirstLetter(ep.typeName))
|
|
conv.add(" let c_" & camelToSnakeCase(ep.name) & " = " & camelToSnakeCase(ep.name) & ".to_c();")
|
|
callArgs.add("c_" & camelToSnakeCase(ep.name) & ".c")
|
|
L.add(" pub fn " & rustMethod(p.procName, libName) & "(&self" & (if params.len > 0: ", " & params.join(", ") else: "") & ") -> Result<" & retT & ", String> {")
|
|
for c in conv: L.add(c)
|
|
L.add(" let (tx, rx) = sync_channel::<Result<" & retT & ", String>>(1);")
|
|
L.add(" let raw = Box::into_raw(Box::new(tx)) as *mut c_void;")
|
|
L.add(" let rc = unsafe { ffi::" & p.procName & "(" & callArgs.join(", ") & ") };")
|
|
L.add(" if rc != RET_OK {")
|
|
L.add(" drop(unsafe { Box::from_raw(raw as *mut SyncSender<Result<" & retT & ", String>>) });")
|
|
L.add(" return Err(String::from(\"" & p.procName & " dispatch failed\"));")
|
|
L.add(" }")
|
|
L.add(" rx.recv().map_err(|_| String::from(\"callback channel closed\"))?")
|
|
L.add(" }")
|
|
L.add("")
|
|
|
|
# event listeners: one typed registrar per event + a remove by handle.
|
|
for e in events:
|
|
let pt = capitalizeFirstLetter(e.payloadTypeName)
|
|
let hs = snakeToPascalCase(e.wireName) & "Handler"
|
|
L.add(" pub fn add_" & e.wireName & "_listener<F>(&self, handler: F) -> ListenerHandle")
|
|
L.add(" where F: Fn(&" & pt & ") + Send + Sync + 'static {")
|
|
L.add(" let owned: Box<" & hs & "> = Box::new(" & hs & " { f: Box::new(handler) });")
|
|
L.add(" let raw = &*owned as *const " & hs & " as *mut c_void;")
|
|
L.add(" let id = unsafe { ffi::" & libName & "_add_event_listener(self.ctx, b\"" & e.wireName & "\\0\".as_ptr() as *const c_char, " & e.wireName & "_trampoline, raw) };")
|
|
L.add(" if id != 0 { self.listeners.lock().unwrap().insert(id, owned as Box<dyn std::any::Any + Send>); }")
|
|
L.add(" ListenerHandle { id }")
|
|
L.add(" }")
|
|
L.add("")
|
|
if hasEvents:
|
|
L.add(" pub fn remove_event_listener(&self, handle: ListenerHandle) -> bool {")
|
|
L.add(" if handle.id == 0 { return false; }")
|
|
L.add(" let rc = unsafe { ffi::" & libName & "_remove_event_listener(self.ctx, handle.id) };")
|
|
L.add(" self.listeners.lock().unwrap().remove(&handle.id);")
|
|
L.add(" rc == RET_OK")
|
|
L.add(" }")
|
|
L.add("")
|
|
|
|
# dtor
|
|
for p in procs:
|
|
if p.kind == FFIKind.DTOR:
|
|
L.add("}")
|
|
L.add("")
|
|
L.add("impl Drop for " & nodeT & " {")
|
|
L.add(" fn drop(&mut self) { unsafe { ffi::" & p.procName & "(self.ctx); } }")
|
|
L.add("}")
|
|
return L.join("\n")
|
|
|
|
proc generateRustNativeCrate*(
|
|
procs: seq[FFIProcMeta],
|
|
types: seq[FFITypeMeta],
|
|
libName: string,
|
|
outputDir: string,
|
|
nimSrcRelPath: string,
|
|
events: seq[FFIEventMeta] = @[],
|
|
) =
|
|
createDir(outputDir / "src")
|
|
writeFile(outputDir / "Cargo.toml",
|
|
"[package]\nname = \"" & libName & "_native\"\nversion = \"0.1.0\"\nedition = \"2021\"\n")
|
|
writeFile(outputDir / "src" / "lib.rs",
|
|
"mod ffi;\nmod types;\nmod api;\npub use types::*;\npub use api::*;\n")
|
|
writeFile(outputDir / "src" / "ffi.rs", emitFfiRs(procs, types, libName, events))
|
|
writeFile(outputDir / "src" / "types.rs", emitTypesRs(types))
|
|
writeFile(outputDir / "src" / "api.rs", emitApiRs(procs, types, libName, events))
|