book icon indicating copy to clipboard operation
book copied to clipboard

Expand game of life with a section about rendering to the canvas from rust

Open sacovo opened this issue 5 years ago • 0 comments

Is your feature request related to a problem? Please describe.

I just started learning wasm and rust and found the implementation of the game of life really helpful. But in the end I was wondering, whether or not it would be possible to implement the rendering of the universe in rust. So I did some research and tried to implement it by using web_sys::CanvasRenderingContext2d and it works. But I don't know whether or not it is a good solution, since there aren't a lot of tutorials for this.

Describe the solution you'd like I think a section about implementing the rendering in rust would be a nice addition to the tutorial. It could include:

  • the usage of the web-sys crate and how the enable the necessary features for using the canvas
  • how to pass the canvas to rust
  • applying styles to the canvas and draw with it
  • maybe something about performance and best practice

Describe alternatives you've considered I tried to implement something myself and succeeded, but I'm unsure whether or not the solution is actually smart or not. I'm still calling the requestAnimationFrame from JavaScript and passing the reference to the canvas each call. Maybe it would be better to store the reference to the canvas in a struct or call the request_animation_frame from rust.

Additional context Relevant part of my code in Rust.

#[wasm_bindgen]
impl Universe {
    pub fn new(width: u32, height: u32, cell_size: f64) -> Universe {
        ...
        Universe {
            width,
            height,
            cells,
            cell_size,
        }
    }

    pub fn draw(&self, context: &web_sys::CanvasRenderingContext2d) {
        self.draw_grid(context);
        self.draw_cells(context);
    }

    fn draw_grid(&self, context: &web_sys::CanvasRenderingContext2d) {
        context.begin_path();
        context.set_stroke_style(&JsValue::from("#ccc"));

        let canvas_height = (self.cell_size + 1.0) * (self.height + 1) as f64;
        let canvas_width = (self.cell_size + 1.0) * (self.width + 1) as f64;

        for i in 0..self.width {
            context.move_to((self.cell_size + 1.0) * (i as f64 + 1.0), 0.0);
            context.line_to((self.cell_size + 1.0) * (i as f64 + 1.0), canvas_height);
        }
        for i in 0..self.height {
            context.move_to(0.0, i as f64 * (self.cell_size + 1.0) + 1.0);
            context.line_to(canvas_width, i as f64 * (self.cell_size + 1.0) + 1.0);
        }
        context.stroke();
    }

    fn draw_cells(&self, context: &web_sys::CanvasRenderingContext2d) {
        context.begin_path();

        for row in 0..self.height {
            for col in 0..self.width {
                let idx = self.get_index(row, col);
                match self.cells.get(idx).unwrap() {
                    Cell::Alive => context.set_fill_style(&JsValue::from("#000")),
                    Cell::Dead => context.set_fill_style(&JsValue::from("#fff")),
                }
                context.fill_rect(
                    (col as f64 * (self.cell_size + 1.0) + 1.0) as f64,
                    (row as f64 * (self.cell_size + 1.0) + 1.0) as f64,
                    self.cell_size,
                    self.cell_size,
                );
            }
        }
        context.stroke();
    }
}

And in JS

      const universe = Universe.new(width, height, CELL_SIZE);

      const canvas = document.getElementById("game-of-live-canvas");

      canvas.height = (CELL_SIZE + 1) * height + 1;
      canvas.width = (CELL_SIZE + 1) * width + 1;

      const ctx = canvas.getContext('2d');

      const renderLoop = () => {
        universe.tick();
        universe.draw(ctx);
        requestAnimationFrame(renderLoop);
      }

      universe.tick();
      universe.draw(ctx);

      requestAnimationFrame(renderLoop);

sacovo avatar Jul 23 '20 07:07 sacovo