fluffos/docs/efun/jsbridge/js_export.md
Yucong Sun e6f0c377f8
wasm: run the full driver in the browser (Emscripten port) (#1231)
The driver cross-compiles to WebAssembly and runs a complete mudlib
inside a webpage: compiler, VM, all portable efun packages, and the
real telnet protocol layer -- the page is the telnet client, JavaScript
is the wire. The LPC testsuite passes inside the wasm driver (browser
and node).

Architecture (src/wasm/README.md):
- Transport interface (src/net/transport.h): each interactive_t owns an
  abstract byte pipe (write/flush/schedule_command/close). comm.cc and
  telnet.cc are transport-agnostic and compile unchanged on every
  target. Implementations select at link time: SocketTransport +
  WebsocketTransport (net/transport_libevent.cc, native) vs
  WasmConsoleTransport (src/wasm/comm_wasm.cc).
- Inverted event loop: the shared gametick/event core stays in
  backend.cc; backend_libevent.cc (blocking loop) vs
  src/wasm/backend_wasm.cc (page-driven fluffos_tick(now_ms), walltime
  priority queue, capped catch-up). Other per-target singletons (TLS,
  DNS resolver, crash handler) follow the same link-time pattern -- no
  #ifdef __EMSCRIPTEN__ in shared logic files.
- Mudlib rides in Emscripten's MEMFS via file_packager; driver file I/O
  needed zero changes.

jsbridge package (WASM only): js_eval() (sync), js_call() (async page
handlers with LPC callbacks), js_export() (page calls LPC via
Module.fluffos.callLPC, Promise-resolved on a later tick) -- fetch,
canvas/WebGL, storage, page UIs driving the game. Demo in
testsuite/command/jsdemo.lpc + the bundled web terminal.

Build/tooling: native-tools + wasm CMake presets (host codegen tools,
then cross build); tools/wasm/build-deps.sh (ICU + zlib cross-builds,
including the ICU genccode data quirk); tools/wasm/pack-mudlib.sh
(any mudlib + driver -> static web bundle, also works standalone from
the release zip); src/www/wasm/index.html (self-contained web terminal
with a minimal telnet client). On emsdk >= 3.1.57 the build uses native
wasm exceptions (-fwasm-exceptions); older toolchains fall back to
-sDISABLE_EXCEPTION_CATCHING=0.

CI/release: a wasm CI job (latest emsdk, deps cache keyed on resolved
emcc version) gates PRs on the LPC testsuite running inside the wasm
driver under node; release.yml ships fluffos-<version>-wasm.zip
(driver + web terminal + pack-mudlib.sh). Fixed a stale-predefine bug:
options.autogen.h now depends on packages.autogen.h + config.h.

Docs: docs/build-wasm.md (end-to-end workflow), docs/driver/wasm.md
(packer + jsbridge cookbook), docs/efun/jsbridge/*, README + AGENTS
updated. Testsuite files for optional packages guard themselves with
#ifdef __PACKAGE_*__.


Claude-Session: https://claude.ai/code/session_01VVpphH3cgXyziRDCbjUVkb

Co-authored-by: Claude <noreply@anthropic.com>
2026-07-10 23:33:51 -04:00

2.2 KiB

title
jsbridge / js_export

js_export

NAME

js_export - register an LPC function the hosting page can call

SYNOPSIS

int js_export(string name);                              // unregister
int js_export(string name, string | function callback);  // register

DESCRIPTION

Registers `callback` under `name` so the page's JavaScript can call
into LPC:

    // JS (Promise of the string result)
    const sum = await Module.fluffos.callLPC("add", "2", "3");

The callback runs on the next driver tick (proper driver context,
like call_out) as:

    mixed callback(string *args, int id);

`args` are the JavaScript arguments as strings; `id` is the call id.
The return value settles the page's Promise: a string is passed
through as-is, other values are %O-formatted (integers and floats
print plainly; return a JSON-encoded string for structured data). A
runtime error in the callback, or a destructed owner object, rejects
the Promise.

A string callback names a function called on the registering object.
Re-registering a name replaces the previous entry; calling with only
`name` unregisters it. Returns 1 when a callback was registered, 0
when the name was (or is now) unregistered.

Like call_out(1), the callback runs without a command context:
this_player() is 0 inside it.

Only available on the WebAssembly build (guard LPC code with
`#ifdef __PACKAGE_JSBRIDGE__`).

EXAMPLES

```c
private mixed lpc_add(string *args, int id) {
    int total = 0;
    foreach (string a in args) total += to_int(a);
    return total;
}

void create() { js_export("add", (: lpc_add :)); }
```

Page side (helper included in the bundled web shell):
```js
const sum = await fluffos.callLPC("add", "2", "3");   // "5"
```

SEE ALSO

js_call(3), js_eval(3), call_out(3)

NOTE

Exports are driver-global (one namespace per driver instance) and
hold a reference on the registering object until unregistered. The
page can only call names the mudlib chose to export -- the export
table is the mudlib's capability surface toward the page, the mirror
of the page's handler table.