Skip to main content

zerodds_rtc/
lifecycle.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 ZeroDDS Contributors
3
4//! Lifecycle-Modell — 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, S. 19) — Lifecycle-States eines
9/// RTC innerhalb eines `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, S. 30-31) — definiert wann der
28/// Execution-Context die Component-Callbacks invoked.
29#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
30pub enum ExecutionKind {
31    /// `PERIODIC` (§5.2.2.7.1) — `on_execute` + `on_state_update` an
32    /// jedem Tick.
33    Periodic,
34    /// `EVENT_DRIVEN` (§5.2.2.7.2) — `on_action` bei diskretem Event.
35    EventDriven,
36    /// `OTHER` (§5.2.2.7.3) — Implementor-spezifisch.
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/// Default-Implementations geben `ReturnCode::Ok` zurueck — Anwender
47/// implementieren nur die Callbacks, die fuer ihre Domaene relevant
48/// sind (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 wenn `ExecutionKind::Periodic`).
85    fn on_error(&mut self, _exec_handle: u32) -> ReturnCode {
86        ReturnCode::Ok
87    }
88    /// Spec §5.2.2.4.9 — Reset-Versuch aus Error-State zurueck zu
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) — gueltige State-Transitions im
96/// Lifecycle. Liefert `true` wenn die Transition nach Spec erlaubt
97/// ist, sonst `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` automatisch bei `on_*`-Return != Ok.
104/// * `Error → Inactive` via `reset_component` (wenn `on_reset`
105///   `ReturnCode::Ok` liefert).
106/// * Keine Transition aus `Created` ausser 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        // Sicher unterschiedlich.
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 wird repeatedly fuer Error-State
185        // invoked.
186        assert!(is_valid_transition(
187            LifeCycleState::Error,
188            LifeCycleState::Error
189        ));
190    }
191}