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    #[cfg(feature = "std")]
162    pub(crate) fn with_cl_id(&self, cl_id: u64) -> Self {
163        let mut context = self.clone();
164        context.cl_id = cl_id;
165        context
166    }
167}
168
169/// Builder for [`CuContext`].
170#[derive(Clone, Debug)]
171pub struct CuContextBuilder {
172    clock: RobotClock,
173    cl_id: u64,
174    instance_id: u32,
175    subsystem_code: u16,
176    task_ids: &'static [&'static str],
177}
178
179impl CuContextBuilder {
180    /// Sets the copper-list id for the context.
181    pub fn cl_id(mut self, cl_id: u64) -> Self {
182        self.cl_id = cl_id;
183        self
184    }
185
186    /// Sets the runtime instance id carried by the context.
187    pub fn instance_id(mut self, instance_id: u32) -> Self {
188        self.instance_id = instance_id;
189        self
190    }
191
192    /// Sets the static task id table for task metadata access.
193    pub fn task_ids(mut self, task_ids: &'static [&'static str]) -> Self {
194        self.task_ids = task_ids;
195        self
196    }
197
198    /// Builds a context value.
199    pub fn build(self) -> CuContext {
200        CuContext::new(
201            self.clock,
202            self.cl_id,
203            self.instance_id,
204            self.subsystem_code,
205            self.task_ids,
206        )
207    }
208}
209
210impl Deref for CuContext {
211    type Target = RobotClock;
212
213    fn deref(&self) -> &Self::Target {
214        &self.clock
215    }
216}
217
218#[cfg(test)]
219mod tests {
220    use super::CuContext;
221    use cu29_clock::RobotClock;
222
223    #[test]
224    fn default_instance_id_is_zero() {
225        let ctx = CuContext::from_clock(RobotClock::default());
226        assert_eq!(ctx.instance_id(), 0);
227        assert_eq!(ctx.subsystem_code(), 0);
228    }
229
230    #[test]
231    fn builder_overrides_instance_id() {
232        let ctx = CuContext::builder(RobotClock::default())
233            .cl_id(7)
234            .instance_id(42)
235            .build();
236        assert_eq!(ctx.cl_id(), 7);
237        assert_eq!(ctx.instance_id(), 42);
238        assert_eq!(ctx.subsystem_code(), 0);
239    }
240
241    #[test]
242    fn runtime_metadata_sets_subsystem_code() {
243        let ctx = CuContext::from_runtime_metadata(RobotClock::default(), 9, 42, 7, &[]);
244        assert_eq!(ctx.cl_id(), 9);
245        assert_eq!(ctx.instance_id(), 42);
246        assert_eq!(ctx.subsystem_code(), 7);
247        assert_eq!(ctx.current_component_id(), None);
248        assert_eq!(ctx.task_index(), None);
249    }
250
251    #[test]
252    fn task_scope_updates_component_scope() {
253        let mut ctx = CuContext::builder(RobotClock::default())
254            .task_ids(&["task-0"])
255            .build();
256        ctx.set_current_task(0);
257        assert_eq!(ctx.current_component_id(), Some(0));
258        assert_eq!(ctx.task_index(), Some(0));
259        assert_eq!(ctx.task_id(), Some("task-0"));
260    }
261
262    #[test]
263    fn component_scope_can_exist_without_task_scope() {
264        let mut ctx = CuContext::from_clock(RobotClock::default());
265        ctx.set_current_component(7);
266        ctx.clear_current_task();
267        assert_eq!(ctx.current_component_id(), Some(7));
268        assert_eq!(ctx.task_index(), None);
269        assert_eq!(ctx.task_id(), None);
270    }
271}