Compare commits
1 Commits
Author | SHA1 | Date |
---|---|---|
Shad Amethyst | c0e562a3af | 2 years ago |
@ -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 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;
|
||||
use stackline::{
|
||||
world::World as SLWorld,
|
||||
signal::Signal as SLSignal,
|
||||
signal::Value,
|
||||
tile::AnyTile,
|
||||
tile::FullTile as SLFullTile,
|
||||
};
|
||||
use veccell::VecRef;
|
||||
|
||||
// TODO: refactor:
|
||||
// - make everything camelCase
|
||||
// - clearly separate serialized properties/functions from non-serialized
|
||||
// - add javascript code to the classes?
|
||||
mod utils;
|
||||
|
||||
// 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);
|
||||
macro_rules! js_ser {
|
||||
( $type:expr, $value:expr ) => {
|
||||
serde_wasm_bindgen::to_value($value).map_err(|err| {
|
||||
err!(concat!("Error while serializing ", $type, ": {:?}"), err);
|
||||
}).ok()
|
||||
};
|
||||
}
|
||||
|
||||
#[wasm_bindgen(start)]
|
||||
pub fn set_panic() {
|
||||
utils::set_panic_hook();
|
||||
macro_rules! js_de {
|
||||
( $type:ty, $value:expr ) => {
|
||||
serde_wasm_bindgen::from_value::<$type>($value).map_err(|err| {
|
||||
err!(concat!(
|
||||
"Error while deserializing ",
|
||||
stringify!($type),
|
||||
": {:?}",
|
||||
), err);
|
||||
}).ok()
|
||||
};
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn available_tiles() -> Vec<JsValue> {
|
||||
AnyTile::available()
|
||||
.iter()
|
||||
.map(|name| JsValue::from_str(name))
|
||||
.collect()
|
||||
}
|
||||
#[derive(Clone)]
|
||||
pub struct World(Rc<RefCell<SLWorld>>);
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl World {
|
||||
/// Creates a new World instance
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new() -> Self {
|
||||
Self(SLWorld::new())
|
||||
Self(Rc::new(RefCell::new(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))
|
||||
}).ok().map(|world| {
|
||||
Self(Rc::new(RefCell::new(world)))
|
||||
})
|
||||
}
|
||||
|
||||
pub fn serialize(&self) -> JsValue {
|
||||
JsValue::from_serde(&self.0).map_err(|err| {
|
||||
err!("Error while serializing World: {:?}", err);
|
||||
}).unwrap_or(JsValue::NULL)
|
||||
js_ser!("World", &*self.0.borrow()).unwrap_or(JsValue::NULL)
|
||||
}
|
||||
|
||||
/// Initializes the World, making it ready to run
|
||||
pub fn init(&mut self) {
|
||||
self.0.init();
|
||||
pub fn init(&self) {
|
||||
self.0.borrow_mut().init();
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn step(&mut self) {
|
||||
self.0.step();
|
||||
pub fn step(&self) {
|
||||
self.0.borrow_mut().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 {
|
||||
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;
|
||||
|
||||
self.0
|
||||
.borrow_mut()
|
||||
.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 {
|
||||
pub fn getSerialized(&self, x: i32, y: i32) -> JsValue {
|
||||
self.0
|
||||
.get()
|
||||
.map(|tile| {
|
||||
JsValue::from_serde(tile)
|
||||
.map_err(|err| {
|
||||
err!("Error while serializing AnyTile: {}", err);
|
||||
})
|
||||
.ok()
|
||||
.borrow()
|
||||
.get((x, y))
|
||||
.and_then(|tile| {
|
||||
js_ser!("FullTile", &*tile)
|
||||
})
|
||||
.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);
|
||||
}
|
||||
.unwrap_or(JsValue::NULL)
|
||||
}
|
||||
|
||||
pub fn schema(&self) -> JsValue {
|
||||
use serde_json::Value;
|
||||
use stackline::tile::{Tile, TileSchema};
|
||||
pub fn setSerialized(&mut self, x: i32, y: i32, value: JsValue) {
|
||||
let deserialized = js_de!(SLFullTile, value).unwrap();
|
||||
|
||||
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
|
||||
.borrow_mut()
|
||||
.get_mut((x, y)) {
|
||||
*tile = deserialized;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(tile) = self.0.get() {
|
||||
JsValue::from_serde(&construct_value(tile.schema()))
|
||||
.expect("Error while serializing TileSchema")
|
||||
pub fn getRef(&self, x: i32, y: i32) -> Option<FullTileRef> {
|
||||
if let Some((pane_name, _pane, x, y)) = self.0.borrow().get_pane_at((x, y)) {
|
||||
Some(FullTileRef {
|
||||
world: Rc::clone(&self.0),
|
||||
pane: pane_name.to_string(),
|
||||
x,
|
||||
y,
|
||||
})
|
||||
} else {
|
||||
JsValue::UNDEFINED
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
pub fn toString(&self) -> String {
|
||||
format!("{:#}", self.0.borrow())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone)]
|
||||
#[serde(untagged)]
|
||||
enum UntaggedValue {
|
||||
Number(f64),
|
||||
String(String),
|
||||
}
|
||||
// impl Deref for World {
|
||||
// type Target = SLWorld;
|
||||
|
||||
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()),
|
||||
}
|
||||
}
|
||||
}
|
||||
// fn deref(&self) -> &SLWorld {
|
||||
// self.0.borrow()
|
||||
// }
|
||||
// }
|
||||
|
||||
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),
|
||||
}
|
||||
}
|
||||
#[wasm_bindgen]
|
||||
pub struct FullTileRef {
|
||||
world: Rc<RefCell<SLWorld>>,
|
||||
pane: String,
|
||||
pub x: usize,
|
||||
pub y: usize,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[wasm_bindgen]
|
||||
pub struct Signal(SLSignal);
|
||||
impl FullTileRef {
|
||||
fn with<F: FnOnce(&SLFullTile)>(&self, cb: F) {
|
||||
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]
|
||||
impl Signal {
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new() -> Self {
|
||||
Self(SLSignal::empty(Direction::Up))
|
||||
cb(&*reference);
|
||||
}
|
||||
|
||||
/// 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")
|
||||
}
|
||||
fn with_mut<F: FnOnce(&mut SLFullTile)>(&self, cb: F) {
|
||||
let mut guard = self.world
|
||||
.borrow_mut();
|
||||
let reference = guard
|
||||
.get_pane_mut(&self.pane)
|
||||
.expect("Invalid FullTile: no pane")
|
||||
.get_mut((self.x, self.y))
|
||||
.expect("Invalid FullTile: tile out of bounds");
|
||||
|
||||
#[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);
|
||||
}
|
||||
}
|
||||
cb(&mut *reference);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn serialize(&self) -> JsValue {
|
||||
JsValue::from_serde(&self.0).map_err(|err| {
|
||||
err!("Error while serializing Signal: {:?}", err);
|
||||
}).unwrap_or(JsValue::NULL)
|
||||
}
|
||||
#[wasm_bindgen]
|
||||
impl FullTileRef {
|
||||
|
||||
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