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