mirror of
https://github.com/fluffos/fluffos
synced 2026-08-12 18:26:06 -04:00
One coherent change: make the mudlib TUI library (/std/tui) work in the browser on BOTH web terminals, harden everything the work surfaced, and pin the toolchain that broke it. ## Web terminals: xterm.js + a shared telnet client * Vendor @xterm/xterm 6.0.0 + @xterm/addon-fit 0.11.0 (dist files byte-exact from the official npm tarballs, licenses included) under src/www/vendor/. xterm.js does the terminal emulation on both pages: rendering, SGR 16/256/truecolor, alternate screen, cursor state, wide characters, scrollback, mouse reporting, bracketed paste, and keyboard encoding (the whole dialect testsuite/std/tui/keys.lpc decodes, including C-_ undo). * src/www/telnet.js -- one telnet option engine shared by both pages (transport-agnostic; hooks for page-specific options): ECHO masks the password prompt, WILL/WONT SGA -- the driver's char-mode signal (set_charmode) -- automatically switches between the line-input bar and raw keystroke streaming, NAWS reports real terminal geometry from the fit addon and re-reports on resize (driving the window_size apply), TTYPE answers xterm-256color. * src/www/wasm/index.html (the wasm shell): output/input rides xterm.js; the page keeps the synchronous-bridge queueing (sends flush outside receive() -- the wasm bridge re-enters the parser otherwise), the error modal, and the jsbridge handlers. Also guards crypto.getRandomValues() against views backed by resizable ArrayBuffers (see the emsdk section below). * src/www/index.html (the websocket client for the native driver): rewritten on the same stack, replacing a parseANSI() that stripped all cursor sequences (no TUI possible) and a telnet layer whose option bytes leaked into the text stream and never answered negotiations. Passwords now mask, char mode works, GMCP/MSP kept (dead TelnetOverWebSocket/handler classes removed); ws frames arrive as arraybuffers (no Blob/FileReader path); UTF-8 and telnet sequences survive frame splits (streaming decode + stateful parser). * tools/wasm/pack-mudlib.sh and the release zip ship vendor/ and telnet.js next to index.html in both layouts. ## Driver: websocket output wedged permanently on multi-window bursts Re-arming the writeable event from inside LWS_CALLBACK_SERVER_WRITEABLE is lossy with the libevent event lib: after the user callback returns, lws core clears POLLOUT and its pollfd bookkeeping desyncs from the evlib watcher -- the request is dropped and every later lws_callback_on_writable() no-ops, freezing output on that connection for good. First bites on any burst larger than one 2048-byte window (e.g. a full-screen TUI frame; no test had ever pushed one through a ws client). The ws_telnet.cc/ws_ascii.cc handlers now drain the evbuffer in a loop gated on lws_send_pipe_choked(); a choked write is flushed by lws's own core-managed POLLOUT path, which fires the callback again. Found by the browser end-to-end run below; documented in AGENTS.md 14. ## TUI library (/std/tui): review fixes + features Fixes: wslice() dropped combining marks from every sliced render; ESC[1;mR (modified F3) misdecoded as a cursor position report; readline lost the left scroll marker when a line overflowed both viewport edges; stray mouse events cancelled incremental search; Tab on a unique already-complete match missed the trailing space; menu lines wider than the terminal wrapped and desynced the in-place repaint (width now fed by the glue and re-fed on NAWS resize); the menu overflow indicator only showed below the window; backward focus cycling from the initial state skipped the last widget; a terminal-initiated close (disconnect, tui_destroy) leaked the app clone and its widgets -- teardown now runs through a reentry-guarded app_quit() in both directions. Features (from the README's own deferred list): readline C-_ undo (per-keystroke snapshots); pterm-style type-to-filter in select/ multiselect (results index the original choices); mouse-wheel scrolling in list/table/tree/log (wheel no longer click-selects); table clicks honour the header offset; tree Left on a leaf jumps to its parent. All pinned by testsuite/single/tests/std/tui/fixes.lpc, including the app teardown cycle via a runtime-written mock terminal. ## Toolchain: pin emsdk, guard random_get() emsdk 6.0.2 defaulted GROWABLE_ARRAYBUFFERS=1, making every ALLOW_MEMORY_GROWTH build's heap a resizable ArrayBuffer (wasmMemory.toResizableBuffer()) in browsers shipping the wasm rab-integration -- and two emscripten runtime paths pass raw HEAPU8.subarray() views into Web APIs that reject resizable-backed views: random_get() -> crypto.getRandomValues() (threw at boot) and UTF8ToString() -> TextDecoder (broke jsbridge). 6.0.3 reverted the default AND fixed the string codegen (getUnsharedTextDecoderView -> getHeapViewOrCopy), but random_get() is still unguarded upstream. Reproduced and certified against real 6.0.2/6.0.3 toolchains in Chromium (--js-flags=--experimental-wasm-rab-integration): 6.0.2 unpatched throws the exact boot error, 6.0.2 + the page's getRandomValues wrapper passes, 6.0.3 passes. CI now installs a pinned emsdk-ver input (default 6.0.3) instead of "latest". ## Verification * Native Debug+ASan: GTest 312/312; full LPC suite (574 files, per-file ref-count checker) x3 across the work; TUI test dir x3 randomized. * LPC suite inside the wasm driver under node: 5274 checks, 574 files. * Browser e2e (Playwright + Chromium): wasm shell 21/21 checks (charts/SGR, char-mode auto-switch, readline editing + undo + history, select with filter, multiselect/confirm, full-screen app, dashboard live repaint + NAWS resize relayout); websocket client against the native driver 13/13 twice on one instance (plus an ascii-subprotocol multi-window burst) -- the flow that caught the lws wedge. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018UvX1HbmGk9zWGBsW4Rkcq
135 lines
4.6 KiB
Bash
Executable file
135 lines
4.6 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
#
|
|
# pack-mudlib.sh -- package a mudlib with the WASM FluffOS driver into a
|
|
# static web bundle you can serve from any HTTP server.
|
|
#
|
|
# The output directory contains:
|
|
# fluffos.js / fluffos.wasm the driver (from a `wasm` preset build)
|
|
# mudlib.js / mudlib.data your mudlib, packed for the in-memory FS
|
|
# fluffos-boot.js mount point + config file for the page
|
|
# index.html the web terminal page
|
|
# telnet.js the telnet client layer (shared with src/www)
|
|
# vendor/ xterm.js + fit addon (the terminal emulator)
|
|
#
|
|
# Usage:
|
|
# tools/wasm/pack-mudlib.sh --mudlib <dir> --config <relative path> \
|
|
# [--driver <dir with fluffos.js/.wasm>] [--mount </name>] [--out <dir>]
|
|
#
|
|
# Example (package the bundled testsuite):
|
|
# tools/wasm/pack-mudlib.sh --mudlib testsuite --config etc/config.test
|
|
#
|
|
# Example (your game):
|
|
# tools/wasm/pack-mudlib.sh --mudlib /home/me/mylib --config etc/config \
|
|
# --mount /mylib --out dist/
|
|
#
|
|
# The config file is read INSIDE the packed image, with the driver chdir'd
|
|
# to the mount point -- exactly like running `driver etc/config` natively
|
|
# from the mudlib directory. Relative paths in the config keep working.
|
|
#
|
|
# The script also works from an extracted release zip (where it sits next
|
|
# to fluffos.js / fluffos.wasm / index.html): the driver and web shell then
|
|
# default to the script's own directory and --out defaults to ./dist.
|
|
# Emscripten's file_packager must be reachable (emsdk on PATH).
|
|
|
|
set -euo pipefail
|
|
|
|
SELF_DIR=$(cd "$(dirname "$0")" && pwd)
|
|
|
|
# Two layouts: in the source tree this script lives in tools/wasm/ with the
|
|
# driver built under build-wasm/; in the release zip it sits right next to
|
|
# fluffos.js / fluffos.wasm / index.html.
|
|
if [ -f "$SELF_DIR/fluffos.js" ] && [ -f "$SELF_DIR/index.html" ]; then
|
|
DRIVER_DIR="$SELF_DIR"
|
|
SHELL_HTML="$SELF_DIR/index.html"
|
|
OUT="$PWD/dist"
|
|
else
|
|
ROOT=$(cd "$SELF_DIR/../.." && pwd)
|
|
DRIVER_DIR="$ROOT/build-wasm/src"
|
|
SHELL_HTML="$ROOT/src/www/wasm/index.html"
|
|
OUT="$ROOT/build-wasm/dist"
|
|
fi
|
|
|
|
MUDLIB=""
|
|
CONFIG=""
|
|
MOUNT=""
|
|
|
|
usage() { grep '^#' "$0" | sed 's/^# \{0,1\}//'; exit 1; }
|
|
|
|
while [ $# -gt 0 ]; do
|
|
case "$1" in
|
|
--mudlib) MUDLIB=$2; shift 2 ;;
|
|
--config) CONFIG=$2; shift 2 ;;
|
|
--driver) DRIVER_DIR=$2; shift 2 ;;
|
|
--mount) MOUNT=$2; shift 2 ;;
|
|
--out) OUT=$2; shift 2 ;;
|
|
*) echo "unknown argument: $1" >&2; usage ;;
|
|
esac
|
|
done
|
|
|
|
[ -n "$MUDLIB" ] && [ -n "$CONFIG" ] || usage
|
|
MUDLIB=$(cd "$MUDLIB" && pwd)
|
|
[ -z "$MOUNT" ] && MOUNT="/$(basename "$MUDLIB")"
|
|
|
|
if [ ! -f "$MUDLIB/$CONFIG" ]; then
|
|
echo "error: config file '$CONFIG' not found inside mudlib '$MUDLIB'" >&2
|
|
exit 1
|
|
fi
|
|
if [ ! -f "$DRIVER_DIR/fluffos.js" ] || [ ! -f "$DRIVER_DIR/fluffos.wasm" ]; then
|
|
echo "error: driver not found in '$DRIVER_DIR' (build the 'wasm' preset first," >&2
|
|
echo " or pass --driver <dir>)" >&2
|
|
exit 1
|
|
fi
|
|
|
|
# Locate emscripten's file_packager.
|
|
FILE_PACKAGER=""
|
|
if command -v file_packager >/dev/null; then
|
|
FILE_PACKAGER="file_packager"
|
|
else
|
|
EMCC_PATH=$(command -v emcc || true)
|
|
for c in "${EMCC_PATH:+$(dirname "$EMCC_PATH")/tools/file_packager.py}" \
|
|
/usr/share/emscripten/tools/file_packager.py; do
|
|
if [ -n "$c" ] && [ -f "$c" ]; then FILE_PACKAGER="python3 $c"; break; fi
|
|
done
|
|
fi
|
|
if [ -z "$FILE_PACKAGER" ]; then
|
|
echo "error: emscripten file_packager not found (is emsdk on PATH?)" >&2
|
|
exit 1
|
|
fi
|
|
|
|
mkdir -p "$OUT"
|
|
|
|
echo "== packing mudlib $MUDLIB -> $MOUNT"
|
|
(cd "$(dirname "$MUDLIB")" && \
|
|
$FILE_PACKAGER "$OUT/mudlib.data" \
|
|
--preload "$(basename "$MUDLIB")@$MOUNT" \
|
|
--js-output="$OUT/mudlib.js" >/dev/null)
|
|
|
|
echo "== boot configuration ($MOUNT, $CONFIG)"
|
|
cat > "$OUT/fluffos-boot.js" <<EOF
|
|
// Generated by tools/wasm/pack-mudlib.sh -- consumed by index.html.
|
|
window.FLUFFOS_BOOT = {
|
|
mount: "$MOUNT",
|
|
config: "$CONFIG",
|
|
};
|
|
EOF
|
|
|
|
echo "== driver + web shell"
|
|
cp "$DRIVER_DIR/fluffos.js" "$DRIVER_DIR/fluffos.wasm" "$OUT/"
|
|
# Prefer an index.html shipped next to a --driver dir (release zips), then
|
|
# the layout-derived default. The page's companions ship alongside it:
|
|
# vendor/ (xterm.js + fit addon, vendored from npm under src/www/vendor)
|
|
# and telnet.js (the shared telnet client).
|
|
if [ -f "$DRIVER_DIR/index.html" ]; then
|
|
SHELL_DIR="$DRIVER_DIR"
|
|
cp "$DRIVER_DIR/index.html" "$OUT/"
|
|
else
|
|
SHELL_DIR="$(dirname "$SHELL_HTML")/.."
|
|
cp "$SHELL_HTML" "$OUT/"
|
|
fi
|
|
[ -d "$SHELL_DIR/vendor" ] && cp -r "$SHELL_DIR/vendor" "$OUT/"
|
|
[ -f "$SHELL_DIR/telnet.js" ] && cp "$SHELL_DIR/telnet.js" "$OUT/"
|
|
|
|
echo
|
|
echo "Done: $OUT"
|
|
echo "Serve it over HTTP (wasm cannot be loaded from file://), e.g.:"
|
|
echo " python3 -m http.server -d $OUT 8080"
|