mirror of
https://github.com/enso-org/enso.git
synced 2024-11-23 08:08:34 +03:00
Allow collection of EnsoGL stats when Monitor panel is not visible (#3260)
This change makes EnsoGL runtime stats be always collected, even when EnsoGL `Monitor` panel is not visible. Those stats are intended to be used in the future by a profiling framework. **Performance impact:** Continuous collection of stats introduces an overhead of two Web Performance API `now()` calls in each frame of the main rendering loop, plus a small number of simple arithmetic calculations. This is assumed to be a negligible and acceptable overhead. #### Visuals A screenshot of the Monitor panel in full `ide` after applying the PR, taken in IDE built with `./run dist`: <img width="991" alt="Screenshot 2022-02-14 at 16 11 42" src="https://user-images.githubusercontent.com/273837/153891378-8a2fb333-34ce-46ce-99df-7d796817310c.png"> A recording, also in IDE built with `./run dist`; note that FPS is impacted by the act of recording itself: https://user-images.githubusercontent.com/273837/154104016-49a12e23-1210-4477-9743-ec1611e5b4ed.mov https://www.pivotaltracker.com/story/show/181093601 # Important Notes - Responsibility for controlling how `Stats` gathering and calculation is performed at various points in the main rendering loop was removed from `Monitor` - the `Monitor`'s purpose is only to display existing data, it should not influence how the data is collected. - Two previously existing distinct `Monitor` structs were merged into one, to avoid confusion; after previous refactorings, the remaining `stats::Monitor` did not have much useful code anyway. - In `stats` package, refactoring was done, to make `StatsData` a "dumb", data-only type, and to move the logic related to stats collection and frame tracking to other helper types. [ci no changelog needed]
This commit is contained in:
parent
964645fddf
commit
b01217aa69
27
app/ide-desktop/package-lock.json
generated
27
app/ide-desktop/package-lock.json
generated
@ -749,9 +749,9 @@
|
||||
"integrity": "sha512-dZMzN0uAjwJXWYYAcnxIwXqRTZw3o14hGe7O6uhwjD1ZQWPVYA5lASgnNskEBra0knVBsOXB4KXg+HnlKewN/A=="
|
||||
},
|
||||
"node_modules/@grpc/grpc-js": {
|
||||
"version": "1.5.4",
|
||||
"resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.5.4.tgz",
|
||||
"integrity": "sha512-+nJTOsqpFAXnfFrMZ7Too4XXZ/J9O+8jYvSoaunupoC7I7b9H4iex1BRsbTdOmiowfPGJrWit7jUPmbENSUSpw==",
|
||||
"version": "1.5.5",
|
||||
"resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.5.5.tgz",
|
||||
"integrity": "sha512-FTd27ItHlsSG/7hp62xgI9YnqSwRbHRSVmDVR8DwOoC+6t8JhHRXe2JL0U8N9GLc0jS0HrtEbO/KP5+G0ebjLQ==",
|
||||
"dependencies": {
|
||||
"@grpc/proto-loader": "^0.6.4",
|
||||
"@types/node": ">=12.12.47"
|
||||
@ -4871,12 +4871,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/color": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/color/-/color-4.2.0.tgz",
|
||||
"integrity": "sha512-hHTcrbvEnGjC7WBMk6ibQWFVDgEFTVmjrz2Q5HlU6ltwxv0JJN2Z8I7uRbWeQLF04dikxs8zgyZkazRJvSMtyQ==",
|
||||
"version": "4.2.1",
|
||||
"resolved": "https://registry.npmjs.org/color/-/color-4.2.1.tgz",
|
||||
"integrity": "sha512-MFJr0uY4RvTQUKvPq7dh9grVOTYSFeXja2mBXioCGjnjJoXrAp9jJ1NQTDR73c9nwBSAQiNKloKl5zq9WB9UPw==",
|
||||
"dependencies": {
|
||||
"color-convert": "^2.0.1",
|
||||
"color-string": "^1.9.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.5.0"
|
||||
}
|
||||
},
|
||||
"node_modules/color-convert": {
|
||||
@ -19551,9 +19554,9 @@
|
||||
"integrity": "sha512-dZMzN0uAjwJXWYYAcnxIwXqRTZw3o14hGe7O6uhwjD1ZQWPVYA5lASgnNskEBra0knVBsOXB4KXg+HnlKewN/A=="
|
||||
},
|
||||
"@grpc/grpc-js": {
|
||||
"version": "1.5.4",
|
||||
"resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.5.4.tgz",
|
||||
"integrity": "sha512-+nJTOsqpFAXnfFrMZ7Too4XXZ/J9O+8jYvSoaunupoC7I7b9H4iex1BRsbTdOmiowfPGJrWit7jUPmbENSUSpw==",
|
||||
"version": "1.5.5",
|
||||
"resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.5.5.tgz",
|
||||
"integrity": "sha512-FTd27ItHlsSG/7hp62xgI9YnqSwRbHRSVmDVR8DwOoC+6t8JhHRXe2JL0U8N9GLc0jS0HrtEbO/KP5+G0ebjLQ==",
|
||||
"requires": {
|
||||
"@grpc/proto-loader": "^0.6.4",
|
||||
"@types/node": ">=12.12.47"
|
||||
@ -22929,9 +22932,9 @@
|
||||
}
|
||||
},
|
||||
"color": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/color/-/color-4.2.0.tgz",
|
||||
"integrity": "sha512-hHTcrbvEnGjC7WBMk6ibQWFVDgEFTVmjrz2Q5HlU6ltwxv0JJN2Z8I7uRbWeQLF04dikxs8zgyZkazRJvSMtyQ==",
|
||||
"version": "4.2.1",
|
||||
"resolved": "https://registry.npmjs.org/color/-/color-4.2.1.tgz",
|
||||
"integrity": "sha512-MFJr0uY4RvTQUKvPq7dh9grVOTYSFeXja2mBXioCGjnjJoXrAp9jJ1NQTDR73c9nwBSAQiNKloKl5zq9WB9UPw==",
|
||||
"requires": {
|
||||
"color-convert": "^2.0.1",
|
||||
"color-string": "^1.9.0"
|
||||
|
@ -1,4 +1,4 @@
|
||||
//! This module implements performance monitoring utils.
|
||||
//! This module implements the stats monitor view, which can be visible on the screen in debug mode.
|
||||
|
||||
use crate::prelude::*;
|
||||
|
||||
@ -198,9 +198,59 @@ impl Drop for DomData {
|
||||
// === Monitor ===
|
||||
// ===============
|
||||
|
||||
/// Implementation of the monitoring panel.
|
||||
#[derive(Debug)]
|
||||
/// Visual panel showing performance-related statistics.
|
||||
#[derive(Debug, Clone, CloneRef)]
|
||||
pub struct Monitor {
|
||||
renderer: Rc<RefCell<Renderer>>,
|
||||
}
|
||||
|
||||
impl Default for Monitor {
|
||||
fn default() -> Self {
|
||||
let mut renderer = Renderer::new();
|
||||
renderer.add::<FrameTime>();
|
||||
renderer.add::<Fps>();
|
||||
renderer.add::<WasmMemory>();
|
||||
renderer.add::<GpuMemoryUsage>();
|
||||
renderer.add::<DrawCallCount>();
|
||||
renderer.add::<DataUploadCount>();
|
||||
renderer.add::<DataUploadSize>();
|
||||
renderer.add::<BufferCount>();
|
||||
renderer.add::<SymbolCount>();
|
||||
renderer.add::<ShaderCount>();
|
||||
renderer.add::<ShaderCompileCount>();
|
||||
renderer.add::<SpriteSystemCount>();
|
||||
renderer.add::<SpriteCount>();
|
||||
Self { renderer: Rc::new(RefCell::new(renderer)) }
|
||||
}
|
||||
}
|
||||
|
||||
impl Monitor {
|
||||
/// Constructor.
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
/// Draw the monitor and update its graphs based on the provided stats data.
|
||||
/// Does nothing if the monitor is not visible (see: [`toggle()`]).
|
||||
pub fn sample_and_draw(&self, stats: &StatsData) {
|
||||
self.renderer.borrow_mut().sample_and_draw(stats);
|
||||
}
|
||||
|
||||
/// Toggle the visibility of the monitor.
|
||||
pub fn toggle(&self) {
|
||||
self.renderer.borrow_mut().toggle();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ================
|
||||
// === Renderer ===
|
||||
// ================
|
||||
|
||||
/// Code responsible for drawing [`Monitor`]'s data.
|
||||
#[derive(Debug)]
|
||||
struct Renderer {
|
||||
user_config: Config,
|
||||
config: SamplerConfig,
|
||||
width: f64,
|
||||
@ -210,11 +260,8 @@ pub struct Monitor {
|
||||
first_draw: bool,
|
||||
}
|
||||
|
||||
|
||||
// === Public API ===
|
||||
|
||||
impl Default for Monitor {
|
||||
fn default() -> Self {
|
||||
impl Renderer {
|
||||
fn new() -> Self {
|
||||
let user_config = Config::default();
|
||||
let panels = default();
|
||||
let width = default();
|
||||
@ -226,35 +273,21 @@ impl Default for Monitor {
|
||||
out.update_config();
|
||||
out
|
||||
}
|
||||
}
|
||||
|
||||
impl Monitor {
|
||||
/// Cnstructor.
|
||||
pub fn new() -> Self {
|
||||
default()
|
||||
}
|
||||
|
||||
/// Modify the Monitor's config and update the view.
|
||||
pub fn mod_config<F: FnOnce(&mut Config)>(&mut self, f: F) {
|
||||
f(&mut self.user_config);
|
||||
self.update_config();
|
||||
}
|
||||
|
||||
/// Add new display element.
|
||||
pub fn add<S: Sampler + Default + 'static>(&mut self) -> Panel {
|
||||
fn add<S: Sampler + Default + 'static>(&mut self) {
|
||||
let panel = Panel::new(self.config.clone(), S::default());
|
||||
self.panels.push(panel.clone());
|
||||
self.panels.push(panel);
|
||||
self.resize();
|
||||
panel
|
||||
}
|
||||
|
||||
/// Check whether the mointor is visible.
|
||||
pub fn visible(&self) -> bool {
|
||||
/// Check whether the monitor is visible.
|
||||
fn visible(&self) -> bool {
|
||||
self.dom.is_some()
|
||||
}
|
||||
|
||||
/// Show the monitor and add it's DOM to the scene.
|
||||
pub fn show(&mut self) {
|
||||
fn show(&mut self) {
|
||||
if !self.visible() {
|
||||
self.first_draw = true;
|
||||
self.dom = Some(Dom::new());
|
||||
@ -263,12 +296,12 @@ impl Monitor {
|
||||
}
|
||||
|
||||
/// Hides the monitor and remove it's DOM from the scene.
|
||||
pub fn hide(&mut self) {
|
||||
fn hide(&mut self) {
|
||||
self.dom = None;
|
||||
}
|
||||
|
||||
/// Toggle the visibility of the monitor.
|
||||
pub fn toggle(&mut self) {
|
||||
fn toggle(&mut self) {
|
||||
if self.visible() {
|
||||
self.hide();
|
||||
} else {
|
||||
@ -276,8 +309,17 @@ impl Monitor {
|
||||
}
|
||||
}
|
||||
|
||||
/// Draw the Monitor and update all of it's values.
|
||||
pub fn draw(&mut self) {
|
||||
fn sample_and_draw(&mut self, stats: &StatsData) {
|
||||
if self.visible() {
|
||||
for panel in &self.panels {
|
||||
panel.sample_and_postprocess(stats);
|
||||
}
|
||||
self.draw();
|
||||
}
|
||||
}
|
||||
|
||||
/// Draw the widget and update all of the graphs.
|
||||
fn draw(&mut self) {
|
||||
if let Some(dom) = self.dom.clone() {
|
||||
if self.first_draw {
|
||||
self.first_draw = false;
|
||||
@ -288,12 +330,7 @@ impl Monitor {
|
||||
self.draw_plots(&dom);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// === Private API ===
|
||||
|
||||
impl Monitor {
|
||||
fn update_config(&mut self) {
|
||||
self.config = self.user_config.to_js_config()
|
||||
}
|
||||
@ -743,14 +780,8 @@ macro_rules! stats_sampler {
|
||||
$precision
|
||||
}
|
||||
fn check(&self, stats: &StatsData) -> ValueCheck {
|
||||
// Before the first frame, all stat values will be 0, which in (only) this case is
|
||||
// correct.
|
||||
if stats.frame_counter == 0 {
|
||||
ValueCheck::Correct
|
||||
} else {
|
||||
let value = self.value(&stats);
|
||||
ValueCheck::from_threshold($warn_threshold, $err_threshold, value)
|
||||
}
|
||||
let value = self.value(&stats);
|
||||
ValueCheck::from_threshold($warn_threshold, $err_threshold, value)
|
||||
}
|
||||
fn max_value(&self) -> Option<f64> {
|
||||
$max_value
|
||||
@ -786,33 +817,91 @@ stats_sampler!(
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use enso_prelude::*;
|
||||
|
||||
use crate::debug::stats::Stats;
|
||||
use crate::debug::stats::StatsWithTimeProvider;
|
||||
use enso_web::TimeProvider;
|
||||
use std::ops::AddAssign;
|
||||
|
||||
use assert_approx_eq::assert_approx_eq;
|
||||
|
||||
|
||||
#[derive(Default)]
|
||||
struct TestSampler<S: Sampler> {
|
||||
stats: Stats,
|
||||
sampler: S,
|
||||
t: f64,
|
||||
// === MockTimeProvider ===
|
||||
|
||||
#[derive(Default, Clone)]
|
||||
struct MockTimeProvider {
|
||||
t: Rc<RefCell<f64>>,
|
||||
}
|
||||
|
||||
impl TimeProvider for MockTimeProvider {
|
||||
fn now(&self) -> f64 {
|
||||
*self.t.borrow()
|
||||
}
|
||||
}
|
||||
|
||||
impl AddAssign<f64> for MockTimeProvider {
|
||||
fn add_assign(&mut self, dt: f64) {
|
||||
*self.t.borrow_mut() += dt;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// === TestSampler ===
|
||||
|
||||
struct TestSampler<S> {
|
||||
stats: StatsWithTimeProvider<MockTimeProvider>,
|
||||
sampler: S,
|
||||
t: MockTimeProvider,
|
||||
}
|
||||
|
||||
impl<S: Sampler + Default> Default for TestSampler<S> {
|
||||
fn default() -> Self {
|
||||
let t: MockTimeProvider = default();
|
||||
let stats = StatsWithTimeProvider::new(t.clone());
|
||||
let sampler = default();
|
||||
Self { stats, sampler, t }
|
||||
}
|
||||
}
|
||||
|
||||
const STAT_VALUE_COMPARISON_PRECISION: f64 = 0.001;
|
||||
|
||||
macro_rules! test_and_advance_frame {
|
||||
($test:expr, $expected_value:expr, $expected_check:path
|
||||
; next: $frame_time:expr, $post_frame_delay:expr) => {
|
||||
let prev_frame_stats = $test.stats.begin_frame($test.t);
|
||||
let tested_value = $test.sampler.value(&prev_frame_stats);
|
||||
let tested_check = $test.sampler.check(&prev_frame_stats);
|
||||
assert_approx_eq!(tested_value, $expected_value, 0.001);
|
||||
assert!(matches!(tested_check, $expected_check));
|
||||
let prev_frame_stats = $test.stats.begin_frame();
|
||||
if let Some(prev_frame_stats) = prev_frame_stats {
|
||||
let tested_value = $test.sampler.value(&prev_frame_stats);
|
||||
let tested_check = $test.sampler.check(&prev_frame_stats);
|
||||
assert_approx_eq!(tested_value, $expected_value, STAT_VALUE_COMPARISON_PRECISION);
|
||||
let mismatch_msg = iformat!(
|
||||
"Stat check was expected to return: " $expected_check;?
|
||||
", but got: " tested_check;? " instead.");
|
||||
assert!(matches!(tested_check, $expected_check), "{}", mismatch_msg);
|
||||
} else {
|
||||
assert!(false,
|
||||
"Expected previous frame's stats to be returned by begin_frame(), \
|
||||
but got none.");
|
||||
}
|
||||
$test.t += $frame_time;
|
||||
$test.stats.end_frame($test.t);
|
||||
$test.stats.end_frame();
|
||||
$test.t += $post_frame_delay;
|
||||
};
|
||||
|
||||
($test:expr, None; next: $frame_time:expr, $post_frame_delay:expr) => {
|
||||
let prev_frame_stats = $test.stats.begin_frame();
|
||||
let mismatch_msg = iformat!(
|
||||
"Expected no stats to be returned by begin_frame(), but got: "
|
||||
prev_frame_stats;? " instead.");
|
||||
assert!(matches!(prev_frame_stats, None), "{}", mismatch_msg);
|
||||
$test.t += $frame_time;
|
||||
$test.stats.end_frame();
|
||||
$test.t += $post_frame_delay;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
// === Tests ===
|
||||
|
||||
#[test]
|
||||
fn frame_time() {
|
||||
// Note: 60 FPS means there's 16.6(6) ms budget for 1 frame. The test will be written under
|
||||
@ -823,10 +912,8 @@ mod tests {
|
||||
// Frame 1: simulate we managed to complete the work in 10ms, and then we wait 6ms before
|
||||
// starting next frame.
|
||||
//
|
||||
// Note: there is no frame before "Frame 1", so the tested value for the previous frame
|
||||
// will always be 0.0 and "Correct" at this point (which is a special case - depending on
|
||||
// tested stat, for later frames 0.0 could result in a threshold warning/error).
|
||||
test_and_advance_frame!(test, 0.0, ValueCheck::Correct; next: 10.0, 6.0);
|
||||
// Note: we expect no stats data to be returned before the 1st frame was started.
|
||||
test_and_advance_frame!(test, None; next: 10.0, 6.0);
|
||||
|
||||
// Frame 2: simulate we managed to complete the work in 5ms, and then we wait 11ms before
|
||||
// starting next frame.
|
||||
@ -855,11 +942,8 @@ mod tests {
|
||||
// Frame 1: simulate we managed to complete the work in 10ms, and then we wait 6ms before
|
||||
// starting next frame.
|
||||
//
|
||||
// Note: there is no earlier frame before "Frame 1", so the tested value for the previous
|
||||
// frame will always be 0.0 and [`Correct`] at this point (which is a special case -
|
||||
// depending on tested stat, for later frames 0.0 could result in a threshold
|
||||
// warning/error).
|
||||
test_and_advance_frame!(test, 0.0, ValueCheck::Correct; next: 10.0, 6.0);
|
||||
// Note: we expect no stats data to be returned before the 1st frame was started.
|
||||
test_and_advance_frame!(test, None; next: 10.0, 6.0);
|
||||
|
||||
// Frame 2: simulate we managed to complete the work in 5ms, and then we wait 11.67ms before
|
||||
// starting next frame.
|
||||
|
@ -15,6 +15,8 @@
|
||||
|
||||
use enso_prelude::*;
|
||||
|
||||
use enso_web::Performance;
|
||||
use enso_web::TimeProvider;
|
||||
use js_sys::ArrayBuffer;
|
||||
use js_sys::WebAssembly::Memory;
|
||||
use wasm_bindgen::JsCast;
|
||||
@ -25,41 +27,120 @@ use wasm_bindgen::JsCast;
|
||||
// === Stats ===
|
||||
// =============
|
||||
|
||||
/// Structure containing all the gathered stats.
|
||||
#[derive(Debug, Clone, CloneRef)]
|
||||
pub struct Stats {
|
||||
rc: Rc<RefCell<StatsData>>,
|
||||
/// Contains all the gathered stats, and provides methods for modifying and retrieving their
|
||||
/// values. Uses the Web Performance API to access current time for calculating time-dependent
|
||||
/// stats (e.g. FPS).
|
||||
pub type Stats = StatsWithTimeProvider<Performance>;
|
||||
|
||||
|
||||
|
||||
// =============================
|
||||
// === StatsWithTimeProvider ===
|
||||
// =============================
|
||||
|
||||
/// Contains all the gathered stats, and provides methods for modifying and retrieving their
|
||||
/// values.
|
||||
/// Uses [`T`] to access current time for calculating time-dependent stats (e.g. FPS).
|
||||
#[derive(Debug, CloneRef)]
|
||||
pub struct StatsWithTimeProvider<T> {
|
||||
rc: Rc<RefCell<FramedStatsData<T>>>,
|
||||
}
|
||||
|
||||
impl Default for Stats {
|
||||
fn default() -> Self {
|
||||
let rc = Rc::new(RefCell::new(default()));
|
||||
Self { rc }
|
||||
impl<T> Clone for StatsWithTimeProvider<T> {
|
||||
fn clone(&self) -> Self {
|
||||
Self { rc: self.rc.clone() }
|
||||
}
|
||||
}
|
||||
|
||||
impl Stats {
|
||||
impl<T: TimeProvider> StatsWithTimeProvider<T> {
|
||||
/// Constructor.
|
||||
pub fn new(time_provider: T) -> Self {
|
||||
let framed_stats_data = FramedStatsData::new(time_provider);
|
||||
let rc = Rc::new(RefCell::new(framed_stats_data));
|
||||
Self { rc }
|
||||
}
|
||||
|
||||
/// Starts tracking data for a new animation frame.
|
||||
/// Also, calculates the `fps` stat and updates `frame_counter`.
|
||||
/// Also, calculates the [`fps`] stat.
|
||||
/// Returns a snapshot of statistics data for the previous frame.
|
||||
/// Note: on first ever frame, there was no "previous frame", so all returned stats are zero
|
||||
/// (this special case can be recognized by checking `frame_counter == 0`).
|
||||
pub fn begin_frame(&self, time: f64) -> StatsData {
|
||||
self.rc.borrow_mut().begin_frame(time)
|
||||
///
|
||||
/// Note: the code works under an assumption that [`begin_frame()`] and [`end_frame()`] are
|
||||
/// called, respectively, at the beginning and end of every frame. The very first time
|
||||
/// [`begin_frame()`] is called, it returns `None`, because it does not have complete
|
||||
/// statistics data for the preceding frame.
|
||||
pub fn begin_frame(&self) -> Option<StatsData> {
|
||||
self.rc.borrow_mut().begin_frame()
|
||||
}
|
||||
|
||||
/// Ends tracking data for the current animation frame.
|
||||
/// Also, calculates the `frame_time` and `wasm_memory_usage` stats.
|
||||
pub fn end_frame(&self, time: f64) {
|
||||
self.rc.borrow_mut().end_frame(time);
|
||||
}
|
||||
|
||||
/// Resets the per-frame statistics.
|
||||
pub fn reset_per_frame_statistics(&self) {
|
||||
self.rc.borrow_mut().reset_per_frame_statistics();
|
||||
pub fn end_frame(&self) {
|
||||
self.rc.borrow_mut().end_frame();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// =======================
|
||||
// === FramedStatsData ===
|
||||
// =======================
|
||||
|
||||
#[derive(Debug)]
|
||||
struct FramedStatsData<T> {
|
||||
time_provider: T,
|
||||
stats_data: StatsData,
|
||||
frame_begin_time: Option<f64>,
|
||||
}
|
||||
|
||||
impl<T: TimeProvider> FramedStatsData<T> {
|
||||
/// Constructor.
|
||||
fn new(time_provider: T) -> Self {
|
||||
let stats_data = default();
|
||||
let frame_begin_time = None;
|
||||
Self { time_provider, stats_data, frame_begin_time }
|
||||
}
|
||||
|
||||
fn begin_frame(&mut self) -> Option<StatsData> {
|
||||
let time = self.time_provider.now();
|
||||
let mut previous_frame_stats = self.stats_data;
|
||||
self.reset_per_frame_statistics();
|
||||
let previous_frame_begin_time = self.frame_begin_time.replace(time);
|
||||
previous_frame_begin_time.map(|begin_time| {
|
||||
let end_time = time;
|
||||
previous_frame_stats.fps = 1000.0 / (end_time - begin_time);
|
||||
previous_frame_stats
|
||||
})
|
||||
}
|
||||
|
||||
fn end_frame(&mut self) {
|
||||
if let Some(begin_time) = self.frame_begin_time {
|
||||
let end_time = self.time_provider.now();
|
||||
self.stats_data.frame_time = end_time - begin_time;
|
||||
}
|
||||
|
||||
// TODO[MC,IB]: drop the `cfg!` (outlier in our codebase) once wasm_bindgen::memory()
|
||||
// doesn't panic in non-WASM builds (https://www.pivotaltracker.com/story/show/180978631)
|
||||
if cfg!(target_arch = "wasm32") {
|
||||
let memory: Memory = wasm_bindgen::memory().dyn_into().unwrap();
|
||||
let buffer: ArrayBuffer = memory.buffer().dyn_into().unwrap();
|
||||
self.stats_data.wasm_memory_usage = buffer.byte_length();
|
||||
}
|
||||
}
|
||||
|
||||
fn reset_per_frame_statistics(&mut self) {
|
||||
self.stats_data.draw_call_count = 0;
|
||||
self.stats_data.shader_compile_count = 0;
|
||||
self.stats_data.data_upload_count = 0;
|
||||
self.stats_data.data_upload_size = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ========================
|
||||
// === Macro gen_stats! ===
|
||||
// ========================
|
||||
|
||||
/// Emits the 2nd argument only if the 1st argument is an integer type. A helper macro for
|
||||
/// gen_stats!, supports only the types currently used with gen_stats!.
|
||||
macro_rules! emit_if_integer {
|
||||
@ -68,6 +149,8 @@ macro_rules! emit_if_integer {
|
||||
(f64, $($block:tt)*) => ();
|
||||
}
|
||||
|
||||
/// Emits the StatsData struct, and extends StatsWithTimeProvider with accessors to StatsData
|
||||
/// fields.
|
||||
macro_rules! gen_stats {
|
||||
($($field:ident : $field_type:ty),* $(,)?) => { paste::item! {
|
||||
|
||||
@ -78,23 +161,21 @@ macro_rules! gen_stats {
|
||||
#[derive(Debug,Default,Clone,Copy)]
|
||||
#[allow(missing_docs)]
|
||||
pub struct StatsData {
|
||||
frame_begin_time: f64,
|
||||
pub frame_counter: u64,
|
||||
$(pub $field : $field_type),*
|
||||
}
|
||||
|
||||
|
||||
// === Stats fields accessors ===
|
||||
// === StatsWithTimeProvider fields accessors ===
|
||||
|
||||
impl Stats { $(
|
||||
impl<T: TimeProvider> StatsWithTimeProvider<T> { $(
|
||||
/// Field getter.
|
||||
pub fn $field(&self) -> $field_type {
|
||||
self.rc.borrow().$field
|
||||
self.rc.borrow().stats_data.$field
|
||||
}
|
||||
|
||||
/// Field setter.
|
||||
pub fn [<set _ $field>](&self, value:$field_type) {
|
||||
self.rc.borrow_mut().$field = value;
|
||||
self.rc.borrow_mut().stats_data.$field = value;
|
||||
}
|
||||
|
||||
/// Field modifier.
|
||||
@ -137,44 +218,6 @@ gen_stats! {
|
||||
shader_compile_count : usize,
|
||||
}
|
||||
|
||||
|
||||
// === StatsData methods ===
|
||||
|
||||
impl StatsData {
|
||||
fn begin_frame(&mut self, time: f64) -> StatsData {
|
||||
// See [Stats::begin_frame()] docs for explanation of this check.
|
||||
let previous_frame_snapshot = if self.frame_counter == 0 {
|
||||
default()
|
||||
} else {
|
||||
let end_time = time;
|
||||
self.fps = 1000.0 / (end_time - self.frame_begin_time);
|
||||
*self
|
||||
};
|
||||
self.frame_counter += 1;
|
||||
self.frame_begin_time = time;
|
||||
previous_frame_snapshot
|
||||
}
|
||||
|
||||
fn end_frame(&mut self, time: f64) {
|
||||
self.frame_time = time - self.frame_begin_time;
|
||||
|
||||
// TODO[MC,IB]: drop the `cfg!` (outlier in our codebase) once wasm_bindgen::memory()
|
||||
// doesn't panic in non-WASM builds (https://www.pivotaltracker.com/story/show/180978631)
|
||||
if cfg!(target_arch = "wasm32") {
|
||||
let memory: Memory = wasm_bindgen::memory().dyn_into().unwrap();
|
||||
let buffer: ArrayBuffer = memory.buffer().dyn_into().unwrap();
|
||||
self.wasm_memory_usage = buffer.byte_length();
|
||||
}
|
||||
}
|
||||
|
||||
fn reset_per_frame_statistics(&mut self) {
|
||||
self.draw_call_count = 0;
|
||||
self.shader_compile_count = 0;
|
||||
self.data_upload_count = 0;
|
||||
self.data_upload_size = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/// Keeps the body if the `statistics` compilation flag was enabled.
|
||||
#[macro_export]
|
||||
macro_rules! if_compiled_with_stats {
|
||||
|
@ -1,8 +1,6 @@
|
||||
//! This module implements `World`, the main object responsible for handling what you see on the
|
||||
//! screen.
|
||||
|
||||
pub mod stats;
|
||||
|
||||
pub use crate::display::symbol::types::*;
|
||||
|
||||
use crate::prelude::*;
|
||||
@ -11,6 +9,7 @@ use crate::animation;
|
||||
use crate::control::callback;
|
||||
use crate::data::dirty;
|
||||
use crate::data::dirty::traits::*;
|
||||
use crate::debug;
|
||||
use crate::debug::stats::Stats;
|
||||
use crate::debug::stats::StatsData;
|
||||
use crate::display;
|
||||
@ -63,7 +62,8 @@ pub struct World {
|
||||
main_loop: animation::DynamicLoop,
|
||||
uniforms: Uniforms,
|
||||
stats: Stats,
|
||||
stats_monitor: stats::Monitor,
|
||||
stats_monitor: debug::monitor::Monitor,
|
||||
stats_draw_handle: callback::Handle,
|
||||
main_loop_frame: callback::Handle,
|
||||
on_before_frame: callback::SharedRegistryMut1<animation::TimeInfo>,
|
||||
on_after_frame: callback::SharedRegistryMut1<animation::TimeInfo>,
|
||||
@ -75,24 +75,27 @@ impl World {
|
||||
#[allow(clippy::new_ret_no_self)]
|
||||
pub fn new(dom: &web_sys::HtmlElement) -> World {
|
||||
let logger = Logger::new("world");
|
||||
let stats = default();
|
||||
let stats = debug::stats::Stats::new(web::performance());
|
||||
let scene_dirty = dirty::SharedBool::new(Logger::new_sub(&logger, "scene_dirty"), ());
|
||||
let on_change = enclose!((scene_dirty) move || scene_dirty.set());
|
||||
let scene = Scene::new(dom, &logger, &stats, on_change);
|
||||
let uniforms = Uniforms::new(&scene.variables);
|
||||
let main_loop = animation::DynamicLoop::new();
|
||||
let stats_monitor = stats::Monitor::new(&stats);
|
||||
let stats_monitor = debug::monitor::Monitor::new();
|
||||
let on_before_frame = <callback::SharedRegistryMut1<animation::TimeInfo>>::new();
|
||||
let on_after_frame = <callback::SharedRegistryMut1<animation::TimeInfo>>::new();
|
||||
let on_stats_available = <callback::SharedRegistryMut1<StatsData>>::new();
|
||||
let stats_draw_handle = on_stats_available.add(f!([stats_monitor] (stats: &StatsData) {
|
||||
stats_monitor.sample_and_draw(stats);
|
||||
}));
|
||||
let main_loop_frame = main_loop.on_frame(
|
||||
f!([stats_monitor,on_before_frame,on_after_frame,on_stats_available,uniforms,scene_dirty,scene]
|
||||
f!([stats,on_before_frame,on_after_frame,on_stats_available,uniforms,scene_dirty,scene]
|
||||
(t:animation::TimeInfo) {
|
||||
// Note [Main Loop Performance]
|
||||
|
||||
stats_monitor.begin();
|
||||
let previous_frame_stats = stats.begin_frame();
|
||||
on_before_frame.run_all(&t);
|
||||
if let Some(stats) = stats_monitor.previous_frame_stats() {
|
||||
if let Some(stats) = previous_frame_stats {
|
||||
on_stats_available.run_all(&stats);
|
||||
}
|
||||
|
||||
@ -102,7 +105,7 @@ impl World {
|
||||
scene.renderer.run();
|
||||
|
||||
on_after_frame.run_all(&t);
|
||||
stats_monitor.end();
|
||||
stats.end_frame();
|
||||
}),
|
||||
);
|
||||
|
||||
@ -114,6 +117,7 @@ impl World {
|
||||
uniforms,
|
||||
stats,
|
||||
stats_monitor,
|
||||
stats_draw_handle,
|
||||
main_loop_frame,
|
||||
on_before_frame,
|
||||
on_after_frame,
|
||||
|
@ -1,140 +0,0 @@
|
||||
//! This module implements the stats monitor view, which can be visible on the screen in debug mode.
|
||||
|
||||
use crate::prelude::*;
|
||||
|
||||
use crate::debug;
|
||||
use crate::debug::stats::Stats;
|
||||
use crate::debug::stats::StatsData;
|
||||
use crate::system::web;
|
||||
|
||||
|
||||
|
||||
// ===============
|
||||
// === Monitor ===
|
||||
// ===============
|
||||
|
||||
shared! { Monitor
|
||||
|
||||
/// Visual panel showing performance-related methods.
|
||||
#[derive(Debug)]
|
||||
pub struct MonitorData {
|
||||
stats : Stats,
|
||||
previous_frame_stats : Option<StatsData>,
|
||||
current_frame_measurement : FrameMeasurementState,
|
||||
performance : web::Performance,
|
||||
monitor : debug::Monitor,
|
||||
panels : Vec<debug::monitor::Panel>
|
||||
}
|
||||
|
||||
impl {
|
||||
/// Constructor.
|
||||
pub fn new(stats:&Stats) -> Self {
|
||||
let stats = stats.clone_ref();
|
||||
let previous_frame_stats = None;
|
||||
let current_frame_measurement = FrameMeasurementState::Skipped;
|
||||
let performance = web::performance();
|
||||
let mut monitor = debug::Monitor::new();
|
||||
let panels = vec![
|
||||
monitor.add::<debug::monitor::FrameTime>(),
|
||||
monitor.add::<debug::monitor::Fps>(),
|
||||
monitor.add::<debug::monitor::WasmMemory>(),
|
||||
monitor.add::<debug::monitor::GpuMemoryUsage>(),
|
||||
monitor.add::<debug::monitor::DrawCallCount>(),
|
||||
monitor.add::<debug::monitor::DataUploadCount>(),
|
||||
monitor.add::<debug::monitor::DataUploadSize>(),
|
||||
monitor.add::<debug::monitor::BufferCount>(),
|
||||
monitor.add::<debug::monitor::SymbolCount>(),
|
||||
monitor.add::<debug::monitor::ShaderCount>(),
|
||||
monitor.add::<debug::monitor::ShaderCompileCount>(),
|
||||
monitor.add::<debug::monitor::SpriteSystemCount>(),
|
||||
monitor.add::<debug::monitor::SpriteCount>(),
|
||||
];
|
||||
Self {
|
||||
stats,
|
||||
previous_frame_stats,
|
||||
current_frame_measurement,
|
||||
performance,
|
||||
monitor,
|
||||
panels,
|
||||
}
|
||||
}
|
||||
|
||||
/// Start measuring data.
|
||||
pub fn begin(&mut self) {
|
||||
if self.visible() {
|
||||
let time = self.performance.now();
|
||||
if self.current_frame_measurement == FrameMeasurementState::Ended {
|
||||
let previous_frame_stats = self.stats.begin_frame(time);
|
||||
for panel in &self.panels {
|
||||
panel.sample_and_postprocess(&previous_frame_stats);
|
||||
}
|
||||
self.monitor.draw();
|
||||
self.previous_frame_stats = Some(previous_frame_stats);
|
||||
} else {
|
||||
let _ = self.stats.begin_frame(time);
|
||||
self.previous_frame_stats = None;
|
||||
}
|
||||
self.current_frame_measurement = FrameMeasurementState::InProgress;
|
||||
} else {
|
||||
self.current_frame_measurement = FrameMeasurementState::Skipped;
|
||||
self.previous_frame_stats = None;
|
||||
}
|
||||
// This should be done even when hidden in order for the stats not to overflow limits.
|
||||
self.stats.reset_per_frame_statistics();
|
||||
}
|
||||
|
||||
/// Finish measuring data.
|
||||
pub fn end(&mut self) {
|
||||
if self.visible() && self.current_frame_measurement == FrameMeasurementState::InProgress {
|
||||
let time = self.performance.now();
|
||||
self.stats.end_frame(time);
|
||||
self.current_frame_measurement = FrameMeasurementState::Ended;
|
||||
} else {
|
||||
self.current_frame_measurement = FrameMeasurementState::Skipped;
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a snapshot of statistics data for the previous rendering frame, if available.
|
||||
/// A previous rendering frame is the one before the most recent call to [`begin()`].
|
||||
///
|
||||
/// Note: a `None` result is returned if [`Monitor`] was not [`visible()`] during [`begin()`]
|
||||
/// or [`end()`] of the previous frame, or during [`begin()`] of the current frame. That's
|
||||
/// because (as a performance optimization) we're skipping stats tracking when not
|
||||
/// [`visible()`].
|
||||
/// For example, if [`Monitor`] is not [`visible()`] during a call to [`end()`], we skip a
|
||||
/// [`Stats::end_frame()`] call, which makes it impossible for [`Stats`] to correctly calculate
|
||||
/// the value of [`StatsData::frame_time`].
|
||||
pub fn previous_frame_stats(&self) -> Option<StatsData> {
|
||||
self.previous_frame_stats
|
||||
}
|
||||
|
||||
/// Checks if the monitor is visible.
|
||||
pub fn visible(&self) -> bool {
|
||||
self.monitor.visible()
|
||||
}
|
||||
|
||||
/// Show the monitor.
|
||||
pub fn show(&mut self) {
|
||||
self.monitor.show()
|
||||
}
|
||||
|
||||
/// Hide the monitor.
|
||||
pub fn hide(&mut self) {
|
||||
self.monitor.hide()
|
||||
}
|
||||
|
||||
/// Toggle the visibility of the monitor.
|
||||
pub fn toggle(&mut self) {
|
||||
self.monitor.toggle()
|
||||
}
|
||||
}}
|
||||
|
||||
|
||||
// === FrameMeasurementState ===
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
enum FrameMeasurementState {
|
||||
Skipped,
|
||||
InProgress,
|
||||
Ended,
|
||||
}
|
@ -133,6 +133,24 @@ pub fn ignore_context_menu(target: &EventTarget) -> Option<IgnoreContextMenuHand
|
||||
|
||||
|
||||
|
||||
// ====================
|
||||
// === TimeProvider ===
|
||||
// ====================
|
||||
|
||||
/// Trait for an entity that can retrieve current time.
|
||||
pub trait TimeProvider {
|
||||
/// Returns current time, measured in milliseconds.
|
||||
fn now(&self) -> f64;
|
||||
}
|
||||
|
||||
impl TimeProvider for Performance {
|
||||
fn now(&self) -> f64 {
|
||||
self.now()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ===================
|
||||
// === DOM Helpers ===
|
||||
// ===================
|
||||
|
Loading…
Reference in New Issue
Block a user