added minimal frontend

This commit is contained in:
kirjavascript 2023-02-18 16:32:26 +00:00
parent f0f10129fc
commit c6546d9c58
11 changed files with 427 additions and 42 deletions

28
Cargo.lock generated
View file

@ -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"

View file

@ -3,8 +3,9 @@
members = [
"emu",
"frontend",
"frontend-minimal",
]
[profile.release]
opt-level = 2
debug = true
# debug = true

View file

@ -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 = "*"

49
frontend-minimal/build.sh Executable file
View file

@ -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

View file

@ -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)
}
}

View file

@ -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;

Binary file not shown.

View file

@ -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);
}

View file

@ -0,0 +1,118 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=1"
name="viewport"
/>
<link
href="data:image/x-icon;base64,AAABAAEAEBAQAAAAAAAoAQAAFgAAACgAAAAQAAAAIAAAAAEABAAAAAAAgAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAdJ6tAP///wAmOUAAq6ahAIpyUwB4TxkA45MrAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYAZjMzMAAABmZjERETMAAGZjERERERAAZmMREiIhFABmZzIlIiIwQGZ3d1IiACAAZ3d3IiIAUAB3d3ciIgB3AHd3dyIiB3cAd3d3QiRXd1B3d3V0RXd3cHYzd1d3d3cAdjERdVVXdwBmMRZ1d3d2AHdhZ3d3d2YAd2Z3d3dwZgBgPwAAAA8AAAABAAAAAQAAAAEAAAABAAAAAQAAAAEAAAABAAAAAQAAAAEAAAADAAAAAwAAAAMAAAADAAAAEwAA"
rel="icon"
type="image/x-icon"
/>
<style>
canvas {
width: 100%;
max-height: 90vh;
box-shadow: 2px 2px 5px black;
/*
image-rendering: -moz-crisp-edges;
image-rendering: -webkit-crisp-edges;
image-rendering: pixelated;
image-rendering: crisp-edges;
*/
}
main {
width: 800px;
}
html, body {
height: 100%;
margin: 0;
background-color: #111;
color: #777;
}
body {
display: flex;
justify-content: center;
align-items: center;
font-family: monospace;
}
h1, span {
font-size: 15px;
margin: 6px 0;
}
</style>
<title>trueLMAO</title>
</head>
<body>
<noscript>noscript mode</noscript>
<main>
<canvas width="320" height="224"></canvas>
<h1>DEMO</h1>
<span class="frameCount"></span>
</main>
<script type="module">
import init, { MDEmu } from './frontend_minimal.js';
import gamepad from './gamepad.js';
const canvas = document.querySelector('canvas');
const ctx = canvas.getContext('2d', { alpha: false });
const img = ctx.createImageData(320, 240);
(async () => {
const instance = await init();
const emu = new MDEmu();
function draw() {
const buffer = new Uint8ClampedArray(
instance.memory.buffer, emu.screen(), 320 * 240 * 3
);
for (let i = 0; i < 320*240; i++) {
const bufferIndex = i * 3;
const imgIndex = i * 4;
img.data[imgIndex+0] = buffer[bufferIndex+0];
img.data[imgIndex+1] = buffer[bufferIndex+1];
img.data[imgIndex+2] = buffer[bufferIndex+2];
img.data[imgIndex+3] = 255;
}
ctx.putImageData(img, 0, 0);
// ctx.putImageData(new ImageData(buffer, 320), 0, 0);
}
const frameCount = document.querySelector('.frameCount');
const epoch = performance.now();
let framesDone = 0;
const loop = () => {
requestAnimationFrame(loop);
const diff = performance.now() - epoch;
const frames = diff * 0.05992274 | 0;
const frameAmount = frames - framesDone;
frameCount.textContent = String(frameAmount);
if (document.visibilityState !== 'hidden') {
emu.gamepad_p1(gamepad());
if (frameAmount > 5) {
emu.frame(true);
} else {
for (let i = 0; i < frameAmount; i++) {
emu.frame(i === frameAmount - 1);
}
}
if (frameAmount > 0) {
draw();
}
}
framesDone = frames;
};
loop();
})();
</script>
</body>
</html>

View file

@ -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:

View file

@ -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