Skip to main content

yog_event/wire/
message.rs

1//! What crosses the bus: a payload in Yog Data's binary form, at an
2//! address — an event's or a service's — or at its branch for a key.
3
4use yog_data::Schema;
5use yog_ids as ids;
6
7/// A message the bus carries; its payload is `Self` as a frame
8/// (`yog_data::frame`: packed by position — both sides have the schema).
9pub trait Message: Schema + Sized {
10    /// The payload's bytes.
11    fn to_bytes(&self) -> Vec<u8> {
12        yog_data::frame::to_frame(self)
13    }
14
15    /// The message read from a payload; `None` if it isn't one.
16    fn from_bytes(bytes: &[u8]) -> Option<Self> {
17        yog_data::frame::read_frame(bytes)
18    }
19
20    /// `f` on the payload, packed in this thread's packer: nothing
21    /// allocated once its buffers have grown to the thread's messages.
22    fn with_bytes<R>(&self, f: impl FnOnce(&[u8]) -> R) -> R {
23        // Taken out while `f` runs: a message sent from inside `f` packs
24        // in a packer of its own instead of finding this one borrowed.
25        let mut packer = PACKER.take().unwrap_or_default();
26        let out = packer.with_frame(self, f);
27        PACKER.set(Some(packer));
28        out
29    }
30}
31
32impl<T: Schema> Message for T {}
33
34thread_local! {
35    /// The packer each thread's messages are packed in, its buffers reused.
36    static PACKER: std::cell::Cell<Option<yog_data::frame::Packer>> =
37        const { std::cell::Cell::new(None) };
38}
39
40/// `address`'s branch for `key`: where a keyed message with that key goes.
41pub fn keyed(address: &str, key: &str) -> String {
42    format!("{address}{}{key}", ids::sep::PATH)
43}
44
45/// Where a message with `key` (if keyed) goes: `address`, or its branch.
46pub(crate) fn target(address: &str, key: Option<&str>) -> String {
47    match key {
48        Some(key) => keyed(address, key),
49        None => address.to_owned(),
50    }
51}
52
53/// Where `address` sits under `prefix`: `yog:event/player/session/join`
54/// under `yog:event/` → `player/session`.
55pub(crate) fn branch(address: &'static str, prefix: &str) -> &'static str {
56    let path = address.strip_prefix(prefix).unwrap_or(address);
57    path.rsplit_once(ids::sep::PATH)
58        .map_or("", |(branch, _)| branch)
59}
60
61/// A type path's last segment: `client::ui::Open` → `Open`.
62pub(crate) fn last_segment(path: &'static str) -> &'static str {
63    path.rfind(':')
64        .map_or(path, |i| path.split_at(i + 1).1.trim())
65}
66
67/// The key field's name, if an entry gives one.
68macro_rules! key {
69    () => {
70        None
71    };
72    ($key:ident) => {
73        Some(stringify!($key))
74    };
75}
76
77/// A message's key field's value, if an entry gives one.
78macro_rules! key_value {
79    ($self:ident,) => {
80        None
81    };
82    ($self:ident, $key:ident) => {
83        Some(&$self.$key)
84    };
85}
86
87/// An action's method on the handle its first field binds — `uuid` an
88/// [`crate::Entity`], `player` a [`crate::Player`], `dim` a [`crate::World`]:
89/// the action's function with the handle's key put in first.
90macro_rules! handle_method {
91    ($name:ident, $reply:ty, [$($doc:tt)*]; uuid : $t:ty $(, $f:ident : $ft:ty)*) => {
92        handle_method!(@on $crate::Entity, uuid, $name, $reply, [$($doc)*]; $($f : $ft),*);
93    };
94    ($name:ident, $reply:ty, [$($doc:tt)*]; player : $t:ty $(, $f:ident : $ft:ty)*) => {
95        handle_method!(@on $crate::Player, name, $name, $reply, [$($doc)*]; $($f : $ft),*);
96    };
97    ($name:ident, $reply:ty, [$($doc:tt)*]; dim : $t:ty $(, $f:ident : $ft:ty)*) => {
98        handle_method!(@on $crate::World, dim, $name, $reply, [$($doc)*]; $($f : $ft),*);
99    };
100    (@on $handle:path, $key:ident, $name:ident, $reply:ty, [$($doc:tt)*]; $($f:ident : $ft:ty),*) => {
101        pastey::paste! {
102            impl $handle {
103                $($doc)*
104                pub fn [<$name:snake>](&self $(, $f: impl $crate::Arg<$ft>)*) -> $reply {
105                    [<$name:snake>](&*self.$key $(, $f)*)
106                }
107            }
108        }
109    };
110    ($($other:tt)*) => {};
111}
112
113/// Actions the Java host provides (`docs/design/bus.md`, stage 3): each a
114/// request struct, its reply, its address and the host method answering
115/// it; and the domain's `actions()` descriptors.
116macro_rules! actions {
117    ($( $(#[$doc:meta])* $name:ident { $( $(#[$fattr:meta])* $field:ident : $fty:ty ),* $(,)? } -> $reply:ty = $address:expr, $host:literal; )*) => {
118        $(
119            $(#[$doc])*
120            #[derive(Debug, Clone, yog_data::Schema)]
121            pub struct $name { $( $(#[$fattr])* pub $field: $fty ),* }
122
123            impl $crate::Service for $name {
124                const ADDRESS: &'static str = $address;
125                const KEY: Option<&'static str> = None;
126                type Reply = $reply;
127
128                fn key(&self) -> Option<&str> {
129                    None
130                }
131
132                fn address(&self) -> $crate::Address {
133                    static AT: $crate::Cached = $crate::Cached::new();
134                    AT.get($address)
135                }
136            }
137
138            pastey::paste! {
139                $(#[$doc])*
140                #[doc = ""]
141                #[doc = concat!("Calls [`", stringify!($name), "`]; what no one answers reads as the reply's default.")]
142                pub fn [<$name:snake>]($( $field: impl $crate::Arg<$fty> ),*) -> $reply {
143                    $crate::call(&$name { $( $field: $crate::Arg::arg($field) ),* }).unwrap_or_default()
144                }
145            }
146
147            handle_method! { $name, $reply, [$(#[$doc])*]; $( $field : $fty ),* }
148        )*
149
150        /// This domain's actions.
151        pub(crate) fn actions() -> Vec<$crate::ServiceInfo> {
152            vec![$($crate::ServiceInfo {
153                domain: $crate::wire::message::branch($address, yog_ids::service::PREFIX),
154                name: stringify!($name),
155                address: $address,
156                key: None,
157                shape: <$name as yog_data::Schema>::shape(),
158                reply: <$reply as yog_data::Schema>::shape(),
159                host: Some($host),
160            }),*]
161        }
162    };
163}