cranpose_services/
memory_pressure.rs1use std::sync::{Arc, Mutex, OnceLock, atomic::Ordering};
10
11use cranpose_core::{EventStream, rememberEventStream};
12use portable_atomic::AtomicU64;
13
14#[derive(Clone, Copy, Debug, PartialEq, Eq)]
16pub enum MemoryPressure {
17 UiHidden,
20 Low,
22 Critical,
24}
25
26impl MemoryPressure {
27 pub fn from_android_trim_level(level: i32) -> Self {
29 match level {
30 20 => Self::UiHidden,
31 level if level >= 60 || level == 15 => Self::Critical,
32 _ => Self::Low,
33 }
34 }
35}
36
37type Observer = Arc<dyn Fn(MemoryPressure) + Send + Sync>;
38
39struct Registry {
40 observers: Vec<(u64, Observer)>,
41}
42
43impl Registry {
44 fn new() -> Self {
45 Self {
46 observers: Vec::new(),
47 }
48 }
49
50 fn observe(&mut self, id: u64, observer: Observer) {
51 self.observers.push((id, observer));
52 }
53
54 fn publish(&self) -> Vec<Observer> {
55 self.observers
56 .iter()
57 .map(|(_, observer)| Arc::clone(observer))
58 .collect()
59 }
60
61 fn remove_observer(&mut self, id: u64) {
62 self.observers.retain(|(existing, _)| *existing != id);
63 }
64}
65
66fn registry() -> &'static Mutex<Registry> {
67 static REGISTRY: OnceLock<Mutex<Registry>> = OnceLock::new();
68 REGISTRY.get_or_init(|| Mutex::new(Registry::new()))
69}
70
71static NEXT_ID: AtomicU64 = AtomicU64::new(1);
72
73pub struct MemoryPressureObserver {
75 id: u64,
76}
77
78impl Drop for MemoryPressureObserver {
79 fn drop(&mut self) {
80 if let Ok(mut registry) = registry().lock() {
81 registry.remove_observer(self.id);
82 }
83 }
84}
85
86pub fn observe_memory_pressure(
91 observer: impl Fn(MemoryPressure) + Send + Sync + 'static,
92) -> MemoryPressureObserver {
93 let id = NEXT_ID.fetch_add(1, Ordering::Relaxed);
94 if let Ok(mut registry) = registry().lock() {
95 registry.observe(id, Arc::new(observer));
96 }
97 MemoryPressureObserver { id }
98}
99
100pub fn publish_memory_pressure(pressure: MemoryPressure) {
103 let observers = {
104 let Ok(registry) = registry().lock() else {
105 return;
106 };
107 registry.publish()
108 };
109 for observer in observers {
110 observer(pressure);
111 }
112}
113
114#[expect(non_snake_case)]
130#[track_caller]
131pub fn rememberMemoryPressure() -> EventStream<MemoryPressure> {
132 rememberEventStream((), |sender| {
133 observe_memory_pressure(move |pressure| sender.send(pressure))
134 })
135}
136
137#[cfg(test)]
138#[path = "tests/memory_pressure_tests.rs"]
139mod tests;