1use alloc::vec::Vec;
8
9use crate::lifecycle::{ExecutionKind, LifeCycleState};
10use crate::object::{ExecutionContextHandle, LightweightRtObject};
11use crate::return_code::ReturnCode;
12
13pub trait ExecutionContextOperations {
22 fn is_running(&self) -> bool;
24 fn start(&mut self) -> ReturnCode;
26 fn stop(&mut self) -> ReturnCode;
28 fn get_rate(&self) -> f64;
30 fn set_rate(&mut self, rate: f64) -> ReturnCode;
35 fn add_component(
41 &mut self,
42 component: &mut LightweightRtObject,
43 ) -> Result<ExecutionContextHandle, ReturnCode>;
44 fn remove_component(
46 &mut self,
47 component: &mut LightweightRtObject,
48 handle: ExecutionContextHandle,
49 ) -> ReturnCode;
50 fn activate_component(
52 &mut self,
53 component: &mut LightweightRtObject,
54 handle: ExecutionContextHandle,
55 ) -> ReturnCode;
56 fn deactivate_component(
58 &mut self,
59 component: &mut LightweightRtObject,
60 handle: ExecutionContextHandle,
61 ) -> ReturnCode;
62 fn reset_component(
64 &mut self,
65 component: &mut LightweightRtObject,
66 handle: ExecutionContextHandle,
67 ) -> ReturnCode;
68 fn get_component_state(
70 &self,
71 component: &LightweightRtObject,
72 handle: ExecutionContextHandle,
73 ) -> LifeCycleState;
74 fn get_kind(&self) -> ExecutionKind;
76}
77
78pub struct ExecutionContext {
88 running: bool,
89 rate_hz: f64,
90 kind: ExecutionKind,
91 participants: Vec<ExecutionContextHandle>,
92}
93
94impl ExecutionContext {
95 #[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 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 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 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 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 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 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 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 let mut ec = ExecutionContext::new(ExecutionKind::Periodic);
321 let mut r = rtc();
322 assert!(ec.add_component(&mut r).is_err());
324 }
325
326 #[test]
327 fn activate_then_deactivate_round_trips_state() {
328 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 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 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 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}