diff --git a/native/swift/Sources/Deview/Runtime.swift b/native/swift/Sources/Deview/Runtime.swift index c84ffbdb..794a16b6 100644 --- a/native/swift/Sources/Deview/Runtime.swift +++ b/native/swift/Sources/Deview/Runtime.swift @@ -35,6 +35,9 @@ final class Runtime { var input = DeviewInput() var initialised = false + /// Keeps App Nap off for as long as the runtime is open: see `open`. + private var activity: NSObjectProtocol? + private init() { resetInput() } @@ -49,6 +52,16 @@ final class Runtime { self.title = title initialised = true + // Out of App Nap for as long as this runs. A napped process has its timers coalesced and + // its priority lowered, and this one is a loop driven from outside, waiting on + // nextEvent(until:) a frame at a time, with a socket listener beside it: covered, it could + // be slow to answer a failing test's inline send and slower to bring its window forward, + // and hidden while it owns the queue it still has to answer every send promptly. Idle + // sleep stays allowed. The loop only redraws on a change, so staying awake costs a pump. + activity = ProcessInfo.processInfo.beginActivity( + options: .userInitiatedAllowingIdleSystemSleep, + reason: "Reviewing snapshots and answering the queue") + // A hidden start is capture only, and capture draws into a bitmap of its own making. Not // touching AppKit at all in that case is what lets the pixel tests run: NSWindow may only // be instantiated on the main thread, and a test host runs them on whatever thread it @@ -287,6 +300,11 @@ final class Runtime { } func shutdown() { + if let activity { + ProcessInfo.processInfo.endActivity(activity) + self.activity = nil + } + window?.delegate = nil window?.orderOut(nil) window?.close() diff --git a/src/DiffEngineViewer.Mac/runtimes/osx-arm64/native/libdiffengine_viewer.dylib b/src/DiffEngineViewer.Mac/runtimes/osx-arm64/native/libdiffengine_viewer.dylib index 7a4b62de..05c76421 100644 Binary files a/src/DiffEngineViewer.Mac/runtimes/osx-arm64/native/libdiffengine_viewer.dylib and b/src/DiffEngineViewer.Mac/runtimes/osx-arm64/native/libdiffengine_viewer.dylib differ diff --git a/src/DiffEngineViewer.Mac/runtimes/osx-x64/native/libdiffengine_viewer.dylib b/src/DiffEngineViewer.Mac/runtimes/osx-x64/native/libdiffengine_viewer.dylib index 7a4b62de..05c76421 100644 Binary files a/src/DiffEngineViewer.Mac/runtimes/osx-x64/native/libdiffengine_viewer.dylib and b/src/DiffEngineViewer.Mac/runtimes/osx-x64/native/libdiffengine_viewer.dylib differ diff --git a/todo.md b/todo.md index 2b01505c..eca38019 100644 --- a/todo.md +++ b/todo.md @@ -16,10 +16,3 @@ Viewer model - [ ] **Selection columns count one code point to a cell, which wide CJK and combining marks do not take** (verified) - Columns now count code points, which fixed the copy and the highlight for characters outside the basic plane: GDI+ draws those one cell wide and ImGui lays out one glyph per code point (`SelectionText.Cells`). Still off: CJK falls back to a font 1.83 cells wide on Windows, a combining mark takes none, and Core Text substitutes fonts with their own widths. - Fix: have each head report string positions from its own layout rather than cells, or put every code point on the grid. - - -Native - -- [ ] **macOS App Nap can stall the loop while the window is covered** (cannot verify here) - - Nothing opts out (no `beginActivity`, `NSAppSleepDisabled` or power assertion), the only wait is `nextEvent(until: now + 1/60)` (`Runtime.swift:248-253`), and a Focus queued by an arriving patch waits for the next managed frame. - - Check on a Mac: cover the viewer for a minute, confirm Activity Monitor shows App Nap, then time how long a failing inline test takes to bring it forward against an uncovered window.