mirror of
https://github.com/exoticorn/microw8.git
synced 2026-01-20 19:26:43 +01:00
Compare commits
24 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 00d21b4745 | |||
| d11b46576a | |||
| add49a1f8b | |||
| 8815a8e02e | |||
| eb7c33d412 | |||
| 47ad3b4f30 | |||
| 0f668fb6e9 | |||
| f876f59e80 | |||
| 44b8656f29 | |||
| 90467f7c5b | |||
| c56196bd2e | |||
| 266493ca1c | |||
| 4c75ba2e44 | |||
| f1493ebded | |||
| 5d41733142 | |||
| c25a52b61b | |||
| 619ea903ba | |||
| 9b900a49e4 | |||
| f21497dd2e | |||
| 381eaf970f | |||
| 9632adb57f | |||
| 33e08e9b73 | |||
| cacde9136c | |||
| 81a38c2d75 |
1169
Cargo.lock
generated
1169
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
18
Cargo.toml
18
Cargo.toml
@@ -1,16 +1,26 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "uw8"
|
name = "uw8"
|
||||||
version = "0.1.0"
|
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 = "0.19"
|
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"
|
||||||
|
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 }
|
||||||
23
README.md
23
README.md
@@ -13,7 +13,11 @@ See [here](https://exoticorn.github.io/microw8/) for more information and docs.
|
|||||||
* Memory: 256KB
|
* Memory: 256KB
|
||||||
* Gamepad input (D-Pad + 4 Buttons)
|
* Gamepad input (D-Pad + 4 Buttons)
|
||||||
|
|
||||||
## Download
|
## Downloads
|
||||||
|
|
||||||
|
* [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)
|
||||||
|
|
||||||
The download includes
|
The download includes
|
||||||
|
|
||||||
@@ -31,6 +35,8 @@ 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)
|
||||||
-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.
|
||||||
-u, --uncompressed : Use the uncompressed uw8 format for packing.
|
-u, --uncompressed : Use the uncompressed uw8 format for packing.
|
||||||
@@ -48,6 +54,21 @@ Options:
|
|||||||
-l LEVEL, --level LEVEL : Compression level (0-9). Higher compression levels are really slow.
|
-l LEVEL, --level LEVEL : Compression level (0-9). Higher compression levels are really slow.
|
||||||
|
|
||||||
|
|
||||||
|
uw8 unpack <infile> <outfile>
|
||||||
|
|
||||||
|
Unpacks a MicroW8 module into a standard WebAssembly module.
|
||||||
|
|
||||||
|
|
||||||
|
uw8 compile [<options>] <infile> <outfile>
|
||||||
|
|
||||||
|
Compiles a CurlyWas source file to a standard WebAssembly module. Most useful together with
|
||||||
|
the --debug option to get a module that works well in the Chrome debugger.
|
||||||
|
|
||||||
|
Options:
|
||||||
|
|
||||||
|
-d, --debug : Generate a name section to help debugging
|
||||||
|
|
||||||
|
|
||||||
uw8 filter-exports <infile> <outfile>
|
uw8 filter-exports <infile> <outfile>
|
||||||
|
|
||||||
Reads a binary WebAssembly module, removes all exports not used by the MicroW8 platform + everything that is unreachable without those exports and writes the resulting module to <outfile>.
|
Reads a binary WebAssembly module, removes all exports not used by the MicroW8 platform + everything that is unreachable without those exports and writes the resulting module to <outfile>.
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
clang -O2 -Wno-incompatible-library-redeclaration --no-standard-libraries -ffast-math -Xclang -target-feature -Xclang +nontrapping-fptoint -Wl,--no-entry -Wl,--export-all -Wl,--import-memory -Wl,--initial-memory=262144 -Wl,-zstack-size=90000 -o cart.wasm cart.c --target=wasm32 && \
|
clang -O2 -Wno-incompatible-library-redeclaration --no-standard-libraries -ffast-math -Xclang -target-feature -Xclang +nontrapping-fptoint -Wl,--no-entry,--export-all,--import-memory,--initial-memory=262144,--global-base=81920,-zstack-size=4096 -o cart.wasm cart.c --target=wasm32 && \
|
||||||
uw8 filter-exports cart.wasm cart.wasm && \
|
uw8 filter-exports cart.wasm cart.wasm && \
|
||||||
wasm-opt -Oz --fast-math --strip-producers -o cart.wasm cart.wasm && \
|
wasm-opt -Oz --fast-math --strip-producers -o cart.wasm cart.wasm && \
|
||||||
uw8 pack -l 9 cart.wasm cart.uw8
|
uw8 pack -l 9 cart.wasm cart.uw8
|
||||||
@@ -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);
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
@@ -1,102 +1,15 @@
|
|||||||
import "env.memory" memory(4);
|
include "../include/microw8-api.cwa"
|
||||||
|
|
||||||
import "env.cls" fn cls(i32);
|
|
||||||
import "env.setPixel" fn setPixel(i32, i32, i32);
|
|
||||||
import "env.time" fn time() -> f32;
|
|
||||||
import "env.sin" fn sin(f32) -> f32;
|
|
||||||
import "env.cos" fn cos(f32) -> f32;
|
|
||||||
|
|
||||||
fn line(x1: f32, y1: f32, x2: f32, y2: f32, col: i32) {
|
|
||||||
let swapTmp: f32;
|
|
||||||
if x1 > x2 {
|
|
||||||
swapTmp = x1;
|
|
||||||
x1 = x2;
|
|
||||||
x2 = swapTmp;
|
|
||||||
swapTmp = y1;
|
|
||||||
y1 = y2;
|
|
||||||
y2 = swapTmp;
|
|
||||||
}
|
|
||||||
if x1 < 0 as f32 & x2 >= 0 as f32 {
|
|
||||||
y1 = y1 + (y2 - y1) * -x1 / (x2 - x1);
|
|
||||||
x1 = 0 as f32;
|
|
||||||
}
|
|
||||||
if x1 < 320 as f32 & x2 >= 320 as f32 {
|
|
||||||
y2 = y2 + (y2 - y1) * (320 as f32 - x2) / (x2 - x1);
|
|
||||||
x2 = 320 as f32;
|
|
||||||
}
|
|
||||||
|
|
||||||
if y1 > y2 {
|
|
||||||
swapTmp = x1;
|
|
||||||
x1 = x2;
|
|
||||||
x2 = swapTmp;
|
|
||||||
swapTmp = y1;
|
|
||||||
y1 = y2;
|
|
||||||
y2 = swapTmp;
|
|
||||||
}
|
|
||||||
if y1 < 0 as f32 & y2 >= 0 as f32 {
|
|
||||||
x1 = x1 + (x2 - x1) * -y1 / (y2 - y1);
|
|
||||||
y1 = 0 as f32;
|
|
||||||
}
|
|
||||||
if y1 < 240 as f32 & y2 >= 240 as f32 {
|
|
||||||
x2 = x2 + (x2 - x1) * (240 as f32 - y2) / (y2 - y1);
|
|
||||||
y2 = 240 as f32;
|
|
||||||
}
|
|
||||||
|
|
||||||
let lazy dx = x2 - x1;
|
|
||||||
let lazy dy = y2 - y1;
|
|
||||||
let max_axis: f32;
|
|
||||||
let p: f32;
|
|
||||||
if abs(dx) >= dy {
|
|
||||||
max_axis = dx;
|
|
||||||
p = x1;
|
|
||||||
} else {
|
|
||||||
max_axis = dy;
|
|
||||||
p = y1;
|
|
||||||
}
|
|
||||||
|
|
||||||
let steps = floor(p + max_axis) as i32 - floor(p) as i32;
|
|
||||||
p = floor(p) + 0.5 - p;
|
|
||||||
if max_axis < 0 as f32 {
|
|
||||||
steps = -steps;
|
|
||||||
p = -p;
|
|
||||||
max_axis = -max_axis;
|
|
||||||
}
|
|
||||||
dx = dx / max_axis;
|
|
||||||
dy = dy / max_axis;
|
|
||||||
|
|
||||||
let f = min(max_axis, max(0 as f32, p));
|
|
||||||
setPixel((x1 + f * dx) as i32, (y1 + f * dy) as i32, col);
|
|
||||||
|
|
||||||
if !steps {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
x1 = x1 + (1 as f32 + p) * dx;
|
|
||||||
y1 = y1 + (1 as f32 + p) * dy;
|
|
||||||
|
|
||||||
p = p + steps as f32;
|
|
||||||
|
|
||||||
loop pixels {
|
|
||||||
if steps := steps - 1 {
|
|
||||||
setPixel(x1 as i32, y1 as i32, col);
|
|
||||||
x1 = x1 + dx;
|
|
||||||
y1 = y1 + dy;
|
|
||||||
branch pixels;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
f = min(max_axis, p) - p;
|
|
||||||
setPixel((x1 + f * dx) as i32, (y1 + f * dy) as i32, col);
|
|
||||||
}
|
|
||||||
|
|
||||||
export fn upd() {
|
export fn upd() {
|
||||||
cls(0);
|
cls(0);
|
||||||
// line(0.0, 4.0, 7.0, -2.0, 15);
|
|
||||||
// return;
|
|
||||||
let i: i32;
|
let i: i32;
|
||||||
loop lines {
|
loop lines {
|
||||||
let angle = i as f32 * (3.1415 / 25.0) + time() * 0.1;
|
let angle = i as f32 * (3.1415 / 25.0) + time() * 0.125;
|
||||||
line(160.0, 120.0, 160.0 + sin(angle) * 100.0, 120.0 + cos(angle) * 100.0, 47);
|
line(
|
||||||
|
160 as f32, 120 as f32,
|
||||||
|
160 as f32 + sin(angle) * 100 as f32,
|
||||||
|
120 as f32 + cos(angle) * 100 as f32,
|
||||||
|
47);
|
||||||
branch_if (i := i + 1) < 50: lines;
|
branch_if (i := i + 1) < 50: lines;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
50
examples/include/microw8-api.cwa
Normal file
50
examples/include/microw8-api.cwa
Normal 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;
|
||||||
52
examples/include/microw8-api.wat
Normal file
52
examples/include/microw8-api.wat
Normal 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;
|
||||||
65
logo.svg
Normal file
65
logo.svg
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
width="120"
|
||||||
|
height="120"
|
||||||
|
viewBox="0 0 120 120"
|
||||||
|
version="1.1"
|
||||||
|
id="svg5"
|
||||||
|
sodipodi:docname="logo.svg"
|
||||||
|
inkscape:version="1.1.1 (3bf5ae0d25, 2021-09-20, custom)"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg">
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="namedview7"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#666666"
|
||||||
|
borderopacity="1.0"
|
||||||
|
inkscape:pageshadow="2"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pagecheckerboard="0"
|
||||||
|
inkscape:document-units="px"
|
||||||
|
showgrid="false"
|
||||||
|
inkscape:zoom="3.6041667"
|
||||||
|
inkscape:cx="21.780347"
|
||||||
|
inkscape:cy="63.260116"
|
||||||
|
inkscape:window-width="1916"
|
||||||
|
inkscape:window-height="1041"
|
||||||
|
inkscape:window-x="0"
|
||||||
|
inkscape:window-y="18"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
inkscape:current-layer="layer1" />
|
||||||
|
<defs
|
||||||
|
id="defs2" />
|
||||||
|
<g
|
||||||
|
inkscape:label="Layer 1"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1">
|
||||||
|
<rect
|
||||||
|
style="fill:#85a6b2;fill-rule:evenodd;fill-opacity:1"
|
||||||
|
id="rect31"
|
||||||
|
width="112.05161"
|
||||||
|
height="109.64198"
|
||||||
|
x="3.9731982"
|
||||||
|
y="6.3613" />
|
||||||
|
<text
|
||||||
|
xml:space="preserve"
|
||||||
|
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:117.333px;line-height:1.25;font-family:'Fira Code';-inkscape-font-specification:'Fira Code Bold';fill:#ffffff;fill-opacity:1;stroke:none"
|
||||||
|
x="-6.2686396"
|
||||||
|
y="117.70291"
|
||||||
|
id="text2691"><tspan
|
||||||
|
sodipodi:role="line"
|
||||||
|
id="tspan2689"
|
||||||
|
x="-6.2686396"
|
||||||
|
y="117.70291">W8</tspan></text>
|
||||||
|
<circle
|
||||||
|
style="fill:#ffffff;stroke-width:1.32327"
|
||||||
|
id="path121"
|
||||||
|
cx="60.962471"
|
||||||
|
cy="6.7644148"
|
||||||
|
r="14.855442" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.9 KiB |
127
platform/Cargo.lock
generated
127
platform/Cargo.lock
generated
@@ -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"
|
||||||
|
|||||||
@@ -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.
@@ -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)?;
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
//////////
|
//////////
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ highlight_theme = "ascetic-white"
|
|||||||
[extra]
|
[extra]
|
||||||
# Put all your custom variables here
|
# Put all your custom variables here
|
||||||
juice_logo_name = "MicroW8"
|
juice_logo_name = "MicroW8"
|
||||||
juice_logo_path = "microw8.svg"
|
juice_logo_path = "img/microw8.svg"
|
||||||
juice_extra_menu = [
|
juice_extra_menu = [
|
||||||
{ title = "Github", link = "https://github.com/exoticorn/microw8" }
|
{ title = "Github", link = "https://github.com/exoticorn/microw8" }
|
||||||
]
|
]
|
||||||
@@ -15,19 +15,55 @@ 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.1pre5#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.1pre5#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.1pre4#Ag95rdCB5Ww5NofyQaKF4P1mrNRso4azgiem4hK99Gh8OMzSpFq3NsNDo7O7pqln10D11l9uXr/ritw7OEzKwbEfCdvaRnS2Z0Kz0iDEZt/gIqOdvFmxsL1MjPQ4XInPbUJpQUonhQq29oP2omFabnQxn0bzoK7mZjcwc5GetHG+hGajkJcRr8oOnjfCol8RD+ha33GYtPnut+GLe4ktzf5UxZwGs6oT9qqC61lRDakN) (177 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.1pre4#AqL8HeK1M9dn2nWNIF5vaq/Vh64pMt5nJIFoFKpBMPUsGtDtpqjo1JbT9LzPhAxCqJ7Yh4TA6oTGd4xhLowf+cWZMY73+7AZmfXJJsBi4cej/hH+4wlAgxFIrnOYnr/18IpnZbsHf0eGm1BhahX74+cVR0TRmNQmYC7GhCNS3mv/3MJn74lCj7t28aBJPjEZhP9fGXdG2u5Egh/Tjdg=) (158 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.1pre4#AgKaeeOuwg5gCKvFIeiitEwMpUI2rymEcu+DDB1vMu9uBoufvUxIr4Y5p4Jj2ukoNO4PE7QS5cN1ZyDMCRfSzYIGZxKlN2J6NKEWK7KVPk9wVUgn1Ip+hsMinWgEO8ETKfPuHoIa4kjI+ULFOMad7vd3rt/lh1Vy9w+R2MXG/7T61d3c7C6KY+eQNS0eW3ys4iU8R6SycuWZuuZ2Sg3Qxp826s+Kt+2qBojpzNOSoyFqyrVyYMTKEkSl0BZOj59Cs1hPm5bq0F1MmVhGAzMhW9V4YeAe): 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:
|
||||||
|
|
||||||
* [Technotunnel B/W](v0.1pre2#AQrDAQHAAQIBfwp9A0AgAUEAsiABQcACb7JDmhkgQ5MiBCAEIASUIAFBwAJtQfgAa7IiBSAFlJKRIgaVIgcgByAAskHQD7KVIgIQAEPNzEw/lCIDlCAHIAeUIAOUIAOUQQGykiADIAOUk5GSIgiUIAOTQQqylCACkiIJqCAFIAaVIAiUQQqylCACkiIKqHMgCEEyspQgBpUiCyACkkEUspSocUEFcbJBArIgC5OUQRaylJeoOgB4IAFBAWoiAUGA2ARIDQALCw==) (199 bytes uncompressed): A port of my [entry](https://tic80.com/play?cart=1873) in the Outline'21 bytebattle quater final (older MicroW8 version with monochrome palette)
|
* [Technotunnel B/W](v0.1pre2#AQrDAQHAAQIBfwp9A0AgAUEAsiABQcACb7JDmhkgQ5MiBCAEIASUIAFBwAJtQfgAa7IiBSAFlJKRIgaVIgcgByAAskHQD7KVIgIQAEPNzEw/lCIDlCAHIAeUIAOUIAOUQQGykiADIAOUk5GSIgiUIAOTQQqylCACkiIJqCAFIAaVIAiUQQqylCACkiIKqHMgCEEyspQgBpUiCyACkkEUspSocUEFcbJBArIgC5OUQRaylJeoOgB4IAFBAWoiAUGA2ARIDQALCw==) (199 bytes uncompressed): A port of my [entry](https://tic80.com/play?cart=1873) in the Outline'21 bytebattle quater final (older MicroW8 version with monochrome palette)
|
||||||
* [XorScroll](v0.1pre2#AQovAS0BAX8DQCABIAFBwAJvIABBCm1qIAFBwAJtczoAeCABQQFqIgFBgNgESA0ACws=) (50 bytes uncompressed): A simple scrolling XOR pattern. Fun fact: This is the pre-loaded effect when entering a bytebattle.
|
* [XorScroll](v0.1pre2#AQovAS0BAX8DQCABIAFBwAJvIABBCm1qIAFBwAJtczoAeCABQQFqIgFBgNgESA0ACws=) (50 bytes uncompressed): A simple scrolling XOR pattern. Fun fact: This is the pre-loaded effect when entering a bytebattle.
|
||||||
* [CircleWorm](v0.1pre2#AQp7AXkCAX8CfUEgEA0DQCABskEEspUiAkECspUgALJBiCeylSIDQQWylJIQAEEBspJBoAGylCACQQOylSADQQSylJIQAEEBspJB+ACylCADQRGylCACQQKylJIQAEECspJBELKUIAFBAmxBP2oQEiABQQFqIgFBP0gNAAsL) (126 bytes uncompressed): Just a test for the circle fill function.
|
* [CircleWorm](v0.1pre2#AQp7AXkCAX8CfUEgEA0DQCABskEEspUiAkECspUgALJBiCeylSIDQQWylJIQAEEBspJBoAGylCACQQOylSADQQSylJIQAEEBspJB+ACylCADQRGylCACQQKylJIQAEECspJBELKUIAFBAmxBP2oQEiABQQFqIgFBP0gNAAsL) (126 bytes uncompressed): Just a test for the circle fill function.
|
||||||
|
|
||||||
## Versions
|
## Versions
|
||||||
|
|
||||||
* [v0.1pre1](v0.1pre1)
|
### v0.1.2
|
||||||
* [v0.1pre2](v0.1pre2)
|
|
||||||
* [v0.1pre3](v0.1pre3)
|
* [Web runtime](v0.1.2)
|
||||||
* [v0.1pre4](v0.1pre4)
|
* [Linux](https://github.com/exoticorn/microw8/releases/download/v0.1.2/microw8-0.1.2-linux.tgz)
|
||||||
* [v0.1pre5](v0.1pre5)
|
* [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
|
||||||
|
|
||||||
|
* [Web runtime](v0.1.0)
|
||||||
|
* [Linux](https://github.com/exoticorn/microw8/releases/download/v0.1.0/microw8-0.1.0-linux.tgz)
|
||||||
|
* [MacOS](https://github.com/exoticorn/microw8/releases/download/v0.1.0/microw8-0.1.0-macos.tgz)
|
||||||
|
* [Windows](https://github.com/exoticorn/microw8/releases/download/v0.1.0/microw8-0.1.0-windows.zip)
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|
||||||
@@ -105,7 +105,7 @@ as a cheap random-access PRNG (aka noise function).
|
|||||||
|
|
||||||
## Graphics
|
## Graphics
|
||||||
|
|
||||||
The default palette can be seen [here](../v0.1pre4#AgKaeeOuwg5gCKvFIeiitEwMpUI2rymEcu+DDB1vMu9uBoufvUxIr4Y5p4Jj2ukoNO4PE7QS5cN1ZyDMCRfSzYIGZxKlN2J6NKEWK7KVPk9wVUgn1Ip+hsMinWgEO8ETKfPuHoIa4kjI+ULFOMad7vd3rt/lh1Vy9w+R2MXG/7T61d3c7C6KY+eQNS0eW3ys4iU8R6SycuWZuuZ2Sg3Qxp826s+Kt+2qBojpzNOSoyFqyrVyYMTKEkSl0BZOj59Cs1hPm5bq0F1MmVhGAzMhW9V4YeAe). (Press Z on the keyboard to switch to palette.)
|
The default palette can be seen [here](../v0.1.0#At/p39+IBnj6ry1TRe7jzVy2A4tXgBvmoW2itzoyF2aM28pGy5QDiKxqrk8l9sbWZLtnAb+jgOfU+9QhpuyCAkhN6gPOU481IUL/df96vNe3h288Dqwhd3sfFpothIVFsMwRK72kW2hiR7zWsaXyy5pNmjR6BJk4piWx9ApT1ZwoUajhk6/zij6itq/FD1U3jj/J3MOwqZ2ef8Bv6ZPQlJIYVf62icGa69wS6SI1qBpIFiF14F8PcztRVbKIxLpT4ArCS6nz6FPnyUkqATGSBNPJ). (Press Z on the keyboard to switch to palette.)
|
||||||
|
|
||||||
The palette can be changed by writing 32bit rgba colors to addresses 0x13000-0x13400.
|
The palette can be changed by writing 32bit rgba colors to addresses 0x13000-0x13400.
|
||||||
|
|
||||||
@@ -192,7 +192,7 @@ The integer time in milliseconds can also be read at address 0x40.
|
|||||||
|
|
||||||
## Text output
|
## Text output
|
||||||
|
|
||||||
The default font can be seen [here](../v0.1pre4#AgKaeeOuwg5gCKvFIeiitEwMpUI2rymEcu+DDB1vMu9uBoufvUxIr4Y5p4Jj2ukoNO4PE7QS5cN1ZyDMCRfSzYIGZxKlN2J6NKEWK7KVPk9wVUgn1Ip+hsMinWgEO8ETKfPuHoIa4kjI+ULFOMad7vd3rt/lh1Vy9w+R2MXG/7T61d3c7C6KY+eQNS0eW3ys4iU8R6SycuWZuuZ2Sg3Qxp826s+Kt+2qBojpzNOSoyFqyrVyYMTKEkSl0BZOj59Cs1hPm5bq0F1MmVhGAzMhW9V4YeAe).
|
The default font can be seen [here](../v0.1.0#At/p39+IBnj6ry1TRe7jzVy2A4tXgBvmoW2itzoyF2aM28pGy5QDiKxqrk8l9sbWZLtnAb+jgOfU+9QhpuyCAkhN6gPOU481IUL/df96vNe3h288Dqwhd3sfFpothIVFsMwRK72kW2hiR7zWsaXyy5pNmjR6BJk4piWx9ApT1ZwoUajhk6/zij6itq/FD1U3jj/J3MOwqZ2ef8Bv6ZPQlJIYVf62icGa69wS6SI1qBpIFiF14F8PcztRVbKIxLpT4ArCS6nz6FPnyUkqATGSBNPJ).
|
||||||
|
|
||||||
The font can be changed by writing 1bpp 8x8 characters to addresses 0x13400-0x13c00.
|
The font can be changed by writing 1bpp 8x8 characters to addresses 0x13400-0x13c00.
|
||||||
|
|
||||||
@@ -284,6 +284,9 @@ Runs `<file>` which can be a binary WebAssembly module, an `.uw8` cart, a wat (W
|
|||||||
|
|
||||||
Options:
|
Options:
|
||||||
|
|
||||||
|
* `-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)
|
||||||
* `-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.
|
||||||
* `-u`, `--uncompressed`: Use the uncompressed `uw8` format for packing.
|
* `-u`, `--uncompressed`: Use the uncompressed `uw8` format for packing.
|
||||||
@@ -303,6 +306,27 @@ Options:
|
|||||||
* `-u`, `--uncompressed`: Use the uncompressed `uw8` format for packing.
|
* `-u`, `--uncompressed`: Use the uncompressed `uw8` format for packing.
|
||||||
* `-l LEVEL`, `--level LEVEL`: Compression level (0-9). Higher compression levels are really slow.
|
* `-l LEVEL`, `--level LEVEL`: Compression level (0-9). Higher compression levels are really slow.
|
||||||
|
|
||||||
|
## `uw8 unpack`
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
|
||||||
|
`uw8 unpack <infile> <outfile>`
|
||||||
|
|
||||||
|
Unpacks a MicroW8 module into a standard WebAssembly module.
|
||||||
|
|
||||||
|
## `uw8 compile`
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
|
||||||
|
`uw8 compile [<options>] <infile> <outfile>`
|
||||||
|
|
||||||
|
Compiles a [CurlyWas](https://github.com/exoticorn/curlywas) source file to a standard WebAssembly module. Most useful together with
|
||||||
|
the `--debug` option to get a module that works well in the Chrome debugger.
|
||||||
|
|
||||||
|
Options:
|
||||||
|
|
||||||
|
* `-d`, `--debug`: Generate a name section to help debugging
|
||||||
|
|
||||||
## `uw8 filter-exports`
|
## `uw8 filter-exports`
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
@@ -388,3 +412,33 @@ the first function in the file under the name `upd`.
|
|||||||
|
|
||||||
Same as version `01` except everything after the first byte is compressed
|
Same as version `01` except everything after the first byte is compressed
|
||||||
using a [custom LZ compression scheme](https://github.com/exoticorn/upkr).
|
using a [custom LZ compression scheme](https://github.com/exoticorn/upkr).
|
||||||
|
|
||||||
|
# The web runtime
|
||||||
|
|
||||||
|
Load carts into the web runtime either by using the "Load cart..." button, or by dragging the file
|
||||||
|
onto the screen area.
|
||||||
|
|
||||||
|
## Input
|
||||||
|
|
||||||
|
For input, you can either use a standard gamepad or keyboard. On a keyboard use the arrow keys and the keys Z, X, A and S to emulate the A, B, X and Y buttons.
|
||||||
|
|
||||||
|
## Video recording
|
||||||
|
|
||||||
|
Press F10 to start recording, press again to stop. Then a download dialog will open for the video file.
|
||||||
|
The file might miss some metadata needed to load in some video editing tools, in that case you can run
|
||||||
|
it through ffmpeg like this `ffmpeg -i IN_NAME.webm -c copy -o OUT_NAME.webm to fix it up.
|
||||||
|
|
||||||
|
To convert it to 1280x720, for example for a lovebyte upload, you can use:
|
||||||
|
|
||||||
|
```
|
||||||
|
ffmpeg -i IN.webm -vf "scale=960:720:flags=neighbor,pad=1280:720:160:0" -r 60 OUT.mp4
|
||||||
|
```
|
||||||
|
|
||||||
|
## Screenshot
|
||||||
|
|
||||||
|
Pressing F9 opens a download dialog with a screenshot.
|
||||||
|
|
||||||
|
## Devkit mode
|
||||||
|
|
||||||
|
Append `#devkit` to the web runtime url in order to switch to devkit mode. In devkit mode, standard web assembly modules
|
||||||
|
are loaded bypassing the loader, removing all size restrictions. At the same time, the memory limit is increased to 1GB.
|
||||||
5
site/static/img/microw8.svg
Normal file
5
site/static/img/microw8.svg
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
<svg width="120" height="120" version="1.1" viewBox="0 0 120 120" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="m3.9727 6.3613v27.945h7.8691l2.8281 66.549 6.9199-56.922h16.969l6.4375 56.922 4.1523-66.549h17.148l-9.5488 81.697h27.195c-4.1759-2.1972-7.3595-5.016-9.5234-8.4707-2.2464-3.6905-3.3691-7.6805-3.3691-11.973 0-4.7735 1.3845-8.9251 4.1523-12.455 2.7679-3.53 6.4981-6.3194 11.191-8.3652-4.2521-2.7277-7.3402-5.4948-9.2656-8.3027-1.8854-2.8481-2.8281-6.5783-2.8281-11.191 0-4.7334 1.223-8.8256 3.6699-12.275 2.4469-3.4498 5.7559-6.0981 9.9277-7.9434 4.212-1.8854 8.9647-2.8262 14.26-2.8262 5.2203 0 9.8397 0.84602 13.867 2.5234v-28.363h-40.227a14.855 14.855 0 0 1 0.019531 0.40234 14.855 14.855 0 0 1-14.855 14.855 14.855 14.855 0 0 1-14.855-14.855 14.855 14.855 0 0 1 0.052734-0.40234zm98.186 38.775c-3.0888 0-5.4939 0.863-7.2187 2.5879-1.6848 1.6848-2.5273 4.192-2.5273 7.5215 0 2.9283 0.98169 5.3538 2.9473 7.2793 1.9656 1.8854 5.0352 3.6113 9.207 5.1758 2.7277-2.0057 4.5928-3.971 5.5957-5.8965 1.0028-1.9656 1.5039-4.1129 1.5039-6.4395 0-2.9684-0.8017-5.4144-2.4062-7.3398-1.5644-1.9255-3.9326-2.8887-7.1016-2.8887zm-72.203 12.938-6.4902 57.93h12.393zm68.113 21.781c-2.4469 1.5644-4.3938 3.5502-5.8379 5.957-1.4441 2.4068-2.166 5.2741-2.166 8.6035 0 3.4498 1.0041 6.2781 3.0098 8.4844 2.0458 2.1662 5.0726 3.25 9.084 3.25 4.1317 0 7.142-1.1043 9.0274-3.3106 1.8854-2.2464 2.8281-4.8723 2.8281-7.8809 0-2.7679-0.56235-5.0153-1.6856-6.7402-1.0831-1.7249-2.7886-3.2096-5.1152-4.4531-2.3266-1.2836-5.3738-2.5864-9.1445-3.9102z" fill="#85a6b2" fill-rule="evenodd"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.6 KiB |
1
site/static/v0.1.1/index.html
Normal file
1
site/static/v0.1.1/index.html
Normal file
File diff suppressed because one or more lines are too long
1
site/static/v0.1.2/index.html
Normal file
1
site/static/v0.1.2/index.html
Normal file
File diff suppressed because one or more lines are too long
@@ -4,7 +4,7 @@
|
|||||||
<section>
|
<section>
|
||||||
<h1 class="text-center heading-text">A WebAssembly based fantasy console</h1>
|
<h1 class="text-center heading-text">A WebAssembly based fantasy console</h1>
|
||||||
</section>
|
</section>
|
||||||
<a href="v0.1pre5">
|
<a href="v0.1.1">
|
||||||
<img class="demonstration-gif" style="width:640px;height:480px;image-rendering:pixelated" src="img/technotunnel.png"></img>
|
<img class="demonstration-gif" style="width:640px;height:480px;image-rendering:pixelated" src="img/technotunnel.png"></img>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
55
src/filewatcher.rs
Normal file
55
src/filewatcher.rs
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
use anyhow::{anyhow, bail, Result};
|
||||||
|
use notify::{DebouncedEvent, RecommendedWatcher, Watcher};
|
||||||
|
use std::{collections::BTreeSet, path::PathBuf, sync::mpsc, time::Duration};
|
||||||
|
|
||||||
|
pub struct FileWatcher {
|
||||||
|
watcher: RecommendedWatcher,
|
||||||
|
watched_files: BTreeSet<PathBuf>,
|
||||||
|
directories: BTreeSet<PathBuf>,
|
||||||
|
rx: mpsc::Receiver<DebouncedEvent>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FileWatcher {
|
||||||
|
pub fn new() -> Result<FileWatcher> {
|
||||||
|
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>> {
|
||||||
|
let event = self.rx.try_recv();
|
||||||
|
match event {
|
||||||
|
Ok(DebouncedEvent::Create(path) | DebouncedEvent::Write(path)) => {
|
||||||
|
let handle = same_file::Handle::from_path(&path)?;
|
||||||
|
for file in &self.watched_files {
|
||||||
|
if handle == same_file::Handle::from_path(file)? {
|
||||||
|
return Ok(Some(path));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(mpsc::TryRecvError::Disconnected) => bail!("File watcher disconnected"),
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
203
src/lib.rs
203
src/lib.rs
@@ -1,191 +1,22 @@
|
|||||||
use std::io::prelude::*;
|
mod filewatcher;
|
||||||
use std::path::Path;
|
#[cfg(feature = "native")]
|
||||||
use std::{fs::File, time::Instant};
|
mod run_native;
|
||||||
|
#[cfg(feature = "browser")]
|
||||||
|
mod run_web;
|
||||||
|
|
||||||
|
pub use filewatcher::FileWatcher;
|
||||||
|
#[cfg(feature = "native")]
|
||||||
|
pub use run_native::MicroW8;
|
||||||
|
#[cfg(feature = "browser")]
|
||||||
|
pub use run_web::RunWebServer;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use minifb::{Key, Window, WindowOptions};
|
|
||||||
use wasmtime::{
|
|
||||||
Engine, GlobalType, Memory, MemoryType, Module, Mutability, Store, TypedFunc, ValType,
|
|
||||||
};
|
|
||||||
|
|
||||||
static GAMEPAD_KEYS: &'static [Key] = &[Key::Up, Key::Down, Key::Left, Key::Right, Key::Z, Key::X, Key::A, Key::S];
|
pub trait Runtime {
|
||||||
|
fn is_open(&self) -> bool;
|
||||||
pub struct MicroW8 {
|
fn set_timeout(&mut self, _timeout: u32) {
|
||||||
engine: Engine,
|
eprintln!("Warning: runtime doesn't support timeout");
|
||||||
loader_module: Module,
|
|
||||||
window: Window,
|
|
||||||
window_buffer: Vec<u32>,
|
|
||||||
instance: Option<UW8Instance>,
|
|
||||||
}
|
|
||||||
|
|
||||||
struct UW8Instance {
|
|
||||||
store: Store<()>,
|
|
||||||
memory: Memory,
|
|
||||||
end_frame: TypedFunc<(), ()>,
|
|
||||||
update: TypedFunc<(), ()>,
|
|
||||||
start_time: Instant,
|
|
||||||
module: Vec<u8>
|
|
||||||
}
|
|
||||||
|
|
||||||
impl MicroW8 {
|
|
||||||
pub fn new() -> Result<MicroW8> {
|
|
||||||
let engine = wasmtime::Engine::default();
|
|
||||||
|
|
||||||
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,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_open(&self) -> bool {
|
|
||||||
self.window.is_open() && !self.window.is_key_down(Key::Escape)
|
|
||||||
}
|
|
||||||
|
|
||||||
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 instance = linker.instantiate(&mut store, &module)?;
|
|
||||||
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()
|
|
||||||
});
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn run_frame(&mut self) -> Result<()> {
|
|
||||||
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().unwrap_or(Vec::new()) {
|
|
||||||
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());
|
|
||||||
}
|
|
||||||
|
|
||||||
instance.update.call(&mut instance.store, ())?;
|
|
||||||
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 {
|
|
||||||
self.instance = Some(instance);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.window
|
|
||||||
.update_with_buffer(&self.window_buffer, 320, 240)?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
fn load(&mut self, module_data: &[u8]) -> Result<()>;
|
||||||
|
fn run_frame(&mut self) -> Result<()>;
|
||||||
}
|
}
|
||||||
189
src/main.rs
189
src/main.rs
@@ -1,35 +1,40 @@
|
|||||||
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::sync::mpsc;
|
|
||||||
use std::time::Duration;
|
|
||||||
use std::{
|
|
||||||
path::{Path, PathBuf},
|
|
||||||
process::exit,
|
|
||||||
};
|
|
||||||
|
|
||||||
use anyhow::{bail, Result};
|
use anyhow::Result;
|
||||||
use notify::{DebouncedEvent, Watcher};
|
|
||||||
use pico_args::Arguments;
|
use pico_args::Arguments;
|
||||||
|
#[cfg(feature = "native")]
|
||||||
use uw8::MicroW8;
|
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("compile") => compile(args),
|
||||||
Some("filter-exports") => filter_exports(args),
|
Some("filter-exports") => filter_exports(args),
|
||||||
Some("help") | None => {
|
Some("help") | None => {
|
||||||
println!("uw8 {}", env!("CARGO_PKG_VERSION"));
|
println!("uw8 {}", env!("CARGO_PKG_VERSION"));
|
||||||
println!();
|
println!();
|
||||||
println!("Usage:");
|
println!("Usage:");
|
||||||
println!(" uw8 run [-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 compile [-d/--debug] <in-file> <out-file>");
|
||||||
println!(" uw8 filter-exports <in-wasm> <out-wasm>");
|
println!(" uw8 filter-exports <in-wasm> <out-wasm>");
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -40,8 +45,10 @@ 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 mut config = Config::default();
|
let mut config = Config::default();
|
||||||
if args.contains(["-p", "--pack"]) {
|
if args.contains(["-p", "--pack"]) {
|
||||||
@@ -63,36 +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()?;
|
||||||
|
|
||||||
let (tx, rx) = mpsc::channel();
|
use std::process::exit;
|
||||||
let mut watcher = notify::watcher(tx, Duration::from_millis(100))?;
|
|
||||||
|
|
||||||
if watch_mode {
|
let mut runtime: Box<dyn Runtime> = if !run_browser {
|
||||||
watcher.watch(&filename, notify::RecursiveMode::NonRecursive)?;
|
#[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 {
|
||||||
|
runtime.set_timeout(timeout);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Err(err) = start_cart(&filename, &mut uw8, &config) {
|
let mut first_run = true;
|
||||||
|
|
||||||
|
while runtime.is_open() {
|
||||||
|
if first_run || watcher.poll_changed_file()?.is_some() {
|
||||||
|
match start_cart(&filename, &mut *runtime, &config) {
|
||||||
|
Ok(dependencies) => {
|
||||||
|
if watch_mode {
|
||||||
|
for dep in dependencies {
|
||||||
|
watcher.add_file(dep)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
eprintln!("Load error: {}", err);
|
eprintln!("Load error: {}", err);
|
||||||
if !watch_mode {
|
if !watch_mode {
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
while uw8.is_open() {
|
|
||||||
match rx.try_recv() {
|
|
||||||
Ok(DebouncedEvent::Create(_) | DebouncedEvent::Write(_)) => {
|
|
||||||
if let Err(err) = start_cart(&filename, &mut uw8, &config) {
|
|
||||||
eprintln!("Load error: {}", err);
|
|
||||||
}
|
}
|
||||||
}
|
first_run = false;
|
||||||
Err(mpsc::TryRecvError::Disconnected) => bail!("File watcher disconnected"),
|
|
||||||
_ => (),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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);
|
||||||
@@ -109,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 dependencies = Vec::new();
|
||||||
|
let mut cart = match SourceType::of_file(filename)? {
|
||||||
|
SourceType::Binary => {
|
||||||
let mut cart = vec![];
|
let mut cart = vec![];
|
||||||
File::open(filename)?.read_to_end(&mut cart)?;
|
File::open(filename)?.read_to_end(&mut cart)?;
|
||||||
|
dependencies.push(filename.to_path_buf());
|
||||||
if cart[0] >= 10 {
|
cart
|
||||||
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())?
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
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 let Some(pack_config) = pack {
|
if let Some(ref pack_config) = 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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -160,13 +231,41 @@ 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)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn unpack(mut args: Arguments) -> Result<()> {
|
||||||
|
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()))?;
|
||||||
|
|
||||||
|
uw8_tool::unpack_file(&in_file, &out_file)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn compile(mut args: Arguments) -> Result<()> {
|
||||||
|
let mut options = curlywas::Options::default();
|
||||||
|
if args.contains(["-d", "--debug"]) {
|
||||||
|
options = options.with_debug();
|
||||||
|
}
|
||||||
|
|
||||||
|
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 module = curlywas::compile_file(in_file, options)?;
|
||||||
|
File::create(out_file)?.write_all(&module.wasm)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn filter_exports(mut args: Arguments) -> Result<()> {
|
fn filter_exports(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()))?;
|
||||||
|
|||||||
1
src/run-web.html
Normal file
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
260
src/run_native.rs
Normal 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
89
src/run_web.rs
Normal 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
13
test.cwa
Normal 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)
|
||||||
|
}
|
||||||
@@ -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 ¶m 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)
|
||||||
|
];
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ use std::{
|
|||||||
use wasm_encoder as enc;
|
use wasm_encoder as enc;
|
||||||
use wasmparser::{
|
use wasmparser::{
|
||||||
BinaryReader, ExportSectionReader, ExternalKind, FunctionBody, FunctionSectionReader,
|
BinaryReader, ExportSectionReader, ExternalKind, FunctionBody, FunctionSectionReader,
|
||||||
ImportSectionEntryType, ImportSectionReader, TypeSectionReader,
|
ImportSectionEntryType, ImportSectionReader, TableSectionReader, TypeSectionReader,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct PackConfig {
|
pub struct PackConfig {
|
||||||
@@ -31,7 +31,9 @@ impl PackConfig {
|
|||||||
|
|
||||||
impl Default for PackConfig {
|
impl Default for PackConfig {
|
||||||
fn default() -> PackConfig {
|
fn default() -> PackConfig {
|
||||||
PackConfig { compression: Some(2) }
|
PackConfig {
|
||||||
|
compression: Some(2),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -156,6 +158,8 @@ struct ParsedModule<'a> {
|
|||||||
start_section: Option<u32>,
|
start_section: Option<u32>,
|
||||||
function_bodies: Vec<wasmparser::FunctionBody<'a>>,
|
function_bodies: Vec<wasmparser::FunctionBody<'a>>,
|
||||||
data_section: Option<Section<()>>,
|
data_section: Option<Section<()>>,
|
||||||
|
table_section: Option<Section<()>>,
|
||||||
|
element_section: Option<Vec<Element>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> ParsedModule<'a> {
|
impl<'a> ParsedModule<'a> {
|
||||||
@@ -170,6 +174,8 @@ impl<'a> ParsedModule<'a> {
|
|||||||
let mut start_section = None;
|
let mut start_section = None;
|
||||||
let mut function_bodies = Vec::new();
|
let mut function_bodies = Vec::new();
|
||||||
let mut data_section = None;
|
let mut data_section = None;
|
||||||
|
let mut table_section = None;
|
||||||
|
let mut element_section = None;
|
||||||
|
|
||||||
let mut offset = 0;
|
let mut offset = 0;
|
||||||
|
|
||||||
@@ -209,6 +215,17 @@ impl<'a> ParsedModule<'a> {
|
|||||||
Payload::DataSection(_) => {
|
Payload::DataSection(_) => {
|
||||||
data_section = Some(Section::new(range, ()));
|
data_section = Some(Section::new(range, ()));
|
||||||
}
|
}
|
||||||
|
Payload::TableSection(reader) => {
|
||||||
|
validate_table_section(reader)?;
|
||||||
|
table_section = Some(Section::new(range, ()));
|
||||||
|
}
|
||||||
|
Payload::ElementSection(mut reader) => {
|
||||||
|
let mut elements = Vec::with_capacity(reader.get_count() as usize);
|
||||||
|
for _ in 0..reader.get_count() {
|
||||||
|
elements.push(Element::parse(reader.read()?)?);
|
||||||
|
}
|
||||||
|
element_section = Some(elements);
|
||||||
|
}
|
||||||
Payload::CodeSectionStart { .. } => (),
|
Payload::CodeSectionStart { .. } => (),
|
||||||
Payload::CodeSectionEntry(body) => function_bodies.push(body),
|
Payload::CodeSectionEntry(body) => function_bodies.push(body),
|
||||||
Payload::CustomSection { .. } => (),
|
Payload::CustomSection { .. } => (),
|
||||||
@@ -229,6 +246,8 @@ impl<'a> ParsedModule<'a> {
|
|||||||
start_section,
|
start_section,
|
||||||
function_bodies,
|
function_bodies,
|
||||||
data_section,
|
data_section,
|
||||||
|
table_section,
|
||||||
|
element_section,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -405,6 +424,10 @@ impl<'a> ParsedModule<'a> {
|
|||||||
module.section(&function_section);
|
module.section(&function_section);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(tables) = self.table_section {
|
||||||
|
copy_section(&mut module, &self.data[tables.range.clone()])?;
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(ref globals) = self.globals {
|
if let Some(ref globals) = self.globals {
|
||||||
copy_section(&mut module, &self.data[globals.range.clone()])?;
|
copy_section(&mut module, &self.data[globals.range.clone()])?;
|
||||||
for i in 0..globals.data {
|
for i in 0..globals.data {
|
||||||
@@ -446,6 +469,25 @@ impl<'a> ParsedModule<'a> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(elements) = self.element_section {
|
||||||
|
let mut element_section = wasm_encoder::ElementSection::new();
|
||||||
|
for element in elements {
|
||||||
|
let mut functions = Vec::with_capacity(element.functions.len());
|
||||||
|
for index in element.functions {
|
||||||
|
functions.push(*function_map.get(&index).ok_or_else(|| {
|
||||||
|
anyhow!("Function index {} not found in function map", index)
|
||||||
|
})?);
|
||||||
|
}
|
||||||
|
element_section.active(
|
||||||
|
None,
|
||||||
|
&wasm_encoder::Instruction::I32Const(element.start_index as i32),
|
||||||
|
ValType::FuncRef,
|
||||||
|
wasm_encoder::Elements::Functions(&functions),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
module.section(&element_section);
|
||||||
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
let mut code_section = enc::CodeSection::new();
|
let mut code_section = enc::CodeSection::new();
|
||||||
|
|
||||||
@@ -502,6 +544,19 @@ fn read_type_section(reader: TypeSectionReader) -> Result<Vec<base_module::Funct
|
|||||||
Ok(function_types)
|
Ok(function_types)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn validate_table_section(mut reader: TableSectionReader) -> Result<()> {
|
||||||
|
if reader.get_count() != 1 {
|
||||||
|
bail!("Only up to one table supported");
|
||||||
|
}
|
||||||
|
|
||||||
|
let type_ = reader.read()?;
|
||||||
|
if type_.element_type != wasmparser::Type::FuncRef {
|
||||||
|
bail!("Only one funcref table is supported");
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct Section<T> {
|
struct Section<T> {
|
||||||
range: std::ops::Range<usize>,
|
range: std::ops::Range<usize>,
|
||||||
@@ -579,6 +634,51 @@ impl ImportSection {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct Element {
|
||||||
|
start_index: u32,
|
||||||
|
functions: Vec<u32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Element {
|
||||||
|
fn parse(element: wasmparser::Element) -> Result<Element> {
|
||||||
|
if element.ty != wasmparser::Type::FuncRef {
|
||||||
|
bail!("Table element type is not FuncRef");
|
||||||
|
}
|
||||||
|
|
||||||
|
let start_index = if let wasmparser::ElementKind::Active {
|
||||||
|
init_expr,
|
||||||
|
table_index: 0,
|
||||||
|
} = element.kind
|
||||||
|
{
|
||||||
|
let mut init_reader = init_expr.get_operators_reader();
|
||||||
|
if let wasmparser::Operator::I32Const { value: start_index } = init_reader.read()? {
|
||||||
|
start_index as u32
|
||||||
|
} else {
|
||||||
|
bail!("Table element start index is not a integer constant");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
bail!("Unsupported table element kind");
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut items_reader = element.items.get_items_reader()?;
|
||||||
|
|
||||||
|
let mut functions = Vec::with_capacity(items_reader.get_count() as usize);
|
||||||
|
for _ in 0..items_reader.get_count() {
|
||||||
|
if let wasmparser::ElementItem::Func(index) = items_reader.read()? {
|
||||||
|
functions.push(index);
|
||||||
|
} else {
|
||||||
|
bail!("Table element item is not a function");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Element {
|
||||||
|
start_index,
|
||||||
|
functions,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct FunctionImport {
|
struct FunctionImport {
|
||||||
module: String,
|
module: String,
|
||||||
@@ -678,8 +778,13 @@ fn remap_function(
|
|||||||
.get(&function_index)
|
.get(&function_index)
|
||||||
.ok_or_else(|| anyhow!("Function index out of range: {}", function_index))?,
|
.ok_or_else(|| anyhow!("Function index out of range: {}", function_index))?,
|
||||||
),
|
),
|
||||||
De::CallIndirect { .. }
|
De::CallIndirect { index, table_index } => En::CallIndirect {
|
||||||
| De::ReturnCall { .. }
|
ty: *type_map
|
||||||
|
.get(&index)
|
||||||
|
.ok_or_else(|| anyhow!("Unknown function type in call indirect"))?,
|
||||||
|
table: table_index,
|
||||||
|
},
|
||||||
|
De::ReturnCall { .. }
|
||||||
| De::ReturnCallIndirect { .. }
|
| De::ReturnCallIndirect { .. }
|
||||||
| De::Delegate { .. }
|
| De::Delegate { .. }
|
||||||
| De::CatchAll => todo!(),
|
| De::CatchAll => todo!(),
|
||||||
|
|||||||
2
web/build-run-web
Executable file
2
web/build-run-web
Executable file
@@ -0,0 +1,2 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
rm -rf .parcel-cache && yarn parcel build src/run-web.html && cp dist/run-web.html ../src/
|
||||||
@@ -10,10 +10,12 @@
|
|||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="uw8">
|
<div id="uw8">
|
||||||
<a href="https://exoticorn.github.io/microw8">MicroW8</a> 0.1.0
|
<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>
|
<canvas id="screen" width="320" height="240">
|
||||||
|
</canvas>
|
||||||
|
<div id="timer" hidden="true"></div>
|
||||||
<div id="message"></div>
|
<div id="message"></div>
|
||||||
<button id="cartButton" style="visibility:hidden">Load cart...</button>
|
<button id="cartButton" style="visibility:hidden">Load cart...</button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
244
web/src/main.js
244
web/src/main.js
@@ -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,236 +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 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);
|
|
||||||
}
|
|
||||||
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 memory = new WebAssembly.Memory({ initial: 4, maximum: 4 });
|
|
||||||
let memU8 = U8(memory.buffer);
|
|
||||||
|
|
||||||
let importObject = {
|
|
||||||
env: {
|
|
||||||
memory
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
let loader;
|
|
||||||
|
|
||||||
let loadModuleData = (data) => {
|
|
||||||
if (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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
function runModuleFromHash() {
|
function runModuleFromHash() {
|
||||||
let hash = window.location.hash.slice(1);
|
let hash = window.location.hash.slice(1);
|
||||||
|
if(hash == 'devkit') {
|
||||||
|
uw8.setDevkitMode(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -253,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();
|
||||||
@@ -267,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]));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -289,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
319
web/src/microw8.js
Normal 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
46
web/src/run-web.css
Normal 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
18
web/src/run-web.html
Normal 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
19
web/src/run-web.js
Normal 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);
|
||||||
@@ -48,6 +48,16 @@ a:hover {
|
|||||||
cursor: none;
|
cursor: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#timer::before {
|
||||||
|
content: '';
|
||||||
|
display: inline-block;
|
||||||
|
width: 12px;
|
||||||
|
height: 12px;
|
||||||
|
border-radius: 6px;
|
||||||
|
background-color: red;
|
||||||
|
margin-right: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
#message {
|
#message {
|
||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user