Three islands that are programs

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

Life

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.

Falling sand (Click to use)

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.

Minesweeper

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 same island, twice

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.

island/life.rs, beside the JavaScript it compiles to

Rust

//! 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));
    }
}

JavaScript

// 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