#[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>
impl<M, C, E> Decision<M, C, E>
Sourcepub const fn stay() -> Self
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());Sourcepub const fn transition(target: M) -> Self
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"));Sourcepub const fn mode_change(&self) -> Option<&ModeChange<M>>
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());Sourcepub const fn target_mode(&self) -> Option<&M>
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));Sourcepub fn commands(&self) -> &[C]
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());Sourcepub const fn is_transition(&self) -> bool
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());Sourcepub const fn is_stay(&self) -> bool
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());Sourcepub fn into_parts(self) -> (Option<ModeChange<M>>, Vec<C>)
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"]);Sourcepub fn into_mode_change(self) -> Option<ModeChange<M>>
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.
Sourcepub fn into_commands(self) -> Vec<C>
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.
Sourcepub fn with_command(self, command: C) -> Self
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"]);Sourcepub fn emit(self, command: C) -> Self
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?
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
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 }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 }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 }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 }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 }Sourcepub fn with_commands<I>(self, commands: I) -> Selfwhere
I: IntoIterator<Item = C>,
pub fn with_commands<I>(self, commands: I) -> Selfwhere
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]);Sourcepub fn emit_all<I>(self, commands: I) -> Selfwhere
I: IntoIterator<Item = C>,
pub fn emit_all<I>(self, commands: I) -> Selfwhere
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?
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
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 }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 }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 }Sourcepub fn apply(self, current_mode: M) -> (M, Vec<C>)
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?
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
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}Sourcepub fn apply_checked<P>(
self,
current_mode: M,
policy: &P,
) -> Result<(M, Vec<C>), LifecycleError<M>>where
P: Policy<M>,
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?
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}