mirror of
https://github.com/servo/servo.git
synced 2025-08-05 13:40:08 +01:00
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:
parent
0672eca749
commit
fd83281657
8 changed files with 90 additions and 32 deletions
4
Cargo.lock
generated
4
Cargo.lock
generated
|
@ -7745,7 +7745,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "webxr"
|
name = "webxr"
|
||||||
version = "0.0.1"
|
version = "0.0.1"
|
||||||
source = "git+https://github.com/servo/webxr#93ee726f84dc3feb9aa47f9a61fdedd784d14938"
|
source = "git+https://github.com/servo/webxr#790f50587d651fd865736cfd8c70cab0dea5dc7f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"crossbeam-channel",
|
"crossbeam-channel",
|
||||||
"euclid",
|
"euclid",
|
||||||
|
@ -7762,7 +7762,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "webxr-api"
|
name = "webxr-api"
|
||||||
version = "0.0.1"
|
version = "0.0.1"
|
||||||
source = "git+https://github.com/servo/webxr#93ee726f84dc3feb9aa47f9a61fdedd784d14938"
|
source = "git+https://github.com/servo/webxr#790f50587d651fd865736cfd8c70cab0dea5dc7f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"euclid",
|
"euclid",
|
||||||
"ipc-channel",
|
"ipc-channel",
|
||||||
|
|
|
@ -92,6 +92,7 @@ impl Gamepad {
|
||||||
global: &GlobalScope,
|
global: &GlobalScope,
|
||||||
gamepad_id: u32,
|
gamepad_id: u32,
|
||||||
id: String,
|
id: String,
|
||||||
|
mapping_type: String,
|
||||||
axis_bounds: (f64, f64),
|
axis_bounds: (f64, f64),
|
||||||
button_bounds: (f64, f64),
|
button_bounds: (f64, f64),
|
||||||
supported_haptic_effects: GamepadSupportedHapticEffects,
|
supported_haptic_effects: GamepadSupportedHapticEffects,
|
||||||
|
@ -100,6 +101,7 @@ impl Gamepad {
|
||||||
global,
|
global,
|
||||||
gamepad_id,
|
gamepad_id,
|
||||||
id,
|
id,
|
||||||
|
mapping_type,
|
||||||
axis_bounds,
|
axis_bounds,
|
||||||
button_bounds,
|
button_bounds,
|
||||||
supported_haptic_effects,
|
supported_haptic_effects,
|
||||||
|
@ -115,6 +117,7 @@ impl Gamepad {
|
||||||
global: &GlobalScope,
|
global: &GlobalScope,
|
||||||
gamepad_id: u32,
|
gamepad_id: u32,
|
||||||
id: String,
|
id: String,
|
||||||
|
mapping_type: String,
|
||||||
axis_bounds: (f64, f64),
|
axis_bounds: (f64, f64),
|
||||||
button_bounds: (f64, f64),
|
button_bounds: (f64, f64),
|
||||||
supported_haptic_effects: GamepadSupportedHapticEffects,
|
supported_haptic_effects: GamepadSupportedHapticEffects,
|
||||||
|
@ -129,7 +132,7 @@ impl Gamepad {
|
||||||
0,
|
0,
|
||||||
true,
|
true,
|
||||||
0.,
|
0.,
|
||||||
String::from("standard"),
|
mapping_type,
|
||||||
&button_list,
|
&button_list,
|
||||||
None,
|
None,
|
||||||
GamepadHand::_empty,
|
GamepadHand::_empty,
|
||||||
|
|
|
@ -3214,7 +3214,8 @@ impl GlobalScope {
|
||||||
// TODO: 2. If document is not null and is not allowed to use the "gamepad" permission,
|
// TODO: 2. If document is not null and is not allowed to use the "gamepad" permission,
|
||||||
// then abort these steps.
|
// then abort these steps.
|
||||||
let this = Trusted::new(self);
|
let this = Trusted::new(self);
|
||||||
self.gamepad_task_source().queue_with_canceller(
|
self.gamepad_task_source()
|
||||||
|
.queue_with_canceller(
|
||||||
task!(gamepad_connected: move || {
|
task!(gamepad_connected: move || {
|
||||||
let global = this.root();
|
let global = this.root();
|
||||||
|
|
||||||
|
@ -3222,12 +3223,18 @@ impl GlobalScope {
|
||||||
let navigator = window.Navigator();
|
let navigator = window.Navigator();
|
||||||
let selected_index = navigator.select_gamepad_index();
|
let selected_index = navigator.select_gamepad_index();
|
||||||
let gamepad = Gamepad::new(
|
let gamepad = Gamepad::new(
|
||||||
&global, selected_index, name, axis_bounds, button_bounds, supported_haptic_effects
|
&global,
|
||||||
|
selected_index,
|
||||||
|
name,
|
||||||
|
"standard".into(),
|
||||||
|
axis_bounds,
|
||||||
|
button_bounds,
|
||||||
|
supported_haptic_effects
|
||||||
);
|
);
|
||||||
navigator.set_gamepad(selected_index as usize, &gamepad);
|
navigator.set_gamepad(selected_index as usize, &gamepad);
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
&self.task_canceller(TaskSourceName::Gamepad)
|
&self.task_canceller(TaskSourceName::Gamepad),
|
||||||
)
|
)
|
||||||
.expect("Failed to queue gamepad connected task.");
|
.expect("Failed to queue gamepad connected task.");
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,7 +22,7 @@ interface XRInputSource {
|
||||||
readonly attribute XRTargetRayMode targetRayMode;
|
readonly attribute XRTargetRayMode targetRayMode;
|
||||||
[SameObject] readonly attribute XRSpace targetRaySpace;
|
[SameObject] readonly attribute XRSpace targetRaySpace;
|
||||||
[SameObject] readonly attribute XRSpace? gripSpace;
|
[SameObject] readonly attribute XRSpace? gripSpace;
|
||||||
// [SameObject] readonly attribute Gamepad? gamepad;
|
[SameObject] readonly attribute Gamepad? gamepad;
|
||||||
/* [SameObject] */ readonly attribute /* FrozenArray<DOMString> */ any profiles;
|
/* [SameObject] */ readonly attribute /* FrozenArray<DOMString> */ any profiles;
|
||||||
|
|
||||||
[Pref="dom.webxr.hands.enabled"]
|
[Pref="dom.webxr.hands.enabled"]
|
||||||
|
|
|
@ -6,13 +6,15 @@ use dom_struct::dom_struct;
|
||||||
use js::conversions::ToJSValConvertible;
|
use js::conversions::ToJSValConvertible;
|
||||||
use js::jsapi::Heap;
|
use js::jsapi::Heap;
|
||||||
use js::jsval::{JSVal, UndefinedValue};
|
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::{
|
use crate::dom::bindings::codegen::Bindings::XRInputSourceBinding::{
|
||||||
XRHandedness, XRInputSourceMethods, XRTargetRayMode,
|
XRHandedness, XRInputSourceMethods, XRTargetRayMode,
|
||||||
};
|
};
|
||||||
use crate::dom::bindings::reflector::{reflect_dom_object, DomObject, Reflector};
|
use crate::dom::bindings::reflector::{reflect_dom_object, DomObject, Reflector};
|
||||||
use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom};
|
use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom};
|
||||||
|
use crate::dom::gamepad::Gamepad;
|
||||||
use crate::dom::globalscope::GlobalScope;
|
use crate::dom::globalscope::GlobalScope;
|
||||||
use crate::dom::xrhand::XRHand;
|
use crate::dom::xrhand::XRHand;
|
||||||
use crate::dom::xrsession::XRSession;
|
use crate::dom::xrsession::XRSession;
|
||||||
|
@ -32,10 +34,28 @@ pub struct XRInputSource {
|
||||||
hand: MutNullableDom<XRHand>,
|
hand: MutNullableDom<XRHand>,
|
||||||
#[ignore_malloc_size_of = "mozjs"]
|
#[ignore_malloc_size_of = "mozjs"]
|
||||||
profiles: Heap<JSVal>,
|
profiles: Heap<JSVal>,
|
||||||
|
gamepad: DomRoot<Gamepad>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl XRInputSource {
|
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 {
|
XRInputSource {
|
||||||
reflector: Reflector::new(),
|
reflector: Reflector::new(),
|
||||||
session: Dom::from_ref(session),
|
session: Dom::from_ref(session),
|
||||||
|
@ -44,6 +64,7 @@ impl XRInputSource {
|
||||||
grip_space: Default::default(),
|
grip_space: Default::default(),
|
||||||
hand: Default::default(),
|
hand: Default::default(),
|
||||||
profiles: Heap::default(),
|
profiles: Heap::default(),
|
||||||
|
gamepad,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -54,7 +75,7 @@ impl XRInputSource {
|
||||||
info: InputSource,
|
info: InputSource,
|
||||||
) -> DomRoot<XRInputSource> {
|
) -> DomRoot<XRInputSource> {
|
||||||
let source = reflect_dom_object(
|
let source = reflect_dom_object(
|
||||||
Box::new(XRInputSource::new_inherited(session, info)),
|
Box::new(XRInputSource::new_inherited(global, session, info)),
|
||||||
global,
|
global,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -75,6 +96,21 @@ impl XRInputSource {
|
||||||
pub fn session(&self) -> &XRSession {
|
pub fn session(&self) -> &XRSession {
|
||||||
&self.session
|
&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 {
|
impl XRInputSourceMethods for XRInputSource {
|
||||||
|
@ -120,6 +156,11 @@ impl XRInputSourceMethods for XRInputSource {
|
||||||
self.profiles.get()
|
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
|
// https://github.com/immersive-web/webxr-hands-input/blob/master/explainer.md
|
||||||
fn GetHand(&self) -> Option<DomRoot<XRHand>> {
|
fn GetHand(&self) -> Option<DomRoot<XRHand>> {
|
||||||
self.info.hand_support.as_ref().map(|hand| {
|
self.info.hand_support.as_ref().map(|hand| {
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
use std::cell::Cell;
|
use std::cell::Cell;
|
||||||
|
use std::collections::HashMap;
|
||||||
use std::f64::consts::{FRAC_PI_2, PI};
|
use std::f64::consts::{FRAC_PI_2, PI};
|
||||||
use std::mem;
|
use std::mem;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
@ -15,8 +16,8 @@ use metrics::ToMs;
|
||||||
use profile_traits::ipc;
|
use profile_traits::ipc;
|
||||||
use webxr_api::{
|
use webxr_api::{
|
||||||
self, util, ApiSpace, ContextId as WebXRContextId, Display, EntityTypes, EnvironmentBlendMode,
|
self, util, ApiSpace, ContextId as WebXRContextId, Display, EntityTypes, EnvironmentBlendMode,
|
||||||
Event as XREvent, Frame, FrameUpdateEvent, HitTestId, HitTestSource, Ray, SelectEvent,
|
Event as XREvent, Frame, FrameUpdateEvent, HitTestId, HitTestSource, InputFrame, InputId, Ray,
|
||||||
SelectKind, Session, SessionId, View, Viewer, Visibility,
|
SelectEvent, SelectKind, Session, SessionId, View, Viewer, Visibility,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::bindings::trace::HashMapTracedValues;
|
use super::bindings::trace::HashMapTracedValues;
|
||||||
|
@ -92,6 +93,9 @@ pub struct XRSession {
|
||||||
/// Opaque framebuffers need to know the session is "outside of a requestAnimationFrame"
|
/// Opaque framebuffers need to know the session is "outside of a requestAnimationFrame"
|
||||||
/// <https://immersive-web.github.io/webxr/#opaque-framebuffer>
|
/// <https://immersive-web.github.io/webxr/#opaque-framebuffer>
|
||||||
outside_raf: Cell<bool>,
|
outside_raf: Cell<bool>,
|
||||||
|
#[ignore_malloc_size_of = "defined in webxr"]
|
||||||
|
#[no_trace]
|
||||||
|
input_frames: DomRefCell<HashMap<InputId, InputFrame>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl XRSession {
|
impl XRSession {
|
||||||
|
@ -122,6 +126,7 @@ impl XRSession {
|
||||||
next_hit_test_id: Cell::new(HitTestId(0)),
|
next_hit_test_id: Cell::new(HitTestId(0)),
|
||||||
pending_hit_test_promises: DomRefCell::new(HashMapTracedValues::new()),
|
pending_hit_test_promises: DomRefCell::new(HashMapTracedValues::new()),
|
||||||
outside_raf: Cell::new(true),
|
outside_raf: Cell::new(true),
|
||||||
|
input_frames: DomRefCell::new(HashMap::new()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -350,6 +355,9 @@ impl XRSession {
|
||||||
XREvent::UpdateInput(id, source) => {
|
XREvent::UpdateInput(id, source) => {
|
||||||
self.input_sources.add_remove_input_source(self, 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>
|
/// <https://immersive-web.github.io/webxr/#xrframe-apply-frame-updates>
|
||||||
fn apply_frame_updates(&self, _frame: &XRFrame) {
|
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) {
|
fn handle_frame_event(&self, event: FrameUpdateEvent) {
|
||||||
|
|
|
@ -1,4 +0,0 @@
|
||||||
[idlharness.https.window.html]
|
|
||||||
[XRInputSource interface: attribute gamepad]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
[idlharness.https.window.html]
|
|
||||||
[XRInputSource interface: attribute gamepad]
|
|
||||||
expected: FAIL
|
|
Loading…
Add table
Add a link
Reference in a new issue