Files
OpenEmuKit/Source/OEThreadGameCoreManager.swift
Stuart Carnie 645398652e chore: Fix callbacks when terminating app
When the app is being terminated, the RunLoop is nested, and does not
call additional blocks. The main dispatch queue is called.
2022-12-28 08:38:59 +11:00

121 lines
4.4 KiB
Swift

// Copyright (c) 2022, OpenEmu Team
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
// * Neither the name of the OpenEmu Team nor the
// names of its contributors may be used to endorse or promote products
// derived from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY OpenEmu Team ''AS IS'' AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL OpenEmu Team BE LIABLE FOR ANY
// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
import Foundation
import OpenEmuKitPrivate
@objc public class OEThreadGameCoreManager: GameCoreManager {
var helperThread: Thread!
var dummyTimer: Timer!
var helper: OpenEmuHelperApp!
typealias StopHandler = () -> Void
var stopHandler: StopHandler?
public override func loadROM(completionHandler: @escaping () -> Void, errorHandler: @escaping (Error) -> Void) {
loadROM { error in
if let error = error {
errorHandler(error)
} else {
completionHandler()
}
}
}
public override func loadROM(completionHandler: @escaping StartupCompletionHandler) {
helperThread = Thread {
self.execute(completionHandler: completionHandler)
}
helperThread.name = "org.openemu.core-manager-thread"
helperThread.qualityOfService = .userInitiated
helper = .init()
if let proxy = OEThreadProxy(target: gameCoreOwner, thread: .main) as? OEGameCoreOwner {
helper.gameCoreOwner = proxy
} else {
fatalError("Unable to cast to OEGameCoreOwner proxy")
}
helperThread.start()
}
public override func stopEmulation(completionHandler handler: @escaping () -> Void) {
stopHandler = handler
gameCoreHelper?.stopEmulation {
self.stop()
}
}
override func stop() {
if Thread.current == helperThread {
stopHelperThread(nil)
} else {
perform(#selector(stopHelperThread(_:)), on: helperThread, with: nil, waitUntilDone: false)
}
}
private func execute(completionHandler handler: @escaping StartupCompletionHandler) {
autoreleasepool {
do {
if let proxy = OEThreadProxy(target: helper, thread: .current) as? OEGameCoreHelper {
gameCoreHelper = proxy
} else {
fatalError("Unable to cast to OEGameCoreHelper proxy")
}
try helper.load(withStartupInfo: startupInfo)
DispatchQueue.main.async {
handler(nil)
}
dummyTimer = .scheduledTimer(withTimeInterval: 1e9, repeats: true, block: { _ in })
CFRunLoopRun()
if let stopHandler = stopHandler {
DispatchQueue.main.async(execute: stopHandler)
}
notifyGameCoreDidTerminate()
} catch {
DispatchQueue.main.async {
handler(error)
}
}
}
}
@objc private func stopHelperThread(_ id: Any?) {
dummyTimer.invalidate()
dummyTimer = nil
CFRunLoopStop(CFRunLoopGetCurrent())
gameCoreHelper = nil
helperThread = nil
helper = nil
}
}