diff --git a/client/src/lib.rs b/client/src/lib.rs index 1603618a92..ec1115f532 100644 --- a/client/src/lib.rs +++ b/client/src/lib.rs @@ -86,7 +86,7 @@ pub struct Client { last_server_ping: f64, last_server_pong: f64, last_ping_delta: f64, - ping_deltas: VecDeque, + pub ping_deltas: VecDeque, tick: u64, state: State, diff --git a/common/src/cmd.rs b/common/src/cmd.rs index 812af83795..ad60904158 100644 --- a/common/src/cmd.rs +++ b/common/src/cmd.rs @@ -150,7 +150,7 @@ lazy_static! { .collect(); /// List of item specifiers. Useful for tab completing - static ref ITEM_SPECS: Vec = { + pub static ref ITEM_SPECS: Vec = { let path = assets::ASSETS_PATH.join("common").join("items"); let mut items = vec![]; fn list_items (path: &Path, base: &Path, mut items: &mut Vec) -> std::io::Result<()>{ diff --git a/voxygen/src/hud/mod.rs b/voxygen/src/hud/mod.rs index 1a004ab25a..d33e90b9ec 100644 --- a/voxygen/src/hud/mod.rs +++ b/voxygen/src/hud/mod.rs @@ -232,7 +232,7 @@ pub struct DebugInfo { pub velocity: Option, pub ori: Option, pub num_chunks: u32, - pub num_visible_chunks: u32, + pub num_chunks_visible: u32, pub num_figures: u32, pub num_figures_visible: u32, } @@ -633,7 +633,7 @@ impl Hud { &mut self, client: &Client, global_state: &GlobalState, - debug_info: DebugInfo, + debug_info: &DebugInfo, dt: Duration, info: HudInfo, camera: &Camera, @@ -1279,7 +1279,8 @@ impl Hud { } // Display debug window. - if global_state.settings.gameplay.toggle_debug { + // Temporarily disabled while testing ImgUi + if false { //global_state.settings.gameplay.toggle_debug { // Alpha Version Text::new(&version) .top_left_with_margins_on(ui_widgets.window, 5.0, 5.0) @@ -1386,7 +1387,7 @@ impl Hud { // Number of chunks Text::new(&format!( "Chunks: {} ({} visible)", - debug_info.num_chunks, debug_info.num_visible_chunks, + debug_info.num_chunks, debug_info.num_chunks_visible, )) .color(TEXT_COLOR) .down_from(self.ids.entity_count, 5.0) @@ -2293,7 +2294,7 @@ impl Hud { &mut self, client: &Client, global_state: &mut GlobalState, - debug_info: DebugInfo, + debug_info: &DebugInfo, camera: &Camera, dt: Duration, info: HudInfo, diff --git a/voxygen/src/lib.rs b/voxygen/src/lib.rs index ccec393b41..b2ef42d2bd 100644 --- a/voxygen/src/lib.rs +++ b/voxygen/src/lib.rs @@ -51,6 +51,7 @@ pub struct GlobalState { pub singleplayer: Option, // TODO: redo this so that the watcher doesn't have to exist for reloading to occur pub localization_watcher: watch::ReloadIndicator, + pub imgui_render_required: bool } impl GlobalState { diff --git a/voxygen/src/main.rs b/voxygen/src/main.rs index d119fdb788..c9c0c07048 100644 --- a/voxygen/src/main.rs +++ b/voxygen/src/main.rs @@ -165,6 +165,7 @@ fn main() { #[cfg(feature = "singleplayer")] singleplayer: None, localization_watcher, + imgui_render_required: false }; run::run(global_state, event_loop); diff --git a/voxygen/src/run.rs b/voxygen/src/run.rs index 14e19ca544..ce386c5237 100644 --- a/voxygen/src/run.rs +++ b/voxygen/src/run.rs @@ -79,7 +79,6 @@ fn handle_main_events_cleared( let mut exit = true; while let Some(state_result) = states.last_mut().map(|last| { let events = global_state.window.fetch_events(); - global_state.window.imgui_begin_frame(); last.tick(global_state, events) }) { // Implement state transfer logic. @@ -144,8 +143,9 @@ fn handle_main_events_cleared( last.render(global_state.window.renderer_mut(), &global_state.settings); - if global_state.settings.gameplay.toggle_debug { + if global_state.imgui_render_required && global_state.settings.gameplay.toggle_debug { global_state.window.imgui_render(); + global_state.imgui_render_required = false; } global_state.window.renderer_mut().flush(); diff --git a/voxygen/src/session.rs b/voxygen/src/session.rs index 7b2587d884..7977953ca0 100644 --- a/voxygen/src/session.rs +++ b/voxygen/src/session.rs @@ -11,6 +11,7 @@ use crate::{ window::{AnalogGameInput, Event, GameInput}, Direction, Error, GlobalState, PlayState, PlayStateResult, }; +use chrono::NaiveTime; use client::{self, Client}; use common::{ assets::{load_expect, load_watched}, @@ -21,15 +22,15 @@ use common::{ }, event::EventBus, msg::ClientState, - terrain::{Block, BlockKind}, + terrain::{Block, BlockKind, TerrainChunk}, util::Dir, - vol::ReadVol, + vol::{ReadVol, RectRasterableVol} }; use specs::{Join, WorldExt}; use std::{cell::RefCell, rc::Rc, time::Duration}; use tracing::{error, info}; use vek::*; -use imgui::{Condition, im_str, Window}; +use imgui::{Condition, im_str, Window, ImString, ImStr}; /// The action to perform after a tick enum TickAction { @@ -53,6 +54,24 @@ pub struct SessionState { free_look: bool, auto_walk: bool, is_aiming: bool, + imgui_state: ImgUiState +} + +pub struct ImgUiState { + give_item_quantity: Rc>, + items_list_selected_item: Rc>, + /// Stores pending hud events from the last frame + pending_hud_events: Rc>> +} + +impl Default for ImgUiState { + fn default() -> Self { + ImgUiState { + give_item_quantity: Rc::new(RefCell::new(1)), + items_list_selected_item: Rc::new(RefCell::new(0)), + pending_hud_events: Rc::new(RefCell::new(Vec::::new())) + } + } } /// Represents an active game session (i.e., the one being played). @@ -86,6 +105,7 @@ impl SessionState { free_look: false, auto_walk: false, is_aiming: false, + imgui_state: ImgUiState::default() } } @@ -657,42 +677,44 @@ impl PlayState for SessionState { .camera_mut() .compute_dependents(&*self.client.borrow().state().terrain()); + let debug_info = DebugInfo { + tps: global_state.clock.get_tps(), + ping_ms: self.client.borrow().get_ping_ms_rolling_avg(), + coordinates: self + .client + .borrow() + .state() + .ecs() + .read_storage::() + .get(self.client.borrow().entity()) + .cloned(), + velocity: self + .client + .borrow() + .state() + .ecs() + .read_storage::() + .get(self.client.borrow().entity()) + .cloned(), + ori: self + .client + .borrow() + .state() + .ecs() + .read_storage::() + .get(self.client.borrow().entity()) + .cloned(), + num_chunks: self.scene.terrain().chunk_count() as u32, + num_chunks_visible: self.scene.terrain().visible_chunk_count() as u32, + num_figures: self.scene.figure_mgr().figure_count() as u32, + num_figures_visible: self.scene.figure_mgr().figure_count_visible() as u32, + }; + // Extract HUD events ensuring the client borrow gets dropped. let mut hud_events = self.hud.maintain( &self.client.borrow(), global_state, - DebugInfo { - tps: global_state.clock.get_tps(), - ping_ms: self.client.borrow().get_ping_ms_rolling_avg(), - coordinates: self - .client - .borrow() - .state() - .ecs() - .read_storage::() - .get(self.client.borrow().entity()) - .cloned(), - velocity: self - .client - .borrow() - .state() - .ecs() - .read_storage::() - .get(self.client.borrow().entity()) - .cloned(), - ori: self - .client - .borrow() - .state() - .ecs() - .read_storage::() - .get(self.client.borrow().entity()) - .cloned(), - num_chunks: self.scene.terrain().chunk_count() as u32, - num_visible_chunks: self.scene.terrain().visible_chunk_count() as u32, - num_figures: self.scene.figure_mgr().figure_count() as u32, - num_figures_visible: self.scene.figure_mgr().figure_count_visible() as u32, - }, + &debug_info, &self.scene.camera(), global_state.clock.get_last_delta(), HudInfo { @@ -710,20 +732,157 @@ impl PlayState for SessionState { } if global_state.settings.gameplay.toggle_debug { - global_state.window.imgui_run_ui = Box::new(|ui| { - Window::new(im_str!("Veloren ImgUi Test")) - .size([300.0, 100.0], Condition::FirstUseEver) + global_state.window.imgui_begin_frame(); + global_state.imgui_render_required = true; + + // These variables are set outside of the creation of the imgui_run_ui closure below + // to avoid lifetime issues. They are all moved into the closure due to the use of `move` in + // the closure definition. + let loaded_distance = self.client.borrow().loaded_distance(); + let is_admin = self.client.borrow().is_admin(); + + //let ping_deltas = self.client.borrow().ping_deltas.iter().map(|x| *x as f32).collect::>(); + + let version = format!( + "{}-{}", + env!("CARGO_PKG_VERSION"), + common::util::GIT_VERSION.to_string() + ); + let time_in_seconds = self.client.borrow().state().get_time_of_day(); + let current_time = NaiveTime::from_num_seconds_from_midnight( + // Wraps around back to 0s if it exceeds 24 hours (24 hours = 86400s) + (time_in_seconds as u64 % 86400) as u32, + 0, + ); + let entity_count = self.client.borrow().state().ecs().entities().join().count(); + + // Push the hud events generated by the last imgui frame (button clicks etc) + for event in self.imgui_state.pending_hud_events.borrow_mut().drain(..) { + hud_events.push(event); + } + + // TODO: Only do this once + let items: Vec = common::cmd::ITEM_SPECS.iter().map(|x| ImString::new(x)).collect(); + + // ImgUi state is handled with Rc> because the ImgUi window requires mutable + // access to the values that represent the state of UI controls. + let hud_events = Rc::clone(&self.imgui_state.pending_hud_events); + let items_list_selected_item = Rc::clone(&self.imgui_state.items_list_selected_item); + let give_item_quantity = Rc::clone(&self.imgui_state.give_item_quantity); + + global_state.window.imgui_run_ui = Box::new(move |ui| { + ui.show_demo_window(&mut true); + + Window::new(im_str!("Debug Info")) + .size([356.0, 322.0], Condition::FirstUseEver) + .position([10.0, 90.0], Condition::FirstUseEver) .build(ui, || { - ui.text(im_str!("Hello world!")); - ui.text(im_str!("こんにちは世界!")); - ui.text(im_str!("This...is...imgui-rs!")); + ui.label_text(im_str!("Version"), &im_str!("{}", version)); + ui.separator(); + + ui.label_text(im_str!("FPS"), &im_str!("{:.0}", debug_info.tps)); + ui.label_text(im_str!("Ping"), &im_str!("{:.0}", debug_info.ping_ms)); + + // TODO: Ping graph works but looks crap with the current 10 historical values stored + // ui.plot_lines(im_str!("Ping"), &ping_deltas.clone()) + // .graph_size([300.0, 75.0]) + // .build(); + ui.separator(); + + // Time + ui.label_text(im_str!("Time"), &im_str!("{}", current_time.format("%H:%M").to_string())); + + // Player Coords + let coords_text = match debug_info.coordinates { + Some(coordinates) => format!( + "({:.0}, {:.0}, {:.0})", + coordinates.0.x, coordinates.0.y, coordinates.0.z, + ), + None => "Player has no Pos component".to_owned(), + }; + ui.label_text(im_str!("Coords"), &im_str!("{}", coords_text)); + + // Player Velocity + let velocity_text = match debug_info.velocity { + Some(velocity) => format!( + "({:.1}, {:.1}, {:.1}) [{:.1} u/s]", + velocity.0.x, + velocity.0.y, + velocity.0.z, + velocity.0.magnitude() + ), + None => "Player has no Vel component".to_owned(), + }; + ui.label_text(im_str!("Velocity"), &im_str!("{}", velocity_text)); + + // Player Orientation + let orientation_text = match debug_info.ori { + Some(ori) => format!( + "({:.1}, {:.1}, {:.1})", + ori.0.x, ori.0.y, ori.0.z, + ), + None => "Player has no Ori component".to_owned(), + }; + ui.label_text(im_str!("Orientation"), &im_str!("{}", orientation_text)); + + ui.separator(); + + // View Distance + ui.label_text(im_str!("View Distance"), &im_str!("{:.2} blocks ({:.2} chunks)", + loaded_distance, + loaded_distance / TerrainChunk::RECT_SIZE.x as f32)); + + // Entities + + ui.label_text(im_str!("Entities"), &im_str!("{}", entity_count)); + + // Chunks + ui.label_text(im_str!("Chunks"), &im_str!("{} ({} visible)", debug_info.num_chunks, debug_info.num_chunks_visible)); + + // Figures + ui.label_text(im_str!("Figures"), &im_str!("{} ({} visible)", debug_info.num_figures, debug_info.num_figures_visible)); + ui.separator(); + + // Mouse Position let mouse_pos = ui.io().mouse_pos; ui.text(format!( "Mouse Position: ({:.1},{:.1})", mouse_pos[0], mouse_pos[1] )); + }); + + if is_admin { + Window::new(im_str!("Admin Commands")) + .collapsed(true, Condition::FirstUseEver) + .size([600.0, 400.0], Condition::FirstUseEver) + .position([400.0, 20.0], Condition::FirstUseEver) + .build(ui, || { + ui.list_box::(&im_str!("##"), &mut items_list_selected_item.borrow_mut(), &items.iter().map(|x| x.as_ref()).collect::>(), 10); + + ui.set_next_item_width(75.0); + ui.input_int(im_str!("Quantity"), &mut give_item_quantity.borrow_mut()).build(); + + ui.same_line(155.0); + if ui.button(im_str!("Give item"), [70.0, 20.0]) { + if let Some(selected_item) = items.iter().nth(*items_list_selected_item.borrow() as usize) { + let item_name = selected_item.to_str(); + hud_events.borrow_mut().push(HudEvent::SendMessage(format!("/give_item {} {}", item_name, give_item_quantity.borrow()))); + } + } + + if ui.button(im_str!("Explosion!"), [100.0, 20.0]) { + hud_events.borrow_mut().push(HudEvent::SendMessage(format!("/explosion"))); + } + if ui.button(im_str!("Set Waypoint"), [100.0, 20.0]) { + hud_events.borrow_mut().push(HudEvent::SendMessage(format!("/waypoint"))); + } + if ui.button(im_str!("Spawn Dummy"), [100.0, 20.0]) { + hud_events.borrow_mut().push(HudEvent::SendMessage(format!("/dummy"))); + } + }); + } }); } diff --git a/voxygen/src/window.rs b/voxygen/src/window.rs index 60a94b1b18..4c60139b59 100644 --- a/voxygen/src/window.rs +++ b/voxygen/src/window.rs @@ -678,8 +678,10 @@ impl Window { pub fn fetch_events(&mut self) -> Vec { // Refresh ui size (used when changing playstates) if self.needs_refresh_resize { + let logical_size = self.logical_size(); self.events - .push(Event::Ui(ui::Event::new_resize(self.logical_size()))); + .push(Event::Ui(ui::Event::new_resize(logical_size))); + self.imgui.io_mut().display_size = [logical_size.x as f32, logical_size.y as f32]; self.needs_refresh_resize = false; }