From 455c03b4b3b8ddadf77eb564488b47400eacc989 Mon Sep 17 00:00:00 2001 From: Max Bradbury Date: Wed, 4 Nov 2020 15:01:04 +0000 Subject: [PATCH] move state to wasm --- .gitignore | 1 + Cargo.toml | 4 +- script.js | 55 +++++++++---- src/lib.rs | 227 ++++++++++++++++++++++++++++++++++------------------- 4 files changed, 187 insertions(+), 100 deletions(-) diff --git a/.gitignore b/.gitignore index 55acbf9..61b0002 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ /examples/ /tilesy.zip /.idea/ +/dist/ diff --git a/Cargo.toml b/Cargo.toml index 754d1e4..0f72aa6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,7 +13,7 @@ crate-type = ["cdylib"] [dependencies] "base64" = "^0.12.3" -"bitsy-parser" = "^0.72.0" +"bitsy-parser" = "^0.72.3" "image" = "^0.23.7" "lazy_static" = "^1.4.0" -"wasm-bindgen" = "^0.2.64" +"wasm-bindgen" = "=0.2.64" # newer versions are bugged... diff --git a/script.js b/script.js index cac69c5..eb806fd 100644 --- a/script.js +++ b/script.js @@ -1,4 +1,15 @@ -import init, {load_default_game_data, add_tiles} from './pkg/tilesy.js'; +import init, { + add_tiles, + load_image, + load_game, + load_default_game, + output, + set_prefix, + set_invert, + set_flip, + set_mirror, + set_rotate +} from './pkg/tilesy.js'; // if (typeof WebAssembly !== "object") { // document.getElementById("start").innerText = "Sorry - your browser does not support WebAssembly"; @@ -79,7 +90,8 @@ async function run() { } function new_game() { - textareaGameDataInput.value = load_default_game_data(); + load_default_game() + textareaGameDataInput.value = output(); checkGameData(); // we don't need to look at the default game data, so skip ahead to the image page buttonGameDataProceed.click(); @@ -100,6 +112,7 @@ async function run() { el("game").addEventListener("change", function() { readFile(this, function (e) { textareaGameDataInput.value = e.target.result; + console.log(load_game(e.target.result)); checkGameData(); }, "text"); }); @@ -121,24 +134,14 @@ async function run() { el('image').addEventListener('change', function () { readFile(this, function (e) { imagePreview.src = e.target.result; - imagePreview.style.display = "initial" + imagePreview.style.display = "initial"; + console.log(load_image(imagePreview.getAttribute("src"))); }, "image"); }); function addTiles() { - let image = imagePreview.getAttribute("src"); - let gameData = textareaGameDataInput.value; - let prefix = inputPrefix.value; - - textareaGameDataOutput.value = add_tiles( - gameData, - image, - prefix, - checkboxInvertTiles.checked, - checkboxFlipTiles.checked, - checkboxMirrorTiles.checked, - checkboxRotateTiles.checked - ); + console.log(add_tiles()); + textareaGameDataOutput.value = output(); } buttonImportGame.addEventListener("click", addTiles); @@ -160,6 +163,26 @@ async function run() { buttonAddImage.addEventListener("click", addImage); buttonAddImage.addEventListener("touchend", addImage); + inputPrefix.addEventListener("change", () => { + set_prefix(inputPrefix.value); + }) + + checkboxInvertTiles.addEventListener("change", () => { + set_invert(checkboxInvertTiles.checked); + }); + + checkboxFlipTiles.addEventListener("change", () => { + set_flip(checkboxFlipTiles.checked); + }); + + checkboxMirrorTiles.addEventListener("change", () => { + set_mirror(checkboxMirrorTiles.checked); + }); + + checkboxRotateTiles.addEventListener("change", () => { + set_rotate(checkboxRotateTiles.checked); + }); + function reset() { clear_game(); imagePreview.removeAttribute("src"); diff --git a/src/lib.rs b/src/lib.rs index f8dd11c..f060e5f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,40 +1,35 @@ use bitsy_parser::game::Game; use bitsy_parser::image::Image; use bitsy_parser::tile::Tile; -use image::{GenericImageView, Pixel}; -// use lazy_static::lazy_static; -// use std::sync::Mutex; +use image::{GenericImageView, Pixel, DynamicImage}; +use lazy_static::lazy_static; +use std::sync::Mutex; use wasm_bindgen::prelude::*; const SD: u32 = 8; -// -// struct State { -// game: Option, -// image: Option, -// prefix: String, -// invert: bool, -// flip: bool, -// mirror: bool, -// rotate: bool, -// } -// lazy_static! { -// static ref STATE: Mutex = Mutex::new( -// State { -// game: None, -// image: None, -// prefix: "".to_string(), -// invert: false, -// flip: false, -// mirror: false, -// rotate: false, -// } -// ); -// } +struct State { + game: Option, + image: Option, + prefix: String, + invert: bool, + flip: bool, + mirror: bool, + rotate: bool, +} -#[wasm_bindgen] -pub fn load_default_game_data() -> String { - bitsy_parser::mock::game_default().to_string() +lazy_static! { + static ref STATE: Mutex = Mutex::new( + State { + game: None, + image: None, + prefix: "".to_string(), + invert: false, + flip: false, + mirror: false, + rotate: false, + } + ); } fn tile_name(prefix: &str, index: &u32) -> Option { @@ -45,40 +40,108 @@ fn tile_name(prefix: &str, index: &u32) -> Option { } } -/// image is a base64-encoded string +#[wasm_bindgen] +pub fn load_default_game() { + let mut state = STATE.lock().unwrap(); + state.game = Some(bitsy_parser::mock::game_default()); +} + +#[wasm_bindgen] +pub fn load_game(game_data: String) -> String { + let result = Game::from(game_data); + let mut state = STATE.lock().unwrap(); + + match result { + Ok(game) => { + state.game = Some(game); + "".to_string() + }, + _ => { + state.game = None; + format!("{}", result.err().unwrap()) + } + } +} + +#[wasm_bindgen] +pub fn load_image(image_base64: String) -> String { + let mut state = STATE.lock().expect("Couldn't lock application state"); + + let image_base64: Vec<&str> = image_base64.split("base64,").collect(); + let image_base64 = image_base64[1]; + + match base64::decode(image_base64) { + Ok(image) => { + match image::load_from_memory(image.as_ref()) { + Ok(image) => { + state.image = Some(image); + "OK" + }, + _ => { + state.image = None; + "Couldn't load image" + } + } + }, + _ => { + state.image = None; + "Couldn't decode image" + } + }.to_string() +} + +#[wasm_bindgen] +pub fn set_prefix(prefix: String) { + let mut state = STATE.lock().unwrap(); + + state.prefix = prefix; +} + +#[wasm_bindgen] +pub fn set_invert(invert: bool) { + let mut state = STATE.lock().unwrap(); + + state.invert = invert; +} + +#[wasm_bindgen] +pub fn set_flip(flip: bool) { + let mut state = STATE.lock().unwrap(); + + state.flip = flip; +} + +#[wasm_bindgen] +pub fn set_mirror(mirror: bool) { + let mut state = STATE.lock().unwrap(); + + state.mirror = mirror; +} + +#[wasm_bindgen] +pub fn set_rotate(rotate: bool) { + let mut state = STATE.lock().unwrap(); + + state.rotate = rotate; +} + /// prefix will be ignored if empty #[wasm_bindgen] -pub fn add_tiles( - game_data: String, - image: String, - prefix: String, - invert: bool, - flip: bool, - mirror: bool, - rotate: bool -) -> String { - let game = Game::from(game_data); - if game.is_err() { - return format!("Couldn't parse game data"); - } - let mut game = game.unwrap(); +pub fn add_tiles() -> String { + let mut state = STATE.lock().expect("Couldn't lock application state"); - let image: Vec<&str> = image.split("base64,").collect(); - let image = image[1]; - - let image = base64::decode(image); - if image.is_err() { - return format!("Couldn't decode image"); + if state.game.is_none() { + return "No game data loaded".to_string(); } - let image = image.unwrap(); - let image = image::load_from_memory(image.as_ref()); - if image.is_err() { - return format!("Couldn't load image"); - } - let image = image.unwrap(); - let width = image.width(); - let height = image.height(); + let mut game = state.game.clone().unwrap(); + + if state.image.is_none() { + return "No image loaded".to_string(); + } + + let width = state.image.as_ref().unwrap().width(); + let height = state.image.as_ref().unwrap().height(); let columns = (width as f64 / SD as f64).floor() as u32; let rows = (height as f64 / SD as f64).floor() as u32; @@ -90,7 +153,7 @@ pub fn add_tiles( for y in (row * SD)..((row + 1) * SD) { for x in (column * SD)..((column + 1) * SD) { - let pixel = image.get_pixel(x, y).to_rgb(); + let pixel = state.image.as_ref().unwrap().get_pixel(x, y).to_rgb(); let total = pixel[0] as u32 + pixel[1] as u32 + pixel[2] as u32; // is each channel brighter than 128/255 on average? pixels.push(if total >= 384 {1} else {0}); @@ -98,9 +161,9 @@ pub fn add_tiles( } let tile = Tile { - /// "0" will get overwritten to a new, safe tile ID + // "0" will get overwritten to a new, safe tile ID id: "0".to_string(), - name: tile_name(&prefix, &tile_index), + name: tile_name(&state.prefix, &tile_index), wall: None, animation_frames: vec![Image { pixels }], colour_id: None @@ -112,7 +175,7 @@ pub fn add_tiles( tile_index += 1; } - if invert { + if state.invert { let mut inverted = tile.clone(); inverted.invert(); @@ -125,7 +188,7 @@ pub fn add_tiles( } } - if flip { + if state.flip { let mut flipped = tile.clone(); flipped.flip(); @@ -138,7 +201,7 @@ pub fn add_tiles( } } - if mirror { + if state.mirror { let mut mirrored = tile.clone(); mirrored.mirror(); @@ -151,7 +214,7 @@ pub fn add_tiles( } } - if rotate { + if state.rotate { for i in 1..4 { let mut rotated = tile.clone(); @@ -173,31 +236,31 @@ pub fn add_tiles( game.dedupe_tiles(); - let game = game; + state.game = Some(game.to_owned()); - game.to_string() + "OK".to_string() +} + +#[wasm_bindgen] +pub fn output() -> String { + let state = STATE.lock().unwrap(); + + match &state.game { + Some(game) => game.to_string(), + None => "No game loaded".to_string(), + } } #[cfg(test)] mod test { - use crate::add_tiles; + use crate::{add_tiles, load_image, load_default_game, output}; #[test] fn example() { - let game_data = bitsy_parser::mock::game_default().to_string(); - let image = include_str!("test-resources/test.png.base64").to_string(); + load_default_game(); + load_image(include_str!("test-resources/test.png.base64").to_string()); + add_tiles(); - let output = add_tiles( - game_data, - image, - "".to_string(), - false, - false, - false, - false - ); - - let expected = include_str!("test-resources/expected.bitsy"); - assert_eq!(output, expected); + assert_eq!(output(), include_str!("test-resources/expected.bitsy")); } }