Runs slow editor work off the MingaEditor GenServer's input critical path,
serializing the work per lane so a resource that tolerates only one in-flight
operation at a time (e.g. the git index) never has two operations racing.
The single editor process owns input dispatch, GUI action handling, state
mutation, and pre-render housekeeping. If an action runs slow work (git
commands, filesystem walks) inline, later keypresses, clicks, and renderer
writebacks queue behind it. run/3 keeps that work off the critical path:
- The caller does a cheap state transition (e.g. set a "Discarding…" status).
run/3starts the work in aTaskif the lane is idle, or appends it to the lane's FIFO queue if a previous op is still running.- The editor keeps handling input while the Task runs.
- When the Task finishes it sends
{:async_action_result, lane, token, result}back to the editor, which applies it (whencurrent?/3) and thenadvance/2s the lane: the next queued op starts, or the lane goes idle.
A lane identifies a serialized resource. Operations on one lane run strictly
one at a time, in the order they were requested. This is the load-bearing
invariant for mutating resources: two git add/git reset calls can never
fight over .git/index.lock, because the second only starts once the first has
finished and its result has been applied. Latest-wins (drop the older op) would
be wrong here — the older op's mutation already ran; serialization runs every
requested op exactly once, in order.
safely/1 captures a raise/exit/throw in the work function as {:error, reason} so a failing action can never crash the editor; the lane still
advances afterwards, so one bad op cannot wedge the queue.
Summary
Types
Identifies the serialized resource a piece of async work feeds back into.
Message sent to the editor when async work completes. result is whatever
work_fun returned, or {:error, reason} if it raised, exited, or threw.
A zero-arity function run in a Task; its return value becomes the result.
Functions
Advances lane after its in-flight result has been applied: starts the next
queued operation, or marks the lane idle when the queue is empty. Call this
exactly once per applied result so the serial chain keeps draining.
Returns whether token is the operation currently in flight on lane.
Schedules work_fun on lane, returning the updated state immediately so the
caller never blocks on the work.
Types
@type lane() :: atom()
Identifies the serialized resource a piece of async work feeds back into.
Message sent to the editor when async work completes. result is whatever
work_fun returned, or {:error, reason} if it raised, exited, or threw.
@type work() :: (-> term())
A zero-arity function run in a Task; its return value becomes the result.
Functions
@spec advance(MingaEditor.State.t(), lane()) :: MingaEditor.State.t()
Advances lane after its in-flight result has been applied: starts the next
queued operation, or marks the lane idle when the queue is empty. Call this
exactly once per applied result so the serial chain keeps draining.
@spec current?(MingaEditor.State.t(), lane(), reference()) :: boolean()
Returns whether token is the operation currently in flight on lane.
@spec run(MingaEditor.State.t(), lane(), work()) :: MingaEditor.State.t()
Schedules work_fun on lane, returning the updated state immediately so the
caller never blocks on the work.
If the lane is idle the work starts now; if an operation is already in flight
the work is appended to the lane's FIFO queue and starts when the in-flight op
(and anything ahead of it) completes. work_fun is run in the Task and must
not touch editor state. Its return value becomes the result in the
{:async_action_result, lane, token, result} message; a raise/exit/throw is
captured as {:error, reason}.