cranpose_services/
host_surface.rs1use std::sync::{
11 atomic::{AtomicU64, Ordering},
12 Arc, Mutex, OnceLock,
13};
14
15use cranpose_core::{rememberEventStream, State};
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)]
210pub fn rememberHostSurfaceSize() -> State<HostSurfaceSize> {
211 let updates = rememberEventStream((), |sender| {
212 observe_host_surface_size(move |size| sender.send(size))
213 });
214 cranpose_core::collectAsState(updates, (), host_surface_size())
215}
216
217#[cfg(test)]
218mod tests {
219 use super::*;
220
221 struct FixedSurface;
222
223 impl HostSurface for FixedSurface {}
225
226 struct ResizableSurface {
227 requested: Mutex<Option<(f32, f32)>>,
228 }
229
230 impl HostSurface for ResizableSurface {
231 fn can_resize(&self) -> bool {
232 true
233 }
234
235 fn request_size(&self, width: f32, height: f32) -> Result<(), ResizeRefused> {
236 if !(width.is_finite() && height.is_finite()) || width <= 0.0 || height <= 0.0 {
237 return Err(ResizeRefused::Rejected);
238 }
239 *self
240 .requested
241 .lock()
242 .unwrap_or_else(|error| error.into_inner()) = Some((width, height));
243 Ok(())
244 }
245 }
246
247 #[test]
248 fn a_host_that_has_not_drawn_yet_reports_an_empty_surface() {
249 let _guard = crate::registry::test_service_guard();
250 clear_platform_host_surface();
251 let size = host_surface_size();
252 assert_eq!(size, HostSurfaceSize::default());
253 assert_eq!(size.scale, 1.0, "a scale of zero would divide by zero");
254 assert!(!host_surface_can_resize());
255 assert_eq!(
256 request_host_surface_size(320.0, 200.0),
257 Err(ResizeRefused::Unsupported)
258 );
259 }
260
261 #[test]
262 fn the_surface_size_is_whatever_the_host_last_published() {
263 let _guard = crate::registry::test_service_guard();
264 clear_platform_host_surface();
265 publish_host_surface_size(HostSurfaceSize {
266 width: 640.0,
267 height: 480.0,
268 scale: 2.0,
269 });
270 let size = host_surface_size();
271 assert_eq!(size.width, 640.0);
272 assert_eq!(size.physical(), (1280, 960));
273 clear_platform_host_surface();
274 }
275
276 #[test]
277 fn a_host_that_owns_its_window_refuses_resize_requests() {
278 let _guard = crate::registry::test_service_guard();
279 set_platform_host_surface(Arc::new(FixedSurface));
280 assert!(!host_surface_can_resize());
281 assert_eq!(
282 request_host_surface_size(320.0, 200.0),
283 Err(ResizeRefused::Unsupported)
284 );
285 clear_platform_host_surface();
286 }
287
288 #[test]
289 fn a_resizable_surface_takes_the_request_and_refuses_nonsense() {
290 let _guard = crate::registry::test_service_guard();
291 let surface = Arc::new(ResizableSurface {
292 requested: Mutex::new(None),
293 });
294 set_platform_host_surface(surface.clone());
295 assert!(host_surface_can_resize());
296 assert_eq!(request_host_surface_size(275.0, 116.0), Ok(()));
297 assert_eq!(
298 *surface
299 .requested
300 .lock()
301 .unwrap_or_else(|error| error.into_inner()),
302 Some((275.0, 116.0))
303 );
304 assert_eq!(
305 request_host_surface_size(0.0, 116.0),
306 Err(ResizeRefused::Rejected)
307 );
308 assert_eq!(
309 request_host_surface_size(f32::NAN, 116.0),
310 Err(ResizeRefused::Rejected)
311 );
312 clear_platform_host_surface();
313 }
314
315 #[test]
316 fn observers_see_published_sizes_and_stop_when_dropped() {
317 let _guard = crate::registry::test_service_guard();
318 clear_platform_host_surface();
319 let seen = Arc::new(Mutex::new(Vec::new()));
320 let recorder = Arc::clone(&seen);
321 let registration = observe_host_surface_size(move |size| {
322 recorder
323 .lock()
324 .unwrap_or_else(|error| error.into_inner())
325 .push(size)
326 });
327 let first = HostSurfaceSize {
328 width: 100.0,
329 height: 50.0,
330 scale: 1.0,
331 };
332 publish_host_surface_size(first);
333 publish_host_surface_size(first);
335 assert_eq!(
336 seen.lock().unwrap_or_else(|e| e.into_inner()).as_slice(),
337 [first]
338 );
339 drop(registration);
340 publish_host_surface_size(HostSurfaceSize {
341 width: 200.0,
342 ..first
343 });
344 assert_eq!(seen.lock().unwrap_or_else(|e| e.into_inner()).len(), 1);
345 clear_platform_host_surface();
346 }
347}