commit fc70db9aa799bc71db91847c8002bb87cb9efa4b
parent 20a969c0df8b881613ace5cf34b624b9ea5e22f4
Author: Yuval Langer <yuval.langer@gmail.com>
Date: Wed, 12 Jun 2019 23:25:50 +0300
Publish quicksilver and wasm32 post.
Diffstat:
1 file changed, 189 insertions(+), 0 deletions(-)
diff --git a/posts/2019-06-12-making-quicksilver-and-friends-play-nice-with-wasm32.md b/posts/2019-06-12-making-quicksilver-and-friends-play-nice-with-wasm32.md
@@ -0,0 +1,189 @@
+---
+title: Making Quicksilver and friends play nice with wasm32.
+published_date: "2019-06-12 20:23:48 +0000"
+layout: default.liquid
+is_draft: false
+---
+## Disclaimer:
+
+This technology is indistinguishable from magic. I am merely recalling which
+ spells and incantations worked for me. If you have anything to add, you can
+ reach me on <https://gitgud.io/yuvallanger/kaka.farm/> or
+ <https://gitlab.com/yuvallanger/kaka.farm/>.
+
+Several months ago, writing a Flappy Bird clone called [Rectangly
+Rect](https://gitgud.io/yuvallanger/rectangly-rect/), I have done a bunch of
+asking around and found exactly which parts don't work and how to replace them.
+Yesterday, trying to adapt YasamSim to the web, I have re-discovered those
+workarounds, and decided to write this down.
+
+## `println!`!
+
+First thing, drop all of your `println!`s. For some esoteric reason, this
+function throws a wrench into the web's machinery. Same goes for
+`std::time::Instance::now()`. For now I just dropped all calls to `now()`,
+maybe I could ask the browser manually with whatever function Javascript has,
+or maybe there is a more standardized `std` alternative for the web - I don't
+know.
+
+In order to replace `println!`, I had to add to `Cargo.toml` a general
+dependency for the crate `log`, an entry for every target that is not wasm32
+for the crate `env_logger`, and an entry for the crate `web_logger` for the
+wasm32 target:
+
+```Cargo.toml
+[dependencies]
+log = "0.4"
+
+[target.'cfg(not(wasm32))'.dependencies]
+env_logger = "0.6"
+
+[target.'cfg(wasm32)'.dependencies]
+web_logger = "0.1"
+```
+
+In `src/logging.rs`, conditionally compile a different `init_logger()` function
+for each platform, wasm32 and not-wasm32:
+
+```src/logging.rs
+#[cfg(target_arch = "wasm32")]
+pub fn init_logger() {
+ ::web_logger::init();
+}
+
+#[cfg(not(target_arch = "wasm32"))]
+pub fn init_logger() {
+ ::env_logger::init();
+}
+```
+
+In `src/main.rs`, call the `init_logger()` defined in the `logging.rs`
+sub-module at the head of your `main()` function:
+
+```src/main.rs
+mod logging;
+
+fn main() {
+ logging::init_logger();
+ …
+}
+```
+
+Now you can call `info!()`, `error!()`, `warn!()`, etc., as described in <https://docs.rs/log/0.4/log/>.
+
+If you also debug it in your native target, you can also provide the `RUST_LOG`
+environment variable, as per <https://docs.rs/env_logger/0.6/env_logger/>'s
+documentation, in your command line incantations:
+
+```
+$ RUST_LOG=DEBUG cargo run
+```
+
+## Sequentialize SPECS.
+
+For some more esoteric reasons, probably something to do with threads, I had to
+rewrite how I run my `specs::System`s , and
+how `specs::System`s written.
+
+### Dispatching `specs::Dispatcher`.
+
+One normally builds a dependency graph of `specs::System`s using something
+like:
+
+```src/main.rs
+fn make_specs_dispatcher() -> specs::Dispatcher<'static, 'static> {
+ specs::DispatcherBuilder::new()
+ .with(
+ SystemFoo,
+ "system_foo",
+ &[],
+ )
+ .with(
+ SystemBar,
+ "system_bar",
+ &["system_foo"],
+ )
+ .build()
+}
+
+struct OurGameState {
+ specs_world: specs::World,
+ specs_dispatcher: specs::Dispatcher,
+}
+
+
+impl State for OurGameState {
+ fn new() -> Result<World> {
+ let specs_world = make_specs_world_and_register_components(); // Implemented elsewhere…
+ let specs_dispatcher = make_specs_dispatcher();
+
+ Ok(
+ OurGameState {
+ specs_world,
+ specs_dispatcher,
+ }
+ )
+ }
+
+ fn update(&mut self, window: &mut Window) -> Result<()> {
+ let system_foo = SystemFoo;
+ let system_bar = SystemBar;
+
+ system_foo.run_now(&selfworld.res);
+ system_bar.run_now(&world.res);
+
+ world.maintain();
+
+ Ok(())
+ }
+
+ [imagine the rest of the quicksilver::lifecycle::State methods implemented here…]
+}
+```
+
+In this example `SystemBar` depends on the state of the `specs::World` left by
+`SystemFoo` after it does its thing.
+
+Instead of using this `Dispatcher` as described in
+<https://slide-rs.github.io/specs/03_dispatcher.html>, you do this in your
+`quicksilver::lifecycle::State::update()`
+
+```
+struct OurGameState {
+ specs_world: specs::World,
+}
+
+impl State for OurGameState {
+ fn new() -> Result<World> {
+ let specs_world = make_specs_world_and_register_components(); // Implemented elsewhere…
+
+ Ok(
+ OurGameState {
+ specs_world,
+ }
+ )
+ }
+
+ fn update(&mut self, window: &mut Window) -> Result<()> {
+ let system_foo = SystemFoo;
+ let system_bar = SystemBar;
+
+ system_foo.run_now(&selfworld.res);
+ system_bar.run_now(&world.res);
+
+ world.maintain();
+
+ Ok(())
+ }
+
+ [imagine the rest of the quicksilver::lifecycle::State methods implemented here…]
+}
+```
+
+But in order to sequentialize how you deal with `specs`, you'd need to change one more thing:
+
+### Lay off `specs::LazyUpdater`.
+
+If you do anything with `specs::LazyUpdate`, you would have to convert it into
+another form, interacting with your `Component` `Storage`s directly with
+`WriteStorage` or whatever.