Skip to content
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

Implement glfw joystick API #5175

Merged
merged 18 commits into from
May 18, 2017
Merged

Conversation

satoshinm
Copy link
Contributor

@satoshinm satoshinm commented Apr 29, 2017

Adds support for glfw3's joystick functions: http://www.glfw.org/docs/latest/input_guide.html#joystick

  • glfwJoystickPresent
  • glfwGetJoystickAxes
  • glfwGetJoystickButtons
  • glfwGetJoystickName
  • glfwSetJoystickCallback

Includes a test based on the sdl joystick test:

python tests/runner.py browser.test_glfw_joystick

Also manually tested with a real joystick and a more complex real-world application which had native glfw joystick support (satoshinm/NetCraft#80)


  • Add _free()s on gamepaddisconnect, .id .buttons
  • Add _free()s on gamepaddisconnect, .axes
  • Incorporate most review feedback
  • Browser test: python tests/runner.py browser.test_glfw_joystick
  • Interactive test: python tests/runner.py interactive.test_glfw_joystick
  • Test versus emscripten gamepad API, and native glfw API
  • Poll once per frame, see gamepad_only_once_per_frame #4292

@satoshinm
Copy link
Contributor Author

This is functional but is it ok to refresh the joystick status the calls to glfwGetJoystickAxes and glfwGetJoystickButtons? Wasn't sure if the joystick state should be read on each frame instead. Also do I need to free the memory allocated for the axes and buttons arrays (from allocate, couldn't find out how to free it)

@kripken
Copy link
Member

kripken commented May 1, 2017

I'm not sure about the refreshing, but looks like it might be fast enough. I guess if you don't see it show up in profiling, it's probably fine. Or was the concern something other than speed?

For the allocations, ALLOC_NORMAL uses malloc, so you can do _free(..).

@satoshinm satoshinm changed the title Implement glfw joystick API [WIP] Implement glfw joystick API May 3, 2017
@@ -57,6 +56,9 @@ var LibraryGLFW = {
};
this.buttons = 0;
this.keys = new Array();
this.joyLast = 0; // GLFW_JOYSTICK_1
this.joys = {};
this.index2joy = {};
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you document here what is the purpose of these three variables. The name index2joy is a bit ad hoc, it would be good to have some details here.

@@ -382,6 +385,38 @@ var LibraryGLFW = {
#endif
},

onGamepadConnected: function(event) {
if (!GLFW.active.joystickFunc) return;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if GLFW should be tracking gamepads already before the user code has registered a joystick callback function. For example, it can take a while until the user code runs, and a gamepad connected event may have fired already before. Or perhaps the tightest way would be to enumerate all already known joysticks when a callback is registered or GLW initialized to initially populate the already known joysticks. I'm thinking about the scenario where user is clicking on gamepad buttons way before the page has fully loaded up.


var joy = GLFW.active.index2joy[event.gamepad.index];

Module['dynCall_vii'](GLFW.active.joystickFunc, joy, 0x00040002); // GLFW_DISCONNECTED
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if joy is undefined here? I suppose the callback should not be called then if such an odd situation arises?


Module['dynCall_vii'](GLFW.active.joystickFunc, joy, 0x00040002); // GLFW_DISCONNECTED

// TODO: free memory .axes, .buttons
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can do _free(GLFW.active.joys[joy].id); and same for buttons and axes. (ALLOC_NORMAL is equivalent to a _malloc())

},

onGamepadDisconnected: function(event) {
if (!GLFW.active.joystickFunc) return;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here also if user code has already detached a user callback function, the code below should still run to deinitialize the data for that joystick?

var j = GLFW.active.joys[joy];
if (!j) return;

var gamepad = navigator.getGamepads()[j.index];
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reading the documentation at http://www.glfw.org/docs/latest/group__input.html#gace54cd930dcd502e118fe4021384ce1b and http://www.glfw.org/docs/latest/input_guide.html#joystick_button, it looks like a suitable usage from user code might be to do a

for(int i = 0; i < numJoysticks; ++i) {
    int count;
    const unsigned char* axes = glfwGetJoystickButtons(GLFW_JOYSTICK_1+i, &count);
    for(int j = 0; j < count; ++j) {
        // process axes[j];
    }
}

In that case, there is a possible subtle bug due to an unspecced feature of the Gamepad API regarding navigator.getGamepads(), see w3c/gamepad#22 and w3c/gamepad#8. The issue is that navigator.getGamepads() polls new state for all gamepads, but the above line only reads data from one of them. So if there are e.g. 3 joysticks, the above loop would poll the state of each of the three joysticks three times, and process only one of them at each loop iteration. This might potentially miss out button presses, but whether that happens in practice is somewhat theoretical. Still good to keep in mind though.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See #4292 for a similar change that was done for the HTML5 API counterpart, and also #4921 which might be of interest.

},

glfwGetJoystickName: function(joy) {
return GLFW.active.joys[joy].id;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If joystick is disconnected right before this function is called, GLFW.active.joys[joy] could be deleted above and be undefined, making this function throw an exception on undefined.id access; so check for if (GLFW.active.joys[joy]) here before proceeding?

@@ -1076,6 +1076,53 @@ def test_sdl_joystick_2(self):
Popen([PYTHON, EMCC, os.path.join(self.get_dir(), 'sdl_joystick.c'), '-O2', '--minify', '0', '-o', 'page.html', '--pre-js', 'pre.js', '-lSDL', '-lGL']).communicate()
self.run_browser('page.html', '', '/report_result?2')

def test_glfw_joystick(self):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great to have a mock input injecting test! It would be nice to add the same test to the interactive suite as well, but without the API mock implementation part, so that could be tested with a live gamepad.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a simple interactive test (run with python tests/runner.py interactive.test_glfw_joystick), no graphics it only logs text of the gamepad state changes but useful for testing (already found & fixed the axes memory allocation error thanks to it)

satoshinm added a commit to satoshinm/NetCraft that referenced this pull request May 5, 2017
As opposed to the glfw joysticks, which of course are bridged to the
same HTML5 Gamepad API on the web build, but, there may be issues so
adding USE_EM_GAMEPAD to switch back (off by default) for testing.

This was previously removed in
#80 Use glfw joystick
everywhere, remove emscripten gamepad
due to including an early version of emscripten-core/emscripten#5175 [WIP]
Implement glfw joystick API
@satoshinm
Copy link
Contributor Author

Incorporated most (but not all) of the review feedback. Next major change needed is to poll the gamepad state once per frame like in #4292. This should also solve the issues I'm seeing on Chrome where gamepadconnected is fired on disconnect. Wondering if it is worth preserving this API nuance of glfw:

      // HTML5 Gamepad API reuses indexes
      // glfw joystick API spreads them out
      // http://www.glfw.org/docs/latest/input_guide.html#joystick
      // "Once a joystick is detected, it keeps its assigned index until it is disconnected,
      // so as joysticks are connected and disconnected, they will become spread out."

Also I added _free() but not to _free(GLFW.active.joys[joy].axes);, crashes in abort() on corrupted memory, need to investigate more what went wrong there...

GLFW_JOYSTICK_1 is 0, so HTML5 Gamepad index 0 maps to GLFW_JOYSTICK_1.
Note: The API subtlety of spreading out glfw joystick IDs is lost.
…-coreGH-4292

Joystick connected/disconnected callbacks are also now called in
refreshJoysticks(). The gamepadconnected/disconnected events are only
used as a hint to refresh the joysticks, whose getGamepads() state is
compared to the previous state to determine the new state.
@satoshinm
Copy link
Contributor Author

Changed to sample once per frame, tracking joystick connect/disconnect on refresh, and fixed the axes memory freeing. I think that's everything, anything else?

@satoshinm satoshinm changed the title [WIP] Implement glfw joystick API Implement glfw joystick API May 6, 2017
@satoshinm
Copy link
Contributor Author

@kripken @juj can this be merged or anything I need to change first?

@kripken
Copy link
Member

kripken commented May 16, 2017

This looks good to me, but let's wait a bit more for @juj.

@kripken
Copy link
Member

kripken commented May 18, 2017

Looks like @juj has no immediate concerns here, so let's merge this, any further stuff can be done in followup work.

@kripken kripken merged commit 4f42f6b into emscripten-core:incoming May 18, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants