1use crate::config::ComponentConfig;
5use crate::context::CuContext;
6use crate::reflect::{GetTypeRegistration, Reflect, TypePath, TypeRegistry};
7#[cfg(feature = "reflect")]
8use bevy_reflect;
9use bincode::de::{Decode, Decoder};
10use bincode::enc::{Encode, Encoder};
11use bincode::error::{DecodeError, EncodeError};
12use compact_str::{CompactString, ToCompactString};
13use core::any::{TypeId, type_name};
14use cu29_clock::{PartialCuTimeRange, Tov};
15use cu29_traits::{
16 COMPACT_STRING_CAPACITY, CuCompactString, CuError, CuMsgMetadataTrait, CuMsgOrigin, CuResult,
17 ErasedCuStampedData, Metadata,
18};
19use serde::de::DeserializeOwned;
20use serde::{Deserialize, Serialize};
21
22use alloc::format;
23use core::fmt::{Debug, Display, Formatter, Result as FmtResult};
24
25#[cfg(feature = "reflect")]
28pub trait CuMsgPayload:
29 Default
30 + Debug
31 + Clone
32 + Encode
33 + Decode<()>
34 + Serialize
35 + DeserializeOwned
36 + Reflect
37 + TypePath
38 + Sized
39{
40}
41
42#[cfg(not(feature = "reflect"))]
43pub trait CuMsgPayload:
44 Default + Debug + Clone + Encode + Decode<()> + Serialize + DeserializeOwned + Reflect + Sized
45{
46}
47
48pub trait CuMsgPack {}
49
50#[cfg(feature = "reflect")]
52impl<T> CuMsgPayload for T where
53 T: Default
54 + Debug
55 + Clone
56 + Encode
57 + Decode<()>
58 + Serialize
59 + DeserializeOwned
60 + Reflect
61 + TypePath
62 + Sized
63{
64}
65
66#[cfg(not(feature = "reflect"))]
67impl<T> CuMsgPayload for T where
68 T: Default
69 + Debug
70 + Clone
71 + Encode
72 + Decode<()>
73 + Serialize
74 + DeserializeOwned
75 + Reflect
76 + Sized
77{
78}
79
80macro_rules! impl_cu_msg_pack {
81 ($($name:ident),+) => {
82 impl<'cl, $($name),+> CuMsgPack for ($(&CuMsg<$name>,)+)
83 where
84 $($name: CuMsgPayload),+
85 {}
86 };
87}
88
89macro_rules! impl_cu_msg_pack_up_to {
90 ($first:ident, $second:ident $(, $rest:ident)* $(,)?) => {
91 impl_cu_msg_pack!($first, $second);
92 impl_cu_msg_pack_up_to!(@accumulate ($first, $second); $($rest),*);
93 };
94 (@accumulate ($($acc:ident),+);) => {};
95 (@accumulate ($($acc:ident),+); $next:ident $(, $rest:ident)*) => {
96 impl_cu_msg_pack!($($acc),+, $next);
97 impl_cu_msg_pack_up_to!(@accumulate ($($acc),+, $next); $($rest),*);
98 };
99}
100
101impl<T: CuMsgPayload> CuMsgPack for CuMsg<T> {}
102impl<T: CuMsgPayload> CuMsgPack for &CuMsg<T> {}
103impl<T: CuMsgPayload> CuMsgPack for (&CuMsg<T>,) {}
104impl CuMsgPack for () {}
105
106impl_cu_msg_pack_up_to!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12);
108
109#[macro_export]
112macro_rules! input_msg {
113 ($lt:lifetime, $first:ty, $($rest:ty),+) => {
114 ( & $lt CuMsg<$first>, $( & $lt CuMsg<$rest> ),+ )
115 };
116 ($ty:ty) => {
117 CuMsg<$ty>
118 };
119}
120
121#[macro_export]
123macro_rules! output_msg {
124 ($lt:lifetime, $first:ty, $($rest:ty),+) => {
125 ( CuMsg<$first>, $( CuMsg<$rest> ),+ )
126 };
127 ($first:ty, $($rest:ty),+) => {
128 ( CuMsg<$first>, $( CuMsg<$rest> ),+ )
129 };
130 ($ty:ty) => {
131 CuMsg<$ty>
132 };
133}
134
135pub trait CuSingleOutputMsg {
138 type Payload: CuMsgPayload;
139}
140
141impl<T: CuMsgPayload> CuSingleOutputMsg for CuMsg<T> {
142 type Payload = T;
143}
144
145#[derive(Debug, Clone, bincode::Encode, bincode::Decode, Serialize, Deserialize, Reflect)]
147#[reflect(opaque, from_reflect = false, no_field_bounds)]
148pub struct CuMsgMetadata {
149 pub process_time: PartialCuTimeRange,
151 pub status_txt: CuCompactString,
154 pub origin: Option<CuMsgOrigin>,
156}
157
158impl Metadata for CuMsgMetadata {}
159
160impl CuMsgMetadata {
161 pub fn set_status(&mut self, status: impl ToCompactString) {
162 self.status_txt = CuCompactString(status.to_compact_string());
163 }
164
165 pub fn set_origin(&mut self, origin: CuMsgOrigin) {
166 self.origin = Some(origin);
167 }
168
169 pub fn clear_origin(&mut self) {
170 self.origin = None;
171 }
172}
173
174impl CuMsgMetadataTrait for CuMsgMetadata {
175 fn process_time(&self) -> PartialCuTimeRange {
176 self.process_time
177 }
178
179 fn status_txt(&self) -> &CuCompactString {
180 &self.status_txt
181 }
182
183 fn origin(&self) -> Option<&CuMsgOrigin> {
184 self.origin.as_ref()
185 }
186}
187
188impl Display for CuMsgMetadata {
189 fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
190 write!(
191 f,
192 "process_time start: {}, process_time end: {}",
193 self.process_time.start, self.process_time.end
194 )
195 }
196}
197
198#[derive(Default, Debug, Clone, bincode::Decode, Serialize, Deserialize, Reflect)]
200#[reflect(opaque, from_reflect = false, no_field_bounds)]
201#[serde(bound(
202 serialize = "T: Serialize, M: Serialize",
203 deserialize = "T: DeserializeOwned, M: DeserializeOwned"
204))]
205pub struct CuStampedData<T, M>
206where
207 T: CuMsgPayload,
208 M: Metadata,
209{
210 payload: Option<T>,
212
213 pub tov: Tov,
216
217 pub metadata: M,
219}
220
221impl<T, M> Encode for CuStampedData<T, M>
222where
223 T: CuMsgPayload,
224 M: Metadata,
225{
226 fn encode<E: Encoder>(&self, encoder: &mut E) -> Result<(), EncodeError> {
227 match &self.payload {
235 None => {
236 0u8.encode(encoder)?;
237 }
238 Some(payload) => {
239 1u8.encode(encoder)?;
240 let encoded_start = cu29_traits::observed_encode_bytes();
241 let handle_start = crate::monitoring::current_payload_handle_bytes();
242 payload.encode(encoder)?;
243 let encoded_bytes =
244 cu29_traits::observed_encode_bytes().saturating_sub(encoded_start);
245 let handle_bytes =
246 crate::monitoring::current_payload_handle_bytes().saturating_sub(handle_start);
247 crate::monitoring::record_current_slot_payload_io_stats(
248 core::mem::size_of::<T>(),
249 encoded_bytes,
250 handle_bytes,
251 );
252 }
253 }
254 self.tov.encode(encoder)?;
255 self.metadata.encode(encoder)?;
256 Ok(())
257 }
258}
259
260pub fn encode_metadata_only<T, M, E>(
268 msg: &CuStampedData<T, M>,
269 encoder: &mut E,
270) -> Result<(), EncodeError>
271where
272 T: CuMsgPayload,
273 M: Metadata,
274 E: Encoder,
275{
276 0u8.encode(encoder)?;
277 msg.tov.encode(encoder)?;
278 msg.metadata.encode(encoder)?;
279 Ok(())
280}
281
282impl Default for CuMsgMetadata {
283 fn default() -> Self {
284 CuMsgMetadata {
285 process_time: PartialCuTimeRange::default(),
286 status_txt: CuCompactString(CompactString::with_capacity(COMPACT_STRING_CAPACITY)),
287 origin: None,
288 }
289 }
290}
291
292impl<T, M> CuStampedData<T, M>
293where
294 T: CuMsgPayload,
295 M: Metadata,
296{
297 pub(crate) fn from_parts(payload: Option<T>, tov: Tov, metadata: M) -> Self {
298 CuStampedData {
299 payload,
300 tov,
301 metadata,
302 }
303 }
304
305 pub fn new(payload: Option<T>) -> Self {
306 Self::from_parts(payload, Tov::default(), M::default())
307 }
308 pub fn payload(&self) -> Option<&T> {
309 self.payload.as_ref()
310 }
311
312 pub fn set_payload(&mut self, payload: T) {
313 self.payload = Some(payload);
314 }
315
316 pub fn clear_payload(&mut self) {
317 self.payload = None;
318 }
319
320 pub fn payload_mut(&mut self) -> &mut Option<T> {
321 &mut self.payload
322 }
323}
324
325impl<T, M> ErasedCuStampedData for CuStampedData<T, M>
326where
327 T: CuMsgPayload,
328 M: CuMsgMetadataTrait + Metadata,
329{
330 fn payload(&self) -> Option<&dyn erased_serde::Serialize> {
331 self.payload
332 .as_ref()
333 .map(|p| p as &dyn erased_serde::Serialize)
334 }
335
336 #[cfg(feature = "reflect")]
337 fn payload_reflect(&self) -> Option<&dyn cu29_traits::Reflect> {
338 self.payload
339 .as_ref()
340 .map(|p| p as &dyn cu29_traits::Reflect)
341 }
342
343 fn tov(&self) -> Tov {
344 self.tov
345 }
346
347 fn metadata(&self) -> &dyn CuMsgMetadataTrait {
348 &self.metadata
349 }
350}
351
352pub type CuMsg<T> = CuStampedData<T, CuMsgMetadata>;
355
356impl<T: CuMsgPayload> CuStampedData<T, CuMsgMetadata> {
357 pub unsafe fn assume_payload<U: CuMsgPayload>(&self) -> &CuMsg<U> {
364 unsafe { &*(self as *const CuMsg<T> as *const CuMsg<U>) }
366 }
367
368 pub unsafe fn assume_payload_mut<U: CuMsgPayload>(&mut self) -> &mut CuMsg<U> {
375 unsafe { &mut *(self as *mut CuMsg<T> as *mut CuMsg<U>) }
377 }
378}
379
380impl<T: CuMsgPayload + 'static> CuStampedData<T, CuMsgMetadata> {
381 fn downcast_err<U: CuMsgPayload + 'static>() -> CuError {
382 CuError::from(format!(
383 "CuMsg payload mismatch: {} cannot be reinterpreted as {}",
384 type_name::<T>(),
385 type_name::<U>()
386 ))
387 }
388
389 pub fn downcast_ref<U: CuMsgPayload + 'static>(&self) -> CuResult<&CuMsg<U>> {
391 if TypeId::of::<T>() == TypeId::of::<U>() {
392 Ok(unsafe { self.assume_payload::<U>() })
394 } else {
395 Err(Self::downcast_err::<U>())
396 }
397 }
398
399 pub fn downcast_mut<U: CuMsgPayload + 'static>(&mut self) -> CuResult<&mut CuMsg<U>> {
401 if TypeId::of::<T>() == TypeId::of::<U>() {
402 Ok(unsafe { self.assume_payload_mut::<U>() })
404 } else {
405 Err(Self::downcast_err::<U>())
406 }
407 }
408}
409
410pub trait Freezable {
413 fn freeze<E: Encoder>(&self, encoder: &mut E) -> Result<(), EncodeError> {
417 Encode::encode(&(), encoder) }
419
420 fn thaw<D: Decoder>(&mut self, _decoder: &mut D) -> Result<(), DecodeError> {
423 Ok(())
424 }
425}
426
427pub struct BincodeAdapter<'a, T: Freezable + ?Sized>(pub &'a T);
430
431impl<'a, T: Freezable + ?Sized> Encode for BincodeAdapter<'a, T> {
432 fn encode<E: Encoder>(&self, encoder: &mut E) -> Result<(), EncodeError> {
433 self.0.freeze(encoder)
434 }
435}
436
437pub trait CuSrcTask: Freezable + Reflect {
442 type Output<'m>: CuMsgPayload;
443 type Resources<'r>;
445
446 fn register_debug_state_types(registry: &mut TypeRegistry)
452 where
453 Self: GetTypeRegistration + Sized,
454 {
455 registry.register::<Self>();
456 }
457
458 fn debug_state_type_path() -> &'static str
460 where
461 Self: TypePath + Sized,
462 {
463 Self::type_path()
464 }
465
466 fn with_debug_state<R>(&self, f: impl FnOnce(&dyn Reflect) -> R) -> R
471 where
472 Self: Sized,
473 {
474 f(self)
475 }
476
477 fn new(_config: Option<&ComponentConfig>, _resources: Self::Resources<'_>) -> CuResult<Self>
480 where
481 Self: Sized;
482
483 fn start(&mut self, _ctx: &CuContext) -> CuResult<()> {
485 Ok(())
486 }
487
488 fn preprocess(&mut self, _ctx: &CuContext) -> CuResult<()> {
492 Ok(())
493 }
494
495 fn process<'o>(&mut self, ctx: &CuContext, new_msg: &mut Self::Output<'o>) -> CuResult<()>;
499
500 fn postprocess(&mut self, _ctx: &CuContext) -> CuResult<()> {
504 Ok(())
505 }
506
507 fn stop(&mut self, _ctx: &CuContext) -> CuResult<()> {
509 Ok(())
510 }
511}
512
513pub trait CuTask: Freezable + Reflect {
515 type Input<'m>: CuMsgPack;
516 type Output<'m>: CuMsgPayload;
517 type Resources<'r>;
519
520 fn register_debug_state_types(registry: &mut TypeRegistry)
526 where
527 Self: GetTypeRegistration + Sized,
528 {
529 registry.register::<Self>();
530 }
531
532 fn debug_state_type_path() -> &'static str
534 where
535 Self: TypePath + Sized,
536 {
537 Self::type_path()
538 }
539
540 fn with_debug_state<R>(&self, f: impl FnOnce(&dyn Reflect) -> R) -> R
545 where
546 Self: Sized,
547 {
548 f(self)
549 }
550
551 fn new(_config: Option<&ComponentConfig>, _resources: Self::Resources<'_>) -> CuResult<Self>
554 where
555 Self: Sized;
556
557 fn start(&mut self, _ctx: &CuContext) -> CuResult<()> {
559 Ok(())
560 }
561
562 fn preprocess(&mut self, _ctx: &CuContext) -> CuResult<()> {
566 Ok(())
567 }
568
569 fn process<'i, 'o>(
573 &mut self,
574 _ctx: &CuContext,
575 input: &Self::Input<'i>,
576 output: &mut Self::Output<'o>,
577 ) -> CuResult<()>;
578
579 fn postprocess(&mut self, _ctx: &CuContext) -> CuResult<()> {
583 Ok(())
584 }
585
586 fn stop(&mut self, _ctx: &CuContext) -> CuResult<()> {
588 Ok(())
589 }
590}
591
592pub trait CuSinkTask: Freezable + Reflect {
594 type Input<'m>: CuMsgPack;
595 type Resources<'r>;
597
598 fn register_debug_state_types(registry: &mut TypeRegistry)
604 where
605 Self: GetTypeRegistration + Sized,
606 {
607 registry.register::<Self>();
608 }
609
610 fn debug_state_type_path() -> &'static str
612 where
613 Self: TypePath + Sized,
614 {
615 Self::type_path()
616 }
617
618 fn with_debug_state<R>(&self, f: impl FnOnce(&dyn Reflect) -> R) -> R
623 where
624 Self: Sized,
625 {
626 f(self)
627 }
628
629 fn new(_config: Option<&ComponentConfig>, _resources: Self::Resources<'_>) -> CuResult<Self>
632 where
633 Self: Sized;
634
635 fn start(&mut self, _ctx: &CuContext) -> CuResult<()> {
637 Ok(())
638 }
639
640 fn preprocess(&mut self, _ctx: &CuContext) -> CuResult<()> {
644 Ok(())
645 }
646
647 fn process<'i>(&mut self, _ctx: &CuContext, input: &Self::Input<'i>) -> CuResult<()>;
651
652 fn postprocess(&mut self, _ctx: &CuContext) -> CuResult<()> {
656 Ok(())
657 }
658
659 fn stop(&mut self, _ctx: &CuContext) -> CuResult<()> {
661 Ok(())
662 }
663}
664
665#[cfg(test)]
666mod tests {
667 use super::*;
668 use bincode::{config, decode_from_slice, encode_to_vec};
669
670 #[test]
671 fn test_cucompactstr_encode_decode() {
672 let cstr = CuCompactString(CompactString::from("hello"));
673 let config = config::standard();
674 let encoded = encode_to_vec(&cstr, config).expect("Encoding failed");
675 let (decoded, _): (CuCompactString, usize) =
676 decode_from_slice(&encoded, config).expect("Decoding failed");
677 assert_eq!(cstr.0, decoded.0);
678 }
679
680 #[cfg(not(feature = "reflect"))]
688 #[derive(Debug, Clone, bincode::Encode, bincode::Decode, Serialize, Deserialize)]
689 struct TestHandlePayload {
690 handle: crate::pool::CuHandle<Vec<u8>>,
691 }
692
693 #[cfg(not(feature = "reflect"))]
694 impl Default for TestHandlePayload {
695 fn default() -> Self {
696 Self {
697 handle: crate::pool::CuHandle::new_detached(Vec::new()),
698 }
699 }
700 }
701
702 #[cfg(not(feature = "reflect"))]
703 impl TestHandlePayload {
704 fn payload_should_log(&self) -> bool {
707 self.handle.payload_should_log()
708 }
709 }
710
711 #[cfg(not(feature = "reflect"))]
717 #[test]
718 fn test_encode_skips_payload_for_untouched_handle() {
719 use crate::pool::{CuHandle, HandleContent};
720 let cfg = config::standard();
721
722 let untouched = TestHandlePayload {
723 handle: CuHandle::new_detached_with_mode(
724 vec![0xAA, 0xBB, 0xCC, 0xDD],
725 HandleContent::TouchedOnly,
726 ),
727 };
728 let msg_skip: CuMsg<TestHandlePayload> = CuMsg::new(Some(untouched));
729 let skip_bytes = encode_to_vec(&msg_skip, cfg).expect("encode");
730
731 let touched_payload = TestHandlePayload {
732 handle: CuHandle::new_detached_with_mode(
733 vec![0xAA, 0xBB, 0xCC, 0xDD],
734 HandleContent::TouchedOnly,
735 ),
736 };
737 touched_payload.handle.mark_touched();
738 let msg_keep: CuMsg<TestHandlePayload> = CuMsg::new(Some(touched_payload));
739 let keep_bytes = encode_to_vec(&msg_keep, cfg).expect("encode");
740
741 assert_eq!(
742 skip_bytes[0], 0u8,
743 "first byte must be the no-payload presence tag for an untouched TouchedOnly handle"
744 );
745 assert_eq!(
746 keep_bytes[0], 1u8,
747 "first byte must be the payload-present tag once the handle was touched"
748 );
749 assert!(
750 keep_bytes.len() > skip_bytes.len(),
751 "touched encoding ({} bytes) must include payload content; skip is {} bytes",
752 keep_bytes.len(),
753 skip_bytes.len()
754 );
755 }
756
757 #[cfg(not(feature = "reflect"))]
760 #[test]
761 fn test_encode_keeps_payload_for_default_mode() {
762 use crate::pool::{CuHandle, HandleContent};
763 let cfg = config::standard();
764
765 let payload = TestHandlePayload {
766 handle: CuHandle::new_detached_with_mode(vec![1, 2, 3], HandleContent::All),
767 };
768 let msg: CuMsg<TestHandlePayload> = CuMsg::new(Some(payload));
769 let bytes = encode_to_vec(&msg, cfg).expect("encode");
770 assert_eq!(
771 bytes[0], 1u8,
772 "default (HandleContent::All) must keep emitting the payload"
773 );
774 }
775}