Compare commits
3 Commits
082fbf3e80
...
main
Author | SHA1 | Date | |
---|---|---|---|
c124e60168 | |||
042f2dca79 | |||
aae336ca66 |
2
.cargo/config.toml
Normal file
2
.cargo/config.toml
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
[alias]
|
||||||
|
run-wasm = "run --release --package run-wasm --"
|
7
.vscode/settings.json
vendored
Normal file
7
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
// "go.lintTool": "golint"
|
||||||
|
"go.toolsEnvVars": {
|
||||||
|
"GOOS": "wasip1",
|
||||||
|
"GOARCH": "wasm"
|
||||||
|
}
|
||||||
|
}
|
2414
Cargo.lock
generated
2414
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -10,7 +10,9 @@ anyhow = "1.0.82"
|
|||||||
|
|
||||||
[workspace]
|
[workspace]
|
||||||
members = [
|
members = [
|
||||||
|
"run-wasm",
|
||||||
"crates/editor",
|
"crates/editor",
|
||||||
"crates/minifb_host",
|
"crates/minifb_host",
|
||||||
"crates/player"
|
"crates/player",
|
||||||
|
"crates/web_runner"
|
||||||
]
|
]
|
@@ -4,3 +4,12 @@ To launch the editor:
|
|||||||
$ cargo run --manifest-path crates\editor\Cargo.toml
|
$ cargo run --manifest-path crates\editor\Cargo.toml
|
||||||
```
|
```
|
||||||
|
|
||||||
|
To run the web runner:
|
||||||
|
```
|
||||||
|
$ cargo run-wasm --manifest-path crates\web_runner\Cargo.toml --release --package web_runner
|
||||||
|
```
|
||||||
|
|
||||||
|
To build the web runner:
|
||||||
|
```
|
||||||
|
$ wasm-pack build crates\web_runner
|
||||||
|
```
|
@@ -7,5 +7,6 @@ edition = "2021"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0.82"
|
anyhow = "1.0.82"
|
||||||
|
minifb_host = { path = "../minifb_host" }
|
||||||
player = { path = "../player" }
|
player = { path = "../player" }
|
||||||
viperid = { path = "../.." }
|
viperid = { path = "../.." }
|
||||||
|
@@ -3,8 +3,7 @@ use std::{fs::File, io::Read, path::{Path, PathBuf}, process::Command};
|
|||||||
use viperid::VResult;
|
use viperid::VResult;
|
||||||
|
|
||||||
pub struct GoBuilder {
|
pub struct GoBuilder {
|
||||||
project_directory: PathBuf,
|
project_directory: PathBuf
|
||||||
entry_point: PathBuf
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GoBuilder {
|
impl GoBuilder {
|
||||||
@@ -27,15 +26,9 @@ impl GoBuilder {
|
|||||||
anyhow::bail!("project directory must exist: {}", project_directory.to_string_lossy())
|
anyhow::bail!("project directory must exist: {}", project_directory.to_string_lossy())
|
||||||
}
|
}
|
||||||
|
|
||||||
let entry_point = project_directory.join("main.go");
|
|
||||||
if !entry_point.exists() {
|
|
||||||
anyhow::bail!("entry point must exist: {}", entry_point.to_string_lossy())
|
|
||||||
}
|
|
||||||
|
|
||||||
// OK, we should be able to do things with Go without any errors
|
// OK, we should be able to do things with Go without any errors
|
||||||
Ok(GoBuilder {
|
Ok(GoBuilder {
|
||||||
project_directory: PathBuf::from(project_directory),
|
project_directory: PathBuf::from(project_directory),
|
||||||
entry_point
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -47,24 +40,17 @@ impl GoBuilder {
|
|||||||
self.project_directory.to_string_lossy()
|
self.project_directory.to_string_lossy()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if !self.entry_point.exists() {
|
|
||||||
anyhow::bail!(
|
|
||||||
"entry point disappeared: {}",
|
|
||||||
self.entry_point.to_string_lossy()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let project_directory = &self.project_directory;
|
let build_directory = self.project_directory.join("build");
|
||||||
let entry_point = &self.entry_point;
|
|
||||||
let build_directory = project_directory.join("build");
|
|
||||||
let wasm_name = build_directory.join("game.wasm");
|
let wasm_name = build_directory.join("game.wasm");
|
||||||
|
|
||||||
let mut child = Command::new("go")
|
let mut child = Command::new("go")
|
||||||
.args(["build", "-o"]).arg(&wasm_name)
|
.arg("-C").arg(self.project_directory.clone())
|
||||||
|
.args(["build", "-o"]).arg("build/game.wasm")
|
||||||
.env("GOOS", "wasip1")
|
.env("GOOS", "wasip1")
|
||||||
.env("GOARCH", "wasm")
|
.env("GOARCH", "wasm")
|
||||||
.arg("-trimpath")
|
.arg("-trimpath")
|
||||||
.arg(entry_point)
|
.arg(".")
|
||||||
.spawn()?;
|
.spawn()?;
|
||||||
let status = child.wait()?;
|
let status = child.wait()?;
|
||||||
if !status.success() {
|
if !status.success() {
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use player::HOSTED_DEVICE;
|
||||||
use viperid::VResult;
|
use viperid::VResult;
|
||||||
|
|
||||||
use crate::go_builder::GoBuilder;
|
use crate::go_builder::GoBuilder;
|
||||||
@@ -10,14 +11,14 @@ fn main() -> VResult<()> {
|
|||||||
let builder = GoBuilder::new(
|
let builder = GoBuilder::new(
|
||||||
&PathBuf::from("example_project"))?;
|
&PathBuf::from("example_project"))?;
|
||||||
let wasm = builder.build()?;
|
let wasm = builder.build()?;
|
||||||
|
|
||||||
let mut executor = player::Executor::new(&wasm)?;
|
let mut executor = player::Executor::new(&wasm)?;
|
||||||
|
|
||||||
while executor.is_running() {
|
minifb_host::host(HOSTED_DEVICE.with(|d| d.share()), || {
|
||||||
println!("update started");
|
|
||||||
executor.update();
|
executor.update();
|
||||||
println!("update completed");
|
|
||||||
executor.get_error()?;
|
executor.get_error()?;
|
||||||
}
|
Ok(())
|
||||||
|
})?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@@ -6,3 +6,5 @@ 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]
|
||||||
|
minifb = "0.25.0"
|
||||||
|
viperid = { path = "../.." }
|
||||||
|
@@ -0,0 +1,27 @@
|
|||||||
|
use minifb::{Window, WindowOptions};
|
||||||
|
use viperid::{Device, VResult, SCREEN_H, SCREEN_W};
|
||||||
|
|
||||||
|
pub fn host(
|
||||||
|
device: Device,
|
||||||
|
mut update: impl FnMut() -> VResult<()>
|
||||||
|
) -> VResult<()> {
|
||||||
|
let mut window = Window::new(
|
||||||
|
"viperid",
|
||||||
|
SCREEN_W,
|
||||||
|
SCREEN_H,
|
||||||
|
WindowOptions::default()
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// limit to 60FPS
|
||||||
|
window.limit_update_rate(Some(std::time::Duration::from_micros(16600)));
|
||||||
|
|
||||||
|
let mut buffer: Vec<u32> = vec![0; SCREEN_W * SCREEN_H];
|
||||||
|
while window.is_open() {
|
||||||
|
update()?;
|
||||||
|
device.shared.screen.to_bitmap(&mut buffer);
|
||||||
|
|
||||||
|
window.update_with_buffer(&buffer, SCREEN_W, SCREEN_H)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
@@ -8,4 +8,5 @@ edition = "2021"
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0.82"
|
anyhow = "1.0.82"
|
||||||
viperid = { path = "../.." }
|
viperid = { path = "../.." }
|
||||||
|
wasm-timer = "0.2.5"
|
||||||
wasmi = "0.31.2"
|
wasmi = "0.31.2"
|
||||||
|
@@ -3,14 +3,23 @@ use std::fmt::Display;
|
|||||||
use viperid::VResult;
|
use viperid::VResult;
|
||||||
use wasmi::{core::{HostError, Trap}, Caller, Linker};
|
use wasmi::{core::{HostError, Trap}, Caller, Linker};
|
||||||
|
|
||||||
use crate::executor::ExecutorState;
|
use crate::{executor::ExecutorState, HOSTED_DEVICE};
|
||||||
|
|
||||||
pub(crate) fn integrate(linker: &mut Linker<ExecutorState>) -> VResult<()> {
|
pub(crate) fn integrate(linker: &mut Linker<ExecutorState>) -> VResult<()> {
|
||||||
|
linker.func_wrap("viperid", "Pset", pset)?;
|
||||||
|
|
||||||
linker.func_wrap("viperid", "YieldFrame", yield_frame)?;
|
linker.func_wrap("viperid", "YieldFrame", yield_frame)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn pset(mut _caller: Caller<ExecutorState>, x: i32, y: i32, color: i32) -> Result<(), Trap> {
|
||||||
|
HOSTED_DEVICE.with(|dev| {
|
||||||
|
dev.shared.screen.pset(x, y, (color & 0xff) as u8);
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
fn yield_frame(_caller: Caller<ExecutorState>) -> Result<(), Trap> {
|
fn yield_frame(_caller: Caller<ExecutorState>) -> Result<(), Trap> {
|
||||||
Err(Trap::from(YieldFrame {}))
|
Err(Trap::from(YieldFrame {}))
|
||||||
}
|
}
|
||||||
|
@@ -11,13 +11,13 @@ pub struct Executor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) struct ExecutorState {
|
pub(crate) struct ExecutorState {
|
||||||
wasi: wasi::StockWasi
|
wasi: wasi::StockWasi,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ExecutorState {
|
impl ExecutorState {
|
||||||
fn new() -> Self {
|
fn new() -> Self {
|
||||||
ExecutorState {
|
ExecutorState {
|
||||||
wasi: StockWasi::new()
|
wasi: StockWasi::new(),
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
7
crates/player/src/hosted_device.rs
Normal file
7
crates/player/src/hosted_device.rs
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
use std::thread_local;
|
||||||
|
|
||||||
|
use viperid::Device;
|
||||||
|
|
||||||
|
thread_local! {
|
||||||
|
pub static HOSTED_DEVICE: Device = Device::new();
|
||||||
|
}
|
@@ -1,5 +1,7 @@
|
|||||||
mod engine_api;
|
mod engine_api;
|
||||||
mod executor;
|
mod executor;
|
||||||
mod wasi;
|
mod wasi;
|
||||||
|
mod hosted_device;
|
||||||
|
|
||||||
pub use executor::Executor;
|
pub use executor::Executor;
|
||||||
|
pub use hosted_device::HOSTED_DEVICE;
|
@@ -1,5 +1,3 @@
|
|||||||
use std::time::SystemTime;
|
|
||||||
|
|
||||||
use wasmi::core::Trap;
|
use wasmi::core::Trap;
|
||||||
|
|
||||||
use super::MinimalPreview1;
|
use super::MinimalPreview1;
|
||||||
@@ -50,7 +48,7 @@ impl MinimalPreview1 for StockWasi {
|
|||||||
println!("clock_time_get: {} {} {}", id, precision, outptr);
|
println!("clock_time_get: {} {} {}", id, precision, outptr);
|
||||||
// TODO: Actually fetch time
|
// TODO: Actually fetch time
|
||||||
let duration =
|
let duration =
|
||||||
SystemTime::now().duration_since(SystemTime::UNIX_EPOCH)
|
wasm_timer::SystemTime::now().duration_since(wasm_timer::SystemTime::UNIX_EPOCH)
|
||||||
.expect("time must be post-epoch");
|
.expect("time must be post-epoch");
|
||||||
|
|
||||||
write_u64(memory, outptr, duration.as_nanos() as u64);
|
write_u64(memory, outptr, duration.as_nanos() as u64);
|
||||||
|
30
crates/web_runner/Cargo.toml
Normal file
30
crates/web_runner/Cargo.toml
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
[package]
|
||||||
|
name = "web_runner"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[build]
|
||||||
|
target = "wasm32-unknown-unknown"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[features]
|
||||||
|
optimize = ["log/release_max_level_warn"]
|
||||||
|
default = ["optimize"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
error-iter = "0.4"
|
||||||
|
log = "0.4"
|
||||||
|
pixels = "0.13.0"
|
||||||
|
player = { path = "../player" }
|
||||||
|
viperid = { path = "../.." }
|
||||||
|
|
||||||
|
winit = "0.28"
|
||||||
|
winit_input_helper = "0.14"
|
||||||
|
console_error_panic_hook = "0.1"
|
||||||
|
console_log = "1"
|
||||||
|
wasm-bindgen = "0.2"
|
||||||
|
wasm-bindgen-futures = "0.4"
|
||||||
|
web-sys = { version = "0.3", features = ["GpuTextureFormat"] }
|
||||||
|
anyhow = "1.0.82"
|
||||||
|
|
128
crates/web_runner/src/main.rs
Normal file
128
crates/web_runner/src/main.rs
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
use error_iter::ErrorIter as _;
|
||||||
|
use log::error;
|
||||||
|
use pixels::{Pixels, SurfaceTexture};
|
||||||
|
use player::HOSTED_DEVICE;
|
||||||
|
use viperid::{Device, VResult, SCREEN_H, SCREEN_W};
|
||||||
|
// https://github.com/parasyte/pixels/blob/main/examples/minimal-web/src/main.rs
|
||||||
|
use winit::{dpi::LogicalSize, event::Event, event_loop::{ControlFlow, EventLoop}, window::WindowBuilder};
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
std::panic::set_hook(Box::new(console_error_panic_hook::hook));
|
||||||
|
console_log::init_with_level(log::Level::Trace).expect("error initializing logger");
|
||||||
|
wasm_bindgen_futures::spawn_local(run());
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn run() {
|
||||||
|
let wasm = include_bytes!("../../../example_project/build/game.wasm");
|
||||||
|
let mut executor =
|
||||||
|
player::Executor::new(wasm)
|
||||||
|
.expect("should have been able to create executor");
|
||||||
|
|
||||||
|
host(HOSTED_DEVICE.with(|d| d.share()), move || {
|
||||||
|
executor.update();
|
||||||
|
executor.get_error()?;
|
||||||
|
Ok(())
|
||||||
|
}).await.expect("error while hosting");
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn host(
|
||||||
|
device: Device,
|
||||||
|
mut update: impl FnMut() -> VResult<()> + 'static
|
||||||
|
) -> VResult<()> {
|
||||||
|
let event_loop = EventLoop::new();
|
||||||
|
|
||||||
|
let size = LogicalSize::new(SCREEN_W as f64, SCREEN_H as f64);
|
||||||
|
let window = WindowBuilder::new()
|
||||||
|
.with_title("viperid")
|
||||||
|
.with_inner_size(size)
|
||||||
|
.with_min_inner_size(size)
|
||||||
|
.build(&event_loop)
|
||||||
|
.expect("WindowBuilder error");
|
||||||
|
let window = Rc::new(window);
|
||||||
|
|
||||||
|
#[cfg(target_arch = "wasm32")]
|
||||||
|
{
|
||||||
|
use wasm_bindgen::JsCast;
|
||||||
|
use winit::platform::web::WindowExtWebSys;
|
||||||
|
|
||||||
|
// Retrieve current width and height dimensions of browser client window
|
||||||
|
let get_window_size = || {
|
||||||
|
let client_window = web_sys::window().unwrap();
|
||||||
|
LogicalSize::new(
|
||||||
|
client_window.inner_width().unwrap().as_f64().unwrap(),
|
||||||
|
client_window.inner_height().unwrap().as_f64().unwrap(),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
let window = Rc::clone(&window);
|
||||||
|
|
||||||
|
// Initialize winit window with current dimensions of browser client
|
||||||
|
// window.set_inner_size(get_window_size());
|
||||||
|
|
||||||
|
let client_window = web_sys::window().unwrap();
|
||||||
|
|
||||||
|
// Attach winit canvas to body element
|
||||||
|
web_sys::window()
|
||||||
|
.and_then(|win| win.document())
|
||||||
|
.and_then(|doc| doc.body())
|
||||||
|
.and_then(|body| {
|
||||||
|
body.append_child(&web_sys::Element::from(window.canvas()))
|
||||||
|
.ok()
|
||||||
|
})
|
||||||
|
.expect("couldn't append canvas to document body");
|
||||||
|
|
||||||
|
/*
|
||||||
|
// Listen for resize event on browser client. Adjust winit window dimensions
|
||||||
|
// on event trigger
|
||||||
|
let closure = wasm_bindgen::closure::Closure::wrap(Box::new(move |_e: web_sys::Event| {
|
||||||
|
let size = get_window_size();
|
||||||
|
window.set_inner_size(size)
|
||||||
|
}) as Box<dyn FnMut(_)>);
|
||||||
|
client_window
|
||||||
|
.add_event_listener_with_callback("resize", closure.as_ref().unchecked_ref())
|
||||||
|
.unwrap();
|
||||||
|
closure.forget();
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
// let mut input = WinitInputHelper::new();
|
||||||
|
let mut pixels = {
|
||||||
|
let window_size = window.inner_size();
|
||||||
|
let surface_texture =
|
||||||
|
SurfaceTexture::new(window_size.width, window_size.height, window.as_ref());
|
||||||
|
Pixels::new_async(SCREEN_W as u32, SCREEN_H as u32, surface_texture).await.expect("Pixels error")
|
||||||
|
};
|
||||||
|
|
||||||
|
event_loop.run(move |event, _, control_flow| {
|
||||||
|
if let Event::RedrawRequested(_) = event {
|
||||||
|
let frame = pixels.frame_mut();
|
||||||
|
if let Err(err) = update() {
|
||||||
|
log_anyhow_error("update", err);
|
||||||
|
*control_flow = ControlFlow::Exit;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
device.shared.screen.to_bitmap_slice(frame).expect("to_bitmap_slice error");
|
||||||
|
if let Err(err) = pixels.render() {
|
||||||
|
log_error("pixels.render", err);
|
||||||
|
*control_flow = ControlFlow::Exit;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.request_redraw();
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn log_error<E: std::error::Error + 'static>(method_name: &str, err: E) {
|
||||||
|
error!("{method_name}() failed: {err}");
|
||||||
|
for source in err.sources().skip(1) {
|
||||||
|
error!(" Caused by: {source}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn log_anyhow_error(method_name: &str, err: anyhow::Error) {
|
||||||
|
error!("{method_name}() failed: {err}");
|
||||||
|
}
|
@@ -1 +1,3 @@
|
|||||||
go 1.21.5
|
go 1.21.5
|
||||||
|
|
||||||
|
use .
|
@@ -1,21 +1,25 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import "example_project/viperid"
|
||||||
"fmt"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
count := 0
|
t := 0
|
||||||
for {
|
for {
|
||||||
fmt.Printf("Hello! %d", count)
|
for y := 0; y < 120; y++ {
|
||||||
count++
|
for x := 0; x < 160; x++ {
|
||||||
|
viperid.Pset(
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
(x+y+t)%64,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// fmt.Printf("Hello! %d", count)
|
||||||
|
// count++
|
||||||
/*
|
/*
|
||||||
debug.PrintStack()
|
debug.PrintStack()
|
||||||
*/
|
*/
|
||||||
YieldFrame()
|
viperid.YieldFrame()
|
||||||
|
t += 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//go:wasmimport viperid YieldFrame
|
|
||||||
//go:noescape
|
|
||||||
func YieldFrame()
|
|
||||||
|
13
example_project/viperid/exports.go
Normal file
13
example_project/viperid/exports.go
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
package viperid
|
||||||
|
|
||||||
|
func YieldFrame() {
|
||||||
|
yieldFrame()
|
||||||
|
}
|
||||||
|
|
||||||
|
func Pset(x int, y int, color int) {
|
||||||
|
pset(int32(x), int32(y), int32(color))
|
||||||
|
}
|
||||||
|
|
||||||
|
func Pget(x int, y int) int {
|
||||||
|
return int(pget(int32(x), int32(y)))
|
||||||
|
}
|
15
example_project/viperid/imports.go
Normal file
15
example_project/viperid/imports.go
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
//lint:file-ignore U1000 Ignore all unused code, it's generated
|
||||||
|
|
||||||
|
package viperid
|
||||||
|
|
||||||
|
//go:wasmimport viperid YieldFrame
|
||||||
|
//go:noescape
|
||||||
|
func yieldFrame()
|
||||||
|
|
||||||
|
//go:wasmimport viperid Pset
|
||||||
|
//go:noescape
|
||||||
|
func pset(x int32, y int32, color int32)
|
||||||
|
|
||||||
|
//go:wasmimport viperid Pget
|
||||||
|
//go:noescape
|
||||||
|
func pget(x int32, y int32) int32
|
7
run-wasm/Cargo.toml
Normal file
7
run-wasm/Cargo.toml
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
[package]
|
||||||
|
name = "run-wasm"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
cargo-run-wasm = "0.3"
|
9
run-wasm/src/main.rs
Normal file
9
run-wasm/src/main.rs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
fn main() {
|
||||||
|
let css = r#"body {
|
||||||
|
background-color: #000;
|
||||||
|
margin: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
}"#;
|
||||||
|
|
||||||
|
cargo_run_wasm::run_wasm_with_css(css);
|
||||||
|
}
|
@@ -182,14 +182,18 @@ function dump(fname, colors, width, height, scalar) {
|
|||||||
|
|
||||||
function dumpRust(fname, colors) {
|
function dumpRust(fname, colors) {
|
||||||
var paletteString = "";
|
var paletteString = "";
|
||||||
paletteString += "pub const PALETTE: [[u8; 3]; " + palette.length + "] = [\n"
|
paletteString += "pub const PALETTE: [u32; " + palette.length + "] = [\n"
|
||||||
for (var i = 0; i < colors.length; i++) {
|
for (var i = 0; i < colors.length; i++) {
|
||||||
var c = colors[i];
|
var c = colors[i];
|
||||||
paletteString += (" [" +
|
paletteString += (
|
||||||
clamp(255 * c.srgb.r) + ", " +
|
" " +
|
||||||
clamp(255 * c.srgb.g) + ", " +
|
(
|
||||||
clamp(255 * c.srgb.b) +
|
clamp(255 * c.srgb.r) * 256 * 256 +
|
||||||
"],\n");
|
clamp(255 * c.srgb.g) * 256 +
|
||||||
|
clamp(255 * c.srgb.b)
|
||||||
|
) +
|
||||||
|
",\n"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
paletteString += "];\n"
|
paletteString += "];\n"
|
||||||
fs.writeFileSync(fname, paletteString);
|
fs.writeFileSync(fname, paletteString);
|
||||||
|
35
src/device.rs
Normal file
35
src/device.rs
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
use crate::screen::Screen;
|
||||||
|
|
||||||
|
pub struct DeviceT {
|
||||||
|
pub screen: Screen
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DeviceT {
|
||||||
|
fn new() -> Self {
|
||||||
|
let screen = Screen::new();
|
||||||
|
|
||||||
|
DeviceT {
|
||||||
|
screen
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Device {
|
||||||
|
pub shared: Rc<DeviceT>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Device {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
return Device {
|
||||||
|
shared: Rc::new(DeviceT::new())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn share(&self) -> Self {
|
||||||
|
return Device {
|
||||||
|
shared: self.shared.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -1,4 +1,9 @@
|
|||||||
|
mod device;
|
||||||
mod palette;
|
mod palette;
|
||||||
|
mod screen;
|
||||||
|
|
||||||
|
pub use device::Device;
|
||||||
pub use palette::PALETTE;
|
pub use palette::PALETTE;
|
||||||
|
pub use screen::{Screen, SCREEN_W, SCREEN_H};
|
||||||
|
|
||||||
pub type VResult<T> = Result<T, anyhow::Error>;
|
pub type VResult<T> = Result<T, anyhow::Error>;
|
||||||
|
130
src/palette.rs
130
src/palette.rs
@@ -1,66 +1,66 @@
|
|||||||
pub const PALETTE: [[u8; 3]; 64] = [
|
pub const PALETTE: [u32; 64] = [
|
||||||
[0, 0, 0],
|
0,
|
||||||
[0, 0, 255],
|
255,
|
||||||
[0, 255, 0],
|
65280,
|
||||||
[0, 255, 255],
|
65535,
|
||||||
[255, 0, 0],
|
16711680,
|
||||||
[255, 0, 255],
|
16711935,
|
||||||
[255, 255, 0],
|
16776960,
|
||||||
[255, 255, 255],
|
16777215,
|
||||||
[32, 45, 44],
|
2108716,
|
||||||
[41, 60, 67],
|
2702403,
|
||||||
[18, 22, 32],
|
1185312,
|
||||||
[40, 34, 49],
|
2630193,
|
||||||
[27, 17, 29],
|
1773853,
|
||||||
[54, 42, 47],
|
3549743,
|
||||||
[46, 33, 23],
|
3023127,
|
||||||
[66, 60, 43],
|
4340779,
|
||||||
[39, 92, 148],
|
2579604,
|
||||||
[55, 83, 107],
|
3625835,
|
||||||
[110, 45, 90],
|
7220570,
|
||||||
[79, 50, 74],
|
5190218,
|
||||||
[130, 103, 52],
|
8546100,
|
||||||
[100, 85, 68],
|
6575428,
|
||||||
[80, 133, 85],
|
5277013,
|
||||||
[88, 106, 80],
|
5794384,
|
||||||
[59, 115, 206],
|
3896270,
|
||||||
[76, 108, 153],
|
5008537,
|
||||||
[165, 53, 114],
|
10827122,
|
||||||
[124, 70, 99],
|
8144483,
|
||||||
[188, 157, 22],
|
12360982,
|
||||||
[154, 137, 89],
|
10127705,
|
||||||
[57, 194, 137],
|
3785353,
|
||||||
[109, 161, 129],
|
7184769,
|
||||||
[39, 187, 255],
|
2604031,
|
||||||
[123, 67, 240],
|
8078320,
|
||||||
[186, 66, 177],
|
12206769,
|
||||||
[239, 60, 80],
|
15678544,
|
||||||
[255, 170, 48],
|
16755248,
|
||||||
[186, 255, 0],
|
12254976,
|
||||||
[69, 255, 115],
|
4587379,
|
||||||
[2, 241, 228],
|
192996,
|
||||||
[150, 190, 255],
|
9879295,
|
||||||
[185, 113, 226],
|
12153314,
|
||||||
[220, 118, 175],
|
14448303,
|
||||||
[249, 134, 85],
|
16352853,
|
||||||
[242, 204, 114],
|
15912050,
|
||||||
[194, 255, 116],
|
12779380,
|
||||||
[100, 255, 173],
|
6619053,
|
||||||
[105, 242, 252],
|
6943484,
|
||||||
[201, 169, 248],
|
13216248,
|
||||||
[221, 193, 228],
|
14533092,
|
||||||
[250, 156, 143],
|
16424079,
|
||||||
[240, 197, 180],
|
15779252,
|
||||||
[230, 237, 137],
|
15134089,
|
||||||
[233, 243, 193],
|
15332289,
|
||||||
[116, 253, 234],
|
7667178,
|
||||||
[182, 247, 242],
|
11991026,
|
||||||
[243, 222, 238],
|
15982318,
|
||||||
[237, 228, 236],
|
15590636,
|
||||||
[254, 235, 224],
|
16706528,
|
||||||
[245, 241, 240],
|
16118256,
|
||||||
[245, 254, 225],
|
16121569,
|
||||||
[250, 253, 246],
|
16449014,
|
||||||
[211, 250, 250],
|
13892346,
|
||||||
[230, 247, 244],
|
15136756,
|
||||||
];
|
];
|
||||||
|
62
src/screen.rs
Normal file
62
src/screen.rs
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
use std::cell::RefCell;
|
||||||
|
|
||||||
|
use crate::{VResult, PALETTE};
|
||||||
|
|
||||||
|
pub const SCREEN_W: usize = 640;
|
||||||
|
pub const SCREEN_H: usize = 480;
|
||||||
|
|
||||||
|
const PIXEL_SCALAR: usize = 4;
|
||||||
|
// const CURSES_SNAP: usize = 8;
|
||||||
|
|
||||||
|
pub struct Screen {
|
||||||
|
// NOTE: There are better data structures we could use than this
|
||||||
|
pixels: RefCell<Vec<u8>>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Screen {
|
||||||
|
pub fn new() -> Screen {
|
||||||
|
Screen {
|
||||||
|
pixels: RefCell::new(vec![0; SCREEN_W * SCREEN_H])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_bitmap(&self, bitmap: &mut Vec<u32>) {
|
||||||
|
bitmap.resize(SCREEN_W * SCREEN_H, 0);
|
||||||
|
|
||||||
|
let pxl = self.pixels.borrow();
|
||||||
|
for i in 0..pxl.len() {
|
||||||
|
bitmap[i] = PALETTE[pxl[i] as usize];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_bitmap_slice(&self, slice: &mut [u8]) -> VResult<()> {
|
||||||
|
if slice.len() != SCREEN_W * SCREEN_H * 4 {
|
||||||
|
anyhow::bail!("slice should be {} x {} in length", SCREEN_W, SCREEN_H);
|
||||||
|
}
|
||||||
|
|
||||||
|
let pxl = self.pixels.borrow();
|
||||||
|
for i in 0..pxl.len() {
|
||||||
|
slice[i * 4..i * 4 + 4].copy_from_slice(&(0xff000000 | PALETTE[pxl[i] as usize]).to_le_bytes());
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn pset(&self, x: i32, y: i32, color: u8) {
|
||||||
|
let px0 = x * PIXEL_SCALAR as i32;
|
||||||
|
let py0 = y * PIXEL_SCALAR as i32;
|
||||||
|
|
||||||
|
if px0 < 0 || py0 < 0 || px0 >= SCREEN_W as i32 || py0 >= SCREEN_H as i32 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let px0 = px0 as usize;
|
||||||
|
let py0 = py0 as usize;
|
||||||
|
|
||||||
|
let mut pxl = self.pixels.borrow_mut();
|
||||||
|
for py in py0..py0 + PIXEL_SCALAR {
|
||||||
|
for px in px0..px0 + PIXEL_SCALAR {
|
||||||
|
pxl[py * SCREEN_W + px] = color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user