Reuse line layouts when reusing view

Antonio Scandurra created

Change summary

crates/gpui/src/text_system.rs             | 16 +++++-
crates/gpui/src/text_system/line_layout.rs | 51 +++++++++++++++++++++--
crates/gpui/src/window.rs                  | 36 +++++++++-------
3 files changed, 78 insertions(+), 25 deletions(-)

Detailed changes

crates/gpui/src/text_system.rs 🔗

@@ -9,11 +9,11 @@ pub use line_layout::*;
 pub use line_wrapper::*;
 
 use crate::{
-    px, Bounds, DevicePixels, Hsla, Pixels, PlatformTextSystem, Point, Result, SharedString, Size,
-    UnderlineStyle,
+    px, Bounds, DevicePixels, EntityId, Hsla, Pixels, PlatformTextSystem, Point, Result,
+    SharedString, Size, UnderlineStyle,
 };
 use anyhow::anyhow;
-use collections::FxHashMap;
+use collections::{FxHashMap, FxHashSet};
 use core::fmt;
 use itertools::Itertools;
 use parking_lot::{Mutex, RwLock, RwLockUpgradableReadGuard};
@@ -186,6 +186,10 @@ impl TextSystem {
         }
     }
 
+    pub fn with_view<R>(&self, view_id: EntityId, f: impl FnOnce() -> R) -> R {
+        self.line_layout_cache.with_view(view_id, f)
+    }
+
     pub fn layout_line(
         &self,
         text: &str,
@@ -361,7 +365,11 @@ impl TextSystem {
     }
 
     pub fn start_frame(&self) {
-        self.line_layout_cache.start_frame()
+        self.line_layout_cache.start_frame();
+    }
+
+    pub fn end_frame(&self, reused_views: &FxHashSet<EntityId>) {
+        self.line_layout_cache.end_frame(reused_views)
     }
 
     pub fn line_wrapper(

crates/gpui/src/text_system/line_layout.rs 🔗

@@ -1,5 +1,5 @@
-use crate::{px, FontId, GlyphId, Pixels, PlatformTextSystem, Point, Size};
-use collections::FxHashMap;
+use crate::{px, EntityId, FontId, GlyphId, Pixels, PlatformTextSystem, Point, Size};
+use collections::{FxHashMap, FxHashSet};
 use parking_lot::{Mutex, RwLock, RwLockUpgradableReadGuard};
 use smallvec::SmallVec;
 use std::{
@@ -236,6 +236,7 @@ impl WrappedLineLayout {
 }
 
 pub(crate) struct LineLayoutCache {
+    view_stack: Mutex<Vec<EntityId>>,
     previous_frame: Mutex<FxHashMap<CacheKey, Arc<LineLayout>>>,
     current_frame: RwLock<FxHashMap<CacheKey, Arc<LineLayout>>>,
     previous_frame_wrapped: Mutex<FxHashMap<CacheKey, Arc<WrappedLineLayout>>>,
@@ -246,6 +247,7 @@ pub(crate) struct LineLayoutCache {
 impl LineLayoutCache {
     pub fn new(platform_text_system: Arc<dyn PlatformTextSystem>) -> Self {
         Self {
+            view_stack: Mutex::default(),
             previous_frame: Mutex::default(),
             current_frame: RwLock::default(),
             previous_frame_wrapped: Mutex::default(),
@@ -254,11 +256,43 @@ impl LineLayoutCache {
         }
     }
 
-    pub fn start_frame(&self) {
+    pub fn end_frame(&self, reused_views: &FxHashSet<EntityId>) {
+        debug_assert_eq!(self.view_stack.lock().len(), 0);
+
         let mut prev_frame = self.previous_frame.lock();
         let mut curr_frame = self.current_frame.write();
+        for (key, layout) in prev_frame.drain() {
+            if key
+                .parent_view_id
+                .map_or(false, |view_id| reused_views.contains(&view_id))
+            {
+                curr_frame.insert(key, layout);
+            }
+        }
         std::mem::swap(&mut *prev_frame, &mut *curr_frame);
-        curr_frame.clear();
+
+        let mut prev_frame_wrapped = self.previous_frame_wrapped.lock();
+        let mut curr_frame_wrapped = self.current_frame_wrapped.write();
+        for (key, layout) in prev_frame_wrapped.drain() {
+            if key
+                .parent_view_id
+                .map_or(false, |view_id| reused_views.contains(&view_id))
+            {
+                curr_frame_wrapped.insert(key, layout);
+            }
+        }
+        std::mem::swap(&mut *prev_frame_wrapped, &mut *curr_frame_wrapped);
+    }
+
+    pub fn with_view<R>(&self, view_id: EntityId, f: impl FnOnce() -> R) -> R {
+        self.view_stack.lock().push(view_id);
+        let result = f();
+        self.view_stack.lock().pop();
+        result
+    }
+
+    fn parent_view_id(&self) -> Option<EntityId> {
+        self.view_stack.lock().last().copied()
     }
 
     pub fn layout_wrapped_line(
@@ -273,6 +307,7 @@ impl LineLayoutCache {
             font_size,
             runs,
             wrap_width,
+            parent_view_id: self.parent_view_id(),
         } as &dyn AsCacheKeyRef;
 
         let current_frame = self.current_frame_wrapped.upgradable_read();
@@ -301,6 +336,7 @@ impl LineLayoutCache {
                 font_size,
                 runs: SmallVec::from(runs),
                 wrap_width,
+                parent_view_id: self.parent_view_id(),
             };
             current_frame.insert(key, layout.clone());
             layout
@@ -313,6 +349,7 @@ impl LineLayoutCache {
             font_size,
             runs,
             wrap_width: None,
+            parent_view_id: self.parent_view_id(),
         } as &dyn AsCacheKeyRef;
 
         let current_frame = self.current_frame.upgradable_read();
@@ -331,6 +368,7 @@ impl LineLayoutCache {
                 font_size,
                 runs: SmallVec::from(runs),
                 wrap_width: None,
+                parent_view_id: self.parent_view_id(),
             };
             current_frame.insert(key, layout.clone());
             layout
@@ -348,12 +386,13 @@ trait AsCacheKeyRef {
     fn as_cache_key_ref(&self) -> CacheKeyRef;
 }
 
-#[derive(Eq)]
+#[derive(Debug, Eq)]
 struct CacheKey {
     text: String,
     font_size: Pixels,
     runs: SmallVec<[FontRun; 1]>,
     wrap_width: Option<Pixels>,
+    parent_view_id: Option<EntityId>,
 }
 
 #[derive(Copy, Clone, PartialEq, Eq, Hash)]
@@ -362,6 +401,7 @@ struct CacheKeyRef<'a> {
     font_size: Pixels,
     runs: &'a [FontRun],
     wrap_width: Option<Pixels>,
+    parent_view_id: Option<EntityId>,
 }
 
 impl<'a> PartialEq for (dyn AsCacheKeyRef + 'a) {
@@ -385,6 +425,7 @@ impl AsCacheKeyRef for CacheKey {
             font_size: self.font_size,
             runs: self.runs.as_slice(),
             wrap_width: self.wrap_width,
+            parent_view_id: self.parent_view_id,
         }
     }
 }

crates/gpui/src/window.rs 🔗

@@ -1461,14 +1461,11 @@ impl<'a> WindowContext<'a> {
             self.window.focus_invalidated = false;
         }
 
-        self.text_system().start_frame();
         if let Some(requested_handler) = self.window.rendered_frame.requested_input_handler.as_mut()
         {
             requested_handler.handler = self.window.platform_window.take_input_handler();
         }
 
-        self.window.layout_engine.as_mut().unwrap().clear();
-        self.window.next_frame.clear();
         let root_view = self.window.root_view.take().unwrap();
 
         self.with_z_index(0, |cx| {
@@ -1528,11 +1525,6 @@ impl<'a> WindowContext<'a> {
             self.platform.set_cursor_style(cursor_style);
         }
 
-        self.window
-            .next_frame
-            .reuse_views(&mut self.window.rendered_frame);
-        self.window.next_frame.scene.finish();
-
         // Register requested input handler with the platform window.
         if let Some(requested_input) = self.window.next_frame.requested_input_handler.as_mut() {
             if let Some(handler) = requested_input.handler.take() {
@@ -1540,16 +1532,25 @@ impl<'a> WindowContext<'a> {
             }
         }
 
+        self.window
+            .next_frame
+            .reuse_views(&mut self.window.rendered_frame);
+        self.window.next_frame.scene.finish();
+        self.window.layout_engine.as_mut().unwrap().clear();
+        self.text_system()
+            .end_frame(&self.window.next_frame.reused_views);
+        ELEMENT_ARENA.with_borrow_mut(|element_arena| element_arena.clear());
+
+        self.window.refreshing = false;
+        self.window.drawing = false;
+
         let previous_focus_path = self.window.rendered_frame.focus_path();
         let previous_window_active = self.window.rendered_frame.window_active;
         mem::swap(&mut self.window.rendered_frame, &mut self.window.next_frame);
+        self.window.next_frame.clear();
         let current_focus_path = self.window.rendered_frame.focus_path();
         let current_window_active = self.window.rendered_frame.window_active;
 
-        self.window.refreshing = false;
-        self.window.drawing = false;
-        ELEMENT_ARENA.with_borrow_mut(|element_arena| element_arena.clear());
-
         if previous_focus_path != current_focus_path
             || previous_window_active != current_window_active
         {
@@ -2005,10 +2006,13 @@ impl<'a> WindowContext<'a> {
         view_id: EntityId,
         f: impl FnOnce(&mut Self) -> R,
     ) -> R {
-        self.window.next_frame.view_stack.push(view_id);
-        let result = f(self);
-        self.window.next_frame.view_stack.pop();
-        result
+        let text_system = self.text_system().clone();
+        text_system.with_view(view_id, || {
+            self.window.next_frame.view_stack.push(view_id);
+            let result = f(self);
+            self.window.next_frame.view_stack.pop();
+            result
+        })
     }
 
     pub(crate) fn paint_view<R>(&mut self, view_id: EntityId, f: impl FnOnce(&mut Self) -> R) -> R {