Skip to main content

concinnity_core/
window_policy.rs

1//! Whether this process is allowed to open a window.
2//!
3//! A backend stands up its window from deep inside device initialisation, and
4//! a process that reaches that point without an operating system to drive it
5//! does not fail: it blocks on an event loop that never ends. Under a test
6//! harness that is a hang with no failing assertion to read.
7//!
8//! The policy is a process-wide latch a caller sets before it runs anything.
9//! Default is permissive, so a shipped binary behaves exactly as it did; a
10//! headless host forbids windows up front and gets a panic naming the backend
11//! instead of a hang.
12
13use core::sync::atomic::{AtomicBool, Ordering};
14
15static FORBIDDEN: AtomicBool = AtomicBool::new(false);
16
17/// Forbid window creation for the rest of the process, until [`allow_windows`].
18///
19/// A backend that reaches its window call after this panics rather than
20/// standing one up.
21pub fn forbid_windows() {
22    FORBIDDEN.store(true, Ordering::SeqCst);
23}
24
25/// Lift the ban set by [`forbid_windows`], restoring the default.
26pub fn allow_windows() {
27    FORBIDDEN.store(false, Ordering::SeqCst);
28}
29
30/// Whether window creation is currently forbidden.
31pub fn windows_forbidden() -> bool {
32    FORBIDDEN.load(Ordering::SeqCst)
33}
34
35/// Panic if windows are forbidden, naming `backend` as what tried to open one.
36///
37/// Called at each backend's window entry point, before any operating-system
38/// resource is taken.
39pub fn assert_windows_allowed(backend: &str) {
40    assert!(
41        !windows_forbidden(),
42        "{backend} tried to open a window in a process that forbids them; \
43         run the world on the headless loop instead"
44    );
45}
46
47#[cfg(test)]
48mod tests {
49    use alloc::string::String;
50
51    use super::*;
52
53    // The latch is process-global, so the two tests that move it share a lock
54    // and put it back. Everything else in the crate reads the default.
55    static LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
56
57    #[test]
58    fn the_default_permits_a_window() {
59        let _guard = LOCK.lock().unwrap_or_else(|e| e.into_inner());
60        allow_windows();
61        assert!(!windows_forbidden());
62        assert_windows_allowed("a backend");
63    }
64
65    #[test]
66    fn a_forbidden_process_panics_naming_the_backend() {
67        let _guard = LOCK.lock().unwrap_or_else(|e| e.into_inner());
68        forbid_windows();
69        assert!(windows_forbidden());
70
71        let panicked = std::panic::catch_unwind(|| assert_windows_allowed("TestBackend"));
72        allow_windows();
73
74        let payload = panicked.expect_err("a forbidden window creation panics");
75        let msg = payload
76            .downcast_ref::<String>()
77            .expect("the panic carries its message");
78        assert!(msg.contains("TestBackend"), "{msg}");
79        assert!(msg.contains("headless"), "{msg}");
80    }
81}