Adds support for syncing GameSaves

This commit is contained in:
Riley Testut 2019-01-29 15:07:46 -08:00
parent dbe298f2a7
commit 3bd0a35c61
13 changed files with 167 additions and 11 deletions

@ -1 +1 @@
Subproject commit 911d36bd01964ad7aeb0b4a37610402fd9845310
Subproject commit 8e6c824c3bc847cc4749e53256b42f9a9207c20f

@ -1 +1 @@
Subproject commit 6da27f9b694e886d6c9805ba54848b0f0a922598
Subproject commit 473474019bd2e3be623cd593da4c1e8c4e51fb80

View File

@ -129,6 +129,8 @@
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 */; };
BFE593CA21F3F8B7003412A6 /* GameSave.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFE593C921F3F8B7003412A6 /* GameSave.swift */; };
BFE593CC21F3F8C2003412A6 /* _GameSave.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFE593CB21F3F8C2003412A6 /* _GameSave.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, ); }; };
BFEF24F31F7DD4FD00454C62 /* SaveStateMigrationPolicy.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFEF24F21F7DD4FB00454C62 /* SaveStateMigrationPolicy.swift */; };
@ -281,6 +283,8 @@
BFDD04F01D5E2C27002D450E /* GameCollectionViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GameCollectionViewController.swift; sourceTree = "<group>"; };
BFE0229F1F5B577D0052D888 /* PopoverMenuButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PopoverMenuButton.swift; sourceTree = "<group>"; };
BFE4269D1D9C68E600DC913F /* SaveStatesStoryboardSegue.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SaveStatesStoryboardSegue.swift; sourceTree = "<group>"; };
BFE593C921F3F8B7003412A6 /* GameSave.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GameSave.swift; sourceTree = "<group>"; };
BFE593CB21F3F8C2003412A6 /* _GameSave.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = _GameSave.swift; sourceTree = "<group>"; };
BFEC732C1AAECC4A00650035 /* Roxas.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Roxas.framework; sourceTree = BUILT_PRODUCTS_DIR; };
BFEF24F21F7DD4FB00454C62 /* SaveStateMigrationPolicy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SaveStateMigrationPolicy.swift; sourceTree = "<group>"; };
BFF0742B1E9DC17500ACDF4A /* GBCDeltaCore.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = GBCDeltaCore.framework; sourceTree = BUILT_PRODUCTS_DIR; };
@ -471,6 +475,7 @@
BF5942791E09BC830051894B /* Game.swift */,
BF59427A1E09BC830051894B /* GameCollection.swift */,
BF6EE5EA1F7C5F8F0051AD6C /* GameControllerInputMapping.swift */,
BFE593C921F3F8B7003412A6 /* GameSave.swift */,
BF59427B1E09BC830051894B /* SaveState.swift */,
);
path = Human;
@ -484,6 +489,7 @@
BF5942831E09BC8B0051894B /* _Game.swift */,
BF5942841E09BC8B0051894B /* _GameCollection.swift */,
BF6EE5E81F7C5F860051AD6C /* _GameControllerInputMapping.swift */,
BFE593CB21F3F8C2003412A6 /* _GameSave.swift */,
BF5942851E09BC8B0051894B /* _SaveState.swift */,
);
path = Machine;
@ -957,6 +963,7 @@
BF525EEA1FF6CD12004AA849 /* DeepLink.swift in Sources */,
BF8A334621A4926F00A42FD4 /* GameSyncStatusViewController.swift in Sources */,
BF59427E1E09BC830051894B /* Game.swift in Sources */,
BFE593CC21F3F8C2003412A6 /* _GameSave.swift in Sources */,
BF63A1A321A4AAAE00EE8F61 /* RecordSyncStatusViewController.swift in Sources */,
BFAA1FED1B8AA4FA00495943 /* Settings.swift in Sources */,
BFA0D1271D3AE1F600565894 /* GameViewController.swift in Sources */,
@ -965,6 +972,7 @@
BF59428E1E09BCFB0051894B /* ImportController.swift in Sources */,
BF13A7581D5D2FD9000BB055 /* EmulatorCore+Cheats.swift in Sources */,
BF48F74E219A16DA00BC2FC1 /* SyncingServicesViewController.swift in Sources */,
BFE593CA21F3F8B7003412A6 /* GameSave.swift in Sources */,
BF63A1B521A4B76E00EE8F61 /* RecordVersionsViewController.swift in Sources */,
BF6424831F5B8F3F00D6AB44 /* ListMenuViewController.swift in Sources */,
BF1020E31F95B05B00313182 /* DeltaToDelta2.xcmappingmodel in Sources */,

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="14460.32" systemVersion="18B75" minimumToolsVersion="Xcode 7.0" sourceLanguage="Objective-C" userDefinedModelVersionIdentifier="1.0">
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="14460.32" systemVersion="18C54" 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"/>
@ -62,6 +62,7 @@
</attribute>
<relationship name="cheats" toMany="YES" deletionRule="Cascade" destinationEntity="Cheat" inverseName="game" inverseEntity="Cheat" syncable="YES"/>
<relationship name="gameCollection" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="GameCollection" inverseName="games" inverseEntity="GameCollection" syncable="YES"/>
<relationship name="gameSave" optional="YES" maxCount="1" deletionRule="Cascade" destinationEntity="GameSave" inverseName="game" inverseEntity="GameSave" 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>
@ -109,6 +110,16 @@
</uniquenessConstraint>
</uniquenessConstraints>
</entity>
<entity name="GameSave" representedClassName="GameSave" syncable="YES">
<attribute name="identifier" attributeType="String" syncable="YES"/>
<attribute name="modifiedDate" attributeType="Date" usesScalarValueType="NO" syncable="YES"/>
<relationship name="game" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Game" inverseName="gameSave" inverseEntity="Game" syncable="YES"/>
<uniquenessConstraints>
<uniquenessConstraint>
<constraint value="identifier"/>
</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">
@ -135,9 +146,10 @@
<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="195"/>
<element name="Game" positionX="-378" positionY="-54" width="128" height="210"/>
<element name="GameCollection" positionX="-585" positionY="-27" width="128" height="90"/>
<element name="GameControllerInputMapping" positionX="-387" positionY="90" width="128" height="120"/>
<element name="SaveState" positionX="-198" positionY="113" width="128" height="165"/>
<element name="GameSave" positionX="-387" positionY="90" width="128" height="90"/>
</elements>
</model>

View File

@ -0,0 +1,60 @@
//
// GameSave.swift
// Delta
//
// Created by Riley Testut on 8/30/16.
// Copyright (c) 2016 Riley Testut. All rights reserved.
//
import Foundation
import Harmony
@objc(GameSave)
public class GameSave: _GameSave
{
public override func awakeFromInsert()
{
super.awakeFromInsert()
self.modifiedDate = Date()
}
}
extension GameSave: Syncable
{
public static var syncablePrimaryKey: AnyKeyPath {
return \GameSave.identifier
}
public var syncableKeys: Set<AnyKeyPath> {
return [\GameSave.modifiedDate]
}
public var syncableRelationships: Set<AnyKeyPath> {
return [\GameSave.game]
}
public var syncableFiles: Set<File> {
guard let game = self.game else { return [] }
var files: Set<File> = [File(identifier: "gameSave", fileURL: game.gameSaveURL)]
if game.type == .gbc
{
let gameTimeSaveURL = game.gameSaveURL.deletingPathExtension().appendingPathExtension("rtc")
files.insert(File(identifier: "gameTimeSave", fileURL: gameTimeSaveURL))
}
return files
}
public var syncableMetadata: [HarmonyMetadataKey : String] {
guard let game = self.game else { return [:] }
return [.gameID: game.identifier, .gameName: game.name]
}
public var syncableLocalizedName: String? {
return self.game?.name
}
}

View File

@ -32,6 +32,8 @@ public class _Game: NSManagedObject
@NSManaged public var gameCollection: GameCollection?
@NSManaged public var gameSave: GameSave?
@NSManaged public var previewSaveState: SaveState?
@NSManaged public var saveStates: Set<SaveState>

View File

@ -0,0 +1,26 @@
// DO NOT EDIT. This file is machine-generated and constantly overwritten.
// Make changes to GameSave.swift instead.
import Foundation
import CoreData
import DeltaCore
public class _GameSave: NSManagedObject
{
@nonobjc public class func fetchRequest() -> NSFetchRequest<GameSave> {
return NSFetchRequest<GameSave>(entityName: "GameSave")
}
// MARK: - Properties
@NSManaged public var identifier: String
@NSManaged public var modifiedDate: Date
// MARK: - Relationships
@NSManaged public var game: Game?
}

View File

@ -66,6 +66,8 @@ class GameViewController: DeltaCore.GameViewController
let game = self.game as? Game
NotificationCenter.default.addObserver(self, selector: #selector(GameViewController.managedObjectContextDidChange(with:)), name: .NSManagedObjectContextObjectsDidChange, object: game?.managedObjectContext)
self.emulatorCore?.saveHandler = { [weak self] _ in self?.updateGameSave() }
self.updateControllerSkin()
self.updateControllers()
}
@ -501,6 +503,40 @@ private extension GameViewController
}
}
//MARK: - Game Saves -
/// Game Saves
private extension GameViewController
{
func updateGameSave()
{
guard let game = self.game as? Game else { return }
DatabaseManager.shared.performBackgroundTask { (context) in
let game = context.object(with: game.objectID) as! Game
if let gameSave = game.gameSave
{
gameSave.modifiedDate = Date()
}
else
{
let gameSave = GameSave(context: context)
gameSave.identifier = game.identifier
game.gameSave = gameSave
}
do
{
try context.save()
}
catch
{
print("Error updating game save.", error)
}
}
}
}
//MARK: - Save States -
/// Save States
extension GameViewController: SaveStatesViewControllerDelegate

View File

@ -85,11 +85,18 @@ private extension GameSyncStatusViewController
}
}
let gameDataSource = RSTArrayTableViewDataSource<Game>(items: [self.game])
gameDataSource.cellConfigurationHandler = { (cell, game, indexPath) in
let gameDataSource = RSTArrayTableViewDataSource<NSManagedObject>(items: [self.game, self.game.gameSave].compactMap { $0 })
gameDataSource.cellConfigurationHandler = { (cell, item, indexPath) in
if item is Game
{
cell.textLabel?.text = NSLocalizedString("Game", comment: "")
}
else
{
cell.textLabel?.text = NSLocalizedString("Game Save", comment: "")
}
configure(cell, recordedObject: game)
configure(cell, recordedObject: item)
}
let saveStatesFetchRequest = SaveState.fetchRequest() as NSFetchRequest<SaveState>
@ -129,7 +136,7 @@ private extension GameSyncStatusViewController
do
{
let recordedObjects = ([self.game!] + Array(self.game.saveStates) + Array(self.game.cheats)) as! [SyncableManagedObject]
let recordedObjects = ([self.game, self.game.gameSave].compactMap { $0 } + Array(self.game.saveStates) + Array(self.game.cheats)) as! [SyncableManagedObject]
let records = try SyncManager.shared.recordController.fetchRecords(for: recordedObjects)
for record in records
@ -156,7 +163,7 @@ extension GameSyncStatusViewController
switch Section.allCases[section]
{
case .game: return NSLocalizedString("Game", comment: "")
case .game: return nil
case .saveStates: return NSLocalizedString("Save States", comment: "")
case .cheats: return NSLocalizedString("Cheats", comment: "")
}

View File

@ -129,6 +129,7 @@ private extension SyncStatusViewController
case let game as Game: conflictedGame = game
case let saveState as SaveState: conflictedGame = saveState.game
case let cheat as Cheat: conflictedGame = cheat.game
case let gameSave as GameSave: conflictedGame = gameSave.game
default: conflictedGame = nil
}

View File

@ -19,6 +19,7 @@ extension SyncManager
case saveState = "SaveState"
case controllerSkin = "ControllerSkin"
case gameControllerInputMapping = "GameControllerInputMapping"
case gameSave = "GameSave"
var localizedName: String {
switch self
@ -29,6 +30,7 @@ extension SyncManager
case .saveState: return NSLocalizedString("Save State", comment: "")
case .controllerSkin: return NSLocalizedString("Controller Skin", comment: "")
case .gameControllerInputMapping: return NSLocalizedString("Game Controller Input Mapping", comment: "")
case .gameSave: return NSLocalizedString("Game Save", comment: "")
}
}
}

View File

@ -119,6 +119,7 @@ private extension SyncResultViewController
switch recordType
{
case .game: title = NSLocalizedString("Game", comment: "")
case .gameSave: title = NSLocalizedString("Game Save", comment: "")
case .saveState, .cheat, .controllerSkin, .gameCollection, .gameControllerInputMapping: title = error.record.localizedName ?? recordType.localizedName
}
@ -210,6 +211,7 @@ private extension SyncResultViewController
switch recordType
{
case .game: group = .game(error.record.recordID)
case .gameSave: group = .game(error.record.recordID)
case .gameCollection: group = .gameCollection
case .controllerSkin: group = .controllerSkin
case .gameControllerInputMapping: group = .gameControllerInputMapping

2
External/Harmony vendored

@ -1 +1 @@
Subproject commit 920a34311389cd4e3248b62f27f594058539b28b
Subproject commit 935be63ed805013b8e16191036720b5e32be55e7