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