#timer #profiler #main-loop

min_timer

基于f64的简单持续时间计时器;此外,还有使用它的主循环实现

4个版本 (破坏性更新)

0.4.0 2022年2月19日
0.3.0 2022年2月18日
0.2.0 2022年2月17日
0.1.0 2022年2月17日

#614 in 游戏开发


用于 min_gl

MIT/Apache

29KB
401

min_timer


随着事物的增长,这个库在过去的两天里增长了很多。现在它由三部分组成

  1. 计时器
  2. 分析器
  3. 主循环

每个部分都依赖于前一个部分。


计时器

这是一个提供基于 f64 的持续时间计时器的库。标准库的实现使用整数。因此,对于以 f64 提供时间的时钟,这个库应该有更高的性能。

此外,检查更少。尽管如此,对于SI单位(秒)有很强的类型安全性,希望编译器可以优化掉。

fn count(upto: u32) {
    use min_timer::{Std, Sec, Timer};

    let dur = Sec::MINUTE; // strong type safety.
    let now = Std::new();  // there is a std::time implementation.
    let mut timer = Timer::new(&now);
    let mut count = 0;

    while count < upto {
        if timer >= dur { // straight-forward checking,
            timer -= dur; // flexible manupilation.
            count += 1;
            println!("Counting {}...", count);
        }
    }
}

分析器

还提供了小型统计和分析功能。这些都是打算用于实时应用程序的。

fn subroutine() {}

fn main_routine() {
    use min_timer::{Std, Prf, Stat};

    let mut stat = Stat::new();
    let now = Std::new();

    for _ in 0..10 {
        let _ = Prf::new(&now, &mut stat); // create and forget.
        subroutine();
    }

    // End of cycle.
    // This can be anything.
    // For example: every second in a game engine.
    // This way the rate will be the FPS counter.
    stat.refresh();

    for _ in 0..15 {
        let _ = Prf::new(&now, &mut stat);
        subroutine();
    }

    println!(
        "Subroutine called {} times, with {} average runtime and {} times per cycle.",
        stat.get_count(), // will be 25
        stat.find_average(),
        stat.get_rate()   // will be 15
    );
}

主循环

这是实时应用程序的 核心。设计是这样的,你提供一个状态类和一个可以绘制它的渲染器。滴答率和帧率不同;这样可以在不同的频率下更新以实现平滑的视觉效果。

这是通过在绘制之前插值程序的前一个和当前状态来完成的,使用剩余的滴答次数。因此,状态必须实现缩放和叠加;线性组合。

use min_timer::{Hrt, Now, Render, Std, Stt, Timer};
use std::ops::{Add, Mul};

struct Bar {
    len: u32,
    pre: Option<u32>,
}

impl Default for Bar {
    // Creating the render
    fn default() -> Self {
        Self { len: 50, pre: None }
    }
}

impl Bar {
    fn print(&mut self, per: f64, len: u32) {
        self.pre = Some(len);
        print!("[");
        for _ in 0..len {
            print!("=");
        }
        if self.len != len {
            print!(">");
            for _ in 0..(self.len - len - 1) {
                print!(" ");
            }
        }
        println!("] {}%", per);
    }
}

impl<T: Now> Render<T, Ex> for Bar {
    // Rendering
    fn render(&mut self, _: &Hrt<T>, stt: &Ex) {
        let len = self.len as f64 * stt.0;
        let len = len.floor() as u32;
        let len = len.min(self.len);
        let per = (stt.0 * 100.0).floor();
        if let Some(pre) = self.pre {
            if len != pre {
                self.print(per, len);
            }
        } else {
            self.print(per, len);
        }
    }
}

#[derive(Default, Clone, Copy)]
struct Ex(f64);

impl Mul<f64> for Ex {
    type Output = Ex;

    // Scaling
    fn mul(self, rhs: f64) -> Self::Output {
        Self(self.0 * rhs)
    }
}

impl Add for Ex {
    type Output = Ex;

    // Superposing
    fn add(self, rhs: Ex) -> Self::Output {
        Self(self.0 + rhs.0)
    }
}

impl<T: Now> Stt<T> for Ex {
    // Initialization; timer provided for profiling
    fn init(&mut self, _: &mut Hrt<T>, timer: Timer<T>) {
        println!("Initialization done in {}!", timer);
    }

    // Updating; heart provided for manupilation
    fn update(&mut self, hrt: &mut Hrt<T>) {
        self.0 += 1e-1;
        if self.0 >= 1.0 {
            hrt.stop();
        }
    }

    // Profiling every second; heart provided for manupilation
    fn sec(&mut self, hrt: &mut Hrt<T>) {
        println!(
            "Tick Rate: {} Frame Rate: {}",
            hrt.ticks().avg_rate(),
            hrt.frames().avg_rate()
        );
    }
}

fn main() {
    let now = Std::new(); // using the standard library's clock
    let mut hrt = Hrt::new(1e2, &now); // target tick rate 100.0
    hrt.start::<Ex, Bar>(); // creates from defaults
}

动机

为什么还要写这个,当已经有标准库的时候?

  1. 教育:我得以练习 Rust,特别是 newtype 模式与 Sec

  2. 在编写这个之前,我对 std::time 并不熟悉。

  3. 我将使用这个库与 GLFW 计时器一起,它以秒为单位的 double 返回时间。这样,我将使用 GLFW 实现 Now,并且与之前相比不会有转换。

    fn time(glfw: &Glfw) -> Duration {
        Duration::from_sec_f64(glfw.get_time()) // conversion!
    }
    
    let start = time(&glfw);
    let elapsed = time(&glfw) - start;
    let seconds = elapsed.as_sec_f64(); // conversion!
    

    查看我的其他库,min_gl,以查看 GLFW 计时器的 Now 实现。

  4. 这个库提供了一个可以放置更多关于时间信息的空间,例如分析。

  5. 使用 f64 比较多;我在处理主循环时看到了这一点。


版权 (C) 2022 Cem Geçgel [email protected]

无运行时依赖

功能