Debugging

Before we write much more code, we will want to have some debugging tools in ourbelt for when things go wrong. Take a moment to review the reference pagelisting tools and approaches available for debugging Rust-generatedWebAssembly.

Enable Logging for Panics

If our code panics, we want informative error messages to appear in thedeveloper console.

Our wasm-pack-template comes with an optional, enabled-by-default dependencyon the console_error_panic_hook crate that is configured inwasm-game-of-life/src/utils.rs. All we need to do is install the hook in aninitialization function or common code path. We can call it inside theUniverse::new constructor in wasm-game-of-life/src/lib.rs:

  1. # #![allow(unused_variables)]
  2. #fn main() {
  3. pub fn new() -> Universe {
  4. utils::set_panic_hook();
  5. // ...
  6. }
  7. #}

Add Logging to our Game of Life

Let's use the console.log function via the web-sys crate to add somelogging about each cell in our Universe::tick function.

First, add web-sys as a dependency and enable its "console" feature inwasm-game-of-life/Cargo.toml:

  1. [dependencies.web-sys]
  2. version = "0.3"
  3. features = [
  4. "console",
  5. ]

For ergonomics, we'll wrap the console.log function up in a println!-stylemacro:

  1. # #![allow(unused_variables)]
  2. #fn main() {
  3. extern crate web_sys;
  4. // A macro to provide `println!(..)`-style syntax for `console.log` logging.
  5. macro_rules! log {
  6. ( $( $t:tt )* ) => {
  7. web_sys::console::log_1(&format!( $( $t )* ).into());
  8. }
  9. }
  10. #}

Now, we can start logging messages to the console by inserting calls to log inRust code. For example, to log each cell's state, live neighbors count, and nextstate, we could modify wasm-game-of-life/src/lib.rs like this:

  1. diff --git a/src/lib.rs b/src/lib.rs
  2. index f757641..a30e107 100755
  3. --- a/src/lib.rs
  4. +++ b/src/lib.rs
  5. @@ -123,6 +122,14 @@ impl Universe {
  6. let cell = self.cells[idx];
  7. let live_neighbors = self.live_neighbor_count(row, col);
  8. + log!(
  9. + "cell[{}, {}] is initially {:?} and has {} live neighbors",
  10. + row,
  11. + col,
  12. + cell,
  13. + live_neighbors
  14. + );
  15. +
  16. let next_cell = match (cell, live_neighbors) {
  17. // Rule 1: Any live cell with fewer than two live neighbours
  18. // dies, as if caused by underpopulation.
  19. @@ -140,6 +147,8 @@ impl Universe {
  20. (otherwise, _) => otherwise,
  21. };
  22. + log!(" it becomes {:?}", next_cell);
  23. +
  24. next[idx] = next_cell;
  25. }
  26. }

Using a Debugger to Pause Between Each Tick

Browser's stepping debuggers are useful for inspecting the JavaScript that ourRust-generated WebAssembly interactswith.

For example, we can use the debugger to pause on each iteration of ourrenderLoop function by placing a JavaScript debugger; statementabove our call to universe.tick().

  1. const renderLoop = () => {
  2. debugger;
  3. universe.tick();
  4. drawGrid();
  5. drawCells();
  6. requestAnimationFrame(renderLoop);
  7. };

This provides us with a convenient checkpoint for inspecting logged messages,and comparing the currently rendered frame to the previous one.

Screenshot of debugging the Game of Life

Exercises

  • Add logging to the tick function that records the row and column of eachcell that transitioned states from live to dead or vice versa.

  • Introduce a panic!() in the Universe::new method. Inspect the panic'sbacktrace in your Web browser's JavaScript debugger. Disable debug symbols,rebuild without the console_error_panic_hook optional dependency, andinspect the stack trace again. Not as useful is it?