pub mod entity_highlight;
pub mod colliders;
pub mod transforms;
pub mod system_flow;
pub mod performance_metrics;
pub mod custom_markers;
use crate::brp_messages::DebugOverlayType;
#[cfg(feature = "visual_overlays")]
use bevy::prelude::*;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
pub trait VisualOverlay: Send + Sync + std::fmt::Debug {
fn initialize(&mut self, app: &mut App);
fn update_config(&mut self, config: &serde_json::Value) -> Result<(), String>;
fn set_enabled(&mut self, enabled: bool);
fn is_enabled(&self) -> bool;
fn get_metrics(&self) -> OverlayMetrics;
fn overlay_type(&self) -> DebugOverlayType;
fn cleanup(&mut self);
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct OverlayMetrics {
pub render_time_us: u64,
pub element_count: usize,
pub memory_usage_bytes: usize,
pub frame_updates: usize,
pub active_this_frame: bool,
}
#[cfg_attr(feature = "visual_overlays", derive(Resource))]
#[derive(Debug)]
pub struct VisualOverlayManager {
overlays: HashMap<String, Box<dyn VisualOverlay>>,
global_enabled: bool,
performance_budget_us: u64,
total_metrics: OverlayMetrics,
}
impl VisualOverlayManager {
pub fn new() -> Self {
Self {
overlays: HashMap::new(),
global_enabled: true,
performance_budget_us: 2000, total_metrics: OverlayMetrics::default(),
}
}
pub fn initialize(&mut self, app: &mut App) {
self.register_overlay("entity_highlight", Box::new(entity_highlight::EntityHighlightOverlay::new()));
self.register_overlay("colliders", Box::new(colliders::CollidersOverlay::new()));
self.register_overlay("transforms", Box::new(transforms::TransformsOverlay::new()));
self.register_overlay("system_flow", Box::new(system_flow::SystemFlowOverlay::new()));
self.register_overlay("performance_metrics", Box::new(performance_metrics::PerformanceMetricsOverlay::new()));
for overlay in self.overlays.values_mut() {
overlay.initialize(app);
}
app.add_systems(PostUpdate, (
Self::update_performance_metrics,
Self::check_performance_budget.after(Self::update_performance_metrics),
));
}
fn register_overlay(&mut self, key: &str, overlay: Box<dyn VisualOverlay>) {
self.overlays.insert(key.to_string(), overlay);
}
pub fn set_overlay_enabled(
&mut self,
overlay_type: &DebugOverlayType,
enabled: bool,
config: Option<&serde_json::Value>,
) -> Result<(), String> {
let key = self.overlay_type_to_key(overlay_type);
if let Some(overlay) = self.overlays.get_mut(&key) {
overlay.set_enabled(enabled);
if let Some(config) = config {
overlay.update_config(config)?;
}
info!(
"Visual overlay '{}' {} with config: {:?}",
key,
if enabled { "enabled" } else { "disabled" },
config
);
Ok(())
} else {
Err(format!("Unknown overlay type: {:?}", overlay_type))
}
}
pub fn get_overlay_status(&self, overlay_type: &DebugOverlayType) -> Option<(bool, OverlayMetrics)> {
let key = self.overlay_type_to_key(overlay_type);
self.overlays.get(&key).map(|overlay| {
(overlay.is_enabled(), overlay.get_metrics())
})
}
pub fn get_all_statuses(&self) -> HashMap<String, (bool, OverlayMetrics)> {
self.overlays
.iter()
.map(|(key, overlay)| {
(key.clone(), (overlay.is_enabled(), overlay.get_metrics()))
})
.collect()
}
pub fn get_total_metrics(&self) -> &OverlayMetrics {
&self.total_metrics
}
pub fn is_performance_budget_exceeded(&self) -> bool {
self.total_metrics.render_time_us > self.performance_budget_us
}
fn overlay_type_to_key(&self, overlay_type: &DebugOverlayType) -> String {
match overlay_type {
DebugOverlayType::EntityHighlight => "entity_highlight".to_string(),
DebugOverlayType::Colliders => "colliders".to_string(),
DebugOverlayType::Transforms => "transforms".to_string(),
DebugOverlayType::SystemFlow => "system_flow".to_string(),
DebugOverlayType::PerformanceMetrics => "performance_metrics".to_string(),
DebugOverlayType::ColliderVisualization => "collider_visualization".to_string(),
DebugOverlayType::TransformGizmos => "transform_gizmos".to_string(),
DebugOverlayType::DebugMarkers => "debug_markers".to_string(),
DebugOverlayType::Custom(name) => format!("custom_{}", name),
}
}
fn update_performance_metrics(
) {
}
fn check_performance_budget(
) {
}
}
impl Default for VisualOverlayManager {
fn default() -> Self {
Self::new()
}
}
pub struct VisualDebugOverlayPlugin {
pub auto_initialize: bool,
}
impl Default for VisualDebugOverlayPlugin {
fn default() -> Self {
Self {
auto_initialize: true,
}
}
}
impl Plugin for VisualDebugOverlayPlugin {
fn build(&self, app: &mut App) {
if self.auto_initialize {
let mut manager = VisualOverlayManager::new();
manager.initialize(app);
app.insert_resource(manager);
} else {
app.insert_resource(VisualOverlayManager::new());
}
}
}
pub use entity_highlight::{EntityHighlightOverlay, HighlightedEntity, HighlightMode, HighlightConfig};
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_overlay_type_to_key() {
let manager = VisualOverlayManager::new();
assert_eq!(
manager.overlay_type_to_key(&DebugOverlayType::EntityHighlight),
"entity_highlight"
);
assert_eq!(
manager.overlay_type_to_key(&DebugOverlayType::Colliders),
"colliders"
);
assert_eq!(
manager.overlay_type_to_key(&DebugOverlayType::Custom("test".to_string())),
"custom_test"
);
}
#[test]
fn test_performance_budget_tracking() {
let manager = VisualOverlayManager::new();
assert_eq!(manager.performance_budget_us, 2000);
assert!(!manager.is_performance_budget_exceeded()); }
}