Optimize opening dropdown (#5688)

* Profiling

* Defer rendering hidden Text
This commit is contained in:
Kaz Wesley 2023-02-18 11:31:57 -08:00 committed by GitHub
parent afb853a03f
commit 2acc61d0b1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 89 additions and 71 deletions

2
Cargo.lock generated
View File

@ -2492,6 +2492,7 @@ dependencies = [
"enso-executor",
"enso-notification",
"enso-prelude",
"enso-profiler",
"enso-text",
"failure",
"flo_stream",
@ -2832,6 +2833,7 @@ dependencies = [
name = "ensogl-example-render-profile-flamegraph"
version = "0.1.0"
dependencies = [
"enso-debug-api",
"enso-frp",
"enso-profiler-data",
"enso-profiler-demo-data",

View File

@ -38,8 +38,6 @@ impl From<JsValue> for Error {
#[wasm_bindgen(module = "/pkg/scala-parser.js")]
extern "C" {
#[wasm_bindgen(catch)]
fn doc_parser_generate_html_source(content: String) -> std::result::Result<String, JsValue>;
#[wasm_bindgen(catch)]
fn doc_parser_generate_html_from_doc(content: String) -> std::result::Result<String, JsValue>;
}
@ -56,16 +54,8 @@ impl Client {
Ok(Client {})
}
/// Calls JS doc parser to generate HTML from documented Enso code.
pub fn generate_html_docs(&self, program: String) -> api::Result<String> {
let html_code = || {
let html_code = doc_parser_generate_html_source(program)?;
Result::Ok(html_code)
};
Ok(html_code()?)
}
/// Calls JS doc parser to generate HTML from pure doc code without Enso's AST.
#[profile(Detail)]
pub fn generate_html_doc_pure(&self, code: String) -> api::Result<String> {
let html_code = || {
let html_code = doc_parser_generate_html_from_doc(code)?;

View File

@ -82,12 +82,6 @@ impl DocParser {
DocParser::new().unwrap_or_else(|e| panic!("Failed to create doc parser: {e:?}"))
}
/// Parses program with documentation and generates HTML code.
/// If the program does not have any documentation will return empty string.
pub fn generate_html_docs(&self, program: String) -> api::Result<String> {
self.borrow_mut().generate_html_docs(program)
}
/// Parses pure documentation code and generates HTML code.
/// Will return empty string for empty entry.
pub fn generate_html_doc_pure(&self, code: String) -> api::Result<String> {

View File

@ -88,7 +88,6 @@ impl From<serde_json::error::Error> for Error {
#[allow(clippy::enum_variant_names)]
#[derive(Debug, serde::Serialize, serde::Deserialize)]
pub enum Request {
DocParserGenerateHtmlSource { program: String },
DocParserGenerateHtmlFromDoc { code: String },
}
@ -196,17 +195,8 @@ impl Client {
Ok(client)
}
/// Sends a request to parser service to generate HTML code from documented Enso code.
pub fn generate_html_docs(&mut self, program: String) -> api::Result<String> {
let request = Request::DocParserGenerateHtmlSource { program };
let response_doc = self.rpc_call_doc(request)?;
match response_doc {
ResponseDoc::SuccessDoc { code } => Ok(code),
ResponseDoc::Error { message } => Err(ParsingError(message)),
}
}
/// Sends a request to parser service to generate HTML code from pure documentation code.
#[profile(Detail)]
pub fn generate_html_doc_pure(&mut self, code: String) -> api::Result<String> {
let request = Request::DocParserGenerateHtmlFromDoc { code };
let response_doc = self.rpc_call_doc(request)?;

View File

@ -14,6 +14,7 @@ span-tree = { path = "../language/span-tree" }
ast = { path = "../language/ast/impl" }
parser = { path = "../language/parser" }
parser-scala = { path = "../language/parser-scala" }
enso-profiler = { path = "../../../lib/rust/profiler" }
enso-text = { path = "../../../lib/rust/text" }
double-representation = { path = "../controller/double-representation" }
engine-protocol = { path = "../controller/engine-protocol" }

View File

@ -43,6 +43,8 @@ use engine_protocol::language_server;
use engine_protocol::language_server::SuggestionId;
use enso_data_structures::hash_map_tree::HashMapTree;
use enso_notification as notification;
use enso_profiler as profiler;
use enso_profiler::prelude::*;
use flo_stream::Subscriber;
use language_server::types::SuggestionDatabaseUpdatesEvent;
use language_server::types::SuggestionsDatabaseVersion;
@ -412,6 +414,7 @@ impl SuggestionDatabase {
}
/// Apply the update event to the database.
#[profile(Detail)]
pub fn apply_update_event(&self, event: SuggestionDatabaseUpdatesEvent) {
for update in event.updates {
let mut entries = self.entries.borrow_mut();

View File

@ -103,6 +103,7 @@ pub struct View {
impl View {
/// Create a new node widget. The widget is initialized to empty state, waiting for widget
/// metadata to be provided using `set_node_data` and `set_metadata` FRP endpoints.
#[profile(Task)]
pub fn new(app: &Application) -> Self {
let frp = Frp::new();
let model = Rc::new(Model::new(app));
@ -160,6 +161,7 @@ impl Model {
Self { app, display_object, kind_model: kind }
}
#[profile(Task)]
fn set_widget_data(&self, frp: &SampledFrp, meta: &Option<Metadata>, node_data: &NodeData) {
trace!("Setting widget data: {:?} {:?}", meta, node_data);
@ -426,6 +428,7 @@ impl LazyDropdown {
}
}
#[profile(Detail)]
fn initialize_on_open(&mut self) {
match self {
LazyDropdown::Initialized(..) => {}

View File

@ -2130,6 +2130,7 @@ impl GraphEditorModel {
}
}
#[profile(Debug)]
fn set_node_expression_usage_type(
&self,
node_id: impl Into<NodeId>,

View File

@ -86,6 +86,9 @@ pub struct EntryData {
display_object: display::object::Instance,
label_thin: text::Text,
label_bold: text::Text,
selected: Cell<bool>,
/// A text change to the currently-hidden label that has not yet been applied.
deferred_label: RefCell<Option<ImString>>,
}
impl EntryData {
@ -101,16 +104,28 @@ impl EntryData {
label_thin.add_to_scene_layer(layer);
label_bold.add_to_scene_layer(layer);
}
Self { display_object, label_thin, label_bold }
let selected = default();
let deferred_label = default();
Self { display_object, label_thin, label_bold, selected, deferred_label }
}
fn update_selected(&self, selected: bool) {
if selected {
self.display_object.remove_child(&self.label_thin);
self.display_object.add_child(&self.label_bold);
} else {
self.display_object.remove_child(&self.label_bold);
self.display_object.add_child(&self.label_thin);
let was_selected = self.selected.replace(selected);
if selected != was_selected {
let new = self.selected_label();
if let Some(label) = self.deferred_label.take() {
new.set_content(label);
}
self.display_object.remove_all_children();
self.display_object.add_child(new);
}
}
/// Render the currently-enabled text control.
fn selected_label(&self) -> &text::Text {
match self.selected.get() {
true => &self.label_bold,
false => &self.label_thin,
}
}
@ -119,6 +134,11 @@ impl EntryData {
self.label_thin.set_xy(label_pos);
self.label_bold.set_xy(label_pos);
}
fn set_content(&self, text: &ImString) {
self.selected_label().set_content(text.clone_ref());
self.deferred_label.set(text.clone_ref());
}
}
@ -135,6 +155,7 @@ impl ensogl_grid_view::Entry for Entry {
type Model = EntryModel;
type Params = EntryParams;
#[profile(Debug)]
fn new(app: &Application, text_layer: Option<&Layer>) -> Self {
let data = Rc::new(EntryData::new(app, text_layer));
let frp = EntryFrp::<Self>::new();
@ -160,8 +181,6 @@ impl ensogl_grid_view::Entry for Entry {
);
layout <- all(contour, text_size, text_offset);
eval layout ((&(c, ts, to)) data.update_layout(c, ts, to));
selected <- input.set_model.map(|m| *m.selected);
eval selected ((&s) data.update_selected(s));
text_size <- text_size.ref_into_some();
data.label_thin.set_property_default <+ text_size;
@ -171,7 +190,10 @@ impl ensogl_grid_view::Entry for Entry {
data.label_thin.set_font <+ font;
data.label_bold.set_font <+ font;
desired_entry_width <- data.label_bold.width.map2(&text_offset, |w, offset| w + offset);
bold_width <- data.label_bold.width.map2(&text_offset, |w, offset| w + offset);
thin_width <- data.label_thin.width.map2(&text_offset, |w, offset| w + offset);
widths <- all(bold_width, thin_width);
desired_entry_width <- widths.map(|&(b, t)| b.max(t)).on_change();
limited_entry_width <- desired_entry_width.map2(&input.set_params, |width, params| {
// Using min/max to avoid a panic in clamp when min_width > max_width. In those
// cases, the max value is returned instead.
@ -184,9 +206,10 @@ impl ensogl_grid_view::Entry for Entry {
data.label_thin.set_view_width <+ view_width;
data.label_bold.set_view_width <+ view_width;
content <- input.set_model.map(|m| m.text.clone_ref());;
data.label_thin.set_content <+ content;
data.label_bold.set_content <+ content;
eval input.set_model ((m) {
data.update_selected(*m.selected);
data.set_content(&m.text);
});
out.contour <+ contour;
out.highlight_contour <+ contour;

View File

@ -142,6 +142,7 @@ ensogl_core::define_endpoints_2! { <T: (DropdownValue)>
}
impl<T: DropdownValue> Frp<T> {
#[profile(Debug)]
fn init(network: &frp::Network, api: &api::Private<T>, model: &Model<T>) {
let input = &api.input;
let output = &api.output;

View File

@ -76,6 +76,7 @@ impl<T> component::Model for Model<T> {
"Dropdown"
}
#[profile(Debug)]
fn new(app: &Application) -> Self {
let display_object = display::object::Instance::new();
@ -97,6 +98,7 @@ impl<T> component::Model for Model<T> {
impl<T: DropdownValue> Model<T> {
/// Set the minimum and maximum allowed inner width of an entry.
#[profile(Debug)]
pub fn set_outer_width_bounds(&self, min_outer_width: f32, max_outer_width: f32) {
let corners_radius = CORNER_RADIUS - CLIP_PADDING;
let max_width = max_outer_width - CLIP_PADDING * 2.0;
@ -107,6 +109,7 @@ impl<T: DropdownValue> Model<T> {
}
/// Set the dimensions of all ui elements of the dropdown.
#[profile(Debug)]
pub fn set_dimensions(
&self,
num_entries: usize,
@ -136,6 +139,7 @@ impl<T: DropdownValue> Model<T> {
self.grid.resize_grid(num_entries, 1);
}
#[profile(Debug)]
pub fn set_selection(&self, selected: &HashSet<T>, allow_multiselect: bool) {
let mut entries = self.selected_entries.borrow_mut();
entries.clear();
@ -148,6 +152,7 @@ impl<T: DropdownValue> Model<T> {
/// Convert provided list of indices onto sets of index ranges. One set of ranges is for indices
/// that are already in cache, and the other set is for indices that need to be requested.
#[profile(Debug)]
pub fn get_ready_and_request_ranges(
&self,
requested_indices: &[usize],
@ -180,6 +185,7 @@ impl<T: DropdownValue> Model<T> {
/// Accepts entry at given index, modifying selection. If entry is already selected, it will be
/// unselected, unless it is the last selected entry and `allow_empty` is false. For
/// single-select dropdowns, previously selected entry will be unselected.
#[profile(Debug)]
pub fn accept_entry_at_index(&self, index: usize, allow_multiselect: bool, allow_empty: bool) {
let cache = self.cache.borrow();
let Some(entry) = cache.get(index) else { return };
@ -201,6 +207,7 @@ impl<T: DropdownValue> Model<T> {
///
/// Note: The iterator borrows cache and selection. Make sure to drop it before calling any
/// methods that need to borrow them mutably.
#[profile(Debug)]
pub fn entry_models_for_range(
&self,
range: Range<usize>,
@ -216,6 +223,7 @@ impl<T: DropdownValue> Model<T> {
}
/// Update cache with new entries at given range. Returns range of indices that were updated.
#[profile(Debug)]
pub fn insert_entries_in_range(
&self,
updated_range: Range<usize>,
@ -236,6 +244,7 @@ impl<T: DropdownValue> Model<T> {
/// Prune selection according to changed multiselect mode. Returns true if the selection was
/// changed.
#[profile(Debug)]
pub fn set_multiselect(&self, multiselect: bool) -> bool {
let mut entries = self.selected_entries.borrow_mut();
if !multiselect && entries.len() > 1 {

View File

@ -316,6 +316,7 @@ impl<Entry: entry::Entry, EntryParams> Model<Entry, EntryParams> {
}
impl<E: Entry> Model<E, E::Params> {
#[profile(Debug)]
fn update_entry(
&self,
row: Row,

View File

@ -1020,6 +1020,7 @@ impl TextModel {
}
/// Recompute the shape of the provided line index.
#[profile(Debug)]
pub fn shape_line(&self, line: Line) -> ShapedLine {
let line_range = self.line_range_snapped(line);
let glyph_sets = self.shape_range(line_range.clone());
@ -1116,6 +1117,7 @@ impl TextModel {
/// and to the right of the insertion point. Unfortunately, the current Rustybuzz
/// implementation does not support such use cases:
/// https://github.com/RazrFalcon/rustybuzz/issues/54
#[profile(Debug)]
fn update_lines_after_change(&self, changes: Option<&[buffer::Change]>) {
debug_span!("update_lines_after_change").in_scope(|| {
self.detach_glyphs_from_cursors();
@ -1193,6 +1195,7 @@ impl TextModel {
}
/// Replace selections with new ones.
#[profile(Debug)]
fn replace_selections(&self, do_edit: bool, buffer_selections: &buffer::selection::Group) {
let mut new_selection_map = SelectionMap::default();
for buffer_selection in buffer_selections {
@ -1278,6 +1281,7 @@ impl TextModel {
}
/// Redraw the given line ranges.
#[profile(Debug)]
fn redraw_sorted_line_ranges(
&self,
sorted_line_ranges: impl Iterator<Item = RangeInclusive<ViewLine>>,
@ -1293,6 +1297,7 @@ impl TextModel {
}
/// Redraw the line. This will re-position all line glyphs.
#[profile(Debug)]
fn redraw_line(&self, view_line: ViewLine) {
let line = &mut self.lines.borrow_mut()[view_line];
let default_divs = || NonEmptyVec::singleton(0.0);
@ -1439,6 +1444,7 @@ impl TextModel {
}
/// Attach glyphs to cursors if cursors are in edit mode.
#[profile(Debug)]
pub fn attach_glyphs_to_cursors(&self) {
for line in ViewLine(0)..=self.buffer.last_view_line_index() {
self.attach_glyphs_to_cursors_for_line(line)
@ -1486,6 +1492,7 @@ impl TextModel {
}
/// Detach all glyphs from cursors and place them back in lines.
#[profile(Debug)]
pub fn detach_glyphs_from_cursors(&self) {
let selection_map = self.selection_map.borrow();
for (&line, cursor_map) in &selection_map.location_map {
@ -1754,6 +1761,7 @@ impl TextModel {
}
/// Position all lines in the provided line range. The range has to be sorted.
#[profile(Debug)]
fn position_sorted_line_ranges(
&self,
sorted_line_ranges: impl Iterator<Item = RangeInclusive<ViewLine>>,

View File

@ -386,6 +386,7 @@ impl Family for NonVariableFamily {
impl Family for VariableFamily {
type Variations = VariationAxes;
#[profile(Debug)]
fn update_msdfgen_variations(&self, variations: &Self::Variations) {
if let Some(face) = self.face.borrow().as_ref() {
if self.last_axes.borrow().as_ref() != Some(variations) {

View File

@ -569,6 +569,7 @@ impl System {
/// Create new glyph. In the returned glyph the further parameters (position,size,character)
/// may be set.
#[profile(Debug)]
pub fn new_glyph(&self) -> Glyph {
let frp = Frp::new();
#[allow(clippy::clone_on_copy)]

View File

@ -75,6 +75,7 @@ impl GlyphRenderInfo {
/// Load new [`GlyphRenderInfo`] from msdf_sys font handle. This also extends the atlas with
/// MSDF generated for this character.
#[profile(Debug)]
pub fn load(handle: &msdf_sys::OwnedFace, glyph_id: GlyphId, atlas: &msdf::Texture) -> Self {
let params = Self::MSDF_PARAMS;
let msdf = Msdf::generate_by_index(handle, glyph_id.0 as usize, &params);

View File

@ -57,6 +57,7 @@ impl<T: mix::Mixable + frp::Data> Animation<T>
where mix::Repr<T>: inertia::Value
{
/// Constructor. The initial value of the animation is set to `default`.
#[profile(Debug)]
pub fn new(network: &frp::Network) -> Self {
frp::extend! { network
value_src <- any_mut::<T>();

View File

@ -75,6 +75,10 @@ macro_rules! define_bindings {
let shape = dom.shape.clone_ref();
let dispatcher = dispatchers.$name.clone_ref();
let closure : MouseEventJsClosure = Closure::new(move |event:JsValue| {
let _profiler = profiler::start_task!(
profiler::APP_LIFETIME,
concat!("mouse_", stringify!($name))
);
let shape = shape.value();
let event = event.unchecked_into::<web::$js_event>();
dispatcher.run_all(&event::$target::new(event,shape))

View File

@ -1707,6 +1707,7 @@ impl Model {
/// Recompute the transformation matrix of the display object tree starting with this object and
/// traversing all of its dirty children.
#[profile(Detail)]
pub fn update(&self, scene: &Scene) {
self.refresh_layout();
let origin0 = Matrix4::identity();

View File

@ -8,6 +8,7 @@ edition = "2021"
crate-type = ["cdylib", "rlib"]
[dependencies]
enso-debug-api = { path = "../../../debug-api" }
enso-frp = { path = "../../../frp" }
enso-profiler-data = { path = "../../../profiler/data" }
enso-profiler-demo-data = { path = "../../../profiler/demo-data" }

View File

@ -39,7 +39,7 @@ use ensogl_flame_graph as flame_graph;
#[wasm_bindgen]
#[allow(dead_code)]
pub async fn entry_point_render_profile_flamegraph() {
let data = get_data().await;
let data = get_data().await.unwrap();
let profile: profiler_data::Profile<profiler_data::OpaqueMetadata> = data.parse().unwrap();
ensogl_text_msdf::initialized().await;
use ensogl_core::display::object::ObjectOps;
@ -76,32 +76,13 @@ pub async fn entry_point_render_profile_flamegraph() {
// implement some way of invoking this scene with paths to multiple profile files, and then
// we'd replace this. For now, this is a copy of the file-loading code from the `render-profile`
// scene.
/// Read profile data from a file. The file must be located at `/profile.json` in the root of the
/// directory that will be made available by the webserver, i.e. `enso/dist/content`.
async fn get_data() -> String {
use wasm_bindgen::JsCast;
let url = "assets/profile.json";
let mut opts = web_sys::RequestInit::new();
opts.method("GET");
opts.mode(web_sys::RequestMode::Cors);
let request = web_sys::Request::new_with_str_and_init(url, &opts).unwrap();
request.headers().set("Accept", "application/json").unwrap();
let window = web_sys::window().unwrap();
let response = window.fetch_with_request(&request);
let response = wasm_bindgen_futures::JsFuture::from(response).await.unwrap();
assert!(response.is_instance_of::<web_sys::Response>());
let response: web_sys::Response = response.dyn_into().unwrap();
if !response.ok() {
error!(
"Error retrieving profile file from {url}: {}. Falling back to demo data.",
response.status_text()
);
return enso_profiler_demo_data::create_data().await;
/// Read profile data from a file specified on the command line.
async fn get_data() -> Option<String> {
let files = enso_debug_api::load_profiles()?.await;
if files.len() > 1 {
error!("Entry point profiling-run-graph doesn't support multiple profile file arguments.");
}
let data = response.text().unwrap();
let data = wasm_bindgen_futures::JsFuture::from(data).await.unwrap();
data.as_string().unwrap()
files.into_iter().next()
}
fn init_theme(scene: &display::Scene) {

View File

@ -80,6 +80,7 @@ pub fn set_spawner(spawner_to_set: impl LocalSpawn + 'static) {
pub fn spawn(f: impl Future<Output = ()> + 'static) {
SPAWNER.with(|s| s.spawn(f));
}
/// Process stream elements while object under `weak` handle exists.
///
/// Like [`utils::channel::process_stream_with_handle`] but automatically spawns the processor.

View File

@ -112,10 +112,10 @@ macro_rules! _new_dynamic_network {
#[macro_export]
macro_rules! new_bridge_network {
([$($($path:ident).*),*] $label:ident $($ts:tt)*) => {
let _birdge_network_ = $crate::Network::new(stringify!($label));
$crate::extend! { _birdge_network_ $($ts)* }
let _birdge_network_ = $crate::BridgeNetwork::from(_birdge_network_);
$($($path).*.register_bridge_network(&_birdge_network_);)*
let _bridge_network_ = $crate::Network::new(stringify!($label));
$crate::extend! { _bridge_network_ $($ts)* }
let _bridge_network_ = $crate::BridgeNetwork::from(_bridge_network_);
$($($path).*.register_bridge_network(&_bridge_network_);)*
};
}