Нет описания
Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

InputControlScheme.cs 52KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173
  1. using System;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. using System.Linq;
  5. using System.Text;
  6. using Unity.Collections;
  7. using UnityEngine.InputSystem.Layouts;
  8. using UnityEngine.InputSystem.Utilities;
  9. #if UNITY_EDITOR
  10. using UnityEditor;
  11. #endif
  12. ////TODO: introduce the concept of a "variation"
  13. //// - a variation is just a variant of a control scheme, not a full control scheme by itself
  14. //// - an individual variation can be toggled on and off independently
  15. //// - while a control is is active, all its variations that are toggled on are also active
  16. //// - assignment to variations works the same as assignment to control schemes
  17. //// use case: left/right stick toggles, left/right bumper toggles, etc
  18. ////TODO: introduce concept of precedence where one control scheme will be preferred over another that is also a match
  19. //// (might be its enough to represent this simply through ordering by giving the user control over the ordering through the UI)
  20. ////REVIEW: allow associating control schemes with platforms, too?
  21. namespace UnityEngine.InputSystem
  22. {
  23. /// <summary>
  24. /// A named set of zero or more device requirements along with an associated binding group.
  25. /// </summary>
  26. /// <remarks>
  27. /// Control schemes provide an additional layer on top of binding groups. While binding
  28. /// groups allow differentiating sets of bindings (e.g. a "Keyboard&amp;Mouse" group versus
  29. /// a "Gamepad" group), control schemes impose a set of devices requirements that must be
  30. /// met in order for a specific set of bindings to be usable.
  31. ///
  32. /// Note that control schemes can only be defined at the <see cref="InputActionAsset"/> level.
  33. /// </remarks>
  34. /// <seealso cref="InputActionAsset.controlSchemes"/>
  35. /// <seealso cref="InputActionSetupExtensions.AddControlScheme(InputActionAsset,string)"/>
  36. [Serializable]
  37. public struct InputControlScheme : IEquatable<InputControlScheme>
  38. {
  39. /// <summary>
  40. /// Name of the control scheme. Not <c>null</c> or empty except if InputControlScheme
  41. /// instance is invalid (i.e. default-initialized).
  42. /// </summary>
  43. /// <value>Name of the scheme.</value>
  44. /// <remarks>
  45. /// May be empty or null except if the control scheme is part of an <see cref="InputActionAsset"/>.
  46. /// </remarks>
  47. /// <seealso cref="InputActionSetupExtensions.AddControlScheme(InputActionAsset,string)"/>
  48. public string name => m_Name;
  49. /// <summary>
  50. /// Binding group that is associated with the control scheme. Not <c>null</c> or empty
  51. /// except if InputControlScheme is invalid (i.e. default-initialized).
  52. /// </summary>
  53. /// <value>Binding group for the scheme.</value>
  54. /// <remarks>
  55. /// All bindings in this group are considered to be part of the control scheme.
  56. /// </remarks>
  57. /// <seealso cref="InputBinding.groups"/>
  58. public string bindingGroup
  59. {
  60. get => m_BindingGroup;
  61. set => m_BindingGroup = value;
  62. }
  63. /// <summary>
  64. /// Devices used by the control scheme.
  65. /// </summary>
  66. /// <value>Device requirements of the scheme.</value>
  67. /// <remarks>
  68. /// No two entries will be allowed to match the same control or device at runtime in order for the requirements
  69. /// of the control scheme to be considered satisfied. If, for example, one entry requires a "&lt;Gamepad&gt;" and
  70. /// another entry requires a "&lt;Gamepad&gt;", then at runtime two gamepads will be required even though a single
  71. /// one will match both requirements individually. However, if, for example, one entry requires "&lt;Gamepad&gt;/leftStick"
  72. /// and another requires "&lt;Gamepad&gt;, the same device can match both requirements as each one resolves to
  73. /// a different control.
  74. ///
  75. /// It it allowed to define control schemes without device requirements, i.e. for which this
  76. /// property will be an empty array. Note, however, that features such as automatic control scheme
  77. /// switching in <see cref="PlayerInput"/> will not work with such control schemes.
  78. /// </remarks>
  79. public ReadOnlyArray<DeviceRequirement> deviceRequirements =>
  80. new ReadOnlyArray<DeviceRequirement>(m_DeviceRequirements);
  81. /// <summary>
  82. /// Initialize the control scheme with the given name, device requirements,
  83. /// and binding group.
  84. /// </summary>
  85. /// <param name="name">Name to use for the scheme. Required.</param>
  86. /// <param name="devices">List of device requirements.</param>
  87. /// <param name="bindingGroup">Name to use for the binding group (see <see cref="InputBinding.groups"/>)
  88. /// associated with the control scheme. If this is <c>null</c> or empty, <paramref name="name"/> is
  89. /// used instead (with <see cref="InputBinding.Separator"/> characters stripped from the name).</param>
  90. /// <exception cref="ArgumentNullException"><paramref name="name"/> is <c>null</c> or empty.</exception>
  91. public InputControlScheme(string name, IEnumerable<DeviceRequirement> devices = null, string bindingGroup = null)
  92. : this()
  93. {
  94. if (string.IsNullOrEmpty(name))
  95. throw new ArgumentNullException(nameof(name));
  96. SetNameAndBindingGroup(name, bindingGroup);
  97. m_DeviceRequirements = null;
  98. if (devices != null)
  99. {
  100. m_DeviceRequirements = devices.ToArray();
  101. if (m_DeviceRequirements.Length == 0)
  102. m_DeviceRequirements = null;
  103. }
  104. }
  105. #if UNITY_EDITOR && UNITY_INPUT_SYSTEM_PROJECT_WIDE_ACTIONS
  106. internal InputControlScheme(SerializedProperty sp)
  107. {
  108. var requirements = new List<DeviceRequirement>();
  109. var deviceRequirementsArray = sp.FindPropertyRelative(nameof(m_DeviceRequirements));
  110. if (deviceRequirementsArray == null)
  111. throw new ArgumentException("The serialized property does not contain an InputControlScheme object.");
  112. foreach (SerializedProperty deviceRequirement in deviceRequirementsArray)
  113. {
  114. requirements.Add(new DeviceRequirement
  115. {
  116. controlPath = deviceRequirement.FindPropertyRelative(nameof(DeviceRequirement.m_ControlPath)).stringValue,
  117. m_Flags = (DeviceRequirement.Flags)deviceRequirement.FindPropertyRelative(nameof(DeviceRequirement.m_Flags)).enumValueFlag
  118. });
  119. }
  120. m_Name = sp.FindPropertyRelative(nameof(m_Name)).stringValue;
  121. m_DeviceRequirements = requirements.ToArray();
  122. m_BindingGroup = sp.FindPropertyRelative(nameof(m_BindingGroup)).stringValue;
  123. }
  124. #endif
  125. internal void SetNameAndBindingGroup(string name, string bindingGroup = null)
  126. {
  127. m_Name = name;
  128. if (!string.IsNullOrEmpty(bindingGroup))
  129. m_BindingGroup = bindingGroup;
  130. else
  131. m_BindingGroup = name.Contains(InputBinding.Separator)
  132. ? name.Replace(InputBinding.kSeparatorString, "")
  133. : name;
  134. }
  135. /// <summary>
  136. /// Given a list of devices and a list of control schemes, find the most suitable control
  137. /// scheme to use with the devices.
  138. /// </summary>
  139. /// <param name="devices">A list of devices. If the list is empty, only schemes with
  140. /// empty <see cref="deviceRequirements"/> lists will get matched.</param>
  141. /// <param name="schemes">A list of control schemes.</param>
  142. /// <param name="mustIncludeDevice">If not <c>null</c>, a successful match has to include the given device.</param>
  143. /// <param name="allowUnsuccesfulMatch">If true, then allow returning a match that has unsatisfied requirements but still
  144. /// matched at least some requirement. If there are several unsuccessful matches, the returned scheme is still the highest
  145. /// scoring one among those.</param>
  146. /// <typeparam name="TDevices">Collection type to use for the list of devices.</typeparam>
  147. /// <typeparam name="TSchemes">Collection type to use for the list of schemes.</typeparam>
  148. /// <returns>The control scheme that best matched the given devices or <c>null</c> if no
  149. /// scheme was found suitable.</returns>
  150. /// <exception cref="ArgumentNullException"><paramref name="devices"/> is <c>null</c> -or-
  151. /// <paramref name="schemes"/> is <c>null</c>.</exception>
  152. /// <remarks>
  153. /// Any successful match (see <see cref="MatchResult.isSuccessfulMatch"/>) will be considered.
  154. /// The one that matches the most amount of devices (see <see cref="MatchResult.devices"/>)
  155. /// will be returned. If more than one schemes matches equally well, the first one encountered
  156. /// in the list is returned.
  157. ///
  158. /// Note that schemes are not required to match all devices available in the list. The result
  159. /// will simply be the scheme that matched the most devices of what was devices. Use <see
  160. /// cref="PickDevicesFrom{TDevices}"/> to find the devices that a control scheme selects.
  161. ///
  162. /// This method is parameterized over <typeparamref name="TDevices"/> and <typeparamref name="TSchemes"/>
  163. /// to allow avoiding GC heap allocations from boxing of structs such as <see cref="ReadOnlyArray{TValue}"/>.
  164. ///
  165. /// <example>
  166. /// <code>
  167. /// // Create an .inputactions asset.
  168. /// var asset = ScriptableObject.CreateInstance&lt;InputActionAsset&gt;();
  169. ///
  170. /// // Add some control schemes to the asset.
  171. /// asset.AddControlScheme("KeyboardMouse")
  172. /// .WithRequiredDevice&lt;Keyboard&gt;()
  173. /// .WithRequiredDevice&lt;Mouse&gt;());
  174. /// asset.AddControlScheme("Gamepad")
  175. /// .WithRequiredDevice&lt;Gamepad&gt;());
  176. /// asset.AddControlScheme("DualGamepad")
  177. /// .WithRequiredDevice&lt;Gamepad&gt;())
  178. /// .WithOptionalGamepad&lt;Gamepad&gt;());
  179. ///
  180. /// // Add some devices that we can test with.
  181. /// var keyboard = InputSystem.AddDevice&lt;Keyboard&gt;();
  182. /// var mouse = InputSystem.AddDevice&lt;Mouse&gt;();
  183. /// var gamepad1 = InputSystem.AddDevice&lt;Gamepad&gt;();
  184. /// var gamepad2 = InputSystem.AddDevice&lt;Gamepad&gt;();
  185. ///
  186. /// // Matching with just a keyboard won't match any scheme.
  187. /// InputControlScheme.FindControlSchemeForDevices(
  188. /// new InputDevice[] { keyboard }, asset.controlSchemes);
  189. ///
  190. /// // Matching with a keyboard and mouse with match the "KeyboardMouse" scheme.
  191. /// InputControlScheme.FindControlSchemeForDevices(
  192. /// new InputDevice[] { keyboard, mouse }, asset.controlSchemes);
  193. ///
  194. /// // Matching with a single gamepad will match the "Gamepad" scheme.
  195. /// // Note that since the second gamepad is optional in "DualGamepad" could
  196. /// // match the same set of devices but it doesn't match any better than
  197. /// // "Gamepad" and that one comes first in the list.
  198. /// InputControlScheme.FindControlSchemeForDevices(
  199. /// new InputDevice[] { gamepad1 }, asset.controlSchemes);
  200. ///
  201. /// // Matching with two gamepads will match the "DualGamepad" scheme.
  202. /// // Note that "Gamepad" will match this device list as well. If "DualGamepad"
  203. /// // didn't exist, "Gamepad" would be the result here. However, "DualGamepad"
  204. /// // matches the list better than "Gamepad" so that's what gets returned here.
  205. /// InputControlScheme.FindControlSchemeForDevices(
  206. /// new InputDevice[] { gamepad1, gamepad2 }, asset.controlSchemes);
  207. /// </code>
  208. /// </example>
  209. /// </remarks>
  210. public static InputControlScheme? FindControlSchemeForDevices<TDevices, TSchemes>(TDevices devices, TSchemes schemes, InputDevice mustIncludeDevice = null, bool allowUnsuccesfulMatch = false)
  211. where TDevices : IReadOnlyList<InputDevice>
  212. where TSchemes : IEnumerable<InputControlScheme>
  213. {
  214. if (devices == null)
  215. throw new ArgumentNullException(nameof(devices));
  216. if (schemes == null)
  217. throw new ArgumentNullException(nameof(schemes));
  218. if (!FindControlSchemeForDevices(devices, schemes, out var controlScheme, out var matchResult, mustIncludeDevice, allowUnsuccesfulMatch))
  219. return null;
  220. matchResult.Dispose();
  221. return controlScheme;
  222. }
  223. public static bool FindControlSchemeForDevices<TDevices, TSchemes>(TDevices devices, TSchemes schemes,
  224. out InputControlScheme controlScheme, out MatchResult matchResult, InputDevice mustIncludeDevice = null, bool allowUnsuccessfulMatch = false)
  225. where TDevices : IReadOnlyList<InputDevice>
  226. where TSchemes : IEnumerable<InputControlScheme>
  227. {
  228. if (devices == null)
  229. throw new ArgumentNullException(nameof(devices));
  230. if (schemes == null)
  231. throw new ArgumentNullException(nameof(schemes));
  232. MatchResult? bestResult = null;
  233. InputControlScheme? bestScheme = null;
  234. foreach (var scheme in schemes)
  235. {
  236. var result = scheme.PickDevicesFrom(devices, favorDevice: mustIncludeDevice);
  237. // Ignore if scheme doesn't fit devices.
  238. if (!result.isSuccessfulMatch && (!allowUnsuccessfulMatch || result.score <= 0))
  239. {
  240. result.Dispose();
  241. continue;
  242. }
  243. // Ignore if we have a device we specifically want to be part of the result and
  244. // the current match doesn't have it.
  245. if (mustIncludeDevice != null && !result.devices.Contains(mustIncludeDevice))
  246. {
  247. result.Dispose();
  248. continue;
  249. }
  250. // Ignore if it does fit but we already have a better fit.
  251. if (bestResult != null && bestResult.Value.score >= result.score)
  252. {
  253. result.Dispose();
  254. continue;
  255. }
  256. bestResult?.Dispose();
  257. bestResult = result;
  258. bestScheme = scheme;
  259. }
  260. matchResult = bestResult ?? default;
  261. controlScheme = bestScheme ?? default;
  262. return bestResult.HasValue;
  263. }
  264. ////FIXME: docs are wrong now
  265. /// <summary>
  266. /// Return the first control schemes from the given list that supports the given
  267. /// device (see <see cref="SupportsDevice"/>).
  268. /// </summary>
  269. /// <param name="device">An input device.</param>
  270. /// <param name="schemes">A list of control schemes. Can be empty.</param>
  271. /// <typeparam name="TSchemes">Collection type to use for the list of schemes.</typeparam>
  272. /// <returns>The first schemes from <paramref name="schemes"/> that supports <paramref name="device"/>
  273. /// or <c>null</c> if none of the schemes is usable with the device.</returns>
  274. /// <exception cref="ArgumentNullException"><paramref name="device"/> is <c>null</c> -or-
  275. /// <paramref name="schemes"/> is <c>null</c>.</exception>
  276. public static InputControlScheme? FindControlSchemeForDevice<TSchemes>(InputDevice device, TSchemes schemes)
  277. where TSchemes : IEnumerable<InputControlScheme>
  278. {
  279. if (schemes == null)
  280. throw new ArgumentNullException(nameof(schemes));
  281. if (device == null)
  282. throw new ArgumentNullException(nameof(device));
  283. return FindControlSchemeForDevices(new OneOrMore<InputDevice, ReadOnlyArray<InputDevice>>(device), schemes);
  284. }
  285. /// <summary>
  286. /// Whether the control scheme has a requirement in <see cref="deviceRequirements"/> that
  287. /// targets the given device.
  288. /// </summary>
  289. /// <param name="device">An input device.</param>
  290. /// <returns>True if the control scheme has a device requirement matching the device.</returns>
  291. /// <exception cref="ArgumentNullException"><paramref name="device"/> is <c>null</c>.</exception>
  292. /// <remarks>
  293. /// Note that both optional (see <see cref="DeviceRequirement.isOptional"/>) and non-optional
  294. /// device requirements are taken into account.
  295. ///
  296. /// </remarks>
  297. public bool SupportsDevice(InputDevice device)
  298. {
  299. if (device == null)
  300. throw new ArgumentNullException(nameof(device));
  301. ////REVIEW: does this need to take AND and OR into account?
  302. for (var i = 0; i < m_DeviceRequirements.Length; ++i)
  303. {
  304. var control = InputControlPath.TryFindControl(device, m_DeviceRequirements[i].controlPath);
  305. if (control != null)
  306. return true;
  307. }
  308. return false;
  309. }
  310. ////REVIEW: have mode where instead of matching only the first device that matches a requirement, we match as many
  311. //// as we can get? (could be useful for single-player)
  312. /// <summary>
  313. /// Based on a list of devices, make a selection that matches the <see cref="deviceRequirements">requirements</see>
  314. /// imposed by the control scheme.
  315. /// </summary>
  316. /// <param name="devices">A list of devices to choose from.</param>
  317. /// <param name="favorDevice">If not null, the device will be favored over other devices in <paramref name="devices"/>.
  318. /// Note that the device must be present in the list also.</param>
  319. /// <returns>A <see cref="MatchResult"/> structure containing the result of the pick. Note that this structure
  320. /// must be manually <see cref="MatchResult.Dispose">disposed</see> or unmanaged memory will be leaked.</returns>
  321. /// <remarks>
  322. /// Does not allocate managed memory.
  323. /// </remarks>
  324. public MatchResult PickDevicesFrom<TDevices>(TDevices devices, InputDevice favorDevice = null)
  325. where TDevices : IReadOnlyList<InputDevice>
  326. {
  327. // Empty device requirements match anything while not really picking anything.
  328. if (m_DeviceRequirements == null || m_DeviceRequirements.Length == 0)
  329. {
  330. return new MatchResult
  331. {
  332. m_Result = MatchResult.Result.AllSatisfied,
  333. // Prevent zero score on successful match but make less than one which would
  334. // result from having a single requirement.
  335. m_Score = 0.5f,
  336. };
  337. }
  338. // Go through each requirement and match it.
  339. // NOTE: Even if `devices` is empty, we don't know yet whether we have a NoMatch.
  340. // All our devices may be optional.
  341. var haveAllRequired = true;
  342. var haveAllOptional = true;
  343. var requirementCount = m_DeviceRequirements.Length;
  344. var score = 0f;
  345. var controls = new InputControlList<InputControl>(Allocator.Persistent, requirementCount);
  346. try
  347. {
  348. var orChainIsSatisfied = false;
  349. var orChainHasRequiredDevices = false;
  350. for (var i = 0; i < requirementCount; ++i)
  351. {
  352. var isOR = m_DeviceRequirements[i].isOR;
  353. var isOptional = m_DeviceRequirements[i].isOptional;
  354. // If this is an OR requirement and we already have a match in this OR chain,
  355. // skip this requirement.
  356. if (isOR && orChainIsSatisfied)
  357. {
  358. // Skill need to add an entry for this requirement.
  359. controls.Add(null);
  360. continue;
  361. }
  362. // Null and empty paths shouldn't make it into the list but make double
  363. // sure here. Simply ignore entries that don't have a path.
  364. var path = m_DeviceRequirements[i].controlPath;
  365. if (string.IsNullOrEmpty(path))
  366. {
  367. score += 1;
  368. controls.Add(null);
  369. continue;
  370. }
  371. // Find the first matching control among the devices we have.
  372. InputControl match = null;
  373. for (var n = 0; n < devices.Count; ++n)
  374. {
  375. var device = devices[n];
  376. // If we should favor a device, we swap it in at index #0 regardless
  377. // of where in the list the device occurs (it MUST, however, occur in the list).
  378. if (favorDevice != null)
  379. {
  380. if (n == 0)
  381. device = favorDevice;
  382. else if (device == favorDevice)
  383. device = devices[0];
  384. }
  385. // See if we have a match.
  386. var matchedControl = InputControlPath.TryFindControl(device, path);
  387. if (matchedControl == null)
  388. continue; // No.
  389. // We have a match but if we've already matched the same control through another requirement,
  390. // we can't use the match.
  391. if (controls.Contains(matchedControl))
  392. continue;
  393. match = matchedControl;
  394. // Compute score for match.
  395. var deviceLayoutOfControlPath = new InternedString(InputControlPath.TryGetDeviceLayout(path));
  396. if (deviceLayoutOfControlPath.IsEmpty())
  397. {
  398. // Generic match adds 1 to score.
  399. score += 1;
  400. }
  401. else
  402. {
  403. var deviceLayoutOfControl = matchedControl.device.m_Layout;
  404. if (InputControlLayout.s_Layouts.ComputeDistanceInInheritanceHierarchy(deviceLayoutOfControlPath,
  405. deviceLayoutOfControl, out var distance))
  406. {
  407. score += 1 + 1f / (Math.Abs(distance) + 1);
  408. }
  409. else
  410. {
  411. // Shouldn't really get here as for the control to be a match for the path, the device layouts
  412. // would be expected to be related to each other. But just add 1 for a generic match and go on.
  413. score += 1;
  414. }
  415. }
  416. break;
  417. }
  418. // Check requirements in AND and OR chains. We look ahead here to find out whether
  419. // the next requirement is starting an OR chain. As the OR combines with the previous
  420. // requirement in the list, this affects our current requirement.
  421. var nextIsOR = i + 1 < requirementCount && m_DeviceRequirements[i + 1].isOR;
  422. if (nextIsOR)
  423. {
  424. // Shouldn't get here if the chain is already satisfied. Should be handled
  425. // at beginning of loop and we shouldn't even be looking at finding controls
  426. // in that case.
  427. Debug.Assert(!orChainIsSatisfied);
  428. // It's an OR with the next requirement. Depends on the outcome of other matches whether
  429. // we're good or not.
  430. if (match != null)
  431. {
  432. // First match in this chain.
  433. orChainIsSatisfied = true;
  434. }
  435. else
  436. {
  437. // Chain not satisfied yet.
  438. if (!isOptional)
  439. orChainHasRequiredDevices = true;
  440. }
  441. }
  442. else if (isOR && i == requirementCount - 1)
  443. {
  444. // It's an OR at the very end of the requirements list. Terminate
  445. // the OR chain.
  446. if (match == null)
  447. {
  448. if (orChainHasRequiredDevices)
  449. haveAllRequired = false;
  450. else
  451. haveAllOptional = false;
  452. }
  453. }
  454. else
  455. {
  456. // It's an AND.
  457. if (match == null)
  458. {
  459. if (isOptional)
  460. haveAllOptional = false;
  461. else
  462. haveAllRequired = false;
  463. }
  464. // Terminate ongoing OR chain.
  465. if (i > 0 && m_DeviceRequirements[i - 1].isOR)
  466. {
  467. if (!orChainIsSatisfied)
  468. {
  469. if (orChainHasRequiredDevices)
  470. haveAllRequired = false;
  471. else
  472. haveAllOptional = false;
  473. }
  474. orChainIsSatisfied = false;
  475. }
  476. }
  477. // Add match to list. Maybe null.
  478. controls.Add(match);
  479. }
  480. // We should have matched each of our requirements.
  481. Debug.Assert(controls.Count == requirementCount);
  482. }
  483. catch (Exception)
  484. {
  485. controls.Dispose();
  486. throw;
  487. }
  488. return new MatchResult
  489. {
  490. m_Result = !haveAllRequired
  491. ? MatchResult.Result.MissingRequired
  492. : !haveAllOptional
  493. ? MatchResult.Result.MissingOptional
  494. : MatchResult.Result.AllSatisfied,
  495. m_Controls = controls,
  496. m_Requirements = m_DeviceRequirements,
  497. m_Score = score,
  498. };
  499. }
  500. public bool Equals(InputControlScheme other)
  501. {
  502. if (!(string.Equals(m_Name, other.m_Name, StringComparison.InvariantCultureIgnoreCase) &&
  503. string.Equals(m_BindingGroup, other.m_BindingGroup, StringComparison.InvariantCultureIgnoreCase)))
  504. return false;
  505. // Compare device requirements.
  506. if (m_DeviceRequirements == null || m_DeviceRequirements.Length == 0)
  507. return other.m_DeviceRequirements == null || other.m_DeviceRequirements.Length == 0;
  508. if (other.m_DeviceRequirements == null || m_DeviceRequirements.Length != other.m_DeviceRequirements.Length)
  509. return false;
  510. var deviceCount = m_DeviceRequirements.Length;
  511. for (var i = 0; i < deviceCount; ++i)
  512. {
  513. var device = m_DeviceRequirements[i];
  514. var haveMatch = false;
  515. for (var n = 0; n < deviceCount; ++n)
  516. {
  517. if (other.m_DeviceRequirements[n] == device)
  518. {
  519. haveMatch = true;
  520. break;
  521. }
  522. }
  523. if (!haveMatch)
  524. return false;
  525. }
  526. return true;
  527. }
  528. public override bool Equals(object obj)
  529. {
  530. if (ReferenceEquals(null, obj))
  531. return false;
  532. return obj is InputControlScheme && Equals((InputControlScheme)obj);
  533. }
  534. public override int GetHashCode()
  535. {
  536. unchecked
  537. {
  538. var hashCode = (m_Name != null ? m_Name.GetHashCode() : 0);
  539. hashCode = (hashCode * 397) ^ (m_BindingGroup != null ? m_BindingGroup.GetHashCode() : 0);
  540. hashCode = (hashCode * 397) ^ (m_DeviceRequirements != null ? m_DeviceRequirements.GetHashCode() : 0);
  541. return hashCode;
  542. }
  543. }
  544. public override string ToString()
  545. {
  546. if (string.IsNullOrEmpty(m_Name))
  547. return base.ToString();
  548. if (m_DeviceRequirements == null)
  549. return m_Name;
  550. var builder = new StringBuilder();
  551. builder.Append(m_Name);
  552. builder.Append('(');
  553. var isFirst = true;
  554. foreach (var device in m_DeviceRequirements)
  555. {
  556. if (!isFirst)
  557. builder.Append(',');
  558. builder.Append(device.controlPath);
  559. isFirst = false;
  560. }
  561. builder.Append(')');
  562. return builder.ToString();
  563. }
  564. public static bool operator==(InputControlScheme left, InputControlScheme right)
  565. {
  566. return left.Equals(right);
  567. }
  568. public static bool operator!=(InputControlScheme left, InputControlScheme right)
  569. {
  570. return !left.Equals(right);
  571. }
  572. [SerializeField] internal string m_Name;
  573. [SerializeField] internal string m_BindingGroup;
  574. [SerializeField] internal DeviceRequirement[] m_DeviceRequirements;
  575. /// <summary>
  576. /// The result of matching a list of <see cref="InputDevice">devices</see> against a list of
  577. /// <see cref="DeviceRequirement">requirements</see> in an <see cref="InputControlScheme"/>.
  578. /// </summary>
  579. /// <remarks>
  580. /// This struct uses <see cref="InputControlList{TControl}"/> which allocates unmanaged memory
  581. /// and thus must be disposed in order to not leak unmanaged heap memory.
  582. /// </remarks>
  583. /// <seealso cref="InputControlScheme.PickDevicesFrom{TDevices}"/>
  584. public struct MatchResult : IEnumerable<MatchResult.Match>, IDisposable
  585. {
  586. /// <summary>
  587. /// Overall, relative measure for how well the control scheme matches.
  588. /// </summary>
  589. /// <value>Scoring value for the control scheme match.</value>
  590. /// <remarks>
  591. /// Two control schemes may, for example, both support gamepads but one may be tailored to a specific
  592. /// gamepad whereas the other one is a generic gamepad control scheme. To differentiate the two, we need
  593. /// to know not only that a control schemes but how well it matches relative to other schemes. This is
  594. /// what the score value is used for.
  595. ///
  596. /// Scores are computed primarily based on layouts referenced from device requirements. To start with, each
  597. /// matching device requirement (whether optional or mandatory) will add 1 to the score. This the base
  598. /// score of a match. Then, for each requirement a delta is computed from the device layout referenced by
  599. /// the requirement to the device layout used by the matching control. For example, if the requirement is
  600. /// <c>"&lt;Gamepad&gt;</c> and the matching control uses the <see cref="DualShock.DualShock4GamepadHID"/>
  601. /// layout, the delta is 2 as the latter layout is derived from <see cref="Gamepad"/> via the intermediate
  602. /// <see cref="DualShock.DualShockGamepad"/> layout, i.e. two steps in the inheritance hierarchy. The
  603. /// <em>inverse</em> of the delta plus one, i.e. <c>1/(delta+1)</c> is then added to the score. This means
  604. /// that an exact match will add an additional 1 to the score and less exact matches will add progressively
  605. /// smaller values to the score (proportional to the distance of the actual layout to the one used in the
  606. /// requirement).
  607. ///
  608. /// What this leads to is that, for example, a control scheme with a <c>"&lt;Gamepad&gt;"</c> requirement
  609. /// will match a <see cref="DualShock.DualShock4GamepadHID"/> with a <em>lower</em> score than a control
  610. /// scheme with a <c>"&lt;DualShockGamepad&gt;"</c> requirement as the <see cref="Gamepad"/> layout is
  611. /// further removed (i.e. smaller inverse delta) from <see cref="DualShock.DualShock4GamepadHID"/> than
  612. /// <see cref="DualShock.DualShockGamepad"/>.
  613. /// </remarks>
  614. public float score => m_Score;
  615. /// <summary>
  616. /// Whether the device requirements got successfully matched.
  617. /// </summary>
  618. /// <value>True if the scheme's device requirements were satisfied.</value>
  619. public bool isSuccessfulMatch => m_Result != Result.MissingRequired;
  620. /// <summary>
  621. /// Whether there are missing required devices.
  622. /// </summary>
  623. /// <value>True if there are missing, non-optional devices.</value>
  624. /// <seealso cref="DeviceRequirement.isOptional"/>
  625. public bool hasMissingRequiredDevices => m_Result == Result.MissingRequired;
  626. /// <summary>
  627. /// Whether there are missing optional devices. This does not prevent
  628. /// a successful match.
  629. /// </summary>
  630. /// <value>True if there are missing optional devices.</value>
  631. /// <seealso cref="DeviceRequirement.isOptional"/>
  632. public bool hasMissingOptionalDevices => m_Result == Result.MissingOptional;
  633. /// <summary>
  634. /// The devices that got picked from the available devices.
  635. /// </summary>
  636. public InputControlList<InputDevice> devices
  637. {
  638. get
  639. {
  640. // Lazily construct the device list. If we have missing required
  641. // devices, though, always return an empty list. The user can still see
  642. // the individual matches on each of the requirement entries but we
  643. // consider the device picking itself failed.
  644. if (m_Devices.Count == 0 && !hasMissingRequiredDevices)
  645. {
  646. var controlCount = m_Controls.Count;
  647. if (controlCount != 0)
  648. {
  649. m_Devices.Capacity = controlCount;
  650. for (var i = 0; i < controlCount; ++i)
  651. {
  652. var control = m_Controls[i];
  653. if (control == null)
  654. continue;
  655. var device = control.device;
  656. if (m_Devices.Contains(device))
  657. continue; // Duplicate match of same device.
  658. m_Devices.Add(device);
  659. }
  660. }
  661. }
  662. return m_Devices;
  663. }
  664. }
  665. public Match this[int index]
  666. {
  667. get
  668. {
  669. if (index < 0 || m_Requirements == null || index >= m_Requirements.Length)
  670. throw new ArgumentOutOfRangeException("index");
  671. return new Match
  672. {
  673. m_RequirementIndex = index,
  674. m_Requirements = m_Requirements,
  675. m_Controls = m_Controls,
  676. };
  677. }
  678. }
  679. /// <summary>
  680. /// Enumerate the match for each individual <see cref="DeviceRequirement"/> in the control scheme.
  681. /// </summary>
  682. /// <returns>An enumerate going over each individual match.</returns>
  683. public IEnumerator<Match> GetEnumerator()
  684. {
  685. return new Enumerator
  686. {
  687. m_Index = -1,
  688. m_Requirements = m_Requirements,
  689. m_Controls = m_Controls,
  690. };
  691. }
  692. /// <summary>
  693. /// Enumerate the match for each individual <see cref="DeviceRequirement"/> in the control scheme.
  694. /// </summary>
  695. /// <returns>An enumerate going over each individual match.</returns>
  696. IEnumerator IEnumerable.GetEnumerator()
  697. {
  698. return GetEnumerator();
  699. }
  700. /// <summary>
  701. /// Discard the list of devices.
  702. /// </summary>
  703. public void Dispose()
  704. {
  705. m_Controls.Dispose();
  706. m_Devices.Dispose();
  707. }
  708. internal Result m_Result;
  709. internal float m_Score;
  710. internal InputControlList<InputDevice> m_Devices;
  711. internal InputControlList<InputControl> m_Controls;
  712. internal DeviceRequirement[] m_Requirements;
  713. internal enum Result
  714. {
  715. AllSatisfied,
  716. MissingRequired,
  717. MissingOptional,
  718. }
  719. ////REVIEW: would be great to not have to repeatedly copy InputControlLists around
  720. /// <summary>
  721. /// A single matched <see cref="DeviceRequirement"/>.
  722. /// </summary>
  723. /// <remarks>
  724. /// Links the control that was matched with the respective device requirement.
  725. /// </remarks>
  726. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1724:TypeNamesShouldNotMatchNamespaces", Justification = "Conflicts with UnityEngine.Networking.Match, which is deprecated and will go away.")]
  727. public struct Match
  728. {
  729. /// <summary>
  730. /// The control that was match from the requirement's <see cref="DeviceRequirement.controlPath"/>
  731. /// </summary>
  732. /// <remarks>
  733. /// This is the same as <see cref="device"/> if the <see cref="DeviceRequirement.controlPath">control
  734. /// path</see> matches the device directly rather than matching a control on the device.
  735. ///
  736. /// Note that while a control path can match arbitrary many controls, only the first matched control
  737. /// will be returned here. To get all controls that were matched by a specific requirement, a
  738. /// manual query must be performed using <see cref="InputControlPath"/>.
  739. ///
  740. /// If the match failed, this will be null.
  741. /// </remarks>
  742. public InputControl control => m_Controls[m_RequirementIndex];
  743. /// <summary>
  744. /// The device that got matched.
  745. /// </summary>
  746. /// <remarks>
  747. /// If a specific control on the device was matched, this will be <see cref="InputControl.device"/> or
  748. /// <see cref="control"/>. If a device was matched directly, this will be the same as <see cref="control"/>.
  749. /// </remarks>
  750. public InputDevice device
  751. {
  752. get
  753. {
  754. var control = this.control;
  755. return control?.device;
  756. }
  757. }
  758. /// <summary>
  759. /// Index of the requirement in <see cref="InputControlScheme.deviceRequirements"/>.
  760. /// </summary>
  761. public int requirementIndex => m_RequirementIndex;
  762. /// <summary>
  763. /// The device requirement that got matched.
  764. /// </summary>
  765. public DeviceRequirement requirement => m_Requirements[m_RequirementIndex];
  766. public bool isOptional => requirement.isOptional;
  767. internal int m_RequirementIndex;
  768. internal DeviceRequirement[] m_Requirements;
  769. internal InputControlList<InputControl> m_Controls;
  770. }
  771. private struct Enumerator : IEnumerator<Match>
  772. {
  773. public bool MoveNext()
  774. {
  775. ++m_Index;
  776. return m_Requirements != null && m_Index < m_Requirements.Length;
  777. }
  778. public void Reset()
  779. {
  780. m_Index = -1;
  781. }
  782. public Match Current
  783. {
  784. get
  785. {
  786. if (m_Requirements == null || m_Index < 0 || m_Index >= m_Requirements.Length)
  787. throw new InvalidOperationException("Enumerator is not valid");
  788. return new Match
  789. {
  790. m_RequirementIndex = m_Index,
  791. m_Requirements = m_Requirements,
  792. m_Controls = m_Controls,
  793. };
  794. }
  795. }
  796. object IEnumerator.Current => Current;
  797. public void Dispose()
  798. {
  799. }
  800. internal int m_Index;
  801. internal DeviceRequirement[] m_Requirements;
  802. internal InputControlList<InputControl> m_Controls;
  803. }
  804. }
  805. /// <summary>
  806. ///
  807. /// </summary>
  808. /// <remarks>
  809. /// Note that device requirements may require specific controls to be present rather than only requiring
  810. /// the presence of a certain type of device. For example, a requirement with a <see cref="controlPath"/>
  811. /// of "*/{PrimaryAction}" will be satisfied by any device that has a control marked as <see cref="CommonUsages.PrimaryAction"/>.
  812. ///
  813. /// Requirements are ordered in a list and can combine with their previous requirement in either <see cref="isAND">
  814. /// AND</see> or in <see cref="isOR">OR</see> fashion. The default is for requirements to combine with AND.
  815. ///
  816. /// Note that it is not possible to express nested constraints like <c>(a AND b) OR (c AND d)</c>. Also note that
  817. /// operator precedence is the opposite of C#, meaning that OR has *higher* precedence than AND. This means
  818. /// that <c>a OR b AND c OR d</c> reads as <c>(a OR b) AND (c OR d)</c> (in C# it would read as <c>a OR
  819. /// (b AND c) OR d</c>.
  820. ///
  821. /// More complex expressions can often be expressed differently. For example, <c>(a AND b) OR (c AND d)</c>
  822. /// can be expressed as <c>a OR c AND b OR d</c>.
  823. /// </remarks>
  824. [Serializable]
  825. public struct DeviceRequirement : IEquatable<DeviceRequirement>
  826. {
  827. /// <summary>
  828. /// <see cref="InputControlPath">Control path</see> that is matched against a device to determine
  829. /// whether it qualifies for the control scheme.
  830. /// </summary>
  831. /// <remarks>
  832. /// </remarks>
  833. /// <example>
  834. /// <code>
  835. /// // A left-hand XR controller.
  836. /// "&lt;XRController&gt;{LeftHand}"
  837. ///
  838. /// // A gamepad.
  839. /// "&lt;Gamepad&gt;"
  840. /// </code>
  841. /// </example>
  842. public string controlPath
  843. {
  844. get => m_ControlPath;
  845. set => m_ControlPath = value;
  846. }
  847. /// <summary>
  848. /// If true, a device with the given <see cref="controlPath">device path</see> is employed by the
  849. /// control scheme if one is available. If none is available, the control scheme is still
  850. /// functional.
  851. /// </summary>
  852. public bool isOptional
  853. {
  854. get => (m_Flags & Flags.Optional) != 0;
  855. set
  856. {
  857. if (value)
  858. m_Flags |= Flags.Optional;
  859. else
  860. m_Flags &= ~Flags.Optional;
  861. }
  862. }
  863. /// <summary>
  864. /// Whether the requirement combines with the previous requirement (if any) as a boolean AND.
  865. /// </summary>
  866. /// <remarks>
  867. /// This is the default. For example, to require both a left hand and a right XR controller,
  868. /// the first requirement would be for "&lt;XRController&gt;{LeftHand}" and the second
  869. /// requirement would be for "&gt;XRController&gt;{RightHand}" and would return true for this
  870. /// property.
  871. /// </remarks>
  872. /// <seealso cref="isOR"/>
  873. public bool isAND
  874. {
  875. get => !isOR;
  876. set => isOR = !value;
  877. }
  878. /// <summary>
  879. /// Whether the requirement combines with the previous requirement (if any) as a boolean OR.
  880. /// </summary>
  881. /// <remarks>
  882. /// This allows designing control schemes that flexibly work with combinations of devices such that
  883. /// if one specific device isn't present, another device can substitute for it.
  884. ///
  885. /// For example, to design a mouse+keyboard control scheme that can alternatively work with a pen
  886. /// instead of a mouse, the first requirement could be for "&lt;Keyboard&gt;", the second one
  887. /// could be for "&lt;Mouse&gt;" and the third one could be for "&lt;Pen&gt;" and return true
  888. /// for this property. Both the mouse and the pen would be marked as required (i.e. not <see cref="isOptional"/>)
  889. /// but the device requirements are satisfied even if either device is present.
  890. ///
  891. /// Note that if both a pen and a mouse are present at the same time, still only one device is
  892. /// picked. In this case, the mouse "wins" as it comes first in the list of requirements.
  893. /// </remarks>
  894. public bool isOR
  895. {
  896. get => (m_Flags & Flags.Or) != 0;
  897. set
  898. {
  899. if (value)
  900. m_Flags |= Flags.Or;
  901. else
  902. m_Flags &= ~Flags.Or;
  903. }
  904. }
  905. [SerializeField] internal string m_ControlPath;
  906. [SerializeField] internal Flags m_Flags;
  907. [Flags]
  908. internal enum Flags
  909. {
  910. None = 0,
  911. Optional = 1 << 0,
  912. Or = 1 << 1,
  913. }
  914. public override string ToString()
  915. {
  916. if (!string.IsNullOrEmpty(controlPath))
  917. {
  918. if (isOptional)
  919. return controlPath + " (Optional)";
  920. return controlPath + " (Required)";
  921. }
  922. return base.ToString();
  923. }
  924. public bool Equals(DeviceRequirement other)
  925. {
  926. return string.Equals(m_ControlPath, other.m_ControlPath) && m_Flags == other.m_Flags &&
  927. string.Equals(controlPath, other.controlPath) && isOptional == other.isOptional;
  928. }
  929. public override bool Equals(object obj)
  930. {
  931. if (ReferenceEquals(null, obj))
  932. return false;
  933. return obj is DeviceRequirement && Equals((DeviceRequirement)obj);
  934. }
  935. public override int GetHashCode()
  936. {
  937. unchecked
  938. {
  939. var hashCode = (m_ControlPath != null ? m_ControlPath.GetHashCode() : 0);
  940. hashCode = (hashCode * 397) ^ m_Flags.GetHashCode();
  941. hashCode = (hashCode * 397) ^ (controlPath != null ? controlPath.GetHashCode() : 0);
  942. hashCode = (hashCode * 397) ^ isOptional.GetHashCode();
  943. return hashCode;
  944. }
  945. }
  946. public static bool operator==(DeviceRequirement left, DeviceRequirement right)
  947. {
  948. return left.Equals(right);
  949. }
  950. public static bool operator!=(DeviceRequirement left, DeviceRequirement right)
  951. {
  952. return !left.Equals(right);
  953. }
  954. }
  955. /// <summary>
  956. /// JSON-serialized form of a control scheme.
  957. /// </summary>
  958. [Serializable]
  959. internal struct SchemeJson
  960. {
  961. public string name;
  962. public string bindingGroup;
  963. public DeviceJson[] devices;
  964. [Serializable]
  965. public struct DeviceJson
  966. {
  967. public string devicePath;
  968. public bool isOptional;
  969. public bool isOR;
  970. public DeviceRequirement ToDeviceEntry()
  971. {
  972. return new DeviceRequirement
  973. {
  974. controlPath = devicePath,
  975. isOptional = isOptional,
  976. isOR = isOR,
  977. };
  978. }
  979. public static DeviceJson From(DeviceRequirement requirement)
  980. {
  981. return new DeviceJson
  982. {
  983. devicePath = requirement.controlPath,
  984. isOptional = requirement.isOptional,
  985. isOR = requirement.isOR,
  986. };
  987. }
  988. }
  989. public InputControlScheme ToScheme()
  990. {
  991. DeviceRequirement[] deviceRequirements = null;
  992. if (devices != null && devices.Length > 0)
  993. {
  994. var count = devices.Length;
  995. deviceRequirements = new DeviceRequirement[count];
  996. for (var i = 0; i < count; ++i)
  997. deviceRequirements[i] = devices[i].ToDeviceEntry();
  998. }
  999. return new InputControlScheme
  1000. {
  1001. m_Name = string.IsNullOrEmpty(name) ? null : name,
  1002. m_BindingGroup = string.IsNullOrEmpty(bindingGroup) ? null : bindingGroup,
  1003. m_DeviceRequirements = deviceRequirements,
  1004. };
  1005. }
  1006. public static SchemeJson ToJson(InputControlScheme scheme)
  1007. {
  1008. DeviceJson[] devices = null;
  1009. if (scheme.m_DeviceRequirements != null && scheme.m_DeviceRequirements.Length > 0)
  1010. {
  1011. var count = scheme.m_DeviceRequirements.Length;
  1012. devices = new DeviceJson[count];
  1013. for (var i = 0; i < count; ++i)
  1014. devices[i] = DeviceJson.From(scheme.m_DeviceRequirements[i]);
  1015. }
  1016. return new SchemeJson
  1017. {
  1018. name = scheme.m_Name,
  1019. bindingGroup = scheme.m_BindingGroup,
  1020. devices = devices,
  1021. };
  1022. }
  1023. public static SchemeJson[] ToJson(InputControlScheme[] schemes)
  1024. {
  1025. if (schemes == null || schemes.Length == 0)
  1026. return null;
  1027. var count = schemes.Length;
  1028. var result = new SchemeJson[count];
  1029. for (var i = 0; i < count; ++i)
  1030. result[i] = ToJson(schemes[i]);
  1031. return result;
  1032. }
  1033. public static InputControlScheme[] ToSchemes(SchemeJson[] schemes)
  1034. {
  1035. if (schemes == null || schemes.Length == 0)
  1036. return null;
  1037. var count = schemes.Length;
  1038. var result = new InputControlScheme[count];
  1039. for (var i = 0; i < count; ++i)
  1040. result[i] = schemes[i].ToScheme();
  1041. return result;
  1042. }
  1043. }
  1044. }
  1045. }