cranpose_services/
power.rs1use std::sync::{
11 Arc, Mutex, OnceLock,
12 atomic::{AtomicU64, Ordering},
13};
14
15use cranpose_core::{State, rememberEventStream};
16
17use crate::registry::ServiceRegistry;
18
19#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
21pub enum ThermalState {
22 #[default]
24 Normal,
25 Light,
27 Moderate,
29 Severe,
31 Critical,
33 Emergency,
35 Shutdown,
37}
38
39impl ThermalState {
40 pub fn should_pause_work(self) -> bool {
42 self >= ThermalState::Severe
43 }
44}
45
46#[derive(Clone, Copy, Debug, PartialEq, Eq)]
48pub struct BatteryStatus {
49 pub percent: u8,
51 pub charging: bool,
53}
54
55#[derive(Clone, Copy, Debug, PartialEq, Eq)]
61pub enum PowerReading<T> {
62 Known(T),
64 Unsupported,
66 Unknown,
68}
69
70impl<T> PowerReading<T> {
71 pub fn known(self) -> Option<T> {
73 match self {
74 PowerReading::Known(value) => Some(value),
75 _ => None,
76 }
77 }
78
79 pub fn is_supported(&self) -> bool {
81 !matches!(self, PowerReading::Unsupported)
82 }
83
84 pub fn unwrap_or(self, fallback: T) -> T {
86 self.known().unwrap_or(fallback)
87 }
88}
89
90#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
92pub struct PowerCapabilities {
93 pub thermal: bool,
95 pub battery: bool,
97 pub background_restriction: bool,
99}
100
101#[derive(Clone, Copy, Debug, PartialEq, Eq)]
103pub struct PowerState {
104 pub thermal: PowerReading<ThermalState>,
106 pub battery: PowerReading<BatteryStatus>,
108 pub unrestricted_background_work: PowerReading<bool>,
110}
111
112impl PowerState {
113 pub const fn unsupported() -> Self {
115 Self {
116 thermal: PowerReading::Unsupported,
117 battery: PowerReading::Unsupported,
118 unrestricted_background_work: PowerReading::Unsupported,
119 }
120 }
121
122 pub fn should_pause_work(&self) -> bool {
125 matches!(self.thermal, PowerReading::Known(level) if level.should_pause_work())
126 }
127}
128
129pub trait PowerMonitor: Send + Sync {
131 fn capabilities(&self) -> PowerCapabilities {
133 PowerCapabilities::default()
134 }
135
136 fn thermal_state(&self) -> PowerReading<ThermalState> {
138 PowerReading::Unsupported
139 }
140
141 fn battery_status(&self) -> PowerReading<BatteryStatus> {
143 PowerReading::Unsupported
144 }
145
146 fn unrestricted_background_work(&self) -> PowerReading<bool> {
148 PowerReading::Unsupported
149 }
150
151 fn request_unrestricted_background_work(&self) {}
153}
154
155pub type PowerMonitorRef = Arc<dyn PowerMonitor>;
157
158struct DefaultPowerMonitor;
159impl PowerMonitor for DefaultPowerMonitor {}
160
161static PLATFORM_POWER_MONITOR: ServiceRegistry<dyn PowerMonitor> = ServiceRegistry::new();
162
163pub fn set_platform_power_monitor(monitor: PowerMonitorRef) {
165 PLATFORM_POWER_MONITOR.set(monitor);
166 publish_power_state(power_state());
167}
168
169pub fn clear_platform_power_monitor() {
171 PLATFORM_POWER_MONITOR.clear();
172 if let Ok(mut observers) = power_observers().lock() {
173 observers.clear();
174 }
175}
176
177pub fn power_monitor() -> PowerMonitorRef {
179 PLATFORM_POWER_MONITOR
180 .get()
181 .unwrap_or_else(|| Arc::new(DefaultPowerMonitor))
182}
183
184pub fn power_state() -> PowerState {
186 let monitor = power_monitor();
187 PowerState {
188 thermal: monitor.thermal_state(),
189 battery: monitor.battery_status(),
190 unrestricted_background_work: monitor.unrestricted_background_work(),
191 }
192}
193
194pub fn power_capabilities() -> PowerCapabilities {
196 power_monitor().capabilities()
197}
198
199type PowerObserver = Arc<dyn Fn(PowerState) + Send + Sync>;
200
201fn power_observers() -> &'static Mutex<Vec<(u64, PowerObserver)>> {
202 static SLOT: OnceLock<Mutex<Vec<(u64, PowerObserver)>>> = OnceLock::new();
203 SLOT.get_or_init(|| Mutex::new(Vec::new()))
204}
205
206static NEXT_OBSERVER: AtomicU64 = AtomicU64::new(1);
207
208pub struct PowerObserverRegistration {
210 id: u64,
211}
212
213impl Drop for PowerObserverRegistration {
214 fn drop(&mut self) {
215 if let Ok(mut observers) = power_observers().lock() {
216 observers.retain(|(id, _)| *id != self.id);
217 }
218 }
219}
220
221pub fn observe_power_state(
224 observer: impl Fn(PowerState) + Send + Sync + 'static,
225) -> PowerObserverRegistration {
226 let id = NEXT_OBSERVER.fetch_add(1, Ordering::Relaxed);
227 if let Ok(mut observers) = power_observers().lock() {
228 observers.push((id, Arc::new(observer)));
229 }
230 PowerObserverRegistration { id }
231}
232
233pub fn publish_power_state(state: PowerState) {
236 let observers = power_observers()
237 .lock()
238 .map(|observers| {
239 observers
240 .iter()
241 .map(|(_, observer)| Arc::clone(observer))
242 .collect::<Vec<_>>()
243 })
244 .unwrap_or_default();
245 for observer in observers {
246 observer(state);
247 }
248}
249
250#[expect(non_snake_case)]
253#[track_caller]
254pub fn rememberPowerState() -> State<PowerState> {
255 let updates = rememberEventStream((), |sender| {
256 observe_power_state(move |state| sender.send(state))
257 });
258 cranpose_core::collectAsState(updates, (), power_state())
259}
260
261#[cfg(test)]
262#[path = "tests/power_tests.rs"]
263mod tests;