12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967 |
- using System;
- using System.Collections.Generic;
- using Unity.Collections;
- using UnityEngine.InputSystem.LowLevel;
- using UnityEngine.InputSystem.Utilities;
- using UnityEngine.Profiling;
-
- ////REVIEW: remove users automatically when exiting play mode?
-
- ////REVIEW: do we need to handle the case where devices are added to a user that are each associated with a different user account
-
- ////REVIEW: how should we handle pairings of devices *not* called for by a control scheme? should that result in a failed match?
-
- ////TODO: option to bind to *all* devices instead of just the paired ones (bindToAllDevices)
-
- ////TODO: the account selection stuff needs cleanup; the current flow is too convoluted
-
- namespace UnityEngine.InputSystem.Users
- {
- /// <summary>
- /// Represents a specific user/player interacting with one or more devices and input actions.
- /// </summary>
- /// <remarks>
- /// Principally, an InputUser represents a human interacting with the application. Moreover, at any point
- /// each InputUser represents a human actor distinct from all other InputUsers in the system.
- ///
- /// Each user has one or more paired devices. In general, these devices are unique to each user. However,
- /// it is permitted to use <see cref="PerformPairingWithDevice"/> to pair the same device to multiple users.
- /// This can be useful in setups such as split-keyboard (e.g. one user using left side of keyboard and the
- /// other the right one) use or hotseat-style gameplay (e.g. two players taking turns on the same game controller).
- ///
- /// Note that the InputUser API, like <see cref="InputAction"/>) is a play mode-only feature. When exiting play mode,
- /// all users are automatically removed and all devices automatically unpaired.
- /// </remarks>
- /// <seealso cref="InputUserChange"/>
- public struct InputUser : IEquatable<InputUser>
- {
- public const uint InvalidId = 0;
-
- /// <summary>
- /// Whether this is a currently active user record in <see cref="all"/>.
- /// </summary>
- /// <remarks>
- /// Users that are removed (<see cref="UnpairDevicesAndRemoveUser"/>) will become invalid.
- /// </remarks>
- /// <seealso cref="UnpairDevicesAndRemoveUser"/>
- /// <seealso cref="InputUserChange.Removed"/>
- public bool valid
- {
- get
- {
- if (m_Id == InvalidId)
- return false;
-
- // See if there's a currently active user with the given ID.
- for (var i = 0; i < s_GlobalState.allUserCount; ++i)
- if (s_GlobalState.allUsers[i].m_Id == m_Id)
- return true;
-
- return false;
- }
- }
-
- /// <summary>
- /// The sequence number of the user.
- /// </summary>
- /// <remarks>
- /// It can be useful to establish a sorting of players locally such that it is
- /// known who is the first player, who is the second, and so on. This property
- /// gives the positioning of the user within <see cref="all"/>.
- ///
- /// Note that the index of a user may change as users are added and removed.
- /// </remarks>
- /// <seealso cref="all"/>
- public int index
- {
- get
- {
- if (m_Id == InvalidId)
- throw new InvalidOperationException("Invalid user");
-
- var userIndex = TryFindUserIndex(m_Id);
- if (userIndex == -1)
- throw new InvalidOperationException($"User with ID {m_Id} is no longer valid");
-
- return userIndex;
- }
- }
-
- /// <summary>
- /// The unique numeric ID of the user.
- /// </summary>
- /// <remarks>
- /// The ID of a user is internally assigned and cannot be changed over its lifetime. No two users, even
- /// if not concurrently active, will receive the same ID.
- ///
- /// The ID stays valid and unique even if the user is removed and no longer <see cref="valid"/>.
- /// </remarks>
- public uint id => m_Id;
-
- ////TODO: bring documentation for these back when user management is implemented on Xbox and PS
- public InputUserAccountHandle? platformUserAccountHandle => s_GlobalState.allUserData[index].platformUserAccountHandle;
- public string platformUserAccountName => s_GlobalState.allUserData[index].platformUserAccountName;
- public string platformUserAccountId => s_GlobalState.allUserData[index].platformUserAccountId;
-
- ////REVIEW: Does it make sense to track used devices separately from paired devices?
- /// <summary>
- /// Devices assigned/paired/linked to the user.
- /// </summary>
- /// <remarks>
- /// It is generally valid for a device to be assigned to multiple users. For example, two users could
- /// both use the local keyboard in a split-keyboard or hot seat setup. However, a platform may restrict this
- /// and mandate that a device never belong to more than one user. This is the case on Xbox and PS4, for
- /// example.
- ///
- /// To associate devices with users, use <see cref="PerformPairingWithDevice"/>. To remove devices, use
- /// <see cref="UnpairDevice"/> or <see cref="UnpairDevicesAndRemoveUser"/>.
- ///
- /// The array will be empty for a user who is currently not paired to any devices.
- ///
- /// If <see cref="actions"/> is set (<see cref="AssociateActionsWithUser(IInputActionCollection)"/>), then
- /// <see cref="IInputActionCollection.devices"/> will be kept synchronized with the devices paired to the user.
- /// </remarks>
- /// <seealso cref="PerformPairingWithDevice"/>
- /// <seealso cref="UnpairDevice"/>
- /// <seealso cref="UnpairDevices"/>
- /// <seealso cref="UnpairDevicesAndRemoveUser"/>
- /// <seealso cref="InputUserChange.DevicePaired"/>
- /// <seealso cref="InputUserChange.DeviceUnpaired"/>
- public ReadOnlyArray<InputDevice> pairedDevices
- {
- get
- {
- var userIndex = index;
- return new ReadOnlyArray<InputDevice>(s_GlobalState.allPairedDevices, s_GlobalState.allUserData[userIndex].deviceStartIndex,
- s_GlobalState.allUserData[userIndex].deviceCount);
- }
- }
-
- /// <summary>
- /// Devices that were removed while they were still paired to the user.
- /// </summary>
- /// <remarks>
- ///
- /// This list is cleared once the user has either regained lost devices or has regained other devices
- /// such that the <see cref="controlScheme"/> is satisfied.
- /// </remarks>
- /// <seealso cref="InputUserChange.DeviceRegained"/>
- /// <seealso cref="InputUserChange.DeviceLost"/>
- public ReadOnlyArray<InputDevice> lostDevices
- {
- get
- {
- var userIndex = index;
- return new ReadOnlyArray<InputDevice>(s_GlobalState.allLostDevices, s_GlobalState.allUserData[userIndex].lostDeviceStartIndex,
- s_GlobalState.allUserData[userIndex].lostDeviceCount);
- }
- }
-
-
- /// <summary>
- /// Actions associated with the user.
- /// </summary>
- /// <remarks>
- /// Associating actions with a user will synchronize the actions with the devices paired to the
- /// user. Also, it makes it possible to use support for control scheme activation (<see
- /// cref="ActivateControlScheme(InputControlScheme)"/> and related APIs like <see cref="controlScheme"/>
- /// and <see cref="controlSchemeMatch"/>).
- ///
- /// Note that is generally does not make sense for users to share actions. Instead, each user should
- /// receive a set of actions private to the user.
- /// </remarks>
- /// <seealso cref="AssociateActionsWithUser(IInputActionCollection)"/>
- /// <seealso cref="InputActionMap"/>
- /// <seealso cref="InputActionAsset"/>
- /// <seealso cref="InputUserChange.ControlsChanged"/>
- public IInputActionCollection actions => s_GlobalState.allUserData[index].actions;
-
- /// <summary>
- /// The control scheme currently employed by the user.
- /// </summary>
- /// <remarks>
- /// This is null by default.
- ///
- /// Any time the value of this property changes (whether by <see cref="ActivateControlScheme(string)"/>
- /// or by automatic switching), a notification is sent on <see cref="onChange"/> with
- /// <see cref="InputUserChange.ControlSchemeChanged"/>.
- ///
- /// Be aware that using control schemes with InputUsers requires <see cref="actions"/> to
- /// be set, i.e. input actions to be associated with the user (<see
- /// cref="AssociateActionsWithUser(IInputActionCollection)"/>).
- /// </remarks>
- /// <seealso cref="ActivateControlScheme(string)"/>
- /// <seealso cref="ActivateControlScheme(InputControlScheme)"/>
- /// <seealso cref="InputUserChange.ControlSchemeChanged"/>
- public InputControlScheme? controlScheme => s_GlobalState.allUserData[index].controlScheme;
-
- /// <summary>
- /// The result of matching the device requirements given by <see cref="controlScheme"/> against
- /// the devices paired to the user (<see cref="pairedDevices"/>).
- /// </summary>
- /// <remarks>
- /// When devices are paired to or unpaired from a user, as well as when a new control scheme is
- /// activated on a user, this property is updated automatically.
- /// </remarks>
- /// <seealso cref="InputControlScheme.deviceRequirements"/>
- /// <seealso cref="InputControlScheme.PickDevicesFrom{TDevices}"/>
- public InputControlScheme.MatchResult controlSchemeMatch => s_GlobalState.allUserData[index].controlSchemeMatch;
-
- /// <summary>
- /// Whether the user is missing devices required by the <see cref="controlScheme"/> activated
- /// on the user.
- /// </summary>
- /// <remarks>
- /// This will only take required devices into account. Device requirements marked optional (<see
- /// cref="InputControlScheme.DeviceRequirement.isOptional"/>) will not be considered missing
- /// devices if they cannot be satisfied based on the devices paired to the user.
- /// </remarks>
- /// <seealso cref="InputControlScheme.deviceRequirements"/>
- public bool hasMissingRequiredDevices => s_GlobalState.allUserData[index].controlSchemeMatch.hasMissingRequiredDevices;
-
- /// <summary>
- /// List of all current users.
- /// </summary>
- /// <remarks>
- /// Use <see cref="PerformPairingWithDevice"/> to add new users and <see cref="UnpairDevicesAndRemoveUser"/> to
- /// remove users.
- ///
- /// Note that this array does not necessarily correspond to the list of users present at the platform level
- /// (e.g. Xbox and PS4). There can be users present at the platform level that are not present in this array
- /// (e.g. because they are not joined to the game) and users can even be present more than once (e.g. if
- /// playing on the user account but as two different players in the game). Also, there can be users in the array
- /// that are not present at the platform level.
- /// </remarks>
- /// <seealso cref="PerformPairingWithDevice"/>
- /// <seealso cref="UnpairDevicesAndRemoveUser"/>
- public static ReadOnlyArray<InputUser> all => new ReadOnlyArray<InputUser>(s_GlobalState.allUsers, 0, s_GlobalState.allUserCount);
-
- /// <summary>
- /// Event that is triggered when the <see cref="InputUser">user</see> setup in the system
- /// changes.
- /// </summary>
- /// <remarks>
- /// Each notification receives the user that was affected by the change and, in the form of <see cref="InputUserChange"/>,
- /// a description of what has changed about the user. The third parameter may be null but if the change will be related
- /// to an input device, will reference the device involved in the change.
- /// </remarks>
- public static event Action<InputUser, InputUserChange, InputDevice> onChange
- {
- add
- {
- if (value == null)
- throw new ArgumentNullException(nameof(value));
- s_GlobalState.onChange.AddCallback(value);
- }
- remove
- {
- if (value == null)
- throw new ArgumentNullException(nameof(value));
- s_GlobalState.onChange.RemoveCallback(value);
- }
- }
-
- /// <summary>
- /// Event that is triggered when a device is used that is not currently paired to any user.
- /// </summary>
- /// <remarks>
- /// A device is considered "used" when it has magnitude (<see cref="InputControl.EvaluateMagnitude()"/>) greater than zero
- /// on a control that is not noisy (<see cref="InputControl.noisy"/>) and not synthetic (i.e. not a control that is
- /// "made up" like <see cref="Keyboard.anyKey"/>; <see cref="InputControl.synthetic"/>).
- ///
- /// Detecting the use of unpaired devices has a non-zero cost. While multiple levels of tests are applied to try to
- /// cheaply ignore devices that have events sent to them that do not contain user activity, finding out whether
- /// a device had real user activity will eventually require going through the device control by control.
- ///
- /// To enable detection of the use of unpaired devices, set <see cref="listenForUnpairedDeviceActivity"/> to true.
- /// It is disabled by default.
- ///
- /// The callback is invoked for each non-leaf, non-synthetic, non-noisy control that has been actuated on the device.
- /// It being restricted to non-leaf controls means that if, say, the stick on a gamepad is actuated in both X and Y
- /// direction, you will see two calls: one with stick/x and one with stick/y.
- ///
- /// The reason that the callback is invoked for each individual control is that pairing often relies on checking
- /// for specific kinds of interactions. For example, a pairing callback may listen exclusively for button presses.
- ///
- /// Note that whether the use of unpaired devices leads to them getting paired is under the control of the application.
- /// If the device should be paired, invoke <see cref="PerformPairingWithDevice"/> from the callback. If you do so,
- /// no further callbacks will get triggered for other controls that may have been actuated in the same event.
- ///
- /// Be aware that the callback is fired <em>before</em> input is actually incorporated into the device (it is
- /// indirectly triggered from <see cref="InputSystem.onEvent"/>). This means at the time the callback is run,
- /// the state of the given device does not yet have the input that triggered the callback. For this reason, the
- /// callback receives a second argument that references the event from which the use of an unpaired device was
- /// detected.
- ///
- /// What this sequence allows is to make changes to the system before the input is processed. For example, an
- /// action that is enabled as part of the callback will subsequently respond to the input that triggered the
- /// callback.
- ///
- /// <example>
- /// <code>
- /// // Activate support for listening to device activity.
- /// ++InputUser.listenForUnpairedDeviceActivity;
- ///
- /// // When a button on an unpaired device is pressed, pair the device to a new
- /// // or existing user.
- /// InputUser.onUnpairedDeviceUsed +=
- /// usedControl =>
- /// {
- /// // Only react to button presses on unpaired devices.
- /// if (!(usedControl is ButtonControl))
- /// return;
- ///
- /// // Pair the device to a user.
- /// InputUser.PerformPairingWithDevice(usedControl.device);
- /// };
- /// </code>
- /// </example>
- ///
- /// Another possible use of the callback is for implementing automatic control scheme switching for a user such that
- /// the user can, for example, switch from keyboard&mouse to gamepad seamlessly by simply picking up the gamepad
- /// and starting to play.
- /// </remarks>
- public static event Action<InputControl, InputEventPtr> onUnpairedDeviceUsed
- {
- add
- {
- if (value == null)
- throw new ArgumentNullException(nameof(value));
- s_GlobalState.onUnpairedDeviceUsed.AddCallback(value);
- if (s_GlobalState.listenForUnpairedDeviceActivity > 0)
- HookIntoEvents();
- }
- remove
- {
- if (value == null)
- throw new ArgumentNullException(nameof(value));
- s_GlobalState.onUnpairedDeviceUsed.RemoveCallback(value);
- if (s_GlobalState.onUnpairedDeviceUsed.length == 0)
- UnhookFromDeviceStateChange();
- }
- }
-
- /// <summary>
- /// Callback that works in combination with <see cref="onUnpairedDeviceUsed"/>. If all callbacks
- /// added to this event return <c>false</c> for a
- /// </summary>
- /// <remarks>
- /// Checking a given event for activity of interest is relatively fast but is still costlier than
- /// not doing it all. In case only certain devices are of interest for <see cref="onUnpairedDeviceUsed"/>,
- /// this "pre-filter" can be used to quickly reject entire devices and thus skip looking closer at
- /// an event.
- ///
- /// The first argument is the <see cref="InputDevice"/> than an event has been received for.
- /// The second argument is the <see cref="InputEvent"/> that is being looked at.
- ///
- /// A callback should return <c>true</c> if it wants <see cref="onUnpairedDeviceUsed"/> to proceed
- /// looking at the event and should return <c>false</c> if the event should be skipped.
- ///
- /// If multiple callbacks are added to the event, it is enough for any single one callback
- /// to return <c>true</c> for the event to get looked at.
- /// </remarks>
- /// <seealso cref="onUnpairedDeviceUsed"/>
- /// <seealso cref="listenForUnpairedDeviceActivity"/>
- public static event Func<InputDevice, InputEventPtr, bool> onPrefilterUnpairedDeviceActivity
- {
- add
- {
- if (value == null)
- throw new ArgumentNullException(nameof(value));
- s_GlobalState.onPreFilterUnpairedDeviceUsed.AddCallback(value);
- }
- remove
- {
- if (value == null)
- throw new ArgumentNullException(nameof(value));
- s_GlobalState.onPreFilterUnpairedDeviceUsed.RemoveCallback(value);
- }
- }
-
- ////TODO: After 1.0, make this a simple bool API that *underneath* uses a counter rather than exposing the counter
- //// directly to the user.
- /// <summary>
- /// Whether to listen for user activity on currently unpaired devices and invoke <see cref="onUnpairedDeviceUsed"/>
- /// if such activity is detected.
- /// </summary>
- /// <remarks>
- /// This is off by default.
- ///
- /// Note that enabling this has a non-zero cost. Whenever the state changes of a device that is not currently paired
- /// to a user, the system has to spend time figuring out whether there was a meaningful change or whether it's just
- /// noise on the device.
- ///
- /// This is an integer rather than a bool to allow multiple systems to concurrently use to listen for unpaired
- /// device activity without treading on each other when enabling/disabling the code path.
- /// </remarks>
- /// <seealso cref="onUnpairedDeviceUsed"/>
- /// <seealso cref="pairedDevices"/>
- /// <seealso cref="PerformPairingWithDevice"/>
- public static int listenForUnpairedDeviceActivity
- {
- get => s_GlobalState.listenForUnpairedDeviceActivity;
- set
- {
- if (value < 0)
- throw new ArgumentOutOfRangeException(nameof(value), "Cannot be negative");
- if (value > 0 && s_GlobalState.onUnpairedDeviceUsed.length > 0)
- HookIntoEvents();
- else if (value == 0)
- UnhookFromDeviceStateChange();
- s_GlobalState.listenForUnpairedDeviceActivity = value;
- }
- }
-
- public override string ToString()
- {
- if (!valid)
- return $"<Invalid> (id: {m_Id})";
-
- var deviceList = string.Join(",", pairedDevices);
- return $"User #{index} (id: {m_Id}, devices: {deviceList}, actions: {actions})";
- }
-
- /// <summary>
- /// Associate a collection of <see cref="InputAction"/>s with the user.
- /// </summary>
- /// <param name="actions">Actions to associate with the user, either an <see cref="InputActionAsset"/>
- /// or an <see cref="InputActionMap"/>. Can be <c>null</c> to unset the current association.</param>
- /// <exception cref="InvalidOperationException">The user instance is invalid.</exception>
- /// <remarks>
- /// Associating actions with a user will ensure that the <see cref="IInputActionCollection.devices"/> and
- /// <see cref="IInputActionCollection.bindingMask"/> property of the action collection are automatically
- /// kept in sync with the device paired to the user (see <see cref="pairedDevices"/>) and the control
- /// scheme active on the user (see <see cref="controlScheme"/>).
- ///
- /// <example>
- /// <code>
- /// var gamepad = Gamepad.all[0];
- ///
- /// // Pair the gamepad to a user.
- /// var user = InputUser.PerformPairingWithDevice(gamepad);
- ///
- /// // Create an action map with an action.
- /// var actionMap = new InputActionMap():
- /// actionMap.AddAction("Fire", binding: "<Gamepad>/buttonSouth");
- ///
- /// // Associate the action map with the user (the same works for an asset).
- /// user.AssociateActionsWithUser(actionMap);
- ///
- /// // Now the action map is restricted to just the gamepad that is paired
- /// // with the user, even if there are more gamepads currently connected.
- /// </code>
- /// </example>
- /// </remarks>
- /// <seealso cref="actions"/>
- public void AssociateActionsWithUser(IInputActionCollection actions)
- {
- var userIndex = index; // Throws if user is invalid.
- if (s_GlobalState.allUserData[userIndex].actions == actions)
- return;
-
- // If we already had actions associated, reset the binding mask and device list.
- var oldActions = s_GlobalState.allUserData[userIndex].actions;
- if (oldActions != null)
- {
- oldActions.devices = null;
- oldActions.bindingMask = null;
- }
-
- s_GlobalState.allUserData[userIndex].actions = actions;
-
- // If we've switched to a different set of actions, synchronize our state.
- if (actions != null)
- {
- HookIntoActionChange();
-
- actions.devices = pairedDevices;
- if (s_GlobalState.allUserData[userIndex].controlScheme != null)
- ActivateControlSchemeInternal(userIndex, s_GlobalState.allUserData[userIndex].controlScheme.Value);
- }
- }
-
- public ControlSchemeChangeSyntax ActivateControlScheme(string schemeName)
- {
- // Look up control scheme by name in actions.
- if (!string.IsNullOrEmpty(schemeName))
- {
- FindControlScheme(schemeName, out InputControlScheme scheme); // throws if not found
- return ActivateControlScheme(scheme);
- }
- return ActivateControlScheme(new InputControlScheme());
- }
-
- private bool TryFindControlScheme(string schemeName, out InputControlScheme scheme)
- {
- if (string.IsNullOrEmpty(schemeName))
- {
- scheme = default;
- return false;
- }
-
- // Need actions to be available to be able to activate control schemes by name only.
- if (s_GlobalState.allUserData[index].actions == null)
- throw new InvalidOperationException(
- $"Cannot set control scheme '{schemeName}' by name on user #{index} as not actions have been associated with the user yet (AssociateActionsWithUser)");
-
- // Attempt to find control scheme by name
- var controlSchemes = s_GlobalState.allUserData[index].actions.controlSchemes;
- for (var i = 0; i < controlSchemes.Count; ++i)
- {
- if (string.Compare(controlSchemes[i].name, schemeName,
- StringComparison.InvariantCultureIgnoreCase) == 0)
- {
- scheme = controlSchemes[i];
- return true;
- }
- }
-
- scheme = default;
- return false;
- }
-
- internal void FindControlScheme(string schemeName, out InputControlScheme scheme)
- {
- if (TryFindControlScheme(schemeName, out scheme))
- return;
- throw new ArgumentException(
- $"Cannot find control scheme '{schemeName}' in actions '{s_GlobalState.allUserData[index].actions}'");
- }
-
- public ControlSchemeChangeSyntax ActivateControlScheme(InputControlScheme scheme)
- {
- var userIndex = index; // Throws if user is invalid.
-
- if (s_GlobalState.allUserData[userIndex].controlScheme != scheme ||
- (scheme == default && s_GlobalState.allUserData[userIndex].controlScheme != null))
- {
- ActivateControlSchemeInternal(userIndex, scheme);
- Notify(userIndex, InputUserChange.ControlSchemeChanged, null);
- }
-
- return new ControlSchemeChangeSyntax { m_UserIndex = userIndex };
- }
-
- private void ActivateControlSchemeInternal(int userIndex, InputControlScheme scheme)
- {
- var isEmpty = scheme == default;
-
- if (isEmpty)
- s_GlobalState.allUserData[userIndex].controlScheme = null;
- else
- s_GlobalState.allUserData[userIndex].controlScheme = scheme;
-
- if (s_GlobalState.allUserData[userIndex].actions != null)
- {
- if (isEmpty)
- {
- s_GlobalState.allUserData[userIndex].actions.bindingMask = null;
- s_GlobalState.allUserData[userIndex].controlSchemeMatch.Dispose();
- s_GlobalState.allUserData[userIndex].controlSchemeMatch = new InputControlScheme.MatchResult();
- }
- else
- {
- s_GlobalState.allUserData[userIndex].actions.bindingMask = new InputBinding { groups = scheme.bindingGroup };
- UpdateControlSchemeMatch(userIndex);
-
- // If we had lost some devices, flush the list. We haven't regained the device
- // but we're no longer missing devices to play.
- if (s_GlobalState.allUserData[userIndex].controlSchemeMatch.isSuccessfulMatch)
- RemoveLostDevicesForUser(userIndex);
- }
- }
- }
-
- /// <summary>
- /// Unpair a single device from the user.
- /// </summary>
- /// <param name="device">Device to unpair from the user. If the device is not currently paired to the user,
- /// the method does nothing.</param>
- /// <exception cref="ArgumentNullException"><paramref name="device"/> is <c>null</c>.</exception>
- /// <remarks>
- /// If actions are associated with the user (<see cref="actions"/>), the list of devices used by the
- /// actions (<see cref="IInputActionCollection.devices"/>) is automatically updated.
- ///
- /// If a control scheme is activated on the user (<see cref="controlScheme"/>), <see cref="controlSchemeMatch"/>
- /// is automatically updated.
- ///
- /// Sends <see cref="InputUserChange.DeviceUnpaired"/> through <see cref="onChange"/>.
- /// </remarks>
- /// <seealso cref="PerformPairingWithDevice"/>
- /// <seealso cref="pairedDevices"/>
- /// <seealso cref="UnpairDevices"/>
- /// <seealso cref="UnpairDevicesAndRemoveUser"/>
- /// <seealso cref="InputUserChange.DeviceUnpaired"/>
- public void UnpairDevice(InputDevice device)
- {
- if (device == null)
- throw new ArgumentNullException(nameof(device));
-
- var userIndex = index; // Throws if user is invalid.
-
- // Ignore if not currently paired to user.
- if (!pairedDevices.ContainsReference(device))
- return;
-
- RemoveDeviceFromUser(userIndex, device);
- }
-
- /// <summary>
- /// Unpair all devices from the user.
- /// </summary>
- /// <remarks>
- /// If actions are associated with the user (<see cref="actions"/>), the list of devices used by the
- /// actions (<see cref="IInputActionCollection.devices"/>) is automatically updated.
- ///
- /// If a control scheme is activated on the user (<see cref="controlScheme"/>), <see cref="controlSchemeMatch"/>
- /// is automatically updated.
- ///
- /// Sends <see cref="InputUserChange.DeviceUnpaired"/> through <see cref="onChange"/> for every device
- /// unpaired from the user.
- /// </remarks>
- /// <seealso cref="PerformPairingWithDevice"/>
- /// <seealso cref="pairedDevices"/>
- /// <seealso cref="UnpairDevice"/>
- /// <seealso cref="UnpairDevicesAndRemoveUser"/>
- /// <seealso cref="InputUserChange.DeviceUnpaired"/>
- public void UnpairDevices()
- {
- var userIndex = index; // Throws if user is invalid.
-
- RemoveLostDevicesForUser(userIndex);
-
- using (InputActionRebindingExtensions.DeferBindingResolution())
- {
- // We could remove the devices in bulk here but we still have to notify one
- // by one which ends up being more complicated than just unpairing the devices
- // individually here.
- while (s_GlobalState.allUserData[userIndex].deviceCount > 0)
- UnpairDevice(s_GlobalState.allPairedDevices[s_GlobalState.allUserData[userIndex].deviceStartIndex + s_GlobalState.allUserData[userIndex].deviceCount - 1]);
- }
-
- // Update control scheme, if necessary.
- if (s_GlobalState.allUserData[userIndex].controlScheme != null)
- UpdateControlSchemeMatch(userIndex);
- }
-
- private static void RemoveLostDevicesForUser(int userIndex)
- {
- var lostDeviceCount = s_GlobalState.allUserData[userIndex].lostDeviceCount;
- if (lostDeviceCount > 0)
- {
- var lostDeviceStartIndex = s_GlobalState.allUserData[userIndex].lostDeviceStartIndex;
- ArrayHelpers.EraseSliceWithCapacity(ref s_GlobalState.allLostDevices, ref s_GlobalState.allLostDeviceCount,
- lostDeviceStartIndex, lostDeviceCount);
-
- s_GlobalState.allUserData[userIndex].lostDeviceCount = 0;
- s_GlobalState.allUserData[userIndex].lostDeviceStartIndex = 0;
-
- // Adjust indices of other users.
- for (var i = 0; i < s_GlobalState.allUserCount; ++i)
- {
- if (s_GlobalState.allUserData[i].lostDeviceStartIndex > lostDeviceStartIndex)
- s_GlobalState.allUserData[i].lostDeviceStartIndex -= lostDeviceCount;
- }
- }
- }
-
- /// <summary>
- /// Unpair all devices from the user and remove the user.
- /// </summary>
- /// <remarks>
- /// If actions are associated with the user (<see cref="actions"/>), the list of devices used by the
- /// actions (<see cref="IInputActionCollection.devices"/>) is reset as is the binding mask (<see
- /// cref="IInputActionCollection.bindingMask"/>) in case a control scheme is activated on the user.
- ///
- /// Sends <see cref="InputUserChange.DeviceUnpaired"/> through <see cref="onChange"/> for every device
- /// unpaired from the user.
- ///
- /// Sends <see cref="InputUserChange.Removed"/>.
- /// </remarks>
- /// <seealso cref="PerformPairingWithDevice"/>
- /// <seealso cref="pairedDevices"/>
- /// <seealso cref="UnpairDevice"/>
- /// <seealso cref="UnpairDevicesAndRemoveUser"/>
- /// <seealso cref="InputUserChange.DeviceUnpaired"/>
- /// <seealso cref="InputUserChange.Removed"/>
- public void UnpairDevicesAndRemoveUser()
- {
- UnpairDevices();
-
- var userIndex = index;
- RemoveUser(userIndex);
-
- m_Id = default;
- }
-
- /// <summary>
- /// Return a list of all currently added devices that are not paired to any user.
- /// </summary>
- /// <returns>A (possibly empty) list of devices that are currently not paired to a user.</returns>
- /// <remarks>
- /// The resulting list uses <see cref="Allocator.Temp"> temporary, unmanaged memory</see>. If not disposed of
- /// explicitly, the list will automatically be deallocated at the end of the frame and will become unusable.
- /// </remarks>
- /// <seealso cref="InputSystem.devices"/>
- /// <seealso cref="pairedDevices"/>
- /// <seealso cref="PerformPairingWithDevice"/>
- public static InputControlList<InputDevice> GetUnpairedInputDevices()
- {
- var list = new InputControlList<InputDevice>(Allocator.Temp);
- GetUnpairedInputDevices(ref list);
- return list;
- }
-
- /// <summary>
- /// Add all currently added devices that are not paired to any user to <paramref name="list"/>.
- /// </summary>
- /// <param name="list">List to add the devices to. Devices will be added to the end.</param>
- /// <returns>Number of devices added to <paramref name="list"/>.</returns>
- /// <seealso cref="InputSystem.devices"/>
- /// <seealso cref="pairedDevices"/>
- /// <seealso cref="PerformPairingWithDevice"/>
- public static int GetUnpairedInputDevices(ref InputControlList<InputDevice> list)
- {
- var countBefore = list.Count;
- foreach (var device in InputSystem.devices)
- {
- // If it's in s_AllPairedDevices, there is *some* user that is using the device.
- // We don't care which one it is here.
- if (ArrayHelpers.ContainsReference(s_GlobalState.allPairedDevices, s_GlobalState.allPairedDeviceCount, device))
- continue;
-
- list.Add(device);
- }
-
- return list.Count - countBefore;
- }
-
- /// <summary>
- /// Find the user (if any) that <paramref name="device"/> is currently paired to.
- /// </summary>
- /// <param name="device">An input device.</param>
- /// <returns>The user that <paramref name="device"/> is currently paired to or <c>null</c> if the device
- /// is not currently paired to an user.</returns>
- /// <remarks>
- /// Note that multiple users may be paired to the same device. If that is the case for <paramref name="device"/>,
- /// the method will return one of the users with no guarantee which one it is.
- ///
- /// To find all users paired to a device requires manually going through the list of users and their paired
- /// devices.
- /// </remarks>
- /// <exception cref="ArgumentNullException"><paramref name="device"/> is <c>null</c>.</exception>
- /// <seealso cref="pairedDevices"/>
- /// <seealso cref="PerformPairingWithDevice"/>
- public static InputUser? FindUserPairedToDevice(InputDevice device)
- {
- if (device == null)
- throw new ArgumentNullException(nameof(device));
-
- var userIndex = TryFindUserIndex(device);
- if (userIndex == -1)
- return null;
-
- return s_GlobalState.allUsers[userIndex];
- }
-
- public static InputUser? FindUserByAccount(InputUserAccountHandle platformUserAccountHandle)
- {
- if (platformUserAccountHandle == default(InputUserAccountHandle))
- throw new ArgumentException("Empty platform user account handle", nameof(platformUserAccountHandle));
-
- var userIndex = TryFindUserIndex(platformUserAccountHandle);
- if (userIndex == -1)
- return null;
-
- return s_GlobalState.allUsers[userIndex];
- }
-
- public static InputUser CreateUserWithoutPairedDevices()
- {
- var userIndex = AddUser();
- return s_GlobalState.allUsers[userIndex];
- }
-
- ////REVIEW: allow re-adding a user through this method?
- /// <summary>
- /// Pair the given device to a user.
- /// </summary>
- /// <param name="device">Device to pair to a user.</param>
- /// <param name="user">Optional parameter. If given, instead of creating a new user to pair the device
- /// to, the device is paired to the given user.</param>
- /// <param name="options">Optional set of options to modify pairing behavior.</param>
- /// <remarks>
- /// By default, a new user is created and <paramref name="device"/> is added <see cref="pairedDevices"/>
- /// of the user and <see cref="InputUserChange.DevicePaired"/> is sent on <see cref="onChange"/>.
- ///
- /// If a valid user is supplied to <paramref name="user"/>, the device is paired to the given user instead
- /// of creating a new user. By default, the device is added to the list of already paired devices for the user.
- /// This can be changed by using <see cref="InputUserPairingOptions.UnpairCurrentDevicesFromUser"/> which causes
- /// devices currently paired to the user to first be unpaired.
- ///
- /// The method will not prevent pairing of the same device to multiple users.
- ///
- /// Note that if the user has an associated set of actions (<see cref="actions"/>), the list of devices on the
- /// actions (<see cref="IInputActionCollection.devices"/>) will automatically be updated meaning that the newly
- /// paired devices will automatically reflect in the set of devices available to the user's actions. If the
- /// user has a control scheme that is currently activated (<see cref="controlScheme"/>), then <see cref="controlSchemeMatch"/>
- /// will also automatically update to reflect the matching of devices to the control scheme's device requirements.
- ///
- /// <example>
- /// <code>
- /// // Pair device to new user.
- /// var user = InputUser.PerformPairingWithDevice(wand1);
- ///
- /// // Pair another device to the same user.
- /// InputUser.PerformPairingWithDevice(wand2, user: user);
- /// </code>
- /// </example>
- /// </remarks>
- /// <seealso cref="pairedDevices"/>
- /// <seealso cref="UnpairDevice"/>
- /// <seealso cref="UnpairDevices"/>
- /// <seealso cref="UnpairDevicesAndRemoveUser"/>
- /// <seealso cref="InputUserChange.DevicePaired"/>
- public static InputUser PerformPairingWithDevice(InputDevice device,
- InputUser user = default,
- InputUserPairingOptions options = InputUserPairingOptions.None)
- {
- if (device == null)
- throw new ArgumentNullException(nameof(device));
- if (user != default && !user.valid)
- throw new ArgumentException("Invalid user", nameof(user));
-
- // Create new user, if needed.
- int userIndex;
- if (user == default)
- {
- userIndex = AddUser();
- }
- else
- {
- // We have an existing user.
- userIndex = user.index;
-
- // See if we're supposed to clear out the user's currently paired devices first.
- if ((options & InputUserPairingOptions.UnpairCurrentDevicesFromUser) != 0)
- user.UnpairDevices();
-
- // Ignore call if device is already paired to user.
- if (user.pairedDevices.ContainsReference(device))
- {
- // Still might have to initiate user account selection.
- if ((options & InputUserPairingOptions.ForcePlatformUserAccountSelection) != 0)
- InitiateUserAccountSelection(userIndex, device, options);
- return user;
- }
- }
-
- // Handle the user account side of pairing.
- var accountSelectionInProgress = InitiateUserAccountSelection(userIndex, device, options);
-
- // Except if we have initiate user account selection, pair the device to
- // to the user now.
- if (!accountSelectionInProgress)
- AddDeviceToUser(userIndex, device);
-
- return s_GlobalState.allUsers[userIndex];
- }
-
- private static bool InitiateUserAccountSelection(int userIndex, InputDevice device,
- InputUserPairingOptions options)
- {
- // See if there's a platform user account we can get from the device.
- // NOTE: We don't query the current user account if the caller has opted to force account selection.
- var queryUserAccountResult =
- (options & InputUserPairingOptions.ForcePlatformUserAccountSelection) == 0
- ? UpdatePlatformUserAccount(userIndex, device)
- : 0;
-
- ////REVIEW: what should we do if there already is an account selection in progress? InvalidOperationException?
- // If the device supports user account selection but we didn't get one,
- // try to initiate account selection.
- if ((options & InputUserPairingOptions.ForcePlatformUserAccountSelection) != 0 ||
- (queryUserAccountResult != InputDeviceCommand.GenericFailure &&
- (queryUserAccountResult & (long)QueryPairedUserAccountCommand.Result.DevicePairedToUserAccount) == 0 &&
- (options & InputUserPairingOptions.ForceNoPlatformUserAccountSelection) == 0))
- {
- if (InitiateUserAccountSelectionAtPlatformLevel(device))
- {
- s_GlobalState.allUserData[userIndex].flags |= UserFlags.UserAccountSelectionInProgress;
- s_GlobalState.ongoingAccountSelections.Append(
- new OngoingAccountSelection
- {
- device = device,
- userId = s_GlobalState.allUsers[userIndex].id,
- });
-
- // Make sure we receive a notification for the configuration event.
- HookIntoDeviceChange();
-
- // Tell listeners that we started an account selection.
- Notify(userIndex, InputUserChange.AccountSelectionInProgress, device);
-
- return true;
- }
- }
-
- return false;
- }
-
- public bool Equals(InputUser other)
- {
- return m_Id == other.m_Id;
- }
-
- public override bool Equals(object obj)
- {
- if (ReferenceEquals(null, obj))
- return false;
- return obj is InputUser && Equals((InputUser)obj);
- }
-
- public override int GetHashCode()
- {
- return (int)m_Id;
- }
-
- public static bool operator==(InputUser left, InputUser right)
- {
- return left.m_Id == right.m_Id;
- }
-
- public static bool operator!=(InputUser left, InputUser right)
- {
- return left.m_Id != right.m_Id;
- }
-
- /// <summary>
- /// Add a new user.
- /// </summary>
- /// <returns>Index of the newly created user.</returns>
- /// <remarks>
- /// Adding a user sends a notification with <see cref="InputUserChange.Added"/> through <see cref="onChange"/>.
- ///
- /// The user will start out with no devices and no actions assigned.
- ///
- /// The user is added to <see cref="all"/>.
- /// </remarks>
- private static int AddUser()
- {
- var id = ++s_GlobalState.lastUserId;
-
- // Add to list.
- var userCount = s_GlobalState.allUserCount;
- ArrayHelpers.AppendWithCapacity(ref s_GlobalState.allUsers, ref userCount, new InputUser { m_Id = id });
- var userIndex = ArrayHelpers.AppendWithCapacity(ref s_GlobalState.allUserData, ref s_GlobalState.allUserCount, new UserData());
-
- // Send notification.
- Notify(userIndex, InputUserChange.Added, null);
-
- return userIndex;
- }
-
- /// <summary>
- /// Remove an active user.
- /// </summary>
- /// <param name="userIndex">Index of active user.</param>
- /// <remarks>
- /// Removing a user also unassigns all currently assigned devices from the user. On completion of this
- /// method, <see cref="pairedDevices"/> of <paramref name="user"/> will be empty.
- /// </remarks>
- private static void RemoveUser(int userIndex)
- {
- Debug.Assert(userIndex >= 0 && userIndex < s_GlobalState.allUserCount, "User index is invalid");
- Debug.Assert(s_GlobalState.allUserData[userIndex].deviceCount == 0, "User must not have paired devices still");
-
- // Reset data from control scheme.
- if (s_GlobalState.allUserData[userIndex].controlScheme != null)
- {
- if (s_GlobalState.allUserData[userIndex].actions != null)
- s_GlobalState.allUserData[userIndex].actions.bindingMask = null;
- }
- s_GlobalState.allUserData[userIndex].controlSchemeMatch.Dispose();
-
- // Remove lost devices.
- RemoveLostDevicesForUser(userIndex);
-
- // Remove account selections that are in progress.
- for (var i = 0; i < s_GlobalState.ongoingAccountSelections.length; ++i)
- {
- if (s_GlobalState.ongoingAccountSelections[i].userId != s_GlobalState.allUsers[userIndex].id)
- continue;
-
- s_GlobalState.ongoingAccountSelections.RemoveAtByMovingTailWithCapacity(i);
- --i;
- }
-
- // Send notification (do before we actually remove the user).
- Notify(userIndex, InputUserChange.Removed, null);
-
- // Remove.
- var userCount = s_GlobalState.allUserCount;
- s_GlobalState.allUsers.EraseAtWithCapacity(ref userCount, userIndex);
- s_GlobalState.allUserData.EraseAtWithCapacity(ref s_GlobalState.allUserCount, userIndex);
-
- // Remove our hook if we no longer need it.
- if (s_GlobalState.allUserCount == 0)
- {
- UnhookFromDeviceChange();
- UnhookFromActionChange();
- }
- }
-
- private static void Notify(int userIndex, InputUserChange change, InputDevice device)
- {
- Debug.Assert(userIndex >= 0 && userIndex < s_GlobalState.allUserCount, "User index is invalid");
-
- if (s_GlobalState.onChange.length == 0)
- return;
- Profiler.BeginSample("InputUser.onChange");
- s_GlobalState.onChange.LockForChanges();
- for (var i = 0; i < s_GlobalState.onChange.length; ++i)
- {
- try
- {
- s_GlobalState.onChange[i](s_GlobalState.allUsers[userIndex], change, device);
- }
- catch (Exception exception)
- {
- Debug.LogError($"{exception.GetType().Name} while executing 'InputUser.onChange' callbacks");
- Debug.LogException(exception);
- }
- }
- s_GlobalState.onChange.UnlockForChanges();
- Profiler.EndSample();
- }
-
- private static int TryFindUserIndex(uint userId)
- {
- Debug.Assert(userId != InvalidId, "User ID is invalid");
-
- for (var i = 0; i < s_GlobalState.allUserCount; ++i)
- {
- if (s_GlobalState.allUsers[i].m_Id == userId)
- return i;
- }
- return -1;
- }
-
- private static int TryFindUserIndex(InputUserAccountHandle platformHandle)
- {
- Debug.Assert(platformHandle != default, "User platform handle is invalid");
-
- for (var i = 0; i < s_GlobalState.allUserCount; ++i)
- {
- if (s_GlobalState.allUserData[i].platformUserAccountHandle == platformHandle)
- return i;
- }
- return -1;
- }
-
- /// <summary>
- /// Find the user (if any) that is currently assigned the given <paramref name="device"/>.
- /// </summary>
- /// <param name="device">An input device that has been added to the system.</param>
- /// <returns>Index of the user that has <paramref name="device"/> among its <see cref="pairedDevices"/> or -1 if
- /// no user is currently assigned the given device.</returns>
- private static int TryFindUserIndex(InputDevice device)
- {
- Debug.Assert(device != null, "Device cannot be null");
-
- var indexOfDevice = s_GlobalState.allPairedDevices.IndexOfReference(device, s_GlobalState.allPairedDeviceCount);
- if (indexOfDevice == -1)
- return -1;
-
- for (var i = 0; i < s_GlobalState.allUserCount; ++i)
- {
- var startIndex = s_GlobalState.allUserData[i].deviceStartIndex;
- if (startIndex <= indexOfDevice && indexOfDevice < startIndex + s_GlobalState.allUserData[i].deviceCount)
- return i;
- }
-
- return -1;
- }
-
- /// <summary>
- /// Add the given device to the user as either a lost device or a paired device.
- /// </summary>
- /// <param name="userIndex"></param>
- /// <param name="device"></param>
- /// <param name="asLostDevice"></param>
- private static void AddDeviceToUser(int userIndex, InputDevice device, bool asLostDevice = false, bool dontUpdateControlScheme = false)
- {
- Debug.Assert(userIndex >= 0 && userIndex < s_GlobalState.allUserCount, "User index is invalid");
- Debug.Assert(device != null, "Device cannot be null");
- if (asLostDevice)
- Debug.Assert(!s_GlobalState.allUsers[userIndex].lostDevices.ContainsReference(device), "Device already in set of lostDevices for user");
- else
- Debug.Assert(!s_GlobalState.allUsers[userIndex].pairedDevices.ContainsReference(device), "Device already in set of pairedDevices for user");
-
- var deviceCount = asLostDevice
- ? s_GlobalState.allUserData[userIndex].lostDeviceCount
- : s_GlobalState.allUserData[userIndex].deviceCount;
- var deviceStartIndex = asLostDevice
- ? s_GlobalState.allUserData[userIndex].lostDeviceStartIndex
- : s_GlobalState.allUserData[userIndex].deviceStartIndex;
-
- ++s_GlobalState.pairingStateVersion;
-
- // Move our devices to end of array.
- if (deviceCount > 0)
- {
- ArrayHelpers.MoveSlice(asLostDevice ? s_GlobalState.allLostDevices : s_GlobalState.allPairedDevices, deviceStartIndex,
- asLostDevice ? s_GlobalState.allLostDeviceCount - deviceCount : s_GlobalState.allPairedDeviceCount - deviceCount,
- deviceCount);
-
- // Adjust users that have been impacted by the change.
- for (var i = 0; i < s_GlobalState.allUserCount; ++i)
- {
- if (i == userIndex)
- continue;
-
- if ((asLostDevice ? s_GlobalState.allUserData[i].lostDeviceStartIndex : s_GlobalState.allUserData[i].deviceStartIndex) <= deviceStartIndex)
- continue;
-
- if (asLostDevice)
- s_GlobalState.allUserData[i].lostDeviceStartIndex -= deviceCount;
- else
- s_GlobalState.allUserData[i].deviceStartIndex -= deviceCount;
- }
- }
-
- // Append to array.
- if (asLostDevice)
- {
- s_GlobalState.allUserData[userIndex].lostDeviceStartIndex = s_GlobalState.allLostDeviceCount - deviceCount;
- ArrayHelpers.AppendWithCapacity(ref s_GlobalState.allLostDevices, ref s_GlobalState.allLostDeviceCount, device);
- ++s_GlobalState.allUserData[userIndex].lostDeviceCount;
- }
- else
- {
- s_GlobalState.allUserData[userIndex].deviceStartIndex = s_GlobalState.allPairedDeviceCount - deviceCount;
- ArrayHelpers.AppendWithCapacity(ref s_GlobalState.allPairedDevices, ref s_GlobalState.allPairedDeviceCount, device);
- ++s_GlobalState.allUserData[userIndex].deviceCount;
-
- // If the user has actions, sync the devices on them with what we have now.
- var actions = s_GlobalState.allUserData[userIndex].actions;
- if (actions != null)
- {
- actions.devices = s_GlobalState.allUsers[userIndex].pairedDevices;
-
- // Also, if we have a control scheme, update the matching of device requirements
- // against the device we now have.
- if (!dontUpdateControlScheme && s_GlobalState.allUserData[userIndex].controlScheme != null)
- UpdateControlSchemeMatch(userIndex);
- }
- }
-
- // Make sure we get OnDeviceChange notifications.
- HookIntoDeviceChange();
-
- // Let listeners know.
- Notify(userIndex, asLostDevice ? InputUserChange.DeviceLost : InputUserChange.DevicePaired, device);
- }
-
- private static void RemoveDeviceFromUser(int userIndex, InputDevice device, bool asLostDevice = false)
- {
- Debug.Assert(userIndex >= 0 && userIndex < s_GlobalState.allUserCount, "User index is invalid");
- Debug.Assert(device != null, "Device cannot be null");
-
- var deviceIndex = asLostDevice
- ? s_GlobalState.allLostDevices.IndexOfReference(device, s_GlobalState.allLostDeviceCount)
- : s_GlobalState.allPairedDevices.IndexOfReference(device, s_GlobalState.allUserData[userIndex].deviceStartIndex,
- s_GlobalState.allUserData[userIndex].deviceCount);
- if (deviceIndex == -1)
- {
- // Device not in list. Ignore.
- return;
- }
-
- if (asLostDevice)
- {
- s_GlobalState.allLostDevices.EraseAtWithCapacity(ref s_GlobalState.allLostDeviceCount, deviceIndex);
- --s_GlobalState.allUserData[userIndex].lostDeviceCount;
- }
- else
- {
- ++s_GlobalState.pairingStateVersion;
- s_GlobalState.allPairedDevices.EraseAtWithCapacity(ref s_GlobalState.allPairedDeviceCount, deviceIndex);
- --s_GlobalState.allUserData[userIndex].deviceCount;
- }
-
- // Adjust indices of other users.
- for (var i = 0; i < s_GlobalState.allUserCount; ++i)
- {
- if ((asLostDevice ? s_GlobalState.allUserData[i].lostDeviceStartIndex : s_GlobalState.allUserData[i].deviceStartIndex) <= deviceIndex)
- continue;
-
- if (asLostDevice)
- --s_GlobalState.allUserData[i].lostDeviceStartIndex;
- else
- --s_GlobalState.allUserData[i].deviceStartIndex;
- }
-
- if (!asLostDevice)
- {
- // Remove any ongoing account selections for the user on the given device.
- for (var i = 0; i < s_GlobalState.ongoingAccountSelections.length; ++i)
- {
- if (s_GlobalState.ongoingAccountSelections[i].userId != s_GlobalState.allUsers[userIndex].id ||
- s_GlobalState.ongoingAccountSelections[i].device != device)
- continue;
-
- s_GlobalState.ongoingAccountSelections.RemoveAtByMovingTailWithCapacity(i);
- --i;
- }
-
- // If the user has actions, sync the devices on them with what we have now.
- var actions = s_GlobalState.allUserData[userIndex].actions;
- if (actions != null)
- {
- actions.devices = s_GlobalState.allUsers[userIndex].pairedDevices;
-
- if (s_GlobalState.allUsers[userIndex].controlScheme != null)
- UpdateControlSchemeMatch(userIndex);
- }
-
- // Notify listeners.
- Notify(userIndex, InputUserChange.DeviceUnpaired, device);
- }
- }
-
- private static void UpdateControlSchemeMatch(int userIndex, bool autoPairMissing = false)
- {
- Debug.Assert(userIndex >= 0 && userIndex < s_GlobalState.allUserCount, "User index is invalid");
-
- // Nothing to do if we don't have a control scheme.
- if (s_GlobalState.allUserData[userIndex].controlScheme == null)
- return;
-
- // Get rid of last match result and start new match.
- s_GlobalState.allUserData[userIndex].controlSchemeMatch.Dispose();
- var matchResult = new InputControlScheme.MatchResult();
- try
- {
- // Match the control scheme's requirements against the devices paired to the user.
- var scheme = s_GlobalState.allUserData[userIndex].controlScheme.Value;
- if (scheme.deviceRequirements.Count > 0)
- {
- var availableDevices = new InputControlList<InputDevice>(Allocator.Temp);
- try
- {
- // Add devices already paired to user.
- availableDevices.AddSlice(s_GlobalState.allUsers[userIndex].pairedDevices);
-
- // If we're supposed to grab whatever additional devices we need from what's
- // available, add all unpaired devices to the list.
- // NOTE: These devices go *after* the devices already paired (if any) meaning that
- // the control scheme matching will grab already paired devices *first*.
- if (autoPairMissing)
- {
- var startIndex = availableDevices.Count;
- var count = GetUnpairedInputDevices(ref availableDevices);
-
- // We want to favor devices that are already assigned to the same platform user account.
- // Sort the unpaired devices we've added to the list such that the ones belonging to the
- // same user account come first.
- if (s_GlobalState.allUserData[userIndex].platformUserAccountHandle != null)
- availableDevices.Sort(startIndex, count,
- new CompareDevicesByUserAccount
- {
- platformUserAccountHandle = s_GlobalState.allUserData[userIndex].platformUserAccountHandle.Value
- });
- }
-
- matchResult = scheme.PickDevicesFrom(availableDevices);
- if (matchResult.isSuccessfulMatch)
- {
- // Control scheme is satisfied with the devices we have available.
- // If we may have grabbed as of yet unpaired devices, go and pair them to the user.
- if (autoPairMissing)
- {
- // Update match result on user before potentially invoking callbacks.
- s_GlobalState.allUserData[userIndex].controlSchemeMatch = matchResult;
-
- foreach (var device in matchResult.devices)
- {
- // Skip if already paired to user.
- if (s_GlobalState.allUsers[userIndex].pairedDevices.ContainsReference(device))
- continue;
-
- AddDeviceToUser(userIndex, device, dontUpdateControlScheme: true);
- }
- }
- }
- }
- finally
- {
- availableDevices.Dispose();
- }
- }
-
- s_GlobalState.allUserData[userIndex].controlSchemeMatch = matchResult;
- }
- catch (Exception)
- {
- // If we had an exception and are bailing out, make sure we aren't leaking native memory
- // we allocated.
- matchResult.Dispose();
- throw;
- }
- }
-
- private static long UpdatePlatformUserAccount(int userIndex, InputDevice device)
- {
- Debug.Assert(userIndex >= 0 && userIndex < s_GlobalState.allUserCount, "User index is invalid");
-
- // Fetch account details from backend.
- var queryResult = QueryPairedPlatformUserAccount(device, out var platformUserAccountHandle,
- out var platformUserAccountName, out var platformUserAccountId);
-
- // Nothing much to do if not supported by device.
- if (queryResult == InputDeviceCommand.GenericFailure)
- {
- // Check if there's an account selection in progress. There shouldn't be as it's
- // weird for the device to no signal it does not support querying user account, but
- // just to be safe, we check.
- if ((s_GlobalState.allUserData[userIndex].flags & UserFlags.UserAccountSelectionInProgress) != 0)
- Notify(userIndex, InputUserChange.AccountSelectionCanceled, null);
-
- s_GlobalState.allUserData[userIndex].platformUserAccountHandle = null;
- s_GlobalState.allUserData[userIndex].platformUserAccountName = null;
- s_GlobalState.allUserData[userIndex].platformUserAccountId = null;
-
- return queryResult;
- }
-
- // Check if there's an account selection that we have initiated.
- if ((s_GlobalState.allUserData[userIndex].flags & UserFlags.UserAccountSelectionInProgress) != 0)
- {
- // Yes, there is. See if it is complete.
-
- if ((queryResult & (long)QueryPairedUserAccountCommand.Result.UserAccountSelectionInProgress) != 0)
- {
- // No, still in progress.
- }
- else if ((queryResult & (long)QueryPairedUserAccountCommand.Result.UserAccountSelectionCanceled) != 0)
- {
- // Got canceled.
- Notify(userIndex, InputUserChange.AccountSelectionCanceled, device);
- }
- else
- {
- // Yes, it is complete.
- s_GlobalState.allUserData[userIndex].flags &= ~UserFlags.UserAccountSelectionInProgress;
-
- s_GlobalState.allUserData[userIndex].platformUserAccountHandle = platformUserAccountHandle;
- s_GlobalState.allUserData[userIndex].platformUserAccountName = platformUserAccountName;
- s_GlobalState.allUserData[userIndex].platformUserAccountId = platformUserAccountId;
-
- Notify(userIndex, InputUserChange.AccountSelectionComplete, device);
- }
- }
- // Check if user account details have changed.
- else if (s_GlobalState.allUserData[userIndex].platformUserAccountHandle != platformUserAccountHandle ||
- s_GlobalState.allUserData[userIndex].platformUserAccountId != platformUserAccountId)
- {
- s_GlobalState.allUserData[userIndex].platformUserAccountHandle = platformUserAccountHandle;
- s_GlobalState.allUserData[userIndex].platformUserAccountName = platformUserAccountName;
- s_GlobalState.allUserData[userIndex].platformUserAccountId = platformUserAccountId;
-
- Notify(userIndex, InputUserChange.AccountChanged, device);
- }
- else if (s_GlobalState.allUserData[userIndex].platformUserAccountName != platformUserAccountName)
- {
- Notify(userIndex, InputUserChange.AccountNameChanged, device);
- }
-
- return queryResult;
- }
-
- ////TODO: bring documentation for these back when user management is implemented on Xbox and PS
- private static long QueryPairedPlatformUserAccount(InputDevice device,
- out InputUserAccountHandle? platformAccountHandle, out string platformAccountName, out string platformAccountId)
- {
- Debug.Assert(device != null, "Device cannot be null");
-
- // Query user account info from backend.
- var queryPairedUser = QueryPairedUserAccountCommand.Create();
- var result = device.ExecuteCommand(ref queryPairedUser);
- if (result == InputDeviceCommand.GenericFailure)
- {
- // Not currently paired to user account in backend.
- platformAccountHandle = null;
- platformAccountName = null;
- platformAccountId = null;
- return InputDeviceCommand.GenericFailure;
- }
-
- // Success. There is a user account currently paired to the device and we now have the
- // platform's user account details.
-
- if ((result & (long)QueryPairedUserAccountCommand.Result.DevicePairedToUserAccount) != 0)
- {
- platformAccountHandle =
- new InputUserAccountHandle(device.description.interfaceName ?? "<Unknown>", queryPairedUser.handle);
- platformAccountName = queryPairedUser.name;
- platformAccountId = queryPairedUser.id;
- }
- else
- {
- // The device supports QueryPairedUserAccountCommand but reports that the
- // device is not currently paired to a user.
- //
- // NOTE: On Switch, where the system itself does not store account<->pairing, we will always
- // end up here until we've initiated an account selection through the backend itself.
- platformAccountHandle = null;
- platformAccountName = null;
- platformAccountId = null;
- }
-
- return result;
- }
-
- /// <summary>
- /// Try to initiate user account pairing for the given device at the platform level.
- /// </summary>
- /// <param name="device"></param>
- /// <returns>True if the device accepted the request and an account picker has been raised.</returns>
- /// <remarks>
- /// Sends <see cref="InitiateUserAccountPairingCommand"/> to the device.
- /// </remarks>
- private static bool InitiateUserAccountSelectionAtPlatformLevel(InputDevice device)
- {
- Debug.Assert(device != null, "Device cannot be null");
-
- var initiateUserPairing = InitiateUserAccountPairingCommand.Create();
- var initiatePairingResult = device.ExecuteCommand(ref initiateUserPairing);
- if (initiatePairingResult == (long)InitiateUserAccountPairingCommand.Result.ErrorAlreadyInProgress)
- throw new InvalidOperationException("User pairing already in progress");
-
- return initiatePairingResult == (long)InitiateUserAccountPairingCommand.Result.SuccessfullyInitiated;
- }
-
- private static void OnActionChange(object obj, InputActionChange change)
- {
- if (change == InputActionChange.BoundControlsChanged)
- {
- for (var i = 0; i < s_GlobalState.allUserCount; ++i)
- {
- ref var user = ref s_GlobalState.allUsers[i];
- if (ReferenceEquals(user.actions, obj))
- Notify(i, InputUserChange.ControlsChanged, null);
- }
- }
- }
-
- /// <summary>
- /// Invoked in response to <see cref="InputSystem.onDeviceChange"/>.
- /// </summary>
- /// <param name="device"></param>
- /// <param name="change"></param>
- /// <remarks>
- /// We monitor the device setup in the system for activity that impacts the user setup.
- /// </remarks>
- private static void OnDeviceChange(InputDevice device, InputDeviceChange change)
- {
- switch (change)
- {
- // Existing device removed. May mean a user has lost a device due to the battery running
- // out or the device being unplugged.
- // NOTE: We ignore Disconnected here. Removed is what gets sent whenever a device is taken off of
- // InputSystem.devices -- which is what we're interested in here.
- case InputDeviceChange.Removed:
- {
- // Could have been removed from multiple users. Repeatedly search in s_AllPairedDevices
- // until we can't find the device anymore.
- var deviceIndex = s_GlobalState.allPairedDevices.IndexOfReference(device, s_GlobalState.allPairedDeviceCount);
- while (deviceIndex != -1)
- {
- // Find user. Must be there as we found the device in s_AllPairedDevices.
- var userIndex = -1;
- for (var i = 0; i < s_GlobalState.allUserCount; ++i)
- {
- var deviceStartIndex = s_GlobalState.allUserData[i].deviceStartIndex;
- if (deviceStartIndex <= deviceIndex && deviceIndex < deviceStartIndex + s_GlobalState.allUserData[i].deviceCount)
- {
- userIndex = i;
- break;
- }
- }
-
- // Add device to list of lost devices.
- // NOTE: This will also send a DeviceLost notification.
- // NOTE: Temporarily the device is on both lists.
- AddDeviceToUser(userIndex, device, asLostDevice: true);
-
- // Remove it from the user.
- RemoveDeviceFromUser(userIndex, device);
-
- // Search for another user paired to the same device.
- deviceIndex = s_GlobalState.allPairedDevices.IndexOfReference(device, s_GlobalState.allPairedDeviceCount);
- }
- break;
- }
-
- // New device was added. See if it was a device we previously lost on a user.
- case InputDeviceChange.Added:
- {
- // Search all lost devices. Could affect multiple users.
- // Note that RemoveDeviceFromUser removes one element, hence no advancement of deviceIndex.
- for (var deviceIndex = FindLostDevice(device); deviceIndex != -1;
- deviceIndex = FindLostDevice(device, deviceIndex))
- {
- // Find user. Must be there as we found the device in s_AllLostDevices.
- var userIndex = -1;
- for (var i = 0; i < s_GlobalState.allUserCount; ++i)
- {
- var deviceStartIndex = s_GlobalState.allUserData[i].lostDeviceStartIndex;
- if (deviceStartIndex <= deviceIndex && deviceIndex < deviceStartIndex + s_GlobalState.allUserData[i].lostDeviceCount)
- {
- userIndex = i;
- break;
- }
- }
-
- // Remove from list of lost devices. No notification. Notice that we need to use device
- // from lost device list even if its another instance.
- RemoveDeviceFromUser(userIndex, s_GlobalState.allLostDevices[deviceIndex], asLostDevice: true);
-
- // Notify.
- Notify(userIndex, InputUserChange.DeviceRegained, device);
-
- // Add back as normally paired device.
- AddDeviceToUser(userIndex, device);
- }
- break;
- }
-
- // Device had its configuration changed which may mean we have a different user account paired
- // to the device now.
- case InputDeviceChange.ConfigurationChanged:
- {
- // See if the this is a device that we were waiting for an account selection on. If so, pair
- // it to the user that was waiting.
- var wasOngoingAccountSelection = false;
- for (var i = 0; i < s_GlobalState.ongoingAccountSelections.length; ++i)
- {
- if (s_GlobalState.ongoingAccountSelections[i].device != device)
- continue;
-
- var userIndex = new InputUser { m_Id = s_GlobalState.ongoingAccountSelections[i].userId }.index;
- var queryResult = UpdatePlatformUserAccount(userIndex, device);
- if ((queryResult & (long)QueryPairedUserAccountCommand.Result.UserAccountSelectionInProgress) == 0)
- {
- wasOngoingAccountSelection = true;
- s_GlobalState.ongoingAccountSelections.RemoveAtByMovingTailWithCapacity(i);
- --i;
-
- // If the device wasn't paired to the user, pair it now.
- if (!s_GlobalState.allUsers[userIndex].pairedDevices.ContainsReference(device))
- AddDeviceToUser(userIndex, device);
- }
- }
-
- // If it wasn't a configuration change event from an account selection, go and check whether
- // there was a user account change that happened outside the application.
- if (!wasOngoingAccountSelection)
- {
- // Could be paired to multiple users. Repeatedly search in s_AllPairedDevices
- // until we can't find the device anymore.
- var deviceIndex = s_GlobalState.allPairedDevices.IndexOfReference(device, s_GlobalState.allPairedDeviceCount);
- while (deviceIndex != -1)
- {
- // Find user. Must be there as we found the device in s_AllPairedDevices.
- var userIndex = -1;
- for (var i = 0; i < s_GlobalState.allUserCount; ++i)
- {
- var deviceStartIndex = s_GlobalState.allUserData[i].deviceStartIndex;
- if (deviceStartIndex <= deviceIndex && deviceIndex < deviceStartIndex + s_GlobalState.allUserData[i].deviceCount)
- {
- userIndex = i;
- break;
- }
- }
-
- // Check user account.
- UpdatePlatformUserAccount(userIndex, device);
-
- // Search for another user paired to the same device.
- // Note that action is tied to user and hence we can skip to end of slice associated
- // with the current user or at least one element forward.
- var offsetNextSlice = deviceIndex + Math.Max(1, s_GlobalState.allUserData[userIndex].deviceCount);
- deviceIndex = s_GlobalState.allPairedDevices.IndexOfReference(device, offsetNextSlice, s_GlobalState.allPairedDeviceCount - offsetNextSlice);
- }
- }
- break;
- }
- }
- }
-
- private static int FindLostDevice(InputDevice device, int startIndex = 0)
- {
- // Compare both by device ID and by reference. We may be looking at a device that was recreated
- // due to layout changes (new InputDevice instance, same ID) or a device that was reconnected
- // and thus fetched out of `disconnectedDevices` (same InputDevice instance, new ID).
-
- var newDeviceId = device.deviceId;
- for (var i = startIndex; i < s_GlobalState.allLostDeviceCount; ++i)
- {
- var lostDevice = s_GlobalState.allLostDevices[i];
- if (device == lostDevice || lostDevice.deviceId == newDeviceId) return i;
- }
-
- return -1;
- }
-
- // We hook this into InputSystem.onEvent when listening for activity on unpaired devices.
- // What this means is that we get to run *before* state reaches the device. This in turn
- // means that should the device get paired as a result, actions that are enabled as part
- // of the pairing will immediately get triggered. This would not be the case if we hook
- // into InputState.onDeviceChange instead which only triggers once state has been altered.
- //
- // NOTE: This also means that unpaired device activity will *only* be detected from events,
- // NOT from state changes applied directly through InputState.Change.
- private static void OnEvent(InputEventPtr eventPtr, InputDevice device)
- {
- Debug.Assert(s_GlobalState.listenForUnpairedDeviceActivity != 0,
- "This should only be called while listening for unpaired device activity");
- if (s_GlobalState.listenForUnpairedDeviceActivity == 0)
- return;
-
- // Ignore input in editor.
- #if UNITY_EDITOR
- if (InputState.currentUpdateType == InputUpdateType.Editor)
- return;
- #endif
-
- // Ignore any state change not triggered from a state event.
- var eventType = eventPtr.type;
- if (eventType != StateEvent.Type && eventType != DeltaStateEvent.Type)
- return;
-
- // Ignore event if device is disabled.
- if (!device.enabled)
- return;
-
- // See if it's a device not belonging to any user.
- if (ArrayHelpers.ContainsReference(s_GlobalState.allPairedDevices, s_GlobalState.allPairedDeviceCount, device))
- {
- // No, it's a device already paired to a player so do nothing.
- return;
- }
-
- Profiler.BeginSample("InputCheckForUnpairedDeviceActivity");
-
- // Apply the pre-filter. If there's callbacks and none of them return true,
- // we early out and ignore the event entirely.
- if (!DelegateHelpers.InvokeCallbacksSafe_AnyCallbackReturnsTrue(
- ref s_GlobalState.onPreFilterUnpairedDeviceUsed, device, eventPtr, "InputUser.onPreFilterUnpairedDeviceActivity"))
- {
- Profiler.EndSample();
- return;
- }
-
- // Go through the changed controls in the event and look for ones actuated
- // above a magnitude of a little above zero.
- foreach (var control in eventPtr.EnumerateChangedControls(device: device, magnitudeThreshold: 0.0001f))
- {
- var deviceHasBeenPaired = false;
- s_GlobalState.onUnpairedDeviceUsed.LockForChanges();
- for (var n = 0; n < s_GlobalState.onUnpairedDeviceUsed.length; ++n)
- {
- var pairingStateVersionBefore = s_GlobalState.pairingStateVersion;
-
- try
- {
- s_GlobalState.onUnpairedDeviceUsed[n](control, eventPtr);
- }
- catch (Exception exception)
- {
- Debug.LogError($"{exception.GetType().Name} while executing 'InputUser.onUnpairedDeviceUsed' callbacks");
- Debug.LogException(exception);
- }
-
- if (pairingStateVersionBefore != s_GlobalState.pairingStateVersion
- && FindUserPairedToDevice(device) != null)
- {
- deviceHasBeenPaired = true;
- break;
- }
- }
- s_GlobalState.onUnpairedDeviceUsed.UnlockForChanges();
-
- // If the device was paired in one of the callbacks, stop processing
- // changes on it.
- if (deviceHasBeenPaired)
- break;
- }
-
- Profiler.EndSample();
- }
-
- /// <summary>
- /// Syntax for configuring a control scheme on a user.
- /// </summary>
- public struct ControlSchemeChangeSyntax
- {
- /// <summary>
- /// Leave the user's paired devices in place but pair any available devices
- /// that are still required by the control scheme.
- /// </summary>
- /// <returns></returns>
- /// <remarks>
- /// If there are unpaired devices that, at the platform level, are associated with the same
- /// user account, those will take precedence over other unpaired devices.
- /// </remarks>
- public ControlSchemeChangeSyntax AndPairRemainingDevices()
- {
- UpdateControlSchemeMatch(m_UserIndex, autoPairMissing: true);
- return this;
- }
-
- internal int m_UserIndex;
- }
-
- private uint m_Id;
-
- [Flags]
- internal enum UserFlags
- {
- BindToAllDevices = 1 << 0,
-
- /// <summary>
- /// Whether we have initiated a user account selection.
- /// </summary>
- UserAccountSelectionInProgress = 1 << 1,
- }
-
- /// <summary>
- /// Data we store for each user.
- /// </summary>
- private struct UserData
- {
- /// <summary>
- /// The platform handle associated with the user.
- /// </summary>
- /// <remarks>
- /// If set, this identifies the user on the platform. It also means that the devices
- /// assigned to the user may be paired at the platform level.
- /// </remarks>
- public InputUserAccountHandle? platformUserAccountHandle;
-
- /// <summary>
- /// Plain-text user name as returned by the underlying platform. Null if not associated with user on platform.
- /// </summary>
- public string platformUserAccountName;
-
- /// <summary>
- /// Platform-specific ID that identifies the user across sessions even if the user
- /// name changes.
- /// </summary>
- /// <remarks>
- /// This might not be a human-readable string.
- /// </remarks>
- public string platformUserAccountId;
-
- /// <summary>
- /// Number of devices in <see cref="InputUser.s_AllPairedDevices"/> assigned to the user.
- /// </summary>
- public int deviceCount;
-
- /// <summary>
- /// Index in <see cref="InputUser.s_AllPairedDevices"/> where the devices for this user start. Only valid
- /// if <see cref="deviceCount"/> is greater than zero.
- /// </summary>
- public int deviceStartIndex;
-
- /// <summary>
- /// Input actions associated with the user.
- /// </summary>
- public IInputActionCollection actions;
-
- /// <summary>
- /// Currently active control scheme or null if no control scheme has been set on the user.
- /// </summary>
- /// <remarks>
- /// This also dictates the binding mask that we're using with <see cref="actions"/>.
- /// </remarks>
- public InputControlScheme? controlScheme;
-
- public InputControlScheme.MatchResult controlSchemeMatch;
-
- /// <summary>
- /// Number of devices in <see cref="InputUser.s_AllLostDevices"/> assigned to the user.
- /// </summary>
- public int lostDeviceCount;
-
- /// <summary>
- /// Index in <see cref="InputUser.s_AllLostDevices"/> where the lost devices for this user start. Only valid
- /// if <see cref="lostDeviceCount"/> is greater than zero.
- /// </summary>
- public int lostDeviceStartIndex;
-
- ////TODO
- //public InputUserSettings settings;
-
- public UserFlags flags;
- }
-
- /// <summary>
- /// Compare two devices for being associated with a specific platform user account.
- /// </summary>
- private struct CompareDevicesByUserAccount : IComparer<InputDevice>
- {
- public InputUserAccountHandle platformUserAccountHandle;
-
- public int Compare(InputDevice x, InputDevice y)
- {
- var firstAccountHandle = GetUserAccountHandleForDevice(x);
- var secondAccountHandle = GetUserAccountHandleForDevice(x);
-
- if (firstAccountHandle == platformUserAccountHandle &&
- secondAccountHandle == platformUserAccountHandle)
- return 0;
-
- if (firstAccountHandle == platformUserAccountHandle)
- return -1;
-
- if (secondAccountHandle == platformUserAccountHandle)
- return 1;
-
- return 0;
- }
-
- [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "device", Justification = "Keep this for future implementation")]
- private static InputUserAccountHandle? GetUserAccountHandleForDevice(InputDevice device)
- {
- ////TODO (need to cache this)
- return null;
- }
- }
-
- private struct OngoingAccountSelection
- {
- public InputDevice device;
- public uint userId;
- }
-
- private struct GlobalState
- {
- internal int pairingStateVersion;
- internal uint lastUserId;
- internal int allUserCount;
- internal int allPairedDeviceCount;
- internal int allLostDeviceCount;
- internal InputUser[] allUsers;
- internal UserData[] allUserData;
- internal InputDevice[] allPairedDevices; // We keep a single array that we slice out to each user.
- internal InputDevice[] allLostDevices; // We keep a single array that we slice out to each user.
- internal InlinedArray<OngoingAccountSelection> ongoingAccountSelections;
- internal CallbackArray<Action<InputUser, InputUserChange, InputDevice>> onChange;
- internal CallbackArray<Action<InputControl, InputEventPtr>> onUnpairedDeviceUsed;
- internal CallbackArray<Func<InputDevice, InputEventPtr, bool>> onPreFilterUnpairedDeviceUsed;
- internal Action<object, InputActionChange> actionChangeDelegate;
- internal Action<InputDevice, InputDeviceChange> onDeviceChangeDelegate;
- internal Action<InputEventPtr, InputDevice> onEventDelegate;
- internal bool onActionChangeHooked;
- internal bool onDeviceChangeHooked;
- internal bool onEventHooked;
- internal int listenForUnpairedDeviceActivity;
- }
-
- private static GlobalState s_GlobalState;
-
- internal static ISavedState SaveAndResetState()
- {
- // Save current state and provide an opaque interface to restore it
- var savedState = new SavedStructState<GlobalState>(
- ref s_GlobalState,
- (ref GlobalState state) => s_GlobalState = state, // restore
- () => DisposeAndResetGlobalState()); // static dispose
-
- // Reset global state
- s_GlobalState = default;
-
- return savedState;
- }
-
- private static void HookIntoActionChange()
- {
- if (s_GlobalState.onActionChangeHooked)
- return;
- if (s_GlobalState.actionChangeDelegate == null)
- s_GlobalState.actionChangeDelegate = OnActionChange;
- InputSystem.onActionChange += OnActionChange;
- s_GlobalState.onActionChangeHooked = true;
- }
-
- private static void UnhookFromActionChange()
- {
- if (!s_GlobalState.onActionChangeHooked)
- return;
- InputSystem.onActionChange -= OnActionChange;
- s_GlobalState.onActionChangeHooked = false;
- }
-
- private static void HookIntoDeviceChange()
- {
- if (s_GlobalState.onDeviceChangeHooked)
- return;
- if (s_GlobalState.onDeviceChangeDelegate == null)
- s_GlobalState.onDeviceChangeDelegate = OnDeviceChange;
- InputSystem.onDeviceChange += s_GlobalState.onDeviceChangeDelegate;
- s_GlobalState.onDeviceChangeHooked = true;
- }
-
- private static void UnhookFromDeviceChange()
- {
- if (!s_GlobalState.onDeviceChangeHooked)
- return;
- InputSystem.onDeviceChange -= s_GlobalState.onDeviceChangeDelegate;
- s_GlobalState.onDeviceChangeHooked = false;
- }
-
- private static void HookIntoEvents()
- {
- if (s_GlobalState.onEventHooked)
- return;
- if (s_GlobalState.onEventDelegate == null)
- s_GlobalState.onEventDelegate = OnEvent;
- InputSystem.onEvent += s_GlobalState.onEventDelegate;
- s_GlobalState.onEventHooked = true;
- }
-
- private static void UnhookFromDeviceStateChange()
- {
- if (!s_GlobalState.onEventHooked)
- return;
- InputSystem.onEvent -= s_GlobalState.onEventDelegate;
- s_GlobalState.onEventHooked = false;
- }
-
- private static void DisposeAndResetGlobalState()
- {
- // Release native memory held by control scheme match results.
- for (var i = 0; i < s_GlobalState.allUserCount; ++i)
- s_GlobalState.allUserData[i].controlSchemeMatch.Dispose();
-
- // Don't reset s_LastUserId and just let it increment instead so we never generate
- // the same ID twice.
-
- var storedLastUserId = s_GlobalState.lastUserId;
- s_GlobalState = default;
- s_GlobalState.lastUserId = storedLastUserId;
- }
-
- internal static void ResetGlobals()
- {
- UnhookFromActionChange();
- UnhookFromDeviceChange();
- UnhookFromDeviceStateChange();
-
- DisposeAndResetGlobalState();
- }
- }
- }
|