Skip to main content

zerodds_rtc/
execution.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 ZeroDDS Contributors
3
4//! `ExecutionContext` + `ExecutionContextOperations` — Spec §5.2.2.5
5//! / §5.2.2.6.
6
7use alloc::vec::Vec;
8
9use crate::lifecycle::{ExecutionKind, LifeCycleState};
10use crate::object::{ExecutionContextHandle, LightweightRtObject};
11use crate::return_code::ReturnCode;
12
13/// `ExecutionContext` Trait — Spec §5.2.2.5 (S. 22-23).
14///
15/// "An ExecutionContext allows the business logic of an RTC to be
16/// decoupled from the thread of control in which it is executed."
17///
18/// Diese Trait definiert die externe Sicht; konkrete Realisationen
19/// erben davon. Der Body der Trait ist die `ExecutionContextOperations`-
20/// Iface aus Spec §5.2.2.6.
21pub trait ExecutionContextOperations {
22    /// Spec §5.2.2.6.1 — `is_running`.
23    fn is_running(&self) -> bool;
24    /// Spec §5.2.2.6.2 — `start`. Stopped → Running.
25    fn start(&mut self) -> ReturnCode;
26    /// Spec §5.2.2.6.3 — `stop`. Running → Stopped.
27    fn stop(&mut self) -> ReturnCode;
28    /// Spec §5.2.2.6.4 — `get_rate`. Tick-Rate in Hz.
29    fn get_rate(&self) -> f64;
30    /// Spec §5.2.2.6.5 — `set_rate(rate)`.
31    ///
32    /// # Errors
33    /// `BAD_PARAMETER` wenn `rate <= 0.0` oder `NaN`.
34    fn set_rate(&mut self, rate: f64) -> ReturnCode;
35    /// Spec §5.2.2.6.6 — `add_component`.
36    ///
37    /// # Errors
38    /// Liefert `ReturnCode::Err` wenn das RTC nicht alive ist oder
39    /// schon im Context registriert.
40    fn add_component(
41        &mut self,
42        component: &mut LightweightRtObject,
43    ) -> Result<ExecutionContextHandle, ReturnCode>;
44    /// Spec §5.2.2.6.7 — `remove_component`.
45    fn remove_component(
46        &mut self,
47        component: &mut LightweightRtObject,
48        handle: ExecutionContextHandle,
49    ) -> ReturnCode;
50    /// Spec §5.2.2.6.8 — `activate_component`.
51    fn activate_component(
52        &mut self,
53        component: &mut LightweightRtObject,
54        handle: ExecutionContextHandle,
55    ) -> ReturnCode;
56    /// Spec §5.2.2.6.9 — `deactivate_component`.
57    fn deactivate_component(
58        &mut self,
59        component: &mut LightweightRtObject,
60        handle: ExecutionContextHandle,
61    ) -> ReturnCode;
62    /// Spec §5.2.2.6.10 — `reset_component`.
63    fn reset_component(
64        &mut self,
65        component: &mut LightweightRtObject,
66        handle: ExecutionContextHandle,
67    ) -> ReturnCode;
68    /// Spec §5.2.2.6.11 — `get_component_state`.
69    fn get_component_state(
70        &self,
71        component: &LightweightRtObject,
72        handle: ExecutionContextHandle,
73    ) -> LifeCycleState;
74    /// Spec §5.2.2.6.12 — `get_kind`.
75    fn get_kind(&self) -> ExecutionKind;
76}
77
78/// Konkrete `ExecutionContext`-Implementation — Spec §5.2.2.5
79/// (Local-PSM-Variante).
80///
81/// Verwaltet:
82/// * Running/Stopped-State (Spec Fig 5.7).
83/// * Tick-Rate (Hz, Spec §5.2.2.6.4-§5.2.2.6.5).
84/// * Liste der teilnehmenden RTCs (durch Handle gekennzeichnet —
85///   die RTCs selbst werden vom Caller gehalten).
86/// * `ExecutionKind` (Spec §5.2.2.7).
87pub struct ExecutionContext {
88    running: bool,
89    rate_hz: f64,
90    kind: ExecutionKind,
91    participants: Vec<ExecutionContextHandle>,
92}
93
94impl ExecutionContext {
95    /// Konstruiert ein neues, gestopptes `ExecutionContext` mit dem
96    /// gegebenen `ExecutionKind` und Default-Rate 1.0 Hz.
97    #[must_use]
98    pub const fn new(kind: ExecutionKind) -> Self {
99        Self {
100            running: false,
101            rate_hz: 1.0,
102            kind,
103            participants: Vec::new(),
104        }
105    }
106
107    /// Konstruiert mit expliziter Rate.
108    ///
109    /// # Errors
110    /// `Err(BAD_PARAMETER)` wenn `rate <= 0.0` oder `NaN`.
111    pub fn with_rate(kind: ExecutionKind, rate_hz: f64) -> Result<Self, ReturnCode> {
112        if !rate_hz.is_finite() || rate_hz <= 0.0 {
113            return Err(ReturnCode::BadParameter);
114        }
115        Ok(Self {
116            running: false,
117            rate_hz,
118            kind,
119            participants: Vec::new(),
120        })
121    }
122}
123
124impl core::fmt::Debug for ExecutionContext {
125    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
126        f.debug_struct("ExecutionContext")
127            .field("running", &self.running)
128            .field("rate_hz", &self.rate_hz)
129            .field("kind", &self.kind)
130            .field("participants", &self.participants)
131            .finish()
132    }
133}
134
135impl ExecutionContextOperations for ExecutionContext {
136    fn is_running(&self) -> bool {
137        self.running
138    }
139
140    fn start(&mut self) -> ReturnCode {
141        if self.running {
142            // Spec §5.2.2.6.2 — already running ist nicht expliziter
143            // Fehler, aber wir liefern `Ok` (idempotent).
144            return ReturnCode::Ok;
145        }
146        self.running = true;
147        ReturnCode::Ok
148    }
149
150    fn stop(&mut self) -> ReturnCode {
151        if !self.running {
152            return ReturnCode::Ok;
153        }
154        self.running = false;
155        ReturnCode::Ok
156    }
157
158    fn get_rate(&self) -> f64 {
159        self.rate_hz
160    }
161
162    fn set_rate(&mut self, rate: f64) -> ReturnCode {
163        if !rate.is_finite() || rate <= 0.0 {
164            return ReturnCode::BadParameter;
165        }
166        self.rate_hz = rate;
167        ReturnCode::Ok
168    }
169
170    fn add_component(
171        &mut self,
172        component: &mut LightweightRtObject,
173    ) -> Result<ExecutionContextHandle, ReturnCode> {
174        let handle = component.attach_context()?;
175        self.participants.push(handle);
176        Ok(handle)
177    }
178
179    fn remove_component(
180        &mut self,
181        component: &mut LightweightRtObject,
182        handle: ExecutionContextHandle,
183    ) -> ReturnCode {
184        let Some(idx) = self.participants.iter().position(|h| *h == handle) else {
185            return ReturnCode::BadParameter;
186        };
187        let rc = component.detach_context(handle);
188        if rc.is_ok() {
189            self.participants.swap_remove(idx);
190        }
191        rc
192    }
193
194    fn activate_component(
195        &mut self,
196        component: &mut LightweightRtObject,
197        handle: ExecutionContextHandle,
198    ) -> ReturnCode {
199        if !self.participants.contains(&handle) {
200            return ReturnCode::BadParameter;
201        }
202        component.activate(handle)
203    }
204
205    fn deactivate_component(
206        &mut self,
207        component: &mut LightweightRtObject,
208        handle: ExecutionContextHandle,
209    ) -> ReturnCode {
210        if !self.participants.contains(&handle) {
211            return ReturnCode::BadParameter;
212        }
213        component.deactivate(handle)
214    }
215
216    fn reset_component(
217        &mut self,
218        component: &mut LightweightRtObject,
219        handle: ExecutionContextHandle,
220    ) -> ReturnCode {
221        if !self.participants.contains(&handle) {
222            return ReturnCode::BadParameter;
223        }
224        component.reset(handle)
225    }
226
227    fn get_component_state(
228        &self,
229        component: &LightweightRtObject,
230        handle: ExecutionContextHandle,
231    ) -> LifeCycleState {
232        // Spec §5.2.2.6.11 — wenn Handle unbekannt, semantisch
233        // unklar; wir liefern Created als Default.
234        component
235            .get_context_state(handle)
236            .unwrap_or(LifeCycleState::Created)
237    }
238
239    fn get_kind(&self) -> ExecutionKind {
240        self.kind
241    }
242}
243
244#[cfg(test)]
245#[allow(clippy::expect_used)]
246mod tests {
247    use super::*;
248    use crate::lifecycle::ComponentAction;
249
250    struct NoOp;
251    impl ComponentAction for NoOp {}
252
253    fn rtc() -> LightweightRtObject {
254        LightweightRtObject::new(alloc::boxed::Box::new(NoOp))
255    }
256
257    #[test]
258    fn fresh_context_is_stopped_with_default_rate() {
259        // Spec §5.2.2.5 Fig 5.7 — Starts Stopped.
260        let ec = ExecutionContext::new(ExecutionKind::Periodic);
261        assert!(!ec.is_running());
262        assert!((ec.get_rate() - 1.0).abs() < f64::EPSILON);
263        assert_eq!(ec.get_kind(), ExecutionKind::Periodic);
264    }
265
266    #[test]
267    fn start_stop_round_trips_running_flag() {
268        // Spec §5.2.2.6.2 + §5.2.2.6.3.
269        let mut ec = ExecutionContext::new(ExecutionKind::Periodic);
270        assert_eq!(ec.start(), ReturnCode::Ok);
271        assert!(ec.is_running());
272        assert_eq!(ec.stop(), ReturnCode::Ok);
273        assert!(!ec.is_running());
274    }
275
276    #[test]
277    fn double_start_is_idempotent() {
278        let mut ec = ExecutionContext::new(ExecutionKind::Periodic);
279        ec.start();
280        assert_eq!(ec.start(), ReturnCode::Ok);
281    }
282
283    #[test]
284    fn set_rate_rejects_non_positive_or_nan() {
285        // Spec §5.2.2.6.5.
286        let mut ec = ExecutionContext::new(ExecutionKind::Periodic);
287        assert_eq!(ec.set_rate(0.0), ReturnCode::BadParameter);
288        assert_eq!(ec.set_rate(-1.0), ReturnCode::BadParameter);
289        assert_eq!(ec.set_rate(f64::NAN), ReturnCode::BadParameter);
290        assert_eq!(ec.set_rate(f64::INFINITY), ReturnCode::BadParameter);
291    }
292
293    #[test]
294    fn set_rate_accepts_positive_finite() {
295        let mut ec = ExecutionContext::new(ExecutionKind::Periodic);
296        assert_eq!(ec.set_rate(50.0), ReturnCode::Ok);
297        assert!((ec.get_rate() - 50.0).abs() < f64::EPSILON);
298    }
299
300    #[test]
301    fn with_rate_validates_rate_argument() {
302        assert!(ExecutionContext::with_rate(ExecutionKind::Periodic, 100.0).is_ok());
303        assert!(ExecutionContext::with_rate(ExecutionKind::Periodic, 0.0).is_err());
304        assert!(ExecutionContext::with_rate(ExecutionKind::Periodic, -1.0).is_err());
305    }
306
307    #[test]
308    fn add_component_attaches_and_returns_handle() {
309        // Spec §5.2.2.6.6 — invokes attach_context on RTC.
310        let mut ec = ExecutionContext::new(ExecutionKind::Periodic);
311        let mut r = rtc();
312        r.initialize();
313        let h = ec.add_component(&mut r).expect("attach");
314        assert_eq!(ec.get_component_state(&r, h), LifeCycleState::Inactive);
315    }
316
317    #[test]
318    fn add_uninitialized_component_fails() {
319        // Spec §5.2.2.6.6 + §5.2.2.2.5.
320        let mut ec = ExecutionContext::new(ExecutionKind::Periodic);
321        let mut r = rtc();
322        // Not initialized → attach fails.
323        assert!(ec.add_component(&mut r).is_err());
324    }
325
326    #[test]
327    fn activate_then_deactivate_round_trips_state() {
328        // Spec §5.2.2.6.8 + §5.2.2.6.9.
329        let mut ec = ExecutionContext::new(ExecutionKind::Periodic);
330        let mut r = rtc();
331        r.initialize();
332        let h = ec.add_component(&mut r).expect("attach");
333        assert_eq!(ec.activate_component(&mut r, h), ReturnCode::Ok);
334        assert_eq!(ec.get_component_state(&r, h), LifeCycleState::Active);
335        assert_eq!(ec.deactivate_component(&mut r, h), ReturnCode::Ok);
336        assert_eq!(ec.get_component_state(&r, h), LifeCycleState::Inactive);
337    }
338
339    #[test]
340    fn activate_with_unknown_handle_yields_bad_parameter() {
341        // Spec §5.2.2.6.8 — Handle muss zu diesem Context gehoeren.
342        let mut ec = ExecutionContext::new(ExecutionKind::Periodic);
343        let mut r = rtc();
344        r.initialize();
345        assert_eq!(
346            ec.activate_component(&mut r, 99_999),
347            ReturnCode::BadParameter
348        );
349    }
350
351    #[test]
352    fn remove_component_detaches_handle() {
353        // Spec §5.2.2.6.7.
354        let mut ec = ExecutionContext::new(ExecutionKind::Periodic);
355        let mut r = rtc();
356        r.initialize();
357        let h = ec.add_component(&mut r).expect("attach");
358        assert_eq!(ec.remove_component(&mut r, h), ReturnCode::Ok);
359        assert!(r.get_participating_contexts().is_empty());
360    }
361
362    #[test]
363    fn cannot_remove_active_component() {
364        // Spec §5.2.2.2.6 — detach when Active is forbidden.
365        let mut ec = ExecutionContext::new(ExecutionKind::Periodic);
366        let mut r = rtc();
367        r.initialize();
368        let h = ec.add_component(&mut r).expect("attach");
369        ec.activate_component(&mut r, h);
370        assert_eq!(
371            ec.remove_component(&mut r, h),
372            ReturnCode::PreconditionNotMet
373        );
374    }
375}