Skip to main content

palladium_actor/
message.rs

1use crate::errors::PayloadError;
2use bytes::Bytes;
3use std::any::Any;
4
5// ── Message ───────────────────────────────────────────────────────────────────
6
7/// FNV-1a 64-bit hash.  Stable across builds for a fixed input string, making
8/// it suitable for generating compile-time `TYPE_TAG` constants.
9pub const fn fnv1a_64(s: &str) -> u64 {
10    let bytes = s.as_bytes();
11    let mut hash = 14695981039346656037_u64;
12    let mut i = 0;
13    while i < bytes.len() {
14        hash ^= bytes[i] as u64;
15        hash = hash.wrapping_mul(1099511628211_u64);
16        i += 1;
17    }
18    hash
19}
20
21/// A typed message that can be sent to an actor.
22///
23/// Implementors define the response type and a compile-time type tag used for
24/// wire routing.  The tag is a `u64`; the `#[derive(Message)]` proc-macro
25/// (Phase 2) will generate it via FNV-1a of the fully-qualified type name.
26/// For now, implementations provide it manually.
27pub trait Message: Send + 'static {
28    type Response: Send + 'static;
29    const TYPE_TAG: u64;
30}
31
32// ── RemoteMessage ───────────────────────────────────────────────────────────
33
34/// A message that can be serialized for remote delivery.
35#[cfg(feature = "serde")]
36pub trait RemoteMessage: Message + serde::Serialize + for<'de> serde::Deserialize<'de> {}
37
38/// Blanket impl for any type satisfying the `RemoteMessage` bounds.
39#[cfg(feature = "serde")]
40impl<M> RemoteMessage for M where M: Message + serde::Serialize + for<'de> serde::Deserialize<'de> {}
41
42// ── MessagePayload ────────────────────────────────────────────────────────────
43
44/// The payload carried alongside an [`Envelope`](crate::envelope::Envelope).
45///
46/// `Local` is used for same-core delivery with zero serialization.
47/// `Serialized` carries an opaque byte buffer for cross-core or cross-engine
48/// delivery.
49#[derive(Debug)]
50pub enum MessagePayload {
51    Local(Box<dyn Any + Send>),
52    Serialized(Bytes),
53}
54
55impl MessagePayload {
56    pub fn local<M: Send + 'static>(msg: M) -> Self {
57        Self::Local(Box::new(msg))
58    }
59
60    pub fn serialized(bytes: impl Into<Bytes>) -> Self {
61        Self::Serialized(bytes.into())
62    }
63
64    /// Consume the payload and extract an owned value of type `T`.
65    ///
66    /// Returns `None` if the payload is `Serialized` or the stored value is
67    /// not of type `T`.
68    pub fn downcast_owned<T: Send + 'static>(self) -> Option<T> {
69        match self {
70            Self::Local(v) => v.downcast::<T>().ok().map(|b| *b),
71            Self::Serialized(_) => None,
72        }
73    }
74
75    /// Typed access to a `Local` payload.  Returns [`PayloadError::TypeMismatch`]
76    /// if the stored value is not of type `M`, or [`PayloadError::WrongVariant`]
77    /// if the payload is `Serialized`.
78    pub fn extract<M: Message>(&self) -> Result<&M, PayloadError> {
79        match self {
80            Self::Local(v) => v.downcast_ref::<M>().ok_or(PayloadError::TypeMismatch),
81            Self::Serialized(_) => Err(PayloadError::WrongVariant),
82        }
83    }
84
85    pub fn downcast_ref<M: 'static>(&self) -> Option<&M> {
86        match self {
87            Self::Local(v) => v.downcast_ref::<M>(),
88            Self::Serialized(_) => None,
89        }
90    }
91
92    pub fn as_serialized(&self) -> Option<&Bytes> {
93        match self {
94            Self::Serialized(b) => Some(b),
95            Self::Local(_) => None,
96        }
97    }
98
99    pub fn into_serialized(self) -> Option<Bytes> {
100        match self {
101            Self::Serialized(b) => Some(b),
102            Self::Local(_) => None,
103        }
104    }
105
106    /// Meaningful only for `Serialized`; returns 0 for `Local`.
107    pub fn payload_len(&self) -> u32 {
108        match self {
109            Self::Local(_) => 0,
110            Self::Serialized(b) => b.len().min(u32::MAX as usize) as u32,
111        }
112    }
113}