Skip to main content

cu29_runtime/
payload.rs

1//! Copper-friendly payload helpers used in task messages and task-local caches.
2//!
3//! Payload types need to stay compatible with Copper's preallocated message buffers,
4//! deterministic logging, and `no_std` targets. This module therefore focuses on
5//! fixed-capacity containers and explicit state-transition payloads such as
6//! [`CuLatchedStateUpdate`] and [`CuLatchedState`].
7//!
8//! A latched state is useful when a value changes rarely, but consumers still need a
9//! deterministic view of it during live execution, logging, and replay. Producers send
10//! updates with [`CuLatchedStateUpdate`], and each consumer keeps its own local cache in
11//! [`CuLatchedState`].
12use crate::reflect::Reflect;
13use arrayvec::ArrayVec;
14#[cfg(feature = "reflect")]
15use bevy_reflect;
16use bincode::BorrowDecode;
17use bincode::de::{BorrowDecoder, Decoder};
18use bincode::enc::Encoder;
19use bincode::error::{DecodeError, EncodeError};
20use bincode::{Decode, Encode};
21use serde_derive::{Deserialize, Serialize};
22
23#[cfg(not(feature = "std"))]
24pub use alloc::format;
25#[cfg(not(feature = "std"))]
26pub use alloc::vec::Vec;
27
28/// Copper friendly wrapper for a fixed size array.
29/// `T: Clone` is required because this type derives `Reflect`, and
30/// the reflection path requires the reflected value to be cloneable.
31#[derive(Clone, Debug, Default, Serialize, Deserialize, Reflect)]
32#[reflect(opaque, from_reflect = false, no_field_bounds)]
33pub struct CuArray<T: Clone, const N: usize> {
34    inner: ArrayVec<T, N>,
35}
36
37impl<T: Clone, const N: usize> CuArray<T, N> {
38    pub fn new() -> Self {
39        Self {
40            inner: ArrayVec::new(),
41        }
42    }
43
44    pub fn fill_from_iter<I>(&mut self, iter: I)
45    where
46        I: IntoIterator<Item = T>,
47    {
48        self.inner.clear(); // Clear existing data
49        for value in iter.into_iter().take(N) {
50            self.inner.push(value);
51        }
52    }
53
54    pub fn len(&self) -> usize {
55        self.inner.len()
56    }
57
58    pub fn is_empty(&self) -> bool {
59        self.inner.len() == 0
60    }
61
62    pub fn as_slice(&self) -> &[T] {
63        &self.inner
64    }
65
66    pub fn capacity(&self) -> usize {
67        N
68    }
69}
70
71impl<T, const N: usize> Encode for CuArray<T, N>
72where
73    T: Encode + Clone,
74{
75    fn encode<E: Encoder>(&self, encoder: &mut E) -> Result<(), EncodeError> {
76        // Encode the length first
77        (self.inner.len() as u32).encode(encoder)?;
78
79        // Encode elements in the `ArrayVec`
80        for elem in &self.inner {
81            elem.encode(encoder)?;
82        }
83
84        Ok(())
85    }
86}
87
88impl<T, const N: usize> Decode<()> for CuArray<T, N>
89where
90    T: Decode<()> + Clone,
91{
92    fn decode<D: Decoder<Context = ()>>(decoder: &mut D) -> Result<Self, DecodeError> {
93        // Decode the length first
94        let len = u32::decode(decoder)? as usize;
95        if len > N {
96            return Err(DecodeError::OtherString(format!(
97                "Decoded length {len} exceeds maximum capacity {N}"
98            )));
99        }
100
101        // Decode elements into a new `ArrayVec`
102        let mut inner = ArrayVec::new();
103        for _ in 0..len {
104            inner.push(T::decode(decoder)?);
105        }
106
107        Ok(Self { inner })
108    }
109}
110
111/// A Copper-friendly wrapper around ArrayVec with bincode serialization support.
112///
113/// This provides a fixed-capacity, stack-allocated vector that can be efficiently
114/// serialized and deserialized. It is particularly useful for message payloads that
115/// need to avoid heap allocations while supporting varying lengths of data up to a maximum.
116///
117/// Unlike standard Vec, CuArrayVec will never reallocate or use the heap for the elements storage.
118#[derive(Debug, Clone)]
119pub struct CuArrayVec<T, const N: usize>(pub ArrayVec<T, N>);
120
121impl<T, const N: usize> Default for CuArrayVec<T, N> {
122    fn default() -> Self {
123        Self(ArrayVec::new())
124    }
125}
126
127impl<T, const N: usize> Encode for CuArrayVec<T, N>
128where
129    T: Encode + 'static,
130{
131    fn encode<E: Encoder>(&self, encoder: &mut E) -> Result<(), EncodeError> {
132        let CuArrayVec(inner) = self;
133        inner.as_slice().encode(encoder)
134    }
135}
136
137impl<T, const N: usize> Decode<()> for CuArrayVec<T, N>
138where
139    T: Decode<()> + 'static,
140{
141    fn decode<D: Decoder<Context = ()>>(decoder: &mut D) -> Result<Self, DecodeError> {
142        let inner = Vec::<T>::decode(decoder)?;
143        let actual_len = inner.len();
144        if actual_len > N {
145            return Err(DecodeError::ArrayLengthMismatch {
146                required: N,
147                found: actual_len,
148            });
149        }
150
151        let mut array_vec = ArrayVec::new();
152        for item in inner {
153            array_vec.push(item); // Push elements one by one
154        }
155        Ok(CuArrayVec(array_vec))
156    }
157}
158
159impl<'de, T, const N: usize> BorrowDecode<'de, ()> for CuArrayVec<T, N>
160where
161    T: BorrowDecode<'de, ()> + 'static,
162{
163    fn borrow_decode<D: BorrowDecoder<'de, Context = ()>>(
164        decoder: &mut D,
165    ) -> Result<Self, DecodeError> {
166        let inner = Vec::<T>::borrow_decode(decoder)?;
167        let actual_len = inner.len();
168        if actual_len > N {
169            return Err(DecodeError::ArrayLengthMismatch {
170                required: N,
171                found: actual_len,
172            });
173        }
174
175        let mut array_vec = ArrayVec::new();
176        for item in inner {
177            array_vec.push(item); // Push elements one by one
178        }
179        Ok(CuArrayVec(array_vec))
180    }
181}
182
183/// Producer-side update for a stateful value cached by downstream consumers.
184///
185/// Use this when a value is logically "sticky" across cycles, but you still want its
186/// evolution to be explicit in the message stream. A typical producer pattern is:
187///
188/// - emit [`Self::Set`] when the value first becomes available or changes
189/// - emit [`Self::NoChange`] on cycles where the previously latched value remains valid
190/// - emit [`Self::Clear`] when the cached value must be invalidated
191///
192/// Each consumer that cares about the value should keep a local [`CuLatchedState`] and
193/// apply incoming updates to it. Copper does not implicitly retain or replay the previous
194/// payload for you; the state transition is part of the payload itself.
195///
196/// `NoChange` is intentionally the first variant so its bincode discriminant is zero.
197///
198/// # Examples
199///
200/// ```
201/// use cu29_runtime::payload::{CuLatchedState, CuLatchedStateUpdate};
202///
203/// let mut calibration = CuLatchedState::default();
204///
205/// calibration.update(&CuLatchedStateUpdate::Set(42u32));
206/// assert_eq!(calibration.get(), Some(&42));
207///
208/// calibration.update(&CuLatchedStateUpdate::NoChange);
209/// assert_eq!(calibration.get(), Some(&42));
210///
211/// calibration.update_owned(CuLatchedStateUpdate::Clear);
212/// assert!(calibration.is_unset());
213/// ```
214#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize, Encode, Decode, Reflect)]
215#[reflect(opaque, from_reflect = false, no_field_bounds)]
216pub enum CuLatchedStateUpdate<T: Clone> {
217    /// Leave the consumer-side cache unchanged for this cycle.
218    #[default]
219    NoChange,
220    /// Replace the consumer-side cache with a new value.
221    Set(T),
222    /// Remove the consumer-side cached value.
223    Clear,
224}
225
226impl<T: Clone> CuLatchedStateUpdate<T> {
227    /// Returns `true` when this update leaves the cached value unchanged.
228    pub fn is_no_change(&self) -> bool {
229        matches!(self, Self::NoChange)
230    }
231
232    /// Returns `true` when this update carries a replacement value.
233    pub fn is_set(&self) -> bool {
234        matches!(self, Self::Set(_))
235    }
236
237    /// Returns `true` when this update clears the cached value.
238    pub fn is_clear(&self) -> bool {
239        matches!(self, Self::Clear)
240    }
241}
242
243impl<T: Clone> From<T> for CuLatchedStateUpdate<T> {
244    fn from(value: T) -> Self {
245        Self::Set(value)
246    }
247}
248
249/// Consumer-side cache updated by [`CuLatchedStateUpdate`].
250///
251/// This is typically stored in a task struct and updated as messages arrive. It is not a
252/// runtime-managed global store; each consumer keeps its own copy of the latched state so
253/// replay and deterministic execution follow the same update sequence as live execution.
254#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize, Encode, Decode, Reflect)]
255#[reflect(opaque, from_reflect = false, no_field_bounds)]
256pub enum CuLatchedState<T: Clone> {
257    /// No value has been latched yet, or the previous value was cleared.
258    #[default]
259    Unset,
260    /// The most recently latched value.
261    Set(T),
262}
263
264impl<T: Clone> CuLatchedState<T> {
265    /// Creates an empty latched state.
266    pub fn new() -> Self {
267        Self::Unset
268    }
269
270    /// Returns `true` when a value is currently latched.
271    pub fn is_set(&self) -> bool {
272        matches!(self, Self::Set(_))
273    }
274
275    /// Returns `true` when no value is currently latched.
276    pub fn is_unset(&self) -> bool {
277        matches!(self, Self::Unset)
278    }
279
280    /// Returns the currently latched value, if any.
281    pub fn get(&self) -> Option<&T> {
282        match self {
283            Self::Unset => None,
284            Self::Set(value) => Some(value),
285        }
286    }
287
288    /// Returns the currently latched value as a shared reference, if any.
289    ///
290    /// This is equivalent to [`Self::get`].
291    pub fn as_ref(&self) -> Option<&T> {
292        self.get()
293    }
294
295    /// Replaces the currently latched value.
296    pub fn set(&mut self, value: T) {
297        *self = Self::Set(value);
298    }
299
300    /// Clears the currently latched value.
301    pub fn clear(&mut self) {
302        *self = Self::Unset;
303    }
304
305    /// Removes and returns the currently latched value, leaving the state unset.
306    pub fn take(&mut self) -> Option<T> {
307        let previous = core::mem::take(self);
308        match previous {
309            Self::Unset => None,
310            Self::Set(value) => Some(value),
311        }
312    }
313
314    /// Applies an owned update without cloning the payload value.
315    pub fn update_owned(&mut self, update: CuLatchedStateUpdate<T>) {
316        match update {
317            CuLatchedStateUpdate::NoChange => {}
318            CuLatchedStateUpdate::Set(value) => self.set(value),
319            CuLatchedStateUpdate::Clear => self.clear(),
320        }
321    }
322}
323
324impl<T: Clone> CuLatchedState<T> {
325    /// Applies a borrowed update, cloning only when the update contains a new value.
326    pub fn update(&mut self, update: &CuLatchedStateUpdate<T>) {
327        match update {
328            CuLatchedStateUpdate::NoChange => {}
329            CuLatchedStateUpdate::Set(value) => self.set(value.clone()),
330            CuLatchedStateUpdate::Clear => self.clear(),
331        }
332    }
333}