// // PreviewGameViewController.swift // Delta // // Created by Riley Testut on 8/11/16. // Copyright © 2016 Riley Testut. 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() 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 // 在 vi​​ewWillDisappear 中暂停,而不是像 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 } }