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