Skip to main content

StateMachine

Struct StateMachine 

Source
pub struct StateMachine<S, E>
where S: Copy + Eq + Hash + Debug + 'static, E: Copy + Eq + Hash + Debug + 'static,
{ /* 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>
where S: Copy + Eq + Hash + Debug + 'static, E: Copy + Eq + Hash + Debug + 'static,

Source

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));
Source

pub const fn states(&self) -> &HashSet<S>

Returns all registered states.

§Returns

An immutable view of the registered state set.

§Examples
assert!(machine.states().contains(&State::New));
assert_eq!(machine.states().len(), 2);
Source

pub const fn initial_states(&self) -> &HashSet<S>

Returns all configured initial states.

§Returns

An immutable view of the initial state set.

§Examples
assert!(machine.initial_states().contains(&State::New));
Source

pub const fn final_states(&self) -> &HashSet<S>

Returns all configured final states.

§Returns

An immutable view of the final state set.

§Examples
assert!(machine.final_states().contains(&State::Done));
Source

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)));
Source

pub fn contains_state(&self, state: S) -> bool

Tests whether a state is registered in this state machine.

§Parameters
  • state: State to test.
§Returns

true if the state is registered.

§Examples
assert!(machine.contains_state(State::Running));
assert!(!machine.contains_state(State::Detached));
Source

pub fn is_initial_state(&self, state: S) -> bool

Tests whether a state is configured as an initial state.

§Parameters
  • state: State to test.
§Returns

true if the state is an initial state.

§Examples
assert!(machine.is_initial_state(State::New));
assert!(!machine.is_initial_state(State::Running));
Source

pub fn is_final_state(&self, state: S) -> bool

Tests whether a state is configured as a final state.

§Parameters
  • state: State to test.
§Returns

true if the state is a final state.

§Examples
assert!(machine.is_final_state(State::Done));
assert!(!machine.is_final_state(State::Running));
Source

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);
Source

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);
Source

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)));
Source

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));
Source

pub fn try_trigger_with<F>( &self, state: &AtomicRef<S>, event: E, on_success: F, ) -> bool
where 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>
where S: Copy + Eq + Hash + Debug + 'static + Clone, E: Copy + Eq + Hash + Debug + 'static + Clone,

Source§

fn clone(&self) -> StateMachine<S, E>

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl<S, E> Debug for StateMachine<S, E>
where S: Copy + Eq + Hash + Debug + 'static + Debug, E: Copy + Eq + Hash + Debug + 'static + Debug,

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

§

impl<S, E> Freeze for StateMachine<S, E>

§

impl<S, E> RefUnwindSafe for StateMachine<S, E>

§

impl<S, E> Send for StateMachine<S, E>
where S: Send, E: Send,

§

impl<S, E> Sync for StateMachine<S, E>
where S: Sync, E: Sync,

§

impl<S, E> Unpin for StateMachine<S, E>
where S: Unpin, E: Unpin,

§

impl<S, E> UnsafeUnpin for StateMachine<S, E>

§

impl<S, E> UnwindSafe for StateMachine<S, E>
where S: UnwindSafe, E: UnwindSafe,

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> IntoResult<T> for T

Source§

impl<T> IntoResult<T> for T

Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.