deviceSourceManager.ts 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308
  1. import { DeviceInputSystem, POINTER_DEVICE, KEYBOARD_DEVICE, MOUSE_DEVICE } from '../deviceInputSystem';
  2. import { Engine } from '../../Engines/engine';
  3. import { IDisposable } from '../../scene';
  4. import { DeviceType, DeviceInputs } from './deviceEnums';
  5. import { Nullable } from '../../types';
  6. import { Observable } from '../../Misc/observable';
  7. /**
  8. * Class that handles all input for a specific device
  9. */
  10. export class DeviceSource<T extends DeviceType> {
  11. /**
  12. * Observable to handle device input changes per device
  13. */
  14. public onInputChangedObservable = new Observable<{ inputIndex: number, previousState: Nullable<number>, currentState: Nullable<number> }>();
  15. private _deviceInputSystem: DeviceInputSystem;
  16. private _touchPoints: Array<string>;
  17. /**
  18. * Default Constructor
  19. * @param deviceInputSystem Reference to DeviceInputSystem
  20. * @param deviceName Name of device to be used by DeviceInputSystem
  21. * @param deviceType Type of device
  22. * @param deviceSlot "Slot" or index that device is referenced in
  23. */
  24. constructor(deviceInputSystem: DeviceInputSystem,
  25. /** Name of device to be used by DeviceInputSystem */
  26. public deviceName: string,
  27. /** Type of device */
  28. public deviceType: DeviceInputs<T>,
  29. /** "Slot" or index that device is referenced in */
  30. public deviceSlot: number = 0) {
  31. this._deviceInputSystem = deviceInputSystem;
  32. if (deviceType == DeviceType.Touch) {
  33. this._touchPoints = new Array<string>();
  34. }
  35. }
  36. /**
  37. * Get input for specific input
  38. * @param inputIndex index of specific input on device
  39. * @returns Input value from DeviceInputSystem
  40. */
  41. public getInput(inputIndex: T): Nullable<number> {
  42. if (this.deviceType == DeviceType.Touch) {
  43. return this._deviceInputSystem.pollInput(this._touchPoints[this.deviceSlot], inputIndex);
  44. }
  45. return this._deviceInputSystem.pollInput(this.deviceName, inputIndex);
  46. }
  47. /**
  48. * Add specific point to array of touch points (DeviceType.Touch only)
  49. * @param name Name of Specific Touch Point
  50. */
  51. public addTouchPoints(name: string) {
  52. if (this.deviceType == DeviceType.Touch) {
  53. this._touchPoints.push(name);
  54. }
  55. }
  56. /**
  57. * Remove specific point from array of touch points (DeviceType.Touch only)
  58. * @param name Name of Specific Touch Point
  59. */
  60. public removeTouchPoints(name: string) {
  61. if (this.deviceType == DeviceType.Touch) {
  62. const touchIndex = this._touchPoints.indexOf(name);
  63. this._touchPoints.splice(touchIndex, 1);
  64. }
  65. }
  66. }
  67. /**
  68. * Class to keep track of devices
  69. */
  70. export class DeviceSourceManager implements IDisposable {
  71. // Public Members
  72. /**
  73. * Observable to be triggered when before a device is connected
  74. */
  75. public onBeforeDeviceConnectedObservable = new Observable<{ deviceType: DeviceType, deviceSlot: number }>();
  76. /**
  77. * Observable to be triggered when before a device is disconnected
  78. */
  79. public onBeforeDeviceDisconnectedObservable = new Observable<{ deviceType: DeviceType, deviceSlot: number }>();
  80. /**
  81. * Observable to be triggered when after a device is connected
  82. */
  83. public onAfterDeviceConnectedObservable = new Observable<{ deviceType: DeviceType, deviceSlot: number }>();
  84. /**
  85. * Observable to be triggered when after a device is disconnected
  86. */
  87. public onAfterDeviceDisconnectedObservable = new Observable<{ deviceType: DeviceType, deviceSlot: number }>();
  88. // Private Members
  89. private _devices: Array<Array<DeviceSource<DeviceType>>>;
  90. private _firstDevice: Array<number>;
  91. private _deviceInputSystem: DeviceInputSystem;
  92. /**
  93. * Default Constructor
  94. * @param engine engine to pull input element from
  95. * @param enableObserveEvents boolean to enable use of observe events
  96. */
  97. constructor(engine: Engine, enableObserveEvents: boolean = false) {
  98. const numberOfDeviceTypes = Object.keys(DeviceType).length / 2;
  99. this._devices = new Array<Array<DeviceSource<DeviceType>>>(numberOfDeviceTypes);
  100. this._firstDevice = new Array<number>(numberOfDeviceTypes);
  101. this._deviceInputSystem = new DeviceInputSystem(engine);
  102. this._deviceInputSystem.onDeviceConnected = (deviceName) => {
  103. const deviceType = this._getDeviceTypeFromName(deviceName);
  104. const deviceSlot = this._getDeviceSlot(deviceName);
  105. this.onBeforeDeviceConnectedObservable.notifyObservers({ deviceType, deviceSlot });
  106. this._addDevice(deviceName);
  107. this.onAfterDeviceConnectedObservable.notifyObservers({ deviceType, deviceSlot });
  108. };
  109. this._deviceInputSystem.onDeviceDisconnected = (deviceName) => {
  110. const deviceType = this._getDeviceTypeFromName(deviceName);
  111. const deviceSlot = this._getDeviceSlot(deviceName);
  112. this.onBeforeDeviceDisconnectedObservable.notifyObservers({ deviceType, deviceSlot });
  113. this._removeDevice(deviceName);
  114. this.onAfterDeviceDisconnectedObservable.notifyObservers({ deviceType, deviceSlot });
  115. };
  116. if (!this._deviceInputSystem.observeInput && enableObserveEvents) {
  117. this._deviceInputSystem.observeInput = (deviceName, inputIndex, previousState, currentState) => {
  118. const deviceType = this._getDeviceTypeFromName(deviceName);
  119. const deviceSlot = this._getDeviceSlot(deviceName);
  120. if (deviceType == DeviceType.Keyboard || deviceType == DeviceType.Mouse || deviceType == DeviceType.Touch) {
  121. this.getDeviceSource(deviceType, deviceSlot)?.onInputChangedObservable.notifyObservers({ inputIndex, previousState, currentState });
  122. }
  123. };
  124. }
  125. }
  126. // Public Functions
  127. /**
  128. * Checks for current device input value, given DeviceType, slot, and inputIndex
  129. * @param type Enum specifiying device type
  130. * @param inputIndex Index of device input
  131. * @param deviceSlot "Slot" or index that device is referenced in
  132. * @returns Current value of input
  133. */
  134. public getInput<T extends DeviceType>(type: T, inputIndex: DeviceInputs<T>, deviceSlot: number = this._firstDevice[type]): Nullable<number> {
  135. if (!this._devices[type] || this._firstDevice[type] === undefined || this._devices[type][deviceSlot] === undefined) {
  136. return null;
  137. }
  138. return this._devices[type][deviceSlot].getInput(inputIndex);
  139. }
  140. /**
  141. * Gets a DeviceSource, given a type and slot
  142. * @param deviceType Enum specifiying device type
  143. * @param deviceSlot "Slot" or index that device is referenced in
  144. * @returns DeviceSource object
  145. */
  146. public getDeviceSource(deviceType: DeviceType, deviceSlot: number = this._firstDevice[deviceType]): Nullable<DeviceSource<DeviceType>> {
  147. if (!this._devices[deviceType] || this._firstDevice[deviceType] === undefined || this._devices[deviceType][deviceSlot] === undefined) {
  148. return null;
  149. }
  150. return this._devices[deviceType][deviceSlot];
  151. }
  152. /**
  153. * Gets an array of DeviceSource objects for a given device type
  154. * @param deviceType Enum specifiying device type
  155. * @returns Array of DeviceSource objects
  156. */
  157. public getDeviceSources(deviceType: DeviceType): ReadonlyArray<DeviceSource<DeviceType>> {
  158. return this._devices[deviceType];
  159. }
  160. /**
  161. * Dispose of DeviceInputSystem and other parts
  162. */
  163. public dispose() {
  164. this._deviceInputSystem.dispose();
  165. }
  166. // Private Functions
  167. /**
  168. * Function to add device name to device list
  169. * @param deviceName Name of Device
  170. */
  171. private _addDevice(deviceName: string) {
  172. const deviceSlot = this._getDeviceSlot(deviceName);
  173. const deviceType = this._getDeviceTypeFromName(deviceName);
  174. if (!this._devices[deviceType]) {
  175. this._devices[deviceType] = new Array<DeviceSource<DeviceType>>();
  176. if (deviceType == DeviceType.Touch) {
  177. this._devices[deviceType][0] = new DeviceSource<DeviceType>(this._deviceInputSystem, POINTER_DEVICE, DeviceType.Touch, 0);
  178. }
  179. }
  180. // If device is a touch device, update only touch points. Otherwise, add new device.
  181. if (deviceType == DeviceType.Touch) {
  182. this._devices[deviceType][0].addTouchPoints(deviceName);
  183. }
  184. else {
  185. this._devices[deviceType][deviceSlot] = new DeviceSource<DeviceType>(this._deviceInputSystem, deviceName, deviceType, deviceSlot);
  186. }
  187. this._updateFirstDevices(deviceType);
  188. }
  189. /**
  190. * Function to remove device name to device list
  191. * @param deviceName Name of Device
  192. */
  193. private _removeDevice(deviceName: string) {
  194. const deviceSlot = this._getDeviceSlot(deviceName);
  195. const deviceType = this._getDeviceTypeFromName(deviceName);
  196. if (deviceType == DeviceType.Touch) {
  197. this._devices[deviceType][0].removeTouchPoints(deviceName);
  198. }
  199. else {
  200. delete this._devices[deviceType][deviceSlot];
  201. }
  202. }
  203. /**
  204. * Get slot for a given input
  205. * @param deviceName Name of Device
  206. * @returns Slot Number
  207. */
  208. private _getDeviceSlot(deviceName: string): number {
  209. const splitName = deviceName.split("-");
  210. const deviceSlot = parseInt(splitName[splitName.length - 1]);
  211. if (deviceSlot) {
  212. return deviceSlot;
  213. }
  214. return 0;
  215. }
  216. /**
  217. * Updates array storing first connected device of each type
  218. * @param type Type of Device
  219. */
  220. private _updateFirstDevices(type: DeviceType) {
  221. switch (type) {
  222. case DeviceType.Keyboard:
  223. case DeviceType.Mouse:
  224. case DeviceType.Touch:
  225. this._firstDevice[type] = 0;
  226. break;
  227. case DeviceType.DualShock:
  228. case DeviceType.Xbox:
  229. case DeviceType.Switch:
  230. case DeviceType.Generic:
  231. let i = 0;
  232. let first = -1;
  233. while (first < 0 && i < this._devices[type].length) {
  234. if (this._devices[type][i]) {
  235. first = i;
  236. }
  237. i++;
  238. }
  239. this._firstDevice[type] = first;
  240. break;
  241. }
  242. }
  243. /**
  244. * Gets DeviceType from the device name
  245. * @param deviceName Name of Device from DeviceInputSystem
  246. * @returns DeviceType enum value
  247. */
  248. private _getDeviceTypeFromName(deviceName: string): DeviceType {
  249. if (deviceName == KEYBOARD_DEVICE) {
  250. return DeviceType.Keyboard;
  251. }
  252. else if (deviceName == MOUSE_DEVICE) {
  253. return DeviceType.Mouse;
  254. }
  255. else if (deviceName.search(POINTER_DEVICE) !== -1) {
  256. return DeviceType.Touch;
  257. }
  258. else if (deviceName.search("054c") !== -1) { // DualShock 4 Gamepad
  259. return DeviceType.DualShock;
  260. }
  261. else if (deviceName.search("Xbox One") !== -1 || deviceName.search("Xbox 360") !== -1 || deviceName.search("xinput") !== -1) { // Xbox Gamepad
  262. return DeviceType.Xbox;
  263. }
  264. else if (deviceName.search("057e") !== -1) { // Switch Gamepad
  265. return DeviceType.Switch;
  266. }
  267. return DeviceType.Generic;
  268. }
  269. }