15 Commits

42 changed files with 2353 additions and 787 deletions

904
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,17 +1,26 @@
[package] [package]
name = "uw8" name = "uw8"
version = "0.1.1" version = "0.1.2"
edition = "2021" edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[features]
default = ["native", "browser"]
native = ["wasmtime"]
browser = ["warp", "tokio", "tokio-stream", "webbrowser"]
[dependencies] [dependencies]
wasmtime = "0.30" wasmtime = { version = "0.30", optional = true }
anyhow = "1" anyhow = "1"
minifb = { version = "0.20", default-features = false, features = ["x11"] } minifb = { version = "0.20", default-features = false, features = ["x11"] }
notify = "4" notify = "4"
pico-args = "0.4" pico-args = "0.4"
curlywas = { git = "https://github.com/exoticorn/curlywas.git", rev = "196719b" } curlywas = { git = "https://github.com/exoticorn/curlywas.git", rev = "89638565" }
wat = "1" wat = "1"
uw8-tool = { path = "uw8-tool" } uw8-tool = { path = "uw8-tool" }
same-file = "1" same-file = "1"
warp = { version = "0.3.2", optional = true }
tokio = { version = "1.17.0", features = ["sync", "rt"], optional = true }
tokio-stream = { version = "0.1.8", features = ["sync"], optional = true }
webbrowser = { version = "0.6.0", optional = true }

View File

@@ -15,9 +15,9 @@ See [here](https://exoticorn.github.io/microw8/) for more information and docs.
## Downloads ## Downloads
* [Linux](https://github.com/exoticorn/microw8/releases/download/v0.1.0/microw8-0.1.0-linux.tgz) * [Linux](https://github.com/exoticorn/microw8/releases/download/v0.1.2/microw8-0.1.2-linux.tgz)
* [MacOS](https://github.com/exoticorn/microw8/releases/download/v0.1.0/microw8-0.1.0-macos.tgz) * [MacOS](https://github.com/exoticorn/microw8/releases/download/v0.1.2/microw8-0.1.2-macos.tgz)
* [Windows](https://github.com/exoticorn/microw8/releases/download/v0.1.0/microw8-0.1.0-windows.zip) * [Windows](https://github.com/exoticorn/microw8/releases/download/v0.1.2/microw8-0.1.2-windows.zip)
The download includes The download includes
@@ -35,6 +35,7 @@ Runs <file> which can be a binary WebAssembly module, an `.uw8` cart, a wat (Web
Options: Options:
-b, --browser : Run in browser instead of using native runtime
-t, --timeout FRAMES : Sets the timeout in frames (1/60s) -t, --timeout FRAMES : Sets the timeout in frames (1/60s)
-w, --watch : Reloads the given file every time it changes on disk. -w, --watch : Reloads the given file every time it changes on disk.
-p, --pack : Pack the file into an .uw8 cart before running it and print the resulting size. -p, --pack : Pack the file into an .uw8 cart before running it and print the resulting size.

View File

@@ -1,5 +1,4 @@
import "env.memory" memory(4); include "../include/microw8-api.cwa"
import "env.printString" fn printString(i32);
export fn upd() { export fn upd() {
printString(0x20000); printString(0x20000);

View File

@@ -1,11 +1,4 @@
import "env.time" fn time() -> f32; include "../include/microw8-api.cwa"
import "env.circle" fn circle(f32, f32, f32, i32);
import "env.cls" fn cls(i32);
import "env.randomSeed" fn seed(i32);
import "env.randomf" fn randomf() -> f32;
import "env.sin" fn sin(f32) -> f32;
import "env.cos" fn cos(f32) -> f32;
import "env.fmod" fn fmod(f32, f32) -> f32;
export fn upd() { export fn upd() {
cls(0); cls(0);
@@ -15,10 +8,10 @@ export fn upd() {
let inline rocket = i #>> 9; let inline rocket = i #>> 9;
let lazy local_time = fmod(time() + rocket as f32 / 5 as f32, 2 as f32); let lazy local_time = fmod(time() + rocket as f32 / 5 as f32, 2 as f32);
let lazy rocket = rocket + nearest(time() - local_time) as i32 * 10; let lazy rocket = rocket + nearest(time() - local_time) as i32 * 10;
seed(rocket); randomSeed(rocket);
let inline x = randomf() * 645 as f32; let inline x = randomf() * 645 as f32;
let y = randomf() * 133 as f32; let y = randomf() * 133 as f32;
let lazy angle = { seed(i); randomf() } * 44 as f32; let lazy angle = { randomSeed(i); randomf() } * 44 as f32;
let inline dx = sin(angle); let inline dx = sin(angle);
let inline dy = cos(angle); let inline dy = cos(angle);
let lazy dist = local_time * (randomf() * 44 as f32); let lazy dist = local_time * (randomf() * 44 as f32);

View File

@@ -1,38 +1,30 @@
import "env.memory" memory(4); include "../include/microw8-api.cwa"
import "env.cls" fn cls(i32);
import "env.printString" fn printString(i32);
import "env.printChar" fn printChar(i32);
import "env.setCursorPosition" fn setCursor(i32, i32);
import "env.setTextColor" fn setTextColor(i32);
import "env.line" fn line(f32, f32, f32, f32, i32);
import "env.isButtonTriggered" fn triggered(i32) -> i32;
global mut mode: i32 = 0; global mut mode: i32 = 0;
export fn upd() { export fn upd() {
cls(0); cls(0);
if triggered(4) { if isButtonTriggered(BUTTON_A) {
mode = !mode; mode = !mode;
} }
setTextColor(15); setTextColor(15);
printString(mode * 0x20000); printString(mode * USER_MEM);
let y: i32; let y: i32;
loop y { loop y {
line(0 as f32, (y * 9 + 39) as f32, (14+16*9) as f32, (y * 9 + 39) as f32, 1); line(0 as f32, (y * 9 + 39) as f32, (14+16*9) as f32, (y * 9 + 39) as f32, 1);
line((y * 9 + 15) as f32, 24 as f32, (y * 9 + 15) as f32, (38+16*9) as f32, 1); line((y * 9 + 15) as f32, 24 as f32, (y * 9 + 15) as f32, (38+16*9) as f32, 1);
setTextColor(15); setTextColor(15);
setCursor(y * 9 + 16, 24); setCursorPosition(y * 9 + 16, 24);
let lazy hexChar = select(y < 10, y + 48, y + 87); let lazy hexChar = select(y < 10, y + 48, y + 87);
printChar(hexChar); printChar(hexChar);
setCursor(0, y * 9 + 24+16); setCursorPosition(0, y * 9 + 24+16);
printChar(hexChar); printChar(hexChar);
let x = 0; let x = 0;
loop x { loop x {
setCursor(x * 9 + 16, y * 9 + 24+16); setCursorPosition(x * 9 + 16, y * 9 + 24+16);
setTextColor(select(mode, x + y * 16, -9)); setTextColor(select(mode, x + y * 16, -9));
if y >= 2 | mode { if y >= 2 | mode {
printChar(select(mode, 0xa4, x + y * 16)); printChar(select(mode, 0xa4, x + y * 16));
@@ -47,6 +39,6 @@ data 0 {
"Default font: (press " i8(0xcc) " for palette)" i8(5, 0) "Default font: (press " i8(0xcc) " for palette)" i8(5, 0)
} }
data 0x20000 { data USER_MEM {
"Default palette: (press " i8(0xcc) " for font)" i8(5, 0) "Default palette: (press " i8(0xcc) " for font)" i8(5, 0)
} }

View File

@@ -1,10 +1,4 @@
import "env.memory" memory(4); include "../include/microw8-api.cwa"
import "env.cls" fn cls(i32);
import "env.time" fn time() -> f32;
import "env.line" fn line(f32, f32, f32, f32, i32);
import "env.sin" fn sin(f32) -> f32;
import "env.cos" fn cos(f32) -> f32;
export fn upd() { export fn upd() {
cls(0); cls(0);

View File

@@ -1,12 +1,4 @@
import "env.memory" memory(4); include "../include/microw8-api.cwa"
import "env.rectangle" fn rect(f32, f32, f32, f32, i32);
import "env.circle" fn circle(f32, f32, f32, i32);
import "env.isButtonPressed" fn btn(i32) -> i32;
import "env.random" fn random() -> i32;
import "env.randomSeed" fn randomSeed(i32);
import "env.cls" fn cls(i32);
import "env.printInt" fn printInt(i32);
global mut pz: i32 = 4; global mut pz: i32 = 4;
global mut px: f32 = 2.0; global mut px: f32 = 2.0;
@@ -19,7 +11,7 @@ export fn upd() {
let inline zero = 0.0; let inline zero = 0.0;
let lazy control_speed = 0.03125; let lazy control_speed = 0.03125;
s = s + 0.1875 - (f + control_speed) * btn(4 <| cls(4)) as f32; s = s + 0.1875 - (f + control_speed) * isButtonPressed(4 <| cls(4)) as f32;
f = f * 0.5625; f = f * 0.5625;
printInt(pz); printInt(pz);
@@ -33,8 +25,8 @@ export fn upd() {
let inline c = (z & 1) * -2; let inline c = (z & 1) * -2;
let inline yf = y as f32; let inline yf = y as f32;
rect(rx, yf, rw, yf / 6 as f32, c + 1); rectangle(rx, yf, rw, yf / 6 as f32, c + 1);
rect(rx, yf, rw, 1 as f32, c - 4); rectangle(rx, yf, rw, 1 as f32, c - 4);
if y == 180 & py > zero { if y == 180 & py > zero {
if x > w | x < zero { if x > w | x < zero {
@@ -51,7 +43,7 @@ export fn upd() {
circle(160 as f32, 160 as f32 + py, 22 as f32, -28); circle(160 as f32, 160 as f32 + py, 22 as f32, -28);
circle((160 - 6) as f32, (160 - 6) as f32 + py, 6 as f32, -26); circle((160 - 6) as f32, (160 - 6) as f32 + py, 6 as f32, -26);
px = px + (btn(3) - btn(2)) as f32 * control_speed; px = px + (isButtonPressed(3) - isButtonPressed(2)) as f32 * control_speed;
py = py + s; py = py + s;
pz = pz + 1; pz = pz + 1;
} }

View File

@@ -1,25 +1,26 @@
import "env.memory" memory(4); include "../include/microw8-api.cwa"
import "env.sin" fn sin(f32) -> f32;
import "env.time" fn time() -> f32;
export fn upd() { export fn upd() {
let i: i32; let x: i32;
let y: i32;
loop screen { loop screen {
let inline t = time() / 2 as f32; let inline t = time() / 2 as f32;
let lazy o = sin(t) * 0.8; let lazy o = sin(t) * 0.75;
let lazy q = (i % 320) as f32 - 160.1; let inline q = x as f32 - 160.5;
let lazy w = (i / 320 - 120) as f32; let inline w = (y - 120) as f32;
let lazy r = sqrt(q*q + w*w); let lazy r = sqrt(q*q + w*w);
let lazy z = q / r; let lazy z = q / r;
let lazy s = z * o + sqrt(z * z * o * o + 1 as f32 - o * o); let lazy s = z * o + sqrt(z * z * o * o + 1 as f32 - o * o);
let lazy q2 = (z * s - o) * 10 as f32 + t; let inline q2 = (z * s - o) * 10 as f32 + t;
let lazy w2 = w / r * s * 10 as f32 + t; let inline w2 = w / r * s * 10 as f32 + t;
let lazy s2 = s * 100 as f32 / r; let inline s2 = s * 100 as f32 / r;
i?120 = max( let inline color = max(
0 as f32, 0 as f32,
((q2 as i32 ^ w2 as i32 & ((s2 + time()) * 10 as f32) as i32) & 5) as f32 * ((q2 as i32 ^ w2 as i32 & ((s2 + time()) * 10 as f32) as i32) & 5) as f32 *
(4 as f32 - s2) as f32 (4 as f32 - s2) as f32
) as i32 - 32; ) as i32 - 32;
branch_if (i := i + 1) < 320*240: screen setPixel(x, y, color);
branch_if x := (x + 1) % 320: screen;
branch_if y := (y + 1) % 320: screen;
} }
} }

View File

@@ -1,7 +1,4 @@
import "env.memory" memory(2); include "../include/microw8-api.cwa"
import "env.fmod" fn fmod(f32, f32) -> f32;
import "env.time" fn time() -> f32;
export fn upd() { export fn upd() {
let i: i32; let i: i32;

View File

@@ -1,7 +1,4 @@
import "env.memory" memory(4); include "../include/microw8-api.cwa"
import "env.atan2" fn atan2(f32, f32) -> f32;
import "env.time" fn time() -> f32;
export fn upd() { export fn upd() {
let i: i32; let i: i32;
@@ -12,7 +9,7 @@ export fn upd() {
let inline d = 40000 as f32 / sqrt(x * x + y * y); let inline d = 40000 as f32 / sqrt(x * x + y * y);
let inline u = atan2(x, y) * (512.0 / 3.141); let inline u = atan2(x, y) * (512.0 / 3.141);
let inline c = ((i32.trunc_sat_f32_s(d + t * 2 as f32) ^ i32.trunc_sat_f32_s(u + t)) & 255) >> 4; let inline c = ((i32.trunc_sat_f32_s(d + t * 2 as f32) ^ i32.trunc_sat_f32_s(u + t)) & 255) >> 4;
i?120 = c; i?FRAMEBUFFER = c;
branch_if (i := i + 1) < 320*240: pixels; branch_if (i := i + 1) < 320*240: pixels;
} }

View File

@@ -0,0 +1,50 @@
// MicroW8 APIs, to be `include`d in CurlyWas sources
import "env.memory" memory(4);
import "env.sin" fn sin(f32) -> f32;
import "env.cos" fn cos(f32) -> f32;
import "env.tan" fn tan(f32) -> f32;
import "env.asin" fn asin(f32) -> f32;
import "env.acos" fn acos(f32) -> f32;
import "env.atan" fn atan(f32) -> f32;
import "env.atan2" fn atan2(f32, f32) -> f32;
import "env.pow" fn pow(f32, f32) -> f32;
import "env.log" fn log(f32) -> f32;
import "env.fmod" fn fmod(f32, f32) -> f32;
import "env.random" fn random() -> i32;
import "env.randomf" fn randomf() -> f32;
import "env.randomSeed" fn randomSeed(i32);
import "env.cls" fn cls(i32);
import "env.setPixel" fn setPixel(i32, i32, i32);
import "env.getPixel" fn getPixel(i32, i32) -> i32;
import "env.hline" fn hline(i32, i32, i32, i32);
import "env.rectangle" fn rectangle(f32, f32, f32, f32, i32);
import "env.circle" fn circle(f32, f32, f32, i32);
import "env.line" fn line(f32, f32, f32, f32, i32);
import "env.time" fn time() -> f32;
import "env.isButtonPressed" fn isButtonPressed(i32) -> i32;
import "env.isButtonTriggered" fn isButtonTriggered(i32) -> i32;
import "env.printChar" fn printChar(i32);
import "env.printString" fn printString(i32);
import "env.printInt" fn printInt(i32);
import "env.setTextColor" fn setTextColor(i32);
import "env.setBackgroundColor" fn setBackgroundColor(i32);
import "env.setCursorPosition" fn setCursorPosition(i32, i32);
import "env.rectangle_outline" fn rectangle_outline(f32, f32, f32, f32, i32);
import "env.circle_outline" fn circle_outline(f32, f32, f32, i32);
import "env.exp" fn exp(f32) -> f32;
const TIME_MS = 0x40;
const GAMEPAD = 0x44;
const FRAMEBUFFER = 0x78;
const PALETTE = 0x13000;
const FONT = 0x13400;
const USER_MEM = 0x14000;
const BUTTON_UP = 0x0;
const BUTTON_DOWN = 0x1;
const BUTTON_LEFT = 0x2;
const BUTTON_RIGHT = 0x3;
const BUTTON_A = 0x4;
const BUTTON_B = 0x5;
const BUTTON_X = 0x6;
const BUTTON_Y = 0x7;

View File

@@ -0,0 +1,52 @@
;; MicroW8 APIs, in WAT (Wasm Text) format
(import "env" "memory" (memory 4))
(import "env" "sin" (func $sin (param f32) (result f32)))
(import "env" "cos" (func $cos (param f32) (result f32)))
(import "env" "tan" (func $tan (param f32) (result f32)))
(import "env" "asin" (func $asin (param f32) (result f32)))
(import "env" "acos" (func $acos (param f32) (result f32)))
(import "env" "atan" (func $atan (param f32) (result f32)))
(import "env" "atan2" (func $atan2 (param f32) (param f32) (result f32)))
(import "env" "pow" (func $pow (param f32) (param f32) (result f32)))
(import "env" "log" (func $log (param f32) (result f32)))
(import "env" "fmod" (func $fmod (param f32) (param f32) (result f32)))
(import "env" "random" (func $random (result i32)))
(import "env" "randomf" (func $randomf (result f32)))
(import "env" "randomSeed" (func $randomSeed (param i32)))
(import "env" "cls" (func $cls (param i32)))
(import "env" "setPixel" (func $setPixel (param i32) (param i32) (param i32)))
(import "env" "getPixel" (func $getPixel (param i32) (param i32) (result i32)))
(import "env" "hline" (func $hline (param i32) (param i32) (param i32) (param i32)))
(import "env" "rectangle" (func $rectangle (param f32) (param f32) (param f32) (param f32) (param i32)))
(import "env" "circle" (func $circle (param f32) (param f32) (param f32) (param i32)))
(import "env" "line" (func $line (param f32) (param f32) (param f32) (param f32) (param i32)))
(import "env" "time" (func $time (result f32)))
(import "env" "isButtonPressed" (func $isButtonPressed (param i32) (result i32)))
(import "env" "isButtonTriggered" (func $isButtonTriggered (param i32) (result i32)))
(import "env" "printChar" (func $printChar (param i32)))
(import "env" "printString" (func $printString (param i32)))
(import "env" "printInt" (func $printInt (param i32)))
(import "env" "setTextColor" (func $setTextColor (param i32)))
(import "env" "setBackgroundColor" (func $setBackgroundColor (param i32)))
(import "env" "setCursorPosition" (func $setCursorPosition (param i32) (param i32)))
(import "env" "rectangle_outline" (func $rectangle_outline (param f32) (param f32) (param f32) (param f32) (param i32)))
(import "env" "circle_outline" (func $circle_outline (param f32) (param f32) (param f32) (param i32)))
(import "env" "exp" (func $exp (param f32) (result f32)))
;; to use defines, include this file with a preprocessor
;; like gpp (https://logological.org/gpp).
#define TIME_MS 0x40;
#define GAMEPAD 0x44;
#define FRAMEBUFFER 0x78;
#define PALETTE 0x13000;
#define FONT 0x13400;
#define USER_MEM 0x14000;
#define BUTTON_UP 0x0;
#define BUTTON_DOWN 0x1;
#define BUTTON_LEFT 0x2;
#define BUTTON_RIGHT 0x3;
#define BUTTON_A 0x4;
#define BUTTON_B 0x5;
#define BUTTON_X 0x6;
#define BUTTON_Y 0x7;

127
platform/Cargo.lock generated
View File

@@ -79,9 +79,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]] [[package]]
name = "chumsky" name = "chumsky"
version = "0.5.0" version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2d3efff85e8572b1c3fa0127706af58c4fff8458f8d9436d54b1e97573c7a3f" checksum = "8d02796e4586c6c41aeb68eae9bfb4558a522c35f1430c14b40136c3706e09e4"
dependencies = [ dependencies = [
"ahash 0.3.8", "ahash 0.3.8",
] ]
@@ -146,14 +146,14 @@ checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7"
[[package]] [[package]]
name = "curlywas" name = "curlywas"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/exoticorn/curlywas.git?rev=196719b#196719b35ef377cb7e001554b27ac5de013dcf2b" source = "git+https://github.com/exoticorn/curlywas.git?rev=89638565#896385654ab2c089200920b6dea4abec641c88d6"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"ariadne", "ariadne",
"chumsky", "chumsky",
"pico-args", "pico-args",
"wasm-encoder", "wasm-encoder 0.10.0",
"wasmparser", "wasmparser 0.83.0",
] ]
[[package]] [[package]]
@@ -197,6 +197,21 @@ dependencies = [
"ahash 0.7.6", "ahash 0.7.6",
] ]
[[package]]
name = "heck"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c"
dependencies = [
"unicode-segmentation",
]
[[package]]
name = "id-arena"
version = "2.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "25a2bc672d1148e28034f176e01fffebb08b35768468cc954630da77a1449005"
[[package]] [[package]]
name = "lazy_static" name = "lazy_static"
version = "1.4.0" version = "1.4.0"
@@ -227,6 +242,15 @@ dependencies = [
"rgb", "rgb",
] ]
[[package]]
name = "log"
version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
dependencies = [
"cfg-if",
]
[[package]] [[package]]
name = "miniz_oxide" name = "miniz_oxide"
version = "0.4.4" version = "0.4.4"
@@ -286,6 +310,24 @@ version = "0.5.19"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5"
[[package]]
name = "proc-macro2"
version = "1.0.36"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029"
dependencies = [
"unicode-xid",
]
[[package]]
name = "quote"
version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "864d3e96a899863136fc6e99f3d7cae289dafe43bf2c5ac19b70df7210c0a145"
dependencies = [
"proc-macro2",
]
[[package]] [[package]]
name = "rgb" name = "rgb"
version = "0.8.31" version = "0.8.31"
@@ -304,6 +346,17 @@ dependencies = [
"num-traits", "num-traits",
] ]
[[package]]
name = "syn"
version = "1.0.86"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a65b3f4ffa0092e9887669db0eae07941f023991ab58ea44da8fe8e2d511c6b"
dependencies = [
"proc-macro2",
"quote",
"unicode-xid",
]
[[package]] [[package]]
name = "time" name = "time"
version = "0.1.43" version = "0.1.43"
@@ -323,6 +376,18 @@ dependencies = [
"crunchy", "crunchy",
] ]
[[package]]
name = "unicode-segmentation"
version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e8820f5d777f6224dc4be3632222971ac30164d4a258d595640799554ebfd99"
[[package]]
name = "unicode-xid"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
[[package]] [[package]]
name = "upkr" name = "upkr"
version = "0.1.0" version = "0.1.0"
@@ -342,8 +407,9 @@ dependencies = [
"pbr", "pbr",
"pico-args", "pico-args",
"upkr", "upkr",
"wasm-encoder", "walrus",
"wasmparser", "wasm-encoder 0.8.0",
"wasmparser 0.81.0",
] ]
[[package]] [[package]]
@@ -352,6 +418,32 @@ version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe"
[[package]]
name = "walrus"
version = "0.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4eb08e48cde54c05f363d984bb54ce374f49e242def9468d2e1b6c2372d291f8"
dependencies = [
"anyhow",
"id-arena",
"leb128",
"log",
"walrus-macro",
"wasmparser 0.77.0",
]
[[package]]
name = "walrus-macro"
version = "0.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0a6e5bd22c71e77d60140b0bd5be56155a37e5bd14e24f5f87298040d0cc40d7"
dependencies = [
"heck",
"proc-macro2",
"quote",
"syn",
]
[[package]] [[package]]
name = "wasi" name = "wasi"
version = "0.10.2+wasi-snapshot-preview1" version = "0.10.2+wasi-snapshot-preview1"
@@ -367,12 +459,33 @@ dependencies = [
"leb128", "leb128",
] ]
[[package]]
name = "wasm-encoder"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa9d9bf45fc46f71c407837c9b30b1e874197f2dc357588430b21e5017d290ab"
dependencies = [
"leb128",
]
[[package]]
name = "wasmparser"
version = "0.77.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b35c86d22e720a07d954ebbed772d01180501afe7d03d464f413bb5f8914a8d6"
[[package]] [[package]]
name = "wasmparser" name = "wasmparser"
version = "0.81.0" version = "0.81.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "98930446519f63d00a836efdc22f67766ceae8dbcc1571379f2bcabc6b2b9abc" checksum = "98930446519f63d00a836efdc22f67766ceae8dbcc1571379f2bcabc6b2b9abc"
[[package]]
name = "wasmparser"
version = "0.83.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "718ed7c55c2add6548cca3ddd6383d738cd73b892df400e96b9aa876f0141d7a"
[[package]] [[package]]
name = "winapi" name = "winapi"
version = "0.3.9" version = "0.3.9"

View File

@@ -6,7 +6,7 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
curlywas = { git="https://github.com/exoticorn/curlywas.git", rev="196719b" } curlywas = { git="https://github.com/exoticorn/curlywas.git", rev="89638565" }
uw8-tool = { path="../uw8-tool" } uw8-tool = { path="../uw8-tool" }
anyhow = "1" anyhow = "1"
lodepng = "3.4" lodepng = "3.4"

Binary file not shown.

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

View File

@@ -12,15 +12,15 @@ fn main() -> Result<()> {
println!("Compiling loader module"); println!("Compiling loader module");
let loader = curlywas::compile_file("src/loader.cwa", curlywas::Options::default())?; let loader = curlywas::compile_file("src/loader.cwa", curlywas::Options::default())?;
File::create("bin/loader.wasm")?.write_all(&loader)?; File::create("bin/loader.wasm")?.write_all(&loader.wasm)?;
println!("Loader (including base module): {} bytes", loader.len()); println!("Loader (including base module): {} bytes", loader.wasm.len());
println!("Compiling platform module"); println!("Compiling platform module");
let platform = curlywas::compile_file("src/platform.cwa", curlywas::Options::default())?; let platform = curlywas::compile_file("src/platform.cwa", curlywas::Options::default())?;
println!("Compressing platform module"); println!("Compressing platform module");
let platform = uw8_tool::pack( let platform = uw8_tool::pack(
&platform, &platform.wasm,
&uw8_tool::PackConfig::default().with_compression_level(4), &uw8_tool::PackConfig::default().with_compression_level(4),
)?; )?;
File::create("bin/platform.uw8")?.write_all(&platform)?; File::create("bin/platform.uw8")?.write_all(&platform)?;

View File

@@ -256,6 +256,11 @@ export fn line(x1: f32, y1: f32, x2: f32, y2: f32, col: i32) {
p = y1; p = y1;
} }
if max_axis == 0 as f32 {
setPixel(x1 as i32, y1 as i32, col);
return;
}
let steps = floor(p + max_axis) as i32 - floor(p) as i32; let steps = floor(p + max_axis) as i32 - floor(p) as i32;
p = floor(p) + 0.5 - p; p = floor(p) + 0.5 - p;
if max_axis < 0 as f32 { if max_axis < 0 as f32 {
@@ -267,7 +272,7 @@ export fn line(x1: f32, y1: f32, x2: f32, y2: f32, col: i32) {
dy = dy / max_axis; dy = dy / max_axis;
let f = min(max_axis, max(0 as f32, p)); let f = min(max_axis, max(0 as f32, p));
setPixel((x1 + f * dx) as i32, (y1 + f * dy) as i32, col); setPixel(i32.trunc_sat_f32_s(x1 + f * dx), i32.trunc_sat_f32_s(y1 + f * dy), col);
if !steps { if !steps {
return; return;
@@ -280,7 +285,7 @@ export fn line(x1: f32, y1: f32, x2: f32, y2: f32, col: i32) {
loop pixels { loop pixels {
if steps := steps - 1 { if steps := steps - 1 {
setPixel(x1 as i32, y1 as i32, col); setPixel(i32.trunc_sat_f32_s(x1), i32.trunc_sat_f32_s(y1), col);
x1 = x1 + dx; x1 = x1 + dx;
y1 = y1 + dy; y1 = y1 + dy;
branch pixels; branch pixels;
@@ -288,7 +293,7 @@ export fn line(x1: f32, y1: f32, x2: f32, y2: f32, col: i32) {
} }
f = min(max_axis, p) - p; f = min(max_axis, p) - p;
setPixel((x1 + f * dx) as i32, (y1 + f * dy) as i32, col); setPixel(i32.trunc_sat_f32_s(x1 + f * dx), i32.trunc_sat_f32_s(y1 + f * dy), col);
} }
////////// //////////

View File

@@ -36,6 +36,8 @@ for dir in build/*; do
cp $example $dir/examples cp $example $dir/examples
done done
cp -r ../examples/include $dir/include
mkdir $dir/carts mkdir $dir/carts
for example in $dir/examples/*; do for example in $dir/examples/*; do
build/microw8-linux/uw8 pack -l 9 $example $dir/carts/$(basename ${example%.*}).uw8 build/microw8-linux/uw8 pack -l 9 $example $dir/carts/$(basename ${example%.*}).uw8

View File

@@ -15,11 +15,11 @@ The initial motivation behind MicroW8 was to explore whether there was a way to
* Gamepad input (D-Pad + 4 Buttons) * Gamepad input (D-Pad + 4 Buttons)
## Examples ## Examples
* [Fireworks](v0.1.1#AgwvgP+M59snqjl4CMKw5sqm1Zw9yJCbSviMjeLUdHus2a3yl/a99+uiBeqZgP/2jqSjrLjRk73COMM6OSLpsxK8ugT1kuk/q4hQUqqPpGozHoa0laulzGGcahzdfdJsYaK1sIdeIYS9M5PnJx/Wk9H+PvWEPy2Zvv7I6IW7Fg==) (127 bytes): Some fireworks to welcome 2022. * [Fireworks](v0.1.2#AgwvgP+M59snqjl4CMKw5sqm1Zw9yJCbSviMjeLUdHus2a3yl/a99+uiBeqZgP/2jqSjrLjRk73COMM6OSLpsxK8ugT1kuk/q4hQUqqPpGozHoa0laulzGGcahzdfdJsYaK1sIdeIYS9M5PnJx/Wk9H+PvWEPy2Zvv7I6IW7Fg==) (127 bytes): Some fireworks to welcome 2022.
* [Skip Ahead](v0.1.1#AgyfpZ80wkW28kiUZ9VIK4v+RPnVxqjK1dz2BcDoNyQPsS2g4OgEzkTe6jyoAfFOmqKrS8SM2aRljBal9mjNn8i4fP9eBK+RehQKxxGtJa9FqftvqEnh3ez1YaYxqj7jgTdzJ/WAYVmKMovBT1myrX3FamqKSOgMsNedLhVTLAhQup3sNcYEjGNo8b0HZ5+AgMgCwYRGCe//XQOMAaAAzqDILgmpEZ/43RKHcQpHEQwbURfNQJpadJe2sz3q5FlQnTGXQ9oSMokidhlC+aR/IpNHieuBGLhFZ2GfnwVQ0geBbQpTPA==) (229 bytes): A port of my [TIC-80 256byte game](http://tic80.com/play?cart=1735) from LoveByte'21 * [Skip Ahead](v0.1.2#AgyfpZ80wkW28kiUZ9VIK4v+RPnVxqjK1dz2BcDoNyQPsS2g4OgEzkTe6jyoAfFOmqKrS8SM2aRljBal9mjNn8i4fP9eBK+RehQKxxGtJa9FqftvqEnh3ez1YaYxqj7jgTdzJ/WAYVmKMovBT1myrX3FamqKSOgMsNedLhVTLAhQup3sNcYEjGNo8b0HZ5+AgMgCwYRGCe//XQOMAaAAzqDILgmpEZ/43RKHcQpHEQwbURfNQJpadJe2sz3q5FlQnTGXQ9oSMokidhlC+aR/IpNHieuBGLhFZ2GfnwVQ0geBbQpTPA==) (229 bytes): A port of my [TIC-80 256byte game](http://tic80.com/play?cart=1735) from LoveByte'21
* [OhNoAnotherTunnel](v0.1.1#AgPP1oEFvPzY/rBZwTumtYn37zeMFgpir1Bkn91jsNcp26VzoUpkAOOJTtnzVBfW+/dGnnIdbq/irBUJztY5wuua80DORTYZndgdwZHcSk15ajc4nyO0g1A6kGWyW56oZk0iPYJA9WtUmoj0Plvy1CGwIZrMe57X7QZcdqc3u6zjTA41Tpiqi9vnO3xbhi8o594Vx0XPXwVzpYq1ZCTYenfAGaXKkDmAFJqiVIsiCg==) (175 bytes): A port of my [entry](http://tic80.com/play?cart=1871) in the Outline'21 bytebattle final * [OhNoAnotherTunnel](v0.1.2#AgPP1oEFvPzY/rBZwTumtYn37zeMFgpir1Bkn91jsNcp26VzoUpkAOOJTtnzVBfW+/dGnnIdbq/irBUJztY5wuua80DORTYZndgdwZHcSk15ajc4nyO0g1A6kGWyW56oZk0iPYJA9WtUmoj0Plvy1CGwIZrMe57X7QZcdqc3u6zjTA41Tpiqi9vnO3xbhi8o594Vx0XPXwVzpYq1ZCTYenfAGaXKkDmAFJqiVIsiCg==) (175 bytes): A port of my [entry](http://tic80.com/play?cart=1871) in the Outline'21 bytebattle final
* [Technotunnel](v0.1.1#AhPXpq894LaUhp5+HQf39f39/Jc8g5zUrBSc0uyKh36ivskczhY84h55zL8gWpkdvKuRQI+KIt80isKzh8jkM8nILcx0RUvyk8yjE8TgNsgkcORVI0RY5k3qE4ySjaycxa2DVZH61UWZuLsCouuwT7I80TbmmetQSbMywJ/avrrCZIAH0UzQfvOiCJNG48NI0FFY1vjB7a7dcp8Uqg==) (157 bytes): A port of my [entry](https://tic80.com/play?cart=1873) in the Outline'21 bytebattle quater final * [Technotunnel](v0.1.2#AhPXpq894LaUhp5+HQf39f39/Jc8g5zUrBSc0uyKh36ivskczhY84h55zL8gWpkdvKuRQI+KIt80isKzh8jkM8nILcx0RUvyk8yjE8TgNsgkcORVI0RY5k3qE4ySjaycxa2DVZH61UWZuLsCouuwT7I80TbmmetQSbMywJ/avrrCZIAH0UzQfvOiCJNG48NI0FFY1vjB7a7dcp8Uqg==) (157 bytes): A port of my [entry](https://tic80.com/play?cart=1873) in the Outline'21 bytebattle quater final
* [Font & Palette](v0.1.1#At/p39+IBnj6ry1TRe7jzVy2A4tXgBvmoW2itzoyF2aM28pGy5QDiKxqrk8l9sbWZLtnAb+jgOfU+9QhpuyCAkhN6gPOU481IUL/df96vNe3h288Dqwhd3sfFpothIVFsMwRK72kW2hiR7zWsaXyy5pNmjR6BJk4piWx9ApT1ZwoUajhk6/zij6itq/FD1U3jj/J3MOwqZ2ef8Bv6ZPQlJIYVf62icGa69wS6SI1qBpIFiF14F8PcztRVbKIxLpT4ArCS6nz6FPnyUkqATGSBNPJ): Just a simple viewer for the default font and palette. * [Font & Palette](v0.1.2#At/p39+IBnj6ry1TRe7jzVy2A4tXgBvmoW2itzoyF2aM28pGy5QDiKxqrk8l9sbWZLtnAb+jgOfU+9QhpuyCAkhN6gPOU481IUL/df96vNe3h288Dqwhd3sfFpothIVFsMwRK72kW2hiR7zWsaXyy5pNmjR6BJk4piWx9ApT1ZwoUajhk6/zij6itq/FD1U3jj/J3MOwqZ2ef8Bv6ZPQlJIYVf62icGa69wS6SI1qBpIFiF14F8PcztRVbKIxLpT4ArCS6nz6FPnyUkqATGSBNPJ): Just a simple viewer for the default font and palette.
Examplers for older versions: Examplers for older versions:
@@ -29,6 +29,38 @@ Examplers for older versions:
## Versions ## Versions
### v0.1.2
* [Web runtime](v0.1.2)
* [Linux](https://github.com/exoticorn/microw8/releases/download/v0.1.2/microw8-0.1.2-linux.tgz)
* [MacOS](https://github.com/exoticorn/microw8/releases/download/v0.1.2/microw8-0.1.2-macos.tgz)
* [Windows](https://github.com/exoticorn/microw8/releases/download/v0.1.2/microw8-0.1.2-windows.zip)
Changes:
* add option to `uw8 run` to run the cart in the browser using the web runtime
* CurlyWas: implement `include` support
* CurlyWas: implement support for constants
* fix crash when trying to draw zero sized line
### v0.1.1
* [Web runtime](v0.1.1)
* [Linux](https://github.com/exoticorn/microw8/releases/download/v0.1.1/microw8-0.1.1-linux.tgz)
* [MacOS](https://github.com/exoticorn/microw8/releases/download/v0.1.1/microw8-0.1.1-macos.tgz)
* [Windows](https://github.com/exoticorn/microw8/releases/download/v0.1.1/microw8-0.1.1-windows.zip)
Changes:
* implement more robust file watcher
* add basic video recording on F10 in web runtime
* add screenshot on F9
* add watchdog to interrupt hanging update in native runtime
* add devkit mode to web runtime
* add unpack and compile commands to uw8
* add support for table/element section in pack command
* disable wayland support (caused missing window decorations in gnome)
### v0.1.0 ### v0.1.0
* [Web runtime](v0.1.0) * [Web runtime](v0.1.0)

View File

@@ -47,7 +47,7 @@ Returns the arccosine of `x`.
Returns the arctangent of `x`. Returns the arctangent of `x`.
### fn atan2(y: f32, y: f32) -> f32 ### fn atan2(y: f32, x: f32) -> f32
Returns the angle between the point `(x, y)` and the positive x-axis. Returns the angle between the point `(x, y)` and the positive x-axis.
@@ -284,7 +284,8 @@ Runs `<file>` which can be a binary WebAssembly module, an `.uw8` cart, a wat (W
Options: Options:
* `-t FRAMES`, `--timeout FRAMES` : Sets the timeout in frames (1/60s). If the start or update function runs longer than this it is forcibly interupted * `-b`, `--browser`: Run in browser instead of using native runtime
* `-t FRAMES`, `--timeout FRAMES`: Sets the timeout in frames (1/60s). If the start or update function runs longer than this it is forcibly interupted
and execution of the cart is stopped. Defaults to 30 (0.5s) and execution of the cart is stopped. Defaults to 30 (0.5s)
* `-w`, `--watch`: Reloads the given file every time it changes on disk. * `-w`, `--watch`: Reloads the given file every time it changes on disk.
* `-p`, `--pack`: Pack the file into an `.uw8` cart before running it and print the resulting size. * `-p`, `--pack`: Pack the file into an `.uw8` cart before running it and print the resulting size.

File diff suppressed because one or more lines are too long

View File

@@ -1,23 +1,38 @@
use anyhow::{bail, Result}; use anyhow::{anyhow, bail, Result};
use notify::{DebouncedEvent, Watcher, RecommendedWatcher}; use notify::{DebouncedEvent, RecommendedWatcher, Watcher};
use std::{ use std::{collections::BTreeSet, path::PathBuf, sync::mpsc, time::Duration};
collections::BTreeSet,
path::{Path, PathBuf},
sync::mpsc,
time::Duration,
};
pub struct FileWatcher { pub struct FileWatcher {
_watcher: RecommendedWatcher, watcher: RecommendedWatcher,
watched_files: BTreeSet<PathBuf>, watched_files: BTreeSet<PathBuf>,
directories: BTreeSet<PathBuf>,
rx: mpsc::Receiver<DebouncedEvent>, rx: mpsc::Receiver<DebouncedEvent>,
} }
pub struct FileWatcherBuilder(BTreeSet<PathBuf>);
impl FileWatcher { impl FileWatcher {
pub fn builder() -> FileWatcherBuilder { pub fn new() -> Result<FileWatcher> {
FileWatcherBuilder(BTreeSet::new()) let (tx, rx) = mpsc::channel();
let watcher = notify::watcher(tx, Duration::from_millis(100))?;
Ok(FileWatcher {
watcher,
watched_files: BTreeSet::new(),
directories: BTreeSet::new(),
rx,
})
}
pub fn add_file<P: Into<PathBuf>>(&mut self, path: P) -> Result<()> {
let path = path.into();
let parent = path.parent().ok_or_else(|| anyhow!("File has no parent"))?;
if !self.directories.contains(parent) {
self.watcher
.watch(parent, notify::RecursiveMode::NonRecursive)?;
self.directories.insert(parent.to_path_buf());
}
self.watched_files.insert(path);
Ok(())
} }
pub fn poll_changed_file(&self) -> Result<Option<PathBuf>> { pub fn poll_changed_file(&self) -> Result<Option<PathBuf>> {
@@ -38,33 +53,3 @@ impl FileWatcher {
Ok(None) Ok(None)
} }
} }
impl FileWatcherBuilder {
pub fn add_file<P: Into<PathBuf>>(&mut self, path: P) -> &mut Self {
self.0.insert(path.into());
self
}
pub fn build(self) -> Result<FileWatcher> {
let mut directories: BTreeSet<&Path> = BTreeSet::new();
for file in &self.0 {
if let Some(directory) = file.parent() {
directories.insert(directory);
}
}
let (tx, rx) = mpsc::channel();
let mut watcher = notify::watcher(tx, Duration::from_millis(100))?;
for directory in directories {
watcher.watch(directory, notify::RecursiveMode::NonRecursive)?;
}
Ok(FileWatcher {
_watcher: watcher,
watched_files: self.0,
rx,
})
}
}

View File

@@ -1,270 +1,22 @@
use std::io::prelude::*;
use std::path::Path;
use std::sync::{Arc, Mutex};
use std::time::Duration;
use std::{fs::File, thread, time::Instant};
use anyhow::Result;
use minifb::{Key, Window, WindowOptions};
use wasmtime::{
Engine, GlobalType, Memory, MemoryType, Module, Mutability, Store, TypedFunc, ValType,
};
mod filewatcher; mod filewatcher;
#[cfg(feature = "native")]
mod run_native;
#[cfg(feature = "browser")]
mod run_web;
pub use filewatcher::FileWatcher; pub use filewatcher::FileWatcher;
#[cfg(feature = "native")]
pub use run_native::MicroW8;
#[cfg(feature = "browser")]
pub use run_web::RunWebServer;
static GAMEPAD_KEYS: &'static [Key] = &[ use anyhow::Result;
Key::Up,
Key::Down,
Key::Left,
Key::Right,
Key::Z,
Key::X,
Key::A,
Key::S,
];
pub struct MicroW8 { pub trait Runtime {
engine: Engine, fn is_open(&self) -> bool;
loader_module: Module, fn set_timeout(&mut self, _timeout: u32) {
window: Window, eprintln!("Warning: runtime doesn't support timeout");
window_buffer: Vec<u32>,
instance: Option<UW8Instance>,
timeout: u32,
}
struct UW8Instance {
store: Store<()>,
memory: Memory,
end_frame: TypedFunc<(), ()>,
update: TypedFunc<(), ()>,
start_time: Instant,
module: Vec<u8>,
watchdog: Arc<Mutex<UW8WatchDog>>,
}
impl Drop for UW8Instance {
fn drop(&mut self) {
if let Ok(mut watchdog) = self.watchdog.lock() {
watchdog.stop = true;
}
} }
} fn load(&mut self, module_data: &[u8]) -> Result<()>;
fn run_frame(&mut self) -> Result<()>;
struct UW8WatchDog { }
interupt: wasmtime::InterruptHandle,
timeout: u32,
stop: bool,
}
impl MicroW8 {
pub fn new() -> Result<MicroW8> {
let engine = wasmtime::Engine::new(wasmtime::Config::new().interruptable(true))?;
let loader_module =
wasmtime::Module::new(&engine, include_bytes!("../platform/bin/loader.wasm"))?;
let mut options = WindowOptions::default();
options.scale = minifb::Scale::X2;
options.scale_mode = minifb::ScaleMode::AspectRatioStretch;
options.resize = true;
let mut window = Window::new("MicroW8", 320, 240, options)?;
window.limit_update_rate(Some(std::time::Duration::from_micros(16666)));
Ok(MicroW8 {
engine,
loader_module,
window,
window_buffer: vec![0u32; 320 * 240],
instance: None,
timeout: 30,
})
}
pub fn is_open(&self) -> bool {
self.window.is_open() && !self.window.is_key_down(Key::Escape)
}
pub fn set_timeout(&mut self, timeout: u32) {
self.timeout = timeout;
}
fn reset(&mut self) {
self.instance = None;
for v in &mut self.window_buffer {
*v = 0;
}
}
pub fn load_from_file<P: AsRef<Path>>(&mut self, path: P) -> Result<()> {
self.reset();
let mut module = vec![];
File::open(path)?.read_to_end(&mut module)?;
self.load_from_memory(&module)
}
pub fn load_from_memory(&mut self, module_data: &[u8]) -> Result<()> {
self.reset();
let mut store = wasmtime::Store::new(&self.engine, ());
let memory = wasmtime::Memory::new(&mut store, MemoryType::new(4, Some(4)))?;
let mut linker = wasmtime::Linker::new(&self.engine);
linker.define("env", "memory", memory.clone())?;
let loader_instance = linker.instantiate(&mut store, &self.loader_module)?;
let load_uw8 = loader_instance.get_typed_func::<i32, i32, _>(&mut store, "load_uw8")?;
let platform_data = include_bytes!("../platform/bin/platform.uw8");
memory.data_mut(&mut store)[..platform_data.len()].copy_from_slice(platform_data);
let platform_length =
load_uw8.call(&mut store, platform_data.len() as i32)? as u32 as usize;
let platform_module =
wasmtime::Module::new(&self.engine, &memory.data(&store)[..platform_length])?;
memory.data_mut(&mut store)[..module_data.len()].copy_from_slice(module_data);
let module_length = load_uw8.call(&mut store, module_data.len() as i32)? as u32 as usize;
let module = wasmtime::Module::new(&self.engine, &memory.data(&store)[..module_length])?;
linker.func_wrap("env", "acos", |v: f32| v.acos())?;
linker.func_wrap("env", "asin", |v: f32| v.asin())?;
linker.func_wrap("env", "atan", |v: f32| v.atan())?;
linker.func_wrap("env", "atan2", |x: f32, y: f32| x.atan2(y))?;
linker.func_wrap("env", "cos", |v: f32| v.cos())?;
linker.func_wrap("env", "exp", |v: f32| v.exp())?;
linker.func_wrap("env", "log", |v: f32| v.ln())?;
linker.func_wrap("env", "sin", |v: f32| v.sin())?;
linker.func_wrap("env", "tan", |v: f32| v.tan())?;
linker.func_wrap("env", "pow", |a: f32, b: f32| a.powf(b))?;
for i in 9..64 {
linker.func_wrap("env", &format!("reserved{}", i), || {})?;
}
for i in 0..16 {
linker.define(
"env",
&format!("g_reserved{}", i),
wasmtime::Global::new(
&mut store,
GlobalType::new(ValType::I32, Mutability::Const),
0.into(),
)?,
)?;
}
let platform_instance = linker.instantiate(&mut store, &platform_module)?;
for export in platform_instance.exports(&mut store) {
linker.define(
"env",
export.name(),
export
.into_func()
.expect("platform surely only exports functions"),
)?;
}
let watchdog = Arc::new(Mutex::new(UW8WatchDog {
interupt: store.interrupt_handle()?,
timeout: self.timeout,
stop: false,
}));
{
let watchdog = watchdog.clone();
thread::spawn(move || loop {
thread::sleep(Duration::from_millis(17));
if let Ok(mut watchdog) = watchdog.lock() {
if watchdog.stop {
break;
}
if watchdog.timeout > 0 {
watchdog.timeout -= 1;
if watchdog.timeout == 0 {
watchdog.interupt.interrupt();
}
}
} else {
break;
}
});
}
let instance = linker.instantiate(&mut store, &module)?;
if let Ok(mut watchdog) = watchdog.lock() {
watchdog.timeout = 0;
}
let end_frame = platform_instance.get_typed_func::<(), (), _>(&mut store, "endFrame")?;
let update = instance.get_typed_func::<(), (), _>(&mut store, "upd")?;
self.instance = Some(UW8Instance {
store,
memory,
end_frame,
update,
start_time: Instant::now(),
module: module_data.into(),
watchdog,
});
Ok(())
}
pub fn run_frame(&mut self) -> Result<()> {
let mut result = Ok(());
if let Some(mut instance) = self.instance.take() {
{
let time = instance.start_time.elapsed().as_millis() as i32;
let mut gamepad: u32 = 0;
for key in self.window.get_keys() {
if let Some(index) = GAMEPAD_KEYS
.iter()
.enumerate()
.find(|(_, &k)| k == key)
.map(|(i, _)| i)
{
gamepad |= 1 << index;
}
}
let mem = instance.memory.data_mut(&mut instance.store);
mem[64..68].copy_from_slice(&time.to_le_bytes());
mem[68..72].copy_from_slice(&gamepad.to_le_bytes());
}
if let Ok(mut watchdog) = instance.watchdog.lock() {
watchdog.timeout = self.timeout;
}
result = instance.update.call(&mut instance.store, ());
if let Ok(mut watchdog) = instance.watchdog.lock() {
watchdog.timeout = 0;
}
instance.end_frame.call(&mut instance.store, ())?;
let memory = instance.memory.data(&instance.store);
let framebuffer = &memory[120..];
let palette = &memory[0x13000..];
for i in 0..320 * 240 {
let offset = framebuffer[i] as usize * 4;
self.window_buffer[i] = 0xff000000
| ((palette[offset + 0] as u32) << 16)
| ((palette[offset + 1] as u32) << 8)
| palette[offset + 2] as u32;
}
if self.window.is_key_pressed(Key::R, minifb::KeyRepeat::No) {
self.load_from_memory(&instance.module)?;
} else if result.is_ok() {
self.instance = Some(instance);
}
}
self.window
.update_with_buffer(&self.window_buffer, 320, 240)?;
result?;
Ok(())
}
}

View File

@@ -1,23 +1,26 @@
use std::fs::File; use std::fs::File;
use std::io::prelude::*; use std::io::prelude::*;
use std::path::{Path, PathBuf};
use std::process; use std::process;
use std::{
path::{Path, PathBuf},
process::exit,
};
use anyhow::Result; use anyhow::Result;
use pico_args::Arguments; use pico_args::Arguments;
use uw8::{FileWatcher, MicroW8}; #[cfg(feature = "native")]
use uw8::MicroW8;
#[cfg(feature = "browser")]
use uw8::RunWebServer;
#[cfg(any(feature = "native", feature = "browser"))]
use uw8::Runtime;
fn main() -> Result<()> { fn main() -> Result<()> {
let mut args = Arguments::from_env(); let mut args = Arguments::from_env();
match args.subcommand()?.as_ref().map(|s| s.as_str()) { match args.subcommand()?.as_deref() {
Some("version") => { Some("version") => {
println!("{}", env!("CARGO_PKG_VERSION")); println!("{}", env!("CARGO_PKG_VERSION"));
Ok(()) Ok(())
} }
#[cfg(any(feature = "native", feature = "browser"))]
Some("run") => run(args), Some("run") => run(args),
Some("pack") => pack(args), Some("pack") => pack(args),
Some("unpack") => unpack(args), Some("unpack") => unpack(args),
@@ -27,7 +30,8 @@ fn main() -> Result<()> {
println!("uw8 {}", env!("CARGO_PKG_VERSION")); println!("uw8 {}", env!("CARGO_PKG_VERSION"));
println!(); println!();
println!("Usage:"); println!("Usage:");
println!(" uw8 run [-t/--timeout <frames>] [-w/--watch] [-p/--pack] [-u/--uncompressed] [-l/--level] [-o/--output <out-file>] <file>"); #[cfg(any(feature = "native", feature = "browser"))]
println!(" uw8 run [-t/--timeout <frames>] [--b/--browser] [-w/--watch] [-p/--pack] [-u/--uncompressed] [-l/--level] [-o/--output <out-file>] <file>");
println!(" uw8 pack [-u/--uncompressed] [-l/--level] <in-file> <out-file>"); println!(" uw8 pack [-u/--uncompressed] [-l/--level] <in-file> <out-file>");
println!(" uw8 unpack <in-file> <out-file>"); println!(" uw8 unpack <in-file> <out-file>");
println!(" uw8 compile [-d/--debug] <in-file> <out-file>"); println!(" uw8 compile [-d/--debug] <in-file> <out-file>");
@@ -41,6 +45,7 @@ fn main() -> Result<()> {
} }
} }
#[cfg(any(feature = "native", feature = "browser"))]
fn run(mut args: Arguments) -> Result<()> { fn run(mut args: Arguments) -> Result<()> {
let watch_mode = args.contains(["-w", "--watch"]); let watch_mode = args.contains(["-w", "--watch"]);
let timeout: Option<u32> = args.opt_value_from_str(["-t", "--timeout"])?; let timeout: Option<u32> = args.opt_value_from_str(["-t", "--timeout"])?;
@@ -65,37 +70,56 @@ fn run(mut args: Arguments) -> Result<()> {
config.output_path = Some(path); config.output_path = Some(path);
} }
#[cfg(feature = "native")]
let run_browser = args.contains(["-b", "--browser"]);
#[cfg(not(feature = "native"))]
let run_browser = args.contains(["-b", "--browser"]) || true;
let filename = args.free_from_os_str::<PathBuf, bool>(|s| Ok(s.into()))?; let filename = args.free_from_os_str::<PathBuf, bool>(|s| Ok(s.into()))?;
let mut uw8 = MicroW8::new()?; let mut watcher = uw8::FileWatcher::new()?;
use std::process::exit;
let mut runtime: Box<dyn Runtime> = if !run_browser {
#[cfg(not(feature = "native"))]
unimplemented!();
#[cfg(feature = "native")]
Box::new(MicroW8::new()?)
} else {
#[cfg(not(feature = "browser"))]
unimplemented!();
#[cfg(feature = "browser")]
Box::new(RunWebServer::new())
};
if let Some(timeout) = timeout { if let Some(timeout) = timeout {
uw8.set_timeout(timeout); runtime.set_timeout(timeout);
} }
let mut watcher = FileWatcher::builder(); let mut first_run = true;
if watch_mode { while runtime.is_open() {
watcher.add_file(&filename); if first_run || watcher.poll_changed_file()?.is_some() {
} match start_cart(&filename, &mut *runtime, &config) {
Ok(dependencies) => {
let watcher = watcher.build()?; if watch_mode {
for dep in dependencies {
if let Err(err) = start_cart(&filename, &mut uw8, &config) { watcher.add_file(dep)?;
eprintln!("Load error: {}", err); }
if !watch_mode { }
exit(1); }
} Err(err) => {
} eprintln!("Load error: {}", err);
if !watch_mode {
while uw8.is_open() { exit(1);
if watcher.poll_changed_file()?.is_some() { }
if let Err(err) = start_cart(&filename, &mut uw8, &config) { }
eprintln!("Load error: {}", err);
} }
first_run = false;
} }
if let Err(err) = uw8.run_frame() { if let Err(err) = runtime.run_frame() {
eprintln!("Runtime error: {}", err); eprintln!("Runtime error: {}", err);
if !watch_mode { if !watch_mode {
exit(1); exit(1);
@@ -112,39 +136,83 @@ struct Config {
output_path: Option<PathBuf>, output_path: Option<PathBuf>,
} }
fn load_cart(filename: &Path, pack: &Option<uw8_tool::PackConfig>) -> Result<Vec<u8>> { fn load_cart(filename: &Path, config: &Config) -> Result<(Vec<u8>, Vec<PathBuf>)> {
let mut cart = vec![]; let mut dependencies = Vec::new();
File::open(filename)?.read_to_end(&mut cart)?; let mut cart = match SourceType::of_file(filename)? {
SourceType::Binary => {
let mut cart = vec![];
File::open(filename)?.read_to_end(&mut cart)?;
dependencies.push(filename.to_path_buf());
cart
}
SourceType::Wat => {
let cart = wat::parse_file(filename)?;
dependencies.push(filename.to_path_buf());
cart
}
SourceType::CurlyWas => {
let module = curlywas::compile_file(filename, curlywas::Options::default())?;
dependencies = module.dependencies;
module.wasm
}
};
if cart[0] >= 10 { if let Some(ref pack_config) = config.pack {
let src = String::from_utf8(cart)?;
cart = if src.chars().find(|c| !c.is_whitespace()) == Some('(') {
wat::parse_str(src)?
} else {
curlywas::compile_str(&src, filename, curlywas::Options::default())?
};
}
if let Some(pack_config) = pack {
cart = uw8_tool::pack(&cart, pack_config)?; cart = uw8_tool::pack(&cart, pack_config)?;
println!("packed size: {} bytes", cart.len()); println!("packed size: {} bytes", cart.len());
} }
Ok(cart)
}
fn start_cart(filename: &Path, uw8: &mut MicroW8, config: &Config) -> Result<()> {
let cart = load_cart(filename, &config.pack)?;
if let Some(ref path) = config.output_path { if let Some(ref path) = config.output_path {
File::create(path)?.write_all(&cart)?; File::create(path)?.write_all(&cart)?;
} }
if let Err(err) = uw8.load_from_memory(&cart) { Ok((cart, dependencies))
}
enum SourceType {
Binary,
Wat,
CurlyWas,
}
impl SourceType {
fn of_file(filename: &Path) -> Result<SourceType> {
if let Some(extension) = filename.extension() {
if extension == "uw8" || extension == "wasm" {
return Ok(SourceType::Binary);
} else if extension == "wat" || extension == "wast" {
return Ok(SourceType::Wat);
} else if extension == "cwa" {
return Ok(SourceType::CurlyWas);
}
}
let mut cart = vec![];
File::open(filename)?.read_to_end(&mut cart)?;
let ty = if cart[0] < 10 {
SourceType::Binary
} else {
let src = String::from_utf8(cart)?;
if src.chars().find(|&c| !c.is_whitespace() && c != ';') == Some('(') {
SourceType::Wat
} else {
SourceType::CurlyWas
}
};
Ok(ty)
}
}
#[cfg(any(feature = "native", feature = "browser"))]
fn start_cart(filename: &Path, runtime: &mut dyn Runtime, config: &Config) -> Result<Vec<PathBuf>> {
let cart = load_cart(filename, config)?;
if let Err(err) = runtime.load(&cart.0) {
eprintln!("Load error: {}", err); eprintln!("Load error: {}", err);
Err(err) Err(err)
} else { } else {
Ok(()) Ok(cart.1)
} }
} }
@@ -163,7 +231,13 @@ fn pack(mut args: Arguments) -> Result<()> {
let out_file = args.free_from_os_str::<PathBuf, bool>(|s| Ok(s.into()))?; let out_file = args.free_from_os_str::<PathBuf, bool>(|s| Ok(s.into()))?;
let cart = load_cart(&in_file, &Some(pack_config))?; let (cart, _) = load_cart(
&in_file,
&Config {
pack: Some(pack_config),
output_path: None,
},
)?;
File::create(out_file)?.write_all(&cart)?; File::create(out_file)?.write_all(&cart)?;
@@ -173,8 +247,8 @@ fn pack(mut args: Arguments) -> Result<()> {
fn unpack(mut args: Arguments) -> Result<()> { fn unpack(mut args: Arguments) -> Result<()> {
let in_file = args.free_from_os_str::<PathBuf, bool>(|s| Ok(s.into()))?; let in_file = args.free_from_os_str::<PathBuf, bool>(|s| Ok(s.into()))?;
let out_file = args.free_from_os_str::<PathBuf, bool>(|s| Ok(s.into()))?; let out_file = args.free_from_os_str::<PathBuf, bool>(|s| Ok(s.into()))?;
uw8_tool::unpack_file(&in_file, &out_file).into() uw8_tool::unpack_file(&in_file, &out_file)
} }
fn compile(mut args: Arguments) -> Result<()> { fn compile(mut args: Arguments) -> Result<()> {
@@ -187,7 +261,7 @@ fn compile(mut args: Arguments) -> Result<()> {
let out_file = args.free_from_os_str::<PathBuf, bool>(|s| Ok(s.into()))?; let out_file = args.free_from_os_str::<PathBuf, bool>(|s| Ok(s.into()))?;
let module = curlywas::compile_file(in_file, options)?; let module = curlywas::compile_file(in_file, options)?;
File::create(out_file)?.write_all(&module)?; File::create(out_file)?.write_all(&module.wasm)?;
Ok(()) Ok(())
} }

1
src/run-web.html Normal file

File diff suppressed because one or more lines are too long

260
src/run_native.rs Normal file
View File

@@ -0,0 +1,260 @@
use std::sync::{Arc, Mutex};
use std::time::Duration;
use std::{thread, time::Instant};
use anyhow::Result;
use minifb::{Key, Window, WindowOptions};
use wasmtime::{
Engine, GlobalType, Memory, MemoryType, Module, Mutability, Store, TypedFunc, ValType,
};
static GAMEPAD_KEYS: &[Key] = &[
Key::Up,
Key::Down,
Key::Left,
Key::Right,
Key::Z,
Key::X,
Key::A,
Key::S,
];
pub struct MicroW8 {
engine: Engine,
loader_module: Module,
window: Window,
window_buffer: Vec<u32>,
instance: Option<UW8Instance>,
timeout: u32,
}
struct UW8Instance {
store: Store<()>,
memory: Memory,
end_frame: TypedFunc<(), ()>,
update: TypedFunc<(), ()>,
start_time: Instant,
module: Vec<u8>,
watchdog: Arc<Mutex<UW8WatchDog>>,
}
impl Drop for UW8Instance {
fn drop(&mut self) {
if let Ok(mut watchdog) = self.watchdog.lock() {
watchdog.stop = true;
}
}
}
struct UW8WatchDog {
interupt: wasmtime::InterruptHandle,
timeout: u32,
stop: bool,
}
impl MicroW8 {
pub fn new() -> Result<MicroW8> {
let engine = wasmtime::Engine::new(wasmtime::Config::new().interruptable(true))?;
let loader_module =
wasmtime::Module::new(&engine, include_bytes!("../platform/bin/loader.wasm"))?;
let options = WindowOptions {
scale: minifb::Scale::X2,
scale_mode: minifb::ScaleMode::AspectRatioStretch,
resize: true,
..Default::default()
};
let mut window = Window::new("MicroW8", 320, 240, options)?;
window.limit_update_rate(Some(std::time::Duration::from_micros(16666)));
Ok(MicroW8 {
engine,
loader_module,
window,
window_buffer: vec![0u32; 320 * 240],
instance: None,
timeout: 30,
})
}
fn reset(&mut self) {
self.instance = None;
for v in &mut self.window_buffer {
*v = 0;
}
}
}
impl super::Runtime for MicroW8 {
fn is_open(&self) -> bool {
self.window.is_open() && !self.window.is_key_down(Key::Escape)
}
fn set_timeout(&mut self, timeout: u32) {
self.timeout = timeout;
}
fn load(&mut self, module_data: &[u8]) -> Result<()> {
self.reset();
let mut store = wasmtime::Store::new(&self.engine, ());
let memory = wasmtime::Memory::new(&mut store, MemoryType::new(4, Some(4)))?;
let mut linker = wasmtime::Linker::new(&self.engine);
linker.define("env", "memory", memory)?;
let loader_instance = linker.instantiate(&mut store, &self.loader_module)?;
let load_uw8 = loader_instance.get_typed_func::<i32, i32, _>(&mut store, "load_uw8")?;
let platform_data = include_bytes!("../platform/bin/platform.uw8");
memory.data_mut(&mut store)[..platform_data.len()].copy_from_slice(platform_data);
let platform_length =
load_uw8.call(&mut store, platform_data.len() as i32)? as u32 as usize;
let platform_module =
wasmtime::Module::new(&self.engine, &memory.data(&store)[..platform_length])?;
memory.data_mut(&mut store)[..module_data.len()].copy_from_slice(module_data);
let module_length = load_uw8.call(&mut store, module_data.len() as i32)? as u32 as usize;
let module = wasmtime::Module::new(&self.engine, &memory.data(&store)[..module_length])?;
linker.func_wrap("env", "acos", |v: f32| v.acos())?;
linker.func_wrap("env", "asin", |v: f32| v.asin())?;
linker.func_wrap("env", "atan", |v: f32| v.atan())?;
linker.func_wrap("env", "atan2", |x: f32, y: f32| x.atan2(y))?;
linker.func_wrap("env", "cos", |v: f32| v.cos())?;
linker.func_wrap("env", "exp", |v: f32| v.exp())?;
linker.func_wrap("env", "log", |v: f32| v.ln())?;
linker.func_wrap("env", "sin", |v: f32| v.sin())?;
linker.func_wrap("env", "tan", |v: f32| v.tan())?;
linker.func_wrap("env", "pow", |a: f32, b: f32| a.powf(b))?;
for i in 9..64 {
linker.func_wrap("env", &format!("reserved{}", i), || {})?;
}
for i in 0..16 {
linker.define(
"env",
&format!("g_reserved{}", i),
wasmtime::Global::new(
&mut store,
GlobalType::new(ValType::I32, Mutability::Const),
0.into(),
)?,
)?;
}
let platform_instance = linker.instantiate(&mut store, &platform_module)?;
for export in platform_instance.exports(&mut store) {
linker.define(
"env",
export.name(),
export
.into_func()
.expect("platform surely only exports functions"),
)?;
}
let watchdog = Arc::new(Mutex::new(UW8WatchDog {
interupt: store.interrupt_handle()?,
timeout: self.timeout,
stop: false,
}));
{
let watchdog = watchdog.clone();
thread::spawn(move || loop {
thread::sleep(Duration::from_millis(17));
if let Ok(mut watchdog) = watchdog.lock() {
if watchdog.stop {
break;
}
if watchdog.timeout > 0 {
watchdog.timeout -= 1;
if watchdog.timeout == 0 {
watchdog.interupt.interrupt();
}
}
} else {
break;
}
});
}
let instance = linker.instantiate(&mut store, &module)?;
if let Ok(mut watchdog) = watchdog.lock() {
watchdog.timeout = 0;
}
let end_frame = platform_instance.get_typed_func::<(), (), _>(&mut store, "endFrame")?;
let update = instance.get_typed_func::<(), (), _>(&mut store, "upd")?;
self.instance = Some(UW8Instance {
store,
memory,
end_frame,
update,
start_time: Instant::now(),
module: module_data.into(),
watchdog,
});
Ok(())
}
fn run_frame(&mut self) -> Result<()> {
let mut result = Ok(());
if let Some(mut instance) = self.instance.take() {
{
let time = instance.start_time.elapsed().as_millis() as i32;
let mut gamepad: u32 = 0;
for key in self.window.get_keys() {
if let Some(index) = GAMEPAD_KEYS
.iter()
.enumerate()
.find(|(_, &k)| k == key)
.map(|(i, _)| i)
{
gamepad |= 1 << index;
}
}
let mem = instance.memory.data_mut(&mut instance.store);
mem[64..68].copy_from_slice(&time.to_le_bytes());
mem[68..72].copy_from_slice(&gamepad.to_le_bytes());
}
if let Ok(mut watchdog) = instance.watchdog.lock() {
watchdog.timeout = self.timeout;
}
result = instance.update.call(&mut instance.store, ());
if let Ok(mut watchdog) = instance.watchdog.lock() {
watchdog.timeout = 0;
}
instance.end_frame.call(&mut instance.store, ())?;
let memory = instance.memory.data(&instance.store);
let framebuffer = &memory[120..(120 + 320 * 240)];
let palette = &memory[0x13000..];
for (i, &color_index) in framebuffer.iter().enumerate() {
let offset = color_index as usize * 4;
self.window_buffer[i] = 0xff000000
| ((palette[offset] as u32) << 16)
| ((palette[offset + 1] as u32) << 8)
| palette[offset + 2] as u32;
}
if self.window.is_key_pressed(Key::R, minifb::KeyRepeat::No) {
self.load(&instance.module)?;
} else if result.is_ok() {
self.instance = Some(instance);
}
}
self.window
.update_with_buffer(&self.window_buffer, 320, 240)?;
result?;
Ok(())
}
}

89
src/run_web.rs Normal file
View File

@@ -0,0 +1,89 @@
use anyhow::Result;
use std::{
net::SocketAddr,
sync::{Arc, Mutex},
thread,
};
use tokio::sync::broadcast;
use tokio_stream::{wrappers::BroadcastStream, Stream, StreamExt};
use warp::{http::Response, Filter};
pub struct RunWebServer {
cart: Arc<Mutex<Vec<u8>>>,
tx: broadcast::Sender<()>,
}
impl RunWebServer {
pub fn new() -> RunWebServer {
let cart = Arc::new(Mutex::new(Vec::new()));
let (tx, _) = broadcast::channel(1);
let server_cart = cart.clone();
let server_tx = tx.clone();
thread::spawn(move || {
let rt = tokio::runtime::Builder::new_current_thread()
.enable_io()
.enable_time()
.build()
.expect("Failed to create tokio runtime");
rt.block_on(async {
let html = warp::path::end().map(|| {
Response::builder()
.header("Content-Type", "text/html")
.body(include_str!("run-web.html"))
});
let cart = warp::path("cart")
.map(move || server_cart.lock().map_or(Vec::new(), |c| c.clone()));
let events = warp::path("events").and(warp::get()).map(move || {
fn event_stream(
tx: &broadcast::Sender<()>,
) -> impl Stream<Item = Result<warp::sse::Event, std::convert::Infallible>>
{
BroadcastStream::new(tx.subscribe())
.map(|_| Ok(warp::sse::Event::default().data("L")))
}
warp::sse::reply(warp::sse::keep_alive().stream(event_stream(&server_tx)))
});
let socket_addr = "127.0.0.1:3030"
.parse::<SocketAddr>()
.expect("Failed to parse socket address");
let server_future = warp::serve(html.or(cart).or(events)).bind(socket_addr);
println!("Point browser at http://{}", socket_addr);
let _ignore_result = webbrowser::open(&format!("http://{}", socket_addr));
server_future.await
});
});
RunWebServer { cart, tx }
}
}
impl super::Runtime for RunWebServer {
fn load(&mut self, module_data: &[u8]) -> Result<()> {
if let Ok(mut lock) = self.cart.lock() {
lock.clear();
lock.extend_from_slice(module_data);
}
let _ignore_result = self.tx.send(());
Ok(())
}
fn is_open(&self) -> bool {
true
}
fn run_frame(&mut self) -> Result<()> {
std::thread::sleep(std::time::Duration::from_millis(100));
Ok(())
}
}
impl Default for RunWebServer {
fn default() -> RunWebServer {
RunWebServer::new()
}
}

13
test.cwa Normal file
View File

@@ -0,0 +1,13 @@
import "env.memory" memory(4);
import "env.printString" fn print(i32);
export fn upd() {
}
start fn start() {
print(0);
}
data 0 {
"Press " i8(0xe0) " and " i8(0xe1) " to adjust, " i8(0xcc) " to commit." i8(0)
}

BIN
test.wasm Normal file

Binary file not shown.

View File

@@ -71,26 +71,100 @@ impl BaseModule {
add_function(&mut functions, &type_map, "randomSeed", &[I32], None); add_function(&mut functions, &type_map, "randomSeed", &[I32], None);
add_function(&mut functions, &type_map, "cls", &[I32], None); add_function(&mut functions, &type_map, "cls", &[I32], None);
add_function(&mut functions, &type_map, "setPixel", &[I32, I32, I32], None); add_function(
add_function(&mut functions, &type_map, "getPixel", &[I32, I32], Some(I32)); &mut functions,
add_function(&mut functions, &type_map, "hline", &[I32, I32, I32, I32], None); &type_map,
add_function(&mut functions, &type_map, "rectangle", &[F32, F32, F32, F32, I32], None); "setPixel",
add_function(&mut functions, &type_map, "circle", &[F32, F32, F32, I32], None); &[I32, I32, I32],
add_function(&mut functions, &type_map, "line", &[F32, F32, F32, F32, I32], None); None,
);
add_function(
&mut functions,
&type_map,
"getPixel",
&[I32, I32],
Some(I32),
);
add_function(
&mut functions,
&type_map,
"hline",
&[I32, I32, I32, I32],
None,
);
add_function(
&mut functions,
&type_map,
"rectangle",
&[F32, F32, F32, F32, I32],
None,
);
add_function(
&mut functions,
&type_map,
"circle",
&[F32, F32, F32, I32],
None,
);
add_function(
&mut functions,
&type_map,
"line",
&[F32, F32, F32, F32, I32],
None,
);
add_function(&mut functions, &type_map, "time", &[], Some(F32)); add_function(&mut functions, &type_map, "time", &[], Some(F32));
add_function(&mut functions, &type_map, "isButtonPressed", &[I32], Some(I32)); add_function(
add_function(&mut functions, &type_map, "isButtonTriggered", &[I32], Some(I32)); &mut functions,
&type_map,
"isButtonPressed",
&[I32],
Some(I32),
);
add_function(
&mut functions,
&type_map,
"isButtonTriggered",
&[I32],
Some(I32),
);
add_function(&mut functions, &type_map, "printChar", &[I32], None); add_function(&mut functions, &type_map, "printChar", &[I32], None);
add_function(&mut functions, &type_map, "printString", &[I32], None); add_function(&mut functions, &type_map, "printString", &[I32], None);
add_function(&mut functions, &type_map, "printInt", &[I32], None); add_function(&mut functions, &type_map, "printInt", &[I32], None);
add_function(&mut functions, &type_map, "setTextColor", &[I32], None); add_function(&mut functions, &type_map, "setTextColor", &[I32], None);
add_function(&mut functions, &type_map, "setBackgroundColor", &[I32], None); add_function(
add_function(&mut functions, &type_map, "setCursorPosition", &[I32, I32], None); &mut functions,
&type_map,
"setBackgroundColor",
&[I32],
None,
);
add_function(
&mut functions,
&type_map,
"setCursorPosition",
&[I32, I32],
None,
);
add_function(&mut functions, &type_map, "rectangle_outline", &[F32, F32, F32, F32, I32], None); add_function(
add_function(&mut functions, &type_map, "circle_outline", &[F32, F32, F32, I32], None); &mut functions,
&type_map,
"rectangle_outline",
&[F32, F32, F32, F32, I32],
None,
);
add_function(
&mut functions,
&type_map,
"circle_outline",
&[F32, F32, F32, I32],
None,
);
add_function(&mut functions, &type_map, "exp", &[F32], Some(F32));
for i in functions.len()..64 { for i in functions.len()..64 {
add_function( add_function(
@@ -214,6 +288,68 @@ impl BaseModule {
File::create(path)?.write_all(&data)?; File::create(path)?.write_all(&data)?;
Ok(()) Ok(())
} }
pub fn write_as_cwa<P: AsRef<Path>>(&self, path: P) -> Result<()> {
fn inner(mut file: File, base: &BaseModule) -> Result<()> {
writeln!(file, "// MicroW8 APIs, to be `include`d in CurlyWas sources")?;
writeln!(file, "import \"env.memory\" memory({});", base.memory)?;
writeln!(file)?;
for &(module, ref name, type_id) in &base.function_imports {
if !name.contains("reserved") {
let ty = &base.types[type_id as usize];
let params: Vec<&str> = ty.params.iter().copied().map(type_to_str).collect();
write!(
file,
"import \"{}.{}\" fn {}({})",
module,
name,
name,
params.join(", ")
)?;
if let Some(result) = ty.result {
write!(file, " -> {}", type_to_str(result))?;
}
writeln!(file, ";")?;
}
}
writeln!(file)?;
for &(name, value) in CONSTANTS {
writeln!(file, "const {} = 0x{:x};", name, value)?;
}
Ok(())
}
inner(File::create(path)?, self)
}
pub fn write_as_wat<P: AsRef<Path>>(&self, path: P) -> Result<()> {
fn inner(mut file: File, base: &BaseModule) -> Result<()> {
writeln!(file, ";; MicroW8 APIs, in WAT (Wasm Text) format")?;
writeln!(file, "(import \"env\" \"memory\" (memory {}))", base.memory)?;
writeln!(file)?;
for &(module, ref name, type_id) in &base.function_imports {
if !name.contains("reserved") {
let ty = &base.types[type_id as usize];
write!(file, "(import \"{}\" \"{}\" (func ${}", module, name, name)?;
for &param in &ty.params {
write!(file, " (param {})", type_to_str(param))?;
}
if let Some(result) = ty.result {
write!(file, " (result {})", type_to_str(result))?;
}
writeln!(file, "))")?;
}
}
writeln!(file)?;
writeln!(file, ";; to use defines, include this file with a preprocessor\n;; like gpp (https://logological.org/gpp).")?;
for &(name, value) in CONSTANTS {
writeln!(file, "#define {} 0x{:x};", name, value)?;
}
Ok(())
}
inner(File::create(path)?, self)
}
} }
fn add_function( fn add_function(
@@ -241,3 +377,30 @@ fn lookup_type(
}; };
*type_map.get(&key).unwrap() *type_map.get(&key).unwrap()
} }
fn type_to_str(ty: ValType) -> &'static str {
match ty {
ValType::I32 => "i32",
ValType::I64 => "i64",
ValType::F32 => "f32",
ValType::F64 => "f64",
_ => unimplemented!(),
}
}
const CONSTANTS: &[(&str, u32)] = &[
("TIME_MS", 0x40),
("GAMEPAD", 0x44),
("FRAMEBUFFER", 0x78),
("PALETTE", 0x13000),
("FONT", 0x13400),
("USER_MEM", 0x14000),
("BUTTON_UP", 0),
("BUTTON_DOWN", 1),
("BUTTON_LEFT", 2),
("BUTTON_RIGHT", 3),
("BUTTON_A", 4),
("BUTTON_B", 5),
("BUTTON_X", 6),
("BUTTON_Y", 7)
];

View File

@@ -1,8 +1,8 @@
use std::path::PathBuf; use std::path::PathBuf;
use anyhow::Result; use anyhow::Result;
use uw8_tool::BaseModule;
use pico_args::Arguments; use pico_args::Arguments;
use uw8_tool::BaseModule;
fn main() -> Result<()> { fn main() -> Result<()> {
let mut args = Arguments::from_env(); let mut args = Arguments::from_env();
@@ -32,6 +32,14 @@ fn main() -> Result<()> {
let dest: PathBuf = args.free_from_str()?; let dest: PathBuf = args.free_from_str()?;
uw8_tool::filter_exports(&source, &dest)?; uw8_tool::filter_exports(&source, &dest)?;
} }
"base-cwa" => {
let path: PathBuf = args.free_from_str()?;
BaseModule::for_format_version(1)?.write_as_cwa(path)?;
}
"base-wat" => {
let path: PathBuf = args.free_from_str()?;
BaseModule::for_format_version(1)?.write_as_wat(path)?;
}
_ => { _ => {
eprintln!("Unknown subcommand '{}'", cmd); eprintln!("Unknown subcommand '{}'", cmd);
print_help(); print_help();

2
web/build-run-web Executable file
View File

@@ -0,0 +1,2 @@
#!/bin/bash
rm -rf .parcel-cache && yarn parcel build src/run-web.html && cp dist/run-web.html ../src/

View File

@@ -10,7 +10,7 @@
</head> </head>
<body> <body>
<div id="uw8"> <div id="uw8">
<a href="https://exoticorn.github.io/microw8">MicroW8</a> 0.1.1 <a href="https://exoticorn.github.io/microw8">MicroW8</a> 0.1.2
</div> </div>
<div id="centered"> <div id="centered">
<canvas id="screen" width="320" height="240"> <canvas id="screen" width="320" height="240">

View File

@@ -1,5 +1,4 @@
import loaderUrl from "data-url:../../platform/bin/loader.wasm"; import MicroW8 from './microw8.js';
import platformUrl from "data-url:../../platform/bin/platform.uw8";
function setMessage(size, error) { function setMessage(size, error) {
let html = size ? `${size} bytes` : 'Insert cart'; let html = size ? `${size} bytes` : 'Insert cart';
@@ -9,314 +8,27 @@ function setMessage(size, error) {
document.getElementById('message').innerHTML = html; document.getElementById('message').innerHTML = html;
} }
let screen = document.getElementById('screen'); let uw8 = MicroW8(document.getElementById('screen'), {
let canvasCtx = screen.getContext('2d'); setMessage,
let imageData = canvasCtx.createImageData(320, 240); keyboardElement: window,
timerElement: document.getElementById("timer"),
let devkitMode; });
let cancelFunction;
let currentData;
let U8 = (d) => new Uint8Array(d);
let U32 = (d) => new Uint32Array(d);
let pad = 0;
let keyHandler = (e) => {
let isKeyDown = e.type == 'keydown';
let mask;
switch (e.code) {
case 'ArrowUp':
mask = 1;
break;
case 'ArrowDown':
mask = 2;
break;
case 'ArrowLeft':
mask = 4;
break;
case 'ArrowRight':
mask = 8;
break;
case 'KeyZ':
mask = 16;
break;
case 'KeyX':
mask = 32;
break;
case 'KeyA':
mask = 64;
break;
case 'KeyS':
mask = 128;
break;
case 'KeyR':
if (isKeyDown) {
runModule(currentData, true);
}
break;
case 'F9':
if(isKeyDown) {
screen.toBlob(blob => {
downloadBlob(blob, '.png');
});
}
e.preventDefault();
break;
case 'F10':
if(isKeyDown) {
recordVideo();
}
e.preventDefault();
break;
}
if (isKeyDown) {
pad |= mask;
} else {
pad &= ~mask;
}
};
window.onkeydown = keyHandler;
window.onkeyup = keyHandler;
async function runModule(data, keepUrl) {
if (cancelFunction) {
cancelFunction();
cancelFunction = null;
}
let cartridgeSize = data.byteLength;
setMessage(cartridgeSize);
if (cartridgeSize == 0) {
return;
}
currentData = data;
let newURL = window.location.pathname;
if (cartridgeSize <= 1024 && !keepUrl) {
let dataString = '';
for (let byte of U8(data)) {
dataString += String.fromCharCode(byte);
}
newURL += '#' + btoa(dataString);
if (newURL != window.location.pathname + window.location.hash) {
history.pushState(null, null, newURL);
}
}
screen.width = screen.width;
try {
let memSize = { initial: 4 };
if(!devkitMode) {
memSize.maximum = 4;
}
let memory = new WebAssembly.Memory({ initial: 4, maximum: devkitMode ? 16 : 4 });
let memU8 = U8(memory.buffer);
let importObject = {
env: {
memory
},
};
let loader;
let loadModuleData = (data) => {
if (loader && (!devkitMode || U8(data)[0] != 0)) {
memU8.set(U8(data));
let length = loader.exports.load_uw8(data.byteLength);
data = new ArrayBuffer(length);
U8(data).set(memU8.slice(0, length));
}
return data;
}
let instantiate = async (data) => (await WebAssembly.instantiate(data, importObject)).instance;
let loadModuleURL = async (url) => instantiate(loadModuleData(await (await fetch(url)).arrayBuffer()));
loader = await loadModuleURL(loaderUrl);
for (let n of ['acos', 'asin', 'atan', 'atan2', 'cos', 'exp', 'log', 'sin', 'tan', 'pow']) {
importObject.env[n] = Math[n];
}
for (let i = 9; i < 64; ++i) {
importObject.env['reserved' + i] = () => { };
}
for (let i = 0; i < 16; ++i) {
importObject.env['g_reserved' + i] = 0;
}
data = loadModuleData(data);
let platform_instance = await loadModuleURL(platformUrl);
for (let name in platform_instance.exports) {
importObject.env[name] = platform_instance.exports[name]
}
let instance = await instantiate(data);
let buffer = U32(imageData.data.buffer);
let startTime = Date.now();
let keepRunning = true;
cancelFunction = () => keepRunning = false;
const timePerFrame = 1000 / 60;
let nextFrame = startTime;
function mainloop() {
if (!keepRunning) {
return;
}
try {
let now = Date.now();
let restart = false;
if (now >= nextFrame) {
let gamepads = navigator.getGamepads();
let gamepad = 0;
for (let i = 0; i < 4; ++i) {
let pad = gamepads[i];
if (!pad) {
continue;
}
for (let j = 0; j < 8; ++j) {
let buttonIdx = (j + 12) % 16;
if (pad.buttons.length > buttonIdx && pad.buttons[buttonIdx].pressed) {
gamepad |= 1 << (i * 8 + j);
}
}
if (pad.axes.length > 1) {
for (let j = 0; j < 4; ++j) {
let v = pad.axes[1 - (j >> 1)];
if (((j & 1) ? v : -v) > 0.5) {
gamepad |= 1 << (i * 8 + j);
}
}
}
if (pad.buttons.length > 9 && pad.buttons[9].pressed) {
restart = true;
}
}
let u32Mem = U32(memory.buffer);
u32Mem[16] = now - startTime;
u32Mem[17] = pad | gamepad;
instance.exports.upd();
platform_instance.exports.endFrame();
let palette = U32(memory.buffer.slice(0x13000, 0x13000 + 1024));
for (let i = 0; i < 320 * 240; ++i) {
buffer[i] = palette[memU8[i + 120]] | 0xff000000;
}
canvasCtx.putImageData(imageData, 0, 0);
nextFrame = Math.max(nextFrame + timePerFrame, now);
}
if (restart) {
runModule(currentData);
} else {
window.requestAnimationFrame(mainloop);
}
} catch (err) {
setMessage(cartridgeSize, err.toString());
}
}
mainloop();
} catch (err) {
setMessage(cartridgeSize, err.toString());
}
}
function downloadBlob(blob, ext) {
let a = document.createElement('a');
a.href = URL.createObjectURL(blob);
a.download = 'microw8_' + new Date().toISOString() + ext;
a.click();
URL.revokeObjectURL(a.href);
}
let videoRecorder;
let videoStartTime;
function recordVideo() {
if(videoRecorder) {
videoRecorder.stop();
videoRecorder = null;
return;
}
videoRecorder = new MediaRecorder(screen.captureStream(), {
mimeType: 'video/webm',
videoBitsPerSecond: 25000000
});
let chunks = [];
videoRecorder.ondataavailable = e => {
chunks.push(e.data);
};
let timer = document.getElementById("timer");
timer.hidden = false;
timer.innerText = "00:00";
videoRecorder.onstop = () => {
timer.hidden = true;
downloadBlob(new Blob(chunks, {type: 'video/webm'}), '.webm');
};
videoRecorder.start();
videoStartTime = Date.now();
function updateTimer() {
if(!videoStartTime) {
return;
}
let duration = Math.floor((Date.now() - videoStartTime) / 1000);
timer.innerText = Math.floor(duration / 60).toString().padStart(2, '0') + ':' + (duration % 60).toString().padStart(2, '0');
setTimeout(updateTimer, 1000);
}
setTimeout(updateTimer, 1000);
}
async function runModuleFromURL(url, keepUrl) {
let response = await fetch(url);
let type = response.headers.get('Content-Type');
if(type && type.includes('html')) {
throw false;
}
runModule(await response.arrayBuffer(), keepUrl || devkitMode);
}
function runModuleFromHash() { function runModuleFromHash() {
let hash = window.location.hash.slice(1); let hash = window.location.hash.slice(1);
if(hash == 'devkit') { if(hash == 'devkit') {
devkitMode = true; uw8.setDevkitMode(true);
return; return;
} }
devkitMode = false; uw8.setDevkitMode(false);
if (hash.length > 0) { if (hash.length > 0) {
if (hash.startsWith("url=")) { if (hash.startsWith("url=")) {
runModuleFromURL(hash.slice(4), true); uw8.runModuleFromURL(hash.slice(4), true);
} else { } else {
runModuleFromURL('data:;base64,' + hash); uw8.runModuleFromURL('data:;base64,' + hash);
} }
} else { } else {
runModule(new ArrayBuffer(0)); uw8.runModule(new ArrayBuffer(0));
} }
} }
@@ -331,7 +43,7 @@ let setupLoad = () => {
fileInput.accept = '.wasm,.uw8,application/wasm'; fileInput.accept = '.wasm,.uw8,application/wasm';
fileInput.onchange = () => { fileInput.onchange = () => {
if (fileInput.files.length > 0) { if (fileInput.files.length > 0) {
runModuleFromURL(URL.createObjectURL(fileInput.files[0])); uw8.runModuleFromURL(URL.createObjectURL(fileInput.files[0]));
} }
}; };
fileInput.click(); fileInput.click();
@@ -345,7 +57,7 @@ let setupLoad = () => {
let files = e.dataTransfer && e.dataTransfer.files; let files = e.dataTransfer && e.dataTransfer.files;
if(files && files.length == 1) { if(files && files.length == 1) {
e.preventDefault(); e.preventDefault();
runModuleFromURL(URL.createObjectURL(e.dataTransfer.files[0])); uw8.runModuleFromURL(URL.createObjectURL(e.dataTransfer.files[0]));
} }
} }
@@ -367,7 +79,7 @@ if(location.hash.length != 0) {
url += 'cart.uw8'; url += 'cart.uw8';
} }
try { try {
await runModuleFromURL(url, true); await uw8.runModuleFromURL(url, true);
} catch(e) { } catch(e) {
setupLoad(); setupLoad();
} }

319
web/src/microw8.js Normal file
View File

@@ -0,0 +1,319 @@
import loaderUrl from "data-url:../../platform/bin/loader.wasm";
import platformUrl from "data-url:../../platform/bin/platform.uw8";
export default function MicroW8(screen, config = {}) {
if(!config.setMessage) {
config.setMessage = (s, e) => {
if(e) {
console.log('error: ' + e);
}
}
}
let canvasCtx = screen.getContext('2d');
let imageData = canvasCtx.createImageData(320, 240);
let devkitMode = config.devkitMode;
let cancelFunction;
let currentData;
let U8 = (d) => new Uint8Array(d);
let U32 = (d) => new Uint32Array(d);
let pad = 0;
let keyboardElement = config.keyboardElement == undefined ? screen : config.keyboardElement;
if(keyboardElement) {
let keyHandler = (e) => {
let isKeyDown = e.type == 'keydown';
let mask;
switch (e.code) {
case 'ArrowUp':
mask = 1;
break;
case 'ArrowDown':
mask = 2;
break;
case 'ArrowLeft':
mask = 4;
break;
case 'ArrowRight':
mask = 8;
break;
case 'KeyZ':
mask = 16;
break;
case 'KeyX':
mask = 32;
break;
case 'KeyA':
mask = 64;
break;
case 'KeyS':
mask = 128;
break;
case 'KeyR':
if (isKeyDown) {
runModule(currentData, true);
}
break;
case 'F9':
if(isKeyDown) {
screen.toBlob(blob => {
downloadBlob(blob, '.png');
});
}
e.preventDefault();
break;
case 'F10':
if(isKeyDown) {
recordVideo();
}
e.preventDefault();
break;
}
if (isKeyDown) {
pad |= mask;
} else {
pad &= ~mask;
}
};
keyboardElement.onkeydown = keyHandler;
keyboardElement.onkeyup = keyHandler;
}
async function runModule(data, keepUrl) {
if (cancelFunction) {
cancelFunction();
cancelFunction = null;
}
let cartridgeSize = data.byteLength;
config.setMessage(cartridgeSize);
if (cartridgeSize == 0) {
return;
}
currentData = data;
let newURL = window.location.pathname;
if (cartridgeSize <= 1024 && !keepUrl) {
let dataString = '';
for (let byte of U8(data)) {
dataString += String.fromCharCode(byte);
}
newURL += '#' + btoa(dataString);
if (newURL != window.location.pathname + window.location.hash) {
history.pushState(null, null, newURL);
}
}
screen.width = screen.width;
try {
let memSize = { initial: 4 };
if(!devkitMode) {
memSize.maximum = 4;
}
let memory = new WebAssembly.Memory({ initial: 4, maximum: devkitMode ? 16 : 4 });
let memU8 = U8(memory.buffer);
let importObject = {
env: {
memory
},
};
let loader;
let loadModuleData = (data) => {
if (loader && (!devkitMode || U8(data)[0] != 0)) {
memU8.set(U8(data));
let length = loader.exports.load_uw8(data.byteLength);
data = new ArrayBuffer(length);
U8(data).set(memU8.slice(0, length));
}
return data;
}
let instantiate = async (data) => (await WebAssembly.instantiate(data, importObject)).instance;
let loadModuleURL = async (url) => instantiate(loadModuleData(await (await fetch(url)).arrayBuffer()));
loader = await loadModuleURL(loaderUrl);
for (let n of ['acos', 'asin', 'atan', 'atan2', 'cos', 'exp', 'log', 'sin', 'tan', 'pow']) {
importObject.env[n] = Math[n];
}
for (let i = 9; i < 64; ++i) {
importObject.env['reserved' + i] = () => { };
}
for (let i = 0; i < 16; ++i) {
importObject.env['g_reserved' + i] = 0;
}
data = loadModuleData(data);
let platform_instance = await loadModuleURL(platformUrl);
for (let name in platform_instance.exports) {
importObject.env[name] = platform_instance.exports[name]
}
let instance = await instantiate(data);
let buffer = U32(imageData.data.buffer);
let startTime = Date.now();
let keepRunning = true;
cancelFunction = () => keepRunning = false;
const timePerFrame = 1000 / 60;
let nextFrame = startTime;
function mainloop() {
if (!keepRunning) {
return;
}
try {
let now = Date.now();
let restart = false;
if (now >= nextFrame) {
let gamepads = navigator.getGamepads();
let gamepad = 0;
for (let i = 0; i < 4; ++i) {
let pad = gamepads[i];
if (!pad) {
continue;
}
for (let j = 0; j < 8; ++j) {
let buttonIdx = (j + 12) % 16;
if (pad.buttons.length > buttonIdx && pad.buttons[buttonIdx].pressed) {
gamepad |= 1 << (i * 8 + j);
}
}
if (pad.axes.length > 1) {
for (let j = 0; j < 4; ++j) {
let v = pad.axes[1 - (j >> 1)];
if (((j & 1) ? v : -v) > 0.5) {
gamepad |= 1 << (i * 8 + j);
}
}
}
if (pad.buttons.length > 9 && pad.buttons[9].pressed) {
restart = true;
}
}
let u32Mem = U32(memory.buffer);
u32Mem[16] = now - startTime;
u32Mem[17] = pad | gamepad;
instance.exports.upd();
platform_instance.exports.endFrame();
let palette = U32(memory.buffer.slice(0x13000, 0x13000 + 1024));
for (let i = 0; i < 320 * 240; ++i) {
buffer[i] = palette[memU8[i + 120]] | 0xff000000;
}
canvasCtx.putImageData(imageData, 0, 0);
nextFrame = Math.max(nextFrame + timePerFrame, now);
}
if (restart) {
runModule(currentData);
} else {
window.requestAnimationFrame(mainloop);
}
} catch (err) {
config.setMessage(cartridgeSize, err.toString());
}
}
mainloop();
} catch (err) {
config.setMessage(cartridgeSize, err.toString());
}
}
function downloadBlob(blob, ext) {
let a = document.createElement('a');
a.href = URL.createObjectURL(blob);
a.download = 'microw8_' + new Date().toISOString() + ext;
a.click();
URL.revokeObjectURL(a.href);
}
let videoRecorder;
let videoStartTime;
function recordVideo() {
if(videoRecorder) {
videoRecorder.stop();
videoRecorder = null;
return;
}
videoRecorder = new MediaRecorder(screen.captureStream(), {
mimeType: 'video/webm',
videoBitsPerSecond: 25000000
});
let chunks = [];
videoRecorder.ondataavailable = e => {
chunks.push(e.data);
};
let timer = config.timerElement;
if(timer) {
timer.hidden = false;
timer.innerText = "00:00";
}
videoRecorder.onstop = () => {
if(timer) {
timer.hidden = true;
}
downloadBlob(new Blob(chunks, {type: 'video/webm'}), '.webm');
};
videoRecorder.start();
videoStartTime = Date.now();
function updateTimer() {
if(!videoStartTime) {
return;
}
if(timer) {
let duration = Math.floor((Date.now() - videoStartTime) / 1000);
timer.innerText = Math.floor(duration / 60).toString().padStart(2, '0') + ':' + (duration % 60).toString().padStart(2, '0');
}
setTimeout(updateTimer, 1000);
}
setTimeout(updateTimer, 1000);
}
async function runModuleFromURL(url, keepUrl) {
let response = await fetch(url);
let type = response.headers.get('Content-Type');
if(type && type.includes('html')) {
throw false;
}
runModule(await response.arrayBuffer(), keepUrl || devkitMode);
}
return {
runModule,
runModuleFromURL,
setDevkitMode: (m) => devkitMode = m,
};
}

46
web/src/run-web.css Normal file
View File

@@ -0,0 +1,46 @@
html, body, canvas {
padding: 0;
margin: 0;
background-color: #202024;
}
html {
height: 100%;
}
body {
height: 100%;
display: grid;
grid-template-rows: 1fr;
}
#screen {
align-self: center;
justify-self: center;
image-rendering: pixelated;
border: 4px solid #303040;
}
#message {
position: absolute;
width: calc(100% - 16px);
background-color: rgba(0, 0, 0, 0.4);
color: #c64;
padding: 8px;
font: bold 12pt sans-serif;
z-index: 2;
}
@media (min-width: 648px) and (min-height: 488px) {
#screen {
width: 640px;
height: 480px;
}
}
@media (min-width: 968px) and (min-height: 728px) {
#screen {
width: 960px;
height: 720px;
}
}

18
web/src/run-web.html Normal file
View File

@@ -0,0 +1,18 @@
<!doctype html>
<html>
<head>
<meta charset="utf8" />
<title>uw8-run</title>
<style>
@import "run-web.css";
</style>
</head>
<body>
<canvas id="screen" width="320" height="240" tabindex="1">
</canvas>
<div id="message"></div>
</body>
<script type="module">
import "./run-web.js";
</script>
</html>

19
web/src/run-web.js Normal file
View File

@@ -0,0 +1,19 @@
import MicroW8 from './microw8.js';
let uw8 = MicroW8(document.getElementById('screen'), {
setMessage: (_, err) => {
let elem = document.getElementById('message');
if(err) {
elem.innerText = err;
}
elem.hidden = !err;
}
});
let events = new EventSource('events');
events.onmessage = event => {
console.log(event.data);
if(event.data == 'L') {
uw8.runModuleFromURL('cart', true);
}
};
uw8.runModuleFromURL('cart', true);