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 public var dirtyLineCount: Int public var activeLineIndex: Int public var isFullRender: Bool public var restoredScrollPosition: Bool public init( reason: EditorRenderReason, durationMilliseconds: Double, characterCount: Int, lineCount: Int, dirtyLineCount: Int = 0, activeLineIndex: Int, isFullRender: Bool = false, restoredScrollPosition: Bool = false ) { self.reason = reason self.durationMilliseconds = durationMilliseconds self.characterCount = characterCount self.lineCount = lineCount self.dirtyLineCount = dirtyLineCount self.activeLineIndex = activeLineIndex self.isFullRender = isFullRender self.restoredScrollPosition = restoredScrollPosition } } public struct EditorInstrumentationSnapshot: Hashable, Sendable { public var sourceChangeCount: Int public var selectionChangeCount: Int public var selectionUpdateCount: Int public var activeLineChangeCount: Int public var renderPassCount: Int public var fullRenderCount: Int public var totalDirtyLineCount: Int public var scrollRestorationCount: Int public var totalRenderDurationMilliseconds: Double public var lastRenderDurationMilliseconds: Double public var lastRenderCharacterCount: Int public var lastRenderLineCount: Int public var lastDirtyLineCount: Int public var lastRenderReason: EditorRenderReason? public init( sourceChangeCount: Int = 0, selectionChangeCount: Int = 0, selectionUpdateCount: Int = 0, activeLineChangeCount: Int = 0, renderPassCount: Int = 0, fullRenderCount: Int = 0, totalDirtyLineCount: Int = 0, scrollRestorationCount: Int = 0, totalRenderDurationMilliseconds: Double = 0, lastRenderDurationMilliseconds: Double = 0, lastRenderCharacterCount: Int = 0, lastRenderLineCount: Int = 0, lastDirtyLineCount: Int = 0, lastRenderReason: EditorRenderReason? = nil ) { self.sourceChangeCount = sourceChangeCount self.selectionChangeCount = selectionChangeCount self.selectionUpdateCount = selectionUpdateCount self.activeLineChangeCount = activeLineChangeCount self.renderPassCount = renderPassCount self.fullRenderCount = fullRenderCount self.totalDirtyLineCount = totalDirtyLineCount self.scrollRestorationCount = scrollRestorationCount self.totalRenderDurationMilliseconds = totalRenderDurationMilliseconds self.lastRenderDurationMilliseconds = lastRenderDurationMilliseconds self.lastRenderCharacterCount = lastRenderCharacterCount self.lastRenderLineCount = lastRenderLineCount self.lastDirtyLineCount = lastDirtyLineCount 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 selectionUpdateCount += 1 } public mutating func recordActiveLineChange() { activeLineChangeCount += 1 } public mutating func recordRenderPass(_ metric: EditorRenderPassMetric) { renderPassCount += 1 if metric.isFullRender { fullRenderCount += 1 } totalDirtyLineCount += metric.dirtyLineCount if metric.restoredScrollPosition { scrollRestorationCount += 1 } totalRenderDurationMilliseconds += metric.durationMilliseconds lastRenderDurationMilliseconds = metric.durationMilliseconds lastRenderCharacterCount = metric.characterCount lastRenderLineCount = metric.lineCount lastDirtyLineCount = metric.dirtyLineCount lastRenderReason = metric.reason } } #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