EditorInputControlLayoutCache.cs 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419
  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 InputControlLayout TryGetLayout(string layoutName)
  72. {
  73. if (string.IsNullOrEmpty(layoutName))
  74. throw new ArgumentException("Layout name cannot be null or empty", nameof(layoutName));
  75. Refresh();
  76. return InputControlLayout.cache.FindOrLoadLayout(layoutName, throwIfNotFound: false);
  77. }
  78. public static Type GetValueType(string layoutName)
  79. {
  80. if (string.IsNullOrEmpty(layoutName))
  81. throw new ArgumentException("Layout name cannot be null or empty", nameof(layoutName));
  82. // Load layout.
  83. var layout = TryGetLayout(layoutName);
  84. if (layout == null)
  85. return null;
  86. // Grab type.
  87. var type = layout.type;
  88. Debug.Assert(type != null, "Layout should have associated type");
  89. Debug.Assert(typeof(InputControl).IsAssignableFrom(type),
  90. "Layout's associated type should be derived from InputControl");
  91. return layout.GetValueType();
  92. }
  93. public static IEnumerable<InputDeviceMatcher> GetDeviceMatchers(string layoutName)
  94. {
  95. if (string.IsNullOrEmpty(layoutName))
  96. throw new ArgumentException("Layout name cannot be null or empty", nameof(layoutName));
  97. Refresh();
  98. s_DeviceMatchers.TryGetValue(new InternedString(layoutName), out var matchers);
  99. return matchers;
  100. }
  101. public static string GetDisplayName(string layoutName)
  102. {
  103. if (string.IsNullOrEmpty(layoutName))
  104. throw new ArgumentException("Layout name cannot be null or empty", nameof(layoutName));
  105. var layout = TryGetLayout(layoutName);
  106. if (layout == null)
  107. return layoutName;
  108. if (!string.IsNullOrEmpty(layout.displayName))
  109. return layout.displayName;
  110. return layout.name;
  111. }
  112. /// <summary>
  113. /// List the controls that may be present on controls or devices of the given layout by virtue
  114. /// of being defined in other layouts based on it.
  115. /// </summary>
  116. /// <param name="layoutName"></param>
  117. /// <returns></returns>
  118. public static IEnumerable<OptionalControl> GetOptionalControlsForLayout(string layoutName)
  119. {
  120. if (string.IsNullOrEmpty(layoutName))
  121. throw new ArgumentException("Layout name cannot be null or empty", nameof(layoutName));
  122. Refresh();
  123. if (!s_OptionalControls.TryGetValue(new InternedString(layoutName), out var list))
  124. return Enumerable.Empty<OptionalControl>();
  125. return list;
  126. }
  127. public static Texture2D GetIconForLayout(string layoutName)
  128. {
  129. if (string.IsNullOrEmpty(layoutName))
  130. throw new ArgumentNullException(nameof(layoutName));
  131. Refresh();
  132. // See if we already have it in the cache.
  133. var internedName = new InternedString(layoutName);
  134. if (s_Icons.TryGetValue(internedName, out var icon))
  135. return icon;
  136. // No, so see if we have an icon on disk for exactly the layout
  137. // we're looking at (i.e. with the same name).
  138. icon = GUIHelpers.LoadIcon(layoutName);
  139. if (icon != null)
  140. {
  141. s_Icons.Add(internedName, icon);
  142. return icon;
  143. }
  144. // No, not that either so start walking up the inheritance chain
  145. // until we either bump against the ceiling or find an icon.
  146. var layout = TryGetLayout(layoutName);
  147. if (layout != null)
  148. {
  149. foreach (var baseLayoutName in layout.baseLayouts)
  150. {
  151. icon = GetIconForLayout(baseLayoutName);
  152. if (icon != null)
  153. return icon;
  154. }
  155. // If it's a control and there's no specific icon, return a generic one.
  156. if (layout.isControlLayout)
  157. {
  158. var genericIcon = GUIHelpers.LoadIcon("InputControl");
  159. if (genericIcon != null)
  160. {
  161. s_Icons.Add(internedName, genericIcon);
  162. return genericIcon;
  163. }
  164. }
  165. }
  166. // No icon for anything in this layout's chain.
  167. return null;
  168. }
  169. public struct ControlSearchResult
  170. {
  171. public string controlPath;
  172. public InputControlLayout layout;
  173. public InputControlLayout.ControlItem item;
  174. }
  175. internal static void Clear()
  176. {
  177. s_LayoutRegistrationVersion = 0;
  178. s_LayoutCacheRef.Dispose();
  179. s_Usages.Clear();
  180. s_ControlLayouts.Clear();
  181. s_DeviceLayouts.Clear();
  182. s_ProductLayouts.Clear();
  183. s_DeviceMatchers.Clear();
  184. s_Icons.Clear();
  185. }
  186. // If our layout data is outdated, rescan all the layouts in the system.
  187. private static void Refresh()
  188. {
  189. var manager = InputSystem.s_Manager;
  190. if (manager.m_LayoutRegistrationVersion == s_LayoutRegistrationVersion)
  191. return;
  192. Clear();
  193. if (!s_LayoutCacheRef.valid)
  194. {
  195. // In the editor, we keep a permanent reference on the global layout
  196. // cache. Means that in the editor, we always have all layouts loaded in full
  197. // at all times whereas in the player, we load layouts only while we need
  198. // them and then release them again.
  199. s_LayoutCacheRef = InputControlLayout.CacheRef();
  200. }
  201. var layoutNames = manager.ListControlLayouts().ToArray();
  202. // Remember which layout maps to which device matchers.
  203. var layoutMatchers = InputControlLayout.s_Layouts.layoutMatchers;
  204. foreach (var entry in layoutMatchers)
  205. {
  206. s_DeviceMatchers.TryGetValue(entry.layoutName, out var matchers);
  207. matchers.Append(entry.deviceMatcher);
  208. s_DeviceMatchers[entry.layoutName] = matchers;
  209. }
  210. // Load and store all layouts.
  211. foreach (var layoutName in layoutNames)
  212. {
  213. ////FIXME: does not protect against exceptions
  214. var layout = InputControlLayout.cache.FindOrLoadLayout(layoutName, throwIfNotFound: false);
  215. if (layout == null)
  216. continue;
  217. ScanLayout(layout);
  218. if (layout.isOverride)
  219. continue;
  220. if (layout.isControlLayout)
  221. s_ControlLayouts.Add(layout.name);
  222. else if (s_DeviceMatchers.ContainsKey(layout.name))
  223. s_ProductLayouts.Add(layout.name);
  224. else
  225. s_DeviceLayouts.Add(layout.name);
  226. }
  227. // Move all device layouts without a device description but derived from
  228. // a layout that has one over to the product list.
  229. foreach (var name in s_DeviceLayouts)
  230. {
  231. var layout = InputControlLayout.cache.FindOrLoadLayout(name);
  232. if (layout.m_BaseLayouts.length > 1)
  233. throw new NotImplementedException();
  234. for (var baseLayoutName = layout.baseLayouts.FirstOrDefault(); !baseLayoutName.IsEmpty();)
  235. {
  236. if (s_ProductLayouts.Contains(baseLayoutName))
  237. {
  238. // Defer removing from s_DeviceLayouts to keep iteration stable.
  239. s_ProductLayouts.Add(name);
  240. break;
  241. }
  242. var baseLayout = InputControlLayout.cache.FindOrLoadLayout(baseLayoutName, throwIfNotFound: false);
  243. if (baseLayout == null)
  244. continue;
  245. if (baseLayout.m_BaseLayouts.length > 1)
  246. throw new NotImplementedException();
  247. baseLayoutName = baseLayout.baseLayouts.FirstOrDefault();
  248. }
  249. }
  250. // Remove every product device layout now.
  251. s_DeviceLayouts.ExceptWith(s_ProductLayouts);
  252. s_LayoutRegistrationVersion = manager.m_LayoutRegistrationVersion;
  253. }
  254. private static int s_LayoutRegistrationVersion;
  255. private static InputControlLayout.CacheRefInstance s_LayoutCacheRef;
  256. private static readonly HashSet<InternedString> s_ControlLayouts = new HashSet<InternedString>();
  257. private static readonly HashSet<InternedString> s_DeviceLayouts = new HashSet<InternedString>();
  258. private static readonly HashSet<InternedString> s_ProductLayouts = new HashSet<InternedString>();
  259. private static readonly Dictionary<InternedString, List<OptionalControl>> s_OptionalControls =
  260. new Dictionary<InternedString, List<OptionalControl>>();
  261. private static readonly Dictionary<InternedString, InlinedArray<InputDeviceMatcher>> s_DeviceMatchers =
  262. new Dictionary<InternedString, InlinedArray<InputDeviceMatcher>>();
  263. private static Dictionary<InternedString, Texture2D> s_Icons =
  264. new Dictionary<InternedString, Texture2D>();
  265. // We keep a map of all unique usages we find in layouts and also
  266. // retain a list of the layouts they are used with.
  267. private static readonly SortedDictionary<InternedString, List<InternedString>> s_Usages =
  268. new SortedDictionary<InternedString, List<InternedString>>();
  269. private static void ScanLayout(InputControlLayout layout)
  270. {
  271. var controls = layout.controls;
  272. for (var i = 0; i < controls.Count; ++i)
  273. {
  274. var control = controls[i];
  275. // If it's not just a control modifying some inner child control, add control to all base
  276. // layouts as an optional control.
  277. //
  278. // NOTE: We're looking at layouts post-merging here. Means we have already picked up all the
  279. // controls present on the base.
  280. if (control.isFirstDefinedInThisLayout && !control.isModifyingExistingControl && !control.layout.IsEmpty())
  281. {
  282. foreach (var baseLayout in layout.baseLayouts)
  283. AddOptionalControlRecursive(baseLayout, ref control);
  284. }
  285. // Collect unique usages and the layouts used with them.
  286. foreach (var usage in control.usages)
  287. {
  288. // Empty usages can occur for controls that want to reset inherited usages.
  289. if (string.IsNullOrEmpty(usage))
  290. continue;
  291. var internedUsage = new InternedString(usage);
  292. var internedLayout = new InternedString(control.layout);
  293. if (!s_Usages.TryGetValue(internedUsage, out var layoutList))
  294. {
  295. layoutList = new List<InternedString> {internedLayout};
  296. s_Usages[internedUsage] = layoutList;
  297. }
  298. else
  299. {
  300. var layoutAlreadyInList =
  301. layoutList.Any(x => x == internedLayout);
  302. if (!layoutAlreadyInList)
  303. layoutList.Add(internedLayout);
  304. }
  305. }
  306. }
  307. }
  308. private static void AddOptionalControlRecursive(InternedString layoutName, ref InputControlLayout.ControlItem controlItem)
  309. {
  310. Debug.Assert(!controlItem.isModifyingExistingControl);
  311. Debug.Assert(!controlItem.layout.IsEmpty());
  312. // Recurse into base.
  313. if (InputControlLayout.s_Layouts.baseLayoutTable.TryGetValue(layoutName, out var baseLayoutName))
  314. AddOptionalControlRecursive(baseLayoutName, ref controlItem);
  315. // See if we already have this optional control.
  316. var alreadyPresent = false;
  317. if (!s_OptionalControls.TryGetValue(layoutName, out var list))
  318. {
  319. list = new List<OptionalControl>();
  320. s_OptionalControls[layoutName] = list;
  321. }
  322. else
  323. {
  324. // See if we already have this control.
  325. foreach (var item in list)
  326. {
  327. if (item.name == controlItem.name && item.layout == controlItem.layout)
  328. {
  329. alreadyPresent = true;
  330. break;
  331. }
  332. }
  333. }
  334. if (!alreadyPresent)
  335. list.Add(new OptionalControl {name = controlItem.name, layout = controlItem.layout});
  336. }
  337. /// <summary>
  338. /// An optional control is a control that is not defined on a layout but which is defined
  339. /// on a derived layout.
  340. /// </summary>
  341. /// <remarks>
  342. /// An example is the "acceleration" control defined by some layouts based on <see cref="Gamepad"/> (e.g.
  343. /// <see cref="DualShockGamepad.acceleration"/>. This means gamepads
  344. /// MAY have a gyro and thus MAY have an "acceleration" control.
  345. ///
  346. /// In bindings (<see cref="InputBinding"/>), it is perfectly valid to deal with this opportunistically
  347. /// and create a binding to <c>"&lt;Gamepad&gt;/acceleration"</c> which will bind correctly IF the gamepad has
  348. /// an acceleration control but will do nothing if it doesn't.
  349. ///
  350. /// The concept of optional controls permits setting up such bindings in the UI by making controls that
  351. /// are present on more specific layouts than the one currently looked at available directly on the
  352. /// base layout.
  353. /// </remarks>
  354. public struct OptionalControl
  355. {
  356. public InternedString name;
  357. public InternedString layout;
  358. ////REVIEW: do we want to have the list of layouts that define the control?
  359. }
  360. }
  361. }
  362. #endif // UNITY_EDITOR