use alloc::collections::btree_map::BTreeMap;
use alloc::vec::Vec;
pub use azul_core::geolocation::{GeolocationProbeConfig, LocationFix};
#[derive(Debug, Clone, Copy, PartialEq)]
#[repr(C, u8)]
pub enum GeolocationDiffEvent {
Subscribe { config: GeolocationProbeConfig },
Release,
Reconfigure { config: GeolocationProbeConfig },
}
#[derive(Debug, Clone, PartialEq, Default)]
pub struct GeolocationManager {
pub latest_fix: Option<LocationFix>,
pub active_config: Option<GeolocationProbeConfig>,
pending_events: Vec<GeolocationDiffEvent>,
refcount: u32,
}
impl GeolocationManager {
pub fn new() -> Self {
Self::default()
}
pub fn latest_fix(&self) -> Option<LocationFix> {
self.latest_fix
}
pub fn refcount(&self) -> u32 {
self.refcount
}
pub fn set_latest_fix(&mut self, fix: LocationFix) -> bool {
let changed = match self.latest_fix {
Some(prev) => !Self::location_fix_bitwise_eq(&prev, &fix),
None => true,
};
self.latest_fix = Some(fix);
changed
}
fn location_fix_bitwise_eq(a: &LocationFix, b: &LocationFix) -> bool {
a.latitude_deg.to_bits() == b.latitude_deg.to_bits()
&& a.longitude_deg.to_bits() == b.longitude_deg.to_bits()
&& a.accuracy_m.to_bits() == b.accuracy_m.to_bits()
&& a.altitude_m.to_bits() == b.altitude_m.to_bits()
&& a.altitude_accuracy_m.to_bits() == b.altitude_accuracy_m.to_bits()
&& a.heading_deg.to_bits() == b.heading_deg.to_bits()
&& a.speed_mps.to_bits() == b.speed_mps.to_bits()
&& a.timestamp_ms == b.timestamp_ms
}
pub fn take_pending_events(&mut self) -> Vec<GeolocationDiffEvent> {
core::mem::take(&mut self.pending_events)
}
pub fn diff_layout<F>(&mut self, mut for_each_probe: F)
where
F: FnMut(&mut dyn FnMut(GeolocationProbeConfig)),
{
let mut new_count: u32 = 0;
let mut next_config: Option<GeolocationProbeConfig> = None;
for_each_probe(&mut |cfg| {
new_count += 1;
if next_config.is_none() {
next_config = Some(cfg);
}
});
let old_count = self.refcount;
self.refcount = new_count;
match (old_count, new_count) {
(0, n) if n > 0 => {
let config = next_config.unwrap_or_default();
self.active_config = Some(config);
self.pending_events
.push(GeolocationDiffEvent::Subscribe { config });
}
(m, 0) if m > 0 => {
self.active_config = None;
self.latest_fix = None;
self.pending_events.push(GeolocationDiffEvent::Release);
}
(m, n) if m > 0 && n > 0 => {
let new_config = next_config.unwrap_or_default();
if Some(new_config) != self.active_config {
self.active_config = Some(new_config);
self.pending_events
.push(GeolocationDiffEvent::Reconfigure { config: new_config });
}
}
_ => {
}
}
}
}
static PENDING_FIXES: std::sync::Mutex<Vec<LocationFix>> =
std::sync::Mutex::new(Vec::new());
pub fn push_location_fix(fix: LocationFix) {
let mut q = PENDING_FIXES.lock().unwrap_or_else(|e| e.into_inner());
q.push(fix);
}
pub fn drain_location_fixes() -> Vec<LocationFix> {
let mut q = PENDING_FIXES.lock().unwrap_or_else(|e| e.into_inner());
core::mem::take(&mut *q)
}
#[cfg(test)]
mod tests {
use super::*;
fn cfg() -> GeolocationProbeConfig {
GeolocationProbeConfig::default()
}
fn high_accuracy_cfg() -> GeolocationProbeConfig {
GeolocationProbeConfig {
high_accuracy: true,
..GeolocationProbeConfig::default()
}
}
fn fix(lat: f64, lon: f64) -> LocationFix {
LocationFix {
latitude_deg: lat,
longitude_deg: lon,
accuracy_m: 10.0,
altitude_m: f32::NAN,
altitude_accuracy_m: f32::NAN,
heading_deg: f32::NAN,
speed_mps: f32::NAN,
timestamp_ms: 0,
}
}
#[test]
fn first_probe_emits_subscribe_with_config() {
let mut mgr = GeolocationManager::new();
mgr.diff_layout(|emit| emit(cfg()));
assert_eq!(mgr.refcount(), 1);
let events = mgr.take_pending_events();
assert_eq!(events.len(), 1);
assert!(matches!(events[0], GeolocationDiffEvent::Subscribe { .. }));
}
#[test]
fn last_probe_drop_emits_release_and_clears_fix() {
let mut mgr = GeolocationManager::new();
mgr.diff_layout(|emit| emit(cfg()));
mgr.set_latest_fix(fix(37.0, -122.0));
let _ = mgr.take_pending_events();
mgr.diff_layout(|_emit| {});
assert_eq!(mgr.refcount(), 0);
assert_eq!(mgr.latest_fix(), None);
let events = mgr.take_pending_events();
assert_eq!(events.len(), 1);
assert!(matches!(events[0], GeolocationDiffEvent::Release));
}
#[test]
fn config_drift_emits_reconfigure() {
let mut mgr = GeolocationManager::new();
mgr.diff_layout(|emit| emit(cfg()));
let _ = mgr.take_pending_events();
mgr.diff_layout(|emit| emit(high_accuracy_cfg()));
let events = mgr.take_pending_events();
assert_eq!(events.len(), 1);
let ev = &events[0];
match ev {
GeolocationDiffEvent::Reconfigure { config } => {
assert!(config.high_accuracy);
}
_ => panic!("expected Reconfigure, got {:?}", ev),
}
}
#[test]
fn stable_config_does_not_re_emit() {
let mut mgr = GeolocationManager::new();
mgr.diff_layout(|emit| emit(cfg()));
let _ = mgr.take_pending_events();
mgr.diff_layout(|emit| emit(cfg()));
assert!(mgr.take_pending_events().is_empty());
}
#[test]
fn set_latest_fix_returns_change_flag() {
let mut mgr = GeolocationManager::new();
assert!(mgr.set_latest_fix(fix(37.0, -122.0)));
assert!(!mgr.set_latest_fix(fix(37.0, -122.0)));
assert!(mgr.set_latest_fix(fix(37.7749, -122.4194)));
}
#[test]
fn missing_fields_decode_to_none() {
let f = fix(0.0, 0.0);
assert_eq!(f.altitude(), None);
assert_eq!(f.heading(), None);
assert_eq!(f.speed(), None);
}
#[test]
fn async_fixes_round_trip_through_manager() {
let _ = drain_location_fixes();
push_location_fix(fix(37.0, -122.0));
push_location_fix(fix(48.8566, 2.3522)); let drained = drain_location_fixes();
assert_eq!(drained.len(), 2, "both parked fixes drain in order");
assert_eq!(drained[0].latitude_deg, 37.0);
assert_eq!(drained[1].latitude_deg, 48.8566);
let mut mgr = GeolocationManager::new();
for f in &drained {
mgr.set_latest_fix(*f);
}
let got = mgr.latest_fix().expect("a fix was applied");
assert_eq!(got.latitude_deg, 48.8566, "the last applied fix wins");
assert!(drain_location_fixes().is_empty());
}
}