tessera_ui/runtime.rs
1//! # Tessera Runtime System
2//!
3//! This module provides the global runtime state management for the Tessera UI framework.
4//! The runtime system maintains essential application state including the component tree,
5//! window properties, and user interface state that needs to be shared across the entire
6//! application lifecycle.
7//!
8//! ## Overview
9//!
10//! The [`TesseraRuntime`] serves as the central hub for all runtime data and side effects
11//! in a Tessera application. It uses a thread-safe singleton pattern to ensure consistent
12//! access to shared state from any part of the application, including from multiple threads
13//! during parallel component processing.
14//!
15//! ## Thread Safety
16//!
17//! The runtime is designed with parallelization in mind. It uses [`parking_lot::RwLock`]
18//! for efficient read-write synchronization, allowing multiple concurrent readers while
19//! ensuring exclusive access for writers. This design supports Tessera's parallel
20//! component tree processing capabilities.
21//!
22//! ## Usage
23//!
24//! Access the runtime through the static methods:
25//!
26//! ```
27//! use tessera_ui::{TesseraRuntime, winit};
28//!
29//! // Read-only access (multiple threads can read simultaneously)
30//! {
31//! let window_size = TesseraRuntime::with(|rt| rt.window_size());
32//! println!("Window size: {}x{}", window_size[0], window_size[1]);
33//! }
34//!
35//! // Write access (exclusive access required)
36//! TesseraRuntime::with_mut(|rt| {
37//! rt.cursor_icon_request = Some(winit::window::CursorIcon::Pointer);
38//! });
39//! ```
40//!
41//! ## Performance Considerations
42//!
43//! - Prefer read locks when only accessing data
44//! - Keep lock scopes as narrow as possible to minimize contention
45//! - The runtime is optimized for frequent reads and occasional writes
46//! - Component tree operations may involve parallel processing under read locks
47
48use std::sync::OnceLock;
49
50use parking_lot::{RwLock, RwLockReadGuard, RwLockWriteGuard};
51
52use crate::component_tree::ComponentTree;
53
54/// Global singleton instance of the Tessera runtime.
55///
56/// This static variable ensures that there is exactly one runtime instance per application,
57/// initialized lazily on first access. The [`OnceLock`] provides thread-safe initialization
58/// without the overhead of synchronization after the first initialization.
59static TESSERA_RUNTIME: OnceLock<RwLock<TesseraRuntime>> = OnceLock::new();
60
61/// Central runtime state container for the Tessera UI framework.
62///
63/// The `TesseraRuntime` holds all global state and side effects that need to be shared
64/// across the entire application. This includes the component tree structure, window
65/// properties, and user interface state that persists across frame updates.
66///
67/// ## Design Philosophy
68///
69/// The runtime follows these key principles:
70/// - **Single Source of Truth**: All shared state is centralized in one location
71/// - **Thread Safety**: Safe concurrent access through read-write locks
72/// - **Lazy Initialization**: Runtime is created only when first accessed
73/// - **Minimal Overhead**: Optimized for frequent reads and occasional writes
74///
75/// ## Lifecycle
76///
77/// The runtime is automatically initialized on first access and persists for the
78/// entire application lifetime. It cannot be manually destroyed or recreated.
79///
80/// ## Fields
81///
82/// All fields are public to allow direct access after acquiring the appropriate lock.
83/// However, consider using higher-level APIs when available to maintain consistency.
84#[derive(Default)]
85pub struct TesseraRuntime {
86 /// The hierarchical structure of all UI components in the application.
87 ///
88 /// This tree represents the current state of the UI hierarchy, including
89 /// component relationships, layout information, and rendering data. The
90 /// component tree is rebuilt or updated each frame during the UI update cycle.
91 ///
92 /// ## Thread Safety
93 ///
94 /// While the runtime itself is thread-safe, individual operations on the
95 /// component tree may require coordination to maintain consistency during
96 /// parallel processing phases.
97 pub component_tree: ComponentTree,
98
99 /// Current window dimensions in physical pixels.
100 ///
101 /// This array contains `[width, height]` representing the current size of
102 /// the application window. These values are updated automatically when the
103 /// window is resized and are used for layout calculations and rendering.
104 ///
105 /// ## Coordinate System
106 ///
107 /// - Values are in physical pixels (not density-independent pixels)
108 /// - Origin is at the top-left corner of the window
109 /// - Both dimensions are guaranteed to be non-negative
110 pub(crate) window_size: [u32; 2],
111
112 /// Cursor icon change request from UI components.
113 ///
114 /// Components can request cursor icon changes by setting this field during
115 /// their update cycle. The windowing system will apply the requested cursor
116 /// icon if present, or use the default cursor if `None`.
117 ///
118 /// ## Lifecycle
119 ///
120 /// This field is typically:
121 /// 1. Reset to `None` at the beginning of each frame
122 /// 2. Set by components during event handling or state updates
123 /// 3. Applied by the windowing system at the end of the frame
124 ///
125 /// ## Priority
126 ///
127 /// If multiple components request different cursor icons in the same frame,
128 /// the last request takes precedence. Components should coordinate cursor
129 /// changes or use a priority system if needed.
130 pub cursor_icon_request: Option<winit::window::CursorIcon>,
131
132 /// Called when the window minimize state changes.
133 on_minimize_callbacks: Vec<Box<dyn Fn(bool) + Send + Sync>>,
134 /// Called when the window close event is triggered.
135 on_close_callbacks: Vec<Box<dyn Fn() + Send + Sync>>,
136 /// Whether the window is currently minimized.
137 pub(crate) window_minimized: bool,
138}
139
140impl TesseraRuntime {
141 /// Executes a closure with a shared, read-only reference to the runtime.
142 ///
143 /// This is the recommended way to access runtime state, as it ensures the lock is
144 /// released immediately after the closure finishes, preventing deadlocks caused by
145 /// extended lock lifetimes.
146 ///
147 /// # Example
148 /// ```
149 /// use tessera_ui::TesseraRuntime;
150 ///
151 /// let size = TesseraRuntime::with(|runtime| runtime.window_size());
152 /// println!("Window size: {}x{}", size[0], size[1]);
153 /// ```
154 pub fn with<F, R>(f: F) -> R
155 where
156 F: FnOnce(&Self) -> R,
157 {
158 f(&TESSERA_RUNTIME
159 .get_or_init(|| RwLock::new(Self::default()))
160 .read())
161 }
162
163 /// Executes a closure with an exclusive, mutable reference to the runtime.
164 ///
165 /// This is the recommended way to modify runtime state. The lock is guaranteed
166 /// to be released after the closure completes.
167 ///
168 /// # Example
169 /// ```
170 /// use tessera_ui::{TesseraRuntime, winit};
171 ///
172 /// TesseraRuntime::with_mut(|runtime| {
173 /// runtime.cursor_icon_request = Some(winit::window::CursorIcon::Pointer);
174 /// });
175 /// ```
176 pub fn with_mut<F, R>(f: F) -> R
177 where
178 F: FnOnce(&mut Self) -> R,
179 {
180 f(&mut TESSERA_RUNTIME
181 .get_or_init(|| RwLock::new(Self::default()))
182 .write())
183 }
184
185 /// Acquires shared read access to the runtime state.
186 ///
187 /// This method returns a read guard that allows concurrent access to the runtime
188 /// data from multiple threads. Multiple readers can access the runtime simultaneously,
189 /// but no writers can modify the state while any read guards exist.
190 ///
191 /// ## Blocking Behavior
192 ///
193 /// This method will block the current thread if a write lock is currently held.
194 /// It will return immediately if no write locks are active, even if other read
195 /// locks exist.
196 ///
197 /// ## Usage
198 ///
199 /// ```
200 /// use tessera_ui::TesseraRuntime;
201 ///
202 /// // Access runtime data for reading
203 /// let runtime = TesseraRuntime::read();
204 /// let [width, height] = runtime.window_size();
205 /// println!("Window size: {}x{}", width, height);
206 /// // Lock is automatically released when `runtime` goes out of scope
207 /// ```
208 ///
209 /// ## Performance
210 ///
211 /// Read locks are optimized for high-frequency access and have minimal overhead
212 /// when no write contention exists. Prefer read locks over write locks whenever
213 /// possible to maximize parallelism.
214 ///
215 /// ## Deadlock Prevention
216 ///
217 /// To prevent deadlocks:
218 /// - Always acquire locks in a consistent order
219 /// - Keep lock scopes as narrow as possible
220 /// - Avoid calling other locking functions while holding a lock
221 ///
222 /// # Returns
223 ///
224 /// A [`RwLockReadGuard`] that provides read-only access to the runtime state.
225 /// The guard automatically releases the lock when dropped.
226 #[deprecated(
227 since = "1.8.1",
228 note = "May cause deadlocks due to temporary lifetime extension. Use `TesseraRuntime::with()` instead."
229 )]
230 pub fn read() -> RwLockReadGuard<'static, Self> {
231 TESSERA_RUNTIME
232 .get_or_init(|| RwLock::new(Self::default()))
233 .read()
234 }
235
236 /// Acquires exclusive write access to the runtime state.
237 ///
238 /// This method returns a write guard that provides exclusive access to modify
239 /// the runtime data. Only one writer can access the runtime at a time, and no
240 /// readers can access the state while a write lock is held.
241 ///
242 /// ## Blocking Behavior
243 ///
244 /// This method will block the current thread until all existing read and write
245 /// locks are released. It guarantees exclusive access once acquired.
246 ///
247 /// ## Usage
248 ///
249 /// ```
250 /// use tessera_ui::TesseraRuntime;
251 ///
252 /// // Modify runtime state
253 /// {
254 /// let mut runtime = TesseraRuntime::write();
255 /// runtime.cursor_icon_request = Some(winit::window::CursorIcon::Pointer);
256 /// } // Lock is automatically released
257 /// ```
258 ///
259 /// ## Performance Considerations
260 ///
261 /// Write locks are more expensive than read locks and should be used sparingly:
262 /// - Batch multiple modifications into a single write lock scope
263 /// - Release write locks as quickly as possible
264 /// - Consider if the operation truly requires exclusive access
265 ///
266 /// ## Deadlock Prevention
267 ///
268 /// The same deadlock prevention guidelines apply as with [`read()`](Self::read):
269 /// - Acquire locks in consistent order
270 /// - Minimize lock scope duration
271 /// - Avoid nested locking operations
272 ///
273 /// # Returns
274 ///
275 /// A [`RwLockWriteGuard`] that provides exclusive read-write access to the
276 /// runtime state. The guard automatically releases the lock when dropped.
277 #[deprecated(
278 since = "1.8.1",
279 note = "May cause deadlocks due to temporary lifetime extension. Use `TesseraRuntime::with_mut()` instead."
280 )]
281 pub fn write() -> RwLockWriteGuard<'static, Self> {
282 TESSERA_RUNTIME
283 .get_or_init(|| RwLock::new(Self::default()))
284 .write()
285 }
286
287 /// Get the current window size in physical pixels.
288 pub fn window_size(&self) -> [u32; 2] {
289 self.window_size
290 }
291
292 /// Registers a per-frame callback for minimize state changes.
293 /// Components should call this every frame they wish to be notified.
294 pub fn on_minimize(&mut self, callback: impl Fn(bool) + Send + Sync + 'static) {
295 self.on_minimize_callbacks.push(Box::new(callback));
296 }
297
298 /// Registers a per-frame callback for window close event.
299 /// Components should call this every frame they wish to be notified.
300 pub fn on_close(&mut self, callback: impl Fn() + Send + Sync + 'static) {
301 self.on_close_callbacks.push(Box::new(callback));
302 }
303
304 /// Clears all per-frame registered callbacks.
305 /// Must be called by the event loop at the beginning of each frame.
306 pub fn clear_frame_callbacks(&mut self) {
307 self.on_minimize_callbacks.clear();
308 self.on_close_callbacks.clear();
309 }
310
311 /// Triggers all registered callbacks (global and per-frame).
312 /// Called by the event loop when a minimize event is detected.
313 pub fn trigger_minimize_callbacks(&self, minimized: bool) {
314 for callback in &self.on_minimize_callbacks {
315 callback(minimized);
316 }
317 }
318
319 /// Triggers all registered callbacks (global and per-frame) for window close event.
320 /// Called by the event loop when a close event is detected.
321 pub fn trigger_close_callbacks(&self) {
322 for callback in &self.on_close_callbacks {
323 callback();
324 }
325 }
326}