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
Easing function types for animations and tweens.
pub type Easing {
Linear
EaseInQuad
EaseOutQuad
EaseInOutQuad
EaseInCubic
EaseOutCubic
EaseInOutCubic
}
Constructors
-
Linear
-
EaseInQuad
-
EaseOutQuad
-
EaseInOutQuad
-
EaseInCubic
-
EaseOutCubic
-
EaseInOutCubic
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 cancel_interval(id: Int) -> Effect(msg)
Cancel a recurring interval by its ID.
Pass the interval ID that was dispatched via on_created
when you created the interval.
Example
case model.spawn_interval {
option.Some(id) -> effect.cancel_interval(id)
option.None -> effect.none()
}
pub fn clipboard_read(
on_success on_success: fn(String) -> msg,
on_error on_error: msg,
) -> Effect(msg)
Read text from the system clipboard.
Must be called in response to user interaction due to browser security.
Example
type Msg {
PasteClicked
ClipboardData(String)
PasteFailed
}
fn update(model, msg, ctx) {
case msg {
PasteClicked -> #(
model,
effect.clipboard_read(
on_success: ClipboardData,
on_error: PasteFailed,
),
)
ClipboardData(text) -> {
// Process clipboard text
#(model, effect.none())
}
_ -> #(model, effect.none())
}
}
pub fn clipboard_write(
text text: String,
on_success on_success: msg,
on_error on_error: msg,
) -> Effect(msg)
Write text to the system clipboard.
Must be called in response to user interaction due to browser security.
Example
type Msg {
CopyScoreClicked
CopiedToClipboard
CopyFailed
}
fn update(model, msg, ctx) {
case msg {
CopyScoreClicked -> #(
model,
effect.clipboard_write(
text: "High Score: " <> int.to_string(model.high_score),
on_success: CopiedToClipboard,
on_error: CopyFailed,
),
)
_ -> #(model, effect.none())
}
}
pub fn delay(ms milliseconds: Int, msg msg: msg) -> Effect(msg)
Delay dispatching a message by a specified duration.
Unlike tick
, which waits for the next animation frame, delay
waits
for a specific number of milliseconds using setTimeout
.
Example
type Msg {
PlayerHit
ShowDamageEffect
HideDamageEffect
}
fn update(model, msg, ctx) {
case msg {
PlayerHit -> #(
Model(..model, health: model.health - 10),
effect.batch([
effect.from(fn(_) { show_damage_animation() }),
effect.delay(500, HideDamageEffect), // Hide after 500ms
]),
)
HideDamageEffect -> #(model, effect.none())
_ -> #(model, effect.none())
}
}
pub fn exit_fullscreen() -> Effect(msg)
Exit fullscreen mode.
Example
effect.exit_fullscreen()
pub fn exit_pointer_lock() -> Effect(msg)
Exit pointer lock mode.
Example
effect.exit_pointer_lock()
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 gamepad_vibrate(
gamepad gamepad: Int,
intensity intensity: Float,
duration_ms duration_ms: Int,
) -> Effect(msg)
Trigger haptic feedback on a gamepad.
Intensity ranges from 0.0 (no vibration) to 1.0 (maximum vibration). Duration is in milliseconds.
Example
type Msg {
ExplosionNearPlayer
}
fn update(model, msg, ctx) {
case msg {
ExplosionNearPlayer -> #(
model,
effect.gamepad_vibrate(
gamepad: 0,
intensity: 0.7,
duration_ms: 500,
),
)
_ -> #(model, effect.none())
}
}
pub fn interval(
ms milliseconds: Int,
msg msg: msg,
on_created on_created: fn(Int) -> msg,
) -> Effect(msg)
Create a recurring interval that dispatches a message periodically.
Immediately dispatches on_created
with the interval ID, which you should
store in your model. Use this ID with cancel_interval
to stop it later.
No global state - the interval ID is managed by JavaScript’s setInterval
and you store it in your model.
Example
type Model {
Model(spawn_interval: option.Option(Int))
}
type Msg {
StartSpawning
IntervalCreated(Int)
SpawnEnemy
StopSpawning
}
fn update(model, msg, ctx) {
case msg {
StartSpawning -> #(
model,
effect.interval(
ms: 2000,
msg: SpawnEnemy,
on_created: IntervalCreated,
),
)
IntervalCreated(id) -> #(
Model(..model, spawn_interval: option.Some(id)),
effect.none(),
)
SpawnEnemy -> #(spawn_enemy(model), effect.none())
StopSpawning ->
case model.spawn_interval {
option.Some(id) -> #(
Model(..model, spawn_interval: option.None),
effect.cancel_interval(id),
)
option.None -> #(model, effect.none())
}
}
}
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 request_fullscreen(
on_success on_success: msg,
on_error on_error: msg,
) -> Effect(msg)
Request fullscreen mode for the game canvas.
This must be called in response to a user interaction (click, key press, etc.) due to browser security restrictions.
Example
type Msg {
FullscreenButtonClicked
FullscreenEntered
FullscreenFailed
}
fn update(model, msg, ctx) {
case msg {
FullscreenButtonClicked -> #(
model,
effect.request_fullscreen(
on_success: FullscreenEntered,
on_error: FullscreenFailed,
),
)
_ -> #(model, effect.none())
}
}
pub fn request_pointer_lock(
on_success on_success: msg,
on_error on_error: msg,
) -> Effect(msg)
Request pointer lock for the game canvas.
This hides the cursor and provides unlimited mouse movement, commonly used in first-person games. Must be called in response to user interaction.
Example
type Msg {
StartFPSMode
PointerLocked
PointerLockFailed
}
fn update(model, msg, ctx) {
case msg {
StartFPSMode -> #(
model,
effect.request_pointer_lock(
on_success: PointerLocked,
on_error: PointerLockFailed,
),
)
_ -> #(model, effect.none())
}
}
pub fn set_background(
background: background.Background,
) -> Effect(msg)
Change the scene background dynamically.
Allows you to switch between color, texture, or cube texture backgrounds at runtime from your update function.
Example
import tiramisu
import tiramisu/background
import tiramisu/effect
type Msg {
Tick
ChangeToNight
ChangeToDawn
}
fn update(model, msg, ctx) {
case msg {
ChangeToNight -> #(
model,
effect.set_background(background.Color(0x0a0a1e)),
)
ChangeToDawn -> #(
model,
effect.set_background(background.Texture("assets/dawn-sky.jpg")),
)
Tick -> #(model, effect.tick(Tick))
}
}
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
)
}
}
pub fn tween(
from start: Float,
to end: Float,
duration_ms duration_ms: Int,
easing easing: Easing,
on_update on_update: fn(Float) -> msg,
on_complete on_complete: msg,
) -> Effect(msg)
Animate a value from start to end over a duration with easing.
Dispatches on_update
messages with interpolated values each frame,
and on_complete
when the animation finishes.
Example
type Msg {
FadeIn
UpdateOpacity(Float)
FadeComplete
}
fn update(model, msg, ctx) {
case msg {
FadeIn -> #(
model,
effect.tween(
from: 0.0,
to: 1.0,
duration_ms: 1000,
easing: effect.EaseInOutQuad,
on_update: UpdateOpacity,
on_complete: FadeComplete,
),
)
UpdateOpacity(opacity) -> #(
Model(..model, opacity: opacity),
effect.none(),
)
FadeComplete -> #(model, effect.none())
_ -> #(model, effect.none())
}
}
pub fn vibrate(pattern: List(Int)) -> Effect(msg)
Trigger haptic feedback on mobile devices.
The pattern is a list of vibration durations in milliseconds.
For example, [200, 100, 200]
vibrates for 200ms, pauses 100ms, then vibrates 200ms.
Example
type Msg {
PlayerHit
}
fn update(model, msg, ctx) {
case msg {
PlayerHit -> #(
Model(..model, health: model.health - 10),
effect.vibrate([100, 50, 100]), // Quick buzz pattern
)
_ -> #(model, effect.none())
}
}