pub struct StateMachine<S, E>{ /* private fields */ }Expand description
Immutable finite state machine rules.
S is the state type and E is the event type. Both should usually be
small enum-like types. The state machine itself is immutable and can be
shared across threads; mutable current state is kept in AtomicRef and
updated through qubit_cas::CasExecutor.
§Common usage
Define the valid states and events, build an immutable transition table, and
keep each object’s current state in an AtomicRef.
use qubit_state_machine::{AtomicRef, StateMachine};
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
enum JobState {
Queued,
Running,
Succeeded,
Failed,
}
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
enum JobEvent {
Start,
Complete,
Fail,
}
fn create_job_machine() -> StateMachine<JobState, JobEvent> {
StateMachine::builder()
.add_states(&[
JobState::Queued,
JobState::Running,
JobState::Succeeded,
JobState::Failed,
])
.set_initial_state(JobState::Queued)
.set_final_states(&[JobState::Succeeded, JobState::Failed])
.add_transition(JobState::Queued, JobEvent::Start, JobState::Running)
.add_transition(JobState::Running, JobEvent::Complete, JobState::Succeeded)
.add_transition(JobState::Running, JobEvent::Fail, JobState::Failed)
.build()
.expect("job state machine should be valid")
}
let machine = create_job_machine();
let state = AtomicRef::from_value(JobState::Queued);
assert_eq!(machine.trigger(&state, JobEvent::Start).unwrap(), JobState::Running);
assert_eq!(*state.load(), JobState::Running);
assert!(machine.try_trigger(&state, JobEvent::Complete));
assert_eq!(*state.load(), JobState::Succeeded);Implementations§
Source§impl<S, E> StateMachine<S, E>
impl<S, E> StateMachine<S, E>
Sourcepub fn builder() -> StateMachineBuilder<S, E>
pub fn builder() -> StateMachineBuilder<S, E>
Creates a builder for immutable state machine rules.
§Returns
A new empty StateMachineBuilder.
§Examples
use qubit_state_machine::StateMachine;
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
enum State {
New,
}
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
enum Event {
Start,
}
let machine = StateMachine::<State, Event>::builder()
.add_state(State::New)
.set_initial_state(State::New)
.build()
.expect("single-state machine should build");
assert!(machine.contains_state(State::New));Sourcepub const fn initial_states(&self) -> &HashSet<S>
pub const fn initial_states(&self) -> &HashSet<S>
Sourcepub const fn final_states(&self) -> &HashSet<S>
pub const fn final_states(&self) -> &HashSet<S>
Sourcepub const fn transitions(&self) -> &HashSet<Transition<S, E>>
pub const fn transitions(&self) -> &HashSet<Transition<S, E>>
Returns all registered transitions.
§Returns
An immutable view of the transition set.
§Examples
use qubit_state_machine::{StateMachine, Transition};
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
enum State {
New,
Running,
}
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
enum Event {
Start,
}
let machine = StateMachine::builder()
.add_states(&[State::New, State::Running])
.add_transition(State::New, Event::Start, State::Running)
.build()
.expect("rules should build");
assert!(machine
.transitions()
.contains(&Transition::new(State::New, Event::Start, State::Running)));Sourcepub fn contains_state(&self, state: S) -> bool
pub fn contains_state(&self, state: S) -> bool
Sourcepub fn is_initial_state(&self, state: S) -> bool
pub fn is_initial_state(&self, state: S) -> bool
Sourcepub fn is_final_state(&self, state: S) -> bool
pub fn is_final_state(&self, state: S) -> bool
Sourcepub fn transition_target(&self, source: S, event: E) -> Option<S>
pub fn transition_target(&self, source: S, event: E) -> Option<S>
Looks up the target state for a source state and event.
This method only queries rules; it does not modify any current-state storage.
§Parameters
source: Source state.event: Event to apply.
§Returns
Some(target) if a transition exists, or None otherwise.
§Examples
assert_eq!(
machine.transition_target(State::New, Event::Start),
Some(State::Running),
);
assert_eq!(machine.transition_target(State::New, Event::Finish), None);Sourcepub fn trigger(
&self,
state: &AtomicRef<S>,
event: E,
) -> StateMachineResult<S, E>
pub fn trigger( &self, state: &AtomicRef<S>, event: E, ) -> StateMachineResult<S, E>
Triggers an event and updates the provided atomic state reference.
§Parameters
state: Current state atomic reference.event: Event to apply.
§Returns
The new state after a successful transition.
§Errors
Returns StateMachineError::UnknownState when the current state is not
registered. Returns StateMachineError::UnknownTransition when the
current state is registered but has no transition for event.
§Examples
use qubit_state_machine::{AtomicRef, StateMachine};
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
enum State {
New,
Running,
}
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
enum Event {
Start,
}
let machine = StateMachine::builder()
.add_states(&[State::New, State::Running])
.add_transition(State::New, Event::Start, State::Running)
.build()
.expect("rules should build");
let state = AtomicRef::from_value(State::New);
assert_eq!(machine.trigger(&state, Event::Start).unwrap(), State::Running);
assert_eq!(*state.load(), State::Running);Sourcepub fn trigger_with<F>(
&self,
state: &AtomicRef<S>,
event: E,
on_success: F,
) -> StateMachineResult<S, E>where
F: FnOnce(S, S),
pub fn trigger_with<F>(
&self,
state: &AtomicRef<S>,
event: E,
on_success: F,
) -> StateMachineResult<S, E>where
F: FnOnce(S, S),
Triggers an event, updates the atomic state, and invokes a success callback.
The callback runs after the CAS update has succeeded.
§Parameters
state: Current state atomic reference.event: Event to apply.on_success: Callback receiving(old_state, new_state).
§Returns
The new state after a successful transition.
§Errors
Returns the same errors as StateMachine::trigger. The callback is not
invoked when the transition fails.
§Examples
use qubit_state_machine::{AtomicRef, StateMachine};
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
enum State {
New,
Running,
}
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
enum Event {
Start,
}
let machine = StateMachine::builder()
.add_states(&[State::New, State::Running])
.add_transition(State::New, Event::Start, State::Running)
.build()
.expect("rules should build");
let state = AtomicRef::from_value(State::New);
let mut observed = None;
let next = machine
.trigger_with(&state, Event::Start, |old_state, new_state| {
observed = Some((old_state, new_state));
})
.expect("start should be valid");
assert_eq!(next, State::Running);
assert_eq!(observed, Some((State::New, State::Running)));Sourcepub fn try_trigger(&self, state: &AtomicRef<S>, event: E) -> bool
pub fn try_trigger(&self, state: &AtomicRef<S>, event: E) -> bool
Attempts to trigger an event without returning error details.
§Parameters
state: Current state atomic reference.event: Event to apply.
§Returns
true if the state changed successfully; false if the transition was
invalid.
§Examples
use qubit_state_machine::{AtomicRef, StateMachine};
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
enum State {
New,
Running,
}
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
enum Event {
Start,
Finish,
}
let machine = StateMachine::builder()
.add_states(&[State::New, State::Running])
.add_transition(State::New, Event::Start, State::Running)
.build()
.expect("rules should build");
let state = AtomicRef::from_value(State::New);
assert!(!machine.try_trigger(&state, Event::Finish));
assert_eq!(*state.load(), State::New);
assert!(machine.try_trigger(&state, Event::Start));Sourcepub fn try_trigger_with<F>(
&self,
state: &AtomicRef<S>,
event: E,
on_success: F,
) -> boolwhere
F: FnOnce(S, S),
pub fn try_trigger_with<F>(
&self,
state: &AtomicRef<S>,
event: E,
on_success: F,
) -> boolwhere
F: FnOnce(S, S),
Attempts to trigger an event and invokes a callback only on success.
§Parameters
state: Current state atomic reference.event: Event to apply.on_success: Callback receiving(old_state, new_state).
§Returns
true if the state changed successfully; false if the transition was
invalid. The callback is skipped when this method returns false.
§Examples
use qubit_state_machine::{AtomicRef, StateMachine};
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
enum State {
New,
Running,
}
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
enum Event {
Start,
Finish,
}
let machine = StateMachine::builder()
.add_states(&[State::New, State::Running])
.add_transition(State::New, Event::Start, State::Running)
.build()
.expect("rules should build");
let state = AtomicRef::from_value(State::New);
let mut callback_count = 0;
assert!(!machine.try_trigger_with(&state, Event::Finish, |_, _| {
callback_count += 1;
}));
assert_eq!(callback_count, 0);
assert!(machine.try_trigger_with(&state, Event::Start, |_, _| {
callback_count += 1;
}));
assert_eq!(callback_count, 1);Trait Implementations§
Source§impl<S, E> Clone for StateMachine<S, E>
impl<S, E> Clone for StateMachine<S, E>
Source§fn clone(&self) -> StateMachine<S, E>
fn clone(&self) -> StateMachine<S, E>
1.0.0 · Source§fn clone_from(&mut self, source: &Self)
fn clone_from(&mut self, source: &Self)
source. Read more