perf(editor): extend textkit profiling probes

This commit is contained in:
Feror 2026-05-30 18:47:22 +02:00
parent c02bac3c2d
commit 26ed4956e1

View file

@ -391,6 +391,31 @@ public enum EditorBenchmarkProfiler {
notes: "\(attributedStringResult.value.styledLineCount) styled lines" notes: "\(attributedStringResult.value.styledLineCount) styled lines"
)) ))
let coldViewportStack = makeTextKitStack(attributedString: textStorage)
let coldViewportResult = measure {
coldViewportStack.layoutManager.glyphRange(
forBoundingRect: NSRect(x: 0, y: 50_000, width: 760, height: 900),
in: coldViewportStack.textContainer
)
}
measurements.append(EditorBenchmarkMeasurement(
name: "cold_viewport_glyph_range_middle",
category: .layout,
durationMilliseconds: coldViewportResult.durationMilliseconds,
notes: "Visible glyph range before explicit full-document layout"
))
let coldLineFragmentStack = makeTextKitStack(attributedString: textStorage)
let coldLineFragmentResult = measure {
lineFragmentRect(atCharacterLocation: coldLineFragmentStack.textStorage.length / 2, in: coldLineFragmentStack)
}
measurements.append(EditorBenchmarkMeasurement(
name: "cold_line_fragment_calculation_midpoint",
category: .layout,
durationMilliseconds: coldLineFragmentResult.durationMilliseconds,
notes: "lineFragmentRect near midpoint before explicit full-document layout"
))
let layoutInvalidationResult = measure { let layoutInvalidationResult = measure {
var actualRange = NSRange(location: 0, length: 0) var actualRange = NSRange(location: 0, length: 0)
layoutManager.invalidateLayout( layoutManager.invalidateLayout(
@ -425,6 +450,30 @@ public enum EditorBenchmarkProfiler {
notes: "NSLayoutManager.ensureLayout for text container" notes: "NSLayoutManager.ensureLayout for text container"
)) ))
let cachedLineFragmentResult = measure {
lineFragmentRect(atCharacterLocation: textStorage.length / 2, in: (
textStorage: textStorage,
layoutManager: layoutManager,
textContainer: textContainer
))
}
measurements.append(EditorBenchmarkMeasurement(
name: "cached_line_fragment_calculation_midpoint",
category: .layout,
durationMilliseconds: cachedLineFragmentResult.durationMilliseconds,
notes: "lineFragmentRect near midpoint after full layout"
))
let textContainerUsedRectResult = measure {
layoutManager.usedRect(for: textContainer)
}
measurements.append(EditorBenchmarkMeasurement(
name: "text_container_used_rect_after_full_layout",
category: .layout,
durationMilliseconds: textContainerUsedRectResult.durationMilliseconds,
notes: "NSTextContainer usedRect after full layout"
))
let firstViewportResult = measure { let firstViewportResult = measure {
layoutManager.glyphRange( layoutManager.glyphRange(
forBoundingRect: NSRect(x: 0, y: 0, width: 760, height: 900), forBoundingRect: NSRect(x: 0, y: 0, width: 760, height: 900),
@ -544,5 +593,37 @@ public enum EditorBenchmarkProfiler {
_ = changedSource _ = changedSource
return measurements return measurements
} }
private static func makeTextKitStack(attributedString: NSAttributedString) -> (
textStorage: NSTextStorage,
layoutManager: NSLayoutManager,
textContainer: NSTextContainer
) {
let textStorage = NSTextStorage(attributedString: attributedString)
let layoutManager = NSLayoutManager()
let textContainer = NSTextContainer(size: NSSize(width: 760, height: CGFloat.greatestFiniteMagnitude))
textContainer.widthTracksTextView = false
textContainer.heightTracksTextView = false
layoutManager.addTextContainer(textContainer)
textStorage.addLayoutManager(layoutManager)
return (textStorage, layoutManager, textContainer)
}
@discardableResult
private static func lineFragmentRect(
atCharacterLocation characterLocation: Int,
in stack: (
textStorage: NSTextStorage,
layoutManager: NSLayoutManager,
textContainer: NSTextContainer
)
) -> NSRect {
guard stack.textStorage.length > 0 else { return .zero }
let characterLocation = max(0, min(characterLocation, stack.textStorage.length - 1))
let glyphIndex = stack.layoutManager.glyphIndexForCharacter(at: characterLocation)
var effectiveRange = NSRange(location: 0, length: 0)
return stack.layoutManager.lineFragmentRect(forGlyphAt: glyphIndex, effectiveRange: &effectiveRange)
}
#endif #endif
} }