kaka.farm

Unnamed repository; edit this file 'description' to name the repository.
git clone https://kaka.farm/~git/kaka.farm
Log | Files | Refs | README

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.