diff --git a/alt-tab-macos.xcodeproj/project.pbxproj b/alt-tab-macos.xcodeproj/project.pbxproj index 766fdfe5..7a8d20fb 100644 --- a/alt-tab-macos.xcodeproj/project.pbxproj +++ b/alt-tab-macos.xcodeproj/project.pbxproj @@ -76,6 +76,7 @@ 5F791E4B2F1C65E100B6419C /* ApplicationDiscriminator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F791E4A2F1C65E100B6419C /* ApplicationDiscriminator.swift */; }; 5F882F382EC3C63400110A9F /* CustomRecorderControlTestable.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0C8DC2A355E29516DD2FAF /* CustomRecorderControlTestable.swift */; }; 5F906A342F16A18500B99924 /* Screens.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F906A332F16A18000B99924 /* Screens.swift */; }; + 5FBB24D32F389A0400BF7E14 /* Benchmark.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5FBB24D22F389A0400BF7E14 /* Benchmark.swift */; }; 5FEFDF2F2F181E120065BAA3 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 5FEFDF2D2F181E120065BAA3 /* Localizable.strings */; }; 5FEFDF322F181E250065BAA3 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 5FEFDF312F181E250065BAA3 /* InfoPlist.strings */; }; 76D02BB22BFE7C9E0056008D /* Pods_alt_tab_macos.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C0712B3BEA2B3780398C0999 /* Pods_alt_tab_macos.framework */; }; @@ -383,6 +384,7 @@ 5F906A332F16A18000B99924 /* Screens.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Screens.swift; sourceTree = ""; }; 5F9F965C2EAE731200E25498 /* jetbrains.svg */ = {isa = PBXFileReference; lastKnownFileType = text; path = jetbrains.svg; sourceTree = ""; }; 5FAE000A2F13B49800FE6246 /* update_readme_and_website.sh */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; path = update_readme_and_website.sh; sourceTree = ""; }; + 5FBB24D22F389A0400BF7E14 /* Benchmark.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Benchmark.swift; sourceTree = ""; }; 5FE2AF752EAE849F004A4AC5 /* xcbeautify */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.executable"; path = xcbeautify; sourceTree = ""; }; 5FEFDF2C2F181E120065BAA3 /* be */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = be; path = Localizable.strings; sourceTree = ""; }; 5FEFDF302F181E250065BAA3 /* InfoPlist.strings */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; path = InfoPlist.strings; sourceTree = ""; }; @@ -1336,6 +1338,7 @@ D04BA0D80B24E72B9B981A1D /* logic */ = { isa = PBXGroup; children = ( + 5FBB24D22F389A0400BF7E14 /* Benchmark.swift */, 5F17452D2F1A907800128EF8 /* WindowDiscriminator.swift */, 5F791E4A2F1C65E100B6419C /* ApplicationDiscriminator.swift */, 5F906A332F16A18000B99924 /* Screens.swift */, @@ -2493,6 +2496,7 @@ 5F906A342F16A18500B99924 /* Screens.swift in Sources */, D04BA0AE9865276FF8EF5DEB /* UserDefaultsEvents.swift in Sources */, D04BA4A11F821548EE3C5E95 /* Bash.swift in Sources */, + 5FBB24D32F389A0400BF7E14 /* Benchmark.swift in Sources */, 5F21C9E62C6E94920091F72F /* AnimationsSheet.swift in Sources */, D04BAFB90E00AA5D662EAF24 /* PermissionsWindow.swift in Sources */, D04BA7E39FA539DD8316447A /* PermissionView.swift in Sources */, diff --git a/src/logic/Benchmark.swift b/src/logic/Benchmark.swift new file mode 100644 index 00000000..bb989d3f --- /dev/null +++ b/src/logic/Benchmark.swift @@ -0,0 +1,74 @@ +import Foundation + +final class BenchmarkRunner { + static let launchDuration = 10000 + static let startupDelay = 5000 + static let showDuration = 500 + static let hideDuration = 500 + static let shortcutIndex = 0 + private static var config: BenchmarkConfig? + private static var remainingCycles = 0 + + static func configureFromArgs(_ args: [String]) { + config = BenchmarkConfig.parse(args) + } + + static func startIfNeeded() { + guard let config else { return } + switch config.mode { + case .launch: + scheduleTerminate(after: launchDuration) + case .showUi(let count): + remainingCycles = count + scheduleShow(after: startupDelay) + } + } + + private static func scheduleShow(after delay: Int) { + guard remainingCycles > 0 else { scheduleTerminate(after: hideDuration); return } + DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(delay)) { + App.app.showUi(shortcutIndex) + scheduleHide(after: showDuration) + } + } + + private static func scheduleHide(after delay: Int) { + DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(delay)) { + App.app.hideUi() + remainingCycles -= 1 + scheduleShow(after: hideDuration) + } + } + + private static func scheduleTerminate(after delay: Int) { + DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(delay)) { + App.shared.terminate(nil) + } + } +} + +struct BenchmarkConfig { + enum Mode { + case launch + case showUi(Int) + } + + let mode: Mode + + static func parse(_ args: [String]) -> BenchmarkConfig? { + guard let index = args.firstIndex(of: "--benchmark"), index + 1 < args.count else { return nil } + let mode = args[index + 1] + if mode == "launch" { return BenchmarkConfig(mode: .launch) } + if mode == "showUi" { return parseShowUi(args, index) } + print("Unsupported benchmark mode: \(mode)") + return nil + } + + private static func parseShowUi(_ args: [String], _ index: Int) -> BenchmarkConfig? { + guard index + 2 < args.count, let count = Int(args[index + 2]), count > 0 else { + print("Invalid benchmark showUi count") + return nil + } + return BenchmarkConfig(mode: .showUi(count)) + } +} diff --git a/src/ui/App.swift b/src/ui/App.swift index b7aadd03..df6e70c4 100644 --- a/src/ui/App.swift +++ b/src/ui/App.swift @@ -224,15 +224,16 @@ class App: AppCenterApplication { } func refreshOpenUiWithThrottling( _ block: @escaping () -> Void) { + let throttleDelayInMs = 200 let timeSinceLastRefreshInSeconds = Float(DispatchTime.now().uptimeNanoseconds - lastRefreshTimeInNanoseconds) / 1_000_000 - if timeSinceLastRefreshInSeconds >= 200 { + if timeSinceLastRefreshInSeconds >= Float(throttleDelayInMs) { lastRefreshTimeInNanoseconds = DispatchTime.now().uptimeNanoseconds block() return } guard !nextRefreshScheduled else { return } nextRefreshScheduled = true - DispatchQueue.main.asyncAfter(deadline: .now() + 0.210) { + DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(throttleDelayInMs + 10)) { self.nextRefreshScheduled = false self.refreshOpenUiWithThrottling(block) } @@ -343,6 +344,7 @@ extension App: NSApplicationDelegate { // login item and plist updates can be done a bit later, to accelerate launch DispatchQueue.main.asyncAfter(deadline: .now() + 2) { GeneralTab.startAtLoginCallback() } Logger.info { "Finished launching AltTab" } + BenchmarkRunner.startIfNeeded() #if DEBUG // self.showPreferencesWindow() #endif