From 94cbdbe159020fb1791369089e2d6d843d2d6db2 Mon Sep 17 00:00:00 2001 From: Riley Testut Date: Thu, 28 Sep 2017 12:42:44 -0700 Subject: [PATCH] Persists customized input mappings between app launches --- Cores/DeltaCore | 2 +- Delta.xcodeproj/project.pbxproj | 20 +++ .../Human/GameControllerInputMapping.swift | 77 ++++++++++++ .../Machine/_GameControllerInputMapping.swift | 28 +++++ .../Database/Model/Model.xcdatamodel/contents | 28 ++++- ...ameControllerInputMappingTransformer.swift | 61 +++++++++ Delta/Emulation/GameViewController.swift | 48 +++---- .../ControllerInputsViewController.swift | 118 +++++++++++------- .../ControllersSettingsViewController.swift | 2 +- Delta/Settings/Settings.storyboard | 4 +- 10 files changed, 315 insertions(+), 73 deletions(-) create mode 100644 Delta/Database/Model/Human/GameControllerInputMapping.swift create mode 100644 Delta/Database/Model/Machine/_GameControllerInputMapping.swift create mode 100644 Delta/Database/Model/Transformers/GameControllerInputMappingTransformer.swift diff --git a/Cores/DeltaCore b/Cores/DeltaCore index f33f8bd..7681a93 160000 --- a/Cores/DeltaCore +++ b/Cores/DeltaCore @@ -1 +1 @@ -Subproject commit f33f8bd91a9e0b41ba55b9aa8370397dd7e7f809 +Subproject commit 7681a93515aea5c7642a56c888fe5da3aa4ea09f diff --git a/Delta.xcodeproj/project.pbxproj b/Delta.xcodeproj/project.pbxproj index 7c280a9..ec9623b 100644 --- a/Delta.xcodeproj/project.pbxproj +++ b/Delta.xcodeproj/project.pbxproj @@ -79,6 +79,8 @@ BF6BF31C1EB821A0008E83CD /* GamesDatabaseImportOption.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF6BF31B1EB821A0008E83CD /* GamesDatabaseImportOption.swift */; }; BF6BF3211EB82362008E83CD /* GamesDatabase.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = BF6BF31F1EB82362008E83CD /* GamesDatabase.storyboard */; }; BF6BF3271EB87EB8008E83CD /* PhotoLibraryImportOption.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF6BF3261EB87EB8008E83CD /* PhotoLibraryImportOption.swift */; }; + BF6EE5E91F7C5F860051AD6C /* _GameControllerInputMapping.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF6EE5E81F7C5F860051AD6C /* _GameControllerInputMapping.swift */; }; + BF6EE5EB1F7C5F8F0051AD6C /* GameControllerInputMapping.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF6EE5EA1F7C5F8F0051AD6C /* GameControllerInputMapping.swift */; }; BF70798C1B6B464B0019077C /* ZipZap.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BF70798B1B6B464B0019077C /* ZipZap.framework */; }; BF70798D1B6B464B0019077C /* ZipZap.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = BF70798B1B6B464B0019077C /* ZipZap.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; BF797A2D1C2D339F00F1A000 /* UILabel+FontSize.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF797A2C1C2D339F00F1A000 /* UILabel+FontSize.swift */; }; @@ -109,6 +111,7 @@ BFEC732E1AAECC4A00650035 /* Roxas.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = BFEC732C1AAECC4A00650035 /* Roxas.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 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 */; }; BFF93AA01E0FB036005EC865 /* InputStreamOutputWriter.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFF93A9F1E0FB036005EC865 /* InputStreamOutputWriter.swift */; }; BFFA4C091E8A24D600D87934 /* GameMetadataTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFFA4C081E8A24D600D87934 /* GameMetadataTableViewCell.swift */; }; BFFA71DD1AAC406100EE9DD1 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFFA71DC1AAC406100EE9DD1 /* AppDelegate.swift */; }; @@ -202,6 +205,7 @@ BF6424841F5CBDC900D6AB44 /* UIView+ParentViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+ParentViewController.swift"; sourceTree = ""; }; BF6866161DCAC8B900BF2D06 /* ControllerSkin+Configuring.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "ControllerSkin+Configuring.swift"; sourceTree = ""; }; BF696B7F1D9B2B02009639E0 /* Theme.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Theme.swift; path = Theming/Theme.swift; sourceTree = ""; }; + BF6B82A41F7CC2A300042BFB /* GameControllerInputMappingTransformer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = GameControllerInputMappingTransformer.swift; path = Database/Model/Transformers/GameControllerInputMappingTransformer.swift; sourceTree = ""; }; BF6BB2451BB73FE800CCF94A /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; BF6BF3121EB7E47F008E83CD /* ImportOption.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ImportOption.swift; path = Importing/ImportOption.swift; sourceTree = ""; }; BF6BF3171EB82111008E83CD /* iTunesImportOption.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = iTunesImportOption.swift; path = "Importing/Import Options/iTunesImportOption.swift"; sourceTree = ""; }; @@ -209,6 +213,8 @@ BF6BF31B1EB821A0008E83CD /* GamesDatabaseImportOption.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = GamesDatabaseImportOption.swift; path = "Importing/Import Options/GamesDatabaseImportOption.swift"; sourceTree = ""; }; BF6BF3201EB82362008E83CD /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/GamesDatabase.storyboard; sourceTree = ""; }; BF6BF3261EB87EB8008E83CD /* PhotoLibraryImportOption.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = PhotoLibraryImportOption.swift; path = "Importing/Import Options/PhotoLibraryImportOption.swift"; sourceTree = ""; }; + BF6EE5E81F7C5F860051AD6C /* _GameControllerInputMapping.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = _GameControllerInputMapping.swift; path = Database/Model/Machine/_GameControllerInputMapping.swift; sourceTree = ""; }; + BF6EE5EA1F7C5F8F0051AD6C /* GameControllerInputMapping.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = GameControllerInputMapping.swift; path = Database/Model/Human/GameControllerInputMapping.swift; sourceTree = ""; }; BF70798B1B6B464B0019077C /* ZipZap.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = ZipZap.framework; sourceTree = BUILT_PRODUCTS_DIR; }; BF797A2C1C2D339F00F1A000 /* UILabel+FontSize.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UILabel+FontSize.swift"; sourceTree = ""; }; BF7AE8041C2E858400B1B5BC /* GridMenuViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = GridMenuViewController.swift; path = "Pause Menu/GridMenuViewController.swift"; sourceTree = ""; }; @@ -378,6 +384,7 @@ BF5942721E09BC700051894B /* Model.xcdatamodel */, BF5942741E09BC740051894B /* Human */, BF5942751E09BC780051894B /* Machine */, + BF6B82A31F7CC29A00042BFB /* Transformers */, BF5942761E09BC7C0051894B /* Misc */, ); name = Model; @@ -390,6 +397,7 @@ BF5942781E09BC830051894B /* ControllerSkin.swift */, BF5942791E09BC830051894B /* Game.swift */, BF59427A1E09BC830051894B /* GameCollection.swift */, + BF6EE5EA1F7C5F8F0051AD6C /* GameControllerInputMapping.swift */, BF59427B1E09BC830051894B /* SaveState.swift */, ); name = Human; @@ -402,6 +410,7 @@ BF5942821E09BC8B0051894B /* _ControllerSkin.swift */, BF5942831E09BC8B0051894B /* _Game.swift */, BF5942841E09BC8B0051894B /* _GameCollection.swift */, + BF6EE5E81F7C5F860051AD6C /* _GameControllerInputMapping.swift */, BF5942851E09BC8B0051894B /* _SaveState.swift */, ); name = Machine; @@ -433,6 +442,14 @@ name = Theming; sourceTree = ""; }; + BF6B82A31F7CC29A00042BFB /* Transformers */ = { + isa = PBXGroup; + children = ( + BF6B82A41F7CC2A300042BFB /* GameControllerInputMappingTransformer.swift */, + ); + name = Transformers; + sourceTree = ""; + }; BF6BF3161EB820F4008E83CD /* Import Options */ = { isa = PBXGroup; children = ( @@ -811,6 +828,7 @@ BFC6F7B81F435BC500221B96 /* Input+Display.swift in Sources */, BF59426A1E09BBD00051894B /* GridCollectionViewCell.swift in Sources */, BF6BF3181EB82111008E83CD /* iTunesImportOption.swift in Sources */, + BFF6452E1F7CC5060056533E /* GameControllerInputMappingTransformer.swift in Sources */, BF59427C1E09BC830051894B /* Cheat.swift in Sources */, BFBAB2E31EB685A2004E0B0E /* DeltaCoreProtocol+Delta.swift in Sources */, BF3540081C5DAFAD00C1184C /* PauseTransitionCoordinator.swift in Sources */, @@ -859,9 +877,11 @@ BF797A2D1C2D339F00F1A000 /* UILabel+FontSize.swift in Sources */, BF80E1D21F13117000847008 /* ControllerInputsViewController.swift in Sources */, BF5942641E09BBB10051894B /* LoadControllerSkinImageOperation.swift in Sources */, + BF6EE5E91F7C5F860051AD6C /* _GameControllerInputMapping.swift in Sources */, BF5942871E09BC8B0051894B /* _ControllerSkin.swift in Sources */, BF95E2791E4982A10030E7AD /* GamesDatabaseBrowserViewController.swift in Sources */, BFD097211D3A01B8005A44C2 /* SaveStatesViewController.swift in Sources */, + BF6EE5EB1F7C5F8F0051AD6C /* GameControllerInputMapping.swift in Sources */, BF3540021C5DA3D500C1184C /* PauseStoryboardSegue.swift in Sources */, BF107EC41BF413F000E0C32C /* GamesViewController.swift in Sources */, BF59426F1E09BC5D0051894B /* DatabaseManager.swift in Sources */, diff --git a/Delta/Database/Model/Human/GameControllerInputMapping.swift b/Delta/Database/Model/Human/GameControllerInputMapping.swift new file mode 100644 index 0000000..3a44b4e --- /dev/null +++ b/Delta/Database/Model/Human/GameControllerInputMapping.swift @@ -0,0 +1,77 @@ +// +// GameControllerInputMapping.swift +// Delta +// +// Created by Riley Testut on 8/30/16. +// Copyright (c) 2016 Riley Testut. All rights reserved. +// + +import Foundation + +import DeltaCore + +@objc(GameControllerInputMapping) +public class GameControllerInputMapping: _GameControllerInputMapping +{ + fileprivate var inputMapping: DeltaCore.GameControllerInputMapping { + get { return self.deltaCoreInputMapping as! DeltaCore.GameControllerInputMapping } + set { self.deltaCoreInputMapping = newValue } + } + + public convenience init(inputMapping: DeltaCore.GameControllerInputMapping, context: NSManagedObjectContext) + { + self.init(entity: GameControllerInputMapping.entity(), insertInto: context) + + self.inputMapping = inputMapping + } +} + +extension GameControllerInputMapping +{ + class func inputMapping(for gameController: GameController, gameType: GameType, in managedObjectContext: NSManagedObjectContext) -> GameControllerInputMapping? + { + guard let playerIndex = gameController.playerIndex else { + return nil + } + + let fetchRequest: NSFetchRequest = GameControllerInputMapping.fetchRequest() + fetchRequest.returnsObjectsAsFaults = false + fetchRequest.predicate = NSPredicate(format: "%K == %@ AND %K == %@ AND %K == %d", #keyPath(GameControllerInputMapping.gameControllerInputType), gameController.inputType.rawValue, #keyPath(GameControllerInputMapping.gameType), gameType.rawValue, #keyPath(GameControllerInputMapping.playerIndex), playerIndex) + + do + { + let inputMappings = try managedObjectContext.fetch(fetchRequest) + + let inputMapping = inputMappings.first + return inputMapping + } + catch + { + print(error) + + return nil + } + } +} + +extension GameControllerInputMapping: GameControllerInputMappingProtocol +{ + var name: String? { + get { return self.inputMapping.name } + set { self.inputMapping.name = newValue } + } + + var supportedControllerInputs: [Input] { + return self.inputMapping.supportedControllerInputs + } + + public func input(forControllerInput controllerInput: Input) -> Input? + { + return self.inputMapping.input(forControllerInput: controllerInput) + } + + func set(_ input: Input?, forControllerInput controllerInput: Input) + { + self.inputMapping.set(input, forControllerInput: controllerInput) + } +} diff --git a/Delta/Database/Model/Machine/_GameControllerInputMapping.swift b/Delta/Database/Model/Machine/_GameControllerInputMapping.swift new file mode 100644 index 0000000..cc28acb --- /dev/null +++ b/Delta/Database/Model/Machine/_GameControllerInputMapping.swift @@ -0,0 +1,28 @@ +// DO NOT EDIT. This file is machine-generated and constantly overwritten. +// Make changes to GameControllerInputMapping.swift instead. + +import Foundation +import CoreData + +import DeltaCore + +public class _GameControllerInputMapping: NSManagedObject +{ + @nonobjc public class func fetchRequest() -> NSFetchRequest { + return NSFetchRequest(entityName: "GameControllerInputMapping") + } + + // MARK: - Properties + + @NSManaged public var deltaCoreInputMapping: Any + + @NSManaged public var gameControllerInputType: GameControllerInputType + + @NSManaged public var gameType: GameType + + @NSManaged public var playerIndex: Int16 + + // MARK: - Relationships + +} + diff --git a/Delta/Database/Model/Model.xcdatamodel/contents b/Delta/Database/Model/Model.xcdatamodel/contents index b009f69..6e519be 100644 --- a/Delta/Database/Model/Model.xcdatamodel/contents +++ b/Delta/Database/Model/Model.xcdatamodel/contents @@ -1,5 +1,5 @@ - + @@ -79,6 +79,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + @@ -107,6 +132,7 @@ + \ No newline at end of file diff --git a/Delta/Database/Model/Transformers/GameControllerInputMappingTransformer.swift b/Delta/Database/Model/Transformers/GameControllerInputMappingTransformer.swift new file mode 100644 index 0000000..3993514 --- /dev/null +++ b/Delta/Database/Model/Transformers/GameControllerInputMappingTransformer.swift @@ -0,0 +1,61 @@ +// +// GameControllerInputMappingTransformer.swift +// Delta +// +// Created by Riley Testut on 9/27/17. +// Copyright © 2017 Riley Testut. All rights reserved. +// + +import Foundation + +import DeltaCore + +@objc(GameControllerInputMappingTransformer) +class GameControllerInputMappingTransformer: ValueTransformer +{ + override class func transformedValueClass() -> AnyClass { + return NSData.self + } + + override class func allowsReverseTransformation() -> Bool { + return true + } + + override func transformedValue(_ value: Any?) -> Any? + { + guard let inputMapping = value as? DeltaCore.GameControllerInputMapping else { return nil } + + let plistEncoder = PropertyListEncoder() + + do + { + let data = try plistEncoder.encode(inputMapping) + return data + } + catch + { + print(error) + + return nil + } + } + + override func reverseTransformedValue(_ value: Any?) -> Any? + { + guard let inputMappingData = value as? Data else { return nil } + + let plistDecoder = PropertyListDecoder() + + do + { + let inputMapping = try plistDecoder.decode(DeltaCore.GameControllerInputMapping.self, from: inputMappingData) + return inputMapping + } + catch + { + print(error) + + return nil + } + } +} diff --git a/Delta/Emulation/GameViewController.swift b/Delta/Emulation/GameViewController.swift index 21f9449..1af9f3c 100644 --- a/Delta/Emulation/GameViewController.swift +++ b/Delta/Emulation/GameViewController.swift @@ -32,12 +32,15 @@ private extension GameViewController struct SustainInputsMapping: GameControllerInputMappingProtocol { - let gameControllerInputType: GameControllerInputType - let previousInputMapping: GameControllerInputMappingProtocol? + let gameController: GameController + + var gameControllerInputType: GameControllerInputType { + return self.gameController.inputType + } func input(forControllerInput controllerInput: Input) -> Input? { - if let mappedInput = self.previousInputMapping?.input(forControllerInput: controllerInput), mappedInput == StandardGameControllerInput.menu + if let mappedInput = self.gameController.defaultInputMapping?.input(forControllerInput: controllerInput), mappedInput == StandardGameControllerInput.menu { return mappedInput } @@ -355,6 +358,8 @@ private extension GameViewController { @objc func updateControllers() { + guard let emulatorCore = self.emulatorCore, let game = self.game else { return } + let controllers = [self.controllerView as GameController] + ExternalGameControllerManager.shared.connectedControllers if let index = Settings.localControllerPlayerIndex @@ -368,28 +373,25 @@ private extension GameViewController self.controllerView.isHidden = true } - // Removing all game controllers from EmulatorCore will reset each controller's playerIndex to nil - // We temporarily cache their playerIndexes, and then we reset them after removing all controllers - var controllerIndexes = [ObjectIdentifier: Int?]() - controllers.forEach { controllerIndexes[ObjectIdentifier($0)] = $0.playerIndex } - - self.emulatorCore?.removeAllGameControllers() - - // Reset each controller's playerIndex to what it was before removing all controllers from EmulatorCore - controllers.forEach { $0.playerIndex = controllerIndexes[ObjectIdentifier($0)] ?? nil } - - for controller in controllers + for gameController in controllers { - if let index = controller.playerIndex + if gameController.playerIndex != nil { - // We need to place the underscore here to silence erroneous unused result warning despite annotating function with @discardableResult - // Hopefully this bug won't be around for too long... - _ = self.emulatorCore?.setGameController(controller, at: index) - controller.addReceiver(self) + if let inputMapping = GameControllerInputMapping.inputMapping(for: gameController, gameType: game.type, in: DatabaseManager.shared.viewContext) + { + gameController.addReceiver(self, inputMapping: inputMapping) + gameController.addReceiver(emulatorCore, inputMapping: inputMapping) + } + else + { + gameController.addReceiver(self) + gameController.addReceiver(emulatorCore) + } } else { - controller.removeReceiver(self) + gameController.removeReceiver(self) + gameController.removeReceiver(emulatorCore) } } @@ -623,8 +625,8 @@ private extension GameViewController self.isSelectingSustainedButtons = true - self.sustainInputsMapping = SustainInputsMapping(gameControllerInputType: gameController.inputType, previousInputMapping: gameController.inputMapping) - gameController.inputMapping = self.sustainInputsMapping + let sustainInputsMapping = SustainInputsMapping(gameController: gameController) + gameController.addReceiver(self, inputMapping: sustainInputsMapping) let blurEffect = self.sustainButtonsBlurView.effect self.sustainButtonsBlurView.effect = nil @@ -643,7 +645,7 @@ private extension GameViewController self.isSelectingSustainedButtons = false - gameController.inputMapping = self.sustainInputsMapping?.previousInputMapping + self.updateControllers() self.sustainInputsMapping = nil // Reactivate all sustained inputs, since they will now be mapped to game inputs. diff --git a/Delta/Settings/Controllers/ControllerInputsViewController.swift b/Delta/Settings/Controllers/ControllerInputsViewController.swift index 8edff0a..ac9bc4e 100644 --- a/Delta/Settings/Controllers/ControllerInputsViewController.swift +++ b/Delta/Settings/Controllers/ControllerInputsViewController.swift @@ -17,7 +17,7 @@ class ControllerInputsViewController: UIViewController { var gameController: GameController! { didSet { - self.prepareGameController() + self.gameController.addReceiver(self, inputMapping: nil) } } @@ -28,8 +28,8 @@ class ControllerInputsViewController: UIViewController } } - fileprivate var inputMapping: GameControllerInputMapping! - fileprivate var previousInputMapping: GameControllerInputMappingProtocol? + fileprivate lazy var managedObjectContext: NSManagedObjectContext = DatabaseManager.shared.newBackgroundContext() + fileprivate var inputMappings = [System: GameControllerInputMapping]() fileprivate let supportedActionInputs: [ActionInput] = [.saveState, .loadState, .fastForward] @@ -91,8 +91,11 @@ extension ControllerInputsViewController self.actionsMenuViewController = segue.destination as! GridMenuViewController self.prepareActionsMenuViewController() - case "cancelControllerControls": self.gameController.inputMapping = self.previousInputMapping - case "saveControllerControls": self.gameController.inputMapping = self.inputMapping + case "cancelControllerInputs": break + case "saveControllerInputs": + self.managedObjectContext.performAndWait { + self.managedObjectContext.saveWithErrorLogging() + } default: break } @@ -105,6 +108,7 @@ private extension ControllerInputsViewController { guard self.isViewLoaded else { return } + // Update popoverMenuButton to display correctly on iOS 10. if let popoverMenuButton = self.navigationItem.popoverMenuController?.popoverMenuButton { popoverMenuButton.title = self.system.localizedShortName @@ -113,8 +117,31 @@ private extension ControllerInputsViewController self.navigationController?.navigationBar.layoutIfNeeded() } + // Update controller view's controller skin. self.gameViewController.controllerView.controllerSkin = DeltaCore.ControllerSkin.standardControllerSkin(for: self.system.gameType) + // Fetch input mapping if it hasn't already been fetched. + if let gameController = self.gameController, let playerIndex = gameController.playerIndex, self.inputMappings[self.system] == nil + { + self.managedObjectContext.performAndWait { + let inputMapping = GameControllerInputMapping.inputMapping(for: gameController, gameType: self.system.gameType, in: self.managedObjectContext) ?? { + let deltaCoreInputMapping = gameController.defaultInputMapping as? DeltaCore.GameControllerInputMapping ?? DeltaCore.GameControllerInputMapping(gameControllerInputType: gameController.inputType) + + let inputMapping = GameControllerInputMapping(inputMapping: deltaCoreInputMapping, context: self.managedObjectContext) + inputMapping.gameControllerInputType = gameController.inputType + inputMapping.gameType = self.system.gameType + inputMapping.playerIndex = Int16(playerIndex) + + return inputMapping + }() + + inputMapping.name = String.localizedStringWithFormat("Custom %@", gameController.name) + + self.inputMappings[self.system] = inputMapping + } + } + + // Update callouts, if view is already on screen. if self.view.window != nil { self.calloutViews.forEach { $1.dismissCallout(animated: true) } @@ -126,18 +153,6 @@ private extension ControllerInputsViewController } } - func prepareGameController() - { - self.gameController.addReceiver(self) - - self.previousInputMapping = self.gameController.inputMapping - - self.inputMapping = self.gameController.inputMapping as? GameControllerInputMapping ?? GameControllerInputMapping(gameControllerInputType: self.gameController.inputType) - self.inputMapping.name = String.localizedStringWithFormat("Custom %@", self.gameController.name) - - self.gameController.inputMapping = nil - } - func preparePopoverMenuController() { let listMenuViewController = ListMenuViewController() @@ -186,7 +201,7 @@ private extension ControllerInputsViewController text = NSLocalizedString("Fast Forward", comment: "") } - let item = MenuItem(text: text, image: image) { (item) in + let item = MenuItem(text: text, image: image) { [unowned self] (item) in guard let calloutView = self.calloutViews[AnyInput(input)] else { return } self.toggle(calloutView) } @@ -204,7 +219,8 @@ private extension ControllerInputsViewController let controllerView = self.gameViewController.controllerView, let traits = controllerView.controllerSkinTraits, let items = controllerView.controllerSkin?.items(for: traits), - let controllerViewInputMapping = controllerView.inputMapping + let controllerViewInputMapping = controllerView.defaultInputMapping, + let inputMapping = self.inputMappings[self.system] else { return } // Implicit assumption that all skins used for controller input mapping don't have multiple items with same input. @@ -218,21 +234,23 @@ private extension ControllerInputsViewController self.calloutViews[AnyInput(input)] = calloutView } - // Update callout views with controller inputs that map to callout views' associated controller skin inputs. - for input in self.inputMapping.supportedControllerInputs - { - let mappedInput = self.mappedInput(for: input) - - if let calloutView = self.calloutViews[mappedInput] + self.managedObjectContext.performAndWait { + // Update callout views with controller inputs that map to callout views' associated controller skin inputs. + for input in inputMapping.supportedControllerInputs { - if let previousInput = calloutView.input + let mappedInput = self.mappedInput(for: input) + + if let calloutView = self.calloutViews[mappedInput] { - // Ensure the input we display has a higher priority. - calloutView.input = (input.displayPriority > previousInput.displayPriority) ? input : previousInput - } - else - { - calloutView.input = input + if let previousInput = calloutView.input + { + // Ensure the input we display has a higher priority. + calloutView.input = (input.displayPriority > previousInput.displayPriority) ? input : previousInput + } + else + { + calloutView.input = input + } } } } @@ -252,6 +270,8 @@ private extension ControllerInputsViewController { func updateActiveCalloutView(with controllerInput: Input?) { + guard let inputMapping = self.inputMappings[self.system] else { return } + guard let activeCalloutView = self.activeCalloutView else { return } guard let input = self.calloutViews.first(where: { $0.value == activeCalloutView })?.key else { return } @@ -271,20 +291,22 @@ private extension ControllerInputsViewController } } - for supportedInput in self.inputMapping.supportedControllerInputs - { - let mappedInput = self.mappedInput(for: supportedInput) - - if mappedInput == input + self.managedObjectContext.performAndWait { + for supportedInput in inputMapping.supportedControllerInputs { - // Set all existing controller inputs that currently map to "input" to instead map to nil. - self.inputMapping.set(nil, forControllerInput: supportedInput) + let mappedInput = self.mappedInput(for: supportedInput) + + if mappedInput == input + { + // Set all existing controller inputs that currently map to "input" to instead map to nil. + inputMapping.set(nil, forControllerInput: supportedInput) + } + } + + if let controllerInput = controllerInput + { + inputMapping.set(input, forControllerInput: controllerInput) } - } - - if let controllerInput = controllerInput - { - self.inputMapping.set(input, forControllerInput: controllerInput) } activeCalloutView.input = controllerInput @@ -352,7 +374,11 @@ private extension ControllerInputsViewController { func mappedInput(for input: Input) -> AnyInput { - guard let mappedInput = self.inputMapping.input(forControllerInput: input) else { + guard let inputMapping = self.inputMappings[self.system] else { + fatalError("Input mapping for current system does not exist.") + } + + guard let mappedInput = inputMapping.input(forControllerInput: input) else { fatalError("Mapped input for provided input does not exist.") } @@ -450,6 +476,8 @@ extension ControllerInputsViewController: GameControllerReceiver { func gameController(_ gameController: GameController, didActivate controllerInput: DeltaCore.Input) { + guard self.isViewLoaded else { return } + switch gameController { case self.gameViewController.controllerView: diff --git a/Delta/Settings/Controllers/ControllersSettingsViewController.swift b/Delta/Settings/Controllers/ControllersSettingsViewController.swift index 5123f77..6ca78e5 100644 --- a/Delta/Settings/Controllers/ControllersSettingsViewController.swift +++ b/Delta/Settings/Controllers/ControllersSettingsViewController.swift @@ -36,7 +36,7 @@ private class LocalDeviceController: NSObject, GameController let inputType: GameControllerInputType = .standard - var inputMapping: GameControllerInputMappingProtocol? + var defaultInputMapping: GameControllerInputMappingProtocol? } class ControllersSettingsViewController: UITableViewController diff --git a/Delta/Settings/Settings.storyboard b/Delta/Settings/Settings.storyboard index b4b9f34..e4c634a 100644 --- a/Delta/Settings/Settings.storyboard +++ b/Delta/Settings/Settings.storyboard @@ -286,12 +286,12 @@ - + - +