Skip to main content

Decision

Struct Decision 

Source
#[non_exhaustive]
pub struct Decision<M, C, E = Infallible> { /* private fields */ }
Expand description

The outcome of processing an event through Machine::on_event.

A Decision captures two orthogonal concerns:

  • Whether the system should change mode (ModeChange).
  • What commands the runtime should execute.

Decisions are pure data. They describe intent, not side effects. The runtime is responsible for interpreting and executing them.

§Examples

use ready_active_safe::Decision;

#[derive(Debug, Clone, PartialEq, Eq)]
enum Mode { Ready, Active }

// Stay in the current mode with no commands
let stay: Decision<Mode, &str, ()> = Decision::stay();
assert!(stay.target_mode().is_none());

// Transition to a new mode
let go: Decision<Mode, &str, ()> = Decision::transition(Mode::Active);
assert_eq!(go.target_mode(), Some(&Mode::Active));

Implementations§

Source§

impl<M, C, E> Decision<M, C, E>

Source

pub const fn stay() -> Self

Creates a decision that stays in the current mode with no commands.

§Examples
use ready_active_safe::Decision;

let d: Decision<(), (), ()> = Decision::stay();
assert!(d.mode_change().is_none());
assert!(d.commands().is_empty());
Source

pub const fn transition(target: M) -> Self

Creates a decision that transitions to the given mode.

§Examples
use ready_active_safe::Decision;

let d: Decision<&str, (), ()> = Decision::transition("active");
assert_eq!(d.mode_change().map(|mc| mc.target), Some("active"));
Source

pub const fn mode_change(&self) -> Option<&ModeChange<M>>

Returns the mode change, if any.

§Examples
use ready_active_safe::Decision;

let d: Decision<&str, (), ()> = Decision::stay();
assert!(d.mode_change().is_none());
Source

pub const fn target_mode(&self) -> Option<&M>

Returns the target mode, if this decision requests a transition.

This is a convenience wrapper around Decision::mode_change for the common case where you only care about the target.

§Examples
use ready_active_safe::Decision;

#[derive(Debug, Clone, PartialEq, Eq)]
enum Mode { Ready, Active, Safe }

let d: Decision<Mode, (), ()> = Decision::transition(Mode::Active);
assert_eq!(d.target_mode(), Some(&Mode::Active));
Source

pub fn commands(&self) -> &[C]

Returns the commands to execute.

§Examples
use ready_active_safe::Decision;

let d: Decision<(), (), ()> = Decision::stay();
assert!(d.commands().is_empty());
Source

pub const fn is_transition(&self) -> bool

Returns true if this decision requests a mode transition.

§Examples
use ready_active_safe::Decision;

let d: Decision<&str, (), ()> = Decision::transition("active");
assert!(d.is_transition());

let s: Decision<&str, (), ()> = Decision::stay();
assert!(!s.is_transition());
Source

pub const fn is_stay(&self) -> bool

Returns true if this decision stays in the current mode.

§Examples
use ready_active_safe::Decision;

let d: Decision<&str, (), ()> = Decision::stay();
assert!(d.is_stay());

let t: Decision<&str, (), ()> = Decision::transition("active");
assert!(!t.is_stay());
Source

pub fn into_parts(self) -> (Option<ModeChange<M>>, Vec<C>)

Consumes the decision and returns its parts.

This is useful in runtimes that own the current mode and want to move the next-mode target and commands out of the decision without cloning.

§Examples
use ready_active_safe::Decision;

let d: Decision<&str, &str, ()> =
    Decision::transition("active").with_command("initialize");

let (change, commands) = d.into_parts();
assert_eq!(change.map(|mc| mc.target), Some("active"));
assert_eq!(commands, vec!["initialize"]);
Source

pub fn into_mode_change(self) -> Option<ModeChange<M>>

Consumes the decision and returns the requested mode change, if any.

Prefer Decision::into_parts if you also need the commands.

Source

pub fn into_commands(self) -> Vec<C>

Consumes the decision and returns its commands.

Prefer Decision::into_parts if you also need the mode change.

Source

pub fn with_command(self, command: C) -> Self

Adds a command to this decision.

§Examples
use ready_active_safe::Decision;

let d: Decision<(), &str, ()> = Decision::stay()
    .with_command("initialize");
assert_eq!(d.commands(), &["initialize"]);
Source

pub fn emit(self, command: C) -> Self

Adds a command to this decision.

This is an alias for Decision::with_command. It can read better in match arms:

use ready_active_safe::Decision;

let d: Decision<(), &str, ()> = Decision::stay().emit("log");
assert_eq!(d.commands(), &["log"]);
Examples found in repository?
examples/runner.rs (line 53)
47    fn on_event(&self, mode: &Mode, event: &Event) -> Decision<Mode, Command> {
48        use Command::*;
49        use Event::*;
50        use Mode::*;
51
52        match (mode, event) {
53            (Ready, Start) => transition(Active).emit(Init),
54            (Active, Reset) => transition(Ready),
55            (Active, Fault) => transition(Safe).emit(Shutdown),
56            _ => ignore(),
57        }
58    }
More examples
Hide additional examples
examples/metrics.rs (line 58)
52    fn on_event(&self, mode: &Mode, event: &Event) -> Decision<Mode, Command> {
53        use Command::*;
54        use Event::*;
55        use Mode::*;
56
57        match (mode, event) {
58            (Ready, Start) => transition(Active).emit(Initialize),
59            (Active, Fault) => transition(Safe).emit(EmergencyStop),
60            (Safe, Recover) => transition(Ready).emit(Reset),
61            _ => ignore(),
62        }
63    }
examples/recovery_cycle.rs (line 55)
49    fn on_event(&self, mode: &Mode, event: &Event) -> Decision<Mode, Command> {
50        use Command::*;
51        use Event::*;
52        use Mode::*;
53
54        match (mode, event) {
55            (Ready, Activate) => transition(Active).emit(StartWork),
56            (Active, Fault) => transition(Safe).emit(EnterSafeState),
57            (Safe, RecoveryComplete) => transition(Ready).emit(ResetHardware),
58            _ => ignore(),
59        }
60    }
examples/channel_runtime.rs (line 61)
54    fn on_event(&self, mode: &Mode, event: &Event) -> Decision<Mode, Command> {
55        use Command::*;
56        use Event::*;
57        use Mode::*;
58
59        match (mode, event) {
60            (Initializing, Tick) => transition(Sampling),
61            (Sampling, Measurement(val)) => stay().emit(RecordSample(*val)),
62            (Sampling, Tick) => stay().emit(LogTick),
63            (Sampling, Fault) => transition(Safe).emit(TriggerAlarm),
64            _ => ignore(),
65        }
66    }
examples/basic.rs (line 59)
49    fn on_event(
50        &self,
51        mode: &Self::Mode,
52        event: &Self::Event,
53    ) -> Decision<Self::Mode, Self::Command> {
54        use Command::*;
55        use Event::*;
56        use Mode::*;
57
58        match (mode, event) {
59            (Ready, Initialize) => stay().emit(RunDiagnostics),
60            (Ready, Start) => transition(Active).emit(BeginProcessing),
61            (Active, Fault) => transition(Safe).emit_all([FlushBuffers, NotifyOperator]),
62            _ => ignore(),
63        }
64    }
examples/replay.rs (line 65)
55    fn on_event(
56        &self,
57        mode: &Self::Mode,
58        event: &Self::Event,
59    ) -> Decision<Self::Mode, Self::Command> {
60        use Command::*;
61        use Event::*;
62        use Mode::*;
63
64        match (mode, event) {
65            (Ready, PowerOn) => stay().emit(InitializeSensors),
66            (Ready, BeginMeasurement) => transition(Active).emit(StartDataAcquisition),
67            (Active, DataCollected) => stay().emit(StoreReading),
68            (Active, AnomalyDetected) => transition(Safe).emit(TriggerAlarm),
69            (Safe, OperatorReset) => transition(Ready).emit_all([ClearAlarm, RecalibrateSensors]),
70            _ => ignore(),
71        }
72    }
Source

pub fn with_commands<I>(self, commands: I) -> Self
where I: IntoIterator<Item = C>,

Adds multiple commands to this decision.

Commands are appended in iteration order.

§Examples
use ready_active_safe::Decision;

#[derive(Debug, Clone, PartialEq, Eq)]
enum Command { Initialize, Begin }

let d: Decision<(), Command, ()> =
    Decision::stay().with_commands([Command::Initialize, Command::Begin]);
assert_eq!(d.commands(), &[Command::Initialize, Command::Begin]);
Source

pub fn emit_all<I>(self, commands: I) -> Self
where I: IntoIterator<Item = C>,

Adds multiple commands to this decision.

This is an alias for Decision::with_commands.

use ready_active_safe::Decision;

let d: Decision<(), &str, ()> = Decision::stay().emit_all(["a", "b"]);
assert_eq!(d.commands(), &["a", "b"]);
Examples found in repository?
examples/basic.rs (line 61)
49    fn on_event(
50        &self,
51        mode: &Self::Mode,
52        event: &Self::Event,
53    ) -> Decision<Self::Mode, Self::Command> {
54        use Command::*;
55        use Event::*;
56        use Mode::*;
57
58        match (mode, event) {
59            (Ready, Initialize) => stay().emit(RunDiagnostics),
60            (Ready, Start) => transition(Active).emit(BeginProcessing),
61            (Active, Fault) => transition(Safe).emit_all([FlushBuffers, NotifyOperator]),
62            _ => ignore(),
63        }
64    }
More examples
Hide additional examples
examples/replay.rs (line 69)
55    fn on_event(
56        &self,
57        mode: &Self::Mode,
58        event: &Self::Event,
59    ) -> Decision<Self::Mode, Self::Command> {
60        use Command::*;
61        use Event::*;
62        use Mode::*;
63
64        match (mode, event) {
65            (Ready, PowerOn) => stay().emit(InitializeSensors),
66            (Ready, BeginMeasurement) => transition(Active).emit(StartDataAcquisition),
67            (Active, DataCollected) => stay().emit(StoreReading),
68            (Active, AnomalyDetected) => transition(Safe).emit(TriggerAlarm),
69            (Safe, OperatorReset) => transition(Ready).emit_all([ClearAlarm, RecalibrateSensors]),
70            _ => ignore(),
71        }
72    }
examples/recovery.rs (line 67)
55    fn on_event(
56        &self,
57        mode: &Self::Mode,
58        event: &Self::Event,
59    ) -> Decision<Self::Mode, Self::Command> {
60        use Command::*;
61        use Event::*;
62        use Mode::*;
63
64        match (mode, event) {
65            (Ready, Activate) => transition(Active).emit(StartProcessing),
66            (Active, TransientFault) => stay().emit(LogFault),
67            (Active, CriticalFault) => transition(Safe).emit_all([EmergencyStop, NotifyOperator]),
68            (Safe, RecoveryApproved) => stay().emit(RunRecoveryDiagnostics),
69            (Safe, DiagnosticsComplete) => transition(Ready).emit(ResetSubsystems),
70            (Active | Ready, Shutdown) => transition(Safe).emit(PowerDown),
71            _ => ignore(),
72        }
73    }
examples/openxr_session.rs (line 91)
75    fn on_event(
76        &self,
77        mode: &Self::Mode,
78        event: &Self::Event,
79    ) -> Decision<Self::Mode, Self::Command> {
80        use SessionMode::*;
81        use XrCommand::*;
82        use XrEvent::*;
83
84        match (mode, event) {
85            (Idle, SessionStateChanged(Ready)) => transition(Ready).emit(PrepareResources),
86            (Ready, SessionStateChanged(Synchronized)) => {
87                transition(Synchronized).emit(BeginFrameLoop)
88            }
89            (Synchronized, SessionStateChanged(Visible)) => transition(Visible),
90            (Visible, SessionStateChanged(Focused)) => transition(Focused).emit(PollInput),
91            (Focused, FrameReady) => stay().emit_all([SubmitFrame, PollInput]),
92            (Visible, FrameReady) => stay().emit(SubmitFrame),
93            (mode, RequestExit) if *mode != Idle && *mode != Stopping => {
94                transition(Stopping).emit_all([ReleaseResources, EndSession])
95            }
96            _ => ignore(),
97        }
98    }
Source

pub fn apply(self, current_mode: M) -> (M, Vec<C>)

Applies this decision to an owned current mode.

This is a method form of apply_decision. It tends to read well in runtime loops.

use ready_active_safe::prelude::*;

let mode = "ready";
let decision: Decision<&str, &str, ()> = transition("active").emit("initialize");

let (mode, commands) = decision.apply(mode);
assert_eq!(mode, "active");
assert_eq!(commands, vec!["initialize"]);
Examples found in repository?
examples/basic.rs (line 77)
67fn main() {
68    let controller = Controller;
69    let mut mode = controller.initial_mode();
70
71    let events = [Event::Initialize, Event::Start, Event::Fault];
72
73    for event in &events {
74        let decision = controller.decide(&mode, event);
75        println!("{mode:?} + {event:?} => {decision}");
76
77        let (next_mode, commands) = decision.apply(mode);
78        if !commands.is_empty() {
79            println!("  commands: {commands:?}");
80        }
81
82        mode = next_mode;
83    }
84
85    assert_eq!(mode, Mode::Safe);
86}
More examples
Hide additional examples
examples/openxr_session.rs (line 119)
101fn main() {
102    let session = XrSession;
103    let mut mode = session.initial_mode();
104
105    let events = [
106        XrEvent::SessionStateChanged(SessionMode::Ready),
107        XrEvent::SessionStateChanged(SessionMode::Synchronized),
108        XrEvent::SessionStateChanged(SessionMode::Visible),
109        XrEvent::SessionStateChanged(SessionMode::Focused),
110        XrEvent::FrameReady,
111        XrEvent::FrameReady,
112        XrEvent::RequestExit,
113    ];
114
115    for event in &events {
116        let decision = session.decide(&mode, event);
117        println!("{mode:?} + {event:?} => {decision}");
118
119        let (next_mode, commands) = decision.apply(mode);
120        if !commands.is_empty() {
121            println!("  commands: {commands:?}");
122        }
123
124        mode = next_mode;
125    }
126
127    assert_eq!(mode, SessionMode::Stopping);
128}
Source

pub fn apply_checked<P>( self, current_mode: M, policy: &P, ) -> Result<(M, Vec<C>), LifecycleError<M>>
where P: Policy<M>,

Applies this decision after checking a policy.

This is a method form of apply_decision_checked. If the policy denies the transition, the mode is unchanged and commands are dropped.

§Examples
use ready_active_safe::prelude::*;

#[derive(Debug, Clone, PartialEq, Eq)]
enum Mode { Ready, Active, Safe }

struct ForwardOnly;
impl Policy<Mode> for ForwardOnly {
    fn is_allowed(&self, from: &Mode, to: &Mode) -> bool {
        matches!((from, to), (Mode::Ready, Mode::Active) | (Mode::Active, Mode::Safe))
    }
}

let policy = ForwardOnly;

// Allowed transition
let d: Decision<Mode, &str> = transition(Mode::Active).emit("go");
let (mode, cmds) = d.apply_checked(Mode::Ready, &policy).unwrap();
assert_eq!(mode, Mode::Active);
assert_eq!(cmds, vec!["go"]);

// Denied transition: mode unchanged, commands dropped
let d: Decision<Mode, &str> = transition(Mode::Ready).emit("back");
let err = d.apply_checked(Mode::Active, &policy).unwrap_err();
assert!(matches!(err, LifecycleError::TransitionDenied { .. }));
§Errors

Returns LifecycleError::TransitionDenied if the policy rejects the requested mode change.

Examples found in repository?
examples/recovery.rs (line 116)
97fn main() -> Result<(), LifecycleError<Mode>> {
98    let controller = SafetyController;
99    let policy = LifecyclePolicy;
100    let mut mode = controller.initial_mode();
101
102    let events = [
103        Event::Activate,
104        Event::TransientFault,
105        Event::CriticalFault,
106        Event::RecoveryApproved,
107        Event::DiagnosticsComplete,
108        Event::Activate,
109        Event::Shutdown,
110    ];
111
112    for event in &events {
113        let decision = controller.decide(&mode, event);
114        println!("{mode:?} + {event:?} => {decision}");
115
116        match decision.apply_checked(mode, &policy) {
117            Ok((next_mode, commands)) => {
118                if !commands.is_empty() {
119                    println!("  commands: {commands:?}");
120                }
121                mode = next_mode;
122            }
123            Err(LifecycleError::TransitionDenied { from, to, reason }) => {
124                println!("  denied: {from:?} -> {to:?} ({reason})");
125                mode = from;
126            }
127            Err(err) => return Err(err),
128        }
129    }
130
131    assert!(!policy.is_allowed(&Mode::Active, &Mode::Ready));
132    assert!(!policy.is_allowed(&Mode::Safe, &Mode::Active));
133    assert_eq!(mode, Mode::Safe);
134
135    Ok(())
136}

Trait Implementations§

Source§

impl<M: Clone, C: Clone, E: Clone> Clone for Decision<M, C, E>

Source§

fn clone(&self) -> Decision<M, C, 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<M: Debug, C: Debug, E: Debug> Debug for Decision<M, C, E>

Source§

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

Formats the value using the given formatter. Read more
Source§

impl<M: Display, C, E> Display for Decision<M, C, E>

Source§

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

Formats the value using the given formatter. Read more
Source§

impl<M: PartialEq, C: PartialEq, E: PartialEq> PartialEq for Decision<M, C, E>

Source§

fn eq(&self, other: &Decision<M, C, E>) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, and should not be overridden without very good reason.
Source§

impl<M: Eq, C: Eq, E: Eq> Eq for Decision<M, C, E>

Source§

impl<M, C, E> StructuralPartialEq for Decision<M, C, E>

Auto Trait Implementations§

§

impl<M, C, E> Freeze for Decision<M, C, E>
where M: Freeze,

§

impl<M, C, E> RefUnwindSafe for Decision<M, C, E>

§

impl<M, C, E> Send for Decision<M, C, E>
where E: Send, M: Send, C: Send,

§

impl<M, C, E> Sync for Decision<M, C, E>
where E: Sync, M: Sync, C: Sync,

§

impl<M, C, E> Unpin for Decision<M, C, E>
where E: Unpin, M: Unpin, C: Unpin,

§

impl<M, C, E> UnsafeUnpin for Decision<M, C, E>
where M: UnsafeUnpin,

§

impl<M, C, E> UnwindSafe for Decision<M, C, E>
where E: UnwindSafe, M: UnwindSafe, C: 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> 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> ToString for T
where T: Display + ?Sized,

Source§

fn to_string(&self) -> String

Converts the given value to a String. 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.