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