use std::borrow::Cow;
use serde::{de::DeserializeOwned, Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Message {
meta: MessageMeta,
#[cfg_attr(feature = "engine", serde(default))]
source: MessageSource,
value: serde_json::Value,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
pub struct MessageMeta {
pub namespace: Cow<'static, str>,
pub identifier: Cow<'static, str>,
pub bus: Option<Cow<'static, str>>,
}
impl MessageMeta {
pub const fn new(
namespace: &'static str,
identifier: &'static str,
bus: Option<&'static str>,
) -> Self {
Self {
namespace: Cow::Borrowed(namespace),
identifier: Cow::Borrowed(identifier),
bus: match bus {
Some(bus) => Some(Cow::Borrowed(bus)),
None => None,
},
}
}
}
pub trait MessageType: Serialize + DeserializeOwned {
const MESSAGE_META: MessageMeta;
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct MessageSource {
pub coupling: Option<Coupling>,
pub module_slot_index: Option<u16>,
pub module_slot_cockpit_index: Option<u8>,
}
impl MessageSource {
pub fn is_front(&self) -> bool {
matches!(self.coupling, Some(Coupling::Front))
}
pub fn is_rear(&self) -> bool {
matches!(self.coupling, Some(Coupling::Rear))
}
}
#[doc(hidden)]
#[macro_export]
macro_rules! message_type {
($type:ty, $namespace:expr, $identifier:expr, $bus:expr) => {
impl $crate::message::MessageType for $type {
const MESSAGE_META: $crate::message::MessageMeta =
$crate::message::MessageMeta::new($namespace, $identifier, Some($bus));
}
};
($type:ty, $namespace:expr, $identifier:expr) => {
impl $crate::message::MessageType for $type {
const MESSAGE_META: $crate::message::MessageMeta =
$crate::message::MessageMeta::new($namespace, $identifier, None);
}
};
}
#[doc(inline)]
pub use message_type;
#[derive(Debug, thiserror::Error)]
pub enum MessageValueError {
#[error("invalid message type")]
InvalidType,
#[error("{0}")]
Serialization(SerializationError),
}
#[derive(Debug, thiserror::Error)]
#[error("serialization error: {0}")]
pub struct SerializationError(String);
#[derive(Debug, thiserror::Error)]
pub enum MessageHandleError {
#[error("{0}")]
Serialization(SerializationError),
#[error("handler error: {0}")]
Handler(Box<dyn std::error::Error>),
}
impl Message {
pub fn new<T: MessageType>(value: &T) -> Self {
Self {
meta: T::MESSAGE_META.clone(),
source: MessageSource::default(),
value: serde_json::to_value(value).unwrap(),
}
}
pub fn meta(&self) -> &MessageMeta {
&self.meta
}
pub fn source(&self) -> &MessageSource {
&self.source
}
pub fn value<T: MessageType>(&self) -> Result<T, MessageValueError> {
if self.meta != T::MESSAGE_META {
return Err(MessageValueError::InvalidType);
}
serde_json::from_value(self.value.clone())
.map_err(|e| MessageValueError::Serialization(SerializationError(e.to_string())))
}
pub fn has_type<T: MessageType>(&self) -> bool {
self.meta == T::MESSAGE_META
}
pub fn handle<T: MessageType>(
&self,
f: impl FnOnce(T) -> Result<(), Box<dyn std::error::Error>>,
) -> Result<bool, MessageHandleError> {
match self.value::<T>() {
Ok(v) => f(v).map_err(MessageHandleError::Handler).map(|_| true),
Err(MessageValueError::InvalidType) => Ok(false),
Err(MessageValueError::Serialization(e)) => Err(MessageHandleError::Serialization(e)),
}
}
#[cfg(feature = "engine")]
pub fn with_source(&self, source: MessageSource) -> Self {
Self {
meta: self.meta.clone(),
source,
value: self.value.clone(),
}
}
}
pub trait IntoMessageTargets {
fn into_message_targets(self) -> impl IntoIterator<Item = MessageTarget>;
}
impl IntoMessageTargets for MessageTarget {
fn into_message_targets(self) -> impl IntoIterator<Item = MessageTarget> {
[self]
}
}
impl<T> IntoMessageTargets for T
where
T: IntoIterator<Item = MessageTarget>,
{
fn into_message_targets(self) -> impl IntoIterator<Item = MessageTarget> {
self
}
}
#[cfg(feature = "ffi")]
pub fn send_message<T: MessageType>(message: &T, targets: impl IntoMessageTargets) {
let message = Message::new(message);
let this = lotus_script_sys::FfiObject::new(&message);
let targets = targets
.into_message_targets()
.into_iter()
.collect::<Vec<_>>();
let targets = lotus_script_sys::FfiObject::new(&targets);
unsafe { lotus_script_sys::messages::send(targets.packed(), this.packed()) }
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum MessageTarget {
Myself,
ChildByIndex(usize),
Cockpit(u8),
Broadcast {
across_couplings: bool,
include_self: bool,
},
AcrossCoupling {
coupling: Coupling,
cascade: bool,
},
Parent,
}
impl MessageTarget {
pub fn broadcast_except_self(across_couplings: bool) -> Self {
Self::Broadcast {
across_couplings,
include_self: false,
}
}
pub fn broadcast_all() -> Self {
Self::Broadcast {
across_couplings: true,
include_self: true,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum Coupling {
Front,
Rear,
}
impl Coupling {
#[cfg(feature = "ffi")]
pub fn open_bus(&self, bus: &str) {
let bus = lotus_script_sys::FfiObject::new(&bus);
unsafe { lotus_script_sys::vehicle::open_bus(*self as u32, bus.packed()) };
}
#[cfg(feature = "ffi")]
pub fn close_bus(&self, bus: &str) {
let bus = lotus_script_sys::FfiObject::new(&bus);
unsafe { lotus_script_sys::vehicle::close_bus(*self as u32, bus.packed()) };
}
#[cfg(feature = "ffi")]
pub fn is_open(&self, bus: &str) -> bool {
let bus = lotus_script_sys::FfiObject::new(&bus);
unsafe { lotus_script_sys::vehicle::is_bus_open(*self as u32, bus.packed()) == 1 }
}
#[cfg(feature = "ffi")]
pub fn is_coupled(&self) -> bool {
unsafe { lotus_script_sys::vehicle::is_coupled(*self as u32) == 1 }
}
}
impl From<u32> for Coupling {
fn from(value: u32) -> Self {
match value {
0 => Self::Front,
1 => Self::Rear,
_ => panic!("invalid coupling value: {}", value),
}
}
}
#[cfg(test)]
mod tests {
use serde::Deserialize;
use super::*;
#[derive(Debug, Serialize, Deserialize, PartialEq)]
struct TestMessage {
value: i32,
}
message_type!(TestMessage, "test", "message", "ibis");
#[test]
fn test_message() {
let message = Message::new(&TestMessage { value: 42 });
assert_eq!(message.meta(), &TestMessage::MESSAGE_META);
let value = message.value::<TestMessage>().unwrap();
assert_eq!(value, TestMessage { value: 42 });
message
.handle::<TestMessage>(|m| {
assert_eq!(m.value, 42);
Ok(())
})
.expect("message handle failed");
}
}