use std::{cmp, convert::TryFrom, f64, thread, time};
use crate::Context;
#[derive(Debug, Clone)]
struct LogBuffer<T>
where
T: Clone,
{
head: usize,
size: usize,
samples: usize,
contents: Vec<T>,
}
impl<T> LogBuffer<T>
where
T: Clone + Copy,
{
fn new(size: usize, init_val: T) -> LogBuffer<T> {
LogBuffer {
head: 0,
size,
contents: vec![init_val; size],
samples: 1,
}
}
fn push(&mut self, item: T) {
self.head = (self.head + 1) % self.contents.len();
self.contents[self.head] = item;
self.size = cmp::min(self.size + 1, self.contents.len());
self.samples += 1;
}
fn contents(&self) -> &[T] {
if self.samples > self.size {
&self.contents
} else {
&self.contents[..self.samples]
}
}
fn latest(&self) -> T {
self.contents[self.head]
}
}
#[derive(Debug)]
pub struct TimeContext {
init_instant: time::Instant,
last_instant: time::Instant,
frame_durations: LogBuffer<time::Duration>,
residual_update_dt: time::Duration,
frame_count: usize,
}
const TIME_LOG_FRAMES: usize = 200;
impl TimeContext {
pub fn new() -> TimeContext {
let initial_dt = time::Duration::from_millis(16);
TimeContext {
init_instant: time::Instant::now(),
last_instant: time::Instant::now(),
frame_durations: LogBuffer::new(TIME_LOG_FRAMES, initial_dt),
residual_update_dt: time::Duration::from_secs(0),
frame_count: 0,
}
}
pub fn delta(&self) -> time::Duration {
self.frame_durations.latest()
}
pub fn average_delta(&self) -> time::Duration {
let sum: time::Duration = self.frame_durations.contents().iter().sum();
if self.frame_durations.samples > self.frame_durations.size {
sum / u32::try_from(self.frame_durations.size).unwrap()
} else {
sum / u32::try_from(self.frame_durations.samples).unwrap()
}
}
pub fn fps(&self) -> f64 {
let duration_per_frame = self.average_delta();
let seconds_per_frame = duration_per_frame.as_secs_f64();
1.0 / seconds_per_frame
}
pub fn ticks(&self) -> usize {
self.frame_count
}
pub fn time_since_start(&self) -> time::Duration {
self.init_instant.elapsed()
}
pub fn check_update_time(&mut self, target_fps: u32) -> bool {
let target_dt = fps_as_duration(target_fps);
if self.residual_update_dt > target_dt {
self.residual_update_dt -= target_dt;
true
} else {
false
}
}
pub fn remaining_update_time(&self) -> time::Duration {
self.residual_update_dt
}
pub fn tick(&mut self) {
let now = time::Instant::now();
let time_since_last = now - self.last_instant;
self.frame_durations.push(time_since_last);
self.last_instant = now;
self.frame_count += 1;
self.residual_update_dt += time_since_last;
}
}
impl Default for TimeContext {
fn default() -> Self {
Self::new()
}
}
#[deprecated(note = "Use `ctx.time.delta` instead")]
pub fn delta(ctx: &Context) -> time::Duration {
ctx.time.delta()
}
#[deprecated(note = "Use `ctx.time.average_delta` instead")]
pub fn average_delta(ctx: &Context) -> time::Duration {
ctx.time.average_delta()
}
fn fps_as_duration(fps: u32) -> time::Duration {
let target_dt_seconds = 1.0 / f64::from(fps);
time::Duration::from_secs_f64(target_dt_seconds)
}
#[deprecated(note = "Use `ctx.time.fps` instead")]
pub fn fps(ctx: &Context) -> f64 {
ctx.time.fps()
}
#[deprecated(note = "Use `ctx.time.time_since_start` instead")]
pub fn time_since_start(ctx: &Context) -> time::Duration {
let tc = &ctx.time;
tc.init_instant.elapsed()
}
#[deprecated(note = "Use `ctx.time.check_update_time` instead")]
pub fn check_update_time(ctx: &mut Context, target_fps: u32) -> bool {
let timedata = &mut ctx.time;
let target_dt = fps_as_duration(target_fps);
if timedata.residual_update_dt > target_dt {
timedata.residual_update_dt -= target_dt;
true
} else {
false
}
}
#[deprecated(note = "Use `ctx.time.remaining_update_time` instead")]
pub fn remaining_update_time(ctx: &Context) -> time::Duration {
ctx.time.residual_update_dt
}
pub fn sleep(duration: time::Duration) {
thread::sleep(duration);
}
pub fn yield_now() {
thread::yield_now();
}
#[deprecated(note = "Use `ctx.time.ticks` instead")]
pub fn ticks(ctx: &Context) -> usize {
ctx.time.frame_count
}