A live board: one process that owns the board's shared state and its feeds, and one process per cell under it (spec/15 §8, D-146).
The board process owns the window (in UTC), the zone, the variables, the sync group
and the board's single now, read once per turn from clock:. It holds the
Visualize.Feed.Monitor over the host's feeds (spec/08 §11.4, D-140): each turn it
resolves the window at now, ticks the monitor, and hands each cell its slots'
deliveries, schemas and statuses. Pushes reach the monitor through this process's
mailbox. Setting a window (window/2) re-asks every feed.
The board also holds what its cells share when a person acts on one of them (spec/15
§13, D-151): a brushed window (brush/2), applied once rapid brushes settle and undone
by reset_window/1, and the one selection (select/2), its field read through the
board's links and sent to every cell, which dims the data that do not hold it.
pick/4 reads the row behind a cell's keyboard point from that cell's process.
Each cell is a Visualize.Board.Cell: heap-limited, :temporary under a
DynamicSupervisor this process starts and is the parent of, and monitored here. A
cell that dies is shown {:crashed, reason} to every subscriber at once and started
again after a backoff — 100 ms, doubling to 10 s, reset after 30 s alive — so a design
that dies on every frame never takes a supervisor's restart intensity, its siblings or
the board with it.
Starting
children = [{Visualize.Board.Supervisor, name: MyApp.Boards}]
{:ok, board} =
Visualize.Board.Server.start(MyApp.Boards,
board: board, store: store, binding: binding, feeds: feeds)
{:ok, info} = Visualize.Board.Server.subscribe(board)
def handle_info({:visualize_board, _board, {:cell, name, view}}, socket), do: …A board that does not validate given its store, or whose binding names a feed feeds:
does not hold, is not started: {:error, {:board, errors}} or {:error, {:feeds, names}}, before any process runs.
Options
:board- the%Visualize.Board{}(required):store- what resolution reads, spec/15 §3.2 (required):binding- the connector's%Visualize.Board.Binding{}(default empty):feeds- the feeds by the names the binding uses (default%{}):theme- the theme the cells and their states are drawn in (defaultVisualize.Theme.default/0):system- the unit system every cell's measures are shown in,:metric,:us,:ukor:as_posted(default); a viewer's preference, so a host serving several runs a board per system (spec/14 §4.4, D-156):width- the container width in pixels until aresize/2(default1200):tick- the data rate, milliseconds between turns;nilturns only ontick/1(default1000):budget- the point budget asked of every feed (defaultnil):max_heap- each cell's heap limit in words (default25_000_000):clock- the board's now, a function of no argument answering a UTCDateTime(defaultDateTime.utc_now/0):limits- the limits the components' designs are read under:boards- the boards the cells' drill targets may name, asVisualize.Board.validate/3takes them (default%{}):debounce- milliseconds a brushed window waits for the next (default250);0applies each at once:name- a registered name
Summary
Functions
Returns a specification to start this module under a supervisor.
The board's present state (spec/15 §8.2).
The row behind a cell's keyboard point, {series, index} (spec/10 §17.2), and its
identity (spec/15 §13.2): {:ok, %{row: row, identity: {field, value} | nil}}, or
:error for a cell that is not running or a point it does not have.
The window the board was started with, set as window/2 sets it (spec/15 §13.1).
The container width set and every cell's box recomputed (spec/15 §1.6, §8.2); :ok.
The board's selection (spec/15 §13.2): {field, value}, its field read through the
board's links, or nil to clear it. Every cell is sent it, and every subscriber the
board's info. :ok.
The board frozen as it stands (spec/15 §12.2): a Visualize.Board.Snapshot of the
board, its now and its resolved window, the width, the theme, and every cell's prepared
component, variables and the slots the board last handed it — or the reason a cell that
has crashed died. It holds no pid and no feed: Visualize.Board.Export draws it, and it
reopens anywhere after the board has stopped.
A board started under a DynamicSupervisor (spec/15 §8.1): {:ok, pid}, or
{:error, {:board, errors}} or {:error, {:feeds, names}} for a board that cannot run.
A board started linked to the caller, for a host's own supervisor; as start/2 answers.
The board and its cells stopped; :ok.
As subscribe/2 for the caller.
pid subscribed to the board's messages (spec/15 §8.2): {:ok, info}, and every cell
sends it its present view. Every message is {:visualize_board, board, message}, with
message {:board, info} or {:cell, name, view}.
One turn of the loop now (spec/15 §8.2); :ok once it is done.
A subscriber removed; :ok.
Board variables merged over the board's, and every cell prepared again (spec/15 §5,
The board's window set — nil, %{last: seconds} or %{from: t, to: t} in UTC — and
every feed re-asked (spec/15 §8.2): :ok, or {:error, errors} for a window the board
refuses (spec/15 §3.1). The turn runs at once.
Types
@type info() :: %{ board: Visualize.Board.t(), window: {DateTime.t(), DateTime.t()} | nil, zone: String.t(), now: DateTime.t() | nil, width: number(), sync: String.t() | nil, selection: {atom(), term()} | nil, moved: boolean(), cells: %{ required(atom()) => %{ pid: pid() | nil, state: Visualize.Board.State.t(), restarts: non_neg_integer() } } }
Functions
@spec brush(GenServer.server(), Visualize.Board.window()) :: :ok | {:error, [Visualize.Board.error(), ...]}
A brushed window (spec/15 §13.1): validated at once — :ok, or {:error, errors} as
window/2 answers — and set as window/2 sets it once debounce: milliseconds pass
with no later brush, so rapid brushes re-ask every feed once, for the last.
Returns a specification to start this module under a supervisor.
See Supervisor.
@spec info(GenServer.server()) :: info()
The board's present state (spec/15 §8.2).
@spec pick(GenServer.server(), atom(), non_neg_integer(), non_neg_integer()) :: {:ok, %{row: map(), identity: {atom(), term()} | nil}} | :error
The row behind a cell's keyboard point, {series, index} (spec/10 §17.2), and its
identity (spec/15 §13.2): {:ok, %{row: row, identity: {field, value} | nil}}, or
:error for a cell that is not running or a point it does not have.
The board answers the cell's process and the caller asks it, so a pick never holds the
board; a cell that dies meanwhile answers :error.
@spec reset_window(GenServer.server()) :: :ok
The window the board was started with, set as window/2 sets it (spec/15 §13.1).
@spec resize(GenServer.server(), number()) :: :ok
The container width set and every cell's box recomputed (spec/15 §1.6, §8.2); :ok.
@spec select(GenServer.server(), {atom(), term()} | nil) :: :ok
The board's selection (spec/15 §13.2): {field, value}, its field read through the
board's links, or nil to clear it. Every cell is sent it, and every subscriber the
board's info. :ok.
@spec snapshot(GenServer.server()) :: Visualize.Board.Snapshot.t()
The board frozen as it stands (spec/15 §12.2): a Visualize.Board.Snapshot of the
board, its now and its resolved window, the width, the theme, and every cell's prepared
component, variables and the slots the board last handed it — or the reason a cell that
has crashed died. It holds no pid and no feed: Visualize.Board.Export draws it, and it
reopens anywhere after the board has stopped.
@spec start(Supervisor.supervisor(), keyword()) :: DynamicSupervisor.on_start_child()
A board started under a DynamicSupervisor (spec/15 §8.1): {:ok, pid}, or
{:error, {:board, errors}} or {:error, {:feeds, names}} for a board that cannot run.
@spec start_link(keyword()) :: GenServer.on_start()
A board started linked to the caller, for a host's own supervisor; as start/2 answers.
@spec stop(GenServer.server()) :: :ok
The board and its cells stopped; :ok.
@spec subscribe(GenServer.server()) :: {:ok, info()}
As subscribe/2 for the caller.
@spec subscribe(GenServer.server(), pid()) :: {:ok, info()}
pid subscribed to the board's messages (spec/15 §8.2): {:ok, info}, and every cell
sends it its present view. Every message is {:visualize_board, board, message}, with
message {:board, info} or {:cell, name, view}.
@spec tick(GenServer.server()) :: :ok
One turn of the loop now (spec/15 §8.2); :ok once it is done.
@spec unsubscribe(GenServer.server(), pid()) :: :ok
A subscriber removed; :ok.
@spec vars(GenServer.server(), map()) :: :ok | {:error, [Visualize.Board.error(), ...]}
Board variables merged over the board's, and every cell prepared again (spec/15 §5,
§8.2): :ok, or {:error, errors}.
@spec window(GenServer.server(), Visualize.Board.window()) :: :ok | {:error, [Visualize.Board.error(), ...]}
The board's window set — nil, %{last: seconds} or %{from: t, to: t} in UTC — and
every feed re-asked (spec/15 §8.2): :ok, or {:error, errors} for a window the board
refuses (spec/15 §3.1). The turn runs at once.