Skip to main content

basalt_api/events/
traits.rs

1//! Event trait, execution stages, and bus-routing metadata.
2//!
3//! Events are typed structs carrying domain data and a cancellation
4//! flag. The [`Event`] trait provides type erasure via `Any` so the
5//! [`EventBus`](super::EventBus) can store handlers for different
6//! event types in a single registry. [`EventRouting`] declares which
7//! bus an event type belongs to.
8
9use std::any::Any;
10
11/// Execution stage for event handlers.
12///
13/// Handlers run in stage order: Validate → Process → Post.
14/// If any Validate handler cancels the event, Process and Post
15/// are skipped entirely.
16///
17/// - **Validate**: read-only checks, can cancel (permissions, anti-cheat)
18/// - **Process**: state mutation, one logical owner (world changes)
19/// - **Post**: side effects, never cancels (broadcast, storage, logging)
20#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
21pub enum Stage {
22    /// Validation stage: read-only, can cancel. Runs first.
23    Validate,
24    /// Processing stage: mutates state. Runs second.
25    Process,
26    /// Post-processing stage: side effects. Runs last.
27    Post,
28}
29
30/// Which event bus an event type belongs to.
31///
32/// - **Instant**: dispatched immediately in the net task (chat, commands).
33///   No tick latency. Handlers run inline in the sending player's task.
34/// - **Game**: dispatched in the game loop tick (blocks, movement, lifecycle).
35///   Tick-based with ECS access.
36#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
37pub enum BusKind {
38    /// Events dispatched instantly in the net task (chat, commands).
39    Instant,
40    /// Events dispatched in the game loop tick (blocks, movement, lifecycle).
41    Game,
42}
43
44/// Declares which event bus an event type belongs to.
45///
46/// Every event type must implement this trait (alongside [`Event`])
47/// so the `PluginRegistrar` can route handler registration to the
48/// correct bus automatically. The associated constant `BUS` is
49/// resolved at compile time — no runtime dispatch overhead.
50///
51/// Implemented via the `instant_event!`, `instant_cancellable_event!`,
52/// `game_event!`, and `game_cancellable_event!` macros in `basalt-api`.
53pub trait EventRouting {
54    /// The bus this event type is dispatched on.
55    const BUS: BusKind;
56}
57
58/// Trait implemented by all game events.
59///
60/// Events carry domain data and support cancellation. The `as_any`
61/// methods enable type erasure inside the `EventBus` — handlers
62/// register for concrete types via `TypeId`, and the bus downcasts
63/// during dispatch.
64///
65/// Not all events are cancellable. For non-cancellable events
66/// (e.g., `PlayerJoinedEvent`), `cancel()` is a no-op and
67/// `is_cancelled()` always returns `false`.
68pub trait Event: Any + Send {
69    /// Whether this event has been cancelled by a Validate handler.
70    fn is_cancelled(&self) -> bool;
71
72    /// Cancels this event. Process and Post handlers will be skipped.
73    ///
74    /// Only meaningful during the Validate stage. Non-cancellable
75    /// events ignore this call.
76    fn cancel(&mut self);
77
78    /// Upcasts to `&dyn Any` for type-erased dispatch.
79    fn as_any(&self) -> &dyn Any;
80
81    /// Upcasts to `&mut dyn Any` for mutable type-erased dispatch.
82    fn as_any_mut(&mut self) -> &mut dyn Any;
83
84    /// Returns which event bus this event is dispatched on.
85    ///
86    /// Enables runtime routing of type-erased events (`&mut dyn Event`)
87    /// to the correct bus without hardcoded `TypeId` checks. Every
88    /// event type declares its bus via the macros (`instant_event!`,
89    /// `game_event!`, etc.).
90    fn bus_kind(&self) -> BusKind;
91}
92
93#[cfg(test)]
94mod tests {
95    use super::*;
96
97    struct TestEvent {
98        value: i32,
99        cancelled: bool,
100    }
101
102    impl Event for TestEvent {
103        fn is_cancelled(&self) -> bool {
104            self.cancelled
105        }
106        fn cancel(&mut self) {
107            self.cancelled = true;
108        }
109        fn as_any(&self) -> &dyn Any {
110            self
111        }
112        fn as_any_mut(&mut self) -> &mut dyn Any {
113            self
114        }
115        fn bus_kind(&self) -> BusKind {
116            BusKind::Instant
117        }
118    }
119
120    #[test]
121    fn event_cancellation() {
122        let mut event = TestEvent {
123            value: 42,
124            cancelled: false,
125        };
126        assert!(!event.is_cancelled());
127        event.cancel();
128        assert!(event.is_cancelled());
129        assert_eq!(event.value, 42);
130    }
131
132    #[test]
133    fn event_downcast() {
134        let mut event = TestEvent {
135            value: 99,
136            cancelled: false,
137        };
138        let any = event.as_any_mut();
139        let concrete = any.downcast_mut::<TestEvent>().unwrap();
140        concrete.value = 100;
141        assert_eq!(event.value, 100);
142    }
143
144    #[test]
145    fn stage_ordering() {
146        assert!(Stage::Validate < Stage::Process);
147        assert!(Stage::Process < Stage::Post);
148    }
149
150    #[test]
151    fn bus_kind_equality() {
152        assert_eq!(BusKind::Instant, BusKind::Instant);
153        assert_eq!(BusKind::Game, BusKind::Game);
154        assert_ne!(BusKind::Instant, BusKind::Game);
155    }
156}