Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
216 changes: 216 additions & 0 deletions crates/strato-core/src/inspector.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
use std::collections::HashMap;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::OnceLock;
use std::time::{Duration, SystemTime};

use parking_lot::RwLock;

use crate::state::StateId;
use crate::types::Rect;
use crate::widget::WidgetId;

/// Configuration for the runtime inspector.
#[derive(Debug, Clone)]
pub struct InspectorConfig {
/// Whether the inspector is allowed to capture runtime information.
pub enabled: bool,
/// Whether layout bounds should be captured on every frame.
pub capture_layout: bool,
/// Whether state mutations should be snapshotted.
pub capture_state: bool,
/// Whether performance timelines should be tracked.
pub capture_performance: bool,
}

impl Default for InspectorConfig {
fn default() -> Self {
Self {
enabled: cfg!(debug_assertions),
capture_layout: true,
capture_state: true,
capture_performance: true,
}
}
}

/// A single component record in the captured hierarchy.
#[derive(Debug, Clone)]
pub struct ComponentNodeSnapshot {
pub id: WidgetId,
pub name: String,
pub depth: usize,
pub props: HashMap<String, String>,
pub state: HashMap<String, String>,
}

/// Captured layout box for a widget.
#[derive(Debug, Clone, Copy)]
pub struct LayoutBoxSnapshot {
pub widget_id: WidgetId,
pub bounds: Rect,
}

/// Captured state change metadata.
#[derive(Debug, Clone)]
pub struct StateSnapshot {
pub state_id: StateId,
pub detail: String,
pub recorded_at: SystemTime,
}

/// Performance timeline entry (per frame).
#[derive(Debug, Clone)]
pub struct FrameTimelineSnapshot {
pub frame_id: u64,
pub cpu_time_ms: f32,
pub gpu_time_ms: f32,
pub notes: Option<String>,
}

/// Complete snapshot of inspector data for rendering in the overlay.
#[derive(Debug, Clone)]
pub struct InspectorSnapshot {
pub components: Vec<ComponentNodeSnapshot>,
pub layout_boxes: Vec<LayoutBoxSnapshot>,
pub state_snapshots: Vec<StateSnapshot>,
pub frame_timelines: Vec<FrameTimelineSnapshot>,
}

impl Default for InspectorSnapshot {
fn default() -> Self {
Self {
components: Vec::new(),
layout_boxes: Vec::new(),
state_snapshots: Vec::new(),
frame_timelines: Vec::new(),
}
}
}

/// Runtime inspector for StratoSDK that aggregates data from multiple layers.
pub struct Inspector {
config: RwLock<InspectorConfig>,
enabled: AtomicBool,
components: RwLock<Vec<ComponentNodeSnapshot>>,
layout_boxes: RwLock<Vec<LayoutBoxSnapshot>>,
state_snapshots: RwLock<HashMap<StateId, StateSnapshot>>,
frame_timelines: RwLock<Vec<FrameTimelineSnapshot>>,
}

impl Inspector {
fn new() -> Self {
let config = InspectorConfig::default();
Self {
enabled: AtomicBool::new(config.enabled),
components: RwLock::new(Vec::new()),
layout_boxes: RwLock::new(Vec::new()),
state_snapshots: RwLock::new(HashMap::new()),
frame_timelines: RwLock::new(Vec::new()),
config: RwLock::new(config),
}
}

/// Update the inspector configuration at runtime.
pub fn configure(&self, config: InspectorConfig) {
*self.config.write() = config.clone();
self.enabled.store(config.enabled, Ordering::Relaxed);
}

/// Get the current configuration.
pub fn config(&self) -> InspectorConfig {
self.config.read().clone()
}

/// Check if the inspector is enabled.
pub fn is_enabled(&self) -> bool {
self.enabled.load(Ordering::Relaxed)
}

/// Enable or disable the inspector without replacing the full configuration.
pub fn set_enabled(&self, enabled: bool) {
self.enabled.store(enabled, Ordering::Relaxed);
self.config.write().enabled = enabled;
}

/// Toggle inspector visibility.
pub fn toggle(&self) -> bool {
let next = !self.is_enabled();
self.set_enabled(next);
next
}

/// Reset transient per-frame information so the overlay always shows the latest data.
pub fn begin_frame(&self) {
self.layout_boxes.write().clear();
self.components.write().clear();
}

/// Replace the captured widget hierarchy for the current frame.
pub fn record_component_tree(&self, nodes: Vec<ComponentNodeSnapshot>) {
if !self.is_enabled() {
return;
}
*self.components.write() = nodes;
}

/// Record a layout box for a widget.
pub fn record_layout_box(&self, snapshot: LayoutBoxSnapshot) {
if !self.is_enabled() || !self.config().capture_layout {
return;
}
self.layout_boxes.write().push(snapshot);
}

/// Record a state mutation/snapshot.
pub fn record_state_snapshot(&self, state_id: StateId, detail: impl Into<String>) {
if !self.is_enabled() || !self.config().capture_state {
return;
}

self.state_snapshots.write().insert(
state_id,
StateSnapshot {
state_id,
detail: detail.into(),
recorded_at: SystemTime::now(),
},
);
}

/// Record a per-frame performance timeline entry.
pub fn record_frame_timeline(
&self,
frame_id: u64,
cpu_time: Duration,
gpu_time: Duration,
notes: Option<String>,
) {
if !self.is_enabled() || !self.config().capture_performance {
return;
}

self.frame_timelines.write().push(FrameTimelineSnapshot {
frame_id,
cpu_time_ms: (cpu_time.as_secs_f64() * 1000.0) as f32,
gpu_time_ms: (gpu_time.as_secs_f64() * 1000.0) as f32,
notes,
});
}

/// Get the full snapshot used by the inspector overlay widget.
pub fn snapshot(&self) -> InspectorSnapshot {
InspectorSnapshot {
components: self.components.read().clone(),
layout_boxes: self.layout_boxes.read().clone(),
state_snapshots: self.state_snapshots.read().values().cloned().collect(),
frame_timelines: self.frame_timelines.read().clone(),
}
}
}

static INSPECTOR: OnceLock<Inspector> = OnceLock::new();

/// Access the global inspector instance used by all layers.
pub fn inspector() -> &'static Inspector {
INSPECTOR.get_or_init(Inspector::new)
}
38 changes: 20 additions & 18 deletions crates/strato-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,41 +3,43 @@
//! This crate provides the fundamental building blocks for the StratoUI framework,
//! including state management, event handling, and layout calculations.

pub mod config;
pub mod error;
pub mod event;
pub mod hot_reload;
pub mod inspector;
pub mod layout;
pub mod state;
pub mod logging;
pub mod plugin;
pub mod reactive;
pub mod state;
pub mod text;
pub mod theme;
pub mod types;
pub mod error;
pub mod ui_node;
pub mod vdom;
pub mod widget;
pub mod window;
pub mod hot_reload;
pub mod theme;
pub mod plugin;
pub mod text;
pub mod logging;
pub mod config;
pub mod ui_node;

pub use error::{Result, StratoError, StratoResult};
pub use event::{Event, EventHandler, EventResult};
pub use layout::{Constraints, Layout, LayoutEngine, LayoutConstraints, Size};
pub use state::{Signal, State};
pub use layout::{Constraints, Layout, LayoutConstraints, LayoutEngine, Size};
pub use logging::{LogCategory, LogLevel};
pub use reactive::{Computed, Effect, Reactive};
pub use state::{Signal, State};
pub use types::{Color, Point, Rect, Transform};
pub use error::{StratoError, StratoResult, Result};
pub use logging::{LogLevel, LogCategory};

/// Re-export commonly used types
pub mod prelude {
pub use crate::{
error::{Result, StratoError},
event::{Event, EventHandler, EventResult},
inspector::{inspector, InspectorConfig, InspectorSnapshot},
layout::{Constraints, Layout, Size},
state::{Signal, State},
logging::LogLevel,
reactive::{Computed, Effect},
state::{Signal, State},
types::{Color, Point, Rect},
error::{StratoError, Result},
logging::{LogLevel},
};
}

Expand All @@ -49,12 +51,12 @@ pub fn init() -> Result<()> {
// Initialize logging system with default config
let config = config::LoggingConfig::default();
if let Err(e) = logging::init(&config) {
return Err(StratoError::Initialization {
return Err(StratoError::Initialization {
message: format!("Failed to initialize logging: {}", e),
context: None,
});
}

// Initialize tracing
tracing::info!("StratoUI Core v{} initialized", VERSION);
Ok(())
Expand Down
Loading