From 8a906c45f824e5af0668d28588ab8f0a586853b6 Mon Sep 17 00:00:00 2001 From: sam Date: Tue, 4 Aug 2026 13:21:26 -0700 Subject: [PATCH 1/2] Add configurable gameplay rates --- Source/GameCoreManager.swift | 4 ++++ Source/OEGameCoreHelper.swift | 7 +++++++ Source/OpenEmuHelperApp.swift | 32 +++++++++++++++++++++++++++++--- 3 files changed, 40 insertions(+), 3 deletions(-) diff --git a/Source/GameCoreManager.swift b/Source/GameCoreManager.swift index a661c91..8d068f6 100644 --- a/Source/GameCoreManager.swift +++ b/Source/GameCoreManager.swift @@ -80,6 +80,10 @@ extension GameCoreManager: OEGameCoreHelper { public func setVolume(_ value: Float) { gameCoreHelper?.setVolume(value) } + + public func setGameplayRate(_ normalRate: Float, fastForwardRate: Float) { + gameCoreHelper?.setGameplayRate(normalRate, fastForwardRate: fastForwardRate) + } public func setPauseEmulation(_ pauseEmulation: Bool) { gameCoreHelper?.setPauseEmulation(pauseEmulation) diff --git a/Source/OEGameCoreHelper.swift b/Source/OEGameCoreHelper.swift index 0bc4e80..c9d1adf 100644 --- a/Source/OEGameCoreHelper.swift +++ b/Source/OEGameCoreHelper.swift @@ -43,6 +43,13 @@ import AudioToolbox /// /// - Parameter value: The new volume level, from @c [0,1.0] func setVolume(_ value: Float) + + /// Sets the rates used for normal gameplay and fast-forwarding. + /// + /// - Parameters: + /// - normalRate: The multiplier used during normal gameplay. + /// - fastForwardRate: The multiplier used while fast-forward is active. + func setGameplayRate(_ normalRate: Float, fastForwardRate: Float) /** * Manage the paused status of the core. diff --git a/Source/OpenEmuHelperApp.swift b/Source/OpenEmuHelperApp.swift index 64b4a31..0c12c95 100644 --- a/Source/OpenEmuHelperApp.swift +++ b/Source/OpenEmuHelperApp.swift @@ -89,6 +89,10 @@ extension OSLog { var _handleKeyboardEvents: Bool = false var loadedRom = false + + private var normalGameplayRate: Float = 1.0 + private var fastForwardGameplayRate: Float = 5.0 + private var isFastForwarding = false // frame rate debugging var previous = CFTimeInterval() @@ -143,6 +147,19 @@ extension OSLog { self._shaderParameters = nil } } + + private func applyGameplayRate() { + let rate = isFastForwarding ? fastForwardGameplayRate : normalGameplayRate + + // nextDrawable() otherwise limits cores running faster than the display refresh rate. + // Fixes: https://github.com/OpenEmu/OpenEmu/issues/4780 + _videoLayer?.displaySyncEnabled = rate <= 1.0 + + gameCore.perform { + guard !self.gameCore.isEmulationPaused else { return } + self.gameCore.rate = rate + } + } // MARK: - Core Video and Generic Video @@ -361,10 +378,20 @@ extension OSLog { self._gameAudio.volume = volume } } + + public func setGameplayRate(_ normalRate: Float, fastForwardRate: Float) { + normalGameplayRate = normalRate + fastForwardGameplayRate = fastForwardRate + applyGameplayRate() + } public func setPauseEmulation(_ paused: Bool) { + let rate = isFastForwarding ? fastForwardGameplayRate : normalGameplayRate gameCore.perform { self.gameCore.setPauseEmulation(paused) + if !paused { + self.gameCore.rate = rate + } } } @@ -828,9 +855,8 @@ extension OSLog { } public func fastForwardGameplay(_ enable: Bool) { - // Required so that _videoLayer.nextDrawable() vends frames faster than the display refresh rate - // Fixes: https://github.com/OpenEmu/OpenEmu/issues/4780 - _videoLayer.displaySyncEnabled = !enable + isFastForwarding = enable + applyGameplayRate() gameCoreOwner.fastForwardGameplay(enable) } From 0a8cdacb8b634a0f47d29cec1e1bf7d7a23be9c9 Mon Sep 17 00:00:00 2001 From: sam Date: Sun, 23 Aug 2026 09:11:21 +0200 Subject: [PATCH 2/2] Apply custom gameplay rate after emulation starts --- Source/OpenEmuHelperApp.swift | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/Source/OpenEmuHelperApp.swift b/Source/OpenEmuHelperApp.swift index 0c12c95..578ce03 100644 --- a/Source/OpenEmuHelperApp.swift +++ b/Source/OpenEmuHelperApp.swift @@ -93,6 +93,7 @@ extension OSLog { private var normalGameplayRate: Float = 1.0 private var fastForwardGameplayRate: Float = 5.0 private var isFastForwarding = false + private var hasStartedEmulation = false // frame rate debugging var previous = CFTimeInterval() @@ -149,6 +150,12 @@ extension OSLog { } private func applyGameplayRate() { + // The host sends its configured rates during setup, before the core has + // started. Changing the Metal layer's display-sync mode at that point can + // leave the first drawable stalled when a game is launched again. Store + // those rates during setup and apply them once startEmulation completes. + guard hasStartedEmulation else { return } + let rate = isFastForwarding ? fastForwardGameplayRate : normalGameplayRate // nextDrawable() otherwise limits cores running faster than the display refresh rate. @@ -477,7 +484,11 @@ extension OSLog { } public func startEmulation(completionHandler handler: @escaping () -> Void) { - gameCore.startEmulation(completionHandler: handler) + gameCore.startEmulation { + self.hasStartedEmulation = true + self.applyGameplayRate() + handler() + } } public func resetEmulation(completionHandler handler: @escaping () -> Void) { @@ -486,6 +497,8 @@ extension OSLog { public func stopEmulation(completionHandler handler: @escaping () -> Void) { guard let gameCore = gameCore else { return } + + hasStartedEmulation = false gameCore.stopEmulation { self._gameAudio.stopAudio()