#![warn(missing_docs)]
#![doc = include_str!("../README.md")]
#![cfg_attr(docsrs, feature(doc_cfg))]
#![cfg_attr(not(feature = "std"), no_std)]
pub use moku_macros::state_machine;
pub use moku_macros::machine_module;
pub enum Next<T: StateEnum> {
None,
Target(T),
ExactTarget(T),
}
impl<T: StateEnum> From<T> for Next<T> {
fn from(value: T) -> Self {
Next::Target(value)
}
}
impl<T: StateEnum> From<Option<T>> for Next<T> {
fn from(value: Option<T>) -> Self {
match value {
None => Next::None,
Some(target) => Next::Target(target),
}
}
}
impl<T: StateEnum> From<()> for Next<T> {
fn from(_: ()) -> Self {
Next::None
}
}
pub trait StateMachineEvent {}
impl StateMachineEvent for () {}
pub enum Response<T: StateEnum> {
Next(Next<T>),
Drop,
}
impl<T: StateEnum> From<T> for Response<T> {
fn from(value: T) -> Self {
Response::Next(value.into())
}
}
impl<T: StateEnum> From<Option<T>> for Response<T> {
fn from(value: Option<T>) -> Self {
Response::Next(value.into())
}
}
impl<T: StateEnum> From<()> for Response<T> {
fn from(_: ()) -> Self {
Response::Next(Next::None)
}
}
impl<T: StateEnum> From<Next<T>> for Response<T> {
fn from(value: Next<T>) -> Self {
Response::Next(value)
}
}
pub trait StateEnum: core::fmt::Debug + Clone + Copy + PartialEq + Eq {}
pub trait StateMachine<T, U, V>
where
T: StateEnum,
U: StateMachineEvent,
V: TopState,
{
fn update(&mut self);
fn top_down_update(&mut self);
fn transition(&mut self, target: T);
fn exact_transition(&mut self, target: T);
fn state(&self) -> T;
fn state_matches(&self, state: T) -> bool;
fn top_ref(&self) -> &V;
fn top_mut(&mut self) -> &mut V;
fn name(&self) -> &str;
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
#[cfg(feature = "std")]
fn set_name(&mut self, name: String);
fn handle_event(&mut self, event: &U);
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
#[cfg(feature = "std")]
fn state_list(&self) -> Vec<T>;
}
pub trait StateRef<T, U, V>
where
T: StateEnum,
U: StateMachineEvent,
{
fn state_ref(&self) -> Option<&V>;
fn state_mut(&mut self) -> Option<&mut V>;
}
pub trait StateMachineBuilder<T, U, V, W>
where
T: StateEnum,
U: StateMachineEvent,
V: TopState,
W: StateMachine<T, U, V>,
{
fn new(top_state: V) -> Self;
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
#[cfg(feature = "std")]
fn name(self, name: String) -> Self;
fn build(self) -> W;
}
pub enum Entry<T: StateEnum, U> {
State(U),
Target(T),
ExactTarget(T),
}
impl<T: StateEnum, U> From<U> for Entry<T, U> {
fn from(value: U) -> Self {
Entry::State(value)
}
}
pub trait Substate<Parent>: Sized {
type State: StateEnum;
type Event: StateMachineEvent;
type Context<'a>;
fn enter(_ctx: &mut Self::Context<'_>) -> impl Into<Entry<Self::State, Self>>;
fn init(&mut self, _ctx: &mut Self::Context<'_>) -> impl Into<Next<Self::State>> {}
fn update(&mut self, _ctx: &mut Self::Context<'_>) -> impl Into<Next<Self::State>> {}
fn top_down_update(&mut self, _ctx: &mut Self::Context<'_>) -> impl Into<Next<Self::State>> {}
fn exit(self, _ctx: &mut Self::Context<'_>) -> impl Into<Next<Self::State>> {}
#[allow(unused_variables)]
fn handle_event(
&mut self,
_ctx: &mut Self::Context<'_>,
event: &Self::Event,
) -> impl Into<Response<Self::State>> {
}
}
pub trait TopState: Sized {
type State: StateEnum;
type Event: StateMachineEvent;
fn init(&mut self) -> impl Into<Next<Self::State>> {}
fn update(&mut self) -> impl Into<Next<Self::State>> {}
fn top_down_update(&mut self) -> impl Into<Next<Self::State>> {}
#[allow(unused_variables)]
fn handle_event(&mut self, event: &Self::Event) -> impl Into<Next<Self::State>> {}
}
pub mod internal {
use core::marker::PhantomData;
use log::info;
use super::*;
pub trait StateLike<T, U = ()>: Sized
where
T: StateEnum,
U: StateMachineEvent,
{
type Context<'a>;
fn enter(ctx: &mut Self::Context<'_>) -> Entry<T, Self>;
fn init(&mut self, ctx: &mut Self::Context<'_>) -> impl Into<Next<T>>;
fn update(&mut self, ctx: &mut Self::Context<'_>) -> impl Into<Next<T>>;
fn top_down_update(&mut self, ctx: &mut Self::Context<'_>) -> impl Into<Next<T>>;
fn exit(self, ctx: &mut Self::Context<'_>) -> impl Into<Next<T>>;
fn handle_event(
&mut self,
ctx: &mut Self::Context<'_>,
event: &U,
) -> impl Into<Response<T>>;
}
pub struct TopContext<'a>(PhantomData<&'a ()>);
impl<T, U, V> StateLike<T, U> for V
where
T: StateEnum,
U: StateMachineEvent,
V: TopState<State = T, Event = U>,
{
type Context<'a> = TopContext<'a>;
fn enter(_ctx: &mut Self::Context<'_>) -> Entry<T, Self> {
unreachable!("TopState::enter should never be called")
}
fn init(&mut self, _ctx: &mut Self::Context<'_>) -> impl Into<Next<T>> {
TopState::init(self)
}
fn update(&mut self, _ctx: &mut Self::Context<'_>) -> impl Into<Next<T>> {
TopState::update(self)
}
fn top_down_update(&mut self, _ctx: &mut Self::Context<'_>) -> impl Into<Next<T>> {
TopState::top_down_update(self)
}
fn exit(self, _ctx: &mut Self::Context<'_>) -> impl Into<Next<T>> {
unreachable!("TopState::exit should never be called");
#[allow(unreachable_code)]
Next::None
}
fn handle_event(
&mut self,
_ctx: &mut Self::Context<'_>,
event: &U,
) -> impl Into<Response<T>> {
Response::Next(TopState::handle_event(self, event).into())
}
}
pub enum TransitionResult<T: StateEnum> {
MoveUp,
Next(Next<T>),
}
pub trait SubstateEnum<T, U, V>
where
T: StateEnum,
U: StateMachineEvent,
V: StateLike<T, U>,
{
fn none_variant() -> Self;
fn this_state() -> T;
fn is_state(state: T) -> bool;
fn current_state(&self) -> T {
Self::this_state()
}
#[allow(unused_variables)]
fn is_ancestor(state: T) -> bool {
false
}
#[allow(unused_variables)]
fn update(&mut self, state: &mut V, ctx: &mut V::Context<'_>) -> Next<T> {
Next::None
}
#[allow(unused_variables)]
fn update_in_need(&mut self, state: &mut V, ctx: &mut V::Context<'_>) -> Next<T> {
Next::None
}
#[allow(unused_variables)]
fn top_down_update(&mut self, state: &mut V, ctx: &mut V::Context<'_>) -> Next<T> {
Next::None
}
#[allow(unused_variables)]
fn top_down_update_in_need(&mut self, state: &mut V, ctx: &mut V::Context<'_>) -> Next<T> {
Next::None
}
fn clear_top_down_updated(&mut self) {}
#[allow(unused_variables)]
fn exit(&mut self, state: &mut V, ctx: &mut V::Context<'_>, indent: bool) -> Next<T> {
Next::None
}
#[allow(unused_variables)]
fn transition(
&mut self,
target: T,
state: &mut V,
ctx: &mut V::Context<'_>,
indent: bool,
exact: bool,
) -> TransitionResult<T> {
TransitionResult::MoveUp
}
#[allow(unused_variables)]
fn enter_substate_towards(
&mut self,
target: T,
state: &mut V,
ctx: &mut V::Context<'_>,
indent: bool,
) -> Next<T> {
unreachable!()
}
fn state_matches(&self, state: T) -> bool {
Self::is_state(state)
}
#[allow(unused_variables)]
fn handle_event(
&mut self,
event: &U,
state: &mut V,
ctx: &mut V::Context<'_>,
) -> Response<T> {
Response::Next(Next::None)
}
#[cfg(feature = "std")]
fn state_list(&self, list: Vec<T>) -> Vec<T>;
}
pub enum NodeEntry<T, U, V, W>
where
T: StateEnum,
U: StateMachineEvent,
V: StateLike<T, U>,
W: SubstateEnum<T, U, V>,
{
Node(Node<T, U, V, W>),
Target(T),
ExactTarget(T),
}
impl<T, U, V, W> From<Entry<T, V>> for NodeEntry<T, U, V, W>
where
T: StateEnum,
U: StateMachineEvent,
V: StateLike<T, U>,
W: SubstateEnum<T, U, V>,
{
fn from(entry: Entry<T, V>) -> Self {
match entry {
Entry::State(state) => NodeEntry::Node(Node::from_state(state)),
Entry::Target(target) => NodeEntry::Target(target),
Entry::ExactTarget(target) => NodeEntry::ExactTarget(target),
}
}
}
bitflags::bitflags! {
#[derive(Debug, Clone, Copy, Default)]
struct NodeFlags: u8 {
const NEEDS_UPDATE = 1;
const TOP_DOWN_UPDATED = 2;
}
}
pub struct Node<T, U, V, W>
where
T: StateEnum,
U: StateMachineEvent,
V: StateLike<T, U>,
W: SubstateEnum<T, U, V>,
{
phantom_t: PhantomData<T>,
phantom_u: PhantomData<U>,
#[allow(missing_docs)]
pub state: V,
#[allow(missing_docs)]
pub substate: W,
flags: NodeFlags,
}
impl<T, U, V, W> Node<T, U, V, W>
where
T: StateEnum,
U: StateMachineEvent,
V: StateLike<T, U>,
W: SubstateEnum<T, U, V>,
{
pub fn from_state(state: V) -> Self {
Self {
phantom_t: PhantomData,
phantom_u: PhantomData,
state,
substate: W::none_variant(),
flags: NodeFlags::empty(),
}
}
pub fn needs_update(&self) -> bool {
self.flags.contains(NodeFlags::NEEDS_UPDATE)
}
pub fn enter(ctx: &mut V::Context<'_>, indent: bool) -> NodeEntry<T, U, V, W> {
info!(
"{}\u{02502}Entering {:?}",
if indent { "\u{02502}" } else { "" },
W::this_state()
);
match V::enter(ctx) {
Entry::State(state) => NodeEntry::Node(Self {
phantom_t: PhantomData,
phantom_u: PhantomData,
state,
substate: W::none_variant(),
flags: NodeFlags::empty(),
}),
Entry::Target(target) => {
info!(
"{}\u{02502}Short circuit transition to {target:?}",
if indent { "\u{02502}" } else { "" },
);
NodeEntry::Target(target)
}
Entry::ExactTarget(target) => {
info!(
"{}\u{02502}Short circuit exact transition to {target:?}",
if indent { "\u{02502}" } else { "" },
);
NodeEntry::ExactTarget(target)
}
}
}
pub fn update(&mut self, ctx: &mut V::Context<'_>) -> Next<T> {
self.flags.insert(NodeFlags::NEEDS_UPDATE);
match self.substate.update(&mut self.state, ctx) {
Next::None => {
info!("\u{02502}Updating {:?}", W::this_state());
self.flags.remove(NodeFlags::NEEDS_UPDATE);
self.state.update(ctx).into()
}
target => target,
}
}
pub fn update_in_need(&mut self, ctx: &mut V::Context<'_>) -> Next<T> {
if self.flags.contains(NodeFlags::NEEDS_UPDATE) {
match self.substate.update_in_need(&mut self.state, ctx) {
Next::None => {
info!("\u{02502}Updating {:?}", W::this_state());
self.flags.remove(NodeFlags::NEEDS_UPDATE);
self.state.update(ctx).into()
}
target => target,
}
} else {
Next::None
}
}
pub fn top_down_update(&mut self, ctx: &mut V::Context<'_>) -> Next<T> {
info!("\u{02502}Top-down updating {:?}", W::this_state());
self.flags.insert(NodeFlags::TOP_DOWN_UPDATED);
match self.state.top_down_update(ctx).into() {
Next::None => self.substate.top_down_update(&mut self.state, ctx),
target => target,
}
}
pub fn top_down_update_in_need(&mut self, ctx: &mut V::Context<'_>) -> Next<T> {
if !self.flags.contains(NodeFlags::TOP_DOWN_UPDATED) {
info!("\u{02502}Top-down updating {:?}", W::this_state());
self.flags.insert(NodeFlags::TOP_DOWN_UPDATED);
match self.state.top_down_update(ctx).into() {
Next::None => (),
target => return target,
}
}
self.substate.top_down_update_in_need(&mut self.state, ctx)
}
pub fn clear_top_down_updated(&mut self) {
self.flags.remove(NodeFlags::TOP_DOWN_UPDATED);
self.substate.clear_top_down_updated();
}
pub fn exit(self, ctx: &mut V::Context<'_>, indent: bool) -> Next<T> {
info!(
"{}\u{02502}Exiting {:?}",
if indent { "\u{02502}" } else { "" },
W::this_state()
);
let res = self.state.exit(ctx).into();
match &res {
Next::None => (),
Next::Target(target) => info!(
"{}\u{02502}Short circuit transition to {target:?}",
if indent { "\u{02502}" } else { "" }
),
Next::ExactTarget(target) => info!(
"{}\u{02502}Short circuit exact transition to {target:?}",
if indent { "\u{02502}" } else { "" }
),
}
res
}
pub fn transition(
&mut self,
target: T,
ctx: &mut V::Context<'_>,
indent: bool,
exact: bool,
) -> TransitionResult<T> {
match self
.substate
.transition(target, &mut self.state, ctx, indent, exact)
{
TransitionResult::MoveUp => {
match self.substate.exit(&mut self.state, ctx, indent) {
Next::None => {
if W::is_ancestor(target) {
match self.substate.enter_substate_towards(
target,
&mut self.state,
ctx,
indent,
) {
Next::None => self.substate.transition(
target,
&mut self.state,
ctx,
indent,
false,
),
res => TransitionResult::Next(res),
}
} else if W::is_state(target) {
if exact {
TransitionResult::MoveUp
} else {
let res = self.state.init(ctx).into();
match &res {
Next::Target(new_target) => {
info!("\u{02502}Initial transition to {new_target:?}")
}
Next::ExactTarget(new_target) => {
info!(
"\u{02502}Initial exact transition to {new_target:?}"
)
}
Next::None => (),
}
TransitionResult::Next(res)
}
} else {
TransitionResult::MoveUp
}
}
next => TransitionResult::Next(next),
}
}
res => res,
}
}
pub fn current_state(&self) -> T {
self.substate.current_state()
}
pub fn state_matches(&self, state: T) -> bool {
self.substate.state_matches(state)
}
pub fn handle_event(&mut self, ctx: &mut V::Context<'_>, event: &U) -> Response<T> {
match self.substate.handle_event(event, &mut self.state, ctx) {
Response::Next(Next::None) => {
let res = self.state.handle_event(ctx, event).into();
match &res {
Response::Drop => {
info!("\u{02502}{:?} dropping event", W::this_state())
}
Response::Next(next) => match next {
Next::None => {
info!("\u{02502}{:?} deferring event", W::this_state())
}
Next::Target(target) => info!(
"\u{02502}{:?} triggered transition to {:?}",
W::this_state(),
target
),
Next::ExactTarget(target) => info!(
"\u{02502}{:?} triggered exact transition to {:?}",
W::this_state(),
target
),
},
}
res
}
substate_res => substate_res,
}
}
#[allow(unused)]
#[cfg(feature = "std")]
pub fn state_list(&self, mut list: Vec<T>) -> Vec<T> {
list.push(W::this_state());
self.substate.state_list(list)
}
}
pub struct TopNode<T, U, V, W>
where
T: StateEnum,
U: StateMachineEvent,
V: TopState<State = T, Event = U>,
W: SubstateEnum<T, U, V>,
{
#[allow(missing_docs)]
pub node: Node<T, U, V, W>,
#[cfg(feature = "std")]
name: String,
#[cfg(not(feature = "std"))]
name: &'static str,
}
impl<T, U, V, W> TopNode<T, U, V, W>
where
T: StateEnum,
U: StateMachineEvent,
V: TopState<State = T, Event = U>,
W: SubstateEnum<T, U, V>,
{
#[cfg(feature = "std")]
pub fn new(top_state: V, name: String) -> Self {
Self {
node: Node::from_state(top_state),
name,
}
}
#[cfg(not(feature = "std"))]
pub fn new(top_state: V, name: &'static str) -> Self {
Self {
node: Node::from_state(top_state),
name,
}
}
pub fn init(&mut self) {
match TopState::init(&mut self.node.state).into() {
Next::Target(target) => {
info!("{}: Initial transition to {target:?}", self.name());
self.transition_quiet(target, false, false);
info!("\u{02514}Transition complete");
}
Next::ExactTarget(target) => {
info!("{}: Initial exact transition to {target:?}", self.name());
self.transition_quiet(target, false, true);
info!("\u{02514}Transition complete");
}
Next::None => (),
}
}
pub fn update(&mut self) {
info!("{}: Updating", self.name());
match self.node.update(&mut TopContext(PhantomData)) {
Next::None => (),
next => {
match next {
Next::None => unreachable!(),
Next::Target(target) => self.transition(target, true, false),
Next::ExactTarget(target) => self.transition(target, true, true),
}
while self.node.needs_update() {
match self.node.update_in_need(&mut TopContext(PhantomData)) {
Next::None => (),
Next::Target(target) => self.transition(target, true, false),
Next::ExactTarget(target) => self.transition(target, true, true),
}
}
}
}
info!("\u{02514}Update complete");
}
pub fn top_down_update(&mut self) {
info!("{}: Top-down updating", self.name());
match self.node.top_down_update(&mut TopContext(PhantomData)) {
Next::None => (),
next => {
match next {
Next::None => unreachable!(),
Next::Target(target) => self.transition(target, true, false),
Next::ExactTarget(target) => self.transition(target, true, true),
}
loop {
match self
.node
.top_down_update_in_need(&mut TopContext(PhantomData))
{
Next::None => break,
Next::Target(target) => self.transition(target, true, false),
Next::ExactTarget(target) => self.transition(target, true, true),
}
}
}
}
self.node.clear_top_down_updated();
info!("\u{02514}Top-down update complete");
}
pub fn transition_quiet(&mut self, target: T, indent: bool, exact: bool) {
if !exact && self.state_matches(target) {
return;
}
match self
.node
.transition(target, &mut TopContext(PhantomData), indent, exact)
{
TransitionResult::MoveUp => {
assert!(exact);
if W::is_state(target) {
self.init();
} else {
self.transition_quiet(target, indent, false);
}
}
TransitionResult::Next(next) => match next {
Next::None => (),
Next::Target(new_target) => self.transition_quiet(new_target, indent, false),
Next::ExactTarget(new_target) => {
self.transition_quiet(new_target, indent, true)
}
},
}
}
pub fn transition(&mut self, target: T, indent: bool, exact: bool) {
if indent {
info!(
"\u{02502}Transitioning from {:?} to {target:?}",
self.state(),
);
} else {
info!(
"{}: Transitioning from {:?} to {target:?}",
self.name(),
self.state(),
);
}
if !exact && self.state_matches(target) {
info!(
"{}\u{02502}Already in {target:?}",
if indent { "\u{02502}" } else { "" },
);
} else {
self.transition_quiet(target, indent, exact);
}
info!(
"{}\u{02514}Transition complete",
if indent { "\u{02502}" } else { "" },
);
}
pub fn state(&self) -> T {
self.node.current_state()
}
pub fn name(&self) -> &str {
#[cfg(feature = "std")]
return &self.name;
#[cfg(not(feature = "std"))]
return self.name;
}
#[cfg(feature = "std")]
pub fn set_name(&mut self, name: String) {
self.name = name;
}
pub fn state_matches(&self, state: T) -> bool {
self.node.state_matches(state)
}
pub fn handle_event(&mut self, event: &U) {
info!("{}: Handling event", self.name());
match self.node.handle_event(&mut TopContext(PhantomData), event) {
Response::Drop => (),
Response::Next(next) => match next {
Next::None => (),
Next::Target(target) => self.transition(target, true, false),
Next::ExactTarget(target) => self.transition(target, true, true),
},
}
info!("\u{02514}Event handled");
}
}
}