Skip to main content

yog_api/
bus.rs

1//! Events through the bus: one subscription for every event type, and the
2//! one trampoline that hands its payload to a mod's handler.
3
4use std::marker::PhantomData;
5use std::os::raw::c_void;
6use yog_sync::Arc;
7
8use yog_abi::{YogBytes, YogHandlerFn, YogReply, YogStatus, YogStr, priority, status};
9use yog_event::{Event, EventPhase, keyed};
10
11use crate::registry::{Registry, guard};
12
13/// What a handler decides about an event.
14#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15pub enum Flow {
16    /// Let it happen; the next handler sees it.
17    Continue,
18    /// Stop it (before it happens): the game doesn't do it, later handlers don't see it.
19    Cancel,
20}
21
22/// What a handler may return: nothing (`Continue`), whether to let it
23/// happen (`false` cancels), or a [`Flow`].
24pub trait IntoFlow {
25    fn into_flow(self) -> Flow;
26}
27
28impl IntoFlow for () {
29    fn into_flow(self) -> Flow {
30        Flow::Continue
31    }
32}
33
34impl IntoFlow for bool {
35    fn into_flow(self) -> Flow {
36        if self { Flow::Continue } else { Flow::Cancel }
37    }
38}
39
40impl IntoFlow for Flow {
41    fn into_flow(self) -> Flow {
42        self
43    }
44}
45
46/// What a handler is handed besides the event: when it runs.
47pub struct EventCx {
48    phase: EventPhase,
49}
50
51impl EventCx {
52    /// Before the game does it (`Pre`, can cancel) or after (`Post`).
53    pub fn phase(&self) -> EventPhase {
54        self.phase
55    }
56
57    pub fn is_pre(&self) -> bool {
58        self.phase == EventPhase::Pre
59    }
60}
61
62/// What an event subscription keeps: the handler (shared by its pre and post
63/// subscriptions), and which phase this one is.
64pub(crate) struct Subscription<E, F> {
65    handler: Arc<F>,
66    phase: EventPhase,
67    event: PhantomData<fn(&E)>,
68}
69
70/// The bus's handler for subscription `ud`: the payload read as `E`, the
71/// mod's handler run; a cancel before the event stops it.
72unsafe extern "C" fn on_event<E: Event, F, R>(
73    ud: *mut c_void,
74    payload: YogBytes,
75    _reply: *const YogReply,
76) -> YogStatus
77where
78    F: Fn(&E, &EventCx) -> R + Send + Sync,
79    R: IntoFlow,
80{
81    let s = unsafe { &*(ud as *const Subscription<E, F>) };
82    let Some(event) = E::from_bytes(unsafe { payload.as_slice() }) else {
83        return status::BAD_PAYLOAD;
84    };
85    let flow = guard(Flow::Continue, || (s.handler)(&event, &s.cx()).into_flow());
86    if s.phase == EventPhase::Pre && flow == Flow::Cancel {
87        status::CANCELLED
88    } else {
89        status::OK
90    }
91}
92
93/// The bus's handler for changing subscription `ud`: the mod's handler
94/// changes the event, and the changed event is the reply the next handler
95/// (and the game) sees.
96unsafe extern "C" fn on_change<E: Event, F>(
97    ud: *mut c_void,
98    payload: YogBytes,
99    reply: *const YogReply,
100) -> YogStatus
101where
102    F: Fn(&mut E, &EventCx) + Send + Sync,
103{
104    let s = unsafe { &*(ud as *const Subscription<E, F>) };
105    let Some(mut event) = E::from_bytes(unsafe { payload.as_slice() }) else {
106        return status::BAD_PAYLOAD;
107    };
108    guard((), || (s.handler)(&mut event, &s.cx()));
109    unsafe { YogReply::send(reply, &event.to_bytes()) };
110    status::OK
111}
112
113impl<E, F> Subscription<E, F> {
114    pub(crate) fn new(handler: Arc<F>, phase: EventPhase) -> Self {
115        Self {
116            handler,
117            phase,
118            event: PhantomData,
119        }
120    }
121
122    pub(crate) fn handler(&self) -> &F {
123        &self.handler
124    }
125
126    pub(crate) fn cx(&self) -> EventCx {
127        EventCx { phase: self.phase }
128    }
129}
130
131/// Emits `event` on the bus — a mod's own event, or the game's on its
132/// behalf; whether no handler cancelled it (before the event, where it can be).
133pub fn emit<E: Event>(event: &E) -> bool {
134    let api = crate::registry::global_api();
135    if api.is_null() {
136        return true;
137    }
138    let bus = unsafe { &*(*api).bus };
139    event.with_bytes(|bytes| {
140        let send = |at| unsafe { (bus.emit)(bus.ctx, at, YogBytes::new(bytes), std::ptr::null()) };
141        if let Some(pre) = event.pre_address()
142            && send(pre) == status::CANCELLED
143        {
144            return false;
145        }
146        send(event.address());
147        true
148    })
149}
150
151impl Registry {
152    /// Runs `handler` on every `E`, before the game does it (if `E` can be
153    /// stopped — [`Flow::Cancel`] / `false` stops it) and after.
154    pub fn on<E: Event + 'static, R: IntoFlow + 'static>(
155        &mut self,
156        handler: impl Fn(&E, &EventCx) -> R + Send + Sync + 'static,
157    ) {
158        self.both::<E, _, R>(E::PRE.map(str::to_owned), E::ADDRESS.to_owned(), handler);
159    }
160
161    /// [`Registry::on`] for a keyed event's `key` only (`E::KEY`): the ticks
162    /// of one entity type, the presses of one key binding.
163    pub fn on_key<E: Event + 'static, R: IntoFlow + 'static>(
164        &mut self,
165        key: &str,
166        handler: impl Fn(&E, &EventCx) -> R + Send + Sync + 'static,
167    ) {
168        self.both::<E, _, R>(
169            E::PRE.map(|p| keyed(p, key)),
170            keyed(E::ADDRESS, key),
171            handler,
172        );
173    }
174
175    /// Runs `handler` before the game does `E` — it can cancel it. Nothing
176    /// for an event that can't be stopped.
177    pub fn before<E: Event + 'static, R: IntoFlow + 'static>(
178        &mut self,
179        handler: impl Fn(&E, &EventCx) -> R + Send + Sync + 'static,
180    ) {
181        if let Some(pre) = E::PRE {
182            self.at::<E, _, R>(pre, EventPhase::Pre, Arc::new(handler));
183        }
184    }
185
186    /// [`Registry::before`] for a keyed event's `key` only.
187    pub fn before_key<E: Event + 'static, R: IntoFlow + 'static>(
188        &mut self,
189        key: &str,
190        handler: impl Fn(&E, &EventCx) -> R + Send + Sync + 'static,
191    ) {
192        if let Some(pre) = E::PRE {
193            self.at::<E, _, R>(&keyed(pre, key), EventPhase::Pre, Arc::new(handler));
194        }
195    }
196
197    /// Runs `handler` after the game did `E`.
198    pub fn after<E: Event + 'static, R: IntoFlow + 'static>(
199        &mut self,
200        handler: impl Fn(&E, &EventCx) -> R + Send + Sync + 'static,
201    ) {
202        self.at::<E, _, R>(E::ADDRESS, EventPhase::Post, Arc::new(handler));
203    }
204
205    /// [`Registry::after`] at `priority` among `E`'s handlers (higher first).
206    pub(crate) fn after_at<E: Event + 'static, R: IntoFlow + 'static, F>(
207        &self,
208        priority: i32,
209        handler: F,
210    ) where
211        F: Fn(&E, &EventCx) -> R + Send + Sync + 'static,
212    {
213        self.subscribe_at::<E, F>(
214            E::ADDRESS,
215            priority,
216            EventPhase::Post,
217            Arc::new(handler),
218            on_event::<E, F, R>,
219        );
220    }
221
222    /// Lets `handler` change every `E` before it's used (a loot table's
223    /// items); each handler sees the one before's result.
224    pub fn change<E: Event + 'static>(
225        &mut self,
226        handler: impl Fn(&mut E, &EventCx) + Send + Sync + 'static,
227    ) {
228        self.changing::<E, _>(E::ADDRESS, priority::DEFAULT, Arc::new(handler));
229    }
230
231    /// [`Registry::change`] for a keyed event's `key` only, at `priority`
232    /// among its handlers (higher first).
233    pub(crate) fn change_key_at<E: Event + 'static>(
234        &mut self,
235        key: &str,
236        priority: i32,
237        handler: impl Fn(&mut E, &EventCx) + Send + Sync + 'static,
238    ) {
239        self.changing::<E, _>(&keyed(E::ADDRESS, key), priority, Arc::new(handler));
240    }
241
242    /// `handler` at `pre` (if any) and `post`.
243    fn both<E: Event + 'static, F, R>(&mut self, pre: Option<String>, post: String, handler: F)
244    where
245        F: Fn(&E, &EventCx) -> R + Send + Sync + 'static,
246        R: IntoFlow + 'static,
247    {
248        let handler = Arc::new(handler);
249        if let Some(pre) = pre {
250            self.at::<E, F, R>(&pre, EventPhase::Pre, Arc::clone(&handler));
251        }
252        self.at::<E, F, R>(&post, EventPhase::Post, handler);
253    }
254
255    /// `handler` on `E` at `address`, in `phase`.
256    fn at<E: Event + 'static, F, R>(&mut self, address: &str, phase: EventPhase, handler: Arc<F>)
257    where
258        F: Fn(&E, &EventCx) -> R + Send + Sync + 'static,
259        R: IntoFlow + 'static,
260    {
261        self.subscribe::<E, F>(address, phase, handler, on_event::<E, F, R>);
262    }
263
264    fn changing<E: Event + 'static, F>(&mut self, address: &str, priority: i32, handler: Arc<F>)
265    where
266        F: Fn(&mut E, &EventCx) + Send + Sync + 'static,
267    {
268        self.subscribe_at::<E, F>(
269            address,
270            priority,
271            EventPhase::Post,
272            handler,
273            on_change::<E, F>,
274        );
275    }
276
277    fn subscribe<E: 'static, F: Send + Sync + 'static>(
278        &mut self,
279        address: &str,
280        phase: EventPhase,
281        handler: Arc<F>,
282        run: YogHandlerFn,
283    ) {
284        self.subscribe_at::<E, F>(address, priority::DEFAULT, phase, handler, run);
285    }
286
287    fn subscribe_at<E: 'static, F: Send + Sync + 'static>(
288        &self,
289        address: &str,
290        priority: i32,
291        phase: EventPhase,
292        handler: Arc<F>,
293        run: YogHandlerFn,
294    ) {
295        let api = self.api();
296        let bus = unsafe { &*api.bus };
297        let sub = Box::into_raw(Box::new(Subscription::<E, F>::new(handler, phase)));
298        unsafe {
299            let at = (bus.resolve)(bus.ctx, YogStr::from_str(address));
300            (bus.on)(bus.ctx, at, priority, sub.cast::<c_void>(), run);
301        }
302    }
303}
304
305#[cfg(test)]
306mod tests {
307    use super::*;
308    use yog_event::{block, player};
309
310    /// Runs `on_event` for `handler` in `phase` on `bytes`.
311    fn deliver_bytes<E: Event, R: IntoFlow, F: Fn(&E, &EventCx) -> R + Send + Sync>(
312        handler: F,
313        phase: EventPhase,
314        bytes: &[u8],
315    ) -> YogStatus {
316        let sub = Subscription::<E, F>::new(Arc::new(handler), phase);
317        unsafe {
318            on_event::<E, F, R>(
319                (&sub as *const Subscription<E, F>)
320                    .cast_mut()
321                    .cast::<c_void>(),
322                YogBytes::new(bytes),
323                std::ptr::null(),
324            )
325        }
326    }
327
328    fn deliver<E: Event, R: IntoFlow, F: Fn(&E, &EventCx) -> R + Send + Sync>(
329        handler: F,
330        phase: EventPhase,
331        event: &E,
332    ) -> YogStatus {
333        deliver_bytes(handler, phase, &event.to_bytes())
334    }
335
336    fn chat(message: &str) -> player::chat::Message {
337        player::chat::Message {
338            player_name: "Dev".into(),
339            message: message.into(),
340            dimension: "minecraft:overworld".into(),
341        }
342    }
343
344    #[test]
345    fn events_arrive_read_with_their_phase_and_a_pre_false_cancels() {
346        let allow = |e: &player::chat::Message, _: &EventCx| e.message != "cancel me";
347        assert_eq!(deliver(allow, EventPhase::Pre, &chat("hi")), status::OK);
348        assert_eq!(
349            deliver(allow, EventPhase::Pre, &chat("cancel me")),
350            status::CANCELLED
351        );
352        assert_eq!(
353            deliver(allow, EventPhase::Post, &chat("cancel me")),
354            status::OK,
355            "only a pre-event can be cancelled"
356        );
357    }
358
359    #[test]
360    fn a_panicking_handler_allows_the_action() {
361        let handler = |_: &block::Break, _: &EventCx| -> bool { panic!("mod bug") };
362        let event = block::Break {
363            player_name: "Dev".into(),
364            block_id: "minecraft:stone".into(),
365            pos: yog_core::BlockPos { x: 1, y: 2, z: 3 },
366            dimension: "minecraft:overworld".into(),
367        };
368        assert_eq!(deliver(handler, EventPhase::Pre, &event), status::OK);
369    }
370
371    #[test]
372    fn a_payload_that_isnt_the_event_is_refused() {
373        let handler = |_: &player::chat::Message, _: &EventCx| true;
374        assert_eq!(
375            deliver_bytes(handler, EventPhase::Post, b"junk"),
376            status::BAD_PAYLOAD
377        );
378    }
379
380    /// Runs `on_change` for `handler` on `event`: its status and reply.
381    fn change<E: Event, F: Fn(&mut E, &EventCx) + Send + Sync>(
382        handler: F,
383        event: &E,
384    ) -> (YogStatus, Vec<u8>) {
385        let sub = Subscription::<E, F>::new(Arc::new(handler), EventPhase::Post);
386        let mut out = Vec::<u8>::new();
387        let reply = YogReply::to_vec(&mut out);
388        let ud = (&sub as *const Subscription<E, F>)
389            .cast_mut()
390            .cast::<c_void>();
391        let st = unsafe { on_change::<E, F>(ud, YogBytes::new(&event.to_bytes()), &reply) };
392        (st, out)
393    }
394
395    #[test]
396    fn a_changing_handler_replies_with_the_changed_event() {
397        use yog_event::Message;
398        use yog_event::world::loot::{Roll, Stack};
399        let more =
400            |loot: &mut Roll, _: &EventCx| loot.items.push(Stack::new("minecraft:diamond", 1));
401        let (st, out) = change(
402            more,
403            &Roll {
404                table: "minecraft:chests/igloo_chest".into(),
405                ..Default::default()
406            },
407        );
408        assert_eq!(st, status::OK);
409        let changed = Roll::from_bytes(&out).expect("the reply is the event");
410        assert_eq!(changed.items, vec![Stack::new("minecraft:diamond", 1)]);
411    }
412
413    #[test]
414    fn a_handler_returning_nothing_lets_it_happen() {
415        let handler = |_: &player::chat::Message, _: &EventCx| {};
416        assert_eq!(deliver(handler, EventPhase::Pre, &chat("hi")), status::OK);
417        let cancel = |_: &player::chat::Message, _: &EventCx| Flow::Cancel;
418        assert_eq!(
419            deliver(cancel, EventPhase::Pre, &chat("hi")),
420            status::CANCELLED
421        );
422    }
423}