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, Serialize, Deserialize, Reflect)]
119#[reflect(opaque, from_reflect = false, no_field_bounds)]
120pub struct CuArrayVec<T: Clone, const N: usize>(pub ArrayVec<T, N>);
121
122impl<T: Clone, const N: usize> Default for CuArrayVec<T, N> {
123    fn default() -> Self {
124        Self(ArrayVec::new())
125    }
126}
127
128impl<T, const N: usize> Encode for CuArrayVec<T, N>
129where
130    T: Clone + Encode + 'static,
131{
132    fn encode<E: Encoder>(&self, encoder: &mut E) -> Result<(), EncodeError> {
133        let CuArrayVec(inner) = self;
134        inner.as_slice().encode(encoder)
135    }
136}
137
138impl<T, const N: usize> Decode<()> for CuArrayVec<T, N>
139where
140    T: Clone + Decode<()> + 'static,
141{
142    fn decode<D: Decoder<Context = ()>>(decoder: &mut D) -> Result<Self, DecodeError> {
143        let inner = Vec::<T>::decode(decoder)?;
144        let actual_len = inner.len();
145        if actual_len > N {
146            return Err(DecodeError::ArrayLengthMismatch {
147                required: N,
148                found: actual_len,
149            });
150        }
151
152        let mut array_vec = ArrayVec::new();
153        for item in inner {
154            array_vec.push(item); // Push elements one by one
155        }
156        Ok(CuArrayVec(array_vec))
157    }
158}
159
160impl<'de, T, const N: usize> BorrowDecode<'de, ()> for CuArrayVec<T, N>
161where
162    T: Clone + BorrowDecode<'de, ()> + 'static,
163{
164    fn borrow_decode<D: BorrowDecoder<'de, Context = ()>>(
165        decoder: &mut D,
166    ) -> Result<Self, DecodeError> {
167        let inner = Vec::<T>::borrow_decode(decoder)?;
168        let actual_len = inner.len();
169        if actual_len > N {
170            return Err(DecodeError::ArrayLengthMismatch {
171                required: N,
172                found: actual_len,
173            });
174        }
175
176        let mut array_vec = ArrayVec::new();
177        for item in inner {
178            array_vec.push(item); // Push elements one by one
179        }
180        Ok(CuArrayVec(array_vec))
181    }
182}
183
184/// Producer-side update for a stateful value cached by downstream consumers.
185///
186/// Use this when a value is logically "sticky" across cycles, but you still want its
187/// evolution to be explicit in the message stream. A typical producer pattern is:
188///
189/// - emit [`Self::Set`] when the value first becomes available or changes
190/// - emit [`Self::NoChange`] on cycles where the previously latched value remains valid
191/// - emit [`Self::Clear`] when the cached value must be invalidated
192///
193/// Each consumer that cares about the value should keep a local [`CuLatchedState`] and
194/// apply incoming updates to it. Copper does not implicitly retain or replay the previous
195/// payload for you; the state transition is part of the payload itself.
196///
197/// `NoChange` is intentionally the first variant so its bincode discriminant is zero.
198///
199/// # Examples
200///
201/// ```
202/// use cu29_runtime::payload::{CuLatchedState, CuLatchedStateUpdate};
203///
204/// let mut calibration = CuLatchedState::default();
205///
206/// calibration.update(&CuLatchedStateUpdate::Set(42u32));
207/// assert_eq!(calibration.get(), Some(&42));
208///
209/// calibration.update(&CuLatchedStateUpdate::NoChange);
210/// assert_eq!(calibration.get(), Some(&42));
211///
212/// calibration.update_owned(CuLatchedStateUpdate::Clear);
213/// assert!(calibration.is_unset());
214/// ```
215#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize, Encode, Decode, Reflect)]
216#[reflect(opaque, from_reflect = false, no_field_bounds)]
217pub enum CuLatchedStateUpdate<T: Clone> {
218    /// Leave the consumer-side cache unchanged for this cycle.
219    #[default]
220    NoChange,
221    /// Replace the consumer-side cache with a new value.
222    Set(T),
223    /// Remove the consumer-side cached value.
224    Clear,
225}
226
227impl<T: Clone> CuLatchedStateUpdate<T> {
228    /// Returns `true` when this update leaves the cached value unchanged.
229    pub fn is_no_change(&self) -> bool {
230        matches!(self, Self::NoChange)
231    }
232
233    /// Returns `true` when this update carries a replacement value.
234    pub fn is_set(&self) -> bool {
235        matches!(self, Self::Set(_))
236    }
237
238    /// Returns `true` when this update clears the cached value.
239    pub fn is_clear(&self) -> bool {
240        matches!(self, Self::Clear)
241    }
242}
243
244impl<T: Clone> From<T> for CuLatchedStateUpdate<T> {
245    fn from(value: T) -> Self {
246        Self::Set(value)
247    }
248}
249
250/// Consumer-side cache updated by [`CuLatchedStateUpdate`].
251///
252/// This is typically stored in a task struct and updated as messages arrive. It is not a
253/// runtime-managed global store; each consumer keeps its own copy of the latched state so
254/// replay and deterministic execution follow the same update sequence as live execution.
255#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize, Encode, Decode, Reflect)]
256#[reflect(opaque, from_reflect = false, no_field_bounds)]
257pub enum CuLatchedState<T: Clone> {
258    /// No value has been latched yet, or the previous value was cleared.
259    #[default]
260    Unset,
261    /// The most recently latched value.
262    Set(T),
263}
264
265impl<T: Clone> CuLatchedState<T> {
266    /// Creates an empty latched state.
267    pub fn new() -> Self {
268        Self::Unset
269    }
270
271    /// Returns `true` when a value is currently latched.
272    pub fn is_set(&self) -> bool {
273        matches!(self, Self::Set(_))
274    }
275
276    /// Returns `true` when no value is currently latched.
277    pub fn is_unset(&self) -> bool {
278        matches!(self, Self::Unset)
279    }
280
281    /// Returns the currently latched value, if any.
282    pub fn get(&self) -> Option<&T> {
283        match self {
284            Self::Unset => None,
285            Self::Set(value) => Some(value),
286        }
287    }
288
289    /// Returns the currently latched value as a shared reference, if any.
290    ///
291    /// This is equivalent to [`Self::get`].
292    pub fn as_ref(&self) -> Option<&T> {
293        self.get()
294    }
295
296    /// Replaces the currently latched value.
297    pub fn set(&mut self, value: T) {
298        *self = Self::Set(value);
299    }
300
301    /// Clears the currently latched value.
302    pub fn clear(&mut self) {
303        *self = Self::Unset;
304    }
305
306    /// Removes and returns the currently latched value, leaving the state unset.
307    pub fn take(&mut self) -> Option<T> {
308        let previous = core::mem::take(self);
309        match previous {
310            Self::Unset => None,
311            Self::Set(value) => Some(value),
312        }
313    }
314
315    /// Applies an owned update without cloning the payload value.
316    pub fn update_owned(&mut self, update: CuLatchedStateUpdate<T>) {
317        match update {
318            CuLatchedStateUpdate::NoChange => {}
319            CuLatchedStateUpdate::Set(value) => self.set(value),
320            CuLatchedStateUpdate::Clear => self.clear(),
321        }
322    }
323}
324
325impl<T: Clone> CuLatchedState<T> {
326    /// Applies a borrowed update, cloning only when the update contains a new value.
327    pub fn update(&mut self, update: &CuLatchedStateUpdate<T>) {
328        match update {
329            CuLatchedStateUpdate::NoChange => {}
330            CuLatchedStateUpdate::Set(value) => self.set(value.clone()),
331            CuLatchedStateUpdate::Clear => self.clear(),
332        }
333    }
334}