galeon_engine/event.rs
1// SPDX-License-Identifier: AGPL-3.0-only OR Commercial
2
3use std::any::TypeId;
4
5use crate::system_param::{Access, SystemParam};
6use crate::world::UnsafeWorldCell;
7
8// =============================================================================
9// Events<T> — double-buffered typed event queue
10// =============================================================================
11
12/// Double-buffered typed event queue.
13///
14/// Events are written to `current` during the tick they are sent. At the start
15/// of the next `Schedule::run()`, `World::update_events()` swaps the buffers:
16/// `current` becomes `previous` and the old `previous` is cleared. Systems
17/// that use `EventReader<T>` iterate over `previous`, so they always read
18/// events from the *previous* tick.
19///
20/// ```text
21/// Tick N: EventWriter sends → current
22/// tick N+1: update_events() → current becomes previous, cleared current
23/// EventReader reads previous (events from tick N)
24/// Tick N+2: update_events() → previous is cleared
25/// ```
26///
27/// Register an event type with [`World::add_event::<T>()`] before using
28/// `EventWriter<T>` or `EventReader<T>` in systems.
29pub struct Events<T: 'static> {
30 /// Events written during the previous tick — readable by `EventReader`.
31 previous: Vec<T>,
32 /// Events being written this tick — by `EventWriter`.
33 current: Vec<T>,
34}
35
36impl<T: 'static> Events<T> {
37 /// Create an empty double buffer.
38 pub fn new() -> Self {
39 Self {
40 previous: Vec::new(),
41 current: Vec::new(),
42 }
43 }
44
45 /// Send an event. It will be readable by `EventReader` on the next tick.
46 pub fn send(&mut self, event: T) {
47 self.current.push(event);
48 }
49
50 /// Iterate over events sent during the previous tick.
51 pub fn read(&self) -> impl Iterator<Item = &T> {
52 self.previous.iter()
53 }
54
55 /// Iterate over events in the current (not yet swapped) buffer.
56 ///
57 /// For render extraction that runs *after* a tick completes: events
58 /// sent during tick N live in `current` until the next `update()`.
59 pub fn read_current(&self) -> impl Iterator<Item = &T> {
60 self.current.iter()
61 }
62
63 /// Number of events in the current (not yet swapped) buffer.
64 ///
65 /// Used by render event extractors with offset tracking to avoid
66 /// re-reading events that were already captured in a prior flush.
67 pub fn current_len(&self) -> usize {
68 self.current.len()
69 }
70
71 /// Advance the double buffer.
72 ///
73 /// The previous buffer is cleared. The current buffer becomes the new
74 /// previous buffer. Called automatically by `World::update_events()` at
75 /// the start of each `Schedule::run()`.
76 pub fn update(&mut self) {
77 // Move current → previous (swap), then clear current.
78 // Using swap + clear avoids a heap allocation: we reuse the old
79 // previous buffer (now cleared) as the new current buffer.
80 std::mem::swap(&mut self.previous, &mut self.current);
81 self.current.clear();
82 }
83
84 /// Number of readable events (in the previous buffer).
85 pub fn len(&self) -> usize {
86 self.previous.len()
87 }
88
89 /// Returns `true` if there are no readable events.
90 pub fn is_empty(&self) -> bool {
91 self.previous.is_empty()
92 }
93}
94
95impl<T: 'static> Default for Events<T> {
96 fn default() -> Self {
97 Self::new()
98 }
99}
100
101// =============================================================================
102// EventWriter<'w, T> — exclusive write access as a SystemParam
103// =============================================================================
104
105/// Exclusive write access to the `Events<T>` resource.
106///
107/// Use this in a system to send events that other systems can read on the
108/// next schedule tick via [`EventReader<T>`].
109///
110/// ```rust,ignore
111/// fn fire_cannon(mut writer: EventWriter<'_, CannonFired>) {
112/// writer.send(CannonFired { power: 9000 });
113/// }
114/// ```
115pub struct EventWriter<'w, T: Send + 'static> {
116 events: &'w mut Events<T>,
117}
118
119impl<'w, T: Send + 'static> EventWriter<'w, T> {
120 /// Send an event. Readable by `EventReader<T>` systems on the next tick.
121 pub fn send(&mut self, event: T) {
122 self.events.send(event);
123 }
124}
125
126// SAFETY: access() reports ResWrite(Events<T>). fetch() only touches the
127// Events<T> resource field via get_resource_mut — no other field is accessed.
128unsafe impl<T: Send + 'static> SystemParam for EventWriter<'_, T> {
129 type Item<'w> = EventWriter<'w, T>;
130
131 fn access() -> Vec<Access> {
132 vec![Access::ResWrite(TypeId::of::<Events<T>>())]
133 }
134
135 unsafe fn fetch<'w>(world: UnsafeWorldCell) -> Self::Item<'w> {
136 EventWriter {
137 events: unsafe { world.get_resource_mut::<Events<T>>() },
138 }
139 }
140}
141
142// =============================================================================
143// EventReader<'w, T> — shared read access as a SystemParam
144// =============================================================================
145
146/// Shared read access to the `Events<T>` resource.
147///
148/// Iterates over events sent by `EventWriter<T>` on the *previous* tick.
149///
150/// ```rust,ignore
151/// fn on_cannon_fired(reader: EventReader<'_, CannonFired>) {
152/// for ev in reader.read() {
153/// println!("cannon fired with power {}", ev.power);
154/// }
155/// }
156/// ```
157pub struct EventReader<'w, T: Send + 'static> {
158 events: &'w Events<T>,
159}
160
161impl<'w, T: Send + 'static> EventReader<'w, T> {
162 /// Iterate over events from the previous tick.
163 pub fn read(&self) -> impl Iterator<Item = &T> {
164 self.events.read()
165 }
166
167 /// Number of readable events.
168 pub fn len(&self) -> usize {
169 self.events.len()
170 }
171
172 /// Returns `true` if there are no readable events.
173 pub fn is_empty(&self) -> bool {
174 self.events.is_empty()
175 }
176}
177
178// SAFETY: access() reports ResRead(Events<T>). fetch() only reads the
179// Events<T> resource via get_resource — no mutation occurs.
180unsafe impl<T: Send + 'static> SystemParam for EventReader<'_, T> {
181 type Item<'w> = EventReader<'w, T>;
182
183 fn access() -> Vec<Access> {
184 vec![Access::ResRead(TypeId::of::<Events<T>>())]
185 }
186
187 unsafe fn fetch<'w>(world: UnsafeWorldCell) -> Self::Item<'w> {
188 EventReader {
189 events: unsafe { world.get_resource::<Events<T>>() },
190 }
191 }
192}
193
194// =============================================================================
195// Tests
196// =============================================================================
197
198#[cfg(test)]
199mod tests {
200 use super::*;
201 use crate::system_param::{SystemParam, has_conflicts};
202 use crate::world::World;
203
204 #[derive(Debug, PartialEq)]
205 struct DamageEvent {
206 amount: u32,
207 }
208
209 #[derive(Debug, PartialEq)]
210 struct SpawnEvent {
211 x: f32,
212 }
213
214 // -------------------------------------------------------------------------
215 // Events<T> unit tests
216 // -------------------------------------------------------------------------
217
218 #[test]
219 fn events_send_and_read() {
220 let mut events: Events<DamageEvent> = Events::new();
221
222 // No events readable yet.
223 assert!(events.is_empty());
224 assert_eq!(events.len(), 0);
225
226 // Send an event.
227 events.send(DamageEvent { amount: 42 });
228
229 // Not yet readable — still in current.
230 assert!(events.is_empty());
231
232 // Advance the buffer.
233 events.update();
234
235 // Now readable in previous.
236 assert!(!events.is_empty());
237 assert_eq!(events.len(), 1);
238 let collected: Vec<_> = events.read().collect();
239 assert_eq!(collected, vec![&DamageEvent { amount: 42 }]);
240 }
241
242 #[test]
243 fn read_current_returns_unswaped_events() {
244 let mut events: Events<DamageEvent> = Events::new();
245
246 events.send(DamageEvent { amount: 7 });
247
248 // read() only sees previous (empty).
249 assert_eq!(events.read().count(), 0);
250 // read_current() sees the not-yet-swapped buffer.
251 let current: Vec<_> = events.read_current().collect();
252 assert_eq!(current, vec![&DamageEvent { amount: 7 }]);
253
254 // After update, event moves to previous.
255 events.update();
256 assert_eq!(events.read().count(), 1);
257 assert_eq!(events.read_current().count(), 0);
258 }
259
260 #[test]
261 fn events_double_buffer_semantics() {
262 let mut events: Events<DamageEvent> = Events::new();
263
264 // Tick N: send event.
265 events.send(DamageEvent { amount: 10 });
266 events.update(); // current → previous
267
268 // Tick N+1 start: event is in previous → readable.
269 assert_eq!(events.len(), 1);
270
271 // Tick N+1: no new events, advance again.
272 events.update(); // previous is cleared, empty current stays current
273
274 // Tick N+2 start: previous is now cleared.
275 assert!(events.is_empty());
276 }
277
278 #[test]
279 fn events_multiple_sends_same_tick() {
280 let mut events: Events<DamageEvent> = Events::new();
281
282 events.send(DamageEvent { amount: 1 });
283 events.send(DamageEvent { amount: 2 });
284 events.send(DamageEvent { amount: 3 });
285 events.update();
286
287 assert_eq!(events.len(), 3);
288 let amounts: Vec<u32> = events.read().map(|e| e.amount).collect();
289 assert_eq!(amounts, vec![1, 2, 3]);
290 }
291
292 // -------------------------------------------------------------------------
293 // Access declaration tests
294 // -------------------------------------------------------------------------
295
296 #[test]
297 fn event_writer_access_is_res_write() {
298 let access = <EventWriter<'_, DamageEvent> as SystemParam>::access();
299 assert_eq!(access.len(), 1);
300 assert_eq!(
301 access[0],
302 Access::ResWrite(TypeId::of::<Events<DamageEvent>>())
303 );
304 }
305
306 #[test]
307 fn event_reader_access_is_res_read() {
308 let access = <EventReader<'_, DamageEvent> as SystemParam>::access();
309 assert_eq!(access.len(), 1);
310 assert_eq!(
311 access[0],
312 Access::ResRead(TypeId::of::<Events<DamageEvent>>())
313 );
314 }
315
316 // -------------------------------------------------------------------------
317 // Conflict detection tests
318 // -------------------------------------------------------------------------
319
320 #[test]
321 fn event_writer_reader_different_types_no_conflict() {
322 // EventWriter<DamageEvent> and EventReader<SpawnEvent> — different
323 // Events<T> TypeIds — must not conflict.
324 let writer_access = <EventWriter<'_, DamageEvent> as SystemParam>::access();
325 let reader_access = <EventReader<'_, SpawnEvent> as SystemParam>::access();
326 assert!(!has_conflicts(&writer_access, &reader_access));
327 }
328
329 #[test]
330 fn event_writer_reader_same_type_conflicts() {
331 // EventWriter<DamageEvent> and EventReader<DamageEvent> both touch
332 // Events<DamageEvent> — ResWrite + ResRead on the same TypeId → conflict.
333 let writer_access = <EventWriter<'_, DamageEvent> as SystemParam>::access();
334 let reader_access = <EventReader<'_, DamageEvent> as SystemParam>::access();
335 assert!(has_conflicts(&writer_access, &reader_access));
336 }
337
338 #[test]
339 fn event_reader_reader_same_type_no_conflict() {
340 // Two EventReaders on the same type — ResRead + ResRead → no conflict.
341 let a = <EventReader<'_, DamageEvent> as SystemParam>::access();
342 let b = <EventReader<'_, DamageEvent> as SystemParam>::access();
343 assert!(!has_conflicts(&a, &b));
344 }
345
346 // -------------------------------------------------------------------------
347 // SystemParam fetch tests
348 // -------------------------------------------------------------------------
349
350 #[test]
351 fn event_writer_fetch_and_send() {
352 let mut world = World::new();
353 world.add_event::<DamageEvent>();
354
355 let cell = unsafe { UnsafeWorldCell::new(&mut world as *mut World) };
356 unsafe {
357 let mut writer = <EventWriter<'_, DamageEvent> as SystemParam>::fetch(cell);
358 writer.send(DamageEvent { amount: 99 });
359 }
360
361 // Advance to make current → previous.
362 world.update_events();
363
364 assert_eq!(world.resource::<Events<DamageEvent>>().len(), 1);
365 }
366
367 #[test]
368 fn event_reader_fetch_and_read() {
369 let mut world = World::new();
370 world.add_event::<DamageEvent>();
371
372 // Send an event directly through the resource.
373 world
374 .resource_mut::<Events<DamageEvent>>()
375 .send(DamageEvent { amount: 7 });
376 world.update_events();
377
378 let cell = unsafe { UnsafeWorldCell::new(&mut world as *mut World) };
379 unsafe {
380 let reader = <EventReader<'_, DamageEvent> as SystemParam>::fetch(cell);
381 assert_eq!(reader.len(), 1);
382 let ev = reader.read().next().unwrap();
383 assert_eq!(ev.amount, 7);
384 }
385 }
386
387 #[test]
388 fn add_event_duplicate_no_updater_duplication() {
389 let mut world = World::new();
390 world.add_event::<DamageEvent>();
391 world.add_event::<DamageEvent>(); // no-op
392
393 world
394 .resource_mut::<Events<DamageEvent>>()
395 .send(DamageEvent { amount: 5 });
396 world.update_events();
397
398 // If the updater were duplicated, the second would clear previous.
399 assert_eq!(world.resource::<Events<DamageEvent>>().len(), 1);
400 }
401
402 #[test]
403 fn add_event_duplicate_does_not_drop_queued_events() {
404 let mut world = World::new();
405 world.add_event::<DamageEvent>();
406
407 // Queue an event, then call add_event again — must not reset the buffer.
408 world
409 .resource_mut::<Events<DamageEvent>>()
410 .send(DamageEvent { amount: 99 });
411 world.add_event::<DamageEvent>(); // no-op
412
413 world.update_events();
414 assert_eq!(world.resource::<Events<DamageEvent>>().len(), 1);
415 assert_eq!(
416 world
417 .resource::<Events<DamageEvent>>()
418 .read()
419 .next()
420 .unwrap()
421 .amount,
422 99
423 );
424 }
425
426 #[test]
427 fn add_event_after_take_resource_restores_without_duplicate_updater() {
428 let mut world = World::new();
429 world.add_event::<DamageEvent>();
430
431 // Remove the resource via the public API.
432 let _old: Events<DamageEvent> = world.take_resource();
433
434 // Re-register — must restore the resource and not duplicate the updater.
435 world.add_event::<DamageEvent>();
436
437 world
438 .resource_mut::<Events<DamageEvent>>()
439 .send(DamageEvent { amount: 77 });
440 world.update_events();
441
442 // One updater → event survives in previous. Two would clear it.
443 assert_eq!(world.resource::<Events<DamageEvent>>().len(), 1);
444 assert_eq!(
445 world
446 .resource::<Events<DamageEvent>>()
447 .read()
448 .next()
449 .unwrap()
450 .amount,
451 77
452 );
453 }
454}