-
-
Notifications
You must be signed in to change notification settings - Fork 352
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Added MFi Controller Support #225
Added MFi Controller Support #225
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hello, this looks good! I'm amazed to see someone that pick up this as good as you did.
- Just a few comments to keep the style consistent.
- There is a
Close
call missing. It would remove the controller from the elems. (have to be done in Go and Objc). - What should happen if a controller is disconnected? Should it be closed or a controller can be reopened.
- Did you take a look on what is done on other platform? Look enough generic for me but just in case.
- Logs wrapper should be added too.
- After logs wrapper is added, ensure test coverage is still at 100% on
app
andapp/internal/core
.
Again very good job on this!
controller.go
Outdated
// ControllerConfig is a struct that describes a controller | ||
type ControllerConfig struct { | ||
// event handlers | ||
OnControllerDpadChange func(button string, xValue float64, yValue float64) `json:"-"` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- No need to prefix each handler with controller since it already belongs to
ControllerConfig
:OnControllerDpadChange
=>OnDpadChange
- Would be nice to have a description for each handlers.
xValue
andyValue
should bex
andy
- Would it make sens to have an enum or some const that map to the button?
drivers/mac/controller.go
Outdated
id string | ||
|
||
// event handlers | ||
onControllerDpadChange func(string, float64, float64) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same as above for the Controller
prefix.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I changed all the other ones prefixed with 'controller.', but I kept the other literals the same as to keep the same convention for windows methods (e.g. OnWindow...)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this one should be changed too.
The ones with prefix kept are the one that are objc handler called outside of the controller struct.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah I got you now
drivers/mac/controller.go
Outdated
} | ||
|
||
// Contains satistfies the app.ElemWithCompo interface. | ||
func (c *Controller) Contains(compo app.Compo) bool { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This does not need to be set since it does not contain a dom.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
removed
drivers/mac/controller.go
Outdated
if c.onControllerDisconnected != nil { | ||
c.onControllerDisconnected() | ||
} | ||
return nil |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would like to keep the code style consistent with the rest of the codebase.
Please put an endline between the bracket and the return.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
changed 👍
drivers/mac/controller.m
Outdated
Driver *driver = [Driver current]; | ||
NSString *ID = in[@"ID"]; | ||
|
||
// create blank controller, track it for future connection |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Keep the comment style consistent with the rest of the codebase:
create blank controller, track it for future connection
=> Create blank controller, track it for future connection.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
changed
drivers/mac/controller.m
Outdated
NSDictionary *in = @{ | ||
@"ID": ID, | ||
@"Button": button, | ||
@"XValue": @(xValue), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same as above for x
and y
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
changed
@maxence-charriere Hey, thanks! This was actually my first attempt at objective-c, apologies for my lack of super cool idioms 😄 To address your message above:
Sounds good. I tried my best to keep it consistent, but I'll go ahead and fix up the addressed issues.
Ah! I knew there was something to clear up. I tried
So I tried putting a lot of thought into this. A lot of apps are annoying to use with a controller when it gets disconnected. But if you instantiate an
Definitely something to look into. I'm willing to take a wack at linux, but I'll be honest, I'm not gonna be too helpful on the Windows front 😄
Sounds good 👍 |
Just setting stuff to Also when I say removing the elems:
|
@maxence-charriere I've added |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hello,
Thanks for modifying :).
I spotted a few other things:
- We should use enum to identify the button/pad that is the source of an event.
- Left/Right stick button are not handled.
- Just some little typo to modify.
- I m wondering if we should rename
Controller
byGamePad
. Let me know what you think.
I would like that the api look like the following:
// ControllerInput describes a controller input.
type ControllerInput int
// Constants that describe the controller inputs.
const (
DirectionalPad ControllerInput = iota
LeftThumbstick
RightThumbstick
A
B
X
Y
L1
L2
R1
R2
Pause
)
// ControllerConfig is a struct that describes a controller.
type ControllerConfig struct {
// The function that is called when when the directional pad is pressed.
OnControllerDpadChange func(in ControllerInput, x float64, y float64) `json:"-"`
// The function that is called when a button in pressed.
OnControllerButtonChange func(in ControllerInput, value float64, pressed bool) `json:"-"`
// The function that is called when the controller is connected.
OnControllerConnected func() `json:"-"`
// The function that is called when the controller is disconnected.
OnControllerDisconnected func() `json:"-"`
// The function that is called when the pause button is pressed.
OnControllerPause func() `json:"-"`
// The function that is called when the controller is closed.
OnControllerClose func() `json:"-"`
}
It would allow to auto document what pad and button are available and allow strict check of the values for when we use the api.
Could you modify the code to make it work within what is described above?
I know I m very strict with the requirement but I really want this package to be highest quality as I can.
Thanks you so much for this. Again this is an amazing job :).
app.go
Outdated
@@ -131,6 +131,14 @@ func NewContextMenu(c MenuConfig) Menu { | |||
return driver.NewContextMenu(c) | |||
} | |||
|
|||
// NewController creates a controller described by the given |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
creates a controller
=> creates the controller
controller.go
Outdated
@@ -0,0 +1,18 @@ | |||
package app | |||
|
|||
// ControllerConfig is a struct that describes a controller |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Typo => describes a controller.
controller.go
Outdated
|
||
// ControllerConfig is a struct that describes a controller | ||
type ControllerConfig struct { | ||
// event handlers |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I meant a comment that describes every functions.
drivers/mac/controller.go
Outdated
id string | ||
|
||
// event handlers | ||
onControllerDpadChange func(string, float64, float64) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this one should be changed too.
The ones with prefix kept are the one that are objc handler called outside of the controller struct.
drivers/mac/controller.m
Outdated
[driver.goRPC call:@"controller.OnPause" withInput:in onUI:YES]; | ||
}; | ||
|
||
// diamond buttons + triggers + shoulders |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Look like you forgot to add the thumbstick buttons.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
those are down below with the dpad. I separated them because their event payloads are similar
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Its when you click on the stick. They are their own button different than the position.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh I see, the Nimbus I've been testing on doesn't have click-able thumbsticks, but I can add the code for it like the rest.
@maxence-charriere Added a new round of changes (I like the enum idea btw, will allow the user to more easily dispatch based on button). The only thing to my knowledge I didn't do was add the thumbstick click buttons. I'm not seeing anything in Apple's documentation outlining those buttons. |
I've also updated the sandbox example in case you want to try out the changes |
full disclosure: your example for the enum had a bit about: I've changed |
For the in rather than input, just keep it in go. Its a common pattern to use shortname especially when the type next to have the full name. Totally agree for objc. |
I apparently can't do documentation either, must have missed that |
@maxence-charriere what about cases like: func onControllerDpadChange(c *Controller, in map[string]interface{}) interface{} {
if c.onDpadChange != nil {
input := app.ControllerInput(in["Input"].(float64))
x := in["X"].(float64)
y := in["Y"].(float64)
c.onDpadChange(input, x, y)
}
return nil
} |
It is fine, I care more in the API declaration. |
api declaration updated (I'll squash these commits at the end btw 😄 ) |
I'm not necessarily against renaming Controller to GamePad. I just figured since |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great job!
I think keep with controller is fine.
I gave some other feedbacks that are detailed below.
We should also add the log wrapper for the controller:
- in
logs.go
, add thecontrollerWithLogs
struct:
// Controller logs.
type controllerWithLogs struct {
Controller
}
func (c *controllerWithLogs) Close() {
WhenDebug(func() {
Logf("controller %s is closing", c.ID())
})
c.Controller.Close()
if c.Err() != nil {
Logf("controller %s failed to close: %s",
c.ID(),
c.Err(),
)
}
}
- Still in
logs.go
, add theNewController
func todriverWithLogs
:
func (d *driverWithLogs) NewController(c ControllerConfig) Controller {
WhenDebug(func() {
config, _ := json.MarshalIndent(c, "", " ")
Logf("creating controller: %s", config)
})
d.Driver.NewController(c)
}
- Check that test are still 100%.
I think we are close. Thank you so much for your work.
@@ -34,6 +34,8 @@ type Driver interface { | |||
// given configuration. | |||
NewContextMenu(MenuConfig) Menu | |||
|
|||
NewController(ControllerConfig) Controller |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Doc is missing here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
added 👍
drivers/mac/controller.go
Outdated
|
||
// newController creates an *app.Controller from | ||
// the specified app.ControllerConfig | ||
func newController(cc app.ControllerConfig) *Controller { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see why you used cc
.
I would prefer you keep c
for the config and name the controller variable with its full name.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
changed 👍
// leftShoulder, rightShoulder | ||
// leftTrigger, rightTrigger | ||
func onControllerButtonChange(c *Controller, in map[string]interface{}) interface{} { | ||
if c.onButtonChange != nil { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
here you can put everything in the function call:
c.onButtonChange(
app.ControllerInput(in["Input"].(float64)),
in["Value"].(float64),
in["Pressed"].(bool),
)
It will avoid to create temporary variables.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
made them inline params 👍
drivers/mac/controller.h
Outdated
+ (void) close:(NSDictionary *)in return:(NSString *)returnID; | ||
+ (void) listen:(NSDictionary *)in return:(NSString *)returnID; | ||
|
||
- (void) controllerConnected; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No need to prefix by controller here since it is already in a Controller object.
- (void ) connected;
- (void ) disconnected;
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
changed 👍
[[NSNotificationCenter defaultCenter] addObserver:controller selector:@selector(controllerDisconnected) name:GCControllerDidDisconnectNotification object:nil]; | ||
|
||
// Check if controller was connected before application started | ||
if ([[GCController controllers] count] > 0) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thank you 😎
drivers/mac/controller.m
Outdated
|
||
// Called when a controller becomes connected | ||
- (void) controllerConnected { | ||
NSLog(@"info - controller connected"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- remove NSLog
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
drivers/mac/controller.m
Outdated
|
||
// Called when a controller becomes disconnected | ||
- (void) controllerDisconnected { | ||
NSLog(@"info - controller disconnected"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same here.
drivers/mac/controller.m
Outdated
// joysticks + dpad | ||
controller.profile.dpad.valueChangedHandler = ^(GCControllerDirectionPad *dpad, float x, float y) { | ||
// Directional pad has been changed | ||
[Controller emitDpad:ID Input:0 X:x Y:y]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Lets redeclare the button type in the .h:
typedef enum ControllerInput : NSUInteger {
// equivalent to Go ...
} ControllerInput;
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
@maxence-charriere Alright, I think I tackled all the things you suggested, plus I was getting these warnings:
so I added checks for macOS 10.14.1 around those two handlers and that got rid of them |
Also, thanks for the sample code, definitely expedites the corrections 👍 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good. Almost there :)
driver.go
Outdated
@@ -34,6 +34,8 @@ type Driver interface { | |||
// given configuration. | |||
NewContextMenu(MenuConfig) Menu | |||
|
|||
// NewController creates and listens for controller input described by the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
NewController creates the controller described by the given configuration.
@@ -26,28 +26,28 @@ type Controller struct { | |||
|
|||
// newController creates an *app.Controller from | |||
// the specified app.ControllerConfig | |||
func newController(cc app.ControllerConfig) *Controller { | |||
c := &Controller{ | |||
func newController(c app.ControllerConfig) *Controller { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
also you can remove the comment here since this is not exported.
@@ -113,56 +111,58 @@ + (void) listen:(NSDictionary *)in return:(NSString *)returnID { | |||
// joysticks + dpad | |||
controller.profile.dpad.valueChangedHandler = ^(GCControllerDirectionPad *dpad, float x, float y) { | |||
// Directional pad has been changed | |||
[Controller emitDpad:ID Input:0 X:x Y:y]; | |||
[Controller emitDpad:ID Input:DirectionalPad X:x Y:y]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you can remove all these comments about what it does since the code self explain.
logs.go
Outdated
} | ||
} | ||
|
||
func (d *driverWithLogs) NewController(c ControllerConfig) Controller { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be moved with the other functions related to the driverWithLogs
. Please keep the same order as where you declared it in driver.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see, I swapped NewController
and Close
. Is that all you meant? Or do you want me to move the overall placement?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh! I see it now ha
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Driver with logs is a different struct than controllerwithlogs
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah that was a subtle difference that went over my head 😄
updated 👍 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good to me, just fix the last feedback and i ll merge it in the weekend. Thank you so much for this, I did not even think about game controller at all.
Also thanks for your patience and the modifications in the review.
logs.go
Outdated
@@ -73,6 +73,20 @@ func (d *driverWithLogs) NewWindow(c WindowConfig) Window { | |||
return &windowWithLogs{Window: w} | |||
} | |||
|
|||
func (d *driverWithLogs) NewController(c ControllerConfig) Controller { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please keep the same func order as in driver. I think its after newcontextmenu
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done 👍
No problem at all, I needed to grow my go skills and objective-c is a whole new world 😆 |
squashed commits |
Summary
I've written out a basic implementation for handling MFi controller events and providing Go callbacks that can be used to interact with the application.
As per
CONTRIBUTING.md
:Fixes
Using
GameController/GCController.h
, I setup handlers for the various buttons and dpads that comply with MFi. I've tested this with the Steelseries Nimbus and all keys register with their respective button.I wanted to add DOM events, but the contributing doc said not to get too crazy. Happy to add if this is good.
Example
This is how I've sandboxed the following changes:
edit: updated sandbox example