parent
2597a7e1b7
commit
c0e562a3af
@ -0,0 +1,477 @@
|
|||||||
|
mod utils;
|
||||||
|
|
||||||
|
use js_sys::Function;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use wasm_bindgen::prelude::*;
|
||||||
|
|
||||||
|
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;
|
||||||
|
use stackline::text::TextSurface;
|
||||||
|
|
||||||
|
// TODO: refactor:
|
||||||
|
// - make everything camelCase
|
||||||
|
// - clearly separate serialized properties/functions from non-serialized
|
||||||
|
// - add javascript code to the classes?
|
||||||
|
|
||||||
|
// When the `wee_alloc` feature is enabled, use `wee_alloc` as the global
|
||||||
|
// allocator.
|
||||||
|
#[cfg(feature = "wee_alloc")]
|
||||||
|
#[global_allocator]
|
||||||
|
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
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
|
||||||
|
#[wasm_bindgen(constructor)]
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self(SLWorld::new())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deserialize(serialized: &JsValue) -> Option<World> {
|
||||||
|
serialized.into_serde().map_err(|err| {
|
||||||
|
err!("Error while deserializing World: {:?}", err);
|
||||||
|
}).ok().map(|world| World(world))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn serialize(&self) -> JsValue {
|
||||||
|
JsValue::from_serde(&self.0).map_err(|err| {
|
||||||
|
err!("Error while serializing World: {:?}", err);
|
||||||
|
}).unwrap_or(JsValue::NULL)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Initializes the World, making it ready to run
|
||||||
|
pub fn init(&mut self) {
|
||||||
|
self.0.init();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn step(&mut self) {
|
||||||
|
self.0.step();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run(&mut self, steps: usize) {
|
||||||
|
for _ in 0..steps {
|
||||||
|
self.step();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
} else if cfg!(debug_assertions) {
|
||||||
|
err!("Index out of bound: {}:{}", x, y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn panes(&self) -> Vec<JsValue> {
|
||||||
|
self.0.panes().keys().map(|key| {
|
||||||
|
JsValue::from_str(key)
|
||||||
|
}).collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn draw(&self, x: i32, y: i32, width: usize, height: usize) -> Vec<u32> {
|
||||||
|
let mut surface = TextSurface::new(width, height);
|
||||||
|
|
||||||
|
self.0.draw(x, y, &mut surface);
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
struct Char {
|
||||||
|
ch: char,
|
||||||
|
fg: (u8, u8, u8),
|
||||||
|
bg: Option<(u8, u8, u8)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_u32(red: u8, green: u8, blue: u8) -> u32 {
|
||||||
|
0xff000000 | (red as u32).checked_shl(16).unwrap() | (green as u32).checked_shl(8).unwrap() | (blue as u32)
|
||||||
|
}
|
||||||
|
|
||||||
|
surface.iter().map(|ch| {
|
||||||
|
vec![
|
||||||
|
ch.ch as u32,
|
||||||
|
to_u32(ch.fg.red, ch.fg.green, ch.fg.blue),
|
||||||
|
ch.bg.map(|bg| to_u32(bg.red, bg.green, bg.blue)).unwrap_or(0)
|
||||||
|
].into_iter()
|
||||||
|
}).flatten().collect::<Vec<_>>()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
pub struct Pane(SLPane);
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
impl Pane {
|
||||||
|
#[wasm_bindgen(constructor)]
|
||||||
|
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(setter)]
|
||||||
|
pub fn set_width(&mut self, width: usize) {
|
||||||
|
let height = self.0.height().get();
|
||||||
|
|
||||||
|
self.0.resize(width, height).unwrap_or_else(|| {
|
||||||
|
err!("Error while resizing Pane");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen(getter)]
|
||||||
|
pub fn height(&self) -> usize {
|
||||||
|
self.0.height().get()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen(setter)]
|
||||||
|
pub fn set_height(&mut self, height: usize) {
|
||||||
|
let width = self.0.width().get();
|
||||||
|
|
||||||
|
self.0.resize(width, height).unwrap_or_else(|| {
|
||||||
|
err!("Error while resizing Pane");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen(getter)]
|
||||||
|
pub fn position(&self) -> Vec<i32> {
|
||||||
|
let (x, y) = self.0.position();
|
||||||
|
vec![x, y]
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen(getter)]
|
||||||
|
pub fn x(&self) -> i32 {
|
||||||
|
self.0.position().0
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen(getter)]
|
||||||
|
pub fn y(&self) -> i32 {
|
||||||
|
self.0.position().1
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen(setter)]
|
||||||
|
pub fn set_x(&mut self, value: i32) {
|
||||||
|
let mut pos = self.0.position();
|
||||||
|
pos.0 = value;
|
||||||
|
self.0.set_position(pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen(setter)]
|
||||||
|
pub fn set_y(&mut self, value: i32) {
|
||||||
|
let mut pos = self.0.position();
|
||||||
|
pos.1 = value;
|
||||||
|
self.0.set_position(pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen(setter)]
|
||||||
|
pub fn set_position(&mut self, position: &[i32]) {
|
||||||
|
if let [x, y] = position[..] {
|
||||||
|
self.0.set_position((x, y));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get(&self, x: usize, y: usize) -> Option<FullTile> {
|
||||||
|
self.0.get((x, y)).map(|tile| FullTile((*tile).clone()))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set(&mut self, x: usize, y: usize, tile: FullTile) {
|
||||||
|
if let Some(target) = self.0.get_mut((x, y)) {
|
||||||
|
*target = tile.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn serialize(&self) -> JsValue {
|
||||||
|
JsValue::from_serde(&self.0).map_err(|err| {
|
||||||
|
err!("Error serializing Pane: {:?}", err);
|
||||||
|
}).unwrap_or(JsValue::NULL)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deserialize(value: JsValue) -> Option<Pane> {
|
||||||
|
value.into_serde::<SLPane>().map_err(|err| {
|
||||||
|
err!("Error deserializing Pane: {:?}", err);
|
||||||
|
}).ok().map(|pane| Pane(pane))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// NOTE: `self` must not be part of world
|
||||||
|
pub fn blit(&self, x: i32, y: i32, world: &mut World) {
|
||||||
|
for pane in world.0.panes().values() {
|
||||||
|
if pane as *const SLPane == &self.0 as *const SLPane {
|
||||||
|
panic!("Cannot blit to a World containing self");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (dx, dy, tile) in self.0.tiles_iter() {
|
||||||
|
let x = x + dx as i32;
|
||||||
|
let y = y + dy as i32;
|
||||||
|
world.set(x, y, FullTile((*tile).clone()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_text(&self) -> String {
|
||||||
|
let mut surface = TextSurface::new(self.0.width().get(), self.0.height().get());
|
||||||
|
|
||||||
|
self.0.draw(0, 0, &mut surface, stackline::utils::Blink::default());
|
||||||
|
|
||||||
|
format!("{:#}", surface)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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))))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn clone(&self) -> FullTile {
|
||||||
|
<Self as Clone>::clone(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
pub fn toString(&self) -> String {
|
||||||
|
format!("{:#?}", self.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen(getter)]
|
||||||
|
pub fn signal(&self) -> JsValue {
|
||||||
|
self.0.signal().and_then(|signal| JsValue::from_serde(signal).ok()).unwrap_or(JsValue::NULL)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen(setter)]
|
||||||
|
pub fn set_signal(&mut self, signal: JsValue) {
|
||||||
|
if signal.is_null() {
|
||||||
|
self.0.set_signal(None);
|
||||||
|
} else {
|
||||||
|
match signal.into_serde() {
|
||||||
|
Ok(signal) => {
|
||||||
|
self.0.set_signal(Some(signal));
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
err!("Couldn't serialize Signal: {:?}", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen(getter)]
|
||||||
|
pub fn state(&self) -> JsValue {
|
||||||
|
JsValue::from_serde(&self.0.state()).expect("Error while serializing State")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen(setter)]
|
||||||
|
pub fn set_state(&mut self, state: &JsValue) {
|
||||||
|
if let Ok(state) = state.into_serde() {
|
||||||
|
self.0.set_state(state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn schema(&self) -> JsValue {
|
||||||
|
use serde_json::Value;
|
||||||
|
use stackline::tile::{Tile, TileSchema};
|
||||||
|
|
||||||
|
fn construct_value(schema: TileSchema) -> Value {
|
||||||
|
match schema {
|
||||||
|
TileSchema::Tuple(arr) => {
|
||||||
|
Value::Array(arr.into_iter().map(construct_value).collect())
|
||||||
|
}
|
||||||
|
TileSchema::Map(map) => Value::Object(
|
||||||
|
map.into_iter()
|
||||||
|
.map(|(key, value)| (key, construct_value(value)))
|
||||||
|
.collect(),
|
||||||
|
),
|
||||||
|
TileSchema::Value(label, ty) => Value::String(format!("{}:{}", label, ty)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(tile) = self.0.get() {
|
||||||
|
JsValue::from_serde(&construct_value(tile.schema()))
|
||||||
|
.expect("Error while serializing TileSchema")
|
||||||
|
} else {
|
||||||
|
JsValue::UNDEFINED
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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()
|
||||||
|
.collect::<Vec<_>>(),
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen(getter)]
|
||||||
|
pub fn direction(&self) -> JsValue {
|
||||||
|
JsValue::from_serde(&self.0.direction()).expect("Couldn't serialize Direction")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen(setter)]
|
||||||
|
pub fn set_direction(&mut self, direction: JsValue) {
|
||||||
|
match direction.into_serde() {
|
||||||
|
Ok(dir) => {
|
||||||
|
self.0.set_direction(dir);
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
err!("Couldn't serialize Direction: {:?}", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn serialize(&self) -> JsValue {
|
||||||
|
JsValue::from_serde(&self.0).map_err(|err| {
|
||||||
|
err!("Error while serializing Signal: {:?}", err);
|
||||||
|
}).unwrap_or(JsValue::NULL)
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,477 +1,171 @@
|
|||||||
mod utils;
|
#![allow(non_snake_case)]
|
||||||
|
|
||||||
|
use std::cell::RefCell;
|
||||||
|
use std::rc::Rc;
|
||||||
|
use std::ops::Deref;
|
||||||
|
|
||||||
use js_sys::Function;
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use wasm_bindgen::prelude::*;
|
use wasm_bindgen::prelude::*;
|
||||||
|
|
||||||
use stackline::pane::Pane as SLPane;
|
use stackline::{
|
||||||
use stackline::signal::Signal as SLSignal;
|
world::World as SLWorld,
|
||||||
use stackline::signal::Value;
|
signal::Signal as SLSignal,
|
||||||
use stackline::tile::AnyTile;
|
signal::Value,
|
||||||
use stackline::tile::FullTile as SLFullTile;
|
tile::AnyTile,
|
||||||
use stackline::utils::Direction;
|
tile::FullTile as SLFullTile,
|
||||||
use stackline::world::World as SLWorld;
|
};
|
||||||
use stackline::text::TextSurface;
|
use veccell::VecRef;
|
||||||
|
|
||||||
// TODO: refactor:
|
mod utils;
|
||||||
// - make everything camelCase
|
|
||||||
// - clearly separate serialized properties/functions from non-serialized
|
|
||||||
// - add javascript code to the classes?
|
|
||||||
|
|
||||||
// When the `wee_alloc` feature is enabled, use `wee_alloc` as the global
|
|
||||||
// allocator.
|
|
||||||
#[cfg(feature = "wee_alloc")]
|
|
||||||
#[global_allocator]
|
|
||||||
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
|
|
||||||
|
|
||||||
#[wasm_bindgen]
|
macro_rules! js_ser {
|
||||||
pub struct World(SLWorld);
|
( $type:expr, $value:expr ) => {
|
||||||
|
serde_wasm_bindgen::to_value($value).map_err(|err| {
|
||||||
|
err!(concat!("Error while serializing ", $type, ": {:?}"), err);
|
||||||
|
}).ok()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
#[wasm_bindgen(start)]
|
macro_rules! js_de {
|
||||||
pub fn set_panic() {
|
( $type:ty, $value:expr ) => {
|
||||||
utils::set_panic_hook();
|
serde_wasm_bindgen::from_value::<$type>($value).map_err(|err| {
|
||||||
|
err!(concat!(
|
||||||
|
"Error while deserializing ",
|
||||||
|
stringify!($type),
|
||||||
|
": {:?}",
|
||||||
|
), err);
|
||||||
|
}).ok()
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
pub fn available_tiles() -> Vec<JsValue> {
|
#[derive(Clone)]
|
||||||
AnyTile::available()
|
pub struct World(Rc<RefCell<SLWorld>>);
|
||||||
.iter()
|
|
||||||
.map(|name| JsValue::from_str(name))
|
|
||||||
.collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
impl World {
|
impl World {
|
||||||
/// Creates a new World instance
|
|
||||||
#[wasm_bindgen(constructor)]
|
#[wasm_bindgen(constructor)]
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self(SLWorld::new())
|
Self(Rc::new(RefCell::new(SLWorld::new())))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deserialize(serialized: &JsValue) -> Option<World> {
|
pub fn deserialize(serialized: &JsValue) -> Option<World> {
|
||||||
serialized.into_serde().map_err(|err| {
|
serialized.into_serde().map_err(|err| {
|
||||||
err!("Error while deserializing World: {:?}", err);
|
err!("Error while deserializing World: {:?}", err);
|
||||||
}).ok().map(|world| World(world))
|
}).ok().map(|world| {
|
||||||
|
Self(Rc::new(RefCell::new(world)))
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn serialize(&self) -> JsValue {
|
pub fn serialize(&self) -> JsValue {
|
||||||
JsValue::from_serde(&self.0).map_err(|err| {
|
js_ser!("World", &*self.0.borrow()).unwrap_or(JsValue::NULL)
|
||||||
err!("Error while serializing World: {:?}", err);
|
|
||||||
}).unwrap_or(JsValue::NULL)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Initializes the World, making it ready to run
|
pub fn init(&self) {
|
||||||
pub fn init(&mut self) {
|
self.0.borrow_mut().init();
|
||||||
self.0.init();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
pub fn step(&self) {
|
||||||
pub fn step(&mut self) {
|
self.0.borrow_mut().step();
|
||||||
self.0.step();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run(&mut self, steps: usize) {
|
pub fn run(&self, steps: usize) {
|
||||||
|
let mut borrowed = self.0.borrow_mut();
|
||||||
|
|
||||||
for _ in 0..steps {
|
for _ in 0..steps {
|
||||||
self.step();
|
borrowed.step();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_blink_duration(&mut self, blink_duration: f64) {
|
pub fn setBlinkDuration(&mut self, blink_duration: f64) {
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
self.0
|
self.0
|
||||||
|
.borrow_mut()
|
||||||
.set_blink_duration(Duration::from_secs_f64(blink_duration));
|
.set_blink_duration(Duration::from_secs_f64(blink_duration));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(non_snake_case)]
|
pub fn getSerialized(&self, x: i32, y: i32) -> JsValue {
|
||||||
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;
|
|
||||||
} else if cfg!(debug_assertions) {
|
|
||||||
err!("Index out of bound: {}:{}", x, y);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn panes(&self) -> Vec<JsValue> {
|
|
||||||
self.0.panes().keys().map(|key| {
|
|
||||||
JsValue::from_str(key)
|
|
||||||
}).collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn draw(&self, x: i32, y: i32, width: usize, height: usize) -> Vec<u32> {
|
|
||||||
let mut surface = TextSurface::new(width, height);
|
|
||||||
|
|
||||||
self.0.draw(x, y, &mut surface);
|
|
||||||
|
|
||||||
#[derive(Serialize)]
|
|
||||||
struct Char {
|
|
||||||
ch: char,
|
|
||||||
fg: (u8, u8, u8),
|
|
||||||
bg: Option<(u8, u8, u8)>,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn to_u32(red: u8, green: u8, blue: u8) -> u32 {
|
|
||||||
0xff000000 | (red as u32).checked_shl(16).unwrap() | (green as u32).checked_shl(8).unwrap() | (blue as u32)
|
|
||||||
}
|
|
||||||
|
|
||||||
surface.iter().map(|ch| {
|
|
||||||
vec![
|
|
||||||
ch.ch as u32,
|
|
||||||
to_u32(ch.fg.red, ch.fg.green, ch.fg.blue),
|
|
||||||
ch.bg.map(|bg| to_u32(bg.red, bg.green, bg.blue)).unwrap_or(0)
|
|
||||||
].into_iter()
|
|
||||||
}).flatten().collect::<Vec<_>>()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[wasm_bindgen]
|
|
||||||
pub struct Pane(SLPane);
|
|
||||||
|
|
||||||
#[wasm_bindgen]
|
|
||||||
impl Pane {
|
|
||||||
#[wasm_bindgen(constructor)]
|
|
||||||
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(setter)]
|
|
||||||
pub fn set_width(&mut self, width: usize) {
|
|
||||||
let height = self.0.height().get();
|
|
||||||
|
|
||||||
self.0.resize(width, height).unwrap_or_else(|| {
|
|
||||||
err!("Error while resizing Pane");
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
#[wasm_bindgen(getter)]
|
|
||||||
pub fn height(&self) -> usize {
|
|
||||||
self.0.height().get()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[wasm_bindgen(setter)]
|
|
||||||
pub fn set_height(&mut self, height: usize) {
|
|
||||||
let width = self.0.width().get();
|
|
||||||
|
|
||||||
self.0.resize(width, height).unwrap_or_else(|| {
|
|
||||||
err!("Error while resizing Pane");
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
#[wasm_bindgen(getter)]
|
|
||||||
pub fn position(&self) -> Vec<i32> {
|
|
||||||
let (x, y) = self.0.position();
|
|
||||||
vec![x, y]
|
|
||||||
}
|
|
||||||
|
|
||||||
#[wasm_bindgen(getter)]
|
|
||||||
pub fn x(&self) -> i32 {
|
|
||||||
self.0.position().0
|
|
||||||
}
|
|
||||||
|
|
||||||
#[wasm_bindgen(getter)]
|
|
||||||
pub fn y(&self) -> i32 {
|
|
||||||
self.0.position().1
|
|
||||||
}
|
|
||||||
|
|
||||||
#[wasm_bindgen(setter)]
|
|
||||||
pub fn set_x(&mut self, value: i32) {
|
|
||||||
let mut pos = self.0.position();
|
|
||||||
pos.0 = value;
|
|
||||||
self.0.set_position(pos);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[wasm_bindgen(setter)]
|
|
||||||
pub fn set_y(&mut self, value: i32) {
|
|
||||||
let mut pos = self.0.position();
|
|
||||||
pos.1 = value;
|
|
||||||
self.0.set_position(pos);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[wasm_bindgen(setter)]
|
|
||||||
pub fn set_position(&mut self, position: &[i32]) {
|
|
||||||
if let [x, y] = position[..] {
|
|
||||||
self.0.set_position((x, y));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get(&self, x: usize, y: usize) -> Option<FullTile> {
|
|
||||||
self.0.get((x, y)).map(|tile| FullTile((*tile).clone()))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set(&mut self, x: usize, y: usize, tile: FullTile) {
|
|
||||||
if let Some(target) = self.0.get_mut((x, y)) {
|
|
||||||
*target = tile.0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn serialize(&self) -> JsValue {
|
|
||||||
JsValue::from_serde(&self.0).map_err(|err| {
|
|
||||||
err!("Error serializing Pane: {:?}", err);
|
|
||||||
}).unwrap_or(JsValue::NULL)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn deserialize(value: JsValue) -> Option<Pane> {
|
|
||||||
value.into_serde::<SLPane>().map_err(|err| {
|
|
||||||
err!("Error deserializing Pane: {:?}", err);
|
|
||||||
}).ok().map(|pane| Pane(pane))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// NOTE: `self` must not be part of world
|
|
||||||
pub fn blit(&self, x: i32, y: i32, world: &mut World) {
|
|
||||||
for pane in world.0.panes().values() {
|
|
||||||
if pane as *const SLPane == &self.0 as *const SLPane {
|
|
||||||
panic!("Cannot blit to a World containing self");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (dx, dy, tile) in self.0.tiles_iter() {
|
|
||||||
let x = x + dx as i32;
|
|
||||||
let y = y + dy as i32;
|
|
||||||
world.set(x, y, FullTile((*tile).clone()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn to_text(&self) -> String {
|
|
||||||
let mut surface = TextSurface::new(self.0.width().get(), self.0.height().get());
|
|
||||||
|
|
||||||
self.0.draw(0, 0, &mut surface, stackline::utils::Blink::default());
|
|
||||||
|
|
||||||
format!("{:#}", surface)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[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))))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn clone(&self) -> FullTile {
|
|
||||||
<Self as Clone>::clone(self)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(non_snake_case)]
|
|
||||||
pub fn toString(&self) -> String {
|
|
||||||
format!("{:#?}", self.0)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[wasm_bindgen(getter)]
|
|
||||||
pub fn signal(&self) -> JsValue {
|
|
||||||
self.0.signal().and_then(|signal| JsValue::from_serde(signal).ok()).unwrap_or(JsValue::NULL)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[wasm_bindgen(setter)]
|
|
||||||
pub fn set_signal(&mut self, signal: JsValue) {
|
|
||||||
if signal.is_null() {
|
|
||||||
self.0.set_signal(None);
|
|
||||||
} else {
|
|
||||||
match signal.into_serde() {
|
|
||||||
Ok(signal) => {
|
|
||||||
self.0.set_signal(Some(signal));
|
|
||||||
}
|
|
||||||
Err(err) => {
|
|
||||||
err!("Couldn't serialize Signal: {:?}", err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[wasm_bindgen(getter)]
|
|
||||||
pub fn tile(&self) -> JsValue {
|
|
||||||
self.0
|
self.0
|
||||||
.get()
|
.borrow()
|
||||||
.map(|tile| {
|
.get((x, y))
|
||||||
JsValue::from_serde(tile)
|
.and_then(|tile| {
|
||||||
.map_err(|err| {
|
js_ser!("FullTile", &*tile)
|
||||||
err!("Error while serializing AnyTile: {}", err);
|
|
||||||
})
|
|
||||||
.ok()
|
|
||||||
})
|
})
|
||||||
.flatten()
|
.unwrap_or(JsValue::NULL)
|
||||||
.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),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[wasm_bindgen(getter)]
|
|
||||||
pub fn state(&self) -> JsValue {
|
|
||||||
JsValue::from_serde(&self.0.state()).expect("Error while serializing State")
|
|
||||||
}
|
|
||||||
|
|
||||||
#[wasm_bindgen(setter)]
|
|
||||||
pub fn set_state(&mut self, state: &JsValue) {
|
|
||||||
if let Ok(state) = state.into_serde() {
|
|
||||||
self.0.set_state(state);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn schema(&self) -> JsValue {
|
pub fn setSerialized(&mut self, x: i32, y: i32, value: JsValue) {
|
||||||
use serde_json::Value;
|
let deserialized = js_de!(SLFullTile, value).unwrap();
|
||||||
use stackline::tile::{Tile, TileSchema};
|
|
||||||
|
|
||||||
fn construct_value(schema: TileSchema) -> Value {
|
if let Some(tile) = self.0
|
||||||
match schema {
|
.borrow_mut()
|
||||||
TileSchema::Tuple(arr) => {
|
.get_mut((x, y)) {
|
||||||
Value::Array(arr.into_iter().map(construct_value).collect())
|
*tile = deserialized;
|
||||||
}
|
|
||||||
TileSchema::Map(map) => Value::Object(
|
|
||||||
map.into_iter()
|
|
||||||
.map(|(key, value)| (key, construct_value(value)))
|
|
||||||
.collect(),
|
|
||||||
),
|
|
||||||
TileSchema::Value(label, ty) => Value::String(format!("{}:{}", label, ty)),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(tile) = self.0.get() {
|
pub fn getRef(&self, x: i32, y: i32) -> Option<FullTileRef> {
|
||||||
JsValue::from_serde(&construct_value(tile.schema()))
|
if let Some((pane_name, _pane, x, y)) = self.0.borrow().get_pane_at((x, y)) {
|
||||||
.expect("Error while serializing TileSchema")
|
Some(FullTileRef {
|
||||||
|
world: Rc::clone(&self.0),
|
||||||
|
pane: pane_name.to_string(),
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
})
|
||||||
} else {
|
} else {
|
||||||
JsValue::UNDEFINED
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn map_tile(&mut self, callback: &Function) {
|
pub fn toString(&self) -> String {
|
||||||
let res = callback
|
format!("{:#}", self.0.borrow())
|
||||||
.call1(&JsValue::NULL, &self.tile())
|
|
||||||
.expect("Error while calling javascript callback");
|
|
||||||
self.set_tile(res);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone)]
|
// impl Deref for World {
|
||||||
#[serde(untagged)]
|
// type Target = SLWorld;
|
||||||
enum UntaggedValue {
|
|
||||||
Number(f64),
|
|
||||||
String(String),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<&'_ Value> for UntaggedValue {
|
// fn deref(&self) -> &SLWorld {
|
||||||
fn from(value: &'_ Value) -> Self {
|
// self.0.borrow()
|
||||||
match value {
|
// }
|
||||||
Value::Number(x) => Self::Number(*x),
|
// }
|
||||||
Value::String(x) => Self::String(x.clone()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<Value> for UntaggedValue {
|
#[wasm_bindgen]
|
||||||
fn from(value: Value) -> Self {
|
pub struct FullTileRef {
|
||||||
match value {
|
world: Rc<RefCell<SLWorld>>,
|
||||||
Value::Number(x) => Self::Number(x),
|
pane: String,
|
||||||
Value::String(x) => Self::String(x),
|
pub x: usize,
|
||||||
}
|
pub y: usize,
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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)]
|
impl FullTileRef {
|
||||||
#[wasm_bindgen]
|
fn with<F: FnOnce(&SLFullTile)>(&self, cb: F) {
|
||||||
pub struct Signal(SLSignal);
|
let guard = self.world
|
||||||
|
.borrow();
|
||||||
|
let reference = guard
|
||||||
|
.get_pane(&self.pane)
|
||||||
|
.expect("Invalid FullTile: no pane")
|
||||||
|
.get((self.x, self.y))
|
||||||
|
.expect("Invalid FullTile: tile out of bounds");
|
||||||
|
|
||||||
#[wasm_bindgen]
|
cb(&*reference);
|
||||||
impl Signal {
|
|
||||||
#[wasm_bindgen(constructor)]
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Self(SLSignal::empty(Direction::Up))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a read-only array
|
fn with_mut<F: FnOnce(&mut SLFullTile)>(&self, cb: F) {
|
||||||
#[wasm_bindgen(getter)]
|
let mut guard = self.world
|
||||||
pub fn stack(&self) -> JsValue {
|
.borrow_mut();
|
||||||
JsValue::from_serde(
|
let reference = guard
|
||||||
&self
|
.get_pane_mut(&self.pane)
|
||||||
.0
|
.expect("Invalid FullTile: no pane")
|
||||||
.stack()
|
.get_mut((self.x, self.y))
|
||||||
.iter()
|
.expect("Invalid FullTile: tile out of bounds");
|
||||||
.collect::<Vec<_>>(),
|
|
||||||
)
|
|
||||||
.unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[wasm_bindgen(getter)]
|
|
||||||
pub fn direction(&self) -> JsValue {
|
|
||||||
JsValue::from_serde(&self.0.direction()).expect("Couldn't serialize Direction")
|
|
||||||
}
|
|
||||||
|
|
||||||
#[wasm_bindgen(setter)]
|
cb(&mut *reference);
|
||||||
pub fn set_direction(&mut self, direction: JsValue) {
|
|
||||||
match direction.into_serde() {
|
|
||||||
Ok(dir) => {
|
|
||||||
self.0.set_direction(dir);
|
|
||||||
}
|
|
||||||
Err(err) => {
|
|
||||||
err!("Couldn't serialize Direction: {:?}", err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn serialize(&self) -> JsValue {
|
#[wasm_bindgen]
|
||||||
JsValue::from_serde(&self.0).map_err(|err| {
|
impl FullTileRef {
|
||||||
err!("Error while serializing Signal: {:?}", err);
|
|
||||||
}).unwrap_or(JsValue::NULL)
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in new issue