StateMachine

A generic, type-safe state machine library for Gleam with easing function support and transition weights.

Features

Installation

gleam add statemachine

Quick Start

import statemachine
import gleam/option.{None}
import gleam/time/duration

// Define your state type
type PlayerState {
  Idle
  Walking
  Running
}

// Define your context
type GameContext {
  GameContext(velocity: Float)
}

// Create state machine
let machine =
  statemachine.new(initial_state: Idle)
  |> statemachine.with_state(state: Walking)
  |> statemachine.with_state(state: Running)
  |> statemachine.with_transition(
    from: Idle,
    to: Walking,
    condition: statemachine.Custom(fn(ctx: GameContext) { ctx.velocity >. 0.1 }),
    blend_duration: duration.milliseconds(200),
    easing: None,
    weight: 0,
  )
  |> statemachine.with_transition(
    from: Walking,
    to: Running,
    condition: statemachine.Custom(fn(ctx: GameContext) { ctx.velocity >. 5.0 }),
    blend_duration: duration.milliseconds(300),
    easing: None,
    weight: 0,
  )

// Update in game loop
fn update(model: Model, delta_time: duration.Duration) {
  let ctx = GameContext(velocity: model.velocity)
  let #(new_machine, transitioned) = statemachine.update(machine, ctx, delta_time)
  
  case statemachine.state_data(new_machine) {
    statemachine.Single(state) -> // Use single state
    statemachine.BlendingData(from, to, factor) -> // Interpolate
  }
}

Using with easings_gleam

The library works seamlessly with easings_gleam for beautiful transitions:

import statemachine
import easings
import gleam/option.{Some}
import gleam/time/duration

statemachine.with_transition(
  from: Idle,
  to: Running,
  condition: statemachine.Always,
  blend_duration: duration.milliseconds(500),
  easing: Some(easings.cubic_in_out),
  weight: 0,
)

Transition Conditions

Three types of conditions control when transitions occur:

Always

Transition immediately:

import gleam/time/duration

statemachine.with_transition(
  from: Idle,
  to: Jump,
  condition: statemachine.Always,
  blend_duration: duration.milliseconds(100),
  easing: None,
  weight: 0,
)

AfterDuration

Transition after spending time in the current state:

import gleam/time/duration
import easings
import gleam/option.{Some}

// Automatically transition after 5 seconds
statemachine.with_transition(
  from: Idle,
  to: Sleeping,
  condition: statemachine.AfterDuration(duration.seconds(5)),
  blend_duration: duration.seconds(1),
  easing: Some(easings.ease_in_out_sine),
  weight: 0,
)

Custom

Transition based on custom game/app context:

// Velocity-based
statemachine.Custom(fn(ctx) { ctx.velocity >. 5.0 })

// Input-based
statemachine.Custom(fn(ctx) { ctx.jump_pressed && ctx.is_grounded })

Transition Weights

When multiple transitions from the same state have their conditions met, the transition with the highest weight is chosen:

import statemachine
import gleam/time/duration

statemachine.new(initial_state: Idle)
|> statemachine.with_state(state: Idle)
|> statemachine.with_state(state: Walking)
|> statemachine.with_state(state: Running)
// Lower priority transition
|> statemachine.with_transition(
  from: Idle,
  to: Walking,
  condition: statemachine.Always,
  blend_duration: duration.milliseconds(200),
  easing: None,
  weight: 5,
)
// Higher priority transition - this one will be chosen!
|> statemachine.with_transition(
  from: Idle,
  to: Running,
  condition: statemachine.Always,
  blend_duration: duration.milliseconds(200),
  easing: None,
  weight: 10,
)

This is useful for creating layered logic where more specific conditions take precedence.

Manual Transitions

Force transitions programmatically (useful for events like damage, death, cutscenes):

import easings
import gleam/option.{None, Some}
import gleam/time/duration

// Force transition with custom blend and easing
let machine = statemachine.transition_to(
  machine,
  HitReaction,
  blend_duration: Some(duration.milliseconds(100)),
  easing: Some(easings.ease_out_back),
)

// Force transition with defaults
let machine = statemachine.transition_to(
  machine,
  Dead,
  blend_duration: None,
  easing: None,
)

API Overview

Building State Machines

Runtime

Querying

Types

Philosophy

StateMachine is designed to be:

Use Cases

License

MIT

Contributing

Contributions welcome! This library was extracted from Tiramisu to benefit the broader Gleam ecosystem.

Search Document