Browse Source

LibVT: Improve scrolling performance

Previously, we would remove lines from the buffer, create new lines and
insert them into the buffer when we scrolled. Since scrolling does not
always happen at the last line, this meant `Line` objects were
pointlessly moved forwards, and then immediately backwards.

We now swap them in-place and clear those lines that are "inserted". As
a result, performance is better and scrolling is smoother in `vim` and
`nano`.
Daniel Bertalan 4 years ago
parent
commit
221ba1aac8
1 changed files with 26 additions and 19 deletions
  1. 26 19
      Userland/Libraries/LibVT/Terminal.cpp

+ 26 - 19
Userland/Libraries/LibVT/Terminal.cpp

@@ -775,20 +775,28 @@ void Terminal::scroll_up(u16 region_top, u16 region_bottom, size_t count)
     // NOTE: We have to invalidate the cursor first.
     // NOTE: We have to invalidate the cursor first.
     invalidate_cursor();
     invalidate_cursor();
 
 
-    if (!m_use_alternate_screen_buffer && max_history_size() != 0)
+    bool should_move_to_scrollback = !m_use_alternate_screen_buffer && max_history_size() != 0;
+    if (should_move_to_scrollback) {
         for (size_t i = 0; i < count; ++i)
         for (size_t i = 0; i < count; ++i)
             add_line_to_history(move(active_buffer().ptr_at(region_top + i)));
             add_line_to_history(move(active_buffer().ptr_at(region_top + i)));
+    }
 
 
-    if (count == region_size) {
-        for (u16 row = region_top; row <= region_bottom; ++row)
+    // Move lines into their new place.
+    for (u16 row = region_top; row + count <= region_bottom; ++row)
+        swap(active_buffer().ptr_at(row), active_buffer().ptr_at(row + count));
+    // Clear 'new' lines at the bottom.
+    if (should_move_to_scrollback) {
+        // Since we moved the previous lines into history, we can't just clear them.
+        for (u16 row = region_bottom + 1 - count; row <= region_bottom; ++row)
+            active_buffer().ptr_at(row) = make<Line>(columns());
+    } else {
+        // The new lines haven't been moved and we don't want to leak memory.
+        for (u16 row = region_bottom + 1 - count; row <= region_bottom; ++row)
             active_buffer()[row].clear(Attribute());
             active_buffer()[row].clear(Attribute());
-        return;
     }
     }
-
-    active_buffer().remove(region_top, count);
-    for (size_t i = 0; i < count; ++i)
-        active_buffer().insert(region_bottom - count + i + 1, make<Line>(m_columns));
-    for (u16 row = region_top; row <= region_bottom; ++row)
+    // Set dirty flag on swapped lines.
+    // The other lines have implicitly been set dirty by being cleared.
+    for (u16 row = region_top; row <= region_bottom - count; ++row)
         active_buffer()[row].set_dirty(true);
         active_buffer()[row].set_dirty(true);
     if (!m_use_alternate_screen_buffer && max_history_size() != 0)
     if (!m_use_alternate_screen_buffer && max_history_size() != 0)
         m_client.terminal_history_changed();
         m_client.terminal_history_changed();
@@ -806,16 +814,15 @@ void Terminal::scroll_down(u16 region_top, u16 region_bottom, size_t count)
     // NOTE: We have to invalidate the cursor first.
     // NOTE: We have to invalidate the cursor first.
     invalidate_cursor();
     invalidate_cursor();
 
 
-    if (count == region_size) {
-        for (u16 row = region_top; row <= region_bottom; ++row)
-            active_buffer()[row].set_dirty(true);
-        return;
-    }
-
-    active_buffer().remove(region_bottom - count + 1, count);
-    for (size_t i = 0; i < count; ++i)
-        active_buffer().insert(region_top, make<Line>(m_columns));
-    for (u16 row = region_top; row <= region_bottom; ++row)
+    // Move lines into their new place.
+    for (int row = region_bottom; row >= static_cast<int>(region_top + count); --row)
+        swap(active_buffer().ptr_at(row), active_buffer().ptr_at(row - count));
+    // Clear the 'new' lines at the top.
+    for (u16 row = region_top; row < region_top + count; ++row)
+        active_buffer()[row].clear(Attribute());
+    // Set dirty flag on swapped lines.
+    // The other lines have implicitly been set dirty by being cleared.
+    for (u16 row = region_top + count; row <= region_bottom; ++row)
         active_buffer()[row].set_dirty(true);
         active_buffer()[row].set_dirty(true);
 }
 }