Adds ability to Peek & Pop save states via 3D Touch

This commit is contained in:
Riley Testut 2016-05-16 17:30:53 -05:00
parent 832fac25b3
commit c6e2820458
5 changed files with 164 additions and 14 deletions

@ -1 +1 @@
Subproject commit 4406d30cec9e252979a87966bcce5c5d3711b985
Subproject commit 29abe10afb7f720e40b9345bc88f321c518efbf6

@ -1 +1 @@
Subproject commit 015d8b1a7fc364a9887a2bb2d76a9215f7ead19c
Subproject commit 87d9cfa5574fe42db453f9b6b08b3e7190a4b7d2

View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="9531" systemVersion="15D21" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="wKV-3d-NIY">
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="9532" systemVersion="15D21" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="wKV-3d-NIY">
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="9529"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="9530"/>
<capability name="Segues with Peek and Pop" minToolsVersion="7.1"/>
</dependencies>
<scenes>
@ -93,7 +93,7 @@
<!--Emulation View Controller-->
<scene sceneID="g58-A4-ib1">
<objects>
<viewController id="hx4-Ze-0Jw" customClass="EmulationViewController" customModule="Delta" customModuleProvider="target" sceneMemberID="viewController">
<viewController storyboardIdentifier="emulationViewController" id="hx4-Ze-0Jw" customClass="EmulationViewController" customModule="Delta" customModuleProvider="target" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="Fo4-YI-5cN"/>
<viewControllerLayoutGuide type="bottom" id="UIe-oC-tUc"/>

View File

@ -23,10 +23,23 @@ class EmulationViewController: UIViewController
guard oldValue != game else { return }
self.emulatorCore = SNESEmulatorCore(game: game)
}
}
private(set) var emulatorCore: EmulatorCore! {
didSet
{
self.preferredContentSize = self.emulatorCore.preferredRenderingSize
}
}
private(set) var emulatorCore: EmulatorCore!
// If non-nil, will override the default preview action items returned in previewActionItems()
var overridePreviewActionItems: [UIPreviewActionItem]?
// Annoying iOS gotcha: if the previewingContext(_:viewControllerForLocation:) callback takes too long, the peek/preview starts, but fails to actually present the view controller
// To workaround, we have this closure to defer work for Peeking/Popping until the view controller appears
// Hacky, but works
var deferredPreparationHandler: (Void -> Void)?
//MARK: - Private Properties
@IBOutlet private var controllerView: ControllerView!
@ -90,7 +103,14 @@ class EmulationViewController: UIViewController
{
super.viewDidAppear(animated)
self.emulatorCore.startEmulation()
self.deferredPreparationHandler?()
switch self.emulatorCore.state
{
case .Stopped: self.emulatorCore.startEmulation()
case .Running: break
case .Paused: self.emulatorCore.resumeEmulation()
}
}
override func viewDidLayoutSubviews()
@ -180,12 +200,25 @@ class EmulationViewController: UIViewController
self.pauseViewController = nil
self.emulatorCore.resumeEmulation()
// Temporarily disable audioManager to prevent delayed audio bug when using 3D Touch Peek & Pop
self.emulatorCore.audioManager.enabled = false
// Re-enable after delay
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(0.1 * Double(NSEC_PER_SEC))), dispatch_get_main_queue()) {
self.emulatorCore.audioManager.enabled = true
}
}
//MARK: - 3D Touch -
/// 3D Touch
override func previewActionItems() -> [UIPreviewActionItem]
{
if let previewActionItems = self.overridePreviewActionItems
{
return previewActionItems
}
let presentingViewController = self.presentingViewController
let launchGameAction = UIPreviewAction(title: NSLocalizedString("Launch \(self.game.name)", comment: ""), style: .Default) { (action, viewController) in
@ -227,9 +260,9 @@ private extension EmulationViewController
/// Save States
extension EmulationViewController: SaveStatesViewControllerDelegate
{
func saveStatesViewControllerActiveGame(saveStatesViewController: SaveStatesViewController) -> Game
func saveStatesViewControllerActiveEmulatorCore(saveStatesViewController: SaveStatesViewController) -> EmulatorCore
{
return self.game
return self.emulatorCore
}
func saveStatesViewController(saveStatesViewController: SaveStatesViewController, updateSaveState saveState: SaveState)
@ -276,7 +309,7 @@ extension EmulationViewController: SaveStatesViewControllerDelegate
}
}
func saveStatesViewController(saveStatesViewController: SaveStatesViewController, loadSaveState saveState: SaveState)
func saveStatesViewController(saveStatesViewController: SaveStatesViewController, loadSaveState saveState: SaveStateType)
{
self.emulatorCore.loadSaveState(saveState)

View File

@ -14,9 +14,9 @@ import Roxas
protocol SaveStatesViewControllerDelegate: class
{
func saveStatesViewControllerActiveGame(saveStatesViewController: SaveStatesViewController) -> Game
func saveStatesViewControllerActiveEmulatorCore(saveStatesViewController: SaveStatesViewController) -> EmulatorCore
func saveStatesViewController(saveStatesViewController: SaveStatesViewController, updateSaveState saveState: SaveState)
func saveStatesViewController(saveStatesViewController: SaveStatesViewController, loadSaveState saveState: SaveState)
func saveStatesViewController(saveStatesViewController: SaveStatesViewController, loadSaveState saveState: SaveStateType)
}
extension SaveStatesViewController
@ -56,6 +56,8 @@ class SaveStatesViewController: UICollectionViewController
private let imageOperationQueue = RSTOperationQueue()
private let imageCache = NSCache()
private var currentGameState: SaveStateType?
private let dateFormatter: NSDateFormatter
required init?(coder aDecoder: NSCoder)
@ -109,6 +111,8 @@ extension SaveStatesViewController
let longPressGestureRecognizer = UILongPressGestureRecognizer(target: self, action: "handleLongPressGesture:")
self.collectionView?.addGestureRecognizer(longPressGestureRecognizer)
self.registerForPreviewingWithDelegate(self, sourceView: self.collectionView!)
self.updateBackgroundView()
}
@ -130,6 +134,13 @@ extension SaveStatesViewController
super.viewWillAppear(animated)
}
override func viewDidDisappear(animated: Bool)
{
super.viewDidDisappear(animated)
self.resetEmulatorCoreIfNeeded()
}
override func didReceiveMemoryWarning()
{
@ -143,7 +154,7 @@ private extension SaveStatesViewController
func updateFetchedResultsController()
{
let game = self.delegate.saveStatesViewControllerActiveGame(self)
let game = self.delegate.saveStatesViewControllerActiveEmulatorCore(self).game as! Game
let fetchRequest = SaveState.fetchRequest()
fetchRequest.returnsObjectsAsFaults = false
@ -264,7 +275,7 @@ private extension SaveStatesViewController
let backgroundContext = DatabaseManager.sharedManager.backgroundManagedObjectContext()
backgroundContext.performBlock {
var game = self.delegate.saveStatesViewControllerActiveGame(self)
var game = self.delegate.saveStatesViewControllerActiveEmulatorCore(self).game as! Game
game = backgroundContext.objectWithID(game.objectID) as! Game
let saveState = SaveState.insertIntoManagedObjectContext(backgroundContext)
@ -324,6 +335,39 @@ private extension SaveStatesViewController
}
}
//MARK: - Emulator -
func resetEmulatorCoreIfNeeded()
{
// Kinda hacky, but isMovingFromParentViewController only returns yes when popping off navigation controller, and not being dismissed modally
// Because of this, this is only run when the user returns to PauseMenuViewController, and not when they choose a save state to load
guard let saveState = self.currentGameState where self.isMovingFromParentViewController() else { return }
// We stopped emulation for 3D Touch, so now we must resume emulation and load the save state we made to make it seem like it was never stopped
let emulatorCore = self.delegate.saveStatesViewControllerActiveEmulatorCore(self)
// Temporarily disable video rendering to prevent flickers
emulatorCore.videoManager.enabled = false
// Load the save state we stored a reference to
emulatorCore.startEmulation()
emulatorCore.pauseEmulation()
emulatorCore.loadSaveState(saveState)
// Re-enable video rendering
emulatorCore.videoManager.enabled = true
// Remove temporary save state file
do
{
try NSFileManager.defaultManager().removeItemAtURL(saveState.fileURL)
}
catch let error as NSError
{
print(error)
}
}
//MARK: - Convenience Methods -
func correctedSectionForSectionIndex(section: Int) -> Section
@ -336,6 +380,79 @@ private extension SaveStatesViewController
}
}
//MARK: - <UIViewControllerPreviewingDelegate> -
extension SaveStatesViewController: UIViewControllerPreviewingDelegate
{
func previewingContext(previewingContext: UIViewControllerPreviewing, viewControllerForLocation location: CGPoint) -> UIViewController?
{
guard let indexPath = self.collectionView?.indexPathForItemAtPoint(location), layoutAttributes = self.collectionViewLayout.layoutAttributesForItemAtIndexPath(indexPath) else { return nil }
previewingContext.sourceRect = layoutAttributes.frame
let emulatorCore = self.delegate.saveStatesViewControllerActiveEmulatorCore(self)
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let emulationViewController = storyboard.instantiateViewControllerWithIdentifier("emulationViewController") as! EmulationViewController
emulationViewController.game = emulatorCore.game as! Game
emulationViewController.overridePreviewActionItems = []
emulationViewController.deferredPreparationHandler = { [unowned emulationViewController] in
// Store reference to current game state before we stop emulation so we can resume it if user decides to not load a save state
if self.currentGameState == nil
{
emulatorCore.saveSaveState() { saveState in
let fileURL = NSFileManager.uniqueTemporaryURL()
do
{
try NSFileManager.defaultManager().moveItemAtURL(saveState.fileURL, toURL: fileURL)
}
catch let error as NSError
{
print(error)
}
self.currentGameState = DeltaCore.SaveState(name: nil, fileURL: fileURL)
}
}
emulatorCore.stopEmulation()
let saveState = self.fetchedResultsController.objectAtIndexPath(indexPath) as! SaveState
emulationViewController.emulatorCore.startEmulation()
emulationViewController.emulatorCore.pauseEmulation()
emulationViewController.emulatorCore.loadSaveState(saveState)
}
return emulationViewController
}
func previewingContext(previewingContext: UIViewControllerPreviewing, commitViewController viewControllerToCommit: UIViewController)
{
let emulationViewController = viewControllerToCommit as! EmulationViewController
emulationViewController.emulatorCore.pauseEmulation()
emulationViewController.emulatorCore.saveSaveState() { saveState in
emulationViewController.emulatorCore.stopEmulation()
let emulatorCore = self.delegate.saveStatesViewControllerActiveEmulatorCore(self)
emulatorCore.audioManager.stop()
emulatorCore.startEmulation()
emulatorCore.pauseEmulation()
self.delegate.saveStatesViewController(self, loadSaveState: saveState)
emulatorCore.videoManager.enabled = true
}
}
}
//MARK: - <UICollectionViewDataSource> -
extension SaveStatesViewController
{