Skip to main content

cu29_runtime/
context.rs

1//! User-facing execution context passed to task and bridge process callbacks.
2
3use core::ops::Deref;
4use cu29_clock::{RobotClock, RobotClockMock};
5
6/// Execution context passed to task and bridge callbacks.
7///
8/// `CuContext` provides callback code with:
9/// - time access through `clock` and `Deref<Target = RobotClock>`
10/// - current execution sequence id via `cl_id()`
11/// - process instance metadata via `instance_id()`
12/// - compile-time subsystem identity via `subsystem_code()`
13/// - current component metadata via `current_component_id()`
14/// - current task metadata via `task_id()` / `task_index()`
15///
16/// The execution sequence id matches the copper-list id of the iteration being
17/// processed. It is also available in other lifecycle callbacks
18/// (`start`/`preprocess`/`postprocess`/`stop`) for continuity, but outside
19/// `process` callbacks it must not be treated as a live copper-list handle.
20///
21/// The runtime creates one context per execution loop and updates transient
22/// fields such as the currently executing component/task before each callback.
23#[derive(Clone, Debug)]
24pub struct CuContext {
25    /// Runtime clock. Kept as a field for direct access (`context.clock.now()`).
26    pub clock: RobotClock,
27    cl_id: u64,
28    instance_id: u32,
29    subsystem_code: u16,
30    task_ids: &'static [&'static str],
31    current_component_index: Option<usize>,
32    current_task_index: Option<usize>,
33}
34
35impl CuContext {
36    /// Starts a context builder from a clock.
37    pub fn builder(clock: RobotClock) -> CuContextBuilder {
38        CuContextBuilder {
39            clock,
40            cl_id: 0,
41            instance_id: 0,
42            subsystem_code: 0,
43            task_ids: &[],
44        }
45    }
46
47    /// Creates a context from an existing clock with default metadata.
48    ///
49    /// Defaults:
50    /// - `cl_id = 0`
51    /// - no task id table
52    pub fn from_clock(clock: RobotClock) -> Self {
53        Self::builder(clock).build()
54    }
55
56    /// Creates a context backed by a real robot clock.
57    ///
58    /// Defaults:
59    /// - `cl_id = 0`
60    /// - no task id table
61    #[cfg(feature = "std")]
62    pub fn new_with_clock() -> Self {
63        Self::from_clock(RobotClock::new())
64    }
65
66    /// Creates a context backed by a mock clock.
67    ///
68    /// Returns both the context and its [`RobotClockMock`] control handle.
69    pub fn new_mock_clock() -> (Self, RobotClockMock) {
70        let (clock, mock) = RobotClock::mock();
71        (Self::from_clock(clock), mock)
72    }
73
74    /// Internal constructor used by runtime internals and code generation.
75    pub(crate) fn new(
76        clock: RobotClock,
77        clid: u64,
78        instance_id: u32,
79        subsystem_code: u16,
80        task_ids: &'static [&'static str],
81    ) -> Self {
82        Self {
83            clock,
84            cl_id: clid,
85            instance_id,
86            subsystem_code,
87            task_ids,
88            current_component_index: None,
89            current_task_index: None,
90        }
91    }
92
93    /// Internal constructor used by generated runtime code.
94    #[doc(hidden)]
95    pub fn from_runtime_metadata(
96        clock: RobotClock,
97        clid: u64,
98        instance_id: u32,
99        subsystem_code: u16,
100        task_ids: &'static [&'static str],
101    ) -> Self {
102        Self::new(clock, clid, instance_id, subsystem_code, task_ids)
103    }
104
105    /// Sets the currently executing component index.
106    pub fn set_current_component(&mut self, component_index: usize) {
107        self.current_component_index = Some(component_index);
108    }
109
110    /// Clears the currently executing component.
111    pub fn clear_current_component(&mut self) {
112        self.current_component_index = None;
113    }
114
115    /// Sets the currently executing task index.
116    pub fn set_current_task(&mut self, task_index: usize) {
117        self.current_component_index = Some(task_index);
118        self.current_task_index = Some(task_index);
119    }
120
121    /// Clears the currently executing task.
122    pub fn clear_current_task(&mut self) {
123        self.current_task_index = None;
124    }
125
126    /// Returns the current execution sequence id.
127    ///
128    /// In `process` callbacks, this value is the id of the copper-list being
129    /// processed. In other lifecycle callbacks, this value is still meaningful
130    /// for sequencing but does not imply that a copper-list instance is alive.
131    pub fn cl_id(&self) -> u64 {
132        self.cl_id
133    }
134
135    /// Returns the runtime instance id attached to this context.
136    pub fn instance_id(&self) -> u32 {
137        self.instance_id
138    }
139
140    /// Returns the compile-time subsystem code for this Copper process.
141    pub fn subsystem_code(&self) -> u16 {
142        self.subsystem_code
143    }
144
145    /// Returns the current component index, if any.
146    pub fn current_component_id(&self) -> Option<usize> {
147        self.current_component_index
148    }
149
150    /// Returns the current task index, if any.
151    pub fn task_index(&self) -> Option<usize> {
152        self.current_task_index
153    }
154
155    /// Returns the current task id, if any.
156    pub fn task_id(&self) -> Option<&'static str> {
157        self.current_task_index
158            .and_then(|idx| self.task_ids.get(idx).copied())
159    }
160}
161
162/// Builder for [`CuContext`].
163#[derive(Clone, Debug)]
164pub struct CuContextBuilder {
165    clock: RobotClock,
166    cl_id: u64,
167    instance_id: u32,
168    subsystem_code: u16,
169    task_ids: &'static [&'static str],
170}
171
172impl CuContextBuilder {
173    /// Sets the copper-list id for the context.
174    pub fn cl_id(mut self, cl_id: u64) -> Self {
175        self.cl_id = cl_id;
176        self
177    }
178
179    /// Sets the runtime instance id carried by the context.
180    pub fn instance_id(mut self, instance_id: u32) -> Self {
181        self.instance_id = instance_id;
182        self
183    }
184
185    /// Sets the static task id table for task metadata access.
186    pub fn task_ids(mut self, task_ids: &'static [&'static str]) -> Self {
187        self.task_ids = task_ids;
188        self
189    }
190
191    /// Builds a context value.
192    pub fn build(self) -> CuContext {
193        CuContext::new(
194            self.clock,
195            self.cl_id,
196            self.instance_id,
197            self.subsystem_code,
198            self.task_ids,
199        )
200    }
201}
202
203impl Deref for CuContext {
204    type Target = RobotClock;
205
206    fn deref(&self) -> &Self::Target {
207        &self.clock
208    }
209}
210
211#[cfg(test)]
212mod tests {
213    use super::CuContext;
214    use cu29_clock::RobotClock;
215
216    #[test]
217    fn default_instance_id_is_zero() {
218        let ctx = CuContext::from_clock(RobotClock::default());
219        assert_eq!(ctx.instance_id(), 0);
220        assert_eq!(ctx.subsystem_code(), 0);
221    }
222
223    #[test]
224    fn builder_overrides_instance_id() {
225        let ctx = CuContext::builder(RobotClock::default())
226            .cl_id(7)
227            .instance_id(42)
228            .build();
229        assert_eq!(ctx.cl_id(), 7);
230        assert_eq!(ctx.instance_id(), 42);
231        assert_eq!(ctx.subsystem_code(), 0);
232    }
233
234    #[test]
235    fn runtime_metadata_sets_subsystem_code() {
236        let ctx = CuContext::from_runtime_metadata(RobotClock::default(), 9, 42, 7, &[]);
237        assert_eq!(ctx.cl_id(), 9);
238        assert_eq!(ctx.instance_id(), 42);
239        assert_eq!(ctx.subsystem_code(), 7);
240        assert_eq!(ctx.current_component_id(), None);
241        assert_eq!(ctx.task_index(), None);
242    }
243
244    #[test]
245    fn task_scope_updates_component_scope() {
246        let mut ctx = CuContext::builder(RobotClock::default())
247            .task_ids(&["task-0"])
248            .build();
249        ctx.set_current_task(0);
250        assert_eq!(ctx.current_component_id(), Some(0));
251        assert_eq!(ctx.task_index(), Some(0));
252        assert_eq!(ctx.task_id(), Some("task-0"));
253    }
254
255    #[test]
256    fn component_scope_can_exist_without_task_scope() {
257        let mut ctx = CuContext::from_clock(RobotClock::default());
258        ctx.set_current_component(7);
259        ctx.clear_current_task();
260        assert_eq!(ctx.current_component_id(), Some(7));
261        assert_eq!(ctx.task_index(), None);
262        assert_eq!(ctx.task_id(), None);
263    }
264}