Improving Performance Monitor (#5895)

This commit is contained in:
Wojciech Daniło 2023-03-21 09:17:54 +01:00 committed by GitHub
parent c9806496ee
commit abb0b447d5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
33 changed files with 1308 additions and 890 deletions

View File

@ -172,11 +172,16 @@
generated shaders differ per theme (only light theme is available, the dark
theme has been disabled). We will support multiple themes in the future, but
this is not on our priority list right now.
- [Performance monitor was extended with the ability to print details of actions
performed in a given frame][5895]. In particular, you can now inspect names of
all symbols rendered in a given frame. You can also pause the performance
monitor and inspect results recorded in the past.
[3857]: https://github.com/enso-org/enso/pull/3857
[3985]: https://github.com/enso-org/enso/pull/3985
[4047]: https://github.com/enso-org/enso/pull/4047
[4003]: https://github.com/enso-org/enso/pull/4003
[5895]: https://github.com/enso-org/enso/pull/5895
#### Enso Standard Library

View File

@ -43,8 +43,6 @@ pub enum Metadata {
RpcRequest(json_rpc::log::RpcRequest),
/// A message between the Language Server and the Engine.
BackendMessage(backend::Message),
/// Performance stats gathered from the EnsoGL rendering engine.
RenderStats(ensogl_core::debug::StatsData),
/// Any other metadata type.
///
/// The types defined above are handled specially by `enso-profiler-enso-data` tools: E.g. the
@ -63,7 +61,6 @@ impl Display for Metadata {
Metadata::RpcEvent(name) => f.collect_str(name),
Metadata::RpcRequest(method) => f.collect_str(&method.to_string()),
Metadata::BackendMessage(backend::Message { endpoint, .. }) => f.collect_str(endpoint),
Metadata::RenderStats(stats) => f.collect_str(&format!("{stats:#?}")),
Metadata::Other => f.collect_str("<value>"),
}
}

View File

@ -370,17 +370,9 @@ impl View {
frp.source.is_hovered <+ model.overlay.events.mouse_over.constant(true);
frp.source.is_hovered <+ model.overlay.events.mouse_out.constant(false);
let mouse_up = scene.mouse.frp.up.clone_ref();
let mouse_down = scene.mouse.frp.down.clone_ref();
let mouse_wheel = scene.mouse.frp.wheel.clone_ref();
let mouse_position = scene.mouse.frp.position.clone_ref();
caught_mouse <- any_(mouse_up,mouse_down,mouse_wheel,mouse_position);
pass_to_dom <- caught_mouse.gate(&frp.source.is_hovered);
eval_ pass_to_dom(scene.current_js_event.pass_to_dom.emit(()));
}
init.emit(());
style.init.emit(());
visualization.pass_events_to_dom_if_active(scene, network);
self
}
}

View File

@ -298,7 +298,6 @@ class Histogram extends Visualization {
.attr('width', canvas.inner.width)
.attr('height', canvas.inner.height)
.style('fill', 'none')
.style('pointer-events', 'all')
.call(zoom)
const self = this
@ -396,6 +395,18 @@ class Histogram extends Visualization {
return { zoomElem, zoom, transformedScale }
}
/** Removing `pointer-events` handling from brush element, as we want it to be inherited. D3 inserts
* `pointer-events: all` in the brush element and some of its children on brush creation and after brushing ends.
* There is no documentation on that topic as far as we are aware, so this was observed and tested manually. */
removePointerEventsAttrsFromBrush(brushElem) {
brushElem.attr('pointer-events', null)
brushElem.select(function () {
for (const child of this.childNodes) {
child.removeAttribute('pointer-events')
}
})
}
/**
* Initialise brushing functionality on the visualization.
*
@ -417,7 +428,7 @@ class Histogram extends Visualization {
// The brush element must be child of zoom element - this is only way we found to have both
// zoom and brush events working at the same time. See https://stackoverflow.com/a/59757276 .
const brushElem = zoom.zoomElem.append('g').attr('class', brushClass).call(brush)
this.removePointerEventsAttrsFromBrush(brushElem)
const self = this
/**
@ -463,6 +474,7 @@ class Histogram extends Visualization {
selectedZoomBtn.style.display = 'none'
selectedZoomBtn.removeEventListener('click', zoomIn, true)
document.removeEventListener('keydown', zoomInKeyEvent, true)
this.removePointerEventsAttrsFromBrush(brushElem)
}
let endEvents = ['click', 'auxclick', 'contextmenu', 'scroll']

View File

@ -234,7 +234,6 @@ class ScatterPlot extends Visualization {
.attr('width', boxWidth)
.attr('height', boxHeight)
.style('fill', 'none')
.style('pointer-events', 'all')
.call(zoom)
let transformedScale = Object.assign({}, scaleAndAxis)
@ -355,6 +354,18 @@ class ScatterPlot extends Visualization {
return { zoomElem, zoom, transformedScale }
}
/** Removing `pointer-events` handling from brush element, as we want it to be inherited. D3 inserts
* `pointer-events: all` in the brush element and some of its children on brush creation and after brushing ends.
* There is no documentation on that topic as far as we are aware, so this was observed and tested manually. */
removePointerEventsAttrsFromBrush(brushElem) {
brushElem.attr('pointer-events', null)
brushElem.select(function () {
for (const child of this.childNodes) {
child.removeAttribute('pointer-events')
}
})
}
/**
* Adds brushing functionality to the plot.
*
@ -377,6 +388,7 @@ class ScatterPlot extends Visualization {
// events working at the same time. See https://stackoverflow.com/a/59757276 .
let brushElem = zoom.zoomElem.append('g').attr('class', brushClass).call(brush)
this.removePointerEventsAttrsFromBrush(brushElem)
let self = this
/**
@ -426,6 +438,7 @@ class ScatterPlot extends Visualization {
selectedZoomBtn.style.display = 'none'
selectedZoomBtn.removeEventListener('click', zoomIn, true)
document.removeEventListener('keydown', zoomInKeyEvent, true)
this.removePointerEventsAttrsFromBrush(brushElem)
}
let endEvents = ['click', 'auxclick', 'contextmenu', 'scroll']

View File

@ -115,7 +115,6 @@ impl InstanceModel {
let bg_blue = bg_color.blue * 255.0;
let bg_hex = format!("rgba({},{},{},{})", bg_red, bg_green, bg_blue, bg_color.alpha);
root_node.dom().set_style_or_warn("background", bg_hex);
Ok(root_node)
}
@ -247,6 +246,11 @@ impl InstanceModel {
fn set_layer(&self, layer: Layer) {
layer.apply_for_html_component(&self.scene, &self.root_node)
}
fn set_active(&self, active: bool) {
let attribute = if active { "all" } else { "none " };
self.root_node.set_style_or_warn("pointer-events", attribute);
}
}
@ -273,10 +277,11 @@ impl Instance {
let frp = visualization::instance::Frp::new(&network);
let model = InstanceModel::from_class(class, scene)?;
model.set_dom_layer(&scene.dom.layers.back);
Ok(Instance { model, frp, network }.init_frp(scene).init_preprocessor_change_callback())
model.set_active(false);
Ok(Instance { model, frp, network }.init_frp().init_preprocessor_change_callback())
}
fn init_frp(self, scene: &Scene) -> Self {
fn init_frp(self) -> Self {
let network = &self.network;
let model = self.model.clone_ref();
let frp = self.frp.clone_ref();
@ -288,8 +293,8 @@ impl Instance {
}
});
eval frp.set_layer ((layer) model.set_layer(*layer));
eval frp.is_active ((is_active) model.set_active(*is_active));
}
frp.pass_events_to_dom_if_active(scene, network);
self
}

View File

@ -8,7 +8,6 @@ use crate::data::enso;
use enso_frp as frp;
use ensogl::display;
use ensogl::display::DomSymbol;
use ensogl::display::Scene;
@ -179,7 +178,7 @@ impl Frp {
def preprocessor_change = any_mut();
on_preprocessor_change <- preprocessor_change.sampler();
def data_receive_error = source();
is_active <- bool(&inputs.deactivate,&inputs.activate);
is_active <- bool(&inputs.deactivate, &inputs.activate);
};
preprocessor_change.emit(PreprocessorConfiguration::default());
let on_data_receive_error = data_receive_error.clone_ref().into();
@ -192,26 +191,6 @@ impl Frp {
preprocessor_change,
}
}
/// Extend the FRP network with mechanism of passing all mouse and keyboard event to DOM when
/// visualization is active.
///
/// Used mainly in visualizations based on DOM elements (e.g. JavaScript visualization).
pub fn pass_events_to_dom_if_active(&self, scene: &Scene, network: &frp::Network) {
frp::extend! { network
let mouse_up = scene.mouse.frp.up.clone_ref();
let mouse_down = scene.mouse.frp.down.clone_ref();
let mouse_wheel = scene.mouse.frp.wheel.clone_ref();
let mouse_position = scene.mouse.frp.position.clone_ref();
let keyboard_up = scene.keyboard.frp.up.clone_ref();
let keyboard_down = scene.keyboard.frp.down.clone_ref();
caught_mouse <- any_(mouse_up,mouse_down,mouse_wheel,mouse_position);
caught_keyboard <- any_(keyboard_up,keyboard_down);
caught_event <- any(caught_mouse,caught_keyboard);
should_process <- caught_event.gate(&self.is_active);
eval_ should_process (scene.current_js_event.pass_to_dom.emit(()));
}
}
}

View File

@ -80,6 +80,7 @@ body {
body {
margin: 0;
overscroll-behavior: none;
}
#root {
@ -93,9 +94,6 @@ body {
.visualization {
z-index: 2;
border-radius: 14px;
/* visualizations may be put into the fullscreen-vis layer which has pointer-events set to none, so we need to
override it */
pointer-events: auto;
}
.auth-header {

View File

@ -284,6 +284,8 @@ impl IsTarget for Wasm {
}
}
#[derive(Clone, Derivative)]
#[derivative(Debug)]
pub struct WatchInput {
@ -343,11 +345,18 @@ impl IsWatchable for Wasm {
let mut watch_cmd = Cargo.cmd()?;
let (watch_cmd_name, mut watch_cmd_opts) = match std::env::var("USE_CARGO_WATCH_PLUS") {
Ok(_) => ("watch-plus", vec!["--why"]),
Err(_) => ("watch", vec![]),
};
watch_cmd_opts.push("--ignore");
watch_cmd_opts.push("README.md");
watch_cmd
.kill_on_drop(true)
.current_dir(&context.repo_root)
.arg("watch")
.args(["--ignore", "README.md"])
.arg(watch_cmd_name)
.args(watch_cmd_opts)
.args(cargo_watch_flags)
.arg("--");

View File

@ -131,7 +131,7 @@ The following operating systems are supported for developing Enso:
- macOS 10.14 and above
- Linux 4.4 and above
Currently we support `x86_64` (all mentioned OS) and `arm64` (Mac only)
Currently, we support `x86_64` (all mentioned OS) and `arm64` (Mac only)
architectures. You may be able to develop Enso on other systems, but issues
arising from unsupported configurations will not be fixed by the core team.
@ -228,6 +228,15 @@ enso$ cargo +stable install cargo-watch # To enable `./run wasm watch` utility
The previous three steps shall be enough to build the IDE via
`./run wasm build run wasm build --wasm-profile dev`.
### Using Cargo Watch Plus
Currently, `cargo-watch` has
[many issues](https://github.com/enso-org/cargo-watch-plus), including not
working on modern macOS properly. Thus, we've developed a replacement, the
[Cargo Watch Plus](https://github.com/enso-org/cargo-watch-plus). To use it,
simply export the `USE_CARGO_WATCH_PLUS=1` in your shell and the build system
will pick it up instead of the `cargo-watch`.
### Getting Set Up (JVM)
In order to properly build the `runtime` component, the JVM running SBT needs to

View File

@ -12,9 +12,9 @@ use ensogl_core::system::web::Map;
// =================
/// Path within the asset directory to store the vertex shader.
const VERTEX_FILE: &'static str = "vertex.glsl";
const VERTEX_FILE: &str = "vertex.glsl";
/// Path within the asset directory to store the fragment shader.
const FRAGMENT_FILE: &'static str = "fragment.glsl";
const FRAGMENT_FILE: &str = "fragment.glsl";

View File

@ -105,13 +105,13 @@ impl Application {
let data = &self.inner;
let network = self.frp.network();
enso_frp::extend! { network
eval self.display.default_scene.frp.focused ((t) data.show_system_cursor(!t));
frp.private.output.tooltip <+ frp.private.input.set_tooltip;
eval_ frp.private.input.show_system_cursor(data.show_system_cursor(true));
eval_ frp.private.input.hide_system_cursor(data.show_system_cursor(false));
}
// We hide the system cursor to replace it with the EnsoGL-provided one.
self.frp.hide_system_cursor();
self
}

View File

@ -41,7 +41,13 @@ pub struct MouseManager {
pub type MouseEventJsClosure = Closure<dyn FnMut(JsValue)>;
macro_rules! define_bindings {
( $( $js_event:ident :: $js_name:ident => $name:ident ($target:ident) ),* $(,)? ) => {
(
$target:ident,
$global_target:ident,
$( $js_event:ident :: $js_name:ident =>
$name:ident ($event_target:ident, $event:ident)
),* $(,)?
) => {
/// Keeps references to JavaScript closures in order to keep them alive.
#[derive(Debug)]
@ -53,22 +59,30 @@ macro_rules! define_bindings {
#[derive(Clone,CloneRef,Debug,Default)]
#[allow(missing_docs)]
pub struct MouseManagerDispatchers {
$(pub $name : callback::registry::Ref1<$target>),*
$(pub $name : callback::registry::Ref1<$event>),*
}
impl MouseManager {
/// Constructor.
pub fn new(dom:&web::dom::WithKnownShape<web::EventTarget>) -> Self {
Self::new_separated(dom,dom.deref())
}
/// Constructor which takes the exact element to set listener as a separate argument.
/// This is the constructor for mouse listeners which takes three arguments:
///
/// Sometimes we want to listen for mouse event for element without ResizeObserver.
/// Thus, some html element may be passed as a size provider, and another one where we
/// attach listeners (for example `body` and `window` respectively).
pub fn new_separated
(dom:&web::dom::WithKnownShape<web::EventTarget>,target:&web::EventTarget) -> Self {
/// 1. A DOM object to set resize observer on. This object should cover the entire screen.
/// Since EnsoGL's scene origin is positioned in the left-bottom corner, the size of
/// the DOM object is used to translate mouse coordinates from HTML to the EnsoGL space.
///
/// 2. A DOM object to set the 'mousedown', 'mousewheel', and 'mouseleave' listeners on.
/// In most cases, this should be the canvas used by EnsoGL. Alternatively, you can set
/// this argument to the window object if you want EnsoGL to capture all events, even if
/// it is placed behind another DOM element.
///
/// 3. A DOM object to set the 'mouseup' and 'mousemove' listeners on. In most cases,
/// this should be the window object. It is common for the element drag action to be
/// initiated by a 'mousedown' event on one element and finished by a 'mouseup' event
/// on another element. Handling these events globally covers such situations.
pub fn new(
dom: &web::dom::WithKnownShape<web::EventTarget>,
$target: &web::EventTarget,
$global_target: &web::EventTarget,
) -> Self {
let dispatchers = MouseManagerDispatchers::default();
let dom = dom.clone();
$(
@ -81,11 +95,12 @@ macro_rules! define_bindings {
);
let shape = shape.value();
let event = event.unchecked_into::<web::$js_event>();
dispatcher.run_all(&event::$target::new(event,shape))
dispatcher.run_all(&event::$event::new(event,shape))
});
let js_name = stringify!($js_name);
let opt = event_listener_options();
let $name = web::add_event_listener_with_options(&target,js_name,closure,&opt);
let $name = web::add_event_listener_with_options
(&$event_target, js_name, closure, &opt);
)*
let handles = Rc::new(MouseManagerEventListenerHandles {$($name),*});
Self {dispatchers,handles,dom}
@ -98,18 +113,20 @@ macro_rules! define_bindings {
/// https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener
fn event_listener_options() -> web::AddEventListenerOptions {
let mut options = web::AddEventListenerOptions::new();
// We listen for events in capture phase, so we can decide ourself if it should be passed
// further.
options.capture(true);
// We listen for events in the bubbling phase. If we ever would like to listen in the capture
// phase, it would need to be set to "bubbling" for the "mouseleave" and "mouseenter" events,
// as they provide incorrect events for the "capture" phase.
options.capture(false);
// We want to prevent default action on wheel events, thus listener cannot be passive.
options.passive(false);
options
}
define_bindings! {
MouseEvent::mousedown => on_down (OnDown),
MouseEvent::mouseup => on_up (OnUp),
MouseEvent::mousemove => on_move (OnMove),
MouseEvent::mouseleave => on_leave (OnLeave),
WheelEvent::wheel => on_wheel (OnWheel),
define_bindings! { target, gloabl_target,
MouseEvent::mousedown => on_down (target, OnDown),
MouseEvent::mouseup => on_up (gloabl_target, OnUp),
MouseEvent::mousemove => on_move (gloabl_target, OnMove),
MouseEvent::mouseleave => on_leave (target, OnLeave),
MouseEvent::mouseenter => on_enter (target, OnEnter),
WheelEvent::wheel => on_wheel (target, OnWheel),
}

View File

@ -98,5 +98,6 @@ define_events! {
MouseEvent::OnUp,
MouseEvent::OnMove,
MouseEvent::OnLeave,
MouseEvent::OnEnter,
WheelEvent::OnWheel,
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,267 @@
//! Definition of performance monitor sampler and a set of predefined samplers. Sampler is a utility
//! capable of displaying a single performance metric, like the current FPS or number of draw calls
//! per frame.
use crate::prelude::*;
use crate::debug::stats::StatsData;
use num_traits::cast::AsPrimitive;
// ==================
// === ValueCheck ===
// ==================
/// Values drawn in the monitor can be assigned with a check: `Correct`, `Warning`, and `Error`.
/// It affects the way they are visually displayed.
#[derive(Copy, Clone, Debug)]
#[allow(missing_docs)]
pub enum ValueCheck {
Correct,
Warning,
Error,
}
impl Default for ValueCheck {
fn default() -> Self {
Self::Correct
}
}
// To be removed after this gets resolved: https://github.com/rust-lang/rust-clippy/issues/4971
#[allow(clippy::collapsible_else_if)]
impl ValueCheck {
/// Construct the check by comparing the provided value to two threshold values.
pub fn from_threshold(warn_threshold: f64, err_threshold: f64, value: f64) -> Self {
if warn_threshold > err_threshold {
if value >= warn_threshold {
ValueCheck::Correct
} else if value >= err_threshold {
ValueCheck::Warning
} else {
ValueCheck::Error
}
} else {
if value <= warn_threshold {
ValueCheck::Correct
} else if value <= err_threshold {
ValueCheck::Warning
} else {
ValueCheck::Error
}
}
}
}
// ===============
// === Sampler ===
// ===============
/// Sampler is an utility to gather performance-related data and expose it in a way understandable
/// by the performance monitor.
#[derive(Copy, Clone)]
pub struct Sampler {
/// Label of the sampler to be displayed in the performance monitor window.
pub label: &'static str,
/// Get the newest value of the sampler. The value will be displayed in the monitor panel.
pub expr: fn(&StatsData) -> f64,
/// Get the details to be displayed in the details view.
pub details: Option<fn(&StatsData) -> &[&'static str]>,
/// If the value crosses this threshold, the graph will be drawn in the warning color.
pub warn_threshold: f64,
/// If the value crosses this threshold, the graph will be drawn in the error color.
pub err_threshold: f64,
/// The value will be divided by this number before being displayed.
pub value_divisor: f64,
/// The minimum expected value in order to set proper scaling of the monitor plots. If the real
/// value will be smaller than this parameter, it will be clamped.
pub min_value: Option<f64>,
/// The maximum expected value in order to set proper scaling of the monitor plots. If the real
/// value will be bigger than this parameter, it will be clamped.
pub max_value: Option<f64>,
/// The number of digits after the dot which should be displayed in the monitor panel.
pub precision: usize,
}
impl Debug for Sampler {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Sampler")
}
}
impl const Default for Sampler {
fn default() -> Self {
Self {
label: "Unlabeled",
expr: |_| 0.0,
details: None,
warn_threshold: 0.0,
err_threshold: 0.0,
value_divisor: 1.0,
min_value: None,
max_value: None,
precision: 0,
}
}
}
impl Sampler {
/// The current sampler value.
pub fn value(&self, stats: &StatsData) -> f64 {
let raw_value: f64 = (self.expr)(stats).as_();
raw_value / self.value_divisor
}
/// Check the current value in order to draw it with warning or error if it exceeds the allowed
/// thresholds.
pub fn check(&self, stats: &StatsData) -> ValueCheck {
let value = self.value(stats);
ValueCheck::from_threshold(self.warn_threshold, self.err_threshold, value)
}
/// Minimum size of the size the sampler should occupy in the performance monitor view.
pub fn min_size(&self) -> Option<f64> {
Some(self.warn_threshold)
}
}
// ================
// === Samplers ===
// ================
const MB: f64 = (1024 * 1024) as f64;
const DEFAULT_SAMPLER: Sampler = Default::default();
#[allow(missing_docs)]
pub const FPS: Sampler = Sampler {
label: "Frames per second",
expr: |s| s.fps,
warn_threshold: 55.0,
err_threshold: 25.0,
precision: 2,
max_value: Some(60.0),
..DEFAULT_SAMPLER
};
#[allow(missing_docs)]
pub const FRAME_TIME: Sampler = Sampler {
label: "Frame time (ms)",
expr: |s| s.frame_time,
warn_threshold: 1000.0 / 55.0,
err_threshold: 1000.0 / 25.0,
precision: 2,
..DEFAULT_SAMPLER
};
#[allow(missing_docs)]
pub const WASM_MEMORY_USAGE: Sampler = Sampler {
label: "WASM memory usage (Mb)",
expr: |s| s.wasm_memory_usage as f64,
warn_threshold: 50.0,
err_threshold: 100.0,
precision: 2,
value_divisor: MB,
..DEFAULT_SAMPLER
};
#[allow(missing_docs)]
pub const GPU_MEMORY_USAGE: Sampler = Sampler {
label: "GPU memory usage (Mb)",
expr: |s| s.gpu_memory_usage as f64,
warn_threshold: 100.0,
err_threshold: 500.0,
precision: 2,
value_divisor: MB,
..DEFAULT_SAMPLER
};
#[allow(missing_docs)]
pub const DRAW_CALL_COUNT: Sampler = Sampler {
label: "Draw call count",
expr: |s| s.draw_calls.len() as f64,
details: Some(|s| &s.draw_calls),
warn_threshold: 100.0,
err_threshold: 500.0,
..DEFAULT_SAMPLER
};
#[allow(missing_docs)]
pub const BUFFER_COUNT: Sampler = Sampler {
label: "Buffer count",
expr: |s| s.buffer_count as f64,
warn_threshold: 100.0,
err_threshold: 500.0,
..DEFAULT_SAMPLER
};
#[allow(missing_docs)]
pub const DATA_UPLOAD_COUNT: Sampler = Sampler {
label: "Data upload count",
expr: |s| s.data_upload_count as f64,
warn_threshold: 100.0,
err_threshold: 500.0,
..DEFAULT_SAMPLER
};
#[allow(missing_docs)]
pub const DATA_UPLOAD_SIZE: Sampler = Sampler {
label: "Data upload size (Mb)",
expr: |s| s.data_upload_size as f64,
warn_threshold: 1.0,
err_threshold: 10.0,
precision: 2,
value_divisor: MB,
..DEFAULT_SAMPLER
};
#[allow(missing_docs)]
pub const SPRITE_SYSTEM_COUNT: Sampler = Sampler {
label: "Sprite system count",
expr: |s| s.sprite_system_count as f64,
warn_threshold: 100.0,
err_threshold: 500.0,
..DEFAULT_SAMPLER
};
#[allow(missing_docs)]
pub const SYMBOL_COUNT: Sampler = Sampler {
label: "Symbol count",
expr: |s| s.symbol_count as f64,
warn_threshold: 100.0,
err_threshold: 500.0,
..DEFAULT_SAMPLER
};
#[allow(missing_docs)]
pub const SPRITE_COUNT: Sampler = Sampler {
label: "Sprite count",
expr: |s| s.sprite_count as f64,
warn_threshold: 100_000.0,
err_threshold: 500_000.0,
..DEFAULT_SAMPLER
};
#[allow(missing_docs)]
pub const SHADER_COUNT: Sampler = Sampler {
label: "Shader count",
expr: |s| s.shader_count as f64,
warn_threshold: 100.0,
err_threshold: 500.0,
..DEFAULT_SAMPLER
};
#[allow(missing_docs)]
pub const SHADER_COMPILE_COUNT: Sampler = Sampler {
label: "Shader compile count",
expr: |s| s.shader_compile_count as f64,
warn_threshold: 10.0,
err_threshold: 100.0,
..DEFAULT_SAMPLER
};

View File

@ -15,6 +15,9 @@
use enso_prelude::*;
use crate::display::world;
use crate::display::SymbolId;
use enso_types::unit2::Duration;
use enso_web::Performance;
use enso_web::TimeProvider;
@ -42,7 +45,7 @@ pub type Stats = StatsWithTimeProvider<Performance>;
/// 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)]
#[derive(Debug, Deref, CloneRef)]
pub struct StatsWithTimeProvider<T> {
rc: Rc<RefCell<FramedStatsData<T>>>,
}
@ -61,16 +64,17 @@ impl<T: TimeProvider> StatsWithTimeProvider<T> {
Self { rc }
}
/// Starts tracking data for a new animation frame.
/// Also, calculates the [`fps`] stat.
/// Returns a snapshot of statistics data for the previous frame.
///
/// 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, time: Duration) -> Option<StatsData> {
self.rc.borrow_mut().begin_frame(time)
/// Calculate FPS for the last frame. This function should be called on the very beginning of
/// every frame. Please note, that it does not clean the per-frame statistics. You want to run
/// the [`reset_per_frame_statistics`] function before running rendering operations.
pub fn calculate_prev_frame_fps(&self, time: Duration) {
self.rc.borrow_mut().calculate_prev_frame_fps(time)
}
/// Clean the per-frame statistics, such as the per-frame number of draw calls. This function
/// should be called before any rendering calls were made.
pub fn reset_per_frame_statistics(&self) {
self.rc.borrow_mut().reset_per_frame_statistics()
}
/// Ends tracking data for the current animation frame.
@ -78,6 +82,14 @@ impl<T: TimeProvider> StatsWithTimeProvider<T> {
pub fn end_frame(&self) {
self.rc.borrow_mut().end_frame();
}
/// Register a new draw call for the given symbol.
pub fn register_draw_call(&self, symbol_id: SymbolId) {
let label = world::with_context(|ctx| {
ctx.get_symbol(symbol_id).map(|t| t.label).unwrap_or("Unknown")
});
self.rc.borrow_mut().stats_data.register_draw_call(label);
}
}
@ -86,10 +98,12 @@ impl<T: TimeProvider> StatsWithTimeProvider<T> {
// === FramedStatsData ===
// =======================
/// Internal representation of [`StatsWithTimeProvider`].
#[allow(missing_docs)]
#[derive(Debug)]
struct FramedStatsData<T> {
pub struct FramedStatsData<T> {
time_provider: T,
stats_data: StatsData,
pub stats_data: StatsData,
frame_begin_time: Option<f64>,
}
@ -101,19 +115,14 @@ impl<T: TimeProvider> FramedStatsData<T> {
Self { time_provider, stats_data, frame_begin_time }
}
/// Starts tracking data for a new animation frame. The time is provided explicitly from the
/// JS `requestAnimationFrame` callback in order to be sure that we measure all the time,
/// including operations happening before calling this function.
fn begin_frame(&mut self, time: Duration) -> Option<StatsData> {
/// Calculate FPS for the last frame. This function should be called on the very beginning of
/// every frame. Please note, that it does not clean the per-frame statistics. You want to run
/// the [`reset_per_frame_statistics`] function before running rendering operations.
fn calculate_prev_frame_fps(&mut self, time: Duration) {
let time = time.unchecked_raw() as f64;
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
})
if let Some(previous_frame_begin_time) = self.frame_begin_time.replace(time) {
self.stats_data.fps = 1000.0 / (time - previous_frame_begin_time);
}
}
fn end_frame(&mut self) {
@ -131,8 +140,10 @@ impl<T: TimeProvider> FramedStatsData<T> {
}
}
/// Clean the per-frame statistics, such as the per-frame number of draw calls. This function
/// should be called before any rendering calls were made.
fn reset_per_frame_statistics(&mut self) {
self.stats_data.draw_call_count = 0;
self.stats_data.draw_calls = default();
self.stats_data.shader_compile_count = 0;
self.stats_data.data_upload_count = 0;
self.stats_data.data_upload_size = 0;
@ -150,7 +161,7 @@ impl<T: TimeProvider> FramedStatsData<T> {
macro_rules! emit_if_integer {
(u32, $($block:tt)*) => ($($block)*);
(usize, $($block:tt)*) => ($($block)*);
(f64, $($block:tt)*) => ();
($other:ty, $($block:tt)*) => ();
}
/// Emits the StatsData struct, and extends StatsWithTimeProvider with accessors to StatsData
@ -162,7 +173,7 @@ macro_rules! gen_stats {
// === StatsData ===
/// Raw data of all the gathered stats.
#[derive(Debug,Default,Clone,Copy,serde::Serialize,serde::Deserialize)]
#[derive(Debug, Default, Clone)]
#[allow(missing_docs)]
pub struct StatsData {
$(pub $field : $field_type),*
@ -174,7 +185,7 @@ macro_rules! gen_stats {
impl<T: TimeProvider> StatsWithTimeProvider<T> { $(
/// Field getter.
pub fn $field(&self) -> $field_type {
self.rc.borrow().stats_data.$field
self.rc.borrow().stats_data.$field.clone()
}
/// Field setter.
@ -189,7 +200,6 @@ macro_rules! gen_stats {
self.[<set _ $field>](value);
}
// FIXME: saturating_add is proper solution, but even without it it should not crash, but it does. To be investigated.
emit_if_integer!($field_type,
/// Increments field's value.
pub fn [<inc _ $field>](&self) {
@ -211,7 +221,7 @@ gen_stats! {
fps : f64,
wasm_memory_usage : u32,
gpu_memory_usage : u32,
draw_call_count : usize,
draw_calls : Vec<&'static str>,
buffer_count : usize,
data_upload_count : usize,
data_upload_size : u32,
@ -223,6 +233,13 @@ gen_stats! {
shader_compile_count : usize,
}
impl StatsData {
/// Register a new draw call for the given symbol.
pub fn register_draw_call(&mut self, symbol_name: &'static str) {
self.draw_calls.push(symbol_name);
}
}
/// Keeps the body if the `statistics` compilation flag was enabled.
#[macro_export]
macro_rules! if_compiled_with_stats {

View File

@ -238,9 +238,6 @@ impl NavigatorEvents {
let data = Rc::downgrade(&self.data);
let listener = self.mouse_manager.on_wheel.add(move |event: &mouse::OnWheel| {
if let Some(data) = data.upgrade() {
if data.is_navigator_enabled() {
event.prevent_default();
}
if event.ctrl_key() {
// Prevent zoom event to be handed to the browser. This avoids browser scaling
// being applied to the whole IDE, thus we need to do this always when ctrl is
@ -314,10 +311,6 @@ impl NavigatorEvents {
let data = Rc::downgrade(&self.data);
let listener = self.mouse_manager.on_move.add(move |event: &mouse::OnMove| {
if let Some(data) = data.upgrade() {
if data.is_navigator_enabled() {
event.prevent_default();
}
let position = event.position_relative_to_event_handler();
data.set_mouse_position(position);
let movement = data.mouse_position() - data.last_mouse_position();

View File

@ -30,7 +30,7 @@ use crate::system::web;
use crate::system::web::EventListenerHandle;
use enso_frp as frp;
use enso_frp::io::js::CurrentJsEvent;
use enso_frp::io::js::JsEvent;
use enso_shapely::shared;
use std::any::TypeId;
use web::HtmlElement;
@ -126,7 +126,7 @@ pub struct Mouse {
pub click_count: Uniform<i32>,
pub hover_rgba: Uniform<Vector4<u32>>,
pub target: Rc<Cell<PointerTargetId>>,
pub handles: Rc<[callback::Handle; 4]>,
pub handles: Rc<[callback::Handle; 6]>,
pub frp: enso_frp::io::Mouse,
pub scene_frp: Frp,
}
@ -136,37 +136,41 @@ impl Mouse {
scene_frp: &Frp,
root: &web::dom::WithKnownShape<web::HtmlDivElement>,
variables: &UniformScope,
current_js_event: &CurrentJsEvent,
js_event: &JsEvent,
display_mode: &Rc<Cell<glsl::codes::DisplayModes>>,
) -> Self {
let scene_frp = scene_frp.clone_ref();
let target = PointerTargetId::default();
let last_position = Rc::new(Cell::new(Vector2::new(0, 0)));
let position = variables.add_or_panic("mouse_position", Vector2(0, 0));
let last_position = Rc::new(Cell::new(Vector2::default()));
let position = variables.add_or_panic("mouse_position", Vector2::default());
let click_count = variables.add_or_panic("mouse_click_count", 0);
let hover_rgba = variables.add_or_panic("mouse_hover_ids", Vector4(0, 0, 0, 0));
let hover_rgba = variables.add_or_panic("mouse_hover_ids", Vector4::default());
let target = Rc::new(Cell::new(target));
let mouse_manager = MouseManager::new_separated(&root.clone_ref().into(), &web::window);
let shaped_dom = root.clone_ref().into();
let mouse_manager = MouseManager::new(&shaped_dom, root, &web::window);
let frp = frp::io::Mouse::new();
let on_move = mouse_manager.on_move.add(current_js_event.make_event_handler(
let on_move = mouse_manager.on_move.add(js_event.handler(
f!([frp, scene_frp, position, last_position] (event: &mouse::OnMove) {
let shape = scene_frp.shape.value();
let pixel_ratio = shape.pixel_ratio;
let screen_x = event.client_x();
let screen_y = event.client_y();
let new_pos = Vector2::new(screen_x,screen_y);
let pos_changed = new_pos != last_position.get();
if pos_changed {
last_position.set(new_pos);
let new_canvas_position = new_pos.map(|v| (v as f32 * pixel_ratio) as i32);
position.set(new_canvas_position);
let position = Vector2(new_pos.x as f32,new_pos.y as f32) - shape.center();
let position_bottom_left = Vector2(new_pos.x as f32, new_pos.y as f32);
let position_top_left = Vector2(new_pos.x as f32, shape.height - new_pos.y as f32);
let position = position_bottom_left - shape.center();
frp.position_bottom_left.emit(position_bottom_left);
frp.position_top_left.emit(position_top_left);
frp.position.emit(position);
}
}),
));
let on_down = mouse_manager.on_down.add(current_js_event.make_event_handler(
let on_down = mouse_manager.on_down.add(js_event.handler(
f!([frp, click_count, display_mode] (event:&mouse::OnDown) {
click_count.modify(|v| *v += 1);
if display_mode.get().allow_mouse_events() {
@ -174,21 +178,27 @@ impl Mouse {
}
}),
));
let on_up = mouse_manager.on_up.add(current_js_event.make_event_handler(
let on_up = mouse_manager.on_up.add(js_event.handler(
f!([frp, display_mode] (event:&mouse::OnUp) {
if display_mode.get().allow_mouse_events() {
frp.up.emit(event.button())
}
}),
));
let on_wheel = mouse_manager.on_wheel.add(current_js_event.make_event_handler(
f_!([frp, display_mode] {
if display_mode.get().allow_mouse_events() {
frp.wheel.emit(())
}
}),
));
let handles = Rc::new([on_move, on_down, on_up, on_wheel]);
let on_wheel = mouse_manager.on_wheel.add(js_event.handler(f_!([frp, display_mode] {
if display_mode.get().allow_mouse_events() {
frp.wheel.emit(())
}
})));
let on_leave = mouse_manager.on_leave.add(js_event.handler(f_!(
scene_frp.focused_source.emit(false);
)));
let on_enter = mouse_manager.on_enter.add(js_event.handler(f_!(
scene_frp.focused_source.emit(true);
)));
let handles = Rc::new([on_move, on_down, on_up, on_wheel, on_leave, on_enter]);
Self {
mouse_manager,
last_position,
@ -243,7 +253,7 @@ pub struct Keyboard {
}
impl Keyboard {
pub fn new(current_event: &CurrentJsEvent) -> Self {
pub fn new(current_event: &JsEvent) -> Self {
let frp = enso_frp::io::keyboard::Keyboard::default();
let bindings = Rc::new(enso_frp::io::keyboard::DomBindings::new(&frp, current_event));
Self { frp, bindings }
@ -624,8 +634,10 @@ pub struct Frp {
pub shape: frp::Sampler<Shape>,
pub camera_changed: frp::Stream,
pub frame_time: frp::Stream<f32>,
pub focused: frp::Stream<bool>,
camera_changed_source: frp::Source,
frame_time_source: frp::Source<f32>,
focused_source: frp::Source<bool>,
}
impl Frp {
@ -633,18 +645,22 @@ impl Frp {
pub fn new(shape: &frp::Sampler<Shape>) -> Self {
frp::new_network! { network
camera_changed_source <- source();
frame_time_source <- source();
frame_time_source <- source();
focused_source <- source();
}
let shape = shape.clone_ref();
let camera_changed = camera_changed_source.clone_ref().into();
let frame_time = frame_time_source.clone_ref().into();
let focused = focused_source.clone_ref().into();
Self {
network,
shape,
camera_changed,
frame_time,
focused,
camera_changed_source,
frame_time_source,
focused_source,
}
}
}
@ -701,7 +717,7 @@ pub struct SceneData {
pub context: Rc<RefCell<Option<Context>>>,
pub context_lost_handler: Rc<RefCell<Option<ContextLostHandler>>>,
pub variables: UniformScope,
pub current_js_event: CurrentJsEvent,
pub js_event: JsEvent,
pub mouse: Mouse,
pub keyboard: Keyboard,
pub uniforms: Uniforms,
@ -743,11 +759,11 @@ impl SceneData {
let uniforms = Uniforms::new(&variables);
let renderer = Renderer::new(&dom, &variables);
let style_sheet = world::with_context(|t| t.style_sheet.clone_ref());
let current_js_event = CurrentJsEvent::new();
let js_event = JsEvent::new();
let frp = Frp::new(&dom.root.shape);
let mouse = Mouse::new(&frp, &dom.root, &variables, &current_js_event, &display_mode);
let mouse = Mouse::new(&frp, &dom.root, &variables, &js_event, &display_mode);
let disable_context_menu = Rc::new(web::ignore_context_menu(&dom.root));
let keyboard = Keyboard::new(&current_js_event);
let keyboard = Keyboard::new(&js_event);
let network = &frp.network;
let extensions = Extensions::default();
let bg_color_var = style_sheet.var("application.background");
@ -776,7 +792,7 @@ impl SceneData {
context,
context_lost_handler,
variables,
current_js_event,
js_event,
mouse,
keyboard,
uniforms,
@ -822,8 +838,8 @@ impl SceneData {
self.layers.main.camera()
}
pub fn new_symbol(&self) -> Symbol {
world::with_context(|t| t.new())
pub fn new_symbol(&self, label: &'static str) -> Symbol {
world::with_context(|t| t.new(label))
}
fn update_shape(&self) -> bool {

View File

@ -367,7 +367,7 @@ impl ShapeSystemModel {
/// Constructor.
#[profile(Detail)]
pub fn new(shape: def::AnyShape, pointer_events: bool, definition_path: &'static str) -> Self {
let sprite_system = SpriteSystem::new();
let sprite_system = SpriteSystem::new(definition_path);
let material = Rc::new(RefCell::new(Self::default_material()));
let geometry_material = Rc::new(RefCell::new(Self::default_geometry_material()));
let pointer_events = Immutable(pointer_events);

View File

@ -323,6 +323,7 @@ impl Symbol {
/// Constructor.
pub fn new<OnMut: Fn() + Clone + 'static>(
stats: &Stats,
label: &'static str,
id: SymbolId,
global_id_provider: &GlobalInstanceIdProvider,
on_mut: OnMut,
@ -332,7 +333,7 @@ impl Symbol {
let shader_dirty = ShaderDirty::new(Box::new(on_mut));
let shader_on_mut = Box::new(f!(shader_dirty.set()));
let shader = Shader::new(stats, shader_on_mut);
let data = Rc::new(SymbolData::new(stats, id, global_id_provider, on_mut2));
let data = Rc::new(SymbolData::new(stats, label, id, global_id_provider, on_mut2));
Self { data, shader_dirty, shader }
})
}
@ -393,7 +394,7 @@ impl Symbol {
let count = self.surface.point_scope().size() as i32;
let instance_count = self.surface.instance_scope().size() as i32;
self.stats.inc_draw_call_count();
self.stats.register_draw_call(self.id);
if instance_count > 0 {
context.draw_arrays_instanced(*mode, first, count, instance_count);
} else {
@ -536,6 +537,7 @@ impl WeakElement for WeakSymbol {
#[derive(Debug, Clone)]
#[allow(missing_docs)]
pub struct SymbolData {
pub label: &'static str,
pub id: SymbolId,
global_id_provider: GlobalInstanceIdProvider,
display_object: display::object::Instance,
@ -553,6 +555,7 @@ impl SymbolData {
/// Create new instance with the provided on-dirty callback.
pub fn new<OnMut: Fn() + Clone + 'static>(
stats: &Stats,
label: &'static str,
id: SymbolId,
global_id_provider: &GlobalInstanceIdProvider,
on_mut: OnMut,
@ -572,6 +575,7 @@ impl SymbolData {
let global_instance_id = instance_scope.add_buffer("global_instance_id");
Self {
label,
id,
global_id_provider,
display_object,

View File

@ -23,7 +23,7 @@ impl Screen {
/// Constructor.
#[profile(Detail)]
pub fn new(surface_material: Material) -> Self {
let sprite_system = SpriteSystem::new();
let sprite_system = SpriteSystem::new("screen");
sprite_system.set_geometry_material(Self::geometry_material());
sprite_system.set_material(surface_material);
let sprite = sprite_system.new_instance();

View File

@ -288,8 +288,8 @@ pub struct SpriteSystem {
impl SpriteSystem {
/// Constructor.
#[profile(Detail)]
pub fn new() -> Self {
let (stats, symbol) = world::with_context(|t| (t.stats.clone_ref(), t.new()));
pub fn new(label: &'static str) -> Self {
let (stats, symbol) = world::with_context(|t| (t.stats.clone_ref(), t.new(label)));
let mesh = symbol.surface();
let point_scope = mesh.point_scope();
let instance_scope = mesh.instance_scope();

View File

@ -137,19 +137,23 @@ impl SymbolRegistry {
/// Creates a new `Symbol`.
#[allow(clippy::new_ret_no_self)]
pub fn new(&self) -> Symbol {
pub fn new(&self, label: &'static str) -> Symbol {
let dirty = self.dirty.clone();
let stats = &self.stats;
let id_value = self.next_id.get();
self.next_id.set(id_value + 1);
let id = SymbolId::new(id_value);
let on_mut = move || dirty.set(id);
let symbol = Symbol::new(stats, id, &self.global_id_provider, on_mut);
let symbol = Symbol::new(stats, label, id, &self.global_id_provider, on_mut);
symbol.set_context(self.context.borrow().as_ref());
self.symbols.borrow_mut().insert(id, symbol.clone_ref());
symbol
}
pub fn get_symbol(&self, id: SymbolId) -> Option<Symbol> {
self.symbols.borrow().get(&id)
}
/// Set the GPU context. In most cases, this happens during app initialization or during context
/// restoration, after the context was lost. See the docs of [`Context`] to learn more.
pub fn set_context(&self, context: Option<&Context>) {

View File

@ -230,13 +230,6 @@ impl Uniforms {
}
// =========================
// === Metadata Profiler ===
// =========================
profiler::metadata_logger!("RenderStats", log_render_stats(StatsData));
// =============
// === World ===
@ -436,7 +429,6 @@ impl WorldData {
let garbage_collector = default();
let stats_draw_handle = on.prev_frame_stats.add(f!([stats_monitor] (stats: &StatsData) {
stats_monitor.sample_and_draw(stats);
log_render_stats(*stats)
}));
let themes = with_context(|t| t.theme_manager.clone_ref());
let update_themes_handle = on.before_frame.add(f_!(themes.update()));
@ -536,10 +528,12 @@ impl WorldData {
}
fn run_stats(&self, time: Duration) {
let previous_frame_stats = self.stats.begin_frame(time);
if let Some(stats) = previous_frame_stats {
self.on.prev_frame_stats.run_all(&stats);
self.stats.calculate_prev_frame_fps(time);
{
let stats_borrowed = self.stats.borrow();
self.on.prev_frame_stats.run_all(&stats_borrowed.stats_data);
}
self.stats.reset_per_frame_statistics();
}
/// Begin incrementally submitting [`profiler`] data to the User Timing web API.

View File

@ -82,7 +82,7 @@ pub fn main() {
let scene = &world.default_scene;
let camera = scene.camera();
let navigator = Navigator::new(scene, &camera);
let sprite_system = SpriteSystem::new();
let sprite_system = SpriteSystem::new("test_sprite_system");
world.add_child(&sprite_system);
let dom_front_layer = &scene.dom.layers.front;

View File

@ -22,6 +22,7 @@ use ensogl_core::prelude::*;
use wasm_bindgen::prelude::*;
use enso_profiler_data::parse_multiprocess_profile;
use enso_profiler_data::Class;
use enso_profiler_data::Profile;
use enso_profiler_enso_data::Metadata;
use enso_profiler_flame_graph as profiler_flame_graph;
@ -178,7 +179,6 @@ fn make_marks_from_profile(profile: &Profile<Metadata>) -> Vec<profiler_flame_gr
profile
.metadata()
.filter_map(|metadata: &enso_profiler_data::Timestamped<Metadata>| match metadata.data {
Metadata::RenderStats(_) => None,
Metadata::RpcEvent(_) if !SHOW_RPC_EVENT_MARKS => None,
Metadata::BackendMessage(_) if !SHOW_BACKEND_MESSAGE_MARKS => None,
_ => {
@ -190,28 +190,29 @@ fn make_marks_from_profile(profile: &Profile<Metadata>) -> Vec<profiler_flame_gr
.collect()
}
fn make_rendering_performance_blocks(
profile: &Profile<Metadata>,
fn make_rendering_performance_blocks<M>(
profile: &Profile<M>,
) -> Vec<profiler_flame_graph::Block<Performance>> {
let mut blocks = Vec::default();
let render_stats = profile.metadata().filter_map(|metadata| match metadata.data {
Metadata::RenderStats(data) => Some(metadata.as_ref().map(|_| data)),
_ => None,
});
for (prev, current) in render_stats.tuple_windows() {
let start = prev.time.into_ms();
let end = current.time.into_ms();
let row = -1;
let label = format!("{:#?}", current.data);
let block_type = match current.data.fps {
fps if fps > 55.0 => Performance::Good,
fps if fps > 25.0 => Performance::Medium,
_ => Performance::Bad,
};
let block = profiler_flame_graph::Block { start, end, row, label, block_type };
blocks.push(block);
}
blocks
profile
.intervals
.iter()
.filter(|interval| matches!(profile[interval.measurement].classify(), Class::OnFrame))
.map(|interval| interval.interval.start.into_ms())
.tuple_windows()
.map(|(start, end)| {
let label = "<frame>".to_string();
let row = -1;
let block_type = match end - start {
// 60 FPS
dt if dt < 17.0 => Performance::Good,
// 30 FPS
dt if dt < 34.0 => Performance::Medium,
// <30 FPS
_ => Performance::Bad,
};
profiler_flame_graph::Block { start, end, row, label, block_type }
})
.collect()
}

View File

@ -39,7 +39,7 @@ pub fn main() {
let scene = &world.default_scene;
let camera = scene.camera().clone_ref();
let navigator = Navigator::new(scene, &camera);
let sprite_system = SpriteSystem::new();
let sprite_system = SpriteSystem::new("test_sprite_system");
let sprite1 = sprite_system.new_instance();
sprite1.set_size(Vector2::new(10.0, 10.0));

View File

@ -32,7 +32,7 @@ use ensogl_core::display::symbol::geometry::SpriteSystem;
pub fn main() {
let world = World::new().displayed_in("root");
let navigator = Navigator::new(&world.default_scene, &world.default_scene.camera());
let sprite_system = SpriteSystem::new();
let sprite_system = SpriteSystem::new("test_sprite_system");
let sprite2 = sprite_system.new_instance();
let sprite1 = sprite_system.new_instance();

View File

@ -85,88 +85,44 @@ fn event_listener_options() -> enso_web::AddEventListenerOptions {
// === JsEventHandler ===
// ======================
/// Handler of currently processed js event.
///
/// Managed js event by this structure will be NOT propagated further to DOM elements, unless the
/// `pass_to_dom` event will be emitted.
///
/// To make this class manage js event, you should wrap the closure passed as event listener using
/// `make_event_handler` function.
/// FRP wrapper for JS events. You can use the [`Self::handler`] method to generate a new event
/// handler closure. When event is fired, it will be emitted as [`Self::event`] stream. After the
/// event stops propagating, [`None`] will be emitted instead.
#[allow(missing_docs)]
#[derive(Clone, CloneRef, Debug)]
pub struct CurrentJsEvent {
/// Currently handled js event.
pub event: frp::Stream<Option<enso_web::Event>>,
/// Emitting this signal while handling js event (`current_js_event` is Some) makes this event
/// pass to the DOM elements. Otherwise the js event propagation will be stopped.
pub pass_to_dom: frp::Source,
event_source: frp::Source<Option<enso_web::Event>>,
network: frp::Network,
pub struct JsEvent {
pub event: frp::Stream<Option<enso_web::Event>>,
event_source: frp::Source<Option<enso_web::Event>>,
network: frp::Network,
}
impl Default for CurrentJsEvent {
impl Default for JsEvent {
fn default() -> Self {
Self::new()
}
}
impl CurrentJsEvent {
impl JsEvent {
/// Constructor
pub fn new() -> Self {
frp::new_network! { network
event_source <- source();
pass_to_dom <- source();
event_is_passed_to_dom <- any(...);
event_is_passed_to_dom <+ pass_to_dom.constant(true);
event <- any(...);
new_event <- event_source.map3(&event,&event_is_passed_to_dom,Self::on_event_change);
event_is_passed_to_dom <+ new_event.constant(false);
event <+ new_event;
event_source <- source();
}
let event = event.into();
Self { event, pass_to_dom, event_source, network }
let event = event_source.clone().into();
Self { event, event_source, network }
}
/// A helper function for creating mouse event handlers.
///
/// This wraps the `processing_fn` so before processing the current js event is
/// set to the received js event, and after processing it is set back to `None`.
pub fn make_event_handler<Event>(
&self,
mut processing_fn: impl FnMut(&Event),
) -> impl FnMut(&Event)
where
Event: AsRef<enso_web::Event>,
{
let event_source = self.event_source.clone_ref();
move |event| {
/// Creates an event handler which wraps the event in an FRP network. The event will be emitted
/// on the `event` output stream. After the event is emitted, `None` will be emitted.
pub fn handler<Event>(&self, mut processing_fn: impl FnMut(&Event)) -> impl FnMut(&Event)
where Event: AsRef<enso_web::Event> {
let event_source = &self.event_source;
f!([event_source] (event) {
let _profiler = profiler::start_debug!(profiler::APP_LIFETIME, "event_handler");
let js_event = event.as_ref().clone();
event_source.emit(Some(js_event));
processing_fn(event);
event_source.emit(None);
}
}
// The bool is passed by reference to match the signatures expected by FRP eval.
#[allow(clippy::trivially_copy_pass_by_ref)]
fn on_event_change(
new: &Option<enso_web::Event>,
current: &Option<enso_web::Event>,
is_passed: &bool,
) -> Option<enso_web::Event> {
// Whenever the current js event change, we pass the processed one to the dom if someone
// asked to.
if let Some(e) = current {
if !is_passed {
// Prevent events from propagating to user agent, so default browser actions will
// not be triggered.
e.prevent_default();
e.stop_propagation();
}
}
new.clone()
})
}
}

View File

@ -3,7 +3,7 @@
use crate::prelude::*;
use crate as frp;
use crate::io::js::CurrentJsEvent;
use crate::io::js::JsEvent;
use crate::io::js::Listener;
use enso_web::KeyboardEvent;
@ -440,15 +440,16 @@ pub struct DomBindings {
impl DomBindings {
/// Create new Keyboard and Frp bindings.
pub fn new(keyboard: &Keyboard, current_event: &CurrentJsEvent) -> Self {
let key_down = Listener::new_key_down(current_event.make_event_handler(
pub fn new(keyboard: &Keyboard, current_event: &JsEvent) -> Self {
let key_down = Listener::new_key_down(current_event.handler(
f!((event:&KeyboardEvent) keyboard.source.down.emit(KeyWithCode::from(event))),
));
let key_up = Listener::new_key_up(current_event.make_event_handler(
f!((event:&KeyboardEvent) keyboard.source.up.emit(KeyWithCode::from(event))),
));
let key_up =
Listener::new_key_up(current_event.handler(
f!((event:&KeyboardEvent) keyboard.source.up.emit(KeyWithCode::from(event))),
));
let blur = Listener::new_blur(
current_event.make_event_handler(f_!(keyboard.source.window_defocused.emit(()))),
current_event.handler(f_!(keyboard.source.window_defocused.emit(()))),
);
Self { key_down, key_up, blur }
}

View File

@ -185,49 +185,51 @@ impl From<&ButtonMask> for ButtonMask {
#[derive(Clone, CloneRef, Debug)]
#[allow(missing_docs)]
pub struct Mouse {
pub network: frp::Network,
pub up: frp::Source<Button>,
pub down: frp::Source<Button>,
pub wheel: frp::Source,
pub up_0: frp::Stream,
pub up_1: frp::Stream,
pub up_2: frp::Stream,
pub up_3: frp::Stream,
pub up_4: frp::Stream,
pub up_primary: frp::Stream,
pub up_middle: frp::Stream,
pub up_secondary: frp::Stream,
pub down_0: frp::Stream,
pub down_1: frp::Stream,
pub down_2: frp::Stream,
pub down_3: frp::Stream,
pub down_4: frp::Stream,
pub down_primary: frp::Stream,
pub down_middle: frp::Stream,
pub down_secondary: frp::Stream,
pub is_up_0: frp::Stream<bool>,
pub is_up_1: frp::Stream<bool>,
pub is_up_2: frp::Stream<bool>,
pub is_up_3: frp::Stream<bool>,
pub is_up_4: frp::Stream<bool>,
pub is_up_primary: frp::Stream<bool>,
pub is_up_middle: frp::Stream<bool>,
pub is_up_secondary: frp::Stream<bool>,
pub is_down_0: frp::Stream<bool>,
pub is_down_1: frp::Stream<bool>,
pub is_down_2: frp::Stream<bool>,
pub is_down_3: frp::Stream<bool>,
pub is_down_4: frp::Stream<bool>,
pub is_down_primary: frp::Stream<bool>,
pub is_down_middle: frp::Stream<bool>,
pub is_down_secondary: frp::Stream<bool>,
pub position: frp::Source<Vector2<f32>>,
pub prev_position: frp::Stream<Vector2<f32>>,
pub translation: frp::Stream<Vector2<f32>>,
pub distance: frp::Stream<f32>,
pub ever_moved: frp::Stream<bool>,
pub button_mask: frp::Stream<ButtonMask>,
pub prev_button_mask: frp::Stream<ButtonMask>,
pub network: frp::Network,
pub up: frp::Source<Button>,
pub down: frp::Source<Button>,
pub wheel: frp::Source,
pub up_0: frp::Stream,
pub up_1: frp::Stream,
pub up_2: frp::Stream,
pub up_3: frp::Stream,
pub up_4: frp::Stream,
pub up_primary: frp::Stream,
pub up_middle: frp::Stream,
pub up_secondary: frp::Stream,
pub down_0: frp::Stream,
pub down_1: frp::Stream,
pub down_2: frp::Stream,
pub down_3: frp::Stream,
pub down_4: frp::Stream,
pub down_primary: frp::Stream,
pub down_middle: frp::Stream,
pub down_secondary: frp::Stream,
pub is_up_0: frp::Stream<bool>,
pub is_up_1: frp::Stream<bool>,
pub is_up_2: frp::Stream<bool>,
pub is_up_3: frp::Stream<bool>,
pub is_up_4: frp::Stream<bool>,
pub is_up_primary: frp::Stream<bool>,
pub is_up_middle: frp::Stream<bool>,
pub is_up_secondary: frp::Stream<bool>,
pub is_down_0: frp::Stream<bool>,
pub is_down_1: frp::Stream<bool>,
pub is_down_2: frp::Stream<bool>,
pub is_down_3: frp::Stream<bool>,
pub is_down_4: frp::Stream<bool>,
pub is_down_primary: frp::Stream<bool>,
pub is_down_middle: frp::Stream<bool>,
pub is_down_secondary: frp::Stream<bool>,
pub position: frp::Source<Vector2<f32>>,
pub position_top_left: frp::Source<Vector2<f32>>,
pub position_bottom_left: frp::Source<Vector2<f32>>,
pub prev_position: frp::Stream<Vector2<f32>>,
pub translation: frp::Stream<Vector2<f32>>,
pub distance: frp::Stream<f32>,
pub ever_moved: frp::Stream<bool>,
pub button_mask: frp::Stream<ButtonMask>,
pub prev_button_mask: frp::Stream<ButtonMask>,
}
impl Mouse {
@ -283,6 +285,8 @@ impl Default for Mouse {
down <- source();
wheel <- source();
position <- source();
position_top_left <- source();
position_bottom_left <- source();
prev_position <- position.previous();
translation <- position.map2(&prev_position,|t,s|t-s);
distance <- translation.map(|t:&Vector2<f32>|t.norm());
@ -381,6 +385,8 @@ impl Default for Mouse {
is_down_middle,
is_down_secondary,
position,
position_top_left,
position_bottom_left,
prev_position,
translation,
distance,

View File

@ -526,6 +526,7 @@ ops! { DocumentOps for Document
trait {
fn body_or_panic(&self) -> HtmlElement;
fn create_element_or_panic(&self, local_name: &str) -> Element;
fn create_html_element_or_panic(&self, local_name: &str) -> HtmlElement;
fn create_div_or_panic(&self) -> HtmlDivElement;
fn create_canvas_or_panic(&self) -> HtmlCanvasElement;
fn get_html_element_by_id(&self, id: &str) -> Option<HtmlElement>;
@ -541,6 +542,10 @@ ops! { DocumentOps for Document
self.create_element(local_name).unwrap()
}
fn create_html_element_or_panic(&self, local_name: &str) -> HtmlElement {
self.create_element(local_name).unwrap().unchecked_into()
}
fn create_div_or_panic(&self) -> HtmlDivElement {
self.create_element_or_panic("div").unchecked_into()
}