1use alloc::vec::Vec;
19
20use azul_core::dom::DomNodeId;
21use azul_core::events::{
22 EventData, EventProvider, EventSource as CoreEventSource, EventType, SyntheticEvent,
23};
24use azul_core::task::Instant;
25pub use azul_core::sensors::{SensorKind, SensorReading};
26
27#[derive(Copy, Debug, Clone, PartialEq, Default)]
30pub struct SensorManager {
31 pub accelerometer: Option<SensorReading>,
33 pub gyroscope: Option<SensorReading>,
35 pub magnetometer: Option<SensorReading>,
37 pub pending_event: bool,
41 pub has_listeners: bool,
47}
48
49impl SensorManager {
50 #[must_use] pub fn new() -> Self {
51 Self::default()
52 }
53
54 #[must_use] pub const fn reading(&self, kind: SensorKind) -> Option<SensorReading> {
56 match kind {
57 SensorKind::Accelerometer => self.accelerometer,
58 SensorKind::Gyroscope => self.gyroscope,
59 SensorKind::Magnetometer => self.magnetometer,
60 }
61 }
62
63 pub fn set_reading(&mut self, reading: SensorReading) -> bool {
67 let slot = match reading.kind {
68 SensorKind::Accelerometer => &mut self.accelerometer,
69 SensorKind::Gyroscope => &mut self.gyroscope,
70 SensorKind::Magnetometer => &mut self.magnetometer,
71 };
72 let changed = slot.as_mut().is_none_or(|prev| !reading_bitwise_eq(prev, &reading));
73 *slot = Some(reading);
74 if changed {
75 self.pending_event = true;
76 }
77 changed
78 }
79
80 pub const fn clear_pending_event(&mut self) {
83 self.pending_event = false;
84 }
85
86 pub const fn set_has_listeners(&mut self, has: bool) {
88 self.has_listeners = has;
89 }
90
91 #[must_use] pub const fn has_listeners(&self) -> bool {
93 self.has_listeners
94 }
95}
96
97impl EventProvider for SensorManager {
98 fn get_pending_events(&self, timestamp: Instant) -> Vec<SyntheticEvent> {
102 if self.pending_event {
103 alloc::vec![SyntheticEvent::new(
104 EventType::SensorChanged,
105 CoreEventSource::User,
106 DomNodeId::ROOT,
107 timestamp,
108 EventData::None,
109 )]
110 } else {
111 Vec::new()
112 }
113 }
114}
115
116fn reading_bitwise_eq(a: &SensorReading, b: &SensorReading) -> bool {
117 a.kind == b.kind
118 && a.x.to_bits() == b.x.to_bits()
119 && a.y.to_bits() == b.y.to_bits()
120 && a.z.to_bits() == b.z.to_bits()
121 && a.timestamp_ms == b.timestamp_ms
122}
123
124static PENDING_READINGS: std::sync::Mutex<Vec<SensorReading>> =
133 std::sync::Mutex::new(Vec::new());
134
135pub fn push_sensor_reading(reading: SensorReading) {
138 let mut q = PENDING_READINGS.lock().unwrap_or_else(std::sync::PoisonError::into_inner);
139 q.push(reading);
140}
141
142pub fn drain_sensor_readings() -> Vec<SensorReading> {
146 let mut q = PENDING_READINGS.lock().unwrap_or_else(std::sync::PoisonError::into_inner);
147 core::mem::take(&mut *q)
148}
149
150#[cfg(test)]
151mod tests {
152 use super::*;
153
154 #[test]
155 fn listener_flag_gates_polling_decision() {
156 let mut mgr = SensorManager::new();
157 assert!(!mgr.has_listeners(), "no listeners until the relayout walk reports some");
158 mgr.set_has_listeners(true);
159 assert!(mgr.has_listeners());
160 mgr.set_has_listeners(false);
161 assert!(!mgr.has_listeners());
162 }
163
164 fn r(kind: SensorKind, x: f32, y: f32, z: f32) -> SensorReading {
165 SensorReading {
166 kind,
167 x,
168 y,
169 z,
170 timestamp_ms: 0,
171 }
172 }
173
174 #[test]
175 fn manager_defaults_to_no_readings() {
176 let mgr = SensorManager::new();
177 assert_eq!(mgr.reading(SensorKind::Accelerometer), None);
178 assert_eq!(mgr.reading(SensorKind::Gyroscope), None);
179 assert_eq!(mgr.reading(SensorKind::Magnetometer), None);
180 }
181
182 #[test]
183 fn set_reading_routes_by_kind_and_flags_change() {
184 let mut mgr = SensorManager::new();
185 assert!(mgr.set_reading(r(SensorKind::Accelerometer, 0.0, 0.0, 9.81)));
186 assert!(mgr.reading(SensorKind::Accelerometer).is_some());
188 assert_eq!(mgr.reading(SensorKind::Gyroscope), None);
189 assert!(!mgr.set_reading(r(SensorKind::Accelerometer, 0.0, 0.0, 9.81)));
191 assert!(mgr.set_reading(r(SensorKind::Accelerometer, 1.0, 0.0, 9.81)));
193 assert!(mgr.set_reading(r(SensorKind::Gyroscope, 0.1, 0.0, 0.0)));
195 assert_eq!(
196 mgr.reading(SensorKind::Gyroscope).map(|r| r.x),
197 Some(0.1)
198 );
199 }
200
201 #[test]
202 fn magnitude_of_resting_accelerometer() {
203 let g = r(SensorKind::Accelerometer, 0.0, 0.0, 9.81);
204 assert!((g.magnitude() - 9.81).abs() < 1e-4);
205 }
206
207 #[test]
208 fn readings_round_trip_through_manager() {
209 drop(drain_sensor_readings());
210
211 push_sensor_reading(r(SensorKind::Accelerometer, 1.0, 2.0, 3.0));
212 push_sensor_reading(r(SensorKind::Accelerometer, 4.0, 5.0, 6.0)); push_sensor_reading(r(SensorKind::Magnetometer, 20.0, 0.0, 40.0));
214 let drained = drain_sensor_readings();
215 assert_eq!(drained.len(), 3, "all parked readings drain in order");
216
217 let mut mgr = SensorManager::new();
218 for reading in &drained {
219 mgr.set_reading(*reading);
220 }
221 assert_eq!(
222 mgr.reading(SensorKind::Accelerometer).map(|r| r.x),
223 Some(4.0),
224 "the last accelerometer reading wins"
225 );
226 assert_eq!(
227 mgr.reading(SensorKind::Magnetometer).map(|r| r.z),
228 Some(40.0)
229 );
230
231 assert!(drain_sensor_readings().is_empty());
232 }
233}
234
235#[cfg(test)]
236#[allow(clippy::float_cmp, clippy::eq_op)] mod autotest_generated {
238 use alloc::vec::Vec;
242
243 use azul_core::{
244 dom::DomNodeId,
245 events::{EventProvider, EventType},
246 task::{Instant, SystemTick},
247 };
248
249 use super::*;
250
251 const KINDS: [SensorKind; 3] = [
263 SensorKind::Accelerometer,
264 SensorKind::Gyroscope,
265 SensorKind::Magnetometer,
266 ];
267
268 const fn r(kind: SensorKind, x: f32, y: f32, z: f32, timestamp_ms: u64) -> SensorReading {
269 SensorReading {
270 kind,
271 x,
272 y,
273 z,
274 timestamp_ms,
275 }
276 }
277
278 const fn base() -> SensorReading {
281 r(SensorKind::Accelerometer, 1.5, -2.25, 9.81, 1_234)
282 }
283
284 const fn nan_sample(kind: SensorKind) -> SensorReading {
287 r(kind, f32::NAN, f32::NAN, f32::NAN, 7)
288 }
289
290 fn tick() -> Instant {
291 Instant::Tick(SystemTick::new(0))
292 }
293
294 fn ulp_flip(v: f32) -> f32 {
298 f32::from_bits(v.to_bits() ^ 1)
299 }
300
301 fn bits_identical(a: &SensorReading, b: &SensorReading) -> bool {
304 a.kind == b.kind
305 && a.x.to_bits() == b.x.to_bits()
306 && a.y.to_bits() == b.y.to_bits()
307 && a.z.to_bits() == b.z.to_bits()
308 && a.timestamp_ms == b.timestamp_ms
309 }
310
311 fn hostile_floats() -> [f32; 12] {
315 [
316 0.0,
317 -0.0,
318 1.0,
319 -1.0,
320 f32::MIN_POSITIVE,
321 f32::from_bits(1), f32::MAX,
323 f32::MIN,
324 f32::INFINITY,
325 f32::NEG_INFINITY,
326 f32::NAN,
327 f32::from_bits(0xffc0_0000), ]
329 }
330
331 #[test]
336 fn new_starts_cold_and_matches_default() {
337 let mgr = SensorManager::new();
338 for kind in KINDS {
339 assert_eq!(mgr.reading(kind), None, "{kind:?} slot must start empty");
340 }
341 assert!(!mgr.pending_event, "no event may be pending before any sample");
342 assert!(!mgr.has_listeners(), "polling must not be armed at construction");
343 assert_eq!(mgr, SensorManager::default(), "new() must equal Default");
344 assert_eq!(mgr, SensorManager::new(), "new() must be deterministic");
345 }
346
347 #[test]
352 fn reading_slots_are_independent_per_kind() {
353 for written in KINDS {
354 let mut mgr = SensorManager::new();
355 assert!(mgr.set_reading(r(written, 1.0, 2.0, 3.0, 0)));
356 for probed in KINDS {
357 if probed == written {
358 assert_eq!(
359 mgr.reading(probed).map(|s| s.kind),
360 Some(written),
361 "{written:?} must land in its own slot, tagged with its kind"
362 );
363 } else {
364 assert_eq!(
365 mgr.reading(probed),
366 None,
367 "writing {written:?} must not leak into the {probed:?} slot"
368 );
369 }
370 }
371 }
372 }
373
374 #[test]
377 fn reading_returns_a_copy_not_an_alias() {
378 let mut mgr = SensorManager::new();
379 mgr.set_reading(base());
380 let mut got = mgr.reading(SensorKind::Accelerometer).unwrap();
381 got.x = -999.0;
382 got.timestamp_ms = u64::MAX;
383 assert_eq!(
384 mgr.reading(SensorKind::Accelerometer),
385 Some(base()),
386 "mutating the returned copy must not write through to the manager"
387 );
388 }
389
390 #[test]
397 fn repeated_nan_sample_does_not_advance() {
398 for kind in KINDS {
399 let mut mgr = SensorManager::new();
400 let sample = nan_sample(kind);
401
402 assert!(
403 mgr.set_reading(sample),
404 "{kind:?}: the first sample always advances (slot was empty)"
405 );
406 assert!(
407 !mgr.set_reading(sample),
408 "{kind:?}: an identical NaN sample must not be reported as changed"
409 );
410 assert!(
411 !mgr.set_reading(sample),
412 "{kind:?}: still no change on the third identical NaN sample"
413 );
414
415 let stored = mgr.reading(kind).unwrap();
417 assert!(
418 stored != sample,
419 "{kind:?}: sanity — NaN fields make PartialEq report inequality, \
420 which is exactly why set_reading must compare bit patterns"
421 );
422 }
423 }
424
425 #[test]
428 fn first_sample_always_advances() {
429 for kind in KINDS {
430 for v in hostile_floats() {
431 let mut mgr = SensorManager::new();
432 assert!(
433 mgr.set_reading(r(kind, v, v, v, 0)),
434 "{kind:?}: first sample ({v:?}) must advance"
435 );
436 assert!(mgr.pending_event, "{kind:?}: an advance must arm the event");
437 }
438 }
439 }
440
441 #[test]
445 fn extremes_round_trip_bit_exactly_and_resend_is_idempotent() {
446 for kind in KINDS {
447 for x in hostile_floats() {
448 for ts in [0_u64, 1, u64::MAX] {
449 let sample = r(kind, x, -x, x, ts);
450 let mut mgr = SensorManager::new();
451
452 assert!(mgr.set_reading(sample));
453 let stored = mgr.reading(kind).expect("just written");
454 assert!(
455 bits_identical(&stored, &sample),
456 "{kind:?}: stored sample must be bit-identical to the input \
457 (x = {x:?}, ts = {ts})"
458 );
459 assert!(
460 !mgr.set_reading(sample),
461 "{kind:?}: resending the identical sample must not advance \
462 (x = {x:?}, ts = {ts})"
463 );
464 }
465 }
466 }
467 }
468
469 #[test]
474 fn a_change_in_any_single_field_advances() {
475 let mut with_x = base();
476 with_x.x = ulp_flip(base().x);
477 let mut with_y = base();
478 with_y.y = ulp_flip(base().y);
479 let mut with_z = base();
480 with_z.z = ulp_flip(base().z);
481 let mut with_ts = base();
482 with_ts.timestamp_ms = base().timestamp_ms + 1;
483
484 for (field, mutated) in [
485 ("x", with_x),
486 ("y", with_y),
487 ("z", with_z),
488 ("timestamp_ms", with_ts),
489 ] {
490 let mut mgr = SensorManager::new();
491 assert!(mgr.set_reading(base()));
492 assert!(
493 mgr.set_reading(mutated),
494 "a change in `{field}` alone must be reported as an advance"
495 );
496 assert!(
497 !mgr.set_reading(mutated),
498 "`{field}`: the mutated sample is now the previous one"
499 );
500 assert!(
501 mgr.set_reading(base()),
502 "`{field}`: reverting back to the original is also an advance"
503 );
504 }
505 }
506
507 #[test]
510 fn signed_zero_flip_advances_even_though_it_compares_equal() {
511 assert_eq!(0.0_f32, -0.0_f32, "sanity: the two zeroes compare equal");
512
513 let mut mgr = SensorManager::new();
514 let pos = r(SensorKind::Gyroscope, 0.0, 0.0, 0.0, 0);
515 let neg = r(SensorKind::Gyroscope, -0.0, 0.0, 0.0, 0);
516
517 assert!(mgr.set_reading(pos));
518 assert!(
519 mgr.set_reading(neg),
520 "+0.0 → -0.0 differs in bits and must count as an advance"
521 );
522 assert!(!mgr.set_reading(neg), "…but the same -0.0 twice must not");
523 assert!(
524 mgr.reading(SensorKind::Gyroscope)
525 .is_some_and(|s| s.x.is_sign_negative()),
526 "the -0.0 must actually be the stored value"
527 );
528 }
529
530 #[test]
532 fn distinct_nan_payloads_advance_but_identical_ones_do_not() {
533 let quiet = f32::NAN;
534 let other = f32::from_bits(quiet.to_bits() | 1); let negative = f32::from_bits(0xffc0_0000); assert!(quiet.is_nan() && other.is_nan() && negative.is_nan());
537
538 let mut mgr = SensorManager::new();
539 assert!(mgr.set_reading(r(SensorKind::Magnetometer, quiet, 0.0, 0.0, 0)));
540 assert!(
541 mgr.set_reading(r(SensorKind::Magnetometer, other, 0.0, 0.0, 0)),
542 "a different NaN payload is a different bit pattern → advance"
543 );
544 assert!(
545 mgr.set_reading(r(SensorKind::Magnetometer, negative, 0.0, 0.0, 0)),
546 "a sign-flipped NaN is a different bit pattern → advance"
547 );
548 assert!(
549 !mgr.set_reading(r(SensorKind::Magnetometer, negative, 0.0, 0.0, 0)),
550 "the very same NaN bit pattern → no advance"
551 );
552 }
553
554 #[test]
557 fn set_reading_routes_strictly_by_kind() {
558 let mut mgr = SensorManager::new();
559 mgr.set_reading(r(SensorKind::Accelerometer, 1.0, 1.0, 1.0, 1));
560 mgr.set_reading(r(SensorKind::Gyroscope, 2.0, 2.0, 2.0, 2));
561 mgr.set_reading(r(SensorKind::Magnetometer, 3.0, 3.0, 3.0, 3));
562
563 assert_eq!(mgr.reading(SensorKind::Accelerometer).map(|s| s.x), Some(1.0));
564 assert_eq!(mgr.reading(SensorKind::Gyroscope).map(|s| s.x), Some(2.0));
565 assert_eq!(mgr.reading(SensorKind::Magnetometer).map(|s| s.x), Some(3.0));
566 for kind in KINDS {
567 assert_eq!(
568 mgr.reading(kind).map(|s| s.kind),
569 Some(kind),
570 "the {kind:?} slot must hold a {kind:?}-tagged sample"
571 );
572 }
573 }
574
575 #[test]
580 fn redundant_sample_does_not_disarm_a_pending_event() {
581 let mut mgr = SensorManager::new();
582 let sample = base();
583
584 assert!(mgr.set_reading(sample));
585 assert!(mgr.pending_event, "the advance armed the event");
586
587 assert!(!mgr.set_reading(sample), "identical sample: no advance");
588 assert!(
589 mgr.pending_event,
590 "a redundant sample must NOT clear an event the pass has not seen yet"
591 );
592 assert_eq!(
593 mgr.get_pending_events(tick()).len(),
594 1,
595 "the event must still be deliverable"
596 );
597 }
598
599 #[test]
601 fn redundant_sample_after_clear_does_not_re_arm() {
602 let mut mgr = SensorManager::new();
603 mgr.set_reading(base());
604 mgr.clear_pending_event();
605
606 assert!(!mgr.set_reading(base()), "identical sample: no advance");
607 assert!(
608 !mgr.pending_event,
609 "a no-op sample must not raise a fresh event"
610 );
611 assert!(mgr.get_pending_events(tick()).is_empty());
612
613 let mut moved = base();
614 moved.x += 1.0;
615 assert!(mgr.set_reading(moved), "a real change re-arms");
616 assert!(mgr.pending_event);
617 }
618
619 #[test]
624 fn clear_pending_event_is_idempotent_and_narrow() {
625 let mut mgr = SensorManager::new();
626 mgr.clear_pending_event(); assert!(!mgr.pending_event);
628
629 mgr.set_has_listeners(true);
630 mgr.set_reading(base());
631 assert!(mgr.pending_event);
632
633 mgr.clear_pending_event();
634 mgr.clear_pending_event(); assert!(!mgr.pending_event);
636 assert_eq!(
637 mgr.reading(SensorKind::Accelerometer),
638 Some(base()),
639 "clearing the flag must not discard the stored reading"
640 );
641 assert!(
642 mgr.has_listeners(),
643 "clearing the flag must not disarm polling"
644 );
645 }
646
647 #[test]
650 fn has_listeners_round_trips_and_stays_independent() {
651 let mut mgr = SensorManager::new();
652 assert!(!mgr.has_listeners(), "default is 'do not poll'");
653
654 for arm in [true, true, false, false, true] {
655 mgr.set_has_listeners(arm);
656 assert_eq!(mgr.has_listeners(), arm, "set_has_listeners({arm}) must stick");
657 }
658
659 assert_eq!(mgr.reading(SensorKind::Accelerometer), None);
661 assert!(!mgr.pending_event);
662
663 mgr.set_reading(base());
664 mgr.set_has_listeners(false);
665 assert!(
666 mgr.pending_event,
667 "dropping the listeners must not swallow an already-pending event"
668 );
669 assert_eq!(mgr.reading(SensorKind::Accelerometer), Some(base()));
670 }
671
672 #[test]
675 fn set_reading_never_arms_the_listener_flag() {
676 let mut mgr = SensorManager::new();
677 for kind in KINDS {
678 mgr.set_reading(nan_sample(kind));
679 assert!(
680 !mgr.has_listeners(),
681 "{kind:?}: only set_has_listeners may arm polling"
682 );
683 }
684 }
685
686 #[test]
691 fn bitwise_eq_is_reflexive_even_for_nan() {
692 for kind in KINDS {
693 let a = nan_sample(kind);
694 let b = a; assert!(
696 reading_bitwise_eq(&a, &b),
697 "{kind:?}: identical bit patterns must compare equal"
698 );
699 assert!(
700 a != b,
701 "{kind:?}: sanity — derived PartialEq disagrees, because NaN != NaN"
702 );
703 }
704 }
705
706 #[test]
709 fn bitwise_eq_separates_signed_zeroes_that_partialeq_merges() {
710 let pos = r(SensorKind::Accelerometer, 0.0, 0.0, 0.0, 0);
711 let neg = r(SensorKind::Accelerometer, -0.0, -0.0, -0.0, 0);
712
713 assert_eq!(pos, neg, "sanity: derived PartialEq calls the zeroes equal");
714 assert!(
715 !reading_bitwise_eq(&pos, &neg),
716 "the bitwise comparison must tell +0.0 and -0.0 apart"
717 );
718 }
719
720 #[test]
724 fn bitwise_eq_watches_every_field() {
725 let b = base();
726
727 let mut other_kind = b;
728 other_kind.kind = SensorKind::Gyroscope;
729 let mut other_x = b;
730 other_x.x = ulp_flip(b.x);
731 let mut other_y = b;
732 other_y.y = ulp_flip(b.y);
733 let mut other_z = b;
734 other_z.z = ulp_flip(b.z);
735 let mut other_ts = b;
736 other_ts.timestamp_ms = b.timestamp_ms + 1;
737
738 for (field, mutated) in [
739 ("kind", other_kind),
740 ("x", other_x),
741 ("y", other_y),
742 ("z", other_z),
743 ("timestamp_ms", other_ts),
744 ] {
745 assert!(
746 !reading_bitwise_eq(&b, &mutated),
747 "a difference in `{field}` must be detected"
748 );
749 assert!(
750 !reading_bitwise_eq(&mutated, &b),
751 "`{field}`: and symmetrically so"
752 );
753 }
754 }
755
756 #[test]
759 fn bitwise_eq_is_reflexive_and_symmetric_over_hostile_inputs() {
760 let mut samples = Vec::new();
761 for kind in KINDS {
762 for v in hostile_floats() {
763 for ts in [0_u64, u64::MAX] {
764 samples.push(r(kind, v, -v, v, ts));
765 }
766 }
767 }
768
769 for a in &samples {
770 assert!(
771 reading_bitwise_eq(a, a),
772 "must be reflexive for {a:?}"
773 );
774 for b in &samples {
775 assert_eq!(
776 reading_bitwise_eq(a, b),
777 reading_bitwise_eq(b, a),
778 "must be symmetric for {a:?} vs {b:?}"
779 );
780 assert_eq!(
781 reading_bitwise_eq(a, b),
782 bits_identical(a, b),
783 "must agree with a field-by-field bit comparison for {a:?} vs {b:?}"
784 );
785 }
786 }
787 }
788
789 #[test]
794 fn sensor_changed_event_is_emitted_only_while_pending() {
795 let mut mgr = SensorManager::new();
796 assert!(
797 mgr.get_pending_events(tick()).is_empty(),
798 "a cold manager emits nothing"
799 );
800
801 assert!(mgr.set_reading(base()));
802 let events = mgr.get_pending_events(tick());
803 assert_eq!(events.len(), 1, "one advance → exactly one event");
804 assert_eq!(events[0].event_type, EventType::SensorChanged);
805 assert_eq!(events[0].target, DomNodeId::ROOT, "window-level: target is root");
806
807 assert_eq!(
808 mgr.get_pending_events(tick()).len(),
809 1,
810 "collecting is non-destructive; only clear_pending_event disarms"
811 );
812
813 mgr.clear_pending_event();
814 assert!(
815 mgr.get_pending_events(tick()).is_empty(),
816 "after the event pass drained it, nothing more is pending"
817 );
818 }
819
820 #[test]
823 fn many_advances_collapse_into_one_event() {
824 let mut mgr = SensorManager::new();
825 for kind in KINDS {
826 for (ts, x) in [(0_u64, 1.0_f32), (1, 2.0), (2, 3.0), (3, 4.0)] {
827 mgr.set_reading(r(kind, x, 0.0, 0.0, ts));
828 }
829 }
830 assert_eq!(
831 mgr.get_pending_events(tick()).len(),
832 1,
833 "12 samples across 3 kinds still yield one SensorChanged"
834 );
835 }
836}