mirror of
https://github.com/enso-org/enso.git
synced 2024-12-27 23:15:01 +03:00
Performance fine-tuning & monitoring (https://github.com/enso-org/ide/pull/95)
Original commit: 4351c60828
This commit is contained in:
parent
cd95b281e8
commit
5af7a0fd2f
@ -13,5 +13,14 @@ members = [
|
||||
"lib/system/web",
|
||||
]
|
||||
|
||||
[profile.dev]
|
||||
opt-level = 1
|
||||
lto = false
|
||||
|
||||
[profile.release]
|
||||
opt-level = 3
|
||||
lto = true
|
||||
|
||||
[profile.bench]
|
||||
opt-level = 3
|
||||
lto = true
|
||||
|
107
gui/README.md
107
gui/README.md
@ -1,23 +1,24 @@
|
||||
[![License](https://img.shields.io/static/v1?label=License&message=MIT&color=2ec352&labelColor=2c3239)](https://github.com/luna/basegl/blob/master/LICENSE) [![Actions Status](https://github.com/luna/basegl/workflows/Build%20%28MacOS%2C%20Linux%2C%20Windows%29/badge.svg)](https://github.com/luna/basegl/actions) [![Coverage](https://img.shields.io/codecov/c/github/luna/basegl?label=Coverage&labelColor=2c3239)](https://codecov.io/gh/luna/basegl/branch/master)
|
||||
[![License](https://img.shields.io/static/v1?label=License&message=MIT&color=2ec352&labelColor=2c3239)](https://github.com/luna/basegl/blob/master/LICENSE)
|
||||
[![Actions Status](https://github.com/luna/basegl/workflows/Build%20%28MacOS%2C%20Linux%2C%20Windows%29/badge.svg)](https://github.com/luna/basegl/actions)
|
||||
[![Coverage](https://img.shields.io/codecov/c/github/luna/basegl?label=Coverage&labelColor=2c3239)](https://codecov.io/gh/luna/basegl/branch/master)
|
||||
![Stability](https://img.shields.io/static/v1?label=Stability&message=Unstable&color=d52229&labelColor=2c3239)
|
||||
|
||||
# BaseGL
|
||||
|
||||
BaseGL is a blazing fast 2D drawing API. This repository is a work in progress
|
||||
of BaseGL 2.0. Please refer to BaseGL 1.0 repository for more information:
|
||||
https://github.com/luna/basegl-old.
|
||||
BaseGL is a blazing fast 2D vector rendering engine with a rich set of
|
||||
primitives and a GUI component library. It is able to display millions of shapes
|
||||
60 frames per second in a web browser on a modern laptop hardware.
|
||||
|
||||
## Working with the code
|
||||
This repository is a work in progress of BaseGL 2.0. Please refer to BaseGL 1.0
|
||||
repository for more information: https://github.com/luna/basegl-old.
|
||||
|
||||
|
||||
## Development
|
||||
|
||||
### The Rust toolchain
|
||||
|
||||
In order to use some of the WASM pipeline features we need to use a nightly Rust
|
||||
channel. The same applies to the code auto-formatter and it's advanced
|
||||
configuration options used here. You would neither be able to compile not format
|
||||
the code using the stable branch.
|
||||
|
||||
To setup the toolchain, please use the [the Rust toolchain installer
|
||||
](https://rustup.rs/):
|
||||
This project uses several features available only in the nightly Rust toolchain.
|
||||
To setup the toolchain, please use the [the Rust toolchain
|
||||
installer](https://rustup.rs/):
|
||||
|
||||
```bash
|
||||
rustup toolchain install nightly-2019-11-04 # Install the nightly channel.
|
||||
@ -26,59 +27,59 @@ rustup component add rustfmt # Install the code auto-formatter.
|
||||
rustup component add clippy # Install the linter.
|
||||
```
|
||||
|
||||
### Building and testing the project
|
||||
### Building the sources
|
||||
Please use the `script/build.sh` script to build the project or the
|
||||
`script/watch.sh` script to run a file watch utility which will build the
|
||||
project when on every source change. The scripts are thin wrappers over
|
||||
[wasm-pack](https://github.com/rustwasm/wasm-pack) and accept the same [command
|
||||
line arguments](https://rustwasm.github.io/wasm-pack/book/commands/build.html).
|
||||
In particular, you can provide them with `--release`, `--dev`, or `--profile`
|
||||
flags to switch the compilation profile. If not option is provided, the scripts
|
||||
default to the `--release` profile.
|
||||
|
||||
Please use the `script/build.sh`, `script/watch.sh`, and `script/lint.sh`
|
||||
scripts to build, watch, and lint the project respectively. We need to use a
|
||||
simple custom wrappers here because of the several Rust toolchain issues:
|
||||
|
||||
- [No direct support for Cargo Workspaces in
|
||||
wasm-pack.](https://github.com/rustwasm/wasm-pack/issues/642). Fixed in
|
||||
`build.sh`.
|
||||
- There is no watch utility in wasm-pack, which makes using it harder than it
|
||||
should be. Fixed in `watch.sh`.
|
||||
- [The commands cargo-check and cargo-clippy do not clean local cache if used
|
||||
several times.](https://github.com/rust-lang/cargo/issues/6986). Fixed in
|
||||
`lint.sh`.
|
||||
|
||||
In order to build an example demo scene, please use the following commands:
|
||||
### Running examples
|
||||
Please note that in order to run the examples you have to first build the
|
||||
project. For best experience, it is recommended to use the `scripts/watch.sh`
|
||||
in a second shell. In order to build the demo scenes, follow the steps below:
|
||||
|
||||
```bash
|
||||
./script/watch.sh # Build and watch for changes.
|
||||
|
||||
# Wait till the project finishes building.
|
||||
# Run the following lines from other cmd:
|
||||
|
||||
cd examples/01-scene
|
||||
cd examples
|
||||
npm install
|
||||
npm run start
|
||||
npm run start
|
||||
```
|
||||
|
||||
You can now open the following address in your browser: http://localhost:8080.
|
||||
You can now navigate to http://localhost:8080 and play with the demo scenes!
|
||||
|
||||
There are also _web test_ which are run in browser and produce some output. To
|
||||
run them, pick a test suite (`html_renderer` for instance) and run:
|
||||
```bash
|
||||
wasm-pack test lib/core --chrome --release -- --test html_renderer
|
||||
```
|
||||
Now the test output is available at http://127.0.0.1:8000. There are benchmark
|
||||
tests too (that's why `--release` flag is recommended).
|
||||
Please note that `npm run start` runs the Webpack Dev-Server in the production
|
||||
mode. You can use the `npm run start-dev` in order to enable the development
|
||||
mode, however, as all sources are provided to Webpack in form of WASM binaries,
|
||||
we haven't observed any differences between them in this project.
|
||||
|
||||
While Webpack provides handy utilities for development, like live-reloading on
|
||||
sources change, it also adds some runtime overhead. In order to run the compiled
|
||||
examples using a lightweight http-server (without live-reloading functionality),
|
||||
please use the `npm run prod-server` command.
|
||||
|
||||
**Please remember to disable the cache in your browser during development!**
|
||||
|
||||
### Running tests
|
||||
The sources use both unit tests and web test, which are run in a browser and
|
||||
produce visual results. To run them, use the `scripts/test.sh` script and follow
|
||||
the output in the terminal.
|
||||
|
||||
**Please remember to disable the cache in your browser!**
|
||||
|
||||
### Working with the source code
|
||||
|
||||
#### Formatting
|
||||
|
||||
All codebase should be auto-formatted using `rustfmt`. It is highly recommended
|
||||
that you use an IDE which takes care of formatting the code as you type. Please
|
||||
remember that auto-formatting does not mean you should not care of the way your
|
||||
code looks and feels! Be sure to carefully read the [Rust style
|
||||
guide](https://github.com/luna/enso/blob/master/doc/rust-style-guide.md) and
|
||||
apply it everywhere in your codebase.
|
||||
Please note that this codebase does not use `rustfmt`. Please read the following
|
||||
documents to learn more about reasons behind this decision and the recommended
|
||||
code style guide. Be sure to carefully read the documents before contributing to
|
||||
this repository:
|
||||
- [Rust style guide](https://github.com/luna/basegl/blob/master/docs/style-guide.md)
|
||||
- [Rust style
|
||||
guide](https://github.com/luna/enso/blob/master/doc/rust-style-guide.md)
|
||||
|
||||
|
||||
#### Linting
|
||||
|
||||
Please be sure to fix all errors reported by `cargo clippy` before creating a
|
||||
Please be sure to fix all errors reported by `scripts/lint.sh` before creating a
|
||||
pull request to this repository.
|
||||
|
1
gui/examples/.gitignore
vendored
1
gui/examples/.gitignore
vendored
@ -1,3 +1,2 @@
|
||||
node_modules
|
||||
dist
|
||||
package-lock.json
|
@ -11,7 +11,7 @@ function hello_screen(msg) {
|
||||
}
|
||||
}
|
||||
|
||||
if(msg==="" && msg===null && msg===undefined) {
|
||||
if(msg==="" || msg===null || msg===undefined) {
|
||||
msg = ""
|
||||
}
|
||||
let newDiv = document.createElement("div");
|
||||
|
5646
gui/examples/package-lock.json
generated
Normal file
5646
gui/examples/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,18 +1,21 @@
|
||||
{
|
||||
"name": "example-scene",
|
||||
"name": "example-scenes",
|
||||
"version": "0.1.0",
|
||||
"description": "Example Scene",
|
||||
"description": "Example Scenes",
|
||||
"main": "index.js",
|
||||
"bin": {
|
||||
"create-wasm-app": ".bin/create-wasm-app.js"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "webpack --config webpack.config.js",
|
||||
"start": "webpack-dev-server"
|
||||
"build": "webpack --config webpack.prod.js",
|
||||
"build-dev": "webpack --config webpack.dev.js",
|
||||
"start": "webpack-dev-server --config webpack.prod.js",
|
||||
"start-dev": "webpack-dev-server --config webpack.dev.js",
|
||||
"prod-server": "npx http-server -p 3000 --proxy http://localhost:3000\\? dist"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/rustwasm/create-wasm-app.git"
|
||||
"url": "git@github.com:luna/basegl.git"
|
||||
},
|
||||
"keywords": [
|
||||
"webassembly",
|
||||
@ -20,19 +23,20 @@
|
||||
"rust",
|
||||
"webpack"
|
||||
],
|
||||
"author": "Ashley Williams <ashley666ashley@gmail.com>",
|
||||
"author": "Enso Team (contact@luna-lang.org)",
|
||||
"license": "(MIT OR Apache-2.0)",
|
||||
"bugs": {
|
||||
"url": "https://github.com/rustwasm/create-wasm-app/issues"
|
||||
"url": "https://github.com/luna/basegl/issues"
|
||||
},
|
||||
"homepage": "https://github.com/rustwasm/create-wasm-app#readme",
|
||||
"homepage": "https://github.com/luna/basegl",
|
||||
"devDependencies": {
|
||||
"basegl": "file:../target/web",
|
||||
"copy-webpack-plugin": "^5.1.1",
|
||||
"hello-wasm-pack": "^0.1.0",
|
||||
"html-webpack-plugin": "^3.2.0",
|
||||
"webpack": "^4.29.3",
|
||||
"webpack-cli": "^3.1.0",
|
||||
"webpack-dev-server": "^3.1.5"
|
||||
"webpack-dev-server": "^3.1.5",
|
||||
"webpack-merge": "^4.2.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"basegl": "file:../target/web"
|
||||
}
|
||||
}
|
||||
|
@ -1,24 +1,30 @@
|
||||
const CopyWebpackPlugin = require("copy-webpack-plugin");
|
||||
const HtmlWebpackPlugin = require('html-webpack-plugin')
|
||||
const CopyWebpackPlugin = require("copy-webpack-plugin")
|
||||
const path = require('path');
|
||||
|
||||
const mb = 1024 * 1024;
|
||||
|
||||
module.exports = {
|
||||
entry: "./bootstrap.js",
|
||||
output: {
|
||||
path: path.resolve(__dirname, "dist"),
|
||||
filename: "bootstrap.js",
|
||||
},
|
||||
mode: "development",
|
||||
node: {
|
||||
fs: 'empty'
|
||||
},
|
||||
plugins: [
|
||||
new CopyWebpackPlugin(['index.html']),
|
||||
// new HtmlWebpackPlugin({template: 'index.html'}),
|
||||
],
|
||||
devServer: {
|
||||
historyApiFallback: {
|
||||
index: 'index.html'
|
||||
}
|
||||
}
|
||||
},
|
||||
resolve: {
|
||||
modules: [path.resolve(__dirname, "node_modules")]
|
||||
},
|
||||
performance: {
|
||||
hints: 'error',
|
||||
maxAssetSize: 3.7 * mb,
|
||||
},
|
||||
};
|
10
gui/examples/webpack.dev.js
Normal file
10
gui/examples/webpack.dev.js
Normal file
@ -0,0 +1,10 @@
|
||||
const merge = require('webpack-merge');
|
||||
const common = require('./webpack.common.js');
|
||||
|
||||
module.exports = merge(common, {
|
||||
mode: 'development',
|
||||
devtool: '',
|
||||
devServer: {
|
||||
contentBase: './dist',
|
||||
},
|
||||
});
|
6
gui/examples/webpack.prod.js
Normal file
6
gui/examples/webpack.prod.js
Normal file
@ -0,0 +1,6 @@
|
||||
const merge = require('webpack-merge');
|
||||
const common = require('./webpack.common.js');
|
||||
|
||||
module.exports = merge(common, {
|
||||
mode: 'production',
|
||||
});
|
@ -44,6 +44,7 @@ features = [
|
||||
'Element',
|
||||
'HtmlElement',
|
||||
'HtmlCollection',
|
||||
'CanvasRenderingContext2d',
|
||||
'CssStyleDeclaration',
|
||||
'HtmlCanvasElement',
|
||||
'WebGlBuffer',
|
||||
|
@ -1,2 +1 @@
|
||||
pub mod callback;
|
||||
pub mod event_loop;
|
||||
|
@ -0,0 +1 @@
|
||||
pub mod monitor;
|
603
gui/lib/core/src/debug/monitor.rs
Normal file
603
gui/lib/core/src/debug/monitor.rs
Normal file
@ -0,0 +1,603 @@
|
||||
use crate::prelude::*;
|
||||
|
||||
use js_sys::ArrayBuffer;
|
||||
use js_sys::WebAssembly::Memory;
|
||||
use std::collections::VecDeque;
|
||||
use std::f64;
|
||||
use wasm_bindgen::JsCast;
|
||||
use wasm_bindgen::prelude::*;
|
||||
use wasm_bindgen;
|
||||
use web_sys::*;
|
||||
|
||||
|
||||
|
||||
// ===============
|
||||
// === Helpers ===
|
||||
// ===============
|
||||
|
||||
fn window() -> web_sys::Window {
|
||||
web_sys::window().unwrap_or_else(|| panic!("Cannot access window."))
|
||||
}
|
||||
|
||||
fn document() -> web_sys::Document {
|
||||
window().document().unwrap_or_else(|| panic!("Cannot access window.document."))
|
||||
}
|
||||
|
||||
fn body() -> web_sys::HtmlElement {
|
||||
document().body().unwrap_or_else(|| panic!("Cannot access window.document.body."))
|
||||
}
|
||||
|
||||
fn performance() -> Performance {
|
||||
window().performance().unwrap_or_else(|| panic!("Cannot access window.performance."))
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ==============
|
||||
// === Config ===
|
||||
// ==============
|
||||
|
||||
#[derive(Clone,Debug)]
|
||||
pub struct ConfigTemplate<Str,Num> {
|
||||
pub background_color : Str,
|
||||
pub label_color_ok : Str,
|
||||
pub label_color_warn : Str,
|
||||
pub label_color_err : Str,
|
||||
pub plot_color_ok : Str,
|
||||
pub plot_color_warn : Str,
|
||||
pub plot_color_err : Str,
|
||||
pub plot_background_color : Str,
|
||||
pub plot_step_size : Num,
|
||||
pub margin : Num,
|
||||
pub panel_height : Num,
|
||||
pub labels_width : Num,
|
||||
pub results_width : Num,
|
||||
pub plots_width : Num,
|
||||
pub font_size : Num,
|
||||
pub font_vertical_offset : Num,
|
||||
}
|
||||
|
||||
pub type Config = ConfigTemplate<String,u32>;
|
||||
pub type SamplerConfig = ConfigTemplate<JsValue,f64>;
|
||||
|
||||
impl Default for Config {
|
||||
fn default() -> Config {
|
||||
Config {
|
||||
background_color : "#222222".into(),
|
||||
label_color_ok : "#8e939a".into(),
|
||||
label_color_warn : "#ffba18".into(),
|
||||
label_color_err : "#eb3941".into(),
|
||||
plot_color_ok : "#8e939a".into(),
|
||||
plot_color_warn : "#ffba18".into(),
|
||||
plot_color_err : "#eb3941".into(),
|
||||
plot_background_color : "#333333".into(),
|
||||
plot_step_size : 1,
|
||||
margin : 4,
|
||||
panel_height : 15,
|
||||
labels_width : 120,
|
||||
results_width : 30,
|
||||
plots_width : 100,
|
||||
font_size : 9,
|
||||
font_vertical_offset : 4,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Config {
|
||||
pub fn to_plot_config(&self) -> SamplerConfig {
|
||||
let ratio = window().device_pixel_ratio();
|
||||
SamplerConfig {
|
||||
background_color : (&self.background_color) . into(),
|
||||
label_color_ok : (&self.label_color_ok) . into(),
|
||||
label_color_warn : (&self.label_color_warn) . into(),
|
||||
label_color_err : (&self.label_color_err) . into(),
|
||||
plot_color_ok : (&self.plot_color_ok) . into(),
|
||||
plot_color_warn : (&self.plot_color_warn) . into(),
|
||||
plot_color_err : (&self.plot_color_err) . into(),
|
||||
plot_background_color : (&self.plot_background_color) . into(),
|
||||
plot_step_size : self.plot_step_size as f64 * ratio,
|
||||
margin : self.margin as f64 * ratio,
|
||||
panel_height : self.panel_height as f64 * ratio,
|
||||
labels_width : self.labels_width as f64 * ratio,
|
||||
results_width : self.results_width as f64 * ratio,
|
||||
plots_width : self.plots_width as f64 * ratio,
|
||||
font_size : self.font_size as f64 * ratio,
|
||||
font_vertical_offset : self.font_vertical_offset as f64 * ratio,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ===============
|
||||
// === Monitor ===
|
||||
// ===============
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Monitor {
|
||||
user_config : Config,
|
||||
config : SamplerConfig,
|
||||
width : f64,
|
||||
height : f64,
|
||||
dom : Element,
|
||||
panels : Vec<Panel>,
|
||||
canvas : HtmlCanvasElement,
|
||||
context : CanvasRenderingContext2d,
|
||||
is_first_draw : bool,
|
||||
}
|
||||
|
||||
|
||||
// === Public API ===
|
||||
|
||||
// TODO: All the `unwraps` below should be handled nicer when this lib will be finished.
|
||||
|
||||
impl Default for Monitor {
|
||||
fn default() -> Self {
|
||||
let user_config: Config = default();
|
||||
let panels = default();
|
||||
let width = default();
|
||||
let height = default();
|
||||
let is_first_draw = true;
|
||||
let config = user_config.to_plot_config();
|
||||
|
||||
let dom = document().create_element("div").unwrap();
|
||||
dom.set_attribute("style", "position:absolute;").unwrap();
|
||||
body().prepend_with_node_1(&dom).unwrap();
|
||||
|
||||
let canvas = document().create_element("canvas").unwrap();
|
||||
let canvas: HtmlCanvasElement = canvas.dyn_into().unwrap();
|
||||
|
||||
let context = canvas.get_context("2d").unwrap().unwrap();
|
||||
let context: CanvasRenderingContext2d = context.dyn_into().unwrap();
|
||||
dom.append_child(&canvas).unwrap();
|
||||
|
||||
let mut out = Self{user_config,config,width,height,dom,panels,canvas,context,is_first_draw};
|
||||
out.update_config();
|
||||
out
|
||||
}
|
||||
}
|
||||
|
||||
impl Monitor {
|
||||
/// Creates a new, empty monitor instance. Ise the `add` method to fill it with samplers.
|
||||
pub fn new() -> Self { default() }
|
||||
|
||||
/// Modifies the Monitor's config and updates the view.
|
||||
pub fn mod_config<F:FnOnce(&mut Config)>(&mut self, f:F) {
|
||||
f(&mut self.user_config);
|
||||
self.update_config();
|
||||
}
|
||||
|
||||
/// Adds new display element.
|
||||
pub fn add<M:Sampler+'static>(&mut self, monitor:M) -> Panel {
|
||||
let panel = Panel::new(self.context.clone(),self.config.clone(),monitor);
|
||||
self.panels.push(panel.clone());
|
||||
self.resize();
|
||||
panel
|
||||
}
|
||||
|
||||
/// Draws the Monitor and updates all of it's values.
|
||||
pub fn draw(&mut self) {
|
||||
if self.is_first_draw {
|
||||
self.is_first_draw = false;
|
||||
self.first_draw();
|
||||
}
|
||||
self.shift_plot_area_left();
|
||||
self.clear_labels_area();
|
||||
self.draw_plots();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// === Private API ===
|
||||
|
||||
impl Monitor {
|
||||
fn update_config(&mut self) {
|
||||
self.config = self.user_config.to_plot_config()
|
||||
}
|
||||
|
||||
fn resize(&mut self) {
|
||||
let width = self.config.labels_width
|
||||
+ self.config.results_width
|
||||
+ self.config.plots_width
|
||||
+ 4.0 * self.config.margin;
|
||||
let mut height = 0.0;
|
||||
for _panel in &self.panels {
|
||||
height += self.config.margin + self.config.panel_height;
|
||||
}
|
||||
height += self.config.margin;
|
||||
|
||||
let u_width = width as u32;
|
||||
let u_height = height as u32;
|
||||
let style = format!("width:{}px; height:{}px",u_width/2,u_height/2);
|
||||
self.width = width;
|
||||
self.height = height;
|
||||
self.canvas.set_width (u_width);
|
||||
self.canvas.set_height (u_height);
|
||||
self.canvas.set_attribute("style",&style).unwrap();
|
||||
}
|
||||
|
||||
fn shift_plot_area_left(&mut self) {
|
||||
let width = self.width as f64;
|
||||
let height = self.height as f64;
|
||||
let shift = -(self.config.plot_step_size as f64);
|
||||
self.context.draw_image_with_html_canvas_element_and_sw_and_sh_and_dx_and_dy_and_dw_and_dh
|
||||
(&self.canvas,0.0,0.0,width,height,shift,0.0,self.width,self.height).unwrap();
|
||||
}
|
||||
|
||||
fn clear_labels_area(&mut self) {
|
||||
let step = self.config.plot_step_size;
|
||||
let width = self.config.labels_width + self.config.results_width + 3.0 * self.config.margin;
|
||||
self.context.set_fill_style(&self.config.background_color);
|
||||
self.context.fill_rect(0.0,0.0,width,self.height);
|
||||
self.context.fill_rect(self.width-step,0.0,step,self.height);
|
||||
}
|
||||
|
||||
fn draw_plots(&mut self) {
|
||||
self.with_all_panels(|panel| panel.draw());
|
||||
}
|
||||
|
||||
fn first_draw(&self) {
|
||||
self.context.set_fill_style(&self.config.background_color);
|
||||
self.context.fill_rect(0.0,0.0,self.width,self.height);
|
||||
self.with_all_panels(|panel| panel.first_draw());
|
||||
}
|
||||
|
||||
fn with_all_panels<F:Fn(&Panel)>(&self,f:F) {
|
||||
let mut total_off = 0.0;
|
||||
for panel in &self.panels {
|
||||
let off = self.config.margin;
|
||||
self.context.translate(0.0,off).unwrap();
|
||||
total_off += off;
|
||||
f(panel);
|
||||
let off = self.config.panel_height;
|
||||
self.context.translate(0.0,off).unwrap();
|
||||
total_off += off;
|
||||
}
|
||||
self.context.translate(0.0,-total_off).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// =============
|
||||
// === Panel ===
|
||||
// =============
|
||||
|
||||
/// A single element in the `Monitor`. It can display labels, values and plots. Each `Panel` uses
|
||||
/// a `Sampler` under the hood, which defines both its behavior and its look and feel.
|
||||
#[derive(Clone,Debug)]
|
||||
pub struct Panel {
|
||||
rc: Rc<RefCell<PanelData>>
|
||||
}
|
||||
|
||||
impl Panel {
|
||||
/// Creates a new, empty Panel with a given sampler.
|
||||
pub fn new<S:Sampler+'static>
|
||||
(context:CanvasRenderingContext2d, config:SamplerConfig, sampler:S) -> Self {
|
||||
let rc = Rc::new(RefCell::new(PanelData::new(context,config,sampler)));
|
||||
Self {rc}
|
||||
}
|
||||
|
||||
/// Display results of last measurements.
|
||||
pub fn draw(&self) {
|
||||
self.rc.borrow_mut().draw()
|
||||
}
|
||||
|
||||
/// Start measuring the data.
|
||||
pub fn begin(&self) {
|
||||
self.rc.borrow_mut().begin()
|
||||
}
|
||||
|
||||
/// Stop measuring the data.
|
||||
pub fn end(&self) {
|
||||
self.rc.borrow_mut().end()
|
||||
}
|
||||
|
||||
fn first_draw(&self) {
|
||||
self.rc.borrow_mut().first_draw()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ==================
|
||||
// === ValueCheck ===
|
||||
// ==================
|
||||
|
||||
#[derive(Copy,Clone,Debug)]
|
||||
pub enum ValueCheck {Correct,Warning,Error}
|
||||
|
||||
impl Default for ValueCheck {
|
||||
fn default() -> Self {
|
||||
Self::Correct
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ===============
|
||||
// === Sampler ===
|
||||
// ===============
|
||||
|
||||
pub trait Sampler: Debug {
|
||||
fn label (&self) -> &str;
|
||||
fn begin (&mut self, _time:f64) {}
|
||||
fn end (&mut self, _time:f64) {}
|
||||
fn value (&self) -> f64;
|
||||
fn check (&self) -> ValueCheck { ValueCheck::Correct }
|
||||
fn max_value (&self) -> Option<f64> { None }
|
||||
fn min_value (&self) -> Option<f64> { None }
|
||||
fn min_size (&self) -> Option<f64> { None }
|
||||
fn smooth_range (&self) -> usize { 2 }
|
||||
}
|
||||
|
||||
|
||||
|
||||
// =================
|
||||
// === PanelData ===
|
||||
// =================
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct PanelData {
|
||||
label : String,
|
||||
context : CanvasRenderingContext2d,
|
||||
performance : Performance,
|
||||
config : SamplerConfig,
|
||||
min_value : f64,
|
||||
max_value : f64,
|
||||
begin_value : f64,
|
||||
value : f64,
|
||||
last_values : VecDeque<f64>,
|
||||
norm_value : f64,
|
||||
draw_offset : f64,
|
||||
value_check : ValueCheck,
|
||||
sampler : Box<dyn Sampler>
|
||||
}
|
||||
|
||||
|
||||
// === Construction ===
|
||||
|
||||
impl PanelData {
|
||||
pub fn new<S:Sampler+'static>
|
||||
(context:CanvasRenderingContext2d, config:SamplerConfig, sampler:S) -> Self {
|
||||
let label = sampler.label().into();
|
||||
let performance = performance();
|
||||
let min_value = f64::INFINITY;
|
||||
let max_value = f64::NEG_INFINITY;
|
||||
let begin_value = default();
|
||||
let value = default();
|
||||
let last_values = default();
|
||||
let norm_value = default();
|
||||
let draw_offset = 0.0;
|
||||
let value_check = default();
|
||||
let sampler = Box::new(sampler);
|
||||
Self {label,context,performance,config,min_value,max_value,begin_value,value,last_values
|
||||
,norm_value,draw_offset,value_check,sampler}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// === Begin / End ===
|
||||
|
||||
impl PanelData {
|
||||
pub fn begin(&mut self) {
|
||||
let time = self.performance.now();
|
||||
self.sampler.begin(time);
|
||||
}
|
||||
|
||||
pub fn end(&mut self) {
|
||||
let time = self.performance.now();
|
||||
self.sampler.end(time);
|
||||
self.value_check = self.sampler.check();
|
||||
self.value = self.sampler.value();
|
||||
self.clamp_value();
|
||||
self.smooth_value();
|
||||
self.normalize_value();
|
||||
}
|
||||
|
||||
fn clamp_value(&mut self) {
|
||||
if let Some(max_value) = self.sampler.max_value() {
|
||||
if self.value > max_value { self.value = max_value; }
|
||||
}
|
||||
if let Some(min_value) = self.sampler.min_value() {
|
||||
if self.value > min_value { self.value = min_value; }
|
||||
}
|
||||
if self.value > self.max_value { self.max_value = self.value; }
|
||||
if self.value < self.min_value { self.min_value = self.value; }
|
||||
}
|
||||
|
||||
fn smooth_value(&mut self) {
|
||||
self.last_values.push_front(self.value);
|
||||
if self.last_values.len() > self.sampler.smooth_range() {
|
||||
self.last_values.pop_back();
|
||||
}
|
||||
|
||||
self.value = self.last_values.iter().sum();
|
||||
self.value /= self.last_values.len() as f64
|
||||
}
|
||||
|
||||
fn normalize_value(&mut self) {
|
||||
let mut size = self.max_value - self.min_value;
|
||||
if let Some(min_size) = self.sampler.min_size() {
|
||||
if size < min_size { size = min_size; }
|
||||
}
|
||||
self.norm_value = (self.value - self.min_value) / size;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// === Draw ===
|
||||
|
||||
impl PanelData {
|
||||
pub fn draw(&mut self) {
|
||||
self.init_draw();
|
||||
self.draw_plots();
|
||||
self.finish_draw();
|
||||
}
|
||||
|
||||
pub fn first_draw(&mut self) {
|
||||
self.init_draw();
|
||||
self.context.set_fill_style(&self.config.plot_background_color);
|
||||
self.context.fill_rect(0.0,0.0,self.config.plots_width,self.config.panel_height);
|
||||
self.finish_draw();
|
||||
}
|
||||
|
||||
fn move_pen_to_next_element(&mut self, offset:f64) {
|
||||
self.context.translate(offset,0.0).unwrap();
|
||||
self.draw_offset += offset;
|
||||
}
|
||||
|
||||
fn finish_draw(&mut self) {
|
||||
self.context.translate(-self.draw_offset,0.0).unwrap();
|
||||
self.draw_offset = 0.0;
|
||||
}
|
||||
|
||||
fn init_draw(&mut self) {
|
||||
self.move_pen_to_next_element(self.config.margin);
|
||||
self.draw_labels();
|
||||
self.draw_results();
|
||||
}
|
||||
|
||||
fn draw_labels(&mut self) {
|
||||
let fonts = "Helvetica,Arial,sans-serif";
|
||||
let y_pos = self.config.panel_height - self.config.font_vertical_offset;
|
||||
self.context.set_font(&format!("bold {}px {}",self.config.font_size,fonts));
|
||||
self.context.set_text_align("right");
|
||||
self.context.set_fill_style(&self.config.label_color_ok);
|
||||
self.context.fill_text(&self.label,self.config.labels_width,y_pos).unwrap();
|
||||
self.move_pen_to_next_element(self.config.labels_width + self.config.margin);
|
||||
}
|
||||
|
||||
fn draw_results(&mut self) {
|
||||
let display_value = (self.value * 100.0).round() / 100.0;
|
||||
let display_value = format!("{:.*}",2,display_value);
|
||||
let y_pos = self.config.panel_height - self.config.font_vertical_offset;
|
||||
let color = match self.value_check {
|
||||
ValueCheck::Correct => &self.config.label_color_ok,
|
||||
ValueCheck::Warning => &self.config.label_color_warn,
|
||||
ValueCheck::Error => &self.config.label_color_err
|
||||
};
|
||||
self.context.set_fill_style(color);
|
||||
self.context.fill_text(&display_value,self.config.results_width,y_pos).unwrap();
|
||||
self.move_pen_to_next_element(self.config.results_width + self.config.margin);
|
||||
}
|
||||
|
||||
fn draw_plots(&mut self) {
|
||||
self.move_pen_to_next_element(self.config.plots_width - self.config.plot_step_size);
|
||||
self.context.set_fill_style(&self.config.plot_background_color);
|
||||
self.context.fill_rect(0.0,0.0,self.config.plot_step_size,self.config.panel_height);
|
||||
let value_height = self.norm_value * self.config.panel_height;
|
||||
let y_pos = self.config.panel_height-value_height;
|
||||
let color = match self.value_check {
|
||||
ValueCheck::Correct => &self.config.plot_color_ok,
|
||||
ValueCheck::Warning => &self.config.plot_color_warn,
|
||||
ValueCheck::Error => &self.config.plot_color_err
|
||||
};
|
||||
self.context.set_fill_style(color);
|
||||
self.context.fill_rect(0.0,y_pos,self.config.plot_step_size,value_height);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// =================================================================================================
|
||||
// === Samplers ====================================================================================
|
||||
// =================================================================================================
|
||||
|
||||
// =================
|
||||
// === FrameTime ===
|
||||
// =================
|
||||
|
||||
#[derive(Debug,Default)]
|
||||
pub struct FrameTime {
|
||||
begin_time : f64,
|
||||
value : f64,
|
||||
value_check : ValueCheck,
|
||||
}
|
||||
|
||||
impl FrameTime {
|
||||
pub fn new() -> Self { default() }
|
||||
}
|
||||
|
||||
impl Sampler for FrameTime {
|
||||
fn label (&self) -> &str { "Frame time (ms)" }
|
||||
fn value (&self) -> f64 { self.value }
|
||||
fn check (&self) -> ValueCheck { self.value_check }
|
||||
fn begin (&mut self, time:f64) { self.begin_time = time; }
|
||||
fn end (&mut self, time:f64) {
|
||||
let end_time = time;
|
||||
self.value = end_time - self.begin_time;
|
||||
self.value_check =
|
||||
if self.value < 1000.0 / 55.0 { ValueCheck::Correct }
|
||||
else if self.value < 1000.0 / 25.0 { ValueCheck::Warning }
|
||||
else { ValueCheck::Error };
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ===========
|
||||
// === Fps ===
|
||||
// ===========
|
||||
|
||||
#[derive(Debug,Default)]
|
||||
pub struct Fps {
|
||||
begin_time : f64,
|
||||
value : f64,
|
||||
value_check : ValueCheck,
|
||||
}
|
||||
|
||||
impl Fps {
|
||||
pub fn new() -> Self { default() }
|
||||
}
|
||||
|
||||
impl Sampler for Fps {
|
||||
fn label (&self) -> &str { "Frames per second" }
|
||||
fn value (&self) -> f64 { self.value }
|
||||
fn check (&self) -> ValueCheck { self.value_check }
|
||||
fn max_value (&self) -> Option<f64> { Some(60.0) }
|
||||
fn begin (&mut self, time:f64) {
|
||||
if self.begin_time > 0.0 {
|
||||
let end_time = time;
|
||||
self.value = 1000.0 / (end_time - self.begin_time);
|
||||
self.value_check =
|
||||
if self.value >= 55.0 { ValueCheck::Correct }
|
||||
else if self.value >= 25.0 { ValueCheck::Warning }
|
||||
else { ValueCheck::Error };
|
||||
}
|
||||
self.begin_time = time;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ==================
|
||||
// === WasmMemory ===
|
||||
// ==================
|
||||
|
||||
#[derive(Debug,Default)]
|
||||
pub struct WasmMemory {
|
||||
value : f64,
|
||||
value_check : ValueCheck,
|
||||
}
|
||||
|
||||
impl WasmMemory {
|
||||
pub fn new() -> Self { default() }
|
||||
}
|
||||
|
||||
impl Sampler for WasmMemory {
|
||||
fn label (&self) -> &str { "WASM memory usage (Mb)" }
|
||||
fn value (&self) -> f64 { self.value }
|
||||
fn check (&self) -> ValueCheck { self.value_check }
|
||||
fn min_size (&self) -> Option<f64> { Some(100.0) }
|
||||
fn end (&mut self, _time:f64) {
|
||||
let memory: Memory = wasm_bindgen::memory().dyn_into().unwrap();
|
||||
let buffer: ArrayBuffer = memory.buffer().dyn_into().unwrap();
|
||||
self.value = (buffer.byte_length() as f64) / (1024.0 * 1024.0);
|
||||
self.value_check =
|
||||
if self.value <= 50.0 { ValueCheck::Correct }
|
||||
else if self.value <= 100.0 { ValueCheck::Warning }
|
||||
else { ValueCheck::Error };
|
||||
}
|
||||
}
|
@ -1,3 +1,4 @@
|
||||
pub mod event_loop;
|
||||
pub mod scene;
|
||||
pub mod workspace;
|
||||
|
||||
@ -8,7 +9,6 @@ pub use crate::display::world::workspace::SymbolId;
|
||||
|
||||
use crate::closure;
|
||||
use crate::control::callback::CallbackHandle;
|
||||
use crate::control::event_loop::EventLoop;
|
||||
use crate::data::opt_vec::OptVec;
|
||||
use crate::data::dirty;
|
||||
use crate::data::dirty::traits::*;
|
||||
@ -19,6 +19,7 @@ use crate::system::web::group;
|
||||
use crate::system::web::Logger;
|
||||
use crate::display::shape::text::font::Fonts;
|
||||
|
||||
use event_loop::EventLoop;
|
||||
use eval_tt::*;
|
||||
|
||||
|
||||
@ -40,7 +41,7 @@ pub struct World {
|
||||
pub event_loop : EventLoop,
|
||||
pub fonts : Fonts,
|
||||
pub update_handle : Option<CallbackHandle>,
|
||||
pub self_reference : Option<WorldRef>
|
||||
pub self_reference : Option<WorldRef>,
|
||||
}
|
||||
|
||||
|
||||
@ -66,12 +67,12 @@ impl World {
|
||||
#[allow(clippy::new_ret_no_self)]
|
||||
pub fn new() -> WorldRef {
|
||||
println!("NOTICE! When profiling in Chrome check 'Disable JavaScript Samples' under the \
|
||||
gear icon in the 'Performance' tab. It can drastically slow the reading.");
|
||||
gear icon in the 'Performance' tab. It can drastically slow the rendering.");
|
||||
let world_ref = WorldRef::new(Self::new_uninitialized());
|
||||
let world_ref2 = world_ref.clone_rc();
|
||||
let world_ref3 = world_ref.clone_rc();
|
||||
with(world_ref.borrow_mut(), |mut data| {
|
||||
let update = move || world_ref2.borrow_mut().update();
|
||||
let update = move || world_ref2.borrow_mut().run();
|
||||
let update_handle = data.event_loop.add_callback(update);
|
||||
data.update_handle = Some(update_handle);
|
||||
data.self_reference = Some(world_ref3);
|
||||
@ -123,12 +124,16 @@ impl World {
|
||||
/// which when dropped will cancel the callback. If you want the function
|
||||
/// to run forever, you can use the `forget` method in the handle.
|
||||
pub fn on_frame<F:FnMut(&mut World)+'static>
|
||||
(&mut self, mut callback: F) -> CallbackHandle {
|
||||
(&mut self, mut callback:F) -> CallbackHandle {
|
||||
let this = self.self_reference.as_ref().unwrap().clone_rc();
|
||||
let func = move || callback(&mut this.borrow_mut());
|
||||
self.event_loop.add_callback(func)
|
||||
}
|
||||
|
||||
pub fn run(&mut self) {
|
||||
self.update();
|
||||
}
|
||||
|
||||
/// Check dirty flags and update the state accordingly.
|
||||
pub fn update(&mut self) {
|
||||
if self.workspace_dirty.check_all() {
|
||||
|
@ -5,6 +5,9 @@ use crate::control::callback::CallbackHandle;
|
||||
use crate::control::callback::CallbackRegistry;
|
||||
use crate::system::web;
|
||||
use wasm_bindgen::prelude::Closure;
|
||||
use crate::debug::monitor;
|
||||
use crate::debug::monitor::Monitor;
|
||||
use crate::debug::monitor::Panel;
|
||||
|
||||
|
||||
// =================
|
||||
@ -58,22 +61,46 @@ impl EventLoop {
|
||||
|
||||
/// The internal state of the `EventLoop`.
|
||||
#[derive(Derivative)]
|
||||
#[derivative(Debug, Default)]
|
||||
#[derivative(Debug)]
|
||||
pub struct EventLoopData {
|
||||
main : Option<Closure<dyn FnMut(f32)>>,
|
||||
callbacks : CallbackRegistry,
|
||||
monitor : Monitor,
|
||||
time : Panel,
|
||||
fps : Panel,
|
||||
mem : Panel,
|
||||
main_id : i32,
|
||||
}
|
||||
|
||||
impl Default for EventLoopData {
|
||||
fn default() -> Self {
|
||||
let main = default();
|
||||
let callbacks = default();
|
||||
let main_id = default();
|
||||
let mut monitor = Monitor::new();
|
||||
let time = monitor.add(monitor::FrameTime::new());
|
||||
let fps = monitor.add(monitor::Fps::new());
|
||||
let mem = monitor.add(monitor::WasmMemory::new());
|
||||
Self {main,callbacks,monitor,time,fps,mem,main_id}
|
||||
}
|
||||
}
|
||||
|
||||
impl EventLoopData {
|
||||
/// Create new instance.
|
||||
pub fn run(&mut self) {
|
||||
self.time.begin();
|
||||
self.fps.begin();
|
||||
self.mem.begin();
|
||||
let callbacks = &mut self.callbacks;
|
||||
let callback_id = self.main.as_ref().map_or(default(), |main| {
|
||||
callbacks.run_all();
|
||||
web::request_animation_frame(main).unwrap()
|
||||
});
|
||||
self.main_id = callback_id;
|
||||
self.time.end();
|
||||
self.fps.end();
|
||||
self.mem.end();
|
||||
self.monitor.draw();
|
||||
}
|
||||
}
|
||||
|
@ -146,7 +146,6 @@ mod example_01 {
|
||||
let make_widget = |scope: &mut VarScope| {
|
||||
let inst_1_ix = scope.add_instance();
|
||||
let transform1 = transform.get(inst_1_ix);
|
||||
// transform1.modify(|t| {t.append_translation_mut(&Vector3::new( 0.0,0.0,0.0));}); // DELETEME
|
||||
Widget::new(Logger::new("widget"),transform1)
|
||||
};
|
||||
|
||||
@ -158,7 +157,7 @@ mod example_01 {
|
||||
|
||||
|
||||
let mut widgets: Vec<Widget> = default();
|
||||
let count = 1000;
|
||||
let count = 100;
|
||||
for _ in 0 .. count {
|
||||
let widget = make_widget(inst_scope);
|
||||
widgets.push(widget);
|
||||
@ -168,23 +167,47 @@ mod example_01 {
|
||||
|
||||
|
||||
let mut i:i32 = 0;
|
||||
world.on_frame(move |_| on_frame(&mut i,&w1, &mut widgets,&performance)).forget();
|
||||
world.on_frame(move |w| on_frame(w,&mut i,&w1, &mut widgets,&performance,wspace_id,sym_id,&transform)).forget();
|
||||
|
||||
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
#[allow(clippy::many_single_char_names)]
|
||||
pub fn on_frame(ii:&mut i32, w1:&Widget, widgets:&mut Vec<Widget>, performance:&Performance) {
|
||||
pub fn on_frame(world:&mut World, ii:&mut i32, w1:&Widget, widgets:&mut Vec<Widget>, performance:&Performance, wspace_id:WorkspaceID, sym_id:SymbolId, transform : &Buffer<Matrix4<f32>>) {
|
||||
// camera.mod_position(|p| {
|
||||
// p.x -= 0.1;
|
||||
// p.z += 1.0
|
||||
// });
|
||||
|
||||
let workspace : &mut Workspace = &mut world[wspace_id];
|
||||
let symbol : &mut Symbol = &mut workspace[sym_id];
|
||||
let mesh : &mut Mesh = &mut symbol.surface;
|
||||
let scopes : &mut Scopes = &mut mesh.scopes;
|
||||
let inst_scope: &mut VarScope = &mut scopes.instance;
|
||||
|
||||
let make_widget = |scope: &mut VarScope| {
|
||||
let inst_1_ix = scope.add_instance();
|
||||
let transform1 = transform.get(inst_1_ix);
|
||||
Widget::new(Logger::new("widget"),transform1)
|
||||
};
|
||||
|
||||
|
||||
|
||||
w1.transform.mod_position(|p| p.y += 0.5);
|
||||
w1.transform.update();
|
||||
|
||||
*ii += 1;
|
||||
|
||||
if *ii < 1000i32 {
|
||||
if *ii < 200i32 {
|
||||
let count = 100;
|
||||
if widgets.len() < 100_000 {
|
||||
for _ in 0..count {
|
||||
let widget = make_widget(inst_scope);
|
||||
widgets.push(widget);
|
||||
}
|
||||
}
|
||||
|
||||
let t = (performance.now() / 1000.0) as f32;
|
||||
let length = widgets.len() as f32;
|
||||
for (i, object) in widgets.iter_mut().enumerate() {
|
||||
|
@ -15,6 +15,7 @@ use web_sys::Node;
|
||||
use std::fmt::Debug;
|
||||
|
||||
pub use web_sys::console;
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
|
||||
// =============
|
||||
@ -328,3 +329,8 @@ impl NodeRemover for Node {
|
||||
self.remove_child(node).unwrap_or_else(panic_msg);
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen(inline_js = "export function request_animation_frame2(f) { requestAnimationFrame(f) }")]
|
||||
extern "C" {
|
||||
pub fn request_animation_frame2(closure: &Closure<dyn FnMut()>) -> i32;
|
||||
}
|
@ -1,2 +1,2 @@
|
||||
#!/bin/sh
|
||||
wasm-pack build --out-dir '../../target/web' lib/core
|
||||
#!/bin/bash
|
||||
wasm-pack build $@ --no-typescript --out-dir '../../target/web' lib/core
|
||||
|
@ -1,3 +1,2 @@
|
||||
#!/bin/sh
|
||||
cargo clean -p basegl -p basegl-backend-webgl -p basegl-prelude -p basegl-system-web
|
||||
#!/bin/bash
|
||||
cargo clippy -- -D warnings
|
@ -1,4 +1,4 @@
|
||||
#!/bin/sh
|
||||
#!/bin/bash
|
||||
cargo test &&
|
||||
cargo run --manifest-path=script/rust/Cargo.toml --bin test-all -- \
|
||||
--node --firefox --chrome --headless
|
||||
|
@ -1,2 +1,2 @@
|
||||
#!/bin/sh
|
||||
cargo watch -i .gitignore -i "pkg/*" -s "script/build.sh"
|
||||
#!/bin/bash
|
||||
cargo watch -i .gitignore -i "pkg/*" -s "script/build.sh ${@}"
|
Loading…
Reference in New Issue
Block a user