2019-06-12-making-quicksilver-and-friends-play-nice-with-wasm32.md (5169B)
1 title: Making Quicksilver and friends play nice with wasm32. 2 date: 2019-06-12 20:23:48 3 --- 4 ## Disclaimer: 5 6 This technology is indistinguishable from magic. I am merely recalling which 7 spells and incantations worked for me. If you have anything to add, you can 8 reach me on <https://gitgud.io/yuvallanger/kaka.farm/> or 9 <https://gitlab.com/yuvallanger/kaka.farm/>. 10 11 Several months ago, writing a Flappy Bird clone called [Rectangly 12 Rect](https://gitgud.io/yuvallanger/rectangly-rect/), I have done a bunch of 13 asking around and found exactly which parts don't work and how to replace them. 14 Yesterday, trying to adapt YasamSim to the web, I have re-discovered those 15 workarounds, and decided to write this down. 16 17 ## `println!`! 18 19 First thing, drop all of your `println!`s. For some esoteric reason, this 20 function throws a wrench into the web's machinery. Same goes for 21 `std::time::Instance::now()`. For now I just dropped all calls to `now()`, 22 maybe I could ask the browser manually with whatever function Javascript has, 23 or maybe there is a more standardized `std` alternative for the web - I don't 24 know. 25 26 In order to replace `println!`, I had to add to `Cargo.toml` a general 27 dependency for the crate `log`, an entry for every target that is not wasm32 28 for the crate `env_logger`, and an entry for the crate `web_logger` for the 29 wasm32 target: 30 31 ```Cargo.toml 32 [dependencies] 33 log = "0.4" 34 35 [target.'cfg(not(wasm32))'.dependencies] 36 env_logger = "0.6" 37 38 [target.'cfg(wasm32)'.dependencies] 39 web_logger = "0.1" 40 ``` 41 42 In `src/logging.rs`, conditionally compile a different `init_logger()` function 43 for each platform, wasm32 and not-wasm32: 44 45 ```src/logging.rs 46 #[cfg(target_arch = "wasm32")] 47 pub fn init_logger() { 48 ::web_logger::init(); 49 } 50 51 #[cfg(not(target_arch = "wasm32"))] 52 pub fn init_logger() { 53 ::env_logger::init(); 54 } 55 ``` 56 57 In `src/main.rs`, call the `init_logger()` defined in the `logging.rs` 58 sub-module at the head of your `main()` function: 59 60 ```src/main.rs 61 mod logging; 62 63 fn main() { 64 logging::init_logger(); 65 … 66 } 67 ``` 68 69 Now you can call `info!()`, `error!()`, `warn!()`, etc., as described in <https://docs.rs/log/0.4/log/>. 70 71 If you also debug it in your native target, you can also provide the `RUST_LOG` 72 environment variable, as per <https://docs.rs/env_logger/0.6/env_logger/>'s 73 documentation, in your command line incantations: 74 75 ``` 76 $ RUST_LOG=DEBUG cargo run 77 ``` 78 79 ## Sequentialize SPECS. 80 81 For some more esoteric reasons, probably something to do with threads, I had to 82 rewrite how I run my `specs::System`s , and 83 how `specs::System`s written. 84 85 ### Dispatching `specs::Dispatcher`. 86 87 One normally builds a dependency graph of `specs::System`s using something 88 like: 89 90 ```src/main.rs 91 fn make_specs_dispatcher() -> specs::Dispatcher<'static, 'static> { 92 specs::DispatcherBuilder::new() 93 .with( 94 SystemFoo, 95 "system_foo", 96 &[], 97 ) 98 .with( 99 SystemBar, 100 "system_bar", 101 &["system_foo"], 102 ) 103 .build() 104 } 105 106 struct OurGameState { 107 specs_world: specs::World, 108 specs_dispatcher: specs::Dispatcher, 109 } 110 111 112 impl State for OurGameState { 113 fn new() -> Result<World> { 114 let specs_world = make_specs_world_and_register_components(); // Implemented elsewhere… 115 let specs_dispatcher = make_specs_dispatcher(); 116 117 Ok( 118 OurGameState { 119 specs_world, 120 specs_dispatcher, 121 } 122 ) 123 } 124 125 fn update(&mut self, window: &mut Window) -> Result<()> { 126 let system_foo = SystemFoo; 127 let system_bar = SystemBar; 128 129 system_foo.run_now(&selfworld.res); 130 system_bar.run_now(&world.res); 131 132 world.maintain(); 133 134 Ok(()) 135 } 136 137 [imagine the rest of the quicksilver::lifecycle::State methods implemented here…] 138 } 139 ``` 140 141 In this example `SystemBar` depends on the state of the `specs::World` left by 142 `SystemFoo` after it does its thing. 143 144 Instead of using this `Dispatcher` as described in 145 <https://slide-rs.github.io/specs/03_dispatcher.html>, you do this in your 146 `quicksilver::lifecycle::State::update()` 147 148 ``` 149 struct OurGameState { 150 specs_world: specs::World, 151 } 152 153 impl State for OurGameState { 154 fn new() -> Result<World> { 155 let specs_world = make_specs_world_and_register_components(); // Implemented elsewhere… 156 157 Ok( 158 OurGameState { 159 specs_world, 160 } 161 ) 162 } 163 164 fn update(&mut self, window: &mut Window) -> Result<()> { 165 let system_foo = SystemFoo; 166 let system_bar = SystemBar; 167 168 system_foo.run_now(&selfworld.res); 169 system_bar.run_now(&world.res); 170 171 world.maintain(); 172 173 Ok(()) 174 } 175 176 [imagine the rest of the quicksilver::lifecycle::State methods implemented here…] 177 } 178 ``` 179 180 But in order to sequentialize how you deal with `specs`, you'd need to change one more thing: 181 182 ### Lay off `specs::LazyUpdater`. 183 184 If you do anything with `specs::LazyUpdate`, you would have to convert it into 185 another form, interacting with your `Component` `Storage`s directly with 186 `WriteStorage` or whatever.