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 {}
92
93static PLATFORM_HOST_SURFACE: ServiceRegistry<dyn HostSurface> = ServiceRegistry::new();
94
95pub fn set_platform_host_surface(surface: HostSurfaceRef) {
97 PLATFORM_HOST_SURFACE.set(surface);
98}
99
100pub fn clear_platform_host_surface() {
102 PLATFORM_HOST_SURFACE.clear();
103 if let Ok(mut observers) = observers().lock() {
104 observers.clear();
105 }
106 if let Ok(mut last) = last_published().lock() {
107 *last = HostSurfaceSize::default();
108 }
109}
110
111pub fn host_surface_can_resize() -> bool {
113 host_surface().can_resize()
114}
115
116pub fn host_surface() -> HostSurfaceRef {
118 PLATFORM_HOST_SURFACE
119 .get()
120 .unwrap_or_else(|| Arc::new(NoHostSurface))
121}
122
123pub fn host_surface_size() -> HostSurfaceSize {
132 last_published()
133 .lock()
134 .map(|size| *size)
135 .unwrap_or_default()
136}
137
138fn last_published() -> &'static Mutex<HostSurfaceSize> {
139 static SLOT: OnceLock<Mutex<HostSurfaceSize>> = OnceLock::new();
140 SLOT.get_or_init(|| Mutex::new(HostSurfaceSize::default()))
141}
142
143pub fn request_host_surface_size(width: f32, height: f32) -> Result<(), ResizeRefused> {
145 host_surface().request_size(width, height)
146}
147
148type Observer = Arc<dyn Fn(HostSurfaceSize) + Send + Sync>;
149
150fn observers() -> &'static Mutex<Vec<(u64, Observer)>> {
151 static SLOT: OnceLock<Mutex<Vec<(u64, Observer)>>> = OnceLock::new();
152 SLOT.get_or_init(|| Mutex::new(Vec::new()))
153}
154
155static NEXT_OBSERVER: AtomicU64 = AtomicU64::new(1);
156
157pub struct HostSurfaceObserver {
159 id: u64,
160}
161
162impl Drop for HostSurfaceObserver {
163 fn drop(&mut self) {
164 if let Ok(mut observers) = observers().lock() {
165 observers.retain(|(id, _)| *id != self.id);
166 }
167 }
168}
169
170pub fn observe_host_surface_size(
173 observer: impl Fn(HostSurfaceSize) + Send + Sync + 'static,
174) -> HostSurfaceObserver {
175 let id = NEXT_OBSERVER.fetch_add(1, Ordering::Relaxed);
176 if let Ok(mut observers) = observers().lock() {
177 observers.push((id, Arc::new(observer)));
178 }
179 HostSurfaceObserver { id }
180}
181
182pub fn publish_host_surface_size(size: HostSurfaceSize) {
185 if let Ok(mut last) = last_published().lock() {
186 if *last == size {
187 return;
188 }
189 *last = size;
190 }
191 let observers = observers()
192 .lock()
193 .map(|observers| {
194 observers
195 .iter()
196 .map(|(_, observer)| Arc::clone(observer))
197 .collect::<Vec<_>>()
198 })
199 .unwrap_or_default();
200 for observer in observers {
201 observer(size);
202 }
203}
204
205#[allow(non_snake_case)]
208#[track_caller]
209pub fn rememberHostSurfaceSize() -> State<HostSurfaceSize> {
210 let updates = rememberEventStream((), |sender| {
211 observe_host_surface_size(move |size| sender.send(size))
212 });
213 cranpose_core::collectAsState(updates, (), host_surface_size())
214}
215
216#[cfg(test)]
217mod tests {
218 use super::*;
219
220 struct FixedSurface;
221
222 impl HostSurface for FixedSurface {}
223
224 struct ResizableSurface {
225 requested: Mutex<Option<(f32, f32)>>,
226 }
227
228 impl HostSurface for ResizableSurface {
229 fn can_resize(&self) -> bool {
230 true
231 }
232
233 fn request_size(&self, width: f32, height: f32) -> Result<(), ResizeRefused> {
234 if !(width.is_finite() && height.is_finite()) || width <= 0.0 || height <= 0.0 {
235 return Err(ResizeRefused::Rejected);
236 }
237 *self
238 .requested
239 .lock()
240 .unwrap_or_else(|error| error.into_inner()) = Some((width, height));
241 Ok(())
242 }
243 }
244
245 #[test]
246 fn a_host_that_has_not_drawn_yet_reports_an_empty_surface() {
247 let _guard = crate::registry::test_service_guard();
248 clear_platform_host_surface();
249 let size = host_surface_size();
250 assert_eq!(size, HostSurfaceSize::default());
251 assert_eq!(size.scale, 1.0, "a scale of zero would divide by zero");
252 assert!(!host_surface_can_resize());
253 assert_eq!(
254 request_host_surface_size(320.0, 200.0),
255 Err(ResizeRefused::Unsupported)
256 );
257 }
258
259 #[test]
260 fn the_surface_size_is_whatever_the_host_last_published() {
261 let _guard = crate::registry::test_service_guard();
262 clear_platform_host_surface();
263 publish_host_surface_size(HostSurfaceSize {
264 width: 640.0,
265 height: 480.0,
266 scale: 2.0,
267 });
268 let size = host_surface_size();
269 assert_eq!(size.width, 640.0);
270 assert_eq!(size.physical(), (1280, 960));
271 clear_platform_host_surface();
272 }
273
274 #[test]
275 fn a_host_that_owns_its_window_refuses_resize_requests() {
276 let _guard = crate::registry::test_service_guard();
277 set_platform_host_surface(Arc::new(FixedSurface));
278 assert!(!host_surface_can_resize());
279 assert_eq!(
280 request_host_surface_size(320.0, 200.0),
281 Err(ResizeRefused::Unsupported)
282 );
283 clear_platform_host_surface();
284 }
285
286 #[test]
287 fn a_resizable_surface_takes_the_request_and_refuses_nonsense() {
288 let _guard = crate::registry::test_service_guard();
289 let surface = Arc::new(ResizableSurface {
290 requested: Mutex::new(None),
291 });
292 set_platform_host_surface(surface.clone());
293 assert!(host_surface_can_resize());
294 assert_eq!(request_host_surface_size(275.0, 116.0), Ok(()));
295 assert_eq!(
296 *surface
297 .requested
298 .lock()
299 .unwrap_or_else(|error| error.into_inner()),
300 Some((275.0, 116.0))
301 );
302 assert_eq!(
303 request_host_surface_size(0.0, 116.0),
304 Err(ResizeRefused::Rejected)
305 );
306 assert_eq!(
307 request_host_surface_size(f32::NAN, 116.0),
308 Err(ResizeRefused::Rejected)
309 );
310 clear_platform_host_surface();
311 }
312
313 #[test]
314 fn observers_see_published_sizes_and_stop_when_dropped() {
315 let _guard = crate::registry::test_service_guard();
316 clear_platform_host_surface();
317 let seen = Arc::new(Mutex::new(Vec::new()));
318 let recorder = Arc::clone(&seen);
319 let registration = observe_host_surface_size(move |size| {
320 recorder
321 .lock()
322 .unwrap_or_else(|error| error.into_inner())
323 .push(size)
324 });
325 let first = HostSurfaceSize {
326 width: 100.0,
327 height: 50.0,
328 scale: 1.0,
329 };
330 publish_host_surface_size(first);
331 publish_host_surface_size(first);
332 assert_eq!(
333 seen.lock().unwrap_or_else(|e| e.into_inner()).as_slice(),
334 [first]
335 );
336 drop(registration);
337 publish_host_surface_size(HostSurfaceSize {
338 width: 200.0,
339 ..first
340 });
341 assert_eq!(seen.lock().unwrap_or_else(|e| e.into_inner()).len(), 1);
342 clear_platform_host_surface();
343 }
344}