9 releases
0.3.0 | Apr 28, 2025 |
---|---|
0.2.6 | Apr 22, 2025 |
0.1.0 | Apr 18, 2025 |
#253 in Algorithms
795 downloads per month
260KB
4K
SLoC
rustate Crate
This crate contains the core RuState state machine library.
Overview
RuState provides the following features:
- ✅ Finite state machines and statecharts
- ✅ Hierarchical states
- ✅ Parallel states
- ✅ Transition conditions (guards)
- ✅ Actions (side effects)
- ✅ Context (extended state)
- ✅ Type-safe API
- ✅ Serializable machines (State snapshot only; actions/guards are not fully serialized)
- ✅ Cross-crate state machine integration (via shared memory patterns)
- ✅ Actor pattern support (Basic: Actor trait, ActorRef, spawn, system)
- ❌ Model-based testing (MBT) support (Planned, not implemented)
Model-Based Testing (MBT) Integration
TODO: Implement MBT integration. (Currently not implemented)
Cross-Crate State Machine Integration
(Current approach) RuState primarily supports integrating multiple state machines across different crates using shared memory (Arc<Mutex>
, Arc<RwLock>
), allowing state machines within the same process to communicate via shared context or event forwarding.
Design Patterns for State Machine Integration (Shared Memory)
-
Event Forwarding Pattern: State machines communicate by forwarding events to each other
use rustate::{Action, Context, Event, Machine, MachineBuilder, State, Transition}; use std::sync::{Arc, Mutex}; // Define a shared state machine in a common crate pub struct SharedMachineRef { machine: Arc<Mutex<Machine>>, } impl SharedMachineRef { pub fn new(machine: Machine) -> Self { Self { machine: Arc::new(Mutex::new(machine)), } } pub fn send_event(&self, event: &str) -> rustate::Result<bool> { let mut machine = self.machine.lock().unwrap(); machine.send(event) } } // In crate A: Create a parent machine that forwards events to child fn setup_parent_machine(child_machine: SharedMachineRef) -> Machine { let parent_state = State::new("parent"); // Define action that forwards events to child machine let forward_to_child = Action::new( "forwardToChild", ActionType::Transition, move |_ctx, evt| { if evt.event_type == "CHILD_EVENT" { let _ = child_machine.send_event("HANDLE_EVENT"); } }, ); MachineBuilder::new("parentMachine") .state(parent_state) .initial("parent") .on_entry("parent", forward_to_child) .build() .unwrap() }
-
Context-Based Communication Pattern: Share data between machines using Context
use rustate::{Context, Machine, MachineBuilder, State, Transition}; use std::sync::{Arc, RwLock}; // Define shared context type in a common crate #[derive(Clone, Default)] pub struct SharedContext { data: Arc<RwLock<serde_json::Value>>, } impl SharedContext { pub fn new() -> Self { Self { data: Arc::new(RwLock::new(serde_json::json!({}))), } } pub fn set<T: serde::Serialize>(&self, key: &str, value: T) -> Result<(), serde_json::Error> { let mut data = self.data.write().unwrap(); match &mut *data { serde_json::Value::Object(map) => { map.insert(key.to_string(), serde_json::to_value(value)?); Ok(()) } _ => { *data = serde_json::json!({ key: value }); Ok(()) } } } pub fn get<T: for<'de> serde::Deserialize<'de>>(&self, key: &str) -> Option<T> { let data = self.data.read().unwrap(); match &*data { serde_json::Value::Object(map) => map .get(key) .and_then(|val| serde_json::from_value(val.clone()).ok()), _ => None, } } } // Use in machine actions across different crates fn create_machines(shared_context: SharedContext) -> (Machine, Machine) { // Machine in crate A let machine_a = MachineBuilder::new("machineA") // ...setup states and transitions... .on_entry("someState", move |ctx, _evt| { // Read shared context data if let Some(value) = shared_context.get::<String>("status") { ctx.set("localStatus", value).unwrap(); } }) .build() .unwrap(); // Machine in crate B let machine_b = MachineBuilder::new("machineB") // ...setup states and transitions... .on_entry("anotherState", move |_ctx, _evt| { // Update shared context shared_context.set("status", "active").unwrap(); }) .build() .unwrap(); (machine_a, machine_b) }
-
Hierarchical Integration Pattern: Define parent-child relationships between machines
use rustate::{Action, Machine, MachineBuilder, State, Transition}; // In a common crate: Define a trait for child machines trait ChildMachine { fn handle_parent_event(&mut self, event: &str) -> rustate::Result<bool>; fn is_in_final_state(&self) -> bool; } // In child crate: Implement child machine struct ConcreteChildMachine { machine: Machine, } impl ConcreteChildMachine { fn new() -> Self { let final_state = State::new_final("final"); let initial = State::new("initial"); let machine = MachineBuilder::new("childMachine") .state(initial) .state(final_state) .initial("initial") .transition(Transition::new("initial", "COMPLETE", "final")) .build() .unwrap(); Self { machine } } } impl ChildMachine for ConcreteChildMachine { fn handle_parent_event(&mut self, event: &str) -> rustate::Result<bool> { self.machine.send(event) } fn is_in_final_state(&self) -> bool { self.machine.is_in("final") } } // In parent crate: Create parent machine that coordinates with child fn setup_parent_machine(mut child: impl ChildMachine + 'static) -> Machine { let check_child_status = Action::new( "checkChildStatus", ActionType::Transition, move |ctx, _evt| { if child.is_in_final_state() { let _ = ctx.set("childComplete", true); } }, ); MachineBuilder::new("parentMachine") // ...setup states and transitions... .on_entry("monitoring", check_child_status) .build() .unwrap() }
Best Practices for Cross-Crate Integration
- Define Common Types: Create a shared crate for common event and state types
- Use Trait Abstraction: Define traits for machine capabilities to allow different implementations
- Leverage Context: Use context for data sharing with clear read/write patterns
- Event Namespacing: Prefix events with module or crate names to avoid collisions
- Minimize Coupling: Design machines to be as independent as possible
- Error Handling: Use Result types for robust cross-machine communication
- Testing: Test integrated machines as a whole system. (Note: Utilizing Model-Based Testing (MBT) techniques is a goal, pending verification of MBT feature status).
This approach allows you to build complex applications with modular, type-safe state management across multiple crates, perfect for large Rust applications with distinct domains.
Key Concepts
- State: Represents a node in the statechart
- Transition: Defines movement between states in response to events
- Guard: Logic that determines transition conditions
- Action: Side effects executed during state transitions
- Context: Stores the extended state of the machine
- Cross-Crate Integration: Patterns for connecting state machines across different crates
- Actor Pattern: Concepts related to actor-based concurrency and state management within
rustate_core
.
Roadmap
(Current status as of v0.3.0)
- Implement Model-Based Testing (MBT) Support: Integrate MBT capabilities for automated test generation and validation based on the state machine models. (Status: Not started)
- Explore Alternative Cross-Crate Communication: Investigate and potentially implement/document alternative communication patterns beyond shared memory (e.g., message passing, event bus). (Status: Planning/Research)
- Enhance Actor Pattern Support: Further develop and document the actor pattern features within
rustate
(e.g., ask pattern, supervision). (Status: Basic implementation exists, enhancements planned) - Add Comprehensive Examples: Provide more diverse and complex usage examples. (Status: Ongoing)
- Benchmarking & Performance Optimization: Conduct performance analysis and optimize critical paths. (Status: Basic benchmark setup exists, optimization not started)
- Formalize
.ssot
Integration: Define and potentially implement a workflow for using.ssot
files to define/generaterustate
machines. (Status: Planned) - Improve CI/CD and Testing: Enhance the testing suite (unit, integration, MBT) and automate builds/releases. (Status: Basic setup exists, enhancements planned)
- Implement Deep History: Complete the implementation for deep history states. (Status: Partially implemented/needs review)
- Stabilize Codegen Macros: Finalize and document the
create_machine
macro (potentially in a separaterustate-macros
crate). (Status: Basic macro exists but needs separate crate and testing) - Full Serialization Support: Investigate methods for serializing/deserializing actions and guards (e.g., using identifiers). (Status: Planning/Research)
Dependencies
~4–13MB
~150K SLoC