Implement WebXR Gamepads Module (#32860)

* Expose gamepad attribute on XRInputSource

Signed-off-by: Daniel Adams <msub2official@gmail.com>

* Tidy, add spec links

Signed-off-by: Daniel Adams <msub2official@gmail.com>

* Update WPT test expectations

Signed-off-by: Daniel Adams <msub2official@gmail.com>

* Update gamepad state on InputChanged event

Signed-off-by: Daniel Adams <msub2official@gmail.com>

* Pin webxr commit

Signed-off-by: Daniel Adams <msub2official@gmail.com>

* Apply gamepad updates during frame updates

Signed-off-by: Daniel Adams <msub2official@gmail.com>

* Drain input frame map

Signed-off-by: Daniel Adams <msub2official@gmail.com>

* Don't store gamepad as option

Signed-off-by: Daniel Adams <msub2official@gmail.com>

---------

Signed-off-by: Daniel Adams <msub2official@gmail.com>
This commit is contained in:
Daniel Adams 2024-08-03 02:51:44 -10:00 committed by GitHub
parent 0672eca749
commit fd83281657
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 90 additions and 32 deletions

4
Cargo.lock generated
View file

@ -7745,7 +7745,7 @@ dependencies = [
[[package]]
name = "webxr"
version = "0.0.1"
source = "git+https://github.com/servo/webxr#93ee726f84dc3feb9aa47f9a61fdedd784d14938"
source = "git+https://github.com/servo/webxr#790f50587d651fd865736cfd8c70cab0dea5dc7f"
dependencies = [
"crossbeam-channel",
"euclid",
@ -7762,7 +7762,7 @@ dependencies = [
[[package]]
name = "webxr-api"
version = "0.0.1"
source = "git+https://github.com/servo/webxr#93ee726f84dc3feb9aa47f9a61fdedd784d14938"
source = "git+https://github.com/servo/webxr#790f50587d651fd865736cfd8c70cab0dea5dc7f"
dependencies = [
"euclid",
"ipc-channel",

View file

@ -92,6 +92,7 @@ impl Gamepad {
global: &GlobalScope,
gamepad_id: u32,
id: String,
mapping_type: String,
axis_bounds: (f64, f64),
button_bounds: (f64, f64),
supported_haptic_effects: GamepadSupportedHapticEffects,
@ -100,6 +101,7 @@ impl Gamepad {
global,
gamepad_id,
id,
mapping_type,
axis_bounds,
button_bounds,
supported_haptic_effects,
@ -115,6 +117,7 @@ impl Gamepad {
global: &GlobalScope,
gamepad_id: u32,
id: String,
mapping_type: String,
axis_bounds: (f64, f64),
button_bounds: (f64, f64),
supported_haptic_effects: GamepadSupportedHapticEffects,
@ -129,7 +132,7 @@ impl Gamepad {
0,
true,
0.,
String::from("standard"),
mapping_type,
&button_list,
None,
GamepadHand::_empty,

View file

@ -3214,22 +3214,29 @@ impl GlobalScope {
// TODO: 2. If document is not null and is not allowed to use the "gamepad" permission,
// then abort these steps.
let this = Trusted::new(self);
self.gamepad_task_source().queue_with_canceller(
task!(gamepad_connected: move || {
let global = this.root();
self.gamepad_task_source()
.queue_with_canceller(
task!(gamepad_connected: move || {
let global = this.root();
if let Some(window) = global.downcast::<Window>() {
let navigator = window.Navigator();
let selected_index = navigator.select_gamepad_index();
let gamepad = Gamepad::new(
&global, selected_index, name, axis_bounds, button_bounds, supported_haptic_effects
);
navigator.set_gamepad(selected_index as usize, &gamepad);
}
}),
&self.task_canceller(TaskSourceName::Gamepad)
)
.expect("Failed to queue gamepad connected task.");
if let Some(window) = global.downcast::<Window>() {
let navigator = window.Navigator();
let selected_index = navigator.select_gamepad_index();
let gamepad = Gamepad::new(
&global,
selected_index,
name,
"standard".into(),
axis_bounds,
button_bounds,
supported_haptic_effects
);
navigator.set_gamepad(selected_index as usize, &gamepad);
}
}),
&self.task_canceller(TaskSourceName::Gamepad),
)
.expect("Failed to queue gamepad connected task.");
}
/// <https://www.w3.org/TR/gamepad/#dfn-gamepaddisconnected>

View file

@ -22,7 +22,7 @@ interface XRInputSource {
readonly attribute XRTargetRayMode targetRayMode;
[SameObject] readonly attribute XRSpace targetRaySpace;
[SameObject] readonly attribute XRSpace? gripSpace;
// [SameObject] readonly attribute Gamepad? gamepad;
[SameObject] readonly attribute Gamepad? gamepad;
/* [SameObject] */ readonly attribute /* FrozenArray<DOMString> */ any profiles;
[Pref="dom.webxr.hands.enabled"]

View file

@ -6,13 +6,15 @@ use dom_struct::dom_struct;
use js::conversions::ToJSValConvertible;
use js::jsapi::Heap;
use js::jsval::{JSVal, UndefinedValue};
use webxr_api::{Handedness, InputId, InputSource, TargetRayMode};
use script_traits::GamepadSupportedHapticEffects;
use webxr_api::{Handedness, InputFrame, InputId, InputSource, TargetRayMode};
use crate::dom::bindings::codegen::Bindings::XRInputSourceBinding::{
XRHandedness, XRInputSourceMethods, XRTargetRayMode,
};
use crate::dom::bindings::reflector::{reflect_dom_object, DomObject, Reflector};
use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom};
use crate::dom::gamepad::Gamepad;
use crate::dom::globalscope::GlobalScope;
use crate::dom::xrhand::XRHand;
use crate::dom::xrsession::XRSession;
@ -32,10 +34,28 @@ pub struct XRInputSource {
hand: MutNullableDom<XRHand>,
#[ignore_malloc_size_of = "mozjs"]
profiles: Heap<JSVal>,
gamepad: DomRoot<Gamepad>,
}
impl XRInputSource {
pub fn new_inherited(session: &XRSession, info: InputSource) -> XRInputSource {
pub fn new_inherited(
global: &GlobalScope,
session: &XRSession,
info: InputSource,
) -> XRInputSource {
// <https://www.w3.org/TR/webxr-gamepads-module-1/#gamepad-differences>
let gamepad = Gamepad::new(
global,
0,
"".into(),
"xr-standard".into(),
(-1.0, 1.0),
(0.0, 1.0),
GamepadSupportedHapticEffects {
supports_dual_rumble: false,
supports_trigger_rumble: false,
},
);
XRInputSource {
reflector: Reflector::new(),
session: Dom::from_ref(session),
@ -44,6 +64,7 @@ impl XRInputSource {
grip_space: Default::default(),
hand: Default::default(),
profiles: Heap::default(),
gamepad,
}
}
@ -54,7 +75,7 @@ impl XRInputSource {
info: InputSource,
) -> DomRoot<XRInputSource> {
let source = reflect_dom_object(
Box::new(XRInputSource::new_inherited(session, info)),
Box::new(XRInputSource::new_inherited(global, session, info)),
global,
);
@ -75,6 +96,21 @@ impl XRInputSource {
pub fn session(&self) -> &XRSession {
&self.session
}
pub fn update_gamepad_state(&self, frame: InputFrame) {
frame
.button_values
.iter()
.enumerate()
.for_each(|(i, value)| {
self.gamepad
.map_and_normalize_buttons(i as usize, *value as f64);
});
frame.axis_values.iter().enumerate().for_each(|(i, value)| {
self.gamepad
.map_and_normalize_axes(i as usize, *value as f64);
});
}
}
impl XRInputSourceMethods for XRInputSource {
@ -120,6 +156,11 @@ impl XRInputSourceMethods for XRInputSource {
self.profiles.get()
}
/// <https://www.w3.org/TR/webxr-gamepads-module-1/#xrinputsource-interface>
fn GetGamepad(&self) -> Option<DomRoot<Gamepad>> {
Some(DomRoot::from_ref(&*self.gamepad))
}
// https://github.com/immersive-web/webxr-hands-input/blob/master/explainer.md
fn GetHand(&self) -> Option<DomRoot<XRHand>> {
self.info.hand_support.as_ref().map(|hand| {

View file

@ -3,6 +3,7 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use std::cell::Cell;
use std::collections::HashMap;
use std::f64::consts::{FRAC_PI_2, PI};
use std::mem;
use std::rc::Rc;
@ -15,8 +16,8 @@ use metrics::ToMs;
use profile_traits::ipc;
use webxr_api::{
self, util, ApiSpace, ContextId as WebXRContextId, Display, EntityTypes, EnvironmentBlendMode,
Event as XREvent, Frame, FrameUpdateEvent, HitTestId, HitTestSource, Ray, SelectEvent,
SelectKind, Session, SessionId, View, Viewer, Visibility,
Event as XREvent, Frame, FrameUpdateEvent, HitTestId, HitTestSource, InputFrame, InputId, Ray,
SelectEvent, SelectKind, Session, SessionId, View, Viewer, Visibility,
};
use super::bindings::trace::HashMapTracedValues;
@ -92,6 +93,9 @@ pub struct XRSession {
/// Opaque framebuffers need to know the session is "outside of a requestAnimationFrame"
/// <https://immersive-web.github.io/webxr/#opaque-framebuffer>
outside_raf: Cell<bool>,
#[ignore_malloc_size_of = "defined in webxr"]
#[no_trace]
input_frames: DomRefCell<HashMap<InputId, InputFrame>>,
}
impl XRSession {
@ -122,6 +126,7 @@ impl XRSession {
next_hit_test_id: Cell::new(HitTestId(0)),
pending_hit_test_promises: DomRefCell::new(HashMapTracedValues::new()),
outside_raf: Cell::new(true),
input_frames: DomRefCell::new(HashMap::new()),
}
}
@ -350,6 +355,9 @@ impl XRSession {
XREvent::UpdateInput(id, source) => {
self.input_sources.add_remove_input_source(self, id, source);
},
XREvent::InputChanged(id, frame) => {
self.input_frames.borrow_mut().insert(id, frame);
},
}
}
@ -510,7 +518,13 @@ impl XRSession {
/// <https://immersive-web.github.io/webxr/#xrframe-apply-frame-updates>
fn apply_frame_updates(&self, _frame: &XRFrame) {
// TODO: add a comment about why this is empty right now!
// <https://www.w3.org/TR/webxr-gamepads-module-1/#xrframe-apply-gamepad-frame-updates>
for (id, frame) in self.input_frames.borrow_mut().drain() {
let source = self.input_sources.find(id);
if let Some(source) = source {
source.update_gamepad_state(frame);
}
}
}
fn handle_frame_event(&self, event: FrameUpdateEvent) {

View file

@ -1,4 +0,0 @@
[idlharness.https.window.html]
[XRInputSource interface: attribute gamepad]
expected: FAIL

View file

@ -1,3 +0,0 @@
[idlharness.https.window.html]
[XRInputSource interface: attribute gamepad]
expected: FAIL