Skip to content

Commit 13ba575

Browse files
committed
Fixes a major performance regression from the introduction of Kitty image
support. The scroll() method was computing very expensive operations on each line scrolled, now instead, we keep track of which lines have images, and only trigger the expensive call if we have to. So the manual poking to attach images needs to be gated so that we can keep our accounting correct.
1 parent 4630b7e commit 13ba575

7 files changed

Lines changed: 337 additions & 18 deletions

File tree

Sources/SwiftTerm/Apple/AppleTerminalView.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1693,7 +1693,7 @@ extension TerminalView {
16931693
attachedImage.kittyPixelOffsetY = context.pixelOffsetY
16941694
}
16951695

1696-
buffer.lines [buffer.y+buffer.yBase].attach(image: attachedImage)
1696+
buffer.attachImage(attachedImage, toLineAt: buffer.y+buffer.yBase)
16971697

16981698
terminal.updateRange (buffer.y)
16991699

Sources/SwiftTerm/Buffer.swift

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ public final class Buffer {
2020
private var _lines: CircularBufferLineList
2121
var xDisp, _yDisp, xBase: Int
2222
private var _x, _y, _yBase: Int
23+
private var _linesWithImagesCount: Int = 0
2324

2425
// this keeps incrementing even as we run out of space in _lines and trim out
2526
// old lines.
@@ -200,6 +201,54 @@ public final class Buffer {
200201
var lines : CircularBufferLineList {
201202
get { return _lines }
202203
}
204+
205+
/// Returns true if any lines in this buffer have images attached
206+
public var hasAnyImages: Bool {
207+
return _linesWithImagesCount > 0
208+
}
209+
210+
/// Attaches an image to the line at the given index, tracking the count of lines with images
211+
func attachImage(_ image: TerminalImage, toLineAt index: Int) {
212+
let line = lines[index]
213+
let hadImages = line.images != nil
214+
line.attach(image: image)
215+
if !hadImages {
216+
_linesWithImagesCount += 1
217+
}
218+
}
219+
220+
/// Clears images from the line at the given index, tracking the count of lines with images
221+
func clearImagesFromLine(at index: Int) {
222+
let line = lines[index]
223+
if line.images != nil {
224+
_linesWithImagesCount -= 1
225+
line.images = nil
226+
}
227+
}
228+
229+
/// Recalculates the count of lines with images (used after reflow operations)
230+
func recalculateLinesWithImagesCount() {
231+
var count = 0
232+
for i in 0..<lines.count {
233+
if lines[i].images != nil {
234+
count += 1
235+
}
236+
}
237+
_linesWithImagesCount = count
238+
}
239+
240+
private func setupLinesCallbacks() {
241+
_lines.onLineRecycled = { [weak self] hadImages in
242+
if hadImages {
243+
self?._linesWithImagesCount -= 1
244+
}
245+
}
246+
_lines.onLinePushed = { [weak self] hasImages in
247+
if hasImages {
248+
self?._linesWithImagesCount += 1
249+
}
250+
}
251+
}
203252

204253
private var curAttr: Attribute = Attribute.empty
205254
private var insertMode: Bool = false
@@ -242,6 +291,7 @@ public final class Buffer {
242291
let len = hasScrollback ? (scrollback ?? 0) + rows : rows
243292
_lines = CircularBufferLineList (maxLength: len)
244293
_lines.makeEmpty = { [unowned self] line in getBlankLine(attribute: CharData.defaultAttr, isWrapped: false) }
294+
setupLinesCallbacks()
245295
setupTabStops (tabStopWidth: tabStopWidth)
246296
}
247297

@@ -308,6 +358,8 @@ public final class Buffer {
308358

309359
_lines = CircularBufferLineList (maxLength: getCorrectBufferLength(rows))
310360
_lines.makeEmpty = { [unowned self] line in getBlankLine(attribute: CharData.defaultAttr, isWrapped: false) }
361+
setupLinesCallbacks()
362+
_linesWithImagesCount = 0
311363
scrollTop = 0
312364
scrollBottom = rows - 1
313365

@@ -1036,6 +1088,7 @@ public final class Buffer {
10361088
} else {
10371089
reflowNarrower (cols, rows, newCols, newRows)
10381090
}
1091+
recalculateLinesWithImagesCount()
10391092
}
10401093

10411094
static var n = 0

Sources/SwiftTerm/BufferLine.swift

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -287,8 +287,9 @@ public final class BufferLine: CustomDebugStringConvertible {
287287
return result
288288
}
289289

290-
/// Attaches the specified terminal image to this buffer line
291-
public func attach (image: TerminalImage) {
290+
/// Attaches the specified terminal image to this buffer line.
291+
/// This method is internal - use Buffer.attachImage() to attach images with proper tracking.
292+
func attach (image: TerminalImage) {
292293
if var imageArray = self.images {
293294
imageArray.append (image)
294295
images = imageArray

Sources/SwiftTerm/CircularList.swift

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,12 @@ internal class CircularBufferLineList {
266266
/// does not exist, or the index requested otherwise
267267
//
268268
var makeEmpty: ((_ idx: Int) -> BufferLine)? = nil
269+
270+
/// Called when a line is about to be recycled, with true if the line had images
271+
var onLineRecycled: ((_ hadImages: Bool) -> Void)? = nil
272+
273+
/// Called when a line is pushed, with true if the line has images
274+
var onLinePushed: ((_ hasImages: Bool) -> Void)? = nil
269275

270276
public init (maxLength: Int)
271277
{
@@ -314,6 +320,7 @@ internal class CircularBufferLineList {
314320
} else {
315321
count = count + 1
316322
}
323+
onLinePushed?(value.images != nil)
317324
}
318325

319326
func recycle ()
@@ -325,7 +332,9 @@ internal class CircularBufferLineList {
325332
let index = getCyclicIndex(count)
326333
startIndex += 1
327334
startIndex = startIndex % maxLength
335+
let hadImages = array[index]?.images != nil
328336
array[index]?.clear(with: CharData.defaultAttr)
337+
onLineRecycled?(hadImages)
329338
//array [index] = makeEmpty! (-1)
330339
}
331340

Sources/SwiftTerm/KittyGraphics.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1322,7 +1322,7 @@ extension Terminal {
13221322
image.col = newLeftCol
13231323
image.kittyCol = newLeftCol
13241324
image.kittyRow = newTopRow
1325-
buffer.lines[move.targetRow].attach(image: image)
1325+
buffer.attachImage(image, toLineAt: move.targetRow)
13261326
}
13271327
}
13281328

@@ -1462,10 +1462,10 @@ extension Terminal {
14621462

14631463
func clearAllKittyImages() {
14641464
for idx in 0..<buffer.lines.count {
1465-
buffer.lines[idx].images = nil
1465+
buffer.clearImagesFromLine(at: idx)
14661466
}
14671467
for idx in 0..<altBuffer.lines.count {
1468-
altBuffer.lines[idx].images = nil
1468+
altBuffer.clearImagesFromLine(at: idx)
14691469
}
14701470
kittyGraphicsState.imagesById.removeAll()
14711471
kittyGraphicsState.imageNumbers.removeAll()

0 commit comments

Comments
 (0)