cranpose_services/
host_surface.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, PartialEq)]
22pub struct HostSurfaceSize {
23 pub width: f32,
25 pub height: f32,
27 pub scale: f32,
29}
30
31impl Default for HostSurfaceSize {
32 fn default() -> Self {
33 Self {
34 width: 0.0,
35 height: 0.0,
36 scale: 1.0,
37 }
38 }
39}
40
41impl HostSurfaceSize {
42 pub fn physical(&self) -> (u32, u32) {
44 (
45 (self.width * self.scale).round().max(0.0) as u32,
46 (self.height * self.scale).round().max(0.0) as u32,
47 )
48 }
49}
50
51#[derive(Clone, Copy, Debug, PartialEq, Eq, thiserror::Error)]
53pub enum ResizeRefused {
54 #[error("this host does not accept surface resize requests")]
57 Unsupported,
58 #[error("the host refused the requested surface size")]
60 Rejected,
61}
62
63pub trait HostSurface: Send + Sync {
70 fn can_resize(&self) -> bool {
72 false
73 }
74
75 fn request_size(&self, width: f32, height: f32) -> Result<(), ResizeRefused> {
81 let _ = (width, height);
82 Err(ResizeRefused::Unsupported)
83 }
84}
85
86pub type HostSurfaceRef = Arc<dyn HostSurface>;
88
89struct NoHostSurface;
90
91impl HostSurface for NoHostSurface {}
94
95static PLATFORM_HOST_SURFACE: ServiceRegistry<dyn HostSurface> = ServiceRegistry::new();
96
97pub fn set_platform_host_surface(surface: HostSurfaceRef) {
99 PLATFORM_HOST_SURFACE.set(surface);
100}
101
102pub fn clear_platform_host_surface() {
104 PLATFORM_HOST_SURFACE.clear();
105 if let Ok(mut observers) = observers().lock() {
106 observers.clear();
107 }
108 if let Ok(mut last) = last_published().lock() {
109 *last = HostSurfaceSize::default();
110 }
111}
112
113pub fn host_surface_can_resize() -> bool {
115 host_surface().can_resize()
116}
117
118pub fn host_surface() -> HostSurfaceRef {
120 PLATFORM_HOST_SURFACE
121 .get()
122 .unwrap_or_else(|| Arc::new(NoHostSurface))
123}
124
125pub fn host_surface_size() -> HostSurfaceSize {
134 last_published()
135 .lock()
136 .map(|size| *size)
137 .unwrap_or_default()
138}
139
140fn last_published() -> &'static Mutex<HostSurfaceSize> {
141 static SLOT: OnceLock<Mutex<HostSurfaceSize>> = OnceLock::new();
142 SLOT.get_or_init(|| Mutex::new(HostSurfaceSize::default()))
143}
144
145pub fn request_host_surface_size(width: f32, height: f32) -> Result<(), ResizeRefused> {
147 host_surface().request_size(width, height)
148}
149
150type Observer = Arc<dyn Fn(HostSurfaceSize) + Send + Sync>;
151
152fn observers() -> &'static Mutex<Vec<(u64, Observer)>> {
153 static SLOT: OnceLock<Mutex<Vec<(u64, Observer)>>> = OnceLock::new();
154 SLOT.get_or_init(|| Mutex::new(Vec::new()))
155}
156
157static NEXT_OBSERVER: AtomicU64 = AtomicU64::new(1);
158
159pub struct HostSurfaceObserver {
161 id: u64,
162}
163
164impl Drop for HostSurfaceObserver {
165 fn drop(&mut self) {
166 if let Ok(mut observers) = observers().lock() {
167 observers.retain(|(id, _)| *id != self.id);
168 }
169 }
170}
171
172pub fn observe_host_surface_size(
175 observer: impl Fn(HostSurfaceSize) + Send + Sync + 'static,
176) -> HostSurfaceObserver {
177 let id = NEXT_OBSERVER.fetch_add(1, Ordering::Relaxed);
178 if let Ok(mut observers) = observers().lock() {
179 observers.push((id, Arc::new(observer)));
180 }
181 HostSurfaceObserver { id }
182}
183
184pub fn publish_host_surface_size(size: HostSurfaceSize) {
187 if let Ok(mut last) = last_published().lock() {
188 if *last == size {
189 return;
190 }
191 *last = size;
192 }
193 let observers = observers()
194 .lock()
195 .map(|observers| {
196 observers
197 .iter()
198 .map(|(_, observer)| Arc::clone(observer))
199 .collect::<Vec<_>>()
200 })
201 .unwrap_or_default();
202 for observer in observers {
203 observer(size);
204 }
205}
206
207#[allow(non_snake_case)]
210#[track_caller]
211pub fn rememberHostSurfaceSize() -> State<HostSurfaceSize> {
212 let updates = rememberEventStream((), |sender| {
213 observe_host_surface_size(move |size| sender.send(size))
214 });
215 cranpose_core::collectAsState(updates, (), host_surface_size())
216}
217
218#[cfg(test)]
219mod tests {
220 use super::*;
221
222 struct FixedSurface;
223
224 impl HostSurface for FixedSurface {}
226
227 struct ResizableSurface {
228 requested: Mutex<Option<(f32, f32)>>,
229 }
230
231 impl HostSurface for ResizableSurface {
232 fn can_resize(&self) -> bool {
233 true
234 }
235
236 fn request_size(&self, width: f32, height: f32) -> Result<(), ResizeRefused> {
237 if !(width.is_finite() && height.is_finite()) || width <= 0.0 || height <= 0.0 {
238 return Err(ResizeRefused::Rejected);
239 }
240 *self
241 .requested
242 .lock()
243 .unwrap_or_else(|error| error.into_inner()) = Some((width, height));
244 Ok(())
245 }
246 }
247
248 #[test]
249 fn a_host_that_has_not_drawn_yet_reports_an_empty_surface() {
250 let _guard = crate::registry::test_service_guard();
251 clear_platform_host_surface();
252 let size = host_surface_size();
253 assert_eq!(size, HostSurfaceSize::default());
254 assert_eq!(size.scale, 1.0, "a scale of zero would divide by zero");
255 assert!(!host_surface_can_resize());
256 assert_eq!(
257 request_host_surface_size(320.0, 200.0),
258 Err(ResizeRefused::Unsupported)
259 );
260 }
261
262 #[test]
263 fn the_surface_size_is_whatever_the_host_last_published() {
264 let _guard = crate::registry::test_service_guard();
265 clear_platform_host_surface();
266 publish_host_surface_size(HostSurfaceSize {
267 width: 640.0,
268 height: 480.0,
269 scale: 2.0,
270 });
271 let size = host_surface_size();
272 assert_eq!(size.width, 640.0);
273 assert_eq!(size.physical(), (1280, 960));
274 clear_platform_host_surface();
275 }
276
277 #[test]
278 fn a_host_that_owns_its_window_refuses_resize_requests() {
279 let _guard = crate::registry::test_service_guard();
280 set_platform_host_surface(Arc::new(FixedSurface));
281 assert!(!host_surface_can_resize());
282 assert_eq!(
283 request_host_surface_size(320.0, 200.0),
284 Err(ResizeRefused::Unsupported)
285 );
286 clear_platform_host_surface();
287 }
288
289 #[test]
290 fn a_resizable_surface_takes_the_request_and_refuses_nonsense() {
291 let _guard = crate::registry::test_service_guard();
292 let surface = Arc::new(ResizableSurface {
293 requested: Mutex::new(None),
294 });
295 set_platform_host_surface(surface.clone());
296 assert!(host_surface_can_resize());
297 assert_eq!(request_host_surface_size(275.0, 116.0), Ok(()));
298 assert_eq!(
299 *surface
300 .requested
301 .lock()
302 .unwrap_or_else(|error| error.into_inner()),
303 Some((275.0, 116.0))
304 );
305 assert_eq!(
306 request_host_surface_size(0.0, 116.0),
307 Err(ResizeRefused::Rejected)
308 );
309 assert_eq!(
310 request_host_surface_size(f32::NAN, 116.0),
311 Err(ResizeRefused::Rejected)
312 );
313 clear_platform_host_surface();
314 }
315
316 #[test]
317 fn observers_see_published_sizes_and_stop_when_dropped() {
318 let _guard = crate::registry::test_service_guard();
319 clear_platform_host_surface();
320 let seen = Arc::new(Mutex::new(Vec::new()));
321 let recorder = Arc::clone(&seen);
322 let registration = observe_host_surface_size(move |size| {
323 recorder
324 .lock()
325 .unwrap_or_else(|error| error.into_inner())
326 .push(size)
327 });
328 let first = HostSurfaceSize {
329 width: 100.0,
330 height: 50.0,
331 scale: 1.0,
332 };
333 publish_host_surface_size(first);
334 publish_host_surface_size(first);
336 assert_eq!(
337 seen.lock().unwrap_or_else(|e| e.into_inner()).as_slice(),
338 [first]
339 );
340 drop(registration);
341 publish_host_surface_size(HostSurfaceSize {
342 width: 200.0,
343 ..first
344 });
345 assert_eq!(seen.lock().unwrap_or_else(|e| e.into_inner()).len(), 1);
346 clear_platform_host_surface();
347 }
348}