Timing Module Update
- Implement a new timing module - Utilize the new timing module in glium platform implementation for frame limiting and fixed engine updates timing.
This commit is contained in:
parent
5e8897c271
commit
84ab3a26b1
9 changed files with 221 additions and 11 deletions
|
|
@ -7,7 +7,8 @@ pub trait EngineTrait {
|
||||||
type PlatformCtx: Clone;
|
type PlatformCtx: Clone;
|
||||||
fn new() -> Self;
|
fn new() -> Self;
|
||||||
fn initialize(&mut self, platform_context: Self::PlatformCtx);
|
fn initialize(&mut self, platform_context: Self::PlatformCtx);
|
||||||
fn update(&mut self, platform_context: Self::PlatformCtx);
|
fn frame_update(&mut self, platform_context: Self::PlatformCtx);
|
||||||
|
fn fixed_update(&mut self, platform_context: Self::PlatformCtx);
|
||||||
fn handle_event(&mut self, platform_context: Self::PlatformCtx);
|
fn handle_event(&mut self, platform_context: Self::PlatformCtx);
|
||||||
fn current_scene_mut(&mut self) -> &mut Scene;
|
fn current_scene_mut(&mut self) -> &mut Scene;
|
||||||
fn get_debug_ui_buffer(&self) -> Rc<RefCell<DebugUIBuffer>>;
|
fn get_debug_ui_buffer(&self) -> Rc<RefCell<DebugUIBuffer>>;
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
pub mod engine;
|
pub mod engine;
|
||||||
pub mod scene;
|
pub mod scene;
|
||||||
pub mod debug_ui;
|
pub mod debug_ui;
|
||||||
|
pub mod time;
|
||||||
|
|
||||||
pub use debug_ui::*;
|
pub use debug_ui::*;
|
||||||
|
|
|
||||||
144
core/src/time.rs
Normal file
144
core/src/time.rs
Normal file
|
|
@ -0,0 +1,144 @@
|
||||||
|
use std::thread;
|
||||||
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct Config {
|
||||||
|
pub target_frame_hz: Option<f64>,
|
||||||
|
pub target_update_hz: f64,
|
||||||
|
pub max_updates_per_frame: u32,
|
||||||
|
pub max_accumulated_steps: u32,
|
||||||
|
pub sleep_tolerance: Duration,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Config {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
target_frame_hz: Some(144.0),
|
||||||
|
target_update_hz: 60.0,
|
||||||
|
max_updates_per_frame: 5,
|
||||||
|
max_accumulated_steps: 8,
|
||||||
|
sleep_tolerance: Duration::from_micros(500),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Time {
|
||||||
|
cfg: Config,
|
||||||
|
last_instant: Instant,
|
||||||
|
next_frame_due: Instant,
|
||||||
|
frame_interval: Option<Duration>,
|
||||||
|
fixed_dt: Duration,
|
||||||
|
|
||||||
|
// tracking
|
||||||
|
frame_dt: Duration,
|
||||||
|
accumulator: Duration,
|
||||||
|
|
||||||
|
// counters
|
||||||
|
pub frame_count: u64,
|
||||||
|
pub update_count: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct TickPlan {
|
||||||
|
/// How many fixed updates to run this frame
|
||||||
|
pub updates: u32,
|
||||||
|
/// Interpolation factor for rendering between previous/next sim states
|
||||||
|
pub alpha: f32,
|
||||||
|
/// Measured last frame delta (seconds)
|
||||||
|
pub frame_dt: f32,
|
||||||
|
/// Fixed timestep (seconds)
|
||||||
|
pub fixed_dt: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Time {
|
||||||
|
pub fn new(cfg: Config) -> Self {
|
||||||
|
let now = Instant::now();
|
||||||
|
let frame_interval = cfg.target_frame_hz.map(|hz| Duration::from_secs_f64(1.0 / hz));
|
||||||
|
let fixed_dt = Duration::from_secs_f64(1.0 / cfg.target_update_hz);
|
||||||
|
Self {
|
||||||
|
cfg,
|
||||||
|
last_instant: now,
|
||||||
|
next_frame_due: now,
|
||||||
|
frame_interval,
|
||||||
|
fixed_dt,
|
||||||
|
frame_dt: Duration::ZERO,
|
||||||
|
accumulator: Duration::ZERO,
|
||||||
|
frame_count: 0,
|
||||||
|
update_count: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn reconfigure(&mut self, cfg: Config) {
|
||||||
|
self.cfg = cfg.clone();
|
||||||
|
self.frame_interval = cfg.target_frame_hz.map(|hz| Duration::from_secs_f64(1.0 / hz));
|
||||||
|
self.fixed_dt = Duration::from_secs_f64(1.0 / cfg.target_update_hz);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn begin_frame_blocking(&mut self) -> TickPlan {
|
||||||
|
// 1) If there's a frame cap, block until next frame deadline
|
||||||
|
if let Some(interval) = self.frame_interval {
|
||||||
|
let mut now = Instant::now();
|
||||||
|
if now < self.next_frame_due {
|
||||||
|
// Sleep most of the remainder, then spin the last tiny bit for precision
|
||||||
|
let total_remaining = self.next_frame_due - now;
|
||||||
|
if total_remaining > self.cfg.sleep_tolerance {
|
||||||
|
let sleep_for = total_remaining - self.cfg.sleep_tolerance;
|
||||||
|
thread::sleep(sleep_for);
|
||||||
|
}
|
||||||
|
// Short spin-wait for precision
|
||||||
|
while Instant::now() < self.next_frame_due {
|
||||||
|
std::hint::spin_loop();
|
||||||
|
}
|
||||||
|
now = self.next_frame_due;
|
||||||
|
}
|
||||||
|
self.next_frame_due = self.next_frame_due + interval;
|
||||||
|
// In case we fell far behind (e.g., debugger pause), resync.
|
||||||
|
if self.next_frame_due < now {
|
||||||
|
self.next_frame_due = now + interval;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2) Measure frame dt
|
||||||
|
let now = Instant::now();
|
||||||
|
self.frame_dt = now.saturating_duration_since(self.last_instant);
|
||||||
|
self.last_instant = now;
|
||||||
|
self.frame_count += 1;
|
||||||
|
|
||||||
|
// 3) Accumulate for fixed updates
|
||||||
|
self.accumulator += self.frame_dt;
|
||||||
|
|
||||||
|
// Clamp accumulator to avoid doing a huge number of updates after a stall
|
||||||
|
let max_accumulated = self.fixed_dt * self.cfg.max_accumulated_steps;
|
||||||
|
if self.accumulator > max_accumulated {
|
||||||
|
self.accumulator = max_accumulated;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4) Determine how many updates to run this frame
|
||||||
|
let mut updates = 0u32;
|
||||||
|
while self.accumulator >= self.fixed_dt && updates < self.cfg.max_updates_per_frame {
|
||||||
|
self.accumulator -= self.fixed_dt;
|
||||||
|
updates += 1;
|
||||||
|
self.update_count += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5) Compute interpolation factor for rendering (0..1)
|
||||||
|
let alpha = if self.fixed_dt.is_zero() {
|
||||||
|
1.0
|
||||||
|
} else {
|
||||||
|
(self.accumulator.as_secs_f32() / self.fixed_dt.as_secs_f32()).clamp(0.0, 1.0)
|
||||||
|
};
|
||||||
|
|
||||||
|
TickPlan {
|
||||||
|
updates,
|
||||||
|
alpha,
|
||||||
|
frame_dt: self.frame_dt.as_secs_f32(),
|
||||||
|
fixed_dt: self.fixed_dt.as_secs_f32(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn frame_dt_seconds(&self) -> f32 { self.frame_dt.as_secs_f32() }
|
||||||
|
pub fn fixed_dt_seconds(&self) -> f32 { self.fixed_dt.as_secs_f32() }
|
||||||
|
pub fn alpha(&self) -> f32 {
|
||||||
|
if self.fixed_dt.is_zero() { 1.0 } else { (self.accumulator.as_secs_f32() / self.fixed_dt.as_secs_f32()).clamp(0.0, 1.0) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -44,7 +44,7 @@ impl EngineTrait for Engine {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Update the engine
|
/// Update the engine
|
||||||
fn update(&mut self, platform_context: PlatformContext) {
|
fn frame_update(&mut self, platform_context: PlatformContext) {
|
||||||
self.debug_ui_buffer.borrow_mut().reset_buffer();
|
self.debug_ui_buffer.borrow_mut().reset_buffer();
|
||||||
let mut ctx = SystemContext {
|
let mut ctx = SystemContext {
|
||||||
scene: self.scene_manager.current_mut(),
|
scene: self.scene_manager.current_mut(),
|
||||||
|
|
@ -53,7 +53,19 @@ impl EngineTrait for Engine {
|
||||||
};
|
};
|
||||||
|
|
||||||
for system in self.system_manager.systems.values_mut() {
|
for system in self.system_manager.systems.values_mut() {
|
||||||
system.update(&mut ctx);
|
system.frame_update(&mut ctx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fixed_update(&mut self, platform_context: PlatformContext) {
|
||||||
|
let mut ctx = SystemContext {
|
||||||
|
scene: self.scene_manager.current_mut(),
|
||||||
|
platform_context,
|
||||||
|
debug_ui_buffer: self.debug_ui_buffer.clone(),
|
||||||
|
};
|
||||||
|
|
||||||
|
for system in self.system_manager.systems.values_mut() {
|
||||||
|
system.fixed_update(&mut ctx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,8 @@ pub trait System {
|
||||||
/// Spawn the first entities of the world.
|
/// Spawn the first entities of the world.
|
||||||
fn load_world(&mut self, _ctx: &mut SystemContext) {}
|
fn load_world(&mut self, _ctx: &mut SystemContext) {}
|
||||||
fn handle_event(&mut self, _ctx: &mut SystemContext) {}
|
fn handle_event(&mut self, _ctx: &mut SystemContext) {}
|
||||||
fn update(&mut self, _ctx: &mut SystemContext) {}
|
fn fixed_update(&mut self, _ctx: &mut SystemContext) {}
|
||||||
|
fn frame_update(&mut self, _ctx: &mut SystemContext) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct SystemManager {
|
pub struct SystemManager {
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
use std::fmt::format;
|
||||||
use glam::{Quat, Vec3};
|
use glam::{Quat, Vec3};
|
||||||
use raidillon_engine::{Engine, system::System};
|
use raidillon_engine::{Engine, system::System};
|
||||||
use raidillon_engine::system::SystemContext;
|
use raidillon_engine::system::SystemContext;
|
||||||
|
|
@ -34,8 +35,8 @@ impl System for UpdateAspectRatioSystem {
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
struct RenderingTestSystem;
|
struct RenderingTestSystem;
|
||||||
|
|
||||||
impl System for RenderingTestSystem {
|
impl System for RenderingTestSystem {
|
||||||
fn initialize(&mut self) {}
|
|
||||||
fn load_world(&mut self, ctx: &mut SystemContext) {
|
fn load_world(&mut self, ctx: &mut SystemContext) {
|
||||||
ctx.scene.world.spawn((Camera {
|
ctx.scene.world.spawn((Camera {
|
||||||
eye: Vec3::new(0.0, 0.0, 2.0),
|
eye: Vec3::new(0.0, 0.0, 2.0),
|
||||||
|
|
@ -61,9 +62,19 @@ impl System for RenderingTestSystem {
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update(&mut self, ctx: &mut SystemContext) {
|
fn frame_update(&mut self, ctx: &mut SystemContext) {
|
||||||
ctx.debug_ui_buffer.borrow_mut().text("Hello World!".to_owned());
|
let mut dbg_ui = ctx.debug_ui_buffer.borrow_mut();
|
||||||
|
dbg_ui.text("Hello World!".to_owned());
|
||||||
|
dbg_ui.text(format!("Frame Delta: {}", ctx.platform_context.time_ctx.frame_dt));
|
||||||
|
dbg_ui.text(format!("Fixed Delta: {}", ctx.platform_context.time_ctx.fixed_dt));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn fixed_update(&mut self, ctx: &mut SystemContext) {
|
||||||
|
ctx.scene.world.query_mut::<(&mut Transform, &ModelHandle)>().into_iter().for_each(|(_, (t, _))| {
|
||||||
|
t.rotation *= Quat::from_rotation_y(10.0 * ctx.platform_context.time_ctx.fixed_dt);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use raidillon_platform::{Platform, PlatformContext};
|
use raidillon_platform::{Platform, PlatformContext, TimeContext};
|
||||||
use glium::backend::glutin::Display;
|
use glium::backend::glutin::Display;
|
||||||
use glium::backend::glutin::SimpleWindowBuilder;
|
use glium::backend::glutin::SimpleWindowBuilder;
|
||||||
use glium::glutin::surface::WindowSurface;
|
use glium::glutin::surface::WindowSurface;
|
||||||
|
|
@ -11,6 +11,8 @@ use crate::system::{RenderingContext, RenderingSystemManager};
|
||||||
use winit::event::{Event, WindowEvent};
|
use winit::event::{Event, WindowEvent};
|
||||||
use raidillon_assets::ModelManagerRef;
|
use raidillon_assets::ModelManagerRef;
|
||||||
use raidillon_core::engine::EngineTrait;
|
use raidillon_core::engine::EngineTrait;
|
||||||
|
use raidillon_core::time;
|
||||||
|
use raidillon_core::time::Time;
|
||||||
use crate::render::debug_ui::ImguiBridge;
|
use crate::render::debug_ui::ImguiBridge;
|
||||||
use crate::render::BasicMeshRenderingSystem;
|
use crate::render::BasicMeshRenderingSystem;
|
||||||
use crate::GliumAssetManager;
|
use crate::GliumAssetManager;
|
||||||
|
|
@ -22,6 +24,7 @@ pub struct GliumPlatform<E: EngineTrait<PlatformCtx = PlatformContext>> {
|
||||||
rendering_system_manager: RenderingSystemManager,
|
rendering_system_manager: RenderingSystemManager,
|
||||||
asset_manager: ModelManagerRef,
|
asset_manager: ModelManagerRef,
|
||||||
engine: E,
|
engine: E,
|
||||||
|
time: time::Time,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<E: EngineTrait<PlatformCtx = PlatformContext>> Platform<E> for GliumPlatform<E> {
|
impl<E: EngineTrait<PlatformCtx = PlatformContext>> Platform<E> for GliumPlatform<E> {
|
||||||
|
|
@ -38,6 +41,9 @@ impl<E: EngineTrait<PlatformCtx = PlatformContext>> Platform<E> for GliumPlatfor
|
||||||
let asset_manager: ModelManagerRef = Rc::new(RefCell::new(Box::new(GliumAssetManager::new(Box::new(display.clone())))));
|
let asset_manager: ModelManagerRef = Rc::new(RefCell::new(Box::new(GliumAssetManager::new(Box::new(display.clone())))));
|
||||||
let mut rendering_system_manager = RenderingSystemManager::new();
|
let mut rendering_system_manager = RenderingSystemManager::new();
|
||||||
|
|
||||||
|
let time_cfg = time::Config::default();
|
||||||
|
let time = time::Time::new(time_cfg);
|
||||||
|
|
||||||
// Install rendering systems
|
// Install rendering systems
|
||||||
rendering_system_manager.add::<BasicMeshRenderingSystem>(&display, &window);
|
rendering_system_manager.add::<BasicMeshRenderingSystem>(&display, &window);
|
||||||
rendering_system_manager.add::<ImguiBridge>(&display, &window);
|
rendering_system_manager.add::<ImguiBridge>(&display, &window);
|
||||||
|
|
@ -49,6 +55,7 @@ impl<E: EngineTrait<PlatformCtx = PlatformContext>> Platform<E> for GliumPlatfor
|
||||||
rendering_system_manager,
|
rendering_system_manager,
|
||||||
asset_manager,
|
asset_manager,
|
||||||
engine,
|
engine,
|
||||||
|
time,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -59,6 +66,7 @@ impl<E: EngineTrait<PlatformCtx = PlatformContext>> Platform<E> for GliumPlatfor
|
||||||
asset_manager: self.asset_manager.clone(),
|
asset_manager: self.asset_manager.clone(),
|
||||||
frame_width: w as f32,
|
frame_width: w as f32,
|
||||||
frame_height: h as f32,
|
frame_height: h as f32,
|
||||||
|
time_ctx: self.construct_time_ctx(),
|
||||||
};
|
};
|
||||||
self.engine.initialize(ctx.clone());
|
self.engine.initialize(ctx.clone());
|
||||||
let _ = &self.event_loop.run(move |event, el| {
|
let _ = &self.event_loop.run(move |event, el| {
|
||||||
|
|
@ -98,9 +106,22 @@ impl<E: EngineTrait<PlatformCtx = PlatformContext>> Platform<E> for GliumPlatfor
|
||||||
_ => {},
|
_ => {},
|
||||||
},
|
},
|
||||||
Event::AboutToWait => {
|
Event::AboutToWait => {
|
||||||
|
let plan = self.time.begin_frame_blocking();
|
||||||
|
|
||||||
let mut ctx2 = ctx.clone();
|
let mut ctx2 = ctx.clone();
|
||||||
ctx2.current_event = event.clone();
|
ctx2.time_ctx = TimeContext {
|
||||||
self.engine.update(ctx2);
|
frame_dt: self.time.frame_dt_seconds(),
|
||||||
|
fixed_dt: self.time.fixed_dt_seconds(),
|
||||||
|
alpha: self.time.alpha(),
|
||||||
|
};
|
||||||
|
ctx2.current_event = Event::AboutToWait;
|
||||||
|
|
||||||
|
for _ in 0..plan.updates {
|
||||||
|
self.engine.fixed_update(ctx2.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
self.engine.frame_update(ctx2.clone());
|
||||||
|
|
||||||
self.rendering_system_manager
|
self.rendering_system_manager
|
||||||
.systems
|
.systems
|
||||||
.values_mut()
|
.values_mut()
|
||||||
|
|
@ -112,3 +133,13 @@ impl<E: EngineTrait<PlatformCtx = PlatformContext>> Platform<E> for GliumPlatfor
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<E: EngineTrait<PlatformCtx = PlatformContext>> GliumPlatform<E> {
|
||||||
|
fn construct_time_ctx(&self) -> TimeContext {
|
||||||
|
TimeContext {
|
||||||
|
frame_dt: self.time.frame_dt_seconds(),
|
||||||
|
fixed_dt: self.time.fixed_dt_seconds(),
|
||||||
|
alpha: self.time.alpha(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,4 +7,12 @@ pub struct PlatformContext {
|
||||||
pub asset_manager: ModelManagerRef,
|
pub asset_manager: ModelManagerRef,
|
||||||
pub frame_width: f32,
|
pub frame_width: f32,
|
||||||
pub frame_height: f32,
|
pub frame_height: f32,
|
||||||
|
pub time_ctx: TimeContext,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct TimeContext {
|
||||||
|
pub frame_dt: f32,
|
||||||
|
pub fixed_dt: f32,
|
||||||
|
pub alpha: f32,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,4 +5,4 @@ pub mod context;
|
||||||
|
|
||||||
pub use platform::Platform;
|
pub use platform::Platform;
|
||||||
pub use camera::Camera;
|
pub use camera::Camera;
|
||||||
pub use context::PlatformContext;
|
pub use context::{PlatformContext, TimeContext};
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue