WIP wasm API

main
Shad Amethyst 2 years ago
parent 21ae3bee01
commit 17c9af6063
Signed by: amethyst
GPG Key ID: D970C8DD1D6DEE36

@ -7,5 +7,5 @@ description = "A simple runner and editor for Stackline 2"
[dependencies]
clap = { version = "3.2", features = ["derive"] }
stackline = { path = "../stackline" }
stackline = { path = "../stackline", features = ["time"] }
serde_json = "1.0"

@ -11,8 +11,11 @@ crate-type = ["cdylib", "rlib"]
default = ["console_error_panic_hook"]
[dependencies]
wasm-bindgen = "0.2.63"
stackline = { path = "../stackline" }
wasm-bindgen = { version = "0.2.63", features = ["serde-serialize"] }
serde_json = "1.0"
serde = { version = "1", features = ["derive"] }
stackline = { path = "../stackline", features = [], default-features = false }
# The `console_error_panic_hook` crate provides better debugging of panics by
# logging them with `console.error`. This is great for development, but requires
@ -27,5 +30,8 @@ console_error_panic_hook = { version = "0.1.6", optional = true }
# Unfortunately, `wee_alloc` requires nightly Rust when targeting wasm for now.
wee_alloc = { version = "0.4.5", optional = true }
web-sys = { version = "0.3", features = ["console"] }
js-sys = "0.3"
[dev-dependencies]
wasm-bindgen-test = "0.3.13"

@ -1,3 +1,9 @@
# stackline-wasm
WASM bindings for stackline.
To build, run
```sh
wasm-pack build stackline-wasm --target web
```

@ -1,6 +1,16 @@
mod utils;
use serde::{Deserialize, Serialize};
use wasm_bindgen::prelude::*;
use js_sys::Function;
use stackline::pane::Pane as SLPane;
use stackline::signal::Signal as SLSignal;
use stackline::signal::Value;
use stackline::tile::AnyTile;
use stackline::tile::FullTile as SLFullTile;
use stackline::utils::Direction;
use stackline::world::World as SLWorld;
// When the `wee_alloc` feature is enabled, use `wee_alloc` as the global
// allocator.
@ -14,6 +24,247 @@ extern "C" {
}
#[wasm_bindgen]
pub fn greet() {
alert("Hello, stackline2-wasm!");
pub struct World(SLWorld);
#[wasm_bindgen(start)]
pub fn set_panic() {
utils::set_panic_hook();
}
#[wasm_bindgen]
pub fn available_tiles() -> Vec<JsValue> {
AnyTile::available().iter().map(|name| {
JsValue::from_str(name)
}).collect()
}
#[wasm_bindgen]
impl World {
/// Creates a new World instance
pub fn new() -> Self {
Self(SLWorld::new())
}
/// Initializes the World, making it ready to run
pub fn init(&mut self) {
self.0.init();
}
pub fn set_blink_duration(&mut self, blink_duration: f64) {
use std::time::Duration;
self.0
.set_blink_duration(Duration::from_secs_f64(blink_duration));
}
#[allow(non_snake_case)]
pub fn toString(&self) -> String {
format!("{:#}", self.0)
}
/// NOTE: We have to [`Clone`] the FullTile
pub fn get(&self, x: i32, y: i32) -> Option<FullTile> {
self.0.get((x, y)).map(|tile| FullTile((*tile).clone()))
}
pub fn set(&mut self, x: i32, y: i32, tile: &FullTile) {
if let Some(tile_ref) = self.0.get_mut((x, y)) {
*tile_ref = tile.0.clone();
}
}
pub fn get_pane(&self, name: String) -> Option<Pane> {
self.0.get_pane(&name).map(|pane| Pane(pane.clone()))
}
pub fn set_pane(&mut self, name: String, pane: Pane) {
self.0.set_pane(name, pane.0);
}
}
#[wasm_bindgen]
pub struct Pane(SLPane);
#[wasm_bindgen]
impl Pane {
pub fn empty(width: usize, height: usize) -> Option<Pane> {
SLPane::empty(width, height).map(|pane| Self(pane))
}
#[allow(non_snake_case)]
pub fn toString(&self) -> String {
format!("{:#?}", self.0)
}
#[wasm_bindgen(getter)]
pub fn width(&self) -> usize {
self.0.width().get()
}
#[wasm_bindgen(getter)]
pub fn height(&self) -> usize {
self.0.height().get()
}
#[wasm_bindgen(getter)]
pub fn position(&self) -> Vec<i32> {
let (x, y) = self.0.position();
vec![x, y]
}
#[wasm_bindgen(setter)]
pub fn set_position(&mut self, position: &[i32]) {
if let [x, y] = position[..] {
self.0.set_position((x, y));
}
}
}
#[derive(Clone, Debug)]
#[wasm_bindgen]
pub struct FullTile(SLFullTile);
#[wasm_bindgen]
impl FullTile {
pub fn empty() -> Self {
FullTile(SLFullTile::new(None))
}
#[wasm_bindgen(constructor)]
pub fn new(name: &str) -> Option<FullTile> {
AnyTile::new(name).map(|tile| FullTile(SLFullTile::new(Some(tile))))
}
#[allow(non_snake_case)]
pub fn toString(&self) -> String {
format!("{:#?}", self.0)
}
#[wasm_bindgen(getter)]
pub fn signal(&self) -> Option<Signal> {
self.0.signal().map(|signal| Signal(signal.clone()))
}
#[wasm_bindgen(setter)]
pub fn set_signal(&mut self, signal: Option<Signal>) {
self.0.set_signal(signal.map(|s| s.0));
}
#[wasm_bindgen(getter)]
pub fn tile(&self) -> JsValue {
self.0.get().map(|tile| {
JsValue::from_serde(tile).map_err(|err| {
err!("Error while serializing AnyTile: {}", err);
}).ok()
}).flatten().unwrap_or(JsValue::UNDEFINED)
}
#[wasm_bindgen(setter)]
pub fn set_tile(&mut self, tile: JsValue) {
if tile.is_null() || tile.is_undefined() {
self.0.set(None);
} else {
match tile.into_serde::<AnyTile>() {
Ok(tile) => self.0.set(Some(tile)),
Err(err) => err!("Error while deserializing AnyTile: {}", err),
}
}
}
pub fn map_tile(&mut self, callback: &Function) {
let res = callback.call1(&JsValue::NULL, &self.tile()).expect("Error while calling javascript callback");
self.set_tile(res);
}
}
#[derive(Serialize, Deserialize, Clone)]
#[serde(untagged)]
enum UntaggedValue {
Number(f64),
String(String),
}
impl From<&'_ Value> for UntaggedValue {
fn from(value: &'_ Value) -> Self {
match value {
Value::Number(x) => Self::Number(*x),
Value::String(x) => Self::String(x.clone()),
}
}
}
impl From<Value> for UntaggedValue {
fn from(value: Value) -> Self {
match value {
Value::Number(x) => Self::Number(x),
Value::String(x) => Self::String(x),
}
}
}
impl From<UntaggedValue> for Value {
fn from(value: UntaggedValue) -> Self {
match value {
UntaggedValue::Number(x) => Self::Number(x),
UntaggedValue::String(x) => Self::String(x),
}
}
}
#[derive(Clone, Debug)]
#[wasm_bindgen]
pub struct Signal(SLSignal);
#[wasm_bindgen]
impl Signal {
#[wasm_bindgen(constructor)]
pub fn new() -> Self {
Self(SLSignal::empty(Direction::Up))
}
/// Returns a read-only array
#[wasm_bindgen(getter)]
pub fn stack(&self) -> JsValue {
JsValue::from_serde(
&self
.0
.stack()
.iter()
.map(<UntaggedValue as From<&'_ Value>>::from)
.collect::<Vec<_>>(),
)
.unwrap()
}
#[wasm_bindgen(getter)]
pub fn direction(&self) -> u8 {
match self.0.direction() {
Direction::Up => 0,
Direction::Right => 1,
Direction::Down => 2,
Direction::Left => 3,
}
}
#[wasm_bindgen(setter)]
pub fn set_direction(&mut self, direction: u8) {
let direction = match direction {
0 => Direction::Up,
1 => Direction::Right,
2 => Direction::Down,
3 => Direction::Left,
_ => return,
};
self.0.set_direction(direction);
}
pub fn push(&mut self, value: JsValue) {
if let Some(num) = value.as_f64() {
self.0.push(Value::Number(num));
} else if let Some(string) = value.as_string() {
self.0.push(Value::String(string));
} else {
panic!("Invalid value: expected number or string, got {:?}", value);
}
}
}

@ -8,3 +8,19 @@ pub fn set_panic_hook() {
#[cfg(feature = "console_error_panic_hook")]
console_error_panic_hook::set_once();
}
// A macro to provide `println!(..)`-style syntax for `console.log` logging.
#[macro_export]
macro_rules! log {
( $( $t:tt )* ) => {
web_sys::console::log_1(&format!( $( $t )* ).into())
}
}
// A macro to provide `println!(..)`-style syntax for `console.error` logging.
#[macro_export]
macro_rules! err {
( $( $t:tt )* ) => {
web_sys::console::error_1(&format!( $( $t )* ).into())
}
}

@ -5,6 +5,9 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[features]
time = []
[dependencies]
palette = "0.6"
enum_dispatch = "0.3"

@ -142,7 +142,23 @@ fn generate_code(files: Vec<(PathBuf, Vec<String>)>, names: Vec<String>) -> Stri
}
res += " _ => None\n";
res += " }\n }\n}\n";
res += " }\n }\n";
writeln!(
res,
" pub fn available() -> [&'static str; {}] {{\n [",
names.len()
).unwrap();
for name in names.iter() {
writeln!(
res,
" \"{}\",",
name
)
.unwrap();
}
res += " ]\n";
res += " }\n}\n\n";
for name in names {
// impl<T: Tile> TryInto<&T> for &AnyTile

@ -3,6 +3,7 @@ use serde::{Deserialize, Serialize};
use veccell::{VecCell, VecRef, VecRefMut};
#[derive(Debug, Serialize, Deserialize)]
#[derive(Clone)]
pub struct Pane {
tiles: VecCell<FullTile>,
width: NonZeroUsize,

@ -140,6 +140,10 @@ impl Signal {
self.direction
}
pub fn set_direction(&mut self, direction: Direction) {
self.direction = direction;
}
/// Pushes a value onto the stack of the signal.
/// Signals are pushed on top of the stack and can be [`pop`ped](Signal::pop) in reverse order.
///

@ -64,6 +64,15 @@ impl FullTile {
self.cell.as_mut()
}
#[inline]
pub fn set(&mut self, tile: Option<AnyTile>) {
if tile.is_none() {
self.signal = None;
self.state = State::Idle;
}
self.cell = tile;
}
/// Returns the signal of this tile
#[inline]
pub fn signal(&self) -> Option<&Signal> {

@ -1,16 +1,25 @@
use super::*;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::time::{Duration, Instant};
use std::time::Duration;
use veccell::VecRef;
#[cfg(feature = "time")]
use std::time::Instant;
#[derive(Debug, Serialize, Deserialize)]
pub struct World {
panes: HashMap<String, Pane>,
#[serde(default = "Instant::now")]
#[serde(skip)]
#[cfg(feature = "time")]
blink_start: Instant,
#[serde(skip)]
#[cfg(not(feature = "time"))]
blink_duration: Duration,
#[serde(default)]
blink_speed: Duration,
}
@ -20,7 +29,12 @@ impl World {
Self {
panes: HashMap::new(),
#[cfg(feature = "time")]
blink_start: Instant::now(),
#[cfg(not(feature = "time"))]
blink_duration: Duration::default(),
blink_speed: Duration::default(),
}
}
@ -200,7 +214,7 @@ impl World {
}
pub fn draw(&self, dx: i32, dy: i32, surface: &mut TextSurface) {
let blink = Blink::new(Instant::now() - self.blink_start, self.blink_speed);
let blink = self.blink();
for pane in self.panes.values() {
pane.draw(dx, dy, surface, blink.clone());
}
@ -224,6 +238,21 @@ impl World {
pub fn set_blink(&mut self, blink_speed: Duration) {
self.blink_speed = blink_speed;
}
#[cfg(not(feature = "time"))]
pub fn set_blink_duration(&mut self, blink_duration: Duration) {
self.blink_duration = blink_duration;
}
#[cfg(feature = "time")]
fn blink(&self) -> Blink {
Blink::new(Instant::now() - self.blink_start, self.blink_speed)
}
#[cfg(not(feature = "time"))]
fn blink(&self) -> Blink {
Blink::new(self.blink_duration, self.blink_speed)
}
}
impl std::fmt::Display for World {
@ -233,7 +262,7 @@ impl std::fmt::Display for World {
let height = (bounds.3 - bounds.2) as usize;
let mut surface = TextSurface::new(width, height);
let blink = Blink::new(Instant::now() - self.blink_start, self.blink_speed);
let blink = self.blink();
for pane in self.panes.values() {
pane.draw(bounds.0, bounds.2, &mut surface, blink.clone());

Loading…
Cancel
Save