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