Re: GamepadObserver (ie. MutationObserver + Gamepad)
On 08/07/2012 03:29 AM, Glenn Maynard wrote: On Sat, Aug 4, 2012 at 4:24 AM, Olli Pettay olli.pet...@helsinki.fi mailto:olli.pet...@helsinki.fi wrote: 5ms is quite low when the aim is 60Hz updates... but with incremental/generational GCs 5ms sounds very much possible. 5ms is an *eternity* when you're aiming for 60 FPS, where you only have 16.6ms per frame to play with. That's 30% of your CPU budget just for memory management. It doesn't matter if it's 5ms every 100 frames, since it's the worst case you have to optimize for. (I've spent a lot of time optimizing non-web games to stay at 60 FPS, and it's a battle of microseconds, optimizing away .1ms here and .2ms there, so calling 5ms quite low is a bit troubling.) It is quite different if you need to assume that GC takes 180-250ms or if it takes only 5ms. But sure, getting anything major done in 16.6ms, and even so that things work ok on slower machines too can be tricky.
Re: GamepadObserver (ie. MutationObserver + Gamepad)
On Tue, Aug 7, 2012 at 2:29 AM, Glenn Maynard gl...@zewt.org wrote: 5ms is an *eternity* when you're aiming for 60 FPS, where you only have 16.6ms per frame to play with. That's 30% of your CPU budget just for memory management. It doesn't matter if it's 5ms every 100 frames, since it's the worst case you have to optimize for. (I've spent a lot of time optimizing non-web games to stay at 60 FPS, and it's a battle of microseconds, optimizing away .1ms here and .2ms there, so calling 5ms quite low is a bit troubling.) On a sidenote, the coming Oculus Rift (VR headset with wide FOV) will be quite sensitive to lag/jitter and the aim to support that is to have a total RTT from input - display of no more than 20ms per frame, for every frame at preferably 120hz (the dev kit display ships @60hz)
Re: GamepadObserver (ie. MutationObserver + Gamepad)
On Sat, Aug 4, 2012 at 3:11 AM, Glenn Maynard gl...@zewt.org wrote: On a quick test, Firefox is firing mousemove events at 120Hz; this is about the same magnitude of data. We don't currently have any infrastructure for using ArrayBuffers for complex data, so it'd either need to be something new (some sort of complex view), or an unfriendly API. Either would be a major obstacle to getting anything adopted. The update rate depends on the device. Tablet updates reach way beyond 120HZ and even my 3D mouse clocks in at about 500 events/s. And a major obstacle for a realtime input device is when the realtime app trying to use it stutters/jitters every quarter second because of 180-250ms GC-pauses
Re: GamepadObserver (ie. MutationObserver + Gamepad)
On Sat, Aug 4, 2012 at 11:07 AM, b...@pettay.fi b...@pettay.fi wrote: The update rate depends on the device. Tablet updates reach way beyond 120HZ and even my 3D mouse clocks in at about 500 events/s. And a major obstacle for a realtime input device is when the realtime app trying to use it stutters/jitters every quarter second because of 180-250ms GC-pauses That long GC pauses are bugs in the implementations. File some bugs on all the browser engines you see it (after testing latest nightly builds). It doesn't matter if they're bugs (I often see them in conjunction to array buffer allocation). Even at the best of times the GC-pauses are no less than 90ms, and even if you got it down to 60ms, that's still more than 5 frames @80hz. Until you get GC-pauses down to ~5ms or so, any GC use will introduce unpleasant stuttering/jittering. And even then it's a close call to not miss a frame.
Re: GamepadObserver (ie. MutationObserver + Gamepad)
On 08/04/2012 12:16 PM, Florian Bösch wrote: On Sat, Aug 4, 2012 at 11:07 AM, b...@pettay.fi mailto:b...@pettay.fi b...@pettay.fi mailto:b...@pettay.fi wrote: The update rate depends on the device. Tablet updates reach way beyond 120HZ and even my 3D mouse clocks in at about 500 events/s. And a major obstacle for a realtime input device is when the realtime app trying to use it stutters/jitters every quarter second because of 180-250ms GC-pauses That long GC pauses are bugs in the implementations. File some bugs on all the browser engines you see it (after testing latest nightly builds). It doesn't matter if they're bugs (I often see them in conjunction to array buffer allocation). Of course it matters. APIs shouldn't be designed based on implementation bugs Even at the best of times the GC-pauses are no less than 90ms, and even if you got it down to 60ms, that's still more than 5 frames @80hz. Until you get GC-pauses down to ~5ms or so, any GC use will introduce unpleasant stuttering/jittering. And even then it's a close call to not miss a frame. 5ms is quite low when the aim is 60Hz updates... but with incremental/generational GCs 5ms sounds very much possible.
Re: GamepadObserver (ie. MutationObserver + Gamepad)
On Sat, Aug 4, 2012 at 11:24 AM, Olli Pettay olli.pet...@helsinki.fiwrote: It doesn't matter if they're bugs (I often see them in conjunction to array buffer allocation). Of course it matters. APIs shouldn't be designed based on implementation bugs It doesn't because those bugs have not been fixed the last odd 15 years and with every new vendor repeating them enthusiastically, and the entire WebGL tool ecosystem is growing around those bugs to avoid to allocate, ever, during a drawing loop. If you intended to limit the damage, it's too late, done deal. That should've happend 3 years ago. Now it's just dealing with the facts. Even at the best of times the GC-pauses are no less than 90ms, and even if you got it down to 60ms, that's still more than 5 frames @80hz. Until you get GC-pauses down to ~5ms or so, any GC use will introduce unpleasant stuttering/jittering. And even then it's a close call to not miss a frame. 5ms is quite low when the aim is 60Hz updates... but with incremental/generational GCs 5ms sounds very much possible. @60hz you're running 16.6ms/frame. 5ms is nearly 1/3 of a frame. If you take up say 12ms to render a frame, then a 5ms pause *will* make you skip a frame. @80hz you take 12.5ms/frame, if your frame needs longer than 7.5ms to produce, again, 5ms *will* skip a frame. 5ms isn't low, it's actually generously big. It's an upper acceptable delay we can work with. Most frames don't take longer than 5ms to produce and render, personally I'm always aiming at keeping the CPU time per frame in the sub millisecond region and see to it the GPU part does not exceed 5ms.
Re: GamepadObserver (ie. MutationObserver + Gamepad)
On Sat, Aug 4, 2012 at 11:31 AM, Florian Bösch pya...@gmail.com wrote: On Sat, Aug 4, 2012 at 11:24 AM, Olli Pettay olli.pet...@helsinki.fiwrote: It doesn't matter if they're bugs (I often see them in conjunction to array buffer allocation). Of course it matters. APIs shouldn't be designed based on implementation bugs It doesn't because those bugs have not been fixed the last odd 15 years and with every new vendor repeating them enthusiastically, and the entire WebGL tool ecosystem is growing around those bugs to avoid to allocate, ever, during a drawing loop. If you intended to limit the damage, it's too late, done deal. That should've happend 3 years ago. Now it's just dealing with the facts. Btw. about half of those projects reason for existence owes to this bug, Emscripten http://emscripten.org , LLJS http://mbebenita.github.com/LLJS/ , Heap.coffee http://syg.github.com/heap.coffee/ , gl-matrix https://github.com/toji/gl-matrix and that's OK It's OK because the other half these projects owe it to array buffers to make things run blazing fast, since the JIT in V8 enthusiastically optimizes JS, the reduced pointer indirection speeds things up and the better cache coherence helps a lot.
Re: GamepadObserver (ie. MutationObserver + Gamepad)
On Sat, Aug 4, 2012 at 11:46 AM, Florian Bösch pya...@gmail.com wrote: It's OK because the other half these projects owe it to array buffers to make things run blazing fast, since the JIT in V8 enthusiastically optimizes JS, the reduced pointer indirection speeds things up and the better cache coherence helps a lot. Longwinded way of saying, if you do realtime APIs and you ignore buffers you're doing it wrong.
Re: GamepadObserver (ie. MutationObserver + Gamepad)
Here's a rough sketch of an API that provides clean forwards-compatibility for devices. I think this also avoids all of the issues I talked about earlier: it gives a clean, easy to use event-based API that preserves ordering and timestamps; it can be used in both an event-based and polling way; it can retrieve both events and the current state; and most importantly it doesn't push things into a library, with all of the serious (and hopefully obvious to everyone on this list) problems that would cause. // Retrieve a representation of the device, with no actual access to inputs: var gamepad = getSomeInputDevice(); // Get a list of supported layouts: console.log(gamepad.layouts); [X360, NES, SNES, raw] // Get an interface, specifying which layout to use. var gamepadInput = gamepad.getLayout(X360); // Read the current state. var state = gamepadInput.getCurrentState(); /* state == { // These values are defined by the X360 layout. leftThumbStickX: 0, leftThumbStickY: 0, rightThumbStickX: 0.4, rightThumbStickY: 0.5, up: true, right: false, down: false, left: false, buttonX: false, buttonY: false, buttonA: false, buttonB: false, // Inputs which have no representation in this layout are returned in raw form. rawCompassAngle: 45, } */ // Read the buffer of changes: var stateChanges = gamepadInput.read(); /* stateChanges == [{ input: rightThumbstickX, // These labels are equal to the object keys in state above. value: 0.5, lastValue: 0.4, timestamp: 1336102719319, }, { input: rawCompassAngle, value: 55, lastValue: 45, timestamp: 1336102721319, }] */ function processInput(stateChanges) { // Process inputs: for(var i = 0; i stateChanges.length; ++i) { var change = stateChanges[i]; var delta = change.lastValue - change.value; switch(change.input) { case left: console.log(Left button was + change.value? pushed:released); break; case leftThumbStickX: console.log(Left stick X axis: + change.value + with a relative change of + delta); break; } } // Update the game state. This API allows us to process all inputs that happen simultaneously together, // such as axis changes, and then only update the game state once for the whole batch. processInputs(); } // Receiving an event on change: gamepadInput.onchange = function(e) { processInput(gamepadInput.read()); }; Some points: - A browser may support many different layouts for the same device. For example, an Xbox 360 controller can easily be used as an NES or SNES controller, so it can expose those profiles. - Raw inputs for any unmapped inputs are included for all profiles. This allows games to make use of additional controls, without being forced to drop back to raw to access them at all. - If a profile is exposed at all, the inputs of the profile must be completely covered. - Layout may not always map 1:1 to raw inputs. For example, the Playstation controller's D-pad is four buttons in a cross; layouts may map these four buttons down to two axes. - All data in getCurrentState is absolute, since it doesn't mutate the object. Therefore, purely relative inputs, such as trackballs and (probably) steering wheels, are returned in an absolute, accumulated form. To get relative motion, subtract the value from the previous value. By the way, while I only included NES as an example, having a baseline layout like that (with a more generic name) is useful: tons of simple games use only a D-pad and a couple buttons. This would give them get the widest input device support possible, by not having to request a profile with far more features than they need. I haven't tried to incorporate Florian's suggestion of using something like ArrayBuffer. That could be supported later, eg. by providing a readIntoBuffer(buffer) next to read(). That's too complex to try to tackle all at once. -- Glenn Maynard
Re: GamepadObserver (ie. MutationObserver + Gamepad)
On Sat, Aug 4, 2012 at 7:03 PM, Glenn Maynard gl...@zewt.org wrote: Here's a rough sketch of an API that provides clean forwards-compatibility for devices. I think this also avoids all of the issues I talked about earlier: it gives a clean, easy to use event-based API that preserves ordering and timestamps; it can be used in both an event-based and polling way; it can retrieve both events and the current state; and most importantly it doesn't push things into a library, with all of the serious (and hopefully obvious to everyone on this list) problems that would cause. // Retrieve a representation of the device, with no actual access to inputs: var gamepad = getSomeInputDevice(); // Get a list of supported layouts: console.log(gamepad.layouts); [X360, NES, SNES, raw] // Get an interface, specifying which layout to use. var gamepadInput = gamepad.getLayout(X360); // Read the current state. var state = gamepadInput.getCurrentState(); /* state == { // These values are defined by the X360 layout. leftThumbStickX: 0, leftThumbStickY: 0, rightThumbStickX: 0.4, rightThumbStickY: 0.5, up: true, right: false, down: false, left: false, buttonX: false, buttonY: false, buttonA: false, buttonB: false, // Inputs which have no representation in this layout are returned in raw form. rawCompassAngle: 45, } */ // Read the buffer of changes: var stateChanges = gamepadInput.read(); /* stateChanges == [{ input: rightThumbstickX, // These labels are equal to the object keys in state above. value: 0.5, lastValue: 0.4, timestamp: 1336102719319, }, { input: rawCompassAngle, value: 55, lastValue: 45, timestamp: 1336102721319, }] */ function processInput(stateChanges) { // Process inputs: for(var i = 0; i stateChanges.length; ++i) { var change = stateChanges[i]; var delta = change.lastValue - change.value; switch(change.input) { case left: console.log(Left button was + change.value? pushed:released); break; case leftThumbStickX: console.log(Left stick X axis: + change.value + with a relative change of + delta); break; } } // Update the game state. This API allows us to process all inputs that happen simultaneously together, // such as axis changes, and then only update the game state once for the whole batch. processInputs(); } // Receiving an event on change: gamepadInput.onchange = function(e) { processInput(gamepadInput.read()); }; Some points: - A browser may support many different layouts for the same device. For example, an Xbox 360 controller can easily be used as an NES or SNES controller, so it can expose those profiles. - Raw inputs for any unmapped inputs are included for all profiles. This allows games to make use of additional controls, without being forced to drop back to raw to access them at all. - If a profile is exposed at all, the inputs of the profile must be completely covered. - Layout may not always map 1:1 to raw inputs. For example, the Playstation controller's D-pad is four buttons in a cross; layouts may map these four buttons down to two axes. - All data in getCurrentState is absolute, since it doesn't mutate the object. Therefore, purely relative inputs, such as trackballs and (probably) steering wheels, are returned in an absolute, accumulated form. To get relative motion, subtract the value from the previous value. By the way, while I only included NES as an example, having a baseline layout like that (with a more generic name) is useful: tons of simple games use only a D-pad and a couple buttons. This would give them get the widest input device support possible, by not having to request a profile with far more features than they need. I haven't tried to incorporate Florian's suggestion of using something like ArrayBuffer. That could be supported later, eg. by providing a readIntoBuffer(buffer) next to read(). That's too complex to try to tackle all at once. What happens if there is no supported profile?
Re: GamepadObserver (ie. MutationObserver + Gamepad)
On Sat, Aug 4, 2012 at 7:10 PM, Florian Bösch pya...@gmail.com wrote: What happens if there is no supported profile? Oh nm, then it's raw, stupid question.
Re: GamepadObserver (ie. MutationObserver + Gamepad)
On Sat, Aug 4, 2012 at 7:03 PM, Glenn Maynard gl...@zewt.org wrote: I haven't tried to incorporate Florian's suggestion of using something like ArrayBuffer. That could be supported later, eg. by providing a readIntoBuffer(buffer) next to read(). That's too complex to try to tackle all at once. I've had an idea how improve the issues around current state and button misses. - Some uses (like look/strafe/move etc.) that are served by an axis always use current state. There is no benefit to know the event history leading up to that state. So this would use the current state. - Button uses always require all events as button press/release misses would be bad. So this would get all events. - A mixed model is where an axis is used with tresholds (like say a weapon trigger with the index finger). A miss is not bad, but response accuracy will improve from getting the event as close to the threshold as possible. So this would inform the API of its thresholds for this axis, so events can be issued when the high frequency polling detects the axis going over the it.
Re: GamepadObserver (ie. MutationObserver + Gamepad)
Sorry for the long-delayed reply. The tablets thread reminded me about this mail, which got lost in the black hole known as drafts... On Mon, May 7, 2012 at 2:33 PM, Scott Graham scot...@chromium.org wrote: - As you point out, the 360 controller is by far the most common PC gamepad, and on its native platforms its standard API XInput is exclusively polling based [4]. That doesn't mean we must or even should follow that design, but it does mean that even if we design a bunch of fancier/cleverer APIs we're still going to be largely constrained by that API. That is, there's not going to be any extra information in events that are synthesized from polled data. [5] Not exactly, since you can poll devices natively with these kinds of APIs much more quickly (without being excessively wasteful) than you can in JS. Polling at 120Hz (or even 1000Hz) natively, in a well-tuned thread, is much more realistic than doing it in JS. You can also do it in a high-priority or realtime thread, so timestamps are as accurate as possible. - Native deadzone handling (by the OS or internally to the device) is the only way to correctly handle deadzones when you don't have intimate knowledge of the hardware. It should be explicitly (if non-normatively) noted that native deadzone handling should be used when available. This has been discussed before, and it's certainly important. The one complexity that needs to be solved is handling 1D vs. 2D dead zones for axes. That is, should the X and Y be deadzoned as independent 1D axes, or should they be deadzoned as a 2D vector. I guess this could either be left to non-normative discussion and hope the implementations do something sensible, or additional API surface will be needed for a full solution. (I would probably lean towards non-normative, and ignore the separate 1D axes problem, myself.) Some hardware does deadzoning internally. Also, the type of deadzoning may depend on the device; the correct answer may be different for gamepad thumbsticks and joysticks, for example, and devices with different amounts of slop need dead zones tuned differently. I think this should be entirely up to the implementation. However, it may be worth a note that it *is* intended that deadzoning be handled either by the browser or somewhere lower down the stack, and not by web developers (up the stack). I'd suggest a halfway point between polling and events: a function to retrieve a list of device changes since the last call, and an event fired on the object the first time a new change is made. For example, var gamepad = window.openGamepad(0); gamepad.addEventListener(input, function(e) { var changes = gamepad.readChanges(); }, false); with changes being an array of objects, each object describing a timestamped change of state, eg: changes = [ { button: 0, state: 1.0, lastState: 0.85, timestamp: 1336102719319 } ] This allows using polling if your application is based on requestAnimationFrame, or events if you want to be told when there's something to read. There isn't an excess of events dispatched, because the event is only dispatched once per call to readChanges; if you only read changes once in a while you'll only receive the one event. It also solves all of the above problems with a most-recent-state-only interface. I think it's a loss to not have a full most-recent-state available. This isn't mutually exclusive with a separate getCurrentState() method. Many common styles of applications (rAF+games) would have to add a bunch of boilerplate that then merges these events to maintain the current state (especially awkward on XInput since the browser will be splitting them out of most-recent-state!). I don't see a lot of benefit to splitting each individual axis/button change out into a separate object. Is there a reason you prefer that way? It seems natural to me. You can detect the order of events, their relative timing, and you don't have to analyze a data structure to determine which piece changed. If all you care about is the most recent state, then use something like getCurrentState(). When readChanges() happens, I guess we'd need to consider some sort of staleness? What happens if a client only reads once every second? Or one every 5 minutes? Implementations should definitely be able to cap on the amount of stored data. For the most part, events can simply be dropped from the beginning of the queue. However, button release events should never be dropped unless there's another button depress event later in the buffer. Otherwise, buttons would get stuck to the application's view. The timestamp should be as accurate as possible to when the event actually happened, in the clock used by Date.now(), so you can tell how long ago the event happened. Accurate timestamps are important for games that require fine-grained timing; 2D
Re: GamepadObserver (ie. MutationObserver + Gamepad)
On Sat, Aug 4, 2012 at 12:52 AM, Glenn Maynard gl...@zewt.org wrote: On Mon, May 7, 2012 at 2:33 PM, Scott Graham scot...@chromium.org wrote: - As you point out, the 360 controller is by far the most common PC gamepad, and on its native platforms its standard API XInput is exclusively polling based [4]. That doesn't mean we must or even should follow that design, but it does mean that even if we design a bunch of fancier/cleverer APIs we're still going to be largely constrained by that API. That is, there's not going to be any extra information in events that are synthesized from polled data. [5] Not exactly, since you can poll devices natively with these kinds of APIs much more quickly (without being excessively wasteful) than you can in JS. Polling at 120Hz (or even 1000Hz) natively, in a well-tuned thread, is much more realistic than doing it in JS. You can also do it in a high-priority or realtime thread, so timestamps are as accurate as possible. Also don't forget that the browser only needs to poll once at each poll iteration and then the event queues can be multiplexed to any page reading from them, vastly more efficient. - Native deadzone handling (by the OS or internally to the device) is the only way to correctly handle deadzones when you don't have intimate knowledge of the hardware. It should be explicitly (if non-normatively) noted that native deadzone handling should be used when available. This has been discussed before, and it's certainly important. The one complexity that needs to be solved is handling 1D vs. 2D dead zones for axes. That is, should the X and Y be deadzoned as independent 1D axes, or should they be deadzoned as a 2D vector. I guess this could either be left to non-normative discussion and hope the implementations do something sensible, or additional API surface will be needed for a full solution. (I would probably lean towards non-normative, and ignore the separate 1D axes problem, myself.) Some hardware does deadzoning internally. Also, the type of deadzoning may depend on the device; the correct answer may be different for gamepad thumbsticks and joysticks, for example, and devices with different amounts of slop need dead zones tuned differently. I think this should be entirely up to the implementation. However, it may be worth a note that it *is* intended that deadzoning be handled either by the browser or somewhere lower down the stack, and not by web developers (up the stack). There is a snag with deadzoning and axis ranges. Driver enumeration of axes of an auto-calibrated device (or any device really) is often not delivering any usable information on this. There is a zoo of range/calibration utilities out there alongside some built-in utilities (don't know if windows still has those) which may or may not work satisfactory. I've tested a variety of devices for these issues, and after some work I came to the conclusion that the only way to reliably determine any dead zones and ranges is to let the user max out the axes and remember that. I'd suggest a halfway point between polling and events: a function to retrieve a list of device changes since the last call, and an event fired on the object the first time a new change is made. For example, var gamepad = window.openGamepad(0); gamepad.addEventListener(input, function(e) { var changes = gamepad.readChanges(); }, false); with changes being an array of objects, each object describing a timestamped change of state, eg: changes = [ { button: 0, state: 1.0, lastState: 0.85, timestamp: 1336102719319 } ] This allows using polling if your application is based on requestAnimationFrame, or events if you want to be told when there's something to read. There isn't an excess of events dispatched, because the event is only dispatched once per call to readChanges; if you only read changes once in a while you'll only receive the one event. It also solves all of the above problems with a most-recent-state-only interface. I think it's a loss to not have a full most-recent-state available. This isn't mutually exclusive with a separate getCurrentState() method. The most recent state is certainly important. But in some situations you want access to the entire queue of events since you last asked for it. I strongly agree that emptying the event queue is the right way to go. I strongly disagree that this should be objects. Working trough thousands of objects would be slow and would trigger the GC like mad. The events should be filled into an array buffer where they can processed at near C speeds and will not trigger the GC. When readChanges() happens, I guess we'd need to consider some sort of staleness? What happens if a client only reads once every second? Or one every 5 minutes? Implementations should definitely be able to cap on the amount of stored data.
Re: GamepadObserver (ie. MutationObserver + Gamepad)
On Fri, Aug 3, 2012 at 6:17 PM, Florian Bösch pya...@gmail.com wrote: There is a snag with deadzoning and axis ranges. Driver enumeration of axes of an auto-calibrated device (or any device really) is often not delivering any usable information on this. There is a zoo of range/calibration utilities out there alongside some built-in utilities (don't know if windows still has those) which may or may not work satisfactory. I've tested a variety of devices for these issues, and after some work I came to the conclusion that the only way to reliably determine any dead zones and ranges is to let the user max out the axes and remember that. This is still something implementations can deal with better than web apps. (Range calibration in Windows is done globally, but configuring dead zones is very rare.) Also, I'm guessing this isn't a problem for the Xbox 360 controller. I strongly disagree that this should be objects. Working trough thousands of objects would be slow and would trigger the GC like mad. On a quick test, Firefox is firing mousemove events at 120Hz; this is about the same magnitude of data. We don't currently have any infrastructure for using ArrayBuffers for complex data, so it'd either need to be something new (some sort of complex view), or an unfriendly API. Either would be a major obstacle to getting anything adopted. -- Glenn Maynard
Re: GamepadObserver (ie. MutationObserver + Gamepad)
On Thu, May 3, 2012 at 9:14 PM, Glenn Maynard gl...@zewt.org wrote: Here are some piecemeal thoughts on the subject of gamepads and the gamepad spec. I havn't closely followed earlier discussions (and there don't seem to have been any in a while), so much of this may have been covered before. Hi Glenn, Thanks for thinking about gamepads and the current draft, it's much appreciated. There's a lot of separate points here that it'd be great to get other people's thoughts on. A few high-level things to note in addition to my inline comments: - As you point out, the 360 controller is by far the most common PC gamepad, and on its native platforms its standard API XInput is exclusively polling based [4]. That doesn't mean we must or even should follow that design, but it does mean that even if we design a bunch of fancier/cleverer APIs we're still going to be largely constrained by that API. That is, there's not going to be any extra information in events that are synthesized from polled data. [5] - Some of the tradeoffs that you mention in terms of making the API more usable, or adding more functionality have been discussed before. In general the preference has been to expose data in a more raw/unprocessed format to allow for more flexibility to the consumer of the data. I'm not sure that the correct balance has been struck yet though. There's a tension between trying to make something usable out-of-the-box and avoiding getting into specifying things in too much detail in the spec, which will be harder to evolve and update, especially if we over-specify in a first version. - It should be possible to tell what's changed, not just the current state of the device. Needing to compare each piece of input to the previous state is cumbersome. This is the sort of thing that might go in the content helper library. It seems reasonable in an event-based api to provide previous and current though. - Native deadzone handling (by the OS or internally to the device) is the only way to correctly handle deadzones when you don't have intimate knowledge of the hardware. It should be explicitly (if non-normatively) noted that native deadzone handling should be used when available. This has been discussed before, and it's certainly important. The one complexity that needs to be solved is handling 1D vs. 2D dead zones for axes. That is, should the X and Y be deadzoned as independent 1D axes, or should they be deadzoned as a 2D vector. I guess this could either be left to non-normative discussion and hope the implementations do something sensible, or additional API surface will be needed for a full solution. (I would probably lean towards non-normative, and ignore the separate 1D axes problem, myself.) - It's very important that it's possible to receive gamepad data without polling. We don't need more web pages running setTimeout(0) loops as fast as browsers will let them, which is what it encourages. Not all pages are based on a requestAnimationFrame loop. Noted. - An API that can only return the current state loses button presses if they're released too quickly. It's common to press a button while the UI thread is held up for one reason or another--and you also can't assume that users can't press and release a button in under 16ms (they can). - APIs like that also lose the *order* of button presses, when they're pressed too quickly. (I've encountered this problem in my own experience; it's definitely possible--it's not even particularly hard--for a user to press two buttons in a specific order in under 16ms.) Both noted, balanced against my comment about XInput above. I'd suggest a halfway point between polling and events: a function to retrieve a list of device changes since the last call, and an event fired on the object the first time a new change is made. For example, var gamepad = window.openGamepad(0); gamepad.addEventListener(input, function(e) { var changes = gamepad.readChanges(); }, false); with changes being an array of objects, each object describing a timestamped change of state, eg: changes = [ { button: 0, state: 1.0, lastState: 0.85, timestamp: 1336102719319 } ] This allows using polling if your application is based on requestAnimationFrame, or events if you want to be told when there's something to read. There isn't an excess of events dispatched, because the event is only dispatched once per call to readChanges; if you only read changes once in a while you'll only receive the one event. It also solves all of the above problems with a most-recent-state-only interface. I think it's a loss to not have a full most-recent-state available. Many common styles of applications (rAF+games) would have to add a bunch of boilerplate that then merges these events to maintain the current state (especially awkward on XInput since the browser will be splitting them out of most-recent-state!). I don't see a
Inking out a PenObserver, was Re: GamepadObserver (ie. MutationObserver + Gamepad)
Glenn, all of your points apply well to pressure sensitive pen input. Pen vendors are not on the list, but their devices are quite similar. I've changed the subject line and my responses are to your gamepad notes in respect to their application to pen input. Pen input in this case is pressure sensitive input with high sensitivity to time, pressure and coordinates, with a sample rate faster than 60hz. Pen devices are commonly connected via USB. The Gamepad API is an experimental API which deals with similar device characteristics. Pen vendors are not active with the W3C though the InkML specification did make it into recommendation status. On 5/3/2012 9:14 PM, Glenn Maynard wrote: Here are some piecemeal thoughts on the subject of gamepads and the gamepad spec. I havn't closely followed earlier discussions (and there don't seem to have been any in a while), so much of this may have been covered before. - It should be possible to tell what's changed, not just the current state of the device. Needing to compare each piece of input to the previous state is cumbersome. There's a variety of options that may not ever change. Some devices may simply not support the values. With pen, we've got items like azimuth and altitude. Few pens support it. As authors, we just ignore that input or do feature tests (if pressure exists, and those metrics are still 0, then they're likely not supported). - Native deadzone handling (by the OS or internally to the device) is the only way to correctly handle deadzones when you don't have intimate knowledge of the hardware. It should be explicitly (if non-normatively) noted that native deadzone handling should be used when available. - It's very important that it's possible to receive gamepad data without polling. We don't need more web pages running setTimeout(0) loops as fast as browsers will let them, which is what it encourages. Not all pages are based on a requestAnimationFrame loop. Recently, I found a setTimeout(20ms, then rAF) to work reasonably well for my little laptop. Any faster, and I lose more data points, and slower and the lag [drawing to the screen] is disorienting. That's just a subjective observation on a light-duty laptop with one vendor, just using a track pad. - An API that can only return the current state loses button presses if they're released too quickly. It's common to press a button while the UI thread is held up for one reason or another--and you also can't assume that users can't press and release a button in under 16ms (they can). The rAF loop can in some circumstances slow the polling or otherwise result in data loss. It's a real pain. We get our best fidelity if the screen is empty and we're not drawing. Once we start drawing, we're bound to lose some data. - APIs like that also lose the *order* of button presses, when they're pressed too quickly. (I've encountered this problem in my own experience; it's definitely possible--it's not even particularly hard--for a user to press two buttons in a specific order in under 16ms.) My pen has two buttons in addition to two high sensitivity pressure inputs. Wacom introduced onpressurechange events in their Flash SDL. In some sense, it's an experimental plugin for WebKit as it runs in Air, but it's really closer to the Flash side of things. I'd suggest a halfway point between polling and events: a function to retrieve a list of device changes since the last call, and an event fired on the object the first time a new change is made. For example, var gamepad = window.openGamepad(0); gamepad.addEventListener(input, function(e) { var changes = gamepad.readChanges(); }, false); with changes being an array of objects, each object describing a timestamped change of state, eg: changes = [ { button: 0, state: 1.0, lastState: 0.85, timestamp: 1336102719319 } ] At what point would we consider entries to be stale? Is that something that should be a concern? It'd take quite a lot of changes to be a problem. As a security issue -- do we need to point out that two windows should not have concurrent access to the same gamepad (such as applies to keyboard and mouse)? That's another issue I noticed with pen tablet plugins. -Charles
Re: GamepadObserver (ie. MutationObserver + Gamepad)
snip Weird, because you posted this: https://docs.google.com/document/d/1atsxnstVybfovkX_f6xf2P25i1NT0ilCihJuPDwYWEU/edit?hl=en_US here: https://bugzilla.mozilla.org/show_bug.cgi?id=604039#c40 Just to be clear, I'm not trying to be aggressive or confrontational, but I just reread my message from last night and realized it could easily be read that way - apologies! :) I'm not sure what you mean. It is my opinion is that it would be inefficient to spam 50+ button and axis events multiplied by (say) 4 devices @ 60Hz, which is why I've been pursuing the design of a polling API. So, though what you quoted was written a while back, it seems to still represent my opinion pretty accurately I'm not sure if everyone agrees with me on that though, which is why I said I didn't think there was a consensus. I believe Mozilla's vendor-prefixed implementation includes some of those type of events, so perhaps we will soon have more empirical data on the subject. I agree with both arguments, because you're right: it's simply too much volume. As for existing data or APIs, Microsoft's DirectInput API provides both polling and event notifications. Going back to the notes at http://dvcs.w3.org/hg/gamepad/raw-file/tip/gamepad.html#other-events I'd say the single event gamepagechanged, where the event object has a property describing the complete state of the gamepad, is more in line with the DirectInput event system. Something like this would also make it easier to detect multiple, sequentially and/or simultaneously changed states... eg. press and hold B; while holding B, press A (for the sake of example, the states will be HIGH and LOW, both start LOW) Dispatch an event for the change of state on B. B set HIGH Dispatch an event for the change of state on A. A set HIGH, B still HIGH When the event is dispatched for the changed state of A, the handler receives an event object that will show no change to the state of B since it was set to HIGH. With separate axis and button events, I dont think this could be reasonably feasible. Again, apologies for any perceived negative tone from earlier messages :) Rick [snip]
Re: GamepadObserver (ie. MutationObserver + Gamepad)
say the single event gamepagechanged, where the event object has a property describing the complete state of the gamepad That seems fine to me. One caveat is that XInput on Windows (for example) is a polling-only API anyway, so it might not be reasonable to expect better data in the event, depending on how it's synthesized. i.e. Depending on UA, platform, etc., content might get better results from polling than events, or vice versa. Again, apologies for any perceived negative tone from earlier messages :) shrug Email sucks. :-) Hope my response wasn't too brusque either. scott
Re: GamepadObserver (ie. MutationObserver + Gamepad)
Here are some piecemeal thoughts on the subject of gamepads and the gamepad spec. I havn't closely followed earlier discussions (and there don't seem to have been any in a while), so much of this may have been covered before. - It should be possible to tell what's changed, not just the current state of the device. Needing to compare each piece of input to the previous state is cumbersome. - Native deadzone handling (by the OS or internally to the device) is the only way to correctly handle deadzones when you don't have intimate knowledge of the hardware. It should be explicitly (if non-normatively) noted that native deadzone handling should be used when available. - It's very important that it's possible to receive gamepad data without polling. We don't need more web pages running setTimeout(0) loops as fast as browsers will let them, which is what it encourages. Not all pages are based on a requestAnimationFrame loop. - An API that can only return the current state loses button presses if they're released too quickly. It's common to press a button while the UI thread is held up for one reason or another--and you also can't assume that users can't press and release a button in under 16ms (they can). - APIs like that also lose the *order* of button presses, when they're pressed too quickly. (I've encountered this problem in my own experience; it's definitely possible--it's not even particularly hard--for a user to press two buttons in a specific order in under 16ms.) I'd suggest a halfway point between polling and events: a function to retrieve a list of device changes since the last call, and an event fired on the object the first time a new change is made. For example, var gamepad = window.openGamepad(0); gamepad.addEventListener(input, function(e) { var changes = gamepad.readChanges(); }, false); with changes being an array of objects, each object describing a timestamped change of state, eg: changes = [ { button: 0, state: 1.0, lastState: 0.85, timestamp: 1336102719319 } ] This allows using polling if your application is based on requestAnimationFrame, or events if you want to be told when there's something to read. There isn't an excess of events dispatched, because the event is only dispatched once per call to readChanges; if you only read changes once in a while you'll only receive the one event. It also solves all of the above problems with a most-recent-state-only interface. The timestamp should be as accurate as possible to when the event actually happened, in the clock used by Date.now(), so you can tell how long ago the event happened. Accurate timestamps are important for games that require fine-grained timing; 2D fighters and rhythm games can make use of this, for example (they may render at 60 FPS but perform game logic at a much higher rate). Finally, some considerations other than the basic API: Any serious gamepad API must not ignore the realities of the most common joystick layouts, most importantly the 360 controller (far and away the most common PC gamepad today). The API needs to give recommended mappings from buttons and axes to the parts of those real-world controllers, so users don't have to configure devices manually. This is absolutely critical; every native PC game that supports gamepads now just works for the 360 controller, requiring no configuration. (For years joystick APIs on PCs tried to pretend that joysticks could be boiled down to a bunch of abstract axes and buttons. The user experience that results in is abysmal.) If the gamepad model is known, it should also be explicitly exposed, so applications can show help texts which match the actual buttons on the gamepad (with a registry somewhere--which could just be a wiki page--describing the string to product mappings). Every native PC game also now does this, finally catching up to what console games have done for decades. If a web API for gamepads doesn't do this, it'll be years behind everything else. Additionally, a more involved system of input profiles would be needed for practical forward-compatibility, eg. so applications can say this is a list of device types I understand and UAs can expose the device as the nearest match. This allows old applications to work better with devices they don't know about, without requiring the user to manually bind buttons and axes. I can't stress how important it is to not require the user to configure every game manually; it's simply no longer acceptable. -- Glenn Maynard
Re: GamepadObserver (ie. MutationObserver + Gamepad)
On 05/03/2012 12:48 AM, Rick Waldron wrote: Instead of traditional DOM events being used for Other Events[1], and considering the high frequency of Gamepad state changes, it might make sense to provide an API similar to MutationObserver, where a MutationRecord is created that has snapshots of current and previous states of axes or buttons... This is entirely hypothetical: (new GamepadObserver(function(mutations) { console.log( mutations ); /* { previousState: { readonly attribute string id; readonly attribute long index; readonly attribute DOMTimeStamp timestamp; // Either or both of the following, bases on the options list readonly attribute float[] axes; readonly attribute float[] buttons; } currentState: { readonly attribute string id; readonly attribute long index; readonly attribute DOMTimeStamp timestamp; // Either or both of the following, bases on the options list readonly attribute float[] axes; readonly attribute float[] buttons; } } */ })).observe(navigator.gamepads[0], { axesList: true }); // axesList, buttonsList [1] http://dvcs.w3.org/hg/gamepad/raw-file/tip/gamepad.html#other-events Rick no need for this kind of thing. Gamepad data is external, so dispatching events is better. The event can of course keep a list of changes since the previous event dispatch. -Olli
Re: GamepadObserver (ie. MutationObserver + Gamepad)
On Wed, May 2, 2012 at 5:54 PM, Olli Pettay olli.pet...@helsinki.fi wrote: On 05/03/2012 12:48 AM, Rick Waldron wrote: Instead of traditional DOM events being used for Other Events[1], and considering the high frequency of Gamepad state changes, it might make sense to provide an API similar to MutationObserver, where a MutationRecord is created that has snapshots of current and previous states of axes or buttons... This is entirely hypothetical: (new GamepadObserver(function(**mutations) { console.log( mutations ); /* { previousState: { readonly attribute string id; readonly attribute long index; readonly attribute DOMTimeStamp timestamp; // Either or both of the following, bases on the options list readonly attribute float[] axes; readonly attribute float[] buttons; } currentState: { readonly attribute string id; readonly attribute long index; readonly attribute DOMTimeStamp timestamp; // Either or both of the following, bases on the options list readonly attribute float[] axes; readonly attribute float[] buttons; } } */ })).observe(navigator.**gamepads[0], { axesList: true }); // axesList, buttonsList [1] http://dvcs.w3.org/hg/gamepad/**raw-file/tip/gamepad.html#** other-eventshttp://dvcs.w3.org/hg/gamepad/raw-file/tip/gamepad.html#other-events Rick no need for this kind of thing. Gamepad data is external, so dispatching events is better. The event can of course keep a list of changes since the previous event dispatch. I had originally argued for an event, though met with resistance. The reasoning was that gamepad state changes are far too high resolution which would result in high event dispatch volume. Is there documented discussion surrounding the return to an event being desirable? Thanks for your time. Rick -Olli
Re: GamepadObserver (ie. MutationObserver + Gamepad)
On Wed, May 2, 2012 at 3:12 PM, Rick Waldron waldron.r...@gmail.com wrote: On Wed, May 2, 2012 at 5:54 PM, Olli Pettay olli.pet...@helsinki.fi wrote: On 05/03/2012 12:48 AM, Rick Waldron wrote: Instead of traditional DOM events being used for Other Events[1], and considering the high frequency of Gamepad state changes, it might make sense to provide an API similar to MutationObserver, where a MutationRecord is created that has snapshots of current and previous states of axes or buttons... This is entirely hypothetical: (new GamepadObserver(function(mutations) { console.log( mutations ); /* { previousState: { readonly attribute string id; readonly attribute long index; readonly attribute DOMTimeStamp timestamp; // Either or both of the following, bases on the options list readonly attribute float[] axes; readonly attribute float[] buttons; } currentState: { readonly attribute string id; readonly attribute long index; readonly attribute DOMTimeStamp timestamp; // Either or both of the following, bases on the options list readonly attribute float[] axes; readonly attribute float[] buttons; } } */ })).observe(navigator.gamepads[0], { axesList: true }); // axesList, buttonsList [1] http://dvcs.w3.org/hg/gamepad/raw-file/tip/gamepad.html#other-events Rick no need for this kind of thing. Gamepad data is external, so dispatching events is better. The event can of course keep a list of changes since the previous event dispatch. I had originally argued for an event, though met with resistance. The reasoning was that gamepad state changes are far too high resolution which would result in high event dispatch volume. Is there documented discussion surrounding the return to an event being desirable? I'm not aware of any pro/against discussion on that other than the Mozilla bug: https://bugzilla.mozilla.org/show_bug.cgi?id=604039. I don't think there's consensus on that point yet. The observer ability of filtering to desired-only seems interesting. I guess it might get complex if it was to be filtered more finely than just axesList/buttonsList. Thanks for your time. Rick -Olli
Re: GamepadObserver (ie. MutationObserver + Gamepad)
On Wed, May 2, 2012 at 7:01 PM, Scott Graham scot...@chromium.org wrote: On Wed, May 2, 2012 at 3:12 PM, Rick Waldron waldron.r...@gmail.com wrote: On Wed, May 2, 2012 at 5:54 PM, Olli Pettay olli.pet...@helsinki.fi wrote: On 05/03/2012 12:48 AM, Rick Waldron wrote: Instead of traditional DOM events being used for Other Events[1], and considering the high frequency of Gamepad state changes, it might make sense to provide an API similar to MutationObserver, where a MutationRecord is created that has snapshots of current and previous states of axes or buttons... This is entirely hypothetical: (new GamepadObserver(function(mutations) { console.log( mutations ); /* { previousState: { readonly attribute string id; readonly attribute long index; readonly attribute DOMTimeStamp timestamp; // Either or both of the following, bases on the options list readonly attribute float[] axes; readonly attribute float[] buttons; } currentState: { readonly attribute string id; readonly attribute long index; readonly attribute DOMTimeStamp timestamp; // Either or both of the following, bases on the options list readonly attribute float[] axes; readonly attribute float[] buttons; } } */ })).observe(navigator.gamepads[0], { axesList: true }); // axesList, buttonsList [1] http://dvcs.w3.org/hg/gamepad/raw-file/tip/gamepad.html#other-events Rick no need for this kind of thing. Gamepad data is external, so dispatching events is better. The event can of course keep a list of changes since the previous event dispatch. I had originally argued for an event, though met with resistance. The reasoning was that gamepad state changes are far too high resolution which would result in high event dispatch volume. Is there documented discussion surrounding the return to an event being desirable? I'm not aware of any pro/against discussion on that other than the Mozilla bug: https://bugzilla.mozilla.org/show_bug.cgi?id=604039. I don't think there's consensus on that point yet. Weird, because you posted this: https://docs.google.com/document/d/1atsxnstVybfovkX_f6xf2P25i1NT0ilCihJuPDwYWEU/edit?hl=en_US here: https://bugzilla.mozilla.org/show_bug.cgi?id=604039#c40 In which you state: *The current proposal for accessing Joysticks from Mozilla is an event-based one, mirroring keyboard and mouse input. https://wiki.mozilla.org/JoystickAPI* At 60fps, with all the of the analog buttons, triggers, vibration, gyroscope on modern game pad (PS3, Xbox 360, and others) there will easily be many events within each 16ms frame. With so many events, it’s both inefficient, as many events will only be handled in order to update the “current state”, and complex to correctly maintain and update the state, which will have to be written in every application. On the positive side, events and polling are not mutually exclusive. While it seems like the primary use-case for gamepad input is for games, which would prefer polling, there may be some uses where the event-based is better, say for using the gamepad as an input device to advance frames in a presentation. Rick The observer ability of filtering to desired-only seems interesting. I guess it might get complex if it was to be filtered more finely than just axesList/buttonsList. Thanks for your time. Rick -Olli
Re: GamepadObserver (ie. MutationObserver + Gamepad)
On Wed, May 2, 2012 at 6:09 PM, Rick Waldron waldron.r...@gmail.com wrote: On Wed, May 2, 2012 at 7:01 PM, Scott Graham scot...@chromium.org wrote: On Wed, May 2, 2012 at 3:12 PM, Rick Waldron waldron.r...@gmail.com wrote: On Wed, May 2, 2012 at 5:54 PM, Olli Pettay olli.pet...@helsinki.fi wrote: On 05/03/2012 12:48 AM, Rick Waldron wrote: Instead of traditional DOM events being used for Other Events[1], and considering the high frequency of Gamepad state changes, it might make sense to provide an API similar to MutationObserver, where a MutationRecord is created that has snapshots of current and previous states of axes or buttons... This is entirely hypothetical: (new GamepadObserver(function(mutations) { console.log( mutations ); /* { previousState: { readonly attribute string id; readonly attribute long index; readonly attribute DOMTimeStamp timestamp; // Either or both of the following, bases on the options list readonly attribute float[] axes; readonly attribute float[] buttons; } currentState: { readonly attribute string id; readonly attribute long index; readonly attribute DOMTimeStamp timestamp; // Either or both of the following, bases on the options list readonly attribute float[] axes; readonly attribute float[] buttons; } } */ })).observe(navigator.gamepads[0], { axesList: true }); // axesList, buttonsList [1] http://dvcs.w3.org/hg/gamepad/raw-file/tip/gamepad.html#other-events Rick no need for this kind of thing. Gamepad data is external, so dispatching events is better. The event can of course keep a list of changes since the previous event dispatch. I had originally argued for an event, though met with resistance. The reasoning was that gamepad state changes are far too high resolution which would result in high event dispatch volume. Is there documented discussion surrounding the return to an event being desirable? I'm not aware of any pro/against discussion on that other than the Mozilla bug: https://bugzilla.mozilla.org/show_bug.cgi?id=604039. I don't think there's consensus on that point yet. Weird, because you posted this: https://docs.google.com/document/d/1atsxnstVybfovkX_f6xf2P25i1NT0ilCihJuPDwYWEU/edit?hl=en_US here: https://bugzilla.mozilla.org/show_bug.cgi?id=604039#c40 I'm not sure what you mean. It is my opinion is that it would be inefficient to spam 50+ button and axis events multiplied by (say) 4 devices @ 60Hz, which is why I've been pursuing the design of a polling API. So, though what you quoted was written a while back, it seems to still represent my opinion pretty accurately. I'm not sure if everyone agrees with me on that though, which is why I said I didn't think there was a consensus. I believe Mozilla's vendor-prefixed implementation includes some of those type of events, so perhaps we will soon have more empirical data on the subject. In which you state: The current proposal for accessing Joysticks from Mozilla is an event-based one, mirroring keyboard and mouse input. https://wiki.mozilla.org/JoystickAPI At 60fps, with all the of the analog buttons, triggers, vibration, gyroscope on modern game pad (PS3, Xbox 360, and others) there will easily be many events within each 16ms frame. With so many events, it’s both inefficient, as many events will only be handled in order to update the “current state”, and complex to correctly maintain and update the state, which will have to be written in every application. On the positive side, events and polling are not mutually exclusive. While it seems like the primary use-case for gamepad input is for games, which would prefer polling, there may be some uses where the event-based is better, say for using the gamepad as an input device to advance frames in a presentation. Rick The observer ability of filtering to desired-only seems interesting. I guess it might get complex if it was to be filtered more finely than just axesList/buttonsList. Thanks for your time. Rick -Olli