tiramisu/effect
Effect system for managing side effects in Tiramisu.
Effects represent side effects as immutable data, following The Elm Architecture.
Your update function returns effects that the runtime executes for you.
Quick Example
import tiramisu/effect
type Msg {
Tick
PlayerMoved(Vec3(Float))
}
fn update(model: Model, msg: Msg, ctx: Context) {
case msg {
Tick -> #(
update_physics(model),
effect.batch([
effect.tick(Tick), // Request next frame
effect.from(fn(dispatch) {
// Custom side effect
log_position(model.player_pos)
dispatch(PlayerMoved(model.player_pos))
}),
]),
)
PlayerMoved(_) -> #(model, effect.none())
}
}
Types
Values
pub fn batch(effects: List(Effect(msg))) -> Effect(msg)
Batch multiple effects to run them together.
All effects execute in order during the same frame.
Example
effect.batch([
effect.tick(NextFrame),
play_sound_effect("jump.wav"),
update_scoreboard(score),
])
pub fn from(effect: fn(fn(msg) -> Nil) -> Nil) -> Effect(msg)
Create a custom effect from a function.
The function receives a dispatch callback to send messages back to your update function.
Example
effect.from(fn(dispatch) {
log("Player score: " <> int.to_string(score))
dispatch(ScoreLogged)
})
pub fn from_promise(p: promise.Promise(msg)) -> Effect(msg)
Create an effect from a JavaScript Promise.
When the promise resolves, it dispatches the resulting message.
Example
let fetch_promise = fetch_data()
effect.from_promise(promise.map(fetch_promise, DataLoaded))
pub fn map(effect: Effect(a), f: fn(a) -> b) -> Effect(b)
Map effect messages to a different type.
Useful when composing effects from subcomponents.
Example
let player_effect = player.update(player_model, player_msg)
effect.map(player_effect, PlayerMsg)
pub fn none() -> Effect(msg)
Create an effect that performs no side effects.
Use when you want to update state without triggering any effects.
Example
fn update(model, msg, ctx) {
case msg {
Idle -> #(model, effect.none())
}
}
pub fn tick(msg: msg) -> Effect(msg)
Request the next animation frame and dispatch a message.
This is the primary way to create frame-based game loops. Call this in your
update function to receive a message on the next frame.
Example
type Msg {
Tick
}
fn update(model, msg, ctx) {
case msg {
Tick -> #(
Model(..model, time: model.time +. ctx.delta_time),
effect.tick(Tick), // Request next frame
)
}
}