diff --git a/src/library_html5.js b/src/library_html5.js index 8e2e5c59d8696..9b18f38fbbbf8 100644 --- a/src/library_html5.js +++ b/src/library_html5.js @@ -13,6 +13,13 @@ var LibraryJSEvents = { visibilityChangeEvent: 0, touchEvent: 0, + // In order to ensure most coherent Gamepad API state as possible (https://github.com/w3c/gamepad/issues/22) and + // to minimize the amount of garbage created, we sample the gamepad state at most once per frame, and not e.g. once per + // each controller or similar. To implement that, the following variables retain a cache of the most recent polled gamepad + // state. + lastGamepadState: null, + lastGamepadStateFrame: null, // The integer value of Browser.mainLoop.currentFrameNumber of when the last gamepad state was produced. + // When we transition from fullscreen to windowed mode, we remember here the element that was just in fullscreen mode // so that we can report information about that element in the event message. previousFullscreenElement: null, @@ -1650,37 +1657,36 @@ var LibraryJSEvents = { return {{{ cDefine('EMSCRIPTEN_RESULT_SUCCESS') }}}; }, - emscripten_get_num_gamepads: function() { - if (!navigator.getGamepads && !navigator.webkitGetGamepads) return {{{ cDefine('EMSCRIPTEN_RESULT_NOT_SUPPORTED') }}}; - if (navigator.getGamepads) { - return navigator.getGamepads().length; - } else if (navigator.webkitGetGamepads) { - return navigator.webkitGetGamepads().length; + _emscripten_sample_gamepad_data: function() { + // Produce a new Gamepad API sample if we are ticking a new game frame, or if not using emscripten_set_main_loop() at all to drive animation. + if (Browser.mainLoop.currentFrameNumber !== JSEvents.lastGamepadStateFrame || !Browser.mainLoop.currentFrameNumber) { + JSEvents.lastGamepadState = navigator.getGamepads ? navigator.getGamepads() : (navigator.webkitGetGamepads ? navigator.webkitGetGamepads : null); + JSEvents.lastGamepadStateFrame = Browser.mainLoop.currentFrameNumber; } }, + + emscripten_get_num_gamepads__deps: ['_emscripten_sample_gamepad_data'], + emscripten_get_num_gamepads: function() { + __emscripten_sample_gamepad_data(); + if (!JSEvents.lastGamepadState) return {{{ cDefine('EMSCRIPTEN_RESULT_NOT_SUPPORTED') }}}; + return JSEvents.lastGamepadState.length; + }, + emscripten_get_gamepad_status__deps: ['_emscripten_sample_gamepad_data'], emscripten_get_gamepad_status: function(index, gamepadState) { - if (!navigator.getGamepads && !navigator.webkitGetGamepads) return {{{ cDefine('EMSCRIPTEN_RESULT_NOT_SUPPORTED') }}}; - var gamepads; - if (navigator.getGamepads) { - gamepads = navigator.getGamepads(); - } else if (navigator.webkitGetGamepads) { - gamepads = navigator.webkitGetGamepads(); - } - if (index < 0 || index >= gamepads.length) { - return {{{ cDefine('EMSCRIPTEN_RESULT_INVALID_PARAM') }}}; - } - // For previously disconnected gamepads there should be a null at the index. + __emscripten_sample_gamepad_data(); + if (!JSEvents.lastGamepadState) return {{{ cDefine('EMSCRIPTEN_RESULT_NOT_SUPPORTED') }}}; + + // INVALID_PARAM is returned on a Gamepad index that never was there. + if (index < 0 || index >= JSEvents.lastGamepadState.length) return {{{ cDefine('EMSCRIPTEN_RESULT_INVALID_PARAM') }}}; + + // NO_DATA is returned on a Gamepad index that was removed. + // For previously disconnected gamepads there should be an empty slot (null/undefined/false) at the index. // This is because gamepads must keep their original position in the array. - // For example, removing the first of two gamepads produces [null, gamepad]. - // Older implementations of the Gamepad API used undefined instead of null. - // The following check works because null and undefined evaluate to false. - if (!gamepads[index]) { - // There is a "false" but no gamepad at index because it was disconnected. - return {{{ cDefine('EMSCRIPTEN_RESULT_NO_DATA') }}}; - } - // There should be a gamepad at index which can be queried. - JSEvents.fillGamepadEventData(gamepadState, gamepads[index]); + // For example, removing the first of two gamepads produces [null/undefined/false, gamepad]. + if (!JSEvents.lastGamepadState[index]) return {{{ cDefine('EMSCRIPTEN_RESULT_NO_DATA') }}}; + + JSEvents.fillGamepadEventData(gamepadState, JSEvents.lastGamepadState[index]); return {{{ cDefine('EMSCRIPTEN_RESULT_SUCCESS') }}}; }, diff --git a/tests/test_html5.c b/tests/test_html5.c index ece7dd8f09ecc..ad8fba431b410 100644 --- a/tests/test_html5.c +++ b/tests/test_html5.c @@ -264,8 +264,8 @@ void mainloop() for(int i = 0; i < numGamepads && i < 32; ++i) { EmscriptenGamepadEvent ge; - int failed = emscripten_get_gamepad_status(i, &ge); - if (!failed) + int ret = emscripten_get_gamepad_status(i, &ge); + if (ret == EMSCRIPTEN_RESULT_SUCCESS) { int g = ge.index; for(int j = 0; j < ge.numAxes; ++j)