use alloc::vec::Vec;
use core::sync::atomic::{AtomicU32, Ordering};
use crate::lifecycle::{ComponentAction, LifeCycleState, is_valid_transition};
use crate::return_code::ReturnCode;
pub type ExecutionContextHandle = u32;
pub const INVALID_HANDLE: ExecutionContextHandle = 0;
fn next_handle() -> ExecutionContextHandle {
static COUNTER: AtomicU32 = AtomicU32::new(1);
let n = COUNTER.fetch_add(1, Ordering::SeqCst);
if n == 0 {
COUNTER.fetch_add(1, Ordering::SeqCst)
} else {
n
}
}
pub struct LightweightRtObject {
is_alive: bool,
contexts: Vec<ContextEntry>,
callbacks: alloc::boxed::Box<dyn ComponentAction>,
}
#[derive(Debug, Clone)]
struct ContextEntry {
handle: ExecutionContextHandle,
state: LifeCycleState,
}
impl core::fmt::Debug for LightweightRtObject {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("LightweightRtObject")
.field("is_alive", &self.is_alive)
.field("contexts", &self.contexts)
.finish_non_exhaustive()
}
}
impl LightweightRtObject {
#[must_use]
pub fn new(callbacks: alloc::boxed::Box<dyn ComponentAction>) -> Self {
Self {
is_alive: false,
contexts: Vec::new(),
callbacks,
}
}
pub fn initialize(&mut self) -> ReturnCode {
if self.is_alive {
return ReturnCode::PreconditionNotMet;
}
let cb = self.callbacks.on_initialize();
if !cb.is_ok() {
return cb;
}
self.is_alive = true;
ReturnCode::Ok
}
pub fn finalize(&mut self) -> ReturnCode {
if !self.is_alive {
return ReturnCode::PreconditionNotMet;
}
if !self.contexts.is_empty() {
return ReturnCode::PreconditionNotMet;
}
let cb = self.callbacks.on_finalize();
if !cb.is_ok() {
return cb;
}
self.is_alive = false;
ReturnCode::Ok
}
#[must_use]
pub const fn is_alive(&self) -> bool {
self.is_alive
}
pub fn attach_context(&mut self) -> Result<ExecutionContextHandle, ReturnCode> {
if !self.is_alive {
return Err(ReturnCode::PreconditionNotMet);
}
let handle = next_handle();
self.contexts.push(ContextEntry {
handle,
state: LifeCycleState::Inactive,
});
Ok(handle)
}
pub fn detach_context(&mut self, handle: ExecutionContextHandle) -> ReturnCode {
let Some(idx) = self.contexts.iter().position(|c| c.handle == handle) else {
return ReturnCode::PreconditionNotMet;
};
if self.contexts[idx].state == LifeCycleState::Active {
return ReturnCode::PreconditionNotMet;
}
self.contexts.swap_remove(idx);
ReturnCode::Ok
}
#[must_use]
pub fn get_participating_contexts(&self) -> Vec<ExecutionContextHandle> {
self.contexts.iter().map(|c| c.handle).collect()
}
#[must_use]
pub fn get_context_state(&self, handle: ExecutionContextHandle) -> Option<LifeCycleState> {
self.contexts
.iter()
.find(|c| c.handle == handle)
.map(|c| c.state)
}
pub(crate) fn activate(&mut self, handle: ExecutionContextHandle) -> ReturnCode {
let Some(entry) = self.contexts.iter_mut().find(|c| c.handle == handle) else {
return ReturnCode::BadParameter;
};
if !is_valid_transition(entry.state, LifeCycleState::Active) {
return ReturnCode::PreconditionNotMet;
}
entry.state = LifeCycleState::Active;
let cb = self.callbacks.on_activated(handle);
if !cb.is_ok() {
entry.state = LifeCycleState::Error;
self.callbacks.on_aborting(handle);
return cb;
}
ReturnCode::Ok
}
pub(crate) fn deactivate(&mut self, handle: ExecutionContextHandle) -> ReturnCode {
let Some(entry) = self.contexts.iter_mut().find(|c| c.handle == handle) else {
return ReturnCode::BadParameter;
};
if !is_valid_transition(entry.state, LifeCycleState::Inactive) {
return ReturnCode::PreconditionNotMet;
}
entry.state = LifeCycleState::Inactive;
self.callbacks.on_deactivated(handle)
}
pub(crate) fn reset(&mut self, handle: ExecutionContextHandle) -> ReturnCode {
let Some(entry) = self.contexts.iter_mut().find(|c| c.handle == handle) else {
return ReturnCode::BadParameter;
};
if entry.state != LifeCycleState::Error {
return ReturnCode::PreconditionNotMet;
}
let cb = self.callbacks.on_reset(handle);
if cb.is_ok() {
entry.state = LifeCycleState::Inactive;
}
cb
}
pub fn transition_to_error(&mut self, handle: ExecutionContextHandle) {
if let Some(entry) = self.contexts.iter_mut().find(|c| c.handle == handle) {
if entry.state == LifeCycleState::Active {
entry.state = LifeCycleState::Error;
self.callbacks.on_aborting(handle);
}
}
}
pub fn callbacks_mut(&mut self) -> &mut dyn ComponentAction {
self.callbacks.as_mut()
}
}
#[cfg(test)]
#[allow(clippy::expect_used)]
mod tests {
use super::*;
struct CountingCallbacks {
initialize: u32,
finalize: u32,
activated: u32,
deactivated: u32,
reset: u32,
force_init_fail: bool,
}
impl ComponentAction for CountingCallbacks {
fn on_initialize(&mut self) -> ReturnCode {
self.initialize += 1;
if self.force_init_fail {
ReturnCode::Error
} else {
ReturnCode::Ok
}
}
fn on_finalize(&mut self) -> ReturnCode {
self.finalize += 1;
ReturnCode::Ok
}
fn on_activated(&mut self, _h: u32) -> ReturnCode {
self.activated += 1;
ReturnCode::Ok
}
fn on_deactivated(&mut self, _h: u32) -> ReturnCode {
self.deactivated += 1;
ReturnCode::Ok
}
fn on_reset(&mut self, _h: u32) -> ReturnCode {
self.reset += 1;
ReturnCode::Ok
}
}
fn make() -> LightweightRtObject {
LightweightRtObject::new(alloc::boxed::Box::new(CountingCallbacks {
initialize: 0,
finalize: 0,
activated: 0,
deactivated: 0,
reset: 0,
force_init_fail: false,
}))
}
#[test]
fn fresh_rtc_is_not_alive() {
let r = make();
assert!(!r.is_alive());
}
#[test]
fn initialize_then_finalize_round_trips_alive_flag() {
let mut r = make();
assert_eq!(r.initialize(), ReturnCode::Ok);
assert!(r.is_alive());
assert_eq!(r.finalize(), ReturnCode::Ok);
assert!(!r.is_alive());
}
#[test]
fn double_initialize_yields_precondition_not_met() {
let mut r = make();
assert_eq!(r.initialize(), ReturnCode::Ok);
assert_eq!(r.initialize(), ReturnCode::PreconditionNotMet);
}
#[test]
fn finalize_in_created_state_yields_precondition_not_met() {
let mut r = make();
assert_eq!(r.finalize(), ReturnCode::PreconditionNotMet);
}
#[test]
fn finalize_with_attached_context_yields_precondition_not_met() {
let mut r = make();
r.initialize();
let _ = r.attach_context().expect("attach ok");
assert_eq!(r.finalize(), ReturnCode::PreconditionNotMet);
}
#[test]
fn attach_context_in_created_state_fails() {
let mut r = make();
assert!(matches!(
r.attach_context(),
Err(ReturnCode::PreconditionNotMet)
));
}
#[test]
fn attach_then_detach_works() {
let mut r = make();
r.initialize();
let h = r.attach_context().expect("attach");
assert_eq!(r.get_participating_contexts(), alloc::vec![h]);
assert_eq!(r.detach_context(h), ReturnCode::Ok);
assert!(r.get_participating_contexts().is_empty());
}
#[test]
fn detach_unknown_handle_yields_precondition_not_met() {
let mut r = make();
r.initialize();
assert_eq!(r.detach_context(99_999), ReturnCode::PreconditionNotMet);
}
#[test]
fn detach_active_rtc_yields_precondition_not_met() {
let mut r = make();
r.initialize();
let h = r.attach_context().expect("attach");
assert_eq!(r.activate(h), ReturnCode::Ok);
assert_eq!(r.detach_context(h), ReturnCode::PreconditionNotMet);
}
#[test]
fn activate_inactive_rtc_invokes_on_activated() {
let mut r = make();
r.initialize();
let h = r.attach_context().expect("attach");
assert_eq!(r.activate(h), ReturnCode::Ok);
assert_eq!(r.get_context_state(h), Some(LifeCycleState::Active));
}
#[test]
fn deactivate_active_rtc_invokes_on_deactivated() {
let mut r = make();
r.initialize();
let h = r.attach_context().expect("attach");
r.activate(h);
assert_eq!(r.deactivate(h), ReturnCode::Ok);
assert_eq!(r.get_context_state(h), Some(LifeCycleState::Inactive));
}
#[test]
fn reset_only_works_from_error_state() {
let mut r = make();
r.initialize();
let h = r.attach_context().expect("attach");
assert_eq!(r.reset(h), ReturnCode::PreconditionNotMet);
r.activate(h);
r.transition_to_error(h);
assert_eq!(r.get_context_state(h), Some(LifeCycleState::Error));
assert_eq!(r.reset(h), ReturnCode::Ok);
assert_eq!(r.get_context_state(h), Some(LifeCycleState::Inactive));
}
#[test]
fn initialize_failure_keeps_rtc_in_created_state() {
let mut r = LightweightRtObject::new(alloc::boxed::Box::new(CountingCallbacks {
initialize: 0,
finalize: 0,
activated: 0,
deactivated: 0,
reset: 0,
force_init_fail: true,
}));
assert_eq!(r.initialize(), ReturnCode::Error);
assert!(!r.is_alive());
}
#[test]
fn handles_are_unique_across_attaches() {
let mut r = make();
r.initialize();
let h1 = r.attach_context().expect("attach1");
let h2 = r.attach_context().expect("attach2");
assert_ne!(h1, h2);
assert_ne!(h1, INVALID_HANDLE);
assert_ne!(h2, INVALID_HANDLE);
}
}