1use std::cell::RefCell;
31use std::sync::atomic::{AtomicU64, AtomicUsize, Ordering};
32
33use crate::event::Events;
34use crate::world::World;
35
36#[derive(Debug, Clone, Copy, PartialEq)]
46pub struct FrameEvent {
47 pub kind: u32,
50 pub entity: u32,
52 pub position: [f32; 3],
54 pub intensity: f32,
56 pub data: [f32; 4],
59}
60
61pub const FRAME_EVENT_STRIDE: usize = 10;
64
65pub trait RenderEvent: 'static + Send + Sync {
75 const KIND: u32;
77
78 fn entity(&self) -> u32;
80
81 fn position(&self) -> [f32; 3];
83
84 fn intensity(&self) -> f32 {
86 1.0
87 }
88
89 fn data(&self) -> [f32; 4] {
93 [0.0; 4]
94 }
95}
96
97type EventExtractFn = Box<dyn Fn(&World) -> Vec<FrameEvent> + Send + Sync>;
103
104pub struct RenderEventRegistry {
129 extractors: Vec<EventExtractFn>,
130 pending: RefCell<Vec<FrameEvent>>,
134}
135
136impl RenderEventRegistry {
137 pub fn new() -> Self {
139 Self {
140 extractors: Vec::new(),
141 pending: RefCell::new(Vec::new()),
142 }
143 }
144
145 pub fn register<T: RenderEvent>(&mut self) {
152 let offset = AtomicUsize::new(0);
153 let last_epoch = AtomicU64::new(0);
154 self.extractors.push(Box::new(move |world: &World| {
155 let Some(events) = world.try_resource::<Events<T>>() else {
156 return Vec::new();
157 };
158 let epoch = world.event_swap_epoch();
160 if last_epoch.load(Ordering::Relaxed) != epoch {
161 offset.store(0, Ordering::Relaxed);
162 last_epoch.store(epoch, Ordering::Relaxed);
163 }
164 let skip = offset.load(Ordering::Relaxed);
165 let result: Vec<FrameEvent> = events
166 .read_current()
167 .skip(skip)
168 .map(|e| FrameEvent {
169 kind: T::KIND,
170 entity: e.entity(),
171 position: e.position(),
172 intensity: e.intensity(),
173 data: e.data(),
174 })
175 .collect();
176 offset.store(events.current_len(), Ordering::Relaxed);
177 result
178 }));
179 }
180
181 pub fn accumulate(&self, world: &World) {
189 let mut pending = self.pending.borrow_mut();
190 for extractor in &self.extractors {
191 pending.extend(extractor(world));
192 }
193 }
194
195 pub fn drain(&self) -> Vec<FrameEvent> {
200 std::mem::take(&mut *self.pending.borrow_mut())
201 }
202
203 pub fn len(&self) -> usize {
205 self.extractors.len()
206 }
207
208 pub fn is_empty(&self) -> bool {
210 self.extractors.is_empty()
211 }
212}
213
214impl Default for RenderEventRegistry {
215 fn default() -> Self {
216 Self::new()
217 }
218}
219
220#[cfg(test)]
225mod tests {
226 use super::*;
227 use crate::world::World;
228
229 #[derive(Debug, PartialEq)]
232 struct ImpactEvent {
233 entity_index: u32,
234 pos: [f32; 3],
235 force: f32,
236 }
237
238 impl RenderEvent for ImpactEvent {
239 const KIND: u32 = 1;
240 fn entity(&self) -> u32 {
241 self.entity_index
242 }
243 fn position(&self) -> [f32; 3] {
244 self.pos
245 }
246 fn intensity(&self) -> f32 {
247 self.force
248 }
249 }
250
251 #[derive(Debug, PartialEq)]
252 struct ExplosionEvent {
253 pos: [f32; 3],
254 radius: f32,
255 }
256
257 impl RenderEvent for ExplosionEvent {
258 const KIND: u32 = 2;
259 fn entity(&self) -> u32 {
260 0
261 }
262 fn position(&self) -> [f32; 3] {
263 self.pos
264 }
265 fn intensity(&self) -> f32 {
266 self.radius
267 }
268 }
269
270 #[test]
273 fn empty_registry() {
274 let registry = RenderEventRegistry::new();
275 assert!(registry.is_empty());
276 assert_eq!(registry.len(), 0);
277 }
278
279 #[test]
280 fn register_increments_len() {
281 let mut registry = RenderEventRegistry::new();
282 registry.register::<ImpactEvent>();
283 assert_eq!(registry.len(), 1);
284 registry.register::<ExplosionEvent>();
285 assert_eq!(registry.len(), 2);
286 }
287
288 fn flush_and_drain(registry: &RenderEventRegistry, world: &World) -> Vec<FrameEvent> {
290 registry.accumulate(world);
291 registry.drain()
292 }
293
294 #[test]
295 fn drain_empty_when_no_events() {
296 let mut world = World::new();
297 world.add_event::<ImpactEvent>();
298
299 let mut registry = RenderEventRegistry::new();
300 registry.register::<ImpactEvent>();
301
302 let events = flush_and_drain(®istry, &world);
303 assert!(events.is_empty());
304 }
305
306 #[test]
307 fn drain_empty_when_events_resource_missing() {
308 let world = World::new();
309
310 let mut registry = RenderEventRegistry::new();
311 registry.register::<ImpactEvent>();
312
313 let events = flush_and_drain(®istry, &world);
314 assert!(events.is_empty());
315 }
316
317 #[test]
318 fn accumulate_captures_current_buffer() {
319 let mut world = World::new();
320 world.add_event::<ImpactEvent>();
321
322 world
323 .resource_mut::<Events<ImpactEvent>>()
324 .send(ImpactEvent {
325 entity_index: 42,
326 pos: [1.0, 2.0, 3.0],
327 force: 0.75,
328 });
329
330 let mut registry = RenderEventRegistry::new();
331 registry.register::<ImpactEvent>();
332
333 let events = flush_and_drain(®istry, &world);
334 assert_eq!(events.len(), 1);
335 assert_eq!(events[0].kind, 1);
336 assert_eq!(events[0].entity, 42);
337 assert_eq!(events[0].position, [1.0, 2.0, 3.0]);
338 assert!((events[0].intensity - 0.75).abs() < f32::EPSILON);
339 assert_eq!(events[0].data, [0.0; 4]);
340 }
341
342 #[test]
343 fn accumulate_multiple_events_same_type() {
344 let mut world = World::new();
345 world.add_event::<ImpactEvent>();
346
347 let events_res = world.resource_mut::<Events<ImpactEvent>>();
348 events_res.send(ImpactEvent {
349 entity_index: 1,
350 pos: [0.0; 3],
351 force: 1.0,
352 });
353 events_res.send(ImpactEvent {
354 entity_index: 2,
355 pos: [5.0; 3],
356 force: 0.5,
357 });
358
359 let mut registry = RenderEventRegistry::new();
360 registry.register::<ImpactEvent>();
361
362 let events = flush_and_drain(®istry, &world);
363 assert_eq!(events.len(), 2);
364 assert_eq!(events[0].entity, 1);
365 assert_eq!(events[1].entity, 2);
366 }
367
368 #[test]
369 fn accumulate_multiple_event_types() {
370 let mut world = World::new();
371 world.add_event::<ImpactEvent>();
372 world.add_event::<ExplosionEvent>();
373
374 world
375 .resource_mut::<Events<ImpactEvent>>()
376 .send(ImpactEvent {
377 entity_index: 10,
378 pos: [1.0, 0.0, 0.0],
379 force: 0.9,
380 });
381 world
382 .resource_mut::<Events<ExplosionEvent>>()
383 .send(ExplosionEvent {
384 pos: [5.0, 5.0, 5.0],
385 radius: 3.0,
386 });
387
388 let mut registry = RenderEventRegistry::new();
389 registry.register::<ImpactEvent>();
390 registry.register::<ExplosionEvent>();
391
392 let events = flush_and_drain(®istry, &world);
393 assert_eq!(events.len(), 2);
394
395 let impact = events.iter().find(|e| e.kind == 1).unwrap();
396 assert_eq!(impact.entity, 10);
397
398 let explosion = events.iter().find(|e| e.kind == 2).unwrap();
399 assert_eq!(explosion.entity, 0);
400 assert!((explosion.intensity - 3.0).abs() < f32::EPSILON);
401 }
402
403 #[test]
404 fn multi_tick_accumulation_no_event_loss() {
405 let mut world = World::new();
406 world.add_event::<ImpactEvent>();
407
408 let mut registry = RenderEventRegistry::new();
409 registry.register::<ImpactEvent>();
410
411 world
413 .resource_mut::<Events<ImpactEvent>>()
414 .send(ImpactEvent {
415 entity_index: 1,
416 pos: [0.0; 3],
417 force: 1.0,
418 });
419 registry.accumulate(&world);
420 world.update_events(); world
424 .resource_mut::<Events<ImpactEvent>>()
425 .send(ImpactEvent {
426 entity_index: 2,
427 pos: [5.0; 3],
428 force: 0.5,
429 });
430 registry.accumulate(&world);
431 world.update_events();
432
433 let events = registry.drain();
435 assert_eq!(events.len(), 2);
436 assert_eq!(events[0].entity, 1);
437 assert_eq!(events[1].entity, 2);
438 }
439
440 #[test]
441 fn drain_clears_pending() {
442 let mut world = World::new();
443 world.add_event::<ImpactEvent>();
444
445 let mut registry = RenderEventRegistry::new();
446 registry.register::<ImpactEvent>();
447
448 world
449 .resource_mut::<Events<ImpactEvent>>()
450 .send(ImpactEvent {
451 entity_index: 1,
452 pos: [0.0; 3],
453 force: 1.0,
454 });
455 registry.accumulate(&world);
456
457 assert_eq!(registry.drain().len(), 1);
458 assert!(registry.drain().is_empty());
460 }
461
462 #[test]
463 fn no_double_delivery_across_frames() {
464 let mut world = World::new();
465 world.add_event::<ImpactEvent>();
466
467 let mut registry = RenderEventRegistry::new();
468 registry.register::<ImpactEvent>();
469
470 world
472 .resource_mut::<Events<ImpactEvent>>()
473 .send(ImpactEvent {
474 entity_index: 1,
475 pos: [0.0; 3],
476 force: 1.0,
477 });
478 registry.accumulate(&world);
479 let frame1 = registry.drain();
480 assert_eq!(frame1.len(), 1);
481
482 world.update_events();
484
485 registry.accumulate(&world);
487 let frame2 = registry.drain();
488 assert!(frame2.is_empty()); }
490
491 #[test]
492 fn default_intensity_is_one() {
493 #[derive(Debug)]
494 struct MinimalEvent;
495
496 impl RenderEvent for MinimalEvent {
497 const KIND: u32 = 99;
498 fn entity(&self) -> u32 {
499 0
500 }
501 fn position(&self) -> [f32; 3] {
502 [0.0; 3]
503 }
504 }
505
506 let mut world = World::new();
507 world.add_event::<MinimalEvent>();
508 world
509 .resource_mut::<Events<MinimalEvent>>()
510 .send(MinimalEvent);
511
512 let mut registry = RenderEventRegistry::new();
513 registry.register::<MinimalEvent>();
514
515 let events = flush_and_drain(®istry, &world);
516 assert_eq!(events.len(), 1);
517 assert!((events[0].intensity - 1.0).abs() < f32::EPSILON);
518 }
519
520 #[test]
521 fn custom_data_payload() {
522 #[derive(Debug)]
523 struct HitFlash {
524 color: [f32; 4],
525 }
526
527 impl RenderEvent for HitFlash {
528 const KIND: u32 = 50;
529 fn entity(&self) -> u32 {
530 0
531 }
532 fn position(&self) -> [f32; 3] {
533 [0.0; 3]
534 }
535 fn data(&self) -> [f32; 4] {
536 self.color
537 }
538 }
539
540 let mut world = World::new();
541 world.add_event::<HitFlash>();
542 world.resource_mut::<Events<HitFlash>>().send(HitFlash {
543 color: [1.0, 0.0, 0.0, 0.8],
544 });
545
546 let mut registry = RenderEventRegistry::new();
547 registry.register::<HitFlash>();
548
549 let events = flush_and_drain(®istry, &world);
550 assert_eq!(events.len(), 1);
551 assert_eq!(events[0].kind, 50);
552 assert_eq!(events[0].data, [1.0, 0.0, 0.0, 0.8]);
553 }
554
555 #[test]
556 fn drain_with_empty_registry_returns_empty() {
557 let registry = RenderEventRegistry::new();
558 assert!(registry.drain().is_empty());
559 }
560}