rapace_core/
descriptor.rs

1//! Message descriptors (hot and cold paths).
2
3use crate::FrameFlags;
4
5/// Size of inline payload in bytes.
6pub const INLINE_PAYLOAD_SIZE: usize = 16;
7
8/// Sentinel value indicating payload is inline (not in a slot).
9pub const INLINE_PAYLOAD_SLOT: u32 = u32::MAX;
10
11/// Sentinel value indicating no deadline.
12pub const NO_DEADLINE: u64 = u64::MAX;
13
14/// Hot-path message descriptor (64 bytes, one cache line).
15///
16/// This is the primary descriptor used for frame dispatch.
17/// Fits in a single cache line for performance.
18#[derive(Clone, Copy)]
19#[repr(C, align(64))]
20pub struct MsgDescHot {
21    // Identity (16 bytes)
22    /// Unique message ID per session, monotonic.
23    pub msg_id: u64,
24    /// Logical stream (0 = control channel).
25    pub channel_id: u32,
26    /// For RPC dispatch, or control verb.
27    pub method_id: u32,
28
29    // Payload location (16 bytes)
30    /// Slot index (u32::MAX = inline).
31    pub payload_slot: u32,
32    /// Generation counter for ABA safety.
33    pub payload_generation: u32,
34    /// Offset within slot.
35    pub payload_offset: u32,
36    /// Actual payload length.
37    pub payload_len: u32,
38
39    // Flow control & timing (16 bytes)
40    /// Frame flags (EOS, CANCEL, ERROR, etc.).
41    pub flags: FrameFlags,
42    /// Credits being granted to peer.
43    pub credit_grant: u32,
44    /// Deadline in nanoseconds (monotonic clock). NO_DEADLINE = no deadline.
45    pub deadline_ns: u64,
46
47    // Inline payload for small messages (16 bytes)
48    /// When payload_slot == u32::MAX, payload lives here.
49    /// No alignment guarantees beyond u8.
50    pub inline_payload: [u8; INLINE_PAYLOAD_SIZE],
51}
52
53const _: () = assert!(core::mem::size_of::<MsgDescHot>() == 64);
54
55impl MsgDescHot {
56    /// Create a new descriptor with default values.
57    pub const fn new() -> Self {
58        Self {
59            msg_id: 0,
60            channel_id: 0,
61            method_id: 0,
62            payload_slot: INLINE_PAYLOAD_SLOT,
63            payload_generation: 0,
64            payload_offset: 0,
65            payload_len: 0,
66            flags: FrameFlags::empty(),
67            credit_grant: 0,
68            deadline_ns: NO_DEADLINE,
69            inline_payload: [0; INLINE_PAYLOAD_SIZE],
70        }
71    }
72
73    /// Returns true if this frame has a deadline set.
74    #[inline]
75    pub const fn has_deadline(&self) -> bool {
76        self.deadline_ns != NO_DEADLINE
77    }
78
79    /// Check if the deadline has passed.
80    ///
81    /// Returns `true` if the frame has a deadline and it has expired.
82    #[inline]
83    pub fn is_expired(&self, now_ns: u64) -> bool {
84        self.deadline_ns != NO_DEADLINE && now_ns > self.deadline_ns
85    }
86
87    /// Returns true if payload is inline (not in a slot).
88    #[inline]
89    pub const fn is_inline(&self) -> bool {
90        self.payload_slot == INLINE_PAYLOAD_SLOT
91    }
92
93    /// Returns true if this is a control frame (channel 0).
94    #[inline]
95    pub const fn is_control(&self) -> bool {
96        self.channel_id == 0
97    }
98
99    /// Get inline payload slice (only valid if is_inline()).
100    #[inline]
101    pub fn inline_payload(&self) -> &[u8] {
102        &self.inline_payload[..self.payload_len as usize]
103    }
104}
105
106impl Default for MsgDescHot {
107    fn default() -> Self {
108        Self::new()
109    }
110}
111
112impl core::fmt::Debug for MsgDescHot {
113    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
114        f.debug_struct("MsgDescHot")
115            .field("msg_id", &self.msg_id)
116            .field("channel_id", &self.channel_id)
117            .field("method_id", &self.method_id)
118            .field("payload_slot", &self.payload_slot)
119            .field("payload_generation", &self.payload_generation)
120            .field("payload_offset", &self.payload_offset)
121            .field("payload_len", &self.payload_len)
122            .field("flags", &self.flags)
123            .field("credit_grant", &self.credit_grant)
124            .field("deadline_ns", &self.deadline_ns)
125            .field("is_inline", &self.is_inline())
126            .finish()
127    }
128}
129
130/// Cold-path message descriptor (observability data).
131///
132/// Stored in a parallel array or separate telemetry ring.
133/// Can be disabled for performance.
134#[derive(Debug, Clone, Copy, Default)]
135#[repr(C, align(64))]
136pub struct MsgDescCold {
137    /// Correlates with hot descriptor.
138    pub msg_id: u64,
139    /// Distributed tracing ID.
140    pub trace_id: u64,
141    /// Span within trace.
142    pub span_id: u64,
143    /// Parent span ID.
144    pub parent_span_id: u64,
145    /// When enqueued (nanos since epoch).
146    pub timestamp_ns: u64,
147    /// 0=off, 1=metadata, 2=full payload.
148    pub debug_level: u32,
149    pub _reserved: u32,
150}
151
152const _: () = assert!(core::mem::size_of::<MsgDescCold>() == 64);