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

Opaque effect type that can dispatch messages back to the application.

Effects are data descriptions of side effects to perform. The runtime executes them after your update function returns.

pub opaque type Effect(msg)

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())
  }
}
Search Document