Provenance-Emu / Provenance

iOS & tvOS multi-emulator frontend, supporting various Atari, Bandai, NEC, Nintendo, Sega, SNK and Sony console systems… Get Started: https://wiki.provenance-emu.com |
https://provenance-emu.com
Other
5.92k stars 686 forks source link

Input Abstraction + Custom Button Mapping #705

Open Emerald-Wolf opened 6 years ago

Emerald-Wolf commented 6 years ago

There are already tickets for different features involving input such as custom mapping (#56) for combinations to generate a different input value, but the common response is that a more general solution is needed. By removing the actual button mapping and controller support from the cores and creating layers to handle user input and converting it into the expected input for the cores more features and controller types will be easier to add now and in the future.

This proposal includes custom button mapping since it will already be doing button mapping and the only additional step would be handling file selection from the available map files uploaded. The ability for the application to generate these custom map files can be added during or after implementation.

The following pieces and steps are needed as the basics to accomplish basic button mapping:

  1. A loader to read the map from a file and initialize or update item 2.
  2. A thread to receive user input and compare it to the defined map for the current core and store information in item 3.
  3. A structure to hold current input states and bridge the input thread and the active core. (Should probably be designed to bridge Swift and Objective-C and further discussion is required on should it be within the input thread, the core, or mirrored between the two.)
  4. Replacing current core button code with code to receive, hold, and process the structure that is item 3.

Additional Considerations: General:

Item 1:

Item 2:

Items 3 & 4:

Possible improvements after implementation:

JoeMatt commented 6 years ago

So I already devised a way to to this auto-magically, at least the linking of buttons on a controller, to a core. It's maybe a bit complicated at first because it uses Swift 4 key paths, generic protocols, protocol default implementation extensions and such. It's basically a protocol oriented design, but by doing it this way, a Core just has to have an extension in a .swift extension (can still be an ObjC defined class). It only has to declare it uses the MappableCore protocol, define an enum or struct that fire the required type alias, and implement 1 method, which is what do to with the data. It's based on polling GCController's since most cores wouldn't notice a difference between that an async button events since they run on a cycle timed run loop internally (though I suppose it could be extended for event callbacks too)

This is a Proof of Concept code, it compiles but there are issues, namely I didn't get far enough in KeyPath generalising to make it work with various controller types, since GCGamePad and GCExtendedGamePad annoyingly don't share a root class, but you can use all sorts of other protocol things to make it look like they do, or just make MappableCore a generalised protocol and make default extensions for 3 built in controller classes. Or use a middle class that is generalised and just pass the class type of the controller when the core is running. Either way, I'm pretty sure I can get all that to work.

Not worried about the Swift requirement, I have a branch where almost everything used here is already converted to Swift 4 or really close. That's why I was putting this feature off. They whole thing is done easier with swift enums and protocols You can see my N64+Swift branch in this repo for the progress so far. There was already a plan for Cores to expose their configuration data in their own .plist in their framework and the class to use to initialise them like .bundles of code do on OS X so that the main app can be agnostic of what caores it's built with, Open EMU is built this way. Provenance was started before .frameworks on iOS so evetyihng was static libs and a global plist.

It seems complex but it's a write once type of solution, instead of each core having to know how to read config settings for each possible controller type which is how it's done now, but with hard coded mappings. We have at least over a dozen cores each with card coded mappings for 3 controller class types, not to mention iCade.

I have an extreme headache and eye strain so I just glanced at your proposal. But I'm just sharing my work so far,

https://gist.github.com/JoeMatt/48f2640f87aae6c48b9854c67da18934

Emerald-Wolf commented 6 years ago

That definitely looks like the start of a way to do it.

You're comments in the code seem to indicate that the keypaths are a hinderance to generalizing the code for MFi controllers. Would it be easier to just use strings to indicate which buttons the core wants to use as defaults and then work the other direction by finding out which controller profile is connected then cycling through the supplied list and make the actual connections that are available?

If the number of controller mappings is so high it might be easier to just try and make a loop on another thread, if necessary a different loop could be loaded per core, where controllers can register a listener for events such as the valueChangedHandler used by the GCGamepad/GCExtendedGamepad and tie those to the functions created for use by the on screen buttons such as are found in the files like:

Provenance/Controller/PVPSXControllerViewController.m

You wouldn't get instant compatibility with all cores for all controller types, but it sounds like there might not be a magic solution for the iCade any way so might it be easier to do?

JoeMatt commented 6 years ago

Yeah since key paths might be a pain in the ass, you can do something really similar where I made an extension to the controller classes to expose an array of keypaths, instead make an array with enums, the enums containing an input type like, switch/button/pressure button/axis, and an associated value that's a tuple or struct of conguration info about that input on that controller. You could even store a function that is used to grab the value like (void)(float) { return self.buttonA.value }. KeyPaths are nearly the same thing as using a function like that.