Add a browser (WebAssembly) build of the emulator#177
Merged
Conversation
Split the crate by cargo features so the deterministic core compiles without the desktop stack, and add a thin wasm frontend that runs the same core in a browser at real speed, published to copperline.dev/try. Feature split (defaults unchanged; native builds are byte-for-byte behaviourally identical, verified by the full test suite and matching --benchmark-until numbers): - `frontend`: the winit/pixels window, launcher/UI, cpal output, gilrs gamepads, rfd dialogs, and the console's arboard paste. - `wasm-boards`: the wasmtime plugin-board host. Cranelift cannot be compiled *to* wasm32, so browser builds turn it off; the pure-data manifest structs move to `wasm_manifest.rs` so config/zorro still compile without it. - `bench-bin`: a headless benchmark binary (`copperline-bench`) that also builds for wasm32-wasip1 and runs under Node's WASI (tools/wasi-bench.mjs), measuring wasm performance on the same V8 engine Chrome uses. With --render it includes the presentation pipeline; render checksums match native exactly. Portability work in the core: - `timebase.rs` re-exports std::time natively and web-time on wasm32-unknown-unknown (std Instant/SystemTime abort there); the hot-path probe sampling in bus.rs and the emulator stats go through it. envcfg snapshots an empty environment on that target instead of panicking in std::env::vars_os(). - The pure presentation helpers (post-process, recentring, TV mask, aperture predicates) move from window/present.rs to `video/present_common.rs` with re-exports, so frontend-less builds (cpu.rs debug screenshots, the web crate) can present frames. - Byte-based ingestion: `FloppyImage::from_bytes` and `FloppyController::insert_disk_image_bytes` mirror the path-based loaders for hosts with no filesystem. The browser frontend (`crates/copperline-web`, standalone cdylib, no wgpu, ~1.4 MiB wasm) exports a small WebEmu API: rAF-driven pacing with the audio queue as master clock, canvas presentation via ImageData, AudioWorklet audio, the desktop key table transliterated to W3C codes, and Pointer Lock mouse. The page glue (www/try.js, www/audio-worklet.js) lives here so it cannot drift from the API; the wasm-demo.yml workflow publishes it with the bundle and the AROS ROMs to the website repository on release tags, mirroring docs-site.yml. A new CI job guards the portability invariant (core check without default features, web crate check for wasm32-unknown-unknown). Documented in docs/guide/browser.md: architecture, local build steps, the embedding API, and the wasip1 benchmarking workflow.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this is
Copperline compiled to WebAssembly, running the same cycle-driven core in a browser at real speed. A hosted page goes to copperline.dev under
/try(site-repo commit, published separately): it boots the bundled AROS ROM, takes your own Kickstart image and disk files, and runs entirely client-side.Full write-up of the architecture, local build steps, the embedding API, and the benchmarking workflow:
docs/guide/browser.md.How it is structured
frontend(winit/pixels/cpal/gilrs/rfd/arboard) andwasm-boards(the wasmtime plugin host, which cannot be compiled to wasm32).--no-default-featuresis the portable headless core; a new CI job keeps it compiling, plus the web crate forwasm32-unknown-unknown.timebase.rs(std::time natively, web-time in browsers), an empty envcfg snapshot on wasm (std::env panics there), the pure presentation helpers moved tovideo/present_common.rs(re-exported into the window module family, so the desktop path is untouched), and byte-based ROM/floppy ingestion for hosts with no filesystem.crates/copperline-web: a standalone cdylib (not a workspace member; the root lockfile never changes when building it) exporting a smallWebEmuAPI. No wgpu -- canvasputImageData+ AudioWorklet -- so the wasm is ~1.4 MiB (~0.6 MiB gzipped) and single-threaded (no COOP/COEP headers needed; GitHub Pages can serve it).wasm-demo.ymlmirrorsdocs-site.yml-- onv*tags it rebuilds the bundle and pushes it, the page glue (www/try.js,www/audio-worklet.js), and the AROS ROMs to the website repository with the existingSITE_DEPLOY_KEY. The glue lives in this repo so it can never drift from the WebEmu API.copperline-bench(bench-binfeature): headless benchmark that also builds for wasm32-wasip1 and runs under Node's WASI (same V8 as Chrome) viatools/wasi-bench.mjs.Desktop regression checks
cargo fmt --check,cargo clippy --all-targets --all-features --locked -- -D warnings: clean.cargo build --release --locked+ fullcargo test --release --locked: green.--benchmark-until 5before/after the refactor: 440.3/s vs 443.1/s, p50 1.73 ms vs 1.68 ms (noise).myst build --html --ci --strict --check-links: clean with the new docs page.window.rscall sites are unchanged.BoardDevice's serde variant set is unchanged in default builds, so save states are unaffected.Measured wasm performance (Node/V8, Apple Silicon,
--render)Render checksums are identical between native and wasm runs.
Verified in Chrome against the hosted page layout: AROS boots, 0.99x realtime pacing with a stable ~120 ms audio queue, ADF insert boots Amiga Test Kit, the keyboard map reports the expected raw codes, mouse position/buttons register in ATK's controller-port test, and a Kickstart 1.3 swap cold-boots correctly.
Not in this PR
The website page shell (
try/index.html) lives in the site repository. Joystick mapping, gamepads, save states in the browser, and a Firefox/Safari pass are follow-ups.