1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
//! The process-wide GPU-device slot: [`install_gpu_handle`]/[`gpu_handle`].
//!
//! # The gap this closes
//!
//! Every shell creates its GPU device lazily, on its own thread, the first
//! time a surface comes up (`frust_render::RenderContext`, owned wholly by
//! the shell's render executor — the desktop split moves it onto a dedicated
//! render thread, the mobile shells' equivalent). Nothing before this module
//! ever handed that device back out: an app compiled with the facade's `gpu`
//! feature had no way to reach the *live*, shell-owned device — only to build
//! a throwaway standalone one of its own (`frust::gpu::Context::new`).
//!
//! This module is that hand-back seam's shared slot: a shell installs its
//! device once, from wherever it creates one, and app code — through the
//! facade's `frust::gpu::with_context` — reads it back without the facade
//! ever depending on `frust-gpu`/`frust-render` for this seam's own sake (it
//! already carries a separate, narrower, optional edge for the standalone
//! `Context` — see `crates/frust/src/lib.rs`'s `gpu` module docs).
//!
//! # Type-erased on purpose — this crate stays a platform-free leaf
//!
//! `frust-shell-common` depends on none of `frust-gpu`/`frust-render`/`wgpu`
//! (see the crate's own docs' "deliberately platform-free" paragraph) and
//! this seam adds no edge to change that: the slot stores an opaque
//! `Box<dyn Any + Send + Sync>` rather than naming the concrete device type
//! (`frust_render::DeviceHandle`, cheap to clone — see its own doc comment).
//! Every real caller — [`install_gpu_handle`] from a shell's render executor,
//! [`gpu_handle`] from the facade's `with_context` — instantiates the generic
//! with that one concrete type, so the erasure is invisible in practice; it
//! only exists so this crate never has to name a GPU type to host the slot.
//! Reading with a mismatched `T` (a caller error, never a real code path) is
//! not a panic — it answers `None`, exactly like reading before anything
//! installed.
//!
//! # Layering and thread contract
//!
//! One `OnceLock`, unlike the `Mutex`-backed slots in
//! [`crate::theme_override`]/[`crate::surface_mode`]/[`crate::system_ui`]:
//! those slots change during a running app's lifetime (a theme override, a
//! resolved surface mode), so they need a lock a later write can take again.
//! A GPU device does not — once a shell's device exists it lives for the rest
//! of the process (`frust_gpu::context::RenderContext` reuses it across
//! surface loss/recreation), so this slot is **install-once**:
//! [`install_gpu_handle`] only ever moves it from empty to occupied, is a
//! no-op (returning `false`) on every call after the first, and there is no
//! corresponding clear. That also means [`gpu_handle`] never blocks: once
//! occupied, a read is a lock-free `OnceLock::get`, which is what lets the
//! facade's `with_context` promise it never blocks the UI thread.
//!
//! Both functions are callable from any thread — the installing shell's
//! render thread and a reading UI thread are typically different ones, and
//! `OnceLock` itself is the synchronization; no additional lock is taken.
//!
//! # Callers
//!
//! [`install_gpu_handle`] is called by a shell's render executor, at the
//! point it first brings a device up (desktop: `frust-shell-desktop::render`,
//! right after a surface install succeeds, so the device is guaranteed
//! present — see `RenderContext::device_handle`'s own doc comment for why
//! that call is panic-free there). Unlike
//! [`crate::surface_mode::declare_host_translucent_surface`] this is not
//! restricted to generated host glue — installing a device handle carries no
//! host-configuration precondition to violate — so it is `pub`, reachable
//! from any crate that depends on this one. In practice only a shell's own
//! render executor ever calls it.
//!
//! [`gpu_handle`] is read by the facade's `frust::gpu::with_context` (see
//! `crates/frust/src/lib.rs`); nothing stops another caller from reading it
//! directly, the same openness [`crate::surface_mode::resolved_surface_mode`]
//! has.
use Any;
use OnceLock;
/// The process-wide slot, install-once — see the module docs' Layering and
/// thread contract. Erased to `Box<dyn Any + Send + Sync>` so this crate
/// never names the concrete GPU-device type; every real caller instantiates
/// [`install_gpu_handle`]/[`gpu_handle`]'s generic with the same one type.
static GPU_HANDLE: = new;
/// Install the shell's GPU device handle, once.
///
/// Returns `true` if this call was the one that occupied the slot, `false`
/// if it was already occupied (by an earlier call — with this type or any
/// other) — the slot never overwrites, so a later call is inert rather than
/// replacing a live handle a reader may already be holding a reference into.
///
/// `T` must be `Any + Send + Sync` (enforced at the call site, not asserted
/// separately): a device handle read back on a different thread than it was
/// installed on must be safe to share, which is exactly the guarantee a
/// shell's own device type documents (cheap to clone, `Arc`-backed wgpu
/// resources underneath).
/// Read the installed GPU device handle back, `None` before any shell has
/// installed one (no surface has come up yet) or if `T` does not match the
/// type that was actually installed (a caller error — see the module docs'
/// Type-erased section — never a real code path in practice, since exactly
/// one concrete type is ever installed).
///
/// A lock-free `OnceLock::get` once occupied, so this never blocks — see the
/// module docs' Layering and thread contract.