@@ -0,0 +1,216 @@
1+use dodrio::bumpalo::{self, Bump};
2+use dodrio::{Node, Render, Vdom};
3+use futures::prelude::*;
4+use std::cell::RefCell;
5+use std::rc::Rc;
6+use wasm_bindgen::{prelude::*, JsCast};
7+
8+#[repr(u8)]
9+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
10+pub enum Cell {
11+ Dead = 0,
12+ Alive = 1,
13+}
14+
15+/// The universe containing all of the cells for our Game of Life.
16+struct Universe {
17+ width: u32,
18+ height: u32,
19+ cells: Vec<Cell>,
20+}
21+
22+impl Universe {
23+ /// Construct a new universe.
24+ pub fn new() -> Universe {
25+ let width = 64;
26+ let height = 64;
27+
28+ let cells = (0..width * height)
29+ .map(|i| {
30+ if i % 2 == 0 || i % 7 == 0 {
31+ Cell::Alive
32+ } else {
33+ Cell::Dead
34+ }
35+ })
36+ .collect();
37+
38+ Universe {
39+ width,
40+ height,
41+ cells,
42+ }
43+ }
44+
45+ /// Compute one tick of the universe.
46+ pub fn tick(&mut self) {
47+ let mut next = self.cells.clone();
48+
49+ for row in 0..self.height {
50+ for col in 0..self.width {
51+ let idx = self.get_index(row, col);
52+ let cell = self.cells[idx];
53+ let live_neighbors = self.live_neighbor_count(row, col);
54+
55+ let next_cell = match (cell, live_neighbors) {
56+ // Rule 1: Any live cell with fewer than two live neighbours
57+ // dies, as if caused by underpopulation.
58+ (Cell::Alive, x) if x < 2 => Cell::Dead,
59+ // Rule 2: Any live cell with two or three live neighbours
60+ // lives on to the next generation.
61+ (Cell::Alive, 2) | (Cell::Alive, 3) => Cell::Alive,
62+ // Rule 3: Any live cell with more than three live
63+ // neighbours dies, as if by overpopulation.
64+ (Cell::Alive, x) if x > 3 => Cell::Dead,
65+ // Rule 4: Any dead cell with exactly three live neighbours
66+ // becomes a live cell, as if by reproduction.
67+ (Cell::Dead, 3) => Cell::Alive,
68+ // All other cells remain in the same state.
69+ (otherwise, _) => otherwise,
70+ };
71+
72+ next[idx] = next_cell;
73+ }
74+ }
75+
76+ self.cells = next;
77+ }
78+
79+ fn get_index(&self, row: u32, column: u32) -> usize {
80+ (row * self.width + column) as usize
81+ }
82+
83+ fn live_neighbor_count(&self, row: u32, column: u32) -> u8 {
84+ let mut count = 0;
85+
86+ let north = if row == 0 { self.height - 1 } else { row - 1 };
87+
88+ let south = if row == self.height - 1 { 0 } else { row + 1 };
89+
90+ let west = if column == 0 {
91+ self.width - 1
92+ } else {
93+ column - 1
94+ };
95+
96+ let east = if column == self.width - 1 {
97+ 0
98+ } else {
99+ column + 1
100+ };
101+
102+ let nw = self.get_index(north, west);
103+ count += self.cells[nw] as u8;
104+
105+ let n = self.get_index(north, column);
106+ count += self.cells[n] as u8;
107+
108+ let ne = self.get_index(north, east);
109+ count += self.cells[ne] as u8;
110+
111+ let w = self.get_index(row, west);
112+ count += self.cells[w] as u8;
113+
114+ let e = self.get_index(row, east);
115+ count += self.cells[e] as u8;
116+
117+ let sw = self.get_index(south, west);
118+ count += self.cells[sw] as u8;
119+
120+ let s = self.get_index(south, column);
121+ count += self.cells[s] as u8;
122+
123+ let se = self.get_index(south, east);
124+ count += self.cells[se] as u8;
125+
126+ count
127+ }
128+}
129+
130+/// The rendering implementation for our Game of Life.
131+impl Render for Universe {
132+ fn render<'a, 'bump>(&'a self, bump: &'bump Bump) -> Node<'bump>
133+ where
134+ 'a: 'bump,
135+ {
136+ use dodrio::builder::*;
137+
138+ let mut rows = bumpalo::collections::Vec::with_capacity_in(self.height as usize, bump);
139+
140+ for row in self.cells.chunks(self.width as usize) {
141+ let mut cells = bumpalo::collections::Vec::with_capacity_in(self.width as usize, bump);
142+
143+ for cell in row {
144+ cells.push(
145+ span(bump)
146+ .attr("class", "cell")
147+ .attr(
148+ "style",
149+ match cell {
150+ Cell::Alive => "background-color: black",
151+ Cell::Dead => "background-color: white",
152+ },
153+ )
154+ .finish(),
155+ );
156+ }
157+
158+ rows.push(div(bump).attr("class", "row").children(cells).finish());
159+ }
160+
161+ div(bump).attr("class", "universe").children(rows).finish()
162+ }
163+}
164+
165+#[wasm_bindgen(start)]
166+pub fn run() {
167+ // Set up the panic hook for debugging when things go wrong.
168+ console_error_panic_hook::set_once();
169+
170+ // Grab the document's `<body>`.
171+ let window = web_sys::window().unwrap_throw();
172+ let document = window.document().unwrap_throw();
173+ let body = document.body().unwrap_throw();
174+
175+ // Create a new Game of Life `Universe` render component.
176+ let universe = Universe::new();
177+
178+ // Create a virtual DOM and mount it and the `Hello` render component to the
179+ // `<body>`.
180+ let vdom = Vdom::new(body.as_ref(), universe);
181+
182+ // Kick off a loop that keeps computing a tick of the universe and then
183+ // re-rendering on every animation frame.
184+ let rc: Rc<RefCell<Option<Closure<FnMut()>>>> = Rc::new(RefCell::new(None));
185+ let rc2 = rc.clone();
186+ let window2 = window.clone();
187+ let weak = vdom.weak();
188+ let f = Closure::wrap(Box::new(move || {
189+ weak.schedule_render();
190+
191+ wasm_bindgen_futures::spawn_local(
192+ weak.with_component(|root| {
193+ let universe = root.unwrap_mut::<Universe>();
194+ universe.tick();
195+ })
196+ .map_err(|_| wasm_bindgen::throw_str("impossible, we always keep the vdom alive")),
197+ );
198+
199+ window
200+ .request_animation_frame(
201+ rc.borrow()
202+ .as_ref()
203+ .unwrap_throw()
204+ .as_ref()
205+ .unchecked_ref::<js_sys::Function>(),
206+ )
207+ .unwrap_throw();
208+ }) as Box<FnMut()>);
209+ window2
210+ .request_animation_frame(f.as_ref().unchecked_ref::<js_sys::Function>())
211+ .unwrap_throw();
212+ *rc2.borrow_mut() = Some(f);
213+
214+ // Run the virtual DOM forever and don't unmount it.
215+ vdom.forget();
216+}