diff --git a/Cargo.lock b/Cargo.lock index f1f46d8..19fe2b5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -840,6 +840,14 @@ dependencies = [ "wasm-bindgen-futures", ] +[[package]] +name = "frontend-minimal" +version = "0.1.0" +dependencies = [ + "emu", + "wasm-bindgen", +] + [[package]] name = "gethostname" version = "0.2.3" @@ -1893,9 +1901,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.83" +version = "0.2.84" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268" +checksum = "31f8dcbc21f30d9b8f2ea926ecb58f6b91192c17e9d33594b3df58b2007ca53b" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -1903,9 +1911,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.83" +version = "0.2.84" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142" +checksum = "95ce90fd5bcc06af55a641a86428ee4229e44e07033963a2290a8e241607ccb9" dependencies = [ "bumpalo", "log", @@ -1930,9 +1938,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.83" +version = "0.2.84" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810" +checksum = "4c21f77c0bedc37fd5dc21f897894a5ca01e7bb159884559461862ae90c0b4c5" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1940,9 +1948,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.83" +version = "0.2.84" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c" +checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6" dependencies = [ "proc-macro2", "quote", @@ -1953,9 +1961,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.83" +version = "0.2.84" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f" +checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d" [[package]] name = "wayland-client" diff --git a/Cargo.toml b/Cargo.toml index ccf4fec..f6fb195 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,8 +3,9 @@ members = [ "emu", "frontend", + "frontend-minimal", ] [profile.release] opt-level = 2 -debug = true +# debug = true diff --git a/frontend-minimal/Cargo.toml b/frontend-minimal/Cargo.toml new file mode 100644 index 0000000..b7ca4c3 --- /dev/null +++ b/frontend-minimal/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "frontend-minimal" +version = "0.1.0" +edition = "2021" + +[lib] +crate-type = ["cdylib", "rlib"] + +[dependencies] +emu = { path = "../emu" } +wasm-bindgen = "*" diff --git a/frontend-minimal/build.sh b/frontend-minimal/build.sh new file mode 100755 index 0000000..3cce427 --- /dev/null +++ b/frontend-minimal/build.sh @@ -0,0 +1,49 @@ +#!/bin/sh +set -eu +script_path=$( cd "$(dirname "${BASH_SOURCE[0]}")" ; pwd -P ) +cd "$script_path" + +FAST=false + +while test $# -gt 0; do + case "$1" in + -h|--help) + echo "build_web.sh [--fast]" + echo " --fast: skip optimization step" + exit 0 + ;; + --fast) + shift + FAST=true + ;; + *) + break + ;; + esac +done + +FOLDER_NAME=${PWD##*/} +CRATE_NAME=$FOLDER_NAME # assume crate name is the same as the folder name +CRATE_NAME_SNAKE_CASE="${CRATE_NAME//-/_}" # for those who name crates with-kebab-case +WASM_BIN="static/${CRATE_NAME_SNAKE_CASE}_bg.wasm" + +rm -f "${WASM_BIN}" + +echo "Building rust…" +BUILD=release +cargo build -p "${CRATE_NAME}" --release --lib --target wasm32-unknown-unknown + +# Get the output directory (in the workspace it is in another location) +TARGET=$(cargo metadata --format-version=1 | jq --raw-output .target_directory) + +echo "Generating JS bindings for wasm…" +TARGET_NAME="${CRATE_NAME_SNAKE_CASE}.wasm" +WASM_PATH="${TARGET}/wasm32-unknown-unknown/${BUILD}/${TARGET_NAME}" +wasm-bindgen "${WASM_PATH}" --out-dir static --target web --no-typescript + +if [[ "${FAST}" == false ]]; then + echo "Optimizing wasm…" + # to get wasm-opt: apt/brew/dnf install binaryen + # https://github.com/WebAssembly/binaryen/releases + wasm-opt "${WASM_BIN}" -O2 --fast-math -o "${WASM_BIN}" # add -g to get debug symbols +fi diff --git a/frontend-minimal/src/lib.rs b/frontend-minimal/src/lib.rs new file mode 100644 index 0000000..431e9da --- /dev/null +++ b/frontend-minimal/src/lib.rs @@ -0,0 +1,30 @@ +use wasm_bindgen::prelude::*; + +use emu::Megadrive; + +// https://rustwasm.github.io/wasm-bindgen/contributing/design/exporting-rust-struct.html + +#[wasm_bindgen] +pub struct MDEmu(Megadrive); + +#[wasm_bindgen] +impl MDEmu { + #[wasm_bindgen(constructor)] + pub fn new() -> MDEmu { + MDEmu(Megadrive::new( + include_bytes!("/home/cake/sonic/roms/s1p.bin").to_vec() + )) + } + + pub fn frame(&mut self, render: bool) { + self.0.frame(render); + } + + pub fn screen(&self) -> *const u8 { + self.0.gfx.screen.as_ptr() + } + + pub fn gamepad_p1(&mut self, value: usize) { + self.0.core.mem.io.gamepad[0].set(value) + } +} diff --git a/frontend-minimal/static/frontend_minimal.js b/frontend-minimal/static/frontend_minimal.js new file mode 100644 index 0000000..6a61007 --- /dev/null +++ b/frontend-minimal/static/frontend_minimal.js @@ -0,0 +1,157 @@ +import * as __wbg_star0 from 'env'; + +let wasm; + +const cachedTextDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }); + +cachedTextDecoder.decode(); + +let cachedUint8Memory0 = null; + +function getUint8Memory0() { + if (cachedUint8Memory0 === null || cachedUint8Memory0.byteLength === 0) { + cachedUint8Memory0 = new Uint8Array(wasm.memory.buffer); + } + return cachedUint8Memory0; +} + +function getStringFromWasm0(ptr, len) { + return cachedTextDecoder.decode(getUint8Memory0().subarray(ptr, ptr + len)); +} +/** +*/ +export class MDEmu { + + static __wrap(ptr) { + const obj = Object.create(MDEmu.prototype); + obj.ptr = ptr; + + return obj; + } + + __destroy_into_raw() { + const ptr = this.ptr; + this.ptr = 0; + + return ptr; + } + + free() { + const ptr = this.__destroy_into_raw(); + wasm.__wbg_mdemu_free(ptr); + } + /** + */ + constructor() { + const ret = wasm.mdemu_new(); + return MDEmu.__wrap(ret); + } + /** + * @param {boolean} render + */ + frame(render) { + wasm.mdemu_frame(this.ptr, render); + } + /** + * @returns {number} + */ + screen() { + const ret = wasm.mdemu_screen(this.ptr); + return ret; + } + /** + * @param {number} value + */ + gamepad_p1(value) { + wasm.mdemu_gamepad_p1(this.ptr, value); + } +} + +async function load(module, imports) { + if (typeof Response === 'function' && module instanceof Response) { + if (typeof WebAssembly.instantiateStreaming === 'function') { + try { + return await WebAssembly.instantiateStreaming(module, imports); + + } catch (e) { + if (module.headers.get('Content-Type') != 'application/wasm') { + console.warn("`WebAssembly.instantiateStreaming` failed because your server does not serve wasm with `application/wasm` MIME type. Falling back to `WebAssembly.instantiate` which is slower. Original error:\n", e); + + } else { + throw e; + } + } + } + + const bytes = await module.arrayBuffer(); + return await WebAssembly.instantiate(bytes, imports); + + } else { + const instance = await WebAssembly.instantiate(module, imports); + + if (instance instanceof WebAssembly.Instance) { + return { instance, module }; + + } else { + return instance; + } + } +} + +function getImports() { + const imports = {}; + imports.wbg = {}; + imports.wbg.__wbindgen_throw = function(arg0, arg1) { + throw new Error(getStringFromWasm0(arg0, arg1)); + }; + imports['env'] = __wbg_star0; + + return imports; +} + +function initMemory(imports, maybe_memory) { + +} + +function finalizeInit(instance, module) { + wasm = instance.exports; + init.__wbindgen_wasm_module = module; + cachedUint8Memory0 = null; + + + return wasm; +} + +function initSync(module) { + const imports = getImports(); + + initMemory(imports); + + if (!(module instanceof WebAssembly.Module)) { + module = new WebAssembly.Module(module); + } + + const instance = new WebAssembly.Instance(module, imports); + + return finalizeInit(instance, module); +} + +async function init(input) { + if (typeof input === 'undefined') { + input = new URL('frontend_minimal_bg.wasm', import.meta.url); + } + const imports = getImports(); + + if (typeof input === 'string' || (typeof Request === 'function' && input instanceof Request) || (typeof URL === 'function' && input instanceof URL)) { + input = fetch(input); + } + + initMemory(imports); + + const { instance, module } = await load(await input, imports); + + return finalizeInit(instance, module); +} + +export { initSync } +export default init; diff --git a/frontend-minimal/static/frontend_minimal_bg.wasm b/frontend-minimal/static/frontend_minimal_bg.wasm new file mode 100644 index 0000000..576c6f4 Binary files /dev/null and b/frontend-minimal/static/frontend_minimal_bg.wasm differ diff --git a/frontend-minimal/static/gamepad.js b/frontend-minimal/static/gamepad.js new file mode 100644 index 0000000..d6a70e9 --- /dev/null +++ b/frontend-minimal/static/gamepad.js @@ -0,0 +1,38 @@ +const controls = new Set([]); + +window.addEventListener('blur', () => { controls.clear(); }); +window.addEventListener('focus', () => { controls.clear(); }); + +const keymap = {}; + +[ + [1 << 7, ['Enter', 'Shift']], // start + [1 << 6, ['z', 'Z', ',', '<']], // A + [1 << 5, ['c', 'C', '/', '?']], // C + [1 << 4, ['x', 'X', '.', '>', ' ']], // B + [1 << 3, ['ArrowRight', 'd', 'D']], // R + [1 << 2, ['ArrowLeft', 'a', 'A']], // L + [1 << 1, ['ArrowDown', 's', 'S']], // D + [1, ['ArrowUp', 'w', 'W']], // U +].forEach(([value, keys]) => { + keys.forEach(key => { keymap[key] = value; }); +}); + +const html = document.documentElement; + +html.addEventListener('keydown', e => { + if (e.key in keymap) { + controls.add(e.key); + e.preventDefault(); + } +}); +html.addEventListener('keyup', e => { + if (e.key in keymap) { + controls.delete(e.key); + e.preventDefault(); + } +}); + +export default function() { + return [...controls].reduce((a, c) => a | keymap[c], 0); +} diff --git a/frontend-minimal/static/index.html b/frontend-minimal/static/index.html new file mode 100644 index 0000000..67d48aa --- /dev/null +++ b/frontend-minimal/static/index.html @@ -0,0 +1,118 @@ + + + + + + + + trueLMAO + + + +
+ +

DEMO

+ +
+ + + diff --git a/frontend/Cargo.toml b/frontend/Cargo.toml index bfc3de6..7e3cd4b 100644 --- a/frontend/Cargo.toml +++ b/frontend/Cargo.toml @@ -24,10 +24,6 @@ emu = { path = "../emu" } egui = "0.20.1" eframe = { version = "0.20.1", features = ["persistence"] } -# egui = { path = "/home/cake/dev/egui/egui" } -# eframe = { path = "/home/cake/dev/egui/eframe" } -# egui = { git = "https://github.com/emilk/egui", rev = "7eeb292a" } -# eframe = { git = "https://github.com/emilk/egui", rev = "7eeb292a" } # serde = { version = "1", features = ["derive"] } # You only need this if you want app persistence # native: diff --git a/frontend/build_web.sh b/frontend/build_web.sh index d374d1c..d0adc5b 100755 --- a/frontend/build_web.sh +++ b/frontend/build_web.sh @@ -3,33 +3,25 @@ set -eu script_path=$( cd "$(dirname "${BASH_SOURCE[0]}")" ; pwd -P ) cd "$script_path" -OPEN=false FAST=false while test $# -gt 0; do case "$1" in -h|--help) - echo "build_web.sh [--fast] [--open]" + echo "build_web.sh [--fast]" echo " --fast: skip optimization step" - echo " --open: open the result in a browser" exit 0 ;; --fast) shift FAST=true ;; - --open) - shift - OPEN=true - ;; *) break ;; esac done -# ./setup_web.sh # <- call this first! - FOLDER_NAME=${PWD##*/} CRATE_NAME=$FOLDER_NAME # assume crate name is the same as the folder name CRATE_NAME_SNAKE_CASE="${CRATE_NAME//-/_}" # for those who name crates with-kebab-case @@ -38,9 +30,9 @@ CRATE_NAME_SNAKE_CASE="${CRATE_NAME//-/_}" # for those who name crates with-keba # https://rustwasm.github.io/wasm-bindgen/api/web_sys/struct.Clipboard.html # https://rustwasm.github.io/docs/wasm-bindgen/web-sys/unstable-apis.html export RUSTFLAGS=--cfg=web_sys_unstable_apis +WASM_BIN="static/${CRATE_NAME_SNAKE_CASE}_bg.wasm" -# Clear output from old stuff: -rm -f "static/${CRATE_NAME_SNAKE_CASE}_bg.wasm" +rm -f "${WASM_BIN}" echo "Building rust…" BUILD=release @@ -58,20 +50,5 @@ if [[ "${FAST}" == false ]]; then echo "Optimizing wasm…" # to get wasm-opt: apt/brew/dnf install binaryen # https://github.com/WebAssembly/binaryen/releases - ./wasm-opt "static/${CRATE_NAME}_bg.wasm" -O2 --fast-math -o "static/${CRATE_NAME}_bg.wasm" # add -g to get debug symbols -fi - -echo "Finished: static/${CRATE_NAME_SNAKE_CASE}.wasm" - -if [[ "${OPEN}" == true ]]; then - if [[ "$OSTYPE" == "linux-gnu"* ]]; then - # Linux, ex: Fedora - xdg-open http://localhost:8080/index.html - elif [[ "$OSTYPE" == "msys" ]]; then - # Windows - start http://localhost:8080/index.html - else - # Darwin/MacOS, or something else - open http://localhost:8080/index.html - fi + wasm-opt "${WASM_BIN}" -O2 --fast-math -o "${WASM_BIN}" # add -g to get debug symbols fi