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}