diff options
| author | Freya Murphy <freya@freyacat.org> | 2025-11-11 17:35:11 -0500 |
|---|---|---|
| committer | Freya Murphy <freya@freyacat.org> | 2025-11-11 17:35:11 -0500 |
| commit | 9035b14fd2ef35b883a8a62845fbf18e891fefb8 (patch) | |
| tree | 20680428bf1905e55a1a3d2bc81d8c1a61eb1a11 | |
| parent | fix lints in nightly (diff) | |
| download | DungeonCrawl-9035b14fd2ef35b883a8a62845fbf18e891fefb8.tar.gz DungeonCrawl-9035b14fd2ef35b883a8a62845fbf18e891fefb8.tar.bz2 DungeonCrawl-9035b14fd2ef35b883a8a62845fbf18e891fefb8.zip | |
graphics: use custom timer for delta_time/fps
| -rw-r--r-- | game/src/main.rs | 3 | ||||
| -rw-r--r-- | graphics/src/lib.rs | 9 | ||||
| -rw-r--r-- | graphics/src/render.rs | 40 | ||||
| -rw-r--r-- | graphics/src/timer.rs | 67 |
4 files changed, 89 insertions, 30 deletions
diff --git a/game/src/main.rs b/game/src/main.rs index 9449710..28498dc 100644 --- a/game/src/main.rs +++ b/game/src/main.rs @@ -68,7 +68,8 @@ impl Game { }; // Update game state - self.dungeon.update(inputs, self.window.delta_time()); + self.dungeon + .update(inputs, self.window.delta_time().as_secs_f32()); // Draw a single frame self.window.draw_frame(&self.dungeon); diff --git a/graphics/src/lib.rs b/graphics/src/lib.rs index 8dccd63..43bf3ac 100644 --- a/graphics/src/lib.rs +++ b/graphics/src/lib.rs @@ -1,6 +1,8 @@ //! The `graphics` crate contains the core functionality for //! rendering using the `raylib` library. +use std::time::Duration; + use dungeon::Dungeon; use raylib::prelude::*; @@ -9,6 +11,7 @@ use crate::render::Renderer; mod audio; mod render; +mod timer; /// The `Error` type used within this crate pub type Error = Box<dyn std::error::Error>; @@ -77,7 +80,6 @@ impl From<Key> for KeyboardKey { } /// The `WindowBuilder` type allows setting partial options for the window -#[derive(Debug)] pub struct WindowBuilder<'a> { title: &'a str, width: u16, @@ -182,7 +184,6 @@ impl Default for WindowBuilder<'_> { } /// The `Window` type represents the game window -#[derive(Debug)] pub struct Window { // persistant renderer renderer: Renderer, @@ -211,8 +212,8 @@ impl Window { } /// Returns the per frame delta time - pub fn delta_time(&self) -> f32 { - self.handle.get_frame_time() + pub fn delta_time(&self) -> Duration { + self.renderer.delta_time() } /// Returns if the provided `Key` has just been pressed diff --git a/graphics/src/render.rs b/graphics/src/render.rs index 78ff94d..6fd2ea4 100644 --- a/graphics/src/render.rs +++ b/graphics/src/render.rs @@ -1,4 +1,4 @@ -use std::{f32, io::Write}; +use std::{f32, io::Write, time::Duration}; use dungeon::{ Direction, Dungeon, Entity, EntityKind, Floor, Item, MAP_SIZE, PLAYER_INVENTORY_SIZE, @@ -13,6 +13,8 @@ use raylib::{ texture::{RaylibTexture2D, RenderTexture2D, Texture2D}, }; +use crate::timer::Timer; + macro_rules! downcast { ($usize:expr, $type:ty) => { <$type>::try_from($usize).unwrap_or(<$type>::MAX) @@ -124,7 +126,6 @@ const UI_COL2: u16 = UI_COL1 + FONT_SIZE * 10; /// The maxmimum length any text string can be const MAX_TEXT_LEN: usize = 16; -#[derive(Debug)] struct Textures { // Tilemap atlas: Texture2D, @@ -156,7 +157,6 @@ impl Textures { } } -#[derive(Debug)] pub struct Renderer { /* Persistant Render Data */ /// All loaded image resources/textures @@ -175,11 +175,8 @@ pub struct Renderer { debug: bool, /// Scratch buffer to format text into text_buf: [u8; MAX_TEXT_LEN], - /* Per Frame Caculated Data */ - /// Last known FPS - fps: u32, - /// The current frame - frame: u64, + /// Frame timer + timer: Timer, } impl Renderer { pub fn new(handle: &mut RaylibHandle, thread: &RaylibThread) -> crate::Result<Self> { @@ -206,8 +203,7 @@ impl Renderer { framebuffer: Some(framebuffer), debug: false, text_buf: [0u8; MAX_TEXT_LEN], - fps: 0, - frame: 0, + timer: Timer::new(), }) } @@ -215,6 +211,10 @@ impl Renderer { self.debug = !self.debug; } + pub fn delta_time(&self) -> Duration { + self.timer.delta_time() + } + pub fn draw_frame( &mut self, handle: &mut RaylibHandle, @@ -224,12 +224,12 @@ impl Renderer { let render_width = handle.get_render_width() as f32; let render_height = handle.get_render_height() as f32; - // Update render info before drawing - self.update_per_frame_data(handle); - // Start the frame let mut r = handle.begin_drawing(t); + // Update render info before drawing + self.timer.update(); + // Update cached tilemaps self.update_tilemaps(&mut r, t, &dungeon.floor); @@ -263,16 +263,6 @@ impl Renderer { }; } - /// Update frame metadata while we still have access to the - /// `RaylibHandle`. These fields are inaccessable once it's - /// turned into a `RaylibDrawHandle`. - fn update_per_frame_data(&mut self, handle: &RaylibHandle) { - // Get last known fps - self.fps = handle.get_fps(); - // Update frame counter - self.frame += 1; - } - /// Draws the game dungeon fn draw_dungeon<R>(&mut self, r: &mut R, dungeon: &Dungeon) where @@ -668,7 +658,7 @@ impl Renderer { let player = &dungeon.player; // Draw FPS - draw_text!(self, r, UI_COL1, UI_ROW1, "FPS {}", self.fps); + draw_text!(self, r, UI_COL1, UI_ROW1, "FPS {}", self.timer.get_fps()); // Draw Player position let (x, y) = &player.entity.pos.xy(); @@ -687,7 +677,7 @@ impl Renderer { draw_text!(self, r, UI_COL2, UI_ROW2, "{hash:016X}"); // Draw current frame number - let frame = &self.frame; + let frame = self.timer.get_frame(); draw_text!(self, r, UI_COL2, UI_ROW3, "FRAME {frame}"); } diff --git a/graphics/src/timer.rs b/graphics/src/timer.rs new file mode 100644 index 0000000..8310776 --- /dev/null +++ b/graphics/src/timer.rs @@ -0,0 +1,67 @@ +use std::{ + ops::{Add, Div, Mul}, + time::{Duration, Instant}, +}; + +/// How often to update the fps interval on screen +const FPS_INTERVAL: Duration = Duration::from_millis(250); + +/// How much to smooth he `delta_avg` amount (`AVG_RATIO`:1) +const AVG_RATIO: u32 = 10; + +/// Keeps track of frame intervals and FPS +pub struct Timer { + /// Last time updated + previous: Instant, + /// Current time + now: Instant, + /// Rolling delta time average + delta_avg: Duration, + /// Computed fps + fps: f64, + /// Last time the fps was updated + fps_time: Instant, + /// Frame count + count: u32, +} +impl Timer { + pub fn new() -> Self { + let now = Instant::now(); + Self { + previous: now, + now, + delta_avg: Duration::ZERO, + fps: 0.0, + fps_time: now, + count: 0, + } + } + + pub fn update(&mut self) { + self.previous = self.now; + self.now = Instant::now(); + self.delta_avg = self + .delta_avg + .mul(AVG_RATIO) + .add(self.delta_time()) + .div(AVG_RATIO + 1); + if (self.now - self.fps_time) > FPS_INTERVAL { + self.fps = 1.0 / self.delta_avg.as_secs_f64(); + self.fps_time = self.now; + } + self.count += 1; + } + + pub fn get_frame(&self) -> u32 { + self.count + } + + #[expect(clippy::cast_possible_truncation)] + pub fn get_fps(&self) -> u32 { + self.fps.ceil() as u32 + } + + pub fn delta_time(&self) -> Duration { + self.now - self.previous + } +} |