Skip to content

huhlig/statechart-rs

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Statechart Library

License Actions Status Crates.io Documentation

A flexible and reliable statechart library for Rust, implementing hierarchical state machines with support for parallel regions, history states, guards, and actions.

Features

  • 🎯 Hierarchical States: Organize states in a tree structure with parent-child relationships
  • 🔀 Parallel Regions: Run multiple orthogonal state machines concurrently
  • 📜 History States: Remember and restore previous state configurations (shallow and deep)
  • 🛡️ Guards: Conditional transitions based on context
  • Actions: Execute code on state entry, exit, and during transitions
  • 🏗️ Builder API: Fluent, type-safe API for constructing state machines at runtime
  • 🎨 Derive Macros: Declarative state machine definitions using Rust attributes
  • 🔍 Validation: Comprehensive validation of state machine configurations
  • 📊 Visualization: Export state machines to DOT/Graphviz format
  • 🧪 Well-Tested: Extensive unit and integration tests
  • 📚 Well-Documented: Comprehensive API documentation and guides

Quick Start

Add this to your Cargo.toml:

[dependencies]
statechart = "0.1"

Simple Example: Turnstile

use statechart::prelude::*;

#[derive(Debug, Clone, PartialEq, Eq, Hash)]
enum State {
    Locked,
    Unlocked,
}

#[derive(Debug, Clone, PartialEq, Eq, Hash)]
enum Event {
    Coin,
    Push,
}

#[derive(Debug)]
struct Context {
    coins: u32,
    passages: u32,
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Build the state machine
    let definition = StateMachineBuilder::<State, Event, Context>::new()
        .initial_state(State::Locked)
        .add_simple_state(State::Locked)
        .add_simple_state(State::Unlocked)
        .add_transition(
            State::Locked,
            State::Unlocked,
            Event::Coin,
            None,
            vec![Box::new(|ctx| {
                ctx.coins += 1;
                println!("Coin accepted. Total: {}", ctx.coins);
                Ok(())
            })],
        )
        .add_transition(
            State::Unlocked,
            State::Locked,
            Event::Push,
            None,
            vec![Box::new(|ctx| {
                ctx.passages += 1;
                println!("Person passed. Total: {}", ctx.passages);
                Ok(())
            })],
        )
        .build()?;
    
    // Create and run the state machine
    let context = Context { coins: 0, passages: 0 };
    let mut machine = StateMachine::new(definition, context)?;
    
    // Process events
    machine.process_event(Event::new(Event::Coin))?;
    assert_eq!(machine.current_state(), &State::Unlocked);
    
    machine.process_event(Event::new(Event::Push))?;
    assert_eq!(machine.current_state(), &State::Locked);
    
    Ok(())
}

Hierarchical States Example

use statechart::prelude::*;

#[derive(Debug, Clone, PartialEq, Eq, Hash)]
enum PlayerState {
    Stopped,
    Playing,
    PlayingNormal,
    PlayingFastForward,
    Paused,
}

#[derive(Debug, Clone, PartialEq, Eq, Hash)]
enum PlayerEvent {
    Play,
    Pause,
    Stop,
    FastForward,
    Normal,
}

// Create composite "Playing" state
let playing_state = StateBuilder::<PlayerState, PlayerEvent, ()>::new(PlayerState::Playing)
    .composite()
    .initial(PlayerState::PlayingNormal)
    .build();

let definition = StateMachineBuilder::<PlayerState, PlayerEvent, ()>::new()
    .initial_state(PlayerState::Stopped)
    .add_simple_state(PlayerState::Stopped)
    .add_state(playing_state)
    .add_simple_state(PlayerState::PlayingNormal)
    .add_simple_state(PlayerState::PlayingFastForward)
    .add_simple_state(PlayerState::Paused)
    
    // Top-level transitions
    .add_transition(PlayerState::Stopped, PlayerState::Playing, PlayerEvent::Play, None, vec![])
    .add_transition(PlayerState::Playing, PlayerState::Stopped, PlayerEvent::Stop, None, vec![])
    .add_transition(PlayerState::Playing, PlayerState::Paused, PlayerEvent::Pause, None, vec![])
    .add_transition(PlayerState::Paused, PlayerState::Playing, PlayerEvent::Play, None, vec![])
    
    // Transitions within Playing state
    .add_transition(PlayerState::PlayingNormal, PlayerState::PlayingFastForward, PlayerEvent::FastForward, None, vec![])
    .add_transition(PlayerState::PlayingFastForward, PlayerState::PlayingNormal, PlayerEvent::Normal, None, vec![])
    
    .build()?;

For more examples and detailed tutorials, see the User Guide.

Core Concepts

States

States represent the different conditions or modes your system can be in. States can be:

  • Simple: Leaf states with no substates
  • Composite: States containing substates (hierarchical)
  • Parallel: States with multiple orthogonal regions
  • Initial: Entry point pseudo-state
  • Final: Terminal state
  • History: Pseudo-states that remember previous configurations

Transitions

Transitions define how the system moves from one state to another in response to events. Each transition can have:

  • Source State: Where the transition starts
  • Target State: Where the transition ends
  • Event: What triggers the transition
  • Guard: Optional condition that must be true
  • Actions: Code executed during the transition

Events

Events are signals that trigger transitions. They can carry optional payload data.

Guards

Guards are boolean conditions that determine whether a transition can occur. They have access to the state machine's context.

Actions

Actions are side effects executed at specific points:

  • Entry Actions: Run when entering a state
  • Exit Actions: Run when leaving a state
  • Transition Actions: Run during a transition

Context

The context is shared state accessible to all guards and actions. It represents the data your state machine operates on.

Advanced Features

Parallel Regions

Run multiple state machines concurrently within a single parent state. See the User Guide for detailed examples.

History States

Remember and restore previous state configurations. See the User Guide for detailed examples.

Internal Transitions

Handle events without changing state or triggering entry/exit actions using transition kinds.

Visualization

Export your state machine to DOT format for visualization:

use statechart::visualization::to_dot;

let dot = to_dot(&definition);
std::fs::write("machine.dot", dot)?;
// Then: dot -Tpng machine.dot -o machine.png

Documentation

Examples

See the examples/ directory for complete working examples:

Run an example:

cargo run --example traffic_light
cargo run --example door_controller
cargo run --example media_player --features macros

See the examples README for detailed descriptions and learning path.

Testing

Run the test suite:

# Run all tests
cargo test

# Run with output
cargo test -- --nocapture

# Run specific test
cargo test test_basic_transition

# Run benchmarks
cargo bench

Performance

The library is designed for performance:

  • O(1) state and transition lookups using hash maps
  • Minimal allocations during transitions
  • Lazy evaluation of guards
  • Optional arena allocation for state storage

See benches/ for detailed benchmarks.

Project Structure

statechart/
├── src/
│   ├── lib.rs              # Public API exports
│   ├── model/              # Core data structures
│   ├── builder/            # Builder pattern API
│   ├── runtime/            # State machine execution
│   ├── engine/             # Event processing
│   ├── hierarchy/          # Hierarchical state support
│   ├── parallel/           # Parallel region support
│   ├── history/            # History state support
│   ├── visualization/      # DOT export
│   └── error.rs            # Error types
├── statechart-macros/      # Procedural macros
├── examples/               # Example applications
├── tests/                  # Integration tests
├── benches/                # Performance benchmarks
└── docs/                   # Documentation

Contributing

Contributions are welcome! Please see CONTRIBUTING.md for guidelines.

License

This project is licensed under Apache License, Version 2.0.

Contribution

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be licensed as above, without any additional terms or conditions.

Acknowledgments

This library is inspired by:

Roadmap

  • Core state machine implementation
  • Hierarchical states
  • Parallel regions
  • History states
  • Builder API
  • Derive macros
  • Async support
  • WebAssembly support
  • Visual editor integration
  • State machine composition
  • Hot reloading

Support

About

A Statechart library for Rust.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages