lotus_shared/
message.rs

1//! Handle messages between scripts or from the engine.
2//! See [Message] and [MessageType] for more information.
3use std::borrow::Cow;
4
5use serde::{de::DeserializeOwned, Deserialize, Serialize};
6
7/// Represents a message that can be sent between scripts or from the engine.
8///
9/// # Example
10/// ```no_run
11/// # use serde::{Deserialize, Serialize};
12/// # use lotus_shared::message::{Message, MessageType};
13/// # use lotus_shared::message_type;
14///
15/// // Define a message type, has to implement Serialize and Deserialize
16/// #[derive(Serialize, Deserialize)]
17/// struct TestMessage {
18///     value: i32,
19/// }
20///
21/// // Register the message type
22/// message_type!(TestMessage, "test", "message");
23/// ```
24#[derive(Debug, Clone, Serialize, Deserialize)]
25pub struct Message {
26    meta: MessageMeta,
27    #[cfg_attr(feature = "engine", serde(default))]
28    source: MessageSource,
29    value: serde_json::Value,
30}
31
32/// Represents the metadata for a message type.
33///
34/// The combination of `namespace` and `identifier` should be globally unique for each message type.
35#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
36pub struct MessageMeta {
37    /// The namespace of the message type.
38    pub namespace: Cow<'static, str>,
39    /// The identifier of the message type.
40    pub identifier: Cow<'static, str>,
41    /// The bus the message should be sent on.
42    pub bus: Option<Cow<'static, str>>,
43}
44
45impl MessageMeta {
46    /// Creates a new message meta.
47    pub const fn new(
48        namespace: &'static str,
49        identifier: &'static str,
50        bus: Option<&'static str>,
51    ) -> Self {
52        Self {
53            namespace: Cow::Borrowed(namespace),
54            identifier: Cow::Borrowed(identifier),
55            bus: match bus {
56                Some(bus) => Some(Cow::Borrowed(bus)),
57                None => None,
58            },
59        }
60    }
61}
62
63/// Represents a message type that can be sent between scripts or from the engine.
64/// The [MessageType::MESSAGE_META] constant should return a globally unique message meta for the message type.
65pub trait MessageType: Serialize + DeserializeOwned {
66    /// The metadata for the message type.
67    const MESSAGE_META: MessageMeta;
68}
69
70/// Represents the source of a message.
71#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
72pub struct MessageSource {
73    /// If the message is coming from another vehicle across couplings, this will be Some.
74    pub coupling: Option<Coupling>,
75    /// If the message is coming from a module, these will be Some.
76    pub module_slot_index: Option<u16>,
77    pub module_slot_cockpit_index: Option<u8>,
78}
79
80impl MessageSource {
81    /// Returns `true` if the message is coming from the vehicle in front.
82    pub fn is_front(&self) -> bool {
83        matches!(self.coupling, Some(Coupling::Front))
84    }
85
86    /// Returns `true` if the message is coming from the vehicle in rear.
87    pub fn is_rear(&self) -> bool {
88        matches!(self.coupling, Some(Coupling::Rear))
89    }
90}
91
92#[doc(hidden)]
93#[macro_export]
94macro_rules! message_type {
95    ($type:ty, $namespace:expr, $identifier:expr, $bus:expr) => {
96        impl $crate::message::MessageType for $type {
97            const MESSAGE_META: $crate::message::MessageMeta =
98                $crate::message::MessageMeta::new($namespace, $identifier, Some($bus));
99        }
100    };
101    ($type:ty, $namespace:expr, $identifier:expr) => {
102        impl $crate::message::MessageType for $type {
103            const MESSAGE_META: $crate::message::MessageMeta =
104                $crate::message::MessageMeta::new($namespace, $identifier, None);
105        }
106    };
107}
108
109#[doc(inline)]
110pub use message_type;
111
112#[derive(Debug, thiserror::Error)]
113pub enum MessageValueError {
114    #[error("invalid message type")]
115    InvalidType,
116    #[error("{0}")]
117    Serialization(SerializationError),
118}
119
120#[derive(Debug, thiserror::Error)]
121#[error("serialization error: {0}")]
122pub struct SerializationError(String);
123
124#[derive(Debug, thiserror::Error)]
125pub enum MessageHandleError {
126    #[error("{0}")]
127    Serialization(SerializationError),
128    #[error("handler error: {0}")]
129    Handler(Box<dyn std::error::Error>),
130}
131
132impl Message {
133    /// Creates a new message with the given value.
134    pub fn new<T: MessageType>(value: &T) -> Self {
135        Self {
136            meta: T::MESSAGE_META.clone(),
137            source: MessageSource::default(),
138            value: serde_json::to_value(value).unwrap(),
139        }
140    }
141
142    /// Returns the message type metadata.
143    pub fn meta(&self) -> &MessageMeta {
144        &self.meta
145    }
146
147    /// Returns the source of the message.
148    pub fn source(&self) -> &MessageSource {
149        &self.source
150    }
151
152    /// Returns the message value as the given type. Returns a [MessageValueError] if the message has a different type.
153    pub fn value<T: MessageType>(&self) -> Result<T, MessageValueError> {
154        if self.meta != T::MESSAGE_META {
155            return Err(MessageValueError::InvalidType);
156        }
157
158        serde_json::from_value(self.value.clone())
159            .map_err(|e| MessageValueError::Serialization(SerializationError(e.to_string())))
160    }
161
162    /// Returns `true` if the message has the given type.
163    pub fn has_type<T: MessageType>(&self) -> bool {
164        self.meta == T::MESSAGE_META
165    }
166
167    /// Handle the message with the given handler function.
168    /// Returns `Ok(true)` if the message was handled, `Ok(false)` if the message has a different type,
169    /// or `Err` if the message could not be deserialized or the handler function returned an error.
170    /// The handler function should return `Ok(())` if the message was handled successfully.
171    pub fn handle<T: MessageType>(
172        &self,
173        f: impl FnOnce(T) -> Result<(), Box<dyn std::error::Error>>,
174    ) -> Result<bool, MessageHandleError> {
175        match self.value::<T>() {
176            Ok(v) => f(v).map_err(MessageHandleError::Handler).map(|_| true),
177            Err(MessageValueError::InvalidType) => Ok(false),
178            Err(MessageValueError::Serialization(e)) => Err(MessageHandleError::Serialization(e)),
179        }
180    }
181
182    #[cfg(feature = "engine")]
183    pub fn with_source(&self, source: MessageSource) -> Self {
184        Self {
185            meta: self.meta.clone(),
186            source,
187            value: self.value.clone(),
188        }
189    }
190}
191
192pub trait IntoMessageTargets {
193    fn into_message_targets(self) -> impl IntoIterator<Item = MessageTarget>;
194}
195
196impl IntoMessageTargets for MessageTarget {
197    fn into_message_targets(self) -> impl IntoIterator<Item = MessageTarget> {
198        [self]
199    }
200}
201
202impl<T> IntoMessageTargets for T
203where
204    T: IntoIterator<Item = MessageTarget>,
205{
206    fn into_message_targets(self) -> impl IntoIterator<Item = MessageTarget> {
207        self
208    }
209}
210
211/// Sends the message to the given targets.
212///
213/// # Example
214/// ```no_run
215/// # use lotus_shared::message::{Message, MessageTarget, send_message};
216/// # use serde::{Deserialize, Serialize};
217/// # use lotus_shared::message_type;
218/// # #[derive(Serialize, Deserialize)]
219/// # struct TestMessage { value: i32 };
220/// # message_type!(TestMessage, "test", "message");
221/// // Send a message with only a single target
222/// send_message(&TestMessage { value: 42 }, MessageTarget::Myself);
223/// // Send a message to multiple targets
224/// send_message(&TestMessage { value: 42 }, [MessageTarget::Myself, MessageTarget::ChildByIndex(0)]);
225/// ```
226#[cfg(feature = "ffi")]
227pub fn send_message<T: MessageType>(message: &T, targets: impl IntoMessageTargets) {
228    let message = Message::new(message);
229    let this = lotus_script_sys::FfiObject::new(&message);
230    let targets = targets
231        .into_message_targets()
232        .into_iter()
233        .collect::<Vec<_>>();
234    let targets = lotus_script_sys::FfiObject::new(&targets);
235
236    unsafe { lotus_script_sys::messages::send(targets.packed(), this.packed()) }
237}
238
239/// Represents a message target.
240#[derive(Debug, Clone, Serialize, Deserialize)]
241pub enum MessageTarget {
242    /// The script itself.
243    Myself,
244    /// The child script at the given index.
245    ChildByIndex(usize),
246    /// To all modules in the cockpit with the given index.
247    Cockpit(u8),
248    /// Broadcast to scripts based on the specified scope.
249    Broadcast {
250        /// Whether to include coupled vehicles.
251        across_couplings: bool,
252        /// Whether to include the sending script.
253        include_self: bool,
254    },
255    /// Send to a specific coupling.
256    AcrossCoupling {
257        /// The coupling to send to.
258        coupling: Coupling,
259        /// Whether to cascade the message to the next coupling.
260        cascade: bool,
261    },
262    /// The parent script.
263    Parent,
264}
265
266impl MessageTarget {
267    /// Helper to create a broadcast target that excludes self
268    pub fn broadcast_except_self(across_couplings: bool) -> Self {
269        Self::Broadcast {
270            across_couplings,
271            include_self: false,
272        }
273    }
274
275    pub fn broadcast_all() -> Self {
276        Self::Broadcast {
277            across_couplings: true,
278            include_self: true,
279        }
280    }
281}
282
283#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
284#[serde(rename_all = "camelCase")]
285pub enum Coupling {
286    /// The coupling to the front vehicle.
287    Front,
288    /// The coupling to the rear vehicle.
289    Rear,
290}
291
292impl Coupling {
293    #[cfg(feature = "ffi")]
294    /// Opens the given bus.
295    pub fn open_bus(&self, bus: &str) {
296        let bus = lotus_script_sys::FfiObject::new(&bus);
297        unsafe { lotus_script_sys::vehicle::open_bus(*self as u32, bus.packed()) };
298    }
299
300    #[cfg(feature = "ffi")]
301    /// Closes the given bus.
302    pub fn close_bus(&self, bus: &str) {
303        let bus = lotus_script_sys::FfiObject::new(&bus);
304        unsafe { lotus_script_sys::vehicle::close_bus(*self as u32, bus.packed()) };
305    }
306
307    #[cfg(feature = "ffi")]
308    /// Returns `true` if the given bus is open.
309    pub fn is_open(&self, bus: &str) -> bool {
310        let bus = lotus_script_sys::FfiObject::new(&bus);
311        unsafe { lotus_script_sys::vehicle::is_bus_open(*self as u32, bus.packed()) == 1 }
312    }
313
314    #[cfg(feature = "ffi")]
315    pub fn is_coupled(&self) -> bool {
316        unsafe { lotus_script_sys::vehicle::is_coupled(*self as u32) == 1 }
317    }
318}
319
320impl From<u32> for Coupling {
321    fn from(value: u32) -> Self {
322        match value {
323            0 => Self::Front,
324            1 => Self::Rear,
325            _ => panic!("invalid coupling value: {}", value),
326        }
327    }
328}
329
330#[cfg(test)]
331mod tests {
332    use serde::Deserialize;
333
334    use super::*;
335
336    #[derive(Debug, Serialize, Deserialize, PartialEq)]
337    struct TestMessage {
338        value: i32,
339    }
340
341    message_type!(TestMessage, "test", "message", "ibis");
342
343    #[test]
344    fn test_message() {
345        let message = Message::new(&TestMessage { value: 42 });
346        assert_eq!(message.meta(), &TestMessage::MESSAGE_META);
347
348        let value = message.value::<TestMessage>().unwrap();
349
350        assert_eq!(value, TestMessage { value: 42 });
351
352        message
353            .handle::<TestMessage>(|m| {
354                assert_eq!(m.value, 42);
355                Ok(())
356            })
357            .expect("message handle failed");
358    }
359}