@crossbind/core-embind-rust 2.0.0-beta.60 → 2.0.0-beta.63
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 +23 -20
- package/adapters/jsi.cpp +136 -27
- package/adapters/web.cpp +56 -0
- package/crate/src/lib.rs +205 -1
- package/include/crossbind_embind.h +2 -0
- package/package.json +2 -3
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Rust **producer** for the crossbind embind registration protocol. Write an idiomatic Rust
|
|
4
4
|
struct, register it, and it surfaces in JS as a real class - on web through emscripten's
|
|
5
|
-
embind, on mobile through [`@crossbind/core-embind-jsi`](../
|
|
5
|
+
embind, on mobile through [`@crossbind/core-embind-jsi`](../embind-jsi) - **with no
|
|
6
6
|
generated C++ wrapper and no cbindgen header**. The Rust side talks straight to the
|
|
7
7
|
registration ABI that both hosts already consume.
|
|
8
8
|
|
|
@@ -31,7 +31,7 @@ Rust (this package) ──┼── crossbind_embind_register_* ─┤
|
|
|
31
31
|
- `adapters/web.cpp` - passthrough to emscripten's `_embind_*` (10-param, identical).
|
|
32
32
|
- `adapters/jsi.cpp` - wraps the raw invokers into `jsi::Function` (via
|
|
33
33
|
`createFromHostFunction`) and drops `isNonnullReturn` for embind-jsi's 9-param API.
|
|
34
|
-
Compiled inside the React Native native module
|
|
34
|
+
Compiled inside the React Native native module and native Node.js addons.
|
|
35
35
|
|
|
36
36
|
## Demo: plain Rust in, JS classes out
|
|
37
37
|
|
|
@@ -92,7 +92,8 @@ On import the toolchain synthesizes a self-contained bridge crate under
|
|
|
92
92
|
`<project>/.crossbind/rust-bridges/<name>/` (the user file is embedded via `#[path] mod user;` -
|
|
93
93
|
no copy) plus a `counter.rs.d.ts` next to the source for full editor typing. The native builds
|
|
94
94
|
bundle every app-local surface into ONE super staticlib (`rust-bridges/_app_super`) and link it
|
|
95
|
-
into the app lib (iOS: merged into the force_loaded react-native-crossbind.a; Android
|
|
95
|
+
into the app lib (iOS: merged into the force_loaded react-native-crossbind.a; Android and Node.js
|
|
96
|
+
addons: whole-archive, on Windows through its keep symbol).
|
|
96
97
|
|
|
97
98
|
App-local surfaces can use UPSTREAM crates directly - the C++-style model where the app writes
|
|
98
99
|
its own thin surface over a linked library and no crossbind package is involved. Declare the crates
|
|
@@ -165,7 +166,10 @@ constructor share one object), and is linked lazily with that symbol pinned
|
|
|
165
166
|
(iOS `-Wl,-u,_crossbind_keep_<lib>`, Android `-Wl,--undefined=crossbind_keep_<lib>`) - the registration
|
|
166
167
|
object is pulled, libstd stays lazy and deduplicates. Manual-bindings crates (no keep symbol)
|
|
167
168
|
still fall back to force_load/whole-archive and are therefore safe only as the app's single
|
|
168
|
-
Rust archive.
|
|
169
|
+
Rust archive. On Windows the super staticlib links lazily too: two crates in it may import one
|
|
170
|
+
DLL through raw-dylib (std and getrandom both import `ProcessPrng`), and lld cannot load the
|
|
171
|
+
repeated import tables whole. There the link pins `crossbind_keep_crossbind_app_super()`, which
|
|
172
|
+
reaches the keep symbol each app bridge exports; the super builds every bridge as one object.
|
|
169
173
|
|
|
170
174
|
Web specifics: wasm-ld's `-u` does NOT pull archive members, so the wasm link pins keep symbols
|
|
171
175
|
with `-Wl,--export=crossbind_keep_<lib>` instead; and any Rust archive in the link makes `buildWasm`
|
|
@@ -181,17 +185,20 @@ Generation rules (`crossbind/src/utils/rustBridgeGen.js`, run by `buildCargo` be
|
|
|
181
185
|
|---|---|
|
|
182
186
|
| `#[repr(i32)] pub enum Mode { Slow = 0, .. }` | embind enum |
|
|
183
187
|
| `#[repr(C)] #[derive(..Default, Copy..)] pub struct Point { pub x: i32, .. }` | value object `{x, ..}` |
|
|
184
|
-
| `pub fn new(..) -> Self` | constructor (max
|
|
185
|
-
| other `pub fn ..(..) -> Self` | `smart_ptr` + static factory (max
|
|
186
|
-
| `pub fn m(&mut self / &self, ..) -> R` | method (max
|
|
188
|
+
| `pub fn new(..) -> Self` | constructor (max 7 args) |
|
|
189
|
+
| other `pub fn ..(..) -> Self` | `smart_ptr` + static factory (max 6 args) |
|
|
190
|
+
| `pub fn m(&mut self / &self, ..) -> R` | method (max 6 args), JS name camelCased |
|
|
187
191
|
| `text: &str` / `text: &String` parameter | `string` on the wire, borrowed at the call site |
|
|
188
192
|
| `i64` / `u64` param, return or field | JS **BigInt** (full 64-bit range, sign-correct) |
|
|
189
|
-
| top-level `pub fn f(..) -> R` | module-level free function (max
|
|
193
|
+
| top-level `pub fn f(..) -> R` | module-level free function (max 6 args), camelCased |
|
|
190
194
|
| `impl Display for C` | `toString()` on the class (so `` `${obj}` `` formats) |
|
|
191
195
|
| `-> Result<T, E>` (ctor, factory, method or free fn; `E: Display`) | Err becomes a **thrown JS exception** |
|
|
192
196
|
| `-> Option<Self>` (factory) | None becomes **JS null** (typed `X \| null`) |
|
|
193
197
|
| `-> Option<i32 / f64 / bool / String>` (method or free fn) | Some -> value, None -> **null** (typed `T \| null`) |
|
|
194
198
|
| `Option<i32 / f64 / bool / String>` parameter | JS undefined/null -> None (typed `T \| null \| undefined`) |
|
|
199
|
+
| `Option<i64 / u64>` parameter (`JsBigIntOptArg<T>`) | a BigInt or safe-integer Number; undefined/null -> None (typed `bigint \| null \| undefined`) |
|
|
200
|
+
| `Option<&[u8] / &[f64]>` parameter (`JsViewOptArg<T>`) | the typed array, read as for `&[T]`; undefined/null -> None |
|
|
201
|
+
| `NonZeroU64` (and the other `NonZero` integers) parameter, also in `Option` where the integer's `Option` crosses | the integer; 0 is a thrown JS exception before the call |
|
|
195
202
|
| `other: &SomeClass` parameter | pass another bound instance (borrowed for the call) |
|
|
196
203
|
| `export.bindings.vectors: [{ of: 'i32', name: '..' }]` in crossbind.config.mjs | `register_vector` |
|
|
197
204
|
|
|
@@ -207,30 +214,26 @@ scattered `Box::leak`.
|
|
|
207
214
|
|
|
208
215
|
## Test
|
|
209
216
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
2. **mobile-shape** - same crate compiled native, run against a jsi-*shaped* mock consumer
|
|
215
|
-
that mirrors embind-jsi's real signatures. Validates the adapter's wrapping /
|
|
216
|
-
marshalling / param-dropping.
|
|
217
|
+
The conformance kit calls the `demo` crate's classes and functions (the `rust:` checks in
|
|
218
|
+
`e2e/conformance/spec/run.mjs`) on every leg listed in `e2e/conformance/README.md`: through
|
|
219
|
+
`adapters/web.cpp` and **unmodified embind-js** on the wasm legs, through `adapters/jsi.cpp` on
|
|
220
|
+
React Native and native Node.js. `e2e/conformance-rust` checks each Rust construct in more depth.
|
|
217
221
|
|
|
218
222
|
## Status matrix
|
|
219
223
|
|
|
220
224
|
| Path | State |
|
|
221
225
|
|---|---|
|
|
222
226
|
| web: Rust → flat ABI → embind-js (emsdk 5.0.3, 6.0.2 & 6.0.9) | ✅ tested, runs (full demo incl. f64 + string) |
|
|
223
|
-
| mobile: adapter shape vs real embind-jsi signatures | ✅ shape-validated (native mock) |
|
|
224
227
|
| 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
228
|
| 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) |
|
|
226
229
|
| app-local `.rs` import (`./native/counter.rs`, both mobile platforms) | ✅ GREEN — synthesized bridge crate + app super staticlib + typed `counter.rs.d.ts` |
|
|
227
230
|
| typed package import (`import { X } from '@crossbind/embind-rust-demo'`) | ✅ GREEN on iOS, Android AND web (vite) — resolver + proxy module + generated d.ts; web verified by playwright on chromium/firefox/webkit |
|
|
228
231
|
| real-world upstream crate, packageless (`geo` via `cargoDependencies` + app-local surface) | ✅ GREEN — Hull (ConvexHull) on iOS + Android (26/26 smoke) AND in the browser (worker-mode playwright, 3 browsers): the same surface .rs serves all three platforms — a wasm output upstream geo never shipped |
|
|
229
|
-
| Rust idioms: `&str`/`&String` params, `Result` → JS exception, `Option<Self>` → null | ✅ GREEN —
|
|
230
|
-
| i64/u64 → BigInt, `impl Display` → toString(), top-level free functions | ✅ GREEN — same full bar (
|
|
232
|
+
| Rust idioms: `&str`/`&String` params, `Result` → JS exception, `Option<Self>` → null | ✅ GREEN — conformance kit on every leg, worker-mode playwright on 3 browsers, iOS sim + Android emulator (throw message, null, borrowed str all asserted) |
|
|
233
|
+
| i64/u64 → BigInt, `impl Display` → toString(), top-level free functions | ✅ GREEN — same full bar (conformance kit on every leg, 3-browser worker playwright, both devices); u64::MAX round-trips exactly, free `Result` fns throw |
|
|
231
234
|
| 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-null | ✅ GREEN —
|
|
233
|
-
| Option params + `&SomeClass` params + re-export following (`semver` matches, `regex` throwing ctor as DIRECT imports) | ✅ GREEN —
|
|
235
|
+
| `Option<i32/f64/bool/String>` returns → value-or-null | ✅ GREEN — conformance kit on every leg, 3-browser worker playwright, both devices |
|
|
236
|
+
| Option params + `&SomeClass` params + re-export following (`semver` matches, `regex` throwing ctor as DIRECT imports) | ✅ GREEN — conformance kit on every leg, 3-browser worker playwright, Android 32/32 + iOS 32/32 — via the explicit cargo: scheme, so npm names never collide |
|
|
234
237
|
| Rust crate → real mobile archives (android/ios arm64) | ✅ compiles; iOS and Android linked+run |
|
|
235
238
|
|
|
236
239
|
### Wire contract notes (native vs wasm)
|
package/adapters/jsi.cpp
CHANGED
|
@@ -29,11 +29,13 @@
|
|
|
29
29
|
#include <optional>
|
|
30
30
|
#include <string>
|
|
31
31
|
#include <typeinfo>
|
|
32
|
+
#include <unordered_map>
|
|
32
33
|
#include <unordered_set>
|
|
33
34
|
// Real embind-jsi declarations (correct namespaces/linkage/signatures): jsRuntime lives in
|
|
34
35
|
// `emscripten`, the _embind_register_* functions and InitFunc in `emscripten::internal` with C++
|
|
35
36
|
// linkage and jsi::Function& slots. Including its header avoids any hand-declaration mismatch.
|
|
36
37
|
#include <emscripten/bind.h>
|
|
38
|
+
#include <crossbind/direct_reads.h>
|
|
37
39
|
#include "../include/crossbind_embind.h"
|
|
38
40
|
|
|
39
41
|
namespace jsi = facebook::jsi;
|
|
@@ -68,6 +70,12 @@ const void* crossbind_tid_optional_int() { static const char t = 0; return &t; }
|
|
|
68
70
|
const void* crossbind_tid_optional_double() { static const char t = 0; return &t; }
|
|
69
71
|
const void* crossbind_tid_optional_bool() { static const char t = 0; return &t; }
|
|
70
72
|
const void* crossbind_tid_optional_string() { static const char t = 0; return &t; }
|
|
73
|
+
const void* crossbind_tid_optional_int64() { static const char t = 0; return &t; }
|
|
74
|
+
const void* crossbind_tid_optional_uint64() { static const char t = 0; return &t; }
|
|
75
|
+
const void* crossbind_tid_bytes_arg() { static const char t = 0; return &t; }
|
|
76
|
+
const void* crossbind_tid_f64s_arg() { static const char t = 0; return &t; }
|
|
77
|
+
const void* crossbind_tid_bytes_opt_arg() { static const char t = 0; return &t; }
|
|
78
|
+
const void* crossbind_tid_f64s_opt_arg() { static const char t = 0; return &t; }
|
|
71
79
|
|
|
72
80
|
// Idempotent: every Rust archive in the app may ask for the same optional type.
|
|
73
81
|
void crossbind_embind_register_optional(const void* opt, const void* /*inner: adapter converts*/) {
|
|
@@ -129,17 +137,62 @@ static uint8_t* crossbindWireFromString(const std::string& s) {
|
|
|
129
137
|
std::memcpy(w + 4, s.data(), s.size());
|
|
130
138
|
return w;
|
|
131
139
|
}
|
|
132
|
-
|
|
133
|
-
|
|
140
|
+
// Helpers and constructors are looked up once per runtime: a global lookup on every call cost more
|
|
141
|
+
// than the call. Each bindings initialisation starts a new cache; the old one is leaked on purpose,
|
|
142
|
+
// because the runtime its functions belong to may already be gone.
|
|
143
|
+
static std::unordered_map<const char*, jsi::Function>* g_helpers = new std::unordered_map<const char*, jsi::Function>();
|
|
144
|
+
struct ViewNames {
|
|
145
|
+
jsi::PropNameID buffer;
|
|
146
|
+
jsi::PropNameID byteOffset;
|
|
147
|
+
jsi::PropNameID length;
|
|
148
|
+
};
|
|
149
|
+
static ViewNames* g_viewNames = nullptr;
|
|
150
|
+
static void resetHelpers() {
|
|
151
|
+
g_helpers = new std::unordered_map<const char*, jsi::Function>();
|
|
152
|
+
g_viewNames = nullptr;
|
|
153
|
+
}
|
|
154
|
+
static InitFunc g_resetHelpersOnInit(resetHelpers);
|
|
155
|
+
|
|
156
|
+
static ViewNames& viewNames(jsi::Runtime& rt) {
|
|
157
|
+
if (g_viewNames == nullptr) {
|
|
158
|
+
g_viewNames = new ViewNames{jsi::PropNameID::forAscii(rt, "buffer"), jsi::PropNameID::forAscii(rt, "byteOffset"),
|
|
159
|
+
jsi::PropNameID::forAscii(rt, "length")};
|
|
160
|
+
}
|
|
161
|
+
return *g_viewNames;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Typed array arguments ('y' bytes, 'z' doubles; 'Y'/'Z' when optional): embind-jsi hands the
|
|
165
|
+
// invoker the view itself, or null for a missing optional one.
|
|
166
|
+
static void registerTypedArgs() {
|
|
167
|
+
auto& rt = *jsRuntime;
|
|
168
|
+
auto registerTypedArg = rt.global().getPropertyAsFunction(rt, "__crossbind_register_typed_arg");
|
|
169
|
+
registerTypedArg.call(rt, jsi::BigInt::fromUint64(rt, (uint64_t)(uintptr_t)crossbind_tid_bytes_arg()), "Uint8Array", false, false);
|
|
170
|
+
registerTypedArg.call(rt, jsi::BigInt::fromUint64(rt, (uint64_t)(uintptr_t)crossbind_tid_f64s_arg()), "Float64Array", true, false);
|
|
171
|
+
registerTypedArg.call(rt, jsi::BigInt::fromUint64(rt, (uint64_t)(uintptr_t)crossbind_tid_bytes_opt_arg()), "Uint8Array | null", false, true);
|
|
172
|
+
registerTypedArg.call(rt, jsi::BigInt::fromUint64(rt, (uint64_t)(uintptr_t)crossbind_tid_f64s_opt_arg()), "Float64Array | null", true, true);
|
|
173
|
+
}
|
|
174
|
+
static InitFunc g_registerTypedArgsOnInit(registerTypedArgs);
|
|
175
|
+
|
|
176
|
+
// `name` must be a string literal: the cache is keyed by its address.
|
|
177
|
+
static jsi::Function& crossbindHelper(const char* name) {
|
|
178
|
+
auto found = g_helpers->find(name);
|
|
179
|
+
if (found == g_helpers->end()) {
|
|
180
|
+
found = g_helpers->emplace(name, jsRuntime->global().getPropertyAsFunction(*jsRuntime, name)).first;
|
|
181
|
+
}
|
|
182
|
+
return found->second;
|
|
134
183
|
}
|
|
135
184
|
static thread_local std::string g_cbError;
|
|
136
185
|
static thread_local bool g_hasCbError = false;
|
|
137
186
|
|
|
138
187
|
extern "C" {
|
|
188
|
+
// Rust values holding JS handles can outlive the runtime (thread-local ones drop inside exit(),
|
|
189
|
+
// after the host cleared jsRuntime); their handles then have nothing left to release.
|
|
139
190
|
void crossbind_v_ref(size_t h) {
|
|
191
|
+
if (jsRuntime == nullptr) return;
|
|
140
192
|
crossbindHelper("__emval_incref").call(*jsRuntime, crossbindVHandle(h));
|
|
141
193
|
}
|
|
142
194
|
void crossbind_v_unref(size_t h) {
|
|
195
|
+
if (jsRuntime == nullptr) return;
|
|
143
196
|
crossbindHelper("__emval_decref").call(*jsRuntime, crossbindVHandle(h));
|
|
144
197
|
}
|
|
145
198
|
// Fork emval constants: undefined = 1n (init_emval seeds 1..4, reserved = 5).
|
|
@@ -202,46 +255,54 @@ static thread_local bool g_hasPendingError = false;
|
|
|
202
255
|
|
|
203
256
|
static thread_local std::string g_pendingErrorCode;
|
|
204
257
|
|
|
205
|
-
// Typed arrays on the native runtime:
|
|
206
|
-
//
|
|
207
|
-
// jsi
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
258
|
+
// Typed arrays on the native runtime: one copy in each direction, through the backing store of an
|
|
259
|
+
// array JavaScript allocated, so no ArrayBuffer ever wraps native memory. Copying element by element
|
|
260
|
+
// through jsi cost about 100 ns a byte.
|
|
261
|
+
static size_t typedArrayToJs(jsi::Runtime& rt, const char* ctor, const void* data, size_t length, size_t bytes) {
|
|
262
|
+
auto array = crossbindHelper(ctor).callAsConstructor(rt, (double)length).asObject(rt);
|
|
263
|
+
if (bytes) std::memcpy(array.getPropertyAsObject(rt, "buffer").getArrayBuffer(rt).data(rt), data, bytes);
|
|
264
|
+
return (size_t)crossbindHandleOf(crossbindHelper("__crossbind_v_from_value").call(rt, array));
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// A view of one of `kinds` is copied straight out of its backing store; false leaves a plain array
|
|
268
|
+
// or another view to the element-by-element path.
|
|
269
|
+
static bool typedArrayFromJs(jsi::Runtime& rt, size_t h, std::initializer_list<const char*> kinds, void* out, size_t bytes) {
|
|
270
|
+
auto value = crossbindHelper("__crossbind_v_value").call(rt, crossbindVHandle(h));
|
|
271
|
+
if (!value.isObject()) return false;
|
|
272
|
+
auto view = value.getObject(rt);
|
|
273
|
+
bool isKind = false;
|
|
274
|
+
for (const char* kind : kinds) isKind = isKind || view.instanceOf(rt, crossbindHelper(kind));
|
|
275
|
+
if (!isKind) return false;
|
|
276
|
+
auto buffer = view.getPropertyAsObject(rt, "buffer");
|
|
277
|
+
if (!buffer.isArrayBuffer(rt)) return false;
|
|
278
|
+
auto offset = (size_t)view.getProperty(rt, "byteOffset").getNumber();
|
|
279
|
+
if (bytes) std::memcpy(out, buffer.getArrayBuffer(rt).data(rt) + offset, bytes);
|
|
280
|
+
return true;
|
|
212
281
|
}
|
|
213
282
|
|
|
214
283
|
extern "C" size_t crossbind_v_bytes_to_js(const uint8_t* data, unsigned len) {
|
|
215
|
-
|
|
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));
|
|
284
|
+
return typedArrayToJs(*jsRuntime, "Uint8Array", data, len, len);
|
|
220
285
|
}
|
|
221
286
|
|
|
222
287
|
extern "C" size_t crossbind_v_f64s_to_js(const double* data, unsigned len) {
|
|
223
|
-
|
|
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));
|
|
288
|
+
return typedArrayToJs(*jsRuntime, "Float64Array", data, len, (size_t)len * sizeof(double));
|
|
228
289
|
}
|
|
229
290
|
|
|
230
291
|
extern "C" unsigned crossbind_v_typed_len(size_t h) {
|
|
231
|
-
auto
|
|
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);
|
|
292
|
+
auto number = crossbindHelper("__crossbind_v_length").call(*jsRuntime, crossbindVHandle(h));
|
|
234
293
|
return number.isNumber() ? (unsigned)number.getNumber() : 0;
|
|
235
294
|
}
|
|
236
295
|
|
|
237
296
|
extern "C" void crossbind_v_bytes_from_js(size_t h, uint8_t* out, unsigned len) {
|
|
238
297
|
auto& rt = *jsRuntime;
|
|
298
|
+
if (typedArrayFromJs(rt, h, {"Uint8Array", "Uint8ClampedArray", "Int8Array"}, out, len)) return;
|
|
239
299
|
auto array = crossbindHelper("__crossbind_v_to_array").call(rt, crossbindVHandle(h)).asObject(rt).asArray(rt);
|
|
240
300
|
for (unsigned i = 0; i < len; ++i) out[i] = (uint8_t)array.getValueAtIndex(rt, i).getNumber();
|
|
241
301
|
}
|
|
242
302
|
|
|
243
303
|
extern "C" void crossbind_v_f64s_from_js(size_t h, double* out, unsigned len) {
|
|
244
304
|
auto& rt = *jsRuntime;
|
|
305
|
+
if (typedArrayFromJs(rt, h, {"Float64Array"}, out, (size_t)len * sizeof(double))) return;
|
|
245
306
|
auto array = crossbindHelper("__crossbind_v_to_array").call(rt, crossbindVHandle(h)).asObject(rt).asArray(rt);
|
|
246
307
|
for (unsigned i = 0; i < len; ++i) out[i] = array.getValueAtIndex(rt, i).getNumber();
|
|
247
308
|
}
|
|
@@ -280,7 +341,49 @@ static jsi::Value wireStringToJsi(jsi::Runtime& rt, uint64_t w) {
|
|
|
280
341
|
return jsi::Value(jsi::String::createFromUtf8(rt, str));
|
|
281
342
|
}
|
|
282
343
|
|
|
283
|
-
|
|
344
|
+
// Where a typed array argument's elements are: the crate's ViewWire.
|
|
345
|
+
struct ViewWire {
|
|
346
|
+
const void* data;
|
|
347
|
+
size_t length;
|
|
348
|
+
};
|
|
349
|
+
|
|
350
|
+
static crossbind::IDirectReads* directReads(jsi::Runtime& rt) {
|
|
351
|
+
return static_cast<crossbind::IDirectReads*>(rt.castInterface(crossbind::IDirectReads::uuid));
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// embind-jsi's toWireType already made the argument a view of the element kind over an ArrayBuffer,
|
|
355
|
+
// so its elements are lent in place for the call. Reading its properties through the Node-API
|
|
356
|
+
// runtime cost about 440 ns, which the runtime's own interface does in one call.
|
|
357
|
+
static uint64_t readView(jsi::Runtime& rt, const jsi::Value& v, ViewWire& view) {
|
|
358
|
+
auto* reads = directReads(rt);
|
|
359
|
+
void* data = nullptr;
|
|
360
|
+
size_t length = 0;
|
|
361
|
+
if (reads != nullptr && reads->typedArrayElements(v, data, length)) {
|
|
362
|
+
view.data = data;
|
|
363
|
+
view.length = length;
|
|
364
|
+
return (uint64_t)(uintptr_t)&view;
|
|
365
|
+
}
|
|
366
|
+
auto& names = viewNames(rt);
|
|
367
|
+
auto object = v.asObject(rt);
|
|
368
|
+
auto buffer = object.getProperty(rt, names.buffer).asObject(rt);
|
|
369
|
+
if (!buffer.isArrayBuffer(rt)) throw jsi::JSError(rt, "a typed array argument needs an ArrayBuffer");
|
|
370
|
+
view.length = (size_t)object.getProperty(rt, names.length).asNumber();
|
|
371
|
+
view.data = view.length == 0 ? nullptr
|
|
372
|
+
: buffer.getArrayBuffer(rt).data(rt) + (size_t)object.getProperty(rt, names.byteOffset).asNumber();
|
|
373
|
+
return (uint64_t)(uintptr_t)&view;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
static uint64_t readArg(jsi::Runtime& rt, const jsi::Value& v, char c, ViewWire& view) {
|
|
377
|
+
if (c == 'y' || c == 'z') return readView(rt, v, view);
|
|
378
|
+
if (c == 'Y' || c == 'Z') return v.isUndefined() || v.isNull() ? 0 : readView(rt, v, view);
|
|
379
|
+
// Optional 64-bit args ('J'/'U'): the value a plain 'j'/'u' takes, in a cell this adapter frees.
|
|
380
|
+
if (c == 'J' || c == 'U') {
|
|
381
|
+
if (v.isUndefined() || v.isNull()) return 0;
|
|
382
|
+
uint64_t bits = readArg(rt, v, c == 'J' ? 'j' : 'u', view);
|
|
383
|
+
void* cell = std::malloc(8);
|
|
384
|
+
std::memcpy(cell, &bits, 8);
|
|
385
|
+
return (uint64_t)(uintptr_t)cell;
|
|
386
|
+
}
|
|
284
387
|
if (c == 's') {
|
|
285
388
|
// embind-jsi passes std::string args as a jsi::String; build the crate's [u32 len][bytes]
|
|
286
389
|
// wire buffer here (freed by the caller after dispatch - the crate's from_wire only copies).
|
|
@@ -303,7 +406,12 @@ static uint64_t readArg(jsi::Runtime& rt, const jsi::Value& v, char c) {
|
|
|
303
406
|
// i64/u64 ('j'/'u') arrive as JS BigInt (embind bigint types), Number accepted as a courtesy;
|
|
304
407
|
// both directions are raw 64-bit slots, so the unsigned bit copy is sign-correct either way.
|
|
305
408
|
if (c == 'j' || c == 'u') {
|
|
306
|
-
if (v.isBigInt())
|
|
409
|
+
if (v.isBigInt()) {
|
|
410
|
+
auto* reads = directReads(rt);
|
|
411
|
+
uint64_t bits = 0;
|
|
412
|
+
bool lossless = false;
|
|
413
|
+
return reads != nullptr && reads->bigIntToUint64(v, bits, lossless) ? bits : v.getBigInt(rt).getUint64(rt);
|
|
414
|
+
}
|
|
307
415
|
// A Number is accepted only while it is an exact integer JS can represent; anything
|
|
308
416
|
// else would be truncated silently, and a 64-bit slot deserves the exact value.
|
|
309
417
|
double d = v.isNumber() ? v.getNumber() : 0;
|
|
@@ -316,7 +424,7 @@ static uint64_t readArg(jsi::Runtime& rt, const jsi::Value& v, char c) {
|
|
|
316
424
|
// adapter owns and frees after dispatch ('S' carries a plain string wire).
|
|
317
425
|
if (c == 'I' || c == 'D' || c == 'B' || c == 'S') {
|
|
318
426
|
if (v.isUndefined() || v.isNull()) return 0;
|
|
319
|
-
if (c == 'S') return readArg(rt, v, 's');
|
|
427
|
+
if (c == 'S') return readArg(rt, v, 's', view);
|
|
320
428
|
if (c == 'I') { int32_t x = (int32_t)v.getNumber(); void* p = std::malloc(4); std::memcpy(p, &x, 4); return (uint64_t)(uintptr_t)p; }
|
|
321
429
|
if (c == 'D') { double x = v.getNumber(); void* p = std::malloc(8); std::memcpy(p, &x, 8); return (uint64_t)(uintptr_t)p; }
|
|
322
430
|
uint8_t b = v.getBool() ? 1 : 0; void* p = std::malloc(1); std::memcpy(p, &b, 1); return (uint64_t)(uintptr_t)p;
|
|
@@ -344,10 +452,11 @@ static jsi::Value callInvoker(jsi::Runtime& rt, const std::string& sig, void* in
|
|
|
344
452
|
// (embind sliced it off), so the jsi args a[0..] fill sig[2..]; otherwise a[0..] fill sig[1..].
|
|
345
453
|
// Widest call: a baked target + `this` + six declared arguments.
|
|
346
454
|
uint64_t s[8] = {0, 0, 0, 0, 0, 0, 0, 0};
|
|
455
|
+
ViewWire views[8];
|
|
347
456
|
int base = 0;
|
|
348
457
|
if (prepend) { s[0] = ctx; base = 1; }
|
|
349
458
|
// 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;
|
|
459
|
+
for (int j = base; j < total && j < 8; ++j) s[j] = (size_t)(j - base) < count ? readArg(rt, a[j - base], sig[1 + j], views[j]) : 0;
|
|
351
460
|
|
|
352
461
|
uint64_t r = 0;
|
|
353
462
|
if (ret == 'v') {
|
|
@@ -380,7 +489,7 @@ static jsi::Value callInvoker(jsi::Runtime& rt, const std::string& sig, void* in
|
|
|
380
489
|
// now that the crate has copied.
|
|
381
490
|
for (int j = base; j < total && j < 8; ++j) {
|
|
382
491
|
const char cj = sig[1 + j];
|
|
383
|
-
if (cj == 's' || cj == 'S' || cj == 'I' || cj == 'D' || cj == 'B') std::free((void*)(uintptr_t)s[j]);
|
|
492
|
+
if (cj == 's' || cj == 'S' || cj == 'I' || cj == 'D' || cj == 'B' || cj == 'J' || cj == 'U') std::free((void*)(uintptr_t)s[j]);
|
|
384
493
|
}
|
|
385
494
|
|
|
386
495
|
// A shim raised (Result::Err): drop the sentinel return and surface a real JS exception.
|
package/adapters/web.cpp
CHANGED
|
@@ -72,9 +72,57 @@ const void* crossbind_tid_optional_int() { static const char t = 0; return &t; }
|
|
|
72
72
|
const void* crossbind_tid_optional_double() { static const char t = 0; return &t; }
|
|
73
73
|
const void* crossbind_tid_optional_bool() { static const char t = 0; return &t; }
|
|
74
74
|
const void* crossbind_tid_optional_string() { static const char t = 0; return &t; }
|
|
75
|
+
const void* crossbind_tid_optional_int64() { static const char t = 0; return &t; }
|
|
76
|
+
const void* crossbind_tid_optional_uint64() { static const char t = 0; return &t; }
|
|
77
|
+
const void* crossbind_tid_bytes_arg() { static const char t = 0; return &t; }
|
|
78
|
+
const void* crossbind_tid_f64s_arg() { static const char t = 0; return &t; }
|
|
79
|
+
const void* crossbind_tid_bytes_opt_arg() { static const char t = 0; return &t; }
|
|
80
|
+
const void* crossbind_tid_f64s_opt_arg() { static const char t = 0; return &t; }
|
|
75
81
|
|
|
76
82
|
}
|
|
77
83
|
|
|
84
|
+
// Typed array arguments: the view is copied once into wasm memory behind a { data, length } header
|
|
85
|
+
// (the crate's ViewWire) and freed after the call. Another typed array or a plain array is converted
|
|
86
|
+
// element by element first. An optional one passes null or undefined as a null pointer.
|
|
87
|
+
EM_JS_DEPS(crossbind_typed_arg_deps, "$registerType,$throwBindingError,$embindRepr");
|
|
88
|
+
EM_JS(void, crossbind_register_typed_arg, (const void* rawType, const char* name, int isDoubles, int isOptional), {
|
|
89
|
+
const typeName = UTF8ToString(name);
|
|
90
|
+
const View = isDoubles ? Float64Array : Uint8Array;
|
|
91
|
+
const isLent = isDoubles
|
|
92
|
+
? (value) => value instanceof Float64Array
|
|
93
|
+
: (value) => value instanceof Uint8Array || value instanceof Uint8ClampedArray || value instanceof Int8Array;
|
|
94
|
+
registerType(rawType, {
|
|
95
|
+
name: typeName,
|
|
96
|
+
toWireType(destructors, value) {
|
|
97
|
+
if (isOptional && (value === null || value === undefined)) return 0;
|
|
98
|
+
if (!isLent(value)) {
|
|
99
|
+
if (!Array.isArray(value) && !(ArrayBuffer.isView(value) && !(value instanceof DataView))) {
|
|
100
|
+
throwBindingError(`Cannot pass ${embindRepr(value)} as a ${typeName}`);
|
|
101
|
+
}
|
|
102
|
+
value = View.from(value);
|
|
103
|
+
}
|
|
104
|
+
const base = _malloc(8 + value.byteLength);
|
|
105
|
+
if (!base) throwBindingError(`Cannot copy ${value.byteLength} bytes into wasm memory`);
|
|
106
|
+
HEAPU32[base >> 2] = base + 8;
|
|
107
|
+
HEAPU32[(base >> 2) + 1] = value.length;
|
|
108
|
+
if (isDoubles) HEAPF64.set(value, (base + 8) >> 3);
|
|
109
|
+
else HEAPU8.set(value, base + 8);
|
|
110
|
+
if (destructors !== null) destructors.push(_free, base);
|
|
111
|
+
return base;
|
|
112
|
+
},
|
|
113
|
+
destructorFunction(ptr) {
|
|
114
|
+
_free(ptr);
|
|
115
|
+
},
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
EMSCRIPTEN_BINDINGS(crossbind_typed_args) {
|
|
120
|
+
crossbind_register_typed_arg(crossbind_tid_bytes_arg(), "Uint8Array", 0, 0);
|
|
121
|
+
crossbind_register_typed_arg(crossbind_tid_f64s_arg(), "Float64Array", 1, 0);
|
|
122
|
+
crossbind_register_typed_arg(crossbind_tid_bytes_opt_arg(), "Uint8Array | null", 0, 1);
|
|
123
|
+
crossbind_register_typed_arg(crossbind_tid_f64s_opt_arg(), "Float64Array | null", 1, 1);
|
|
124
|
+
}
|
|
125
|
+
|
|
78
126
|
// Optional wire on wasm is an EM_VAL handle (None = the reserved null constant from
|
|
79
127
|
// emscripten's own header: a missing value is null on every runtime, like C++ nullptr). Some-values become handles through Emval.toHandle; the registered
|
|
80
128
|
// optional type's fromWireType does toValue + decref, so the refcount stays balanced.
|
|
@@ -118,6 +166,14 @@ int crossbind_emval_opt_bool(void* h, uint8_t* out) {
|
|
|
118
166
|
*out = v.as<bool>() ? 1 : 0;
|
|
119
167
|
return 1;
|
|
120
168
|
}
|
|
169
|
+
// A 64-bit optional reads through the registered bigint type, so it takes what a plain 64-bit
|
|
170
|
+
// parameter takes: a BigInt or a safe-integer Number.
|
|
171
|
+
int crossbind_emval_opt_bits64(void* h, uint64_t* out, int isSigned) {
|
|
172
|
+
emscripten::val v = emscripten::val::take_ownership((emscripten::EM_VAL)h);
|
|
173
|
+
if (v.isUndefined() || v.isNull()) return 0;
|
|
174
|
+
*out = isSigned ? (uint64_t)v.as<int64_t>() : v.as<uint64_t>();
|
|
175
|
+
return 1;
|
|
176
|
+
}
|
|
121
177
|
int crossbind_emval_opt_string(void* h, uint8_t** out) {
|
|
122
178
|
emscripten::val v = emscripten::val::take_ownership((emscripten::EM_VAL)h);
|
|
123
179
|
if (v.isUndefined() || v.isNull()) return 0;
|
package/crate/src/lib.rs
CHANGED
|
@@ -173,7 +173,9 @@ macro_rules! bindings {
|
|
|
173
173
|
}
|
|
174
174
|
#[used]
|
|
175
175
|
#[cfg_attr(target_vendor = "apple", link_section = "__DATA,__mod_init_func")]
|
|
176
|
-
|
|
176
|
+
// The Windows CRT runs no .init_array; it calls what .CRT$XCU holds when the DLL loads.
|
|
177
|
+
#[cfg_attr(windows, link_section = ".CRT$XCU")]
|
|
178
|
+
#[cfg_attr(not(any(target_vendor = "apple", windows)), link_section = ".init_array")]
|
|
177
179
|
static __CROSSBIND_EMBIND_CTOR: extern "C" fn() = __crossbind_embind_ctor;
|
|
178
180
|
};
|
|
179
181
|
};
|
|
@@ -418,6 +420,65 @@ option_wire! {
|
|
|
418
420
|
String, crossbind_tid_optional_string, 'S';
|
|
419
421
|
}
|
|
420
422
|
|
|
423
|
+
extern "C" {
|
|
424
|
+
fn crossbind_tid_optional_int64() -> *const c_void;
|
|
425
|
+
fn crossbind_tid_optional_uint64() -> *const c_void;
|
|
426
|
+
}
|
|
427
|
+
#[cfg(target_family = "wasm")]
|
|
428
|
+
extern "C" {
|
|
429
|
+
fn crossbind_emval_opt_bits64(handle: usize, out: *mut u64, signed: i32) -> i32;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
mod sealed {
|
|
433
|
+
pub trait Sealed {}
|
|
434
|
+
impl Sealed for i64 {}
|
|
435
|
+
impl Sealed for u64 {}
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
#[doc(hidden)]
|
|
439
|
+
pub trait BigIntElement: sealed::Sealed + Copy + 'static {
|
|
440
|
+
const SIGNED: bool;
|
|
441
|
+
}
|
|
442
|
+
impl BigIntElement for i64 { const SIGNED: bool = true; }
|
|
443
|
+
impl BigIntElement for u64 { const SIGNED: bool = false; }
|
|
444
|
+
|
|
445
|
+
/// An `Option<i64>` / `Option<u64>` parameter: a BigInt or a safe-integer Number, null or undefined
|
|
446
|
+
/// for `None`. Generic, so it is compiled into the crate that uses it and a cargo archive built
|
|
447
|
+
/// before it existed still links next to one built after.
|
|
448
|
+
pub struct JsBigIntOptArg<T>(pub Option<T>);
|
|
449
|
+
|
|
450
|
+
impl<T: BigIntElement> WireType for JsBigIntOptArg<T> {
|
|
451
|
+
#[cfg(target_family = "wasm")]
|
|
452
|
+
type Wire = usize;
|
|
453
|
+
#[cfg(not(target_family = "wasm"))]
|
|
454
|
+
type Wire = *mut u8;
|
|
455
|
+
#[cfg(target_family = "wasm")]
|
|
456
|
+
const SIG: char = 'i';
|
|
457
|
+
#[cfg(not(target_family = "wasm"))]
|
|
458
|
+
const SIG: char = if T::SIGNED { 'J' } else { 'U' };
|
|
459
|
+
fn tid() -> *const c_void {
|
|
460
|
+
unsafe { if T::SIGNED { crossbind_tid_optional_int64() } else { crossbind_tid_optional_uint64() } }
|
|
461
|
+
}
|
|
462
|
+
#[cfg(target_family = "wasm")]
|
|
463
|
+
fn from_wire(w: usize) -> Self {
|
|
464
|
+
let mut bits: u64 = 0;
|
|
465
|
+
if unsafe { crossbind_emval_opt_bits64(w, &mut bits, T::SIGNED as i32) } == 0 { return JsBigIntOptArg(None); }
|
|
466
|
+
JsBigIntOptArg(Some(unsafe { core::mem::transmute_copy::<u64, T>(&bits) }))
|
|
467
|
+
}
|
|
468
|
+
#[cfg(not(target_family = "wasm"))]
|
|
469
|
+
fn from_wire(w: *mut u8) -> Self {
|
|
470
|
+
if w.is_null() { return JsBigIntOptArg(None); }
|
|
471
|
+
JsBigIntOptArg(Some(unsafe { core::ptr::read_unaligned(w as *const T) }))
|
|
472
|
+
}
|
|
473
|
+
fn to_wire(self) -> Self::Wire { unreachable!("an optional 64-bit integer is only a parameter") }
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
/// Registers the optional type a `JsBigIntOptArg<T>` parameter names (adapter-idempotent).
|
|
477
|
+
pub fn register_optional_bigint<T: BigIntElement>() {
|
|
478
|
+
let inner = unsafe { if T::SIGNED { crossbind_tid_int64() } else { crossbind_tid_uint64() } };
|
|
479
|
+
unsafe { crossbind_embind_register_optional(<JsBigIntOptArg<T> as WireType>::tid(), inner) };
|
|
480
|
+
}
|
|
481
|
+
|
|
421
482
|
// wasm Some-path: hand the inner's in-memory repr to _emval_take_value. JS copies the value
|
|
422
483
|
// synchronously, so stack temporaries suffice; the string wire is freed by embind itself.
|
|
423
484
|
#[cfg(target_family = "wasm")]
|
|
@@ -879,6 +940,102 @@ impl WireType for JsF64s {
|
|
|
879
940
|
impl ErrSentinel for JsBytes { fn err_sentinel() -> Self { JsBytes(Vec::new()) } }
|
|
880
941
|
impl ErrSentinel for JsF64s { fn err_sentinel() -> Self { JsF64s(Vec::new()) } }
|
|
881
942
|
|
|
943
|
+
extern "C" {
|
|
944
|
+
fn crossbind_tid_bytes_arg() -> *const c_void;
|
|
945
|
+
fn crossbind_tid_f64s_arg() -> *const c_void;
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
// A typed array argument reaches the adapter as the JS view itself, and the adapter says where its
|
|
949
|
+
// elements are: in the view's own backing store on the native runtime, in a copy in wasm memory on
|
|
950
|
+
// the web. Either stays valid until the call returns.
|
|
951
|
+
#[repr(C)]
|
|
952
|
+
struct ViewWire {
|
|
953
|
+
data: *const c_void,
|
|
954
|
+
length: usize,
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
#[doc(hidden)]
|
|
958
|
+
pub trait ViewElement: Copy + 'static {
|
|
959
|
+
const NATIVE_SIG: char;
|
|
960
|
+
fn tid() -> *const c_void;
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
impl ViewElement for u8 {
|
|
964
|
+
const NATIVE_SIG: char = 'y';
|
|
965
|
+
fn tid() -> *const c_void { unsafe { crossbind_tid_bytes_arg() } }
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
impl ViewElement for f64 {
|
|
969
|
+
const NATIVE_SIG: char = 'z';
|
|
970
|
+
fn tid() -> *const c_void { unsafe { crossbind_tid_f64s_arg() } }
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
/// A byte or double parameter: `&[T]` reads the caller's view in place, `Vec<T>` copies it once.
|
|
974
|
+
pub struct JsViewArg<T> {
|
|
975
|
+
data: *const T,
|
|
976
|
+
length: usize,
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
pub type JsBytesArg = JsViewArg<u8>;
|
|
980
|
+
pub type JsF64sArg = JsViewArg<f64>;
|
|
981
|
+
|
|
982
|
+
impl<T: ViewElement> JsViewArg<T> {
|
|
983
|
+
pub fn as_slice(&self) -> &[T] {
|
|
984
|
+
if self.length == 0 { &[] } else { unsafe { std::slice::from_raw_parts(self.data, self.length) } }
|
|
985
|
+
}
|
|
986
|
+
pub fn to_vec(&self) -> Vec<T> {
|
|
987
|
+
self.as_slice().to_vec()
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
impl<T: ViewElement> WireType for JsViewArg<T> {
|
|
992
|
+
type Wire = usize;
|
|
993
|
+
#[cfg(target_family = "wasm")]
|
|
994
|
+
const SIG: char = 'p';
|
|
995
|
+
#[cfg(not(target_family = "wasm"))]
|
|
996
|
+
const SIG: char = T::NATIVE_SIG;
|
|
997
|
+
fn tid() -> *const c_void { T::tid() }
|
|
998
|
+
fn from_wire(w: usize) -> Self {
|
|
999
|
+
if w == 0 { return JsViewArg { data: core::ptr::null(), length: 0 }; }
|
|
1000
|
+
let view = unsafe { &*(w as *const ViewWire) };
|
|
1001
|
+
JsViewArg { data: view.data as *const T, length: view.length }
|
|
1002
|
+
}
|
|
1003
|
+
fn to_wire(self) -> usize { unreachable!("a typed array argument is never returned") }
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
extern "C" {
|
|
1007
|
+
fn crossbind_tid_bytes_opt_arg() -> *const c_void;
|
|
1008
|
+
fn crossbind_tid_f64s_opt_arg() -> *const c_void;
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
/// An `Option<&[T]>` parameter: null or undefined is `None`, a view is read as for `JsViewArg`.
|
|
1012
|
+
/// Generic like `JsBigIntOptArg`, for the same reason.
|
|
1013
|
+
pub struct JsViewOptArg<T>(Option<JsViewArg<T>>);
|
|
1014
|
+
|
|
1015
|
+
pub type JsBytesOptArg = JsViewOptArg<u8>;
|
|
1016
|
+
pub type JsF64sOptArg = JsViewOptArg<f64>;
|
|
1017
|
+
|
|
1018
|
+
impl<T: ViewElement> JsViewOptArg<T> {
|
|
1019
|
+
pub fn as_slice(&self) -> Option<&[T]> {
|
|
1020
|
+
self.0.as_ref().map(|view| view.as_slice())
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
impl<T: ViewElement> WireType for JsViewOptArg<T> {
|
|
1025
|
+
type Wire = usize;
|
|
1026
|
+
#[cfg(target_family = "wasm")]
|
|
1027
|
+
const SIG: char = 'p';
|
|
1028
|
+
#[cfg(not(target_family = "wasm"))]
|
|
1029
|
+
const SIG: char = if T::NATIVE_SIG == 'y' { 'Y' } else { 'Z' };
|
|
1030
|
+
fn tid() -> *const c_void {
|
|
1031
|
+
unsafe { if T::NATIVE_SIG == 'y' { crossbind_tid_bytes_opt_arg() } else { crossbind_tid_f64s_opt_arg() } }
|
|
1032
|
+
}
|
|
1033
|
+
fn from_wire(w: usize) -> Self {
|
|
1034
|
+
JsViewOptArg(if w == 0 { None } else { Some(JsViewArg::from_wire(w)) })
|
|
1035
|
+
}
|
|
1036
|
+
fn to_wire(self) -> usize { unreachable!("a typed array argument is never returned") }
|
|
1037
|
+
}
|
|
1038
|
+
|
|
882
1039
|
impl Drop for JsValue {
|
|
883
1040
|
fn drop(&mut self) {
|
|
884
1041
|
unsafe { crossbind_v_unref(self.handle) };
|
|
@@ -1287,6 +1444,10 @@ arc_factories! {
|
|
|
1287
1444
|
create_arc0 / arc_factory_invoker0 : ;
|
|
1288
1445
|
create_arc1 / arc_factory_invoker1 : A0;
|
|
1289
1446
|
create_arc2 / arc_factory_invoker2 : A0, A1;
|
|
1447
|
+
create_arc3 / arc_factory_invoker3 : A0, A1, A2;
|
|
1448
|
+
create_arc4 / arc_factory_invoker4 : A0, A1, A2, A3;
|
|
1449
|
+
create_arc5 / arc_factory_invoker5 : A0, A1, A2, A3, A4;
|
|
1450
|
+
create_arc6 / arc_factory_invoker6 : A0, A1, A2, A3, A4, A5;
|
|
1290
1451
|
}
|
|
1291
1452
|
|
|
1292
1453
|
// N-arity constructors: one monomorphized invoker + builder method per argument count.
|
|
@@ -1380,6 +1541,7 @@ constructors_ptr! {
|
|
|
1380
1541
|
constructor_ptr4 / ctor_ptr_invoker4 : A0, A1, A2, A3;
|
|
1381
1542
|
constructor_ptr5 / ctor_ptr_invoker5 : A0, A1, A2, A3, A4;
|
|
1382
1543
|
constructor_ptr6 / ctor_ptr_invoker6 : A0, A1, A2, A3, A4, A5;
|
|
1544
|
+
constructor_ptr7 / ctor_ptr_invoker7 : A0, A1, A2, A3, A4, A5, A6;
|
|
1383
1545
|
}
|
|
1384
1546
|
|
|
1385
1547
|
// Raw-pointer factory variants: `Result<Self, E>` raises on Err, `Option<Self>` returns null
|
|
@@ -1522,3 +1684,45 @@ free_functions! {
|
|
|
1522
1684
|
fn5 / free_invoker5 : A0, A1, A2, A3, A4;
|
|
1523
1685
|
fn6 / free_invoker6 : A0, A1, A2, A3, A4, A5;
|
|
1524
1686
|
}
|
|
1687
|
+
|
|
1688
|
+
// N-arity STATIC functions (`Class.name(..)` in JS): a free function registered on its class,
|
|
1689
|
+
// so it rides the free-function invokers. Generic only, so a cargo archive built before these
|
|
1690
|
+
// existed still links next to one built after.
|
|
1691
|
+
macro_rules! static_functions {
|
|
1692
|
+
($( $method:ident / $invoker:ident : $($arg:ident),* ; )*) => {
|
|
1693
|
+
impl<T: 'static> ClassBuilder<T> {
|
|
1694
|
+
$(
|
|
1695
|
+
pub fn $method<$($arg: WireType,)* R: WireType>(self, name: &str, f: fn($($arg),*) -> R) -> Self {
|
|
1696
|
+
let mut sig = String::new();
|
|
1697
|
+
sig.push(R::SIG); sig.push('p');
|
|
1698
|
+
let mut args = vec![R::tid()];
|
|
1699
|
+
$( sig.push(<$arg as WireType>::SIG); args.push(<$arg as WireType>::tid()); )*
|
|
1700
|
+
let (argc, arg_ptr, name_ptr, sig_ptr);
|
|
1701
|
+
{
|
|
1702
|
+
let mut r = registry().lock().unwrap();
|
|
1703
|
+
argc = args.len() as u32;
|
|
1704
|
+
arg_ptr = r.argtypes(args);
|
|
1705
|
+
name_ptr = r.cstr(name);
|
|
1706
|
+
sig_ptr = r.cstr(&sig);
|
|
1707
|
+
}
|
|
1708
|
+
unsafe {
|
|
1709
|
+
crossbind_embind_register_class_class_function(
|
|
1710
|
+
self.cls, name_ptr, argc, arg_ptr, sig_ptr,
|
|
1711
|
+
$invoker::<$($arg,)* R> as *const () as usize, f as *const () as usize, false, false,
|
|
1712
|
+
);
|
|
1713
|
+
}
|
|
1714
|
+
self
|
|
1715
|
+
}
|
|
1716
|
+
)*
|
|
1717
|
+
}
|
|
1718
|
+
};
|
|
1719
|
+
}
|
|
1720
|
+
static_functions! {
|
|
1721
|
+
static_function0 / free_invoker0 : ;
|
|
1722
|
+
static_function1 / free_invoker1 : A0;
|
|
1723
|
+
static_function2 / free_invoker2 : A0, A1;
|
|
1724
|
+
static_function3 / free_invoker3 : A0, A1, A2;
|
|
1725
|
+
static_function4 / free_invoker4 : A0, A1, A2, A3;
|
|
1726
|
+
static_function5 / free_invoker5 : A0, A1, A2, A3, A4;
|
|
1727
|
+
static_function6 / free_invoker6 : A0, A1, A2, A3, A4, A5;
|
|
1728
|
+
}
|
|
@@ -33,6 +33,8 @@ CrossbindTid crossbind_tid_optional_int(void);
|
|
|
33
33
|
CrossbindTid crossbind_tid_optional_double(void);
|
|
34
34
|
CrossbindTid crossbind_tid_optional_bool(void);
|
|
35
35
|
CrossbindTid crossbind_tid_optional_string(void);
|
|
36
|
+
CrossbindTid crossbind_tid_optional_int64(void);
|
|
37
|
+
CrossbindTid crossbind_tid_optional_uint64(void);
|
|
36
38
|
void crossbind_embind_register_smart_ptr(
|
|
37
39
|
CrossbindTid ptr_ty, CrossbindTid pointee_ty, const char* name, int sharing_policy,
|
|
38
40
|
const char* gp_sig, CrossbindFn get_pointee, const char* ctor_sig, CrossbindFn constructor,
|
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.63",
|
|
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": {
|
|
@@ -19,7 +19,6 @@
|
|
|
19
19
|
"react-native"
|
|
20
20
|
],
|
|
21
21
|
"scripts": {
|
|
22
|
-
"
|
|
23
|
-
"clear": "rm -rf crate/target demo/target e2e/demo.mjs e2e/demo.wasm e2e/jsi-shape-check"
|
|
22
|
+
"clear": "rm -rf crate/target demo/target"
|
|
24
23
|
}
|
|
25
24
|
}
|