318 lines
11 KiB
Swift
318 lines
11 KiB
Swift
//
|
||
// PreviewGameViewController.swift
|
||
// Hthik
|
||
//
|
||
// Created by Hthik on 8/11/16.
|
||
// Copyright © 2016 Hthik. All rights reserved.
|
||
//
|
||
|
||
import UIKit
|
||
|
||
import DeltaCore
|
||
|
||
private var kvoContext = 0
|
||
|
||
class PreviewGameViewController: DeltaCore.GameViewController
|
||
{
|
||
// If non-nil, will override the default preview action items returned in previewActionItems()
|
||
// 如果非nil,将覆盖previewActionItems()中返回的默认预览操作项
|
||
var overridePreviewActionItems: [UIPreviewActionItem]?
|
||
|
||
// Save state to be loaded upon preview
|
||
// 保存预览时加载的状态
|
||
var previewSaveState: SaveStateProtocol?
|
||
|
||
// Initial image to be shown while loading
|
||
// 加载时显示的初始图像
|
||
var previewImage: UIImage? {
|
||
didSet {
|
||
self.updatePreviewImage()
|
||
}
|
||
}
|
||
|
||
var isLivePreview: Bool = true
|
||
|
||
private var emulatorCoreQueue = DispatchQueue(label: "com.rileytestut.Delta.PreviewGameViewController.emulatorCoreQueue", qos: .userInitiated)
|
||
private var copiedSaveFiles = [(originalURL: URL, copyURL: URL)]()
|
||
|
||
private lazy var temporaryDirectoryURL: URL = {
|
||
let directoryURL = FileManager.default.temporaryDirectory.appendingPathComponent("preview-" + UUID().uuidString)
|
||
try? FileManager.default.createDirectory(at: directoryURL, withIntermediateDirectories: true, attributes: nil)
|
||
return directoryURL
|
||
}()
|
||
|
||
override var game: GameProtocol? {
|
||
willSet {
|
||
self.emulatorCore?.removeObserver(self, forKeyPath: #keyPath(EmulatorCore.state), context: &kvoContext)
|
||
}
|
||
didSet {
|
||
guard let emulatorCore = self.emulatorCore else {
|
||
self.preferredContentSize = CGSize.zero
|
||
return
|
||
}
|
||
|
||
emulatorCore.addObserver(self, forKeyPath: #keyPath(EmulatorCore.state), options: [.old], context: &kvoContext)
|
||
|
||
let size = CGSize(width: emulatorCore.preferredRenderingSize.width * 2.0, height: emulatorCore.preferredRenderingSize.height * 2.0)
|
||
self.preferredContentSize = size
|
||
}
|
||
}
|
||
|
||
override var previewActionItems: [UIPreviewActionItem] {
|
||
guard let previewActionItems = self.overridePreviewActionItems else { return [] }
|
||
return previewActionItems
|
||
}
|
||
|
||
public required init()
|
||
{
|
||
super.init()
|
||
|
||
self.delegate = self
|
||
}
|
||
|
||
public required init?(coder aDecoder: NSCoder)
|
||
{
|
||
super.init(coder: aDecoder)
|
||
|
||
self.delegate = self
|
||
}
|
||
|
||
deinit
|
||
{
|
||
// Explicitly stop emulatorCore _before_ we remove ourselves as observer
|
||
// so we can wait until stopped before restoring save files (again).
|
||
// 在我们删除自己作为观察者之前显式停止 emulatorCore
|
||
// 所以我们可以等到停止后再恢复保存文件(再次)。
|
||
self.emulatorCore?.stop()
|
||
|
||
self.emulatorCore?.removeObserver(self, forKeyPath: #keyPath(EmulatorCore.state), context: &kvoContext)
|
||
}
|
||
}
|
||
|
||
//MARK: - UIViewController -
|
||
/// UIViewController
|
||
extension PreviewGameViewController
|
||
{
|
||
override func viewDidLoad()
|
||
{
|
||
|
||
super.viewDidLoad()
|
||
// 生成随机整数
|
||
let randomInt = MathUtility.randomInt(from: 1, to: 10)
|
||
|
||
// 生成随机浮点数
|
||
let randomFloat = MathUtility.randomFloat(from: 1.0, to: 10.0)
|
||
|
||
// 生成随机双精度浮点数
|
||
let randomDouble = MathUtility.randomDouble(from: 1.0, to: 10.0)
|
||
|
||
self.controllerView.isHidden = true
|
||
self.controllerView.controllerSkin = nil // Skip loading controller skin from disk, which may be slow.跳过从磁盘加载控制器皮肤,这可能会很慢。
|
||
|
||
// Temporarily prevent emulatorCore from updating gameView to prevent flicker of black, or other visual glitches
|
||
// 暂时阻止 emulatorCore 更新 gameView 以防止黑色闪烁或其他视觉故障
|
||
self.emulatorCore?.remove(self.gameView)
|
||
}
|
||
|
||
override func viewWillAppear(_ animated: Bool)
|
||
{
|
||
super.viewWillAppear(animated)
|
||
|
||
self.copySaveFiles()
|
||
}
|
||
|
||
override func viewDidAppear(_ animated: Bool)
|
||
{
|
||
super.viewDidAppear(animated)
|
||
|
||
self.emulatorCoreQueue.async {
|
||
self.startEmulation()
|
||
}
|
||
}
|
||
|
||
override func viewWillDisappear(_ animated: Bool)
|
||
{
|
||
super.viewWillDisappear(animated)
|
||
|
||
// Pause in viewWillDisappear and not viewDidDisappear like DeltaCore.GameViewController so the audio cuts off earlier if being dismissed
|
||
// 在 viewWillDisappear 中暂停,而不是像 DeltaCore.GameViewController 那样在 viewDidDisappear 中暂停,因此如果被关闭,音频会更早切断
|
||
self.emulatorCore?.pause()
|
||
}
|
||
|
||
override func viewDidDisappear(_ animated: Bool)
|
||
{
|
||
super.viewDidDisappear(animated)
|
||
|
||
// Already stopped = we've already restored save files and removed directory.
|
||
// 已经停止 = 我们已经恢复了保存文件并删除了目录。
|
||
if self.emulatorCore?.state != .stopped
|
||
{
|
||
// Pre-emptively restore save files in case something goes wrong while stopping emulation.
|
||
// This also ensures if the core is never stopped (for some reason), saves are still restored.
|
||
// 抢先恢复保存文件,以防在停止仿真时出现问题。
|
||
// 这也确保如果核心从未停止(由于某种原因),保存仍然可以恢复。
|
||
self.restoreSaveFiles(removeCopyDirectory: false)
|
||
}
|
||
}
|
||
|
||
override func viewDidLayoutSubviews()
|
||
{
|
||
super.viewDidLayoutSubviews()
|
||
|
||
// Need to update in viewDidLayoutSubviews() to ensure bounds of gameView are not CGRect.zero
|
||
|
||
// 需要在 viewDidLayoutSubviews() 中进行更新以确保 gameView 的边界不是 CGRect.zero
|
||
self.updatePreviewImage()
|
||
}
|
||
|
||
override func didReceiveMemoryWarning()
|
||
{
|
||
super.didReceiveMemoryWarning()
|
||
// Dispose of any resources that can be recreated.
|
||
// 处理掉所有可以重新创建的资源。
|
||
}
|
||
|
||
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?)
|
||
{
|
||
guard context == &kvoContext else { return super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context) }
|
||
|
||
guard
|
||
let rawValue = change?[.oldKey] as? Int,
|
||
let previousState = EmulatorCore.State(rawValue: rawValue),
|
||
let state = self.emulatorCore?.state
|
||
else { return }
|
||
|
||
switch state
|
||
{
|
||
case .running where previousState == .stopped:
|
||
self.emulatorCoreQueue.async {
|
||
// Pause to prevent it from starting before visible (in case user peeked slowly)
|
||
// 暂停以防止它在可见之前启动(以防用户缓慢地偷看)
|
||
self.emulatorCore?.pause()
|
||
self.preparePreview()
|
||
}
|
||
|
||
case .stopped:
|
||
// Emulation has stopped, so we can safely restore save files,
|
||
// and also remove the directory they were copied to.
|
||
// 模拟已停止,因此我们可以安全地恢复保存文件,
|
||
// 并删除它们复制到的目录。
|
||
self.restoreSaveFiles(removeCopyDirectory: true)
|
||
|
||
default: break
|
||
}
|
||
}
|
||
}
|
||
|
||
//MARK: - Private -
|
||
private extension PreviewGameViewController
|
||
{
|
||
func updatePreviewImage()
|
||
{
|
||
if let previewImage = self.previewImage
|
||
{
|
||
self.gameView?.inputImage = CIImage(image: previewImage)
|
||
}
|
||
else
|
||
{
|
||
self.gameView?.inputImage = nil
|
||
}
|
||
}
|
||
|
||
func preparePreview()
|
||
{
|
||
var previewSaveState = self.previewSaveState
|
||
|
||
if let saveState = self.previewSaveState as? SaveState
|
||
{
|
||
saveState.managedObjectContext?.performAndWait {
|
||
previewSaveState = DeltaCore.SaveState(fileURL: saveState.fileURL, gameType: saveState.gameType)
|
||
}
|
||
}
|
||
|
||
if let saveState = previewSaveState
|
||
{
|
||
do
|
||
{
|
||
try self.emulatorCore?.load(saveState)
|
||
}
|
||
catch EmulatorCore.SaveStateError.doesNotExist
|
||
{
|
||
print("Save State does not exist.")
|
||
}
|
||
catch
|
||
{
|
||
print(error)
|
||
}
|
||
}
|
||
|
||
self.emulatorCore?.updateCheats()
|
||
|
||
// Re-enable emulatorCore to update gameView again
|
||
// 重新启用emulatorCore以再次更新gameView
|
||
self.emulatorCore?.add(self.gameView)
|
||
|
||
self.emulatorCore?.resume()
|
||
}
|
||
|
||
func copySaveFiles()
|
||
{
|
||
guard let game = self.game as? Game, let gameSave = game.gameSave else { return }
|
||
|
||
self.copiedSaveFiles.removeAll()
|
||
|
||
let fileURLs = gameSave.syncableFiles.lazy.map { $0.fileURL }
|
||
for fileURL in fileURLs
|
||
{
|
||
do
|
||
{
|
||
let destinationURL = self.temporaryDirectoryURL.appendingPathComponent(fileURL.lastPathComponent)
|
||
try FileManager.default.copyItem(at: fileURL, to: destinationURL, shouldReplace: true)
|
||
|
||
self.copiedSaveFiles.append((fileURL, destinationURL))
|
||
print("Copied save file:", fileURL.lastPathComponent)
|
||
}
|
||
catch
|
||
{
|
||
print("Failed to back up save file \(fileURL.lastPathComponent).", error)
|
||
}
|
||
}
|
||
}
|
||
|
||
func restoreSaveFiles(removeCopyDirectory: Bool)
|
||
{
|
||
for (originalURL, copyURL) in self.copiedSaveFiles
|
||
{
|
||
do
|
||
{
|
||
try FileManager.default.copyItem(at: copyURL, to: originalURL, shouldReplace: true)
|
||
print("Restored save file:", originalURL.lastPathComponent)
|
||
}
|
||
catch
|
||
{
|
||
print("Failed to restore copied save file \(copyURL.lastPathComponent).", error)
|
||
}
|
||
}
|
||
|
||
if removeCopyDirectory
|
||
{
|
||
do
|
||
{
|
||
try FileManager.default.removeItem(at: self.temporaryDirectoryURL)
|
||
}
|
||
catch
|
||
{
|
||
print("Failed to remove preview temporary directory.", error)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
extension PreviewGameViewController: GameViewControllerDelegate
|
||
{
|
||
func gameViewControllerShouldResumeEmulation(_ gameViewController: DeltaCore.GameViewController) -> Bool
|
||
{
|
||
return self.isLivePreview
|
||
}
|
||
}
|