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