brazier/effect
Types
A pending after timer: the id carried by the
{brazier_effect_after, Id} message that erlang:send_after delivers
to the process that performed the effect.
pub type AfterTimer
Values
pub fn after(delay_ms: Int, make_msg: fn() -> msg) -> Effect(msg)
Create a single delayed effect. Dispatches make_msg() once after
delay_ms.
No process is started: the timer is an erlang:send_after into the
runtime’s own mailbox (LiveView’s Process.send_after(self(), ...)),
and make_msg runs in the runtime when it fires. The VM drops the timer
if the runtime stops first.
A negative delay_ms is rejected with an error log. At most
max_after_timers (100) timers can be pending per runtime; one more is
rejected with a warning and [brazier, effect, after, rejected]. Key
it (effect.keyed("toast", effect.after(...))) to replace the pending
timer instead of adding one; effect.cancel cancels it.
Example:
effect.after(3000, fn() { HideNotification })
pub fn background(
callback: fn(fn(msg) -> Nil) -> Nil,
) -> Effect(msg)
Create a background effect that runs in a separate BEAM process.
Unlike from, this doesn’t block the main update loop.
The callback runs in a spawned process and can dispatch messages back.
At most max_background_processes (32) workers can be alive per
runtime; one more is rejected (it never runs) with a warning and
[brazier, effect, background, rejected].
Reference: Reflex rx.event(background=True).
Example:
effect.background(fn(dispatch) {
let data = expensive_database_query()
dispatch(DataLoaded(data))
})
pub fn batch(effects: List(Effect(msg))) -> Effect(msg)
Combine multiple effects into one. All will be executed. No ordering guarantees between effects in the batch.
pub fn cancellable(inner: Effect(msg)) -> #(Effect(msg), Cancel)
Wrap an effect in a generated cancel key and return the cancel token.
pub fn every(
interval_ms: Int,
make_msg: fn() -> msg,
) -> Effect(msg)
Create a periodic timer effect. Dispatches make_msg() every interval_ms.
The timer runs in a separate BEAM process owned by the runtime and stops
when the runtime stops or crashes.
Capped at max_timers (10) concurrent timers per runtime — additional
timers are rejected with a warning and
[brazier, effect, timer, rejected]. An interval_ms below 1 is
rejected with an error log (it would be a busy loop).
Each un-keyed call creates a NEW timer. To replace a timer instead of
adding one, key it: effect.keyed("clock", effect.every(1000, Tick)) —
performing it again stops the previous “clock” timer first, and
effect.cancel (via effect.cancellable) stops it outright.
Reference: Phoenix LiveView Process.send_after in handle_info.
Example:
effect.every(150, fn() { Tick }) // game loop at ~7fps
effect.every(1000, fn() { RefreshStats }) // dashboard update every second
pub fn from(callback: fn(fn(msg) -> Nil) -> Nil) -> Effect(msg)
Create an effect from a callback function.
The callback receives a dispatch function that sends messages
back to the runtime’s update loop.
Example:
effect.from(fn(dispatch) {
// Do some async work...
dispatch(DataLoaded(result))
})
pub fn is_current_key_generation(
key: String,
generation: Int,
) -> Bool
pub fn is_none(effect: Effect(msg)) -> Bool
Check if an effect has any callbacks to execute. An effect that carries stream ops is not none.
pub fn keyed(key: String, inner: Effect(msg)) -> Effect(msg)
Associate an effect with a stable key so stale async completions can be dropped.
Stream ops in inner are not keyed: they are applied when the update
returns, so there is no late completion to drop.
pub fn map(effect: Effect(a), f: fn(a) -> b) -> Effect(b)
Transform the message type of an effect. Useful when composing effects from child components.
pub const max_after_timers: Int
Maximum pending after timers per runtime. A timer counts from the
update that performed it until it fires or is cancelled.
pub const max_background_processes: Int
Maximum live background workers per runtime. Each is two processes
(the worker and its guardian), so a session holds at most 64 of them.
A worker counts until it exits.
pub const max_timers: Int
Maximum concurrent every timers per runtime process.
Prevents runaway timer creation from buggy update handlers.
pub fn none() -> Effect(msg)
No effects to perform. Use when update doesn’t need side effects.
pub fn perform(
effect: Effect(msg),
dispatch: fn(msg) -> Nil,
dispatch_keyed: fn(String, Int, msg) -> Nil,
) -> Nil
Execute all callbacks in the effect with the given dispatch function.
Called by the runtime — not by user code. Started processes and timers
are owned by (and die with) the calling process. Crashes on a stream
op: take them out with take_stream_ops first.
pub fn perform_tracked(
effect: Effect(msg),
dispatch: fn(msg) -> Nil,
dispatch_keyed: fn(String, Int, msg) -> Nil,
) -> List(process.Pid)
Execute callbacks and return the pids of the processes they started
(background workers and every timers; an after timer is not a
process). The calling process also tracks everything it owns, bounded
by the limits, and stop_owned stops it all.