elfo_core/
message.rs

1use std::{any::Any, fmt};
2
3use fxhash::FxHashMap;
4use linkme::distributed_slice;
5use metrics::Label;
6use serde::{Deserialize, Serialize};
7use smallbox::{smallbox, SmallBox};
8
9use crate::dumping;
10
11pub trait Message: fmt::Debug + Clone + Any + Send + Serialize + for<'de> Deserialize<'de> {
12    #[doc(hidden)]
13    const VTABLE: &'static MessageVTable;
14
15    // Called while upcasting/downcasting to avoid
16    // [rust#47384](https://github.com/rust-lang/rust/issues/47384).
17    #[doc(hidden)]
18    fn _touch(&self);
19}
20
21pub trait Request: Message {
22    type Response: fmt::Debug + Clone + Send + Serialize;
23
24    #[doc(hidden)]
25    type Wrapper: Message + Into<Self::Response> + From<Self::Response>;
26}
27
28pub struct AnyMessage {
29    vtable: &'static MessageVTable,
30    data: SmallBox<dyn Any + Send, [usize; 23]>,
31}
32
33impl AnyMessage {
34    #[inline]
35    pub fn new<M: Message>(message: M) -> Self {
36        message._touch();
37
38        AnyMessage {
39            vtable: M::VTABLE,
40            data: smallbox!(message),
41        }
42    }
43
44    #[inline]
45    pub fn name(&self) -> &'static str {
46        self.vtable.name
47    }
48
49    #[inline]
50    pub fn protocol(&self) -> &'static str {
51        self.vtable.protocol
52    }
53
54    #[inline]
55    #[doc(hidden)]
56    pub fn labels(&self) -> &'static [Label] {
57        self.vtable.labels
58    }
59
60    #[inline]
61    #[doc(hidden)]
62    pub fn dumping_allowed(&self) -> bool {
63        self.vtable.dumping_allowed
64    }
65
66    #[inline]
67    pub fn is<M: Message>(&self) -> bool {
68        self.data.is::<M>()
69    }
70
71    #[inline]
72    pub fn downcast_ref<M: Message>(&self) -> Option<&M> {
73        self.data.downcast_ref::<M>().map(|message| {
74            message._touch();
75            message
76        })
77    }
78
79    #[inline]
80    pub fn downcast<M: Message>(self) -> Result<M, AnyMessage> {
81        if !self.is::<M>() {
82            return Err(self);
83        }
84
85        let message = self
86            .data
87            .downcast::<M>()
88            .expect("cannot downcast")
89            .into_inner();
90
91        message._touch();
92        Ok(message)
93    }
94
95    #[inline]
96    #[doc(hidden)]
97    pub fn erase(&self) -> dumping::ErasedMessage {
98        (self.vtable.erase)(self)
99    }
100}
101
102impl Clone for AnyMessage {
103    #[inline]
104    fn clone(&self) -> Self {
105        (self.vtable.clone)(self)
106    }
107}
108
109impl fmt::Debug for AnyMessage {
110    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
111        (self.vtable.debug)(self, f)
112    }
113}
114
115// Message Virtual Table.
116
117// Reexported in `elfo::_priv`.
118#[derive(Clone)]
119pub struct MessageVTable {
120    /// Just a message's name.
121    pub name: &'static str,
122    /// A protocol's name.
123    /// Usually, it's a crate name where the message is defined.
124    pub protocol: &'static str,
125    pub labels: &'static [Label],
126    pub dumping_allowed: bool, // TODO: introduce `DumpingMode`.
127    pub clone: fn(&AnyMessage) -> AnyMessage,
128    pub debug: fn(&AnyMessage, &mut fmt::Formatter<'_>) -> fmt::Result,
129    pub erase: fn(&AnyMessage) -> dumping::ErasedMessage,
130}
131
132#[distributed_slice]
133pub static MESSAGE_LIST: [&'static MessageVTable] = [..];
134
135thread_local! {
136    static MESSAGE_BY_NAME: FxHashMap<(&'static str, &'static str), &'static MessageVTable> = {
137        MESSAGE_LIST.iter()
138            .map(|vtable| ((vtable.protocol, vtable.name), *vtable))
139            .collect()
140    };
141}
142
143pub(crate) fn init() {
144    MESSAGE_BY_NAME.with(|_| ());
145}