Skip to content

Commit

Permalink
improve comments
Browse files Browse the repository at this point in the history
  • Loading branch information
kimonp committed Dec 26, 2023
1 parent fc291e2 commit 5128575
Show file tree
Hide file tree
Showing 5 changed files with 18 additions and 385 deletions.
11 changes: 10 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,13 @@ While the code from the original tutorial is about 50% rust, 50% JavaScript, wit
* Install cargo and rust
* Install the dioxus API: cargo dioxus install
* Run in debug mode with the dioxus cli: dx serve --platform=web
* Point your browser at: http://localhost:8080
* Point your browser at: http://localhost:8080

## Methodology
Defines a GameOfLifeGrid component that renders the game of life (with several control buttons),
and a FramesPerSecond component which shows how many frames per seconds are being rendered (which
depends on the monitor frame rate).

This is stitched together using a use_animation_frame() hook returns a frame_id that can be used
to trigger use_effect calls to render each frame (for the grid, and frames per second). Also returns
frames_running, which can be set false or true to stop or start the frames.
9 changes: 4 additions & 5 deletions src/animation.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//! Animation frame control via a custom Dioxus hook.
//! Animation frame control via a custom Dioxus hook: use_animation_frame()

use std::cell::RefCell;
use std::rc::Rc;
Expand All @@ -10,8 +10,8 @@ use crate::websys_utils::*;

/// A custom Dioxus hook that abstracts the request_animation_frame() and cancel_animation_frame() DOM calls.
///
/// Allows the caller to create a use_effect which watches the frame_id which can then
/// take an action each time a frame is advanced.
/// Allows the caller to create a use_effect() which watches the frame_id,
/// which can then take an action each time a frame is advanced.
///
/// Returns two UseState variables: frame_running and frame_id.
/// * frame_running is true if frames are advancing.
Expand All @@ -28,8 +28,7 @@ pub fn use_animation_frame(cx: Scope, initial_state: bool) -> (&UseState<bool>,
to_owned![cancel_id, frame_id, frame_running];

// frame_loop_holder holds a closure that is passed to request_animation_frame().
// This closure is called each time an animation frame completes. We modify the universe
// inside this closure.
// This closure is called each time an animation frame completes.
let frame_loop_holder = Rc::new(RefCell::new(None));
let frame_loop_holder_clone = frame_loop_holder.clone();

Expand Down
5 changes: 3 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#[macro_use]
pub mod websys_utils;
pub(crate) mod websys_utils;

pub mod animation;
pub mod frames_per_second;
pub mod universe;
pub mod game_of_life;
196 changes: 1 addition & 195 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,28 +1,10 @@
use dioxus::html::GlobalAttributes;
// use dioxus_elements::canvas;
// import the prelude to get access to the `rsx!` macro and the `Scope` and `Element` types
use dioxus::prelude::*;

use game_of_life::animation::use_animation_frame;
use game_of_life::frames_per_second::FramesPerSecond;
use game_of_life::universe::{Cell, Universe, GRID_COLUMNS, GRID_ROWS};
use game_of_life::websys_utils::*;

use game_of_life::console_log;

use wasm_bindgen::prelude::Closure;
use wasm_bindgen::{JsCast, JsValue};

use web_sys::CanvasRenderingContext2d;

const CANVAS_ID: &str = "game-of-life-canvas";
const CELL_SIZE: u32 = 6; // px
const GRID_WIDTH: u32 = (CELL_SIZE + 1) * GRID_COLUMNS + 1;
const GRID_HEIGHT: u32 = (CELL_SIZE + 1) * GRID_ROWS + 1;

const GRID_COLOR: &str = "#CCCCCC";
const ALIVE_COLOR: &str = "#000000";
const DEAD_COLOR: &str = "#FFFFFF";
use game_of_life::game_of_life::GameOfLifeGrid;

// Entry point
fn main() {
Expand Down Expand Up @@ -55,182 +37,6 @@ fn App(cx: Scope) -> Element {
}
}

#[component]
fn GameOfLifeGrid(cx: Scope<'a>, frame_id: i32) -> Element {
let universe = use_ref(cx, Universe::new);

use_on_create(cx, || {
to_owned![universe];
async move {
config_grid(universe);
}
});

use_effect(cx, (frame_id,), |(_frame_id,)| {
to_owned![universe];
async move {
universe.with_mut(|universe| {
universe.tick();
draw_cells(universe.cells());
})
}
});

render! {
div { display: "flex", justify_content: "center", canvas { id: CANVAS_ID } }
div { display: "flex", justify_content: "center",
button {
onclick: move |_| {
universe
.with_mut(|universe| {
universe.random();
});
draw_cells(universe.read().cells());
},
"Random"
}
button {
onclick: move |_| {
universe
.with_mut(|universe| {
universe.clear();
});
draw_cells(universe.read().cells());
},
"Clear"
}
}
}
}

fn config_grid(universe: UseRef<Universe>) {
if let Some(canvas_ele) = document().get_element_by_id(CANVAS_ID) {
let canvas_ele: web_sys::HtmlCanvasElement = canvas_ele
.dyn_into::<web_sys::HtmlCanvasElement>()
.map_err(|_| ())
.unwrap();

if canvas_ele.height() == GRID_HEIGHT {
console_log!("Canvas already configured...");
return;
}
canvas_ele.set_height(GRID_HEIGHT);
canvas_ele.set_width(GRID_WIDTH);

draw_grid();

let universe = universe.clone();
let toggle_cell_closure =
Closure::<dyn FnMut(_)>::new(move |event: web_sys::MouseEvent| {
let canvas_ele = document().get_element_by_id(CANVAS_ID).unwrap();
let canvas_ele: web_sys::HtmlCanvasElement = canvas_ele
.dyn_into::<web_sys::HtmlCanvasElement>()
.map_err(|_| ())
.unwrap();
let bounding_rect = canvas_ele.get_bounding_client_rect();

let scale_x = canvas_ele.width() as f64 / bounding_rect.width();
let scale_y = canvas_ele.width() as f64 / bounding_rect.width();

let canvas_left = event.client_x() as f64 - bounding_rect.left() * scale_x;
let canvas_top = event.client_y() as f64 - bounding_rect.top() * scale_y;

let row = (canvas_top / (CELL_SIZE + 1) as f64)
.floor()
.min((GRID_HEIGHT - 1) as f64) as u32;
let col = (canvas_left / (CELL_SIZE + 1) as f64)
.floor()
.min((GRID_HEIGHT - 1) as f64) as u32;

universe.with_mut(|universe| {
universe.toggle_cell(row, col);
draw_cells(universe.cells());
});
});
let _ = canvas_ele.add_event_listener_with_callback(
"click",
toggle_cell_closure.as_ref().unchecked_ref(),
);
toggle_cell_closure.forget();
} else {
console_log!("Could not find id: {CANVAS_ID}");
}
}


// Draw the grid lines which contain the game of life cells.
fn draw_grid() {
let context = get_2d_context(CANVAS_ID);
let grid_color = JsValue::from_str(GRID_COLOR);
let height = GRID_HEIGHT;
let width = GRID_WIDTH;

context.begin_path();
context.set_line_width(0.5);
context.set_stroke_style(&grid_color);

// Vertical lines
for i in 0..=width {
let x = (i * (CELL_SIZE + 1) + 1) as f64;
let y = ((CELL_SIZE + 1) * height + 1) as f64;
context.move_to(x, 0_f64);
context.line_to(x, y)
}

// Horizontal lines
for i in 0..=height {
let x = ((CELL_SIZE + 1) * width + 1) as f64;
let y = (i * (CELL_SIZE + 1) + 1) as f64;
context.move_to(0_f64, y);
context.line_to(x, y)
}

context.stroke();
}

fn get_grid_index(row: u32, col: u32) -> u32 {
row * GRID_ROWS + col
}

// Draw all cells in the grid based on the state of the universe.
fn draw_cells(cells: &[Cell]) {
let context = get_2d_context(CANVAS_ID);
// let cells = universe.cells();

context.begin_path();

fill_cells(&context, cells, Cell::Alive);
fill_cells(&context, cells, Cell::Dead);
}

// Fill all the cells of the grid of the given cell_type (dead or alive).
//
// Use javascript canvas API for the rendering.
fn fill_cells(context: &CanvasRenderingContext2d, cells: &[Cell], cell_type: Cell) {
let fill_color = JsValue::from_str(match cell_type {
Cell::Alive => ALIVE_COLOR,
Cell::Dead => DEAD_COLOR,
});
context.set_fill_style(&fill_color);

for row in 0..GRID_ROWS {
for col in 0..GRID_COLUMNS {
let index = get_grid_index(row, col);

if let Some(cell) = cells.get(index as usize) {
if cell == &cell_type {
context.fill_rect(
(col * (CELL_SIZE + 1) + 1) as f64,
(row * (CELL_SIZE + 1) + 1) as f64,
CELL_SIZE as f64,
CELL_SIZE as f64,
)
}
}
}
}
}

// use web_sys::HtmlElement;

// #[component]
Expand Down
Loading

0 comments on commit 5128575

Please sign in to comment.