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 {
44 map: HashMap::new(),
45 }
46 }
47
48 /// Emit an event. Multiple calls accumulate; all are visible to later readers.
49 pub fn emit<T: Send + 'static>(&mut self, event: T) {
50 self.map
51 .entry(TypeId::of::<T>())
52 .or_default()
53 .push(Box::new(event));
54 }
55
56 /// Iterate over all events of type `T` emitted this frame.
57 pub fn read<T: Send + 'static>(&self) -> impl Iterator<Item = &T> + '_ {
58 self.map
59 .get(&TypeId::of::<T>())
60 .into_iter()
61 .flat_map(|v| v.iter().filter_map(|b| b.downcast_ref::<T>()))
62 }
63
64 /// Remove and return all events of type `T`, leaving the slot empty.
65 ///
66 /// Calling this twice for the same type returns an empty vec on the second call.
67 pub fn drain<T: Send + 'static>(&mut self) -> Vec<T> {
68 self.map
69 .remove(&TypeId::of::<T>())
70 .unwrap_or_default()
71 .into_iter()
72 .filter_map(|b| b.downcast::<T>().ok().map(|b| *b))
73 .collect()
74 }
75
76 /// Returns `true` if any events of type `T` have been emitted this frame.
77 pub fn has<T: Send + 'static>(&self) -> bool {
78 self.map
79 .get(&TypeId::of::<T>())
80 .map_or(false, |v| !v.is_empty())
81 }
82
83 /// Number of events of type `T` emitted this frame.
84 pub fn count<T: Send + 'static>(&self) -> usize {
85 self.map.get(&TypeId::of::<T>()).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}