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}