Created
March 1, 2018 02:14
-
-
Save JoeMatt/48f2640f87aae6c48b9854c67da18934 to your computer and use it in GitHub Desktop.
Example for using swift protocol defaults and generics with keyPaths to reduce code
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Can maybe use this generic keypath and then narrow it downn later | |
// You can't use KeyPath<T, GCControllerElement> and then pass a | |
// keyPath to a GCControllerButtonInput property even though in inherits. | |
// No idea why or what the work around is but I'm sure there is a proper way | |
// to make this generic | |
protocol BoolRepresentableInput { | |
var isPressed: Bool { get } | |
} | |
protocol FloatRepresentableInput { | |
var value: Float { get } | |
} | |
extension GCControllerButtonInput : BoolRepresentableInput, FloatRepresentableInput {} | |
protocol IterableEnum { | |
static var allCases : [Self] {get} | |
} | |
protocol DefaultExtendedGamePadMappable { | |
typealias ExtendedGamepadMappingDictionary = [KeyPath<GCExtendedGamepad, GCControllerButtonInput> : Self] | |
static var defaultExtendGamePadMappings : ExtendedGamepadMappingDictionary {get} | |
} | |
protocol DefaultGamePadMappable { | |
typealias GamepadMappingDictionary = [KeyPath<GCGamepad, GCControllerButtonInput> : Self] | |
static var defaultGamePadMappings : GamepadMappingDictionary {get} | |
} | |
// Make Buttons enum require all these protocols | |
typealias ButtonsEnum = IterableEnum & DefaultExtendedGamePadMappable & DefaultGamePadMappable | |
// Example enum that can be used to dynamically generate everything we need for a core | |
enum N64Button : String, ButtonsEnum { | |
case Z = "Z Trigger" | |
case L = "L Trigger" | |
case R = "R Trigger" | |
// ... the rest ... | |
// Maybe one day Swift will add this? | |
// There are hacks to build this dynamically | |
static var allCases: [N64Button] = [.Z,.L,.R] | |
// Set the defaults statically | |
static var defaultExtendGamePadMappings : ExtendedGamepadMappingDictionary = [\.buttonA : .Z, \.buttonB : .L, \.leftTrigger : .R] | |
static var defaultGamePadMappings : GamepadMappingDictionary = [\.buttonA : .Z, \.buttonB : .L, \.leftShoulder : .R] | |
} | |
// Unused, had an idea here... | |
enum ControllerInputWrapper { | |
case button(GCControllerButtonInput) | |
case directionalPad(GCControllerDirectionPad) | |
case axis(GCControllerAxisInput) | |
} | |
// Protocl to require class to expose the keypaths it wants to map | |
// GCControllerButtonInput as the value type is a tempoary thing | |
// until I figure out how to generalize that | |
protocol GamePadKeypathable : class { | |
associatedtype ObjectType = Self | |
static var keyPaths : [KeyPath<ObjectType, GCControllerButtonInput>] {get} | |
} | |
// Partial for GCExtendedGamepad and GCGamepad | |
extension GCExtendedGamepad : GamePadKeypathable { | |
static var keyPaths: [KeyPath<GCExtendedGamepad, GCControllerButtonInput>] { | |
return [\.buttonA, \.buttonB, \.leftShoulder, \.rightShoulder] //.... | |
} | |
} | |
extension GCGamepad : GamePadKeypathable { | |
static var keyPaths: [KeyPath<GCGamepad, GCControllerButtonInput>] { | |
return [\.buttonA, \.buttonB] | |
} | |
} | |
// ------ Mappable Core | |
// | |
// In theory, usingn protocol extensions, any core can be | |
// defined as implimenting MappabaleCore and get all the auto-mapping | |
// features that the protocl extension default implimentations provide | |
// Still needs work to generalize the keyPaths Key/Value types | |
// TODO: Replace GCExtendedGamepad and GCControllerButtonInput with generics, | |
// Might have to make protocol extensions with each Where clause or get fancier with keypaths | |
protocol MappableCore { | |
// Core needs to define what | |
// enum , struct whatever that impliments | |
// ButtonEnum protocol it wantts to use | |
associatedtype ButtonEnum : ButtonsEnum | |
var buttonMapping : [KeyPath<GCExtendedGamepad, GCControllerButtonInput> : ButtonEnum?] {get} | |
var gamePads : [GCExtendedGamepad]? {get} | |
func updateValue(forButton button: ButtonEnum, withValue value : Float) | |
} | |
// Could be a way to generalize, wrap each possible controller class in it's own struct helper | |
// And then pass this struct as a parameter to updateValue | |
struct ButtonMapping<GamePadType:GamePadKeypathable, CoreEnumType:ButtonsEnum> { | |
var buttonMapping : [KeyPath<GamePadType, GCControllerButtonInput> : CoreEnumType?]? = nil | |
} | |
extension MappableCore { | |
// A Core would call this each time it needs to poll | |
// the contorller status, usually before a frame is rendered | |
func iterateControllers() { | |
guard let gamePads = gamePads else { | |
return | |
} | |
gamePads.forEach { gamePad in | |
// if let mappableGamepad = gamePad as? GamePadKeypathable { // Can't do this unless we constrain this extension | |
GCExtendedGamepad.keyPaths.forEach { | |
setValueForController(control: gamePad, keyPath: $0) | |
} | |
// } | |
} | |
} | |
// This is what works for now - not general | |
func setValueForController(control : GCExtendedGamepad, keyPath: KeyPath<GCExtendedGamepad, GCControllerButtonInput>) { | |
let button = control[keyPath: keyPath] | |
let value = button.value | |
if let coreButton = buttonMapping[keyPath] { | |
updateValue(forButton: coreButton!, withValue: value) | |
} | |
} | |
// This is a way to generalize, just as? each case, there's only 3. But I'm sure there's a better solution | |
func setValueForController<T:GamePadKeypathable>(control : GCExtendedGamepad, keyPath: KeyPath<T, GCControllerButtonInput>) { | |
if let keyPath = keyPath as? KeyPath<GCExtendedGamepad, GCControllerButtonInput> { | |
setValueForController(control: control, keyPath: keyPath) | |
} // ... do the other cases | |
} | |
// Generic way of getting the mappings, imagine reading from a file or dictionary | |
var buttonMapping : [KeyPath<GCExtendedGamepad, GCControllerButtonInput> : ButtonEnum?] { | |
var mapping = [KeyPath<GCExtendedGamepad, GCControllerButtonInput> : ButtonEnum]() | |
for keyPath in GCExtendedGamepad.keyPaths { | |
// .. read a file for some thing for what the value should be | |
mapping[keyPath] = ButtonEnum.allCases.first! // Replace with real data from user config data | |
} | |
return mapping | |
} | |
} | |
// Specific instance | |
public class N64GameCore { | |
var gamePads: [GCExtendedGamepad]? | |
} | |
extension N64GameCore : MappableCore { | |
// Have to tell the protocol what we're using as 'ButtonEnum' typealias | |
typealias ButtonEnum = N64Button | |
// The only fuction a core really has to do, which is, switch through all the inputs | |
// and when it mataches, do what it already does. Usually modifies a value | |
// in an input buffer that gets sent to the emulator engine for that Core. | |
func updateValue(forButton button: N64Button, withValue value: Float) { | |
switch button { | |
case .Z: | |
print("Z has value of \(value)") | |
default: | |
print("TODO") | |
} | |
} | |
// This is the manaul way of doing it in a core if you don't want to use the | |
// protocl extensions defaults | |
var buttonMapping : [KeyPath<GCExtendedGamepad, GCControllerButtonInput> : N64Button?] { | |
// Coulld also make this generic by using a loop of controller keypaths to read a config file or dict where the Key is var | |
// for kp in GCExtendedGamepad().keyPaths { | |
// | |
// } | |
return [\.buttonA : .Z, \.buttonB : .L] | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment