cu29_runtime/
cutask.rs

1//! This module contains all the main definition of the traits you need to implement
2//! or interact with to create a Copper task.
3
4use crate::config::ComponentConfig;
5use bincode::de::{Decode, Decoder};
6use bincode::enc::{Encode, Encoder};
7use bincode::error::{DecodeError, EncodeError};
8use compact_str::{CompactString, ToCompactString};
9use core::any::{TypeId, type_name};
10use cu29_clock::{PartialCuTimeRange, RobotClock, Tov};
11use cu29_traits::{
12    COMPACT_STRING_CAPACITY, CuCompactString, CuError, CuMsgMetadataTrait, CuResult,
13    ErasedCuStampedData, Metadata,
14};
15use serde::{Deserialize, Serialize};
16
17use alloc::format;
18use core::fmt::{Debug, Display, Formatter, Result as FmtResult};
19
20/// The state of a task.
21// Everything that is stateful in copper for zero copy constraints need to be restricted to this trait.
22pub trait CuMsgPayload: Default + Debug + Clone + Encode + Decode<()> + Serialize + Sized {}
23
24pub trait CuMsgPack {}
25
26// Also anything that follows this contract can be a payload (blanket implementation)
27impl<T: Default + Debug + Clone + Encode + Decode<()> + Serialize + Sized> CuMsgPayload for T {}
28
29macro_rules! impl_cu_msg_pack {
30    ($($name:ident),+) => {
31        impl<'cl, $($name),+> CuMsgPack for ($(&CuMsg<$name>,)+)
32        where
33            $($name: CuMsgPayload),+
34        {}
35    };
36}
37
38impl<T: CuMsgPayload> CuMsgPack for CuMsg<T> {}
39impl<T: CuMsgPayload> CuMsgPack for &CuMsg<T> {}
40impl<T: CuMsgPayload> CuMsgPack for (&CuMsg<T>,) {}
41impl CuMsgPack for () {}
42
43// Apply the macro to generate implementations for tuple sizes up to 5
44impl_cu_msg_pack!(T1, T2);
45impl_cu_msg_pack!(T1, T2, T3);
46impl_cu_msg_pack!(T1, T2, T3, T4);
47impl_cu_msg_pack!(T1, T2, T3, T4, T5);
48
49// A convenience macro to get from a payload or a list of payloads to a proper CuMsg or CuMsgPack
50// declaration for your tasks used for input messages.
51#[macro_export]
52macro_rules! input_msg {
53    ($lt:lifetime, $first:ty, $($rest:ty),+) => {
54        ( & $lt CuMsg<$first>, $( & $lt CuMsg<$rest> ),+ )
55    };
56    ($lt:lifetime, $ty:ty) => {
57        CuMsg<$ty>   // This is for backward compatibility
58    };
59    ($ty:ty) => {
60        CuMsg<$ty>
61    };
62}
63
64// A convenience macro to get from a payload to a proper CuMsg used as output.
65#[macro_export]
66macro_rules! output_msg {
67    ($ty:ty) => {
68        CuMsg<$ty>
69    };
70    ($lt:lifetime, $ty:ty) => {
71        CuMsg<$ty>  // This is for backward compatibility
72    };
73}
74
75/// CuMsgMetadata is a structure that contains metadata common to all CuStampedDataSet.
76#[derive(Debug, Clone, bincode::Encode, bincode::Decode, Serialize, Deserialize)]
77pub struct CuMsgMetadata {
78    /// The time range used for the processing of this message
79    pub process_time: PartialCuTimeRange,
80    /// A small string for real time feedback purposes.
81    /// This is useful for to display on the field when the tasks are operating correctly.
82    pub status_txt: CuCompactString,
83}
84
85impl Metadata for CuMsgMetadata {}
86
87impl CuMsgMetadata {
88    pub fn set_status(&mut self, status: impl ToCompactString) {
89        self.status_txt = CuCompactString(status.to_compact_string());
90    }
91}
92
93impl CuMsgMetadataTrait for CuMsgMetadata {
94    fn process_time(&self) -> PartialCuTimeRange {
95        self.process_time
96    }
97
98    fn status_txt(&self) -> &CuCompactString {
99        &self.status_txt
100    }
101}
102
103impl Display for CuMsgMetadata {
104    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
105        write!(
106            f,
107            "process_time start: {}, process_time end: {}",
108            self.process_time.start, self.process_time.end
109        )
110    }
111}
112
113/// CuMsg is the envelope holding the msg payload and the metadata between tasks.
114#[derive(Default, Debug, Clone, bincode::Encode, bincode::Decode, Serialize)]
115pub struct CuStampedData<T, M>
116where
117    T: CuMsgPayload,
118    M: Metadata,
119{
120    /// This payload is the actual data exchanged between tasks.
121    payload: Option<T>,
122
123    /// The time of validity of the message.
124    /// It can be undefined (None), one measure point or a range of measures (TimeRange).
125    pub tov: Tov,
126
127    /// This metadata is the data that is common to all messages.
128    pub metadata: M,
129}
130
131impl Default for CuMsgMetadata {
132    fn default() -> Self {
133        CuMsgMetadata {
134            process_time: PartialCuTimeRange::default(),
135            status_txt: CuCompactString(CompactString::with_capacity(COMPACT_STRING_CAPACITY)),
136        }
137    }
138}
139
140impl<T, M> CuStampedData<T, M>
141where
142    T: CuMsgPayload,
143    M: Metadata,
144{
145    pub fn new(payload: Option<T>) -> Self {
146        CuStampedData {
147            payload,
148            tov: Tov::default(),
149            metadata: M::default(),
150        }
151    }
152    pub fn payload(&self) -> Option<&T> {
153        self.payload.as_ref()
154    }
155
156    pub fn set_payload(&mut self, payload: T) {
157        self.payload = Some(payload);
158    }
159
160    pub fn clear_payload(&mut self) {
161        self.payload = None;
162    }
163
164    pub fn payload_mut(&mut self) -> &mut Option<T> {
165        &mut self.payload
166    }
167}
168
169impl<T, M> ErasedCuStampedData for CuStampedData<T, M>
170where
171    T: CuMsgPayload,
172    M: CuMsgMetadataTrait + Metadata,
173{
174    fn payload(&self) -> Option<&dyn erased_serde::Serialize> {
175        self.payload
176            .as_ref()
177            .map(|p| p as &dyn erased_serde::Serialize)
178    }
179
180    fn tov(&self) -> Tov {
181        self.tov
182    }
183
184    fn metadata(&self) -> &dyn CuMsgMetadataTrait {
185        &self.metadata
186    }
187}
188
189/// This is the robotics message type for Copper with the correct Metadata type
190/// that will be used by the runtime.
191pub type CuMsg<T> = CuStampedData<T, CuMsgMetadata>;
192
193impl<T: CuMsgPayload> CuStampedData<T, CuMsgMetadata> {
194    /// Reinterprets the payload type carried by this message.
195    ///
196    /// # Safety
197    ///
198    /// The caller must guarantee that the message really contains a payload of type `U`. Failing
199    /// to do so is undefined behaviour.
200    pub unsafe fn assume_payload<U: CuMsgPayload>(&self) -> &CuMsg<U> {
201        unsafe { &*(self as *const CuMsg<T> as *const CuMsg<U>) }
202    }
203
204    /// Mutable variant of [`assume_payload`](Self::assume_payload).
205    ///
206    /// # Safety
207    ///
208    /// The caller must guarantee that mutating the returned message is sound for the actual
209    /// payload type stored in the buffer.
210    pub unsafe fn assume_payload_mut<U: CuMsgPayload>(&mut self) -> &mut CuMsg<U> {
211        unsafe { &mut *(self as *mut CuMsg<T> as *mut CuMsg<U>) }
212    }
213}
214
215impl<T: CuMsgPayload + 'static> CuStampedData<T, CuMsgMetadata> {
216    fn downcast_err<U: CuMsgPayload + 'static>() -> CuError {
217        CuError::from(format!(
218            "CuMsg payload mismatch: {} cannot be reinterpreted as {}",
219            type_name::<T>(),
220            type_name::<U>()
221        ))
222    }
223
224    /// Attempts to view this message as carrying payload `U`.
225    pub fn downcast_ref<U: CuMsgPayload + 'static>(&self) -> CuResult<&CuMsg<U>> {
226        if TypeId::of::<T>() == TypeId::of::<U>() {
227            // Safety: we just proved that T == U.
228            Ok(unsafe { self.assume_payload::<U>() })
229        } else {
230            Err(Self::downcast_err::<U>())
231        }
232    }
233
234    /// Mutable variant of [`downcast_ref`](Self::downcast_ref).
235    pub fn downcast_mut<U: CuMsgPayload + 'static>(&mut self) -> CuResult<&mut CuMsg<U>> {
236        if TypeId::of::<T>() == TypeId::of::<U>() {
237            Ok(unsafe { self.assume_payload_mut::<U>() })
238        } else {
239            Err(Self::downcast_err::<U>())
240        }
241    }
242}
243
244/// The internal state of a task needs to be serializable
245/// so the framework can take a snapshot of the task graph.
246pub trait Freezable {
247    /// This method is called by the framework when it wants to save the task state.
248    /// The default implementation is to encode nothing (stateless).
249    /// If you have a state, you need to implement this method.
250    fn freeze<E: Encoder>(&self, encoder: &mut E) -> Result<(), EncodeError> {
251        Encode::encode(&(), encoder) // default is stateless
252    }
253
254    /// This method is called by the framework when it wants to restore the task to a specific state.
255    /// Here it is similar to Decode but the framework will give you a new instance of the task (the new method will be called)
256    fn thaw<D: Decoder>(&mut self, _decoder: &mut D) -> Result<(), DecodeError> {
257        Ok(())
258    }
259}
260
261/// Bincode Adapter for Freezable tasks
262/// This allows the use of the bincode API directly to freeze and thaw tasks.
263pub struct BincodeAdapter<'a, T: Freezable + ?Sized>(pub &'a T);
264
265impl<'a, T: Freezable + ?Sized> Encode for BincodeAdapter<'a, T> {
266    fn encode<E: Encoder>(&self, encoder: &mut E) -> Result<(), EncodeError> {
267        self.0.freeze(encoder)
268    }
269}
270
271/// A Src Task is a task that only produces messages. For example drivers for sensors are Src Tasks.
272/// They are in push mode from the runtime.
273/// To set the frequency of the pulls and align them to any hw, see the runtime configuration.
274/// Note: A source has the privilege to have a clock passed to it vs a frozen clock.
275pub trait CuSrcTask: Freezable {
276    type Output<'m>: CuMsgPayload;
277    /// Resources required by the task.
278    type Resources<'r>;
279
280    /// Backward-compatible constructor for tasks that do not require resources.
281    fn new(_config: Option<&ComponentConfig>) -> CuResult<Self>
282    where
283        Self: Sized,
284        for<'r> Self::Resources<'r>: Default,
285    {
286        Self::new_with(_config, Default::default())
287    }
288
289    /// Here you need to initialize everything your task will need for the duration of its lifetime.
290    /// The config allows you to access the configuration of the task.
291    fn new_with(
292        _config: Option<&ComponentConfig>,
293        _resources: Self::Resources<'_>,
294    ) -> CuResult<Self>
295    where
296        Self: Sized;
297
298    /// Start is called between the creation of the task and the first call to pre/process.
299    fn start(&mut self, _clock: &RobotClock) -> CuResult<()> {
300        Ok(())
301    }
302
303    /// This is a method called by the runtime before "process". This is a kind of best effort,
304    /// as soon as possible call to give a chance for the task to do some work before to prepare
305    /// to make "process" as short as possible.
306    fn preprocess(&mut self, _clock: &RobotClock) -> CuResult<()> {
307        Ok(())
308    }
309
310    /// Process is the most critical execution of the task.
311    /// The goal will be to produce the output message as soon as possible.
312    /// Use preprocess to prepare the task to make this method as short as possible.
313    fn process<'o>(&mut self, clock: &RobotClock, new_msg: &mut Self::Output<'o>) -> CuResult<()>;
314
315    /// This is a method called by the runtime after "process". It is best effort a chance for
316    /// the task to update some state after process is out of the way.
317    /// It can be use for example to maintain statistics etc. that are not time-critical for the robot.
318    fn postprocess(&mut self, _clock: &RobotClock) -> CuResult<()> {
319        Ok(())
320    }
321
322    /// Called to stop the task. It signals that the *process method won't be called until start is called again.
323    fn stop(&mut self, _clock: &RobotClock) -> CuResult<()> {
324        Ok(())
325    }
326}
327
328/// This is the most generic Task of copper. It is a "transform" task deriving an output from an input.
329pub trait CuTask: Freezable {
330    type Input<'m>: CuMsgPack;
331    type Output<'m>: CuMsgPayload;
332    /// Resources required by the task.
333    type Resources<'r>;
334
335    /// Backward-compatible constructor for tasks that do not require resources.
336    fn new(_config: Option<&ComponentConfig>) -> CuResult<Self>
337    where
338        Self: Sized,
339        for<'r> Self::Resources<'r>: Default,
340    {
341        Self::new_with(_config, Default::default())
342    }
343
344    /// Here you need to initialize everything your task will need for the duration of its lifetime.
345    /// The config allows you to access the configuration of the task.
346    fn new_with(
347        _config: Option<&ComponentConfig>,
348        _resources: Self::Resources<'_>,
349    ) -> CuResult<Self>
350    where
351        Self: Sized;
352
353    /// Start is called between the creation of the task and the first call to pre/process.
354    fn start(&mut self, _clock: &RobotClock) -> CuResult<()> {
355        Ok(())
356    }
357
358    /// This is a method called by the runtime before "process". This is a kind of best effort,
359    /// as soon as possible call to give a chance for the task to do some work before to prepare
360    /// to make "process" as short as possible.
361    fn preprocess(&mut self, _clock: &RobotClock) -> CuResult<()> {
362        Ok(())
363    }
364
365    /// Process is the most critical execution of the task.
366    /// The goal will be to produce the output message as soon as possible.
367    /// Use preprocess to prepare the task to make this method as short as possible.
368    fn process<'i, 'o>(
369        &mut self,
370        _clock: &RobotClock,
371        input: &Self::Input<'i>,
372        output: &mut Self::Output<'o>,
373    ) -> CuResult<()>;
374
375    /// This is a method called by the runtime after "process". It is best effort a chance for
376    /// the task to update some state after process is out of the way.
377    /// It can be use for example to maintain statistics etc. that are not time-critical for the robot.
378    fn postprocess(&mut self, _clock: &RobotClock) -> CuResult<()> {
379        Ok(())
380    }
381
382    /// Called to stop the task. It signals that the *process method won't be called until start is called again.
383    fn stop(&mut self, _clock: &RobotClock) -> CuResult<()> {
384        Ok(())
385    }
386}
387
388/// A Sink Task is a task that only consumes messages. For example drivers for actuators are Sink Tasks.
389pub trait CuSinkTask: Freezable {
390    type Input<'m>: CuMsgPack;
391    /// Resources required by the task.
392    type Resources<'r>;
393
394    /// Backward-compatible constructor for tasks that do not require resources.
395    fn new(_config: Option<&ComponentConfig>) -> CuResult<Self>
396    where
397        Self: Sized,
398        for<'r> Self::Resources<'r>: Default,
399    {
400        Self::new_with(_config, Default::default())
401    }
402
403    /// Here you need to initialize everything your task will need for the duration of its lifetime.
404    /// The config allows you to access the configuration of the task.
405    fn new_with(
406        _config: Option<&ComponentConfig>,
407        _resources: Self::Resources<'_>,
408    ) -> CuResult<Self>
409    where
410        Self: Sized;
411
412    /// Start is called between the creation of the task and the first call to pre/process.
413    fn start(&mut self, _clock: &RobotClock) -> CuResult<()> {
414        Ok(())
415    }
416
417    /// This is a method called by the runtime before "process". This is a kind of best effort,
418    /// as soon as possible call to give a chance for the task to do some work before to prepare
419    /// to make "process" as short as possible.
420    fn preprocess(&mut self, _clock: &RobotClock) -> CuResult<()> {
421        Ok(())
422    }
423
424    /// Process is the most critical execution of the task.
425    /// The goal will be to produce the output message as soon as possible.
426    /// Use preprocess to prepare the task to make this method as short as possible.
427    fn process<'i>(&mut self, _clock: &RobotClock, input: &Self::Input<'i>) -> CuResult<()>;
428
429    /// This is a method called by the runtime after "process". It is best effort a chance for
430    /// the task to update some state after process is out of the way.
431    /// It can be use for example to maintain statistics etc. that are not time-critical for the robot.
432    fn postprocess(&mut self, _clock: &RobotClock) -> CuResult<()> {
433        Ok(())
434    }
435
436    /// Called to stop the task. It signals that the *process method won't be called until start is called again.
437    fn stop(&mut self, _clock: &RobotClock) -> CuResult<()> {
438        Ok(())
439    }
440}
441
442#[cfg(test)]
443mod tests {
444    use super::*;
445    use bincode::{config, decode_from_slice, encode_to_vec};
446
447    #[test]
448    fn test_cucompactstr_encode_decode() {
449        let cstr = CuCompactString(CompactString::from("hello"));
450        let config = config::standard();
451        let encoded = encode_to_vec(&cstr, config).expect("Encoding failed");
452        let (decoded, _): (CuCompactString, usize) =
453            decode_from_slice(&encoded, config).expect("Decoding failed");
454        assert_eq!(cstr.0, decoded.0);
455    }
456}