diff --git a/common/src/lib.rs b/common/src/lib.rs index f7c02076f0..05b6f9c569 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -33,6 +33,7 @@ pub mod combat; pub mod comp; #[cfg(not(target_arch = "wasm32"))] pub mod consts; +pub mod debug_info; #[cfg(not(target_arch = "wasm32"))] pub mod depot; #[cfg(not(target_arch = "wasm32"))] pub mod effect; @@ -77,7 +78,6 @@ pub mod uid; #[cfg(not(target_arch = "wasm32"))] pub mod vol; #[cfg(not(target_arch = "wasm32"))] pub mod volumes; -pub mod debug_info; #[cfg(not(target_arch = "wasm32"))] pub use cached_spatial_grid::CachedSpatialGrid; diff --git a/voxygen/egui/src/lib.rs b/voxygen/egui/src/lib.rs index 38f3f684bc..70567a6a17 100644 --- a/voxygen/egui/src/lib.rs +++ b/voxygen/egui/src/lib.rs @@ -10,9 +10,14 @@ use common::{ comp, comp::{Poise, PoiseState}, }; +use core::mem; #[cfg(feature = "use-dyn-lib")] pub use dyn_lib::init; -use egui::{Color32, Grid, ScrollArea, Slider, Ui}; +use egui::{ + plot::{Plot, Value}, + widgets::plot::Curve, + Color32, Grid, ScrollArea, Slider, Ui, +}; use std::{cmp::Ordering, ffi::CStr}; #[cfg(feature = "use-dyn-lib")] @@ -23,10 +28,17 @@ pub fn maintain( egui_state: &mut EguiInnerState, client: &Client, debug_info: &Option, -) { + added_cylinder_shape_id: Option, +) -> EguiActions { #[cfg(not(feature = "use-dyn-lib"))] { - maintain_egui_inner(platform, egui_state, client, debug_info); + return maintain_egui_inner( + platform, + egui_state, + client, + debug_info, + added_cylinder_shape_id, + ); } #[cfg(feature = "use-dyn-lib")] @@ -35,15 +47,14 @@ pub fn maintain( let lib = &lock.as_ref().unwrap().lib; let maintain_fn: libloading::Symbol< - fn(&mut Platform, &mut EguiInnerState, &Client, &Option), - > = unsafe { - //let start = std::time::Instant::now(); - // Overhead of 0.5-5 us (could use hashmap to mitigate if this is an issue) - let f = lib.get(MAINTAIN_EGUI_FN); - //println!("{}", start.elapsed().as_nanos()); - f - } - .unwrap_or_else(|e| { + fn( + &mut Platform, + &mut EguiInnerState, + &Client, + &Option, + Option, + ) -> EguiActions, + > = unsafe { lib.get(MAINTAIN_EGUI_FN) }.unwrap_or_else(|e| { panic!( "Trying to use: {} but had error: {:?}", CStr::from_bytes_with_nul(MAINTAIN_EGUI_FN) @@ -54,14 +65,66 @@ pub fn maintain( ) }); - maintain_fn(platform, egui_state, client, debug_info); + return maintain_fn( + platform, + egui_state, + client, + debug_info, + added_cylinder_shape_id, + ); + } +} + +pub struct SelectedEntityInfo { + entity_id: u32, + debug_shape_id: Option, +} + +impl SelectedEntityInfo { + fn new(entity_id: u32) -> Self { + Self { + entity_id, + debug_shape_id: None, + } } } pub struct EguiInnerState { - pub read_ecs: bool, - pub selected_entity_id: u32, - pub max_entity_distance: f32, + read_ecs: bool, + selected_entity_info: Option, + max_entity_distance: f32, + selected_entity_cylinder_height: f32, + frame_times: Vec, +} + +impl EguiInnerState { + pub fn new() -> Self { + Self { + read_ecs: false, + selected_entity_info: None, + max_entity_distance: 100000.0, + selected_entity_cylinder_height: 10.0, + frame_times: Vec::new(), + } + } +} + +pub enum DebugShapeAction { + AddCylinder { + radius: f32, + height: f32, + }, + SetPosAndColor { + id: u64, + pos: [f32; 4], + color: [f32; 4], + }, + RemoveCylinder(u64), +} + +#[derive(Default)] +pub struct EguiActions { + pub actions: Vec, } #[cfg_attr(feature = "be-dyn-lib", export_name = "maintain_egui_inner")] @@ -70,27 +133,54 @@ pub fn maintain_egui_inner( egui_state: &mut EguiInnerState, client: &Client, debug_info: &Option, -) { + added_cylinder_shape_id: Option, +) -> EguiActions { platform.begin_frame(); + let mut egui_actions = EguiActions::default(); + let mut previous_selected_entity: Option = None; + let mut max_entity_distance = egui_state.max_entity_distance; + let mut selected_entity_cylinder_height = egui_state.selected_entity_cylinder_height; + + // If a debug cylinder was added in the last frame, store it against the + // selected entity + if let Some(shape_id) = added_cylinder_shape_id { + if let Some(selected_entity) = &mut egui_state.selected_entity_info { + selected_entity.debug_shape_id = Some(shape_id); + } + } + + debug_info.as_ref().map(|x| { + egui_state.frame_times.push(x.frame_time.as_nanos() as f32); + if egui_state.frame_times.len() > 250 { + egui_state.frame_times.remove(0); + } + }); if egui_state.read_ecs { let ecs = client.state().ecs(); let positions = client.state().ecs().read_storage::(); let client_pos = positions.get(client.entity()); - let mut max_entity_distance = egui_state.max_entity_distance; + egui::Window::new("ECS Entities") .default_width(500.0) .default_height(500.0) .show(&platform.context(), |ui| { ui.label(format!("Entity count: {}", &ecs.entities().join().count())); ui.add( - Slider::new(&mut max_entity_distance, 1.0..=17000.0) + Slider::new(&mut max_entity_distance, 1.0..=100000.0) .logarithmic(true) .clamp_to_range(true) .text("Max entity distance"), ); + ui.add( + Slider::new(&mut selected_entity_cylinder_height, 0.1..=100.0) + .logarithmic(true) + .clamp_to_range(true) + .text("Cylinder height"), + ); + let mut scroll_area = ScrollArea::from_max_height(800.0); let (current_scroll, max_scroll) = scroll_area.show(ui, |ui| { // if scroll_top { @@ -108,16 +198,17 @@ pub fn maintain_egui_inner( ui.label("Body"); ui.label("Poise"); ui.end_row(); - for (entity, body, pos, ori, vel, poise) in ( + for (entity, body, stats, pos, ori, vel, poise) in ( &ecs.entities(), ecs.read_storage::().maybe(), + ecs.read_storage::().maybe(), ecs.read_storage::().maybe(), ecs.read_storage::().maybe(), ecs.read_storage::().maybe(), ecs.read_storage::().maybe(), ) .join() - .filter(|(_, _, pos, _, _, _)| { + .filter(|(_, _, _, pos, _, _, _)| { client_pos.map_or(true, |client_pos| { pos.map_or(0.0, |pos| pos.0.distance_squared(client_pos.0)) < max_entity_distance @@ -130,28 +221,40 @@ pub fn maintain_egui_inner( // }) { if ui.button("View").clicked() { - egui_state.selected_entity_id = entity.id(); + previous_selected_entity = + mem::take(&mut egui_state.selected_entity_info); + + if pos.is_some() { + egui_actions.actions.push(DebugShapeAction::AddCylinder { + radius: 1.0, + height: egui_state.selected_entity_cylinder_height, + }); + } + egui_state.selected_entity_info = + Some(SelectedEntityInfo::new(entity.id())); } ui.label(format!("{}", entity.id())); + if let Some(pos) = pos { ui.label(format!( - "{:.3},{:.3},{:.3}", + "{:.0},{:.0},{:.0}", pos.0.x, pos.0.y, pos.0.z )); } else { ui.label("-"); } + if let Some(vel) = vel { ui.label(format!( - "{:.3},{:.3},{:.3}", + "{:.1},{:.1},{:.1}", vel.0.x, vel.0.y, vel.0.z )); } else { ui.label("-"); } - if let Some(body) = body { - ui.label(format!("{:?}", body)); + if let Some(stats) = stats { + ui.label(&stats.name); } else { ui.label("-"); } @@ -174,42 +277,41 @@ pub fn maintain_egui_inner( (current_scroll, max_scroll) }); }); - egui_state.max_entity_distance = max_entity_distance; - let selected_entity = ecs.entities().entity(egui_state.selected_entity_id); - if selected_entity.gen().is_alive() { - egui::Window::new("Selected Entity") - .default_width(300.0) - .default_height(200.0) - .show(&platform.context(), |ui| { - ui.horizontal_wrapped(|ui| { - for (entity, body, pos, ori, vel, poise, buffs) in ( - &ecs.entities(), - ecs.read_storage::().maybe(), - ecs.read_storage::().maybe(), - ecs.read_storage::().maybe(), - ecs.read_storage::().maybe(), - ecs.read_storage::().maybe(), - ecs.read_storage::().maybe(), - ) - .join() - .filter(|(e, _, _, _, _, _, _)| e.id() == egui_state.selected_entity_id) - { - if let Some(body) = body { - ui.group(|ui| { - ui.vertical(|ui| { - ui.label("Body"); - Grid::new("selected_entity_body_grid") - .spacing([40.0, 4.0]) - .max_col_width(100.0) - .striped(true) - .show(ui, |ui| { - ui.label("Type"); - ui.label(format!("{:?}", body)); - }); - }); - }); - } + if let Some(selected_entity_info) = &mut egui_state.selected_entity_info { + let selected_entity = ecs.entities().entity(selected_entity_info.entity_id); + if !selected_entity.gen().is_alive() { + previous_selected_entity = mem::take(&mut egui_state.selected_entity_info); + } else { + for (entity, body, stats, pos, ori, vel, poise, buffs) in ( + &ecs.entities(), + ecs.read_storage::().maybe(), + ecs.read_storage::().maybe(), + ecs.read_storage::().maybe(), + ecs.read_storage::().maybe(), + ecs.read_storage::().maybe(), + ecs.read_storage::().maybe(), + ecs.read_storage::().maybe(), + ) + .join() + .filter(|(e, _, _, _, _, _, _, _)| e.id() == selected_entity_info.entity_id) + { + egui::Window::new(format!( + "Selected Entity - {}", + stats.as_ref().map_or("", |x| &x.name) + )) + .default_width(300.0) + .default_height(200.0) + .show(&platform.context(), |ui| { + ui.horizontal_wrapped(|ui| { + 9- if let Some(pos) = pos { + if let Some(shape_id) = selected_entity_info.debug_shape_id { + egui_actions.actions.push(DebugShapeAction::SetPosAndColor { + id: shape_id, + color: [1.0, 1.0, 0.0, 0.5], + pos: [pos.0.x, pos.0.y, pos.0.z + 2.0, 0.0], + }); + } ui.group(|ui| { ui.vertical(|ui| { ui.label("Pos"); @@ -284,9 +386,10 @@ pub fn maintain_egui_inner( }); }); } - } + }); }); - }); + } + } } } @@ -305,6 +408,46 @@ pub fn maintain_egui_inner( egui_state.read_ecs = true; } }); + + egui::Window::new("Frame Time") + .default_width(200.0) + .default_height(200.0) + .show(&platform.context(), |ui| { + let plot = Plot::default().curve(Curve::from_values_iter( + egui_state + .frame_times + .iter() + .enumerate() + .map(|(i, x)| Value::new(i as f64, *x)), + )); + ui.add(plot); + }); + + if let Some(previous) = previous_selected_entity { + if let Some(debug_shape_id) = previous.debug_shape_id { + egui_actions + .actions + .push(DebugShapeAction::RemoveCylinder(debug_shape_id)); + } + }; + + if let Some(selected_entity) = &egui_state.selected_entity_info { + if let Some(debug_shape_id) = selected_entity.debug_shape_id { + if egui_state.selected_entity_cylinder_height != selected_entity_cylinder_height { + egui_actions + .actions + .push(DebugShapeAction::RemoveCylinder(debug_shape_id)); + egui_actions.actions.push(DebugShapeAction::AddCylinder { + radius: 1.0, + height: selected_entity_cylinder_height, + }); + } + } + }; + + egui_state.max_entity_distance = max_entity_distance; + egui_state.selected_entity_cylinder_height = selected_entity_cylinder_height; + egui_actions } fn poise_state_label(ui: &mut Ui, poise: &Poise) { diff --git a/voxygen/src/scene/debug.rs b/voxygen/src/scene/debug.rs index e4ddf857ac..1bca73dca8 100644 --- a/voxygen/src/scene/debug.rs +++ b/voxygen/src/scene/debug.rs @@ -50,7 +50,7 @@ impl DebugShape { } #[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)] -pub struct DebugShapeId(u64); +pub struct DebugShapeId(pub u64); pub struct Debug { next_shape_id: DebugShapeId, diff --git a/voxygen/src/session/mod.rs b/voxygen/src/session/mod.rs index 0690a5ae4e..c069eeb8ac 100644 --- a/voxygen/src/session/mod.rs +++ b/voxygen/src/session/mod.rs @@ -44,10 +44,10 @@ use crate::{ window::{AnalogGameInput, Event, GameInput}, Direction, Error, GlobalState, PlayState, PlayStateResult, }; +use common::debug_info::DebugInfo; use egui_wgpu_backend::epi::App; use hashbrown::HashMap; use settings_change::Language::ChangeLanguage; -use common::debug_info::DebugInfo; /// The action to perform after a tick enum TickAction { @@ -1016,7 +1016,9 @@ impl PlayState for SessionState { ); // Maintain egui (debug interface) - global_state.egui_state.maintain(&self.client.borrow(), &debug_info,); + global_state + .egui_state + .maintain(&self.client.borrow(), &mut self.scene, &debug_info); // Look for changes in the localization files if global_state.i18n.reloaded() { @@ -1457,10 +1459,7 @@ impl PlayState for SessionState { drop(third_pass); - drawer.draw_egui( - &mut global_state.egui_state.platform, - scale_factor, - ); + drawer.draw_egui(&mut global_state.egui_state.platform, scale_factor); } } diff --git a/voxygen/src/ui/egui/mod.rs b/voxygen/src/ui/egui/mod.rs index 7797cda222..572352ae69 100644 --- a/voxygen/src/ui/egui/mod.rs +++ b/voxygen/src/ui/egui/mod.rs @@ -1,13 +1,18 @@ -use crate::window::Window; +use crate::{ + scene::{DebugShape, DebugShapeId, Scene}, + window::Window, +}; use client::Client; use common::debug_info::DebugInfo; +use core::mem; use egui::FontDefinitions; use egui_winit_platform::{Platform, PlatformDescriptor}; -use voxygen_egui::EguiInnerState; +use voxygen_egui::{DebugShapeAction, EguiInnerState}; pub struct EguiState { pub platform: Platform, egui_inner_state: EguiInnerState, + new_debug_shape_id: Option, } impl EguiState { @@ -22,20 +27,36 @@ impl EguiState { Self { platform, - egui_inner_state: EguiInnerState { - read_ecs: false, - selected_entity_id: 0, - max_entity_distance: 17000.0, - }, + egui_inner_state: EguiInnerState::new(), + new_debug_shape_id: None, } } - pub fn maintain(&mut self, client: &Client, debug_info: &Option) { - voxygen_egui::maintain( + pub fn maintain(&mut self, client: &Client, scene: &mut Scene, debug_info: &Option) { + let egui_actions = voxygen_egui::maintain( &mut self.platform, &mut self.egui_inner_state, client, debug_info, + mem::take(&mut self.new_debug_shape_id), ); + + egui_actions.actions.iter().for_each(|action| match action { + DebugShapeAction::AddCylinder { height, radius } => { + let shape_id = scene.debug.add_shape(DebugShape::Cylinder { + height: *height, + radius: *radius, + }); + self.new_debug_shape_id = Some(shape_id.0); + }, + DebugShapeAction::RemoveCylinder(debug_shape_id) => { + scene.debug.remove_shape(DebugShapeId(*debug_shape_id)); + }, + DebugShapeAction::SetPosAndColor { id, pos, color } => { + scene + .debug + .set_pos_and_color(DebugShapeId(*id), pos.clone(), color.clone()); + }, + }) } }