Seregon/StratoSDK

StratoSDK is a framework with a declarative approach similar to Flutter/React, written and designed entirely for Rust.

Rust/27.3 KB/No license
crates/strato-core/src/inspector.rs
StratoSDK / crates / strato-core / src / inspector.rs
1use std::collections::HashMap;
2use std::sync::atomic::{AtomicBool, Ordering};
3use std::sync::OnceLock;
4use std::time::{Duration, SystemTime};
5 
6use parking_lot::RwLock;
7 
8use crate::state::StateId;
9use crate::types::Rect;
10use crate::widget::WidgetId;
11 
12/// Configuration for the runtime inspector.
13#[derive(Debug, Clone)]
14pub struct InspectorConfig {
15 /// Whether the inspector is allowed to capture runtime information.
16 pub enabled: bool,
17 /// Whether layout bounds should be captured on every frame.
18 pub capture_layout: bool,
19 /// Whether state mutations should be snapshotted.
20 pub capture_state: bool,
21 /// Whether performance timelines should be tracked.
22 pub capture_performance: bool,
23}
24 
25impl Default for InspectorConfig {
26 fn default() -> Self {
27 Self {
28 enabled: cfg!(debug_assertions),
29 capture_layout: true,
30 capture_state: true,
31 capture_performance: true,
32 }
33 }
34}
35 
36/// A single component record in the captured hierarchy.
37#[derive(Debug, Clone)]
38pub struct ComponentNodeSnapshot {
39 pub id: WidgetId,
40 pub name: String,
41 pub depth: usize,
42 pub props: HashMap<String, String>,
43 pub state: HashMap<String, String>,
44}
45 
46/// Captured layout box for a widget.
47#[derive(Debug, Clone, Copy)]
48pub struct LayoutBoxSnapshot {
49 pub widget_id: WidgetId,
50 pub bounds: Rect,
51}
52 
53/// Captured state change metadata.
54#[derive(Debug, Clone)]
55pub struct StateSnapshot {
56 pub state_id: StateId,
57 pub detail: String,
58 pub recorded_at: SystemTime,
59}
60 
61/// Performance timeline entry (per frame).
62#[derive(Debug, Clone)]
63pub struct FrameTimelineSnapshot {
64 pub frame_id: u64,
65 pub cpu_time_ms: f32,
66 pub gpu_time_ms: f32,
67 pub notes: Option<String>,
68}
69 
70/// Complete snapshot of inspector data for rendering in the overlay.
71#[derive(Debug, Clone)]
72pub struct InspectorSnapshot {
73 pub components: Vec<ComponentNodeSnapshot>,
74 pub layout_boxes: Vec<LayoutBoxSnapshot>,
75 pub state_snapshots: Vec<StateSnapshot>,
76 pub frame_timelines: Vec<FrameTimelineSnapshot>,
77}
78 
79impl Default for InspectorSnapshot {
80 fn default() -> Self {
81 Self {
82 components: Vec::new(),
83 layout_boxes: Vec::new(),
84 state_snapshots: Vec::new(),
85 frame_timelines: Vec::new(),
86 }
87 }
88}
89 
90/// Runtime inspector for StratoSDK that aggregates data from multiple layers.
91pub struct Inspector {
92 config: RwLock<InspectorConfig>,
93 enabled: AtomicBool,
94 components: RwLock<Vec<ComponentNodeSnapshot>>,
95 layout_boxes: RwLock<Vec<LayoutBoxSnapshot>>,
96 state_snapshots: RwLock<HashMap<StateId, StateSnapshot>>,
97 frame_timelines: RwLock<Vec<FrameTimelineSnapshot>>,
98}
99 
100impl Inspector {
101 fn new() -> Self {
102 let config = InspectorConfig::default();
103 Self {
104 enabled: AtomicBool::new(config.enabled),
105 components: RwLock::new(Vec::new()),
106 layout_boxes: RwLock::new(Vec::new()),
107 state_snapshots: RwLock::new(HashMap::new()),
108 frame_timelines: RwLock::new(Vec::new()),
109 config: RwLock::new(config),
110 }
111 }
112 
113 /// Update the inspector configuration at runtime.
114 pub fn configure(&self, config: InspectorConfig) {
115 *self.config.write() = config.clone();
116 self.enabled.store(config.enabled, Ordering::Relaxed);
117 }
118 
119 /// Get the current configuration.
120 pub fn config(&self) -> InspectorConfig {
121 self.config.read().clone()
122 }
123 
124 /// Check if the inspector is enabled.
125 pub fn is_enabled(&self) -> bool {
126 self.enabled.load(Ordering::Relaxed)
127 }
128 
129 /// Enable or disable the inspector without replacing the full configuration.
130 pub fn set_enabled(&self, enabled: bool) {
131 self.enabled.store(enabled, Ordering::Relaxed);
132 self.config.write().enabled = enabled;
133 }
134 
135 /// Toggle inspector visibility.
136 pub fn toggle(&self) -> bool {
137 let next = !self.is_enabled();
138 self.set_enabled(next);
139 next
140 }
141 
142 /// Reset transient per-frame information so the overlay always shows the latest data.
143 pub fn begin_frame(&self) {
144 self.layout_boxes.write().clear();
145 self.components.write().clear();
146 }
147 
148 /// Replace the captured widget hierarchy for the current frame.
149 pub fn record_component_tree(&self, nodes: Vec<ComponentNodeSnapshot>) {
150 if !self.is_enabled() {
151 return;
152 }
153 *self.components.write() = nodes;
154 }
155 
156 /// Record a layout box for a widget.
157 pub fn record_layout_box(&self, snapshot: LayoutBoxSnapshot) {
158 if !self.is_enabled() || !self.config().capture_layout {
159 return;
160 }
161 self.layout_boxes.write().push(snapshot);
162 }
163 
164 /// Record a state mutation/snapshot.
165 pub fn record_state_snapshot(&self, state_id: StateId, detail: impl Into<String>) {
166 if !self.is_enabled() || !self.config().capture_state {
167 return;
168 }
169 
170 self.state_snapshots.write().insert(
171 state_id,
172 StateSnapshot {
173 state_id,
174 detail: detail.into(),
175 recorded_at: SystemTime::now(),
176 },
177 );
178 }
179 
180 /// Record a per-frame performance timeline entry.
181 pub fn record_frame_timeline(
182 &self,
183 frame_id: u64,
184 cpu_time: Duration,
185 gpu_time: Duration,
186 notes: Option<String>,
187 ) {
188 if !self.is_enabled() || !self.config().capture_performance {
189 return;
190 }
191 
192 self.frame_timelines.write().push(FrameTimelineSnapshot {
193 frame_id,
194 cpu_time_ms: (cpu_time.as_secs_f64() * 1000.0) as f32,
195 gpu_time_ms: (gpu_time.as_secs_f64() * 1000.0) as f32,
196 notes,
197 });
198 }
199 
200 /// Get the full snapshot used by the inspector overlay widget.
201 pub fn snapshot(&self) -> InspectorSnapshot {
202 InspectorSnapshot {
203 components: self.components.read().clone(),
204 layout_boxes: self.layout_boxes.read().clone(),
205 state_snapshots: self.state_snapshots.read().values().cloned().collect(),
206 frame_timelines: self.frame_timelines.read().clone(),
207 }
208 }
209}
210 
211static INSPECTOR: OnceLock<Inspector> = OnceLock::new();
212 
213/// Access the global inspector instance used by all layers.
214pub fn inspector() -> &'static Inspector {
215 INSPECTOR.get_or_init(Inspector::new)
216}
217