lotus_shared/
message.rs

1//! Handle messages between scripts or from the engine.
2//! See [Message] and [MessageType] for more information.
3use serde::{de::DeserializeOwned, Deserialize, Serialize};
4
5/// Represents a message that can be sent between scripts or from the engine.
6///
7/// # Example
8/// ```no_run
9/// # use serde::{Deserialize, Serialize};
10/// # use lotus_script::prelude::*;
11/// # use lotus_shared::message::{Message, MessageType};
12/// #[derive(Serialize, Deserialize)]
13/// struct TestMessage {
14///     value: i32,
15/// }
16///
17/// impl MessageType for TestMessage {
18///     fn id() -> &'static str {
19///         "test_message"
20///     }
21/// }
22///
23/// fn handle(message: &Message) {
24///     message.handle::<TestMessage>(|m| {
25///        log::info!("Received message: {}", m.value);
26///        Ok(())
27///     }).expect("message handle failed");
28/// }
29/// # handle(&Message::new(TestMessage { value: 42 }));
30#[derive(Debug, Clone, Serialize, Deserialize)]
31pub struct Message {
32    id: String,
33    value: serde_json::Value,
34}
35
36/// Represents a message type that can be sent between scripts or from the engine.
37/// The [MessageType::id] method should return a globally unique identifier for the message type. If in doubt, use a UUID.
38pub trait MessageType: Serialize + DeserializeOwned {
39    /// The identifier for the message type.
40    fn id() -> &'static str;
41}
42
43#[derive(Debug, thiserror::Error)]
44pub enum MessageValueError {
45    #[error("invalid message type")]
46    InvalidType,
47    #[error("{0}")]
48    Serialization(SerializationError),
49}
50
51#[derive(Debug, thiserror::Error)]
52#[error("serialization error: {0}")]
53pub struct SerializationError(String);
54
55#[derive(Debug, thiserror::Error)]
56pub enum MessageHandleError {
57    #[error("{0}")]
58    Serialization(SerializationError),
59    #[error("handler error: {0}")]
60    Handler(Box<dyn std::error::Error>),
61}
62
63impl Message {
64    /// Creates a new message with the given value.
65    pub fn new<T: MessageType>(value: T) -> Self {
66        Self {
67            id: T::id().to_string(),
68            value: serde_json::to_value(value).unwrap(),
69        }
70    }
71
72    /// Returns the message type ID.
73    pub fn id(&self) -> &str {
74        &self.id
75    }
76
77    /// Returns the message value as the given type. Returns a [MessageValueError] if the message has a different type.
78    pub fn value<T: MessageType>(&self) -> Result<T, MessageValueError> {
79        if self.id != T::id() {
80            return Err(MessageValueError::InvalidType);
81        }
82
83        serde_json::from_value(self.value.clone())
84            .map_err(|e| MessageValueError::Serialization(SerializationError(e.to_string())))
85    }
86
87    /// Returns `true` if the message has the given type.
88    pub fn has_type<T: MessageType>(&self) -> bool {
89        self.id == T::id()
90    }
91
92    /// Handle the message with the given handler function.
93    /// Returns `Ok(true)` if the message was handled, `Ok(false)` if the message has a different type,
94    /// or `Err` if the message could not be deserialized or the handler function returned an error.
95    /// The handler function should return `Ok(())` if the message was handled successfully.
96    pub fn handle<T: MessageType>(
97        &self,
98        f: impl FnOnce(T) -> Result<(), Box<dyn std::error::Error>>,
99    ) -> Result<bool, MessageHandleError> {
100        match self.value::<T>() {
101            Ok(v) => f(v).map_err(MessageHandleError::Handler).map(|_| true),
102            Err(MessageValueError::InvalidType) => Ok(false),
103            Err(MessageValueError::Serialization(e)) => Err(MessageHandleError::Serialization(e)),
104        }
105    }
106
107    /// Sends the message to the given target.
108    #[cfg(feature = "ffi")]
109    pub fn send(&self, target: MessageTarget) {
110        let this = lotus_script_sys::FfiObject::new(self);
111        let target = lotus_script_sys::FfiObject::new(&target);
112
113        unsafe { lotus_script_sys::messages::send(target.packed(), this.packed()) }
114    }
115}
116
117/// Represents a message target.
118#[derive(Debug, Clone, Serialize, Deserialize)]
119pub enum MessageTarget {
120    /// The script itself.
121    Myself,
122    /// The child script at the given index.
123    ChildByIndex(usize),
124    /// All scripts of this vehicle.
125    Broadcast,
126    /// All scripts of this vehicle except this one.
127    BroadcastExceptSelf,
128    /// The parent script.
129    Parent,
130}
131
132#[cfg(test)]
133mod tests {
134    use serde::Deserialize;
135
136    use super::*;
137
138    #[derive(Debug, Serialize, Deserialize, PartialEq)]
139    struct TestMessage {
140        value: i32,
141    }
142
143    impl MessageType for TestMessage {
144        fn id() -> &'static str {
145            "test_message"
146        }
147    }
148
149    #[test]
150    fn test_message() {
151        let message = Message::new(TestMessage { value: 42 });
152        assert_eq!(message.id(), "test_message");
153
154        let value = message.value::<TestMessage>().unwrap();
155
156        assert_eq!(value, TestMessage { value: 42 });
157
158        message
159            .handle::<TestMessage>(|m| {
160                assert_eq!(m.value, 42);
161                Ok(())
162            })
163            .expect("message handle failed");
164    }
165}