viewport_lib/runtime/events.rs
1//! Generic typed event bus for runtime plugin communication.
2
3use std::any::{Any, TypeId};
4use std::collections::HashMap;
5
6/// Typed per-frame event accumulator for runtime plugin communication.
7///
8/// Stored on [`super::output::RuntimeOutput`] and accessible via `ctx.output.events`
9/// from any plugin step. Events accumulate during the frame and are returned to the
10/// app in [`super::output::RuntimeOutput`] at the end of each [`super::ViewportRuntime::step`] call.
11///
12/// Events are cleared each frame because `RuntimeOutput` is constructed fresh on
13/// every `step` call. Use [`super::resources::RuntimeResources`] for state that
14/// must persist across frames.
15///
16/// The event type IS the category. Define a distinct struct for each kind of event
17/// your plugin emits. Multiple plugins can emit events of the same type; all
18/// accumulate and are visible together.
19///
20/// # Example
21///
22/// ```rust,ignore
23/// // Emit from a plugin step.
24/// ctx.output.events.emit(TriggerEntered { trigger_id, actor_id });
25///
26/// // Read from a later plugin in the same frame.
27/// for ev in ctx.output.events.read::<TriggerEntered>() { ... }
28///
29/// // Read from the app after step() returns.
30/// for ev in output.events.read::<TriggerEntered>() { ... }
31///
32/// // Drain (take ownership) after step() returns.
33/// for ev in output.events.drain::<TriggerEntered>() { ... }
34/// ```
35#[derive(Default)]
36pub struct RuntimeEventBus {
37 map: HashMap<TypeId, Vec<Box<dyn Any + Send + 'static>>>,
38}
39
40impl RuntimeEventBus {
41 /// Create an empty event bus.
42 pub fn new() -> Self {
43 Self { map: HashMap::new() }
44 }
45
46 /// Emit an event. Multiple calls accumulate; all are visible to later readers.
47 pub fn emit<T: Send + 'static>(&mut self, event: T) {
48 self.map
49 .entry(TypeId::of::<T>())
50 .or_default()
51 .push(Box::new(event));
52 }
53
54 /// Iterate over all events of type `T` emitted this frame.
55 pub fn read<T: Send + 'static>(&self) -> impl Iterator<Item = &T> + '_ {
56 self.map
57 .get(&TypeId::of::<T>())
58 .into_iter()
59 .flat_map(|v| v.iter().filter_map(|b| b.downcast_ref::<T>()))
60 }
61
62 /// Remove and return all events of type `T`, leaving the slot empty.
63 ///
64 /// Calling this twice for the same type returns an empty vec on the second call.
65 pub fn drain<T: Send + 'static>(&mut self) -> Vec<T> {
66 self.map
67 .remove(&TypeId::of::<T>())
68 .unwrap_or_default()
69 .into_iter()
70 .filter_map(|b| b.downcast::<T>().ok().map(|b| *b))
71 .collect()
72 }
73
74 /// Returns `true` if any events of type `T` have been emitted this frame.
75 pub fn has<T: Send + 'static>(&self) -> bool {
76 self.map
77 .get(&TypeId::of::<T>())
78 .map_or(false, |v| !v.is_empty())
79 }
80
81 /// Number of events of type `T` emitted this frame.
82 pub fn count<T: Send + 'static>(&self) -> usize {
83 self.map
84 .get(&TypeId::of::<T>())
85 .map_or(0, |v| v.len())
86 }
87
88 /// Returns `true` if no events of any type have been emitted this frame.
89 pub fn is_empty(&self) -> bool {
90 self.map.values().all(|v| v.is_empty())
91 }
92}