diff --git a/src/lib.rs b/src/lib.rs index ac930c8..6ee4f18 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,6 +2,7 @@ pub mod agnostic_interface; mod faux_quicksilver; mod original_impl; mod shaders; +mod wasm_helpers; use agnostic_interface::raylib_impl::RaylibGame; use faux_quicksilver::Window; diff --git a/src/original_impl.rs b/src/original_impl.rs index e8d9500..48f533f 100644 --- a/src/original_impl.rs +++ b/src/original_impl.rs @@ -3,6 +3,8 @@ use std::{fs::File, io::Result as IOResult, path::PathBuf, str::FromStr}; use crate::agnostic_interface::CameraInterface; use crate::faux_quicksilver::{Circle, Color, Rectangle, Transform, Vector, Window}; use rand::prelude::*; +use std::os::raw::{c_int, c_void}; +use std::sync::mpsc::{Receiver, TryRecvError}; const WIDTH_F: f32 = 800.0; const HEIGHT_F: f32 = 600.0; @@ -1899,6 +1901,8 @@ pub struct GameState { camera: Box, move_to: Vector, save_load_notification: Option, + #[cfg(target_family = "wasm")] + load_recv: Option>>, } impl GameState { @@ -2028,6 +2032,8 @@ impl GameState { camera, move_to: Vector::new(400.0, 300.0), save_load_notification: None, + #[cfg(target_family = "wasm")] + load_recv: None, }) } @@ -2498,7 +2504,6 @@ impl GameState { match sl { SaveLoadNotification::Save { text, timer } => { *timer -= dt; - println!("save, timer is {}", timer); if *timer <= 0.0 { self.save_load_notification = None; } else if text.is_none() { @@ -2520,6 +2525,57 @@ impl GameState { fish.update(dt); } + #[cfg(target_family = "wasm")] + if let Some(rx) = &mut self.load_recv { + let recv_result = rx.try_recv(); + if let Ok(v) = recv_result { + if v.is_empty() { + self.save_load_notification = Some(SaveLoadNotification::Load { + text: Some(String::from("Failed to load! (callback failure)")), + timer: SL_NOTIF_TIME, + }); + } else { + let des_result = SaveData::deserialize(&v); + if let Ok((save_data, _)) = des_result { + self.planets = save_data.planets; + self.stars = save_data.stars; + self.fishes = save_data.fishes; + self.player = save_data.player; + self.joining_particles = save_data.joining_particles; + self.expl_conv_p_systems.clear(); + self.move_to = Vector::new(self.player.x, self.player.y); + self.camera + .set_view_xy( + self.player.x - WIDTH_F / 2.0, + self.player.y - HEIGHT_F / 2.0, + ) + .ok(); + self.dbl_click_timeout = None; + self.click_time = None; + self.click_release_time = DOUBLE_CLICK_TIME; + self.state = 10; + self.state_dirty = true; + self.save_load_notification = Some(SaveLoadNotification::Load { + text: None, + timer: SL_NOTIF_TIME, + }); + } else { + self.save_load_notification = Some(SaveLoadNotification::Load { + text: Some(String::from("Failed to load! (parse issue)")), + timer: SL_NOTIF_TIME, + }); + } + } + self.load_recv = None; + } else if recv_result == Err(TryRecvError::Disconnected) { + self.save_load_notification = Some(SaveLoadNotification::Load { + text: Some(String::from("Failed to load! (sender disconnected)")), + timer: SL_NOTIF_TIME, + }); + self.load_recv = None; + } + } + Ok(()) } @@ -2703,11 +2759,34 @@ impl GameState { #[cfg(target_family = "wasm")] pub fn save(&mut self) -> IOResult<()> { + let save_bytes = SaveData { + planets: self.planets.clone(), + stars: self.stars.clone(), + fishes: self.fishes.clone(), + player: self.player, + joining_particles: self.joining_particles.clone(), + } + .serialize(); + + crate::wasm_helpers::save_data(&save_bytes)?; + self.save_load_notification = Some(SaveLoadNotification::Save { + text: None, + timer: SL_NOTIF_TIME, + }); + Ok(()) } #[cfg(target_family = "wasm")] pub fn load(&mut self) -> IOResult<()> { + let receiver = crate::wasm_helpers::load_data()?; + + self.load_recv = Some(receiver); + self.save_load_notification = Some(SaveLoadNotification::Save { + text: Some(String::from("Loading...")), + timer: SL_NOTIF_TIME, + }); + Ok(()) } } diff --git a/src/wasm_helpers.rs b/src/wasm_helpers.rs new file mode 100644 index 0000000..512559a --- /dev/null +++ b/src/wasm_helpers.rs @@ -0,0 +1,61 @@ +use std::os::raw::*; +use std::sync::mpsc::{channel, Receiver, Sender}; + +#[cfg(not(target_family = "wasm"))] +pub fn save_data(data: &[u8]) -> std::io::Result<()> { + Err(std::io::Error::other("Unimplemented for native")) +} + +#[cfg(not(target_family = "wasm"))] +pub fn load_data() -> std::io::Result>> { + Err(std::io::Error::other("Unimplemented for native")) +} + +#[cfg(target_family = "wasm")] +#[no_mangle] +pub extern "C" fn ld45_load_rust_handler(usr: *mut c_void, data: *const c_void, len: c_int) { + let mut sender_box: Box>> = + unsafe { Box::from_raw(usr as *mut Sender>) }; + + if data.is_null() || len == 0 { + (*sender_box).send(Vec::new()).ok(); + drop(sender_box); + println!("callback: Failed to load data!"); + return; + } + + let v: Vec = + unsafe { std::slice::from_raw_parts(data as *const u8, len as usize).to_owned() }; + + (*sender_box).send(v).ok(); + println!("callback: Loaded data!"); + + drop(sender_box); +} + +#[cfg(target_family = "wasm")] +extern "C" { + fn ld45_save_async(data: *const c_void, length: c_int); + fn ld45_load_async(usr: *const c_void); +} + +#[cfg(target_family = "wasm")] +pub fn save_data(data: &[u8]) -> std::io::Result<()> { + unsafe { + ld45_save_async(data as *const [u8] as *const c_void, data.len() as c_int); + } + Ok(()) +} + +#[cfg(target_family = "wasm")] +pub fn load_data() -> std::io::Result>> { + let (tx, rx) = channel(); + let mut handler = Box::new(tx); + + unsafe { + let mut ptr = Box::into_raw(handler); + ld45_load_async(ptr as *mut c_void); + } + + Ok(rx) +} diff --git a/wasm/include/ld45_lib.h b/wasm/include/ld45_lib.h index cb87442..f0ad3eb 100644 --- a/wasm/include/ld45_lib.h +++ b/wasm/include/ld45_lib.h @@ -5,4 +5,8 @@ extern void *ld45_initialize(); extern void ld45_iterate(void *context); +extern void ld45_save_async(void *data, int length); +extern void ld45_load_async(void *usr); +extern void ld45_load_rust_handler(void *usr, void *data, int len); + #endif diff --git a/wasm/src/main.c b/wasm/src/main.c index 8363297..d20261b 100644 --- a/wasm/src/main.c +++ b/wasm/src/main.c @@ -5,6 +5,40 @@ #include +void ld45_saved_result_ok(void *usr) { + puts("Save OK"); +} + +void ld45_saved_result_err(void *usr) { + puts("Save ERR"); +} + +void ld45_load_result_ok(void *usr, void *data, int len) { + ld45_load_rust_handler(usr, data, len); +} + +void ld45_load_result_err(void *usr) { + ld45_load_rust_handler(usr, NULL, 0); +} + +void ld45_save_async(void *data, int length) { + emscripten_idb_async_store("ld45_oneandall_db", + "savedata", + data, + length, + NULL, + ld45_saved_result_ok, + ld45_saved_result_err); +} + +void ld45_load_async(void *usr) { + emscripten_idb_async_load("ld45_oneandall_db", + "savedata", + usr, + ld45_load_result_ok, + ld45_load_result_err); +} + void main_loop(void *ud) { ld45_iterate(ud); }