1
//! Sensor manager — cross-platform state for the motion-sensor surface
2
//! (SUPER_PLAN_2 §1 feature 5 + research/03).
3
//!
4
//! Continuous + push-driven, like geolocation:
5
//!
6
//! - The **platform backend** (`dll/src/desktop/extra/sensors/<plat>.rs`)
7
//!   subscribes to CoreMotion (`CMMotionManager`) / Android `SensorManager`
8
//!   and calls [`push_sensor_reading`] on every sample (arbitrary thread).
9
//! - The dll **layout pass** drains the channel via
10
//!   [`drain_sensor_readings`] and folds each into the manager through
11
//!   [`SensorManager::set_reading`].
12
//! - **Callbacks** read `reading(kind)` synchronously (via
13
//!   `CallbackInfo::get_sensor_reading`) to drive tilt / shake / compass UI.
14
//!
15
//! One reading slot per [`SensorKind`]. No platform deps
16
//! (SUPER_PLAN_2 §0.5); the channel mirrors `geolocation.rs` verbatim.
17

            
18
use alloc::vec::Vec;
19

            
20
use azul_core::dom::DomNodeId;
21
use azul_core::events::{
22
    EventData, EventProvider, EventSource as CoreEventSource, EventType, SyntheticEvent,
23
};
24
use azul_core::task::Instant;
25
pub use azul_core::sensors::{SensorKind, SensorReading};
26

            
27
/// Cross-platform sensor state. One per `App` — the OS exposes a single
28
/// per-process sensor subscription, not per-window.
29
#[derive(Debug, Clone, PartialEq, Default)]
30
pub struct SensorManager {
31
    /// Latest accelerometer reading (m/s²), or `None` until a sample arrives.
32
    pub accelerometer: Option<SensorReading>,
33
    /// Latest gyroscope reading (rad/s).
34
    pub gyroscope: Option<SensorReading>,
35
    /// Latest magnetometer reading (µT).
36
    pub magnetometer: Option<SensorReading>,
37
    /// `true` when a reading advanced since the last event-pass drain. Set by
38
    /// [`set_reading`](Self::set_reading), read by the `EventProvider` impl,
39
    /// cleared by [`clear_pending_event`](Self::clear_pending_event).
40
    pub pending_event: bool,
41
}
42

            
43
impl SensorManager {
44
2394
    pub fn new() -> Self {
45
2394
        Self::default()
46
2394
    }
47

            
48
    /// Latest reading for `kind`, or `None` if no backend has delivered one.
49
9
    pub fn reading(&self, kind: SensorKind) -> Option<SensorReading> {
50
9
        match kind {
51
4
            SensorKind::Accelerometer => self.accelerometer,
52
3
            SensorKind::Gyroscope => self.gyroscope,
53
2
            SensorKind::Magnetometer => self.magnetometer,
54
        }
55
9
    }
56

            
57
    /// Apply a reading the backend delivered. Returns `true` if it advanced
58
    /// (bit-pattern different from the previous, so missing-as-`NaN` axes
59
    /// don't make every sample look "changed").
60
112
    pub fn set_reading(&mut self, reading: SensorReading) -> bool {
61
112
        let slot = match reading.kind {
62
40
            SensorKind::Accelerometer => &mut self.accelerometer,
63
71
            SensorKind::Gyroscope => &mut self.gyroscope,
64
1
            SensorKind::Magnetometer => &mut self.magnetometer,
65
        };
66
112
        let changed = match slot {
67
38
            Some(prev) => !reading_bitwise_eq(prev, &reading),
68
74
            None => true,
69
        };
70
112
        *slot = Some(reading);
71
112
        if changed {
72
76
            self.pending_event = true;
73
76
        }
74
112
        changed
75
112
    }
76

            
77
    /// Clear the pending-event flag. The dll calls this after the event pass
78
    /// has collected the `SensorChanged` event (mirrors `clear_changeset`).
79
1
    pub fn clear_pending_event(&mut self) {
80
1
        self.pending_event = false;
81
1
    }
82
}
83

            
84
impl EventProvider for SensorManager {
85
    /// Yield a window-level `SensorChanged` event when a reading advanced
86
    /// since the last drain (target = root; read the value via
87
    /// `CallbackInfo::get_sensor_reading` inside the callback).
88
70
    fn get_pending_events(&self, timestamp: Instant) -> Vec<SyntheticEvent> {
89
70
        if self.pending_event {
90
35
            alloc::vec![SyntheticEvent::new(
91
35
                EventType::SensorChanged,
92
35
                CoreEventSource::User,
93
                DomNodeId::ROOT,
94
35
                timestamp,
95
35
                EventData::None,
96
            )]
97
        } else {
98
35
            Vec::new()
99
        }
100
70
    }
101
}
102

            
103
38
fn reading_bitwise_eq(a: &SensorReading, b: &SensorReading) -> bool {
104
38
    a.kind == b.kind
105
38
        && a.x.to_bits() == b.x.to_bits()
106
36
        && a.y.to_bits() == b.y.to_bits()
107
36
        && a.z.to_bits() == b.z.to_bits()
108
36
        && a.timestamp_ms == b.timestamp_ms
109
38
}
110

            
111
// ────────── Async reading channel (platform backend → manager) ─────────
112
//
113
// CoreMotion / Android `SensorManager` deliver on an arbitrary thread with
114
// no handle to the live `SensorManager` (inside the window's
115
// `LayoutWindow`). The backend parks each reading here; the layout pass
116
// drains it and applies the latest per kind. Pure Rust — no platform
117
// dependency (SUPER_PLAN_2 §0.5). Mirrors the geolocation fix channel.
118

            
119
static PENDING_READINGS: std::sync::Mutex<Vec<SensorReading>> =
120
    std::sync::Mutex::new(Vec::new());
121

            
122
/// Park a sensor reading delivered by a platform backend (in the dll).
123
/// Thread-safe; poison-recovering.
124
38
pub fn push_sensor_reading(reading: SensorReading) {
125
38
    let mut q = PENDING_READINGS.lock().unwrap_or_else(|e| e.into_inner());
126
38
    q.push(reading);
127
38
}
128

            
129
/// Drain every reading parked by [`push_sensor_reading`], in arrival order.
130
/// Called once per layout pass; the caller applies them through
131
/// [`SensorManager::set_reading`] (the last per kind wins).
132
108
pub fn drain_sensor_readings() -> Vec<SensorReading> {
133
108
    let mut q = PENDING_READINGS.lock().unwrap_or_else(|e| e.into_inner());
134
108
    core::mem::take(&mut *q)
135
108
}
136

            
137
#[cfg(test)]
138
mod tests {
139
    use super::*;
140

            
141
8
    fn r(kind: SensorKind, x: f32, y: f32, z: f32) -> SensorReading {
142
8
        SensorReading {
143
8
            kind,
144
8
            x,
145
8
            y,
146
8
            z,
147
8
            timestamp_ms: 0,
148
8
        }
149
8
    }
150

            
151
    #[test]
152
1
    fn manager_defaults_to_no_readings() {
153
1
        let mgr = SensorManager::new();
154
1
        assert_eq!(mgr.reading(SensorKind::Accelerometer), None);
155
1
        assert_eq!(mgr.reading(SensorKind::Gyroscope), None);
156
1
        assert_eq!(mgr.reading(SensorKind::Magnetometer), None);
157
1
    }
158

            
159
    #[test]
160
1
    fn set_reading_routes_by_kind_and_flags_change() {
161
1
        let mut mgr = SensorManager::new();
162
1
        assert!(mgr.set_reading(r(SensorKind::Accelerometer, 0.0, 0.0, 9.81)));
163
        // Only the accelerometer slot is filled.
164
1
        assert!(mgr.reading(SensorKind::Accelerometer).is_some());
165
1
        assert_eq!(mgr.reading(SensorKind::Gyroscope), None);
166
        // Same value again — no change.
167
1
        assert!(!mgr.set_reading(r(SensorKind::Accelerometer, 0.0, 0.0, 9.81)));
168
        // Different value — change.
169
1
        assert!(mgr.set_reading(r(SensorKind::Accelerometer, 1.0, 0.0, 9.81)));
170
        // A different kind fills its own slot.
171
1
        assert!(mgr.set_reading(r(SensorKind::Gyroscope, 0.1, 0.0, 0.0)));
172
1
        assert_eq!(
173
1
            mgr.reading(SensorKind::Gyroscope).map(|r| r.x),
174
            Some(0.1)
175
        );
176
1
    }
177

            
178
    #[test]
179
1
    fn magnitude_of_resting_accelerometer() {
180
1
        let g = r(SensorKind::Accelerometer, 0.0, 0.0, 9.81);
181
1
        assert!((g.magnitude() - 9.81).abs() < 1e-4);
182
1
    }
183

            
184
    #[test]
185
1
    fn readings_round_trip_through_manager() {
186
1
        let _ = drain_sensor_readings();
187

            
188
1
        push_sensor_reading(r(SensorKind::Accelerometer, 1.0, 2.0, 3.0));
189
1
        push_sensor_reading(r(SensorKind::Accelerometer, 4.0, 5.0, 6.0)); // last wins per kind
190
1
        push_sensor_reading(r(SensorKind::Magnetometer, 20.0, 0.0, 40.0));
191
1
        let drained = drain_sensor_readings();
192
1
        assert_eq!(drained.len(), 3, "all parked readings drain in order");
193

            
194
1
        let mut mgr = SensorManager::new();
195
4
        for reading in &drained {
196
3
            mgr.set_reading(*reading);
197
3
        }
198
1
        assert_eq!(
199
1
            mgr.reading(SensorKind::Accelerometer).map(|r| r.x),
200
            Some(4.0),
201
            "the last accelerometer reading wins"
202
        );
203
1
        assert_eq!(
204
1
            mgr.reading(SensorKind::Magnetometer).map(|r| r.z),
205
            Some(40.0)
206
        );
207

            
208
1
        assert!(drain_sensor_readings().is_empty());
209
1
    }
210
}