1use alloc::vec::Vec;
7use core::sync::atomic::{AtomicU32, Ordering};
8
9use crate::lifecycle::{ComponentAction, LifeCycleState, is_valid_transition};
10use crate::return_code::ReturnCode;
11
12pub type ExecutionContextHandle = u32;
15
16pub const INVALID_HANDLE: ExecutionContextHandle = 0;
18
19fn next_handle() -> ExecutionContextHandle {
21 static COUNTER: AtomicU32 = AtomicU32::new(1);
22 let n = COUNTER.fetch_add(1, Ordering::SeqCst);
23 if n == 0 {
24 COUNTER.fetch_add(1, Ordering::SeqCst)
25 } else {
26 n
27 }
28}
29
30pub struct LightweightRtObject {
44 is_alive: bool,
48 contexts: Vec<ContextEntry>,
50 callbacks: alloc::boxed::Box<dyn ComponentAction>,
52}
53
54#[derive(Debug, Clone)]
56struct ContextEntry {
57 handle: ExecutionContextHandle,
58 state: LifeCycleState,
59}
60
61impl core::fmt::Debug for LightweightRtObject {
62 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
63 f.debug_struct("LightweightRtObject")
64 .field("is_alive", &self.is_alive)
65 .field("contexts", &self.contexts)
66 .finish_non_exhaustive()
67 }
68}
69
70impl LightweightRtObject {
71 #[must_use]
74 pub fn new(callbacks: alloc::boxed::Box<dyn ComponentAction>) -> Self {
75 Self {
76 is_alive: false,
77 contexts: Vec::new(),
78 callbacks,
79 }
80 }
81
82 pub fn initialize(&mut self) -> ReturnCode {
89 if self.is_alive {
90 return ReturnCode::PreconditionNotMet;
91 }
92 let cb = self.callbacks.on_initialize();
93 if !cb.is_ok() {
94 return cb;
95 }
96 self.is_alive = true;
97 ReturnCode::Ok
98 }
99
100 pub fn finalize(&mut self) -> ReturnCode {
106 if !self.is_alive {
107 return ReturnCode::PreconditionNotMet;
109 }
110 if !self.contexts.is_empty() {
111 return ReturnCode::PreconditionNotMet;
112 }
113 let cb = self.callbacks.on_finalize();
114 if !cb.is_ok() {
115 return cb;
116 }
117 self.is_alive = false;
118 ReturnCode::Ok
119 }
120
121 #[must_use]
124 pub const fn is_alive(&self) -> bool {
125 self.is_alive
126 }
127
128 pub fn attach_context(&mut self) -> Result<ExecutionContextHandle, ReturnCode> {
135 if !self.is_alive {
136 return Err(ReturnCode::PreconditionNotMet);
137 }
138 let handle = next_handle();
139 self.contexts.push(ContextEntry {
140 handle,
141 state: LifeCycleState::Inactive,
142 });
143 Ok(handle)
144 }
145
146 pub fn detach_context(&mut self, handle: ExecutionContextHandle) -> ReturnCode {
149 let Some(idx) = self.contexts.iter().position(|c| c.handle == handle) else {
150 return ReturnCode::PreconditionNotMet;
151 };
152 if self.contexts[idx].state == LifeCycleState::Active {
153 return ReturnCode::PreconditionNotMet;
154 }
155 self.contexts.swap_remove(idx);
156 ReturnCode::Ok
157 }
158
159 #[must_use]
162 pub fn get_participating_contexts(&self) -> Vec<ExecutionContextHandle> {
163 self.contexts.iter().map(|c| c.handle).collect()
164 }
165
166 #[must_use]
169 pub fn get_context_state(&self, handle: ExecutionContextHandle) -> Option<LifeCycleState> {
170 self.contexts
171 .iter()
172 .find(|c| c.handle == handle)
173 .map(|c| c.state)
174 }
175
176 pub(crate) fn activate(&mut self, handle: ExecutionContextHandle) -> ReturnCode {
179 let Some(entry) = self.contexts.iter_mut().find(|c| c.handle == handle) else {
180 return ReturnCode::BadParameter;
181 };
182 if !is_valid_transition(entry.state, LifeCycleState::Active) {
183 return ReturnCode::PreconditionNotMet;
184 }
185 entry.state = LifeCycleState::Active;
186 let cb = self.callbacks.on_activated(handle);
187 if !cb.is_ok() {
188 entry.state = LifeCycleState::Error;
190 self.callbacks.on_aborting(handle);
191 return cb;
192 }
193 ReturnCode::Ok
194 }
195
196 pub(crate) fn deactivate(&mut self, handle: ExecutionContextHandle) -> ReturnCode {
198 let Some(entry) = self.contexts.iter_mut().find(|c| c.handle == handle) else {
199 return ReturnCode::BadParameter;
200 };
201 if !is_valid_transition(entry.state, LifeCycleState::Inactive) {
202 return ReturnCode::PreconditionNotMet;
203 }
204 entry.state = LifeCycleState::Inactive;
205 self.callbacks.on_deactivated(handle)
206 }
207
208 pub(crate) fn reset(&mut self, handle: ExecutionContextHandle) -> ReturnCode {
211 let Some(entry) = self.contexts.iter_mut().find(|c| c.handle == handle) else {
212 return ReturnCode::BadParameter;
213 };
214 if entry.state != LifeCycleState::Error {
215 return ReturnCode::PreconditionNotMet;
216 }
217 let cb = self.callbacks.on_reset(handle);
218 if cb.is_ok() {
219 entry.state = LifeCycleState::Inactive;
220 }
221 cb
222 }
223
224 pub fn transition_to_error(&mut self, handle: ExecutionContextHandle) {
228 if let Some(entry) = self.contexts.iter_mut().find(|c| c.handle == handle) {
229 if entry.state == LifeCycleState::Active {
230 entry.state = LifeCycleState::Error;
231 self.callbacks.on_aborting(handle);
232 }
233 }
234 }
235
236 pub fn callbacks_mut(&mut self) -> &mut dyn ComponentAction {
240 self.callbacks.as_mut()
241 }
242}
243
244#[cfg(test)]
245#[allow(clippy::expect_used)]
246mod tests {
247 use super::*;
248
249 struct CountingCallbacks {
250 initialize: u32,
251 finalize: u32,
252 activated: u32,
253 deactivated: u32,
254 reset: u32,
255 force_init_fail: bool,
256 }
257
258 impl ComponentAction for CountingCallbacks {
259 fn on_initialize(&mut self) -> ReturnCode {
260 self.initialize += 1;
261 if self.force_init_fail {
262 ReturnCode::Error
263 } else {
264 ReturnCode::Ok
265 }
266 }
267 fn on_finalize(&mut self) -> ReturnCode {
268 self.finalize += 1;
269 ReturnCode::Ok
270 }
271 fn on_activated(&mut self, _h: u32) -> ReturnCode {
272 self.activated += 1;
273 ReturnCode::Ok
274 }
275 fn on_deactivated(&mut self, _h: u32) -> ReturnCode {
276 self.deactivated += 1;
277 ReturnCode::Ok
278 }
279 fn on_reset(&mut self, _h: u32) -> ReturnCode {
280 self.reset += 1;
281 ReturnCode::Ok
282 }
283 }
284
285 fn make() -> LightweightRtObject {
286 LightweightRtObject::new(alloc::boxed::Box::new(CountingCallbacks {
287 initialize: 0,
288 finalize: 0,
289 activated: 0,
290 deactivated: 0,
291 reset: 0,
292 force_init_fail: false,
293 }))
294 }
295
296 #[test]
297 fn fresh_rtc_is_not_alive() {
298 let r = make();
300 assert!(!r.is_alive());
301 }
302
303 #[test]
304 fn initialize_then_finalize_round_trips_alive_flag() {
305 let mut r = make();
307 assert_eq!(r.initialize(), ReturnCode::Ok);
308 assert!(r.is_alive());
309 assert_eq!(r.finalize(), ReturnCode::Ok);
310 assert!(!r.is_alive());
311 }
312
313 #[test]
314 fn double_initialize_yields_precondition_not_met() {
315 let mut r = make();
317 assert_eq!(r.initialize(), ReturnCode::Ok);
318 assert_eq!(r.initialize(), ReturnCode::PreconditionNotMet);
319 }
320
321 #[test]
322 fn finalize_in_created_state_yields_precondition_not_met() {
323 let mut r = make();
325 assert_eq!(r.finalize(), ReturnCode::PreconditionNotMet);
326 }
327
328 #[test]
329 fn finalize_with_attached_context_yields_precondition_not_met() {
330 let mut r = make();
332 r.initialize();
333 let _ = r.attach_context().expect("attach ok");
334 assert_eq!(r.finalize(), ReturnCode::PreconditionNotMet);
335 }
336
337 #[test]
338 fn attach_context_in_created_state_fails() {
339 let mut r = make();
341 assert!(matches!(
342 r.attach_context(),
343 Err(ReturnCode::PreconditionNotMet)
344 ));
345 }
346
347 #[test]
348 fn attach_then_detach_works() {
349 let mut r = make();
350 r.initialize();
351 let h = r.attach_context().expect("attach");
352 assert_eq!(r.get_participating_contexts(), alloc::vec![h]);
353 assert_eq!(r.detach_context(h), ReturnCode::Ok);
354 assert!(r.get_participating_contexts().is_empty());
355 }
356
357 #[test]
358 fn detach_unknown_handle_yields_precondition_not_met() {
359 let mut r = make();
361 r.initialize();
362 assert_eq!(r.detach_context(99_999), ReturnCode::PreconditionNotMet);
363 }
364
365 #[test]
366 fn detach_active_rtc_yields_precondition_not_met() {
367 let mut r = make();
369 r.initialize();
370 let h = r.attach_context().expect("attach");
371 assert_eq!(r.activate(h), ReturnCode::Ok);
372 assert_eq!(r.detach_context(h), ReturnCode::PreconditionNotMet);
373 }
374
375 #[test]
376 fn activate_inactive_rtc_invokes_on_activated() {
377 let mut r = make();
378 r.initialize();
379 let h = r.attach_context().expect("attach");
380 assert_eq!(r.activate(h), ReturnCode::Ok);
381 assert_eq!(r.get_context_state(h), Some(LifeCycleState::Active));
382 }
383
384 #[test]
385 fn deactivate_active_rtc_invokes_on_deactivated() {
386 let mut r = make();
387 r.initialize();
388 let h = r.attach_context().expect("attach");
389 r.activate(h);
390 assert_eq!(r.deactivate(h), ReturnCode::Ok);
391 assert_eq!(r.get_context_state(h), Some(LifeCycleState::Inactive));
392 }
393
394 #[test]
395 fn reset_only_works_from_error_state() {
396 let mut r = make();
398 r.initialize();
399 let h = r.attach_context().expect("attach");
400 assert_eq!(r.reset(h), ReturnCode::PreconditionNotMet);
402 r.activate(h);
404 r.transition_to_error(h);
405 assert_eq!(r.get_context_state(h), Some(LifeCycleState::Error));
406 assert_eq!(r.reset(h), ReturnCode::Ok);
408 assert_eq!(r.get_context_state(h), Some(LifeCycleState::Inactive));
409 }
410
411 #[test]
412 fn initialize_failure_keeps_rtc_in_created_state() {
413 let mut r = LightweightRtObject::new(alloc::boxed::Box::new(CountingCallbacks {
416 initialize: 0,
417 finalize: 0,
418 activated: 0,
419 deactivated: 0,
420 reset: 0,
421 force_init_fail: true,
422 }));
423 assert_eq!(r.initialize(), ReturnCode::Error);
424 assert!(!r.is_alive());
425 }
426
427 #[test]
428 fn handles_are_unique_across_attaches() {
429 let mut r = make();
430 r.initialize();
431 let h1 = r.attach_context().expect("attach1");
432 let h2 = r.attach_context().expect("attach2");
433 assert_ne!(h1, h2);
434 assert_ne!(h1, INVALID_HANDLE);
435 assert_ne!(h2, INVALID_HANDLE);
436 }
437}