1use std::borrow::Cow;
4
5use serde::{de::DeserializeOwned, Deserialize, Serialize};
6
7#[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#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
36pub struct MessageMeta {
37 pub namespace: Cow<'static, str>,
39 pub identifier: Cow<'static, str>,
41 pub bus: Option<Cow<'static, str>>,
43}
44
45impl MessageMeta {
46 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
63pub trait MessageType: Serialize + DeserializeOwned {
66 const MESSAGE_META: MessageMeta;
68}
69
70#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
72pub struct MessageSource {
73 pub coupling: Option<Coupling>,
75 pub module_slot_index: Option<u16>,
77 pub module_slot_cockpit_index: Option<u8>,
78}
79
80impl MessageSource {
81 pub fn is_front(&self) -> bool {
83 matches!(self.coupling, Some(Coupling::Front))
84 }
85
86 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 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 pub fn meta(&self) -> &MessageMeta {
144 &self.meta
145 }
146
147 pub fn source(&self) -> &MessageSource {
149 &self.source
150 }
151
152 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 pub fn has_type<T: MessageType>(&self) -> bool {
164 self.meta == T::MESSAGE_META
165 }
166
167 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#[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#[derive(Debug, Clone, Serialize, Deserialize)]
241pub enum MessageTarget {
242 Myself,
244 ChildByIndex(usize),
246 Cockpit(u8),
248 Broadcast {
250 across_couplings: bool,
252 include_self: bool,
254 },
255 AcrossCoupling {
257 coupling: Coupling,
259 cascade: bool,
261 },
262 Parent,
264}
265
266impl MessageTarget {
267 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 Front,
288 Rear,
290}
291
292impl Coupling {
293 #[cfg(feature = "ffi")]
294 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 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 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}