use std::collections::VecDeque; use super::*; /// Debugging systems, resources, events, etc. pub struct DebuggingPlugin; impl Plugin for DebuggingPlugin { fn build(&self, app: &mut App) { app.init_state::() .init_resource::() .init_resource::() .init_resource::() .init_resource::() .add_plugins(PhysicsDebugPlugin::default()) .add_systems(Startup, init_debug_ui) .add_systems( Update, ( (toggle_state_visibility::,) .run_if(state_changed::), toggle_debug_state.run_if(on_keyboard_press(KeyCode::F12)), (toggle_light_gizmo, toggle_aabb_gizmo).run_if(state_changed::), ( (hover_mesh, hover_ui) .run_if(on_event::>.or(on_event::>)), tooltip_follow.run_if(any_component_changed::), sync_resource_to_ui::.run_if(resource_changed::), track_fps, sync_resource_to_ui::.run_if(resource_changed::), track_entity_count, sync_resource_to_ui::.run_if(resource_changed::), sync_resource_to_ui::.run_if(resource_changed::), ) .run_if(in_state(DebuggingState::On)), ), ); // WebGL2-incompatible systems #[cfg(not(target_arch = "wasm32"))] { app.add_plugins(WireframePlugin::default()) .insert_resource(WireframeConfig { global: false, default_color: MAGENTA.into(), }) .add_systems( Update, toggle_physics_debug_render.run_if(state_changed::), ) .add_systems(OnEnter(DebuggingState::On), enable_wireframe) .add_systems(OnExit(DebuggingState::On), disable_wireframe); } } } /// Tracks if the debugger is on or off for other games systems to hook into /// /// The Debugging state may add it's own global debugging information, but is mostly a shell #[derive(States, Component, Default, Debug, Clone, Hash, Eq, PartialEq)] pub enum DebuggingState { #[default] Off, On, } /// Create the Debugging UI fn init_debug_ui(mut commands: Commands) { // Version string for troubleshooting commands.spawn(( DebuggingState::On, Name::new("Version #"), children![Text::new(VERSION),], GlobalZIndex(i32::MAX - 1), Node { max_width: Val::Percent(50.0), align_self: AlignSelf::End, justify_self: JustifySelf::End, ..default() }, )); commands .spawn(( DebuggingState::On, Name::new("Notice"), children![(Text::new(""), SyncResource::::default(),)], GlobalZIndex(i32::MAX - 1), Node { max_width: Val::Percent(50.0), align_self: AlignSelf::End, justify_self: JustifySelf::Start, ..default() }, )) .observe(close_on_click); // "Turn on Debugging" button commands .spawn(( Node { align_self: AlignSelf::Start, justify_self: JustifySelf::End, flex_direction: FlexDirection::Column, ..default() }, DebuggingState::Off, Name::new("Debug Indicator"), GlobalZIndex(i32::MAX - 1), children![Text("Debug: OFF".into()),], Button, )) .observe(toggle_debug); commands .spawn(( DebuggingState::On, Node { align_self: AlignSelf::Start, justify_self: JustifySelf::End, flex_direction: FlexDirection::Column, ..default() }, )) .with_children(|parent| { parent .spawn(( // Debug is active & button to toggle DebuggingState::On, Name::new("Debug Indicator"), GlobalZIndex(i32::MAX - 1), children![Text("Debug: ON".into()),], Button, )) .observe(toggle_debug); parent.spawn(( // FPS Counter for troubleshooting DebuggingState::On, Name::new("FPS"), GlobalZIndex(i32::MAX - 1), Text::new("FPS: ##.#"), SyncResource::::default(), )); parent.spawn(( // Entity count DebuggingState::On, Name::new("Entity Count"), GlobalZIndex(i32::MAX - 1), Text::new("Entities: ###"), SyncResource::::default(), )); }); // Tooltip commands .spawn(( DebuggingState::On, SyncResource::::default(), Pickable::IGNORE, GlobalZIndex(i32::MAX), Node { position_type: PositionType::Absolute, margin: UiRect { left: Val::Px(20.0), ..default() }, align_content: AlignContent::Center, justify_content: JustifyContent::Center, ..default() }, )) .with_children(|parent| { parent.spawn(( Text("Tooltip Placeholder".into()), SyncResource::::default(), )); }); } /// Toggles the debug state from off -> on // off -> on when triggered fn toggle_debug_state( mut next: ResMut>, curr: Res>, ) { use DebuggingState::*; next.set(match curr.get() { On => Off, Off => On, }); info!("Toggling debug state: {:?} -> {:?}", curr, next); } #[derive(Default, Resource)] pub struct Notice(pub String); impl Display for Notice { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { writeln!(f, "{}", self.0) } } /// Add a generic Tooltip that follows the mouse in debug mode #[derive(Default, Resource)] pub struct ToolTip(HashMap); impl ToolTip { pub fn insert(&mut self, k: &str, v: String) { self.0.insert(k.into(), v); } pub fn remove(&mut self, k: &str) { self.0.remove(k); } } impl Display for ToolTip { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { for (k, v) in self.0.iter() { writeln!(f, "{k}: {v}")? } Ok(()) } } fn tooltip_follow( mut tooltip: Single<(&mut Node, &mut Visibility), (With>, Without)>, window: Single<&Window>, ) { if let Some(Vec2 { x, y }) = window.cursor_position() { *tooltip.1 = Visibility::Inherited; tooltip.0.left = Val::Px(x); tooltip.0.top = Val::Px(y); } else { *tooltip.1 = Visibility::Hidden; } } /// When you hover over a mesh, update the tooltip with some info fn hover_mesh( mut over_events: EventReader>, mut out_events: EventReader>, mut tooltip: ResMut, meshes: Query<(&Transform, Option<&Name>), With>, ) { out_events .read() .filter_map(|Pointer { target, .. }| meshes.contains(*target).then_some(*target)) .for_each(|_| { tooltip.remove("ID"); tooltip.remove("Pos"); tooltip.remove("Name"); }); over_events .read() .filter_map(|Pointer { target, .. }| meshes.contains(*target).then_some(*target)) .for_each(|e| { if let Ok((t, n)) = meshes.get(e) { let pos = (t.translation.x, t.translation.y, t.translation.z); let name = match n { Some(x) => x, None => "N/A", }; tooltip.insert("ID", format!("{e}")); tooltip.insert("Pos", format!("{pos:.3?}")); tooltip.insert("Name", name.into()); } else { warn!("Failed to query data"); } }); } fn hover_ui( mut over_events: EventReader>, mut out_events: EventReader>, mut tooltip: ResMut, nodes: Query, With>, ) { out_events .read() .filter_map(|Pointer { target, .. }| nodes.contains(*target).then_some(*target)) .for_each(|_| { tooltip.remove("ID"); tooltip.remove("Name"); }); over_events .read() .filter_map(|Pointer { target, .. }| nodes.contains(*target).then_some(*target)) .for_each(|e| { if let Ok(n) = nodes.get(e) { let name = match n { Some(x) => x, None => "N/A", }; tooltip.insert("ID", format!("{e}")); tooltip.insert("Name", name.into()); } else { warn!("Failed to query data"); } }); } #[cfg(not(target_arch = "wasm32"))] fn enable_wireframe(mut wireframe_config: ResMut) { wireframe_config.global = true; } #[cfg(not(target_arch = "wasm32"))] fn disable_wireframe(mut wireframe_config: ResMut) { wireframe_config.global = false; } // Toggle the light gizmo config group fn toggle_light_gizmo( state: Res>, mut config_store: ResMut, ) { let (_, light_group) = config_store.config_mut::(); light_group.draw_all = *state.get() == DebuggingState::On; } // Toggle the aabb gizmo config group fn toggle_aabb_gizmo( state: Res>, mut config_store: ResMut, ) { let (_, aabb_group) = config_store.config_mut::(); aabb_group.draw_all = *state.get() == DebuggingState::On; } #[derive(Resource, Default, Debug)] struct Fps(f32); impl Display for Fps { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { writeln!(f, "FPS: {:0.1}", self.0) } } fn track_fps(time: Res