From 4b830e6ba7d040fb2aa79014d597ee4280a169ce Mon Sep 17 00:00:00 2001 From: lwouis Date: Fri, 6 Feb 2026 09:47:52 +0100 Subject: [PATCH] perf: optim hangs from DebugMenu --- src/ui/DebugMenu.swift | 146 ++++++++++++++++++++++++++--------------- 1 file changed, 93 insertions(+), 53 deletions(-) diff --git a/src/ui/DebugMenu.swift b/src/ui/DebugMenu.swift index 14c4d902..6708733b 100644 --- a/src/ui/DebugMenu.swift +++ b/src/ui/DebugMenu.swift @@ -1,4 +1,5 @@ import Cocoa +import CoreText final class DebugMenu: NSPanel { static let width = CGFloat(400) @@ -54,61 +55,66 @@ final class DebugMenu: NSPanel { private final class QueueGraphView: NSView { private var history: [(timestamp: Date, data: [String: Double])] = [] + private var latestCounts: [String: Int] = [:] + private var orderedQueues: [String] = [] + private var colors: [NSColor] = [] + private var axisYLines: [CTLine] = [] + private var axisYValues: [Int] = [] + private var legendLines: [CTLine] = [] + private var cachedMaxY = 1.0 private let maxDuration: TimeInterval = 60 private let padding: CGFloat = 20 + private let textFont = NSFont.systemFont(ofSize: 8) + private lazy var axisTextAttributes: [NSAttributedString.Key: Any] = [.font: textFont, .foregroundColor: NSColor.white] + private lazy var legendTextAttributes: [NSAttributedString.Key: Any] = [.font: textFont, .foregroundColor: NSColor.white] + private lazy var axisXLines: [CTLine] = (0...3).map { makeLine("\(Int(Double($0) * 20))s", attributes: axisTextAttributes) } + private let legendRowHeight: CGFloat = 15 - func reset() { history.removeAll(); needsDisplay = true } + func reset() { + history.removeAll() + latestCounts.removeAll() + orderedQueues.removeAll() + colors.removeAll() + axisYLines.removeAll() + axisYValues.removeAll() + legendLines.removeAll() + cachedMaxY = 1.0 + needsDisplay = true + } func addData(_ data: [String: Double]) { let now = Date() history.append((now, data)) - history = history.filter { now.timeIntervalSince($0.timestamp) <= maxDuration } + trimHistory(now) + updateCaches(data) needsDisplay = true } override func draw(_ dirtyRect: NSRect) { guard let ctx = NSGraphicsContext.current?.cgContext else { return } ctx.clear(bounds) + ctx.textMatrix = .identity drawAxes(in: ctx) drawCurves(in: ctx) drawLegend(in: ctx) } private func drawAxes(in ctx: CGContext) { + updateAxisLines() let width = bounds.width - 2*padding let height = DebugMenu.height - 2*padding - let maxY = (history.flatMap { $0.data.values }.max() ?? 1) * 1.1 - ctx.setStrokeColor(NSColor.white.cgColor) ctx.setLineWidth(1) - - // Y grid - for i in 0...5 { + for i in 0.. [String] { - guard let keys = history.last?.data.keys else { return [] } - return keys.sorted() - } - private func distinctColors(count: Int) -> [NSColor] { guard count > 0 else { return [] } return (0.. maxY { maxY = value } + } + let next = maxY * 1.1 + if next == cachedMaxY { return } + cachedMaxY = next + axisYValues.removeAll() + } + + private func updateQueues(_ data: [String: Double]) { + let next = data.keys.sorted() + if next == orderedQueues { return } + orderedQueues = next + colors = distinctColors(count: orderedQueues.count) + legendLines.removeAll() + } + + private func updateLegend(_ data: [String: Double]) { + var counts: [String: Int] = [:] + for (key, value) in data { counts[key] = Int(value) } + if counts == latestCounts, !legendLines.isEmpty { return } + latestCounts = counts + legendLines = orderedQueues.map { makeLine("\($0): \(latestCounts[$0] ?? 0)", attributes: legendTextAttributes) } + } + + private func updateAxisLines() { + let values = (0...5).map { Int(Double($0)/5 * cachedMaxY) } + if values == axisYValues, !axisYLines.isEmpty { return } + axisYValues = values + axisYLines = values.map { makeLine("\($0)", attributes: axisTextAttributes) } + } + + private func makeLine(_ text: String, attributes: [NSAttributedString.Key: Any]) -> CTLine { + CTLineCreateWithAttributedString(NSAttributedString(string: text, attributes: attributes)) + } }