Runs a slow thing off the UI's process and sends the answer back to a view.
Atui.Runtime is a single process: it reads the keyboard, runs the view
callbacks and paints the frame, one after another. Anything done inside a
callback is therefore done instead of drawing, and a callback that waits on
the network is a UI that has stopped — the keyboard included. A round trip
that takes a fifth of a second is felt; one to a host that has gone away
takes as long as its timeout, and the person at the keyboard cannot even
quit.
So the work happens in a process of its own and the answer arrives as an ordinary event. Two details make that safe to do from a view:
- The event is addressed to the view's module rather than to whichever view has focus, so an answer that lands while a popup is open still reaches the view that asked for it underneath.
- The process cannot crash. Everything the function might raise, exit or
throw is caught and handed back as
{:error, message}— an unhandled exit would print a crash report onto a terminal the UI has drawn a frame on, which is a worse failure than whatever caused it.
Using it
Start it in the callback that decides something is needed, and handle the
answer in Atui.View.handle_event/2:
def mount(opts) do
{:ok, load(%{rows: [], loading?: true, error: nil})}
end
def handle_key({[:ctrl], "r"}, state), do: {:ok, load(state)}
def handle_event({:rows, {:ok, rows}}, state) do
{:ok, %{state | rows: rows, loading?: false, error: nil}}
end
def handle_event({:rows, {:error, message}}, state) do
{:ok, %{state | loading?: false, error: message}}
end
defp load(state) do
Atui.Fetch.start(__MODULE__, :rows, fn -> Repo.slow_query() end)
%{state | loading?: true}
endA flag in the state is what makes the first frame draw before the answer is in, and what stops a second fetch being started while one is in flight.
Summary
Functions
Runs fun in another process and sends {tag, result} to module's view.
Functions
Runs fun in another process and sends {tag, result} to module's view.
Call it from inside a view callback: it addresses the runtime the view is running in, which is the process making the call.
tag is how a view tells its fetches apart, and result is whatever fun
answered — or {:error, message} if it could not answer at all.