[demo] add frame time graph to demo app

This commit is contained in:
Emil Ernerfeldt 2020-10-01 22:25:44 +02:00
parent 1156525ce9
commit 6fcfb52aa0
18 changed files with 371 additions and 130 deletions

51
egui/src/align.rs Normal file
View file

@ -0,0 +1,51 @@
use crate::math::{pos2, Rect};
/// left/center/right or top/center/bottom alignment for e.g. anchors and `Layout`s.
#[derive(Clone, Copy, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
pub enum Align {
/// Left/Top
Min,
/// Note: requires a bounded/known available_width.
Center,
/// Right/Bottom
/// Note: requires a bounded/known available_width.
Max,
}
impl Default for Align {
fn default() -> Align {
Align::Min
}
}
pub type Align2 = (Align, Align);
pub const LEFT_BOTTOM: Align2 = (Align::Min, Align::Max);
pub const LEFT_CENTER: Align2 = (Align::Min, Align::Center);
pub const LEFT_TOP: Align2 = (Align::Min, Align::Min);
pub const CENTER_BOTTOM: Align2 = (Align::Center, Align::Max);
pub const CENTER_CENTER: Align2 = (Align::Center, Align::Center);
pub const CENTER_TOP: Align2 = (Align::Center, Align::Min);
pub const RIGHT_BOTTOM: Align2 = (Align::Max, Align::Max);
pub const RIGHT_CENTER: Align2 = (Align::Max, Align::Center);
pub const RIGHT_TOP: Align2 = (Align::Max, Align::Min);
/// Used e.g. to anchor a piece of text to a part of the rectangle.
/// Give a position within the rect, specified by the aligns
pub(crate) fn anchor_rect(rect: Rect, anchor: (Align, Align)) -> Rect {
let x = match anchor.0 {
Align::Min => rect.left(),
Align::Center => rect.left() - 0.5 * rect.width(),
Align::Max => rect.left() - rect.width(),
};
let y = match anchor.1 {
Align::Min => rect.top(),
Align::Center => rect.top() - 0.5 * rect.height(),
Align::Max => rect.top() - rect.height(),
};
Rect::from_min_size(pos2(x, y), rect.size())
}

View file

@ -30,11 +30,9 @@ pub trait Backend {
None
}
/// excludes painting
fn cpu_time(&self) -> f32;
/// Smoothed frames per second
fn fps(&self) -> f32;
/// Seconds of cpu usage (in seconds) of UI code on the previous frame.
/// Zero if this is the first frame.
fn cpu_usage(&self) -> Option<f32>;
/// Local time. Used for the clock in the demo app.
fn seconds_since_midnight(&self) -> Option<f64> {

View file

@ -85,6 +85,16 @@ impl Resize {
self.min_size = min_size.into();
self
}
/// Won't shrink to smaller than this
pub fn min_width(mut self, min_width: f32) -> Self {
self.min_size.x = min_width;
self
}
/// Won't shrink to smaller than this
pub fn min_height(mut self, min_height: f32) -> Self {
self.min_size.y = min_height;
self
}
/// Won't expand to larger than this
pub fn max_size(mut self, max_size: impl Into<Vec2>) -> Self {

View file

@ -77,6 +77,17 @@ impl<'open> Window<'open> {
self
}
/// Set minimum width of the window.
pub fn min_width(mut self, min_width: f32) -> Self {
self.resize = self.resize.min_width(min_width);
self
}
/// Set minimum height of the window.
pub fn min_height(mut self, min_height: f32) -> Self {
self.resize = self.resize.min_height(min_height);
self
}
/// Set initial position of the window.
pub fn default_pos(mut self, default_pos: impl Into<Pos2>) -> Self {
self.area = self.area.default_pos(default_pos);

View file

@ -47,6 +47,120 @@ impl Default for RunMode {
// ----------------------------------------------------------------------------
struct FrameHistory {
frame_times: History<f32>,
}
impl Default for FrameHistory {
fn default() -> Self {
let max_age: f64 = 1.0;
Self {
frame_times: History::from_max_len_age((max_age * 300.0).round() as usize, max_age),
}
}
}
impl FrameHistory {
pub fn on_new_frame(&mut self, now: f64, previus_frame_time: Option<f32>) {
let previus_frame_time = previus_frame_time.unwrap_or_default();
if let Some(latest) = self.frame_times.latest_mut() {
*latest = previus_frame_time; // rewrite history now that we know
}
self.frame_times.add(now, previus_frame_time); // projected
}
fn fps(&self) -> f32 {
1.0 / self.frame_times.mean_time_interval().unwrap_or_default()
}
fn ui(&mut self, ui: &mut Ui) {
ui.label(format!(
"Total frames painted: {}",
self.frame_times.total_count()
));
ui.label(format!(
"Mean CPU usage per frame: {:.2} ms / frame",
1e3 * self.frame_times.average().unwrap_or_default()
))
.tooltip_text(
"Includes Egui layout and tesselation time.\n\
Does not include GPU usage, nor overhead for sending data to GPU.",
);
CollapsingHeader::new("CPU usage history")
.default_open(false)
.show(ui, |ui| {
self.graph(ui);
});
}
fn graph(&mut self, ui: &mut Ui) {
let graph_top_cpu_usage = 0.010;
ui.label("Egui CPU usage history");
let history = &self.frame_times;
// TODO: we should not use `slider_width` as default graph width.
let height = ui.style().spacing.slider_width;
let rect = ui.allocate_space(vec2(ui.available_finite().width(), height));
let style = ui.style().noninteractive();
let mut cmds = vec![PaintCmd::Rect {
rect,
corner_radius: style.corner_radius,
fill: ui.style().visuals.dark_bg_color,
stroke: ui.style().noninteractive().bg_stroke,
}];
let rect = rect.shrink(4.0);
let line_stroke = Stroke::new(1.0, Srgba::additive_luminance(128));
if let Some(mouse_pos) = ui.input().mouse.pos {
if rect.contains(mouse_pos) {
let y = mouse_pos.y;
cmds.push(PaintCmd::line_segment(
[pos2(rect.left(), y), pos2(rect.right(), y)],
line_stroke,
));
let cpu_usage = remap(y, rect.bottom_up_range(), 0.0..=graph_top_cpu_usage);
let text = format!("{:.1} ms", 1e3 * cpu_usage);
cmds.push(PaintCmd::text(
ui.fonts(),
pos2(rect.left(), y),
align::LEFT_BOTTOM,
text,
TextStyle::Monospace,
color::WHITE,
));
}
}
let circle_color = Srgba::additive_luminance(196);
let radius = 2.0;
let right_side_time = ui.input().time; // Time at right side of screen
for (time, cpu_usage) in history.iter() {
let age = (right_side_time - time) as f32;
let x = remap(age, history.max_age()..=0.0, rect.range_x());
let y = remap_clamp(cpu_usage, 0.0..=graph_top_cpu_usage, rect.bottom_up_range());
cmds.push(PaintCmd::line_segment(
[pos2(x, rect.bottom()), pos2(x, y)],
line_stroke,
));
if cpu_usage < graph_top_cpu_usage {
cmds.push(PaintCmd::circle_filled(pos2(x, y), radius, circle_color));
}
}
ui.painter().extend(cmds);
}
}
// ----------------------------------------------------------------------------
/// Special input to the demo-app.
#[derive(Default)]
pub struct DemoEnvironment {
@ -62,6 +176,7 @@ pub struct DemoEnvironment {
///
/// Implements `egui::app::App` so it can be used with
/// [`egui_glium`](https://crates.io/crates/egui_glium) and [`egui_web`](https://crates.io/crates/egui_web).
// TODO: split into `DemoWindows` and `app::DemoApp`
#[derive(Default)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "serde", serde(default))]
@ -72,7 +187,10 @@ pub struct DemoApp {
open_windows: OpenWindows,
demo_window: DemoWindow,
fractal_clock: FractalClock,
num_frames_painted: u64,
#[cfg_attr(feature = "serde", serde(skip))]
frame_history: FrameHistory,
#[cfg_attr(feature = "serde", serde(skip))]
color_test: ColorTest,
show_color_test: bool,
@ -195,15 +313,18 @@ impl DemoApp {
});
}
// TODO: give cpu_usage and web_info via `struct BackendInfo`
fn backend_ui(&mut self, ui: &mut Ui, backend: &mut dyn app::Backend) {
self.frame_history
.on_new_frame(ui.input().time, backend.cpu_usage());
let is_web = backend.web_info().is_some();
if is_web {
ui.label("Egui is an immediate mode GUI written in Rust, compiled to WebAssembly, rendered with WebGL.");
ui.label(
"Everything you see is rendered as textured triangles. There is no DOM. There are no HTML elements."
);
ui.label("This is not JavaScript. This is Rust, running at 60 FPS. This is the web page, reinvented with game tech.");
"Everything you see is rendered as textured triangles. There is no DOM. There are no HTML elements. \
This is not JavaScript. This is Rust, running at 60 FPS. This is the web page, reinvented with game tech.");
ui.label("This is also work in progress, and not ready for production... yet :)");
ui.horizontal(|ui| {
ui.label("Project home page:");
@ -219,16 +340,28 @@ impl DemoApp {
ui.separator();
ui.add(
label!(
"CPU usage: {:.2} ms / frame (excludes painting)",
1e3 * backend.cpu_time()
)
.text_style(TextStyle::Monospace),
);
self.run_mode_ui(ui);
if self.run_mode == RunMode::Continuous {
ui.label(format!(
"Repainting the UI each frame. FPS: {:.1}",
self.frame_history.fps()
));
} else {
ui.label("Only running UI code when there are animations or input");
}
ui.separator();
self.frame_history.ui(ui);
ui.separator();
ui.checkbox(
"Show color blend test (debug backend painter)",
&mut self.show_color_test,
);
}
fn run_mode_ui(&mut self, ui: &mut Ui) {
ui.horizontal(|ui| {
let run_mode = &mut self.run_mode;
ui.label("Run mode:");
@ -237,30 +370,15 @@ impl DemoApp {
ui.radio_value("Reactive", run_mode, RunMode::Reactive)
.tooltip_text("Repaint when there are animations or input (e.g. mouse movement)");
});
if self.run_mode == RunMode::Continuous {
ui.add(
label!("Repainting the UI each frame. FPS: {:.1}", backend.fps())
.text_style(TextStyle::Monospace),
);
} else {
ui.label("Only running UI code when there are animations or input");
}
self.num_frames_painted += 1;
ui.label(format!("Total frames painted: {}", self.num_frames_painted));
ui.separator();
ui.checkbox(
"Show color blend test (debug backend painter)",
&mut self.show_color_test,
);
}
}
impl app::App for DemoApp {
fn ui(&mut self, ui: &mut Ui, backend: &mut dyn app::Backend) {
Window::new("Backend").scroll(false).show(ui.ctx(), |ui| {
Window::new("Backend")
.min_width(360.0)
.scroll(false)
.show(ui.ctx(), |ui| {
self.backend_ui(ui, backend);
});

View file

@ -1,40 +1,84 @@
use std::collections::VecDeque;
/// This struct tracks recent values of some time series.
///
/// One use is to show a log of recent events,
/// or show a graph over recent events.
///
/// It has both a maximum length and a maximum storage time.
/// Elements are dropped when either max length or max age is reached.
///
/// Time difference between values can be zero, but never negative.
///
/// This can be used for things like smoothed averages (for e.g. FPS)
/// or for smoothed velocity (e.g. mouse pointer speed).
/// All times are in seconds.
#[derive(Clone, Debug)]
pub struct MovementTracker<T> {
pub struct History<T> {
/// In elements, i.e. of `values.len()`
max_len: usize,
max_age: f64,
/// (time, value) pais
/// In seconds
max_age: f64, // TODO: f32
/// Total number of elements seen ever
total_count: u64,
/// (time, value) pairs, oldest front, newest back.
/// Time difference between values can be zero, but never negative.
values: VecDeque<(f64, T)>,
}
impl<T> MovementTracker<T>
impl<T> History<T>
where
T: Copy,
{
pub fn new(max_len: usize, max_age: f64) -> Self {
Self::from_max_len_age(max_len, max_age)
}
pub fn from_max_len_age(max_len: usize, max_age: f64) -> Self {
Self {
max_len,
max_age,
total_count: 0,
values: Default::default(),
}
}
pub fn max_len(&self) -> usize {
self.max_len
}
pub fn max_age(&self) -> f32 {
self.max_age as f32
}
pub fn is_empty(&self) -> bool {
self.values.is_empty()
}
/// Current number of values kept in history
pub fn len(&self) -> usize {
self.values.len()
}
/// Amount of time contained from start to end in this `MovementTracker`
pub fn dt(&self) -> f32 {
/// Total number of values seen.
/// Includes those that have been discarded due to `max_len` or `max_age`.
pub fn total_count(&self) -> u64 {
self.total_count
}
pub fn latest(&self) -> Option<T> {
self.values.back().map(|(_, value)| *value)
}
pub fn latest_mut(&mut self) -> Option<&mut T> {
self.values.back_mut().map(|(_, value)| value)
}
/// Amount of time contained from start to end in this `History`.
pub fn duration(&self) -> f32 {
if let (Some(front), Some(back)) = (self.values.front(), self.values.back()) {
(back.0 - front.0) as f32
} else {
@ -42,6 +86,13 @@ where
}
}
/// `(time, value)` pairs
/// Time difference between values can be zero, but never negative.
// TODO: impl IntoIter
pub fn iter<'a>(&'a self) -> impl Iterator<Item = (f64, T)> + 'a {
self.values.iter().map(|(time, value)| (*time, *value))
}
pub fn values<'a>(&'a self) -> impl Iterator<Item = T> + 'a {
self.values.iter().map(|(_time, value)| *value)
}
@ -53,13 +104,14 @@ where
/// Values must be added with a monotonically increasing time, or at least not decreasing.
pub fn add(&mut self, now: f64, value: T) {
if let Some((last_time, _)) = self.values.back() {
debug_assert!(now >= *last_time, "Time shouldn't go backwards");
debug_assert!(now >= *last_time, "Time shouldn't move backwards");
}
self.total_count += 1;
self.values.push_back((now, value));
self.flush(now);
}
/// Mean time difference between values in this `MovementTracker`.
/// Mean time difference between values in this `History`.
pub fn mean_time_interval(&self) -> Option<f32> {
if let (Some(first), Some(last)) = (self.values.front(), self.values.back()) {
let n = self.len();
@ -88,7 +140,7 @@ where
}
}
impl<T> MovementTracker<T>
impl<T> History<T>
where
T: Copy,
T: std::iter::Sum,
@ -108,7 +160,7 @@ where
}
}
impl<T, Vel> MovementTracker<T>
impl<T, Vel> History<T>
where
T: Copy,
T: std::ops::Sub<Output = Vel>,

View file

@ -1,6 +1,6 @@
//! The input needed by Egui.
use crate::math::*;
use crate::{math::*, History};
/// If mouse moves more than this, it is no longer a click (but maybe a drag)
const MAX_CLICK_DIST: f32 = 6.0;
@ -128,7 +128,7 @@ pub struct MouseInput {
/// Recent movement of the mouse.
/// Used for calculating velocity of mouse pointer.
pub pos_tracker: MovementTracker<Pos2>,
pos_history: History<Pos2>,
}
impl Default for MouseInput {
@ -145,7 +145,7 @@ impl Default for MouseInput {
press_origin: None,
delta: Vec2::zero(),
velocity: Vec2::zero(),
pos_tracker: MovementTracker::new(1000, 0.1),
pos_history: History::new(1000, 0.1),
}
}
}
@ -295,20 +295,20 @@ impl MouseInput {
if pressed {
// Start of a drag: we want to track the velocity for during the drag
// and ignore any incoming movement
self.pos_tracker.clear();
self.pos_history.clear();
}
if let Some(mouse_pos) = new.mouse_pos {
self.pos_tracker.add(new.time, mouse_pos);
self.pos_history.add(new.time, mouse_pos);
} else {
// we do not clear the `mouse_tracker` here, because it is exactly when a finger has
// released from the touch screen that we may want to assign a velocity to whatever
// the user tried to throw
}
self.pos_tracker.flush(new.time);
let velocity = if self.pos_tracker.len() >= 3 && self.pos_tracker.dt() > 0.01 {
self.pos_tracker.velocity().unwrap_or_default()
self.pos_history.flush(new.time);
let velocity = if self.pos_history.len() >= 3 && self.pos_history.duration() > 0.01 {
self.pos_history.velocity().unwrap_or_default()
} else {
Vec2::default()
};
@ -325,7 +325,7 @@ impl MouseInput {
press_origin,
delta,
velocity,
pos_tracker: self.pos_tracker,
pos_history: self.pos_history,
}
}
}
@ -411,7 +411,7 @@ impl MouseInput {
press_origin,
delta,
velocity,
pos_tracker: _,
pos_history: _,
} = self;
ui.label(format!("down: {}", down));

View file

@ -1,4 +1,4 @@
use crate::{math::*, style::Style};
use crate::{math::*, style::Style, Align};
// ----------------------------------------------------------------------------
@ -17,44 +17,6 @@ impl Default for Direction {
}
}
/// left/center/right or top/center/bottom alignment for e.g. anchors and `Layout`s.
#[derive(Clone, Copy, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
pub enum Align {
/// Left/Top
Min,
/// Note: requires a bounded/known available_width.
Center,
/// Right/Bottom
/// Note: requires a bounded/known available_width.
Max,
}
impl Default for Align {
fn default() -> Align {
Align::Min
}
}
/// Used e.g. to anchor a piece of text to a part of the rectangle.
/// Give a position within the rect, specified by the aligns
pub(crate) fn anchor_rect(rect: Rect, anchor: (Align, Align)) -> Rect {
let x = match anchor.0 {
Align::Min => rect.left(),
Align::Center => rect.left() - 0.5 * rect.width(),
Align::Max => rect.left() - rect.width(),
};
let y = match anchor.1 {
Align::Min => rect.top(),
Align::Center => rect.top() - 0.5 * rect.height(),
Align::Max => rect.top() - rect.height(),
};
Rect::from_min_size(pos2(x, y), rect.size())
}
// ----------------------------------------------------------------------------
/// The layout of a `Ui`, e.g. horizontal left-aligned.

View file

@ -44,12 +44,14 @@
rust_2018_idioms,
)]
pub mod align;
mod animation_manager;
pub mod app;
pub(crate) mod cache;
pub mod containers;
mod context;
pub mod demos;
mod history;
mod id;
mod input;
mod introspection;
@ -66,9 +68,11 @@ mod ui;
pub mod widgets;
pub use {
align::Align,
containers::*,
context::Context,
demos::DemoApp,
history::History,
id::Id,
input::*,
layers::*,

View file

@ -4,13 +4,12 @@ use std::ops::{Add, Mul, RangeInclusive};
// ----------------------------------------------------------------------------
mod movement_tracker;
mod pos2;
mod rect;
pub mod smart_aim;
mod vec2;
pub use {movement_tracker::*, pos2::*, rect::*, vec2::*};
pub use {pos2::*, rect::*, vec2::*};
// ----------------------------------------------------------------------------

View file

@ -147,6 +147,10 @@ impl Rect {
self.min.y..=self.max.y
}
pub fn bottom_up_range(&self) -> RangeInclusive<f32> {
self.max.y..=self.min.y
}
pub fn is_empty(&self) -> bool {
self.max.x < self.min.x || self.max.y < self.min.y
}

View file

@ -1,6 +1,9 @@
use {
super::{font::Galley, fonts::TextStyle, Srgba, Triangles},
crate::math::{Pos2, Rect},
super::{font::Galley, fonts::TextStyle, Fonts, Srgba, Triangles},
crate::{
align::{anchor_rect, Align},
math::{Pos2, Rect},
},
};
// TODO: rename, e.g. `paint::Cmd`?
@ -86,6 +89,25 @@ impl PaintCmd {
stroke: stroke.into(),
}
}
pub fn text(
fonts: &Fonts,
pos: Pos2,
anchor: (Align, Align),
text: impl Into<String>,
text_style: TextStyle,
color: Srgba,
) -> Self {
let font = &fonts[text_style];
let galley = font.layout_multiline(text.into(), f32::INFINITY);
let rect = anchor_rect(Rect::from_min_size(pos, galley.size), anchor);
Self::Text {
pos: rect.min,
galley,
text_style,
color,
}
}
}
#[derive(Clone, Copy, Debug, Default)]

View file

@ -1,11 +1,12 @@
use std::sync::Arc;
use crate::{
anchor_rect, color,
align::{anchor_rect, Align, LEFT_TOP},
color,
layers::PaintCmdIdx,
math::{Pos2, Rect, Vec2},
paint::{font, Fonts, PaintCmd, Stroke, TextStyle},
Align, Context, Layer, Srgba,
Context, Layer, Srgba,
};
/// Helper to paint shapes and text to a specific region on a specific layer.
@ -116,18 +117,16 @@ impl Painter {
impl Painter {
pub fn debug_rect(&mut self, rect: Rect, color: Srgba, text: impl Into<String>) {
self.rect_stroke(rect, 0.0, (1.0, color));
let anchor = (Align::Min, Align::Min);
let text_style = TextStyle::Monospace;
self.text(rect.min, anchor, text.into(), text_style, color);
self.text(rect.min, LEFT_TOP, text.into(), text_style, color);
}
pub fn error(&self, pos: Pos2, text: impl Into<String>) {
let text = text.into();
let anchor = (Align::Min, Align::Min);
let text_style = TextStyle::Monospace;
let font = &self.fonts()[text_style];
let galley = font.layout_multiline(text, f32::INFINITY);
let rect = anchor_rect(Rect::from_min_size(pos, galley.size), anchor);
let rect = anchor_rect(Rect::from_min_size(pos, galley.size), LEFT_TOP);
self.add(PaintCmd::Rect {
rect: rect.expand(2.0),
corner_radius: 0.0,

View file

@ -23,11 +23,18 @@ pub struct Style {
}
impl Style {
// TODO: rename style.interact() to maybe... `style.response_visuals` ?
/// Use this style for interactive things
// TODO: rename style.interact() to maybe... `style.interactive` ?
/// Use this style for interactive things.
/// Note that you must already have a response,
/// i.e. you must allocate space and interact BEFORE painting the widget!
pub fn interact(&self, response: &Response) -> &WidgetVisuals {
self.visuals.widgets.style(response)
}
/// Style to use for non-interactive widgets.
pub fn noninteractive(&self) -> &WidgetVisuals {
&self.visuals.widgets.noninteractive
}
}
#[derive(Clone, Debug)]
@ -116,6 +123,10 @@ pub struct Visuals {
}
impl Visuals {
pub fn noninteractive(&self) -> &WidgetVisuals {
&self.widgets.noninteractive
}
pub fn text_color(&self) -> Srgba {
self.widgets.noninteractive.text_color()
}

View file

@ -478,6 +478,11 @@ impl Ui {
self.add(label.into().heading())
}
/// Shortcut for `add(Label::new(text).monospace())`
pub fn monospace(&mut self, label: impl Into<Label>) -> Response {
self.add(label.into().monospace())
}
/// Shortcut for `add(Hyperlink::new(url))`
pub fn hyperlink(&mut self, url: impl Into<String>) -> Response {
self.add(Hyperlink::new(url))
@ -616,10 +621,10 @@ impl Ui {
impl Ui {
pub fn collapsing<R>(
&mut self,
text: impl Into<String>,
heading: impl Into<String>,
add_contents: impl FnOnce(&mut Ui) -> R,
) -> Option<R> {
CollapsingHeader::new(text).show(self, add_contents)
CollapsingHeader::new(heading).show(self, add_contents)
}
/// Create a child ui at the current cursor.

View file

@ -65,6 +65,10 @@ impl Label {
self.text_style(TextStyle::Heading)
}
pub fn monospace(self) -> Self {
self.text_style(TextStyle::Monospace)
}
pub fn text_color(mut self, text_color: Srgba) -> Self {
self.text_color = Some(text_color);
self

View file

@ -14,7 +14,7 @@ const EGUI_MEMORY_KEY: &str = "egui";
const WINDOW_KEY: &str = "window";
pub struct GliumBackend {
frame_times: egui::MovementTracker<f32>,
previous_frame_time: Option<f32>,
quit: bool,
painter: Painter,
}
@ -22,7 +22,7 @@ pub struct GliumBackend {
impl GliumBackend {
pub fn new(painter: Painter) -> Self {
Self {
frame_times: egui::MovementTracker::new(1000, 1.0),
previous_frame_time: None,
quit: false,
painter,
}
@ -30,12 +30,8 @@ impl GliumBackend {
}
impl Backend for GliumBackend {
fn cpu_time(&self) -> f32 {
self.frame_times.average().unwrap_or_default()
}
fn fps(&self) -> f32 {
1.0 / self.frame_times.mean_time_interval().unwrap_or_default()
fn cpu_usage(&self) -> Option<f32> {
self.previous_frame_time
}
fn seconds_since_midnight(&self) -> Option<f64> {
@ -100,7 +96,7 @@ pub fn run(title: &str, mut storage: FileStorage, mut app: impl App + 'static) -
let (output, paint_jobs) = ctx.end_frame();
let frame_time = (Instant::now() - egui_start).as_secs_f64() as f32;
runner.frame_times.add(raw_input.time, frame_time);
runner.previous_frame_time = Some(frame_time);
runner
.painter
@ -127,7 +123,7 @@ pub fn run(title: &str, mut storage: FileStorage, mut app: impl App + 'static) -
glutin::event::Event::WindowEvent { event, .. } => {
input_to_egui(event, clipboard.as_mut(), &mut raw_input, control_flow);
display.gl_window().window().request_redraw(); // TODO: maybe only on some events?
display.gl_window().window().request_redraw(); // TODO: ask Egui if the events warrants a repaint instead
}
glutin::event::Event::LoopDestroyed => {
egui::app::set_value(

View file

@ -10,7 +10,7 @@ pub use egui::{
pub struct WebBackend {
ctx: Arc<egui::Context>,
painter: webgl::Painter,
frame_times: egui::MovementTracker<f32>,
previous_frame_time: Option<f32>,
frame_start: Option<f64>,
last_save_time: Option<f64>,
}
@ -22,7 +22,7 @@ impl WebBackend {
Ok(Self {
ctx,
painter: webgl::Painter::new(canvas_id)?,
frame_times: egui::MovementTracker::new(1000, 1.0),
previous_frame_time: None,
frame_start: None,
last_save_time: None,
})
@ -50,7 +50,7 @@ impl WebBackend {
self.auto_save();
let now = now_sec();
self.frame_times.add(now, (now - frame_start) as f32);
self.previous_frame_time = Some((now - frame_start) as f32);
Ok((output, paint_jobs))
}
@ -87,13 +87,8 @@ impl Backend for WebBackend {
})
}
/// excludes painting
fn cpu_time(&self) -> f32 {
self.frame_times.average().unwrap_or_default()
}
fn fps(&self) -> f32 {
1.0 / self.frame_times.mean_time_interval().unwrap_or_default()
fn cpu_usage(&self) -> Option<f32> {
self.previous_frame_time
}
fn seconds_since_midnight(&self) -> Option<f64> {