Explorar o código

Fixed issue with Connected Gamepads and DeviceSourceManager (#8961)

* Fixed an issue with DeviceSourceManager where already connected gamepads were no longer registering.

* Doc stuff required for PR

* PR Feedback

* Moved gamepad check to constructor and named it checkForConnectedDevices

* PR Feedback (clean up checkForConnectedDevices)

* Changed logic for onDeviceConnected (DeviceInputSystem) and onBefore/AfterDeviceConnectedObservable to execute code when changed.

* PR Feedback

* Fixed up logic and added mouse to checkForConnectedDevices

* Added comments to clarify that checkForConnectedDevices will only check for the gamepads and mouse

* Added comment for mouse init
Dave Solares %!s(int64=4) %!d(string=hai) anos
pai
achega
c899a2c4b2

+ 29 - 23
src/DeviceInput/InputDevices/deviceSourceManager.ts

@@ -49,24 +49,18 @@ export class DeviceSource<T extends DeviceType> {
 export class DeviceSourceManager implements IDisposable {
     // Public Members
     /**
-     * Observable to be triggered when before a device is connected
+     * Observable to be triggered when after a device is connected, any new observers added will be triggered against already connected devices
      */
-    public readonly onBeforeDeviceConnectedObservable = new Observable<{ deviceType: DeviceType, deviceSlot: number }>();
-
-    /**
-     * Observable to be triggered when before a device is disconnected
-     */
-    public readonly onBeforeDeviceDisconnectedObservable = new Observable<{ deviceType: DeviceType, deviceSlot: number }>();
-
-    /**
-     * Observable to be triggered when after a device is connected
-     */
-    public readonly onAfterDeviceConnectedObservable = new Observable<{ deviceType: DeviceType, deviceSlot: number }>();
+    public readonly onDeviceConnectedObservable = new Observable<DeviceSource<DeviceType>>((observer) => {
+        this.getDevices().forEach((device) => {
+            this.onDeviceConnectedObservable.notifyObserver(observer, device);
+        });
+    });
 
     /**
      * Observable to be triggered when after a device is disconnected
      */
-    public readonly onAfterDeviceDisconnectedObservable = new Observable<{ deviceType: DeviceType, deviceSlot: number }>();
+    public readonly onDeviceDisconnectedObservable = new Observable<DeviceSource<DeviceType>>();
 
     // Private Members
     private readonly _devices: Array<Array<DeviceSource<DeviceType>>>;
@@ -84,14 +78,13 @@ export class DeviceSourceManager implements IDisposable {
         this._deviceInputSystem = DeviceInputSystem.Create(engine);
 
         this._deviceInputSystem.onDeviceConnected = (deviceType, deviceSlot) => {
-            this.onBeforeDeviceConnectedObservable.notifyObservers({ deviceType, deviceSlot });
             this._addDevice(deviceType, deviceSlot);
-            this.onAfterDeviceConnectedObservable.notifyObservers({ deviceType, deviceSlot });
+            this.onDeviceConnectedObservable.notifyObservers(this.getDeviceSource(deviceType, deviceSlot)!);
         };
         this._deviceInputSystem.onDeviceDisconnected = (deviceType, deviceSlot) => {
-            this.onBeforeDeviceDisconnectedObservable.notifyObservers({ deviceType, deviceSlot });
+            const device = this.getDeviceSource(deviceType, deviceSlot)!; // Grab local reference to use before removing from devices
             this._removeDevice(deviceType, deviceSlot);
-            this.onAfterDeviceDisconnectedObservable.notifyObservers({ deviceType, deviceSlot });
+            this.onDeviceDisconnectedObservable.notifyObservers(device);
         };
 
         if (!this._deviceInputSystem.onInputChanged) {
@@ -134,13 +127,24 @@ export class DeviceSourceManager implements IDisposable {
     }
 
     /**
+     * Returns a read-only list of all available devices
+     * @returns Read-only array with active devices
+     */
+    public getDevices(): ReadonlyArray<DeviceSource<DeviceType>> {
+        const deviceArray = new Array<DeviceSource<DeviceType>>();
+        this._devices.forEach((deviceSet) => {
+            deviceArray.push.apply(deviceArray, deviceSet);
+        });
+
+        return deviceArray;
+    }
+
+    /**
      * Dispose of DeviceInputSystem and other parts
      */
     public dispose() {
-        this.onBeforeDeviceConnectedObservable.clear();
-        this.onBeforeDeviceDisconnectedObservable.clear();
-        this.onAfterDeviceConnectedObservable.clear();
-        this.onAfterDeviceDisconnectedObservable.clear();
+        this.onDeviceConnectedObservable.clear();
+        this.onDeviceDisconnectedObservable.clear();
         this._deviceInputSystem.dispose();
     }
 
@@ -155,8 +159,10 @@ export class DeviceSourceManager implements IDisposable {
             this._devices[deviceType] = new Array<DeviceSource<DeviceType>>();
         }
 
-        this._devices[deviceType][deviceSlot] = new DeviceSource(this._deviceInputSystem, deviceType, deviceSlot);
-        this._updateFirstDevices(deviceType);
+        if (!this._devices[deviceType][deviceSlot]) {
+            this._devices[deviceType][deviceSlot] = new DeviceSource(this._deviceInputSystem, deviceType, deviceSlot);
+            this._updateFirstDevices(deviceType);
+        }
     }
 
     /**

+ 84 - 18
src/DeviceInput/deviceInputSystem.ts

@@ -13,10 +13,31 @@ declare const _native: any;
  * pointer device and one keyboard.
  */
 export class DeviceInputSystem implements IDisposable {
+
     /**
-     * Callback to be triggered when a device is connected
+     * Returns onDeviceConnected callback property
+     * @returns Callback with function to execute when a device is connected
      */
-    public onDeviceConnected: (deviceType: DeviceType, deviceSlot: number) => void = () => { };
+    public get onDeviceConnected() { return this._onDeviceConnected; }
+
+    /**
+     * Sets callback function when a device is connected and executes against all connected devices
+     * @param callback Function to execute when a device is connected
+     */
+    public set onDeviceConnected(callback) {
+        this._onDeviceConnected = callback;
+
+        // Iterate through each active device and rerun new callback
+        for (let deviceType = 0; deviceType < this._inputs.length; deviceType++) {
+            if (this._inputs[deviceType]) {
+                for (let deviceSlot = 0; deviceSlot < this._inputs[deviceType].length; deviceSlot++) {
+                    if (this._inputs[deviceType][deviceSlot]) {
+                        this._onDeviceConnected(deviceType, deviceSlot);
+                    }
+                }
+            }
+        }
+    }
 
     /**
      * Callback to be triggered when a device is disconnected
@@ -45,16 +66,22 @@ export class DeviceInputSystem implements IDisposable {
     private _gamepadConnectedEvent = (evt: any) => { };
     private _gamepadDisconnectedEvent = (evt: any) => { };
 
+    private _onDeviceConnected: (deviceType: DeviceType, deviceSlot: number) => void = () => { };
+
     private static _MAX_KEYCODES: number = 255;
     private static _MAX_POINTER_INPUTS: number = 7;
 
     private constructor(engine: Engine) {
         const inputElement = engine.getInputElement();
+
         if (inputElement) {
             this._elementToAttachTo = inputElement;
             this._handleKeyActions();
             this._handlePointerActions();
             this._handleGamepadActions();
+
+            // Check for devices that are already connected but aren't registered. Currently, only checks for gamepads and mouse
+            this._checkForConnectedDevices();
         }
     }
 
@@ -125,8 +152,57 @@ export class DeviceInputSystem implements IDisposable {
         window.removeEventListener("gamepaddisconnected", this._gamepadDisconnectedEvent);
     }
 
+    /**
+     * Checks for existing connections to devices and register them, if necessary
+     * Currently handles gamepads and mouse
+     */
+    private _checkForConnectedDevices() {
+        const gamepads = navigator.getGamepads();
+
+        for (const gamepad of gamepads) {
+            if (gamepad) {
+                this._addGamePad(gamepad);
+            }
+        }
+
+        // If the device in use has mouse capabilities, pre-register mouse
+        if (matchMedia('(pointer:fine)').matches) {
+            // This will provide a dummy value for the cursor position and is expected to be overriden when the first mouse event happens.
+            // There isn't any good way to get the current position outside of a pointer event so that's why this was done.
+            this._addPointerDevice(DeviceType.Mouse, 0, 0, 0);
+        }
+    }
+
     // Private functions
     /**
+     * Add a gamepad to the DeviceInputSystem
+     * @param gamepad A single DOM Gamepad object
+     */
+    private _addGamePad(gamepad: any) {
+        const deviceType = this._getGamepadDeviceType(gamepad.id);
+        const deviceSlot = gamepad.index;
+
+        this._registerDevice(deviceType, deviceSlot, gamepad.buttons.length + gamepad.axes.length);
+        this._gamepads = this._gamepads || new Array<DeviceType>(gamepad.index + 1);
+        this._gamepads[deviceSlot] = deviceType;
+    }
+
+    /**
+     * Add pointer device to DeviceInputSystem
+     * @param deviceType Type of Pointer to add
+     * @param deviceSlot Pointer ID (0 for mouse, pointerId for Touch)
+     * @param currentX Current X at point of adding
+     * @param currentY Current Y at point of adding
+     */
+    private _addPointerDevice(deviceType: DeviceType, deviceSlot: number, currentX: number, currentY: number) {
+        this._pointerActive = true;
+        this._registerDevice(deviceType, deviceSlot, DeviceInputSystem._MAX_POINTER_INPUTS);
+        const pointer = this._inputs[deviceType][deviceSlot]; /* initalize our pointer position immediately after registration */
+        pointer[0] = currentX;
+        pointer[1] = currentY;
+    }
+
+    /**
      * Add device and inputs to device array
      * @param deviceType Enum specifiying device type
      * @param deviceSlot "Slot" or index that device is referenced in
@@ -207,11 +283,7 @@ export class DeviceInputSystem implements IDisposable {
             }
 
             if (!this._inputs[deviceType][deviceSlot]) {
-                this._pointerActive = true;
-                this._registerDevice(deviceType, deviceSlot, DeviceInputSystem._MAX_POINTER_INPUTS);
-                const pointer = this._inputs[deviceType][deviceSlot]; /* initalize our pointer position immediately after registration */
-                pointer[0] = evt.clientX;
-                pointer[1] = evt.clientY;
+                this._addPointerDevice(deviceType, deviceSlot, evt.clientX, evt.clientY);
             }
 
             const pointer = this._inputs[deviceType][deviceSlot];
@@ -234,11 +306,7 @@ export class DeviceInputSystem implements IDisposable {
             }
 
             if (!this._inputs[deviceType][deviceSlot]) {
-                this._pointerActive = true;
-                this._registerDevice(deviceType, deviceSlot, DeviceInputSystem._MAX_POINTER_INPUTS);
-                const pointer = this._inputs[deviceType][deviceSlot]; /* initalize our pointer position immediately after registration */
-                pointer[0] = evt.clientX;
-                pointer[1] = evt.clientY;
+                this._addPointerDevice(deviceType, deviceSlot, evt.clientX, evt.clientY);
             }
 
             const pointer = this._inputs[deviceType][deviceSlot];
@@ -263,6 +331,9 @@ export class DeviceInputSystem implements IDisposable {
                 if (this.onInputChanged) {
                     this.onInputChanged(deviceType, deviceSlot, evt.button + 2, pointer[evt.button + 2], 0);
                 }
+
+                pointer[0] = evt.clientX;
+                pointer[1] = evt.clientY;
                 pointer[evt.button + 2] = 0;
             }
             // We don't want to unregister the mouse because we may miss input data when a mouse is moving after a click
@@ -282,12 +353,7 @@ export class DeviceInputSystem implements IDisposable {
      */
     private _handleGamepadActions() {
         this._gamepadConnectedEvent = ((evt: any) => {
-            const deviceType = this._getGamepadDeviceType(evt.gamepad.id);
-            const deviceSlot = evt.gamepad.index;
-
-            this._registerDevice(deviceType, deviceSlot, evt.gamepad.buttons.length + evt.gamepad.axes.length);
-            this._gamepads = this._gamepads || new Array<string>(evt.gamepad.index + 1);
-            this._gamepads[deviceSlot] = deviceType;
+            this._addGamePad(evt.gamepad);
         });
 
         this._gamepadDisconnectedEvent = ((evt: any) => {