use state_machines::state_machine;
use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum MothershipStatus {
#[default]
Initializing,
Preflight,
Electing,
Prelaunch,
Docking,
Launching,
Running,
Mayday,
Draining,
Landed,
Failed,
Crashed,
}
impl MothershipStatus {
fn from_str(s: &str) -> Self {
match s {
"Initializing" => MothershipStatus::Initializing,
"Preflight" => MothershipStatus::Preflight,
"Electing" => MothershipStatus::Electing,
"Prelaunch" => MothershipStatus::Prelaunch,
"Docking" => MothershipStatus::Docking,
"Launching" => MothershipStatus::Launching,
"Running" => MothershipStatus::Running,
"Mayday" => MothershipStatus::Mayday,
"Draining" => MothershipStatus::Draining,
"Landed" => MothershipStatus::Landed,
"Failed" => MothershipStatus::Failed,
"Crashed" => MothershipStatus::Crashed,
_ => MothershipStatus::Initializing,
}
}
}
impl fmt::Display for MothershipStatus {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
MothershipStatus::Initializing => write!(f, "initializing"),
MothershipStatus::Preflight => write!(f, "preflight"),
MothershipStatus::Electing => write!(f, "electing"),
MothershipStatus::Prelaunch => write!(f, "prelaunch"),
MothershipStatus::Docking => write!(f, "docking"),
MothershipStatus::Launching => write!(f, "launching"),
MothershipStatus::Running => write!(f, "running"),
MothershipStatus::Mayday => write!(f, "mayday"),
MothershipStatus::Draining => write!(f, "draining"),
MothershipStatus::Landed => write!(f, "landed"),
MothershipStatus::Failed => write!(f, "failed"),
MothershipStatus::Crashed => write!(f, "crashed"),
}
}
}
state_machine! {
name: MothershipLifecycle,
context: (),
dynamic: true,
initial: Initializing,
states: [
Initializing,
Preflight,
Electing,
Prelaunch,
Docking,
Launching,
Running,
Mayday,
Draining,
Landed,
Failed,
Crashed,
],
events {
manifest_loaded {
transition: { from: Initializing, to: Preflight }
}
preflight_complete {
transition: { from: Preflight, to: Electing }
}
preflight_skipped {
transition: { from: [Initializing, Preflight], to: Electing }
}
election_complete {
transition: { from: Electing, to: Prelaunch }
}
election_skipped {
transition: { from: [Preflight, Initializing, Electing], to: Prelaunch }
}
prelaunch_complete {
transition: { from: Prelaunch, to: Docking }
}
prelaunch_skipped {
transition: { from: [Electing, Preflight, Prelaunch], to: Docking }
}
docking_complete {
transition: { from: Docking, to: Launching }
}
docking_skipped {
transition: { from: [Prelaunch, Electing, Docking], to: Launching }
}
launch_complete {
transition: { from: Launching, to: Running }
}
drain_started {
transition: { from: [Running, Mayday], to: Draining }
}
drain_complete {
transition: { from: Draining, to: Landed }
}
distress {
transition: { from: Running, to: Mayday }
}
stabilized {
transition: { from: Mayday, to: Running }
}
preflight_failed {
transition: { from: Preflight, to: Failed }
}
election_failed {
transition: { from: Electing, to: Failed }
}
prelaunch_failed {
transition: { from: Prelaunch, to: Failed }
}
docking_failed {
transition: { from: Docking, to: Failed }
}
launch_failed {
transition: { from: Launching, to: Failed }
}
crash {
transition: { from: [Running, Mayday, Draining], to: Crashed }
}
shutdown {
transition: { from: [Initializing, Preflight, Electing, Prelaunch, Docking, Launching, Running, Mayday, Draining], to: Landed }
}
abort {
transition: { from: [Initializing, Preflight, Electing, Prelaunch, Docking, Launching], to: Failed }
}
}
}
pub struct Lifecycle {
machine: DynamicMothershipLifecycle,
status: MothershipStatus,
}
macro_rules! transition_method {
($(#[$meta:meta])* $method:ident, $event:ident) => {
$(#[$meta])*
pub fn $method(&mut self) -> bool {
if self.machine.handle(MothershipLifecycleEvent::$event).is_ok() {
self.refresh_status();
true
} else {
false
}
}
};
}
impl Lifecycle {
pub fn new() -> Self {
let machine = DynamicMothershipLifecycle::new(());
let status = MothershipStatus::from_str(machine.current_state());
Self { machine, status }
}
pub fn status(&self) -> MothershipStatus {
self.status
}
fn refresh_status(&mut self) {
self.status = MothershipStatus::from_str(self.machine.current_state());
}
transition_method!(
manifest_loaded, ManifestLoaded
);
transition_method!(
preflight_complete, PreflightComplete
);
transition_method!(
preflight_skipped, PreflightSkipped
);
transition_method!(
election_complete, ElectionComplete
);
transition_method!(
election_skipped, ElectionSkipped
);
transition_method!(
prelaunch_complete, PrelaunchComplete
);
transition_method!(
prelaunch_skipped, PrelaunchSkipped
);
transition_method!(
docking_complete, DockingComplete
);
transition_method!(
docking_skipped, DockingSkipped
);
transition_method!(
launch_complete, LaunchComplete
);
transition_method!(
drain_started, DrainStarted
);
transition_method!(
drain_complete, DrainComplete
);
transition_method!(
distress, Distress
);
transition_method!(
stabilized, Stabilized
);
transition_method!(
preflight_failed, PreflightFailed
);
transition_method!(
election_failed, ElectionFailed
);
transition_method!(
prelaunch_failed, PrelaunchFailed
);
transition_method!(
docking_failed, DockingFailed
);
transition_method!(
launch_failed, LaunchFailed
);
transition_method!(
crash, Crash
);
transition_method!(
shutdown, Shutdown
);
transition_method!(
abort, Abort
);
pub fn is_running(&self) -> bool {
self.status == MothershipStatus::Running
}
pub fn is_mayday(&self) -> bool {
self.status == MothershipStatus::Mayday
}
pub fn is_operational(&self) -> bool {
matches!(
self.status,
MothershipStatus::Running | MothershipStatus::Mayday
)
}
pub fn is_terminal(&self) -> bool {
matches!(
self.status,
MothershipStatus::Landed | MothershipStatus::Failed | MothershipStatus::Crashed
)
}
pub fn is_starting(&self) -> bool {
matches!(
self.status,
MothershipStatus::Initializing
| MothershipStatus::Preflight
| MothershipStatus::Electing
| MothershipStatus::Prelaunch
| MothershipStatus::Docking
| MothershipStatus::Launching
)
}
pub fn is_landed(&self) -> bool {
self.status == MothershipStatus::Landed
}
pub fn is_crashed(&self) -> bool {
self.status == MothershipStatus::Crashed
}
pub fn is_failed(&self) -> bool {
self.status == MothershipStatus::Failed
}
}
impl Default for Lifecycle {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_initial_state() {
let lifecycle = Lifecycle::new();
assert_eq!(lifecycle.status(), MothershipStatus::Initializing);
assert!(lifecycle.is_starting());
assert!(!lifecycle.is_running());
assert!(!lifecycle.is_terminal());
}
#[test]
fn test_normal_flow() {
let mut lifecycle = Lifecycle::new();
assert!(lifecycle.manifest_loaded());
assert_eq!(lifecycle.status(), MothershipStatus::Preflight);
assert!(lifecycle.preflight_complete());
assert_eq!(lifecycle.status(), MothershipStatus::Electing);
assert!(lifecycle.election_complete());
assert_eq!(lifecycle.status(), MothershipStatus::Prelaunch);
assert!(lifecycle.prelaunch_complete());
assert_eq!(lifecycle.status(), MothershipStatus::Docking);
assert!(lifecycle.docking_complete());
assert_eq!(lifecycle.status(), MothershipStatus::Launching);
assert!(lifecycle.launch_complete());
assert_eq!(lifecycle.status(), MothershipStatus::Running);
assert!(lifecycle.is_running());
assert!(lifecycle.is_operational());
assert!(lifecycle.drain_started());
assert_eq!(lifecycle.status(), MothershipStatus::Draining);
assert!(lifecycle.drain_complete());
assert_eq!(lifecycle.status(), MothershipStatus::Landed);
assert!(lifecycle.is_terminal());
assert!(lifecycle.is_landed());
}
#[test]
fn test_skip_preflight() {
let mut lifecycle = Lifecycle::new();
assert!(lifecycle.preflight_skipped());
assert_eq!(lifecycle.status(), MothershipStatus::Electing);
}
#[test]
fn test_skip_election() {
let mut lifecycle = Lifecycle::new();
assert!(lifecycle.manifest_loaded());
assert!(lifecycle.election_skipped());
assert_eq!(lifecycle.status(), MothershipStatus::Prelaunch);
}
#[test]
fn test_skip_prelaunch() {
let mut lifecycle = Lifecycle::new();
assert!(lifecycle.preflight_skipped());
assert!(lifecycle.election_complete());
assert!(lifecycle.prelaunch_skipped());
assert_eq!(lifecycle.status(), MothershipStatus::Docking);
}
#[test]
fn test_preflight_failure() {
let mut lifecycle = Lifecycle::new();
assert!(lifecycle.manifest_loaded());
assert!(lifecycle.preflight_failed());
assert_eq!(lifecycle.status(), MothershipStatus::Failed);
assert!(lifecycle.is_terminal());
assert!(lifecycle.is_failed());
}
#[test]
fn test_prelaunch_failure() {
let mut lifecycle = Lifecycle::new();
lifecycle.preflight_skipped();
lifecycle.election_complete();
assert!(lifecycle.prelaunch_failed());
assert_eq!(lifecycle.status(), MothershipStatus::Failed);
}
#[test]
fn test_mayday_and_recovery() {
let mut lifecycle = Lifecycle::new();
lifecycle.preflight_skipped();
lifecycle.election_skipped();
lifecycle.prelaunch_skipped();
lifecycle.docking_complete();
lifecycle.launch_complete();
assert!(lifecycle.is_running());
assert!(lifecycle.distress());
assert_eq!(lifecycle.status(), MothershipStatus::Mayday);
assert!(lifecycle.is_mayday());
assert!(lifecycle.is_operational()); assert!(!lifecycle.is_running());
assert!(lifecycle.stabilized());
assert_eq!(lifecycle.status(), MothershipStatus::Running);
assert!(lifecycle.is_running());
}
#[test]
fn test_crash_from_running() {
let mut lifecycle = Lifecycle::new();
lifecycle.preflight_skipped();
lifecycle.election_skipped();
lifecycle.prelaunch_skipped();
lifecycle.docking_complete();
lifecycle.launch_complete();
assert!(lifecycle.is_running());
assert!(lifecycle.crash());
assert_eq!(lifecycle.status(), MothershipStatus::Crashed);
assert!(lifecycle.is_crashed());
assert!(lifecycle.is_terminal());
}
#[test]
fn test_crash_from_mayday() {
let mut lifecycle = Lifecycle::new();
lifecycle.preflight_skipped();
lifecycle.election_skipped();
lifecycle.prelaunch_skipped();
lifecycle.docking_complete();
lifecycle.launch_complete();
lifecycle.distress();
assert!(lifecycle.is_mayday());
assert!(lifecycle.crash());
assert_eq!(lifecycle.status(), MothershipStatus::Crashed);
}
#[test]
fn test_drain_from_mayday() {
let mut lifecycle = Lifecycle::new();
lifecycle.preflight_skipped();
lifecycle.election_skipped();
lifecycle.prelaunch_skipped();
lifecycle.docking_complete();
lifecycle.launch_complete();
lifecycle.distress();
assert!(lifecycle.drain_started());
assert_eq!(lifecycle.status(), MothershipStatus::Draining);
}
#[test]
fn test_abort_during_startup() {
let mut lifecycle = Lifecycle::new();
lifecycle.manifest_loaded();
assert!(lifecycle.abort());
assert_eq!(lifecycle.status(), MothershipStatus::Failed);
assert!(lifecycle.is_failed());
}
#[test]
fn test_shutdown_from_any_state() {
let mut lifecycle = Lifecycle::new();
lifecycle.manifest_loaded();
assert!(lifecycle.shutdown());
assert_eq!(lifecycle.status(), MothershipStatus::Landed);
assert!(lifecycle.is_landed());
}
#[test]
fn test_invalid_transition() {
let mut lifecycle = Lifecycle::new();
assert!(!lifecycle.preflight_complete());
assert_eq!(lifecycle.status(), MothershipStatus::Initializing);
}
#[test]
fn test_status_display() {
assert_eq!(MothershipStatus::Running.to_string(), "running");
assert_eq!(MothershipStatus::Preflight.to_string(), "preflight");
assert_eq!(MothershipStatus::Draining.to_string(), "draining");
assert_eq!(MothershipStatus::Mayday.to_string(), "mayday");
assert_eq!(MothershipStatus::Landed.to_string(), "landed");
assert_eq!(MothershipStatus::Crashed.to_string(), "crashed");
}
}