Skip to main content

polybox_core/
inbox.rs

1use crate::*;
2use futures::future::BoxFuture;
3use std::{any::TypeId, future::Future, marker::PhantomData, sync::Arc};
4use type_sets::SubsetOf;
5
6/// A trait that allows for conversions to [`DynInbox`].
7pub trait PolyBox: DynPolyBox + Clone {
8    /// The set of message types that this inbox can accept.
9    type Set: Members;
10
11    /// Converts into a dynamic inbox without checking if the types are compatible.
12    ///
13    /// Avoid using this method unless you are sure that the types are compatible, as it can lead to runtime errors. Instead, consider using `into_dyn_checked` or `into_dyn_subset` for safer conversions.
14    ///
15    /// # Safety
16    /// This method is not marked as unsafe, because violating the type system can
17    /// only lead to runtime errors, not undefined behavior.
18    fn into_dyn_unchecked<T>(self) -> DynInbox<T>;
19}
20
21/// A trait that extends [`PolyBox`] with some helper methods.
22pub trait PolyboxExt: PolyBox {
23    /// Converts into a dynamic inbox with a subset of the original types.
24    ///
25    /// This conversion is type-safe, and entirely at compile-time.
26    fn into_dyn_subset<T>(self) -> DynInbox<T>
27    where
28        T: SubsetOf<Self::Set>,
29    {
30        self.into_dyn_unchecked()
31    }
32
33    /// Converts into a dynamic inbox with the full set of original types.
34    fn into_dyn(self) -> DynInbox<Self::Set> {
35        self.into_dyn_unchecked()
36    }
37
38    /// Converts into a dynamic inbox, checking at runtime if the types are compatible.
39    fn into_dyn_checked<T: Members>(self) -> Result<DynInbox<T>, Self> {
40        if self.accepts_msgs(&T::members()) {
41            Ok(self.into_dyn_unchecked())
42        } else {
43            Err(self)
44        }
45    }
46
47    /// Checks if the inbox accepts a message of the given type.
48    #[must_use]
49    fn accepts_msg(&self, id: TypeId) -> bool {
50        <Self::Set as Members>::members().contains(&id)
51    }
52
53    /// Checks if the inbox accepts messages of the given types.
54    #[must_use]
55    fn accepts_msgs(&self, ids: &[TypeId]) -> bool {
56        ids.iter()
57            .all(|id| <Self::Set as Members>::members().contains(id))
58    }
59
60    /// Send any message, checking at runtime if the message is accepted or not.
61    fn send_checked<T: Message>(
62        &self,
63        msg: T,
64    ) -> impl Future<Output = Result<Output<T>, SendCheckedError<T>>> + Send {
65        async {
66            let (payload, output) = T::build_payload(msg);
67            let payload = BoxedPayload::new::<T>(payload);
68
69            match self._send_boxed_payload_checked(payload).await {
70                Ok(()) => Ok(output),
71                Err(SendCheckedError::Closed(payload)) => {
72                    let payload = payload
73                        .downcast::<T>()
74                        .expect("Failed to convert payload back");
75
76                    Err(SendCheckedError::Closed(T::destroy_payload(payload)))
77                }
78                Err(SendCheckedError::NotAccepted(payload)) => {
79                    Err(SendCheckedError::NotAccepted(T::destroy_payload(
80                        payload
81                            .downcast::<T>()
82                            .expect("Failed to convert payload back"),
83                    )))
84                }
85            }
86        }
87    }
88
89    /// Same as [`Self::send_checked`], but blocks the current thread until the message is sent.
90    fn send_checked_blocking<T: Message>(&self, msg: T) -> Result<Output<T>, SendCheckedError<T>> {
91        let (payload, output) = T::build_payload(msg);
92        let payload = BoxedPayload::new::<T>(payload);
93
94        match self._send_boxed_payload_checked_blocking(payload) {
95            Ok(()) => Ok(output),
96            Err(SendCheckedError::Closed(payload)) => {
97                let payload = payload
98                    .downcast::<T>()
99                    .expect("Failed to convert payload back");
100
101                Err(SendCheckedError::Closed(T::destroy_payload(payload)))
102            }
103            Err(SendCheckedError::NotAccepted(payload)) => {
104                Err(SendCheckedError::NotAccepted(T::destroy_payload(
105                    payload
106                        .downcast::<T>()
107                        .expect("Failed to convert payload back"),
108                )))
109            }
110        }
111    }
112}
113impl<T: PolyBox> PolyboxExt for T {}
114
115/// Object-safe sub-trait of [`PolyBox`], allowing for dynamic dispatch.
116pub trait DynPolyBox: Send + Sync {
117    /// Send a boxed payload.
118    fn _send_boxed_payload_checked(
119        &self,
120        msg: BoxedPayload,
121    ) -> BoxFuture<'_, Result<(), SendCheckedError<BoxedPayload>>>;
122
123    /// Same as [`Self::_send_boxed_payload_checked`], but blocks the current thread until the message is sent.
124    fn _send_boxed_payload_checked_blocking(
125        &self,
126        msg: BoxedPayload,
127    ) -> Result<(), SendCheckedError<BoxedPayload>> {
128        futures::executor::block_on(self._send_boxed_payload_checked(msg))
129    }
130}
131
132/// A dynamic inbox that can accept messages of any type, as long as they are part of the specified set.
133///
134/// An inbox is typed as: `DynInbox<Set![Msg1, Msg2, ...]>`.
135///
136/// Conversions between inboxes:
137/// - Into more specific subsets -> [`PolyboxExt::into_dyn_subset`].
138/// - Into more general supersets -> [`PolyboxExt::into_dyn_checked`] or [`PolyBox::into_dyn_unchecked`].
139pub struct DynInbox<T> {
140    inbox: Arc<dyn DynPolyBox>,
141    _t: PhantomData<fn() -> T>,
142}
143
144impl<T> Clone for DynInbox<T> {
145    fn clone(&self) -> Self {
146        Self {
147            inbox: self.inbox.clone(),
148            _t: PhantomData,
149        }
150    }
151}
152
153impl<T> DynInbox<T> {
154    pub fn new_unchecked(inbox: Arc<dyn DynPolyBox>) -> Self {
155        Self {
156            inbox,
157            _t: PhantomData,
158        }
159    }
160
161    pub fn new<R>(inbox: R) -> Self
162    where
163        R: DynPolyBox + PolyBox + 'static,
164        T: SubsetOf<R::Set>,
165    {
166        Self {
167            inbox: Arc::new(inbox),
168            _t: PhantomData,
169        }
170    }
171}
172
173impl<T: Members> PolyBox for DynInbox<T> {
174    type Set = T;
175
176    fn into_dyn_unchecked<R>(self) -> DynInbox<R> {
177        DynInbox::new_unchecked(self.inbox)
178    }
179}
180
181impl<T, R> Sends<T> for DynInbox<R>
182where
183    T: Message<Kind: MessageSpecifier<T, Output: Send, Payload: Send>>,
184    R: Members + Contains<T>,
185{
186    async fn send(&self, msg: T) -> Result<Output<T>, SendError<T>> {
187        self.send_checked(msg).await.map_err(|e| match e {
188            SendCheckedError::Closed(msg) => SendError(msg),
189            SendCheckedError::NotAccepted(_msg) => {
190                panic!(
191                    "Payload was not accepted, this should not happen if the type system is used correctly"
192                )
193            }
194        })
195    }
196}
197
198impl<T> DynPolyBox for DynInbox<T> {
199    fn _send_boxed_payload_checked(
200        &self,
201        msg: BoxedPayload,
202    ) -> BoxFuture<'_, Result<(), SendCheckedError<BoxedPayload>>> {
203        self.inbox._send_boxed_payload_checked(msg)
204    }
205}