Skip to content

Instantly share code, notes, and snippets.

@JoeMatt
Created March 1, 2018 02:14
Show Gist options
  • Save JoeMatt/48f2640f87aae6c48b9854c67da18934 to your computer and use it in GitHub Desktop.
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
// 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