暂无描述
您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

EditorInputControlLayoutCache.cs 19KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471
  1. #if UNITY_EDITOR
  2. using System;
  3. using System.Collections.Generic;
  4. using System.Linq;
  5. using UnityEngine.InputSystem.Layouts;
  6. using UnityEngine.InputSystem.DualShock;
  7. using UnityEngine.InputSystem.Utilities;
  8. namespace UnityEngine.InputSystem.Editor
  9. {
  10. /// <summary>
  11. /// Caches <see cref="InputControlLayout"/> instances.
  12. /// </summary>
  13. /// <remarks>
  14. /// In the editor we need access to the <see cref="InputControlLayout">InputControlLayouts</see>
  15. /// registered with the system in order to facilitate various UI features. Instead of
  16. /// constructing layout instances over and over, we keep them around in here.
  17. ///
  18. /// This class is only available in the editor (when <c>UNITY_EDITOR</c> is true).
  19. /// </remarks>
  20. internal static class EditorInputControlLayoutCache
  21. {
  22. /// <summary>
  23. /// Iterate over all control layouts in the system.
  24. /// </summary>
  25. public static IEnumerable<InputControlLayout> allLayouts
  26. {
  27. get
  28. {
  29. Refresh();
  30. return InputControlLayout.cache.table.Values;
  31. }
  32. }
  33. /// <summary>
  34. /// Iterate over all unique usages and their respective lists of layouts that use them.
  35. /// </summary>
  36. public static IEnumerable<Tuple<string, IEnumerable<string>>> allUsages
  37. {
  38. get
  39. {
  40. Refresh();
  41. return s_Usages.Select(pair => new Tuple<string, IEnumerable<string>>(pair.Key, pair.Value.Select(x => x.ToString())));
  42. }
  43. }
  44. public static IEnumerable<InputControlLayout> allControlLayouts
  45. {
  46. get
  47. {
  48. Refresh();
  49. foreach (var name in s_ControlLayouts)
  50. yield return InputControlLayout.cache.FindOrLoadLayout(name.ToString());
  51. }
  52. }
  53. public static IEnumerable<InputControlLayout> allDeviceLayouts
  54. {
  55. get
  56. {
  57. Refresh();
  58. foreach (var name in s_DeviceLayouts)
  59. yield return InputControlLayout.cache.FindOrLoadLayout(name.ToString());
  60. }
  61. }
  62. public static IEnumerable<InputControlLayout> allProductLayouts
  63. {
  64. get
  65. {
  66. Refresh();
  67. foreach (var name in s_ProductLayouts)
  68. yield return InputControlLayout.cache.FindOrLoadLayout(name.ToString());
  69. }
  70. }
  71. public static bool HasChildLayouts(string layoutName)
  72. {
  73. if (string.IsNullOrEmpty(layoutName))
  74. throw new ArgumentException("Layout name cannot be null or empty", nameof(layoutName));
  75. Refresh();
  76. var internedLayout = new InternedString(layoutName);
  77. // return nothing is the layout does not have any derivations
  78. return s_DeviceChildLayouts.TryGetValue(internedLayout, out var derivations) && derivations.Count > 0;
  79. }
  80. public static IEnumerable<InputControlLayout> TryGetChildLayouts(string layoutName)
  81. {
  82. if (string.IsNullOrEmpty(layoutName))
  83. throw new ArgumentException("Layout name cannot be null or empty", nameof(layoutName));
  84. Refresh();
  85. var internedLayout = new InternedString(layoutName);
  86. // return nothing is the layout does not have any derivations
  87. if (!s_DeviceChildLayouts.TryGetValue(internedLayout, out var derivations))
  88. yield break;
  89. else
  90. {
  91. foreach (var name in derivations)
  92. yield return InputControlLayout.cache.FindOrLoadLayout(name.ToString());
  93. }
  94. }
  95. public static InputControlLayout TryGetLayout(string layoutName)
  96. {
  97. if (string.IsNullOrEmpty(layoutName))
  98. throw new ArgumentException("Layout name cannot be null or empty", nameof(layoutName));
  99. Refresh();
  100. return InputControlLayout.cache.FindOrLoadLayout(layoutName, throwIfNotFound: false);
  101. }
  102. public static Type GetValueType(string layoutName)
  103. {
  104. if (string.IsNullOrEmpty(layoutName))
  105. throw new ArgumentException("Layout name cannot be null or empty", nameof(layoutName));
  106. // Load layout.
  107. var layout = TryGetLayout(layoutName);
  108. if (layout == null)
  109. return null;
  110. // Grab type.
  111. var type = layout.type;
  112. Debug.Assert(type != null, "Layout should have associated type");
  113. Debug.Assert(typeof(InputControl).IsAssignableFrom(type),
  114. "Layout's associated type should be derived from InputControl");
  115. return layout.GetValueType();
  116. }
  117. public static IEnumerable<InputDeviceMatcher> GetDeviceMatchers(string layoutName)
  118. {
  119. if (string.IsNullOrEmpty(layoutName))
  120. throw new ArgumentException("Layout name cannot be null or empty", nameof(layoutName));
  121. Refresh();
  122. s_DeviceMatchers.TryGetValue(new InternedString(layoutName), out var matchers);
  123. return matchers;
  124. }
  125. public static string GetDisplayName(string layoutName)
  126. {
  127. if (string.IsNullOrEmpty(layoutName))
  128. throw new ArgumentException("Layout name cannot be null or empty", nameof(layoutName));
  129. var layout = TryGetLayout(layoutName);
  130. if (layout == null)
  131. return layoutName;
  132. if (!string.IsNullOrEmpty(layout.displayName))
  133. return layout.displayName;
  134. return layout.name;
  135. }
  136. /// <summary>
  137. /// List the controls that may be present on controls or devices of the given layout by virtue
  138. /// of being defined in other layouts based on it.
  139. /// </summary>
  140. /// <param name="layoutName"></param>
  141. /// <returns></returns>
  142. public static IEnumerable<OptionalControl> GetOptionalControlsForLayout(string layoutName)
  143. {
  144. if (string.IsNullOrEmpty(layoutName))
  145. throw new ArgumentException("Layout name cannot be null or empty", nameof(layoutName));
  146. Refresh();
  147. if (!s_OptionalControls.TryGetValue(new InternedString(layoutName), out var list))
  148. return Enumerable.Empty<OptionalControl>();
  149. return list;
  150. }
  151. public static Texture2D GetIconForLayout(string layoutName)
  152. {
  153. if (string.IsNullOrEmpty(layoutName))
  154. throw new ArgumentNullException(nameof(layoutName));
  155. Refresh();
  156. // See if we already have it in the cache.
  157. var internedName = new InternedString(layoutName);
  158. if (s_Icons.TryGetValue(internedName, out var icon))
  159. return icon;
  160. // No, so see if we have an icon on disk for exactly the layout
  161. // we're looking at (i.e. with the same name).
  162. icon = GUIHelpers.LoadIcon(layoutName);
  163. if (icon != null)
  164. {
  165. s_Icons.Add(internedName, icon);
  166. return icon;
  167. }
  168. // No, not that either so start walking up the inheritance chain
  169. // until we either bump against the ceiling or find an icon.
  170. var layout = TryGetLayout(layoutName);
  171. if (layout != null)
  172. {
  173. foreach (var baseLayoutName in layout.baseLayouts)
  174. {
  175. icon = GetIconForLayout(baseLayoutName);
  176. if (icon != null)
  177. return icon;
  178. }
  179. // If it's a control and there's no specific icon, return a generic one.
  180. if (layout.isControlLayout)
  181. {
  182. var genericIcon = GUIHelpers.LoadIcon("InputControl");
  183. if (genericIcon != null)
  184. {
  185. s_Icons.Add(internedName, genericIcon);
  186. return genericIcon;
  187. }
  188. }
  189. }
  190. // No icon for anything in this layout's chain.
  191. return null;
  192. }
  193. public struct ControlSearchResult
  194. {
  195. public string controlPath;
  196. public InputControlLayout layout;
  197. public InputControlLayout.ControlItem item;
  198. }
  199. internal static void Clear()
  200. {
  201. s_LayoutRegistrationVersion = 0;
  202. s_LayoutCacheRef.Dispose();
  203. s_Usages.Clear();
  204. s_ControlLayouts.Clear();
  205. s_DeviceLayouts.Clear();
  206. s_ProductLayouts.Clear();
  207. s_DeviceMatchers.Clear();
  208. s_Icons.Clear();
  209. }
  210. // If our layout data is outdated, rescan all the layouts in the system.
  211. private static void Refresh()
  212. {
  213. var manager = InputSystem.s_Manager;
  214. if (manager.m_LayoutRegistrationVersion == s_LayoutRegistrationVersion)
  215. return;
  216. Clear();
  217. if (!s_LayoutCacheRef.valid)
  218. {
  219. // In the editor, we keep a permanent reference on the global layout
  220. // cache. Means that in the editor, we always have all layouts loaded in full
  221. // at all times whereas in the player, we load layouts only while we need
  222. // them and then release them again.
  223. s_LayoutCacheRef = InputControlLayout.CacheRef();
  224. }
  225. var layoutNames = manager.ListControlLayouts().ToArray();
  226. // Remember which layout maps to which device matchers.
  227. var layoutMatchers = InputControlLayout.s_Layouts.layoutMatchers;
  228. foreach (var entry in layoutMatchers)
  229. {
  230. s_DeviceMatchers.TryGetValue(entry.layoutName, out var matchers);
  231. matchers.Append(entry.deviceMatcher);
  232. s_DeviceMatchers[entry.layoutName] = matchers;
  233. }
  234. // Load and store all layouts.
  235. foreach (var layoutName in layoutNames)
  236. {
  237. ////FIXME: does not protect against exceptions
  238. var layout = InputControlLayout.cache.FindOrLoadLayout(layoutName, throwIfNotFound: false);
  239. if (layout == null)
  240. continue;
  241. ScanLayout(layout);
  242. if (layout.isOverride)
  243. continue;
  244. if (layout.isControlLayout)
  245. s_ControlLayouts.Add(layout.name);
  246. else if (s_DeviceMatchers.ContainsKey(layout.name))
  247. s_ProductLayouts.Add(layout.name);
  248. else
  249. s_DeviceLayouts.Add(layout.name);
  250. }
  251. // Move all device layouts without a device description but derived from
  252. // a layout that has one over to the product list.
  253. foreach (var name in s_DeviceLayouts)
  254. {
  255. var layout = InputControlLayout.cache.FindOrLoadLayout(name);
  256. if (layout.m_BaseLayouts.length > 1)
  257. throw new NotImplementedException();
  258. for (var baseLayoutName = layout.baseLayouts.FirstOrDefault(); !baseLayoutName.IsEmpty();)
  259. {
  260. if (s_ProductLayouts.Contains(baseLayoutName))
  261. {
  262. // Defer removing from s_DeviceLayouts to keep iteration stable.
  263. s_ProductLayouts.Add(name);
  264. break;
  265. }
  266. var baseLayout = InputControlLayout.cache.FindOrLoadLayout(baseLayoutName, throwIfNotFound: false);
  267. if (baseLayout == null)
  268. continue;
  269. if (baseLayout.m_BaseLayouts.length > 1)
  270. throw new NotImplementedException();
  271. baseLayoutName = baseLayout.baseLayouts.FirstOrDefault();
  272. }
  273. }
  274. // Remove every product device layout now.
  275. s_DeviceLayouts.ExceptWith(s_ProductLayouts);
  276. s_LayoutRegistrationVersion = manager.m_LayoutRegistrationVersion;
  277. }
  278. private static int s_LayoutRegistrationVersion;
  279. private static InputControlLayout.CacheRefInstance s_LayoutCacheRef;
  280. private static readonly HashSet<InternedString> s_ControlLayouts = new HashSet<InternedString>();
  281. private static readonly HashSet<InternedString> s_DeviceLayouts = new HashSet<InternedString>();
  282. private static readonly HashSet<InternedString> s_ProductLayouts = new HashSet<InternedString>();
  283. private static readonly Dictionary<InternedString, List<OptionalControl>> s_OptionalControls =
  284. new Dictionary<InternedString, List<OptionalControl>>();
  285. private static readonly Dictionary<InternedString, InlinedArray<InputDeviceMatcher>> s_DeviceMatchers =
  286. new Dictionary<InternedString, InlinedArray<InputDeviceMatcher>>();
  287. private static Dictionary<InternedString, Texture2D> s_Icons =
  288. new Dictionary<InternedString, Texture2D>();
  289. // We keep a map of the devices which a derived from a base device.
  290. private static readonly Dictionary<InternedString, HashSet<InternedString>> s_DeviceChildLayouts =
  291. new Dictionary<InternedString, HashSet<InternedString>>();
  292. // We keep a map of all unique usages we find in layouts and also
  293. // retain a list of the layouts they are used with.
  294. private static readonly SortedDictionary<InternedString, HashSet<InternedString>> s_Usages =
  295. new SortedDictionary<InternedString, HashSet<InternedString>>();
  296. private static void ScanLayout(InputControlLayout layout)
  297. {
  298. var controls = layout.controls;
  299. for (var i = 0; i < controls.Count; ++i)
  300. {
  301. var control = controls[i];
  302. // If it's not just a control modifying some inner child control, add control to all base
  303. // layouts as an optional control.
  304. //
  305. // NOTE: We're looking at layouts post-merging here. Means we have already picked up all the
  306. // controls present on the base.
  307. // Only controls which belong to UI-facing layouts are included, as optional controls are used solely by
  308. // the InputControlPickerDropdown UI
  309. if (control.isFirstDefinedInThisLayout && !control.isModifyingExistingControl && !control.layout.IsEmpty() && !layout.hideInUI)
  310. {
  311. foreach (var baseLayout in layout.baseLayouts)
  312. AddOptionalControlRecursive(baseLayout, ref control);
  313. }
  314. // Collect unique usages and the layouts used with them.
  315. foreach (var usage in control.usages)
  316. {
  317. // Empty usages can occur for controls that want to reset inherited usages.
  318. if (string.IsNullOrEmpty(usage))
  319. continue;
  320. var internedUsage = new InternedString(usage);
  321. var internedLayout = new InternedString(control.layout);
  322. if (!s_Usages.TryGetValue(internedUsage, out var layoutSet))
  323. {
  324. layoutSet = new HashSet<InternedString> { internedLayout };
  325. s_Usages[internedUsage] = layoutSet;
  326. }
  327. else
  328. {
  329. layoutSet.Add(internedLayout);
  330. }
  331. }
  332. // Create a dependency tree matching each concrete device layout exposed in the UI
  333. // to all of the layouts that are directly derived from it.
  334. if (layout.isDeviceLayout && !layout.hideInUI)
  335. {
  336. foreach (var baseLayoutName in layout.baseLayouts)
  337. {
  338. if (!s_DeviceChildLayouts.TryGetValue(baseLayoutName, out var derivedSet))
  339. {
  340. derivedSet = new HashSet<InternedString> { layout.name };
  341. s_DeviceChildLayouts[baseLayoutName] = derivedSet;
  342. }
  343. else
  344. {
  345. derivedSet.Add(layout.name);
  346. }
  347. }
  348. }
  349. }
  350. }
  351. private static void AddOptionalControlRecursive(InternedString layoutName, ref InputControlLayout.ControlItem controlItem)
  352. {
  353. Debug.Assert(!controlItem.isModifyingExistingControl);
  354. Debug.Assert(!controlItem.layout.IsEmpty());
  355. // Recurse into base.
  356. if (InputControlLayout.s_Layouts.baseLayoutTable.TryGetValue(layoutName, out var baseLayoutName))
  357. AddOptionalControlRecursive(baseLayoutName, ref controlItem);
  358. // See if we already have this optional control.
  359. var alreadyPresent = false;
  360. if (!s_OptionalControls.TryGetValue(layoutName, out var list))
  361. {
  362. list = new List<OptionalControl>();
  363. s_OptionalControls[layoutName] = list;
  364. }
  365. else
  366. {
  367. // See if we already have this control.
  368. foreach (var item in list)
  369. {
  370. if (item.name == controlItem.name && item.layout == controlItem.layout)
  371. {
  372. alreadyPresent = true;
  373. break;
  374. }
  375. }
  376. }
  377. if (!alreadyPresent)
  378. list.Add(new OptionalControl {name = controlItem.name, layout = controlItem.layout});
  379. }
  380. /// <summary>
  381. /// An optional control is a control that is not defined on a layout but which is defined
  382. /// on a derived layout.
  383. /// </summary>
  384. /// <remarks>
  385. /// An example is the "acceleration" control defined by some layouts based on <see cref="Gamepad"/> (e.g.
  386. /// <see cref="DualShockGamepad.acceleration"/>. This means gamepads
  387. /// MAY have a gyro and thus MAY have an "acceleration" control.
  388. ///
  389. /// In bindings (<see cref="InputBinding"/>), it is perfectly valid to deal with this opportunistically
  390. /// and create a binding to <c>"&lt;Gamepad&gt;/acceleration"</c> which will bind correctly IF the gamepad has
  391. /// an acceleration control but will do nothing if it doesn't.
  392. ///
  393. /// The concept of optional controls permits setting up such bindings in the UI by making controls that
  394. /// are present on more specific layouts than the one currently looked at available directly on the
  395. /// base layout.
  396. /// </remarks>
  397. public struct OptionalControl
  398. {
  399. public InternedString name;
  400. public InternedString layout;
  401. ////REVIEW: do we want to have the list of layouts that define the control?
  402. }
  403. }
  404. }
  405. #endif // UNITY_EDITOR