Adds support for handling ActionInputs from GameControllers

• Quick Save
• Quick Load
• Fast Forward
This commit is contained in:
Riley Testut 2017-10-10 14:52:11 -07:00
parent c6875c44b6
commit 61440ef994
13 changed files with 684 additions and 66 deletions

@ -1 +1 @@
Subproject commit 5d0d2d40b7f7797b3bb270241c7d6a0c16abe4bc
Subproject commit ff014bab5c42e5c0a5fcbacb5c3c737ef33564e7

View File

@ -52,7 +52,6 @@
BF59426B1E09BBD00051894B /* GridCollectionViewLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF5942691E09BBD00051894B /* GridCollectionViewLayout.swift */; };
BF59426F1E09BC5D0051894B /* DatabaseManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF59426D1E09BC5D0051894B /* DatabaseManager.swift */; };
BF5942701E09BC5D0051894B /* GamesDatabase.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF59426E1E09BC5D0051894B /* GamesDatabase.swift */; };
BF5942731E09BC700051894B /* Delta.xcdatamodel in Sources */ = {isa = PBXBuildFile; fileRef = BF5942721E09BC700051894B /* Delta.xcdatamodel */; };
BF59427C1E09BC830051894B /* Cheat.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF5942771E09BC830051894B /* Cheat.swift */; };
BF59427D1E09BC830051894B /* ControllerSkin.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF5942781E09BC830051894B /* ControllerSkin.swift */; };
BF59427E1E09BC830051894B /* Game.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF5942791E09BC830051894B /* Game.swift */; };
@ -104,11 +103,14 @@
BFC9B7391CEFCD34008629BB /* CheatsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFC9B7381CEFCD34008629BB /* CheatsViewController.swift */; };
BFCEA67E1D56FF640061A534 /* UIViewControllerContextTransitioning+Conveniences.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFCEA67D1D56FF640061A534 /* UIViewControllerContextTransitioning+Conveniences.swift */; };
BFD097211D3A01B8005A44C2 /* SaveStatesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF3540041C5DA70400C1184C /* SaveStatesViewController.swift */; };
BFDC7B9F1F7DE0CF0052A7C5 /* Delta.xcmappingmodel in Sources */ = {isa = PBXBuildFile; fileRef = BFDC7B9E1F7DE0CF0052A7C5 /* Delta.xcmappingmodel */; };
BFDD04F11D5E2C27002D450E /* GameCollectionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFDD04F01D5E2C27002D450E /* GameCollectionViewController.swift */; };
BFE022A01F5B57FF0052D888 /* PopoverMenuButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFE0229F1F5B577D0052D888 /* PopoverMenuButton.swift */; };
BFE4269E1D9C68E600DC913F /* SaveStatesStoryboardSegue.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFE4269D1D9C68E600DC913F /* SaveStatesStoryboardSegue.swift */; };
BFEC732D1AAECC4A00650035 /* Roxas.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BFEC732C1AAECC4A00650035 /* Roxas.framework */; };
BFEC732E1AAECC4A00650035 /* Roxas.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = BFEC732C1AAECC4A00650035 /* Roxas.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
BFEF24EF1F7DD3F100454C62 /* Delta.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = BFEF24EC1F7DD3F100454C62 /* Delta.xcdatamodeld */; };
BFEF24F31F7DD4FD00454C62 /* SaveStateMigrationPolicy.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFEF24F21F7DD4FB00454C62 /* SaveStateMigrationPolicy.swift */; };
BFF0742C1E9DC17500ACDF4A /* GBCDeltaCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BFF0742B1E9DC17500ACDF4A /* GBCDeltaCore.framework */; };
BFF0742D1E9DC17500ACDF4A /* GBCDeltaCore.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = BFF0742B1E9DC17500ACDF4A /* GBCDeltaCore.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
BFF6452E1F7CC5060056533E /* GameControllerInputMappingTransformer.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF6B82A41F7CC2A300042BFB /* GameControllerInputMappingTransformer.swift */; };
@ -180,7 +182,6 @@
BF5942691E09BBD00051894B /* GridCollectionViewLayout.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = GridCollectionViewLayout.swift; path = "Components/Collection View/GridCollectionViewLayout.swift"; sourceTree = "<group>"; };
BF59426D1E09BC5D0051894B /* DatabaseManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = DatabaseManager.swift; path = Database/DatabaseManager.swift; sourceTree = "<group>"; };
BF59426E1E09BC5D0051894B /* GamesDatabase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = GamesDatabase.swift; path = Database/OpenVGDB/GamesDatabase.swift; sourceTree = "<group>"; };
BF5942721E09BC700051894B /* Delta.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; name = Delta.xcdatamodel; path = Database/Model/Delta.xcdatamodel; sourceTree = "<group>"; };
BF5942771E09BC830051894B /* Cheat.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Cheat.swift; path = Database/Model/Human/Cheat.swift; sourceTree = "<group>"; };
BF5942781E09BC830051894B /* ControllerSkin.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ControllerSkin.swift; path = Database/Model/Human/ControllerSkin.swift; sourceTree = "<group>"; };
BF5942791E09BC830051894B /* Game.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Game.swift; path = Database/Model/Human/Game.swift; sourceTree = "<group>"; };
@ -232,10 +233,14 @@
BFC6F7B71F435BC500221B96 /* Input+Display.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Input+Display.swift"; sourceTree = "<group>"; };
BFC9B7381CEFCD34008629BB /* CheatsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = CheatsViewController.swift; path = "Pause Menu/Cheats/CheatsViewController.swift"; sourceTree = "<group>"; };
BFCEA67D1D56FF640061A534 /* UIViewControllerContextTransitioning+Conveniences.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIViewControllerContextTransitioning+Conveniences.swift"; sourceTree = "<group>"; };
BFDC7B9E1F7DE0CF0052A7C5 /* Delta.xcmappingmodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcmappingmodel; name = Delta.xcmappingmodel; path = Database/Model/Migrations/Delta.xcmappingmodel; sourceTree = "<group>"; };
BFDD04F01D5E2C27002D450E /* GameCollectionViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GameCollectionViewController.swift; sourceTree = "<group>"; };
BFE0229F1F5B577D0052D888 /* PopoverMenuButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = PopoverMenuButton.swift; path = "Components/Popover Menu/PopoverMenuButton.swift"; sourceTree = "<group>"; };
BFE4269D1D9C68E600DC913F /* SaveStatesStoryboardSegue.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SaveStatesStoryboardSegue.swift; path = Segues/SaveStatesStoryboardSegue.swift; sourceTree = "<group>"; };
BFEC732C1AAECC4A00650035 /* Roxas.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Roxas.framework; sourceTree = BUILT_PRODUCTS_DIR; };
BFEF24ED1F7DD3F100454C62 /* Delta 2.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "Delta 2.xcdatamodel"; sourceTree = "<group>"; };
BFEF24EE1F7DD3F100454C62 /* Delta.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = Delta.xcdatamodel; sourceTree = "<group>"; };
BFEF24F21F7DD4FB00454C62 /* SaveStateMigrationPolicy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = SaveStateMigrationPolicy.swift; path = Database/Model/Migrations/Policies/SaveStateMigrationPolicy.swift; sourceTree = "<group>"; };
BFF0742B1E9DC17500ACDF4A /* GBCDeltaCore.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = GBCDeltaCore.framework; sourceTree = BUILT_PRODUCTS_DIR; };
BFF93A9F1E0FB036005EC865 /* InputStreamOutputWriter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = InputStreamOutputWriter.swift; path = Database/InputStreamOutputWriter.swift; sourceTree = "<group>"; };
BFFA4C081E8A24D600D87934 /* GameMetadataTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = GameMetadataTableViewCell.swift; path = Database/OpenVGDB/GameMetadataTableViewCell.swift; sourceTree = "<group>"; };
@ -381,10 +386,11 @@
BF5942711E09BC690051894B /* Model */ = {
isa = PBXGroup;
children = (
BF5942721E09BC700051894B /* Delta.xcdatamodel */,
BFEF24EC1F7DD3F100454C62 /* Delta.xcdatamodeld */,
BF5942741E09BC740051894B /* Human */,
BF5942751E09BC780051894B /* Machine */,
BF6B82A31F7CC29A00042BFB /* Transformers */,
BFEF24F01F7DD4B600454C62 /* Migrations */,
BF5942761E09BC7C0051894B /* Misc */,
);
name = Model;
@ -565,6 +571,23 @@
path = Resources;
sourceTree = "<group>";
};
BFEF24F01F7DD4B600454C62 /* Migrations */ = {
isa = PBXGroup;
children = (
BFDC7B9E1F7DE0CF0052A7C5 /* Delta.xcmappingmodel */,
BFEF24F11F7DD4BE00454C62 /* Policies */,
);
name = Migrations;
sourceTree = "<group>";
};
BFEF24F11F7DD4BE00454C62 /* Policies */ = {
isa = PBXGroup;
children = (
BFEF24F21F7DD4FB00454C62 /* SaveStateMigrationPolicy.swift */,
);
name = Policies;
sourceTree = "<group>";
};
BFFA71CE1AAC406100EE9DD1 = {
isa = PBXGroup;
children = (
@ -844,7 +867,6 @@
BF6BF3131EB7E47F008E83CD /* ImportOption.swift in Sources */,
BF31878B1D489AAA00BD020D /* CheatValidator.swift in Sources */,
BFFC46201D59823500AF2CC6 /* InitialGamesStoryboardSegue.swift in Sources */,
BF5942731E09BC700051894B /* Delta.xcdatamodel in Sources */,
BF15AF841F54B43B009B6AAB /* ActionInput.swift in Sources */,
BF5942951E09BD1A0051894B /* NSManagedObjectContext+Conveniences.swift in Sources */,
BF353FF91C5D870B00C1184C /* MenuItem.swift in Sources */,
@ -854,6 +876,7 @@
BFFA71DD1AAC406100EE9DD1 /* AppDelegate.swift in Sources */,
BF59426B1E09BBD00051894B /* GridCollectionViewLayout.swift in Sources */,
BF6424851F5CBDC900D6AB44 /* UIView+ParentViewController.swift in Sources */,
BFDC7B9F1F7DE0CF0052A7C5 /* Delta.xcmappingmodel in Sources */,
BF04E6FF1DB8625C000F35D3 /* ControllerSkinsViewController.swift in Sources */,
BF5942891E09BC8B0051894B /* _GameCollection.swift in Sources */,
BF34FA111CF1899D006624C7 /* CheatTextView.swift in Sources */,
@ -878,6 +901,7 @@
BF80E1D21F13117000847008 /* ControllerInputsViewController.swift in Sources */,
BF5942641E09BBB10051894B /* LoadControllerSkinImageOperation.swift in Sources */,
BF6EE5E91F7C5F860051AD6C /* _GameControllerInputMapping.swift in Sources */,
BFEF24EF1F7DD3F100454C62 /* Delta.xcdatamodeld in Sources */,
BF5942871E09BC8B0051894B /* _ControllerSkin.swift in Sources */,
BF95E2791E4982A10030E7AD /* GamesDatabaseBrowserViewController.swift in Sources */,
BFD097211D3A01B8005A44C2 /* SaveStatesViewController.swift in Sources */,
@ -895,6 +919,7 @@
BF5942861E09BC8B0051894B /* _Cheat.swift in Sources */,
BF5E7F441B9A650B00AE44F8 /* SettingsViewController.swift in Sources */,
BF8CA9361F5F651900499FDD /* PopoverMenuController.swift in Sources */,
BFEF24F31F7DD4FD00454C62 /* SaveStateMigrationPolicy.swift in Sources */,
BF5942931E09BD1A0051894B /* NSFetchedResultsController+Conveniences.m in Sources */,
BF6BF3271EB87EB8008E83CD /* PhotoLibraryImportOption.swift in Sources */,
BF5942661E09BBB10051894B /* LoadImageURLOperation.swift in Sources */,
@ -1129,6 +1154,21 @@
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
/* Begin XCVersionGroup section */
BFEF24EC1F7DD3F100454C62 /* Delta.xcdatamodeld */ = {
isa = XCVersionGroup;
children = (
BFEF24ED1F7DD3F100454C62 /* Delta 2.xcdatamodel */,
BFEF24EE1F7DD3F100454C62 /* Delta.xcdatamodel */,
);
currentVersion = BFEF24ED1F7DD3F100454C62 /* Delta 2.xcdatamodel */;
name = Delta.xcdatamodeld;
path = Database/Model/Delta.xcdatamodeld;
sourceTree = "<group>";
versionGroupType = wrapper.xcdatamodel;
};
/* End XCVersionGroup section */
};
rootObject = BFFA71CF1AAC406100EE9DD1 /* Project object */;
}

View File

@ -65,11 +65,10 @@ final class DatabaseManager: NSPersistentContainer
private init()
{
guard
let modelURL = Bundle(for: DatabaseManager.self).url(forResource: "Delta", withExtension: "mom"),
let modelURL = Bundle(for: DatabaseManager.self).url(forResource: "Delta", withExtension: "momd"),
let managedObjectModel = NSManagedObjectModel(contentsOf: modelURL)
else { fatalError("Core Data model cannot be found. Aborting.") }
super.init(name: "Delta", managedObjectModel: managedObjectModel)
self.viewContext.automaticallyMergesChangesFromParent = true

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>_XCCurrentVersionName</key>
<string>Delta 2.xcdatamodel</string>
</dict>
</plist>

View File

@ -0,0 +1,138 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="13240" systemVersion="16G29" minimumToolsVersion="Xcode 7.0" sourceLanguage="Objective-C" userDefinedModelVersionIdentifier="1.0">
<entity name="Cheat" representedClassName="Cheat" syncable="YES">
<attribute name="code" attributeType="String" syncable="YES"/>
<attribute name="creationDate" attributeType="Date" usesScalarValueType="NO" syncable="YES"/>
<attribute name="identifier" attributeType="String" syncable="YES"/>
<attribute name="isEnabled" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="NO" syncable="YES"/>
<attribute name="modifiedDate" attributeType="Date" usesScalarValueType="NO" syncable="YES"/>
<attribute name="name" optional="YES" attributeType="String" syncable="YES"/>
<attribute name="type" attributeType="Transformable" syncable="YES">
<userInfo>
<entry key="attributeValueClassName" value="CheatType"/>
</userInfo>
</attribute>
<relationship name="game" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Game" inverseName="cheats" inverseEntity="Game" syncable="YES"/>
<uniquenessConstraints>
<uniquenessConstraint>
<constraint value="identifier"/>
</uniquenessConstraint>
</uniquenessConstraints>
</entity>
<entity name="ControllerSkin" representedClassName="ControllerSkin" syncable="YES">
<attribute name="filename" attributeType="String" syncable="YES"/>
<attribute name="gameType" attributeType="Transformable" syncable="YES">
<userInfo>
<entry key="attributeValueClassName" value="GameType"/>
</userInfo>
</attribute>
<attribute name="identifier" attributeType="String" syncable="YES"/>
<attribute name="isStandard" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
<attribute name="name" attributeType="String" syncable="YES"/>
<attribute name="supportedConfigurations" attributeType="Integer 16" defaultValueString="0" usesScalarValueType="YES" syncable="YES">
<userInfo>
<entry key="attributeValueScalarType" value="ControllerSkinConfigurations"/>
</userInfo>
</attribute>
<uniquenessConstraints>
<uniquenessConstraint>
<constraint value="identifier"/>
<constraint value="gameType"/>
</uniquenessConstraint>
</uniquenessConstraints>
</entity>
<entity name="Game" representedClassName="Game" syncable="YES">
<attribute name="artworkURL" optional="YES" attributeType="Transformable" syncable="YES">
<userInfo>
<entry key="attributeValueClassName" value="URL"/>
</userInfo>
</attribute>
<attribute name="filename" attributeType="String" syncable="YES">
<userInfo>
<entry key="attributeValueClassName" value="NSURL"/>
</userInfo>
</attribute>
<attribute name="identifier" attributeType="String" syncable="YES"/>
<attribute name="name" attributeType="String" syncable="YES"/>
<attribute name="type" attributeType="Transformable" syncable="YES">
<userInfo>
<entry key="attributeValueClassName" value="GameType"/>
</userInfo>
</attribute>
<relationship name="cheats" toMany="YES" deletionRule="Cascade" destinationEntity="Cheat" inverseName="game" inverseEntity="Cheat" syncable="YES"/>
<relationship name="gameCollections" toMany="YES" deletionRule="Nullify" destinationEntity="GameCollection" inverseName="games" inverseEntity="GameCollection" syncable="YES"/>
<relationship name="previewSaveState" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="SaveState" inverseName="previewGame" inverseEntity="SaveState" syncable="YES"/>
<relationship name="saveStates" toMany="YES" deletionRule="Cascade" destinationEntity="SaveState" inverseName="game" inverseEntity="SaveState" syncable="YES"/>
<uniquenessConstraints>
<uniquenessConstraint>
<constraint value="identifier"/>
</uniquenessConstraint>
</uniquenessConstraints>
</entity>
<entity name="GameCollection" representedClassName="GameCollection" syncable="YES">
<attribute name="identifier" attributeType="String" syncable="YES"/>
<attribute name="index" attributeType="Integer 16" defaultValueString="0" usesScalarValueType="NO" syncable="YES"/>
<relationship name="games" toMany="YES" deletionRule="Nullify" destinationEntity="Game" inverseName="gameCollections" inverseEntity="Game" syncable="YES"/>
<uniquenessConstraints>
<uniquenessConstraint>
<constraint value="identifier"/>
</uniquenessConstraint>
</uniquenessConstraints>
</entity>
<entity name="GameControllerInputMapping" representedClassName="GameControllerInputMapping" syncable="YES">
<attribute name="deltaCoreInputMapping" attributeType="Transformable" valueTransformerName="GameControllerInputMappingTransformer" syncable="YES">
<userInfo>
<entry key="attributeValueClassName" value="Any"/>
</userInfo>
</attribute>
<attribute name="gameControllerInputType" attributeType="Transformable" valueTransformerName="" syncable="YES">
<userInfo>
<entry key="attributeValueClassName" value="GameControllerInputType"/>
</userInfo>
</attribute>
<attribute name="gameType" attributeType="Transformable" syncable="YES">
<userInfo>
<entry key="attributeValueClassName" value="GameType"/>
</userInfo>
</attribute>
<attribute name="playerIndex" attributeType="Integer 16" defaultValueString="0" usesScalarValueType="NO" syncable="YES"/>
<uniquenessConstraints>
<uniquenessConstraint>
<constraint value="gameControllerInputType"/>
<constraint value="gameType"/>
<constraint value="playerIndex"/>
</uniquenessConstraint>
</uniquenessConstraints>
</entity>
<entity name="SaveState" representedClassName="SaveState" versionHashModifier="quick" syncable="YES">
<attribute name="creationDate" attributeType="Date" usesScalarValueType="NO" syncable="YES"/>
<attribute name="filename" attributeType="String" syncable="YES">
<userInfo>
<entry key="attributeValueClassName" value="NSURL"/>
</userInfo>
</attribute>
<attribute name="identifier" attributeType="String" syncable="YES"/>
<attribute name="modifiedDate" attributeType="Date" usesScalarValueType="NO" syncable="YES"/>
<attribute name="name" optional="YES" attributeType="String" syncable="YES"/>
<attribute name="type" attributeType="Integer 16" defaultValueString="1" usesScalarValueType="NO" syncable="YES">
<userInfo>
<entry key="attributeValueScalarType" value="SaveStateType"/>
</userInfo>
</attribute>
<relationship name="game" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Game" inverseName="saveStates" inverseEntity="Game" syncable="YES"/>
<relationship name="previewGame" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Game" inverseName="previewSaveState" inverseEntity="Game" syncable="YES"/>
<uniquenessConstraints>
<uniquenessConstraint>
<constraint value="identifier"/>
</uniquenessConstraint>
</uniquenessConstraints>
</entity>
<elements>
<element name="Cheat" positionX="-198" positionY="-63" width="128" height="165"/>
<element name="ControllerSkin" positionX="-387" positionY="90" width="128" height="135"/>
<element name="Game" positionX="-378" positionY="-54" width="128" height="180"/>
<element name="GameCollection" positionX="-585" positionY="-27" width="128" height="90"/>
<element name="GameControllerInputMapping" positionX="-387" positionY="90" width="128" height="105"/>
<element name="SaveState" positionX="-198" positionY="113" width="128" height="165"/>
</elements>
</model>

View File

@ -13,6 +13,7 @@ import DeltaCore
@objc public enum SaveStateType: Int16
{
case auto
case quick
case general
case locked
}
@ -78,4 +79,14 @@ public class SaveState: _SaveState, SaveStateProtocol
print(error)
}
}
class func fetchRequest(for game: Game, type: SaveStateType) -> NSFetchRequest<SaveState>
{
let predicate = NSPredicate(format: "%K == %@ AND %K == %d", #keyPath(SaveState.game), game, #keyPath(SaveState.type), type.rawValue)
let fetchRequest: NSFetchRequest<SaveState> = SaveState.fetchRequest()
fetchRequest.predicate = predicate
return fetchRequest
}
}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,25 @@
//
// SaveStateMigrationPolicy.swift
// Delta
//
// Created by Riley Testut on 9/28/17.
// Copyright © 2017 Riley Testut. All rights reserved.
//
import UIKit
@objc(SaveStateToSaveStateMigrationPolicy)
class SaveStateToSaveStateMigrationPolicy: NSEntityMigrationPolicy
{
@objc(migrateSaveStateType:)
func migrateSaveStateType(_ rawValue: NSNumber) -> NSNumber
{
switch rawValue.intValue
{
case 0: return NSNumber(value: SaveStateType.auto.rawValue)
case 1: return NSNumber(value: SaveStateType.general.rawValue)
case 2: return NSNumber(value: SaveStateType.locked.rawValue)
default: return rawValue
}
}
}

View File

@ -15,8 +15,8 @@ public extension GameControllerInputType
enum ActionInput: String
{
case saveState
case loadState
case quickSave
case quickLoad
case fastForward
}

View File

@ -139,13 +139,41 @@ class GameViewController: DeltaCore.GameViewController
{
super.gameController(gameController, didActivate: input)
guard self.isSelectingSustainedButtons else { return }
guard let pausingGameController = self.pausingGameController, gameController == pausingGameController else { return }
if input != StandardGameControllerInput.menu
if self.isSelectingSustainedButtons
{
gameController.sustain(input)
guard let pausingGameController = self.pausingGameController, gameController == pausingGameController else { return }
if input != StandardGameControllerInput.menu
{
gameController.sustain(input)
}
}
else if self.emulatorCore?.state == .running
{
guard let actionInput = ActionInput(input: input) else { return }
switch actionInput
{
case .quickSave: self.performQuickSaveAction()
case .quickLoad: self.performQuickLoadAction()
case .fastForward: self.performFastForwardAction(activate: true)
}
}
}
override func gameController(_ gameController: GameController, didDeactivate input: Input)
{
super.gameController(gameController, didDeactivate: input)
guard !self.isSelectingSustainedButtons else { return }
guard let actionInput = ActionInput(input: input) else { return }
switch actionInput
{
case .quickSave: break
case .quickLoad: break
case .fastForward: self.performFastForwardAction(activate: false)
}
}
}
@ -251,8 +279,7 @@ extension GameViewController
pauseViewController.fastForwardItem?.isSelected = (self.emulatorCore?.rate != self.emulatorCore?.deltaCore.supportedRates.lowerBound)
pauseViewController.fastForwardItem?.action = { [unowned self] item in
guard let emulatorCore = self.emulatorCore else { return }
emulatorCore.rate = item.isSelected ? emulatorCore.deltaCore.supportedRates.upperBound : emulatorCore.deltaCore.supportedRates.lowerBound
self.performFastForwardAction(activate: item.isSelected)
}
pauseViewController.sustainButtonsItem?.isSelected = gameController.sustainedInputs.count > 0
@ -441,42 +468,37 @@ extension GameViewController: SaveStatesViewControllerDelegate
let game = backgroundContext.object(with: game.objectID) as! Game
let predicate = NSPredicate(format: "%K == %d AND %K == %@", #keyPath(SaveState.type), SaveStateType.auto.rawValue, #keyPath(SaveState.game), game)
let fetchRequest: NSFetchRequest<SaveState> = SaveState.fetchRequest()
fetchRequest.predicate = predicate
let fetchRequest = SaveState.fetchRequest(for: game, type: .auto)
fetchRequest.sortDescriptors = [NSSortDescriptor(key: #keyPath(SaveState.creationDate), ascending: true)]
var saveStates: [SaveState]? = nil
do
{
saveStates = try fetchRequest.execute()
let saveStates = try fetchRequest.execute()
if let saveState = saveStates.first, saveStates.count >= 2
{
// If there are two or more auto save states, update the oldest one
self.update(saveState, with: self.pausedSaveState)
// Tiny hack: SaveStatesViewController sorts save states by creation date, so we update the creation date too
// Simpler than deleting old save states ¯\_()_/¯
saveState.creationDate = saveState.modifiedDate
}
else
{
// Otherwise, create a new one
let saveState = SaveState.insertIntoManagedObjectContext(backgroundContext)
saveState.type = .auto
saveState.game = game
self.update(saveState, with: self.pausedSaveState)
}
}
catch
{
print(error)
}
if let saveStates = saveStates, let saveState = saveStates.first, saveStates.count >= 2
{
// If there are two or more auto save states, update the oldest one
self.update(saveState, with: self.pausedSaveState)
// Tiny hack; SaveStatesViewController sorts save states by creation date, so we update the creation date too
// Simpler than deleting old save states ¯\_()_/¯
saveState.creationDate = saveState.modifiedDate
}
else
{
// Otherwise, create a new one
let saveState = SaveState.insertIntoManagedObjectContext(backgroundContext)
saveState.type = .auto
saveState.game = game
self.update(saveState, with: self.pausedSaveState)
}
backgroundContext.saveWithErrorLogging()
}
}
@ -535,25 +557,14 @@ extension GameViewController: SaveStatesViewControllerDelegate
}
}
//MARK: - SaveStatesViewControllerDelegate
func saveStatesViewController(_ saveStatesViewController: SaveStatesViewController, updateSaveState saveState: SaveState)
fileprivate func load(_ saveState: SaveStateProtocol)
{
let updatingExistingSaveState = FileManager.default.fileExists(atPath: saveState.fileURL.path)
let isRunning = (self.emulatorCore?.state == .running)
self.update(saveState)
// Dismiss if updating an existing save state.
// If creating a new one, don't dismiss.
if updatingExistingSaveState
if isRunning
{
self.pauseViewController?.dismiss()
self.pauseEmulation()
}
}
func saveStatesViewController(_ saveStatesViewController: SaveStatesViewController, loadSaveState saveState: SaveStateProtocol)
{
self._isLoadingSaveState = true
// If we're loading the auto save state, we need to create a temporary copy of saveState.
// Then, we update the auto save state, but load our copy so everything works out.
@ -597,6 +608,34 @@ extension GameViewController: SaveStatesViewControllerDelegate
print(error)
}
if isRunning
{
self.resumeEmulation()
}
}
//MARK: - SaveStatesViewControllerDelegate
func saveStatesViewController(_ saveStatesViewController: SaveStatesViewController, updateSaveState saveState: SaveState)
{
let updatingExistingSaveState = FileManager.default.fileExists(atPath: saveState.fileURL.path)
self.update(saveState)
// Dismiss if updating an existing save state.
// If creating a new one, don't dismiss.
if updatingExistingSaveState
{
self.pauseViewController?.dismiss()
}
}
func saveStatesViewController(_ saveStatesViewController: SaveStatesViewController, loadSaveState saveState: SaveStateProtocol)
{
self._isLoadingSaveState = true
self.load(saveState)
self.pauseViewController?.dismiss()
}
}
@ -666,6 +705,78 @@ private extension GameViewController
}
}
//MARK: - Action Inputs -
/// Action Inputs
extension GameViewController
{
func performQuickSaveAction()
{
guard let game = self.game as? Game else { return }
let backgroundContext = DatabaseManager.shared.newBackgroundContext()
backgroundContext.performAndWait {
let game = backgroundContext.object(with: game.objectID) as! Game
let fetchRequest = SaveState.fetchRequest(for: game, type: .quick)
do
{
if let quickSaveState = try fetchRequest.execute().first
{
self.update(quickSaveState)
}
else
{
let saveState = SaveState(context: backgroundContext)
saveState.type = .quick
saveState.game = game
self.update(saveState)
}
}
catch
{
print(error)
}
backgroundContext.saveWithErrorLogging()
}
}
func performQuickLoadAction()
{
guard let game = self.game as? Game else { return }
let fetchRequest = SaveState.fetchRequest(for: game, type: .quick)
do
{
if let quickSaveState = try fetchRequest.execute().first
{
self.load(quickSaveState)
}
}
catch
{
print(error)
}
}
func performFastForwardAction(activate: Bool)
{
guard let emulatorCore = self.emulatorCore else { return }
if activate
{
emulatorCore.rate = emulatorCore.deltaCore.supportedRates.upperBound
}
else
{
emulatorCore.rate = emulatorCore.deltaCore.supportedRates.lowerBound
}
}
}
//MARK: - GameViewControllerDelegate -
/// GameViewControllerDelegate
extension GameViewController: GameViewControllerDelegate

View File

@ -29,6 +29,7 @@ extension SaveStatesViewController
enum Section: Int
{
case auto
case quick
case general
case locked
}
@ -268,6 +269,7 @@ private extension SaveStatesViewController
switch section
{
case .auto: title = NSLocalizedString("Auto Save", comment: "")
case .quick: title = NSLocalizedString("Quick Save", comment: "")
case .general: title = NSLocalizedString("General", comment: "")
case .locked: title = NSLocalizedString("Locked", comment: "")
}
@ -310,6 +312,7 @@ private extension SaveStatesViewController
let game = backgroundContext.object(with: self.game.objectID) as! Game
saveState = SaveState.insertIntoManagedObjectContext(backgroundContext)
saveState.type = .general
saveState.game = game
}
@ -480,6 +483,7 @@ private extension SaveStatesViewController
switch saveState.type
{
case .auto: break
case .quick: break
case .general:
let lockAction = Action(title: NSLocalizedString("Lock", comment: ""), style: .default, action: { [unowned self] action in
self.lockSaveState(saveState)
@ -643,11 +647,11 @@ extension SaveStatesViewController
{
case .saving:
let section = self.correctedSectionForSectionIndex((indexPath as NSIndexPath).section)
let section = self.correctedSectionForSectionIndex(indexPath.section)
switch section
{
case .auto: break
case .general:
case .quick, .general:
let backgroundContext = DatabaseManager.shared.newBackgroundContext()
backgroundContext.performAndWait() {
let temporarySaveState = backgroundContext.object(with: saveState.objectID) as! SaveState

View File

@ -31,7 +31,7 @@ class ControllerInputsViewController: UIViewController
fileprivate lazy var managedObjectContext: NSManagedObjectContext = DatabaseManager.shared.newBackgroundContext()
fileprivate var inputMappings = [System: GameControllerInputMapping]()
fileprivate let supportedActionInputs: [ActionInput] = [.saveState, .loadState, .fastForward]
fileprivate let supportedActionInputs: [ActionInput] = [.quickSave, .quickLoad, .fastForward]
fileprivate var gameViewController: DeltaCore.GameViewController!
fileprivate var actionsMenuViewController: GridMenuViewController!
@ -188,13 +188,13 @@ private extension ControllerInputsViewController
switch input
{
case .saveState:
case .quickSave:
image = #imageLiteral(resourceName: "SaveSaveState")
text = NSLocalizedString("Save State", comment: "")
text = NSLocalizedString("Quick Save", comment: "")
case .loadState:
case .quickLoad:
image = #imageLiteral(resourceName: "LoadSaveState")
text = NSLocalizedString("Load State", comment: "")
text = NSLocalizedString("Quick Load", comment: "")
case .fastForward:
image = #imageLiteral(resourceName: "FastForward")