InputUser.cs 93 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992
  1. using System;
  2. using System.Collections.Generic;
  3. using Unity.Collections;
  4. using UnityEngine.InputSystem.LowLevel;
  5. using UnityEngine.InputSystem.Utilities;
  6. using UnityEngine.Profiling;
  7. ////REVIEW: do we need to handle the case where devices are added to a user that are each associated with a different user account
  8. ////REVIEW: how should we handle pairings of devices *not* called for by a control scheme? should that result in a failed match?
  9. ////TODO: option to bind to *all* devices instead of just the paired ones (bindToAllDevices)
  10. ////TODO: the account selection stuff needs cleanup; the current flow is too convoluted
  11. namespace UnityEngine.InputSystem.Users
  12. {
  13. /// <summary>
  14. /// Represents a specific user/player interacting with one or more devices and input actions.
  15. /// </summary>
  16. /// <remarks>
  17. /// Principally, an InputUser represents a human interacting with the application. Moreover, at any point
  18. /// each InputUser represents a human actor distinct from all other InputUsers in the system.
  19. ///
  20. /// Each user has one or more paired devices. In general, these devices are unique to each user. However,
  21. /// it is permitted to use <see cref="PerformPairingWithDevice"/> to pair the same device to multiple users.
  22. /// This can be useful in setups such as split-keyboard (e.g. one user using left side of keyboard and the
  23. /// other the right one) use or hotseat-style gameplay (e.g. two players taking turns on the same game controller).
  24. ///
  25. /// A user may be associated with a platform user account (<see cref="platformUserAccountHandle"/>), if supported by the
  26. /// platform and the devices used. Support for this is commonly found on consoles. Note that the account
  27. /// associated with an InputUser may change if the player uses the system's facilities to switch to a different
  28. /// account (<see cref="InputUserChange.AccountChanged"/>). On Xbox and Switch, this may also be initiated from
  29. /// the application by passing <see cref="InputUserPairingOptions.ForcePlatformUserAccountSelection"/> to
  30. /// <see cref="PerformPairingWithDevice"/>.
  31. ///
  32. /// Platforms that support user account association are <see cref="RuntimePlatform.XboxOne"/>,
  33. /// <see cref="RuntimePlatform.PS4"/>, <see cref="RuntimePlatform.Switch"/>, <see cref="RuntimePlatform.WSAPlayerX86"/>,
  34. /// <see cref="RuntimePlatform.WSAPlayerX64"/>, and <see cref="RuntimePlatform.WSAPlayerARM"/>. Note that
  35. /// for WSA/UWP apps, the "User Account Information" capability must be enabled for the app in order for
  36. /// user information to come through on input devices.
  37. /// </remarks>
  38. /// <seealso cref="InputUserChange"/>
  39. public struct InputUser : IEquatable<InputUser>
  40. {
  41. public const uint InvalidId = 0;
  42. /// <summary>
  43. /// Whether this is a currently active user record in <see cref="all"/>.
  44. /// </summary>
  45. /// <remarks>
  46. /// Users that are removed (<see cref="UnpairDevicesAndRemoveUser"/>) will become invalid.
  47. /// </remarks>
  48. /// <seealso cref="UnpairDevicesAndRemoveUser"/>
  49. /// <seealso cref="InputUserChange.Removed"/>
  50. public bool valid
  51. {
  52. get
  53. {
  54. if (m_Id == InvalidId)
  55. return false;
  56. // See if there's a currently active user with the given ID.
  57. for (var i = 0; i < s_AllUserCount; ++i)
  58. if (s_AllUsers[i].m_Id == m_Id)
  59. return true;
  60. return false;
  61. }
  62. }
  63. /// <summary>
  64. /// The sequence number of the user.
  65. /// </summary>
  66. /// <remarks>
  67. /// It can be useful to establish a sorting of players locally such that it is
  68. /// known who is the first player, who is the second, and so on. This property
  69. /// gives the positioning of the user within <see cref="all"/>.
  70. ///
  71. /// Note that the index of a user may change as users are added and removed.
  72. /// </remarks>
  73. /// <seealso cref="all"/>
  74. public int index
  75. {
  76. get
  77. {
  78. if (m_Id == InvalidId)
  79. throw new InvalidOperationException("Invalid user");
  80. var userIndex = TryFindUserIndex(m_Id);
  81. if (userIndex == -1)
  82. throw new InvalidOperationException($"User with ID {m_Id} is no longer valid");
  83. return userIndex;
  84. }
  85. }
  86. /// <summary>
  87. /// The unique numeric ID of the user.
  88. /// </summary>
  89. /// <remarks>
  90. /// The ID of a user is internally assigned and cannot be changed over its lifetime. No two users, even
  91. /// if not concurrently active, will receive the same ID.
  92. ///
  93. /// Note that this is not the same as the platform's internal user ID (if relevant on the current
  94. /// platform). To get the ID that the platform uses to identify the user, use <see cref="platformUserAccountHandle"/>.
  95. ///
  96. /// The ID stays valid and unique even if the user is removed and no longer <see cref="valid"/>.
  97. /// </remarks>
  98. /// <seealso cref="platformUserAccountHandle"/>
  99. public uint id => m_Id;
  100. /// <summary>
  101. /// If the user is is associated with a user account at the platform level, this is the handle used by the
  102. /// underlying platform API for the account.
  103. /// </summary>
  104. /// <remarks>
  105. /// Users may be associated with user accounts defined by the platform we are running on. Consoles, for example,
  106. /// have user account management built into the OS and marketplaces like Steam also have APIs for user management.
  107. ///
  108. /// If this property is not <c>null</c>, it is the handle associated with the user at the platform level. This can
  109. /// be used, for example, to call platform-specific APIs to fetch additional information about the user (such as
  110. /// user profile images).
  111. ///
  112. /// Be aware that there may be multiple InputUsers that have the same platformUserAccountHandle in case the platform
  113. /// allows different players to log in on the same user account.
  114. /// </remarks>
  115. /// <seealso cref="platformUserAccountName"/>
  116. /// <seealso cref="platformUserAccountId"/>
  117. /// <seealso cref="InputUserChange.AccountChanged"/>
  118. public InputUserAccountHandle? platformUserAccountHandle => s_AllUserData[index].platformUserAccountHandle;
  119. /// <summary>
  120. /// Human-readable name assigned to the user account at the platform level.
  121. /// </summary>
  122. /// <remarks>
  123. /// This property will be <c>null</c> on platforms that do not have user account management. In that case,
  124. /// <see cref="platformUserAccountHandle"/> will be <c>null</c> as well.
  125. ///
  126. /// On platforms such as Xbox, PS4, and Switch, the user name will be the name of the user as logged in on the platform.
  127. /// </remarks>
  128. /// <seealso cref="platformUserAccountHandle"/>
  129. /// <seealso cref="platformUserAccountId"/>
  130. /// <seealso cref="InputUserChange.AccountChanged"/>
  131. /// <seealso cref="InputUserChange.AccountNameChanged"/>
  132. public string platformUserAccountName => s_AllUserData[index].platformUserAccountName;
  133. /// <summary>
  134. /// Platform-specific user ID that is valid across sessions even if the <see cref="platformUserAccountName"/> of
  135. /// the user changes.
  136. /// </summary>
  137. /// <remarks>
  138. /// This is only valid if <see cref="platformUserAccountHandle"/> is not null.
  139. ///
  140. /// Use this to, for example, associate application settings with the user. For display in UIs, use
  141. /// <see cref="platformUserAccountName"/> instead.
  142. /// </remarks>
  143. /// <seealso cref="platformUserAccountHandle"/>
  144. /// <seealso cref="platformUserAccountName"/>
  145. /// <seealso cref="InputUserChange.AccountChanged"/>
  146. public string platformUserAccountId => s_AllUserData[index].platformUserAccountId;
  147. ////REVIEW: Does it make sense to track used devices separately from paired devices?
  148. /// <summary>
  149. /// Devices assigned/paired/linked to the user.
  150. /// </summary>
  151. /// <remarks>
  152. /// It is generally valid for a device to be assigned to multiple users. For example, two users could
  153. /// both use the local keyboard in a split-keyboard or hot seat setup. However, a platform may restrict this
  154. /// and mandate that a device never belong to more than one user. This is the case on Xbox and PS4, for
  155. /// example.
  156. ///
  157. /// To associate devices with users, use <see cref="PerformPairingWithDevice"/>. To remove devices, use
  158. /// <see cref="UnpairDevice"/> or <see cref="UnpairDevicesAndRemoveUser"/>.
  159. ///
  160. /// The array will be empty for a user who is currently not paired to any devices.
  161. ///
  162. /// If <see cref="actions"/> is set (<see cref="AssociateActionsWithUser(IInputActionCollection)"/>), then
  163. /// <see cref="IInputActionCollection.devices"/> will be kept synchronized with the devices paired to the user.
  164. /// </remarks>
  165. /// <seealso cref="PerformPairingWithDevice"/>
  166. /// <seealso cref="UnpairDevice"/>
  167. /// <seealso cref="UnpairDevices"/>
  168. /// <seealso cref="UnpairDevicesAndRemoveUser"/>
  169. /// <seealso cref="InputUserChange.DevicePaired"/>
  170. /// <seealso cref="InputUserChange.DeviceUnpaired"/>
  171. public ReadOnlyArray<InputDevice> pairedDevices
  172. {
  173. get
  174. {
  175. var userIndex = index;
  176. return new ReadOnlyArray<InputDevice>(s_AllPairedDevices, s_AllUserData[userIndex].deviceStartIndex,
  177. s_AllUserData[userIndex].deviceCount);
  178. }
  179. }
  180. /// <summary>
  181. /// Devices that were removed while they were still paired to the user.
  182. /// </summary>
  183. /// <remarks>
  184. ///
  185. /// This list is cleared once the user has either regained lost devices or has regained other devices
  186. /// such that the <see cref="controlScheme"/> is satisfied.
  187. /// </remarks>
  188. /// <seealso cref="InputUserChange.DeviceRegained"/>
  189. /// <seealso cref="InputUserChange.DeviceLost"/>
  190. public ReadOnlyArray<InputDevice> lostDevices
  191. {
  192. get
  193. {
  194. var userIndex = index;
  195. return new ReadOnlyArray<InputDevice>(s_AllLostDevices, s_AllUserData[userIndex].lostDeviceStartIndex,
  196. s_AllUserData[userIndex].lostDeviceCount);
  197. }
  198. }
  199. /// <summary>
  200. /// Actions associated with the user.
  201. /// </summary>
  202. /// <remarks>
  203. /// Associating actions with a user will synchronize the actions with the devices paired to the
  204. /// user. Also, it makes it possible to use support for control scheme activation (<see
  205. /// cref="ActivateControlScheme(InputControlScheme)"/> and related APIs like <see cref="controlScheme"/>
  206. /// and <see cref="controlSchemeMatch"/>).
  207. ///
  208. /// Note that is generally does not make sense for users to share actions. Instead, each user should
  209. /// receive a set of actions private to the user.
  210. /// </remarks>
  211. /// <seealso cref="AssociateActionsWithUser(IInputActionCollection)"/>
  212. /// <seealso cref="InputActionMap"/>
  213. /// <seealso cref="InputActionAsset"/>
  214. /// <seealso cref="InputUserChange.BindingsChanged"/>
  215. public IInputActionCollection actions => s_AllUserData[index].actions;
  216. /// <summary>
  217. /// The control scheme currently employed by the user.
  218. /// </summary>
  219. /// <remarks>
  220. /// This is null by default.
  221. ///
  222. /// Any time the value of this property changes (whether by <see cref="SetControlScheme"/>
  223. /// or by automatic switching), a notification is sent on <see cref="onChange"/> with
  224. /// <see cref="InputUserChange.ControlSchemeChanged"/>.
  225. ///
  226. /// Be aware that using control schemes with InputUsers requires <see cref="actions"/> to
  227. /// be set, i.e. input actions to be associated with the user (<see
  228. /// cref="AssociateActionsWithUser(IInputActionCollection)"/>).
  229. /// </remarks>
  230. /// <seealso cref="ActivateControlScheme(string)"/>
  231. /// <seealso cref="ActivateControlScheme(InputControlScheme)"/>
  232. /// <seealso cref="InputUserChange.ControlSchemeChanged"/>
  233. public InputControlScheme? controlScheme => s_AllUserData[index].controlScheme;
  234. /// <summary>
  235. /// The result of matching the device requirements given by <see cref="controlScheme"/> against
  236. /// the devices paired to the user (<see cref="pairedDevices"/>).
  237. /// </summary>
  238. /// <remarks>
  239. /// When devices are paired to or unpaired from a user, as well as when a new control scheme is
  240. /// activated on a user, this property is updated automatically.
  241. /// </remarks>
  242. /// <seealso cref="InputControlScheme.deviceRequirements"/>
  243. /// <seealso cref="InputControlScheme.PickDevicesFrom{TDevices}"/>
  244. public InputControlScheme.MatchResult controlSchemeMatch => s_AllUserData[index].controlSchemeMatch;
  245. /// <summary>
  246. /// Whether the user is missing devices required by the <see cref="controlScheme"/> activated
  247. /// on the user.
  248. /// </summary>
  249. /// <remarks>
  250. /// This will only take required devices into account. Device requirements marked optional (<see
  251. /// cref="InputControlScheme.DeviceRequirement.isOptional"/>) will not be considered missing
  252. /// devices if they cannot be satisfied based on the devices paired to the user.
  253. /// </remarks>
  254. /// <seealso cref="InputControlScheme.deviceRequirements"/>
  255. public bool hasMissingRequiredDevices => s_AllUserData[index].controlSchemeMatch.hasMissingRequiredDevices;
  256. /// <summary>
  257. /// List of all current users.
  258. /// </summary>
  259. /// <remarks>
  260. /// Use <see cref="PerformPairingWithDevice"/> to add new users and <see cref="UnpairDevicesAndRemoveUser"/> to
  261. /// remove users.
  262. ///
  263. /// Note that this array does not necessarily correspond to the list of users present at the platform level
  264. /// (e.g. Xbox and PS4). There can be users present at the platform level that are not present in this array
  265. /// (e.g. because they are not joined to the game) and users can even be present more than once (e.g. if
  266. /// playing on the user account but as two different players in the game). Also, there can be users in the array
  267. /// that are not present at the platform level.
  268. /// </remarks>
  269. /// <seealso cref="PerformPairingWithDevice"/>
  270. /// <seealso cref="UnpairDevicesAndRemoveUser"/>
  271. public static ReadOnlyArray<InputUser> all => new ReadOnlyArray<InputUser>(s_AllUsers, 0, s_AllUserCount);
  272. /// <summary>
  273. /// Event that is triggered when the <see cref="InputUser">user</see> setup in the system
  274. /// changes.
  275. /// </summary>
  276. /// <remarks>
  277. /// Each notification receives the user that was affected by the change and, in the form of <see cref="InputUserChange"/>,
  278. /// a description of what has changed about the user. The third parameter may be null but if the change will be related
  279. /// to an input device, will reference the device involved in the change.
  280. /// </remarks>
  281. public static event Action<InputUser, InputUserChange, InputDevice> onChange
  282. {
  283. add
  284. {
  285. if (value == null)
  286. throw new ArgumentNullException(nameof(value));
  287. s_OnChange.AppendWithCapacity(value);
  288. }
  289. remove
  290. {
  291. if (value == null)
  292. throw new ArgumentNullException(nameof(value));
  293. var index = s_OnChange.IndexOf(value);
  294. if (index != -1)
  295. s_OnChange.RemoveAtWithCapacity(index);
  296. }
  297. }
  298. /// <summary>
  299. /// Event that is triggered when a device is used that is not currently paired to any user.
  300. /// </summary>
  301. /// <remarks>
  302. /// A device is considered "used" when it has magnitude (<see cref="InputControl.EvaluateMagnitude()"/>) greater than zero
  303. /// on a control that is not noisy (<see cref="InputControl.noisy"/>) and not synthetic (i.e. not a control that is
  304. /// "made up" like <see cref="Keyboard.anyKey"/>; <see cref="InputControl.synthetic"/>).
  305. ///
  306. /// Detecting the use of unpaired devices has a non-zero cost. While multiple levels of tests are applied to try to
  307. /// cheaply ignore devices that have events sent to them that do not contain user activity, finding out whether
  308. /// a device had real user activity will eventually require going through the device control by control.
  309. ///
  310. /// To enable detection of the use of unpaired devices, set <see cref="listenForUnpairedDeviceActivity"/> to true.
  311. /// It is disabled by default.
  312. ///
  313. /// The callback is invoked for each non-leaf, non-synthetic, non-noisy control that has been actuated on the device.
  314. /// It being restricted to non-leaf controls means that if, say, the stick on a gamepad is actuated in both X and Y
  315. /// direction, you will see two calls: one with stick/x and one with stick/y.
  316. ///
  317. /// The reason that the callback is invoked for each individual control is that pairing often relies on checking
  318. /// for specific kinds of interactions. For example, a pairing callback may listen exclusively for button presses.
  319. ///
  320. /// Note that whether the use of unpaired devices leads to them getting paired is under the control of the application.
  321. /// If the device should be paired, invoke <see cref="PerformPairingWithDevice"/> from the callback. If you do so,
  322. /// no further callbacks will get triggered for other controls that may have been actuated in the same event.
  323. ///
  324. /// Be aware that the callback is fired <em>before</em> input is actually incorporated into the device (it is
  325. /// indirectly triggered from <see cref="InputSystem.onEvent"/>). This means at the time the callback is run,
  326. /// the state of the given device does not yet have the input that triggered the callback. For this reason, the
  327. /// callback receives a second argument that references the event from which the use of an unpaired device was
  328. /// detected.
  329. ///
  330. /// What this sequence allows is to make changes to the system before the input is processed. For example, an
  331. /// action that is enabled as part of the callback will subsequently respond to the input that triggered the
  332. /// callback.
  333. ///
  334. /// <example>
  335. /// <code>
  336. /// // Activate support for listening to device activity.
  337. /// ++InputUser.listenForUnpairedDeviceActivity;
  338. ///
  339. /// // When a button on an unpaired device is pressed, pair the device to a new
  340. /// // or existing user.
  341. /// InputUser.onUnpairedDeviceUsed +=
  342. /// usedControl =>
  343. /// {
  344. /// // Only react to button presses on unpaired devices.
  345. /// if (!(usedControl is ButtonControl))
  346. /// return;
  347. ///
  348. /// // Pair the device to a user.
  349. /// InputUser.PerformPairingWithDevice(usedControl.device);
  350. /// };
  351. /// </code>
  352. /// </example>
  353. ///
  354. /// Another possible use of the callback is for implementing automatic control scheme switching for a user such that
  355. /// the user can, for example, switch from keyboard&amp;mouse to gamepad seamlessly by simply picking up the gamepad
  356. /// and starting to play.
  357. /// </remarks>
  358. public static event Action<InputControl, InputEventPtr> onUnpairedDeviceUsed
  359. {
  360. add
  361. {
  362. if (value == null)
  363. throw new ArgumentNullException(nameof(value));
  364. s_OnUnpairedDeviceUsed.AppendWithCapacity(value);
  365. if (s_ListenForUnpairedDeviceActivity > 0)
  366. HookIntoEvents();
  367. }
  368. remove
  369. {
  370. if (value == null)
  371. throw new ArgumentNullException(nameof(value));
  372. var index = s_OnUnpairedDeviceUsed.IndexOf(value);
  373. if (index != -1)
  374. s_OnUnpairedDeviceUsed.RemoveAtWithCapacity(index);
  375. if (s_OnUnpairedDeviceUsed.length == 0)
  376. UnhookFromDeviceStateChange();
  377. }
  378. }
  379. /// <summary>
  380. /// Whether to listen for user activity on currently unpaired devices and invoke <see cref="onUnpairedDeviceUsed"/>
  381. /// if such activity is detected.
  382. /// </summary>
  383. /// <remarks>
  384. /// This is off by default.
  385. ///
  386. /// Note that enabling this has a non-zero cost. Whenever the state changes of a device that is not currently paired
  387. /// to a user, the system has to spend time figuring out whether there was a meaningful change or whether it's just
  388. /// noise on the device.
  389. ///
  390. /// This is an integer rather than a bool to allow multiple systems to concurrently use to listen for unpaired
  391. /// device activity without treading on each other when enabling/disabling the code path.
  392. /// </remarks>
  393. /// <seealso cref="onUnpairedDeviceUsed"/>
  394. /// <seealso cref="pairedDevices"/>
  395. /// <seealso cref="PerformPairingWithDevice"/>
  396. public static int listenForUnpairedDeviceActivity
  397. {
  398. get => s_ListenForUnpairedDeviceActivity;
  399. set
  400. {
  401. if (value < 0)
  402. throw new ArgumentOutOfRangeException(nameof(value), "Cannot be negative");
  403. if (value > 0 && s_OnUnpairedDeviceUsed.length > 0)
  404. HookIntoEvents();
  405. else if (value == 0)
  406. UnhookFromDeviceStateChange();
  407. s_ListenForUnpairedDeviceActivity = value;
  408. }
  409. }
  410. /// <summary>
  411. /// Associate a collection of <see cref="InputAction"/>s with the user.
  412. /// </summary>
  413. /// <param name="actions">Actions to associate with the user, either an <see cref="InputActionAsset"/>
  414. /// or an <see cref="InputActionMap"/>. Can be <c>null</c> to unset the current association.</param>
  415. /// <exception cref="InvalidOperationException">The user instance is invalid.</exception>
  416. /// <remarks>
  417. /// Associating actions with a user will ensure that the <see cref="IInputActionCollection.devices"/> and
  418. /// <see cref="IInputActionCollection.bindingMask"/> property of the action collection are automatically
  419. /// kept in sync with the device paired to the user (see <see cref="pairedDevices"/>) and the control
  420. /// scheme active on the user (see <see cref="controlScheme"/>).
  421. ///
  422. /// <example>
  423. /// <code>
  424. /// var gamepad = Gamepad.all[0];
  425. ///
  426. /// // Pair the gamepad to a user.
  427. /// var user = InputUser.PerformPairingWithDevice(gamepad);
  428. ///
  429. /// // Create an action map with an action.
  430. /// var actionMap = new InputActionMap():
  431. /// actionMap.AddAction("Fire", binding: "&lt;Gamepad&gt;/buttonSouth");
  432. ///
  433. /// // Associate the action map with the user (the same works for an asset).
  434. /// user.AssociateActionsWithUser(actionMap);
  435. ///
  436. /// // Now the action map is restricted to just the gamepad that is paired
  437. /// // with the user, even if there are more gamepads currently connected.
  438. /// </code>
  439. /// </example>
  440. /// </remarks>
  441. /// <seealso cref="actions"/>
  442. public void AssociateActionsWithUser(IInputActionCollection actions)
  443. {
  444. var userIndex = index; // Throws if user is invalid.
  445. if (s_AllUserData[userIndex].actions == actions)
  446. return;
  447. // If we already had actions associated, reset the binding mask and device list.
  448. var oldActions = s_AllUserData[userIndex].actions;
  449. if (oldActions != null)
  450. {
  451. oldActions.devices = null;
  452. oldActions.bindingMask = null;
  453. }
  454. s_AllUserData[userIndex].actions = actions;
  455. // If we've switched to a different set of actions, synchronize our state.
  456. if (actions != null)
  457. {
  458. HookIntoActionChange();
  459. actions.devices = pairedDevices;
  460. if (s_AllUserData[userIndex].controlScheme != null)
  461. ActivateControlSchemeInternal(userIndex, s_AllUserData[userIndex].controlScheme.Value);
  462. }
  463. }
  464. public ControlSchemeChangeSyntax ActivateControlScheme(string schemeName)
  465. {
  466. // Look up control scheme by name in actions.
  467. var scheme = new InputControlScheme();
  468. if (!string.IsNullOrEmpty(schemeName))
  469. {
  470. var userIndex = index; // Throws if user is invalid.
  471. // Need actions to be available to be able to activate control schemes
  472. // by name only.
  473. if (s_AllUserData[userIndex].actions == null)
  474. throw new InvalidOperationException(
  475. $"Cannot set control scheme '{schemeName}' by name on user #{userIndex} as not actions have been associated with the user yet (AssociateActionsWithUser)");
  476. var controlSchemes = s_AllUserData[userIndex].actions.controlSchemes;
  477. for (var i = 0; i < controlSchemes.Count; ++i)
  478. if (string.Compare(controlSchemes[i].name, schemeName,
  479. StringComparison.InvariantCultureIgnoreCase) == 0)
  480. {
  481. scheme = controlSchemes[i];
  482. break;
  483. }
  484. // Throw if we can't find it.
  485. if (scheme == default)
  486. throw new ArgumentException(
  487. $"Cannot find control scheme '{schemeName}' in actions '{s_AllUserData[userIndex].actions}'");
  488. }
  489. return ActivateControlScheme(scheme);
  490. }
  491. public ControlSchemeChangeSyntax ActivateControlScheme(InputControlScheme scheme)
  492. {
  493. var userIndex = index; // Throws if user is invalid.
  494. if (s_AllUserData[userIndex].controlScheme != scheme ||
  495. (scheme == default && s_AllUserData[userIndex].controlScheme != null))
  496. {
  497. ActivateControlSchemeInternal(userIndex, scheme);
  498. Notify(userIndex, InputUserChange.ControlSchemeChanged, null);
  499. }
  500. return new ControlSchemeChangeSyntax {m_UserIndex = userIndex};
  501. }
  502. private void ActivateControlSchemeInternal(int userIndex, InputControlScheme scheme)
  503. {
  504. var isEmpty = scheme == default;
  505. if (isEmpty)
  506. s_AllUserData[userIndex].controlScheme = null;
  507. else
  508. s_AllUserData[userIndex].controlScheme = scheme;
  509. if (s_AllUserData[userIndex].actions != null)
  510. {
  511. if (isEmpty)
  512. {
  513. s_AllUserData[userIndex].actions.bindingMask = null;
  514. s_AllUserData[userIndex].controlSchemeMatch.Dispose();
  515. s_AllUserData[userIndex].controlSchemeMatch = new InputControlScheme.MatchResult();
  516. }
  517. else
  518. {
  519. s_AllUserData[userIndex].actions.bindingMask = new InputBinding {groups = scheme.bindingGroup};
  520. UpdateControlSchemeMatch(userIndex);
  521. }
  522. }
  523. }
  524. /// <summary>
  525. /// Unpair a single device from the user.
  526. /// </summary>
  527. /// <param name="device">Device to unpair from the user. If the device is not currently paired to the user,
  528. /// the method does nothing.</param>
  529. /// <exception cref="ArgumentNullException"><paramref name="device"/> is <c>null</c>.</exception>
  530. /// <remarks>
  531. /// If actions are associated with the user (<see cref="actions"/>), the list of devices used by the
  532. /// actions (<see cref="IInputActionCollection.devices"/>) is automatically updated.
  533. ///
  534. /// If a control scheme is activated on the user (<see cref="controlScheme"/>), <see cref="controlSchemeMatch"/>
  535. /// is automatically updated.
  536. ///
  537. /// Sends <see cref="InputUserChange.DeviceUnpaired"/> through <see cref="onChange"/>.
  538. /// </remarks>
  539. /// <seealso cref="PerformPairingWithDevice"/>
  540. /// <seealso cref="pairedDevices"/>
  541. /// <seealso cref="UnpairDevices"/>
  542. /// <seealso cref="UnpairDevicesAndRemoveUser"/>
  543. /// <seealso cref="InputUserChange.DeviceUnpaired"/>
  544. public void UnpairDevice(InputDevice device)
  545. {
  546. if (device == null)
  547. throw new ArgumentNullException(nameof(device));
  548. var userIndex = index; // Throws if user is invalid.
  549. // Ignore if not currently paired to user.
  550. if (!pairedDevices.ContainsReference(device))
  551. return;
  552. RemoveDeviceFromUser(userIndex, device);
  553. }
  554. /// <summary>
  555. /// Unpair all devices from the user.
  556. /// </summary>
  557. /// <remarks>
  558. /// If actions are associated with the user (<see cref="actions"/>), the list of devices used by the
  559. /// actions (<see cref="IInputActionCollection.devices"/>) is automatically updated.
  560. ///
  561. /// If a control scheme is activated on the user (<see cref="controlScheme"/>), <see cref="controlSchemeMatch"/>
  562. /// is automatically updated.
  563. ///
  564. /// Sends <see cref="InputUserChange.DeviceUnpaired"/> through <see cref="onChange"/> for every device
  565. /// unpaired from the user.
  566. /// </remarks>
  567. /// <seealso cref="PerformPairingWithDevice"/>
  568. /// <seealso cref="pairedDevices"/>
  569. /// <seealso cref="UnpairDevice"/>
  570. /// <seealso cref="UnpairDevicesAndRemoveUser"/>
  571. /// <seealso cref="InputUserChange.DeviceUnpaired"/>
  572. public void UnpairDevices()
  573. {
  574. var userIndex = index; // Throws if user is invalid.
  575. // Reset device count so it appears all devices are gone from the user. We still want to send
  576. // notifications one by one, so we can't yet remove the devices from s_AllPairedDevices.
  577. var deviceCount = s_AllUserData[userIndex].deviceCount;
  578. var deviceStartIndex = s_AllUserData[userIndex].deviceStartIndex;
  579. s_AllUserData[userIndex].deviceCount = 0;
  580. s_AllUserData[userIndex].deviceStartIndex = 0;
  581. // Update actions, if necessary.
  582. var actions = s_AllUserData[userIndex].actions;
  583. if (actions != null)
  584. actions.devices = null;
  585. // Update control scheme, if necessary.
  586. if (s_AllUserData[userIndex].controlScheme != null)
  587. UpdateControlSchemeMatch(userIndex);
  588. // Notify.
  589. for (var i = 0; i < deviceCount; ++i)
  590. Notify(userIndex, InputUserChange.DeviceUnpaired, s_AllPairedDevices[deviceStartIndex + i]);
  591. // Remove.
  592. ArrayHelpers.EraseSliceWithCapacity(ref s_AllPairedDevices, ref s_AllPairedDeviceCount, deviceStartIndex, deviceCount);
  593. if (s_AllUserData[userIndex].lostDeviceCount > 0)
  594. {
  595. ArrayHelpers.EraseSliceWithCapacity(ref s_AllLostDevices, ref s_AllLostDeviceCount,
  596. s_AllUserData[userIndex].lostDeviceStartIndex, s_AllUserData[userIndex].lostDeviceCount);
  597. s_AllUserData[userIndex].lostDeviceCount = 0;
  598. s_AllUserData[userIndex].lostDeviceStartIndex = 0;
  599. }
  600. // Adjust indices of other users.
  601. for (var i = 0; i < s_AllUserCount; ++i)
  602. {
  603. if (s_AllUserData[i].deviceStartIndex <= deviceStartIndex)
  604. continue;
  605. s_AllUserData[i].deviceStartIndex -= deviceCount;
  606. }
  607. }
  608. /// <summary>
  609. /// Unpair all devices from the user and remove the user.
  610. /// </summary>
  611. /// <remarks>
  612. /// If actions are associated with the user (<see cref="actions"/>), the list of devices used by the
  613. /// actions (<see cref="IInputActionCollection.devices"/>) is reset as is the binding mask (<see
  614. /// cref="IInputActionCollection.bindingMask"/>) in case a control scheme is activated on the user.
  615. ///
  616. /// Sends <see cref="InputUserChange.DeviceUnpaired"/> through <see cref="onChange"/> for every device
  617. /// unpaired from the user.
  618. ///
  619. /// Sends <see cref="InputUserChange.Removed"/>.
  620. /// </remarks>
  621. /// <seealso cref="PerformPairingWithDevice"/>
  622. /// <seealso cref="pairedDevices"/>
  623. /// <seealso cref="UnpairDevice"/>
  624. /// <seealso cref="UnpairDevicesAndRemoveUser"/>
  625. /// <seealso cref="InputUserChange.DeviceUnpaired"/>
  626. /// <seealso cref="InputUserChange.Removed"/>
  627. public void UnpairDevicesAndRemoveUser()
  628. {
  629. UnpairDevices();
  630. var userIndex = index;
  631. RemoveUser(userIndex);
  632. m_Id = default;
  633. }
  634. /// <summary>
  635. /// Return a list of all currently added devices that are not paired to any user.
  636. /// </summary>
  637. /// <returns>A (possibly empty) list of devices that are currently not paired to a user.</returns>
  638. /// <remarks>
  639. /// The resulting list uses <see cref="Allocator.Temp"> temporary, unmanaged memory</see>. If not disposed of
  640. /// explicitly, the list will automatically be deallocated at the end of the frame and will become unusable.
  641. /// </remarks>
  642. /// <seealso cref="InputSystem.devices"/>
  643. /// <seealso cref="pairedDevices"/>
  644. /// <seealso cref="PerformPairingWithDevice"/>
  645. public static InputControlList<InputDevice> GetUnpairedInputDevices()
  646. {
  647. var list = new InputControlList<InputDevice>(Allocator.Temp);
  648. GetUnpairedInputDevices(ref list);
  649. return list;
  650. }
  651. /// <summary>
  652. /// Add all currently added devices that are not paired to any user to <paramref name="list"/>.
  653. /// </summary>
  654. /// <param name="list">List to add the devices to. Devices will be added to the end.</param>
  655. /// <returns>Number of devices added to <paramref name="list"/>.</returns>
  656. /// <seealso cref="InputSystem.devices"/>
  657. /// <seealso cref="pairedDevices"/>
  658. /// <seealso cref="PerformPairingWithDevice"/>
  659. public static int GetUnpairedInputDevices(ref InputControlList<InputDevice> list)
  660. {
  661. var countBefore = list.Count;
  662. foreach (var device in InputSystem.devices)
  663. {
  664. // If it's in s_AllPairedDevices, there is *some* user that is using the device.
  665. // We don't care which one it is here.
  666. if (ArrayHelpers.ContainsReference(s_AllPairedDevices, s_AllPairedDeviceCount, device))
  667. continue;
  668. list.Add(device);
  669. }
  670. return list.Count - countBefore;
  671. }
  672. /// <summary>
  673. /// Find the user (if any) that <paramref name="device"/> is currently paired to.
  674. /// </summary>
  675. /// <param name="device">An input device.</param>
  676. /// <returns>The user that <paramref name="device"/> is currently paired to or <c>null</c> if the device
  677. /// is not currently paired to an user.</returns>
  678. /// <remarks>
  679. /// Note that multiple users may be paired to the same device. If that is the case for <paramref name="device"/>,
  680. /// the method will return one of the users with no guarantee which one it is.
  681. ///
  682. /// To find all users paired to a device requires manually going through the list of users and their paired
  683. /// devices.
  684. /// </remarks>
  685. /// <exception cref="ArgumentNullException"><paramref name="device"/> is <c>null</c>.</exception>
  686. /// <seealso cref="pairedDevices"/>
  687. /// <seealso cref="PerformPairingWithDevice"/>
  688. public static InputUser? FindUserPairedToDevice(InputDevice device)
  689. {
  690. if (device == null)
  691. throw new ArgumentNullException(nameof(device));
  692. var userIndex = TryFindUserIndex(device);
  693. if (userIndex == -1)
  694. return null;
  695. return s_AllUsers[userIndex];
  696. }
  697. public static InputUser? FindUserByAccount(InputUserAccountHandle platformUserAccountHandle)
  698. {
  699. if (platformUserAccountHandle == default(InputUserAccountHandle))
  700. throw new ArgumentException("Empty platform user account handle", nameof(platformUserAccountHandle));
  701. var userIndex = TryFindUserIndex(platformUserAccountHandle);
  702. if (userIndex == -1)
  703. return null;
  704. return s_AllUsers[userIndex];
  705. }
  706. public static InputUser CreateUserWithoutPairedDevices()
  707. {
  708. var userIndex = AddUser();
  709. return s_AllUsers[userIndex];
  710. }
  711. ////REVIEW: allow re-adding a user through this method?
  712. /// <summary>
  713. /// Pair the given device to a user.
  714. /// </summary>
  715. /// <param name="device">Device to pair to a user.</param>
  716. /// <param name="user">Optional parameter. If given, instead of creating a new user to pair the device
  717. /// to, the device is paired to the given user.</param>
  718. /// <param name="options">Optional set of options to modify pairing behavior.</param>
  719. /// <remarks>
  720. /// By default, a new user is created and <paramref name="device"/> is added <see cref="pairedDevices"/>
  721. /// of the user and <see cref="InputUserChange.DevicePaired"/> is sent on <see cref="onChange"/>.
  722. ///
  723. /// If a valid user is supplied to <paramref name="user"/>, the device is paired to the given user instead
  724. /// of creating a new user. By default, the device is added to the list of already paired devices for the user.
  725. /// This can be changed by using <see cref="InputUserPairingOptions.UnpairCurrentDevicesFromUser"/> which causes
  726. /// devices currently paired to the user to first be unpaired.
  727. ///
  728. /// The method will not prevent pairing of the same device to multiple users.
  729. ///
  730. /// Note that if the user has an associated set of actions (<see cref="actions"/>), the list of devices on the
  731. /// actions (<see cref="IInputActionCollection.devices"/>) will automatically be updated meaning that the newly
  732. /// paired devices will automatically reflect in the set of devices available to the user's actions. If the
  733. /// user has a control scheme that is currently activated (<see cref="controlScheme"/>), then <see cref="controlSchemeMatch"/>
  734. /// will also automatically update to reflect the matching of devices to the control scheme's device requirements.
  735. ///
  736. /// If the given device is associated with a user account at the platform level (queried through
  737. /// <see cref="QueryPairedUserAccountCommand"/>), the user's platform account details (<see cref="platformUserAccountHandle"/>,
  738. /// <see cref="platformUserAccountName"/>, and <see cref="platformUserAccountId"/>) are updated accordingly. In this case,
  739. /// <see cref="InputUserChange.AccountChanged"/> or <see cref="InputUserChange.AccountNameChanged"/> may be signalled.
  740. /// through <see cref="onChange"/>.
  741. ///
  742. /// If the given device is not associated with a user account at the platform level, but it does
  743. /// respond to <see cref="InitiateUserAccountPairingCommand"/>, then the device is NOT immediately paired
  744. /// to the user. Instead, pairing is deferred to until after an account selection has been made by the user.
  745. /// In this case, <see cref="InputUserChange.AccountSelectionInProgress"/> will be signalled through <see cref="onChange"/>
  746. /// and <see cref="InputUserChange.AccountChanged"/> will be signalled once the user has selected an account or
  747. /// <see cref="InputUserChange.AccountSelectionCanceled"/> will be signalled if the user cancels account
  748. /// selection. The device will be paired to the user once account selection is complete.
  749. ///
  750. /// This behavior is most useful on Xbox and Switch to require the user to choose which account to play with. Note that
  751. /// if the device is already associated with a user account, account selection will not be initiated. However,
  752. /// it can be explicitly forced to be performed by using <see
  753. /// cref="InputUserPairingOptions.ForcePlatformUserAccountSelection"/>. This is useful,
  754. /// for example, to allow the user to explicitly switch accounts.
  755. ///
  756. /// On Xbox and Switch, to permit playing even on devices that do not currently have an associated user account,
  757. /// use <see cref="InputUserPairingOptions.ForceNoPlatformUserAccountSelection"/>.
  758. ///
  759. /// On PS4, devices will always have associated user accounts meaning that the returned InputUser will always
  760. /// have updated platform account details.
  761. ///
  762. /// Note that user account queries and initiating account selection can be intercepted by the application. For
  763. /// example, on Switch where user account pairing is not stored at the platform level, one can, for example, both
  764. /// implement custom pairing logic as well as a custom account selection UI by intercepting <see cref="QueryPairedUserAccountCommand"/>
  765. /// and <seealso cref="InitiateUserAccountPairingCommand"/>.
  766. ///
  767. /// <example>
  768. /// <code>
  769. /// InputSystem.onDeviceCommand +=
  770. /// (device, commandPtr, runtime) =>
  771. /// {
  772. /// // Dealing with InputDeviceCommands requires handling raw pointers.
  773. /// unsafe
  774. /// {
  775. /// // We're only looking for QueryPairedUserAccountCommand and InitiateUserAccountPairingCommand here.
  776. /// if (commandPtr->type != QueryPairedUserAccountCommand.Type && commandPtr->type != InitiateUserAccountPairingCommand)
  777. /// return null; // Command not handled.
  778. ///
  779. /// // Check if device is the one your interested in. As an example, we look for Switch gamepads
  780. /// // here.
  781. /// if (!(device is Npad))
  782. /// return null; // Command not handled.
  783. ///
  784. /// // If it's a QueryPairedUserAccountCommand, see if we have a user ID to use with the Npad
  785. /// // based on last time the application ran.
  786. /// if (commandPtr->type == QueryPairedUserAccountCommand.Type)
  787. /// {
  788. /// ////TODO
  789. /// }
  790. /// };
  791. /// </code>
  792. /// </example>
  793. /// </remarks>
  794. /// <example>
  795. /// <code>
  796. /// // Pair device to new user.
  797. /// var user = InputUser.PerformPairingWithDevice(wand1);
  798. ///
  799. /// // Pair another device to the same user.
  800. /// InputUser.PerformPairingWithDevice(wand2, user: user);
  801. /// </code>
  802. /// </example>
  803. /// <seealso cref="pairedDevices"/>
  804. /// <seealso cref="UnpairDevice"/>
  805. /// <seealso cref="UnpairDevices"/>
  806. /// <seealso cref="UnpairDevicesAndRemoveUser"/>
  807. /// <seealso cref="InputUserChange.DevicePaired"/>
  808. public static InputUser PerformPairingWithDevice(InputDevice device,
  809. InputUser user = default,
  810. InputUserPairingOptions options = InputUserPairingOptions.None)
  811. {
  812. if (device == null)
  813. throw new ArgumentNullException(nameof(device));
  814. if (user != default && !user.valid)
  815. throw new ArgumentException("Invalid user", nameof(user));
  816. // Create new user, if needed.
  817. int userIndex;
  818. if (user == default)
  819. {
  820. userIndex = AddUser();
  821. }
  822. else
  823. {
  824. // We have an existing user.
  825. userIndex = user.index;
  826. // See if we're supposed to clear out the user's currently paired devices first.
  827. if ((options & InputUserPairingOptions.UnpairCurrentDevicesFromUser) != 0)
  828. user.UnpairDevices();
  829. // Ignore call if device is already paired to user.
  830. if (user.pairedDevices.ContainsReference(device))
  831. {
  832. // Still might have to initiate user account selection.
  833. if ((options & InputUserPairingOptions.ForcePlatformUserAccountSelection) != 0)
  834. InitiateUserAccountSelection(userIndex, device, options);
  835. return user;
  836. }
  837. }
  838. // Handle the user account side of pairing.
  839. var accountSelectionInProgress = InitiateUserAccountSelection(userIndex, device, options);
  840. // Except if we have initiate user account selection, pair the device to
  841. // to the user now.
  842. if (!accountSelectionInProgress)
  843. AddDeviceToUser(userIndex, device);
  844. return s_AllUsers[userIndex];
  845. }
  846. private static bool InitiateUserAccountSelection(int userIndex, InputDevice device,
  847. InputUserPairingOptions options)
  848. {
  849. // See if there's a platform user account we can get from the device.
  850. // NOTE: We don't query the current user account if the caller has opted to force account selection.
  851. var queryUserAccountResult =
  852. (options & InputUserPairingOptions.ForcePlatformUserAccountSelection) == 0
  853. ? UpdatePlatformUserAccount(userIndex, device)
  854. : 0;
  855. ////REVIEW: what should we do if there already is an account selection in progress? InvalidOperationException?
  856. // If the device supports user account selection but we didn't get one,
  857. // try to initiate account selection.
  858. if ((options & InputUserPairingOptions.ForcePlatformUserAccountSelection) != 0 ||
  859. (queryUserAccountResult != InputDeviceCommand.GenericFailure &&
  860. (queryUserAccountResult & (long)QueryPairedUserAccountCommand.Result.DevicePairedToUserAccount) == 0 &&
  861. (options & InputUserPairingOptions.ForceNoPlatformUserAccountSelection) == 0))
  862. {
  863. if (InitiateUserAccountSelectionAtPlatformLevel(device))
  864. {
  865. s_AllUserData[userIndex].flags |= UserFlags.UserAccountSelectionInProgress;
  866. s_OngoingAccountSelections.Append(
  867. new OngoingAccountSelection
  868. {
  869. device = device,
  870. userId = s_AllUsers[userIndex].id,
  871. });
  872. // Make sure we receive a notification for the configuration event.
  873. HookIntoDeviceChange();
  874. // Tell listeners that we started an account selection.
  875. Notify(userIndex, InputUserChange.AccountSelectionInProgress, device);
  876. return true;
  877. }
  878. }
  879. return false;
  880. }
  881. public bool Equals(InputUser other)
  882. {
  883. return m_Id == other.m_Id;
  884. }
  885. public override bool Equals(object obj)
  886. {
  887. if (ReferenceEquals(null, obj))
  888. return false;
  889. return obj is InputUser && Equals((InputUser)obj);
  890. }
  891. public override int GetHashCode()
  892. {
  893. return (int)m_Id;
  894. }
  895. public static bool operator==(InputUser left, InputUser right)
  896. {
  897. return left.m_Id == right.m_Id;
  898. }
  899. public static bool operator!=(InputUser left, InputUser right)
  900. {
  901. return left.m_Id != right.m_Id;
  902. }
  903. /// <summary>
  904. /// Add a new user.
  905. /// </summary>
  906. /// <returns>Index of the newly created user.</returns>
  907. /// <remarks>
  908. /// Adding a user sends a notification with <see cref="InputUserChange.Added"/> through <see cref="onChange"/>.
  909. ///
  910. /// The user will start out with no devices and no actions assigned.
  911. ///
  912. /// The user is added to <see cref="all"/>.
  913. /// </remarks>
  914. private static int AddUser()
  915. {
  916. var id = ++s_LastUserId;
  917. // Add to list.
  918. var userCount = s_AllUserCount;
  919. ArrayHelpers.AppendWithCapacity(ref s_AllUsers, ref userCount, new InputUser {m_Id = id});
  920. var userIndex = ArrayHelpers.AppendWithCapacity(ref s_AllUserData, ref s_AllUserCount, new UserData());
  921. // Send notification.
  922. Notify(userIndex, InputUserChange.Added, null);
  923. return userIndex;
  924. }
  925. /// <summary>
  926. /// Remove an active user.
  927. /// </summary>
  928. /// <param name="userIndex">Index of active user.</param>
  929. /// <remarks>
  930. /// Removing a user also unassigns all currently assigned devices from the user. On completion of this
  931. /// method, <see cref="pairedDevices"/> of <paramref name="user"/> will be empty.
  932. /// </remarks>
  933. private static void RemoveUser(int userIndex)
  934. {
  935. Debug.Assert(userIndex >= 0 && userIndex < s_AllUserCount);
  936. Debug.Assert(s_AllUserData[userIndex].deviceCount == 0, "User must not have paired devices still");
  937. // Reset data from control scheme.
  938. if (s_AllUserData[userIndex].controlScheme != null)
  939. {
  940. if (s_AllUserData[userIndex].actions != null)
  941. s_AllUserData[userIndex].actions.bindingMask = null;
  942. }
  943. s_AllUserData[userIndex].controlSchemeMatch.Dispose();
  944. // Remove lost devices.
  945. var lostDeviceCount = s_AllUserData[userIndex].lostDeviceCount;
  946. if (lostDeviceCount > 0)
  947. {
  948. ArrayHelpers.EraseSliceWithCapacity(ref s_AllLostDevices, ref s_AllLostDeviceCount,
  949. s_AllUserData[userIndex].lostDeviceStartIndex, lostDeviceCount);
  950. }
  951. // Remove account selections that are in progress.
  952. for (var i = 0; i < s_OngoingAccountSelections.length; ++i)
  953. {
  954. if (s_OngoingAccountSelections[i].userId != s_AllUsers[userIndex].id)
  955. continue;
  956. s_OngoingAccountSelections.RemoveAtByMovingTailWithCapacity(i);
  957. --i;
  958. }
  959. // Send notification (do before we actually remove the user).
  960. Notify(userIndex, InputUserChange.Removed, null);
  961. // Remove.
  962. var userCount = s_AllUserCount;
  963. ArrayHelpers.EraseAtWithCapacity(s_AllUsers, ref userCount, userIndex);
  964. ArrayHelpers.EraseAtWithCapacity(s_AllUserData, ref s_AllUserCount, userIndex);
  965. // Remove our hook if we no longer need it.
  966. if (s_AllUserCount == 0)
  967. {
  968. UnhookFromDeviceChange();
  969. UnhookFromActionChange();
  970. }
  971. }
  972. private static void Notify(int userIndex, InputUserChange change, InputDevice device)
  973. {
  974. Debug.Assert(userIndex >= 0 && userIndex < s_AllUserCount);
  975. for (var i = 0; i < s_OnChange.length; ++i)
  976. s_OnChange[i](s_AllUsers[userIndex], change, device);
  977. }
  978. private static int TryFindUserIndex(uint userId)
  979. {
  980. Debug.Assert(userId != InvalidId);
  981. for (var i = 0; i < s_AllUserCount; ++i)
  982. {
  983. if (s_AllUsers[i].m_Id == userId)
  984. return i;
  985. }
  986. return -1;
  987. }
  988. private static int TryFindUserIndex(InputUserAccountHandle platformHandle)
  989. {
  990. Debug.Assert(platformHandle != new InputUserAccountHandle());
  991. for (var i = 0; i < s_AllUserCount; ++i)
  992. {
  993. if (s_AllUserData[i].platformUserAccountHandle == platformHandle)
  994. return i;
  995. }
  996. return -1;
  997. }
  998. /// <summary>
  999. /// Find the user (if any) that is currently assigned the given <paramref name="device"/>.
  1000. /// </summary>
  1001. /// <param name="device">An input device that has been added to the system.</param>
  1002. /// <returns>Index of the user that has <paramref name="device"/> among its <see cref="pairedDevices"/> or -1 if
  1003. /// no user is currently assigned the given device.</returns>
  1004. private static int TryFindUserIndex(InputDevice device)
  1005. {
  1006. Debug.Assert(device != null);
  1007. var indexOfDevice = ArrayHelpers.IndexOfReference(s_AllPairedDevices, device, s_AllPairedDeviceCount);
  1008. if (indexOfDevice == -1)
  1009. return -1;
  1010. for (var i = 0; i < s_AllUserCount; ++i)
  1011. {
  1012. var startIndex = s_AllUserData[i].deviceStartIndex;
  1013. if (startIndex <= indexOfDevice && indexOfDevice < startIndex + s_AllUserData[i].deviceCount)
  1014. return i;
  1015. }
  1016. return -1;
  1017. }
  1018. /// <summary>
  1019. /// Add the given device to the user as either a lost device or a paired device.
  1020. /// </summary>
  1021. /// <param name="userIndex"></param>
  1022. /// <param name="device"></param>
  1023. /// <param name="asLostDevice"></param>
  1024. private static void AddDeviceToUser(int userIndex, InputDevice device, bool asLostDevice = false, bool dontUpdateControlScheme = false)
  1025. {
  1026. Debug.Assert(userIndex >= 0 && userIndex < s_AllUserCount);
  1027. Debug.Assert(device != null);
  1028. if (asLostDevice)
  1029. Debug.Assert(!s_AllUsers[userIndex].lostDevices.ContainsReference(device));
  1030. else
  1031. Debug.Assert(!s_AllUsers[userIndex].pairedDevices.ContainsReference(device));
  1032. var deviceCount = asLostDevice
  1033. ? s_AllUserData[userIndex].lostDeviceCount
  1034. : s_AllUserData[userIndex].deviceCount;
  1035. var deviceStartIndex = asLostDevice
  1036. ? s_AllUserData[userIndex].lostDeviceStartIndex
  1037. : s_AllUserData[userIndex].deviceStartIndex;
  1038. ++s_PairingStateVersion;
  1039. // Move our devices to end of array.
  1040. if (deviceCount > 0)
  1041. {
  1042. ArrayHelpers.MoveSlice(asLostDevice ? s_AllLostDevices : s_AllPairedDevices, deviceStartIndex,
  1043. asLostDevice ? s_AllLostDeviceCount - deviceCount : s_AllPairedDeviceCount - deviceCount,
  1044. deviceCount);
  1045. // Adjust users that have been impacted by the change.
  1046. for (var i = 0; i < s_AllUserCount; ++i)
  1047. {
  1048. if (i == userIndex)
  1049. continue;
  1050. if ((asLostDevice ? s_AllUserData[i].lostDeviceStartIndex : s_AllUserData[i].deviceStartIndex) <= deviceStartIndex)
  1051. continue;
  1052. if (asLostDevice)
  1053. s_AllUserData[i].lostDeviceStartIndex -= deviceCount;
  1054. else
  1055. s_AllUserData[i].deviceStartIndex -= deviceCount;
  1056. }
  1057. }
  1058. // Append to array.
  1059. if (asLostDevice)
  1060. {
  1061. s_AllUserData[userIndex].lostDeviceStartIndex = s_AllLostDeviceCount - deviceCount;
  1062. ArrayHelpers.AppendWithCapacity(ref s_AllLostDevices, ref s_AllLostDeviceCount, device);
  1063. ++s_AllUserData[userIndex].lostDeviceCount;
  1064. }
  1065. else
  1066. {
  1067. s_AllUserData[userIndex].deviceStartIndex = s_AllPairedDeviceCount - deviceCount;
  1068. ArrayHelpers.AppendWithCapacity(ref s_AllPairedDevices, ref s_AllPairedDeviceCount, device);
  1069. ++s_AllUserData[userIndex].deviceCount;
  1070. // If the user has actions, sync the devices on them with what we have now.
  1071. var actions = s_AllUserData[userIndex].actions;
  1072. if (actions != null)
  1073. {
  1074. actions.devices = s_AllUsers[userIndex].pairedDevices;
  1075. // Also, if we have a control scheme, update the matching of device requirements
  1076. // against the device we now have.
  1077. if (!dontUpdateControlScheme && s_AllUserData[userIndex].controlScheme != null)
  1078. UpdateControlSchemeMatch(userIndex);
  1079. }
  1080. }
  1081. // Make sure we get OnDeviceChange notifications.
  1082. HookIntoDeviceChange();
  1083. // Let listeners know.
  1084. Notify(userIndex, asLostDevice ? InputUserChange.DeviceLost : InputUserChange.DevicePaired, device);
  1085. }
  1086. private static void RemoveDeviceFromUser(int userIndex, InputDevice device, bool asLostDevice = false)
  1087. {
  1088. Debug.Assert(userIndex >= 0 && userIndex < s_AllUserCount);
  1089. Debug.Assert(device != null);
  1090. var deviceIndex = asLostDevice
  1091. ? ArrayHelpers.IndexOfReference(s_AllLostDevices, device, s_AllLostDeviceCount)
  1092. : ArrayHelpers.IndexOfReference(s_AllPairedDevices, device, s_AllPairedDeviceCount);
  1093. Debug.Assert(deviceIndex != -1);
  1094. if (deviceIndex == -1)
  1095. {
  1096. // Device not in list. Ignore.
  1097. return;
  1098. }
  1099. if (asLostDevice)
  1100. {
  1101. ArrayHelpers.EraseAtWithCapacity(s_AllLostDevices, ref s_AllLostDeviceCount, deviceIndex);
  1102. --s_AllUserData[userIndex].lostDeviceCount;
  1103. }
  1104. else
  1105. {
  1106. --s_PairingStateVersion;
  1107. ArrayHelpers.EraseAtWithCapacity(s_AllPairedDevices, ref s_AllPairedDeviceCount, deviceIndex);
  1108. --s_AllUserData[userIndex].deviceCount;
  1109. }
  1110. // Adjust indices of other users.
  1111. for (var i = 0; i < s_AllUserCount; ++i)
  1112. {
  1113. if ((asLostDevice ? s_AllUserData[i].lostDeviceStartIndex : s_AllUserData[i].deviceStartIndex) <= deviceIndex)
  1114. continue;
  1115. if (asLostDevice)
  1116. --s_AllUserData[i].lostDeviceStartIndex;
  1117. else
  1118. --s_AllUserData[i].deviceStartIndex;
  1119. }
  1120. if (!asLostDevice)
  1121. {
  1122. // Remove any ongoing account selections for the user on the given device.
  1123. for (var i = 0; i < s_OngoingAccountSelections.length; ++i)
  1124. {
  1125. if (s_OngoingAccountSelections[i].userId != s_AllUsers[userIndex].id ||
  1126. s_OngoingAccountSelections[i].device != device)
  1127. continue;
  1128. s_OngoingAccountSelections.RemoveAtByMovingTailWithCapacity(i);
  1129. --i;
  1130. }
  1131. // If the user has actions, sync the devices on them with what we have now.
  1132. var actions = s_AllUserData[userIndex].actions;
  1133. if (actions != null)
  1134. {
  1135. actions.devices = s_AllUsers[userIndex].pairedDevices;
  1136. if (s_AllUsers[userIndex].controlScheme != null)
  1137. UpdateControlSchemeMatch(userIndex);
  1138. }
  1139. // Notify listeners.
  1140. Notify(userIndex, InputUserChange.DeviceUnpaired, device);
  1141. }
  1142. }
  1143. private static void UpdateControlSchemeMatch(int userIndex, bool autoPairMissing = false)
  1144. {
  1145. Debug.Assert(userIndex >= 0 && userIndex < s_AllUserCount);
  1146. // Nothing to do if we don't have a control scheme.
  1147. if (s_AllUserData[userIndex].controlScheme == null)
  1148. return;
  1149. // Get rid of last match result and start new match.
  1150. s_AllUserData[userIndex].controlSchemeMatch.Dispose();
  1151. var matchResult = new InputControlScheme.MatchResult();
  1152. try
  1153. {
  1154. // Match the control scheme's requirements against the devices paired to the user.
  1155. var scheme = s_AllUserData[userIndex].controlScheme.Value;
  1156. if (scheme.deviceRequirements.Count > 0)
  1157. {
  1158. var availableDevices = new InputControlList<InputDevice>(Allocator.Temp);
  1159. try
  1160. {
  1161. // Add devices already paired to user.
  1162. availableDevices.AddSlice(s_AllUsers[userIndex].pairedDevices);
  1163. // If we're supposed to grab whatever additional devices we need from what's
  1164. // available, add all unpaired devices to the list.
  1165. // NOTE: These devices go *after* the devices already paired (if any) meaning that
  1166. // the control scheme matching will grab already paired devices *first*.
  1167. if (autoPairMissing)
  1168. {
  1169. var startIndex = availableDevices.Count;
  1170. var count = GetUnpairedInputDevices(ref availableDevices);
  1171. // We want to favor devices that are already assigned to the same platform user account.
  1172. // Sort the unpaired devices we've added to the list such that the ones belonging to the
  1173. // same user account come first.
  1174. if (s_AllUserData[userIndex].platformUserAccountHandle != null)
  1175. availableDevices.Sort(startIndex, count,
  1176. new CompareDevicesByUserAccount
  1177. {
  1178. platformUserAccountHandle = s_AllUserData[userIndex].platformUserAccountHandle.Value
  1179. });
  1180. }
  1181. matchResult = scheme.PickDevicesFrom(availableDevices);
  1182. if (matchResult.isSuccessfulMatch)
  1183. {
  1184. // If we had lost some devices, flush the list. We haven't regained the device
  1185. // but we're no longer missing devices to play.
  1186. if (s_AllUserData[userIndex].lostDeviceCount > 0)
  1187. ArrayHelpers.EraseSliceWithCapacity(ref s_AllLostDevices, ref s_AllLostDeviceCount,
  1188. s_AllUserData[userIndex].lostDeviceStartIndex,
  1189. s_AllUserData[userIndex].lostDeviceCount);
  1190. // Control scheme is satisfied with the devices we have available.
  1191. // If we may have grabbed as of yet unpaired devices, go and pair them to the user.
  1192. if (autoPairMissing)
  1193. {
  1194. // Update match result on user before potentially invoking callbacks.
  1195. s_AllUserData[userIndex].controlSchemeMatch = matchResult;
  1196. foreach (var device in matchResult.devices)
  1197. {
  1198. // Skip if already paired to user.
  1199. if (s_AllUsers[userIndex].pairedDevices.ContainsReference(device))
  1200. continue;
  1201. AddDeviceToUser(userIndex, device, dontUpdateControlScheme: true);
  1202. }
  1203. }
  1204. }
  1205. }
  1206. finally
  1207. {
  1208. availableDevices.Dispose();
  1209. }
  1210. }
  1211. s_AllUserData[userIndex].controlSchemeMatch = matchResult;
  1212. }
  1213. catch (Exception)
  1214. {
  1215. // If we had an exception and are bailing out, make sure we aren't leaking native memory
  1216. // we allocated.
  1217. matchResult.Dispose();
  1218. throw;
  1219. }
  1220. }
  1221. private static long UpdatePlatformUserAccount(int userIndex, InputDevice device)
  1222. {
  1223. Debug.Assert(userIndex >= 0 && userIndex < s_AllUserCount);
  1224. // Fetch account details from backend.
  1225. var queryResult = QueryPairedPlatformUserAccount(device, out var platformUserAccountHandle,
  1226. out var platformUserAccountName, out var platformUserAccountId);
  1227. // Nothing much to do if not supported by device.
  1228. if (queryResult == InputDeviceCommand.GenericFailure)
  1229. {
  1230. // Check if there's an account selection in progress. There shouldn't be as it's
  1231. // weird for the device to no signal it does not support querying user account, but
  1232. // just to be safe, we check.
  1233. if ((s_AllUserData[userIndex].flags & UserFlags.UserAccountSelectionInProgress) != 0)
  1234. Notify(userIndex, InputUserChange.AccountSelectionCanceled, null);
  1235. s_AllUserData[userIndex].platformUserAccountHandle = null;
  1236. s_AllUserData[userIndex].platformUserAccountName = null;
  1237. s_AllUserData[userIndex].platformUserAccountId = null;
  1238. return queryResult;
  1239. }
  1240. // Check if there's an account selection that we have initiated.
  1241. if ((s_AllUserData[userIndex].flags & UserFlags.UserAccountSelectionInProgress) != 0)
  1242. {
  1243. // Yes, there is. See if it is complete.
  1244. if ((queryResult & (long)QueryPairedUserAccountCommand.Result.UserAccountSelectionInProgress) != 0)
  1245. {
  1246. // No, still in progress.
  1247. }
  1248. else if ((queryResult & (long)QueryPairedUserAccountCommand.Result.UserAccountSelectionCanceled) != 0)
  1249. {
  1250. // Got canceled.
  1251. Notify(userIndex, InputUserChange.AccountSelectionCanceled, device);
  1252. }
  1253. else
  1254. {
  1255. // Yes, it is complete.
  1256. s_AllUserData[userIndex].flags &= ~UserFlags.UserAccountSelectionInProgress;
  1257. s_AllUserData[userIndex].platformUserAccountHandle = platformUserAccountHandle;
  1258. s_AllUserData[userIndex].platformUserAccountName = platformUserAccountName;
  1259. s_AllUserData[userIndex].platformUserAccountId = platformUserAccountId;
  1260. Notify(userIndex, InputUserChange.AccountSelectionComplete, device);
  1261. }
  1262. }
  1263. // Check if user account details have changed.
  1264. else if (s_AllUserData[userIndex].platformUserAccountHandle != platformUserAccountHandle ||
  1265. s_AllUserData[userIndex].platformUserAccountId != platformUserAccountId)
  1266. {
  1267. s_AllUserData[userIndex].platformUserAccountHandle = platformUserAccountHandle;
  1268. s_AllUserData[userIndex].platformUserAccountName = platformUserAccountName;
  1269. s_AllUserData[userIndex].platformUserAccountId = platformUserAccountId;
  1270. Notify(userIndex, InputUserChange.AccountChanged, device);
  1271. }
  1272. else if (s_AllUserData[userIndex].platformUserAccountName != platformUserAccountName)
  1273. {
  1274. Notify(userIndex, InputUserChange.AccountNameChanged, device);
  1275. }
  1276. return queryResult;
  1277. }
  1278. /// <summary>
  1279. /// If the given device is paired to a user account at the platform level, return the platform user
  1280. /// account details.
  1281. /// </summary>
  1282. /// <param name="device">Any input device.</param>
  1283. /// <param name="platformAccountHandle">Receives the platform user account handle or null.</param>
  1284. /// <param name="platformAccountName">Receives the platform user account name or null.</param>
  1285. /// <param name="platformAccountId">Receives the platform user account ID or null.</param>
  1286. /// <returns>True if the device is paired to a user account, false otherwise.</returns>
  1287. /// <remarks>
  1288. /// Sends <see cref="QueryPairedUserAccountCommand"/> to the device.
  1289. /// </remarks>
  1290. /// <seealso cref="QueryPairedUserAccountCommand.handle"/>
  1291. /// <seealso cref="QueryPairedUserAccountCommand.name"/>
  1292. /// <seealso cref="QueryPairedUserAccountCommand.id"/>
  1293. private static long QueryPairedPlatformUserAccount(InputDevice device,
  1294. out InputUserAccountHandle? platformAccountHandle, out string platformAccountName, out string platformAccountId)
  1295. {
  1296. Debug.Assert(device != null);
  1297. // Query user account info from backend.
  1298. var queryPairedUser = QueryPairedUserAccountCommand.Create();
  1299. var result = device.ExecuteCommand(ref queryPairedUser);
  1300. if (result == InputDeviceCommand.GenericFailure)
  1301. {
  1302. // Not currently paired to user account in backend.
  1303. platformAccountHandle = null;
  1304. platformAccountName = null;
  1305. platformAccountId = null;
  1306. return InputDeviceCommand.GenericFailure;
  1307. }
  1308. // Success. There is a user account currently paired to the device and we now have the
  1309. // platform's user account details.
  1310. if ((result & (long)QueryPairedUserAccountCommand.Result.DevicePairedToUserAccount) != 0)
  1311. {
  1312. platformAccountHandle =
  1313. new InputUserAccountHandle(device.description.interfaceName ?? "<Unknown>", queryPairedUser.handle);
  1314. platformAccountName = queryPairedUser.name;
  1315. platformAccountId = queryPairedUser.id;
  1316. }
  1317. else
  1318. {
  1319. // The device supports QueryPairedUserAccountCommand but reports that the
  1320. // device is not currently paired to a user.
  1321. //
  1322. // NOTE: On Switch, where the system itself does not store account<->pairing, we will always
  1323. // end up here until we've initiated an account selection through the backend itself.
  1324. platformAccountHandle = null;
  1325. platformAccountName = null;
  1326. platformAccountId = null;
  1327. }
  1328. return result;
  1329. }
  1330. /// <summary>
  1331. /// Try to initiate user account pairing for the given device at the platform level.
  1332. /// </summary>
  1333. /// <param name="device"></param>
  1334. /// <returns>True if the device accepted the request and an account picker has been raised.</returns>
  1335. /// <remarks>
  1336. /// Sends <see cref="InitiateUserAccountPairingCommand"/> to the device.
  1337. /// </remarks>
  1338. private static bool InitiateUserAccountSelectionAtPlatformLevel(InputDevice device)
  1339. {
  1340. Debug.Assert(device != null);
  1341. var initiateUserPairing = InitiateUserAccountPairingCommand.Create();
  1342. var initiatePairingResult = device.ExecuteCommand(ref initiateUserPairing);
  1343. if (initiatePairingResult == (long)InitiateUserAccountPairingCommand.Result.ErrorAlreadyInProgress)
  1344. throw new InvalidOperationException("User pairing already in progress");
  1345. return initiatePairingResult == (long)InitiateUserAccountPairingCommand.Result.SuccessfullyInitiated;
  1346. }
  1347. private static void OnActionChange(object obj, InputActionChange change)
  1348. {
  1349. if (change == InputActionChange.BoundControlsChanged)
  1350. {
  1351. for (var i = 0; i < s_AllUserCount; ++i)
  1352. {
  1353. ref var user = ref s_AllUsers[i];
  1354. if (ReferenceEquals(user.actions, obj))
  1355. Notify(i, InputUserChange.ControlsChanged, null);
  1356. }
  1357. }
  1358. }
  1359. /// <summary>
  1360. /// Invoked in response to <see cref="InputSystem.onDeviceChange"/>.
  1361. /// </summary>
  1362. /// <param name="device"></param>
  1363. /// <param name="change"></param>
  1364. /// <remarks>
  1365. /// We monitor the device setup in the system for activity that impacts the user setup.
  1366. /// </remarks>
  1367. private static void OnDeviceChange(InputDevice device, InputDeviceChange change)
  1368. {
  1369. switch (change)
  1370. {
  1371. // Existing device removed. May mean a user has lost a device due to the battery running
  1372. // out or the device being unplugged.
  1373. // NOTE: We ignore Disconnected here. Removed is what gets sent whenever a device is taken off of
  1374. // InputSystem.devices -- which is what we're interested in here.
  1375. case InputDeviceChange.Removed:
  1376. {
  1377. // Could have been removed from multiple users. Repeatedly search in s_AllPairedDevices
  1378. // until we can't find the device anymore.
  1379. var deviceIndex = ArrayHelpers.IndexOfReference(s_AllPairedDevices, device, s_AllPairedDeviceCount);
  1380. while (deviceIndex != -1)
  1381. {
  1382. // Find user. Must be there as we found the device in s_AllPairedDevices.
  1383. var userIndex = -1;
  1384. for (var i = 0; i < s_AllUserCount; ++i)
  1385. {
  1386. var deviceStartIndex = s_AllUserData[i].deviceStartIndex;
  1387. if (deviceStartIndex <= deviceIndex && deviceIndex < deviceStartIndex + s_AllUserData[i].deviceCount)
  1388. {
  1389. userIndex = i;
  1390. break;
  1391. }
  1392. }
  1393. // Add device to list of lost devices.
  1394. // NOTE: This will also send a DeviceLost notification.
  1395. // NOTE: Temporarily the device is on both lists.
  1396. AddDeviceToUser(userIndex, device, asLostDevice: true);
  1397. // Remove it from the user.
  1398. RemoveDeviceFromUser(userIndex, device);
  1399. // Search for another user paired to the same device.
  1400. deviceIndex =
  1401. ArrayHelpers.IndexOfReference(s_AllPairedDevices, device, deviceIndex + 1, s_AllPairedDeviceCount);
  1402. }
  1403. break;
  1404. }
  1405. // New device was added. See if it was a device we previously lost on a user.
  1406. case InputDeviceChange.Added:
  1407. {
  1408. // Could be a previously lost device. Could affect multiple users. Repeatedly search in
  1409. // s_AllLostDevices until we can't find the device anymore.
  1410. var deviceIndex = ArrayHelpers.IndexOfReference(s_AllLostDevices, device, s_AllLostDeviceCount);
  1411. while (deviceIndex != -1)
  1412. {
  1413. // Find user. Must be there as we found the device in s_AllLostDevices.
  1414. var userIndex = -1;
  1415. for (var i = 0; i < s_AllUserCount; ++i)
  1416. {
  1417. var deviceStartIndex = s_AllUserData[i].lostDeviceStartIndex;
  1418. if (deviceStartIndex <= deviceIndex && deviceIndex < deviceStartIndex + s_AllUserData[i].lostDeviceCount)
  1419. {
  1420. userIndex = i;
  1421. break;
  1422. }
  1423. }
  1424. // Remove from list of lost devices. No notification.
  1425. RemoveDeviceFromUser(userIndex, device, asLostDevice: true);
  1426. // Notify.
  1427. Notify(userIndex, InputUserChange.DeviceRegained, device);
  1428. // Add back as normally paired device.
  1429. AddDeviceToUser(userIndex, device);
  1430. // Search for another user who had lost the same device.
  1431. deviceIndex =
  1432. ArrayHelpers.IndexOfReference(s_AllLostDevices, device, deviceIndex + 1, s_AllLostDeviceCount);
  1433. }
  1434. break;
  1435. }
  1436. // Device had its configuration changed which may mean we have a different user account paired
  1437. // to the device now.
  1438. case InputDeviceChange.ConfigurationChanged:
  1439. {
  1440. // See if the this is a device that we were waiting for an account selection on. If so, pair
  1441. // it to the user that was waiting.
  1442. var wasOngoingAccountSelection = false;
  1443. for (var i = 0; i < s_OngoingAccountSelections.length; ++i)
  1444. {
  1445. if (s_OngoingAccountSelections[i].device != device)
  1446. continue;
  1447. var userIndex = new InputUser { m_Id = s_OngoingAccountSelections[i].userId }.index;
  1448. var queryResult = UpdatePlatformUserAccount(userIndex, device);
  1449. if ((queryResult & (long)QueryPairedUserAccountCommand.Result.UserAccountSelectionInProgress) == 0)
  1450. {
  1451. wasOngoingAccountSelection = true;
  1452. s_OngoingAccountSelections.RemoveAtByMovingTailWithCapacity(i);
  1453. --i;
  1454. // If the device wasn't paired to the user, pair it now.
  1455. if (!s_AllUsers[userIndex].pairedDevices.ContainsReference(device))
  1456. AddDeviceToUser(userIndex, device);
  1457. }
  1458. }
  1459. // If it wasn't a configuration change event from an account selection, go and check whether
  1460. // there was a user account change that happened outside the application.
  1461. if (!wasOngoingAccountSelection)
  1462. {
  1463. // Could be paired to multiple users. Repeatedly search in s_AllPairedDevices
  1464. // until we can't find the device anymore.
  1465. var deviceIndex = ArrayHelpers.IndexOfReference(s_AllPairedDevices, device, s_AllPairedDeviceCount);
  1466. while (deviceIndex != -1)
  1467. {
  1468. // Find user. Must be there as we found the device in s_AllPairedDevices.
  1469. var userIndex = -1;
  1470. for (var i = 0; i < s_AllUserCount; ++i)
  1471. {
  1472. var deviceStartIndex = s_AllUserData[i].deviceStartIndex;
  1473. if (deviceStartIndex <= deviceIndex && deviceIndex < deviceStartIndex + s_AllUserData[i].deviceCount)
  1474. {
  1475. userIndex = i;
  1476. break;
  1477. }
  1478. }
  1479. // Check user account.
  1480. UpdatePlatformUserAccount(userIndex, device);
  1481. // Search for another user paired to the same device.
  1482. deviceIndex = ArrayHelpers.IndexOfReference(s_AllPairedDevices, device, deviceIndex + 1, s_AllPairedDeviceCount);
  1483. }
  1484. }
  1485. break;
  1486. }
  1487. }
  1488. }
  1489. // We hook this into InputSystem.onEvent when listening for activity on unpaired devices.
  1490. // What this means is that we get to run *before* state reaches the device. This in turn
  1491. // means that should the device get paired as a result, actions that are enabled as part
  1492. // of the pairing will immediately get triggered. This would not be the case if we hook
  1493. // into InputState.onDeviceChange instead which only triggers once state has been altered.
  1494. //
  1495. // NOTE: This also means that unpaired device activity will *only* be detected from events,
  1496. // NOT from state changes applied directly through InputState.Change.
  1497. private static unsafe void OnEvent(InputEventPtr eventPtr, InputDevice device)
  1498. {
  1499. Debug.Assert(s_ListenForUnpairedDeviceActivity != 0,
  1500. "This should only be called while listening for unpaired device activity");
  1501. if (s_ListenForUnpairedDeviceActivity == 0)
  1502. return;
  1503. // Ignore any state change not triggered from a state event.
  1504. if (!eventPtr.IsA<StateEvent>() && !eventPtr.IsA<DeltaStateEvent>())
  1505. return;
  1506. // See if it's a device not belonging to any user.
  1507. if (ArrayHelpers.ContainsReference(s_AllPairedDevices, s_AllPairedDeviceCount, device))
  1508. {
  1509. // No, it's a device already paired to a player so do nothing.
  1510. return;
  1511. }
  1512. Profiler.BeginSample("InputCheckForUnpairedDeviceActivity");
  1513. ////TODO: allow filtering (e.g. by device requirements on user actions)
  1514. // Go through controls and for any one that isn't noisy or synthetic, find out
  1515. // if we have a magnitude greater than zero.
  1516. var controls = device.allControls;
  1517. for (var i = 0; i < controls.Count; ++i)
  1518. {
  1519. var control = controls[i];
  1520. if (control.noisy || control.synthetic)
  1521. continue;
  1522. // Ignore non-leaf controls.
  1523. if (control.children.Count > 0)
  1524. continue;
  1525. // Ignore controls that aren't part of the event.
  1526. var statePtr = control.GetStatePtrFromStateEvent(eventPtr);
  1527. if (statePtr == null)
  1528. continue;
  1529. // Check for default state. Cheaper check than magnitude evaluation
  1530. // which may involve several virtual method calls.
  1531. if (control.CheckStateIsAtDefault(statePtr))
  1532. continue;
  1533. // Ending up here is costly. We now do per-control work that may involve
  1534. // walking all over the place in the InputControl machinery.
  1535. //
  1536. // NOTE: We already know the control has moved away from its default state
  1537. // so in case it does not support magnitudes, we assume that the
  1538. // control has changed value, too.
  1539. var magnitude = control.EvaluateMagnitude(statePtr);
  1540. if (magnitude > 0 || magnitude == -1)
  1541. {
  1542. // Yes, something was actuated on the device.
  1543. var deviceHasBeenPaired = false;
  1544. for (var n = 0; n < s_OnUnpairedDeviceUsed.length; ++n)
  1545. {
  1546. var pairingStateVersionBefore = s_PairingStateVersion;
  1547. s_OnUnpairedDeviceUsed[n](control, eventPtr);
  1548. if (pairingStateVersionBefore != s_PairingStateVersion
  1549. && FindUserPairedToDevice(device) != null)
  1550. {
  1551. deviceHasBeenPaired = true;
  1552. break;
  1553. }
  1554. }
  1555. // If the device was paired in one of the callbacks, stop processing
  1556. // changes on it.
  1557. if (deviceHasBeenPaired)
  1558. break;
  1559. }
  1560. }
  1561. Profiler.EndSample();
  1562. }
  1563. /// <summary>
  1564. /// Syntax for configuring a control scheme on a user.
  1565. /// </summary>
  1566. public struct ControlSchemeChangeSyntax
  1567. {
  1568. /// <summary>
  1569. /// Leave the user's paired devices in place but pair any available devices
  1570. /// that are still required by the control scheme.
  1571. /// </summary>
  1572. /// <returns></returns>
  1573. /// <remarks>
  1574. /// If there are unpaired devices that, at the platform level, are associated with the same
  1575. /// user account, those will take precedence over other unpaired devices.
  1576. /// </remarks>
  1577. public ControlSchemeChangeSyntax AndPairRemainingDevices()
  1578. {
  1579. UpdateControlSchemeMatch(m_UserIndex, autoPairMissing: true);
  1580. return this;
  1581. }
  1582. internal int m_UserIndex;
  1583. }
  1584. private uint m_Id;
  1585. [Flags]
  1586. internal enum UserFlags
  1587. {
  1588. BindToAllDevices = 1 << 0,
  1589. /// <summary>
  1590. /// Whether we have initiated a user account selection.
  1591. /// </summary>
  1592. UserAccountSelectionInProgress = 1 << 1,
  1593. }
  1594. /// <summary>
  1595. /// Data we store for each user.
  1596. /// </summary>
  1597. internal struct UserData
  1598. {
  1599. /// <summary>
  1600. /// The platform handle associated with the user.
  1601. /// </summary>
  1602. /// <remarks>
  1603. /// If set, this identifies the user on the platform. It also means that the devices
  1604. /// assigned to the user may be paired at the platform level.
  1605. /// </remarks>
  1606. public InputUserAccountHandle? platformUserAccountHandle;
  1607. /// <summary>
  1608. /// Plain-text user name as returned by the underlying platform. Null if not associated with user on platform.
  1609. /// </summary>
  1610. public string platformUserAccountName;
  1611. /// <summary>
  1612. /// Platform-specific ID that identifies the user across sessions even if the user
  1613. /// name changes.
  1614. /// </summary>
  1615. /// <remarks>
  1616. /// This might not be a human-readable string.
  1617. /// </remarks>
  1618. public string platformUserAccountId;
  1619. /// <summary>
  1620. /// Number of devices in <see cref="InputUser.s_AllPairedDevices"/> assigned to the user.
  1621. /// </summary>
  1622. public int deviceCount;
  1623. /// <summary>
  1624. /// Index in <see cref="InputUser.s_AllPairedDevices"/> where the devices for this user start. Only valid
  1625. /// if <see cref="deviceCount"/> is greater than zero.
  1626. /// </summary>
  1627. public int deviceStartIndex;
  1628. /// <summary>
  1629. /// Input actions associated with the user.
  1630. /// </summary>
  1631. public IInputActionCollection actions;
  1632. /// <summary>
  1633. /// Currently active control scheme or null if no control scheme has been set on the user.
  1634. /// </summary>
  1635. /// <remarks>
  1636. /// This also dictates the binding mask that we're using with <see cref="actions"/>.
  1637. /// </remarks>
  1638. public InputControlScheme? controlScheme;
  1639. public InputControlScheme.MatchResult controlSchemeMatch;
  1640. public int lostDeviceCount;
  1641. public int lostDeviceStartIndex;
  1642. ////TODO
  1643. //public InputUserSettings settings;
  1644. public UserFlags flags;
  1645. }
  1646. /// <summary>
  1647. /// Compare two devices for being associated with a specific platform user account.
  1648. /// </summary>
  1649. private struct CompareDevicesByUserAccount : IComparer<InputDevice>
  1650. {
  1651. public InputUserAccountHandle platformUserAccountHandle;
  1652. public int Compare(InputDevice x, InputDevice y)
  1653. {
  1654. var firstAccountHandle = GetUserAccountHandleForDevice(x);
  1655. var secondAccountHandle = GetUserAccountHandleForDevice(x);
  1656. if (firstAccountHandle == platformUserAccountHandle &&
  1657. secondAccountHandle == platformUserAccountHandle)
  1658. return 0;
  1659. if (firstAccountHandle == platformUserAccountHandle)
  1660. return -1;
  1661. if (secondAccountHandle == platformUserAccountHandle)
  1662. return 1;
  1663. return 0;
  1664. }
  1665. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "device", Justification = "Keep this for future implementation")]
  1666. private static InputUserAccountHandle? GetUserAccountHandleForDevice(InputDevice device)
  1667. {
  1668. ////TODO (need to cache this)
  1669. return null;
  1670. }
  1671. }
  1672. private struct OngoingAccountSelection
  1673. {
  1674. public InputDevice device;
  1675. public uint userId;
  1676. }
  1677. private static int s_PairingStateVersion;
  1678. private static uint s_LastUserId;
  1679. private static int s_AllUserCount;
  1680. private static int s_AllPairedDeviceCount;
  1681. private static int s_AllLostDeviceCount;
  1682. private static InputUser[] s_AllUsers;
  1683. private static UserData[] s_AllUserData;
  1684. private static InputDevice[] s_AllPairedDevices; // We keep a single array that we slice out to each user.
  1685. private static InputDevice[] s_AllLostDevices;
  1686. private static InlinedArray<OngoingAccountSelection> s_OngoingAccountSelections;
  1687. private static InlinedArray<Action<InputUser, InputUserChange, InputDevice>> s_OnChange;
  1688. private static InlinedArray<Action<InputControl, InputEventPtr>> s_OnUnpairedDeviceUsed;
  1689. private static Action<object, InputActionChange> s_ActionChangeDelegate;
  1690. private static Action<InputDevice, InputDeviceChange> s_OnDeviceChangeDelegate;
  1691. private static Action<InputEventPtr, InputDevice> s_OnEventDelegate;
  1692. private static bool s_OnActionChangeHooked;
  1693. private static bool s_OnDeviceChangeHooked;
  1694. private static bool s_OnEventHooked;
  1695. private static int s_ListenForUnpairedDeviceActivity;
  1696. private static void HookIntoActionChange()
  1697. {
  1698. if (s_OnActionChangeHooked)
  1699. return;
  1700. if (s_ActionChangeDelegate == null)
  1701. s_ActionChangeDelegate = OnActionChange;
  1702. InputSystem.onActionChange += OnActionChange;
  1703. s_OnActionChangeHooked = true;
  1704. }
  1705. private static void UnhookFromActionChange()
  1706. {
  1707. if (!s_OnActionChangeHooked)
  1708. return;
  1709. InputSystem.onActionChange -= OnActionChange;
  1710. s_OnActionChangeHooked = true;
  1711. }
  1712. private static void HookIntoDeviceChange()
  1713. {
  1714. if (s_OnDeviceChangeHooked)
  1715. return;
  1716. if (s_OnDeviceChangeDelegate == null)
  1717. s_OnDeviceChangeDelegate = OnDeviceChange;
  1718. InputSystem.onDeviceChange += s_OnDeviceChangeDelegate;
  1719. s_OnDeviceChangeHooked = true;
  1720. }
  1721. private static void UnhookFromDeviceChange()
  1722. {
  1723. if (!s_OnDeviceChangeHooked)
  1724. return;
  1725. InputSystem.onDeviceChange -= s_OnDeviceChangeDelegate;
  1726. s_OnDeviceChangeHooked = false;
  1727. }
  1728. private static void HookIntoEvents()
  1729. {
  1730. if (s_OnEventHooked)
  1731. return;
  1732. if (s_OnEventDelegate == null)
  1733. s_OnEventDelegate = OnEvent;
  1734. InputSystem.onEvent += s_OnEventDelegate;
  1735. s_OnEventHooked = true;
  1736. }
  1737. private static void UnhookFromDeviceStateChange()
  1738. {
  1739. if (!s_OnEventHooked)
  1740. return;
  1741. InputSystem.onEvent -= s_OnEventDelegate;
  1742. s_OnEventHooked = false;
  1743. }
  1744. internal static void ResetGlobals()
  1745. {
  1746. // Release native memory held by control scheme match results.
  1747. for (var i = 0; i < s_AllUserCount; ++i)
  1748. s_AllUserData[i].controlSchemeMatch.Dispose();
  1749. // Don't reset s_LastUserId and just let it increment instead so we never generate
  1750. // the same ID twice.
  1751. s_PairingStateVersion = 0;
  1752. s_AllUserCount = 0;
  1753. s_AllPairedDeviceCount = 0;
  1754. s_AllUsers = null;
  1755. s_AllUserData = null;
  1756. s_AllPairedDevices = null;
  1757. s_OngoingAccountSelections = new InlinedArray<OngoingAccountSelection>();
  1758. s_OnChange = new InlinedArray<Action<InputUser, InputUserChange, InputDevice>>();
  1759. s_OnUnpairedDeviceUsed = new InlinedArray<Action<InputControl, InputEventPtr>>();
  1760. s_OnDeviceChangeDelegate = null;
  1761. s_OnEventDelegate = null;
  1762. s_OnDeviceChangeHooked = false;
  1763. s_OnActionChangeHooked = false;
  1764. s_OnEventHooked = false;
  1765. s_ListenForUnpairedDeviceActivity = 0;
  1766. }
  1767. }
  1768. }