perf(console): wrap only new console lines via a per-line cache

Each batch of new output re-wrapped all N visible lines glyph by glyph,
allocating a fresh segment vector per line; a line evicted at the
10,000-line cap shifted every index, so nothing could be kept. And on
every frame, idle or not, seeding the scanline shading summed the wrap
rows of every line above the view.

- The model numbers its lines and the view keeps one wrap per line,
  aligned by that number, so an eviction drops a prefix instead of
  forcing a rebuild. New output wraps only the new lines; the layout is
  then a prefix sum over the N visible lines, with no measuring and no
  per-line allocation.
- The wraps are kept while the wrap width, line height, font size, font
  (by ImFont::FontId) and rasterizer density are exactly unchanged. A
  line-spacing change only re-sums heights; filters and fold toggles
  reuse the cached wraps. A resize drag still re-wraps every visible
  line per frame, as before, but reuses their storage.
- The scanline seed is read from the prefix sum: O(1) per frame.

Heights, offsets and segments are bit-identical to the old rebuild. A
font reload at an unchanged size (the colour-emoji toggle) now re-wraps
the console at once; before, it waited for the next line of output.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-10 09:50:03 -05:00
co-authored by Claude Opus 5.5
parent c2c2a8325b
commit ee0a486676
7 changed files with 541 additions and 61 deletions
+6
View File
@@ -81,12 +81,18 @@ public:
// fold toggle). The view memoizes its per-frame filter + text-layout passes against this.
std::uint64_t revision() const { return revision_; }
// Absolute sequence number of lines_.front(): +1 per line evicted from the front by the cap, +size() on
// clear(). Line text never changes after ingest, so a view cache keyed by sequence number realigns with
// one subtraction (eviction becomes an offset, not a rebuild). Never decreases.
std::uint64_t firstSeq() const { return first_seq_; }
private:
const std::size_t max_lines_;
std::deque<ConsoleModelLine> lines_; // visible model — main thread only
std::vector<ConsoleModelLine> pending_; // guarded by ingest_mutex_
std::mutex ingest_mutex_;
std::uint64_t revision_ = 0;
std::uint64_t first_seq_ = 0; // see firstSeq()
};
} // namespace ui