Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,28 @@ jobs:
| sed 's/^/--test /' \
| xargs cargo test --release --lib

wasm:
name: Headless core and browser build
# Guards the portability split behind the browser build (see
# docs/guide/browser.md): the core must keep compiling without the
# desktop frontend, and the web crate must keep compiling for
# wasm32-unknown-unknown. Cheap check-only job, so plain ubuntu is fine
# (no GPU or audio stack is reached without the `frontend` feature).
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with:
targets: wasm32-unknown-unknown
- uses: Swatinem/rust-cache@v2
- name: Core without the desktop frontend
run: cargo check --no-default-features --locked
- name: Headless benchmark binary
run: cargo check --no-default-features --features bench-bin --locked
- name: Web crate (wasm32-unknown-unknown)
working-directory: crates/copperline-web
run: cargo check --target wasm32-unknown-unknown --locked

m68k-singlestep:
name: m68k SingleStepTests (68000 fixtures)
# Runs the per-instruction 68000 fixture suite against the real
Expand Down
81 changes: 81 additions & 0 deletions .github/workflows/wasm-demo.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
name: Browser demo

# Builds the wasm browser frontend (crates/copperline-web) and publishes it
# to the copperline.dev website repository (LinuxJedi/copperline.github.io)
# under /try when a v* tag is pushed, so the in-browser emulator always
# matches the latest release. A workflow_dispatch trigger allows
# re-publishing from whatever ref it is run on without cutting a release.
#
# The page shell (try/index.html) is hand-written in the website repository
# and left alone. This workflow replaces the parts that must change together
# with the emulator: the wasm-bindgen bundle (try/pkg), the JS glue that
# drives the WebEmu API (try/try.js, try/audio-worklet.js, sourced from
# crates/copperline-web/www), and the AROS ROMs (try/aros).
#
# Pushing to the website repository needs the SITE_DEPLOY_KEY secret: the
# private half of an SSH deploy key whose public half is installed with
# write access on LinuxJedi/copperline.github.io (shared with docs-site.yml).
on:
push:
tags: ["v*"]
workflow_dispatch:

concurrency:
group: wasm-demo-${{ github.ref }}
cancel-in-progress: true

jobs:
publish:
name: Build and publish the browser demo
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install the wasm target
run: rustup target add wasm32-unknown-unknown
- name: Install wasm-bindgen-cli
# The CLI must match the crate dependency exactly; the version is
# pinned in crates/copperline-web/Cargo.toml and parsed from there so
# they cannot drift apart.
run: |
V=$(sed -n 's/^wasm-bindgen = "=\(.*\)"$/\1/p' crates/copperline-web/Cargo.toml)
test -n "$V"
cargo install wasm-bindgen-cli --version "$V" --locked
- name: Build the web crate
working-directory: crates/copperline-web
run: |
cargo build --release --target wasm32-unknown-unknown --locked
wasm-bindgen --target web --out-dir pkg \
target/wasm32-unknown-unknown/release/copperline_web.wasm
test -s pkg/copperline_web_bg.wasm
- name: Check out the website repository
uses: actions/checkout@v4
with:
repository: LinuxJedi/copperline.github.io
ssh-key: ${{ secrets.SITE_DEPLOY_KEY }}
path: site
- name: Replace the generated parts of /try
# The hand-written page shell must already exist in the site repo;
# refuse to publish a bundle onto a site that has no page for it.
run: |
test -f site/try/index.html
rm -rf site/try/pkg site/try/aros
mkdir -p site/try/pkg site/try/aros
cp crates/copperline-web/pkg/copperline_web.js \
crates/copperline-web/pkg/copperline_web_bg.wasm site/try/pkg/
cp crates/copperline-web/www/try.js \
crates/copperline-web/www/audio-worklet.js site/try/
cp assets/aros/aros-amiga-m68k-rom.bin \
assets/aros/aros-amiga-m68k-ext.bin \
assets/aros/LICENSE assets/aros/ACKNOWLEDGEMENTS site/try/aros/
- name: Commit and push
working-directory: site
run: |
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git add -A try
if git diff --cached --quiet; then
echo "Browser demo is unchanged; nothing to publish."
exit 0
fi
git commit -m "Publish the browser demo for ${{ github.ref_name }}"
git push
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

66 changes: 55 additions & 11 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,12 @@ repository = "https://github.com/LinuxJedi/Copperline"
keywords = ["amiga", "emulator", "m68k"]
categories = ["emulators"]
publish = false
# `cargo run` should keep launching the emulator now that the headless
# `copperline-bench` bin target exists.
default-run = "copperline"

[features]
default = ["midi"]
default = ["midi", "frontend", "wasm-boards"]
display-plan-trace = []
# Local investigation switches that deliberately alter timing or rendering.
# Normal builds keep these environment-variable overrides inert so release
Expand All @@ -24,39 +27,63 @@ internal-diagnostics = []
# CoreMIDI (macOS), the ALSA sequencer (Linux), WinMM (Windows); other targets
# get a stub. See src/midi/.
midi = []
# The interactive desktop frontend: the winit/pixels window and launcher,
# host audio output, gamepads, file dialogs, and the debugger console's
# clipboard paste. Off, the crate is the portable headless core (the surface
# a wasm32 browser frontend builds against); the `copperline` binary
# requires it.
frontend = [
"dep:winit",
"dep:pixels",
"dep:cpal",
"dep:gilrs",
"dep:rfd",
"dep:arboard",
"dep:env_logger",
]
# The wasmtime host for functional Zorro boards (src/wasmboard.rs). Separate
# from `frontend` because it is a core-machine feature, but Cranelift cannot
# be compiled FOR wasm32, so browser builds turn it off.
wasm-boards = ["dep:wasmtime"]
# The headless `copperline-bench` benchmark binary (core-only; also builds
# for wasm32-wasip1). Off by default so native release artifacts are
# unchanged.
bench-bin = []

[dependencies]
# Path-only dependency on the in-tree fork (crates/m68k). No version requirement
# and a non-crates.io package name (copperline-m68k) guarantee the local copy is
# always used and the upstream `m68k` crate can never be pulled in; aliased back
# to `m68k` so source keeps `use m68k::...`. See crates/m68k/Cargo.toml.
m68k = { package = "copperline-m68k", path = "crates/m68k" }
pixels = "0.17"
winit = "0.30"
pixels = { version = "0.17", optional = true }
winit = { version = "0.30", optional = true }
anyhow = "1"
log = "0.4"
env_logger = "0.11"
env_logger = { version = "0.11", optional = true }
serde = { version = "1", features = ["derive"] }
serde-big-array = "0.5"
bincode = "1"
toml = "0.8"
png = "0.17"
flate2 = "1"
cpal = "0.15"
cpal = { version = "0.15", optional = true }
# Kept unconditional (not `frontend`-gated) because the Windows MIDI backend
# (src/midi/winmm.rs) also uses it; it is pure Rust and wasm-safe.
ringbuf = "0.4"
hound = "3.5"
rfd = "0.17.2"
gilrs = "0.11"
rfd = { version = "0.17.2", optional = true }
gilrs = { version = "0.11", optional = true }
# Host clipboard for the debugger console's paste (text only; the default
# image-data feature is left off). wayland-data-control covers Wayland
# hosts; X11 support is built in on Linux.
arboard = { version = "3", default-features = false, features = ["wayland-data-control"] }
arboard = { version = "3", default-features = false, features = ["wayland-data-control"], optional = true }
# WASM plugin host for functional Zorro boards (src/wasmboard.rs). Lean feature
# set: the Cranelift JIT + runtime only -- no WASI, component model, GC, threads,
# or wat parsing in the shipped binary (kept deterministic; see the determinism
# Config in wasmboard.rs). Pin the version: save-state replay of a plugin's
# linear-memory snapshot is only guaranteed within one wasmtime build.
wasmtime = { version = "=27.0.0", default-features = false, features = ["cranelift", "runtime"] }
wasmtime = { version = "=27.0.0", default-features = false, features = ["cranelift", "runtime"], optional = true }
# Already pulled in transitively; used directly only for localtime_r (local
# time-zone filename stamps) and, on macOS, the pthread QoS class used for the
# optional realtime-priority feature (see src/priority.rs).
Expand All @@ -74,10 +101,27 @@ objc2-foundation = { version = "0.3", default-features = false, features = ["NSD
# Optional realtime-priority feature: cross-platform thread scheduling control.
# Only used off macOS -- the macOS path uses the libc pthread QoS API directly
# (Core Audio already runs the audio callback real-time), so this crate (and
# the windows-sys it pulls in) never enters the macOS build.
[target.'cfg(not(target_os = "macos"))'.dependencies]
# the windows-sys it pulls in) never enters the macOS build. Excluded on
# wasm32, where there are no host threads to schedule.
[target.'cfg(not(any(target_os = "macos", target_arch = "wasm32")))'.dependencies]
thread-priority = "3.1"

# Browser builds only: std::time::Instant/SystemTime panic on
# wasm32-unknown-unknown; web-time backs them with performance.now()/Date.now().
# See src/timebase.rs -- native targets re-export std::time unchanged.
[target.'cfg(all(target_arch = "wasm32", target_os = "unknown"))'.dependencies]
web-time = "1"

[[bin]]
name = "copperline"
path = "src/main.rs"
required-features = ["frontend"]

[[bin]]
name = "copperline-bench"
path = "src/bin/bench.rs"
required-features = ["bench-bin"]

[dev-dependencies]
# Compile WAT to wasm bytes in the WASM-plugin-host tests, so golden test
# plugins live as readable text in the tests rather than checked-in binaries.
Expand Down
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,12 @@ against real hardware.
interactive chip-bus frame analyzer, remote GDB support, deterministic
save states, input recording/replay, and headless screenshot/frame-dump
capture -- the deterministic core makes every replay byte-identical.
- **A browser build**: the same core compiled to WebAssembly with a
canvas/Web Audio frontend, hosted at
[copperline.dev/try](https://copperline.dev/try/) -- boots the bundled
AROS ROM, takes your own Kickstart and disk images, and runs entirely
client-side. See `docs/guide/browser.md` for how it works and how to
embed it.

## Requirements

Expand Down
2 changes: 2 additions & 0 deletions crates/copperline-web/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/target
/pkg
Loading
Loading