From fd832816572e26cafb8207d3710bd704f2617758 Mon Sep 17 00:00:00 2001 From: Daniel Adams <70986246+msub2@users.noreply.github.com> Date: Sat, 3 Aug 2024 02:51:44 -1000 Subject: [PATCH] Implement WebXR Gamepads Module (#32860) * Expose gamepad attribute on XRInputSource Signed-off-by: Daniel Adams * Tidy, add spec links Signed-off-by: Daniel Adams * Update WPT test expectations Signed-off-by: Daniel Adams * Update gamepad state on InputChanged event Signed-off-by: Daniel Adams * Pin webxr commit Signed-off-by: Daniel Adams * Apply gamepad updates during frame updates Signed-off-by: Daniel Adams * Drain input frame map Signed-off-by: Daniel Adams * Don't store gamepad as option Signed-off-by: Daniel Adams --------- Signed-off-by: Daniel Adams --- Cargo.lock | 4 +- components/script/dom/gamepad.rs | 5 +- components/script/dom/globalscope.rs | 37 +++++++++------ .../script/dom/webidls/XRInputSource.webidl | 2 +- components/script/dom/xrinputsource.rs | 47 +++++++++++++++++-- components/script/dom/xrsession.rs | 20 ++++++-- .../idlharness.https.window.js.ini | 4 -- .../idlharness.https.window.js.ini | 3 -- 8 files changed, 90 insertions(+), 32 deletions(-) delete mode 100644 tests/wpt/meta-legacy-layout/webxr/gamepads-module/idlharness.https.window.js.ini delete mode 100644 tests/wpt/meta/webxr/gamepads-module/idlharness.https.window.js.ini diff --git a/Cargo.lock b/Cargo.lock index dc4302f8fae..20d4ab56714 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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", diff --git a/components/script/dom/gamepad.rs b/components/script/dom/gamepad.rs index 951be3d05d5..4c4705d4db7 100644 --- a/components/script/dom/gamepad.rs +++ b/components/script/dom/gamepad.rs @@ -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, diff --git a/components/script/dom/globalscope.rs b/components/script/dom/globalscope.rs index 73ce73e75c1..ba207c3634c 100644 --- a/components/script/dom/globalscope.rs +++ b/components/script/dom/globalscope.rs @@ -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::() { - 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::() { + 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."); } /// diff --git a/components/script/dom/webidls/XRInputSource.webidl b/components/script/dom/webidls/XRInputSource.webidl index b663666c872..da5650eb88a 100644 --- a/components/script/dom/webidls/XRInputSource.webidl +++ b/components/script/dom/webidls/XRInputSource.webidl @@ -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 */ any profiles; [Pref="dom.webxr.hands.enabled"] diff --git a/components/script/dom/xrinputsource.rs b/components/script/dom/xrinputsource.rs index ad0cea99a08..22782c5dfef 100644 --- a/components/script/dom/xrinputsource.rs +++ b/components/script/dom/xrinputsource.rs @@ -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, #[ignore_malloc_size_of = "mozjs"] profiles: Heap, + gamepad: DomRoot, } impl XRInputSource { - pub fn new_inherited(session: &XRSession, info: InputSource) -> XRInputSource { + pub fn new_inherited( + global: &GlobalScope, + session: &XRSession, + info: InputSource, + ) -> XRInputSource { + // + 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 { 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() } + /// + fn GetGamepad(&self) -> Option> { + Some(DomRoot::from_ref(&*self.gamepad)) + } + // https://github.com/immersive-web/webxr-hands-input/blob/master/explainer.md fn GetHand(&self) -> Option> { self.info.hand_support.as_ref().map(|hand| { diff --git a/components/script/dom/xrsession.rs b/components/script/dom/xrsession.rs index 9be375d3e00..2b189002a80 100644 --- a/components/script/dom/xrsession.rs +++ b/components/script/dom/xrsession.rs @@ -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" /// outside_raf: Cell, + #[ignore_malloc_size_of = "defined in webxr"] + #[no_trace] + input_frames: DomRefCell>, } 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 { /// fn apply_frame_updates(&self, _frame: &XRFrame) { - // TODO: add a comment about why this is empty right now! + // + 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) { diff --git a/tests/wpt/meta-legacy-layout/webxr/gamepads-module/idlharness.https.window.js.ini b/tests/wpt/meta-legacy-layout/webxr/gamepads-module/idlharness.https.window.js.ini deleted file mode 100644 index ee1d63b830d..00000000000 --- a/tests/wpt/meta-legacy-layout/webxr/gamepads-module/idlharness.https.window.js.ini +++ /dev/null @@ -1,4 +0,0 @@ -[idlharness.https.window.html] - [XRInputSource interface: attribute gamepad] - expected: FAIL - diff --git a/tests/wpt/meta/webxr/gamepads-module/idlharness.https.window.js.ini b/tests/wpt/meta/webxr/gamepads-module/idlharness.https.window.js.ini deleted file mode 100644 index 5c8d1822672..00000000000 --- a/tests/wpt/meta/webxr/gamepads-module/idlharness.https.window.js.ini +++ /dev/null @@ -1,3 +0,0 @@ -[idlharness.https.window.html] - [XRInputSource interface: attribute gamepad] - expected: FAIL