diff --git a/Cargo.lock b/Cargo.lock index 7052ceb..36c3a4e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -714,6 +714,7 @@ dependencies = [ "crossbeam", "galactica-content", "galactica-galaxy", + "galactica-util", "nalgebra", "rand", "rapier2d", diff --git a/crates/galactica/src/game.rs b/crates/galactica/src/game.rs index 0b01177..1548f30 100644 --- a/crates/galactica/src/game.rs +++ b/crates/galactica/src/game.rs @@ -121,11 +121,11 @@ impl Game { pub fn update(&mut self) { let t: f32 = self.last_update.elapsed().as_secs_f32() * self.time_scale; - self.timing.start(); + self.timing.start_frame(); + self.timing.start_galaxy(); self.galaxy.step(t); self.timing.mark_galaxy(); - self.timing.start(); self.systemsim.step(StepResources { player: self.player, player_controls: ShipControls { @@ -137,9 +137,9 @@ impl Game { ct: &self.content, gx: &mut self.galaxy, particles: &mut self.new_particles, + timing: &mut self.timing, t, }); - self.timing.mark_physics(); if self.input.v_scroll != 0.0 { self.camera.zoom = (self.camera.zoom + self.input.v_scroll).clamp(ZOOM_MIN, ZOOM_MAX); @@ -153,6 +153,7 @@ impl Game { }; self.last_update = Instant::now(); + self.timing.mark_frame(); } pub fn get_frame_state(&mut self) -> RenderInput { @@ -166,7 +167,7 @@ impl Game { player_data: self.player, data: &self.galaxy, current_system: SystemHandle { index: 0 }, - timing: &self.timing, + timing: &mut self.timing, } } } diff --git a/crates/render/src/datastructs.rs b/crates/render/src/datastructs.rs index a72ba02..ceab9b2 100644 --- a/crates/render/src/datastructs.rs +++ b/crates/render/src/datastructs.rs @@ -41,7 +41,7 @@ pub struct RenderInput<'a> { pub particles: &'a mut Vec, /// Time we spent in each part of the game loop - pub timing: &'a Timing, + pub timing: &'a mut Timing, } /// Renderer state. A reference to this struct is often passed to helper functions. diff --git a/crates/render/src/gpustate/render.rs b/crates/render/src/gpustate/render.rs index 2ebd2d1..4fbcfbc 100644 --- a/crates/render/src/gpustate/render.rs +++ b/crates/render/src/gpustate/render.rs @@ -19,7 +19,7 @@ use crate::{ impl super::GPUState { /// Main render function. Draws sprites on a window. pub fn render(&mut self, input: RenderInput) -> Result<(), wgpu::SurfaceError> { - // Set up text renderer + input.timing.start_render(); let output = self.surface.get_current_texture()?; let view = output.texture.create_view(&Default::default()); @@ -214,6 +214,7 @@ impl super::GPUState { self.state.queue.submit(iter::once(encoder.finish())); output.present(); + input.timing.mark_render(); Ok(()) } } diff --git a/crates/render/src/ui/fpsindicator.rs b/crates/render/src/ui/fpsindicator.rs index 3d78f2c..5b7aeb3 100644 --- a/crates/render/src/ui/fpsindicator.rs +++ b/crates/render/src/ui/fpsindicator.rs @@ -1,11 +1,9 @@ use glyphon::{Attrs, Buffer, Color, Family, Metrics, Shaping, TextArea, TextBounds}; -use std::time::Instant; use crate::{datastructs::RenderState, RenderInput}; pub(super) struct FpsIndicator { buffer: Buffer, - last_update: Instant, update_counter: u32, } @@ -21,7 +19,6 @@ impl FpsIndicator { Self { buffer, - last_update: Instant::now(), update_counter: 0, } } @@ -39,15 +36,16 @@ impl FpsIndicator { self.buffer.set_text( &mut state.text_font_system, &format!( - "Game: {:.02?}\nPhys: {:.02?}\nRender: {:.02?}", - 1.0 / input.timing.galaxy, - 1.0 / input.timing.physics, - 1.0 / (self.last_update.elapsed().as_secs_f32() / 100.0) + "Frame: {:04.02?}%\nGame: {:04.02?}%\nShips: {:04.02?}%\nPhys: {:04.02?}%\nRender: {:.02?}", + 100.0 * (input.timing.frame / input.timing.render), + 100.0 * (input.timing.galaxy / input.timing.frame), + 100.0 * (input.timing.physics_sim / input.timing.frame), + 100.0 * (input.timing.physics_ship / input.timing.frame), + 1.0 / input.timing.render ), - Attrs::new().family(Family::SansSerif), + Attrs::new().family(Family::Monospace), Shaping::Basic, ); - self.last_update = Instant::now(); } pub fn get_textarea(&self) -> TextArea { diff --git a/crates/render/src/ui/manager.rs b/crates/render/src/ui/manager.rs index 6ed0c87..ee1778a 100644 --- a/crates/render/src/ui/manager.rs +++ b/crates/render/src/ui/manager.rs @@ -1,8 +1,7 @@ use glyphon::TextArea; -use crate::{datastructs::RenderState, RenderInput}; - use super::{fpsindicator::FpsIndicator, radar::Radar, status::Status}; +use crate::{datastructs::RenderState, RenderInput}; pub struct UiManager { radar: Radar, @@ -26,7 +25,7 @@ impl UiManager { self.fps.update(input, state); } - pub fn get_textareas(&self) -> impl Iterator { - (0..1).map(|_| self.fps.get_textarea()) + pub fn get_textareas(&self) -> [TextArea; 1] { + [self.fps.get_textarea()] } } diff --git a/crates/systemsim/Cargo.toml b/crates/systemsim/Cargo.toml index bd4e13e..c706517 100644 --- a/crates/systemsim/Cargo.toml +++ b/crates/systemsim/Cargo.toml @@ -19,6 +19,7 @@ workspace = true [dependencies] galactica-content = { workspace = true } galactica-galaxy = { workspace = true } +galactica-util = { workspace = true } rapier2d = { workspace = true } nalgebra = { workspace = true } diff --git a/crates/systemsim/src/stepresources.rs b/crates/systemsim/src/stepresources.rs index 95d888d..93c41ba 100644 --- a/crates/systemsim/src/stepresources.rs +++ b/crates/systemsim/src/stepresources.rs @@ -1,6 +1,7 @@ use crate::{objects::ShipControls, ParticleBuilder}; use galactica_content::Content; use galactica_galaxy::{Galaxy, GxShipHandle}; +use galactica_util::timing::Timing; /// External resources we need to compute time steps #[derive(Debug)] @@ -22,4 +23,7 @@ pub struct StepResources<'a> { /// The ship that the player controls pub player: GxShipHandle, + + /// Record detailed frame timing breakdown + pub timing: &'a mut Timing, } diff --git a/crates/systemsim/src/systemsim.rs b/crates/systemsim/src/systemsim.rs index eee6515..2c7102b 100644 --- a/crates/systemsim/src/systemsim.rs +++ b/crates/systemsim/src/systemsim.rs @@ -385,7 +385,11 @@ impl SystemSim { /// Step this physics system by `t` seconds pub fn step(&mut self, mut res: StepResources) { + res.timing.start_physics_ships(); self.step_ships(&mut res); + res.timing.mark_physics_ships(); + + res.timing.start_physics_sim(); // Update physics self.wrapper.step(res.t, &self.collision_handler); @@ -457,6 +461,8 @@ impl SystemSim { } }; } + + res.timing.mark_physics_sim(); } } diff --git a/crates/util/src/timing.rs b/crates/util/src/timing.rs index 0531a98..20a43c9 100644 --- a/crates/util/src/timing.rs +++ b/crates/util/src/timing.rs @@ -3,42 +3,96 @@ use std::time::Instant; /// Utility struct. /// Keeps track of the time we spent in each part of the game loop. +#[derive(Debug)] pub struct Timing { - timer: Option, + /// The time we spent processing the whole frame + pub frame: f32, + frame_timer: Instant, /// The time we spent simulating game state pub galaxy: f32, + galaxy_timer: Instant, /// The time we spent simulating physics - pub physics: f32, + pub physics_sim: f32, + physics_sim_timer: Instant, + + /// The time we spent updating physics ships + pub physics_ship: f32, + physics_ship_timer: Instant, + + /// The time we spent simulating physics + pub render: f32, + render_timer: Instant, } +// TODO: macros +// TODO: document each duration + impl Timing { /// Create a new timing struct pub fn new() -> Self { Self { - timer: None, + frame: f32::NAN, galaxy: f32::NAN, - physics: f32::NAN, + physics_sim: f32::NAN, + physics_ship: f32::NAN, + render: f32::NAN, + galaxy_timer: Instant::now(), + physics_sim_timer: Instant::now(), + physics_ship_timer: Instant::now(), + render_timer: Instant::now(), + frame_timer: Instant::now(), } } - /// Start the timer - pub fn start(&mut self) { - self.timer = Some(Instant::now()); + /// Start frame timer + pub fn start_frame(&mut self) { + self.frame_timer = Instant::now(); } - /// Clear timer and record galaxy simulation time. - /// Assumes timer has been started + /// Start galaxy timer + pub fn start_galaxy(&mut self) { + self.galaxy_timer = Instant::now(); + } + + /// Start physics sim timer + pub fn start_physics_sim(&mut self) { + self.physics_sim_timer = Instant::now(); + } + + /// Start physics ship timer + pub fn start_physics_ships(&mut self) { + self.physics_ship_timer = Instant::now(); + } + + /// Start render timer + pub fn start_render(&mut self) { + self.render_timer = Instant::now(); + } + + /// Record galaxy simulation time. + pub fn mark_frame(&mut self) { + self.frame = self.frame_timer.elapsed().as_secs_f32(); + } + + /// Record galaxy simulation time. pub fn mark_galaxy(&mut self) { - self.galaxy = self.timer.unwrap().elapsed().as_secs_f32(); - self.timer = None; + self.galaxy = self.galaxy_timer.elapsed().as_secs_f32(); } - /// Clear timer and record physics simulation time - /// Asumes timer has been started - pub fn mark_physics(&mut self) { - self.physics = self.timer.unwrap().elapsed().as_secs_f32(); - self.timer = None; + /// Record physics simulation time + pub fn mark_physics_sim(&mut self) { + self.physics_sim = self.physics_sim_timer.elapsed().as_secs_f32(); + } + + /// Record physics ship update time + pub fn mark_physics_ships(&mut self) { + self.physics_ship = self.physics_ship_timer.elapsed().as_secs_f32(); + } + + /// Record physics simulation time + pub fn mark_render(&mut self) { + self.render = self.render_timer.elapsed().as_secs_f32(); } }