Browse Source

Chapter 11 is WASM friendly, even if you can't actually save in WASM yet.

Herbert Wolverson 4 years ago
parent
commit
be36ca4db5

+ 3 - 1
Cargo.lock

@@ -267,6 +267,8 @@ dependencies = [
  "serde_json 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)",
  "specs 0.15.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "specs-derive 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "wasm-bindgen 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)",
+ "web-sys 0.3.27 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
@@ -1343,7 +1345,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 [[package]]
 name = "rltk"
 version = "0.3.1"
-source = "git+https://github.com/thebracket/rltk_rs#71768caea7e8c3ab215f232ae08840738951d0e0"
+source = "git+https://github.com/thebracket/rltk_rs#8fb1ab770f47baf9b71a74f1d11d7bdb5b42472c"
 dependencies = [
  "bresenham 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)",

+ 29 - 0
book/src/chapter_11.md

@@ -657,6 +657,35 @@ gui::MainMenuSelection::LoadGame => {
 }
 ```
 
+## Web Assembly
+
+The example as-is will compile and run on the web assembly (`wasm32`) platform: but as soon as you try to save the game, it crashes. Unfortunately (well, fortunately if you like your computer not being attacked by every website you go to!), `wasm` is sandboxed - and doesn't have the ability to save files locally.
+
+Supporting saving via `LocalStorage` (a browser/JavaScript feature) is planned for a future version of RLTK. In the meantime, we'll add some wrappers to avoid the crash - and simply not actually save the game on `wasm32`.
+
+Rust offers *conditional compilation* (if you are familiar with C, it's a lot like the `#define` madness you find in big, cross-platform libraries). In `saveload_system.rs`, we'll modify `save_game` to only compile on non-web assembly platforms:
+
+```rust
+#[cfg(not(target_arch = "wasm32"))]
+pub fn save_game(ecs : &mut World) {
+```
+
+That `#` tag is scary looking, but it makes sense if you unwrap it. `#[cfg()]` means "only compile if the current configuration matches the contents of the parentheses. `not()` inverts the result of a check, so when we check that `target_arch = "wasm32")` (are we compiling for `wasm32`) the result is inverted. The end result of this is that the function only compiles if you *aren't* building for `wasm32`.
+
+That's all well and good, but there are calls to that function - so compilation on `wasm` will fail. We'll add a *stub* function to take its place:
+
+```rust
+#[cfg(target_arch = "wasm32")]
+pub fn save_game(_ecs : &mut World) {
+}
+```
+
+The `#[cfg(target_arch = "wasm32")]` prefix means "only compile this for web assembly". We've kept the function signature the same, but added a `_` before `_ecs` - telling the compiler that we intend not to use that variable. Then we keep the function empty.
+
+The result? You can compile for `wasm32` and the `save_game` function simply doesn't *do* anything at all. The rest of the structure remains, so the game correctly returns to the main menu - but with no resume function.
+
+(Why does the check that the file exists work? Rust is smart enough to say "no filesystem, so the file can't exist". Thanks, Rust!)
+
 # Wrap-up
 
 This has been a long chapter, with quite heavy content. The great news is that we now have a framework for loading and saving the game whenever we want to. Adding components has gained some steps: we have to register them in `main`, tag them for `Serialize, Deserialize`, and remember to add them to our component type lists in `saveload_system.rs`. That could be easier - but it's a very solid foundation.

+ 4 - 0
chapter-11-loadsave/Cargo.toml

@@ -12,3 +12,7 @@ specs = { version = "0.15.0", features = ["serde"] }
 specs-derive = "0.4.0"
 serde= { version = "1.0", features = ["derive"] }
 serde_json = "1.0"
+
+[target.'cfg(any(target_arch = "wasm32"))'.dependencies]
+web-sys = { version = "0.3", features=["console"] }
+wasm-bindgen = "0.2"

+ 32 - 18
chapter-11-loadsave/src/main.rs

@@ -31,6 +31,8 @@ mod inventory_system;
 use inventory_system::{ ItemCollectionSystem, ItemUseSystem, ItemDropSystem };
 pub mod saveload_system;
 
+rltk::add_wasm_support!();
+
 #[derive(PartialEq, Copy, Clone)]
 pub enum RunState { AwaitingInput, 
     PreRun, 
@@ -44,8 +46,30 @@ pub enum RunState { AwaitingInput,
 }
 
 pub struct State {
-    pub ecs: World,
-    pub systems: Dispatcher<'static, 'static>
+    pub ecs: World
+}
+
+impl State {
+    fn run_systems(&mut self) {
+        let mut mapindex = MapIndexingSystem{};
+        mapindex.run_now(&self.ecs);
+        let mut vis = VisibilitySystem{};
+        vis.run_now(&self.ecs);
+        let mut mob = MonsterAI{};
+        mob.run_now(&self.ecs);
+        let mut melee = MeleeCombatSystem{};
+        melee.run_now(&self.ecs);
+        let mut damage = DamageSystem{};
+        damage.run_now(&self.ecs);
+        let mut pickup = ItemCollectionSystem{};
+        pickup.run_now(&self.ecs);
+        let mut itemuse = ItemUseSystem{};
+        itemuse.run_now(&self.ecs);
+        let mut drop_items = ItemDropSystem{};
+        drop_items.run_now(&self.ecs);
+
+        self.ecs.maintain();
+    }
 }
 
 impl GameState for State {
@@ -82,7 +106,7 @@ impl GameState for State {
         
         match newrunstate {
             RunState::PreRun => {
-                self.systems.dispatch(&self.ecs);
+                self.run_systems();
                 self.ecs.maintain();
                 newrunstate = RunState::AwaitingInput;
             }
@@ -90,12 +114,12 @@ impl GameState for State {
                 newrunstate = player_input(self, ctx);
             }
             RunState::PlayerTurn => {
-                self.systems.dispatch(&self.ecs);
+                self.run_systems();
                 self.ecs.maintain();
                 newrunstate = RunState::MonsterTurn;
             }
             RunState::MonsterTurn => {
-                self.systems.dispatch(&self.ecs);
+                self.run_systems();
                 self.ecs.maintain();
                 newrunstate = RunState::AwaitingInput;
             }
@@ -162,7 +186,7 @@ impl GameState for State {
             }
             RunState::SaveGame => {
                 saveload_system::save_game(&mut self.ecs);
-                newrunstate = RunState::MainMenu{ menu_selection : gui::MainMenuSelection::LoadGame };
+                newrunstate = RunState::MainMenu{ menu_selection : gui::MainMenuSelection::Quit };
             }
         }
 
@@ -175,20 +199,10 @@ impl GameState for State {
 }
 
 fn main() {
-    let mut context = Rltk::init_simple8x8(80, 50, "Hello Rust World", "../resources");
+    let mut context = Rltk::init_simple8x8(80, 50, "Hello Rust World", "resources");
     context.with_post_scanlines(true);
     let mut gs = State {
-        ecs: World::new(),
-        systems : DispatcherBuilder::new()
-            .with(MapIndexingSystem{}, "map_indexing_system", &[])
-            .with(VisibilitySystem{}, "visibility_system", &[])
-            .with(MonsterAI{}, "monster_ai", &["visibility_system", "map_indexing_system"])
-            .with(MeleeCombatSystem{}, "melee_combat", &["monster_ai"])
-            .with(DamageSystem{}, "damage", &["melee_combat"])
-            .with(ItemCollectionSystem{}, "pickup", &["melee_combat"])
-            .with(ItemUseSystem{}, "potions", &["melee_combat"])
-            .with(ItemDropSystem{}, "drop_items", &["melee_combat"])
-            .build(),
+        ecs: World::new()
     };
     gs.ecs.register::<Position>();
     gs.ecs.register::<Renderable>();

+ 6 - 1
chapter-11-loadsave/src/saveload_system.rs

@@ -20,6 +20,11 @@ macro_rules! serialize_individually {
     };
 }
 
+#[cfg(target_arch = "wasm32")]
+pub fn save_game(_ecs : &mut World) {
+}
+
+#[cfg(not(target_arch = "wasm32"))]
 pub fn save_game(ecs : &mut World) {
     // Create helper
     let mapcopy = ecs.get_mut::<super::map::Map>().unwrap().clone();
@@ -55,7 +60,7 @@ macro_rules! deserialize_individually {
         $(
         DeserializeComponents::<NoError, _>::deserialize(
             &mut ( &mut $ecs.write_storage::<$type>(), ),
-            &mut $data.0, // entities
+            &$data.0, // entities
             &mut $data.1, // marker
             &mut $data.2, // allocater
             &mut $de,

+ 1 - 0
wasmbuild.bat

@@ -10,6 +10,7 @@ CALL :Stage chapter-07-damage
 CALL :Stage chapter-08-ui
 CALL :Stage chapter-09-items
 CALL :Stage chapter-10-ranged
+CALL :Stage chapter-11-loadsave
 
 REM Publish or perish
 cd book\book\wasm