Performance fine-tuning & monitoring (https://github.com/enso-org/ide/pull/95)

Original commit: 4351c60828
This commit is contained in:
Wojciech Daniło 2020-01-03 14:29:57 +01:00 committed by GitHub
parent cd95b281e8
commit 5af7a0fd2f
23 changed files with 6436 additions and 91 deletions

View File

@ -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

View File

@ -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.

View File

@ -1,3 +1,2 @@
node_modules
dist
package-lock.json

View File

@ -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

File diff suppressed because it is too large Load Diff

View File

@ -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"
}
}

View File

@ -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,
},
};

View 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',
},
});

View File

@ -0,0 +1,6 @@
const merge = require('webpack-merge');
const common = require('./webpack.common.js');
module.exports = merge(common, {
mode: 'production',
});

View File

@ -44,6 +44,7 @@ features = [
'Element',
'HtmlElement',
'HtmlCollection',
'CanvasRenderingContext2d',
'CssStyleDeclaration',
'HtmlCanvasElement',
'WebGlBuffer',

View File

@ -1,2 +1 @@
pub mod callback;
pub mod event_loop;

View File

@ -0,0 +1 @@
pub mod monitor;

View 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 };
}
}

View File

@ -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() {

View File

@ -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();
}
}

View File

@ -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() {

View File

@ -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;
}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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 ${@}"