This is an archived snapshot of W3C's public bugzilla bug tracker, decommissioned in April 2019. Please see the home page for more details.

Bug 26181 - Spec should specify the presentation of the array returned by navigator.getGamepads() w.r.t "holes"
Summary: Spec should specify the presentation of the array returned by navigator.getGa...
Status: RESOLVED FIXED
Alias: None
Product: WebAppsWG
Classification: Unclassified
Component: HISTORICAL - Gamepad (show other bugs)
Version: unspecified
Hardware: All All
: P2 normal
Target Milestone: ---
Assignee: Ted Mielczarek [:ted]
QA Contact: public-webapps-bugzilla
URL:
Whiteboard:
Keywords:
Depends on: 27549
Blocks:
  Show dependency treegraph
 
Reported: 2014-06-23 18:39 UTC by Ted Mielczarek [:ted]
Modified: 2015-02-09 09:24 UTC (History)
11 users (show)

See Also:


Attachments

Description Ted Mielczarek [:ted] 2014-06-23 18:39:24 UTC
Currently the spec is silent about how the gamepad array should look in the face of removing Gamepads.

What Firefox implements (I need to check other browsers) is like:

<no gamepads connected>
navigator.getGamepads() == []

<connect gamepad 1>
navigator.getGamepads() == [Gamepad]

<connect gamepad 2>
navigator.getGamepads() == [Gamepad, Gamepad]

<disconnect gamepad 1>
navigator.getGamepads() == [null, Gamepad]

The intent is that the array is always at least max(Gamepad.index)+1 length, and entries where there's no longer a Gamepad present are "holes". Firefox uses null for their values, but Chrome and IE use undefined, so the spec should clarify that. (undefined seems sensible)
Comment 1 Boris Zbarsky 2014-06-23 18:42:19 UTC
Undefined, or hole?  Those are actually observably different in JavaScript:

 [,1].hasOwnProperty(0) === false

vs

 [undefined,1].hasOwnProperty(0) === true

while of course both arrays will return undefined if indexed with [0].
Comment 2 Brady Eidson 2014-06-23 18:45:49 UTC
(In reply to Boris Zbarsky from comment #1)
> Undefined, or hole?  Those are actually observably different in JavaScript:
> 

Since Chrome and IE use undefined, and Firefox uses null, none of them being holes...  I would say undefined.
Comment 3 Ted Mielczarek [:ted] 2014-06-23 18:59:39 UTC
(In reply to Boris Zbarsky from comment #1)
> Undefined, or hole?  Those are actually observably different in JavaScript:

I had no idea, I was going to ask you that question. Thanks for the info!

I think since existing implementations already use undefined that's what we'll spec.
Comment 4 Boris Zbarsky 2014-06-23 19:01:09 UTC
You checked that the existing impls actually use undefined, not holes, I assume?
Comment 5 Ted Mielczarek [:ted] 2014-06-23 19:12:45 UTC
...apparently they're holes in IE/Chrome:

Chrome:

> navigator.getGamepads()
GamepadList {0: Gamepad, 1: undefined, 2: Gamepad, 3: undefined, length: 4, item: function}
> navigator.getGamepads().hasOwnProperty(1)
false
> navigator.getGamepads().length
4

IE:

> navigator.getGamepads()
[
   0: undefined,
   1: { },
   2: undefined,
   3: undefined,
   length: 4
]
> navigator.getGamepads().hasOwnProperty(0)
false
> navigator.getGamepads().length
4


(it turns out Developer consoles just aren't very good at making the distinction here)
Comment 6 Ted Mielczarek [:ted] 2014-06-24 00:59:52 UTC
I fleshed out the text around getGamepads:
https://dvcs.w3.org/hg/gamepad/raw-file/default/gamepad.html#navigator-interface-extension
https://dvcs.w3.org/hg/gamepad/rev/1719d51557f2

I'm not sure if I described the behavior with holes clearly. Boris: is there a more-correct way to state that?
Comment 7 Boris Zbarsky 2014-06-24 01:43:38 UTC
It looks OK to me; I'm not aware of a clearer way to define this short of an actual algorithmic description (create an array, set length to N, define property with name K to have the value X, etc).  An informative example that actually makes it clear that unset means the property is not present as opposed to set to undefined might be good.
Comment 8 Brady Eidson 2014-06-24 01:55:19 UTC
> Array indices for which there is no Gamepad with the corresponding index should be unset.

I would say:

"Array indices for which there is no connected Gamepad with the corresponding index should be undefined."
Comment 9 Boris Zbarsky 2014-06-24 02:05:58 UTC
That's too easy to read as "set to the value undefined" as opposed to "not set to anything".
Comment 10 Brady Eidson 2014-06-24 02:07:29 UTC
Ah, fair enough.  Not quite sure of a more clear way to state that part then.

I stand by the s/no Gamepad/no connected Gamepad/ change, though.
Comment 11 Ted Mielczarek [:ted] 2014-06-24 12:45:35 UTC
Added the "connected" clarification, and added a code sample describing expected behavior:
https://dvcs.w3.org/hg/gamepad/rev/7de7f215f35b
Comment 12 Olli Pettay 2014-06-24 14:19:13 UTC
So Chrome seems to return some sort of GamepadList. What does IE return?
What is navigator.getGamepads().__proto__ ?

In Gecko that is currently Array.
Comment 13 Ted Mielczarek [:ted] 2014-06-24 15:08:34 UTC
IE (dev channel, DC1C0):
navigator.getGamepads().__proto__
[
   length: 0
]

Chrome (Canary, 38.0.2065.0) :
navigator.getGamepads().__proto__
GamepadList {item: function}
Comment 14 Ted Mielczarek [:ted] 2014-06-24 15:10:03 UTC
(In reply to Ted Mielczarek [:ted] from comment #13)
> IE (dev channel, DC1C0):
> navigator.getGamepads().__proto__
> [
>    length: 0
> ]

This didn't copy/paste what it showed me in the console, it says [object Array].
Comment 15 Olli Pettay 2014-12-09 17:17:19 UTC
I wish webidl had some way to say that returned array has holes.
Something like
sequence<optional Gamepad> getGamepads();

That would be relatively understandable, IMO.
Comment 16 Ted Mielczarek [:ted] 2014-12-09 18:07:21 UTC
I'd like to make the spec a bit more sensible here. I'm not particularly tied to a certain behavior here (and I don't think web content is at this point either), but I'd like to hew reasonably closely to what implementations are doing and the direction of the web platform.

Apparently ES6 is doing its best to unsupport sparse arrays as a concept, so trying to spec "holes" here is probably not a winning move. Ideally we'd just have a way of spec'ing that the empty slots get undefined.
Comment 17 Ted Mielczarek [:ted] 2014-12-10 17:29:28 UTC
I'm fishing for feedback from other implementers to see if anyone has any reasoning for what they've already implemented.
Comment 18 Scott Graham 2014-12-10 18:02:42 UTC
It wasn't particularly intentional in Chrome to my knowledge, it's just whatever the bindings code did with "nothing".

It looks like maybe due to https://code.google.com/p/chromium/codesearch#chromium/src/third_party/WebKit/Source/modules/gamepad/GamepadList.idl&l=31 .

Per comment 16, undefined rather than holey is fine with me.
Comment 19 Ted Mielczarek [:ted] 2014-12-10 18:47:17 UTC
The only tricky bit here right now is that WebIDL doesn't have a way to specify "Gamepad or undefined" as a type. We could write "sequence<Gamepad?> getGamepads()" to spec the Firefox behavior of "Gamepad or null", but if we want to use undefined we'll have to have the WebIDL spec invent some way to say that.
Comment 20 Scott Graham 2014-12-10 18:58:30 UTC
Uh, OK. Personally, any of {false, null, undefined, <hole>} seem fine to me. null is fine with me if that's easiest to spec and meets people's expectations.
Comment 21 Ted Mielczarek [:ted] 2014-12-10 19:00:13 UTC
Thanks for the feedback. I feel similarly, I just want the spec to say precisely what should happen here so we can make all our implementations agree.
Comment 22 Scott Graham 2014-12-10 19:28:48 UTC
(In reply to Ted Mielczarek [:ted] from comment #21)
> Thanks for the feedback. I feel similarly, I just want the spec to say
> precisely what should happen here so we can make all our implementations
> agree.

Yes of course. sequence<Gamepad?> implying null sounds good then, thanks!
Comment 23 Jacob Rossi [MSFT] 2014-12-10 22:22:30 UTC
Holes make sense to me for keeping track of player/gamepad association. IE uses undefined because that's what our type system happens to produce by default and it matched Chrome. \o/
Comment 24 Boris Zbarsky 2014-12-10 22:26:06 UTC
Except it doesn't match Chrome, because Chrome currently uses holes, not undefined.
Comment 25 Jacob Rossi [MSFT] 2014-12-10 23:11:26 UTC
The console (unclearly) outputs undefined but is a hole in both IE and Chrome. This code runs identical in IE and Chrome:

var g = navigator.getGamepads()
g
   {0: undefined, 1: undefined, 2: undefined, 3: undefined, length: 4, item: function}
g.hasOwnProperty(0)
   false

So, unless I'm missing something, the only difference I see here between IE & Chrome is that IE uses an Array and Chrome uses some GamepadList interface (which seems wrong per spec)?
Comment 26 Brandon Jones 2014-12-11 00:40:31 UTC
If WebIDL doesn't have a way of specifying Gamepad or undefined/hole then I'm all for using a sequence of nullable Gamepads. I'm also not aware of any technical reason for using the GamepadList type, or if there was one I think it's since been negated. We should be able to switch to a proper sequence without any trouble.
Comment 27 Philip Jägenstedt 2014-12-17 23:04:41 UTC
It would be great to resolve this so that GamepadList can be retired in Blink:
https://codereview.chromium.org/808643005/

Doing what the spec currently says with hasOwnPropety(n) being false for the holes seems tricky. Null is easier to implement, but then Array.forEach won't skip those entries, if that was the idea.

Off-topically, I'd be interested to know what purpose Gamepad.index and trying to preserve that index in the returned array serves. It seems a bit odd, but I assume it's been discussed to death already.
Comment 28 Ted Mielczarek [:ted] 2014-12-18 14:26:28 UTC
(In reply to Philip Jägenstedt from comment #27)
> Doing what the spec currently says with hasOwnPropety(n) being false for the
> holes seems tricky. Null is easier to implement, but then Array.forEach
> won't skip those entries, if that was the idea.

I liked the appeal of being able to skip missing entries, but it looks like ES6 is ridding itself of sparse Arrays, so we're swimming upstream by going that way.


> Off-topically, I'd be interested to know what purpose Gamepad.index and
> trying to preserve that index in the returned array serves. It seems a bit
> odd, but I assume it's been discussed to death already.

I don't know that it's been discussed much in the spec context, it's pretty simple really. The index can be thought of as the "player number". This is pretty common in console-land where you have a fixed number of controllers, and Gamepad is taking a cue from them (most specifically you can look at Microsoft's XInput: http://msdn.microsoft.com/en-us/library/windows/desktop/ee417001%28v=vs.85%29.aspx#multiple_controllers ). Being able to keep track of which controller is which, especially when you may have multiple devices of the same type connected is pretty important. Having .index correspond to the index in the getGamepads array just seemed sensible at the time, since it allows you to write code like:

function onconnected(e) {
  player.gamepad = e.gamepad.index;
}

function handleInput() {
  var gamepad = navigator.getGamepads()[player.gamepad];
}

If we compacted the array and got rid of empty entries you'd have to search the array to find the matching device, which seems awkward.
Comment 29 Ted Mielczarek [:ted] 2014-12-18 14:34:29 UTC
AFAICT we have two plausible paths forward:
1) Change the spec to match Firefox's implementation: sequence<Gamepad?> getGamepads()

This has the benefit of being incredibly easy to spec since the concept is easily expressible in WebIDL. Still need some spec language to indicate that implementations should return null for controllers that have been removed, but I need to write that either way.

2) Spec a GamepadList object with an indexed property getter, and additionally an iterator that returns only entries that are present (the default behavior of ES6 iterators on objects with indexed property getters/length properties is to iterate over 0..length-1). This could capture the existing intended semantics, but it's awfully complicated, doesn't seem to provide a whole lot of benefit, and feels like swimming against the tide of the web platform, since ES6 is trying to deprecate sparse Arrays.

I'm leaning towards #1 (and the Blink folks have said they're amenable). Does anyone have a compelling reason to argue for #2?
Comment 30 Ted Mielczarek [:ted] 2014-12-18 14:46:52 UTC
For #2, WebIDL has recently grown a maplike declaration that we could use:
http://heycam.github.io/webidl/#dfn-maplike-declaration

Presumably the resulting spec would look something like:

partial interface Navigator {
  GamepadList getGamepads();
};

interface GamepadList {
  readonly maplike<unsigned long, Gamepad>;
};

This doesn't seem overly terrible, but it does add a fair bit of implementation complexity just to avoid exposing a value for disconnected controllers.

In reality, most of the usage examples I've seen (and written) have looked like:
var gamepads = navigator.getGamepads();
for (var i = 0; i < gamepads.length; i++) {
  if (gamepads[i]) { ... }
}

which works either way.

If we implemented #2 then you could eventually write:
for (var gamepad of navigator.getGamepads()) {
  ...
}

which is certainly appealing.
Comment 31 Ted Mielczarek [:ted] 2014-12-18 14:55:48 UTC
Actually, no, since Map semantics don't match Array semantics, and existing content is relying on that (indexed getter, .length property).

We could define this using an iterator but it'd get a bit torturous.

Jacob: do you have any objections to approach #1? I'm sorry that the spec left this undefined.
Comment 32 Philip Jägenstedt 2014-12-18 14:59:31 UTC
(In reply to Ted Mielczarek [:ted] from comment #28)
> > Off-topically, I'd be interested to know what purpose Gamepad.index and
> > trying to preserve that index in the returned array serves. It seems a bit
> > odd, but I assume it's been discussed to death already.
> 
> I don't know that it's been discussed much in the spec context, it's pretty
> simple really. The index can be thought of as the "player number". This is
> pretty common in console-land where you have a fixed number of controllers,
> and Gamepad is taking a cue from them (most specifically you can look at
> Microsoft's XInput:
> http://msdn.microsoft.com/en-us/library/windows/desktop/ee417001%28v=vs.
> 85%29.aspx#multiple_controllers ). Being able to keep track of which
> controller is which, especially when you may have multiple devices of the
> same type connected is pretty important. Having .index correspond to the
> index in the getGamepads array just seemed sensible at the time, since it
> allows you to write code like:
> 
> function onconnected(e) {
>   player.gamepad = e.gamepad.index;
> }
> 
> function handleInput() {
>   var gamepad = navigator.getGamepads()[player.gamepad];
> }
> 
> If we compacted the array and got rid of empty entries you'd have to search
> the array to find the matching device, which seems awkward.

Why not:

function onconnected(e) {
  player.gamepad = e.gamepad;
}

And then just use that until the gamepaddisconnected fires or player.gamepad.connected is false? Getting the gamepad via getGamepads() on each input event or animation frame will generate a lot of garbage since a new Array is returned each time.

The XInputGetState API seems to need an index because there's no equivalent to Gamepad objects to hold on to.
Comment 33 Philip Jägenstedt 2014-12-18 15:00:28 UTC
Of the two options in comment #29, I think the first (Gamepad?[]) is better.
Comment 34 Ted Mielczarek [:ted] 2014-12-18 15:06:14 UTC
(In reply to Philip Jägenstedt from comment #32)
> Why not:
> 
> function onconnected(e) {
>   player.gamepad = e.gamepad;
> }
> 
> And then just use that until the gamepaddisconnected fires or
> player.gamepad.connected is false? Getting the gamepad via getGamepads() on
> each input event or animation frame will generate a lot of garbage since a
> new Array is returned each time.

Congratulations, you found the other big spec bug I need to fix: bug 21434.
Comment 35 Ted Mielczarek [:ted] 2015-01-22 14:17:42 UTC
I changed the spec to use sequence<Gamepad?>:
https://dvcs.w3.org/hg/gamepad/rev/105439653c33

So implementations should simply use null as the value for disconnected controllers. I'll write a test for this behavior for w3c-test.org.
Comment 36 Philip Jägenstedt 2015-02-09 09:24:22 UTC
Thanks Ted, did you get around to that web-platform-tests thing?