Each panel below is one view! body compiled twice: to HTML by the server, and to a dom-expressions template by the rustc backend. What is new here is not the hydration, which the counter already showed. It is that the thing being compiled is a program -- Conway's rule as a match, a physics pass over a board, a recursive flood fill -- and that all of it is Rust the browser runs as plain JavaScript.
Only Life is hydrated eagerly. Open the Network panel and scroll: sand.js and mines.js arrive as they come into view, because an island the reader never reaches costs nothing.
the counter island | the nesting island | the searching island | the streaming island | back to the three panels
generation 0 · population 10
The board is 640 bytes the page owns and the island borrows: __rt.board hands back the { buf, off, len } record a &mut [u8] is, and the compiled step indexes it in place. Two subscriptions keep it cheap -- the driver reads the clock and is one number of text, the grid reads the generation and rebuilds only when the board really moved.
grains 0 brush sand
Every pixel here is a fillRect the compiled Rust asked for, through eight #[js_extern] declarations and no drawing loop. A frame paints the cells whose value changed and no others, so a settled pile costs nothing; turning a clientX into a cell is Rust too, subtracting the canvas's own rectangle.
15playing
The first click is always safe, because nothing is placed until it happens: the mines are dealt from the seed the server rendered into data-ts, skipping the clicked cell and its eight neighbours. Opening a blank cell runs a recursive flood fill, and a right click flags rather than opening because the handler calls prevent_default().
The two panes below are the Life island as written and the chunk the backend compiled it to, both read at build time with include_str!. Nothing here is interactive, which is the point: it is a server rendered <details> with two strings in it, and the page carries no code for it at all.
//! The Game of Life island: a board the browser plays, stepped by compiled Rust.
//!
//! Like every island this file is a module of two crates. The server crate reads
//! it as ordinary Topcoat and renders the first generation as HTML with the
//! hydration keys in it; the client crate reads it with `topcoat_client` set and
//! the rustc backend compiles the same view to JavaScript. What is new here is
//! that the thing being compiled is a *program*: a wrapping neighbour count, a
//! `match` over a pair that is the whole of Conway's rule, and a double-buffered
//! step, none of which the old runtime expression language could say at all.
//!
//! # Where the board lives
//!
//! Nowhere in this crate. The island crate is `#![no_std]` with no heap and no
//! mutable statics, so a `[u8; 640]` that survives from one generation to the
//! next is not a thing it can declare. It borrows one instead: `__rt.board` hands
//! back the `{ buf, off, len }` record a slice is, over an array the page keeps,
//! and the backend indexes one as `record.buf[record.off + i]`. So `cur()` and
//! `next()` below are a read and a write of the page's own array with nothing
//! copied in either direction. See the showcase section of
//! [`island-rt`](../src/island-rt.mjs).
//!
//! # What the view may say, and what that costs
//!
//! A `$( ... )` value -- a reactive hole or a handler -- is compiled by the
//! server's expression language as well as by the client compiler, and that
//! language has no free function calls: `f(a)` lowers to `f.call((a,))`, which is
//! not a thing a Rust fn item is. It does have method calls. So everything a hole
//! or a handler asks of the simulation is a method of [`Control`], written on the
//! signal that the answer bumps -- `edits.toggle(..)` changes the board and says
//! so, `generation.advance()` steps it and says so. Plain Rust is still plain
//! Rust in the two places a view has it: a `for` loop's iterable and a `signal`
//! initialiser.
//!
//! # Which hole re-runs when
//!
//! Two subscriptions, kept apart on purpose.
//!
//! * The driver hole reads `tick`, which a 20 Hz interval bumps. It is one number of text, so
//! running it 20 times a second costs nothing.
//! * The grid's loop reads `generation` and `edits`, which move only when the board actually
//! changed. 640 rows are rebuilt then and not otherwise, so a paused board is a page doing no DOM
//! work at all.
//!
//! The clock runs at a fixed rate and the speed slider picks a stride over it,
//! because an interval cannot be re-timed through the exports the page lends and
//! starting a second one would leave the first running.
//!
//! # Why writing `generation` from the driver does not loop
//!
//! The driver runs inside an effect, and an effect that reads what it writes
//! re-runs itself for ever unless the write makes the condition false. It does
//! here: `stepped` holds the tick the last step happened at, so the re-run the
//! write provokes finds `stepped == tick` and does nothing. That fixed point is
//! also what keeps a change to `playing` or `speed` -- both read by the driver,
//! both able to re-run it mid-tick -- from stepping the board a second time.
//!
//! # Why the first render matches on both sides
//!
//! The server renders the opening board and the client adopts that markup, so the
//! two have to agree cell for cell. They agree because there is one
//! [`initial_alive`], shared by both halves with no cfg on it: the server maps it
//! over `0..640` and the client stamps the same function into the board before
//! the first render reads it. What that produces is written down twice -- in the
//! test at the foot of this file, against the server half, and in `smoke/life.mjs`,
//! against the compiled client -- so neither side can drift alone.
/// Columns of the board.
const WIDTH: i32 = 32;
/// Rows of it.
const HEIGHT: i32 = 20;
/// How many cells that is. The loop rebuilds all of them whenever the board
/// changes, so it is the number to move if the page ever stutters.
const CELLS: i32 = WIDTH * HEIGHT;
/// The board being read, as [`island-rt`](../src/island-rt.mjs) numbers it.
///
/// The four constants from here to [`NEIGHBOURS`] are the simulation's own, and
/// the simulation is the client's half alone: the server renders the opening
/// board from [`initial_alive`] and never steps it. So they carry the same
/// `topcoat_client` gate their callers do, rather than sitting outside it and
/// being dead code in every server build.
#[cfg(topcoat_client)]
const CUR: f64 = 1.0;
/// The board being written. A generation depends on its neighbours *before* the
/// step, so writing into the board being read would let the first half of a
/// generation change the second half.
#[cfg(topcoat_client)]
const NEXT: f64 = 2.0;
/// The number this island claims its one-time setup under.
///
/// The same number as [`CUR`] rather than a new one, and that is the convention
/// and not a coincidence: three islands share `claim`, the plan hands out board
/// ids and nothing else, so an island claiming under its first board id is an
/// island that invents no number at all.
#[cfg(topcoat_client)]
const CLAIM: f64 = CUR;
/// The eight cells that surround a cell, as offsets from it.
///
/// A table rather than a pair of nested loops, because the centre cell is not its
/// own neighbour and a table says so by leaving `(0, 0)` out.
#[cfg(topcoat_client)]
const NEIGHBOURS: [(i32, i32); 8] = [
(-1, -1),
(0, -1),
(1, -1),
(-1, 0),
(1, 0),
(-1, 1),
(0, 1),
(1, 1),
];
/// The five living cells of a glider, as offsets from the cell it is stamped at.
const GLIDER: [(i32, i32); 5] = [(1, 0), (2, 1), (0, 2), (1, 2), (2, 2)];
/// Where the glider starts.
const GLIDER_AT: (i32, i32) = (1, 1);
/// The five living cells of an r-pentomino, which is the smallest pattern worth
/// watching: it takes over a thousand generations to settle.
const R_PENTOMINO: [(i32, i32); 5] = [(1, 0), (2, 0), (0, 1), (1, 1), (1, 2)];
/// Where it starts, which is roughly the middle of the board.
const R_PENTOMINO_AT: (i32, i32) = (15, 8);
/// Whether the cell at `index` is alive before anything has stepped.
///
/// Shared by both halves with no cfg on it, and that is the whole of the parity
/// story: the server renders this over `0..CELLS` and the client stamps the same
/// answers into the board before its first render reads it, so the markup the
/// browser adopts is the markup it would have built.
///
/// Integer arithmetic only. A float that reads back as `1` on one side and
/// `1.0000000000000002` on the other would be a difference in the HTML.
fn initial_alive(index: i32) -> bool {
if !(0..CELLS).contains(&index) {
return false;
}
let x = index % WIDTH;
let y = index / WIDTH;
covers(&GLIDER, x - GLIDER_AT.0, y - GLIDER_AT.1)
|| covers(&R_PENTOMINO, x - R_PENTOMINO_AT.0, y - R_PENTOMINO_AT.1)
}
/// Whether `shape` has a living cell at the offset `(dx, dy)`.
///
/// An indexed `while` rather than an iterator: this runs 640 times per pattern on
/// the first render of both halves, and a plain loop is the same answer with
/// nothing borrowed.
fn covers(shape: &[(i32, i32)], dx: i32, dy: i32) -> bool {
let mut at = 0;
while at < shape.len() {
let (ox, oy) = shape[at];
if ox == dx && oy == dy {
return true;
}
at += 1;
}
false
}
/// How many cells the opening board has alive.
///
/// What the driver hole renders before anything has stepped, on both halves. The
/// server has no board to count, and the client's first run counts the board it
/// has just stamped, so this is the one place the two answers are made the same.
#[cfg(not(topcoat_client))]
fn initial_population() -> f64 {
let mut alive = 0.0;
let mut index = 0;
while index < CELLS {
if initial_alive(index) {
alive += 1.0;
}
index += 1;
}
alive
}
/// One cell of the board, as the grid's loop renders it.
#[derive(Clone, Copy)]
struct Cell {
/// Which cell this is, which the button carries as its `value` so that one
/// handler on the container can tell which one was clicked.
index: i32,
/// `"cell"` or `"cell on"`.
class: &'static str,
}
/// How a cell in that state is dressed.
///
/// Two `&'static str`s rather than a formatted string, because the island crate
/// has no heap to format into.
fn alive_class(alive: bool) -> &'static str {
if alive { "cell on" } else { "cell" }
}
/// The event a handler is handed.
///
/// The browser's compiler hands a handler a borrowed event and the server's hands
/// it an owned one, so the type is declared once per target and the handler names
/// only this. Naming the type in the parameter instead would name one target's
/// type in both. The search island's arrangement, and for its reasons.
#[cfg(topcoat_client)]
type Ev<'a> = &'a ::view_abi::Event;
#[cfg(not(topcoat_client))]
type Ev<'a> = ::topcoat::runtime::Event;
/// The cells to draw.
///
/// The server has stepped nothing, so its answer is the opening board. Both
/// arguments are read for the subscription rather than for the number: reading
/// them is what makes the loop run again when the board changes, and the board is
/// where the cells come from.
#[cfg(not(topcoat_client))]
fn cells(_generation: f64, _edits: f64, _tick: &::topcoat::runtime::Signal<f64>) -> Vec<Cell> {
(0..CELLS)
.map(|index| Cell {
index,
class: alive_class(initial_alive(index)),
})
.collect()
}
/// The cells to draw, read straight out of the page's array.
///
/// `tick` is taken and never read. It is what [`open`] needs to start the clock,
/// and this loop is one of the two holes that might be the first to run: whichever
/// of them gets there opens the island, so neither has to be written above the
/// other. Taking it does not subscribe the loop to it -- only `get` does that --
/// which is what keeps a 20 Hz clock from rebuilding 640 rows 20 times a second.
#[cfg(topcoat_client)]
fn cells(_generation: f64, _edits: f64, tick: ::view_abi::Sig<f64>) -> Cells {
open(tick);
Cells {
at: 0,
board: cur(),
}
}
/// The cells of the current board, one at a time.
///
/// An iterator rather than a collection, for the reason the search island's is:
/// there is no heap here to collect into.
#[cfg(topcoat_client)]
struct Cells {
at: i32,
board: &'static [u8],
}
#[cfg(topcoat_client)]
impl Iterator for Cells {
type Item = Cell;
fn next(&mut self) -> Option<Cell> {
if self.at >= CELLS {
return None;
}
let index = self.at;
self.at += 1;
Some(Cell {
index,
class: alive_class(self.board[index as usize] != 0),
})
}
}
/// What a hole or a handler may ask of the simulation.
///
/// Methods rather than functions because the view's expression language has no
/// free function calls, and written on the signal each one bumps because that is
/// the part a reader of the view needs: `edits.toggle(..)` says that a click
/// changes the board, `generation.advance()` says that the step button moves the
/// generation on.
///
/// The server's half is every method with an empty body. Its handlers are never
/// called -- the server renders markup and the browser's copy of the island is
/// what runs -- and its driver renders the opening population, which is the one
/// answer it has to get right.
#[cfg(topcoat_client)]
trait Control {
/// Steps the board if this tick is one the speed asks for, and answers with
/// the population either way. Written on `tick`.
fn drive(
self,
playing: ::view_abi::Sig<f64>,
speed: ::view_abi::Sig<f64>,
stepped: ::view_abi::Sig<f64>,
generation: ::view_abi::Sig<f64>,
) -> f64;
/// Steps the board once, whatever the clock is doing. Written on
/// `generation`.
fn advance(self);
/// Flips the cell the click came from, or does nothing if it came from
/// between the cells. Written on `edits`.
fn toggle(self, target: &'static str);
/// Puts the opening board back. Written on `edits`.
fn reset(self);
/// Kills every cell. Written on `edits`.
fn clear(self);
/// Fills the board at random from `seed`. Written on `edits`.
fn scatter(self, seed: f64);
/// Takes the slider's position, which arrives as text. Written on `speed`.
fn tune(self, target: &'static str);
}
#[cfg(topcoat_client)]
impl Control for ::view_abi::Sig<f64> {
fn drive(
self,
playing: ::view_abi::Sig<f64>,
speed: ::view_abi::Sig<f64>,
stepped: ::view_abi::Sig<f64>,
generation: ::view_abi::Sig<f64>,
) -> f64 {
open(self);
let now = self.get();
// `now > 0.0` is what keeps the first run -- the one that happens inside
// hydration -- from stepping: the markup the browser is adopting is the
// board before any step, so a step here would be a mutation on a hydrate
// that should have none.
//
// `stepped.get() != now` is the fixed point. Writing `stepped` and
// `generation` below re-runs this hole, because it read them; the re-run
// finds this false and stops. See the module docs.
if playing.get() != 0.0
&& now > 0.0
&& stepped.get() != now
&& (now as i32) % stride(speed.get()) == 0
{
let population = step(cur(), next());
unsafe { board_swap(CUR, NEXT) };
stepped.set(now);
generation.set(generation.get() + 1.0);
return population;
}
population_of(cur())
}
fn advance(self) {
let _ = step(cur(), next());
unsafe { board_swap(CUR, NEXT) };
self.set(self.get() + 1.0);
}
fn toggle(self, target: &'static str) {
let index = cell_index(target);
if index < 0 {
return;
}
let board = cur_mut();
board[index as usize] = if board[index as usize] == 0 { 1 } else { 0 };
self.set(self.get() + 1.0);
}
fn reset(self) {
stamp();
self.set(self.get() + 1.0);
}
fn clear(self) {
let board = cur_mut();
let mut index = 0;
while index < CELLS {
board[index as usize] = 0;
index += 1;
}
self.set(self.get() + 1.0);
}
fn scatter(self, seed: f64) {
let mut state = seed as u32;
if state == 0 {
state = DEFAULT_SEED;
}
let board = cur_mut();
let mut index = 0;
while index < CELLS {
state = xorshift(state);
board[index as usize] = if state % 100 < DENSITY { 1 } else { 0 };
index += 1;
}
self.set(self.get() + 1.0);
}
fn tune(self, target: &'static str) {
let steps = digits(target);
if steps < 0 {
return;
}
self.set(steps as f64);
}
}
#[cfg(not(topcoat_client))]
trait Control {
fn drive(
&self,
playing: &::topcoat::runtime::SignalSurrogate<f64>,
speed: &::topcoat::runtime::SignalSurrogate<f64>,
stepped: &::topcoat::runtime::SignalSurrogate<f64>,
generation: &::topcoat::runtime::SignalSurrogate<f64>,
) -> ::topcoat::runtime::F64Surrogate;
fn advance(&self);
fn toggle(&self, target: ::topcoat::runtime::StringSurrogate);
fn reset(&self);
fn clear(&self);
fn scatter(&self, seed: ::topcoat::runtime::F64Surrogate);
fn tune(&self, target: ::topcoat::runtime::StringSurrogate);
}
#[cfg(not(topcoat_client))]
impl Control for ::topcoat::runtime::SignalSurrogate<f64> {
/// The population before anything has stepped, which is what the browser's
/// first run answers too.
fn drive(
&self,
_playing: &::topcoat::runtime::SignalSurrogate<f64>,
_speed: &::topcoat::runtime::SignalSurrogate<f64>,
_stepped: &::topcoat::runtime::SignalSurrogate<f64>,
_generation: &::topcoat::runtime::SignalSurrogate<f64>,
) -> ::topcoat::runtime::F64Surrogate {
::topcoat::runtime::Surrogated::into_surrogate(initial_population())
}
fn advance(&self) {}
fn toggle(&self, _target: ::topcoat::runtime::StringSurrogate) {}
fn reset(&self) {}
fn clear(&self) {}
fn scatter(&self, _seed: ::topcoat::runtime::F64Surrogate) {}
fn tune(&self, _target: ::topcoat::runtime::StringSurrogate) {}
}
/// How many ticks the clock delivers a second.
///
/// A fixed rate with a stride over it, rather than an interval the slider
/// re-times: the page lends this island a clock it can start and nothing that
/// stops one, so re-timing would mean a second interval beside the first.
#[cfg(topcoat_client)]
const TICKS_PER_SECOND: i32 = 20;
/// How long that is, as the interval takes it.
#[cfg(topcoat_client)]
const TICK_MS: f64 = 1000.0 / TICKS_PER_SECOND as f64;
/// How many cells in a hundred [`Control::scatter`] leaves alive. Below about a
/// fifth a random board dies out; well above it, it jams.
#[cfg(topcoat_client)]
const DENSITY: u32 = 30;
/// The state [`Control::scatter`] falls back to, because a seed of zero would
/// leave [`xorshift`] producing zero for ever.
#[cfg(topcoat_client)]
const DEFAULT_SEED: u32 = 0x2545_f491;
/// How many ticks apart two steps are, at `speed` steps a second.
///
/// Clamped rather than trusted: `speed` arrives from a range input, and the text
/// of an input is whatever the page was handed.
#[cfg(topcoat_client)]
fn stride(speed: f64) -> i32 {
let steps = speed as i32;
let steps = if steps < 1 {
1
} else if steps > TICKS_PER_SECOND {
TICKS_PER_SECOND
} else {
steps
};
TICKS_PER_SECOND / steps
}
/// Opens the island: stamps the opening board, makes the second one, and starts
/// the clock.
///
/// Called by both of the holes that could be the first to run, and guarded by the
/// page's `claim` so that only the first call does anything. An island's setup
/// must be synchronous -- hydration's window is exactly one synchronous call
/// stack -- and every call here is, so the first run of a hole is the right place
/// for it. That is the dashboard island's arrangement.
#[cfg(topcoat_client)]
fn open(tick: ::view_abi::Sig<f64>) {
if !unsafe { claim(CLAIM) } {
return;
}
stamp();
// Made now rather than in the middle of the first step, so that the step is
// a write into an array that already exists.
let _ = next();
unsafe { clock_start(tick, TICK_MS) };
}
/// Writes the opening board.
#[cfg(topcoat_client)]
fn stamp() {
let board = cur_mut();
let mut index = 0;
while index < CELLS {
board[index as usize] = if initial_alive(index) { 1 } else { 0 };
index += 1;
}
}
/// The board being read.
#[cfg(topcoat_client)]
fn cur() -> &'static [u8] {
unsafe { board_ro(CUR, CELLS as f64) }
}
/// The same board, to write.
#[cfg(topcoat_client)]
fn cur_mut() -> &'static mut [u8] {
unsafe { board(CUR, CELLS as f64) }
}
/// The board the next generation is written into.
#[cfg(topcoat_client)]
fn next() -> &'static mut [u8] {
unsafe { board(NEXT, CELLS as f64) }
}
/// How many of the eight neighbours of `(x, y)` are alive.
///
/// The board wraps: a coordinate that runs off one edge comes back on the
/// opposite one. That is what `rem_euclid` is for, and why it is not `%`. The two
/// differ exactly on negative numbers, which is exactly the case that arises
/// here: `-1 % w` is `-1`, but `(-1).rem_euclid(w)` is `w - 1`, the far edge.
#[cfg(topcoat_client)]
fn living_neighbours(board: &[u8], x: i32, y: i32) -> i32 {
let mut alive = 0;
let mut at = 0;
while at < NEIGHBOURS.len() {
let (dx, dy) = NEIGHBOURS[at];
at += 1;
let nx = (x + dx).rem_euclid(WIDTH);
let ny = (y + dy).rem_euclid(HEIGHT);
if board[(ny * WIDTH + nx) as usize] != 0 {
alive += 1;
}
}
alive
}
/// Writes the generation after `cur` into `next` and answers with how many cells
/// it left alive.
///
/// The rule itself is one `match` over a pair, which is the whole of Life: a
/// living cell with two or three living neighbours stays alive, a dead cell with
/// exactly three is born, and every other case is dead. It is also the thing this
/// page is about -- the expression language the old client had could not say a
/// `match` at all.
#[cfg(topcoat_client)]
fn step(cur: &[u8], next: &mut [u8]) -> f64 {
let mut population = 0.0;
let mut y = 0;
while y < HEIGHT {
let mut x = 0;
while x < WIDTH {
let index = (y * WIDTH + x) as usize;
let alive = match (cur[index] != 0, living_neighbours(cur, x, y)) {
(true, 2) | (true, 3) | (false, 3) => 1,
_ => 0,
};
next[index] = alive;
population += alive as f64;
x += 1;
}
y += 1;
}
population
}
/// How many cells are alive.
///
/// [`step`] already answers this for the board it wrote, so this is for the runs
/// that stepped nothing.
#[cfg(topcoat_client)]
fn population_of(board: &[u8]) -> f64 {
let mut alive = 0.0;
let mut index = 0;
while index < CELLS {
if board[index as usize] != 0 {
alive += 1.0;
}
index += 1;
}
alive
}
/// One step of a 32 bit xorshift generator.
///
/// The caller carries the state, so this is a pure function of its input and a
/// board can be replayed from the seed alone.
#[cfg(topcoat_client)]
fn xorshift(state: u32) -> u32 {
let mut x = state;
x ^= x << 13;
x ^= x >> 17;
x ^= x << 5;
x
}
/// Which cell a click landed on, or `-1` for a click that landed on none.
///
/// A delegated click reports the element it came FROM, and every cell is a
/// `<button value=...>`, so the cell names itself and the grid needs one handler
/// rather than 640 closures. A click on the gap between cells reports the
/// container, which has no value, and `target_value` reads that back as the empty
/// string -- which is why "no cell" is a case here rather than an accident.
#[cfg(topcoat_client)]
fn cell_index(target: &str) -> i32 {
let index = digits(target);
if index < 0 || index >= CELLS {
return -1;
}
index
}
/// The decimal number `text` spells, or `-1` if it spells anything else.
///
/// Written out rather than `str::parse`, which answers with a `Result` carrying
/// an error type this crate has no reason to bring in. `as_bytes` on a `&str` is
/// the host's own UTF-8 bytes, so this walks the string the page handed over with
/// nothing decoded.
#[cfg(topcoat_client)]
fn digits(text: &str) -> i32 {
let bytes = text.as_bytes();
if bytes.is_empty() {
return -1;
}
let mut value = 0;
let mut at = 0;
while at < bytes.len() {
let byte = bytes[at];
if !byte.is_ascii_digit() {
return -1;
}
value = value * 10 + (byte - b'0') as i32;
at += 1;
}
value
}
// What the page lends the island. An `extern "C"` declaration in a crate the
// backend compiles is a call to `__rt.<name>(...)`, and `__rt` is the module the
// import map resolves for this crate, which demo-app serves itself. None of these
// is about Life: a numbered array, an exchange, a counter that goes up. The rules
// are up here.
#[cfg(topcoat_client)]
#[allow(improper_ctypes)]
unsafe extern "C" {
/// True the first time `id` asks and false afterwards.
fn claim(id: f64) -> bool;
/// A board of `len` cells, to write. The array is the page's and is kept, so
/// what this hands back on the next frame is over the cells the last frame
/// wrote.
fn board(id: f64, len: f64) -> &'static mut [u8];
/// The same board, to read. Two declarations because one borrows it mutably
/// and the other does not, and an `extern` name is its JavaScript name.
fn board_ro(id: f64, len: f64) -> &'static [u8];
/// Exchanges what two boards hold. The `buf` moves and the record does not,
/// so a slice taken before the exchange is over the new cells afterwards.
fn board_swap(a: f64, b: f64);
/// Moves `sig` on by one every `ms`, for ever.
fn clock_start(sig: ::view_abi::Sig<f64>, ms: f64);
}
/// A board that plays itself, rendered by the server and taken over by the
/// browser.
#[::view_dom_macro::island]
pub async fn life() -> ::topcoat::Result {
view! {
<div class="island life">
// How many steps have happened. The grid reads it, and the driver
// writes it.
signal generation = 0.0;
// How many times the reader has changed the board by hand. A second
// signal rather than one, because a click is not a generation and
// saying it is would be a lie in the readout.
signal edits = 0.0;
// Whether the clock steps the board. A number and not a bool because
// every signal in this island is one, and one type is one less thing
// for the two halves to disagree about.
signal playing = 1.0;
// Steps a second the reader asked for.
signal speed = 10.0;
// The tick the last step happened at. See the module docs: this is
// what stops the driver re-running itself.
signal stepped = 0.0;
// The clock.
signal tick = 0.0;
<p class="life-readout">
"generation "
<span class="life-generation">$(generation.get())</span>
" · population "
<span class="life-population">
$(tick.drive(playing, speed, stepped, generation))
</span>
</p>
// ONE handler for the whole grid, and each cell carries its own
// number. A handler per cell is the shape this asks for first; it
// works, but it is 640 closures rebuilt on every render. See
// `examples/dom-tests/23_for_row_handlers.rs`, which pins both.
<div
class="life-grid"
@click=$(|e| {
let e: Ev = e;
edits.toggle(e.target_value())
})
>
// `tabindex="-1"` because a board is 640 buttons and a page with
// 640 tab stops in it is a page nobody can tab past. The grid is
// worked by clicking, and the controls below it are not.
for cell in cells(generation.get(), edits.get(), tick) {
<button
class=(cell.class)
type="button"
tabindex="-1"
value=(cell.index)
></button>
}
</div>
<div class="island-controls">
<button
class="island-step"
type="button"
@click=$(|_e| playing.set(1.0 - playing.get()))
>
"play / pause"
</button>
<button class="island-step" type="button" @click=$(|_e| generation.advance())>
"step"
</button>
<button class="island-step" type="button" @click=$(|_e| edits.reset())>
"reset"
</button>
<button class="island-step" type="button" @click=$(|_e| edits.clear())>
"clear"
</button>
<button class="island-step" type="button" @click=$(|_e| edits.scatter(tick.get()))>
"random"
</button>
</div>
<label class="life-speed">
"speed "
<input
class="life-rate"
type="range"
min="1"
max="20"
value="10"
@input=$(|e| {
let e: Ev = e;
speed.tune(e.target_value())
})
>
</label>
</div>
}
}
/// The opening board, as the cells that are alive in it.
///
/// Written down rather than derived, and written down a second time in
/// `smoke/life.mjs`, which asserts the same list against the compiled client's
/// first render. That is the parity check: the server half is measured here, the
/// client half is measured there, and a change to [`initial_alive`] that reached
/// only one of them fails one of the two.
#[cfg(all(test, not(topcoat_client)))]
const OPENING_BOARD: &str = "34,67,97,98,99,272,273,303,304,336";
#[cfg(all(test, not(topcoat_client)))]
mod tests {
use super::*;
/// The cells the server renders alive, as `OPENING_BOARD` spells them.
fn opening() -> String {
cells(0.0, 0.0, &::topcoat::runtime::Signal::new(0.0))
.into_iter()
.filter(|cell| cell.class == "cell on")
.map(|cell| cell.index.to_string())
.collect::<Vec<_>>()
.join(",")
}
#[test]
fn the_server_renders_one_cell_per_position() {
let rendered = cells(0.0, 0.0, &::topcoat::runtime::Signal::new(0.0));
assert_eq!(rendered.len(), CELLS as usize);
for (at, cell) in rendered.iter().enumerate() {
assert_eq!(cell.index, at as i32);
}
}
#[test]
fn the_server_renders_the_opening_board() {
assert_eq!(opening(), OPENING_BOARD);
}
#[test]
fn the_opening_population_is_what_the_driver_renders() {
// The driver hole's first answer on both halves. The client counts the
// board it stamped; this counts the function it stamped it from.
assert_eq!(
initial_population() as usize,
OPENING_BOARD.split(',').count()
);
}
#[test]
fn a_cell_off_the_board_is_dead_rather_than_a_panic() {
// `initial_alive` is handed an index by two callers with different ideas
// of the range, so out of range is an answer rather than a crash.
assert!(!initial_alive(-1));
assert!(!initial_alive(CELLS));
}
}
// rustc_codegen_js — life
import { core$ops$range$impl_2_contains$h169440e1e171c0d1, core$panicking$panic_bounds_check$h0d8d1e9ff46bdeb5, core$panicking$panic_const$panic_const_div_by_zero$h5e2ded061ef157c3, core$panicking$panic_const$panic_const_div_overflow$h89f5776111352364, core$panicking$panic_const$panic_const_rem_by_zero$hed0143a69c96b3da, core$panicking$panic_const$panic_const_rem_overflow$h36134ad66a7e7dcf, core$slice$impl_0_is_empty$h9eaadd529f36fb2f, core$str$impl_0_as_bytes$h402fe2ef908fbf2a, lib$impl_0_target_value$hb940ae051483159d, lib$impl_1_get$h22b2c6516c245f14, lib$impl_1_set$h75b71d3dba9df7c0 } from "./shared.js";
// topcoat-dom
import { createSignal as _$createSignal } from "topcoat-dom";
// topcoat-dom
import { delegateEvents as _$delegateEvents } from "topcoat-dom";
// topcoat-dom
import { getNextElement as _$getNextElement } from "topcoat-dom";
// topcoat-dom
import { insert as _$insert } from "topcoat-dom";
// topcoat-dom
import { setAttribute as _$setAttribute } from "topcoat-dom";
// topcoat-dom
import { template as _$template } from "topcoat-dom";
import * as __rt from "topcoat-island-rt";
const tmpl$h026df491d36add4f = _$template("<div class=\"island life\"><p class=\"life-readout\">generation <span class=\"life-generation\"></span> · population <span class=\"life-population\"></span></p><div class=\"life-grid\"></div><div class=\"island-controls\"><button class=\"island-step\" type=\"button\">play / pause</button><button class=\"island-step\" type=\"button\">step</button><button class=\"island-step\" type=\"button\">reset</button><button class=\"island-step\" type=\"button\">clear</button><button class=\"island-step\" type=\"button\">random</button></div><label class=\"life-speed\">speed <input class=\"life-rate\" type=\"range\" min=\"1\" max=\"20\" value=\"10\"></label></div>");
const tmpl$hf303ebe28ed29889 = _$template("<button type=\"button\" tabindex=\"-1\"></button>");
// life::__island_life
function __island_life() {
let _0, generation, edits, playing, speed, stepped, tick, _10, _13, _16, _19, _22, _25, _28, _31, _34, _37, $t0, $t1, $t2, $t3, $t4, $t5, $t6, $t7, $t8, $t9, $t10, $t11, $t12, $t13, $t14;
generation = _$createSignal(0);
edits = _$createSignal(0);
playing = _$createSignal(1);
speed = _$createSignal(10);
stepped = _$createSignal(0);
tick = _$createSignal(0);
_0 = _$getNextElement(tmpl$h026df491d36add4f);
$t0 = _0.firstChild;
$t1 = $t0.firstChild;
$t2 = $t0.nextSibling;
$t3 = $t1.nextSibling;
$t4 = $t2.nextSibling;
$t5 = $t3.nextSibling;
$t6 = $t4.firstChild;
$t7 = $t4.nextSibling;
$t8 = $t5.nextSibling;
$t9 = $t6.nextSibling;
$t10 = $t7.firstChild;
$t11 = $t9.nextSibling;
$t12 = $t10.nextSibling;
$t13 = $t11.nextSibling;
$t14 = $t13.nextSibling;
_$delegateEvents(["click", "input"]);
_10 = [generation];
_$insert($t3, () => islands_js_expanded$life$__island_life$closure_0$hbd59ade69ee05c33(_10));
_13 = [tick, playing, speed, stepped, generation];
_$insert($t8, () => islands_js_expanded$life$__island_life$closure_1$hf2231d6f1369c66d(_13));
_16 = [edits];
$t2.$$click = ($a0) => islands_js_expanded$life$__island_life$closure_2$h3988398164407c09(_16, $a0);
_19 = [generation, edits, tick];
_$insert($t2, () => islands_js_expanded$life$__island_life$closure_3$hdee15ca421ced76e(_19));
_22 = [playing];
$t6.$$click = ($a0) => islands_js_expanded$life$__island_life$closure_4$h8f2621b8a93047c0(_22, $a0);
_25 = [generation];
$t9.$$click = ($a0) => islands_js_expanded$life$__island_life$closure_5$h92791aae43f432fe(_25, $a0);
_28 = [edits];
$t11.$$click = ($a0) => islands_js_expanded$life$__island_life$closure_6$h7d76397273524e7d(_28, $a0);
_31 = [edits];
$t13.$$click = ($a0) => islands_js_expanded$life$__island_life$closure_7$hd3ddb1c8534d544f(_31, $a0);
_34 = [edits, tick];
$t14.$$click = ($a0) => islands_js_expanded$life$__island_life$closure_8$hacfefd121848dcce(_34, $a0);
_37 = [speed];
$t12.$$input = ($a0) => islands_js_expanded$life$__island_life$closure_9$hd700e89353b1b975(_37, $a0);
return _0;
}
// <I as core::iter::IntoIterator>::into_iter
function core$iter$traits$collect$impl_0_into_iter$h4a618619f58f87a6(self) {
return self;
}
// core::num::<impl i32>::rem_euclid
function core$num$impl_2_rem_euclid$h3772b1046834f466(self, rhs, $loc) {
let _0, r, _6, _9;
if (rhs === 0) {
core$panicking$panic_const$panic_const_rem_by_zero$hed0143a69c96b3da($loc);
}
_6 = self === -2147483648;
if (rhs === -1 && _6) {
core$panicking$panic_const$panic_const_rem_overflow$h36134ad66a7e7dcf($loc);
}
r = self % rhs | 0;
if (r < 0) {
if (rhs < 0) {
_9 = 0 - rhs | 0;
} else {
_9 = rhs;
}
_0 = r + _9 | 0;
} else {
_0 = r;
}
return _0;
}
// core::num::<impl u8>::is_ascii_digit
function core$num$impl_6_is_ascii_digit$h6c25b0a46f2efa01(self) {
let _0;
if (48 <= self.buf[self.off]) {
_0 = self.buf[self.off] <= 57 !== false;
} else {
_0 = false;
}
return _0;
}
// life::__island_life::{closure#0}
function islands_js_expanded$life$__island_life$closure_0$hbd59ade69ee05c33(_1) {
return lib$impl_1_get$h22b2c6516c245f14(_1[0]);
}
// life::__island_life::{closure#1}
function islands_js_expanded$life$__island_life$closure_1$hf2231d6f1369c66d(_1) {
return islands_js_expanded$life$impl_4_drive$h11979c3e2c7dfa89(_1[0], _1[1], _1[2], _1[3], _1[4]);
}
// life::__island_life::{closure#2}
function islands_js_expanded$life$__island_life$closure_2$h3988398164407c09(_1, e) {
let _0, _3;
_3 = _1[0];
_0 = islands_js_expanded$life$impl_4_toggle$hd9fd25a948dd7c6c(_3, lib$impl_0_target_value$hb940ae051483159d(e));
}
// life::__island_life::{closure#3}
function islands_js_expanded$life$__island_life$closure_3$hdee15ca421ced76e(_1) {
let _0, _4, _6, iter, _10, cell, __tc_root_1;
_0 = [];
_4 = lib$impl_1_get$h22b2c6516c245f14(_1[0]);
_6 = lib$impl_1_get$h22b2c6516c245f14(_1[1]);
iter = core$iter$traits$collect$impl_0_into_iter$h4a618619f58f87a6(islands_js_expanded$life$cells$h36ff4003b5a25c83(_4, _6, _1[2]));
for (;;) {
_10 = islands_js_expanded$life$impl_3_next$hcf897dd54bd005fc(iter);
switch (_10.TAG) {
case "None":
return _0;
case "Some":
cell = { index: _10._0.index, _class: _10._0._class };
__tc_root_1 = _$getNextElement(tmpl$hf303ebe28ed29889);
_$setAttribute(__tc_root_1, "class", cell._class);
_$setAttribute(__tc_root_1, "value", cell.index);
_0.push(__tc_root_1);
break;
default:
throw new Error("rustc_codegen_js: entered unreachable code");
}
}
}
// life::__island_life::{closure#4}
function islands_js_expanded$life$__island_life$closure_4$h8f2621b8a93047c0(_1, _e) {
let _0, _3;
_3 = _1[0];
_0 = lib$impl_1_set$h75b71d3dba9df7c0(_3, 1 - lib$impl_1_get$h22b2c6516c245f14(_1[0]));
}
// life::__island_life::{closure#5}
function islands_js_expanded$life$__island_life$closure_5$h92791aae43f432fe(_1, _e) {
let _0;
_0 = islands_js_expanded$life$impl_4_advance$hb5b5cc42f33dffde(_1[0]);
}
// life::__island_life::{closure#6}
function islands_js_expanded$life$__island_life$closure_6$h7d76397273524e7d(_1, _e) {
let _0;
_0 = islands_js_expanded$life$impl_4_reset$h20bc072e8f7d5909(_1[0]);
}
// life::__island_life::{closure#7}
function islands_js_expanded$life$__island_life$closure_7$hd3ddb1c8534d544f(_1, _e) {
let _0;
_0 = islands_js_expanded$life$impl_4_clear$he617227c5f69617b(_1[0]);
}
// life::__island_life::{closure#8}
function islands_js_expanded$life$__island_life$closure_8$hacfefd121848dcce(_1, _e) {
let _0, _3;
_3 = _1[0];
_0 = islands_js_expanded$life$impl_4_scatter$h08220bac4508031f(_3, lib$impl_1_get$h22b2c6516c245f14(_1[1]));
}
// life::__island_life::{closure#9}
function islands_js_expanded$life$__island_life$closure_9$hd700e89353b1b975(_1, e) {
let _0, _3;
_3 = _1[0];
_0 = islands_js_expanded$life$impl_4_tune$h6c0af425e6149147(_3, lib$impl_0_target_value$hb940ae051483159d(e));
}
// life::alive_class
function islands_js_expanded$life$alive_class$h5f440055182067d1(alive) {
let _0;
if (alive) {
_0 = "cell on";
} else {
_0 = "cell";
}
return _0;
}
// life::cell_index
function islands_js_expanded$life$cell_index$h19bc0edd04087922(target) {
let _0, index, _3;
index = islands_js_expanded$life$digits$h33a50e22060cf744(target);
_3 = index < 0;
L0: {
if (!_3) {
if (!(index >= 640)) {
_0 = index;
break L0;
}
}
_0 = -1;
}
return _0;
}
// life::cells
function islands_js_expanded$life$cells$h36ff4003b5a25c83(_generation, _edits, tick) {
islands_js_expanded$life$open$h1b06ffe0b01b44ad(tick);
return { at: 0, board: islands_js_expanded$life$cur$h6142822dbc3c6a6d() };
}
// life::covers
function islands_js_expanded$life$covers$h6a3ffaf163b1c397(shape, dx, dy) {
let _0, at, _5, oy, _10, _11;
at = 0;
for (;;) {
_5 = at < shape.len;
L1: {
if (_5) {
_10 = at;
_11 = shape.len;
if (!(_10 < _11)) {
core$panicking$panic_bounds_check$h0d8d1e9ff46bdeb5(_10, _11, { filename: "demo-app/target/debug/build/demo-app-ddbe189392386330/out/.jsc-build/islands_js_expanded.rs", line: 1138, col: 28 });
}
oy = shape.buf[shape.off + _10][1];
if (shape.buf[shape.off + _10][0] === dx) {
if (oy === dy) {
_0 = true;
break L1;
}
}
at = at + 1 >>> 0;
continue;
} else {
_0 = false;
}
}
return _0;
}
}
// life::cur
function islands_js_expanded$life$cur$h6142822dbc3c6a6d() {
return __rt.board_ro(1, 640);
}
// life::cur_mut
function islands_js_expanded$life$cur_mut$h32b6df912052d94c() {
return __rt.board(1, 640);
}
// life::digits
function islands_js_expanded$life$digits$h33a50e22060cf744(text) {
let _0, bytes, value, at, byte = [], _10, _11;
bytes = core$str$impl_0_as_bytes$h402fe2ef908fbf2a(text);
if (core$slice$impl_0_is_empty$h9eaadd529f36fb2f(bytes)) {
_0 = -1;
} else {
value = 0;
at = 0;
for (;;) {
if (at < bytes.len) {
_10 = at;
_11 = bytes.len;
if (!(_10 < _11)) {
core$panicking$panic_bounds_check$h0d8d1e9ff46bdeb5(_10, _11, { filename: "demo-app/target/debug/build/demo-app-ddbe189392386330/out/.jsc-build/islands_js_expanded.rs", line: 1452, col: 24 });
}
byte[0] = bytes.buf[bytes.off + _10];
if (core$num$impl_6_is_ascii_digit$h6c25b0a46f2efa01({ buf: byte, off: 0 })) {
value = (Math.imul(value, 10) | 0) + (byte[0] - 48 & 255 | 0) | 0;
at = at + 1 >>> 0;
} else {
_0 = -1;
break;
}
} else {
_0 = value;
break;
}
}
}
return _0;
}
// <life::Cells as core::iter::Iterator>::next
function islands_js_expanded$life$impl_3_next$hcf897dd54bd005fc(self) {
let _0, index, _9, _11, _14;
if (self.at >= 640) {
_0 = { TAG: "None" };
} else {
index = self.at;
self.at = self.at + 1 | 0;
_9 = index >>> 0;
_11 = self.board.len;
if (!(_9 < _11)) {
core$panicking$panic_bounds_check$h0d8d1e9ff46bdeb5(_9, _11, { filename: "demo-app/target/debug/build/demo-app-ddbe189392386330/out/.jsc-build/islands_js_expanded.rs", line: 1208, col: 40 });
}
_14 = self.board;
_0 = { TAG: "Some", _0: { index: index, _class: islands_js_expanded$life$alive_class$h5f440055182067d1(_14.buf[_14.off + _9] !== 0) } };
}
return _0;
}
// <lib::Sig<f64> as life::Control>::advance
function islands_js_expanded$life$impl_4_advance$hb5b5cc42f33dffde(self) {
let _3;
_3 = islands_js_expanded$life$cur$h6142822dbc3c6a6d();
islands_js_expanded$life$step$h8685be6bccfe6e62(_3, islands_js_expanded$life$next$he281dc0c3d75ed6d());
__rt.board_swap(1, 2);
lib$impl_1_set$h75b71d3dba9df7c0(self, lib$impl_1_get$h22b2c6516c245f14(self) + 1);
}
// <lib::Sig<f64> as life::Control>::clear
function islands_js_expanded$life$impl_4_clear$he617227c5f69617b(self) {
let board, index, _6, _9;
board = islands_js_expanded$life$cur_mut$h32b6df912052d94c();
index = 0;
for (;;) {
if (index < 640) {
_6 = index >>> 0;
_9 = board.len;
if (!(_6 < _9)) {
core$panicking$panic_bounds_check$h0d8d1e9ff46bdeb5(_6, _9, { filename: "demo-app/target/debug/build/demo-app-ddbe189392386330/out/.jsc-build/islands_js_expanded.rs", line: 1279, col: 35 });
}
board.buf[board.off + _6] = 0;
index = index + 1 | 0;
} else {
lib$impl_1_set$h75b71d3dba9df7c0(self, lib$impl_1_get$h22b2c6516c245f14(self) + 1);
return;
}
}
}
// <lib::Sig<f64> as life::Control>::drive
function islands_js_expanded$life$impl_4_drive$h11979c3e2c7dfa89(self, playing, speed, stepped, generation) {
let _0, now, _8, _15, _16, _20, population, _23;
islands_js_expanded$life$open$h1b06ffe0b01b44ad(self);
now = lib$impl_1_get$h22b2c6516c245f14(self);
_8 = lib$impl_1_get$h22b2c6516c245f14(playing) !== 0;
L0: {
if (_8) {
if (now > 0) {
if (lib$impl_1_get$h22b2c6516c245f14(stepped) !== now) {
_15 = __rt.f2i(now, -2147483648, 2147483647);
_16 = islands_js_expanded$life$stride$h82bc7a8d55f52bd8(lib$impl_1_get$h22b2c6516c245f14(speed));
if (_16 === 0) {
core$panicking$panic_const$panic_const_rem_by_zero$hed0143a69c96b3da({ filename: "demo-app/target/debug/build/demo-app-ddbe189392386330/out/.jsc-build/islands_js_expanded.rs", line: 1253, col: 21 });
}
_20 = _15 === -2147483648;
if (_16 === -1 && _20) {
core$panicking$panic_const$panic_const_rem_overflow$h36134ad66a7e7dcf({ filename: "demo-app/target/debug/build/demo-app-ddbe189392386330/out/.jsc-build/islands_js_expanded.rs", line: 1253, col: 21 });
}
if ((_15 % _16 | 0) === 0) {
_23 = islands_js_expanded$life$cur$h6142822dbc3c6a6d();
population = islands_js_expanded$life$step$h8685be6bccfe6e62(_23, islands_js_expanded$life$next$he281dc0c3d75ed6d());
__rt.board_swap(1, 2);
lib$impl_1_set$h75b71d3dba9df7c0(stepped, now);
lib$impl_1_set$h75b71d3dba9df7c0(generation, lib$impl_1_get$h22b2c6516c245f14(generation) + 1);
_0 = population;
break L0;
}
}
}
}
_0 = islands_js_expanded$life$population_of$h324a67ffba63eaa3(islands_js_expanded$life$cur$h6142822dbc3c6a6d());
}
return _0;
}
// <lib::Sig<f64> as life::Control>::reset
function islands_js_expanded$life$impl_4_reset$h20bc072e8f7d5909(self) {
islands_js_expanded$life$stamp$hd919f5c0ef275b1b();
lib$impl_1_set$h75b71d3dba9df7c0(self, lib$impl_1_get$h22b2c6516c245f14(self) + 1);
}
// <lib::Sig<f64> as life::Control>::scatter
function islands_js_expanded$life$impl_4_scatter$h08220bac4508031f(self, seed) {
let state, board, index, _12, _15, _17, _20;
state = __rt.f2i(seed, 0, 4294967295);
if (state === 0) {
state = 625341585;
}
board = islands_js_expanded$life$cur_mut$h32b6df912052d94c();
index = 0;
for (;;) {
if (index < 640) {
state = islands_js_expanded$life$xorshift$h9c5dcf64c9a21362(state);
_15 = state;
if (100 === 0) {
core$panicking$panic_const$panic_const_rem_by_zero$hed0143a69c96b3da({ filename: "demo-app/target/debug/build/demo-app-ddbe189392386330/out/.jsc-build/islands_js_expanded.rs", line: 1290, col: 24 });
}
if (_15 % 100 >>> 0 < 30) {
_12 = 1;
} else {
_12 = 0;
}
_17 = index >>> 0;
_20 = board.len;
if (!(_17 < _20)) {
core$panicking$panic_bounds_check$h0d8d1e9ff46bdeb5(_17, _20, { filename: "demo-app/target/debug/build/demo-app-ddbe189392386330/out/.jsc-build/islands_js_expanded.rs", line: 1289, col: 17 });
}
board.buf[board.off + _17] = _12;
index = index + 1 | 0;
} else {
lib$impl_1_set$h75b71d3dba9df7c0(self, lib$impl_1_get$h22b2c6516c245f14(self) + 1);
return;
}
}
}
// <lib::Sig<f64> as life::Control>::toggle
function islands_js_expanded$life$impl_4_toggle$hd9fd25a948dd7c6c(self, target) {
let index, board, _6, _9, _11, _13, _15;
index = islands_js_expanded$life$cell_index$h19bc0edd04087922(target);
if (!(index < 0)) {
board = islands_js_expanded$life$cur_mut$h32b6df912052d94c();
_9 = index >>> 0;
_11 = board.len;
if (!(_9 < _11)) {
core$panicking$panic_bounds_check$h0d8d1e9ff46bdeb5(_9, _11, { filename: "demo-app/target/debug/build/demo-app-ddbe189392386330/out/.jsc-build/islands_js_expanded.rs", line: 1272, col: 20 });
}
if (board.buf[board.off + _9] === 0) {
_6 = 1;
} else {
_6 = 0;
}
_13 = index >>> 0;
_15 = board.len;
if (!(_13 < _15)) {
core$panicking$panic_bounds_check$h0d8d1e9ff46bdeb5(_13, _15, { filename: "demo-app/target/debug/build/demo-app-ddbe189392386330/out/.jsc-build/islands_js_expanded.rs", line: 1271, col: 13 });
}
board.buf[board.off + _13] = _6;
lib$impl_1_set$h75b71d3dba9df7c0(self, lib$impl_1_get$h22b2c6516c245f14(self) + 1);
}
}
// <lib::Sig<f64> as life::Control>::tune
function islands_js_expanded$life$impl_4_tune$h6c0af425e6149147(self, target) {
let steps;
steps = islands_js_expanded$life$digits$h33a50e22060cf744(target);
if (!(steps < 0)) {
lib$impl_1_set$h75b71d3dba9df7c0(self, steps);
}
}
// life::initial_alive
function islands_js_expanded$life$initial_alive$h54e72034c64711da(index) {
let _0, x, _8, y, _13;
index = [index];
if (core$ops$range$impl_2_contains$h169440e1e171c0d1({ start: 0, end: 640 }, { buf: index, off: 0 })) {
if (32 === 0) {
core$panicking$panic_const$panic_const_rem_by_zero$hed0143a69c96b3da({ filename: "demo-app/target/debug/build/demo-app-ddbe189392386330/out/.jsc-build/islands_js_expanded.rs", line: 1125, col: 17 });
}
_8 = index[0] === -2147483648;
if (32 === -1 && _8) {
core$panicking$panic_const$panic_const_rem_overflow$h36134ad66a7e7dcf({ filename: "demo-app/target/debug/build/demo-app-ddbe189392386330/out/.jsc-build/islands_js_expanded.rs", line: 1125, col: 17 });
}
x = index[0] % 32 | 0;
if (32 === 0) {
core$panicking$panic_const$panic_const_div_by_zero$h5e2ded061ef157c3({ filename: "demo-app/target/debug/build/demo-app-ddbe189392386330/out/.jsc-build/islands_js_expanded.rs", line: 1126, col: 17 });
}
_13 = index[0] === -2147483648;
if (32 === -1 && _13) {
core$panicking$panic_const$panic_const_div_overflow$h89f5776111352364({ filename: "demo-app/target/debug/build/demo-app-ddbe189392386330/out/.jsc-build/islands_js_expanded.rs", line: 1126, col: 17 });
}
y = index[0] / 32 | 0;
if (islands_js_expanded$life$covers$h6a3ffaf163b1c397({ buf: [[1, 0], [2, 1], [0, 2], [1, 2], [2, 2]], off: 0, len: 5 }, x - [1, 1][0] | 0, y - [1, 1][1] | 0)) {
_0 = true;
} else {
_0 = islands_js_expanded$life$covers$h6a3ffaf163b1c397({ buf: [[1, 0], [2, 0], [0, 1], [1, 1], [1, 2]], off: 0, len: 5 }, x - [15, 8][0] | 0, y - [15, 8][1] | 0);
}
} else {
_0 = false;
}
return _0;
}
// life::living_neighbours
function islands_js_expanded$life$living_neighbours$h2aeef00c12a06c52(board, x, y) {
let alive, at, dy, _13, _14, nx, _22, _25;
alive = 0;
at = 0;
for (;;) {
if (at < 8) {
_13 = [[-1, -1], [0, -1], [1, -1], [-1, 0], [1, 0], [-1, 1], [0, 1], [1, 1]];
_14 = at;
if (!(_14 < 8)) {
core$panicking$panic_bounds_check$h0d8d1e9ff46bdeb5(_14, 8, { filename: "demo-app/target/debug/build/demo-app-ddbe189392386330/out/.jsc-build/islands_js_expanded.rs", line: 1368, col: 28 });
}
at = at + 1 >>> 0;
dy = _13[_14][1];
nx = core$num$impl_2_rem_euclid$h3772b1046834f466(x + _13[_14][0] | 0, 32, { filename: "demo-app/target/debug/build/demo-app-ddbe189392386330/out/.jsc-build/islands_js_expanded.rs", line: 1370, col: 31 });
_22 = ((Math.imul(core$num$impl_2_rem_euclid$h3772b1046834f466(y + dy | 0, 20, { filename: "demo-app/target/debug/build/demo-app-ddbe189392386330/out/.jsc-build/islands_js_expanded.rs", line: 1371, col: 31 }), 32) | 0) + nx | 0) >>> 0;
_25 = board.len;
if (!(_22 < _25)) {
core$panicking$panic_bounds_check$h0d8d1e9ff46bdeb5(_22, _25, { filename: "demo-app/target/debug/build/demo-app-ddbe189392386330/out/.jsc-build/islands_js_expanded.rs", line: 1372, col: 16 });
}
if (board.buf[board.off + _22] !== 0) {
alive = alive + 1 | 0;
}
} else {
return alive;
}
}
}
// life::next
function islands_js_expanded$life$next$he281dc0c3d75ed6d() {
return __rt.board(2, 640);
}
// life::open
function islands_js_expanded$life$open$h1b06ffe0b01b44ad(tick) {
if (__rt.claim(1)) {
islands_js_expanded$life$stamp$hd919f5c0ef275b1b();
islands_js_expanded$life$next$he281dc0c3d75ed6d();
__rt.clock_start(tick, 50);
}
}
// life::population_of
function islands_js_expanded$life$population_of$h324a67ffba63eaa3(board) {
let alive, index, _8, _10;
alive = 0;
index = 0;
for (;;) {
if (index < 640) {
_8 = index >>> 0;
_10 = board.len;
if (!(_8 < _10)) {
core$panicking$panic_bounds_check$h0d8d1e9ff46bdeb5(_8, _10, { filename: "demo-app/target/debug/build/demo-app-ddbe189392386330/out/.jsc-build/islands_js_expanded.rs", line: 1412, col: 16 });
}
if (board.buf[board.off + _8] !== 0) {
alive = alive + 1;
}
index = index + 1 | 0;
} else {
return alive;
}
}
}
// life::stamp
function islands_js_expanded$life$stamp$hd919f5c0ef275b1b() {
let board, index, _5, _8, _11;
board = islands_js_expanded$life$cur_mut$h32b6df912052d94c();
index = 0;
for (;;) {
if (index < 640) {
if (islands_js_expanded$life$initial_alive$h54e72034c64711da(index)) {
_5 = 1;
} else {
_5 = 0;
}
_8 = index >>> 0;
_11 = board.len;
if (!(_8 < _11)) {
core$panicking$panic_bounds_check$h0d8d1e9ff46bdeb5(_8, _11, { filename: "demo-app/target/debug/build/demo-app-ddbe189392386330/out/.jsc-build/islands_js_expanded.rs", line: 1348, col: 13 });
}
board.buf[board.off + _8] = _5;
index = index + 1 | 0;
} else {
return;
}
}
}
// life::step
function islands_js_expanded$life$step$h8685be6bccfe6e62(cur, next) {
let population, y, x, index, alive, _16, _17, _19, _24, _26;
population = 0;
y = 0;
for (;;) {
if (y < 20) {
x = 0;
for (;;) {
if (x < 32) {
index = ((Math.imul(y, 32) | 0) + x | 0) >>> 0;
_19 = cur.len;
if (!(index < _19)) {
core$panicking$panic_bounds_check$h0d8d1e9ff46bdeb5(index, _19, { filename: "demo-app/target/debug/build/demo-app-ddbe189392386330/out/.jsc-build/islands_js_expanded.rs", line: 1392, col: 28 });
}
_17 = cur.buf[cur.off + index] !== 0;
_16 = [_17, islands_js_expanded$life$living_neighbours$h2aeef00c12a06c52(cur, x, y)];
L2: {
L3: {
if (_16[0]) {
if (_16[1] - 2 >>> 0 >= 2) {
break L3;
}
} else if (_16[1] !== 3) {
break L3;
}
alive = 1;
break L2;
}
alive = 0;
}
_26 = next.len;
_24 = alive;
if (!(index < _26)) {
core$panicking$panic_bounds_check$h0d8d1e9ff46bdeb5(index, _26, { filename: "demo-app/target/debug/build/demo-app-ddbe189392386330/out/.jsc-build/islands_js_expanded.rs", line: 1396, col: 17 });
}
next.buf[next.off + index] = _24;
population = population + alive;
x = x + 1 | 0;
} else {
y = y + 1 | 0;
break;
}
}
} else {
return population;
}
}
}
// life::stride
function islands_js_expanded$life$stride$h82bc7a8d55f52bd8(speed) {
let steps, steps$1, _6, _9;
steps = __rt.f2i(speed, -2147483648, 2147483647);
if (steps < 1) {
steps$1 = 1;
} else if (steps > 20) {
steps$1 = 20;
} else {
steps$1 = steps;
}
_6 = steps$1;
if (_6 === 0) {
core$panicking$panic_const$panic_const_div_by_zero$h5e2ded061ef157c3({ filename: "demo-app/target/debug/build/demo-app-ddbe189392386330/out/.jsc-build/islands_js_expanded.rs", line: 1327, col: 9 });
}
_9 = 20 === -2147483648;
if (_6 === -1 && _9) {
core$panicking$panic_const$panic_const_div_overflow$h89f5776111352364({ filename: "demo-app/target/debug/build/demo-app-ddbe189392386330/out/.jsc-build/islands_js_expanded.rs", line: 1327, col: 9 });
}
return 20 / _6 | 0;
}
// life::xorshift
function islands_js_expanded$life$xorshift$h9c5dcf64c9a21362(state) {
let x;
x = state;
x = (x ^ x << 13 >>> 0) >>> 0;
x = (x ^ x >>> 17 >>> 0) >>> 0;
x = (x ^ x << 5 >>> 0) >>> 0;
return x;
}
export { __island_life };
//# sourceMappingURL=life.js.map