use alloc::vec::Vec;
use crate::lifecycle::{ExecutionKind, LifeCycleState};
use crate::object::{ExecutionContextHandle, LightweightRtObject};
use crate::return_code::ReturnCode;
pub trait ExecutionContextOperations {
fn is_running(&self) -> bool;
fn start(&mut self) -> ReturnCode;
fn stop(&mut self) -> ReturnCode;
fn get_rate(&self) -> f64;
fn set_rate(&mut self, rate: f64) -> ReturnCode;
fn add_component(
&mut self,
component: &mut LightweightRtObject,
) -> Result<ExecutionContextHandle, ReturnCode>;
fn remove_component(
&mut self,
component: &mut LightweightRtObject,
handle: ExecutionContextHandle,
) -> ReturnCode;
fn activate_component(
&mut self,
component: &mut LightweightRtObject,
handle: ExecutionContextHandle,
) -> ReturnCode;
fn deactivate_component(
&mut self,
component: &mut LightweightRtObject,
handle: ExecutionContextHandle,
) -> ReturnCode;
fn reset_component(
&mut self,
component: &mut LightweightRtObject,
handle: ExecutionContextHandle,
) -> ReturnCode;
fn get_component_state(
&self,
component: &LightweightRtObject,
handle: ExecutionContextHandle,
) -> LifeCycleState;
fn get_kind(&self) -> ExecutionKind;
}
pub struct ExecutionContext {
running: bool,
rate_hz: f64,
kind: ExecutionKind,
participants: Vec<ExecutionContextHandle>,
}
impl ExecutionContext {
#[must_use]
pub const fn new(kind: ExecutionKind) -> Self {
Self {
running: false,
rate_hz: 1.0,
kind,
participants: Vec::new(),
}
}
pub fn with_rate(kind: ExecutionKind, rate_hz: f64) -> Result<Self, ReturnCode> {
if !rate_hz.is_finite() || rate_hz <= 0.0 {
return Err(ReturnCode::BadParameter);
}
Ok(Self {
running: false,
rate_hz,
kind,
participants: Vec::new(),
})
}
}
impl core::fmt::Debug for ExecutionContext {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("ExecutionContext")
.field("running", &self.running)
.field("rate_hz", &self.rate_hz)
.field("kind", &self.kind)
.field("participants", &self.participants)
.finish()
}
}
impl ExecutionContextOperations for ExecutionContext {
fn is_running(&self) -> bool {
self.running
}
fn start(&mut self) -> ReturnCode {
if self.running {
return ReturnCode::Ok;
}
self.running = true;
ReturnCode::Ok
}
fn stop(&mut self) -> ReturnCode {
if !self.running {
return ReturnCode::Ok;
}
self.running = false;
ReturnCode::Ok
}
fn get_rate(&self) -> f64 {
self.rate_hz
}
fn set_rate(&mut self, rate: f64) -> ReturnCode {
if !rate.is_finite() || rate <= 0.0 {
return ReturnCode::BadParameter;
}
self.rate_hz = rate;
ReturnCode::Ok
}
fn add_component(
&mut self,
component: &mut LightweightRtObject,
) -> Result<ExecutionContextHandle, ReturnCode> {
let handle = component.attach_context()?;
self.participants.push(handle);
Ok(handle)
}
fn remove_component(
&mut self,
component: &mut LightweightRtObject,
handle: ExecutionContextHandle,
) -> ReturnCode {
let Some(idx) = self.participants.iter().position(|h| *h == handle) else {
return ReturnCode::BadParameter;
};
let rc = component.detach_context(handle);
if rc.is_ok() {
self.participants.swap_remove(idx);
}
rc
}
fn activate_component(
&mut self,
component: &mut LightweightRtObject,
handle: ExecutionContextHandle,
) -> ReturnCode {
if !self.participants.contains(&handle) {
return ReturnCode::BadParameter;
}
component.activate(handle)
}
fn deactivate_component(
&mut self,
component: &mut LightweightRtObject,
handle: ExecutionContextHandle,
) -> ReturnCode {
if !self.participants.contains(&handle) {
return ReturnCode::BadParameter;
}
component.deactivate(handle)
}
fn reset_component(
&mut self,
component: &mut LightweightRtObject,
handle: ExecutionContextHandle,
) -> ReturnCode {
if !self.participants.contains(&handle) {
return ReturnCode::BadParameter;
}
component.reset(handle)
}
fn get_component_state(
&self,
component: &LightweightRtObject,
handle: ExecutionContextHandle,
) -> LifeCycleState {
component
.get_context_state(handle)
.unwrap_or(LifeCycleState::Created)
}
fn get_kind(&self) -> ExecutionKind {
self.kind
}
}
#[cfg(test)]
#[allow(clippy::expect_used)]
mod tests {
use super::*;
use crate::lifecycle::ComponentAction;
struct NoOp;
impl ComponentAction for NoOp {}
fn rtc() -> LightweightRtObject {
LightweightRtObject::new(alloc::boxed::Box::new(NoOp))
}
#[test]
fn fresh_context_is_stopped_with_default_rate() {
let ec = ExecutionContext::new(ExecutionKind::Periodic);
assert!(!ec.is_running());
assert!((ec.get_rate() - 1.0).abs() < f64::EPSILON);
assert_eq!(ec.get_kind(), ExecutionKind::Periodic);
}
#[test]
fn start_stop_round_trips_running_flag() {
let mut ec = ExecutionContext::new(ExecutionKind::Periodic);
assert_eq!(ec.start(), ReturnCode::Ok);
assert!(ec.is_running());
assert_eq!(ec.stop(), ReturnCode::Ok);
assert!(!ec.is_running());
}
#[test]
fn double_start_is_idempotent() {
let mut ec = ExecutionContext::new(ExecutionKind::Periodic);
ec.start();
assert_eq!(ec.start(), ReturnCode::Ok);
}
#[test]
fn set_rate_rejects_non_positive_or_nan() {
let mut ec = ExecutionContext::new(ExecutionKind::Periodic);
assert_eq!(ec.set_rate(0.0), ReturnCode::BadParameter);
assert_eq!(ec.set_rate(-1.0), ReturnCode::BadParameter);
assert_eq!(ec.set_rate(f64::NAN), ReturnCode::BadParameter);
assert_eq!(ec.set_rate(f64::INFINITY), ReturnCode::BadParameter);
}
#[test]
fn set_rate_accepts_positive_finite() {
let mut ec = ExecutionContext::new(ExecutionKind::Periodic);
assert_eq!(ec.set_rate(50.0), ReturnCode::Ok);
assert!((ec.get_rate() - 50.0).abs() < f64::EPSILON);
}
#[test]
fn with_rate_validates_rate_argument() {
assert!(ExecutionContext::with_rate(ExecutionKind::Periodic, 100.0).is_ok());
assert!(ExecutionContext::with_rate(ExecutionKind::Periodic, 0.0).is_err());
assert!(ExecutionContext::with_rate(ExecutionKind::Periodic, -1.0).is_err());
}
#[test]
fn add_component_attaches_and_returns_handle() {
let mut ec = ExecutionContext::new(ExecutionKind::Periodic);
let mut r = rtc();
r.initialize();
let h = ec.add_component(&mut r).expect("attach");
assert_eq!(ec.get_component_state(&r, h), LifeCycleState::Inactive);
}
#[test]
fn add_uninitialized_component_fails() {
let mut ec = ExecutionContext::new(ExecutionKind::Periodic);
let mut r = rtc();
assert!(ec.add_component(&mut r).is_err());
}
#[test]
fn activate_then_deactivate_round_trips_state() {
let mut ec = ExecutionContext::new(ExecutionKind::Periodic);
let mut r = rtc();
r.initialize();
let h = ec.add_component(&mut r).expect("attach");
assert_eq!(ec.activate_component(&mut r, h), ReturnCode::Ok);
assert_eq!(ec.get_component_state(&r, h), LifeCycleState::Active);
assert_eq!(ec.deactivate_component(&mut r, h), ReturnCode::Ok);
assert_eq!(ec.get_component_state(&r, h), LifeCycleState::Inactive);
}
#[test]
fn activate_with_unknown_handle_yields_bad_parameter() {
let mut ec = ExecutionContext::new(ExecutionKind::Periodic);
let mut r = rtc();
r.initialize();
assert_eq!(
ec.activate_component(&mut r, 99_999),
ReturnCode::BadParameter
);
}
#[test]
fn remove_component_detaches_handle() {
let mut ec = ExecutionContext::new(ExecutionKind::Periodic);
let mut r = rtc();
r.initialize();
let h = ec.add_component(&mut r).expect("attach");
assert_eq!(ec.remove_component(&mut r, h), ReturnCode::Ok);
assert!(r.get_participating_contexts().is_empty());
}
#[test]
fn cannot_remove_active_component() {
let mut ec = ExecutionContext::new(ExecutionKind::Periodic);
let mut r = rtc();
r.initialize();
let h = ec.add_component(&mut r).expect("attach");
ec.activate_component(&mut r, h);
assert_eq!(
ec.remove_component(&mut r, h),
ReturnCode::PreconditionNotMet
);
}
}