Skip to main content

cranpose_services/
background.rs

1//! Background execution: ask the OS to keep running briefly after the app is
2//! backgrounded, so in-flight work (e.g. draining a recognition queue) can
3//! finish instead of being suspended immediately.
4//!
5//! The platform backend installs an implementation via
6//! [`set_platform_background_activity`] (iOS `beginBackgroundTask`, Android a
7//! foreground service). No default: [`background_activity`] returns `None` where
8//! unsupported, and the app simply runs only while foregrounded.
9
10use std::sync::atomic::{AtomicBool, Ordering};
11use std::sync::Arc;
12use std::sync::Mutex;
13use std::sync::OnceLock;
14
15/// Marks periods of important work so the platform grants background running
16/// time. Implementations are `Send + Sync` (the app toggles activity from its
17/// worker threads).
18pub trait BackgroundActivity: Send + Sync {
19    /// `true` when important work starts, `false` when it finishes. Calls are
20    /// balanced by the app but implementations must tolerate repeats.
21    fn set_active(&self, active: bool);
22}
23
24pub type BackgroundActivityRef = Arc<dyn BackgroundActivity>;
25
26fn slot() -> &'static Mutex<Option<BackgroundActivityRef>> {
27    static SLOT: OnceLock<Mutex<Option<BackgroundActivityRef>>> = OnceLock::new();
28    SLOT.get_or_init(|| Mutex::new(None))
29}
30
31/// Installs the platform background-activity handler, replacing any previous one.
32pub fn set_platform_background_activity(activity: BackgroundActivityRef) {
33    if let Ok(mut s) = slot().lock() {
34        *s = Some(activity);
35    }
36}
37
38/// Removes any registered handler (tests/teardown).
39pub fn clear_platform_background_activity() {
40    if let Ok(mut s) = slot().lock() {
41        *s = None;
42    }
43}
44
45/// The registered background-activity handler, or `None` where unsupported.
46pub fn background_activity() -> Option<BackgroundActivityRef> {
47    slot().lock().ok().and_then(|s| s.clone())
48}
49
50static ACTIVE: AtomicBool = AtomicBool::new(false);
51
52/// Convenience: mark background work active/inactive. The flag is recorded even
53/// where no platform handler is installed, so [`background_active`] answers the
54/// same everywhere.
55pub fn set_background_active(active: bool) {
56    ACTIVE.store(active, Ordering::SeqCst);
57    if let Some(activity) = background_activity() {
58        activity.set_active(active);
59    }
60}
61
62/// `true` between [`set_background_active(true)`](set_background_active) and the
63/// matching `false`.
64///
65/// A platform backend reads this to decide whether the runtime keeps turning
66/// while the app is off screen. Both mobile backends drive composition from the
67/// frame path, and both stop that path when the surface is gone: Android drops
68/// its GPU resources on `TerminateWindow`, iOS stops receiving redraws. Anything
69/// posted to the UI dispatcher then waits until the user comes back, which is
70/// wrong for an app that told the OS it has work to finish.
71pub fn background_active() -> bool {
72    ACTIVE.load(Ordering::SeqCst)
73}
74
75#[cfg(test)]
76mod tests {
77    use super::*;
78    use std::sync::atomic::{AtomicBool, Ordering};
79
80    #[test]
81    fn registration_round_trips() {
82        clear_platform_background_activity();
83        set_background_active(true); // no-op, no panic
84        struct Rec(AtomicBool);
85        impl BackgroundActivity for Rec {
86            fn set_active(&self, active: bool) {
87                self.0.store(active, Ordering::SeqCst);
88            }
89        }
90        let rec = Arc::new(Rec(AtomicBool::new(false)));
91        set_platform_background_activity(rec.clone());
92        set_background_active(true);
93        assert!(rec.0.load(Ordering::SeqCst));
94        assert!(background_active());
95        set_background_active(false);
96        assert!(!background_active());
97        clear_platform_background_activity();
98        set_background_active(true);
99        assert!(background_active());
100        set_background_active(false);
101        assert!(!background_active());
102    }
103}