Skip to main content

rustial_engine/
event_emitter.rs

1//! Callback-based event subscription for [`MapState`](crate::MapState).
2//!
3//! Complements the existing poll-based
4//! [`InteractionManager::drain_events`](crate::interaction_manager::InteractionManager::drain_events)
5//! model with a subscribe/unsubscribe API that mirrors
6//! `map.on(type, listener)` / `map.off(type, listener)` from
7//! MapLibre GL JS.
8//!
9//! # Thread safety
10//!
11//! `EventEmitter` is `Send + Sync` because callbacks are stored as
12//! `Box<dyn Fn(&InteractionEvent) + Send + Sync>`.  The emitter is
13//! designed to be called from the same thread that owns `MapState`
14//! (the update path), so no internal locking is required.
15
16use crate::interaction::{InteractionEvent, InteractionEventKind};
17
18/// Opaque handle returned by [`EventEmitter::on`] and [`EventEmitter::once`].
19///
20/// Pass to [`EventEmitter::off`] to unsubscribe.
21#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
22pub struct ListenerId(u64);
23
24/// A single registered listener.
25struct Listener {
26    id: ListenerId,
27    kind: InteractionEventKind,
28    callback: Box<dyn Fn(&InteractionEvent) + Send + Sync>,
29    once: bool,
30}
31
32/// Callback-based event subscription system.
33///
34/// Embed in [`MapState`](crate::MapState) and call
35/// [`dispatch`](Self::dispatch) each frame with the events drained
36/// from [`InteractionManager`](crate::interaction_manager::InteractionManager).
37pub struct EventEmitter {
38    listeners: Vec<Listener>,
39    next_id: u64,
40}
41
42impl Default for EventEmitter {
43    fn default() -> Self {
44        Self::new()
45    }
46}
47
48impl EventEmitter {
49    /// Create an empty event emitter.
50    pub fn new() -> Self {
51        Self {
52            listeners: Vec::new(),
53            next_id: 0,
54        }
55    }
56
57    /// Subscribe to events of a given kind.
58    ///
59    /// Returns a [`ListenerId`] that can later be passed to [`off`](Self::off).
60    pub fn on<F>(&mut self, kind: InteractionEventKind, callback: F) -> ListenerId
61    where
62        F: Fn(&InteractionEvent) + Send + Sync + 'static,
63    {
64        let id = self.alloc_id();
65        self.listeners.push(Listener {
66            id,
67            kind,
68            callback: Box::new(callback),
69            once: false,
70        });
71        id
72    }
73
74    /// Subscribe to a single occurrence of an event kind.
75    ///
76    /// The listener is automatically removed after the first dispatch.
77    pub fn once<F>(&mut self, kind: InteractionEventKind, callback: F) -> ListenerId
78    where
79        F: Fn(&InteractionEvent) + Send + Sync + 'static,
80    {
81        let id = self.alloc_id();
82        self.listeners.push(Listener {
83            id,
84            kind,
85            callback: Box::new(callback),
86            once: true,
87        });
88        id
89    }
90
91    /// Unsubscribe a previously registered listener.
92    ///
93    /// Returns `true` if the listener was found and removed.
94    pub fn off(&mut self, id: ListenerId) -> bool {
95        let before = self.listeners.len();
96        self.listeners.retain(|l| l.id != id);
97        self.listeners.len() < before
98    }
99
100    /// Dispatch a batch of events to all matching listeners.
101    ///
102    /// `once` listeners are removed after their first invocation.
103    pub fn dispatch(&mut self, events: &[InteractionEvent]) {
104        if self.listeners.is_empty() || events.is_empty() {
105            return;
106        }
107
108        let mut to_remove: Vec<ListenerId> = Vec::new();
109
110        for event in events {
111            for listener in &self.listeners {
112                if listener.kind == event.kind {
113                    (listener.callback)(event);
114                    if listener.once {
115                        to_remove.push(listener.id);
116                    }
117                }
118            }
119        }
120
121        if !to_remove.is_empty() {
122            self.listeners.retain(|l| !to_remove.contains(&l.id));
123        }
124    }
125
126    /// Number of active listeners.
127    pub fn listener_count(&self) -> usize {
128        self.listeners.len()
129    }
130
131    /// Number of listeners for a specific event kind.
132    pub fn listener_count_for(&self, kind: InteractionEventKind) -> usize {
133        self.listeners.iter().filter(|l| l.kind == kind).count()
134    }
135
136    fn alloc_id(&mut self) -> ListenerId {
137        let id = ListenerId(self.next_id);
138        self.next_id += 1;
139        id
140    }
141}
142
143// SAFETY: Listener contains `Box<dyn Fn + Send + Sync>`, which is Send + Sync.
144// Vec<Listener> is therefore Send + Sync as well.
145unsafe impl Send for EventEmitter {}
146unsafe impl Sync for EventEmitter {}
147
148// ---------------------------------------------------------------------------
149// Tests
150// ---------------------------------------------------------------------------
151
152#[cfg(test)]
153mod tests {
154    use super::*;
155    use crate::interaction::{InteractionEvent, InteractionEventKind, PointerKind, ScreenPoint};
156    use std::sync::atomic::{AtomicU32, Ordering};
157    use std::sync::Arc;
158
159    fn make_event(kind: InteractionEventKind) -> InteractionEvent {
160        InteractionEvent::new(kind, PointerKind::Mouse, ScreenPoint::new(0.0, 0.0))
161    }
162
163    #[test]
164    fn on_dispatches_repeatedly() {
165        let mut emitter = EventEmitter::new();
166        let counter = Arc::new(AtomicU32::new(0));
167        let c = counter.clone();
168        emitter.on(InteractionEventKind::Click, move |_| {
169            c.fetch_add(1, Ordering::Relaxed);
170        });
171
172        let events = vec![make_event(InteractionEventKind::Click)];
173        emitter.dispatch(&events);
174        emitter.dispatch(&events);
175
176        assert_eq!(counter.load(Ordering::Relaxed), 2);
177    }
178
179    #[test]
180    fn once_dispatches_only_once() {
181        let mut emitter = EventEmitter::new();
182        let counter = Arc::new(AtomicU32::new(0));
183        let c = counter.clone();
184        emitter.once(InteractionEventKind::Click, move |_| {
185            c.fetch_add(1, Ordering::Relaxed);
186        });
187
188        let events = vec![make_event(InteractionEventKind::Click)];
189        emitter.dispatch(&events);
190        emitter.dispatch(&events);
191
192        assert_eq!(counter.load(Ordering::Relaxed), 1);
193        assert_eq!(emitter.listener_count(), 0);
194    }
195
196    #[test]
197    fn off_removes_listener() {
198        let mut emitter = EventEmitter::new();
199        let counter = Arc::new(AtomicU32::new(0));
200        let c = counter.clone();
201        let id = emitter.on(InteractionEventKind::Click, move |_| {
202            c.fetch_add(1, Ordering::Relaxed);
203        });
204
205        let events = vec![make_event(InteractionEventKind::Click)];
206        emitter.dispatch(&events);
207        assert!(emitter.off(id));
208        emitter.dispatch(&events);
209
210        assert_eq!(counter.load(Ordering::Relaxed), 1);
211    }
212
213    #[test]
214    fn different_kinds_are_isolated() {
215        let mut emitter = EventEmitter::new();
216        let click_count = Arc::new(AtomicU32::new(0));
217        let move_count = Arc::new(AtomicU32::new(0));
218
219        let cc = click_count.clone();
220        emitter.on(InteractionEventKind::Click, move |_| {
221            cc.fetch_add(1, Ordering::Relaxed);
222        });
223
224        let mc = move_count.clone();
225        emitter.on(InteractionEventKind::MouseMove, move |_| {
226            mc.fetch_add(1, Ordering::Relaxed);
227        });
228
229        emitter.dispatch(&[make_event(InteractionEventKind::Click)]);
230
231        assert_eq!(click_count.load(Ordering::Relaxed), 1);
232        assert_eq!(move_count.load(Ordering::Relaxed), 0);
233    }
234
235    #[test]
236    fn listener_count_for_kind() {
237        let mut emitter = EventEmitter::new();
238        emitter.on(InteractionEventKind::Click, |_| {});
239        emitter.on(InteractionEventKind::Click, |_| {});
240        emitter.on(InteractionEventKind::MouseMove, |_| {});
241
242        assert_eq!(emitter.listener_count(), 3);
243        assert_eq!(emitter.listener_count_for(InteractionEventKind::Click), 2);
244        assert_eq!(
245            emitter.listener_count_for(InteractionEventKind::MouseMove),
246            1
247        );
248    }
249}