Skip to main content

yog_event/
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 reused buffers: nothing
21    /// allocated once they've grown to the thread's messages.
22    fn with_bytes<R>(&self, f: impl FnOnce(&[u8]) -> R) -> R {
23        yog_data::frame::with_frame(self, f)
24    }
25}
26
27impl<T: Schema> Message for T {}
28
29/// `address`'s branch for `key`: where a keyed message with that key goes.
30pub fn keyed(address: &str, key: &str) -> String {
31    format!("{address}{}{key}", ids::sep::PATH)
32}
33
34/// Where a message with `key` (if keyed) goes: `address`, or its branch.
35pub(crate) fn target(address: &str, key: Option<&str>) -> String {
36    match key {
37        Some(key) => keyed(address, key),
38        None => address.to_owned(),
39    }
40}
41
42/// Where `address` sits under `prefix`: `yog:event/player/session/join`
43/// under `yog:event/` → `player/session`.
44pub(crate) fn branch(address: &'static str, prefix: &str) -> &'static str {
45    let path = address.strip_prefix(prefix).unwrap_or(address);
46    path.rsplit_once(ids::sep::PATH)
47        .map_or("", |(branch, _)| branch)
48}
49
50/// A type path's last segment: `client::ui::Open` → `Open`.
51pub(crate) fn last_segment(path: &'static str) -> &'static str {
52    path.rfind(':')
53        .map_or(path, |i| path.split_at(i + 1).1.trim())
54}
55
56/// The key field's name, if an entry gives one.
57macro_rules! key {
58    () => {
59        None
60    };
61    ($key:ident) => {
62        Some(stringify!($key))
63    };
64}
65
66/// A message's key field's value, if an entry gives one.
67macro_rules! key_value {
68    ($self:ident,) => {
69        None
70    };
71    ($self:ident, $key:ident) => {
72        Some(&$self.$key)
73    };
74}
75
76/// An action's method on the handle its first field binds — `uuid` an
77/// [`crate::Entity`], `player` a [`crate::Player`], `dim` a [`crate::World`]:
78/// the action's function with the handle's key put in first.
79macro_rules! handle_method {
80    ($name:ident, $reply:ty, [$($doc:tt)*]; uuid : $t:ty $(, $f:ident : $ft:ty)*) => {
81        handle_method!(@on $crate::Entity, uuid, $name, $reply, [$($doc)*]; $($f : $ft),*);
82    };
83    ($name:ident, $reply:ty, [$($doc:tt)*]; player : $t:ty $(, $f:ident : $ft:ty)*) => {
84        handle_method!(@on $crate::Player, name, $name, $reply, [$($doc)*]; $($f : $ft),*);
85    };
86    ($name:ident, $reply:ty, [$($doc:tt)*]; dim : $t:ty $(, $f:ident : $ft:ty)*) => {
87        handle_method!(@on $crate::World, dim, $name, $reply, [$($doc)*]; $($f : $ft),*);
88    };
89    (@on $handle:path, $key:ident, $name:ident, $reply:ty, [$($doc:tt)*]; $($f:ident : $ft:ty),*) => {
90        pastey::paste! {
91            impl $handle {
92                $($doc)*
93                pub fn [<$name:snake>](&self $(, $f: impl $crate::Arg<$ft>)*) -> $reply {
94                    [<$name:snake>](&*self.$key $(, $f)*)
95                }
96            }
97        }
98    };
99    ($($other:tt)*) => {};
100}
101
102/// Actions the Java host provides (`docs/design/bus.md`, stage 3): each a
103/// request struct, its reply, its address and the host method answering
104/// it; and the domain's `actions()` descriptors.
105macro_rules! actions {
106    ($( $(#[$doc:meta])* $name:ident { $( $(#[$fattr:meta])* $field:ident : $fty:ty ),* $(,)? } -> $reply:ty = $address:expr, $host:literal; )*) => {
107        $(
108            $(#[$doc])*
109            #[derive(Debug, Clone, yog_data::Schema)]
110            pub struct $name { $( $(#[$fattr])* pub $field: $fty ),* }
111
112            impl $crate::Service for $name {
113                const ADDRESS: &'static str = $address;
114                const KEY: Option<&'static str> = None;
115                type Reply = $reply;
116
117                fn key(&self) -> Option<&str> {
118                    None
119                }
120
121                fn address(&self) -> $crate::Address {
122                    static AT: $crate::Cached = $crate::Cached::new();
123                    AT.get($address)
124                }
125            }
126
127            pastey::paste! {
128                $(#[$doc])*
129                #[doc = ""]
130                #[doc = concat!("Calls [`", stringify!($name), "`]; what no one answers reads as the reply's default.")]
131                pub fn [<$name:snake>]($( $field: impl $crate::Arg<$fty> ),*) -> $reply {
132                    $crate::call(&$name { $( $field: $crate::Arg::arg($field) ),* }).unwrap_or_default()
133                }
134            }
135
136            handle_method! { $name, $reply, [$(#[$doc])*]; $( $field : $fty ),* }
137        )*
138
139        /// This domain's actions.
140        pub(crate) fn actions() -> Vec<$crate::ServiceInfo> {
141            vec![$($crate::ServiceInfo {
142                domain: $crate::message::branch($address, yog_ids::service::PREFIX),
143                name: stringify!($name),
144                address: $address,
145                key: None,
146                shape: <$name as yog_data::Schema>::shape(),
147                reply: <$reply as yog_data::Schema>::shape(),
148                host: Some($host),
149            }),*]
150        }
151    };
152}