use qubit_cas::{
FastCas,
FastCasDecision,
FastCasState,
};
use super::{
FastStateMachineError,
FastStateMachineResult,
fast_state_machine_error_from_fast_cas_error,
};
const UNSET_TRANSITION: usize = usize::MAX;
#[derive(Debug, Clone)]
pub struct FastStateMachine {
pub(super) state_count: usize,
pub(super) event_count: usize,
pub(super) initial_states: Vec<bool>,
pub(super) final_states: Vec<bool>,
pub(super) transitions: Vec<usize>,
pub(super) cas: FastCas,
}
impl FastStateMachine {
pub fn builder() -> super::FastStateMachineBuilder {
super::FastStateMachineBuilder::new()
}
pub const fn state_count(&self) -> usize {
self.state_count
}
pub const fn event_count(&self) -> usize {
self.event_count
}
pub fn transitions(&self) -> &[usize] {
&self.transitions
}
pub fn cas_policy(&self) -> qubit_cas::FastCasPolicy {
self.cas.policy()
}
pub fn initial_states(&self) -> &[bool] {
&self.initial_states
}
pub fn final_states(&self) -> &[bool] {
&self.final_states
}
pub const fn contains_state(&self, state: usize) -> bool {
state < self.state_count
}
pub fn is_initial_state(&self, state: usize) -> bool {
self.initial_states.get(state).copied().unwrap_or(false)
}
pub fn is_final_state(&self, state: usize) -> bool {
self.final_states.get(state).copied().unwrap_or(false)
}
pub fn transition_target(&self, source: usize, event: usize) -> Option<usize> {
self.get_transition_target(source, event)
.filter(|&target| target != UNSET_TRANSITION)
}
fn get_transition_target(&self, source: usize, event: usize) -> Option<usize> {
if !self.contains_state(source) || event >= self.event_count {
return None;
}
let index = source
.checked_mul(self.event_count)
.and_then(|base| base.checked_add(event));
index.map(|index| self.transitions[index])
}
pub fn trigger(&self, state: &FastCasState, event: usize) -> FastStateMachineResult {
let (_old_state, new_state) = self.change_state(state, event)?;
Ok(new_state)
}
pub fn trigger_with<F>(
&self,
state: &FastCasState,
event: usize,
on_success: F,
) -> FastStateMachineResult
where
F: Fn(usize, usize),
{
let (old_state, new_state) = self.change_state(state, event)?;
on_success(old_state, new_state);
Ok(new_state)
}
pub fn try_trigger(&self, state: &FastCasState, event: usize) -> bool {
self.trigger(state, event).is_ok()
}
pub fn try_trigger_with<F>(&self, state: &FastCasState, event: usize, on_success: F) -> bool
where
F: Fn(usize, usize),
{
self.trigger_with(state, event, on_success).is_ok()
}
fn change_state(
&self,
state: &FastCasState,
event: usize,
) -> Result<(usize, usize), FastStateMachineError> {
match self
.cas
.execute::<usize, FastStateMachineError, _>(state, |current| {
match self.next_state(current, event) {
Ok(new_state) => FastCasDecision::update(new_state, new_state),
Err(error) => FastCasDecision::abort(error),
}
}) {
Ok(success) => Ok((success.previous(), success.current())),
Err(error) => Err(fast_state_machine_error_from_fast_cas_error(error)),
}
}
fn next_state(&self, state: usize, event: usize) -> Result<usize, FastStateMachineError> {
if !self.contains_state(state) {
return Err(FastStateMachineError::UnknownState { state });
}
self.transition_target(state, event)
.ok_or(FastStateMachineError::UnknownTransition {
source_state: state,
event,
})
}
}