Skip to main content

zerodds_rtc/
lifecycle.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 ZeroDDS Contributors
3
4//! Lifecycle model — spec §5.2.2.3 / §5.2.2.4 / §5.2.2.7.
5
6use crate::return_code::ReturnCode;
7
8/// `LifeCycleState` (spec §5.2.2.3, p. 19) — lifecycle states of an
9/// RTC within an `ExecutionContext`.
10///
11/// Spec-Zitat (§5.2.2.3.1-§5.2.2.3.4): "CREATED — instantiated but not
12/// yet initialized; INACTIVE — Alive but not invoked; ACTIVE — Alive
13/// and will be invoked when context is Running; ERROR — encountered
14/// problem and cannot continue without reset."
15#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
16pub enum LifeCycleState {
17    /// `CREATED` (§5.2.2.3.1).
18    Created,
19    /// `INACTIVE` (§5.2.2.3.2).
20    Inactive,
21    /// `ACTIVE` (§5.2.2.3.3).
22    Active,
23    /// `ERROR` (§5.2.2.3.4).
24    Error,
25}
26
27/// `ExecutionKind` (spec §5.2.2.7, p. 30-31) — defines when the
28/// execution context invokes the component callbacks.
29#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
30pub enum ExecutionKind {
31    /// `PERIODIC` (§5.2.2.7.1) — `on_execute` + `on_state_update` on
32    /// every tick.
33    Periodic,
34    /// `EVENT_DRIVEN` (§5.2.2.7.2) — `on_action` on a discrete event.
35    EventDriven,
36    /// `OTHER` (§5.2.2.7.3) — implementor-specific.
37    Other,
38}
39
40/// `ComponentAction` Trait — Spec §5.2.2.4 (S. 20-21).
41///
42/// Spec-Zitat: "ComponentAction interface provides callbacks
43/// corresponding to the execution of the lifecycle operations of
44/// LightweightRTObject and ExecutionContext."
45///
46/// The default implementations return `ReturnCode::Ok` — users
47/// implement only the callbacks relevant to their domain
48/// (spec §5.2.2.4: "An RTC developer may implement these callback
49/// operations in order to execute application-specific logic").
50pub trait ComponentAction {
51    /// Spec §5.2.2.4.1 — RTC has been initialized and entered Alive
52    /// state.
53    fn on_initialize(&mut self) -> ReturnCode {
54        ReturnCode::Ok
55    }
56    /// Spec §5.2.2.4.2 — RTC is being destroyed.
57    fn on_finalize(&mut self) -> ReturnCode {
58        ReturnCode::Ok
59    }
60    /// Spec §5.2.2.4.3 — Execution-Context transitioned Stopped →
61    /// Running.
62    fn on_startup(&mut self, _exec_handle: u32) -> ReturnCode {
63        ReturnCode::Ok
64    }
65    /// Spec §5.2.2.4.4 — Execution-Context transitioned Running →
66    /// Stopped.
67    fn on_shutdown(&mut self, _exec_handle: u32) -> ReturnCode {
68        ReturnCode::Ok
69    }
70    /// Spec §5.2.2.4.5 — RTC has been activated.
71    fn on_activated(&mut self, _exec_handle: u32) -> ReturnCode {
72        ReturnCode::Ok
73    }
74    /// Spec §5.2.2.4.6 — RTC has been deactivated.
75    fn on_deactivated(&mut self, _exec_handle: u32) -> ReturnCode {
76        ReturnCode::Ok
77    }
78    /// Spec §5.2.2.4.7 — RTC transitions Active → Error (single
79    /// invocation).
80    fn on_aborting(&mut self, _exec_handle: u32) -> ReturnCode {
81        ReturnCode::Ok
82    }
83    /// Spec §5.2.2.4.8 — RTC remains in Error state (repeated per
84    /// tick when `ExecutionKind::Periodic`).
85    fn on_error(&mut self, _exec_handle: u32) -> ReturnCode {
86        ReturnCode::Ok
87    }
88    /// Spec §5.2.2.4.9 — reset attempt from the error state back to
89    /// inactive.
90    fn on_reset(&mut self, _exec_handle: u32) -> ReturnCode {
91        ReturnCode::Ok
92    }
93}
94
95/// Spec §5.2.2.3 (Figure 5.4 + 5.5) — valid state transitions in the
96/// lifecycle. Returns `true` if the transition is allowed per spec,
97/// otherwise `false`.
98///
99/// Transitions:
100/// * `Created → Inactive` via `initialize`/`attach_context`.
101/// * `Inactive → Active` via `activate_component`.
102/// * `Active → Inactive` via `deactivate_component`.
103/// * `Active → Error` automatically on `on_*` return != Ok.
104/// * `Error → Inactive` via `reset_component` (when `on_reset`
105///   returns `ReturnCode::Ok`).
106/// * No transition out of `Created` except via `initialize`.
107#[must_use]
108pub const fn is_valid_transition(from: LifeCycleState, to: LifeCycleState) -> bool {
109    use LifeCycleState::{Active, Created, Error, Inactive};
110    matches!(
111        (from, to),
112        (Created, Inactive)
113            | (Inactive, Active)
114            | (Active, Inactive)
115            | (Active, Error)
116            | (Error, Inactive)
117            | (Error, Error)
118    )
119}
120
121#[cfg(test)]
122mod tests {
123    use super::*;
124
125    #[test]
126    fn valid_transitions_match_spec_state_machine() {
127        // Spec §5.2.2.3 Fig 5.4 + §5.2.2.5 Fig 5.5.
128        for (from, to, expected) in [
129            (LifeCycleState::Created, LifeCycleState::Inactive, true),
130            (LifeCycleState::Inactive, LifeCycleState::Active, true),
131            (LifeCycleState::Active, LifeCycleState::Inactive, true),
132            (LifeCycleState::Active, LifeCycleState::Error, true),
133            (LifeCycleState::Error, LifeCycleState::Inactive, true),
134            // Forbidden:
135            (LifeCycleState::Created, LifeCycleState::Active, false),
136            (LifeCycleState::Created, LifeCycleState::Error, false),
137            (LifeCycleState::Inactive, LifeCycleState::Created, false),
138            (LifeCycleState::Inactive, LifeCycleState::Error, false),
139            (LifeCycleState::Active, LifeCycleState::Created, false),
140            (LifeCycleState::Error, LifeCycleState::Active, false),
141            (LifeCycleState::Error, LifeCycleState::Created, false),
142        ] {
143            assert_eq!(
144                is_valid_transition(from, to),
145                expected,
146                "{from:?} -> {to:?}"
147            );
148        }
149    }
150
151    #[test]
152    fn default_component_action_returns_ok_for_all_callbacks() {
153        // Spec §5.2.2.4 — Default callbacks are no-ops returning OK.
154        struct Stub;
155        impl ComponentAction for Stub {}
156        let mut s = Stub;
157        assert_eq!(s.on_initialize(), ReturnCode::Ok);
158        assert_eq!(s.on_finalize(), ReturnCode::Ok);
159        assert_eq!(s.on_startup(0), ReturnCode::Ok);
160        assert_eq!(s.on_shutdown(0), ReturnCode::Ok);
161        assert_eq!(s.on_activated(0), ReturnCode::Ok);
162        assert_eq!(s.on_deactivated(0), ReturnCode::Ok);
163        assert_eq!(s.on_aborting(0), ReturnCode::Ok);
164        assert_eq!(s.on_error(0), ReturnCode::Ok);
165        assert_eq!(s.on_reset(0), ReturnCode::Ok);
166    }
167
168    #[test]
169    fn execution_kind_distinguishes_three_modes() {
170        // Spec §5.2.2.7.
171        let kinds = [
172            ExecutionKind::Periodic,
173            ExecutionKind::EventDriven,
174            ExecutionKind::Other,
175        ];
176        // Definitely distinct.
177        assert_ne!(kinds[0], kinds[1]);
178        assert_ne!(kinds[1], kinds[2]);
179        assert_ne!(kinds[0], kinds[2]);
180    }
181
182    #[test]
183    fn error_self_loop_allowed() {
184        // Spec §5.2.2.4.8: on_error is invoked repeatedly for the error
185        // state.
186        assert!(is_valid_transition(
187            LifeCycleState::Error,
188            LifeCycleState::Error
189        ));
190    }
191}