2026-05-29 20:08:46 +02:00
|
|
|
import Foundation
|
|
|
|
|
|
|
|
|
|
public enum EditorRenderReason: String, Hashable, Codable, Sendable {
|
|
|
|
|
case initial
|
|
|
|
|
case sourceChange
|
|
|
|
|
case activeLineChange
|
|
|
|
|
case viewUpdate
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public struct EditorRenderPassMetric: Hashable, Sendable {
|
|
|
|
|
public var reason: EditorRenderReason
|
|
|
|
|
public var durationMilliseconds: Double
|
|
|
|
|
public var characterCount: Int
|
|
|
|
|
public var lineCount: Int
|
2026-05-29 20:57:03 +02:00
|
|
|
public var dirtyLineCount: Int
|
2026-05-29 20:08:46 +02:00
|
|
|
public var activeLineIndex: Int
|
2026-05-29 20:57:03 +02:00
|
|
|
public var isFullRender: Bool
|
|
|
|
|
public var restoredScrollPosition: Bool
|
2026-05-29 20:08:46 +02:00
|
|
|
|
|
|
|
|
public init(
|
|
|
|
|
reason: EditorRenderReason,
|
|
|
|
|
durationMilliseconds: Double,
|
|
|
|
|
characterCount: Int,
|
|
|
|
|
lineCount: Int,
|
2026-05-29 20:57:03 +02:00
|
|
|
dirtyLineCount: Int = 0,
|
|
|
|
|
activeLineIndex: Int,
|
|
|
|
|
isFullRender: Bool = false,
|
|
|
|
|
restoredScrollPosition: Bool = false
|
2026-05-29 20:08:46 +02:00
|
|
|
) {
|
|
|
|
|
self.reason = reason
|
|
|
|
|
self.durationMilliseconds = durationMilliseconds
|
|
|
|
|
self.characterCount = characterCount
|
|
|
|
|
self.lineCount = lineCount
|
2026-05-29 20:57:03 +02:00
|
|
|
self.dirtyLineCount = dirtyLineCount
|
2026-05-29 20:08:46 +02:00
|
|
|
self.activeLineIndex = activeLineIndex
|
2026-05-29 20:57:03 +02:00
|
|
|
self.isFullRender = isFullRender
|
|
|
|
|
self.restoredScrollPosition = restoredScrollPosition
|
2026-05-29 20:08:46 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public struct EditorInstrumentationSnapshot: Hashable, Sendable {
|
|
|
|
|
public var sourceChangeCount: Int
|
|
|
|
|
public var selectionChangeCount: Int
|
2026-05-29 20:57:03 +02:00
|
|
|
public var selectionUpdateCount: Int
|
2026-05-29 20:08:46 +02:00
|
|
|
public var activeLineChangeCount: Int
|
|
|
|
|
public var renderPassCount: Int
|
2026-05-29 20:57:03 +02:00
|
|
|
public var fullRenderCount: Int
|
|
|
|
|
public var totalDirtyLineCount: Int
|
|
|
|
|
public var scrollRestorationCount: Int
|
2026-05-29 20:08:46 +02:00
|
|
|
public var totalRenderDurationMilliseconds: Double
|
|
|
|
|
public var lastRenderDurationMilliseconds: Double
|
|
|
|
|
public var lastRenderCharacterCount: Int
|
|
|
|
|
public var lastRenderLineCount: Int
|
2026-05-29 20:57:03 +02:00
|
|
|
public var lastDirtyLineCount: Int
|
2026-05-29 20:08:46 +02:00
|
|
|
public var lastRenderReason: EditorRenderReason?
|
|
|
|
|
|
|
|
|
|
public init(
|
|
|
|
|
sourceChangeCount: Int = 0,
|
|
|
|
|
selectionChangeCount: Int = 0,
|
2026-05-29 20:57:03 +02:00
|
|
|
selectionUpdateCount: Int = 0,
|
2026-05-29 20:08:46 +02:00
|
|
|
activeLineChangeCount: Int = 0,
|
|
|
|
|
renderPassCount: Int = 0,
|
2026-05-29 20:57:03 +02:00
|
|
|
fullRenderCount: Int = 0,
|
|
|
|
|
totalDirtyLineCount: Int = 0,
|
|
|
|
|
scrollRestorationCount: Int = 0,
|
2026-05-29 20:08:46 +02:00
|
|
|
totalRenderDurationMilliseconds: Double = 0,
|
|
|
|
|
lastRenderDurationMilliseconds: Double = 0,
|
|
|
|
|
lastRenderCharacterCount: Int = 0,
|
|
|
|
|
lastRenderLineCount: Int = 0,
|
2026-05-29 20:57:03 +02:00
|
|
|
lastDirtyLineCount: Int = 0,
|
2026-05-29 20:08:46 +02:00
|
|
|
lastRenderReason: EditorRenderReason? = nil
|
|
|
|
|
) {
|
|
|
|
|
self.sourceChangeCount = sourceChangeCount
|
|
|
|
|
self.selectionChangeCount = selectionChangeCount
|
2026-05-29 20:57:03 +02:00
|
|
|
self.selectionUpdateCount = selectionUpdateCount
|
2026-05-29 20:08:46 +02:00
|
|
|
self.activeLineChangeCount = activeLineChangeCount
|
|
|
|
|
self.renderPassCount = renderPassCount
|
2026-05-29 20:57:03 +02:00
|
|
|
self.fullRenderCount = fullRenderCount
|
|
|
|
|
self.totalDirtyLineCount = totalDirtyLineCount
|
|
|
|
|
self.scrollRestorationCount = scrollRestorationCount
|
2026-05-29 20:08:46 +02:00
|
|
|
self.totalRenderDurationMilliseconds = totalRenderDurationMilliseconds
|
|
|
|
|
self.lastRenderDurationMilliseconds = lastRenderDurationMilliseconds
|
|
|
|
|
self.lastRenderCharacterCount = lastRenderCharacterCount
|
|
|
|
|
self.lastRenderLineCount = lastRenderLineCount
|
2026-05-29 20:57:03 +02:00
|
|
|
self.lastDirtyLineCount = lastDirtyLineCount
|
2026-05-29 20:08:46 +02:00
|
|
|
self.lastRenderReason = lastRenderReason
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public var averageRenderDurationMilliseconds: Double {
|
|
|
|
|
guard renderPassCount > 0 else { return 0 }
|
|
|
|
|
return totalRenderDurationMilliseconds / Double(renderPassCount)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public mutating func recordSourceChange() {
|
|
|
|
|
sourceChangeCount += 1
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public mutating func recordSelectionChange() {
|
|
|
|
|
selectionChangeCount += 1
|
2026-05-29 20:57:03 +02:00
|
|
|
selectionUpdateCount += 1
|
2026-05-29 20:08:46 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public mutating func recordActiveLineChange() {
|
|
|
|
|
activeLineChangeCount += 1
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public mutating func recordRenderPass(_ metric: EditorRenderPassMetric) {
|
|
|
|
|
renderPassCount += 1
|
2026-05-29 20:57:03 +02:00
|
|
|
if metric.isFullRender {
|
|
|
|
|
fullRenderCount += 1
|
|
|
|
|
}
|
|
|
|
|
totalDirtyLineCount += metric.dirtyLineCount
|
|
|
|
|
if metric.restoredScrollPosition {
|
|
|
|
|
scrollRestorationCount += 1
|
|
|
|
|
}
|
2026-05-29 20:08:46 +02:00
|
|
|
totalRenderDurationMilliseconds += metric.durationMilliseconds
|
|
|
|
|
lastRenderDurationMilliseconds = metric.durationMilliseconds
|
|
|
|
|
lastRenderCharacterCount = metric.characterCount
|
|
|
|
|
lastRenderLineCount = metric.lineCount
|
2026-05-29 20:57:03 +02:00
|
|
|
lastDirtyLineCount = metric.dirtyLineCount
|
2026-05-29 20:08:46 +02:00
|
|
|
lastRenderReason = metric.reason
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-05-29 20:57:03 +02:00
|
|
|
|
|
|
|
|
#if DEBUG
|
|
|
|
|
public enum EditorDiagnostics {
|
|
|
|
|
public static var isRenderLoggingEnabled = false
|
|
|
|
|
|
|
|
|
|
public static func logRenderPass(_ metric: EditorRenderPassMetric) {
|
|
|
|
|
guard isRenderLoggingEnabled else { return }
|
|
|
|
|
|
|
|
|
|
print(
|
|
|
|
|
"SaplingEditor render reason=\(metric.reason.rawValue) "
|
|
|
|
|
+ "full=\(metric.isFullRender) "
|
|
|
|
|
+ "dirtyLines=\(metric.dirtyLineCount)/\(metric.lineCount) "
|
|
|
|
|
+ "chars=\(metric.characterCount) "
|
|
|
|
|
+ "activeLine=\(metric.activeLineIndex) "
|
|
|
|
|
+ "durationMs=\(String(format: "%.3f", metric.durationMilliseconds)) "
|
|
|
|
|
+ "scrollRestored=\(metric.restoredScrollPosition)"
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
#endif
|