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}