@codexo/exojs 0.7.12 → 0.7.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +334 -0
- package/dist/esm/core/Application.d.ts +3 -1
- package/dist/esm/core/Application.js +7 -6
- package/dist/esm/core/Application.js.map +1 -1
- package/dist/esm/core/Scene.d.ts +30 -0
- package/dist/esm/core/Scene.js +56 -0
- package/dist/esm/core/Scene.js.map +1 -1
- package/dist/esm/core/SceneManager.js +2 -2
- package/dist/esm/core/SceneManager.js.map +1 -1
- package/dist/esm/debug/DebugOverlay.js +2 -2
- package/dist/esm/debug/DebugOverlay.js.map +1 -1
- package/dist/esm/debug/PointerStackLayer.js +1 -1
- package/dist/esm/debug/PointerStackLayer.js.map +1 -1
- package/dist/esm/index.js +4 -3
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/input/ArcadeStickGamepadMapping.js +18 -19
- package/dist/esm/input/ArcadeStickGamepadMapping.js.map +1 -1
- package/dist/esm/input/Gamepad.d.ts +164 -62
- package/dist/esm/input/Gamepad.js +290 -134
- package/dist/esm/input/Gamepad.js.map +1 -1
- package/dist/esm/input/GamepadAxis.d.ts +120 -0
- package/dist/esm/input/GamepadAxis.js +106 -0
- package/dist/esm/input/GamepadAxis.js.map +1 -0
- package/dist/esm/input/GamepadButton.d.ts +110 -0
- package/dist/esm/input/GamepadButton.js +99 -0
- package/dist/esm/input/GamepadButton.js.map +1 -0
- package/dist/esm/input/GamepadDefinitions.js +4 -0
- package/dist/esm/input/GamepadDefinitions.js.map +1 -1
- package/dist/esm/input/GamepadMapping.d.ts +28 -24
- package/dist/esm/input/GamepadMapping.js +33 -16
- package/dist/esm/input/GamepadMapping.js.map +1 -1
- package/dist/esm/input/GamepadPromptLayouts.d.ts +10 -8
- package/dist/esm/input/GamepadPromptLayouts.js +21 -20
- package/dist/esm/input/GamepadPromptLayouts.js.map +1 -1
- package/dist/esm/input/GenericDualAnalogGamepadMapping.d.ts +6 -3
- package/dist/esm/input/GenericDualAnalogGamepadMapping.js +55 -46
- package/dist/esm/input/GenericDualAnalogGamepadMapping.js.map +1 -1
- package/dist/esm/input/InputBinding.d.ts +74 -0
- package/dist/esm/input/InputBinding.js +100 -0
- package/dist/esm/input/InputBinding.js.map +1 -0
- package/dist/esm/input/InputManager.d.ts +79 -33
- package/dist/esm/input/InputManager.js +229 -104
- package/dist/esm/input/InputManager.js.map +1 -1
- package/dist/esm/input/InteractionManager.d.ts +1 -1
- package/dist/esm/input/InteractionManager.js +13 -13
- package/dist/esm/input/InteractionManager.js.map +1 -1
- package/dist/esm/input/JoyConLeftGamepadMapping.d.ts +14 -9
- package/dist/esm/input/JoyConLeftGamepadMapping.js +39 -9
- package/dist/esm/input/JoyConLeftGamepadMapping.js.map +1 -1
- package/dist/esm/input/JoyConRightGamepadMapping.d.ts +14 -9
- package/dist/esm/input/JoyConRightGamepadMapping.js +35 -9
- package/dist/esm/input/JoyConRightGamepadMapping.js.map +1 -1
- package/dist/esm/input/Pointer.d.ts +84 -71
- package/dist/esm/input/Pointer.js +71 -71
- package/dist/esm/input/Pointer.js.map +1 -1
- package/dist/esm/input/SteamDeckGamepadMapping.d.ts +18 -0
- package/dist/esm/input/SteamDeckGamepadMapping.js +76 -0
- package/dist/esm/input/SteamDeckGamepadMapping.js.map +1 -0
- package/dist/esm/input/index.d.ts +7 -4
- package/dist/esm/input/types.d.ts +0 -76
- package/dist/esm/input/types.js +1 -80
- package/dist/esm/input/types.js.map +1 -1
- package/dist/esm/resources/CacheFirstStrategy.d.ts +7 -4
- package/dist/esm/resources/CacheFirstStrategy.js +11 -8
- package/dist/esm/resources/CacheFirstStrategy.js.map +1 -1
- package/dist/esm/resources/CacheStrategy.d.ts +14 -6
- package/dist/esm/resources/Loader.d.ts +8 -3
- package/dist/esm/resources/Loader.js +19 -37
- package/dist/esm/resources/Loader.js.map +1 -1
- package/dist/esm/resources/NetworkOnlyStrategy.d.ts +3 -0
- package/dist/esm/resources/NetworkOnlyStrategy.js +8 -3
- package/dist/esm/resources/NetworkOnlyStrategy.js.map +1 -1
- package/dist/esm/resources/factories/ImageFactory.d.ts +2 -2
- package/dist/esm/resources/factories/ImageFactory.js.map +1 -1
- package/dist/esm/resources/factories/TextureFactory.d.ts +2 -2
- package/dist/esm/resources/factories/TextureFactory.js.map +1 -1
- package/dist/esm/resources/factories/VttFactory.d.ts +3 -3
- package/dist/esm/resources/factories/VttFactory.js +83 -6
- package/dist/esm/resources/factories/VttFactory.js.map +1 -1
- package/dist/exo.esm.js +1390 -795
- package/dist/exo.esm.js.map +1 -1
- package/package.json +2 -1
- package/dist/esm/input/GamepadChannels.d.ts +0 -47
- package/dist/esm/input/GamepadChannels.js +0 -53
- package/dist/esm/input/GamepadChannels.js.map +0 -1
- package/dist/esm/input/GamepadControl.d.ts +0 -33
- package/dist/esm/input/GamepadControl.js +0 -42
- package/dist/esm/input/GamepadControl.js.map +0 -1
- package/dist/esm/input/Input.d.ts +0 -52
- package/dist/esm/input/Input.js +0 -90
- package/dist/esm/input/Input.js.map +0 -1
package/dist/exo.esm.js
CHANGED
|
@@ -5592,7 +5592,7 @@ class SceneManager {
|
|
|
5592
5592
|
return { updateScenes, drawScenes };
|
|
5593
5593
|
}
|
|
5594
5594
|
_subscribeInputRouting() {
|
|
5595
|
-
const inputManager = this._app.
|
|
5595
|
+
const inputManager = this._app.input;
|
|
5596
5596
|
inputManager?.onKeyDown?.add?.(this._handleKeyDown);
|
|
5597
5597
|
inputManager?.onKeyUp?.add?.(this._handleKeyUp);
|
|
5598
5598
|
inputManager?.onPointerEnter?.add?.(this._handlePointerEnter);
|
|
@@ -5606,7 +5606,7 @@ class SceneManager {
|
|
|
5606
5606
|
inputManager?.onMouseWheel?.add?.(this._handleMouseWheel);
|
|
5607
5607
|
}
|
|
5608
5608
|
_unsubscribeInputRouting() {
|
|
5609
|
-
const inputManager = this._app.
|
|
5609
|
+
const inputManager = this._app.input;
|
|
5610
5610
|
inputManager?.onKeyDown?.remove?.(this._handleKeyDown);
|
|
5611
5611
|
inputManager?.onKeyUp?.remove?.(this._handleKeyUp);
|
|
5612
5612
|
inputManager?.onPointerEnter?.remove?.(this._handlePointerEnter);
|
|
@@ -12919,85 +12919,6 @@ var ChannelOffset;
|
|
|
12919
12919
|
const maxPointers = 16;
|
|
12920
12920
|
/** Number of channel slots reserved per pointer. 16 pointers × 16 slots = 256 (fills the Pointers category exactly). */
|
|
12921
12921
|
const pointerSlotSize = 16;
|
|
12922
|
-
/**
|
|
12923
|
-
* Channel offsets for unified pointer (mouse / touch / pen) state.
|
|
12924
|
-
*
|
|
12925
|
-
* The un-prefixed aliases (Active, X, Y, …) are identical to Slot0Active, Slot0X, Slot0Y, …
|
|
12926
|
-
* and address the primary pointer (slot 0). Use Slot{N}Active / Slot{N}X / Slot{N}Y for
|
|
12927
|
-
* multi-pointer (e.g. pinch) access. Other per-slot channels beyond Active/X/Y are reachable
|
|
12928
|
-
* via arithmetic: `Pointer.X + slotIndex * pointerSlotSize + channelOffset` (Pointer.X = PointerChannel.X).
|
|
12929
|
-
*
|
|
12930
|
-
* @internal — accessed publicly via the `Pointer` class namespace (see Pointer.ts).
|
|
12931
|
-
*/
|
|
12932
|
-
var PointerChannel;
|
|
12933
|
-
(function (PointerChannel) {
|
|
12934
|
-
// --- Convenience aliases for the primary pointer (slot 0) ---
|
|
12935
|
-
PointerChannel[PointerChannel["Active"] = 256] = "Active";
|
|
12936
|
-
PointerChannel[PointerChannel["X"] = 257] = "X";
|
|
12937
|
-
PointerChannel[PointerChannel["Y"] = 258] = "Y";
|
|
12938
|
-
PointerChannel[PointerChannel["Pressure"] = 259] = "Pressure";
|
|
12939
|
-
PointerChannel[PointerChannel["Width"] = 260] = "Width";
|
|
12940
|
-
PointerChannel[PointerChannel["Height"] = 261] = "Height";
|
|
12941
|
-
PointerChannel[PointerChannel["Twist"] = 262] = "Twist";
|
|
12942
|
-
PointerChannel[PointerChannel["TiltX"] = 263] = "TiltX";
|
|
12943
|
-
PointerChannel[PointerChannel["TiltY"] = 264] = "TiltY";
|
|
12944
|
-
PointerChannel[PointerChannel["Left"] = 265] = "Left";
|
|
12945
|
-
PointerChannel[PointerChannel["Right"] = 266] = "Right";
|
|
12946
|
-
PointerChannel[PointerChannel["Middle"] = 267] = "Middle";
|
|
12947
|
-
PointerChannel[PointerChannel["IsMouse"] = 268] = "IsMouse";
|
|
12948
|
-
PointerChannel[PointerChannel["IsTouch"] = 269] = "IsTouch";
|
|
12949
|
-
PointerChannel[PointerChannel["IsPen"] = 270] = "IsPen";
|
|
12950
|
-
PointerChannel[PointerChannel["IsPrimary"] = 271] = "IsPrimary";
|
|
12951
|
-
// --- Per-slot Active/X/Y for multi-pointer access ---
|
|
12952
|
-
PointerChannel[PointerChannel["Slot0Active"] = 256] = "Slot0Active";
|
|
12953
|
-
PointerChannel[PointerChannel["Slot0X"] = 257] = "Slot0X";
|
|
12954
|
-
PointerChannel[PointerChannel["Slot0Y"] = 258] = "Slot0Y";
|
|
12955
|
-
PointerChannel[PointerChannel["Slot1Active"] = 272] = "Slot1Active";
|
|
12956
|
-
PointerChannel[PointerChannel["Slot1X"] = 273] = "Slot1X";
|
|
12957
|
-
PointerChannel[PointerChannel["Slot1Y"] = 274] = "Slot1Y";
|
|
12958
|
-
PointerChannel[PointerChannel["Slot2Active"] = 288] = "Slot2Active";
|
|
12959
|
-
PointerChannel[PointerChannel["Slot2X"] = 289] = "Slot2X";
|
|
12960
|
-
PointerChannel[PointerChannel["Slot2Y"] = 290] = "Slot2Y";
|
|
12961
|
-
PointerChannel[PointerChannel["Slot3Active"] = 304] = "Slot3Active";
|
|
12962
|
-
PointerChannel[PointerChannel["Slot3X"] = 305] = "Slot3X";
|
|
12963
|
-
PointerChannel[PointerChannel["Slot3Y"] = 306] = "Slot3Y";
|
|
12964
|
-
PointerChannel[PointerChannel["Slot4Active"] = 320] = "Slot4Active";
|
|
12965
|
-
PointerChannel[PointerChannel["Slot4X"] = 321] = "Slot4X";
|
|
12966
|
-
PointerChannel[PointerChannel["Slot4Y"] = 322] = "Slot4Y";
|
|
12967
|
-
PointerChannel[PointerChannel["Slot5Active"] = 336] = "Slot5Active";
|
|
12968
|
-
PointerChannel[PointerChannel["Slot5X"] = 337] = "Slot5X";
|
|
12969
|
-
PointerChannel[PointerChannel["Slot5Y"] = 338] = "Slot5Y";
|
|
12970
|
-
PointerChannel[PointerChannel["Slot6Active"] = 352] = "Slot6Active";
|
|
12971
|
-
PointerChannel[PointerChannel["Slot6X"] = 353] = "Slot6X";
|
|
12972
|
-
PointerChannel[PointerChannel["Slot6Y"] = 354] = "Slot6Y";
|
|
12973
|
-
PointerChannel[PointerChannel["Slot7Active"] = 368] = "Slot7Active";
|
|
12974
|
-
PointerChannel[PointerChannel["Slot7X"] = 369] = "Slot7X";
|
|
12975
|
-
PointerChannel[PointerChannel["Slot7Y"] = 370] = "Slot7Y";
|
|
12976
|
-
PointerChannel[PointerChannel["Slot8Active"] = 384] = "Slot8Active";
|
|
12977
|
-
PointerChannel[PointerChannel["Slot8X"] = 385] = "Slot8X";
|
|
12978
|
-
PointerChannel[PointerChannel["Slot8Y"] = 386] = "Slot8Y";
|
|
12979
|
-
PointerChannel[PointerChannel["Slot9Active"] = 400] = "Slot9Active";
|
|
12980
|
-
PointerChannel[PointerChannel["Slot9X"] = 401] = "Slot9X";
|
|
12981
|
-
PointerChannel[PointerChannel["Slot9Y"] = 402] = "Slot9Y";
|
|
12982
|
-
PointerChannel[PointerChannel["Slot10Active"] = 416] = "Slot10Active";
|
|
12983
|
-
PointerChannel[PointerChannel["Slot10X"] = 417] = "Slot10X";
|
|
12984
|
-
PointerChannel[PointerChannel["Slot10Y"] = 418] = "Slot10Y";
|
|
12985
|
-
PointerChannel[PointerChannel["Slot11Active"] = 432] = "Slot11Active";
|
|
12986
|
-
PointerChannel[PointerChannel["Slot11X"] = 433] = "Slot11X";
|
|
12987
|
-
PointerChannel[PointerChannel["Slot11Y"] = 434] = "Slot11Y";
|
|
12988
|
-
PointerChannel[PointerChannel["Slot12Active"] = 448] = "Slot12Active";
|
|
12989
|
-
PointerChannel[PointerChannel["Slot12X"] = 449] = "Slot12X";
|
|
12990
|
-
PointerChannel[PointerChannel["Slot12Y"] = 450] = "Slot12Y";
|
|
12991
|
-
PointerChannel[PointerChannel["Slot13Active"] = 464] = "Slot13Active";
|
|
12992
|
-
PointerChannel[PointerChannel["Slot13X"] = 465] = "Slot13X";
|
|
12993
|
-
PointerChannel[PointerChannel["Slot13Y"] = 466] = "Slot13Y";
|
|
12994
|
-
PointerChannel[PointerChannel["Slot14Active"] = 480] = "Slot14Active";
|
|
12995
|
-
PointerChannel[PointerChannel["Slot14X"] = 481] = "Slot14X";
|
|
12996
|
-
PointerChannel[PointerChannel["Slot14Y"] = 482] = "Slot14Y";
|
|
12997
|
-
PointerChannel[PointerChannel["Slot15Active"] = 496] = "Slot15Active";
|
|
12998
|
-
PointerChannel[PointerChannel["Slot15X"] = 497] = "Slot15X";
|
|
12999
|
-
PointerChannel[PointerChannel["Slot15Y"] = 498] = "Slot15Y";
|
|
13000
|
-
})(PointerChannel || (PointerChannel = {}));
|
|
13001
12922
|
/**
|
|
13002
12923
|
* Channel indices for keyboard keys, derived from the legacy `KeyboardEvent.keyCode`
|
|
13003
12924
|
* map. Pass any value to the {@link Input} constructor to react to that key.
|
|
@@ -13111,197 +13032,484 @@ var Keyboard;
|
|
|
13111
13032
|
})(Keyboard || (Keyboard = {}));
|
|
13112
13033
|
|
|
13113
13034
|
/**
|
|
13114
|
-
*
|
|
13035
|
+
* {@link Clock} variant with a fixed limit. Inherits start/stop/reset/restart
|
|
13036
|
+
* semantics; adds {@link Timer.expired} (true once `elapsedTime >= limit`)
|
|
13037
|
+
* and remaining-time accessors. Useful for cooldowns, delays, and any timed
|
|
13038
|
+
* gating logic where you want to ask "is the duration up?" each frame.
|
|
13039
|
+
*/
|
|
13040
|
+
class Timer extends Clock {
|
|
13041
|
+
_limit;
|
|
13042
|
+
constructor(limit, autoStart = false) {
|
|
13043
|
+
super();
|
|
13044
|
+
this._limit = limit.clone();
|
|
13045
|
+
if (autoStart) {
|
|
13046
|
+
this.restart();
|
|
13047
|
+
}
|
|
13048
|
+
}
|
|
13049
|
+
set limit(limit) {
|
|
13050
|
+
this._limit.copy(limit);
|
|
13051
|
+
}
|
|
13052
|
+
/** `true` once the elapsed time has reached or exceeded the configured limit. */
|
|
13053
|
+
get expired() {
|
|
13054
|
+
return this.elapsedMilliseconds >= this._limit.milliseconds;
|
|
13055
|
+
}
|
|
13056
|
+
get remainingMilliseconds() {
|
|
13057
|
+
return Math.max(0, this._limit.milliseconds - this.elapsedMilliseconds);
|
|
13058
|
+
}
|
|
13059
|
+
get remainingSeconds() {
|
|
13060
|
+
return this.remainingMilliseconds / Time.seconds;
|
|
13061
|
+
}
|
|
13062
|
+
get remainingMinutes() {
|
|
13063
|
+
return this.remainingMilliseconds / Time.minutes;
|
|
13064
|
+
}
|
|
13065
|
+
get remainingHours() {
|
|
13066
|
+
return this.remainingMilliseconds / Time.hours;
|
|
13067
|
+
}
|
|
13068
|
+
}
|
|
13069
|
+
|
|
13070
|
+
/**
|
|
13071
|
+
* One subscription to one or more input channels. Tracks active state, fires
|
|
13072
|
+
* the {@link onStart} / {@link onActive} / {@link onStop} / {@link onTrigger}
|
|
13073
|
+
* Signals each frame, and registers itself with whichever owner created it
|
|
13074
|
+
* (typically an {@link InputManager}, {@link Gamepad}, or scene-bound
|
|
13075
|
+
* proxy).
|
|
13076
|
+
*
|
|
13077
|
+
* Construct via the owner's `onStart` / `onActive` / `onStop` /
|
|
13078
|
+
* `onTrigger` factory methods rather than `new InputBinding(...)` directly.
|
|
13115
13079
|
*
|
|
13116
|
-
*
|
|
13117
|
-
*
|
|
13118
|
-
*
|
|
13119
|
-
*
|
|
13120
|
-
|
|
13080
|
+
* Lifecycle: a binding lives until {@link unbind} is called, the owner
|
|
13081
|
+
* disposes it, or — for scene-bound bindings — the scene unloads.
|
|
13082
|
+
*
|
|
13083
|
+
* @internal
|
|
13084
|
+
*/
|
|
13085
|
+
class InputBinding {
|
|
13086
|
+
/**
|
|
13087
|
+
* Default tap-window for `onTrigger`. Override per binding via the
|
|
13088
|
+
* `threshold` option. Mutating this static affects only newly created
|
|
13089
|
+
* bindings.
|
|
13090
|
+
*/
|
|
13091
|
+
static defaultTriggerThreshold = 300;
|
|
13092
|
+
channels;
|
|
13093
|
+
onStart = new Signal();
|
|
13094
|
+
onActive = new Signal();
|
|
13095
|
+
onStop = new Signal();
|
|
13096
|
+
onTrigger = new Signal();
|
|
13097
|
+
_triggerTimer;
|
|
13098
|
+
_detacher;
|
|
13099
|
+
_value = 0;
|
|
13100
|
+
_unbound = false;
|
|
13101
|
+
constructor(channels, options = {}, detacher = null) {
|
|
13102
|
+
this.channels = channels;
|
|
13103
|
+
this._triggerTimer = new Timer(milliseconds(options.threshold ?? InputBinding.defaultTriggerThreshold));
|
|
13104
|
+
this._detacher = detacher;
|
|
13105
|
+
}
|
|
13106
|
+
/** Last value sampled this frame. 0 when inactive. */
|
|
13107
|
+
get value() {
|
|
13108
|
+
return this._value;
|
|
13109
|
+
}
|
|
13110
|
+
/** `true` when the last sampled value exceeded the channel's threshold. */
|
|
13111
|
+
get active() {
|
|
13112
|
+
return this._value > 0;
|
|
13113
|
+
}
|
|
13114
|
+
/**
|
|
13115
|
+
* Read the latest values from the unified channel buffer and dispatch
|
|
13116
|
+
* the appropriate Signals. Called once per frame by the owning manager.
|
|
13117
|
+
*
|
|
13118
|
+
* @internal
|
|
13119
|
+
*/
|
|
13120
|
+
update(channels) {
|
|
13121
|
+
if (this._unbound) {
|
|
13122
|
+
return;
|
|
13123
|
+
}
|
|
13124
|
+
let value = 0;
|
|
13125
|
+
for (const channel of this.channels) {
|
|
13126
|
+
const sample = channels[channel];
|
|
13127
|
+
if (Math.abs(sample) > Math.abs(value)) {
|
|
13128
|
+
value = sample;
|
|
13129
|
+
}
|
|
13130
|
+
}
|
|
13131
|
+
this._value = value;
|
|
13132
|
+
if (value !== 0) {
|
|
13133
|
+
if (!this._triggerTimer.running) {
|
|
13134
|
+
this._triggerTimer.restart();
|
|
13135
|
+
this.onStart.dispatch(value);
|
|
13136
|
+
}
|
|
13137
|
+
this.onActive.dispatch(value);
|
|
13138
|
+
}
|
|
13139
|
+
else if (this._triggerTimer.running) {
|
|
13140
|
+
this.onStop.dispatch(0);
|
|
13141
|
+
if (!this._triggerTimer.expired) {
|
|
13142
|
+
this.onTrigger.dispatch(0);
|
|
13143
|
+
}
|
|
13144
|
+
this._triggerTimer.stop();
|
|
13145
|
+
}
|
|
13146
|
+
}
|
|
13147
|
+
/**
|
|
13148
|
+
* Detach this binding from its owner and release its Signals. Idempotent.
|
|
13149
|
+
*/
|
|
13150
|
+
unbind() {
|
|
13151
|
+
if (this._unbound) {
|
|
13152
|
+
return;
|
|
13153
|
+
}
|
|
13154
|
+
this._unbound = true;
|
|
13155
|
+
this._detacher?.detach(this);
|
|
13156
|
+
this._triggerTimer.destroy();
|
|
13157
|
+
this.onStart.destroy();
|
|
13158
|
+
this.onActive.destroy();
|
|
13159
|
+
this.onStop.destroy();
|
|
13160
|
+
this.onTrigger.destroy();
|
|
13161
|
+
}
|
|
13162
|
+
}
|
|
13163
|
+
|
|
13164
|
+
/**
|
|
13165
|
+
* One of four stable gamepad slots. Lives for the entire `Application`
|
|
13166
|
+
* lifetime even when no physical pad is attached — a "mailbox" that
|
|
13167
|
+
* physical hardware moves into and out of.
|
|
13168
|
+
*
|
|
13169
|
+
* Subscribe to {@link onConnect} / {@link onDisconnect} for hardware
|
|
13170
|
+
* lifecycle, {@link onButtonDown} / {@link onButtonUp} / {@link onAxisChange}
|
|
13171
|
+
* for granular per-event notifications, or call {@link onTrigger} /
|
|
13172
|
+
* {@link onActive} / {@link onStart} / {@link onStop} to register
|
|
13173
|
+
* stateful {@link InputBinding}s pinned to this slot.
|
|
13174
|
+
*
|
|
13175
|
+
* Listeners survive disconnect/reconnect cycles — a binding registered when
|
|
13176
|
+
* the slot was empty will automatically activate when a pad connects.
|
|
13121
13177
|
*/
|
|
13122
13178
|
class Gamepad {
|
|
13179
|
+
/** Fires when a physical pad connects to this slot. */
|
|
13123
13180
|
onConnect = new Signal();
|
|
13181
|
+
/** Fires when the physical pad in this slot disconnects. */
|
|
13124
13182
|
onDisconnect = new Signal();
|
|
13125
|
-
|
|
13126
|
-
|
|
13127
|
-
|
|
13128
|
-
|
|
13129
|
-
|
|
13130
|
-
|
|
13131
|
-
|
|
13132
|
-
|
|
13133
|
-
|
|
13134
|
-
|
|
13135
|
-
|
|
13136
|
-
|
|
13137
|
-
|
|
13138
|
-
|
|
13139
|
-
|
|
13140
|
-
|
|
13141
|
-
|
|
13142
|
-
|
|
13143
|
-
|
|
13144
|
-
|
|
13145
|
-
|
|
13146
|
-
|
|
13147
|
-
|
|
13148
|
-
|
|
13149
|
-
|
|
13150
|
-
this.setInfo({
|
|
13151
|
-
name: definition.name,
|
|
13152
|
-
label: definition.descriptor.label,
|
|
13153
|
-
vendorId: definition.descriptor.vendorId,
|
|
13154
|
-
productId: definition.descriptor.productId,
|
|
13155
|
-
productKey: definition.descriptor.productKey,
|
|
13156
|
-
});
|
|
13157
|
-
this.connect(gamepad);
|
|
13158
|
-
}
|
|
13183
|
+
/** Fires for every button transition from inactive to active. */
|
|
13184
|
+
onButtonDown = new Signal();
|
|
13185
|
+
/** Fires for every button transition from active to inactive. */
|
|
13186
|
+
onButtonUp = new Signal();
|
|
13187
|
+
/** Fires whenever an axis crosses its activation threshold. */
|
|
13188
|
+
onAxisChange = new Signal();
|
|
13189
|
+
/**
|
|
13190
|
+
* Fires when this slot's physical pad has been replaced by a previously
|
|
13191
|
+
* higher-numbered slot's pad, after a `'compact'`-strategy disconnect.
|
|
13192
|
+
* Dispatched with the source slot index the pad came from. Listeners
|
|
13193
|
+
* remain attached and the channel buffer is preserved across the move.
|
|
13194
|
+
*/
|
|
13195
|
+
onPadReassigned = new Signal();
|
|
13196
|
+
_slot;
|
|
13197
|
+
_channels;
|
|
13198
|
+
_bindings = new Set();
|
|
13199
|
+
_detacher = { detach: (binding) => { this._bindings.delete(binding); } };
|
|
13200
|
+
_channelOffset;
|
|
13201
|
+
_mapping = null;
|
|
13202
|
+
_info = null;
|
|
13203
|
+
_browserGamepad = null;
|
|
13204
|
+
constructor(slot, channels) {
|
|
13205
|
+
this._slot = slot;
|
|
13206
|
+
this._channels = channels;
|
|
13207
|
+
this._channelOffset = ChannelOffset.Gamepads + (slot * ChannelSize.Gamepad);
|
|
13159
13208
|
}
|
|
13160
|
-
|
|
13161
|
-
|
|
13209
|
+
/** This pad's stable slot (0..3). */
|
|
13210
|
+
get slot() {
|
|
13211
|
+
return this._slot;
|
|
13212
|
+
}
|
|
13213
|
+
/** `true` while a physical pad is attached to this slot. */
|
|
13214
|
+
get connected() {
|
|
13215
|
+
return this._browserGamepad !== null;
|
|
13162
13216
|
}
|
|
13163
|
-
|
|
13164
|
-
|
|
13217
|
+
/** The active mapping, or `null` when disconnected. */
|
|
13218
|
+
get mapping() {
|
|
13219
|
+
return this._mapping;
|
|
13165
13220
|
}
|
|
13166
|
-
/** The {@link GamepadMappingFamily}
|
|
13221
|
+
/** The {@link GamepadMappingFamily}, or `null` when disconnected. */
|
|
13167
13222
|
get mappingFamily() {
|
|
13168
|
-
return this.
|
|
13223
|
+
return this._mapping?.family ?? null;
|
|
13169
13224
|
}
|
|
13170
|
-
|
|
13171
|
-
|
|
13225
|
+
/** Identity metadata, or `null` when disconnected. */
|
|
13226
|
+
get info() {
|
|
13227
|
+
return this._info;
|
|
13172
13228
|
}
|
|
13173
|
-
|
|
13174
|
-
|
|
13229
|
+
/** The underlying browser gamepad object, or `null` when disconnected. */
|
|
13230
|
+
get browserGamepad() {
|
|
13231
|
+
return this._browserGamepad;
|
|
13175
13232
|
}
|
|
13176
|
-
|
|
13177
|
-
|
|
13233
|
+
/**
|
|
13234
|
+
* Browser-assigned hardware index from `navigator.getGamepads()` (i.e.
|
|
13235
|
+
* `Gamepad.index`), or `null` when no pad is attached. Stable for the
|
|
13236
|
+
* lifetime of a single physical connection but may change across
|
|
13237
|
+
* disconnect/reconnect. Low-level escape hatch for advanced consumers
|
|
13238
|
+
* that need to correlate slots with the raw browser API; prefer
|
|
13239
|
+
* {@link slot} for stable per-application pad identity.
|
|
13240
|
+
*/
|
|
13241
|
+
get internalIndex() {
|
|
13242
|
+
return this._browserGamepad?.index ?? null;
|
|
13178
13243
|
}
|
|
13179
|
-
/**
|
|
13180
|
-
get
|
|
13181
|
-
return this.
|
|
13244
|
+
/** `true` when the connected pad supports rumble via the Web Gamepad API. */
|
|
13245
|
+
get canVibrate() {
|
|
13246
|
+
return this._browserGamepad?.vibrationActuator != null;
|
|
13247
|
+
}
|
|
13248
|
+
/**
|
|
13249
|
+
* Returns `true` when this pad's mapping declares the requested channel.
|
|
13250
|
+
* Use to gate listener registration on optional hardware (e.g. Joy-Con
|
|
13251
|
+
* solo lacks a right stick — `pad.hasChannel(GamepadAxis.RightStickX)`
|
|
13252
|
+
* returns `false`).
|
|
13253
|
+
*/
|
|
13254
|
+
hasChannel(channel) {
|
|
13255
|
+
return this._mapping?.hasChannel(channel) ?? false;
|
|
13256
|
+
}
|
|
13257
|
+
/**
|
|
13258
|
+
* Trigger a rumble effect on this pad. Resolves when the effect finishes.
|
|
13259
|
+
* Silent no-op when the pad is disconnected or the platform does not
|
|
13260
|
+
* support haptic feedback. Use {@link canVibrate} to detect support.
|
|
13261
|
+
*/
|
|
13262
|
+
async vibrate(options) {
|
|
13263
|
+
const actuator = this._browserGamepad?.vibrationActuator;
|
|
13264
|
+
if (!actuator?.playEffect) {
|
|
13265
|
+
return;
|
|
13266
|
+
}
|
|
13267
|
+
await actuator.playEffect('dual-rumble', {
|
|
13268
|
+
duration: options.duration,
|
|
13269
|
+
weakMagnitude: options.weakMagnitude ?? 1,
|
|
13270
|
+
strongMagnitude: options.strongMagnitude ?? 1,
|
|
13271
|
+
startDelay: options.startDelay ?? 0,
|
|
13272
|
+
});
|
|
13182
13273
|
}
|
|
13183
|
-
|
|
13184
|
-
|
|
13274
|
+
/** Stop any active rumble on this pad. Silent no-op when unsupported. */
|
|
13275
|
+
stopVibration() {
|
|
13276
|
+
this._browserGamepad?.vibrationActuator?.reset?.();
|
|
13185
13277
|
}
|
|
13186
|
-
|
|
13187
|
-
|
|
13278
|
+
/**
|
|
13279
|
+
* Register a callback fired once when any of `channels` becomes active.
|
|
13280
|
+
* Listener survives disconnect/reconnect; call `.unbind()` on the
|
|
13281
|
+
* returned {@link InputBinding} to detach.
|
|
13282
|
+
*/
|
|
13283
|
+
onStart(channel, callback, options) {
|
|
13284
|
+
const binding = this._createBinding(channel, options);
|
|
13285
|
+
binding.onStart.add(callback);
|
|
13286
|
+
return binding;
|
|
13188
13287
|
}
|
|
13189
|
-
|
|
13190
|
-
|
|
13288
|
+
/**
|
|
13289
|
+
* Register a callback fired every frame while any of `channels` is active.
|
|
13290
|
+
* Receives the channel value (0..1 for buttons, -1..1 for bipolar axes).
|
|
13291
|
+
*/
|
|
13292
|
+
onActive(channel, callback, options) {
|
|
13293
|
+
const binding = this._createBinding(channel, options);
|
|
13294
|
+
binding.onActive.add(callback);
|
|
13295
|
+
return binding;
|
|
13191
13296
|
}
|
|
13192
|
-
|
|
13193
|
-
|
|
13297
|
+
/** Register a callback fired once when all of `channels` become inactive. */
|
|
13298
|
+
onStop(channel, callback, options) {
|
|
13299
|
+
const binding = this._createBinding(channel, options);
|
|
13300
|
+
binding.onStop.add(callback);
|
|
13301
|
+
return binding;
|
|
13194
13302
|
}
|
|
13195
|
-
|
|
13196
|
-
|
|
13303
|
+
/**
|
|
13304
|
+
* Register a callback fired when the input is released within
|
|
13305
|
+
* {@link InputBindingOptions.threshold} ms of activation (a "tap").
|
|
13306
|
+
*/
|
|
13307
|
+
onTrigger(channel, callback, options) {
|
|
13308
|
+
const binding = this._createBinding(channel, options);
|
|
13309
|
+
binding.onTrigger.add(callback);
|
|
13310
|
+
return binding;
|
|
13197
13311
|
}
|
|
13198
13312
|
/**
|
|
13199
|
-
*
|
|
13200
|
-
*
|
|
13201
|
-
*
|
|
13313
|
+
* Attach a physical browser gamepad to this slot. Called by
|
|
13314
|
+
* {@link InputManager} on connect.
|
|
13315
|
+
*
|
|
13316
|
+
* @internal
|
|
13202
13317
|
*/
|
|
13203
|
-
|
|
13204
|
-
this.
|
|
13205
|
-
|
|
13318
|
+
_bind(gamepad, definition) {
|
|
13319
|
+
this._browserGamepad = gamepad;
|
|
13320
|
+
this._mapping = definition.mapping;
|
|
13321
|
+
this._info = {
|
|
13322
|
+
name: definition.name,
|
|
13323
|
+
label: definition.descriptor.label,
|
|
13324
|
+
vendorId: definition.descriptor.vendorId,
|
|
13325
|
+
productId: definition.descriptor.productId,
|
|
13326
|
+
productKey: definition.descriptor.productKey,
|
|
13327
|
+
};
|
|
13328
|
+
this.onConnect.dispatch();
|
|
13206
13329
|
}
|
|
13207
13330
|
/**
|
|
13208
|
-
*
|
|
13209
|
-
*
|
|
13331
|
+
* Detach the physical gamepad and clear its channels.
|
|
13332
|
+
*
|
|
13333
|
+
* @internal
|
|
13210
13334
|
*/
|
|
13211
|
-
|
|
13212
|
-
|
|
13213
|
-
|
|
13214
|
-
if (!wasConnected) {
|
|
13215
|
-
this.onConnect.dispatch(this);
|
|
13335
|
+
_unbind() {
|
|
13336
|
+
if (this._browserGamepad === null) {
|
|
13337
|
+
return;
|
|
13216
13338
|
}
|
|
13217
|
-
|
|
13339
|
+
this._clearMappedChannels();
|
|
13340
|
+
this._browserGamepad = null;
|
|
13341
|
+
this._mapping = null;
|
|
13342
|
+
this._info = null;
|
|
13343
|
+
this.onDisconnect.dispatch();
|
|
13218
13344
|
}
|
|
13219
13345
|
/**
|
|
13220
|
-
*
|
|
13221
|
-
* {@link onDisconnect}.
|
|
13346
|
+
* Detach the physical gamepad and clear channels without firing
|
|
13347
|
+
* {@link onDisconnect}. Used by {@link InputManager} during the compact
|
|
13348
|
+
* slot-shift to silently vacate a slot before another pad shifts into
|
|
13349
|
+
* its place; the disconnect signal is fired separately on the slot that
|
|
13350
|
+
* ends up empty after compaction.
|
|
13351
|
+
*
|
|
13352
|
+
* @internal
|
|
13222
13353
|
*/
|
|
13223
|
-
|
|
13224
|
-
if (this.
|
|
13225
|
-
|
|
13226
|
-
this.clearMappedChannels();
|
|
13227
|
-
this.onDisconnect.dispatch(this);
|
|
13354
|
+
_silentUnbind() {
|
|
13355
|
+
if (this._browserGamepad === null) {
|
|
13356
|
+
return;
|
|
13228
13357
|
}
|
|
13229
|
-
|
|
13358
|
+
this._clearMappedChannels();
|
|
13359
|
+
this._browserGamepad = null;
|
|
13360
|
+
this._mapping = null;
|
|
13361
|
+
this._info = null;
|
|
13230
13362
|
}
|
|
13231
13363
|
/**
|
|
13232
|
-
*
|
|
13233
|
-
*
|
|
13234
|
-
*
|
|
13235
|
-
*
|
|
13364
|
+
* Dispatch this slot's {@link onDisconnect} signal without altering its
|
|
13365
|
+
* state. Used by {@link InputManager} after a compact-mode shift, when a
|
|
13366
|
+
* slot has already been emptied by {@link _rebindFrom} and now needs to
|
|
13367
|
+
* notify subscribers that its mailbox is no longer occupied.
|
|
13368
|
+
*
|
|
13369
|
+
* @internal
|
|
13370
|
+
*/
|
|
13371
|
+
_dispatchDisconnect() {
|
|
13372
|
+
this.onDisconnect.dispatch();
|
|
13373
|
+
}
|
|
13374
|
+
/**
|
|
13375
|
+
* Reassign this slot to take over another slot's physical gamepad without
|
|
13376
|
+
* firing the full disconnect / connect cycle. Used by {@link InputManager}
|
|
13377
|
+
* when the `'compact'` slot strategy shuffles pads after a disconnect.
|
|
13378
|
+
*
|
|
13379
|
+
* @internal
|
|
13380
|
+
*/
|
|
13381
|
+
_rebindFrom(other) {
|
|
13382
|
+
const gamepad = other._browserGamepad;
|
|
13383
|
+
const mapping = other._mapping;
|
|
13384
|
+
const info = other._info;
|
|
13385
|
+
this._clearMappedChannels();
|
|
13386
|
+
other._clearMappedChannels();
|
|
13387
|
+
other._browserGamepad = null;
|
|
13388
|
+
other._mapping = null;
|
|
13389
|
+
other._info = null;
|
|
13390
|
+
this._browserGamepad = gamepad;
|
|
13391
|
+
this._mapping = mapping;
|
|
13392
|
+
this._info = info;
|
|
13393
|
+
}
|
|
13394
|
+
/**
|
|
13395
|
+
* Sample the browser gamepad's current state and write transformed values
|
|
13396
|
+
* into the shared channel buffer, dispatching transition events when
|
|
13397
|
+
* channel activity crosses thresholds. Called once per frame by the
|
|
13398
|
+
* engine's input loop. No-op when disconnected.
|
|
13399
|
+
*
|
|
13400
|
+
* @internal
|
|
13236
13401
|
*/
|
|
13237
13402
|
update() {
|
|
13238
|
-
if (this.
|
|
13239
|
-
|
|
13403
|
+
if (this._browserGamepad === null || this._mapping === null) {
|
|
13404
|
+
this._updateBindings();
|
|
13405
|
+
return;
|
|
13240
13406
|
}
|
|
13241
|
-
const channels = this.
|
|
13242
|
-
const { buttons:
|
|
13243
|
-
for (const
|
|
13244
|
-
|
|
13245
|
-
|
|
13246
|
-
|
|
13247
|
-
|
|
13248
|
-
|
|
13249
|
-
|
|
13250
|
-
|
|
13407
|
+
const channels = this._channels;
|
|
13408
|
+
const { buttons: rawButtons, axes: rawAxes } = this._browserGamepad;
|
|
13409
|
+
for (const button of this._mapping.buttons) {
|
|
13410
|
+
if (button.index >= rawButtons.length) {
|
|
13411
|
+
continue;
|
|
13412
|
+
}
|
|
13413
|
+
const offset = this._resolveOffset(button.channel);
|
|
13414
|
+
const previous = channels[offset];
|
|
13415
|
+
const value = button.transformValue(rawButtons[button.index].value) || 0;
|
|
13416
|
+
if (previous === value) {
|
|
13417
|
+
continue;
|
|
13418
|
+
}
|
|
13419
|
+
channels[offset] = value;
|
|
13420
|
+
if (previous === 0 && value !== 0) {
|
|
13421
|
+
this.onButtonDown.dispatch(button, value);
|
|
13422
|
+
}
|
|
13423
|
+
else if (previous !== 0 && value === 0) {
|
|
13424
|
+
this.onButtonUp.dispatch(button, value);
|
|
13251
13425
|
}
|
|
13252
13426
|
}
|
|
13253
|
-
for (const
|
|
13254
|
-
|
|
13255
|
-
|
|
13256
|
-
const value = control.transformValue(gamepadAxes[control.index]) || 0;
|
|
13257
|
-
if (channels[offsetChannel] !== value) {
|
|
13258
|
-
channels[offsetChannel] = value;
|
|
13259
|
-
this.onUpdate.dispatch(control.channel, value, this);
|
|
13260
|
-
}
|
|
13427
|
+
for (const axis of this._mapping.axes) {
|
|
13428
|
+
if (axis.index >= rawAxes.length) {
|
|
13429
|
+
continue;
|
|
13261
13430
|
}
|
|
13431
|
+
const offset = this._resolveOffset(axis.channel);
|
|
13432
|
+
const previous = channels[offset];
|
|
13433
|
+
const value = axis.transformValue(rawAxes[axis.index]) || 0;
|
|
13434
|
+
if (previous === value) {
|
|
13435
|
+
continue;
|
|
13436
|
+
}
|
|
13437
|
+
channels[offset] = value;
|
|
13438
|
+
this.onAxisChange.dispatch(axis, value);
|
|
13262
13439
|
}
|
|
13263
|
-
|
|
13264
|
-
}
|
|
13265
|
-
/** Zeroes all channel buffer entries that belong to this gamepad's mapping. */
|
|
13266
|
-
clearChannels() {
|
|
13267
|
-
this.clearMappedChannels();
|
|
13268
|
-
return this;
|
|
13440
|
+
this._updateBindings();
|
|
13269
13441
|
}
|
|
13270
13442
|
/**
|
|
13271
|
-
*
|
|
13272
|
-
* The instance must not be used after this call.
|
|
13443
|
+
* Tear down this gamepad slot. Called on application shutdown.
|
|
13273
13444
|
*/
|
|
13274
13445
|
destroy() {
|
|
13275
|
-
this.
|
|
13276
|
-
|
|
13446
|
+
for (const binding of Array.from(this._bindings)) {
|
|
13447
|
+
binding.unbind();
|
|
13448
|
+
}
|
|
13449
|
+
this._bindings.clear();
|
|
13450
|
+
this._unbind();
|
|
13277
13451
|
this.onConnect.destroy();
|
|
13278
13452
|
this.onDisconnect.destroy();
|
|
13279
|
-
this.
|
|
13453
|
+
this.onButtonDown.destroy();
|
|
13454
|
+
this.onButtonUp.destroy();
|
|
13455
|
+
this.onAxisChange.destroy();
|
|
13456
|
+
this.onPadReassigned.destroy();
|
|
13280
13457
|
}
|
|
13281
13458
|
/**
|
|
13282
|
-
*
|
|
13283
|
-
* buffer for this
|
|
13459
|
+
* Convert a slot-relative channel value to its absolute index in the
|
|
13460
|
+
* shared channel buffer for this slot.
|
|
13284
13461
|
*/
|
|
13285
13462
|
resolveChannelOffset(channel) {
|
|
13286
|
-
return this.
|
|
13463
|
+
return this._resolveOffset(channel);
|
|
13287
13464
|
}
|
|
13288
13465
|
/**
|
|
13289
|
-
*
|
|
13290
|
-
* channel
|
|
13466
|
+
* Static counterpart to {@link Gamepad.resolveChannelOffset} — resolves
|
|
13467
|
+
* an absolute channel-buffer offset for a given slot index without
|
|
13468
|
+
* requiring a Gamepad instance.
|
|
13291
13469
|
*/
|
|
13292
|
-
static resolveChannelOffset(
|
|
13293
|
-
return ChannelOffset.Gamepads + (
|
|
13470
|
+
static resolveChannelOffset(slot, channel) {
|
|
13471
|
+
return ChannelOffset.Gamepads + (slot * ChannelSize.Gamepad) + (channel ^ ChannelOffset.Gamepads);
|
|
13294
13472
|
}
|
|
13295
|
-
|
|
13296
|
-
|
|
13297
|
-
|
|
13473
|
+
_resolveOffset(channel) {
|
|
13474
|
+
return this._channelOffset + (channel ^ ChannelOffset.Gamepads);
|
|
13475
|
+
}
|
|
13476
|
+
_clearMappedChannels() {
|
|
13477
|
+
if (this._mapping === null) {
|
|
13478
|
+
return;
|
|
13298
13479
|
}
|
|
13299
|
-
for (const
|
|
13300
|
-
this.
|
|
13480
|
+
for (const button of this._mapping.buttons) {
|
|
13481
|
+
this._channels[this._resolveOffset(button.channel)] = 0;
|
|
13482
|
+
}
|
|
13483
|
+
for (const axis of this._mapping.axes) {
|
|
13484
|
+
this._channels[this._resolveOffset(axis.channel)] = 0;
|
|
13485
|
+
}
|
|
13486
|
+
}
|
|
13487
|
+
_createBinding(channel, options = {}) {
|
|
13488
|
+
const list = Array.isArray(channel) ? channel : [channel];
|
|
13489
|
+
const resolved = list.map((c) => this._resolveExternalChannel(c));
|
|
13490
|
+
const binding = new InputBinding(resolved, options, this._detacher);
|
|
13491
|
+
this._bindings.add(binding);
|
|
13492
|
+
return binding;
|
|
13493
|
+
}
|
|
13494
|
+
_resolveExternalChannel(channel) {
|
|
13495
|
+
// Keyboard channels are global (no slot offset). Gamepad channels
|
|
13496
|
+
// need slot-aware translation. Any channel value within the
|
|
13497
|
+
// gamepad-section maps through this pad's slot offset; others
|
|
13498
|
+
// (Keyboard, Pointer) pass through as-is.
|
|
13499
|
+
if (channel >= ChannelOffset.Gamepads && channel < ChannelOffset.Gamepads + ChannelSize.Category) {
|
|
13500
|
+
return this._resolveOffset(channel);
|
|
13501
|
+
}
|
|
13502
|
+
return channel;
|
|
13503
|
+
}
|
|
13504
|
+
_updateBindings() {
|
|
13505
|
+
for (const binding of this._bindings) {
|
|
13506
|
+
binding.update(this._channels);
|
|
13301
13507
|
}
|
|
13302
13508
|
}
|
|
13303
13509
|
}
|
|
13304
13510
|
|
|
13511
|
+
const pointerCh = (offset) => (ChannelOffset.Pointers + offset);
|
|
13512
|
+
const slot = (s, field) => pointerCh(s * pointerSlotSize + field);
|
|
13305
13513
|
/**
|
|
13306
13514
|
* Bit flags accumulated on a {@link Pointer} between frames so consumers can
|
|
13307
13515
|
* detect transient events (entered the canvas, was released, was cancelled)
|
|
@@ -13525,82 +13733,80 @@ class Pointer {
|
|
|
13525
13733
|
}
|
|
13526
13734
|
}
|
|
13527
13735
|
/**
|
|
13528
|
-
*
|
|
13529
|
-
*
|
|
13530
|
-
*
|
|
13531
|
-
*
|
|
13532
|
-
* For multi-touch access use `Pointer.Slot{N}Active / Slot{N}X / Slot{N}Y`, or compute:
|
|
13533
|
-
* `Pointer.X + slotIndex * pointerSlotSize + channelOffset`.
|
|
13736
|
+
* Channel-identifier constants merged onto the `Pointer` class. The
|
|
13737
|
+
* un-prefixed members (Active, X, Y, …) address slot 0 (the primary
|
|
13738
|
+
* pointer). For multi-touch access use `Pointer.Slot{N}Active /
|
|
13739
|
+
* Slot{N}X / Slot{N}Y`.
|
|
13534
13740
|
*/
|
|
13535
13741
|
// eslint-disable-next-line @typescript-eslint/no-namespace
|
|
13536
13742
|
(function (Pointer) {
|
|
13537
13743
|
/* eslint-disable @typescript-eslint/naming-convention */
|
|
13538
13744
|
// --- Primary-pointer convenience aliases (slot 0) ---
|
|
13539
|
-
Pointer.Active =
|
|
13540
|
-
Pointer.X =
|
|
13541
|
-
Pointer.Y =
|
|
13542
|
-
Pointer.Pressure =
|
|
13543
|
-
Pointer.Width =
|
|
13544
|
-
Pointer.Height =
|
|
13545
|
-
Pointer.Twist =
|
|
13546
|
-
Pointer.TiltX =
|
|
13547
|
-
Pointer.TiltY =
|
|
13548
|
-
Pointer.Left =
|
|
13549
|
-
Pointer.Right =
|
|
13550
|
-
Pointer.Middle =
|
|
13551
|
-
Pointer.IsMouse =
|
|
13552
|
-
Pointer.IsTouch =
|
|
13553
|
-
Pointer.IsPen =
|
|
13554
|
-
Pointer.IsPrimary =
|
|
13745
|
+
Pointer.Active = pointerCh(0);
|
|
13746
|
+
Pointer.X = pointerCh(1);
|
|
13747
|
+
Pointer.Y = pointerCh(2);
|
|
13748
|
+
Pointer.Pressure = pointerCh(3);
|
|
13749
|
+
Pointer.Width = pointerCh(4);
|
|
13750
|
+
Pointer.Height = pointerCh(5);
|
|
13751
|
+
Pointer.Twist = pointerCh(6);
|
|
13752
|
+
Pointer.TiltX = pointerCh(7);
|
|
13753
|
+
Pointer.TiltY = pointerCh(8);
|
|
13754
|
+
Pointer.Left = pointerCh(9);
|
|
13755
|
+
Pointer.Right = pointerCh(10);
|
|
13756
|
+
Pointer.Middle = pointerCh(11);
|
|
13757
|
+
Pointer.IsMouse = pointerCh(12);
|
|
13758
|
+
Pointer.IsTouch = pointerCh(13);
|
|
13759
|
+
Pointer.IsPen = pointerCh(14);
|
|
13760
|
+
Pointer.IsPrimary = pointerCh(15);
|
|
13555
13761
|
// --- Per-slot Active/X/Y for multi-pointer access ---
|
|
13556
|
-
Pointer.Slot0Active =
|
|
13557
|
-
Pointer.Slot0X =
|
|
13558
|
-
Pointer.Slot0Y =
|
|
13559
|
-
Pointer.Slot1Active =
|
|
13560
|
-
Pointer.Slot1X =
|
|
13561
|
-
Pointer.Slot1Y =
|
|
13562
|
-
Pointer.Slot2Active =
|
|
13563
|
-
Pointer.Slot2X =
|
|
13564
|
-
Pointer.Slot2Y =
|
|
13565
|
-
Pointer.Slot3Active =
|
|
13566
|
-
Pointer.Slot3X =
|
|
13567
|
-
Pointer.Slot3Y =
|
|
13568
|
-
Pointer.Slot4Active =
|
|
13569
|
-
Pointer.Slot4X =
|
|
13570
|
-
Pointer.Slot4Y =
|
|
13571
|
-
Pointer.Slot5Active =
|
|
13572
|
-
Pointer.Slot5X =
|
|
13573
|
-
Pointer.Slot5Y =
|
|
13574
|
-
Pointer.Slot6Active =
|
|
13575
|
-
Pointer.Slot6X =
|
|
13576
|
-
Pointer.Slot6Y =
|
|
13577
|
-
Pointer.Slot7Active =
|
|
13578
|
-
Pointer.Slot7X =
|
|
13579
|
-
Pointer.Slot7Y =
|
|
13580
|
-
Pointer.Slot8Active =
|
|
13581
|
-
Pointer.Slot8X =
|
|
13582
|
-
Pointer.Slot8Y =
|
|
13583
|
-
Pointer.Slot9Active =
|
|
13584
|
-
Pointer.Slot9X =
|
|
13585
|
-
Pointer.Slot9Y =
|
|
13586
|
-
Pointer.Slot10Active =
|
|
13587
|
-
Pointer.Slot10X =
|
|
13588
|
-
Pointer.Slot10Y =
|
|
13589
|
-
Pointer.Slot11Active =
|
|
13590
|
-
Pointer.Slot11X =
|
|
13591
|
-
Pointer.Slot11Y =
|
|
13592
|
-
Pointer.Slot12Active =
|
|
13593
|
-
Pointer.Slot12X =
|
|
13594
|
-
Pointer.Slot12Y =
|
|
13595
|
-
Pointer.Slot13Active =
|
|
13596
|
-
Pointer.Slot13X =
|
|
13597
|
-
Pointer.Slot13Y =
|
|
13598
|
-
Pointer.Slot14Active =
|
|
13599
|
-
Pointer.Slot14X =
|
|
13600
|
-
Pointer.Slot14Y =
|
|
13601
|
-
Pointer.Slot15Active =
|
|
13602
|
-
Pointer.Slot15X =
|
|
13603
|
-
Pointer.Slot15Y =
|
|
13762
|
+
Pointer.Slot0Active = slot(0, 0);
|
|
13763
|
+
Pointer.Slot0X = slot(0, 1);
|
|
13764
|
+
Pointer.Slot0Y = slot(0, 2);
|
|
13765
|
+
Pointer.Slot1Active = slot(1, 0);
|
|
13766
|
+
Pointer.Slot1X = slot(1, 1);
|
|
13767
|
+
Pointer.Slot1Y = slot(1, 2);
|
|
13768
|
+
Pointer.Slot2Active = slot(2, 0);
|
|
13769
|
+
Pointer.Slot2X = slot(2, 1);
|
|
13770
|
+
Pointer.Slot2Y = slot(2, 2);
|
|
13771
|
+
Pointer.Slot3Active = slot(3, 0);
|
|
13772
|
+
Pointer.Slot3X = slot(3, 1);
|
|
13773
|
+
Pointer.Slot3Y = slot(3, 2);
|
|
13774
|
+
Pointer.Slot4Active = slot(4, 0);
|
|
13775
|
+
Pointer.Slot4X = slot(4, 1);
|
|
13776
|
+
Pointer.Slot4Y = slot(4, 2);
|
|
13777
|
+
Pointer.Slot5Active = slot(5, 0);
|
|
13778
|
+
Pointer.Slot5X = slot(5, 1);
|
|
13779
|
+
Pointer.Slot5Y = slot(5, 2);
|
|
13780
|
+
Pointer.Slot6Active = slot(6, 0);
|
|
13781
|
+
Pointer.Slot6X = slot(6, 1);
|
|
13782
|
+
Pointer.Slot6Y = slot(6, 2);
|
|
13783
|
+
Pointer.Slot7Active = slot(7, 0);
|
|
13784
|
+
Pointer.Slot7X = slot(7, 1);
|
|
13785
|
+
Pointer.Slot7Y = slot(7, 2);
|
|
13786
|
+
Pointer.Slot8Active = slot(8, 0);
|
|
13787
|
+
Pointer.Slot8X = slot(8, 1);
|
|
13788
|
+
Pointer.Slot8Y = slot(8, 2);
|
|
13789
|
+
Pointer.Slot9Active = slot(9, 0);
|
|
13790
|
+
Pointer.Slot9X = slot(9, 1);
|
|
13791
|
+
Pointer.Slot9Y = slot(9, 2);
|
|
13792
|
+
Pointer.Slot10Active = slot(10, 0);
|
|
13793
|
+
Pointer.Slot10X = slot(10, 1);
|
|
13794
|
+
Pointer.Slot10Y = slot(10, 2);
|
|
13795
|
+
Pointer.Slot11Active = slot(11, 0);
|
|
13796
|
+
Pointer.Slot11X = slot(11, 1);
|
|
13797
|
+
Pointer.Slot11Y = slot(11, 2);
|
|
13798
|
+
Pointer.Slot12Active = slot(12, 0);
|
|
13799
|
+
Pointer.Slot12X = slot(12, 1);
|
|
13800
|
+
Pointer.Slot12Y = slot(12, 2);
|
|
13801
|
+
Pointer.Slot13Active = slot(13, 0);
|
|
13802
|
+
Pointer.Slot13X = slot(13, 1);
|
|
13803
|
+
Pointer.Slot13Y = slot(13, 2);
|
|
13804
|
+
Pointer.Slot14Active = slot(14, 0);
|
|
13805
|
+
Pointer.Slot14X = slot(14, 1);
|
|
13806
|
+
Pointer.Slot14Y = slot(14, 2);
|
|
13807
|
+
Pointer.Slot15Active = slot(15, 0);
|
|
13808
|
+
Pointer.Slot15X = slot(15, 1);
|
|
13809
|
+
Pointer.Slot15Y = slot(15, 2);
|
|
13604
13810
|
/* eslint-enable @typescript-eslint/naming-convention */
|
|
13605
13811
|
})(Pointer || (Pointer = {}));
|
|
13606
13812
|
|
|
@@ -13742,91 +13948,98 @@ class GestureRecognizer {
|
|
|
13742
13948
|
}
|
|
13743
13949
|
|
|
13744
13950
|
/**
|
|
13745
|
-
*
|
|
13746
|
-
*
|
|
13747
|
-
*
|
|
13748
|
-
*
|
|
13749
|
-
*
|
|
13750
|
-
*
|
|
13951
|
+
* Single mappable button on a physical gamepad. Holds the raw browser
|
|
13952
|
+
* `Gamepad.buttons[]` index, the canonical channel the value is written to,
|
|
13953
|
+
* and the deadzone/inversion transform applied each frame by
|
|
13954
|
+
* {@link transformValue}.
|
|
13955
|
+
*
|
|
13956
|
+
* Used by concrete {@link GamepadMapping} subclasses to declare a device's
|
|
13957
|
+
* button layout. User code typically constructs these via
|
|
13958
|
+
* `new GamepadButton(rawIndex, GamepadButton.South)` only when authoring a
|
|
13959
|
+
* custom mapping.
|
|
13960
|
+
*
|
|
13961
|
+
* The static namespace exports (`GamepadButton.South`, `.East`, ...) carry
|
|
13962
|
+
* the canonical channel offsets used to address each button.
|
|
13751
13963
|
*/
|
|
13752
|
-
|
|
13753
|
-
(function (GamepadChannel) {
|
|
13754
|
-
GamepadChannel[GamepadChannel["ButtonSouth"] = 512] = "ButtonSouth";
|
|
13755
|
-
GamepadChannel[GamepadChannel["ButtonWest"] = 513] = "ButtonWest";
|
|
13756
|
-
GamepadChannel[GamepadChannel["ButtonEast"] = 514] = "ButtonEast";
|
|
13757
|
-
GamepadChannel[GamepadChannel["ButtonNorth"] = 515] = "ButtonNorth";
|
|
13758
|
-
GamepadChannel[GamepadChannel["LeftShoulder"] = 516] = "LeftShoulder";
|
|
13759
|
-
GamepadChannel[GamepadChannel["RightShoulder"] = 517] = "RightShoulder";
|
|
13760
|
-
GamepadChannel[GamepadChannel["LeftTrigger"] = 518] = "LeftTrigger";
|
|
13761
|
-
GamepadChannel[GamepadChannel["RightTrigger"] = 519] = "RightTrigger";
|
|
13762
|
-
GamepadChannel[GamepadChannel["Select"] = 520] = "Select";
|
|
13763
|
-
GamepadChannel[GamepadChannel["Start"] = 521] = "Start";
|
|
13764
|
-
GamepadChannel[GamepadChannel["LeftStick"] = 522] = "LeftStick";
|
|
13765
|
-
GamepadChannel[GamepadChannel["RightStick"] = 523] = "RightStick";
|
|
13766
|
-
GamepadChannel[GamepadChannel["DPadUp"] = 524] = "DPadUp";
|
|
13767
|
-
GamepadChannel[GamepadChannel["DPadDown"] = 525] = "DPadDown";
|
|
13768
|
-
GamepadChannel[GamepadChannel["DPadLeft"] = 526] = "DPadLeft";
|
|
13769
|
-
GamepadChannel[GamepadChannel["DPadRight"] = 527] = "DPadRight";
|
|
13770
|
-
GamepadChannel[GamepadChannel["Guide"] = 528] = "Guide";
|
|
13771
|
-
GamepadChannel[GamepadChannel["Share"] = 529] = "Share";
|
|
13772
|
-
GamepadChannel[GamepadChannel["Capture"] = 530] = "Capture";
|
|
13773
|
-
GamepadChannel[GamepadChannel["Touchpad"] = 531] = "Touchpad";
|
|
13774
|
-
GamepadChannel[GamepadChannel["Paddle1"] = 532] = "Paddle1";
|
|
13775
|
-
GamepadChannel[GamepadChannel["LeftStickLeft"] = 533] = "LeftStickLeft";
|
|
13776
|
-
GamepadChannel[GamepadChannel["LeftStickRight"] = 534] = "LeftStickRight";
|
|
13777
|
-
GamepadChannel[GamepadChannel["LeftStickUp"] = 535] = "LeftStickUp";
|
|
13778
|
-
GamepadChannel[GamepadChannel["LeftStickDown"] = 536] = "LeftStickDown";
|
|
13779
|
-
GamepadChannel[GamepadChannel["RightStickLeft"] = 537] = "RightStickLeft";
|
|
13780
|
-
GamepadChannel[GamepadChannel["RightStickRight"] = 538] = "RightStickRight";
|
|
13781
|
-
GamepadChannel[GamepadChannel["RightStickUp"] = 539] = "RightStickUp";
|
|
13782
|
-
GamepadChannel[GamepadChannel["RightStickDown"] = 540] = "RightStickDown";
|
|
13783
|
-
GamepadChannel[GamepadChannel["AuxiliaryAxis0Negative"] = 541] = "AuxiliaryAxis0Negative";
|
|
13784
|
-
GamepadChannel[GamepadChannel["AuxiliaryAxis0Positive"] = 542] = "AuxiliaryAxis0Positive";
|
|
13785
|
-
GamepadChannel[GamepadChannel["AuxiliaryAxis1Negative"] = 543] = "AuxiliaryAxis1Negative";
|
|
13786
|
-
GamepadChannel[GamepadChannel["AuxiliaryAxis1Positive"] = 544] = "AuxiliaryAxis1Positive";
|
|
13787
|
-
GamepadChannel[GamepadChannel["AuxiliaryAxis2Negative"] = 545] = "AuxiliaryAxis2Negative";
|
|
13788
|
-
GamepadChannel[GamepadChannel["AuxiliaryAxis2Positive"] = 546] = "AuxiliaryAxis2Positive";
|
|
13789
|
-
GamepadChannel[GamepadChannel["AuxiliaryAxis3Negative"] = 547] = "AuxiliaryAxis3Negative";
|
|
13790
|
-
GamepadChannel[GamepadChannel["AuxiliaryAxis3Positive"] = 548] = "AuxiliaryAxis3Positive";
|
|
13791
|
-
})(GamepadChannel || (GamepadChannel = {}));
|
|
13792
|
-
|
|
13793
|
-
/**
|
|
13794
|
-
* Represents a single mappable control — one button or one axis — on a physical gamepad.
|
|
13795
|
-
*
|
|
13796
|
-
* Stores the raw Gamepad API `index`, the target {@link GamepadChannel}, and the
|
|
13797
|
-
* transform parameters (`invert`, `normalize`, `threshold`) applied each frame
|
|
13798
|
-
* by {@link transformValue} before the value is written to the channel buffer.
|
|
13799
|
-
*/
|
|
13800
|
-
class GamepadControl {
|
|
13964
|
+
class GamepadButton {
|
|
13801
13965
|
index;
|
|
13802
13966
|
channel;
|
|
13803
13967
|
invert;
|
|
13804
|
-
normalize;
|
|
13805
13968
|
threshold;
|
|
13806
13969
|
constructor(index, channel, options = {}) {
|
|
13807
13970
|
this.index = index;
|
|
13808
13971
|
this.channel = channel;
|
|
13809
13972
|
this.invert = options.invert ?? false;
|
|
13810
|
-
this.normalize = options.normalize ?? false;
|
|
13811
13973
|
this.threshold = clamp(options.threshold ?? 0.2, 0, 1);
|
|
13812
13974
|
}
|
|
13813
13975
|
/**
|
|
13814
|
-
*
|
|
13976
|
+
* Apply the button's transform pipeline to a raw browser button value
|
|
13977
|
+
* (typically `Gamepad.buttons[i].value`, in 0..1).
|
|
13815
13978
|
*
|
|
13816
|
-
* Pipeline: clamp to [
|
|
13817
|
-
*
|
|
13979
|
+
* Pipeline: clamp to [0, 1] → optional invert → deadzone (returns 0 when
|
|
13980
|
+
* the result is at or below `threshold`).
|
|
13818
13981
|
*/
|
|
13819
13982
|
transformValue(value) {
|
|
13820
|
-
let result = clamp(value,
|
|
13983
|
+
let result = clamp(value, 0, 1);
|
|
13821
13984
|
if (this.invert) {
|
|
13822
|
-
result
|
|
13823
|
-
}
|
|
13824
|
-
if (this.normalize) {
|
|
13825
|
-
result = (result + 1) / 2;
|
|
13985
|
+
result = 1 - result;
|
|
13826
13986
|
}
|
|
13827
13987
|
return result > this.threshold ? result : 0;
|
|
13828
13988
|
}
|
|
13829
13989
|
}
|
|
13990
|
+
const button = (offset) => (ChannelOffset.Gamepads + offset);
|
|
13991
|
+
/* eslint-disable @typescript-eslint/no-namespace, @typescript-eslint/naming-convention */
|
|
13992
|
+
/**
|
|
13993
|
+
* Channel-identifier constants — same convention as `Pointer.X` /
|
|
13994
|
+
* `Keyboard.Space`. The first 32 slots of each gamepad sub-buffer are
|
|
13995
|
+
* reserved for buttons (24 named, 8 buffer for future / custom mappings).
|
|
13996
|
+
*/
|
|
13997
|
+
(function (GamepadButton) {
|
|
13998
|
+
/** Bottom face button. Xbox=A, PlayStation=✕, Switch (horizontal Joy-Con)=B. Conventional usage: confirm / primary action / jump. */
|
|
13999
|
+
GamepadButton.South = button(0);
|
|
14000
|
+
/** Right face button. Xbox=B, PlayStation=○, Switch=A. Conventional usage: cancel / back / secondary. */
|
|
14001
|
+
GamepadButton.East = button(1);
|
|
14002
|
+
/** Left face button. Xbox=X, PlayStation=□, Switch=Y. Conventional usage: tertiary action. */
|
|
14003
|
+
GamepadButton.West = button(2);
|
|
14004
|
+
/** Top face button. Xbox=Y, PlayStation=△, Switch=X. Conventional usage: quaternary action. */
|
|
14005
|
+
GamepadButton.North = button(3);
|
|
14006
|
+
GamepadButton.LeftShoulder = button(4);
|
|
14007
|
+
GamepadButton.RightShoulder = button(5);
|
|
14008
|
+
/** Left trigger as a button (analog 0..1 reported through the same channel). */
|
|
14009
|
+
GamepadButton.LeftTrigger = button(6);
|
|
14010
|
+
/** Right trigger as a button. */
|
|
14011
|
+
GamepadButton.RightTrigger = button(7);
|
|
14012
|
+
/** Select / Back / Minus button. */
|
|
14013
|
+
GamepadButton.Select = button(8);
|
|
14014
|
+
/** Start / Options / Plus button. */
|
|
14015
|
+
GamepadButton.Start = button(9);
|
|
14016
|
+
/** Left analog stick click (L3). */
|
|
14017
|
+
GamepadButton.LeftStick = button(10);
|
|
14018
|
+
/** Right analog stick click (R3). */
|
|
14019
|
+
GamepadButton.RightStick = button(11);
|
|
14020
|
+
GamepadButton.DPadUp = button(12);
|
|
14021
|
+
GamepadButton.DPadDown = button(13);
|
|
14022
|
+
GamepadButton.DPadLeft = button(14);
|
|
14023
|
+
GamepadButton.DPadRight = button(15);
|
|
14024
|
+
/** Home / Guide / PS button. */
|
|
14025
|
+
GamepadButton.Guide = button(16);
|
|
14026
|
+
/** Share / Create button (PS4/PS5, Xbox Series). */
|
|
14027
|
+
GamepadButton.Share = button(17);
|
|
14028
|
+
/** Capture / Screenshot button (Switch, Xbox Series). */
|
|
14029
|
+
GamepadButton.Capture = button(18);
|
|
14030
|
+
/** Touchpad click (PlayStation). */
|
|
14031
|
+
GamepadButton.Touchpad = button(19);
|
|
14032
|
+
/** First paddle / extra button (Xbox Elite, Steam Controller, PS5 Edge, Steam Deck L4). */
|
|
14033
|
+
GamepadButton.Paddle1 = button(20);
|
|
14034
|
+
/** Second paddle / extra button (Xbox Elite, Steam Deck R4, PS5 Edge). */
|
|
14035
|
+
GamepadButton.Paddle2 = button(21);
|
|
14036
|
+
/** Third paddle / extra button (Xbox Elite, Steam Deck L5). */
|
|
14037
|
+
GamepadButton.Paddle3 = button(22);
|
|
14038
|
+
/** Fourth paddle / extra button (Xbox Elite, Steam Deck R5). */
|
|
14039
|
+
GamepadButton.Paddle4 = button(23);
|
|
14040
|
+
// Offsets 24..31 reserved for future named buttons / custom mapping use.
|
|
14041
|
+
})(GamepadButton || (GamepadButton = {}));
|
|
14042
|
+
/* eslint-enable @typescript-eslint/no-namespace, @typescript-eslint/naming-convention */
|
|
13830
14043
|
|
|
13831
14044
|
/**
|
|
13832
14045
|
* Discriminant tag identifying which device family a {@link GamepadMapping} belongs to.
|
|
@@ -13842,26 +14055,53 @@ var GamepadMappingFamily;
|
|
|
13842
14055
|
GamepadMappingFamily["JoyConRight"] = "joyConRight";
|
|
13843
14056
|
GamepadMappingFamily["GameCube"] = "gameCube";
|
|
13844
14057
|
GamepadMappingFamily["SteamController"] = "steamController";
|
|
14058
|
+
GamepadMappingFamily["SteamDeck"] = "steamDeck";
|
|
13845
14059
|
GamepadMappingFamily["ArcadeStick"] = "arcadeStick";
|
|
13846
14060
|
})(GamepadMappingFamily || (GamepadMappingFamily = {}));
|
|
13847
14061
|
/**
|
|
13848
14062
|
* Abstract translation layer between the browser's raw {@link https://developer.mozilla.org/en-US/docs/Web/API/Gamepad Gamepad API}
|
|
13849
|
-
* indices and ExoJS-canonical
|
|
14063
|
+
* indices and ExoJS-canonical channel buffers.
|
|
13850
14064
|
*
|
|
13851
14065
|
* Each concrete subclass encodes one device family's button/axis layout as
|
|
13852
|
-
* ordered arrays of {@link
|
|
13853
|
-
* appropriate mapping when a gamepad connects and
|
|
13854
|
-
* values to the correct input channels every frame.
|
|
14066
|
+
* ordered arrays of {@link GamepadButton} / {@link GamepadAxis} instances.
|
|
14067
|
+
* The engine selects the appropriate mapping when a gamepad connects and
|
|
14068
|
+
* uses it to route raw values to the correct input channels every frame.
|
|
13855
14069
|
*/
|
|
13856
14070
|
class GamepadMapping {
|
|
13857
|
-
/** Ordered list of
|
|
14071
|
+
/** Ordered list of buttons, indexed by the Gamepad API button index. */
|
|
13858
14072
|
buttons;
|
|
13859
|
-
/** Ordered list of
|
|
14073
|
+
/** Ordered list of axes, indexed by the Gamepad API axis index. */
|
|
13860
14074
|
axes;
|
|
13861
14075
|
constructor(buttons, axes) {
|
|
13862
14076
|
this.buttons = buttons;
|
|
13863
14077
|
this.axes = axes;
|
|
13864
14078
|
}
|
|
14079
|
+
/**
|
|
14080
|
+
* Returns `true` when this mapping declares at least one button or axis
|
|
14081
|
+
* control that writes to `channel`. Use to detect device-specific
|
|
14082
|
+
* capabilities at runtime — e.g. before binding an input to a
|
|
14083
|
+
* right-stick channel that may not exist on a single Joy-Con.
|
|
14084
|
+
*
|
|
14085
|
+
* @example
|
|
14086
|
+
* ```ts
|
|
14087
|
+
* if (gamepad.mapping?.hasChannel(GamepadAxis.RightStickX)) {
|
|
14088
|
+
* pad.onActive(GamepadAxis.RightStickX, (v) => crosshair.x += v * 8);
|
|
14089
|
+
* }
|
|
14090
|
+
* ```
|
|
14091
|
+
*/
|
|
14092
|
+
hasChannel(channel) {
|
|
14093
|
+
for (const button of this.buttons) {
|
|
14094
|
+
if (button.channel === channel) {
|
|
14095
|
+
return true;
|
|
14096
|
+
}
|
|
14097
|
+
}
|
|
14098
|
+
for (const axis of this.axes) {
|
|
14099
|
+
if (axis.channel === channel) {
|
|
14100
|
+
return true;
|
|
14101
|
+
}
|
|
14102
|
+
}
|
|
14103
|
+
return false;
|
|
14104
|
+
}
|
|
13865
14105
|
/**
|
|
13866
14106
|
* Releases all button and axis control references held by this mapping.
|
|
13867
14107
|
* Call when the associated gamepad disconnects to allow garbage collection.
|
|
@@ -13870,33 +14110,8 @@ class GamepadMapping {
|
|
|
13870
14110
|
this.buttons.length = 0;
|
|
13871
14111
|
this.axes.length = 0;
|
|
13872
14112
|
}
|
|
13873
|
-
/**
|
|
13874
|
-
* Converts an array of {@link GamepadControlDefinition} tuples into fully
|
|
13875
|
-
* constructed {@link GamepadControl} instances.
|
|
13876
|
-
* Shared factory used by all concrete mapping constructors.
|
|
13877
|
-
*/
|
|
13878
|
-
static createControls(definitions) {
|
|
13879
|
-
return definitions.map(([index, channel, options]) => new GamepadControl(index, channel, options));
|
|
13880
|
-
}
|
|
13881
14113
|
}
|
|
13882
14114
|
|
|
13883
|
-
const arcadeStickButtonDefinitions = [
|
|
13884
|
-
[0, GamepadChannel.ButtonSouth],
|
|
13885
|
-
[1, GamepadChannel.ButtonEast],
|
|
13886
|
-
[2, GamepadChannel.ButtonWest],
|
|
13887
|
-
[3, GamepadChannel.ButtonNorth],
|
|
13888
|
-
[4, GamepadChannel.LeftShoulder],
|
|
13889
|
-
[5, GamepadChannel.RightShoulder],
|
|
13890
|
-
[6, GamepadChannel.LeftTrigger],
|
|
13891
|
-
[7, GamepadChannel.RightTrigger],
|
|
13892
|
-
[8, GamepadChannel.Select],
|
|
13893
|
-
[9, GamepadChannel.Start],
|
|
13894
|
-
[12, GamepadChannel.DPadUp],
|
|
13895
|
-
[13, GamepadChannel.DPadDown],
|
|
13896
|
-
[14, GamepadChannel.DPadLeft],
|
|
13897
|
-
[15, GamepadChannel.DPadRight],
|
|
13898
|
-
[16, GamepadChannel.Guide],
|
|
13899
|
-
];
|
|
13900
14115
|
/**
|
|
13901
14116
|
* Mapping for generic arcade-stick controllers.
|
|
13902
14117
|
*
|
|
@@ -13907,65 +14122,190 @@ const arcadeStickButtonDefinitions = [
|
|
|
13907
14122
|
class ArcadeStickGamepadMapping extends GamepadMapping {
|
|
13908
14123
|
family = GamepadMappingFamily.ArcadeStick;
|
|
13909
14124
|
constructor() {
|
|
13910
|
-
super(
|
|
14125
|
+
super([
|
|
14126
|
+
new GamepadButton(0, GamepadButton.South),
|
|
14127
|
+
new GamepadButton(1, GamepadButton.East),
|
|
14128
|
+
new GamepadButton(2, GamepadButton.West),
|
|
14129
|
+
new GamepadButton(3, GamepadButton.North),
|
|
14130
|
+
new GamepadButton(4, GamepadButton.LeftShoulder),
|
|
14131
|
+
new GamepadButton(5, GamepadButton.RightShoulder),
|
|
14132
|
+
new GamepadButton(6, GamepadButton.LeftTrigger),
|
|
14133
|
+
new GamepadButton(7, GamepadButton.RightTrigger),
|
|
14134
|
+
new GamepadButton(8, GamepadButton.Select),
|
|
14135
|
+
new GamepadButton(9, GamepadButton.Start),
|
|
14136
|
+
new GamepadButton(12, GamepadButton.DPadUp),
|
|
14137
|
+
new GamepadButton(13, GamepadButton.DPadDown),
|
|
14138
|
+
new GamepadButton(14, GamepadButton.DPadLeft),
|
|
14139
|
+
new GamepadButton(15, GamepadButton.DPadRight),
|
|
14140
|
+
new GamepadButton(16, GamepadButton.Guide),
|
|
14141
|
+
], []);
|
|
13911
14142
|
}
|
|
13912
14143
|
}
|
|
13913
14144
|
|
|
13914
|
-
|
|
13915
|
-
|
|
13916
|
-
|
|
13917
|
-
|
|
13918
|
-
|
|
13919
|
-
|
|
13920
|
-
|
|
13921
|
-
|
|
13922
|
-
|
|
13923
|
-
|
|
13924
|
-
|
|
13925
|
-
|
|
13926
|
-
|
|
13927
|
-
|
|
13928
|
-
|
|
13929
|
-
|
|
13930
|
-
|
|
13931
|
-
|
|
13932
|
-
|
|
13933
|
-
|
|
13934
|
-
|
|
13935
|
-
|
|
13936
|
-
|
|
13937
|
-
|
|
13938
|
-
|
|
13939
|
-
|
|
13940
|
-
|
|
13941
|
-
|
|
13942
|
-
|
|
13943
|
-
|
|
13944
|
-
|
|
13945
|
-
|
|
13946
|
-
|
|
13947
|
-
|
|
13948
|
-
|
|
13949
|
-
|
|
13950
|
-
|
|
13951
|
-
|
|
13952
|
-
|
|
13953
|
-
|
|
13954
|
-
|
|
14145
|
+
/**
|
|
14146
|
+
* Single mappable analog axis on a physical gamepad. Holds the raw browser
|
|
14147
|
+
* `Gamepad.axes[]` index, the canonical channel the value is written to, and
|
|
14148
|
+
* the transform pipeline applied each frame by {@link transformValue}.
|
|
14149
|
+
*
|
|
14150
|
+
* Direction-split axis channels (e.g. `LeftStickLeft`, `LeftStickRight`)
|
|
14151
|
+
* live in the 0..1 range — set `invert: true` on the negative half so it
|
|
14152
|
+
* reads positive when pushed in its direction.
|
|
14153
|
+
*
|
|
14154
|
+
* Aggregate channels (e.g. `LeftStickX`, `LeftStickY`) live in the full
|
|
14155
|
+
* -1..1 range — set `bipolar: true` to preserve sign through the pipeline.
|
|
14156
|
+
*
|
|
14157
|
+
* The static namespace exports (`GamepadAxis.LeftStickLeft`,
|
|
14158
|
+
* `.LeftStickX`, ...) carry the canonical channel offsets used to address
|
|
14159
|
+
* each axis.
|
|
14160
|
+
*/
|
|
14161
|
+
class GamepadAxis {
|
|
14162
|
+
index;
|
|
14163
|
+
channel;
|
|
14164
|
+
invert;
|
|
14165
|
+
normalize;
|
|
14166
|
+
threshold;
|
|
14167
|
+
bipolar;
|
|
14168
|
+
constructor(index, channel, options = {}) {
|
|
14169
|
+
this.index = index;
|
|
14170
|
+
this.channel = channel;
|
|
14171
|
+
this.invert = options.invert ?? false;
|
|
14172
|
+
this.normalize = options.normalize ?? false;
|
|
14173
|
+
this.threshold = clamp(options.threshold ?? 0.2, 0, 1);
|
|
14174
|
+
this.bipolar = options.bipolar ?? false;
|
|
14175
|
+
}
|
|
14176
|
+
/**
|
|
14177
|
+
* Apply the axis transform pipeline to a raw browser axis value
|
|
14178
|
+
* (typically `Gamepad.axes[i]`, in -1..1).
|
|
14179
|
+
*
|
|
14180
|
+
* Pipeline: clamp to [-1, 1] → optional invert → optional normalize to
|
|
14181
|
+
* [0, 1] → bipolar passthrough OR deadzone (returns 0 when the absolute
|
|
14182
|
+
* value is at or below `threshold`).
|
|
14183
|
+
*/
|
|
14184
|
+
transformValue(value) {
|
|
14185
|
+
let result = clamp(value, -1, 1);
|
|
14186
|
+
if (this.invert) {
|
|
14187
|
+
result *= -1;
|
|
14188
|
+
}
|
|
14189
|
+
if (this.normalize) {
|
|
14190
|
+
result = (result + 1) / 2;
|
|
14191
|
+
}
|
|
14192
|
+
if (this.bipolar) {
|
|
14193
|
+
return Math.abs(result) > this.threshold ? result : 0;
|
|
14194
|
+
}
|
|
14195
|
+
return result > this.threshold ? result : 0;
|
|
14196
|
+
}
|
|
14197
|
+
}
|
|
14198
|
+
const axis = (offset) => (ChannelOffset.Gamepads + offset);
|
|
14199
|
+
/* eslint-disable @typescript-eslint/no-namespace, @typescript-eslint/naming-convention */
|
|
14200
|
+
/**
|
|
14201
|
+
* Channel-identifier constants. The axis section starts after the 32-slot
|
|
14202
|
+
* button block: 24 named axes (offsets 32..55) plus 8 reserved slots
|
|
14203
|
+
* (offsets 56..63).
|
|
14204
|
+
*/
|
|
14205
|
+
(function (GamepadAxis) {
|
|
14206
|
+
// Direction-split (0..1, "buttons-style").
|
|
14207
|
+
GamepadAxis.LeftStickLeft = axis(32);
|
|
14208
|
+
GamepadAxis.LeftStickRight = axis(33);
|
|
14209
|
+
GamepadAxis.LeftStickUp = axis(34);
|
|
14210
|
+
GamepadAxis.LeftStickDown = axis(35);
|
|
14211
|
+
GamepadAxis.RightStickLeft = axis(36);
|
|
14212
|
+
GamepadAxis.RightStickRight = axis(37);
|
|
14213
|
+
GamepadAxis.RightStickUp = axis(38);
|
|
14214
|
+
GamepadAxis.RightStickDown = axis(39);
|
|
14215
|
+
// Aggregate (-1..1, "stick-style").
|
|
14216
|
+
/** Signed left-stick X axis (-1..1). Negative = left, positive = right. */
|
|
14217
|
+
GamepadAxis.LeftStickX = axis(40);
|
|
14218
|
+
/** Signed left-stick Y axis (-1..1). Negative = up (screen-up), positive = down. */
|
|
14219
|
+
GamepadAxis.LeftStickY = axis(41);
|
|
14220
|
+
/** Signed right-stick X axis (-1..1). */
|
|
14221
|
+
GamepadAxis.RightStickX = axis(42);
|
|
14222
|
+
/** Signed right-stick Y axis (-1..1). */
|
|
14223
|
+
GamepadAxis.RightStickY = axis(43);
|
|
14224
|
+
// Touchpad XY (PlayStation 4/5, Steam Deck, dual-touchpad Steam-class hardware).
|
|
14225
|
+
/** Primary touchpad X (0..1, left to right). PlayStation, Steam Deck (left pad), Steam Controller. */
|
|
14226
|
+
GamepadAxis.TouchpadX = axis(44);
|
|
14227
|
+
/** Primary touchpad Y (0..1, top to bottom). */
|
|
14228
|
+
GamepadAxis.TouchpadY = axis(45);
|
|
14229
|
+
/** Secondary touchpad X (0..1). Steam Deck (right pad), other dual-touchpad hardware. */
|
|
14230
|
+
GamepadAxis.Touchpad2X = axis(46);
|
|
14231
|
+
/** Secondary touchpad Y (0..1). */
|
|
14232
|
+
GamepadAxis.Touchpad2Y = axis(47);
|
|
14233
|
+
// Auxiliary axes (4 bipolar axes split into 8 non-negative channels).
|
|
14234
|
+
GamepadAxis.AuxiliaryAxis0Negative = axis(48);
|
|
14235
|
+
GamepadAxis.AuxiliaryAxis0Positive = axis(49);
|
|
14236
|
+
GamepadAxis.AuxiliaryAxis1Negative = axis(50);
|
|
14237
|
+
GamepadAxis.AuxiliaryAxis1Positive = axis(51);
|
|
14238
|
+
GamepadAxis.AuxiliaryAxis2Negative = axis(52);
|
|
14239
|
+
GamepadAxis.AuxiliaryAxis2Positive = axis(53);
|
|
14240
|
+
GamepadAxis.AuxiliaryAxis3Negative = axis(54);
|
|
14241
|
+
GamepadAxis.AuxiliaryAxis3Positive = axis(55);
|
|
14242
|
+
// Offsets 56..63 reserved for future named axes / custom mapping use.
|
|
14243
|
+
})(GamepadAxis || (GamepadAxis = {}));
|
|
14244
|
+
/* eslint-enable @typescript-eslint/no-namespace, @typescript-eslint/naming-convention */
|
|
14245
|
+
|
|
13955
14246
|
/**
|
|
13956
14247
|
* Baseline mapping for dual-analog controllers that follow the standard
|
|
13957
14248
|
* W3C Gamepad API layout (axes 0–3 for both sticks, axes 4–7 auxiliary).
|
|
13958
14249
|
*
|
|
13959
|
-
* Each signed axis is
|
|
13960
|
-
*
|
|
13961
|
-
*
|
|
14250
|
+
* Each signed stick axis is exposed three ways for ergonomic binding:
|
|
14251
|
+
* - Two direction-split, non-negative channels (e.g. `LeftStickLeft` /
|
|
14252
|
+
* `LeftStickRight`) for "buttons-style" subscriptions.
|
|
14253
|
+
* - One signed aggregate channel (e.g. `LeftStickX`) for direct -1..1
|
|
14254
|
+
* consumption — useful for movement or aiming.
|
|
14255
|
+
*
|
|
13962
14256
|
* Device-specific subclasses (Xbox, PlayStation, Switch Pro, etc.) inherit
|
|
13963
14257
|
* this layout and override only {@link GamepadMapping.family}.
|
|
13964
14258
|
*/
|
|
13965
14259
|
class GenericDualAnalogGamepadMapping extends GamepadMapping {
|
|
13966
14260
|
family = GamepadMappingFamily.GenericDualAnalog;
|
|
13967
14261
|
constructor() {
|
|
13968
|
-
super(
|
|
14262
|
+
super([
|
|
14263
|
+
new GamepadButton(0, GamepadButton.South),
|
|
14264
|
+
new GamepadButton(1, GamepadButton.East),
|
|
14265
|
+
new GamepadButton(2, GamepadButton.West),
|
|
14266
|
+
new GamepadButton(3, GamepadButton.North),
|
|
14267
|
+
new GamepadButton(4, GamepadButton.LeftShoulder),
|
|
14268
|
+
new GamepadButton(5, GamepadButton.RightShoulder),
|
|
14269
|
+
new GamepadButton(6, GamepadButton.LeftTrigger),
|
|
14270
|
+
new GamepadButton(7, GamepadButton.RightTrigger),
|
|
14271
|
+
new GamepadButton(8, GamepadButton.Select),
|
|
14272
|
+
new GamepadButton(9, GamepadButton.Start),
|
|
14273
|
+
new GamepadButton(10, GamepadButton.LeftStick),
|
|
14274
|
+
new GamepadButton(11, GamepadButton.RightStick),
|
|
14275
|
+
new GamepadButton(12, GamepadButton.DPadUp),
|
|
14276
|
+
new GamepadButton(13, GamepadButton.DPadDown),
|
|
14277
|
+
new GamepadButton(14, GamepadButton.DPadLeft),
|
|
14278
|
+
new GamepadButton(15, GamepadButton.DPadRight),
|
|
14279
|
+
new GamepadButton(16, GamepadButton.Guide),
|
|
14280
|
+
new GamepadButton(17, GamepadButton.Share),
|
|
14281
|
+
new GamepadButton(18, GamepadButton.Capture),
|
|
14282
|
+
new GamepadButton(19, GamepadButton.Touchpad),
|
|
14283
|
+
new GamepadButton(20, GamepadButton.Paddle1),
|
|
14284
|
+
], [
|
|
14285
|
+
// Direction-split (0..1).
|
|
14286
|
+
new GamepadAxis(0, GamepadAxis.LeftStickLeft, { invert: true }),
|
|
14287
|
+
new GamepadAxis(0, GamepadAxis.LeftStickRight),
|
|
14288
|
+
new GamepadAxis(1, GamepadAxis.LeftStickUp, { invert: true }),
|
|
14289
|
+
new GamepadAxis(1, GamepadAxis.LeftStickDown),
|
|
14290
|
+
new GamepadAxis(2, GamepadAxis.RightStickLeft, { invert: true }),
|
|
14291
|
+
new GamepadAxis(2, GamepadAxis.RightStickRight),
|
|
14292
|
+
new GamepadAxis(3, GamepadAxis.RightStickUp, { invert: true }),
|
|
14293
|
+
new GamepadAxis(3, GamepadAxis.RightStickDown),
|
|
14294
|
+
// Aggregate signed channels (-1..1).
|
|
14295
|
+
new GamepadAxis(0, GamepadAxis.LeftStickX, { bipolar: true }),
|
|
14296
|
+
new GamepadAxis(1, GamepadAxis.LeftStickY, { bipolar: true }),
|
|
14297
|
+
new GamepadAxis(2, GamepadAxis.RightStickX, { bipolar: true }),
|
|
14298
|
+
new GamepadAxis(3, GamepadAxis.RightStickY, { bipolar: true }),
|
|
14299
|
+
// Auxiliary axes (4 bipolar physical axes split into 8 half-channels).
|
|
14300
|
+
new GamepadAxis(4, GamepadAxis.AuxiliaryAxis0Negative, { invert: true }),
|
|
14301
|
+
new GamepadAxis(4, GamepadAxis.AuxiliaryAxis0Positive),
|
|
14302
|
+
new GamepadAxis(5, GamepadAxis.AuxiliaryAxis1Negative, { invert: true }),
|
|
14303
|
+
new GamepadAxis(5, GamepadAxis.AuxiliaryAxis1Positive),
|
|
14304
|
+
new GamepadAxis(6, GamepadAxis.AuxiliaryAxis2Negative, { invert: true }),
|
|
14305
|
+
new GamepadAxis(6, GamepadAxis.AuxiliaryAxis2Positive),
|
|
14306
|
+
new GamepadAxis(7, GamepadAxis.AuxiliaryAxis3Negative, { invert: true }),
|
|
14307
|
+
new GamepadAxis(7, GamepadAxis.AuxiliaryAxis3Positive),
|
|
14308
|
+
]);
|
|
13969
14309
|
}
|
|
13970
14310
|
}
|
|
13971
14311
|
|
|
@@ -13983,29 +14323,83 @@ class GameCubeGamepadMapping extends GenericDualAnalogGamepadMapping {
|
|
|
13983
14323
|
}
|
|
13984
14324
|
|
|
13985
14325
|
/**
|
|
13986
|
-
* Mapping for the Nintendo
|
|
13987
|
-
*
|
|
13988
|
-
*
|
|
13989
|
-
*
|
|
13990
|
-
*
|
|
13991
|
-
*
|
|
13992
|
-
*
|
|
14326
|
+
* Mapping for the Nintendo Joy-Con (L) held horizontally as a solo controller.
|
|
14327
|
+
*
|
|
14328
|
+
* Declares only channels that physically exist on the device — one stick
|
|
14329
|
+
* (mapped to {@link GamepadAxis.LeftStickX} / `LeftStickY` and the
|
|
14330
|
+
* direction-split equivalents), four face buttons, the SL/SR inner shoulders
|
|
14331
|
+
* (routed through the standard shoulder channels), Minus, the Capture
|
|
14332
|
+
* button, and the stick-click.
|
|
14333
|
+
*
|
|
14334
|
+
* Right-stick channels, triggers, Plus/Home, Touchpad, paddles, and
|
|
14335
|
+
* auxiliary axes are intentionally absent. Use
|
|
14336
|
+
* {@link GamepadMapping.hasChannel} to detect availability before binding
|
|
14337
|
+
* inputs that may not exist on every device family.
|
|
13993
14338
|
*/
|
|
13994
|
-
class JoyConLeftGamepadMapping extends
|
|
14339
|
+
class JoyConLeftGamepadMapping extends GamepadMapping {
|
|
13995
14340
|
family = GamepadMappingFamily.JoyConLeft;
|
|
14341
|
+
constructor() {
|
|
14342
|
+
super([
|
|
14343
|
+
new GamepadButton(0, GamepadButton.South),
|
|
14344
|
+
new GamepadButton(1, GamepadButton.East),
|
|
14345
|
+
new GamepadButton(2, GamepadButton.West),
|
|
14346
|
+
new GamepadButton(3, GamepadButton.North),
|
|
14347
|
+
// Inner SL/SR shoulders — routed through the standard shoulder channels.
|
|
14348
|
+
new GamepadButton(4, GamepadButton.LeftShoulder),
|
|
14349
|
+
new GamepadButton(5, GamepadButton.RightShoulder),
|
|
14350
|
+
new GamepadButton(8, GamepadButton.Select), // Minus
|
|
14351
|
+
new GamepadButton(10, GamepadButton.LeftStick), // stick click
|
|
14352
|
+
new GamepadButton(16, GamepadButton.Capture),
|
|
14353
|
+
], [
|
|
14354
|
+
// Single physical stick — surfaced through the LeftStick channels so
|
|
14355
|
+
// gamepad-agnostic code that binds to "the stick" works regardless of
|
|
14356
|
+
// which Joy-Con is held.
|
|
14357
|
+
new GamepadAxis(0, GamepadAxis.LeftStickLeft, { invert: true }),
|
|
14358
|
+
new GamepadAxis(0, GamepadAxis.LeftStickRight),
|
|
14359
|
+
new GamepadAxis(1, GamepadAxis.LeftStickUp, { invert: true }),
|
|
14360
|
+
new GamepadAxis(1, GamepadAxis.LeftStickDown),
|
|
14361
|
+
new GamepadAxis(0, GamepadAxis.LeftStickX, { bipolar: true }),
|
|
14362
|
+
new GamepadAxis(1, GamepadAxis.LeftStickY, { bipolar: true }),
|
|
14363
|
+
]);
|
|
14364
|
+
}
|
|
13996
14365
|
}
|
|
13997
14366
|
|
|
13998
14367
|
/**
|
|
13999
|
-
* Mapping for the Nintendo
|
|
14000
|
-
*
|
|
14001
|
-
*
|
|
14002
|
-
*
|
|
14003
|
-
*
|
|
14004
|
-
*
|
|
14005
|
-
*
|
|
14368
|
+
* Mapping for the Nintendo Joy-Con (R) held horizontally as a solo controller.
|
|
14369
|
+
*
|
|
14370
|
+
* Declares only channels that physically exist on the device — one stick
|
|
14371
|
+
* (mapped to the LeftStick channels to match the W3C standard layout for the
|
|
14372
|
+
* lone reported stick regardless of which Joy-Con reports it), four face
|
|
14373
|
+
* buttons, the SL/SR inner shoulders (routed through the standard shoulder
|
|
14374
|
+
* channels), Plus, the Home button, and the stick-click.
|
|
14375
|
+
*
|
|
14376
|
+
* Right-stick channels, triggers, Minus/Capture, Touchpad, paddles, and
|
|
14377
|
+
* auxiliary axes are intentionally absent. Use
|
|
14378
|
+
* {@link GamepadMapping.hasChannel} to detect availability before binding
|
|
14379
|
+
* inputs that may not exist on every device family.
|
|
14006
14380
|
*/
|
|
14007
|
-
class JoyConRightGamepadMapping extends
|
|
14381
|
+
class JoyConRightGamepadMapping extends GamepadMapping {
|
|
14008
14382
|
family = GamepadMappingFamily.JoyConRight;
|
|
14383
|
+
constructor() {
|
|
14384
|
+
super([
|
|
14385
|
+
new GamepadButton(0, GamepadButton.South),
|
|
14386
|
+
new GamepadButton(1, GamepadButton.East),
|
|
14387
|
+
new GamepadButton(2, GamepadButton.West),
|
|
14388
|
+
new GamepadButton(3, GamepadButton.North),
|
|
14389
|
+
new GamepadButton(4, GamepadButton.LeftShoulder),
|
|
14390
|
+
new GamepadButton(5, GamepadButton.RightShoulder),
|
|
14391
|
+
new GamepadButton(9, GamepadButton.Start), // Plus
|
|
14392
|
+
new GamepadButton(10, GamepadButton.LeftStick), // stick click
|
|
14393
|
+
new GamepadButton(16, GamepadButton.Guide), // Home
|
|
14394
|
+
], [
|
|
14395
|
+
new GamepadAxis(0, GamepadAxis.LeftStickLeft, { invert: true }),
|
|
14396
|
+
new GamepadAxis(0, GamepadAxis.LeftStickRight),
|
|
14397
|
+
new GamepadAxis(1, GamepadAxis.LeftStickUp, { invert: true }),
|
|
14398
|
+
new GamepadAxis(1, GamepadAxis.LeftStickDown),
|
|
14399
|
+
new GamepadAxis(0, GamepadAxis.LeftStickX, { bipolar: true }),
|
|
14400
|
+
new GamepadAxis(1, GamepadAxis.LeftStickY, { bipolar: true }),
|
|
14401
|
+
]);
|
|
14402
|
+
}
|
|
14009
14403
|
}
|
|
14010
14404
|
|
|
14011
14405
|
/**
|
|
@@ -14032,6 +14426,76 @@ class SteamControllerGamepadMapping extends GenericDualAnalogGamepadMapping {
|
|
|
14032
14426
|
family = GamepadMappingFamily.SteamController;
|
|
14033
14427
|
}
|
|
14034
14428
|
|
|
14429
|
+
/**
|
|
14430
|
+
* Mapping for the Valve Steam Deck (and the new Valve Controller via vendor
|
|
14431
|
+
* fallback) when its raw HID gamepad is exposed directly to the browser —
|
|
14432
|
+
* i.e. when Steam Input is *not* intercepting the device. With Steam Input
|
|
14433
|
+
* intercepting, the device appears as `28de:11ff` "Steam Virtual Gamepad"
|
|
14434
|
+
* with a standard W3C layout instead, and is routed to
|
|
14435
|
+
* {@link GenericDualAnalogGamepadMapping}.
|
|
14436
|
+
*
|
|
14437
|
+
* The raw layout is non-standard: face buttons live at indices 3-6 (not the
|
|
14438
|
+
* W3C-standard 0-3), the D-pad lives at indices 16-19, paddles at 20-23, and
|
|
14439
|
+
* triggers report as analog axes 8/9 rather than buttons 6/7. Indices are
|
|
14440
|
+
* derived from the Linux SDL_GameControllerDB entry for `Valve Steam Deck`.
|
|
14441
|
+
*/
|
|
14442
|
+
class SteamDeckGamepadMapping extends GamepadMapping {
|
|
14443
|
+
family = GamepadMappingFamily.SteamDeck;
|
|
14444
|
+
constructor() {
|
|
14445
|
+
super([
|
|
14446
|
+
// Quick Access (Steam Deck "..." button) → mapped to Capture as
|
|
14447
|
+
// the closest semantic match in the canonical channel set.
|
|
14448
|
+
new GamepadButton(2, GamepadButton.Capture),
|
|
14449
|
+
// Face cluster — non-standard offsets.
|
|
14450
|
+
new GamepadButton(3, GamepadButton.South),
|
|
14451
|
+
new GamepadButton(4, GamepadButton.East),
|
|
14452
|
+
new GamepadButton(5, GamepadButton.West),
|
|
14453
|
+
new GamepadButton(6, GamepadButton.North),
|
|
14454
|
+
new GamepadButton(7, GamepadButton.LeftShoulder),
|
|
14455
|
+
new GamepadButton(8, GamepadButton.RightShoulder),
|
|
14456
|
+
// View / Menu / Steam buttons.
|
|
14457
|
+
new GamepadButton(11, GamepadButton.Select),
|
|
14458
|
+
new GamepadButton(12, GamepadButton.Start),
|
|
14459
|
+
new GamepadButton(13, GamepadButton.Guide),
|
|
14460
|
+
// Stick clicks.
|
|
14461
|
+
new GamepadButton(14, GamepadButton.LeftStick),
|
|
14462
|
+
new GamepadButton(15, GamepadButton.RightStick),
|
|
14463
|
+
// D-pad.
|
|
14464
|
+
new GamepadButton(16, GamepadButton.DPadUp),
|
|
14465
|
+
new GamepadButton(17, GamepadButton.DPadDown),
|
|
14466
|
+
new GamepadButton(18, GamepadButton.DPadLeft),
|
|
14467
|
+
new GamepadButton(19, GamepadButton.DPadRight),
|
|
14468
|
+
// Back paddles. SDL labels them paddle1=R4, paddle2=L4,
|
|
14469
|
+
// paddle3=R5, paddle4=L5; we expose them in canonical
|
|
14470
|
+
// L4/R4/L5/R5 order via Paddle1..Paddle4.
|
|
14471
|
+
new GamepadButton(20, GamepadButton.Paddle2),
|
|
14472
|
+
new GamepadButton(21, GamepadButton.Paddle1),
|
|
14473
|
+
new GamepadButton(22, GamepadButton.Paddle4),
|
|
14474
|
+
new GamepadButton(23, GamepadButton.Paddle3),
|
|
14475
|
+
], [
|
|
14476
|
+
// Sticks — direction-split (0..1).
|
|
14477
|
+
new GamepadAxis(0, GamepadAxis.LeftStickLeft, { invert: true }),
|
|
14478
|
+
new GamepadAxis(0, GamepadAxis.LeftStickRight),
|
|
14479
|
+
new GamepadAxis(1, GamepadAxis.LeftStickUp, { invert: true }),
|
|
14480
|
+
new GamepadAxis(1, GamepadAxis.LeftStickDown),
|
|
14481
|
+
new GamepadAxis(2, GamepadAxis.RightStickLeft, { invert: true }),
|
|
14482
|
+
new GamepadAxis(2, GamepadAxis.RightStickRight),
|
|
14483
|
+
new GamepadAxis(3, GamepadAxis.RightStickUp, { invert: true }),
|
|
14484
|
+
new GamepadAxis(3, GamepadAxis.RightStickDown),
|
|
14485
|
+
// Sticks — aggregate signed (-1..1).
|
|
14486
|
+
new GamepadAxis(0, GamepadAxis.LeftStickX, { bipolar: true }),
|
|
14487
|
+
new GamepadAxis(1, GamepadAxis.LeftStickY, { bipolar: true }),
|
|
14488
|
+
new GamepadAxis(2, GamepadAxis.RightStickX, { bipolar: true }),
|
|
14489
|
+
new GamepadAxis(3, GamepadAxis.RightStickY, { bipolar: true }),
|
|
14490
|
+
// Triggers as analog axes (Steam Deck reports them as a8/a9,
|
|
14491
|
+
// not buttons). Browsers expose -1..+1; normalize to 0..1
|
|
14492
|
+
// for the canonical trigger channels.
|
|
14493
|
+
new GamepadAxis(8, GamepadAxis.AuxiliaryAxis0Positive, { normalize: true }),
|
|
14494
|
+
new GamepadAxis(9, GamepadAxis.AuxiliaryAxis1Positive, { normalize: true }),
|
|
14495
|
+
]);
|
|
14496
|
+
}
|
|
14497
|
+
}
|
|
14498
|
+
|
|
14035
14499
|
/**
|
|
14036
14500
|
* Mapping for the Nintendo Switch Pro Controller connected via USB or
|
|
14037
14501
|
* Bluetooth.
|
|
@@ -14222,6 +14686,8 @@ const exactDeviceDefinitions = [
|
|
|
14222
14686
|
createStaticGamepadDefinition('Switch 2 Pro Controller', () => new SwitchProGamepadMapping(), '057e:2069'),
|
|
14223
14687
|
createStaticGamepadDefinition('Switch 2 GameCube Controller', () => new GameCubeGamepadMapping(), '057e:2073'),
|
|
14224
14688
|
createStaticGamepadDefinition('Steam Controller', () => new SteamControllerGamepadMapping(), ['28de:1102', '28de:1142']),
|
|
14689
|
+
createStaticGamepadDefinition('Steam Virtual Gamepad', () => new GenericDualAnalogGamepadMapping(), '28de:11ff'),
|
|
14690
|
+
createStaticGamepadDefinition('Steam Deck', () => new SteamDeckGamepadMapping(), '28de:1205'),
|
|
14225
14691
|
createStaticGamepadDefinition('F310 Gamepad', () => new GenericDualAnalogGamepadMapping(), '046d:c216'),
|
|
14226
14692
|
createStaticGamepadDefinition('F710 Gamepad', () => new GenericDualAnalogGamepadMapping(), ['046d:c219', '046d:c21f']),
|
|
14227
14693
|
createStaticGamepadDefinition('8BitDo P30 Controller', () => new GenericDualAnalogGamepadMapping(), ['2dc8:5107', '2dc8:5108']),
|
|
@@ -14244,6 +14710,7 @@ const exactDeviceDefinitions = [
|
|
|
14244
14710
|
const vendorFallbackDefinitions = [
|
|
14245
14711
|
createStaticGamepadDefinition('Microsoft Controller', () => new XboxGamepadMapping(), '045e'),
|
|
14246
14712
|
createStaticGamepadDefinition('Sony Controller', () => new PlayStationGamepadMapping(), '054c'),
|
|
14713
|
+
createStaticGamepadDefinition('Valve Controller', () => new SteamDeckGamepadMapping(), '28de'),
|
|
14247
14714
|
];
|
|
14248
14715
|
const genericFallbackDefinition = createStaticGamepadDefinition('Generic Gamepad', () => new GenericDualAnalogGamepadMapping());
|
|
14249
14716
|
/**
|
|
@@ -14259,6 +14726,7 @@ const builtInGamepadDefinitions = [
|
|
|
14259
14726
|
genericFallbackDefinition,
|
|
14260
14727
|
];
|
|
14261
14728
|
|
|
14729
|
+
const gamepadSlots = 4;
|
|
14262
14730
|
var InputManagerFlag;
|
|
14263
14731
|
(function (InputManagerFlag) {
|
|
14264
14732
|
InputManagerFlag[InputManagerFlag["None"] = 0] = "None";
|
|
@@ -14275,10 +14743,11 @@ var InputManagerFlag;
|
|
|
14275
14743
|
* rotate / long-press).
|
|
14276
14744
|
*
|
|
14277
14745
|
* All raw inputs are written into a shared `Float32Array` channel buffer.
|
|
14278
|
-
* Bind {@link
|
|
14279
|
-
*
|
|
14280
|
-
*
|
|
14281
|
-
*
|
|
14746
|
+
* Bind input listeners via the {@link onTrigger} / {@link onActive} /
|
|
14747
|
+
* {@link onStart} / {@link onStop} factory methods (or via
|
|
14748
|
+
* {@link Gamepad.onTrigger}-style methods on individual pads), or
|
|
14749
|
+
* subscribe to the signal-style notifications
|
|
14750
|
+
* (`onKeyDown`, `onPointerDown`, `onGamepadConnected`, `onAnyGamepadButtonDown`, …).
|
|
14282
14751
|
*
|
|
14283
14752
|
* Driven each frame by {@link Application.update}; constructed
|
|
14284
14753
|
* automatically — you do not instantiate this class yourself.
|
|
@@ -14286,18 +14755,19 @@ var InputManagerFlag;
|
|
|
14286
14755
|
class InputManager {
|
|
14287
14756
|
canvas;
|
|
14288
14757
|
channels = new Float32Array(ChannelSize.Container);
|
|
14289
|
-
inputs = new Set();
|
|
14290
14758
|
pointers = {};
|
|
14291
|
-
|
|
14292
|
-
|
|
14293
|
-
|
|
14759
|
+
_gamepads;
|
|
14760
|
+
gamepadsByBrowserIndex = new Map();
|
|
14761
|
+
bindings = new Set();
|
|
14762
|
+
bindingDetacher = { detach: (binding) => { this.bindings.delete(binding); } };
|
|
14294
14763
|
wheelOffset = new Vector();
|
|
14295
14764
|
flags = new Flags();
|
|
14296
14765
|
channelsPressed = [];
|
|
14297
14766
|
channelsReleased = [];
|
|
14298
14767
|
gamepadDefinitions;
|
|
14768
|
+
slotStrategy;
|
|
14299
14769
|
// Slot allocation for unified pointer tracking (mouse / touch / pen).
|
|
14300
|
-
pointerSlots = new Map();
|
|
14770
|
+
pointerSlots = new Map();
|
|
14301
14771
|
freeSlots = Array.from({ length: maxPointers }, (_, i) => i);
|
|
14302
14772
|
gestureRecognizer;
|
|
14303
14773
|
canvasFocusedValue;
|
|
@@ -14326,9 +14796,22 @@ class InputManager {
|
|
|
14326
14796
|
onMouseWheel = new Signal();
|
|
14327
14797
|
onKeyDown = new Signal();
|
|
14328
14798
|
onKeyUp = new Signal();
|
|
14799
|
+
/** Fires when a physical pad connects to any slot. */
|
|
14329
14800
|
onGamepadConnected = new Signal();
|
|
14801
|
+
/** Fires when a physical pad disconnects from any slot. */
|
|
14330
14802
|
onGamepadDisconnected = new Signal();
|
|
14331
|
-
|
|
14803
|
+
/**
|
|
14804
|
+
* Fires when a `'compact'`-strategy disconnect shifts a higher-numbered
|
|
14805
|
+
* slot's pad into a lower one. Dispatched once per moved pad with the
|
|
14806
|
+
* destination slot and the slot index it came from.
|
|
14807
|
+
*/
|
|
14808
|
+
onAnyGamepadReassigned = new Signal();
|
|
14809
|
+
/** Fires whenever any pad reports a button press transition. */
|
|
14810
|
+
onAnyGamepadButtonDown = new Signal();
|
|
14811
|
+
/** Fires whenever any pad reports a button release transition. */
|
|
14812
|
+
onAnyGamepadButtonUp = new Signal();
|
|
14813
|
+
/** Fires whenever any pad reports an axis value change. */
|
|
14814
|
+
onAnyGamepadAxisChange = new Signal();
|
|
14332
14815
|
/** Fires on every two-touch-pointer move where the distance between them changed. `scale` > 1 = spreading, < 1 = pinching. */
|
|
14333
14816
|
onPinch = new Signal();
|
|
14334
14817
|
/** Fires on every two-touch-pointer move where the angle between them changed. `angleDelta` is in radians. */
|
|
@@ -14336,16 +14819,25 @@ class InputManager {
|
|
|
14336
14819
|
/** Fires when a pointer has been held without significant movement for ≥ 500 ms. */
|
|
14337
14820
|
onLongPress = new Signal();
|
|
14338
14821
|
constructor(app) {
|
|
14339
|
-
const { gamepadDefinitions = [], pointerDistanceThreshold } = app.options;
|
|
14822
|
+
const { gamepadDefinitions = [], pointerDistanceThreshold, gamepadSlotStrategy = 'sticky', } = app.options;
|
|
14340
14823
|
this.canvas = app.canvas;
|
|
14341
14824
|
this.canvasFocusedValue = document.activeElement === this.canvas;
|
|
14342
14825
|
this.pointerDistanceThreshold = pointerDistanceThreshold;
|
|
14343
14826
|
this.gamepadDefinitions = [...gamepadDefinitions, ...builtInGamepadDefinitions];
|
|
14827
|
+
this.slotStrategy = gamepadSlotStrategy;
|
|
14344
14828
|
// Disable the browser's default pan/zoom/double-tap-zoom on touch devices so
|
|
14345
14829
|
// pointer events reach the canvas without being swallowed by the browser's
|
|
14346
14830
|
// native touch gestures.
|
|
14347
14831
|
this.canvas.style.touchAction = 'none';
|
|
14348
14832
|
this.gestureRecognizer = new GestureRecognizer(pointerDistanceThreshold, this.onPinch, this.onRotate, this.onLongPress);
|
|
14833
|
+
const slot0 = new Gamepad(0, this.channels);
|
|
14834
|
+
const slot1 = new Gamepad(1, this.channels);
|
|
14835
|
+
const slot2 = new Gamepad(2, this.channels);
|
|
14836
|
+
const slot3 = new Gamepad(3, this.channels);
|
|
14837
|
+
this._gamepads = [slot0, slot1, slot2, slot3];
|
|
14838
|
+
for (const pad of this._gamepads) {
|
|
14839
|
+
this.wireGamepadEvents(pad);
|
|
14840
|
+
}
|
|
14349
14841
|
this.addEventListeners();
|
|
14350
14842
|
}
|
|
14351
14843
|
/**
|
|
@@ -14359,7 +14851,6 @@ class InputManager {
|
|
|
14359
14851
|
return { x: pointer.x, y: pointer.y };
|
|
14360
14852
|
}
|
|
14361
14853
|
}
|
|
14362
|
-
// Fall back to first non-cancelled pointer.
|
|
14363
14854
|
for (const pointer of Object.values(this.pointers)) {
|
|
14364
14855
|
if (pointer.currentState !== PointerState.Cancelled) {
|
|
14365
14856
|
return { x: pointer.x, y: pointer.y };
|
|
@@ -14374,58 +14865,103 @@ class InputManager {
|
|
|
14374
14865
|
get canvasFocused() {
|
|
14375
14866
|
return this.canvasFocusedValue;
|
|
14376
14867
|
}
|
|
14868
|
+
/**
|
|
14869
|
+
* Always-4 array of {@link Gamepad} slot mailboxes. Each entry exists for
|
|
14870
|
+
* the application's full lifetime; check `pad.connected` for hardware
|
|
14871
|
+
* presence. Listeners attached to a slot survive disconnect/reconnect.
|
|
14872
|
+
*/
|
|
14377
14873
|
get gamepads() {
|
|
14378
|
-
return this.
|
|
14874
|
+
return this._gamepads;
|
|
14379
14875
|
}
|
|
14380
|
-
|
|
14381
|
-
|
|
14876
|
+
/** The slot strategy active for this `InputManager`. */
|
|
14877
|
+
get gamepadSlotStrategy() {
|
|
14878
|
+
return this.slotStrategy;
|
|
14382
14879
|
}
|
|
14383
14880
|
/**
|
|
14384
|
-
*
|
|
14385
|
-
*
|
|
14386
|
-
* inputs; chainable.
|
|
14881
|
+
* Direct accessor for a single gamepad slot. Equivalent to
|
|
14882
|
+
* `app.input.gamepads[slot]` but reads more clearly at call sites.
|
|
14387
14883
|
*/
|
|
14388
|
-
|
|
14389
|
-
|
|
14390
|
-
|
|
14391
|
-
|
|
14884
|
+
getGamepad(slot) {
|
|
14885
|
+
return this._gamepads[slot];
|
|
14886
|
+
}
|
|
14887
|
+
/** Subset of {@link gamepads} containing only currently connected pads, in slot order. */
|
|
14888
|
+
get connectedGamepads() {
|
|
14889
|
+
const result = [];
|
|
14890
|
+
for (const pad of this._gamepads) {
|
|
14891
|
+
if (pad.connected) {
|
|
14892
|
+
result.push(pad);
|
|
14893
|
+
}
|
|
14392
14894
|
}
|
|
14393
|
-
|
|
14394
|
-
|
|
14895
|
+
return result;
|
|
14896
|
+
}
|
|
14897
|
+
/** Number of slots currently occupied by a physical gamepad. */
|
|
14898
|
+
get connectedGamepadCount() {
|
|
14899
|
+
let count = 0;
|
|
14900
|
+
for (const pad of this._gamepads) {
|
|
14901
|
+
if (pad.connected) {
|
|
14902
|
+
count++;
|
|
14903
|
+
}
|
|
14904
|
+
}
|
|
14905
|
+
return count;
|
|
14906
|
+
}
|
|
14907
|
+
/** First connected gamepad in slot order, or `null` when no pads are attached. */
|
|
14908
|
+
get firstConnectedGamepad() {
|
|
14909
|
+
for (const pad of this._gamepads) {
|
|
14910
|
+
if (pad.connected) {
|
|
14911
|
+
return pad;
|
|
14912
|
+
}
|
|
14913
|
+
}
|
|
14914
|
+
return null;
|
|
14915
|
+
}
|
|
14916
|
+
/** `true` when at least one slot is occupied by a physical gamepad. */
|
|
14917
|
+
get hasGamepad() {
|
|
14918
|
+
for (const pad of this._gamepads) {
|
|
14919
|
+
if (pad.connected) {
|
|
14920
|
+
return true;
|
|
14921
|
+
}
|
|
14922
|
+
}
|
|
14923
|
+
return false;
|
|
14924
|
+
}
|
|
14925
|
+
/**
|
|
14926
|
+
* Register a callback fired once when any of `channels` becomes active.
|
|
14927
|
+
* Manual lifecycle — call `.unbind()` on the returned binding to detach.
|
|
14928
|
+
*/
|
|
14929
|
+
onStart(channel, callback, options) {
|
|
14930
|
+
const binding = this.createBinding(channel, options);
|
|
14931
|
+
binding.onStart.add(callback);
|
|
14932
|
+
return binding;
|
|
14933
|
+
}
|
|
14934
|
+
/** Register a callback fired every frame while any of `channels` is active. */
|
|
14935
|
+
onActive(channel, callback, options) {
|
|
14936
|
+
const binding = this.createBinding(channel, options);
|
|
14937
|
+
binding.onActive.add(callback);
|
|
14938
|
+
return binding;
|
|
14395
14939
|
}
|
|
14396
|
-
/**
|
|
14397
|
-
|
|
14398
|
-
|
|
14399
|
-
|
|
14400
|
-
|
|
14401
|
-
}
|
|
14402
|
-
this.inputs.delete(inputs);
|
|
14403
|
-
return this;
|
|
14940
|
+
/** Register a callback fired once when all of `channels` become inactive. */
|
|
14941
|
+
onStop(channel, callback, options) {
|
|
14942
|
+
const binding = this.createBinding(channel, options);
|
|
14943
|
+
binding.onStop.add(callback);
|
|
14944
|
+
return binding;
|
|
14404
14945
|
}
|
|
14405
14946
|
/**
|
|
14406
|
-
*
|
|
14407
|
-
*
|
|
14408
|
-
* `false` leaves them intact for re-registration.
|
|
14947
|
+
* Register a callback fired when the input is released within
|
|
14948
|
+
* {@link InputBindingOptions.threshold} ms of activation (a "tap").
|
|
14409
14949
|
*/
|
|
14410
|
-
|
|
14411
|
-
|
|
14412
|
-
|
|
14413
|
-
|
|
14414
|
-
}
|
|
14415
|
-
}
|
|
14416
|
-
this.inputs.clear();
|
|
14417
|
-
return this;
|
|
14950
|
+
onTrigger(channel, callback, options) {
|
|
14951
|
+
const binding = this.createBinding(channel, options);
|
|
14952
|
+
binding.onTrigger.add(callback);
|
|
14953
|
+
return binding;
|
|
14418
14954
|
}
|
|
14419
14955
|
/**
|
|
14420
14956
|
* Per-frame entry point invoked by {@link Application.update}. Polls
|
|
14421
14957
|
* the gamepad API, drains queued keyboard/pointer/wheel deltas into
|
|
14422
14958
|
* the channel buffer, fires the corresponding Signals, then evaluates
|
|
14423
|
-
* each registered
|
|
14959
|
+
* each registered binding.
|
|
14424
14960
|
*/
|
|
14425
14961
|
update() {
|
|
14426
14962
|
this.updateGamepads();
|
|
14427
|
-
for (const
|
|
14428
|
-
|
|
14963
|
+
for (const binding of this.bindings) {
|
|
14964
|
+
binding.update(this.channels);
|
|
14429
14965
|
}
|
|
14430
14966
|
if (this.flags.value !== InputManagerFlag.None) {
|
|
14431
14967
|
this.updateEvents();
|
|
@@ -14438,12 +14974,14 @@ class InputManager {
|
|
|
14438
14974
|
for (const pointer of Object.values(this.pointers)) {
|
|
14439
14975
|
pointer.destroy();
|
|
14440
14976
|
}
|
|
14441
|
-
for (const
|
|
14442
|
-
|
|
14977
|
+
for (const pad of this._gamepads) {
|
|
14978
|
+
pad.destroy();
|
|
14443
14979
|
}
|
|
14444
|
-
this.
|
|
14445
|
-
|
|
14446
|
-
|
|
14980
|
+
for (const binding of Array.from(this.bindings)) {
|
|
14981
|
+
binding.unbind();
|
|
14982
|
+
}
|
|
14983
|
+
this.bindings.clear();
|
|
14984
|
+
this.gamepadsByBrowserIndex.clear();
|
|
14447
14985
|
this.channelsPressed.length = 0;
|
|
14448
14986
|
this.channelsReleased.length = 0;
|
|
14449
14987
|
this.pointerSlots.clear();
|
|
@@ -14463,18 +15001,40 @@ class InputManager {
|
|
|
14463
15001
|
this.onKeyUp.destroy();
|
|
14464
15002
|
this.onGamepadConnected.destroy();
|
|
14465
15003
|
this.onGamepadDisconnected.destroy();
|
|
14466
|
-
this.
|
|
15004
|
+
this.onAnyGamepadReassigned.destroy();
|
|
15005
|
+
this.onAnyGamepadButtonDown.destroy();
|
|
15006
|
+
this.onAnyGamepadButtonUp.destroy();
|
|
15007
|
+
this.onAnyGamepadAxisChange.destroy();
|
|
14467
15008
|
this.onPinch.destroy();
|
|
14468
15009
|
this.onRotate.destroy();
|
|
14469
15010
|
this.onLongPress.destroy();
|
|
14470
15011
|
this.onCanvasFocusChange.destroy();
|
|
14471
15012
|
}
|
|
15013
|
+
createBinding(channel, options = {}) {
|
|
15014
|
+
const list = Array.isArray(channel) ? channel : [channel];
|
|
15015
|
+
const slot = options.gamepadSlot ?? 0;
|
|
15016
|
+
const resolved = list.map((c) => this.resolveExternalChannel(c, slot));
|
|
15017
|
+
const binding = new InputBinding(resolved, options, this.bindingDetacher);
|
|
15018
|
+
this.bindings.add(binding);
|
|
15019
|
+
return binding;
|
|
15020
|
+
}
|
|
15021
|
+
resolveExternalChannel(channel, slot) {
|
|
15022
|
+
if (channel >= ChannelOffset.Gamepads && channel < ChannelOffset.Gamepads + ChannelSize.Category) {
|
|
15023
|
+
return ChannelOffset.Gamepads + (slot * ChannelSize.Gamepad) + (channel ^ ChannelOffset.Gamepads);
|
|
15024
|
+
}
|
|
15025
|
+
return channel;
|
|
15026
|
+
}
|
|
15027
|
+
wireGamepadEvents(pad) {
|
|
15028
|
+
pad.onButtonDown.add((button, value) => { this.onAnyGamepadButtonDown.dispatch(pad, button, value); });
|
|
15029
|
+
pad.onButtonUp.add((button, value) => { this.onAnyGamepadButtonUp.dispatch(pad, button, value); });
|
|
15030
|
+
pad.onAxisChange.add((axis, value) => { this.onAnyGamepadAxisChange.dispatch(pad, axis, value); });
|
|
15031
|
+
}
|
|
14472
15032
|
_assignSlot(pointerId) {
|
|
14473
15033
|
if (this.pointerSlots.has(pointerId)) {
|
|
14474
15034
|
return this.pointerSlots.get(pointerId);
|
|
14475
15035
|
}
|
|
14476
15036
|
if (this.freeSlots.length === 0) {
|
|
14477
|
-
return null;
|
|
15037
|
+
return null;
|
|
14478
15038
|
}
|
|
14479
15039
|
const slot = this.freeSlots.shift();
|
|
14480
15040
|
this.pointerSlots.set(pointerId, slot);
|
|
@@ -14484,14 +15044,10 @@ class InputManager {
|
|
|
14484
15044
|
const slot = this.pointerSlots.get(pointerId);
|
|
14485
15045
|
if (slot !== undefined) {
|
|
14486
15046
|
this.pointerSlots.delete(pointerId);
|
|
14487
|
-
// Push to the front so slot 0 is recovered first, keeping allocation predictable.
|
|
14488
15047
|
this.freeSlots.unshift(slot);
|
|
14489
15048
|
}
|
|
14490
15049
|
}
|
|
14491
15050
|
handleKeyDown(event) {
|
|
14492
|
-
// Game-engine convention: keys only register while the canvas
|
|
14493
|
-
// owns focus. Otherwise typing into adjacent <input> fields would
|
|
14494
|
-
// also drive game state, which is never what users want.
|
|
14495
15051
|
if (!this.canvasFocusedValue) {
|
|
14496
15052
|
return;
|
|
14497
15053
|
}
|
|
@@ -14499,9 +15055,6 @@ class InputManager {
|
|
|
14499
15055
|
this.channels[channel] = 1;
|
|
14500
15056
|
this.channelsPressed.push(channel);
|
|
14501
15057
|
this.flags.push(InputManagerFlag.KeyDown);
|
|
14502
|
-
// Consume the event: stop default browser actions (page scroll on
|
|
14503
|
-
// arrow/space, find-as-you-type on /, etc.) and stop propagation
|
|
14504
|
-
// so other listeners on the page don't double-handle.
|
|
14505
15058
|
stopEvent(event);
|
|
14506
15059
|
}
|
|
14507
15060
|
handleKeyUp(event) {
|
|
@@ -14517,7 +15070,7 @@ class InputManager {
|
|
|
14517
15070
|
handlePointerOver(event) {
|
|
14518
15071
|
const slot = this._assignSlot(event.pointerId);
|
|
14519
15072
|
if (slot === null) {
|
|
14520
|
-
return;
|
|
15073
|
+
return;
|
|
14521
15074
|
}
|
|
14522
15075
|
this.pointers[event.pointerId] = new Pointer(event, this.canvas, this.channels, slot);
|
|
14523
15076
|
this.flags.push(InputManagerFlag.PointerUpdate);
|
|
@@ -14542,10 +15095,6 @@ class InputManager {
|
|
|
14542
15095
|
pointer.handlePress(event);
|
|
14543
15096
|
this.gestureRecognizer.onPointerDown(pointer);
|
|
14544
15097
|
this.flags.push(InputManagerFlag.PointerUpdate);
|
|
14545
|
-
// preventDefault stops native drag / text-selection;
|
|
14546
|
-
// stopImmediatePropagation prevents bubbling to host-page click
|
|
14547
|
-
// handlers so an embedded canvas doesn't accidentally trigger UI
|
|
14548
|
-
// outside its bounds.
|
|
14549
15098
|
stopEvent(event);
|
|
14550
15099
|
}
|
|
14551
15100
|
handlePointerMove(event) {
|
|
@@ -14605,13 +15154,6 @@ class InputManager {
|
|
|
14605
15154
|
this.onCanvasFocusChange.dispatch(false);
|
|
14606
15155
|
}
|
|
14607
15156
|
}
|
|
14608
|
-
/**
|
|
14609
|
-
* Force every currently-held keyboard channel back to zero and emit
|
|
14610
|
-
* onKeyUp for each. Called on canvas/window blur so keys held when
|
|
14611
|
-
* focus leaves don't stay stuck "down" forever — without this, a user
|
|
14612
|
-
* who alt-tabs while pressing W would have W register as held until
|
|
14613
|
-
* they manually release while focus is back.
|
|
14614
|
-
*/
|
|
14615
15157
|
releaseAllKeyboardChannels() {
|
|
14616
15158
|
for (let offset = 0; offset < ChannelSize.Category; offset++) {
|
|
14617
15159
|
const channel = ChannelOffset.Keyboard + offset;
|
|
@@ -14660,49 +15202,98 @@ class InputManager {
|
|
|
14660
15202
|
this.canvas.removeEventListener('selectstart', stopEvent, activeListenerOption);
|
|
14661
15203
|
}
|
|
14662
15204
|
updateGamepads() {
|
|
14663
|
-
const
|
|
14664
|
-
|
|
14665
|
-
for (const
|
|
14666
|
-
if (!
|
|
15205
|
+
const browserGamepads = window.navigator.getGamepads();
|
|
15206
|
+
const seenBrowserIndices = new Set();
|
|
15207
|
+
for (const browserGamepad of browserGamepads) {
|
|
15208
|
+
if (!browserGamepad) {
|
|
14667
15209
|
continue;
|
|
14668
15210
|
}
|
|
14669
|
-
const
|
|
14670
|
-
if (
|
|
15211
|
+
const browserIndex = browserGamepad.index;
|
|
15212
|
+
if (browserIndex < 0) {
|
|
14671
15213
|
continue;
|
|
14672
15214
|
}
|
|
14673
|
-
|
|
14674
|
-
|
|
14675
|
-
if (
|
|
14676
|
-
const
|
|
14677
|
-
|
|
14678
|
-
|
|
14679
|
-
|
|
14680
|
-
this.
|
|
14681
|
-
|
|
14682
|
-
else {
|
|
14683
|
-
gamepad.connect(activeGamepad);
|
|
15215
|
+
seenBrowserIndices.add(browserIndex);
|
|
15216
|
+
const existing = this.gamepadsByBrowserIndex.get(browserIndex);
|
|
15217
|
+
if (existing === undefined) {
|
|
15218
|
+
const pad = this.assignSlotForNewPad(browserGamepad);
|
|
15219
|
+
if (pad === null) {
|
|
15220
|
+
continue;
|
|
15221
|
+
}
|
|
15222
|
+
this.gamepadsByBrowserIndex.set(browserIndex, pad);
|
|
15223
|
+
this.onGamepadConnected.dispatch(pad);
|
|
14684
15224
|
}
|
|
14685
|
-
gamepad.update();
|
|
14686
|
-
this.onGamepadUpdated.dispatch(gamepad, this.gamepadsValue);
|
|
14687
15225
|
}
|
|
14688
|
-
for (
|
|
14689
|
-
|
|
14690
|
-
|
|
14691
|
-
|
|
14692
|
-
this.gamepadsValue.splice(index, 1);
|
|
14693
|
-
this.gamepadsByIndex.delete(gamepad.index);
|
|
14694
|
-
this.onGamepadDisconnected.dispatch(gamepad, this.gamepadsValue);
|
|
14695
|
-
gamepad.destroy();
|
|
15226
|
+
for (const [browserIndex, pad] of Array.from(this.gamepadsByBrowserIndex.entries())) {
|
|
15227
|
+
if (!seenBrowserIndices.has(browserIndex)) {
|
|
15228
|
+
this.gamepadsByBrowserIndex.delete(browserIndex);
|
|
15229
|
+
this.handleGamepadDisconnect(pad);
|
|
14696
15230
|
}
|
|
14697
15231
|
}
|
|
15232
|
+
for (const pad of this._gamepads) {
|
|
15233
|
+
pad.update();
|
|
15234
|
+
}
|
|
14698
15235
|
return this;
|
|
14699
15236
|
}
|
|
14700
|
-
|
|
14701
|
-
|
|
14702
|
-
|
|
14703
|
-
|
|
15237
|
+
assignSlotForNewPad(browserGamepad) {
|
|
15238
|
+
const definition = resolveGamepadDefinition(browserGamepad, this.gamepadDefinitions);
|
|
15239
|
+
for (const pad of this._gamepads) {
|
|
15240
|
+
if (!pad.connected) {
|
|
15241
|
+
pad._bind(browserGamepad, definition);
|
|
15242
|
+
return pad;
|
|
15243
|
+
}
|
|
15244
|
+
}
|
|
15245
|
+
return null;
|
|
15246
|
+
}
|
|
15247
|
+
handleGamepadDisconnect(pad) {
|
|
15248
|
+
if (this.slotStrategy !== 'compact') {
|
|
15249
|
+
// Sticky: pad's slot becomes empty in place; fire onDisconnect
|
|
15250
|
+
// on that slot directly.
|
|
15251
|
+
pad._unbind();
|
|
15252
|
+
this.onGamepadDisconnected.dispatch(pad);
|
|
15253
|
+
return;
|
|
15254
|
+
}
|
|
15255
|
+
// Compact: in semantic terms the user lost a player, and the trailing
|
|
15256
|
+
// (highest-numbered) occupied slot is the one that becomes empty.
|
|
15257
|
+
// 1. Snapshot the highest occupied slot before any state change.
|
|
15258
|
+
// 2. Silently vacate the disconnecting pad (no onDisconnect yet).
|
|
15259
|
+
// 3. Shift higher-numbered occupied slots down to fill any gaps,
|
|
15260
|
+
// firing onPadReassigned for each slot that received a new pad.
|
|
15261
|
+
// 4. Fire onDisconnect on the slot that ended up empty (the one
|
|
15262
|
+
// snapshotted in step 1).
|
|
15263
|
+
let lastOccupiedSlot = -1;
|
|
15264
|
+
for (let i = gamepadSlots - 1; i >= 0; i--) {
|
|
15265
|
+
if (this._gamepads[i].connected) {
|
|
15266
|
+
lastOccupiedSlot = i;
|
|
15267
|
+
break;
|
|
15268
|
+
}
|
|
15269
|
+
}
|
|
15270
|
+
pad._silentUnbind();
|
|
15271
|
+
for (let target = 0; target < gamepadSlots; target++) {
|
|
15272
|
+
if (this._gamepads[target].connected) {
|
|
15273
|
+
continue;
|
|
15274
|
+
}
|
|
15275
|
+
for (let source = target + 1; source < gamepadSlots; source++) {
|
|
15276
|
+
const sourcePad = this._gamepads[source];
|
|
15277
|
+
if (!sourcePad.connected) {
|
|
15278
|
+
continue;
|
|
15279
|
+
}
|
|
15280
|
+
const browserIndex = sourcePad.browserGamepad?.index;
|
|
15281
|
+
const targetPad = this._gamepads[target];
|
|
15282
|
+
const sourceSlot = sourcePad.slot;
|
|
15283
|
+
targetPad._rebindFrom(sourcePad);
|
|
15284
|
+
if (browserIndex !== undefined) {
|
|
15285
|
+
this.gamepadsByBrowserIndex.set(browserIndex, targetPad);
|
|
15286
|
+
}
|
|
15287
|
+
targetPad.onPadReassigned.dispatch(sourceSlot);
|
|
15288
|
+
this.onAnyGamepadReassigned.dispatch(targetPad, sourceSlot);
|
|
15289
|
+
break;
|
|
15290
|
+
}
|
|
15291
|
+
}
|
|
15292
|
+
if (lastOccupiedSlot >= 0) {
|
|
15293
|
+
const emptiedSlot = this._gamepads[lastOccupiedSlot];
|
|
15294
|
+
emptiedSlot._dispatchDisconnect();
|
|
15295
|
+
this.onGamepadDisconnected.dispatch(emptiedSlot);
|
|
14704
15296
|
}
|
|
14705
|
-
this.gamepadsValue.splice(insertIndex, 0, gamepad);
|
|
14706
15297
|
}
|
|
14707
15298
|
updateEvents() {
|
|
14708
15299
|
if (this.flags.pop(InputManagerFlag.KeyDown)) {
|
|
@@ -15324,12 +15915,12 @@ class InteractionManager {
|
|
|
15324
15915
|
this._onPointerTapHandler = this._handlePointerTap.bind(this);
|
|
15325
15916
|
this._onPointerCancelHandler = this._handlePointerCancel.bind(this);
|
|
15326
15917
|
this._onPointerLeaveHandler = this._handlePointerLeave.bind(this);
|
|
15327
|
-
app.
|
|
15328
|
-
app.
|
|
15329
|
-
app.
|
|
15330
|
-
app.
|
|
15331
|
-
app.
|
|
15332
|
-
app.
|
|
15918
|
+
app.input.onPointerDown.add(this._onPointerDownHandler);
|
|
15919
|
+
app.input.onPointerMove.add(this._onPointerMoveHandler);
|
|
15920
|
+
app.input.onPointerUp.add(this._onPointerUpHandler);
|
|
15921
|
+
app.input.onPointerTap.add(this._onPointerTapHandler);
|
|
15922
|
+
app.input.onPointerCancel.add(this._onPointerCancelHandler);
|
|
15923
|
+
app.input.onPointerLeave.add(this._onPointerLeaveHandler);
|
|
15333
15924
|
}
|
|
15334
15925
|
/**
|
|
15335
15926
|
* Returns the RenderNode currently hovered by the given pointer, or null.
|
|
@@ -15373,12 +15964,12 @@ class InteractionManager {
|
|
|
15373
15964
|
return this._quadtree;
|
|
15374
15965
|
}
|
|
15375
15966
|
destroy() {
|
|
15376
|
-
this._app.
|
|
15377
|
-
this._app.
|
|
15378
|
-
this._app.
|
|
15379
|
-
this._app.
|
|
15380
|
-
this._app.
|
|
15381
|
-
this._app.
|
|
15967
|
+
this._app.input.onPointerDown.remove(this._onPointerDownHandler);
|
|
15968
|
+
this._app.input.onPointerMove.remove(this._onPointerMoveHandler);
|
|
15969
|
+
this._app.input.onPointerUp.remove(this._onPointerUpHandler);
|
|
15970
|
+
this._app.input.onPointerTap.remove(this._onPointerTapHandler);
|
|
15971
|
+
this._app.input.onPointerCancel.remove(this._onPointerCancelHandler);
|
|
15972
|
+
this._app.input.onPointerLeave.remove(this._onPointerLeaveHandler);
|
|
15382
15973
|
this._lastHit.clear();
|
|
15383
15974
|
this._pending.clear();
|
|
15384
15975
|
this._capturedPointers.clear();
|
|
@@ -15396,7 +15987,7 @@ class InteractionManager {
|
|
|
15396
15987
|
/**
|
|
15397
15988
|
* Process all pending pointer events accumulated since the last frame.
|
|
15398
15989
|
* Must be called once per frame from {@link Application.update}, after
|
|
15399
|
-
* `
|
|
15990
|
+
* `input.update()` has run (so signals are already dispatched and
|
|
15400
15991
|
* queued here) and before game-state updates so that user listeners on
|
|
15401
15992
|
* `onPointerDown` etc. fire before per-frame logic mutates state.
|
|
15402
15993
|
*
|
|
@@ -18469,14 +19060,86 @@ const parseTimestamp = (value) => {
|
|
|
18469
19060
|
}
|
|
18470
19061
|
return seconds;
|
|
18471
19062
|
};
|
|
19063
|
+
const validAlignValues = new Set(['start', 'center', 'end', 'left', 'right']);
|
|
19064
|
+
const validLineAlignValues = new Set(['start', 'center', 'end']);
|
|
19065
|
+
const validPositionAlignValues = new Set(['auto', 'line-left', 'center', 'line-right']);
|
|
19066
|
+
/**
|
|
19067
|
+
* Parses the WebVTT cue-settings tail (`align:center line:80% position:50%`)
|
|
19068
|
+
* and applies each recognized setting to the supplied {@link VTTCue}.
|
|
19069
|
+
*
|
|
19070
|
+
* Unknown keys, malformed values, and unknown enum members are silently
|
|
19071
|
+
* skipped so that one bad token does not invalidate an otherwise valid cue.
|
|
19072
|
+
* Percent signs on numeric values are tolerated and stripped.
|
|
19073
|
+
*
|
|
19074
|
+
* @internal
|
|
19075
|
+
*/
|
|
19076
|
+
const applyCueSettings = (cue, settings) => {
|
|
19077
|
+
if (!settings) {
|
|
19078
|
+
return;
|
|
19079
|
+
}
|
|
19080
|
+
for (const token of settings.split(/\s+/)) {
|
|
19081
|
+
const colonIndex = token.indexOf(':');
|
|
19082
|
+
if (colonIndex === -1) {
|
|
19083
|
+
continue;
|
|
19084
|
+
}
|
|
19085
|
+
const name = token.slice(0, colonIndex);
|
|
19086
|
+
const value = token.slice(colonIndex + 1);
|
|
19087
|
+
switch (name) {
|
|
19088
|
+
case 'vertical':
|
|
19089
|
+
if (value === 'rl' || value === 'lr' || value === '') {
|
|
19090
|
+
cue.vertical = value;
|
|
19091
|
+
}
|
|
19092
|
+
break;
|
|
19093
|
+
case 'line': {
|
|
19094
|
+
if (value === 'auto') {
|
|
19095
|
+
cue.line = 'auto';
|
|
19096
|
+
}
|
|
19097
|
+
else {
|
|
19098
|
+
const [linePart, alignPart] = value.split(',');
|
|
19099
|
+
const num = parseFloat(linePart);
|
|
19100
|
+
if (!Number.isNaN(num)) {
|
|
19101
|
+
cue.line = num;
|
|
19102
|
+
}
|
|
19103
|
+
if (alignPart !== undefined && validLineAlignValues.has(alignPart)) {
|
|
19104
|
+
cue.lineAlign = alignPart;
|
|
19105
|
+
}
|
|
19106
|
+
}
|
|
19107
|
+
break;
|
|
19108
|
+
}
|
|
19109
|
+
case 'position': {
|
|
19110
|
+
const [posPart, alignPart] = value.split(',');
|
|
19111
|
+
const num = parseFloat(posPart);
|
|
19112
|
+
if (!Number.isNaN(num)) {
|
|
19113
|
+
cue.position = num;
|
|
19114
|
+
}
|
|
19115
|
+
if (alignPart !== undefined && validPositionAlignValues.has(alignPart)) {
|
|
19116
|
+
cue.positionAlign = alignPart;
|
|
19117
|
+
}
|
|
19118
|
+
break;
|
|
19119
|
+
}
|
|
19120
|
+
case 'size': {
|
|
19121
|
+
const num = parseFloat(value);
|
|
19122
|
+
if (!Number.isNaN(num)) {
|
|
19123
|
+
cue.size = num;
|
|
19124
|
+
}
|
|
19125
|
+
break;
|
|
19126
|
+
}
|
|
19127
|
+
case 'align':
|
|
19128
|
+
if (validAlignValues.has(value)) {
|
|
19129
|
+
cue.align = value;
|
|
19130
|
+
}
|
|
19131
|
+
break;
|
|
19132
|
+
}
|
|
19133
|
+
}
|
|
19134
|
+
};
|
|
18472
19135
|
/**
|
|
18473
19136
|
* {@link AssetFactory} implementation that parses WebVTT (`.vtt`) subtitle and
|
|
18474
19137
|
* caption files and produces an array of {@link VTTCue} instances.
|
|
18475
19138
|
*
|
|
18476
19139
|
* The parser handles CRLF and CR line endings, skips the `WEBVTT` header and
|
|
18477
|
-
* any metadata blocks, and
|
|
18478
|
-
*
|
|
18479
|
-
*
|
|
19140
|
+
* any metadata blocks, and applies optional cue settings (`align`, `line`,
|
|
19141
|
+
* `position`, `size`, `vertical`) on the timestamp line directly to the
|
|
19142
|
+
* resulting {@link VTTCue}.
|
|
18480
19143
|
*/
|
|
18481
19144
|
class VttFactory extends AbstractAssetFactory {
|
|
18482
19145
|
storageName = 'vtt';
|
|
@@ -18506,8 +19169,11 @@ class VttFactory extends AbstractAssetFactory {
|
|
|
18506
19169
|
const arrowIndex = line.indexOf('-->');
|
|
18507
19170
|
const startStr = line.slice(0, arrowIndex).trim();
|
|
18508
19171
|
const rest = line.slice(arrowIndex + 3).trim();
|
|
18509
|
-
// rest
|
|
18510
|
-
|
|
19172
|
+
// rest contains the end timestamp followed by optional cue
|
|
19173
|
+
// settings (align, line, position, size, vertical).
|
|
19174
|
+
const restTokens = rest.split(/\s+/);
|
|
19175
|
+
const endStr = restTokens[0];
|
|
19176
|
+
const settingsString = restTokens.slice(1).join(' ');
|
|
18511
19177
|
const start = parseTimestamp(startStr);
|
|
18512
19178
|
const end = parseTimestamp(endStr);
|
|
18513
19179
|
i++;
|
|
@@ -18516,7 +19182,9 @@ class VttFactory extends AbstractAssetFactory {
|
|
|
18516
19182
|
textLines.push(lines[i]);
|
|
18517
19183
|
i++;
|
|
18518
19184
|
}
|
|
18519
|
-
|
|
19185
|
+
const cue = new VTTCue(start, end, textLines.join('\n'));
|
|
19186
|
+
applyCueSettings(cue, settingsString);
|
|
19187
|
+
cues.push(cue);
|
|
18520
19188
|
}
|
|
18521
19189
|
else {
|
|
18522
19190
|
i++;
|
|
@@ -18615,6 +19283,53 @@ function describeType(type) {
|
|
|
18615
19283
|
return type.name.length > 0 ? type.name : '(anonymous type)';
|
|
18616
19284
|
}
|
|
18617
19285
|
|
|
19286
|
+
/**
|
|
19287
|
+
* {@link CacheStrategy} that checks every provided {@link CacheStore} before
|
|
19288
|
+
* falling back to the network.
|
|
19289
|
+
*
|
|
19290
|
+
* On a cache hit the stored value is fed directly to
|
|
19291
|
+
* {@link AssetFactory.create | factory.create}; if that throws (stale or
|
|
19292
|
+
* corrupt entry) the entry is deleted and the next store is tried. Only once
|
|
19293
|
+
* all stores miss does the strategy fetch from the network and write the
|
|
19294
|
+
* processed source back to every store. Quota or serialisation errors during
|
|
19295
|
+
* write are swallowed silently so that a full storage can never prevent an
|
|
19296
|
+
* asset from loading.
|
|
19297
|
+
*
|
|
19298
|
+
* Returns the fully constructed resource — callers do not need to call
|
|
19299
|
+
* {@link AssetFactory.create} again.
|
|
19300
|
+
*/
|
|
19301
|
+
class CacheFirstStrategy {
|
|
19302
|
+
async resolve(request, stores) {
|
|
19303
|
+
const { storageName, key, url, requestOptions, factory, options } = request;
|
|
19304
|
+
for (const store of stores) {
|
|
19305
|
+
const cached = await store.load(storageName, key);
|
|
19306
|
+
if (cached !== null && cached !== undefined) {
|
|
19307
|
+
try {
|
|
19308
|
+
return await factory.create(cached, options);
|
|
19309
|
+
}
|
|
19310
|
+
catch {
|
|
19311
|
+
await store.delete(storageName, key);
|
|
19312
|
+
}
|
|
19313
|
+
}
|
|
19314
|
+
}
|
|
19315
|
+
const response = await fetch(url, requestOptions);
|
|
19316
|
+
if (!response.ok) {
|
|
19317
|
+
throw new Error(`Failed to fetch "${url}" (${response.status} ${response.statusText}).`);
|
|
19318
|
+
}
|
|
19319
|
+
const source = await factory.process(response);
|
|
19320
|
+
const resource = await factory.create(source, options);
|
|
19321
|
+
for (const store of stores) {
|
|
19322
|
+
try {
|
|
19323
|
+
await store.save(storageName, key, source);
|
|
19324
|
+
}
|
|
19325
|
+
catch {
|
|
19326
|
+
// Quota exceeded or non-cloneable value — continue without caching.
|
|
19327
|
+
}
|
|
19328
|
+
}
|
|
19329
|
+
return resource;
|
|
19330
|
+
}
|
|
19331
|
+
}
|
|
19332
|
+
|
|
18618
19333
|
// ---------------------------------------------------------------------------
|
|
18619
19334
|
// Loader
|
|
18620
19335
|
// ---------------------------------------------------------------------------
|
|
@@ -18653,6 +19368,7 @@ class Loader {
|
|
|
18653
19368
|
_typeIds = new WeakMap();
|
|
18654
19369
|
_preventStoreKeys = new Set();
|
|
18655
19370
|
_stores;
|
|
19371
|
+
_cacheStrategy;
|
|
18656
19372
|
_resourcePath;
|
|
18657
19373
|
_requestOptions;
|
|
18658
19374
|
_concurrency;
|
|
@@ -18677,6 +19393,7 @@ class Loader {
|
|
|
18677
19393
|
this._stores = options.cache
|
|
18678
19394
|
? (Array.isArray(options.cache) ? options.cache : [options.cache])
|
|
18679
19395
|
: [];
|
|
19396
|
+
this._cacheStrategy = options.cacheStrategy ?? new CacheFirstStrategy();
|
|
18680
19397
|
this._registerBuiltinFactories();
|
|
18681
19398
|
}
|
|
18682
19399
|
// -----------------------------------------------------------------------
|
|
@@ -19037,45 +19754,24 @@ class Loader {
|
|
|
19037
19754
|
async _fetch(type, alias, path, options) {
|
|
19038
19755
|
const factory = this._registry.resolve(type);
|
|
19039
19756
|
const url = this._resolveUrl(path);
|
|
19040
|
-
|
|
19041
|
-
|
|
19042
|
-
|
|
19043
|
-
|
|
19044
|
-
|
|
19045
|
-
|
|
19046
|
-
|
|
19047
|
-
|
|
19048
|
-
|
|
19049
|
-
|
|
19050
|
-
|
|
19051
|
-
await store.delete(factory.storageName, alias);
|
|
19052
|
-
source = null;
|
|
19053
|
-
}
|
|
19054
|
-
}
|
|
19055
|
-
}
|
|
19056
|
-
// Network fetch
|
|
19057
|
-
const response = await fetch(url, this._requestOptions);
|
|
19058
|
-
if (!response.ok) {
|
|
19059
|
-
throw new Error(`Failed to fetch "${alias}" from "${url}" (${response.status} ${response.statusText}).`);
|
|
19757
|
+
try {
|
|
19758
|
+
const resource = await this._cacheStrategy.resolve({
|
|
19759
|
+
storageName: factory.storageName,
|
|
19760
|
+
key: alias,
|
|
19761
|
+
url,
|
|
19762
|
+
requestOptions: this._requestOptions,
|
|
19763
|
+
factory,
|
|
19764
|
+
options,
|
|
19765
|
+
}, this._stores);
|
|
19766
|
+
this._storeResource(type, alias, resource);
|
|
19767
|
+
return resource;
|
|
19060
19768
|
}
|
|
19061
|
-
|
|
19062
|
-
|
|
19063
|
-
|
|
19064
|
-
|
|
19065
|
-
cause,
|
|
19769
|
+
catch (error) {
|
|
19770
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
19771
|
+
throw new Error(`Failed to load "${alias}" from "${url}": ${message}`, {
|
|
19772
|
+
cause: error,
|
|
19066
19773
|
});
|
|
19067
|
-
});
|
|
19068
|
-
// Write to caches
|
|
19069
|
-
for (const store of this._stores) {
|
|
19070
|
-
try {
|
|
19071
|
-
await store.save(factory.storageName, alias, source);
|
|
19072
|
-
}
|
|
19073
|
-
catch {
|
|
19074
|
-
// Quota exceeded or non-cloneable — continue without caching.
|
|
19075
|
-
}
|
|
19076
19774
|
}
|
|
19077
|
-
this._storeResource(type, alias, resource);
|
|
19078
|
-
return resource;
|
|
19079
19775
|
}
|
|
19080
19776
|
// -----------------------------------------------------------------------
|
|
19081
19777
|
// Internal — background queue
|
|
@@ -19322,6 +20018,7 @@ const defaultAppSettings = {
|
|
|
19322
20018
|
spriteRendererBatchSize: 4096, // ~ 262kb
|
|
19323
20019
|
particleRendererBatchSize: 8192, // ~ 1.18mb
|
|
19324
20020
|
gamepadDefinitions: [],
|
|
20021
|
+
gamepadSlotStrategy: 'sticky',
|
|
19325
20022
|
pointerDistanceThreshold: 10,
|
|
19326
20023
|
webglAttributes: {
|
|
19327
20024
|
alpha: false,
|
|
@@ -19367,7 +20064,7 @@ class Application {
|
|
|
19367
20064
|
options;
|
|
19368
20065
|
canvas;
|
|
19369
20066
|
loader;
|
|
19370
|
-
|
|
20067
|
+
input;
|
|
19371
20068
|
interaction;
|
|
19372
20069
|
sceneManager;
|
|
19373
20070
|
tweens = new TweenManager();
|
|
@@ -19409,7 +20106,7 @@ class Application {
|
|
|
19409
20106
|
});
|
|
19410
20107
|
this._backendType = this.resolveInitialBackendType();
|
|
19411
20108
|
this._backend = this.createBackend(this._backendType);
|
|
19412
|
-
this.
|
|
20109
|
+
this.input = new InputManager(this);
|
|
19413
20110
|
this.interaction = new InteractionManager(this);
|
|
19414
20111
|
this.sceneManager = new SceneManager(this);
|
|
19415
20112
|
this._updateHandler = this.update.bind(this);
|
|
@@ -19418,7 +20115,7 @@ class Application {
|
|
|
19418
20115
|
this._documentVisible = document.visibilityState === 'visible';
|
|
19419
20116
|
document.addEventListener('visibilitychange', this._visibilityChangeHandler);
|
|
19420
20117
|
}
|
|
19421
|
-
this.
|
|
20118
|
+
this.input.onCanvasFocusChange.add((focused) => {
|
|
19422
20119
|
this.onCanvasFocusChange.dispatch(focused);
|
|
19423
20120
|
});
|
|
19424
20121
|
this.onVisibilityChange.add((visible) => {
|
|
@@ -19455,7 +20152,7 @@ class Application {
|
|
|
19455
20152
|
return this._capabilities;
|
|
19456
20153
|
}
|
|
19457
20154
|
get canvasFocused() {
|
|
19458
|
-
return this.
|
|
20155
|
+
return this.input.canvasFocused;
|
|
19459
20156
|
}
|
|
19460
20157
|
get documentVisible() {
|
|
19461
20158
|
return this._documentVisible;
|
|
@@ -19513,7 +20210,7 @@ class Application {
|
|
|
19513
20210
|
const frameDelta = this._frameClock.elapsedTime;
|
|
19514
20211
|
const frameStart = performance.now();
|
|
19515
20212
|
this.backend.resetStats();
|
|
19516
|
-
this.
|
|
20213
|
+
this.input.update();
|
|
19517
20214
|
this.interaction.update();
|
|
19518
20215
|
getAudioManager().update();
|
|
19519
20216
|
this.tweens.update(frameDelta.seconds);
|
|
@@ -19586,7 +20283,7 @@ class Application {
|
|
|
19586
20283
|
this.stop();
|
|
19587
20284
|
this.loader.destroy();
|
|
19588
20285
|
this.interaction.destroy();
|
|
19589
|
-
this.
|
|
20286
|
+
this.input.destroy();
|
|
19590
20287
|
this.tweens.destroy();
|
|
19591
20288
|
this._backend.destroy();
|
|
19592
20289
|
this.sceneManager.destroy();
|
|
@@ -19649,6 +20346,41 @@ class Application {
|
|
|
19649
20346
|
}
|
|
19650
20347
|
}
|
|
19651
20348
|
|
|
20349
|
+
/**
|
|
20350
|
+
* Scene-bound input proxy that automatically disposes its bindings when
|
|
20351
|
+
* the owning scene unloads. Created lazily on first access via
|
|
20352
|
+
* {@link Scene.inputs}; do not instantiate directly.
|
|
20353
|
+
*/
|
|
20354
|
+
class SceneInputs {
|
|
20355
|
+
_scene;
|
|
20356
|
+
_bindings = new Set();
|
|
20357
|
+
constructor(_scene) {
|
|
20358
|
+
this._scene = _scene;
|
|
20359
|
+
}
|
|
20360
|
+
onStart(channel, callback, options) {
|
|
20361
|
+
return this._track(this._scene.app.input.onStart(channel, callback, options));
|
|
20362
|
+
}
|
|
20363
|
+
onActive(channel, callback, options) {
|
|
20364
|
+
return this._track(this._scene.app.input.onActive(channel, callback, options));
|
|
20365
|
+
}
|
|
20366
|
+
onStop(channel, callback, options) {
|
|
20367
|
+
return this._track(this._scene.app.input.onStop(channel, callback, options));
|
|
20368
|
+
}
|
|
20369
|
+
onTrigger(channel, callback, options) {
|
|
20370
|
+
return this._track(this._scene.app.input.onTrigger(channel, callback, options));
|
|
20371
|
+
}
|
|
20372
|
+
/** @internal Called by Scene.destroy. */
|
|
20373
|
+
_disposeAll() {
|
|
20374
|
+
for (const binding of Array.from(this._bindings)) {
|
|
20375
|
+
binding.unbind();
|
|
20376
|
+
}
|
|
20377
|
+
this._bindings.clear();
|
|
20378
|
+
}
|
|
20379
|
+
_track(binding) {
|
|
20380
|
+
this._bindings.add(binding);
|
|
20381
|
+
return binding;
|
|
20382
|
+
}
|
|
20383
|
+
}
|
|
19652
20384
|
/**
|
|
19653
20385
|
* A scene's lifecycle host. Subclass to define scene behavior:
|
|
19654
20386
|
*
|
|
@@ -19672,6 +20404,7 @@ class Scene {
|
|
|
19672
20404
|
_root = new Container();
|
|
19673
20405
|
_stackMode = 'overlay';
|
|
19674
20406
|
_inputMode = 'capture';
|
|
20407
|
+
_inputs = null;
|
|
19675
20408
|
get app() {
|
|
19676
20409
|
return this._app;
|
|
19677
20410
|
}
|
|
@@ -19694,6 +20427,24 @@ class Scene {
|
|
|
19694
20427
|
get root() {
|
|
19695
20428
|
return this._root;
|
|
19696
20429
|
}
|
|
20430
|
+
/**
|
|
20431
|
+
* Scene-bound input registry. Bindings created via
|
|
20432
|
+
* `this.inputs.onTrigger(...)` etc. are automatically disposed when the
|
|
20433
|
+
* scene unloads — no manual cleanup required.
|
|
20434
|
+
*
|
|
20435
|
+
* Lazily instantiated on first access; throws if accessed before
|
|
20436
|
+
* {@link Scene.app} is set (i.e. before the scene is registered with
|
|
20437
|
+
* a {@link SceneManager}).
|
|
20438
|
+
*/
|
|
20439
|
+
get inputs() {
|
|
20440
|
+
if (this._inputs === null) {
|
|
20441
|
+
if (this._app === null) {
|
|
20442
|
+
throw new Error('Scene.inputs is unavailable before the scene is attached to an Application.');
|
|
20443
|
+
}
|
|
20444
|
+
this._inputs = new SceneInputs(this);
|
|
20445
|
+
}
|
|
20446
|
+
return this._inputs;
|
|
20447
|
+
}
|
|
19697
20448
|
get stackMode() {
|
|
19698
20449
|
return this._stackMode;
|
|
19699
20450
|
}
|
|
@@ -19790,47 +20541,13 @@ class Scene {
|
|
|
19790
20541
|
// override in subclass
|
|
19791
20542
|
}
|
|
19792
20543
|
destroy() {
|
|
20544
|
+
this._inputs?._disposeAll();
|
|
20545
|
+
this._inputs = null;
|
|
19793
20546
|
this._root.destroy();
|
|
19794
20547
|
this._app = null;
|
|
19795
20548
|
}
|
|
19796
20549
|
}
|
|
19797
20550
|
|
|
19798
|
-
/**
|
|
19799
|
-
* {@link Clock} variant with a fixed limit. Inherits start/stop/reset/restart
|
|
19800
|
-
* semantics; adds {@link Timer.expired} (true once `elapsedTime >= limit`)
|
|
19801
|
-
* and remaining-time accessors. Useful for cooldowns, delays, and any timed
|
|
19802
|
-
* gating logic where you want to ask "is the duration up?" each frame.
|
|
19803
|
-
*/
|
|
19804
|
-
class Timer extends Clock {
|
|
19805
|
-
_limit;
|
|
19806
|
-
constructor(limit, autoStart = false) {
|
|
19807
|
-
super();
|
|
19808
|
-
this._limit = limit.clone();
|
|
19809
|
-
if (autoStart) {
|
|
19810
|
-
this.restart();
|
|
19811
|
-
}
|
|
19812
|
-
}
|
|
19813
|
-
set limit(limit) {
|
|
19814
|
-
this._limit.copy(limit);
|
|
19815
|
-
}
|
|
19816
|
-
/** `true` once the elapsed time has reached or exceeded the configured limit. */
|
|
19817
|
-
get expired() {
|
|
19818
|
-
return this.elapsedMilliseconds >= this._limit.milliseconds;
|
|
19819
|
-
}
|
|
19820
|
-
get remainingMilliseconds() {
|
|
19821
|
-
return Math.max(0, this._limit.milliseconds - this.elapsedMilliseconds);
|
|
19822
|
-
}
|
|
19823
|
-
get remainingSeconds() {
|
|
19824
|
-
return this.remainingMilliseconds / Time.seconds;
|
|
19825
|
-
}
|
|
19826
|
-
get remainingMinutes() {
|
|
19827
|
-
return this.remainingMilliseconds / Time.minutes;
|
|
19828
|
-
}
|
|
19829
|
-
get remainingHours() {
|
|
19830
|
-
return this.remainingMilliseconds / Time.hours;
|
|
19831
|
-
}
|
|
19832
|
-
}
|
|
19833
|
-
|
|
19834
20551
|
/**
|
|
19835
20552
|
* Lightweight visualisation analyser backed by a Web Audio AnalyserNode.
|
|
19836
20553
|
*
|
|
@@ -23027,22 +23744,22 @@ const basePositions = new Map([
|
|
|
23027
23744
|
['RightStick', [0.62, 0.66]],
|
|
23028
23745
|
]);
|
|
23029
23746
|
const channelMap = new Map([
|
|
23030
|
-
['ButtonNorth',
|
|
23031
|
-
['ButtonWest',
|
|
23032
|
-
['ButtonEast',
|
|
23033
|
-
['ButtonSouth',
|
|
23034
|
-
['LeftShoulder',
|
|
23035
|
-
['RightShoulder',
|
|
23036
|
-
['LeftTrigger',
|
|
23037
|
-
['RightTrigger',
|
|
23038
|
-
['Select',
|
|
23039
|
-
['Start',
|
|
23040
|
-
['LeftStick',
|
|
23041
|
-
['RightStick',
|
|
23042
|
-
['DPadUp',
|
|
23043
|
-
['DPadDown',
|
|
23044
|
-
['DPadLeft',
|
|
23045
|
-
['DPadRight',
|
|
23747
|
+
['ButtonNorth', GamepadButton.North],
|
|
23748
|
+
['ButtonWest', GamepadButton.West],
|
|
23749
|
+
['ButtonEast', GamepadButton.East],
|
|
23750
|
+
['ButtonSouth', GamepadButton.South],
|
|
23751
|
+
['LeftShoulder', GamepadButton.LeftShoulder],
|
|
23752
|
+
['RightShoulder', GamepadButton.RightShoulder],
|
|
23753
|
+
['LeftTrigger', GamepadButton.LeftTrigger],
|
|
23754
|
+
['RightTrigger', GamepadButton.RightTrigger],
|
|
23755
|
+
['Select', GamepadButton.Select],
|
|
23756
|
+
['Start', GamepadButton.Start],
|
|
23757
|
+
['LeftStick', GamepadButton.LeftStick],
|
|
23758
|
+
['RightStick', GamepadButton.RightStick],
|
|
23759
|
+
['DPadUp', GamepadButton.DPadUp],
|
|
23760
|
+
['DPadDown', GamepadButton.DPadDown],
|
|
23761
|
+
['DPadLeft', GamepadButton.DPadLeft],
|
|
23762
|
+
['DPadRight', GamepadButton.DPadRight],
|
|
23046
23763
|
]);
|
|
23047
23764
|
const genericLabels = new Map([
|
|
23048
23765
|
['ButtonNorth', 'North'],
|
|
@@ -23109,6 +23826,7 @@ const promptLabelsByFamily = new Map([
|
|
|
23109
23826
|
[GamepadMappingFamily.JoyConRight, switchLabels],
|
|
23110
23827
|
[GamepadMappingFamily.GameCube, genericLabels],
|
|
23111
23828
|
[GamepadMappingFamily.SteamController, genericLabels],
|
|
23829
|
+
[GamepadMappingFamily.SteamDeck, genericLabels],
|
|
23112
23830
|
[GamepadMappingFamily.ArcadeStick, genericLabels],
|
|
23113
23831
|
]);
|
|
23114
23832
|
/**
|
|
@@ -23117,7 +23835,7 @@ const promptLabelsByFamily = new Map([
|
|
|
23117
23835
|
* Provides the canonical set of prompt controls, their normalised [x, y] positions
|
|
23118
23836
|
* on a generic controller silhouette, device-family label strings (e.g. "A" for
|
|
23119
23837
|
* Xbox, "Cross" for PlayStation, "B" for Switch), and the mapping from prompt
|
|
23120
|
-
* control names to {@link
|
|
23838
|
+
* control names to {@link GamepadButton} channel values.
|
|
23121
23839
|
*/
|
|
23122
23840
|
class GamepadPromptLayouts {
|
|
23123
23841
|
/** Complete ordered list of every {@link GamepadPromptControl} token. */
|
|
@@ -23158,98 +23876,14 @@ class GamepadPromptLayouts {
|
|
|
23158
23876
|
}
|
|
23159
23877
|
/**
|
|
23160
23878
|
* Returns the static mapping from each {@link GamepadPromptControl} to its
|
|
23161
|
-
* corresponding {@link
|
|
23162
|
-
* channel entry and is absent from the returned map.
|
|
23879
|
+
* corresponding {@link GamepadButton} channel. The composite `'DPad'`
|
|
23880
|
+
* control has no channel entry and is absent from the returned map.
|
|
23163
23881
|
*/
|
|
23164
23882
|
static getControlChannelMap() {
|
|
23165
23883
|
return channelMap;
|
|
23166
23884
|
}
|
|
23167
23885
|
}
|
|
23168
23886
|
|
|
23169
|
-
/**
|
|
23170
|
-
* Bind one or more input channels (keyboard keys, gamepad buttons, gamepad
|
|
23171
|
-
* axes) to a set of high-level events: `onStart` (became active),
|
|
23172
|
-
* `onStop` (became inactive), `onActive` (per-frame while active), and
|
|
23173
|
-
* `onTrigger` (released within the threshold window — a "tap"). The current
|
|
23174
|
-
* raw value is the max across all subscribed channels.
|
|
23175
|
-
*
|
|
23176
|
-
* Construct ad-hoc, or via {@link InputManager.add}. Driven by the
|
|
23177
|
-
* {@link InputManager} update loop which feeds the unified channel buffer.
|
|
23178
|
-
*
|
|
23179
|
-
* @example
|
|
23180
|
-
* ```ts
|
|
23181
|
-
* const jump = new Input([Keyboard.Space, GamepadChannel.FaceA], {
|
|
23182
|
-
* onTrigger: () => player.jump(),
|
|
23183
|
-
* });
|
|
23184
|
-
* ```
|
|
23185
|
-
*/
|
|
23186
|
-
class Input {
|
|
23187
|
-
static triggerThreshold = 300;
|
|
23188
|
-
channels = new Set();
|
|
23189
|
-
triggerTimer;
|
|
23190
|
-
valueState = 0;
|
|
23191
|
-
onStart = new Signal();
|
|
23192
|
-
onStop = new Signal();
|
|
23193
|
-
onActive = new Signal();
|
|
23194
|
-
onTrigger = new Signal();
|
|
23195
|
-
constructor(channels, { onStart, onStop, onActive, onTrigger, context, threshold } = {}) {
|
|
23196
|
-
this.channels = new Set(Array.isArray(channels) ? channels : [channels]);
|
|
23197
|
-
this.triggerTimer = new Timer(milliseconds(threshold ?? Input.triggerThreshold));
|
|
23198
|
-
if (onStart) {
|
|
23199
|
-
this.onStart.add(onStart, context);
|
|
23200
|
-
}
|
|
23201
|
-
if (onStop) {
|
|
23202
|
-
this.onStop.add(onStop, context);
|
|
23203
|
-
}
|
|
23204
|
-
if (onActive) {
|
|
23205
|
-
this.onActive.add(onActive, context);
|
|
23206
|
-
}
|
|
23207
|
-
if (onTrigger) {
|
|
23208
|
-
this.onTrigger.add(onTrigger, context);
|
|
23209
|
-
}
|
|
23210
|
-
}
|
|
23211
|
-
get activeChannels() {
|
|
23212
|
-
return this.channels;
|
|
23213
|
-
}
|
|
23214
|
-
get value() {
|
|
23215
|
-
return this.valueState;
|
|
23216
|
-
}
|
|
23217
|
-
/**
|
|
23218
|
-
* Read the latest values from the unified channel buffer and dispatch
|
|
23219
|
-
* the appropriate Signals. Called once per frame by {@link InputManager}.
|
|
23220
|
-
* No-op for inputs not bound to any channel.
|
|
23221
|
-
*/
|
|
23222
|
-
update(channels) {
|
|
23223
|
-
this.valueState = 0;
|
|
23224
|
-
for (const channel of this.channels) {
|
|
23225
|
-
this.valueState = Math.max(channels[channel], this.valueState);
|
|
23226
|
-
}
|
|
23227
|
-
if (this.valueState) {
|
|
23228
|
-
if (!this.triggerTimer.running) {
|
|
23229
|
-
this.triggerTimer.restart();
|
|
23230
|
-
this.onStart.dispatch(this.valueState);
|
|
23231
|
-
}
|
|
23232
|
-
this.onActive.dispatch(this.valueState);
|
|
23233
|
-
}
|
|
23234
|
-
else if (this.triggerTimer.running) {
|
|
23235
|
-
this.onStop.dispatch(this.valueState);
|
|
23236
|
-
if (!this.triggerTimer.expired) {
|
|
23237
|
-
this.onTrigger.dispatch(this.valueState);
|
|
23238
|
-
}
|
|
23239
|
-
this.triggerTimer.stop();
|
|
23240
|
-
}
|
|
23241
|
-
return this;
|
|
23242
|
-
}
|
|
23243
|
-
destroy() {
|
|
23244
|
-
this.channels.clear();
|
|
23245
|
-
this.triggerTimer.destroy();
|
|
23246
|
-
this.onStart.destroy();
|
|
23247
|
-
this.onStop.destroy();
|
|
23248
|
-
this.onActive.destroy();
|
|
23249
|
-
this.onTrigger.destroy();
|
|
23250
|
-
}
|
|
23251
|
-
}
|
|
23252
|
-
|
|
23253
23887
|
/**
|
|
23254
23888
|
* Triangulate a simple 2D polygon by ear-clipping.
|
|
23255
23889
|
*
|
|
@@ -27370,50 +28004,6 @@ class WebGpuShaderFilter extends Filter {
|
|
|
27370
28004
|
}
|
|
27371
28005
|
}
|
|
27372
28006
|
|
|
27373
|
-
/**
|
|
27374
|
-
* {@link CacheStrategy} that checks every provided {@link CacheStore} before
|
|
27375
|
-
* falling back to the network.
|
|
27376
|
-
*
|
|
27377
|
-
* On a cache hit the stored value is validated by calling
|
|
27378
|
-
* {@link AssetFactory.create | factory.create}; if that throws (stale or
|
|
27379
|
-
* corrupt entry) the entry is deleted and the next store is tried. Only once
|
|
27380
|
-
* all stores miss does the strategy fetch from the network and write the
|
|
27381
|
-
* result back to every store. Quota or serialisation errors during write are
|
|
27382
|
-
* swallowed silently so that a full storage can never prevent an asset from
|
|
27383
|
-
* loading.
|
|
27384
|
-
*/
|
|
27385
|
-
class CacheFirstStrategy {
|
|
27386
|
-
async resolve(request, stores) {
|
|
27387
|
-
const { storageName, key, url, requestOptions, factory } = request;
|
|
27388
|
-
for (const store of stores) {
|
|
27389
|
-
const cached = await store.load(storageName, key);
|
|
27390
|
-
if (cached !== null && cached !== undefined) {
|
|
27391
|
-
try {
|
|
27392
|
-
await factory.create(cached);
|
|
27393
|
-
return cached;
|
|
27394
|
-
}
|
|
27395
|
-
catch {
|
|
27396
|
-
await store.delete(storageName, key);
|
|
27397
|
-
}
|
|
27398
|
-
}
|
|
27399
|
-
}
|
|
27400
|
-
const response = await fetch(url, requestOptions);
|
|
27401
|
-
if (!response.ok) {
|
|
27402
|
-
throw new Error(`Failed to fetch "${url}" (${response.status} ${response.statusText}).`);
|
|
27403
|
-
}
|
|
27404
|
-
const source = await factory.process(response);
|
|
27405
|
-
for (const store of stores) {
|
|
27406
|
-
try {
|
|
27407
|
-
await store.save(storageName, key, source);
|
|
27408
|
-
}
|
|
27409
|
-
catch {
|
|
27410
|
-
// Quota exceeded or non-cloneable value — continue without caching.
|
|
27411
|
-
}
|
|
27412
|
-
}
|
|
27413
|
-
return source;
|
|
27414
|
-
}
|
|
27415
|
-
}
|
|
27416
|
-
|
|
27417
28007
|
/**
|
|
27418
28008
|
* {@link CacheStrategy} that always fetches from the network and never reads
|
|
27419
28009
|
* from or writes to any {@link CacheStore}.
|
|
@@ -27421,14 +28011,19 @@ class CacheFirstStrategy {
|
|
|
27421
28011
|
* Useful for assets that must always be fresh (e.g. live configuration files)
|
|
27422
28012
|
* or for environments where persistent storage is unavailable. The `stores`
|
|
27423
28013
|
* argument is accepted but intentionally ignored.
|
|
28014
|
+
*
|
|
28015
|
+
* Returns the fully constructed resource — callers do not need to call
|
|
28016
|
+
* {@link AssetFactory.create} again.
|
|
27424
28017
|
*/
|
|
27425
28018
|
class NetworkOnlyStrategy {
|
|
27426
28019
|
async resolve(request, _stores) {
|
|
27427
|
-
const
|
|
28020
|
+
const { url, requestOptions, factory, options } = request;
|
|
28021
|
+
const response = await fetch(url, requestOptions);
|
|
27428
28022
|
if (!response.ok) {
|
|
27429
|
-
throw new Error(`Failed to fetch "${
|
|
28023
|
+
throw new Error(`Failed to fetch "${url}" (${response.status} ${response.statusText}).`);
|
|
27430
28024
|
}
|
|
27431
|
-
|
|
28025
|
+
const source = await factory.process(response);
|
|
28026
|
+
return factory.create(source, options);
|
|
27432
28027
|
}
|
|
27433
28028
|
}
|
|
27434
28029
|
|
|
@@ -27632,5 +28227,5 @@ class IndexedDbStore {
|
|
|
27632
28227
|
}
|
|
27633
28228
|
}
|
|
27634
28229
|
|
|
27635
|
-
export { AbstractAssetFactory, AbstractMedia, AbstractWebGl2BatchedRenderer, AbstractWebGl2Renderer, AbstractWebGpuRenderer, AnimatedSprite, Application, ApplicationStatus, ArcadeStickGamepadMapping, AudioAnalyser, AudioBus, AudioFilter, AudioListener, AudioManager, BeatDetector, BinaryFactory, BlendModes, BlurFilter, Bounds, BufferTypes, BufferUsage, BundleLoadError, CacheFirstStrategy, CallbackRenderPass, Capabilities, ChannelOffset, ChannelSize, ChorusFilter, Circle, Clock, CollisionType, Color, ColorAffector, ColorFilter, CompressorFilter, Container, DelayFilter, Drawable, DuckingFilter, DynamicGlyphAtlas, Ease, Ellipse, Envelope, EqualizerFilter, FactoryRegistry, Filter, Flags, FontFactory, ForceAffector, GameCubeGamepadMapping, Gamepad,
|
|
28230
|
+
export { AbstractAssetFactory, AbstractMedia, AbstractWebGl2BatchedRenderer, AbstractWebGl2Renderer, AbstractWebGpuRenderer, AnimatedSprite, Application, ApplicationStatus, ArcadeStickGamepadMapping, AudioAnalyser, AudioBus, AudioFilter, AudioListener, AudioManager, BeatDetector, BinaryFactory, BlendModes, BlurFilter, Bounds, BufferTypes, BufferUsage, BundleLoadError, CacheFirstStrategy, CallbackRenderPass, Capabilities, ChannelOffset, ChannelSize, ChorusFilter, Circle, Clock, CollisionType, Color, ColorAffector, ColorFilter, CompressorFilter, Container, DelayFilter, Drawable, DuckingFilter, DynamicGlyphAtlas, Ease, Ellipse, Envelope, EqualizerFilter, FactoryRegistry, Filter, Flags, FontFactory, ForceAffector, GameCubeGamepadMapping, Gamepad, GamepadAxis, GamepadButton, GamepadMapping, GamepadMappingFamily, GamepadPromptLayouts, GenericDualAnalogGamepadMapping, GranularFilter, Graphics, HighpassFilter, ImageFactory, IndexedDbDatabase, IndexedDbStore, InputBinding, InputManager, InteractionEvent, InteractionManager, Interval, JoyConLeftGamepadMapping, JoyConRightGamepadMapping, Json, JsonFactory, Keyboard, Line, Loader, LowpassFilter, Matrix, Mesh, Music, MusicFactory, NetworkOnlyStrategy, ObservableSize, ObservableVector, OscillatorSound, Particle, ParticleOptions, ParticleSystem, PitchShiftFilter, PlayStationGamepadMapping, Pointer, PointerState, PointerStateFlag, PolarVector, Polygon, Quadtree, Random, Rectangle, RenderBackendType, RenderNode, RenderTarget, RenderTargetPass, RenderTexture, RendererRegistry, RenderingPrimitives, ReverbFilter, Sampler, ScaleAffector, ScaleModes, Scene, SceneManager, SceneNode, Segment, Shader, ShaderAttribute, ShaderPrimitives, ShaderUniform, Signal, Size, Sound, SoundFactory, SoundPoolStrategy, Sprite, SpriteFlags, Spritesheet, SteamControllerGamepadMapping, SteamDeckGamepadMapping, SvgAsset, SvgFactory, SwitchProGamepadMapping, Text, TextAsset, TextFactory, TextStyle, Texture, TextureFactory, Time, Timer, TorqueAffector, Tween, TweenManager, TweenState, UniversalEmitter, Vector, Video, VideoFactory, View, ViewFlags, VocoderFilter, VoronoiRegion, VttAsset, VttFactory, WasmFactory, WebGl2Backend, WebGl2MeshRenderer, WebGl2ParticleRenderer, WebGl2RenderBuffer, WebGl2ShaderBlock, WebGl2ShaderFilter, WebGl2SpriteRenderer, WebGl2VertexArrayObject, WebGpuBackend, WebGpuMeshRenderer, WebGpuParticleRenderer, WebGpuShaderFilter, WebGpuSpriteRenderer, WorkletFilter, WrapModes, XboxGamepadMapping, bezierCurveTo, buildCircle, buildEllipse, buildLine, buildPath, buildPolygon, buildRectangle, buildStar, builtInGamepadDefinitions, canvasSourceToDataUrl, clamp, createRenderStats, createWebGl2ShaderProgram, crossFade, decodeAudioData, defineAssetManifest, degreesPerRadian, degreesToRadians, determineMimeType, emptyArrayBuffer, getAudioContext, getAudioManager, getCanvasSourceSize, getCollisionCircleCircle, getCollisionCircleRectangle, getCollisionEllipseCircle, getCollisionEllipseRectangle, getCollisionPolygonCircle, getCollisionRectangleRectangle, getCollisionSat, getDistance, getOfflineAudioContext, getPreciseTime, getTextureSourceSize, getVoronoiRegion$1 as getVoronoiRegion, getWebGpuBlendState, hours, inRange, intersectionCircleCircle, intersectionCircleEllipse, intersectionCirclePoly, intersectionEllipseEllipse, intersectionEllipsePoly, intersectionLineCircle, intersectionLineEllipse, intersectionLineLine, intersectionLinePoly, intersectionLineRect, intersectionPointCircle, intersectionPointEllipse, intersectionPointLine, intersectionPointPoint, intersectionPointPoly, intersectionPointRect, intersectionPolyPoly, intersectionRectCircle, intersectionRectEllipse, intersectionRectPoly, intersectionRectRect, intersectionSat, isAudioContextReady, isPowerOfTwo, layoutText, lerp, matchesIds, maxPointers, milliseconds, minutes, noop$1 as noop, onAudioContextReady, parseGamepadDescriptor, pointerSlotSize, quadraticCurveTo, radiansPerDegree, radiansToDegrees, rand, registerWorkletProcessor, removeArrayItems, resetRenderStats, resolveDefinition, resolveGamepadDefinition, seconds, sign, stopEvent, substepSweep, supportsCodec, supportsEventOptions, supportsIndexedDb, supportsPointerEvents, supportsTouchEvents, supportsWebAudio, sweepCircleAgainst, sweepCircleVsCircle, sweepCircleVsRectangle, sweepRectangle, sweepRectangleAgainst, tau, trimRotation, upgradeFragmentShaderToGl300, webGl2PrimitiveArrayConstructors, webGl2PrimitiveByteSizeMapping, webGl2PrimitiveTypeNames };
|
|
27636
28231
|
//# sourceMappingURL=exo.esm.js.map
|