@crossbind/core-embind-rust 2.0.0-beta.53 → 2.0.0-beta.58
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/adapters/jsi.cpp +125 -14
- package/adapters/web.cpp +72 -5
- package/crate/src/lib.rs +341 -3
- package/include/crossbind_embind.h +5 -0
- package/package.json +1 -1
- package/crate/Cargo.lock +0 -7
package/README.md
CHANGED
|
@@ -190,7 +190,7 @@ Generation rules (`crossbind/src/utils/rustBridgeGen.js`, run by `buildCargo` be
|
|
|
190
190
|
| `impl Display for C` | `toString()` on the class (so `` `${obj}` `` formats) |
|
|
191
191
|
| `-> Result<T, E>` (ctor, factory, method or free fn; `E: Display`) | Err becomes a **thrown JS exception** |
|
|
192
192
|
| `-> Option<Self>` (factory) | None becomes **JS null** (typed `X \| null`) |
|
|
193
|
-
| `-> Option<i32 / f64 / bool / String>` (method or free fn) | Some -> value, None -> **
|
|
193
|
+
| `-> Option<i32 / f64 / bool / String>` (method or free fn) | Some -> value, None -> **null** (typed `T \| null`) |
|
|
194
194
|
| `Option<i32 / f64 / bool / String>` parameter | JS undefined/null -> None (typed `T \| null \| undefined`) |
|
|
195
195
|
| `other: &SomeClass` parameter | pass another bound instance (borrowed for the call) |
|
|
196
196
|
| `export.bindings.vectors: [{ of: 'i32', name: '..' }]` in crossbind.config.mjs | `register_vector` |
|
|
@@ -219,7 +219,7 @@ scattered `Box::leak`.
|
|
|
219
219
|
|
|
220
220
|
| Path | State |
|
|
221
221
|
|---|---|
|
|
222
|
-
| web: Rust → flat ABI → embind-js (emsdk 5.0.3 & 6.0.
|
|
222
|
+
| web: Rust → flat ABI → embind-js (emsdk 5.0.3, 6.0.2 & 6.0.9) | ✅ tested, runs (full demo incl. f64 + string) |
|
|
223
223
|
| mobile: adapter shape vs real embind-jsi signatures | ✅ shape-validated (native mock) |
|
|
224
224
|
| mobile: real Hermes / device smoke (iOS simulator, RN app) | ✅ GREEN 32/32 — ctor, N-arity, bool, enum, string both ways, value object both ways, vector, smart_ptr factory, f64 both ways, delete, app-local .rs, idioms, BigInt, Display, free fns, Option both ways, class params, semver/regex imports |
|
|
225
225
|
| mobile: real Hermes / device smoke (Android emulator, same RN app) | ✅ GREEN 32/32 — same demo, same adapter; cargo dep joins the cmake depends graph, keep-symbol linked; a NEW bare-crate import now links in the FIRST build (rust set busts the CMake configure) |
|
|
@@ -229,7 +229,7 @@ scattered `Box::leak`.
|
|
|
229
229
|
| Rust idioms: `&str`/`&String` params, `Result` → JS exception, `Option<Self>` → null | ✅ GREEN — e2e (web + jsi-mock legs), worker-mode playwright on 3 browsers, iOS sim + Android emulator (throw message, null, borrowed str all asserted) |
|
|
230
230
|
| i64/u64 → BigInt, `impl Display` → toString(), top-level free functions | ✅ GREEN — same full bar (e2e both legs, 3-browser worker playwright, both devices); u64::MAX round-trips exactly, free `Result` fns throw |
|
|
231
231
|
| DIRECT crate import (`import { Uuid } from 'cargo:uuid'` — untouched crates.io source, multi-file parse, feature-gated modules) | ✅ GREEN — worker-mode playwright on 3 browsers + iOS sim + Android emulator 26/26 (newV4 format, parse roundtrip, Display toString, Err throws) |
|
|
232
|
-
| `Option<i32/f64/bool/String>` returns → value-or-
|
|
232
|
+
| `Option<i32/f64/bool/String>` returns → value-or-null | ✅ GREEN — e2e both legs (mock reads the nullable cell natively), 3-browser worker playwright, both devices |
|
|
233
233
|
| Option params + `&SomeClass` params + re-export following (`semver` matches, `regex` throwing ctor as DIRECT imports) | ✅ GREEN — e2e both legs, 3-browser worker playwright, Android 32/32 + iOS 32/32 — via the explicit cargo: scheme, so npm names never collide |
|
|
234
234
|
| Rust crate → real mobile archives (android/ios arm64) | ✅ compiles; iOS and Android linked+run |
|
|
235
235
|
|
package/adapters/jsi.cpp
CHANGED
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
// Compiled ONLY inside the RN native module (jsi + embind-jsi headers exist there via build_ios.js).
|
|
23
23
|
|
|
24
24
|
#include <jsi/jsi.h>
|
|
25
|
+
#include <cmath>
|
|
25
26
|
#include <cstdint>
|
|
26
27
|
#include <cstdlib>
|
|
27
28
|
#include <cstring>
|
|
@@ -55,6 +56,12 @@ const void* crossbind_tid_void() { return &typeid(void); }
|
|
|
55
56
|
const void* crossbind_tid_std_string() { return &typeid(std::string); }
|
|
56
57
|
const void* crossbind_tid_int64() { return &typeid(int64_t); }
|
|
57
58
|
const void* crossbind_tid_uint64() { return &typeid(uint64_t); }
|
|
59
|
+
const void* crossbind_tid_int8() { return &typeid(int8_t); }
|
|
60
|
+
const void* crossbind_tid_int16() { return &typeid(int16_t); }
|
|
61
|
+
const void* crossbind_tid_uint8() { return &typeid(uint8_t); }
|
|
62
|
+
const void* crossbind_tid_uint16() { return &typeid(uint16_t); }
|
|
63
|
+
const void* crossbind_tid_uint32() { return &typeid(uint32_t); }
|
|
64
|
+
const void* crossbind_tid_float() { return &typeid(float); }
|
|
58
65
|
// Adapter-local identities, NOT &typeid(std::optional<T>): linked C++ bindings may register
|
|
59
66
|
// the real optional typeids themselves, and these types never interop with C++ signatures.
|
|
60
67
|
const void* crossbind_tid_optional_int() { static const char t = 0; return &t; }
|
|
@@ -193,6 +200,63 @@ uint8_t* crossbind_v_cb_err_take() {
|
|
|
193
200
|
static thread_local std::string g_pendingError;
|
|
194
201
|
static thread_local bool g_hasPendingError = false;
|
|
195
202
|
|
|
203
|
+
static thread_local std::string g_pendingErrorCode;
|
|
204
|
+
|
|
205
|
+
// Typed arrays on the native runtime: the adapter builds the JS view and hands back its handle,
|
|
206
|
+
// one copy in each direction.
|
|
207
|
+
// jsi::Object carries no index setter, so the elements go into a jsi::Array first and the
|
|
208
|
+
// TypedArray constructor copies them (`new Uint8Array([...])`).
|
|
209
|
+
static jsi::Object typedArrayFrom(jsi::Runtime& rt, const char* ctor, jsi::Array values) {
|
|
210
|
+
return rt.global().getPropertyAsFunction(rt, ctor)
|
|
211
|
+
.callAsConstructor(rt, values).asObject(rt);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
extern "C" size_t crossbind_v_bytes_to_js(const uint8_t* data, unsigned len) {
|
|
215
|
+
auto& rt = *jsRuntime;
|
|
216
|
+
auto values = jsi::Array(rt, (size_t)len);
|
|
217
|
+
for (unsigned i = 0; i < len; ++i) values.setValueAtIndex(rt, i, jsi::Value((double)data[i]));
|
|
218
|
+
auto array = typedArrayFrom(rt, "Uint8Array", std::move(values));
|
|
219
|
+
return (size_t)crossbindHandleOf(crossbindHelper("__crossbind_v_from_value").call(rt, array));
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
extern "C" size_t crossbind_v_f64s_to_js(const double* data, unsigned len) {
|
|
223
|
+
auto& rt = *jsRuntime;
|
|
224
|
+
auto values = jsi::Array(rt, (size_t)len);
|
|
225
|
+
for (unsigned i = 0; i < len; ++i) values.setValueAtIndex(rt, i, jsi::Value(data[i]));
|
|
226
|
+
auto array = typedArrayFrom(rt, "Float64Array", std::move(values));
|
|
227
|
+
return (size_t)crossbindHandleOf(crossbindHelper("__crossbind_v_from_value").call(rt, array));
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
extern "C" unsigned crossbind_v_typed_len(size_t h) {
|
|
231
|
+
auto& rt = *jsRuntime;
|
|
232
|
+
auto value = crossbindHelper("__crossbind_v_get").call(rt, crossbindVHandle(h), jsi::String::createFromAscii(rt, "length"));
|
|
233
|
+
auto number = crossbindHelper("__crossbind_v_as_num").call(rt, value);
|
|
234
|
+
return number.isNumber() ? (unsigned)number.getNumber() : 0;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
extern "C" void crossbind_v_bytes_from_js(size_t h, uint8_t* out, unsigned len) {
|
|
238
|
+
auto& rt = *jsRuntime;
|
|
239
|
+
auto array = crossbindHelper("__crossbind_v_to_array").call(rt, crossbindVHandle(h)).asObject(rt).asArray(rt);
|
|
240
|
+
for (unsigned i = 0; i < len; ++i) out[i] = (uint8_t)array.getValueAtIndex(rt, i).getNumber();
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
extern "C" void crossbind_v_f64s_from_js(size_t h, double* out, unsigned len) {
|
|
244
|
+
auto& rt = *jsRuntime;
|
|
245
|
+
auto array = crossbindHelper("__crossbind_v_to_array").call(rt, crossbindVHandle(h)).asObject(rt).asArray(rt);
|
|
246
|
+
for (unsigned i = 0; i < len; ++i) out[i] = array.getValueAtIndex(rt, i).getNumber();
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
extern "C" void crossbind_embind_raise_error(uint8_t* w);
|
|
250
|
+
|
|
251
|
+
extern "C" void crossbind_embind_raise_error_coded(uint8_t* w, uint8_t* c) {
|
|
252
|
+
crossbind_embind_raise_error(w);
|
|
253
|
+
if (!c) { g_pendingErrorCode.clear(); return; }
|
|
254
|
+
uint32_t len;
|
|
255
|
+
std::memcpy(&len, c, 4);
|
|
256
|
+
g_pendingErrorCode.assign((const char*)(c + 4), (size_t)len);
|
|
257
|
+
std::free(c);
|
|
258
|
+
}
|
|
259
|
+
|
|
196
260
|
extern "C" void crossbind_embind_raise_error(uint8_t* w) {
|
|
197
261
|
uint32_t len;
|
|
198
262
|
std::memcpy(&len, w, 4);
|
|
@@ -238,7 +302,16 @@ static uint64_t readArg(jsi::Runtime& rt, const jsi::Value& v, char c) {
|
|
|
238
302
|
}
|
|
239
303
|
// i64/u64 ('j'/'u') arrive as JS BigInt (embind bigint types), Number accepted as a courtesy;
|
|
240
304
|
// both directions are raw 64-bit slots, so the unsigned bit copy is sign-correct either way.
|
|
241
|
-
if (c == 'j' || c == 'u')
|
|
305
|
+
if (c == 'j' || c == 'u') {
|
|
306
|
+
if (v.isBigInt()) return v.getBigInt(rt).getUint64(rt);
|
|
307
|
+
// A Number is accepted only while it is an exact integer JS can represent; anything
|
|
308
|
+
// else would be truncated silently, and a 64-bit slot deserves the exact value.
|
|
309
|
+
double d = v.isNumber() ? v.getNumber() : 0;
|
|
310
|
+
if (!v.isNumber() || d != std::trunc(d) || std::fabs(d) > 9007199254740991.0) {
|
|
311
|
+
throw jsi::JSError(rt, "a 64-bit integer parameter takes a BigInt or a safe integer Number");
|
|
312
|
+
}
|
|
313
|
+
return (uint64_t)(int64_t)d;
|
|
314
|
+
}
|
|
242
315
|
// Optional args ('I'/'D'/'B'/'S'): undefined/null -> 0 (None); otherwise a heap cell this
|
|
243
316
|
// adapter owns and frees after dispatch ('S' carries a plain string wire).
|
|
244
317
|
if (c == 'I' || c == 'D' || c == 'B' || c == 'S') {
|
|
@@ -250,24 +323,31 @@ static uint64_t readArg(jsi::Runtime& rt, const jsi::Value& v, char c) {
|
|
|
250
323
|
}
|
|
251
324
|
// Pointers (this/handle/value-object wire) arrive as BigInt, int/bool/enum as Number; accept
|
|
252
325
|
// either kind defensively so a host that boxed a pointer as a Number still dispatches correctly.
|
|
253
|
-
if (c == 'p') return v.isBigInt() ? v.getBigInt(rt).getUint64(rt) : (uint64_t)(int64_t)v.getNumber();
|
|
254
|
-
|
|
326
|
+
if (c == 'p') return v.isBigInt() ? v.getBigInt(rt).getUint64(rt) : v.isNumber() ? (uint64_t)(int64_t)v.getNumber() : 0;
|
|
327
|
+
// 'i': ints and enums arrive as numbers, bools as booleans (the int wire carries 0/1). Reading
|
|
328
|
+
// a BigInt out of anything else is undefined behaviour (Hermes dies in cloneBigInt), so every
|
|
329
|
+
// other value is 0.
|
|
330
|
+
if (v.isBool()) return v.getBool() ? 1 : 0;
|
|
331
|
+
if (v.isNumber()) return (uint64_t)(int64_t)v.getNumber();
|
|
332
|
+
return v.isBigInt() ? v.getBigInt(rt).getUint64(rt) : 0;
|
|
255
333
|
}
|
|
256
334
|
|
|
257
335
|
// Bounded-dispatch: every wire kind is integer-class in the invoker's C signature (pointers and
|
|
258
336
|
// ints natively; doubles as bit patterns in u64 - see WireType for f64), so uint64-slot casts are
|
|
259
337
|
// ABI-correct on every native ABI. No FP registers, no libffi; the only bound is the arity cap.
|
|
260
338
|
static jsi::Value callInvoker(jsi::Runtime& rt, const std::string& sig, void* invoker,
|
|
261
|
-
const jsi::Value* a, uint64_t ctx, bool prepend) {
|
|
339
|
+
const jsi::Value* a, size_t count, uint64_t ctx, bool prepend) {
|
|
262
340
|
const char ret = sig[0];
|
|
263
341
|
const int total = (int)sig.size() - 1; // C args the raw invoker takes (sig[1..])
|
|
264
342
|
|
|
265
343
|
// s[0..total-1] are the raw invoker's C args. When prepend, sig[1] is the baked target pointer
|
|
266
344
|
// (embind sliced it off), so the jsi args a[0..] fill sig[2..]; otherwise a[0..] fill sig[1..].
|
|
267
|
-
|
|
345
|
+
// Widest call: a baked target + `this` + six declared arguments.
|
|
346
|
+
uint64_t s[8] = {0, 0, 0, 0, 0, 0, 0, 0};
|
|
268
347
|
int base = 0;
|
|
269
348
|
if (prepend) { s[0] = ctx; base = 1; }
|
|
270
|
-
|
|
349
|
+
// A call with fewer JS args than the signature must not read past the argument array.
|
|
350
|
+
for (int j = base; j < total && j < 8; ++j) s[j] = (size_t)(j - base) < count ? readArg(rt, a[j - base], sig[1 + j]) : 0;
|
|
271
351
|
|
|
272
352
|
uint64_t r = 0;
|
|
273
353
|
if (ret == 'v') {
|
|
@@ -278,7 +358,9 @@ static jsi::Value callInvoker(jsi::Runtime& rt, const std::string& sig, void* in
|
|
|
278
358
|
case 3: ((void(*)(uint64_t, uint64_t, uint64_t))invoker)(s[0], s[1], s[2]); break;
|
|
279
359
|
case 4: ((void(*)(uint64_t, uint64_t, uint64_t, uint64_t))invoker)(s[0], s[1], s[2], s[3]); break;
|
|
280
360
|
case 5: ((void(*)(uint64_t, uint64_t, uint64_t, uint64_t, uint64_t))invoker)(s[0], s[1], s[2], s[3], s[4]); break;
|
|
281
|
-
|
|
361
|
+
case 6: ((void(*)(uint64_t, uint64_t, uint64_t, uint64_t, uint64_t, uint64_t))invoker)(s[0], s[1], s[2], s[3], s[4], s[5]); break;
|
|
362
|
+
case 7: ((void(*)(uint64_t, uint64_t, uint64_t, uint64_t, uint64_t, uint64_t, uint64_t))invoker)(s[0], s[1], s[2], s[3], s[4], s[5], s[6]); break;
|
|
363
|
+
default: ((void(*)(uint64_t, uint64_t, uint64_t, uint64_t, uint64_t, uint64_t, uint64_t, uint64_t))invoker)(s[0], s[1], s[2], s[3], s[4], s[5], s[6], s[7]); break;
|
|
282
364
|
}
|
|
283
365
|
} else {
|
|
284
366
|
switch (total) {
|
|
@@ -288,30 +370,43 @@ static jsi::Value callInvoker(jsi::Runtime& rt, const std::string& sig, void* in
|
|
|
288
370
|
case 3: r = ((uint64_t(*)(uint64_t, uint64_t, uint64_t))invoker)(s[0], s[1], s[2]); break;
|
|
289
371
|
case 4: r = ((uint64_t(*)(uint64_t, uint64_t, uint64_t, uint64_t))invoker)(s[0], s[1], s[2], s[3]); break;
|
|
290
372
|
case 5: r = ((uint64_t(*)(uint64_t, uint64_t, uint64_t, uint64_t, uint64_t))invoker)(s[0], s[1], s[2], s[3], s[4]); break;
|
|
291
|
-
|
|
373
|
+
case 6: r = ((uint64_t(*)(uint64_t, uint64_t, uint64_t, uint64_t, uint64_t, uint64_t))invoker)(s[0], s[1], s[2], s[3], s[4], s[5]); break;
|
|
374
|
+
case 7: r = ((uint64_t(*)(uint64_t, uint64_t, uint64_t, uint64_t, uint64_t, uint64_t, uint64_t))invoker)(s[0], s[1], s[2], s[3], s[4], s[5], s[6]); break;
|
|
375
|
+
default: r = ((uint64_t(*)(uint64_t, uint64_t, uint64_t, uint64_t, uint64_t, uint64_t, uint64_t, uint64_t))invoker)(s[0], s[1], s[2], s[3], s[4], s[5], s[6], s[7]); break;
|
|
292
376
|
}
|
|
293
377
|
}
|
|
294
378
|
|
|
295
379
|
// The consumer owns each incoming std::string arg buffer and optional arg cell; free them
|
|
296
380
|
// now that the crate has copied.
|
|
297
|
-
for (int j = base; j < total && j <
|
|
381
|
+
for (int j = base; j < total && j < 8; ++j) {
|
|
298
382
|
const char cj = sig[1 + j];
|
|
299
383
|
if (cj == 's' || cj == 'S' || cj == 'I' || cj == 'D' || cj == 'B') std::free((void*)(uintptr_t)s[j]);
|
|
300
384
|
}
|
|
301
385
|
|
|
302
386
|
// A shim raised (Result::Err): drop the sentinel return and surface a real JS exception.
|
|
387
|
+
// The error is BUILT IN JS, not thrown from here: Hermes re-wraps a C++ exception escaping a
|
|
388
|
+
// host function ("Exception in HostFunction: ..."), which loses both the plain message and the
|
|
389
|
+
// `code` property. The runtime's dyn-caller rethrows what this parks, so the object JS sees is
|
|
390
|
+
// a real Error with the binding's own message and code.
|
|
303
391
|
if (g_hasPendingError) {
|
|
304
392
|
g_hasPendingError = false;
|
|
305
393
|
if (ret == 's' && r) std::free((void*)(uintptr_t)r);
|
|
306
|
-
|
|
394
|
+
std::string message = std::move(g_pendingError);
|
|
395
|
+
std::string code = std::move(g_pendingErrorCode);
|
|
396
|
+
g_pendingError.clear();
|
|
397
|
+
g_pendingErrorCode.clear();
|
|
398
|
+
crossbindHelper("__crossbind_park_error").call(
|
|
399
|
+
rt, jsi::String::createFromUtf8(rt, message),
|
|
400
|
+
code.empty() ? jsi::Value::undefined() : jsi::Value(jsi::String::createFromUtf8(rt, code)));
|
|
401
|
+
return jsi::Value::undefined();
|
|
307
402
|
}
|
|
308
403
|
|
|
309
404
|
if (ret == 'v') return jsi::Value::undefined();
|
|
310
405
|
if (ret == 's') return wireStringToJsi(rt, r); // the crate returned a [u32 len][bytes] buffer
|
|
311
|
-
// Optional returns ('I'/'D'/'B'/'S'): nullable heap cell - 0 is None (
|
|
406
|
+
// Optional returns ('I'/'D'/'B'/'S'): nullable heap cell - 0 is None (null), otherwise
|
|
312
407
|
// the malloc'd inner value ('S' points at a plain string wire); freed here after reading.
|
|
313
408
|
if (ret == 'I' || ret == 'D' || ret == 'B' || ret == 'S') {
|
|
314
|
-
if (!r) return jsi::Value::
|
|
409
|
+
if (!r) return jsi::Value::null();
|
|
315
410
|
if (ret == 'S') return wireStringToJsi(rt, r);
|
|
316
411
|
uint8_t* cell = (uint8_t*)(uintptr_t)r;
|
|
317
412
|
jsi::Value out = jsi::Value::undefined();
|
|
@@ -338,8 +433,8 @@ static jsi::Function wrapInvoker(const std::string& name, const char* sigC, void
|
|
|
338
433
|
unsigned jsArgc = (unsigned)(sig.size() - 1 - (prepend ? 1 : 0)); // args embind actually passes
|
|
339
434
|
return jsi::Function::createFromHostFunction(
|
|
340
435
|
rt, jsi::PropNameID::forAscii(rt, dbg), jsArgc,
|
|
341
|
-
[sig, invoker, ctx, prepend](jsi::Runtime& rt, const jsi::Value&, const jsi::Value* a, size_t) -> jsi::Value {
|
|
342
|
-
return callInvoker(rt, sig, invoker, a, ctx, prepend);
|
|
436
|
+
[sig, invoker, ctx, prepend](jsi::Runtime& rt, const jsi::Value&, const jsi::Value* a, size_t count) -> jsi::Value {
|
|
437
|
+
return callInvoker(rt, sig, invoker, a, count, ctx, prepend);
|
|
343
438
|
});
|
|
344
439
|
}
|
|
345
440
|
|
|
@@ -413,6 +508,22 @@ void crossbind_embind_register_enum_value(CrossbindTid enum_ty, const char* valu
|
|
|
413
508
|
_embind_register_enum_value(enum_ty, value_name, value);
|
|
414
509
|
}
|
|
415
510
|
|
|
511
|
+
void crossbind_embind_register_class_property(CrossbindTid cls, const char* name,
|
|
512
|
+
CrossbindTid getter_ty, const char* getter_sig, CrossbindFn getter, void* getter_ctx,
|
|
513
|
+
CrossbindTid setter_ty, const char* setter_sig, CrossbindFn setter, void* setter_ctx) {
|
|
514
|
+
// Not sliced: the fork's property accessors call `getter(getterContext, ptr)` and
|
|
515
|
+
// `setter(setterContext, ptr, value)`, so both contexts arrive as ordinary arguments
|
|
516
|
+
// (unlike a method invoker, where embind slices the target off).
|
|
517
|
+
auto get = wrapInvoker(std::string(name) + "_get", getter_sig, getter);
|
|
518
|
+
auto set = wrapInvoker(std::string(name) + "_set", setter_sig, setter);
|
|
519
|
+
_embind_register_class_property(cls, name, getter_ty, getter_sig, get, getter_ctx,
|
|
520
|
+
setter_ty, setter_sig, set, setter_ctx);
|
|
521
|
+
}
|
|
522
|
+
void crossbind_embind_register_constant(const char* name, CrossbindTid ty, double value) {
|
|
523
|
+
// The fork takes the constant as a jsi::Value; every kind we offer (int, float, bool) reads
|
|
524
|
+
// back through its own fromWireType from a plain number.
|
|
525
|
+
_embind_register_constant(name, ty, jsi::Value(value));
|
|
526
|
+
}
|
|
416
527
|
void crossbind_embind_register_value_object(CrossbindTid struct_ty, const char* name,
|
|
417
528
|
const char* ctor_sig, CrossbindFn ctor, const char* dtor_sig, CrossbindFn dtor) {
|
|
418
529
|
auto ct = wrapInvoker(std::string(name) + "_ctor", ctor_sig, ctor);
|
package/adapters/web.cpp
CHANGED
|
@@ -22,6 +22,21 @@ EM_JS(void, crossbind_embind_raise_error, (uint8_t* w), {
|
|
|
22
22
|
throw new Error(msg);
|
|
23
23
|
});
|
|
24
24
|
|
|
25
|
+
// The same escape with a machine-readable code on the Error, the way napi-rs carries its
|
|
26
|
+
// `error.code`: a null pointer means the producer had no code to give.
|
|
27
|
+
EM_JS(void, crossbind_embind_raise_error_coded, (uint8_t* w, uint8_t* c), {
|
|
28
|
+
const len = HEAPU32[w >> 2];
|
|
29
|
+
const msg = UTF8ToString(w + 4, len);
|
|
30
|
+
_free(w);
|
|
31
|
+
const error = new Error(msg);
|
|
32
|
+
if (c) {
|
|
33
|
+
const codeLen = HEAPU32[c >> 2];
|
|
34
|
+
error.code = UTF8ToString(c + 4, codeLen);
|
|
35
|
+
_free(c);
|
|
36
|
+
}
|
|
37
|
+
throw error;
|
|
38
|
+
});
|
|
39
|
+
|
|
25
40
|
// Producer sig chars that emscripten's dynCall alphabet lacks: 's' (a jsi-marshalling hint;
|
|
26
41
|
// the wasm string wire is a real pointer) -> 'p', and 'u' (unsigned 64 - signedness lives in
|
|
27
42
|
// the registered typeid) -> 'j'. Translated copies are one-time registration data, leaked.
|
|
@@ -45,6 +60,12 @@ const void* crossbind_tid_void() { return &typeid(void); }
|
|
|
45
60
|
const void* crossbind_tid_std_string() { return &typeid(std::string); }
|
|
46
61
|
const void* crossbind_tid_int64() { return &typeid(int64_t); }
|
|
47
62
|
const void* crossbind_tid_uint64() { return &typeid(uint64_t); }
|
|
63
|
+
const void* crossbind_tid_int8() { return &typeid(int8_t); }
|
|
64
|
+
const void* crossbind_tid_int16() { return &typeid(int16_t); }
|
|
65
|
+
const void* crossbind_tid_uint8() { return &typeid(uint8_t); }
|
|
66
|
+
const void* crossbind_tid_uint16() { return &typeid(uint16_t); }
|
|
67
|
+
const void* crossbind_tid_uint32() { return &typeid(uint32_t); }
|
|
68
|
+
const void* crossbind_tid_float() { return &typeid(float); }
|
|
48
69
|
// Adapter-local identities, NOT &typeid(std::optional<T>): linked C++ bindings may register
|
|
49
70
|
// the real optional typeids themselves, and these types never interop with C++ signatures.
|
|
50
71
|
const void* crossbind_tid_optional_int() { static const char t = 0; return &t; }
|
|
@@ -54,8 +75,8 @@ const void* crossbind_tid_optional_string() { static const char t = 0; return &t
|
|
|
54
75
|
|
|
55
76
|
}
|
|
56
77
|
|
|
57
|
-
// Optional wire on wasm is an EM_VAL handle (None = the reserved
|
|
58
|
-
// emscripten's own header). Some-values become handles through Emval.toHandle; the registered
|
|
78
|
+
// Optional wire on wasm is an EM_VAL handle (None = the reserved null constant from
|
|
79
|
+
// emscripten's own header: a missing value is null on every runtime, like C++ nullptr). Some-values become handles through Emval.toHandle; the registered
|
|
59
80
|
// optional type's fromWireType does toValue + decref, so the refcount stays balanced.
|
|
60
81
|
EM_JS(void*, crossbind_emval_from_number_js, (double v), { return Emval.toHandle(v); });
|
|
61
82
|
EM_JS(void*, crossbind_emval_from_bool_js, (int v), { return Emval.toHandle(!!v); });
|
|
@@ -73,8 +94,8 @@ void* crossbind_emval_take_value(CrossbindTid inner, const void* argv) {
|
|
|
73
94
|
if (inner == crossbind_tid_bool()) return crossbind_emval_from_bool_js((int)*(const uint8_t*)argv);
|
|
74
95
|
return crossbind_emval_from_string_js(*(uint8_t* const*)argv); // std::string wire pointer
|
|
75
96
|
}
|
|
76
|
-
void*
|
|
77
|
-
return (void*)emscripten::internal::
|
|
97
|
+
void* crossbind_emval_null() {
|
|
98
|
+
return (void*)emscripten::internal::_EMVAL_NULL;
|
|
78
99
|
}
|
|
79
100
|
|
|
80
101
|
// Optional PARAMETERS: embind hands the invoker an EM_VAL handle; these readers consume its
|
|
@@ -124,9 +145,20 @@ void* crossbind_emval_json_to_handle(uint8_t* w) {
|
|
|
124
145
|
return (void*)h;
|
|
125
146
|
}
|
|
126
147
|
|
|
148
|
+
// A Set or Map has no JSON form of its own, so the replacer writes it as the array or object the
|
|
149
|
+
// Rust side expects; everything else stringifies unchanged.
|
|
150
|
+
EM_JS(void*, crossbind_json_replacer, (), {
|
|
151
|
+
return Emval.toHandle(function (key, value) {
|
|
152
|
+
if (value instanceof Set) return Array.from(value);
|
|
153
|
+
if (value instanceof Map) return Object.fromEntries(value);
|
|
154
|
+
return value;
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
|
|
127
158
|
uint8_t* crossbind_emval_handle_to_json(void* h) {
|
|
128
159
|
emscripten::val v = emscripten::val::take_ownership((emscripten::EM_VAL)h);
|
|
129
|
-
emscripten::val
|
|
160
|
+
emscripten::val replacer = emscripten::val::take_ownership((emscripten::EM_VAL)crossbind_json_replacer());
|
|
161
|
+
emscripten::val j = emscripten::val::global("JSON").call<emscripten::val>("stringify", v, replacer);
|
|
130
162
|
// JSON.stringify(undefined / a bare function) yields undefined: cross as null.
|
|
131
163
|
std::string s = j.isUndefined() ? "null" : j.as<std::string>();
|
|
132
164
|
uint8_t* w = (uint8_t*)malloc(4 + s.size());
|
|
@@ -147,6 +179,8 @@ void _embind_register_smart_ptr(CrossbindTid, CrossbindTid, const char*, emscrip
|
|
|
147
179
|
void _embind_register_enum(CrossbindTid, const char*, size_t, bool, int);
|
|
148
180
|
void _embind_register_enum_value(CrossbindTid, const char*, int);
|
|
149
181
|
void _embind_register_value_object(CrossbindTid, const char*, const char*, CrossbindFn, const char*, CrossbindFn);
|
|
182
|
+
void _embind_register_constant(const char*, CrossbindTid, double);
|
|
183
|
+
void _embind_register_class_property(CrossbindTid, const char*, CrossbindTid, const char*, CrossbindFn, void*, CrossbindTid, const char*, CrossbindFn, void*);
|
|
150
184
|
void _embind_register_value_object_field(CrossbindTid, const char*, CrossbindTid, const char*, CrossbindFn, void*, CrossbindTid, const char*, CrossbindFn, void*);
|
|
151
185
|
void _embind_finalize_value_object(CrossbindTid);
|
|
152
186
|
|
|
@@ -180,6 +214,18 @@ void crossbind_embind_register_enum(CrossbindTid a, const char* b, size_t c, boo
|
|
|
180
214
|
void crossbind_embind_register_enum_value(CrossbindTid a, const char* b, int c) {
|
|
181
215
|
_embind_register_enum_value(a, b, c);
|
|
182
216
|
}
|
|
217
|
+
// A constant is registered by value: embind carries the wire through a double, which holds every
|
|
218
|
+
// int, float and bool exactly (and a wasm32 pointer).
|
|
219
|
+
// A public field reads and writes as a JS property instead of a pair of calls.
|
|
220
|
+
void crossbind_embind_register_class_property(CrossbindTid cls, const char* name,
|
|
221
|
+
CrossbindTid getter_ty, const char* getter_sig, CrossbindFn getter, void* getter_ctx,
|
|
222
|
+
CrossbindTid setter_ty, const char* setter_sig, CrossbindFn setter, void* setter_ctx) {
|
|
223
|
+
_embind_register_class_property(cls, name, getter_ty, sigForWasm(getter_sig), getter, getter_ctx,
|
|
224
|
+
setter_ty, sigForWasm(setter_sig), setter, setter_ctx);
|
|
225
|
+
}
|
|
226
|
+
void crossbind_embind_register_constant(const char* name, CrossbindTid ty, double value) {
|
|
227
|
+
_embind_register_constant(name, ty, value);
|
|
228
|
+
}
|
|
183
229
|
void crossbind_embind_register_value_object(CrossbindTid a, const char* b, const char* c, CrossbindFn d, const char* e, CrossbindFn f) {
|
|
184
230
|
_embind_register_value_object(a, b, c, d, e, f);
|
|
185
231
|
}
|
|
@@ -243,6 +289,27 @@ EM_JS(uint8_t*, crossbind_v_as_str, (void* h), {
|
|
|
243
289
|
stringToUTF8(s, w + 4, len + 1);
|
|
244
290
|
return w;
|
|
245
291
|
});
|
|
292
|
+
// Typed arrays: bytes and doubles cross as the JS views themselves, copied once in each
|
|
293
|
+
// direction - a view into wasm memory would dangle the moment the heap grows.
|
|
294
|
+
EM_JS(void*, crossbind_v_bytes_to_js, (uint8_t* data, unsigned len), {
|
|
295
|
+
return Emval.toHandle(HEAPU8.slice(data, data + len));
|
|
296
|
+
});
|
|
297
|
+
EM_JS(void*, crossbind_v_f64s_to_js, (double* data, unsigned len), {
|
|
298
|
+
return Emval.toHandle(HEAPF64.slice(data >> 3, (data >> 3) + len));
|
|
299
|
+
});
|
|
300
|
+
EM_JS(unsigned, crossbind_v_typed_len, (void* h), {
|
|
301
|
+
const value = Emval.toValue(h);
|
|
302
|
+
return value && value.length !== undefined ? value.length : 0;
|
|
303
|
+
});
|
|
304
|
+
EM_JS(void, crossbind_v_bytes_from_js, (void* h, uint8_t* out, unsigned len), {
|
|
305
|
+
const value = Emval.toValue(h);
|
|
306
|
+
HEAPU8.set(value.subarray ? value.subarray(0, len) : Uint8Array.from(value).subarray(0, len), out);
|
|
307
|
+
});
|
|
308
|
+
EM_JS(void, crossbind_v_f64s_from_js, (void* h, double* out, unsigned len), {
|
|
309
|
+
const value = Emval.toValue(h);
|
|
310
|
+
HEAPF64.set(value.subarray ? value.subarray(0, len) : Float64Array.from(value).subarray(0, len), out >> 3);
|
|
311
|
+
});
|
|
312
|
+
|
|
246
313
|
EM_JS(void*, crossbind_v_call, (void* f, unsigned argc, void* argv), {
|
|
247
314
|
try {
|
|
248
315
|
const fn = Emval.toValue(f);
|
package/crate/src/lib.rs
CHANGED
|
@@ -36,6 +36,12 @@ extern "C" {
|
|
|
36
36
|
);
|
|
37
37
|
fn crossbind_embind_register_enum(enum_ty: *const c_void, name: *const c_char, size: usize, is_signed: bool, policy: i32);
|
|
38
38
|
fn crossbind_embind_register_enum_value(enum_ty: *const c_void, name: *const c_char, value: i32);
|
|
39
|
+
fn crossbind_embind_register_constant(name: *const c_char, ty: *const c_void, value: f64);
|
|
40
|
+
fn crossbind_embind_register_class_property(
|
|
41
|
+
cls: *const c_void, name: *const c_char,
|
|
42
|
+
getter_ty: *const c_void, getter_sig: *const c_char, getter: usize, getter_ctx: *mut c_void,
|
|
43
|
+
setter_ty: *const c_void, setter_sig: *const c_char, setter: usize, setter_ctx: *mut c_void,
|
|
44
|
+
);
|
|
39
45
|
fn crossbind_embind_register_value_object(
|
|
40
46
|
struct_ty: *const c_void, name: *const c_char,
|
|
41
47
|
ctor_sig: *const c_char, ctor: usize, dtor_sig: *const c_char, dtor: usize,
|
|
@@ -62,6 +68,12 @@ extern "C" {
|
|
|
62
68
|
fn free(ptr: *mut u8);
|
|
63
69
|
fn crossbind_tid_int64() -> *const c_void;
|
|
64
70
|
fn crossbind_tid_uint64() -> *const c_void;
|
|
71
|
+
fn crossbind_tid_int8() -> *const c_void;
|
|
72
|
+
fn crossbind_tid_int16() -> *const c_void;
|
|
73
|
+
fn crossbind_tid_uint8() -> *const c_void;
|
|
74
|
+
fn crossbind_tid_uint16() -> *const c_void;
|
|
75
|
+
fn crossbind_tid_uint32() -> *const c_void;
|
|
76
|
+
fn crossbind_tid_float() -> *const c_void;
|
|
65
77
|
fn crossbind_embind_register_optional(optional_ty: *const c_void, inner_ty: *const c_void);
|
|
66
78
|
fn crossbind_tid_optional_int() -> *const c_void;
|
|
67
79
|
fn crossbind_tid_optional_double() -> *const c_void;
|
|
@@ -71,6 +83,7 @@ extern "C" {
|
|
|
71
83
|
// Adapter-owned error escape ([u32 len][bytes] wire): web throws a JS Error immediately,
|
|
72
84
|
// native stores the message and throws after the invoker returns.
|
|
73
85
|
fn crossbind_embind_raise_error(wire_msg: *mut u8);
|
|
86
|
+
fn crossbind_embind_raise_error_coded(msg: *mut u8, code: *mut u8);
|
|
74
87
|
}
|
|
75
88
|
|
|
76
89
|
// Optional wire helpers exist only on wasm (native optionals are nullable heap cells the jsi
|
|
@@ -78,7 +91,7 @@ extern "C" {
|
|
|
78
91
|
#[cfg(target_family = "wasm")]
|
|
79
92
|
extern "C" {
|
|
80
93
|
fn crossbind_emval_take_value(inner_ty: *const c_void, argv: *const c_void) -> usize;
|
|
81
|
-
fn
|
|
94
|
+
fn crossbind_emval_null() -> usize;
|
|
82
95
|
fn crossbind_emval_opt_i32(handle: usize, out: *mut i32) -> i32;
|
|
83
96
|
fn crossbind_emval_opt_f64(handle: usize, out: *mut f64) -> i32;
|
|
84
97
|
fn crossbind_emval_opt_bool(handle: usize, out: *mut u8) -> i32;
|
|
@@ -94,6 +107,8 @@ struct Registry {
|
|
|
94
107
|
tids: Vec<Box<u8>>,
|
|
95
108
|
argtypes: Vec<Box<[*const c_void]>>,
|
|
96
109
|
class_tid: HashMap<TypeId, *const c_void>,
|
|
110
|
+
class_ptr_tid: HashMap<TypeId, *const c_void>,
|
|
111
|
+
owned_ptr_tid: HashMap<TypeId, *const c_void>,
|
|
97
112
|
shared_ptr_tid: HashMap<TypeId, *const c_void>,
|
|
98
113
|
}
|
|
99
114
|
// Raw pointers are not Send/Sync; the arena is only ever touched under its Mutex.
|
|
@@ -103,6 +118,8 @@ fn registry() -> &'static Mutex<Registry> {
|
|
|
103
118
|
static REG: OnceLock<Mutex<Registry>> = OnceLock::new();
|
|
104
119
|
REG.get_or_init(|| Mutex::new(Registry {
|
|
105
120
|
cstrings: Vec::new(), tids: Vec::new(), argtypes: Vec::new(), class_tid: HashMap::new(),
|
|
121
|
+
class_ptr_tid: HashMap::new(),
|
|
122
|
+
owned_ptr_tid: HashMap::new(),
|
|
106
123
|
shared_ptr_tid: HashMap::new(),
|
|
107
124
|
}))
|
|
108
125
|
}
|
|
@@ -222,6 +239,98 @@ impl WireType for u64 {
|
|
|
222
239
|
}
|
|
223
240
|
|
|
224
241
|
// bool crosses as an int-sized wire (0/1), the way emscripten's ABI passes it.
|
|
242
|
+
// The narrower integers ride the int slot with their own type ids, so the host converter keeps
|
|
243
|
+
// each one's range check instead of silently wrapping a value the Rust type cannot hold.
|
|
244
|
+
macro_rules! int_wire {
|
|
245
|
+
($( $ty:ty, $tid:ident ; )*) => {
|
|
246
|
+
$(
|
|
247
|
+
impl WireType for $ty {
|
|
248
|
+
type Wire = i32;
|
|
249
|
+
const SIG: char = 'i';
|
|
250
|
+
fn tid() -> *const c_void { unsafe { $tid() } }
|
|
251
|
+
fn from_wire(w: i32) -> $ty { w as $ty }
|
|
252
|
+
fn to_wire(self) -> i32 { self as i32 }
|
|
253
|
+
}
|
|
254
|
+
)*
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
int_wire! {
|
|
259
|
+
i8, crossbind_tid_int8;
|
|
260
|
+
i16, crossbind_tid_int16;
|
|
261
|
+
u8, crossbind_tid_uint8;
|
|
262
|
+
u16, crossbind_tid_uint16;
|
|
263
|
+
u32, crossbind_tid_uint32;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// f32 crosses through the double slot: JS has one number type, so the value widens on the way
|
|
267
|
+
// out and narrows on the way in - the Rust signature is what asks for single precision.
|
|
268
|
+
#[cfg(target_family = "wasm")]
|
|
269
|
+
impl WireType for f32 {
|
|
270
|
+
type Wire = f64;
|
|
271
|
+
const SIG: char = 'd';
|
|
272
|
+
fn tid() -> *const c_void { unsafe { crossbind_tid_float() } }
|
|
273
|
+
fn from_wire(w: f64) -> f32 { w as f32 }
|
|
274
|
+
fn to_wire(self) -> f64 { self as f64 }
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
#[cfg(not(target_family = "wasm"))]
|
|
278
|
+
impl WireType for f32 {
|
|
279
|
+
type Wire = u64;
|
|
280
|
+
const SIG: char = 'd';
|
|
281
|
+
fn tid() -> *const c_void { unsafe { crossbind_tid_float() } }
|
|
282
|
+
fn from_wire(w: u64) -> f32 { f64::from_bits(w) as f32 }
|
|
283
|
+
fn to_wire(self) -> u64 { (self as f64).to_bits() }
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// usize/isize are 32-bit on wasm and 64-bit everywhere else, so each platform rides the slot
|
|
287
|
+
// that carries its full range: a number on wasm, a BigInt on native.
|
|
288
|
+
#[cfg(target_pointer_width = "32")]
|
|
289
|
+
impl WireType for usize {
|
|
290
|
+
type Wire = i32;
|
|
291
|
+
const SIG: char = 'i';
|
|
292
|
+
fn tid() -> *const c_void { unsafe { crossbind_tid_uint32() } }
|
|
293
|
+
fn from_wire(w: i32) -> usize { w as usize }
|
|
294
|
+
fn to_wire(self) -> i32 { self as i32 }
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
#[cfg(target_pointer_width = "32")]
|
|
298
|
+
impl WireType for isize {
|
|
299
|
+
type Wire = i32;
|
|
300
|
+
const SIG: char = 'i';
|
|
301
|
+
fn tid() -> *const c_void { unsafe { crossbind_tid_int() } }
|
|
302
|
+
fn from_wire(w: i32) -> isize { w as isize }
|
|
303
|
+
fn to_wire(self) -> i32 { self as i32 }
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
#[cfg(not(target_pointer_width = "32"))]
|
|
307
|
+
impl WireType for usize {
|
|
308
|
+
type Wire = u64;
|
|
309
|
+
const SIG: char = 'u';
|
|
310
|
+
fn tid() -> *const c_void { unsafe { crossbind_tid_uint64() } }
|
|
311
|
+
fn from_wire(w: u64) -> usize { w as usize }
|
|
312
|
+
fn to_wire(self) -> u64 { self as u64 }
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
#[cfg(not(target_pointer_width = "32"))]
|
|
316
|
+
impl WireType for isize {
|
|
317
|
+
type Wire = i64;
|
|
318
|
+
const SIG: char = 'j';
|
|
319
|
+
fn tid() -> *const c_void { unsafe { crossbind_tid_int64() } }
|
|
320
|
+
fn from_wire(w: i64) -> isize { w as isize }
|
|
321
|
+
fn to_wire(self) -> i64 { self as i64 }
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// char crosses as a one-character string, the way JS spells a character; an incoming string
|
|
325
|
+
// yields its first character (an empty one is U+FFFD, the replacement character).
|
|
326
|
+
impl WireType for char {
|
|
327
|
+
type Wire = <String as WireType>::Wire;
|
|
328
|
+
const SIG: char = 's';
|
|
329
|
+
fn tid() -> *const c_void { <String as WireType>::tid() }
|
|
330
|
+
fn from_wire(w: Self::Wire) -> char { String::from_wire(w).chars().next().unwrap_or('\u{fffd}') }
|
|
331
|
+
fn to_wire(self) -> Self::Wire { self.to_string().to_wire() }
|
|
332
|
+
}
|
|
333
|
+
|
|
225
334
|
impl WireType for bool {
|
|
226
335
|
type Wire = i32;
|
|
227
336
|
const SIG: char = 'i';
|
|
@@ -265,7 +374,7 @@ impl WireType for String {
|
|
|
265
374
|
}
|
|
266
375
|
}
|
|
267
376
|
|
|
268
|
-
/// Option<T> both ways: on wasm the wire is an EM_VAL handle (None =
|
|
377
|
+
/// Option<T> both ways: on wasm the wire is an EM_VAL handle (None = null; incoming
|
|
269
378
|
/// undefined/null both read as None); on native it is a nullable heap cell (0 = None) the jsi
|
|
270
379
|
/// adapter converts, tagged 'I'/'D'/'B'/'S'. Incoming cells stay adapter-owned (read, no free).
|
|
271
380
|
macro_rules! option_wire {
|
|
@@ -279,7 +388,7 @@ macro_rules! option_wire {
|
|
|
279
388
|
fn from_wire(w: usize) -> Self { <$inner as EmvalOptRead>::emval_opt_read(w) }
|
|
280
389
|
fn to_wire(self) -> usize {
|
|
281
390
|
match self {
|
|
282
|
-
None => unsafe {
|
|
391
|
+
None => unsafe { crossbind_emval_null() },
|
|
283
392
|
Some(v) => v.emval_take(),
|
|
284
393
|
}
|
|
285
394
|
}
|
|
@@ -418,6 +527,29 @@ pub fn register_optional_f64() { unsafe { crossbind_embind_register_optional(cro
|
|
|
418
527
|
pub fn register_optional_bool() { unsafe { crossbind_embind_register_optional(crossbind_tid_optional_bool(), crossbind_tid_bool()); } }
|
|
419
528
|
pub fn register_optional_string() { unsafe { crossbind_embind_register_optional(crossbind_tid_optional_string(), crossbind_tid_std_string()); } }
|
|
420
529
|
|
|
530
|
+
/// A module-level constant: the value crosses once at registration, so JS reads a plain value
|
|
531
|
+
/// instead of calling a getter. Only the wire kinds a double carries exactly are offered.
|
|
532
|
+
pub trait ConstantValue: WireType {
|
|
533
|
+
fn as_constant(self) -> f64;
|
|
534
|
+
}
|
|
535
|
+
impl ConstantValue for i32 { fn as_constant(self) -> f64 { self as f64 } }
|
|
536
|
+
impl ConstantValue for f64 { fn as_constant(self) -> f64 { self } }
|
|
537
|
+
impl ConstantValue for bool { fn as_constant(self) -> f64 { if self { 1.0 } else { 0.0 } } }
|
|
538
|
+
|
|
539
|
+
/// A string constant: the double the ABI carries is the emval handle of a JS string, which the
|
|
540
|
+
/// emval type reads straight back. The handle is released on purpose - the constant owns it for
|
|
541
|
+
/// the life of the module.
|
|
542
|
+
pub fn constant_str(name: &str, value: &str) {
|
|
543
|
+
let handle = JsValue::from_str(value).release();
|
|
544
|
+
let name_ptr = { registry().lock().unwrap().cstr(name) };
|
|
545
|
+
unsafe { crossbind_embind_register_constant(name_ptr, crossbind_tid_emval(), handle as f64) };
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
pub fn constant<T: ConstantValue>(name: &str, value: T) {
|
|
549
|
+
let name_ptr = { registry().lock().unwrap().cstr(name) };
|
|
550
|
+
unsafe { crossbind_embind_register_constant(name_ptr, T::tid(), value.as_constant()) };
|
|
551
|
+
}
|
|
552
|
+
|
|
421
553
|
/// Post-raise sentinel returns: only native ever observes them (the adapter throws right after
|
|
422
554
|
/// the invoker returns); on web `crossbind_embind_raise_error` throws into JS and never comes back.
|
|
423
555
|
pub trait ErrSentinel {
|
|
@@ -431,13 +563,119 @@ impl ErrSentinel for bool { fn err_sentinel() -> Self { false } }
|
|
|
431
563
|
impl ErrSentinel for () { fn err_sentinel() -> Self {} }
|
|
432
564
|
impl ErrSentinel for String { fn err_sentinel() -> Self { String::new() } }
|
|
433
565
|
impl<T> ErrSentinel for *mut T { fn err_sentinel() -> Self { std::ptr::null_mut() } }
|
|
566
|
+
// A fallible optional (`Result<Option<T>, E>`) raises and then returns None.
|
|
567
|
+
impl<T> ErrSentinel for Option<T> { fn err_sentinel() -> Self { None } }
|
|
568
|
+
// The narrower integers and the pointer-wide ones share the zero sentinel.
|
|
569
|
+
impl ErrSentinel for i8 { fn err_sentinel() -> Self { 0 } }
|
|
570
|
+
impl ErrSentinel for i16 { fn err_sentinel() -> Self { 0 } }
|
|
571
|
+
impl ErrSentinel for u8 { fn err_sentinel() -> Self { 0 } }
|
|
572
|
+
impl ErrSentinel for u16 { fn err_sentinel() -> Self { 0 } }
|
|
573
|
+
impl ErrSentinel for u32 { fn err_sentinel() -> Self { 0 } }
|
|
574
|
+
impl ErrSentinel for f32 { fn err_sentinel() -> Self { 0.0 } }
|
|
575
|
+
impl ErrSentinel for usize { fn err_sentinel() -> Self { 0 } }
|
|
576
|
+
impl ErrSentinel for isize { fn err_sentinel() -> Self { 0 } }
|
|
577
|
+
impl ErrSentinel for char { fn err_sentinel() -> Self { '\u{fffd}' } }
|
|
434
578
|
|
|
435
579
|
/// Routes a shim's `Err` to JS as an exception, then returns a dead sentinel value.
|
|
580
|
+
/// Runs a binding body so that a Rust panic reaches JS as an exception instead of tearing the
|
|
581
|
+
/// process down. The panic still means a bug: state is whatever the panic left behind, the same
|
|
582
|
+
/// caveat napi-rs documents for its own catch_unwind.
|
|
583
|
+
///
|
|
584
|
+
/// Two runtimes, two mechanisms. Native targets unwind, so the panic is caught here. The wasm
|
|
585
|
+
/// target's std aborts instead of unwinding, so a panic hook raises the error from inside the
|
|
586
|
+
/// panic itself - the JS throw leaves the call before the abort can run.
|
|
587
|
+
pub fn guard<R: ErrSentinel>(body: impl FnOnce() -> R) -> R {
|
|
588
|
+
install_panic_hook();
|
|
589
|
+
#[cfg(target_family = "wasm")]
|
|
590
|
+
{
|
|
591
|
+
body()
|
|
592
|
+
}
|
|
593
|
+
#[cfg(not(target_family = "wasm"))]
|
|
594
|
+
{
|
|
595
|
+
match std::panic::catch_unwind(std::panic::AssertUnwindSafe(body)) {
|
|
596
|
+
Ok(value) => value,
|
|
597
|
+
Err(payload) => raise_err(panic_text(&payload)),
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
fn install_panic_hook() {
|
|
603
|
+
static ONCE: std::sync::Once = std::sync::Once::new();
|
|
604
|
+
ONCE.call_once(|| {
|
|
605
|
+
let previous = std::panic::take_hook();
|
|
606
|
+
std::panic::set_hook(Box::new(move |info| {
|
|
607
|
+
let text = match info.payload().downcast_ref::<&str>() {
|
|
608
|
+
Some(text) => (*text).to_string(),
|
|
609
|
+
None => match info.payload().downcast_ref::<String>() {
|
|
610
|
+
Some(text) => text.clone(),
|
|
611
|
+
None => String::from("panicked"),
|
|
612
|
+
},
|
|
613
|
+
};
|
|
614
|
+
let text = match info.location() {
|
|
615
|
+
Some(at) => format!("{text} (panicked at {}:{})", at.file(), at.line()),
|
|
616
|
+
None => text,
|
|
617
|
+
};
|
|
618
|
+
// wasm: this throws into JS and the call never comes back, so the abort below the
|
|
619
|
+
// hook is skipped. Native: the adapter parks the message and throws once the
|
|
620
|
+
// invoker returns, so unwinding still reaches the catch in `guard`.
|
|
621
|
+
#[cfg(target_family = "wasm")]
|
|
622
|
+
unsafe { crossbind_embind_raise_error(text.to_wire()) };
|
|
623
|
+
#[cfg(not(target_family = "wasm"))]
|
|
624
|
+
{
|
|
625
|
+
let _ = &text;
|
|
626
|
+
previous(info);
|
|
627
|
+
}
|
|
628
|
+
}));
|
|
629
|
+
});
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
#[cfg(not(target_family = "wasm"))]
|
|
633
|
+
fn panic_text(payload: &Box<dyn std::any::Any + Send>) -> String {
|
|
634
|
+
if let Some(text) = payload.downcast_ref::<&str>() { return (*text).to_string(); }
|
|
635
|
+
if let Some(text) = payload.downcast_ref::<String>() { return text.clone(); }
|
|
636
|
+
String::from("panicked")
|
|
637
|
+
}
|
|
638
|
+
|
|
436
639
|
pub fn raise_err<W: ErrSentinel>(msg: String) -> W {
|
|
437
640
|
unsafe { crossbind_embind_raise_error(msg.to_wire()); }
|
|
438
641
|
W::err_sentinel()
|
|
439
642
|
}
|
|
440
643
|
|
|
644
|
+
/// The same escape carrying a machine-readable code, so JS can branch on `error.code` instead
|
|
645
|
+
/// of matching message text.
|
|
646
|
+
pub fn raise_err_coded<W: ErrSentinel>(msg: String, code: Option<String>) -> W {
|
|
647
|
+
let code_wire = match code {
|
|
648
|
+
Some(text) => text.to_wire(),
|
|
649
|
+
None => core::ptr::null_mut(),
|
|
650
|
+
};
|
|
651
|
+
unsafe { crossbind_embind_raise_error_coded(msg.to_wire(), code_wire); }
|
|
652
|
+
W::err_sentinel()
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
/// An error type that also implements `AsRef<str>` gives that string as the code, the way
|
|
656
|
+
/// napi-rs reads a code off its error enums. Everything else has no code: the two traits below
|
|
657
|
+
/// are picked apart by autoref, so no specialization feature is needed.
|
|
658
|
+
/// The fallback sits behind one more reference than the specialized impl, so method lookup finds
|
|
659
|
+
/// the coded one first when the bound holds and falls through to None when it does not.
|
|
660
|
+
pub trait CrossbindErrorCode { fn crossbind_code(&self) -> Option<String>; }
|
|
661
|
+
impl<E: AsRef<str>> CrossbindErrorCode for E {
|
|
662
|
+
fn crossbind_code(&self) -> Option<String> { Some(self.as_ref().to_string()) }
|
|
663
|
+
}
|
|
664
|
+
pub trait CrossbindErrorCodeFallback { fn crossbind_code(&self) -> Option<String>; }
|
|
665
|
+
impl<E> CrossbindErrorCodeFallback for &E {
|
|
666
|
+
fn crossbind_code(&self) -> Option<String> { None }
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
extern "C" fn property_getter<T: 'static, F: WireType>(ctx: usize, this: *mut T) -> <F as WireType>::Wire {
|
|
670
|
+
let getter: fn(&mut T) -> F = unsafe { core::mem::transmute(ctx) };
|
|
671
|
+
getter(unsafe { &mut *this }).to_wire()
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
extern "C" fn property_setter<T: 'static, F: WireType>(ctx: usize, this: *mut T, value: <F as WireType>::Wire) {
|
|
675
|
+
let setter: fn(&mut T, F) = unsafe { core::mem::transmute(ctx) };
|
|
676
|
+
setter(unsafe { &mut *this }, F::from_wire(value));
|
|
677
|
+
}
|
|
678
|
+
|
|
441
679
|
extern "C" fn dtor_thunk<T>(p: *mut T) {
|
|
442
680
|
if !p.is_null() {
|
|
443
681
|
drop(unsafe { Box::from_raw(p) });
|
|
@@ -461,6 +699,20 @@ pub fn class_tid<T: 'static>() -> *const c_void {
|
|
|
461
699
|
.expect("class parameter type must be registered before the binding that uses it")
|
|
462
700
|
}
|
|
463
701
|
|
|
702
|
+
/// The registered smart-pointer typeid of an OWNED class (`.smart_ptr(..)`), for a method or
|
|
703
|
+
/// function that returns a fresh instance by value.
|
|
704
|
+
pub fn owned_ptr_tid<T: 'static>() -> *const c_void {
|
|
705
|
+
registry().lock().unwrap().owned_ptr_tid.get(&TypeId::of::<T>()).copied()
|
|
706
|
+
.expect("a class that returns itself by value must register its smart pointer first")
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
/// The registered POINTER typeid of a class: a pointer parameter takes null (a reference does
|
|
710
|
+
/// not), which is how `Option<&Class>` spells None.
|
|
711
|
+
pub fn class_ptr_tid<T: 'static>() -> *const c_void {
|
|
712
|
+
registry().lock().unwrap().class_ptr_tid.get(&TypeId::of::<T>()).copied()
|
|
713
|
+
.expect("class parameter type must be registered before the binding that uses it")
|
|
714
|
+
}
|
|
715
|
+
|
|
464
716
|
/// The registered smart-pointer typeid of a SHARED class (`.smart_ptr_shared(..)`), for the
|
|
465
717
|
/// generated `Arc<T>` return/parameter wrappers.
|
|
466
718
|
pub fn shared_tid<T: 'static>() -> *const c_void {
|
|
@@ -503,6 +755,11 @@ extern "C" {
|
|
|
503
755
|
fn crossbind_v_as_str(h: usize) -> *mut u8;
|
|
504
756
|
fn crossbind_v_call(f: usize, argc: u32, argv: *const usize) -> usize;
|
|
505
757
|
fn crossbind_v_cb_err_take() -> *mut u8;
|
|
758
|
+
fn crossbind_v_bytes_to_js(data: *const u8, len: u32) -> usize;
|
|
759
|
+
fn crossbind_v_f64s_to_js(data: *const f64, len: u32) -> usize;
|
|
760
|
+
fn crossbind_v_typed_len(h: usize) -> u32;
|
|
761
|
+
fn crossbind_v_bytes_from_js(h: usize, out: *mut u8, len: u32);
|
|
762
|
+
fn crossbind_v_f64s_from_js(h: usize, out: *mut f64, len: u32);
|
|
506
763
|
}
|
|
507
764
|
|
|
508
765
|
fn str_wire(s: &str) -> *mut u8 {
|
|
@@ -581,6 +838,47 @@ impl Clone for JsValue {
|
|
|
581
838
|
}
|
|
582
839
|
}
|
|
583
840
|
|
|
841
|
+
/// Typed arrays: a byte or double sequence crosses as the JS view of the same name, copied once
|
|
842
|
+
/// in each direction (a view into wasm memory would dangle when the heap grows).
|
|
843
|
+
pub struct JsBytes(pub Vec<u8>);
|
|
844
|
+
|
|
845
|
+
impl WireType for JsBytes {
|
|
846
|
+
type Wire = usize;
|
|
847
|
+
const SIG: char = 'i';
|
|
848
|
+
fn tid() -> *const c_void { unsafe { crossbind_tid_emval() } }
|
|
849
|
+
fn from_wire(w: usize) -> Self {
|
|
850
|
+
let len = unsafe { crossbind_v_typed_len(w) } as usize;
|
|
851
|
+
let mut data = vec![0u8; len];
|
|
852
|
+
unsafe { crossbind_v_bytes_from_js(w, data.as_mut_ptr(), len as u32) };
|
|
853
|
+
unsafe { crossbind_v_unref(w) };
|
|
854
|
+
JsBytes(data)
|
|
855
|
+
}
|
|
856
|
+
fn to_wire(self) -> usize {
|
|
857
|
+
unsafe { crossbind_v_bytes_to_js(self.0.as_ptr(), self.0.len() as u32) }
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
pub struct JsF64s(pub Vec<f64>);
|
|
862
|
+
|
|
863
|
+
impl WireType for JsF64s {
|
|
864
|
+
type Wire = usize;
|
|
865
|
+
const SIG: char = 'i';
|
|
866
|
+
fn tid() -> *const c_void { unsafe { crossbind_tid_emval() } }
|
|
867
|
+
fn from_wire(w: usize) -> Self {
|
|
868
|
+
let len = unsafe { crossbind_v_typed_len(w) } as usize;
|
|
869
|
+
let mut data = vec![0.0f64; len];
|
|
870
|
+
unsafe { crossbind_v_f64s_from_js(w, data.as_mut_ptr(), len as u32) };
|
|
871
|
+
unsafe { crossbind_v_unref(w) };
|
|
872
|
+
JsF64s(data)
|
|
873
|
+
}
|
|
874
|
+
fn to_wire(self) -> usize {
|
|
875
|
+
unsafe { crossbind_v_f64s_to_js(self.0.as_ptr(), self.0.len() as u32) }
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
impl ErrSentinel for JsBytes { fn err_sentinel() -> Self { JsBytes(Vec::new()) } }
|
|
880
|
+
impl ErrSentinel for JsF64s { fn err_sentinel() -> Self { JsF64s(Vec::new()) } }
|
|
881
|
+
|
|
584
882
|
impl Drop for JsValue {
|
|
585
883
|
fn drop(&mut self) {
|
|
586
884
|
unsafe { crossbind_v_unref(self.handle) };
|
|
@@ -790,6 +1088,7 @@ pub fn class_<T: 'static>(name: &str) -> ClassBuilder<T> {
|
|
|
790
1088
|
name_ptr = r.cstr(name);
|
|
791
1089
|
dtor_sig = r.cstr("vp");
|
|
792
1090
|
r.class_tid.insert(TypeId::of::<T>(), cls);
|
|
1091
|
+
r.class_ptr_tid.insert(TypeId::of::<T>(), ptr_ty);
|
|
793
1092
|
}
|
|
794
1093
|
unsafe {
|
|
795
1094
|
crossbind_embind_register_class(
|
|
@@ -804,6 +1103,26 @@ pub fn class_<T: 'static>(name: &str) -> ClassBuilder<T> {
|
|
|
804
1103
|
}
|
|
805
1104
|
|
|
806
1105
|
impl<T: 'static> ClassBuilder<T> {
|
|
1106
|
+
/// A field that reads and writes as a JS property. The getter and setter are plain shim
|
|
1107
|
+
/// functions over the instance, so a private field can be exposed through accessors too.
|
|
1108
|
+
pub fn property<F: WireType>(self, name: &str, getter: fn(&mut T) -> F, setter: fn(&mut T, F)) -> Self {
|
|
1109
|
+
let (name_ptr, getter_sig, setter_sig);
|
|
1110
|
+
{
|
|
1111
|
+
let mut r = registry().lock().unwrap();
|
|
1112
|
+
name_ptr = r.cstr(name);
|
|
1113
|
+
getter_sig = r.cstr(&format!("{}pp", F::SIG));
|
|
1114
|
+
setter_sig = r.cstr(&format!("vpp{}", F::SIG));
|
|
1115
|
+
}
|
|
1116
|
+
unsafe {
|
|
1117
|
+
crossbind_embind_register_class_property(
|
|
1118
|
+
self.cls, name_ptr,
|
|
1119
|
+
F::tid(), getter_sig, property_getter::<T, F> as *const () as usize, getter as *mut c_void,
|
|
1120
|
+
F::tid(), setter_sig, property_setter::<T, F> as *const () as usize, setter as *mut c_void,
|
|
1121
|
+
);
|
|
1122
|
+
}
|
|
1123
|
+
self
|
|
1124
|
+
}
|
|
1125
|
+
|
|
807
1126
|
/// Registers a smart-pointer type for this class so factory functions can return an
|
|
808
1127
|
/// object that JS owns and frees on delete(). Call before `.createN`.
|
|
809
1128
|
pub fn smart_ptr(mut self, name: &str) -> Self {
|
|
@@ -827,6 +1146,7 @@ impl<T: 'static> ClassBuilder<T> {
|
|
|
827
1146
|
);
|
|
828
1147
|
}
|
|
829
1148
|
self.smart_ptr_ty = Some(ptr_ty);
|
|
1149
|
+
registry().lock().unwrap().owned_ptr_tid.insert(TypeId::of::<T>(), ptr_ty);
|
|
830
1150
|
self
|
|
831
1151
|
}
|
|
832
1152
|
|
|
@@ -918,6 +1238,10 @@ factories! {
|
|
|
918
1238
|
create0 / factory_invoker0 : ;
|
|
919
1239
|
create1 / factory_invoker1 : A0;
|
|
920
1240
|
create2 / factory_invoker2 : A0, A1;
|
|
1241
|
+
create3 / factory_invoker3 : A0, A1, A2;
|
|
1242
|
+
create4 / factory_invoker4 : A0, A1, A2, A3;
|
|
1243
|
+
create5 / factory_invoker5 : A0, A1, A2, A3, A4;
|
|
1244
|
+
create6 / factory_invoker6 : A0, A1, A2, A3, A4, A5;
|
|
921
1245
|
}
|
|
922
1246
|
|
|
923
1247
|
// Arc-allocating factories for shared classes: identical registration shape, but the object
|
|
@@ -1006,6 +1330,9 @@ constructors! {
|
|
|
1006
1330
|
constructor1 / ctor_invoker1 : A0;
|
|
1007
1331
|
constructor2 / ctor_invoker2 : A0, A1;
|
|
1008
1332
|
constructor3 / ctor_invoker3 : A0, A1, A2;
|
|
1333
|
+
constructor4 / ctor_invoker4 : A0, A1, A2, A3;
|
|
1334
|
+
constructor5 / ctor_invoker5 : A0, A1, A2, A3, A4;
|
|
1335
|
+
constructor6 / ctor_invoker6 : A0, A1, A2, A3, A4, A5;
|
|
1009
1336
|
}
|
|
1010
1337
|
|
|
1011
1338
|
// Raw-pointer ctor variants for fallible `new`: the shim boxes the value itself (or raises and
|
|
@@ -1050,6 +1377,9 @@ constructors_ptr! {
|
|
|
1050
1377
|
constructor_ptr1 / ctor_ptr_invoker1 : A0;
|
|
1051
1378
|
constructor_ptr2 / ctor_ptr_invoker2 : A0, A1;
|
|
1052
1379
|
constructor_ptr3 / ctor_ptr_invoker3 : A0, A1, A2;
|
|
1380
|
+
constructor_ptr4 / ctor_ptr_invoker4 : A0, A1, A2, A3;
|
|
1381
|
+
constructor_ptr5 / ctor_ptr_invoker5 : A0, A1, A2, A3, A4;
|
|
1382
|
+
constructor_ptr6 / ctor_ptr_invoker6 : A0, A1, A2, A3, A4, A5;
|
|
1053
1383
|
}
|
|
1054
1384
|
|
|
1055
1385
|
// Raw-pointer factory variants: `Result<Self, E>` raises on Err, `Option<Self>` returns null
|
|
@@ -1095,6 +1425,10 @@ factories_ptr! {
|
|
|
1095
1425
|
create_ptr0 / factory_ptr_invoker0 : ;
|
|
1096
1426
|
create_ptr1 / factory_ptr_invoker1 : A0;
|
|
1097
1427
|
create_ptr2 / factory_ptr_invoker2 : A0, A1;
|
|
1428
|
+
create_ptr3 / factory_ptr_invoker3 : A0, A1, A2;
|
|
1429
|
+
create_ptr4 / factory_ptr_invoker4 : A0, A1, A2, A3;
|
|
1430
|
+
create_ptr5 / factory_ptr_invoker5 : A0, A1, A2, A3, A4;
|
|
1431
|
+
create_ptr6 / factory_ptr_invoker6 : A0, A1, A2, A3, A4, A5;
|
|
1098
1432
|
}
|
|
1099
1433
|
|
|
1100
1434
|
// N-arity instance methods.
|
|
@@ -1141,6 +1475,8 @@ functions! {
|
|
|
1141
1475
|
function2 / method_invoker2 : A0, A1;
|
|
1142
1476
|
function3 / method_invoker3 : A0, A1, A2;
|
|
1143
1477
|
function4 / method_invoker4 : A0, A1, A2, A3;
|
|
1478
|
+
function5 / method_invoker5 : A0, A1, A2, A3, A4;
|
|
1479
|
+
function6 / method_invoker6 : A0, A1, A2, A3, A4, A5;
|
|
1144
1480
|
}
|
|
1145
1481
|
|
|
1146
1482
|
// N-arity FREE functions (`Module.name(..)` in JS, no class). embind slices the target off
|
|
@@ -1183,4 +1519,6 @@ free_functions! {
|
|
|
1183
1519
|
fn2 / free_invoker2 : A0, A1;
|
|
1184
1520
|
fn3 / free_invoker3 : A0, A1, A2;
|
|
1185
1521
|
fn4 / free_invoker4 : A0, A1, A2, A3;
|
|
1522
|
+
fn5 / free_invoker5 : A0, A1, A2, A3, A4;
|
|
1523
|
+
fn6 / free_invoker6 : A0, A1, A2, A3, A4, A5;
|
|
1186
1524
|
}
|
|
@@ -40,6 +40,11 @@ void crossbind_embind_register_smart_ptr(
|
|
|
40
40
|
void crossbind_embind_register_enum(
|
|
41
41
|
CrossbindTid enum_ty, const char* name, size_t size, bool is_signed, int policy_value);
|
|
42
42
|
void crossbind_embind_register_enum_value(CrossbindTid enum_ty, const char* value_name, int value);
|
|
43
|
+
void crossbind_embind_raise_error_coded(unsigned char* msg, unsigned char* code);
|
|
44
|
+
void crossbind_embind_register_constant(const char* name, const void* ty, double value);
|
|
45
|
+
void crossbind_embind_register_class_property(CrossbindTid cls, const char* name,
|
|
46
|
+
CrossbindTid getter_ty, const char* getter_sig, CrossbindFn getter, void* getter_ctx,
|
|
47
|
+
CrossbindTid setter_ty, const char* setter_sig, CrossbindFn setter, void* setter_ctx);
|
|
43
48
|
void crossbind_embind_register_value_object(
|
|
44
49
|
CrossbindTid struct_ty, const char* name,
|
|
45
50
|
const char* ctor_sig, CrossbindFn ctor, const char* dtor_sig, CrossbindFn dtor);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@crossbind/core-embind-rust",
|
|
3
|
-
"version": "2.0.0-beta.
|
|
3
|
+
"version": "2.0.0-beta.58",
|
|
4
4
|
"description": "Rust producer for the crossbind embind registration protocol - bind Rust structs as classes reachable from JS on web (embind) and mobile (embind-jsi). Research/spike stage.",
|
|
5
5
|
"homepage": "https://github.com/crossbind/crossbind/tree/main/core/embind-rust#readme",
|
|
6
6
|
"repository": {
|