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