A game about forced loneliness, made by TACStudios
1using System; 2using System.Collections.Generic; 3using System.Linq; 4using System.Reflection; 5using System.Runtime.CompilerServices; 6using System.Runtime.InteropServices; 7using System.Runtime.Serialization; 8using UnityEngine.InputSystem.LowLevel; 9using UnityEngine.InputSystem.Utilities; 10 11////TODO: *kill* variants! 12 13////TODO: we really need proper verification to be in place to ensure that the resulting layout isn't coming out with a bad memory layout 14 15////TODO: add code-generation that takes a layout and spits out C# code that translates it to a common value format 16//// (this can be used, for example, to translate all the various gamepad formats into one single common gamepad format) 17 18////TODO: allow layouts to set default device names 19 20////TODO: allow creating generic controls as parents just to group child controls 21 22////TODO: allow things like "-something" and "+something" for usages, processors, etc 23 24////TODO: allow setting whether the device should automatically become current and whether it wants noise filtering 25 26////TODO: ensure that if a layout sets a device description, it is indeed a device layout 27 28////TODO: make offset on InputControlAttribute relative to field instead of relative to entire state struct 29 30////REVIEW: common usages are on all layouts but only make sense for devices 31 32////REVIEW: useStateFrom seems like a half-measure; it solves the problem of setting up state blocks but they often also 33//// require a specific set of processors 34 35////REVIEW: Can we allow aliases to be paths rather than just plain names? This would allow changing the hierarchy around while 36//// keeping backwards-compatibility. 37 38// Q: Why is there this layout system instead of just new'ing everything up in hand-written C# code? 39// A: The current approach has a couple advantages. 40// 41// * Since it's data-driven, entire layouts can be represented as just data. They can be added to already deployed applications, 42// can be sent over the wire, can be analyzed by tools, etc. 43// 44// * The layouts can be rearranged in powerful ways, even on the fly. Data can be inserted or modified all along the hierarchy 45// both from within a layout itself as well as from outside through overrides. The resulting compositions would often be very 46// hard/tedious to set up in a linear C# inheritance hierarchy and likely result in repeated reallocation and rearranging of 47// already created setups. 48// 49// * Related to that, the data-driven layouts make it possible to significantly change the data model without requiring changes 50// to existing layouts. This, too, would be more complicated if every device would simply new up everything directly. 51// 52// * We can generate code from them. Means we can, for example, generate code for the DOTS runtime from the same information 53// that exists in the input system but without depending on its InputDevice C# implementation. 54// 55// The biggest drawback, other than code complexity, is that building an InputDevice from an InputControlLayout is slow. 56// This is somewhat offset by having a code generator that can "freeze" a specific layout into simple C# code. For these, 57// the result is code at least as efficient (but likely *more* efficient) than the equivalent in a code-only layout approach 58// while at the same time offering all the advantages of the data-driven approach. 59 60namespace UnityEngine.InputSystem.Layouts 61{ 62 /// <summary> 63 /// Delegate used by <see cref="InputSystem.onFindLayoutForDevice"/>. 64 /// </summary> 65 /// <param name="description">The device description supplied by the runtime or through <see 66 /// cref="InputSystem.AddDevice(InputDeviceDescription)"/>. This is passed by reference instead of 67 /// by value to allow the callback to fill out fields such as <see cref="InputDeviceDescription.capabilities"/> 68 /// on the fly based on information queried from external APIs or from the runtime.</param> 69 /// <param name="matchedLayout">Name of the layout that has been selected for the device or <c>null</c> if 70 /// no matching layout could be found. Matching is determined from the <see cref="InputDeviceMatcher"/>s for 71 /// layouts registered in the system.</param> 72 /// <param name="executeDeviceCommand">A delegate which can be invoked to execute <see cref="InputDeviceCommand"/>s 73 /// on the device.</param> 74 /// <returns> Return <c>null</c> or an empty string to indicate that </returns> 75 /// <remarks> 76 /// </remarks> 77 /// <seealso cref="InputSystem.onFindLayoutForDevice"/> 78 /// <seealso cref="InputSystem.RegisterLayoutBuilder"/> 79 /// <seealso cref="InputControlLayout"/> 80 public delegate string InputDeviceFindControlLayoutDelegate(ref InputDeviceDescription description, 81 string matchedLayout, InputDeviceExecuteCommandDelegate executeDeviceCommand); 82 83 /// <summary> 84 /// A control layout specifies the composition of an <see cref="InputControl"/> or 85 /// <see cref="InputDevice"/>. 86 /// </summary> 87 /// <remarks> 88 /// Control layouts can be created in three possible ways: 89 /// 90 /// <list type="number"> 91 /// <item><description>Loaded from JSON.</description></item> 92 /// <item><description>Constructed through reflection from <see cref="InputControl">InputControls</see> classes.</description></item> 93 /// <item><description>Through layout factories using <see cref="InputControlLayout.Builder"/>.</description></item> 94 /// </list> 95 /// 96 /// Once constructed, control layouts are immutable (but you can always 97 /// replace a registered layout in the system and it will affect 98 /// everything constructed from the layout). 99 /// 100 /// Control layouts can be for arbitrary control rigs or for entire 101 /// devices. Device layouts can be matched to <see cref="InputDeviceDescription"> 102 /// device description</see> using associated <see cref="InputDeviceMatcher"> 103 /// device matchers</see>. 104 /// 105 /// InputControlLayout objects are considered temporaries. Except in the 106 /// editor, they are not kept around beyond device creation. 107 /// 108 /// See the <a href="../manual/Layouts.html">manual</a> for more details on control layouts. 109 /// </remarks> 110 public class InputControlLayout 111 { 112 private static InternedString s_DefaultVariant = new InternedString("Default"); 113 public static InternedString DefaultVariant => s_DefaultVariant; 114 115 public const string VariantSeparator = ";"; 116 117 /// <summary> 118 /// Specification for the composition of a direct or indirect child control. 119 /// </summary> 120 public struct ControlItem 121 { 122 /// <summary> 123 /// Name of the control. Cannot be empty or <c>null</c>. 124 /// </summary> 125 /// <value>Name of the control.</value> 126 /// <remarks> 127 /// This may also be a path of the form <c>"parentName/childName..."</c>. 128 /// This can be used to reach inside another layout and modify properties of 129 /// a control inside of it. An example for this is adding a "leftStick" control 130 /// using the Stick layout and then adding two control layouts that refer to 131 /// "leftStick/x" and "leftStick/y" respectively to modify the state format used 132 /// by the stick. 133 /// 134 /// This field is required. 135 /// </remarks> 136 /// <seealso cref="isModifyingExistingControl"/> 137 /// <seealso cref="InputControlAttribute.name"/> 138 public InternedString name { get; internal set; } 139 140 /// <summary> 141 /// Name of the layout to use for the control. 142 /// </summary> 143 /// <value>Name of layout to use.</value> 144 /// <remarks> 145 /// Must be the name of a control layout, not device layout. 146 /// 147 /// An example would be "Stick". 148 /// </remarks> 149 /// <seealso cref="InputSystem.RegisterLayout(Type,string,Nullable{InputDeviceMatcher}"/> 150 public InternedString layout { get; internal set; } 151 152 public InternedString variants { get; internal set; } 153 public string useStateFrom { get; internal set; } 154 155 /// <summary> 156 /// Optional display name of the control. 157 /// </summary> 158 /// <seealso cref="InputControl.displayName"/> 159 public string displayName { get; internal set; } 160 161 /// <summary> 162 /// Optional abbreviated display name of the control. 163 /// </summary> 164 /// <seealso cref="InputControl.shortDisplayName"/> 165 public string shortDisplayName { get; internal set; } 166 167 public ReadOnlyArray<InternedString> usages { get; internal set; } 168 public ReadOnlyArray<InternedString> aliases { get; internal set; } 169 public ReadOnlyArray<NamedValue> parameters { get; internal set; } 170 public ReadOnlyArray<NameAndParameters> processors { get; internal set; } 171 public uint offset { get; internal set; } 172 public uint bit { get; internal set; } 173 public uint sizeInBits { get; internal set; } 174 public FourCC format { get; internal set; } 175 private Flags flags { get; set; } 176 public int arraySize { get; internal set; } 177 178 /// <summary> 179 /// Optional default value for the state memory associated with the control. 180 /// </summary> 181 public PrimitiveValue defaultState { get; internal set; } 182 183 public PrimitiveValue minValue { get; internal set; } 184 public PrimitiveValue maxValue { get; internal set; } 185 186 /// <summary> 187 /// If true, the item will not add a control but rather a modify a control 188 /// inside the hierarchy added by <see cref="layout"/>. This allows, for example, to modify 189 /// just the X axis control of the left stick directly from within a gamepad 190 /// layout instead of having to have a custom stick layout for the left stick 191 /// than in turn would have to make use of a custom axis layout for the X axis. 192 /// Instead, you can just have a control layout with the name <c>"leftStick/x"</c>. 193 /// </summary> 194 public bool isModifyingExistingControl 195 { 196 get => (flags & Flags.isModifyingExistingControl) == Flags.isModifyingExistingControl; 197 internal set 198 { 199 if (value) 200 flags |= Flags.isModifyingExistingControl; 201 else 202 flags &= ~Flags.isModifyingExistingControl; 203 } 204 } 205 206 /// <summary> 207 /// Get or set whether to mark the control as noisy. 208 /// </summary> 209 /// <value>Whether to mark the control as noisy.</value> 210 /// <remarks> 211 /// Noisy controls may generate varying input even without "proper" user interaction. For example, 212 /// a sensor may generate slightly different input values over time even if in fact the very thing 213 /// (such as the device orientation) that is being measured is not changing. 214 /// </remarks> 215 /// <seealso cref="InputControl.noisy"/> 216 /// <seealso cref="InputControlAttribute.noisy"/> 217 public bool isNoisy 218 { 219 get => (flags & Flags.IsNoisy) == Flags.IsNoisy; 220 internal set 221 { 222 if (value) 223 flags |= Flags.IsNoisy; 224 else 225 flags &= ~Flags.IsNoisy; 226 } 227 } 228 229 /// <summary> 230 /// Get or set whether to mark the control as "synthetic". 231 /// </summary> 232 /// <value>Whether to mark the control as synthetic.</value> 233 /// <remarks> 234 /// Synthetic controls are artificial controls that provide input but do not correspond to actual controls 235 /// on the hardware. An example is <see cref="Keyboard.anyKey"/> which is an artificial button that triggers 236 /// if any key on the keyboard is pressed. 237 /// </remarks> 238 /// <seealso cref="InputControl.synthetic"/> 239 /// <seealso cref="InputControlAttribute.synthetic"/> 240 public bool isSynthetic 241 { 242 get => (flags & Flags.IsSynthetic) == Flags.IsSynthetic; 243 internal set 244 { 245 if (value) 246 flags |= Flags.IsSynthetic; 247 else 248 flags &= ~Flags.IsSynthetic; 249 } 250 } 251 252 /// <summary> 253 /// Get or set whether the control should be excluded when performing a device reset. 254 /// </summary> 255 /// <value>If true, the control will not get reset in a device reset. Off by default.</value> 256 /// <remarks> 257 /// Some controls like, for example, mouse positions do not generally make sense to reset when a 258 /// device is reset. By setting this flag on, the control's state will be excluded in resets. 259 /// 260 /// Note that a full reset can still be forced through <see cref="InputSystem.ResetDevice"/> in 261 /// which case controls that have this flag set will also get reset. 262 /// </remarks> 263 /// <seealso cref="InputSystem.ResetDevice"/> 264 /// <seealso cref="InputControlAttribute.dontReset"/> 265 public bool dontReset 266 { 267 get => (flags & Flags.DontReset) == Flags.DontReset; 268 internal set 269 { 270 if (value) 271 flags |= Flags.DontReset; 272 else 273 flags &= ~Flags.DontReset; 274 } 275 } 276 277 /// <summary> 278 /// Whether the control is introduced by the layout. 279 /// </summary> 280 /// <value>If true, the control is first introduced by this layout.</value> 281 /// <remarks> 282 /// The value of this property is automatically determined by the input system. 283 /// </remarks> 284 public bool isFirstDefinedInThisLayout 285 { 286 get => (flags & Flags.IsFirstDefinedInThisLayout) != 0; 287 internal set 288 { 289 if (value) 290 flags |= Flags.IsFirstDefinedInThisLayout; 291 else 292 flags &= ~Flags.IsFirstDefinedInThisLayout; 293 } 294 } 295 296 public bool isArray => (arraySize != 0); 297 298 /// <summary> 299 /// For any property not set on this control layout, take the setting from <paramref name="other"/>. 300 /// </summary> 301 /// <param name="other">Control layout providing settings.</param> 302 /// <remarks> 303 /// <see cref="name"/> will not be touched. 304 /// </remarks> 305 /// <seealso cref="InputControlLayout.MergeLayout"/> 306 public ControlItem Merge(ControlItem other) 307 { 308 var result = new ControlItem(); 309 310 result.name = name; 311 Debug.Assert(!name.IsEmpty(), "Name must not be empty"); 312 result.isModifyingExistingControl = isModifyingExistingControl; 313 314 result.displayName = string.IsNullOrEmpty(displayName) ? other.displayName : displayName; 315 result.shortDisplayName = string.IsNullOrEmpty(shortDisplayName) ? other.shortDisplayName : shortDisplayName; 316 result.layout = layout.IsEmpty() ? other.layout : layout; 317 result.variants = variants.IsEmpty() ? other.variants : variants; 318 result.useStateFrom = useStateFrom ?? other.useStateFrom; 319 result.arraySize = !isArray ? other.arraySize : arraySize; 320 ////FIXME: allow overrides to unset this 321 result.isNoisy = isNoisy || other.isNoisy; 322 result.dontReset = dontReset || other.dontReset; 323 result.isSynthetic = isSynthetic || other.isSynthetic; 324 result.isFirstDefinedInThisLayout = false; 325 326 if (offset != InputStateBlock.InvalidOffset) 327 result.offset = offset; 328 else 329 result.offset = other.offset; 330 331 if (bit != InputStateBlock.InvalidOffset) 332 result.bit = bit; 333 else 334 result.bit = other.bit; 335 336 if (format != 0) 337 result.format = format; 338 else 339 result.format = other.format; 340 341 if (sizeInBits != 0) 342 result.sizeInBits = sizeInBits; 343 else 344 result.sizeInBits = other.sizeInBits; 345 346 if (aliases.Count > 0) 347 result.aliases = aliases; 348 else 349 result.aliases = other.aliases; 350 351 if (usages.Count > 0) 352 result.usages = usages; 353 else 354 result.usages = other.usages; 355 356 ////FIXME: this should properly merge the parameters, not just pick one or the other 357 //// easiest thing may be to just concatenate the two strings 358 359 if (parameters.Count == 0) 360 result.parameters = other.parameters; 361 else 362 result.parameters = parameters; 363 364 if (processors.Count == 0) 365 result.processors = other.processors; 366 else 367 result.processors = processors; 368 369 if (!string.IsNullOrEmpty(displayName)) 370 result.displayName = displayName; 371 else 372 result.displayName = other.displayName; 373 374 if (!defaultState.isEmpty) 375 result.defaultState = defaultState; 376 else 377 result.defaultState = other.defaultState; 378 379 if (!minValue.isEmpty) 380 result.minValue = minValue; 381 else 382 result.minValue = other.minValue; 383 384 if (!maxValue.isEmpty) 385 result.maxValue = maxValue; 386 else 387 result.maxValue = other.maxValue; 388 389 return result; 390 } 391 392 [Flags] 393 private enum Flags 394 { 395 isModifyingExistingControl = 1 << 0, 396 IsNoisy = 1 << 1, 397 IsSynthetic = 1 << 2, 398 IsFirstDefinedInThisLayout = 1 << 3, 399 DontReset = 1 << 4, 400 } 401 } 402 403 // Unique name of the layout. 404 // NOTE: Case-insensitive. 405 public InternedString name => m_Name; 406 407 public string displayName => m_DisplayName ?? m_Name; 408 409 public Type type => m_Type; 410 411 public InternedString variants => m_Variants; 412 413 public FourCC stateFormat => m_StateFormat; 414 415 public int stateSizeInBytes => m_StateSizeInBytes; 416 417 public IEnumerable<InternedString> baseLayouts => m_BaseLayouts; 418 419 public IEnumerable<InternedString> appliedOverrides => m_AppliedOverrides; 420 421 public ReadOnlyArray<InternedString> commonUsages => new ReadOnlyArray<InternedString>(m_CommonUsages); 422 423 /// <summary> 424 /// List of child controls defined for the layout. 425 /// </summary> 426 /// <value>Child controls defined for the layout.</value> 427 public ReadOnlyArray<ControlItem> controls => new ReadOnlyArray<ControlItem>(m_Controls); 428 429 ////FIXME: this should be a `bool?` 430 public bool updateBeforeRender => m_UpdateBeforeRender ?? false; 431 432 public bool isDeviceLayout => typeof(InputDevice).IsAssignableFrom(m_Type); 433 434 public bool isControlLayout => !isDeviceLayout; 435 436 /// <summary> 437 /// Whether the layout is applies overrides to other layouts instead of 438 /// defining a layout by itself. 439 /// </summary> 440 /// <value>True if the layout acts as an override.</value> 441 /// <seealso cref="InputSystem.RegisterLayoutOverride"/> 442 public bool isOverride 443 { 444 get => (m_Flags & Flags.IsOverride) != 0; 445 internal set 446 { 447 if (value) 448 m_Flags |= Flags.IsOverride; 449 else 450 m_Flags &= ~Flags.IsOverride; 451 } 452 } 453 454 public bool isGenericTypeOfDevice 455 { 456 get => (m_Flags & Flags.IsGenericTypeOfDevice) != 0; 457 internal set 458 { 459 if (value) 460 m_Flags |= Flags.IsGenericTypeOfDevice; 461 else 462 m_Flags &= ~Flags.IsGenericTypeOfDevice; 463 } 464 } 465 466 public bool hideInUI 467 { 468 get => (m_Flags & Flags.HideInUI) != 0; 469 internal set 470 { 471 if (value) 472 m_Flags |= Flags.HideInUI; 473 else 474 m_Flags &= ~Flags.HideInUI; 475 } 476 } 477 478 /// <summary> 479 /// Mark the input device created from this layout as noisy, irrespective of whether or not any 480 /// of its controls have been marked as noisy. 481 /// </summary> 482 /// <seealso cref="InputControlLayoutAttribute.isNoisy"/> 483 public bool isNoisy 484 { 485 get => (m_Flags & Flags.IsNoisy) != 0; 486 internal set 487 { 488 if (value) 489 m_Flags |= Flags.IsNoisy; 490 else 491 m_Flags &= ~Flags.IsNoisy; 492 } 493 } 494 495 /// <summary> 496 /// Override value for <see cref="InputDevice.canRunInBackground"/>. If this is set by the 497 /// layout, it will prevent <see cref="QueryCanRunInBackground"/> from being issued. However, other 498 /// logic that affects <see cref="InputDevice.canRunInBackground"/> may still force a specific value 499 /// on a device regardless of what's set in the layout. 500 /// </summary> 501 /// <seealso cref="InputDevice.canRunInBackground"/> 502 /// <seealso cref="InputSettings.backgroundBehavior"/> 503 public bool? canRunInBackground 504 { 505 get => (m_Flags & Flags.CanRunInBackgroundIsSet) != 0 ? (bool?)((m_Flags & Flags.CanRunInBackground) != 0) : null; 506 internal set 507 { 508 if (!value.HasValue) 509 { 510 m_Flags &= ~Flags.CanRunInBackgroundIsSet; 511 } 512 else 513 { 514 m_Flags |= Flags.CanRunInBackgroundIsSet; 515 if (value.Value) 516 m_Flags |= Flags.CanRunInBackground; 517 else 518 m_Flags &= ~Flags.CanRunInBackground; 519 } 520 } 521 } 522 523 public ControlItem this[string path] 524 { 525 get 526 { 527 if (string.IsNullOrEmpty(path)) 528 throw new ArgumentNullException(nameof(path)); 529 530 // Does not use FindControl so that we don't force-intern the given path string. 531 if (m_Controls != null) 532 { 533 for (var i = 0; i < m_Controls.Length; ++i) 534 { 535 if (m_Controls[i].name == path) 536 return m_Controls[i]; 537 } 538 } 539 540 throw new KeyNotFoundException($"Cannot find control '{path}' in layout '{name}'"); 541 } 542 } 543 544 public ControlItem? FindControl(InternedString path) 545 { 546 if (string.IsNullOrEmpty(path)) 547 throw new ArgumentNullException(nameof(path)); 548 549 if (m_Controls == null) 550 return null; 551 552 for (var i = 0; i < m_Controls.Length; ++i) 553 { 554 if (m_Controls[i].name == path) 555 return m_Controls[i]; 556 } 557 558 return null; 559 } 560 561 public ControlItem? FindControlIncludingArrayElements(string path, out int arrayIndex) 562 { 563 if (string.IsNullOrEmpty(path)) 564 throw new ArgumentNullException(nameof(path)); 565 566 arrayIndex = -1; 567 if (m_Controls == null) 568 return null; 569 570 var arrayIndexAccumulated = 0; 571 var lastDigitIndex = path.Length; 572 while (lastDigitIndex > 0 && char.IsDigit(path[lastDigitIndex - 1])) 573 { 574 --lastDigitIndex; 575 arrayIndexAccumulated *= 10; 576 arrayIndexAccumulated += path[lastDigitIndex] - '0'; 577 } 578 579 var arrayNameLength = 0; 580 if (lastDigitIndex < path.Length && lastDigitIndex > 0) // Protect against name being all digits. 581 arrayNameLength = lastDigitIndex; 582 583 for (var i = 0; i < m_Controls.Length; ++i) 584 { 585 ref var control = ref m_Controls[i]; 586 if (string.Compare(control.name, path, StringComparison.InvariantCultureIgnoreCase) == 0) 587 return control; 588 589 ////FIXME: what this can't handle is "outerArray4/innerArray5"; not sure we care, though 590 // NOTE: This will *not* match something like "touch4/tap". Which is what we want. 591 // In case there is a ControlItem 592 if (control.isArray && arrayNameLength > 0 && arrayNameLength == control.name.length && 593 string.Compare(control.name.ToString(), 0, path, 0, arrayNameLength, 594 StringComparison.InvariantCultureIgnoreCase) == 0) 595 { 596 arrayIndex = arrayIndexAccumulated; 597 return control; 598 } 599 } 600 601 return null; 602 } 603 604 /// <summary> 605 /// Return the type of values produced by controls created from the layout. 606 /// </summary> 607 /// <returns>The value type of the control or null if it cannot be determined.</returns> 608 /// <remarks> 609 /// This method only returns the statically inferred value type. This type corresponds 610 /// to the type argument to <see cref="InputControl{TValue}"/> in the inheritance hierarchy 611 /// of <see cref="type"/>. As the type used by the layout may not inherit from 612 /// <see cref="InputControl{TValue}"/>, this may mean that the value type cannot be inferred 613 /// and the method will return null. 614 /// </remarks> 615 /// <seealso cref="InputControl.valueType"/> 616 public Type GetValueType() 617 { 618 return TypeHelpers.GetGenericTypeArgumentFromHierarchy(type, typeof(InputControl<>), 0); 619 } 620 621 /// <summary> 622 /// Build a layout programmatically. Primarily for use by layout builders 623 /// registered with the system. 624 /// </summary> 625 /// <seealso cref="InputSystem.RegisterLayoutBuilder"/> 626 public class Builder 627 { 628 /// <summary> 629 /// Name to assign to the layout. 630 /// </summary> 631 /// <value>Name to assign to the layout.</value> 632 /// <seealso cref="InputControlLayout.name"/> 633 public string name { get; set; } 634 635 /// <summary> 636 /// Display name to assign to the layout. 637 /// </summary> 638 /// <value>Display name to assign to the layout</value> 639 /// <seealso cref="InputControlLayout.displayName"/> 640 public string displayName { get; set; } 641 642 /// <summary> 643 /// <see cref="InputControl"/> type to instantiate for the layout. 644 /// </summary> 645 /// <value>Control type to instantiate for the layout.</value> 646 /// <seealso cref="InputControlLayout.type"/> 647 public Type type { get; set; } 648 649 /// <summary> 650 /// Memory format FourCC code to apply to state memory used by the 651 /// layout. 652 /// </summary> 653 /// <value>FourCC memory format tag.</value> 654 /// <seealso cref="InputControlLayout.stateFormat"/> 655 /// <seealso cref="InputStateBlock.format"/> 656 public FourCC stateFormat { get; set; } 657 658 /// <summary> 659 /// Total size of memory used by the layout. 660 /// </summary> 661 /// <value>Size of memory used by the layout.</value> 662 /// <seealso cref="InputControlLayout.stateSizeInBytes"/> 663 public int stateSizeInBytes { get; set; } 664 665 /// <summary> 666 /// Which layout to base this layout on. 667 /// </summary> 668 /// <value>Name of base layout.</value> 669 /// <seealso cref="InputControlLayout.baseLayouts"/> 670 public string extendsLayout 671 { 672 get => m_ExtendsLayout; 673 set 674 { 675 if (!string.IsNullOrEmpty(value)) 676 m_ExtendsLayout = value; 677 else 678 m_ExtendsLayout = null; 679 } 680 } 681 682 private string m_ExtendsLayout; 683 684 /// <summary> 685 /// For device layouts, whether the device wants an extra update 686 /// before rendering. 687 /// </summary> 688 /// <value>True if before-render updates should be enabled for the device.</value> 689 /// <seealso cref="InputDevice.updateBeforeRender"/> 690 /// <seealso cref="InputControlLayout.updateBeforeRender"/> 691 public bool? updateBeforeRender { get; set; } 692 693 /// <summary> 694 /// List of control items set up by the layout. 695 /// </summary> 696 /// <value>Controls set up by the layout.</value> 697 /// <seealso cref="AddControl"/> 698 public ReadOnlyArray<ControlItem> controls => new ReadOnlyArray<ControlItem>(m_Controls, 0, m_ControlCount); 699 700 private int m_ControlCount; 701 private ControlItem[] m_Controls; 702 703 /// <summary> 704 /// Syntax for configuring an individual <see cref="ControlItem"/>. 705 /// </summary> 706 public struct ControlBuilder 707 { 708 internal Builder builder; 709 internal int index; 710 711 public ControlBuilder WithDisplayName(string displayName) 712 { 713 builder.m_Controls[index].displayName = displayName; 714 return this; 715 } 716 717 public ControlBuilder WithLayout(string layout) 718 { 719 if (string.IsNullOrEmpty(layout)) 720 throw new ArgumentException("Layout name cannot be null or empty", nameof(layout)); 721 722 builder.m_Controls[index].layout = new InternedString(layout); 723 return this; 724 } 725 726 public ControlBuilder WithFormat(FourCC format) 727 { 728 builder.m_Controls[index].format = format; 729 return this; 730 } 731 732 public ControlBuilder WithFormat(string format) 733 { 734 return WithFormat(new FourCC(format)); 735 } 736 737 public ControlBuilder WithByteOffset(uint offset) 738 { 739 builder.m_Controls[index].offset = offset; 740 return this; 741 } 742 743 public ControlBuilder WithBitOffset(uint bit) 744 { 745 builder.m_Controls[index].bit = bit; 746 return this; 747 } 748 749 public ControlBuilder IsSynthetic(bool value) 750 { 751 builder.m_Controls[index].isSynthetic = value; 752 return this; 753 } 754 755 public ControlBuilder IsNoisy(bool value) 756 { 757 builder.m_Controls[index].isNoisy = value; 758 return this; 759 } 760 761 public ControlBuilder DontReset(bool value) 762 { 763 builder.m_Controls[index].dontReset = value; 764 return this; 765 } 766 767 public ControlBuilder WithSizeInBits(uint sizeInBits) 768 { 769 builder.m_Controls[index].sizeInBits = sizeInBits; 770 return this; 771 } 772 773 public ControlBuilder WithRange(float minValue, float maxValue) 774 { 775 builder.m_Controls[index].minValue = minValue; 776 builder.m_Controls[index].maxValue = maxValue; 777 return this; 778 } 779 780 public ControlBuilder WithUsages(params InternedString[] usages) 781 { 782 if (usages == null || usages.Length == 0) 783 return this; 784 785 for (var i = 0; i < usages.Length; ++i) 786 if (usages[i].IsEmpty()) 787 throw new ArgumentException( 788 $"Empty usage entry at index {i} for control '{builder.m_Controls[index].name}' in layout '{builder.name}'", 789 nameof(usages)); 790 791 builder.m_Controls[index].usages = new ReadOnlyArray<InternedString>(usages); 792 return this; 793 } 794 795 public ControlBuilder WithUsages(IEnumerable<string> usages) 796 { 797 var usagesArray = usages.Select(x => new InternedString(x)).ToArray(); 798 return WithUsages(usagesArray); 799 } 800 801 public ControlBuilder WithUsages(params string[] usages) 802 { 803 return WithUsages((IEnumerable<string>)usages); 804 } 805 806 public ControlBuilder WithParameters(string parameters) 807 { 808 if (string.IsNullOrEmpty(parameters)) 809 return this; 810 var parsed = NamedValue.ParseMultiple(parameters); 811 builder.m_Controls[index].parameters = new ReadOnlyArray<NamedValue>(parsed); 812 return this; 813 } 814 815 public ControlBuilder WithProcessors(string processors) 816 { 817 if (string.IsNullOrEmpty(processors)) 818 return this; 819 var parsed = NameAndParameters.ParseMultiple(processors).ToArray(); 820 builder.m_Controls[index].processors = new ReadOnlyArray<NameAndParameters>(parsed); 821 return this; 822 } 823 824 public ControlBuilder WithDefaultState(PrimitiveValue value) 825 { 826 builder.m_Controls[index].defaultState = value; 827 return this; 828 } 829 830 public ControlBuilder UsingStateFrom(string path) 831 { 832 if (string.IsNullOrEmpty(path)) 833 return this; 834 builder.m_Controls[index].useStateFrom = path; 835 return this; 836 } 837 838 public ControlBuilder AsArrayOfControlsWithSize(int arraySize) 839 { 840 builder.m_Controls[index].arraySize = arraySize; 841 return this; 842 } 843 } 844 845 // This invalidates the ControlBuilders from previous calls! (our array may move) 846 /// <summary> 847 /// Add a new control to the layout. 848 /// </summary> 849 /// <param name="name">Name or path of the control. If it is a path (e.g. <c>"leftStick/x"</c>, 850 /// then the control either modifies the setup of a child control of another control in the layout 851 /// or adds a new child control to another control in the layout. Modifying child control is useful, 852 /// for example, to alter the state format of controls coming from the base layout. Likewise, 853 /// adding child controls to another control is useful to modify the setup of of the control layout 854 /// being used without having to create and register a custom control layout.</param> 855 /// <returns>A control builder that permits setting various parameters on the control.</returns> 856 /// <exception cref="ArgumentException"><paramref name="name"/> is null or empty.</exception> 857 public ControlBuilder AddControl(string name) 858 { 859 if (string.IsNullOrEmpty(name)) 860 throw new ArgumentException(name); 861 862 var index = ArrayHelpers.AppendWithCapacity(ref m_Controls, ref m_ControlCount, 863 new ControlItem 864 { 865 name = new InternedString(name), 866 isModifyingExistingControl = name.IndexOf('/') != -1, 867 offset = InputStateBlock.InvalidOffset, 868 bit = InputStateBlock.InvalidOffset 869 }); 870 871 return new ControlBuilder 872 { 873 builder = this, 874 index = index 875 }; 876 } 877 878 public Builder WithName(string name) 879 { 880 this.name = name; 881 return this; 882 } 883 884 public Builder WithDisplayName(string displayName) 885 { 886 this.displayName = displayName; 887 return this; 888 } 889 890 public Builder WithType<T>() 891 where T : InputControl 892 { 893 type = typeof(T); 894 return this; 895 } 896 897 public Builder WithFormat(FourCC format) 898 { 899 stateFormat = format; 900 return this; 901 } 902 903 public Builder WithFormat(string format) 904 { 905 return WithFormat(new FourCC(format)); 906 } 907 908 public Builder WithSizeInBytes(int sizeInBytes) 909 { 910 stateSizeInBytes = sizeInBytes; 911 return this; 912 } 913 914 public Builder Extend(string baseLayoutName) 915 { 916 extendsLayout = baseLayoutName; 917 return this; 918 } 919 920 public InputControlLayout Build() 921 { 922 ControlItem[] controls = null; 923 if (m_ControlCount > 0) 924 { 925 controls = new ControlItem[m_ControlCount]; 926 Array.Copy(m_Controls, controls, m_ControlCount); 927 } 928 929 // Allow layout to be unnamed. The system will automatically set the 930 // name that the layout has been registered under. 931 var layout = 932 new InputControlLayout(new InternedString(name), 933 type == null && string.IsNullOrEmpty(extendsLayout) ? typeof(InputDevice) : type) 934 { 935 m_DisplayName = displayName, 936 m_StateFormat = stateFormat, 937 m_StateSizeInBytes = stateSizeInBytes, 938 m_BaseLayouts = !string.IsNullOrEmpty(extendsLayout) ? new InlinedArray<InternedString>(new InternedString(extendsLayout)) : default, 939 m_Controls = controls, 940 m_UpdateBeforeRender = updateBeforeRender 941 }; 942 943 return layout; 944 } 945 } 946 947 // Uses reflection to construct a layout from the given type. 948 // Can be used with both control classes and state structs. 949 public static InputControlLayout FromType(string name, Type type) 950 { 951 var controlLayouts = new List<ControlItem>(); 952 var layoutAttribute = type.GetCustomAttribute<InputControlLayoutAttribute>(true); 953 954 // If there's an InputControlLayoutAttribute on the type that has 'stateType' set, 955 // add control layouts from its state (if present) instead of from the type. 956 var stateFormat = new FourCC(); 957 if (layoutAttribute != null && layoutAttribute.stateType != null) 958 { 959 AddControlItems(layoutAttribute.stateType, controlLayouts, name); 960 961 // Get state type code from state struct. 962 if (typeof(IInputStateTypeInfo).IsAssignableFrom(layoutAttribute.stateType)) 963 { 964 stateFormat = ((IInputStateTypeInfo)Activator.CreateInstance(layoutAttribute.stateType)).format; 965 } 966 } 967 else 968 { 969 // Add control layouts from type contents. 970 AddControlItems(type, controlLayouts, name); 971 } 972 973 if (layoutAttribute != null && !string.IsNullOrEmpty(layoutAttribute.stateFormat)) 974 stateFormat = new FourCC(layoutAttribute.stateFormat); 975 976 // Determine variants (if any). 977 var variants = new InternedString(); 978 if (layoutAttribute != null) 979 variants = new InternedString(layoutAttribute.variants); 980 981 ////TODO: make sure all usages are unique (probably want to have a check method that we can run on json layouts as well) 982 ////TODO: make sure all paths are unique (only relevant for JSON layouts?) 983 984 // Create layout object. 985 var layout = new InputControlLayout(name, type) 986 { 987 m_Controls = controlLayouts.ToArray(), 988 m_StateFormat = stateFormat, 989 m_Variants = variants, 990 m_UpdateBeforeRender = layoutAttribute?.updateBeforeRenderInternal, 991 isGenericTypeOfDevice = layoutAttribute?.isGenericTypeOfDevice ?? false, 992 hideInUI = layoutAttribute?.hideInUI ?? false, 993 m_Description = layoutAttribute?.description, 994 m_DisplayName = layoutAttribute?.displayName, 995 canRunInBackground = layoutAttribute?.canRunInBackgroundInternal, 996 isNoisy = layoutAttribute?.isNoisy ?? false 997 }; 998 999 if (layoutAttribute?.commonUsages != null) 1000 layout.m_CommonUsages = 1001 ArrayHelpers.Select(layoutAttribute.commonUsages, x => new InternedString(x)); 1002 1003 return layout; 1004 } 1005 1006 public string ToJson() 1007 { 1008 var layout = LayoutJson.FromLayout(this); 1009 return JsonUtility.ToJson(layout, true); 1010 } 1011 1012 // Constructs a layout from the given JSON source. 1013 public static InputControlLayout FromJson(string json) 1014 { 1015 var layoutJson = JsonUtility.FromJson<LayoutJson>(json); 1016 return layoutJson.ToLayout(); 1017 } 1018 1019 ////REVIEW: shouldn't state be split between input and output? how does output fit into the layout picture in general? 1020 //// should the control layout alone determine the direction things are going in? 1021 1022 private InternedString m_Name; 1023 private Type m_Type; // For extension chains, we can only discover types after loading multiple layouts, so we make this accessible to InputDeviceBuilder. 1024 private InternedString m_Variants; 1025 private FourCC m_StateFormat; 1026 internal int m_StateSizeInBytes; // Note that this is the combined state size for input and output. 1027 internal bool? m_UpdateBeforeRender; 1028 internal InlinedArray<InternedString> m_BaseLayouts; 1029 private InlinedArray<InternedString> m_AppliedOverrides; 1030 private InternedString[] m_CommonUsages; 1031 internal ControlItem[] m_Controls; 1032 internal string m_DisplayName; 1033 private string m_Description; 1034 private Flags m_Flags; 1035 1036 [Flags] 1037 private enum Flags 1038 { 1039 IsGenericTypeOfDevice = 1 << 0, 1040 HideInUI = 1 << 1, 1041 IsOverride = 1 << 2, 1042 CanRunInBackground = 1 << 3, 1043 CanRunInBackgroundIsSet = 1 << 4, 1044 IsNoisy = 1 << 5 1045 } 1046 1047 private InputControlLayout(string name, Type type) 1048 { 1049 m_Name = new InternedString(name); 1050 m_Type = type; 1051 } 1052 1053 private static void AddControlItems(Type type, List<ControlItem> controlLayouts, string layoutName) 1054 { 1055 AddControlItemsFromFields(type, controlLayouts, layoutName); 1056 AddControlItemsFromProperties(type, controlLayouts, layoutName); 1057 } 1058 1059 // Add ControlLayouts for every public property in the given type that has 1060 // InputControlAttribute applied to it or has an InputControl-derived value type. 1061 private static void AddControlItemsFromFields(Type type, List<ControlItem> controlLayouts, string layoutName) 1062 { 1063 var fields = type.GetFields(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly); 1064 AddControlItemsFromMembers(fields, controlLayouts, layoutName); 1065 } 1066 1067 // Add ControlLayouts for every public property in the given type that has 1068 // InputControlAttribute applied to it or has an InputControl-derived value type. 1069 private static void AddControlItemsFromProperties(Type type, List<ControlItem> controlLayouts, string layoutName) 1070 { 1071 var properties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly); 1072 AddControlItemsFromMembers(properties, controlLayouts, layoutName); 1073 } 1074 1075 // Add ControlLayouts for every member in the list that has InputControlAttribute applied to it 1076 // or has an InputControl-derived value type. 1077 private static void AddControlItemsFromMembers(MemberInfo[] members, List<ControlItem> controlItems, string layoutName) 1078 { 1079 foreach (var member in members) 1080 { 1081 // Skip anything declared inside InputControl itself. 1082 // Filters out m_Device etc. 1083 if (member.DeclaringType == typeof(InputControl)) 1084 continue; 1085 1086 var valueType = TypeHelpers.GetValueType(member); 1087 1088 // If the value type of the member is a struct type and implements the IInputStateTypeInfo 1089 // interface, dive inside and look. This is useful for composing states of one another. 1090 if (valueType != null && valueType.IsValueType && typeof(IInputStateTypeInfo).IsAssignableFrom(valueType)) 1091 { 1092 var controlCountBefore = controlItems.Count; 1093 1094 AddControlItems(valueType, controlItems, layoutName); 1095 1096 // If the current member is a field that is embedding the state structure, add 1097 // the field offset to all control layouts that were added from the struct. 1098 var memberAsField = member as FieldInfo; 1099 if (memberAsField != null) 1100 { 1101 var fieldOffset = Marshal.OffsetOf(member.DeclaringType, member.Name).ToInt32(); 1102 var controlCountAfter = controlItems.Count; 1103 for (var i = controlCountBefore; i < controlCountAfter; ++i) 1104 { 1105 var controlLayout = controlItems[i]; 1106 if (controlItems[i].offset != InputStateBlock.InvalidOffset) 1107 { 1108 controlLayout.offset += (uint)fieldOffset; 1109 controlItems[i] = controlLayout; 1110 } 1111 } 1112 } 1113 1114 ////TODO: allow attributes on the member to modify control layouts inside the struct 1115 } 1116 1117 // Look for InputControlAttributes. If they aren't there, the member has to be 1118 // of an InputControl-derived value type. 1119 var attributes = member.GetCustomAttributes<InputControlAttribute>(false).ToArray(); 1120 if (attributes.Length == 0) 1121 { 1122 if (valueType == null || !typeof(InputControl).IsAssignableFrom(valueType)) 1123 continue; 1124 1125 // On properties, we require explicit [InputControl] attributes to 1126 // pick them up. Doing it otherwise has proven to lead too easily to 1127 // situations where you inadvertently add new controls to a layout 1128 // just because you added an InputControl-type property to a class. 1129 if (member is PropertyInfo) 1130 continue; 1131 } 1132 1133 AddControlItemsFromMember(member, attributes, controlItems); 1134 } 1135 } 1136 1137 private static void AddControlItemsFromMember(MemberInfo member, 1138 InputControlAttribute[] attributes, List<ControlItem> controlItems) 1139 { 1140 // InputControlAttribute can be applied multiple times to the same member, 1141 // generating a separate control for each occurrence. However, it can also 1142 // generating a separate control for each occurrence. However, it can also 1143 // not be applied at all in which case we still add a control layout (the 1144 // logic that called us already made sure the member is eligible for this kind 1145 // of operation). 1146 1147 if (attributes.Length == 0) 1148 { 1149 var controlItem = CreateControlItemFromMember(member, null); 1150 controlItems.Add(controlItem); 1151 } 1152 else 1153 { 1154 foreach (var attribute in attributes) 1155 { 1156 var controlItem = CreateControlItemFromMember(member, attribute); 1157 controlItems.Add(controlItem); 1158 } 1159 } 1160 } 1161 1162 private static ControlItem CreateControlItemFromMember(MemberInfo member, InputControlAttribute attribute) 1163 { 1164 ////REVIEW: make sure that the value type of the field and the value type of the control match? 1165 1166 // Determine name. 1167 var name = attribute?.name; 1168 if (string.IsNullOrEmpty(name)) 1169 name = member.Name; 1170 1171 var isModifyingChildControlByPath = name.IndexOf('/') != -1; 1172 1173 // Determine display name. 1174 var displayName = attribute?.displayName; 1175 var shortDisplayName = attribute?.shortDisplayName; 1176 1177 // Determine layout. 1178 var layout = attribute?.layout; 1179 if (string.IsNullOrEmpty(layout) && !isModifyingChildControlByPath && 1180 (!(member is FieldInfo) || member.GetCustomAttribute<FixedBufferAttribute>(false) == null)) // Ignore fixed buffer fields. 1181 { 1182 var valueType = TypeHelpers.GetValueType(member); 1183 layout = InferLayoutFromValueType(valueType); 1184 } 1185 1186 // Determine variants. 1187 string variants = null; 1188 if (attribute != null && !string.IsNullOrEmpty(attribute.variants)) 1189 variants = attribute.variants; 1190 1191 // Determine offset. 1192 var offset = InputStateBlock.InvalidOffset; 1193 if (attribute != null && attribute.offset != InputStateBlock.InvalidOffset) 1194 offset = attribute.offset; 1195 else if (member is FieldInfo && !isModifyingChildControlByPath) 1196 offset = (uint)Marshal.OffsetOf(member.DeclaringType, member.Name).ToInt32(); 1197 1198 // Determine bit offset. 1199 var bit = InputStateBlock.InvalidOffset; 1200 if (attribute != null) 1201 bit = attribute.bit; 1202 1203 ////TODO: if size is not set, determine from type of field 1204 // Determine size. 1205 var sizeInBits = 0u; 1206 if (attribute != null) 1207 sizeInBits = attribute.sizeInBits; 1208 1209 // Determine format. 1210 var format = new FourCC(); 1211 if (attribute != null && !string.IsNullOrEmpty(attribute.format)) 1212 format = new FourCC(attribute.format); 1213 else if (!isModifyingChildControlByPath && bit == InputStateBlock.InvalidOffset) 1214 { 1215 ////REVIEW: this logic makes it hard to inherit settings from the base layout; if we do this stuff, 1216 //// we should probably do it in InputDeviceBuilder and not directly on the layout 1217 var valueType = TypeHelpers.GetValueType(member); 1218 format = InputStateBlock.GetPrimitiveFormatFromType(valueType); 1219 } 1220 1221 // Determine aliases. 1222 InternedString[] aliases = null; 1223 if (attribute != null) 1224 { 1225 var joined = ArrayHelpers.Join(attribute.alias, attribute.aliases); 1226 if (joined != null) 1227 aliases = joined.Select(x => new InternedString(x)).ToArray(); 1228 } 1229 1230 // Determine usages. 1231 InternedString[] usages = null; 1232 if (attribute != null) 1233 { 1234 var joined = ArrayHelpers.Join(attribute.usage, attribute.usages); 1235 if (joined != null) 1236 usages = joined.Select(x => new InternedString(x)).ToArray(); 1237 } 1238 1239 // Determine parameters. 1240 NamedValue[] parameters = null; 1241 if (attribute != null && !string.IsNullOrEmpty(attribute.parameters)) 1242 parameters = NamedValue.ParseMultiple(attribute.parameters); 1243 1244 // Determine processors. 1245 NameAndParameters[] processors = null; 1246 if (attribute != null && !string.IsNullOrEmpty(attribute.processors)) 1247 processors = NameAndParameters.ParseMultiple(attribute.processors).ToArray(); 1248 1249 // Determine whether to use state from another control. 1250 string useStateFrom = null; 1251 if (attribute != null && !string.IsNullOrEmpty(attribute.useStateFrom)) 1252 useStateFrom = attribute.useStateFrom; 1253 1254 // Determine if it's a noisy control. 1255 var isNoisy = false; 1256 if (attribute != null) 1257 isNoisy = attribute.noisy; 1258 1259 // Determine whether it's a dontReset control. 1260 var dontReset = false; 1261 if (attribute != null) 1262 dontReset = attribute.dontReset; 1263 1264 // Determine if it's a synthetic control. 1265 var isSynthetic = false; 1266 if (attribute != null) 1267 isSynthetic = attribute.synthetic; 1268 1269 // Determine array size. 1270 var arraySize = 0; 1271 if (attribute != null) 1272 arraySize = attribute.arraySize; 1273 1274 // Determine default state. 1275 var defaultState = new PrimitiveValue(); 1276 if (attribute != null) 1277 defaultState = PrimitiveValue.FromObject(attribute.defaultState); 1278 1279 // Determine min and max value. 1280 var minValue = new PrimitiveValue(); 1281 var maxValue = new PrimitiveValue(); 1282 if (attribute != null) 1283 { 1284 minValue = PrimitiveValue.FromObject(attribute.minValue); 1285 maxValue = PrimitiveValue.FromObject(attribute.maxValue); 1286 } 1287 1288 return new ControlItem 1289 { 1290 name = new InternedString(name), 1291 displayName = displayName, 1292 shortDisplayName = shortDisplayName, 1293 layout = new InternedString(layout), 1294 variants = new InternedString(variants), 1295 useStateFrom = useStateFrom, 1296 format = format, 1297 offset = offset, 1298 bit = bit, 1299 sizeInBits = sizeInBits, 1300 parameters = new ReadOnlyArray<NamedValue>(parameters), 1301 processors = new ReadOnlyArray<NameAndParameters>(processors), 1302 usages = new ReadOnlyArray<InternedString>(usages), 1303 aliases = new ReadOnlyArray<InternedString>(aliases), 1304 isModifyingExistingControl = isModifyingChildControlByPath, 1305 isFirstDefinedInThisLayout = true, 1306 isNoisy = isNoisy, 1307 dontReset = dontReset, 1308 isSynthetic = isSynthetic, 1309 arraySize = arraySize, 1310 defaultState = defaultState, 1311 minValue = minValue, 1312 maxValue = maxValue, 1313 }; 1314 } 1315 1316 ////REVIEW: this tends to cause surprises; is it worth its cost? 1317 private static string InferLayoutFromValueType(Type type) 1318 { 1319 var layout = s_Layouts.TryFindLayoutForType(type); 1320 if (layout.IsEmpty()) 1321 { 1322 var typeName = new InternedString(type.Name); 1323 if (s_Layouts.HasLayout(typeName)) 1324 layout = typeName; 1325 else if (type.Name.EndsWith("Control")) 1326 { 1327 typeName = new InternedString(type.Name.Substring(0, type.Name.Length - "Control".Length)); 1328 if (s_Layouts.HasLayout(typeName)) 1329 layout = typeName; 1330 } 1331 } 1332 return layout; 1333 } 1334 1335 /// <summary> 1336 /// Merge the settings from <paramref name="other"/> into the layout such that they become 1337 /// the base settings. 1338 /// </summary> 1339 /// <param name="other"></param> 1340 /// <remarks> 1341 /// This is the central method for allowing layouts to 'inherit' settings from their 1342 /// base layout. It will merge the information in <paramref name="other"/> into the current 1343 /// layout such that the existing settings in the current layout acts as if applied on top 1344 /// of the settings in the base layout. 1345 /// </remarks> 1346 public void MergeLayout(InputControlLayout other) 1347 { 1348 if (other == null) 1349 throw new ArgumentNullException(nameof(other)); 1350 1351 m_UpdateBeforeRender = m_UpdateBeforeRender ?? other.m_UpdateBeforeRender; 1352 1353 if (m_Variants.IsEmpty()) 1354 m_Variants = other.m_Variants; 1355 1356 // Determine type. Basically, if the other layout's type is more specific 1357 // than our own, we switch to that one. Otherwise we stay on our own type. 1358 if (m_Type == null) 1359 m_Type = other.m_Type; 1360 else if (m_Type.IsAssignableFrom(other.m_Type)) 1361 m_Type = other.m_Type; 1362 1363 // If the layout has variants set on it, we want to merge away information coming 1364 // from 'other' than isn't relevant to those variants. 1365 var layoutIsTargetingSpecificVariants = !m_Variants.IsEmpty(); 1366 1367 if (m_StateFormat == new FourCC()) 1368 m_StateFormat = other.m_StateFormat; 1369 1370 // Combine common usages. 1371 m_CommonUsages = ArrayHelpers.Merge(other.m_CommonUsages, m_CommonUsages); 1372 1373 // Retain list of overrides. 1374 m_AppliedOverrides.Merge(other.m_AppliedOverrides); 1375 1376 // Inherit display name. 1377 if (string.IsNullOrEmpty(m_DisplayName)) 1378 m_DisplayName = other.m_DisplayName; 1379 1380 // Merge controls. 1381 if (m_Controls == null) 1382 { 1383 m_Controls = other.m_Controls; 1384 } 1385 else if (other.m_Controls != null) 1386 { 1387 var baseControls = other.m_Controls; 1388 1389 // Even if the counts match we don't know how many controls are in the 1390 // set until we actually gone through both control lists and looked at 1391 // the names. 1392 1393 var controls = new List<ControlItem>(); 1394 var baseControlVariants = new List<string>(); 1395 1396 ////REVIEW: should setting variants directly on a layout force that variant to automatically 1397 //// be set on every control item directly defined in that layout? 1398 1399 var baseControlTable = CreateLookupTableForControls(baseControls, baseControlVariants); 1400 var thisControlTable = CreateLookupTableForControls(m_Controls); 1401 1402 // First go through every control we have in this layout. Add every control from 1403 // `thisControlTable` while removing corresponding control items from `baseControlTable`. 1404 foreach (var pair in thisControlTable) 1405 { 1406 if (baseControlTable.TryGetValue(pair.Key, out var baseControlItem)) 1407 { 1408 var mergedLayout = pair.Value.Merge(baseControlItem); 1409 controls.Add(mergedLayout); 1410 1411 // Remove the entry so we don't hit it again in the pass through 1412 // baseControlTable below. 1413 baseControlTable.Remove(pair.Key); 1414 } 1415 ////REVIEW: is this really the most useful behavior? 1416 // We may be looking at a control that is using variants on the base layout but 1417 // isn't targeting specific variants on the derived layout. In that case, we 1418 // want to take each of the variants from the base layout and merge them with 1419 // the control layout in the derived layout. 1420 else if (pair.Value.variants.IsEmpty() || pair.Value.variants == DefaultVariant) 1421 { 1422 var isTargetingVariants = false; 1423 if (layoutIsTargetingSpecificVariants) 1424 { 1425 // We're only looking for specific variants so try only that those. 1426 for (var i = 0; i < baseControlVariants.Count; ++i) 1427 { 1428 if (VariantsMatch(m_Variants.ToLower(), baseControlVariants[i])) 1429 { 1430 var key = $"{pair.Key}@{baseControlVariants[i]}"; 1431 if (baseControlTable.TryGetValue(key, out baseControlItem)) 1432 { 1433 var mergedLayout = pair.Value.Merge(baseControlItem); 1434 controls.Add(mergedLayout); 1435 baseControlTable.Remove(key); 1436 isTargetingVariants = true; 1437 } 1438 } 1439 } 1440 } 1441 else 1442 { 1443 // Try each variants present in the base layout. 1444 foreach (var variant in baseControlVariants) 1445 { 1446 var key = $"{pair.Key}@{variant}"; 1447 if (baseControlTable.TryGetValue(key, out baseControlItem)) 1448 { 1449 var mergedLayout = pair.Value.Merge(baseControlItem); 1450 controls.Add(mergedLayout); 1451 baseControlTable.Remove(key); 1452 isTargetingVariants = true; 1453 } 1454 } 1455 } 1456 1457 // Okay, this control item isn't corresponding to anything in the base layout 1458 // so just add it as is. 1459 if (!isTargetingVariants) 1460 controls.Add(pair.Value); 1461 } 1462 // We may be looking at a control that is targeting a specific variant 1463 // in this layout but not targeting a variant in the base layout. We still want to 1464 // merge information from that non-targeted base control. 1465 else if (baseControlTable.TryGetValue(pair.Value.name.ToLower(), out baseControlItem)) 1466 { 1467 var mergedLayout = pair.Value.Merge(baseControlItem); 1468 controls.Add(mergedLayout); 1469 baseControlTable.Remove(pair.Value.name.ToLower()); 1470 } 1471 // Seems like we can't match it to a control in the base layout. We already know it 1472 // must have a variants setting (because we checked above) so if the variants setting 1473 // doesn't prevent us, just include the control. It's most likely a path-modifying 1474 // control (e.g. "rightStick/x"). 1475 else if (VariantsMatch(m_Variants, pair.Value.variants)) 1476 { 1477 controls.Add(pair.Value); 1478 } 1479 } 1480 1481 // And then go through all the controls in the base and take the 1482 // ones we're missing. We've already removed all the ones that intersect 1483 // and had to be merged so the rest we can just slurp into the list as is. 1484 if (!layoutIsTargetingSpecificVariants) 1485 { 1486 var indexStart = controls.Count; 1487 controls.AddRange(baseControlTable.Values); 1488 1489 // Mark the controls as being inherited. 1490 for (var i = indexStart; i < controls.Count; ++i) 1491 { 1492 var control = controls[i]; 1493 control.isFirstDefinedInThisLayout = false; 1494 controls[i] = control; 1495 } 1496 } 1497 else 1498 { 1499 // Filter out controls coming from the base layout which are targeting variants 1500 // that we're not interested in. 1501 var indexStart = controls.Count; 1502 controls.AddRange( 1503 baseControlTable.Values.Where(x => VariantsMatch(m_Variants, x.variants))); 1504 1505 // Mark the controls as being inherited. 1506 for (var i = indexStart; i < controls.Count; ++i) 1507 { 1508 var control = controls[i]; 1509 control.isFirstDefinedInThisLayout = false; 1510 controls[i] = control; 1511 } 1512 } 1513 1514 m_Controls = controls.ToArray(); 1515 } 1516 } 1517 1518 private static Dictionary<string, ControlItem> CreateLookupTableForControls( 1519 ControlItem[] controlItems, List<string> variants = null) 1520 { 1521 var table = new Dictionary<string, ControlItem>(); 1522 for (var i = 0; i < controlItems.Length; ++i) 1523 { 1524 var key = controlItems[i].name.ToLower(); 1525 // Need to take variants into account as well. Otherwise two variants for 1526 // "leftStick", for example, will overwrite each other. 1527 var itemVariants = controlItems[i].variants; 1528 if (!itemVariants.IsEmpty() && itemVariants != DefaultVariant) 1529 { 1530 // If there's multiple variants on the control, we add it to the table multiple times. 1531 if (itemVariants.ToString().IndexOf(VariantSeparator[0]) != -1) 1532 { 1533 var itemVariantArray = itemVariants.ToLower().Split(VariantSeparator[0]); 1534 foreach (var name in itemVariantArray) 1535 { 1536 variants?.Add(name); 1537 key = $"{key}@{name}"; 1538 table[key] = controlItems[i]; 1539 } 1540 1541 continue; 1542 } 1543 1544 key = $"{key}@{itemVariants.ToLower()}"; 1545 variants?.Add(itemVariants.ToLower()); 1546 } 1547 table[key] = controlItems[i]; 1548 } 1549 return table; 1550 } 1551 1552 internal static bool VariantsMatch(InternedString expected, InternedString actual) 1553 { 1554 return VariantsMatch(expected.ToLower(), actual.ToLower()); 1555 } 1556 1557 internal static bool VariantsMatch(string expected, string actual) 1558 { 1559 ////REVIEW: does this make sense? 1560 // Default variant works with any other expected variant. 1561 if (actual != null && 1562 StringHelpers.CharacterSeparatedListsHaveAtLeastOneCommonElement(DefaultVariant, actual, VariantSeparator[0])) 1563 return true; 1564 1565 // If we don't expect a specific variant, we accept any variant. 1566 if (expected == null) 1567 return true; 1568 1569 // If we there's no variant set on what we actual got, then it matches even if we 1570 // expect specific variants. 1571 if (actual == null) 1572 return true; 1573 1574 // Match if the two variant sets intersect on at least one element. 1575 return StringHelpers.CharacterSeparatedListsHaveAtLeastOneCommonElement(expected, actual, VariantSeparator[0]); 1576 } 1577 1578 internal static void ParseHeaderFieldsFromJson(string json, out InternedString name, 1579 out InlinedArray<InternedString> baseLayouts, out InputDeviceMatcher deviceMatcher) 1580 { 1581 var header = JsonUtility.FromJson<LayoutJsonNameAndDescriptorOnly>(json); 1582 name = new InternedString(header.name); 1583 1584 baseLayouts = new InlinedArray<InternedString>(); 1585 if (!string.IsNullOrEmpty(header.extend)) 1586 baseLayouts.Append(new InternedString(header.extend)); 1587 if (header.extendMultiple != null) 1588 foreach (var item in header.extendMultiple) 1589 baseLayouts.Append(new InternedString(item)); 1590 1591 deviceMatcher = header.device.ToMatcher(); 1592 } 1593 1594 [Serializable] 1595 internal struct LayoutJsonNameAndDescriptorOnly 1596 { 1597 public string name; 1598 public string extend; 1599 public string[] extendMultiple; 1600 public InputDeviceMatcher.MatcherJson device; 1601 } 1602 1603 [Serializable] 1604 private struct LayoutJson 1605 { 1606 // Disable warnings that these fields are never assigned to. They are set 1607 // by JsonUtility. 1608 #pragma warning disable 0649 1609 // ReSharper disable MemberCanBePrivate.Local 1610 1611 public string name; 1612 public string extend; 1613 public string[] extendMultiple; 1614 public string format; 1615 public string beforeRender; // Can't be simple bool as otherwise we can't tell whether it was set or not. 1616 public string runInBackground; 1617 public string[] commonUsages; 1618 public string displayName; 1619 public string description; 1620 public string type; // This is mostly for when we turn arbitrary InputControlLayouts into JSON; less for layouts *coming* from JSON. 1621 public string variant; 1622 public bool isGenericTypeOfDevice; 1623 public bool hideInUI; 1624 public ControlItemJson[] controls; 1625 1626 // ReSharper restore MemberCanBePrivate.Local 1627 #pragma warning restore 0649 1628 1629 public InputControlLayout ToLayout() 1630 { 1631 // By default, the type of the layout is determined from the first layout 1632 // in its 'extend' property chain that has a type set. However, if the layout 1633 // extends nothing, we can't know what type to use for it so we default to 1634 // InputDevice. 1635 Type type = null; 1636 if (!string.IsNullOrEmpty(this.type)) 1637 { 1638 type = Type.GetType(this.type, false); 1639 if (type == null) 1640 { 1641 Debug.Log( 1642 $"Cannot find type '{this.type}' used by layout '{name}'; falling back to using InputDevice"); 1643 type = typeof(InputDevice); 1644 } 1645 else if (!typeof(InputControl).IsAssignableFrom(type)) 1646 { 1647 throw new InvalidOperationException($"'{this.type}' used by layout '{name}' is not an InputControl"); 1648 } 1649 } 1650 else if (string.IsNullOrEmpty(extend)) 1651 type = typeof(InputDevice); 1652 1653 // Create layout. 1654 var layout = new InputControlLayout(name, type) 1655 { 1656 m_DisplayName = displayName, 1657 m_Description = description, 1658 isGenericTypeOfDevice = isGenericTypeOfDevice, 1659 hideInUI = hideInUI, 1660 m_Variants = new InternedString(variant), 1661 m_CommonUsages = ArrayHelpers.Select(commonUsages, x => new InternedString(x)), 1662 }; 1663 if (!string.IsNullOrEmpty(format)) 1664 layout.m_StateFormat = new FourCC(format); 1665 1666 // Base layout. 1667 if (!string.IsNullOrEmpty(extend)) 1668 layout.m_BaseLayouts.Append(new InternedString(extend)); 1669 if (extendMultiple != null) 1670 foreach (var element in extendMultiple) 1671 layout.m_BaseLayouts.Append(new InternedString(element)); 1672 1673 // Before render behavior. 1674 if (!string.IsNullOrEmpty(beforeRender)) 1675 { 1676 var beforeRenderLowerCase = beforeRender.ToLower(); 1677 if (beforeRenderLowerCase == "ignore") 1678 layout.m_UpdateBeforeRender = false; 1679 else if (beforeRenderLowerCase == "update") 1680 layout.m_UpdateBeforeRender = true; 1681 else 1682 throw new InvalidOperationException($"Invalid beforeRender setting '{beforeRender}' (should be 'ignore' or 'update')"); 1683 } 1684 1685 // CanRunInBackground flag. 1686 if (!string.IsNullOrEmpty(runInBackground)) 1687 { 1688 var runInBackgroundLowerCase = runInBackground.ToLower(); 1689 if (runInBackgroundLowerCase == "enabled") 1690 layout.canRunInBackground = true; 1691 else if (runInBackgroundLowerCase == "disabled") 1692 layout.canRunInBackground = false; 1693 else 1694 throw new InvalidOperationException($"Invalid runInBackground setting '{beforeRender}' (should be 'enabled' or 'disabled')"); 1695 } 1696 1697 // Add controls. 1698 if (controls != null) 1699 { 1700 var controlLayouts = new List<ControlItem>(); 1701 foreach (var control in controls) 1702 { 1703 if (string.IsNullOrEmpty(control.name)) 1704 throw new InvalidOperationException($"Control with no name in layout '{name}"); 1705 var controlLayout = control.ToLayout(); 1706 controlLayouts.Add(controlLayout); 1707 } 1708 layout.m_Controls = controlLayouts.ToArray(); 1709 } 1710 1711 return layout; 1712 } 1713 1714 public static LayoutJson FromLayout(InputControlLayout layout) 1715 { 1716 return new LayoutJson 1717 { 1718 name = layout.m_Name, 1719 type = layout.type?.AssemblyQualifiedName, 1720 variant = layout.m_Variants, 1721 displayName = layout.m_DisplayName, 1722 description = layout.m_Description, 1723 isGenericTypeOfDevice = layout.isGenericTypeOfDevice, 1724 hideInUI = layout.hideInUI, 1725 extend = layout.m_BaseLayouts.length == 1 ? layout.m_BaseLayouts[0].ToString() : null, 1726 extendMultiple = layout.m_BaseLayouts.length > 1 ? layout.m_BaseLayouts.ToArray(x => x.ToString()) : null, 1727 format = layout.stateFormat.ToString(), 1728 commonUsages = ArrayHelpers.Select(layout.m_CommonUsages, x => x.ToString()), 1729 controls = ControlItemJson.FromControlItems(layout.m_Controls), 1730 beforeRender = layout.m_UpdateBeforeRender != null ? (layout.m_UpdateBeforeRender.Value ? "Update" : "Ignore") : null, 1731 }; 1732 } 1733 } 1734 1735 // This is a class instead of a struct so that we can assign 'offset' a custom 1736 // default value. Otherwise we can't tell whether the user has actually set it 1737 // or not (0 is a valid offset). Sucks, though, as we now get lots of allocations 1738 // from the control array. 1739 [Serializable] 1740 private class ControlItemJson 1741 { 1742 // Disable warnings that these fields are never assigned to. They are set 1743 // by JsonUtility. 1744 #pragma warning disable 0649 1745 // ReSharper disable MemberCanBePrivate.Local 1746 1747 public string name; 1748 public string layout; 1749 public string variants; 1750 public string usage; // Convenience to not have to create array for single usage. 1751 public string alias; // Same. 1752 public string useStateFrom; 1753 public uint offset; 1754 public uint bit; 1755 public uint sizeInBits; 1756 public string format; 1757 public int arraySize; 1758 public string[] usages; 1759 public string[] aliases; 1760 public string parameters; 1761 public string processors; 1762 public string displayName; 1763 public string shortDisplayName; 1764 public bool noisy; 1765 public bool dontReset; 1766 public bool synthetic; 1767 1768 // This should be an object type field and allow any JSON primitive value type as well 1769 // as arrays of those. Unfortunately, the Unity JSON serializer, given it uses Unity serialization 1770 // and thus doesn't support polymorphism, can do no such thing. Hopefully we do get support 1771 // for this later but for now, we use a string-based value fallback instead. 1772 public string defaultState; 1773 public string minValue; 1774 public string maxValue; 1775 1776 // ReSharper restore MemberCanBePrivate.Local 1777 #pragma warning restore 0649 1778 1779 public ControlItemJson() 1780 { 1781 offset = InputStateBlock.InvalidOffset; 1782 bit = InputStateBlock.InvalidOffset; 1783 } 1784 1785 public ControlItem ToLayout() 1786 { 1787 var layout = new ControlItem 1788 { 1789 name = new InternedString(name), 1790 layout = new InternedString(this.layout), 1791 variants = new InternedString(variants), 1792 displayName = displayName, 1793 shortDisplayName = shortDisplayName, 1794 offset = offset, 1795 useStateFrom = useStateFrom, 1796 bit = bit, 1797 sizeInBits = sizeInBits, 1798 isModifyingExistingControl = name.IndexOf('/') != -1, 1799 isNoisy = noisy, 1800 dontReset = dontReset, 1801 isSynthetic = synthetic, 1802 isFirstDefinedInThisLayout = true, 1803 arraySize = arraySize, 1804 }; 1805 1806 if (!string.IsNullOrEmpty(format)) 1807 layout.format = new FourCC(format); 1808 1809 if (!string.IsNullOrEmpty(usage) || usages != null) 1810 { 1811 var usagesList = new List<string>(); 1812 if (!string.IsNullOrEmpty(usage)) 1813 usagesList.Add(usage); 1814 if (usages != null) 1815 usagesList.AddRange(usages); 1816 layout.usages = new ReadOnlyArray<InternedString>(usagesList.Select(x => new InternedString(x)).ToArray()); 1817 } 1818 1819 if (!string.IsNullOrEmpty(alias) || aliases != null) 1820 { 1821 var aliasesList = new List<string>(); 1822 if (!string.IsNullOrEmpty(alias)) 1823 aliasesList.Add(alias); 1824 if (aliases != null) 1825 aliasesList.AddRange(aliases); 1826 layout.aliases = new ReadOnlyArray<InternedString>(aliasesList.Select(x => new InternedString(x)).ToArray()); 1827 } 1828 1829 if (!string.IsNullOrEmpty(parameters)) 1830 layout.parameters = new ReadOnlyArray<NamedValue>(NamedValue.ParseMultiple(parameters)); 1831 1832 if (!string.IsNullOrEmpty(processors)) 1833 layout.processors = new ReadOnlyArray<NameAndParameters>(NameAndParameters.ParseMultiple(processors).ToArray()); 1834 1835 if (defaultState != null) 1836 layout.defaultState = PrimitiveValue.FromObject(defaultState); 1837 if (minValue != null) 1838 layout.minValue = PrimitiveValue.FromObject(minValue); 1839 if (maxValue != null) 1840 layout.maxValue = PrimitiveValue.FromObject(maxValue); 1841 1842 return layout; 1843 } 1844 1845 public static ControlItemJson[] FromControlItems(ControlItem[] items) 1846 { 1847 if (items == null) 1848 return null; 1849 1850 var count = items.Length; 1851 var result = new ControlItemJson[count]; 1852 1853 for (var i = 0; i < count; ++i) 1854 { 1855 var item = items[i]; 1856 result[i] = new ControlItemJson 1857 { 1858 name = item.name, 1859 layout = item.layout, 1860 variants = item.variants, 1861 displayName = item.displayName, 1862 shortDisplayName = item.shortDisplayName, 1863 bit = item.bit, 1864 offset = item.offset, 1865 sizeInBits = item.sizeInBits, 1866 format = item.format.ToString(), 1867 parameters = string.Join(",", item.parameters.Select(x => x.ToString()).ToArray()), 1868 processors = string.Join(",", item.processors.Select(x => x.ToString()).ToArray()), 1869 usages = item.usages.Select(x => x.ToString()).ToArray(), 1870 aliases = item.aliases.Select(x => x.ToString()).ToArray(), 1871 noisy = item.isNoisy, 1872 dontReset = item.dontReset, 1873 synthetic = item.isSynthetic, 1874 arraySize = item.arraySize, 1875 defaultState = item.defaultState.ToString(), 1876 minValue = item.minValue.ToString(), 1877 maxValue = item.maxValue.ToString(), 1878 }; 1879 } 1880 1881 return result; 1882 } 1883 } 1884 1885 1886 internal struct Collection 1887 { 1888 public const float kBaseScoreForNonGeneratedLayouts = 1.0f; 1889 1890 public struct LayoutMatcher 1891 { 1892 public InternedString layoutName; 1893 public InputDeviceMatcher deviceMatcher; 1894 } 1895 1896 public struct PrecompiledLayout 1897 { 1898 public Func<InputDevice> factoryMethod; 1899 public string metadata; 1900 } 1901 1902 public Dictionary<InternedString, Type> layoutTypes; 1903 public Dictionary<InternedString, string> layoutStrings; 1904 public Dictionary<InternedString, Func<InputControlLayout>> layoutBuilders; 1905 public Dictionary<InternedString, InternedString> baseLayoutTable; 1906 public Dictionary<InternedString, InternedString[]> layoutOverrides; 1907 public HashSet<InternedString> layoutOverrideNames; 1908 public Dictionary<InternedString, PrecompiledLayout> precompiledLayouts; 1909 ////TODO: find a smarter approach that doesn't require linearly scanning through all matchers 1910 //// (also ideally shouldn't be a List but with Collection being a struct and given how it's 1911 //// stored by InputManager.m_Layouts and in s_Layouts; we can't make it a plain array) 1912 public List<LayoutMatcher> layoutMatchers; 1913 1914 public void Allocate() 1915 { 1916 layoutTypes = new Dictionary<InternedString, Type>(); 1917 layoutStrings = new Dictionary<InternedString, string>(); 1918 layoutBuilders = new Dictionary<InternedString, Func<InputControlLayout>>(); 1919 baseLayoutTable = new Dictionary<InternedString, InternedString>(); 1920 layoutOverrides = new Dictionary<InternedString, InternedString[]>(); 1921 layoutOverrideNames = new HashSet<InternedString>(); 1922 layoutMatchers = new List<LayoutMatcher>(); 1923 precompiledLayouts = new Dictionary<InternedString, PrecompiledLayout>(); 1924 } 1925 1926 public InternedString TryFindLayoutForType(Type layoutType) 1927 { 1928 foreach (var entry in layoutTypes) 1929 if (entry.Value == layoutType) 1930 return entry.Key; 1931 return new InternedString(); 1932 } 1933 1934 public InternedString TryFindMatchingLayout(InputDeviceDescription deviceDescription) 1935 { 1936 var highestScore = 0f; 1937 var highestScoringLayout = new InternedString(); 1938 1939 var layoutMatcherCount = layoutMatchers.Count; 1940 for (var i = 0; i < layoutMatcherCount; ++i) 1941 { 1942 var matcher = layoutMatchers[i].deviceMatcher; 1943 var score = matcher.MatchPercentage(deviceDescription); 1944 1945 // We want auto-generated layouts to take a backseat compared to manually created 1946 // layouts. We do this by boosting the score of every layout that isn't coming from 1947 // a layout builder. 1948 if (score > 0 && !layoutBuilders.ContainsKey(layoutMatchers[i].layoutName)) 1949 score += kBaseScoreForNonGeneratedLayouts; 1950 1951 if (score > highestScore) 1952 { 1953 highestScore = score; 1954 highestScoringLayout = layoutMatchers[i].layoutName; 1955 } 1956 } 1957 1958 return highestScoringLayout; 1959 } 1960 1961 public bool HasLayout(InternedString name) 1962 { 1963 return layoutTypes.ContainsKey(name) || layoutStrings.ContainsKey(name) || 1964 layoutBuilders.ContainsKey(name); 1965 } 1966 1967 private InputControlLayout TryLoadLayoutInternal(InternedString name) 1968 { 1969 // See if we have a string layout for it. These 1970 // always take precedence over ones from type so that we can 1971 // override what's in the code using data. 1972 if (layoutStrings.TryGetValue(name, out var json)) 1973 return FromJson(json); 1974 1975 // No, but maybe we have a type layout for it. 1976 if (layoutTypes.TryGetValue(name, out var type)) 1977 return FromType(name, type); 1978 1979 // Finally, check builders. Always the last ones to get a shot at 1980 // providing layouts. 1981 if (layoutBuilders.TryGetValue(name, out var builder)) 1982 { 1983 var layout = builder(); 1984 if (layout == null) 1985 throw new InvalidOperationException($"Layout builder '{name}' returned null when invoked"); 1986 return layout; 1987 } 1988 1989 return null; 1990 } 1991 1992 public InputControlLayout TryLoadLayout(InternedString name, Dictionary<InternedString, InputControlLayout> table = null) 1993 { 1994 // See if we have it cached. 1995 if (table != null && table.TryGetValue(name, out var layout)) 1996 return layout; 1997 1998 layout = TryLoadLayoutInternal(name); 1999 if (layout != null) 2000 { 2001 layout.m_Name = name; 2002 2003 if (layoutOverrideNames.Contains(name)) 2004 layout.isOverride = true; 2005 2006 // If the layout extends another layout, we need to merge the 2007 // base layout into the final layout. 2008 // NOTE: We go through the baseLayoutTable here instead of looking at 2009 // the baseLayouts property so as to make this work for all types 2010 // of layouts (FromType() does not set the property, for example). 2011 var baseLayoutName = new InternedString(); 2012 if (!layout.isOverride && baseLayoutTable.TryGetValue(name, out baseLayoutName)) 2013 { 2014 Debug.Assert(!baseLayoutName.IsEmpty()); 2015 2016 ////TODO: catch cycles 2017 var baseLayout = TryLoadLayout(baseLayoutName, table); 2018 if (baseLayout == null) 2019 throw new LayoutNotFoundException( 2020 $"Cannot find base layout '{baseLayoutName}' of layout '{name}'"); 2021 layout.MergeLayout(baseLayout); 2022 2023 if (layout.m_BaseLayouts.length == 0) 2024 layout.m_BaseLayouts.Append(baseLayoutName); 2025 } 2026 2027 // If there's overrides for the layout, apply them now. 2028 if (layoutOverrides.TryGetValue(name, out var overrides)) 2029 { 2030 for (var i = 0; i < overrides.Length; ++i) 2031 { 2032 var overrideName = overrides[i]; 2033 // NOTE: We do *NOT* pass `table` into TryLoadLayout here so that 2034 // the override we load will not get cached. The reason is that 2035 // we use MergeLayout which is destructive and thus should not 2036 // end up in the table. 2037 var overrideLayout = TryLoadLayout(overrideName); 2038 overrideLayout.MergeLayout(layout); 2039 2040 // We're switching the layout we initially to the layout with 2041 // the overrides applied. Make sure we get rid of information here 2042 // from the override that we don't want to come through once the 2043 // override is applied. 2044 overrideLayout.m_BaseLayouts.Clear(); 2045 overrideLayout.isOverride = false; 2046 overrideLayout.isGenericTypeOfDevice = layout.isGenericTypeOfDevice; 2047 overrideLayout.m_Name = layout.name; 2048 overrideLayout.m_BaseLayouts = layout.m_BaseLayouts; 2049 2050 layout = overrideLayout; 2051 layout.m_AppliedOverrides.Append(overrideName); 2052 } 2053 } 2054 2055 if (table != null) 2056 table[name] = layout; 2057 } 2058 2059 return layout; 2060 } 2061 2062 public InternedString GetBaseLayoutName(InternedString layoutName) 2063 { 2064 if (baseLayoutTable.TryGetValue(layoutName, out var baseLayoutName)) 2065 return baseLayoutName; 2066 return default; 2067 } 2068 2069 // Return name of layout at root of "extend" chain of given layout. 2070 public InternedString GetRootLayoutName(InternedString layoutName) 2071 { 2072 while (baseLayoutTable.TryGetValue(layoutName, out var baseLayout)) 2073 layoutName = baseLayout; 2074 return layoutName; 2075 } 2076 2077 public bool ComputeDistanceInInheritanceHierarchy(InternedString firstLayout, InternedString secondLayout, out int distance) 2078 { 2079 distance = 0; 2080 2081 // First try, assume secondLayout is based on firstLayout. 2082 var secondDistanceToFirst = 0; 2083 var current = secondLayout; 2084 while (!current.IsEmpty() && current != firstLayout) 2085 { 2086 current = GetBaseLayoutName(current); 2087 ++secondDistanceToFirst; 2088 } 2089 if (current == firstLayout) 2090 { 2091 distance = secondDistanceToFirst; 2092 return true; 2093 } 2094 2095 // Second try, assume firstLayout is based on secondLayout. 2096 var firstDistanceToSecond = 0; 2097 current = firstLayout; 2098 while (!current.IsEmpty() && current != secondLayout) 2099 { 2100 current = GetBaseLayoutName(current); 2101 ++firstDistanceToSecond; 2102 } 2103 if (current == secondLayout) 2104 { 2105 distance = firstDistanceToSecond; 2106 return true; 2107 } 2108 2109 return false; 2110 } 2111 2112 public InternedString FindLayoutThatIntroducesControl(InputControl control, Cache cache) 2113 { 2114 // Find the topmost child control on the device. A device layout can only 2115 // add children that sit directly underneath it (e.g. "leftStick"). Children of children 2116 // are indirectly added by other layouts (e.g. "leftStick/x" which is added by "Stick"). 2117 // To determine which device contributes the control as a whole, we have to be looking 2118 // at the topmost child of the device. 2119 var topmostChild = control; 2120 while (topmostChild.parent != control.device) 2121 topmostChild = topmostChild.parent; 2122 2123 // Find the layout in the device's base layout chain that first mentions the given control. 2124 // If we don't find it, we know it's first defined directly in the layout of the given device, 2125 // i.e. it's not an inherited control. 2126 var deviceLayoutName = control.device.m_Layout; 2127 var baseLayoutName = deviceLayoutName; 2128 while (baseLayoutTable.TryGetValue(baseLayoutName, out baseLayoutName)) 2129 { 2130 var layout = cache.FindOrLoadLayout(baseLayoutName); 2131 2132 var controlItem = layout.FindControl(topmostChild.m_Name); 2133 if (controlItem != null) 2134 deviceLayoutName = baseLayoutName; 2135 } 2136 2137 return deviceLayoutName; 2138 } 2139 2140 // Get the type which will be instantiated for the given layout. 2141 // Returns null if no layout with the given name exists. 2142 public Type GetControlTypeForLayout(InternedString layoutName) 2143 { 2144 // Try layout strings. 2145 while (layoutStrings.ContainsKey(layoutName)) 2146 { 2147 if (baseLayoutTable.TryGetValue(layoutName, out var baseLayout)) 2148 { 2149 // Work our way up the inheritance chain. 2150 layoutName = baseLayout; 2151 } 2152 else 2153 { 2154 // Layout doesn't extend anything and ATM we don't support setting 2155 // types explicitly from JSON layouts. So has to be InputDevice. 2156 return typeof(InputDevice); 2157 } 2158 } 2159 2160 // Try layout types. 2161 layoutTypes.TryGetValue(layoutName, out var result); 2162 return result; 2163 } 2164 2165 // Return true if the given control layout has a value type whose values 2166 // can be assigned to variables of type valueType. 2167 public bool ValueTypeIsAssignableFrom(InternedString layoutName, Type valueType) 2168 { 2169 var controlType = GetControlTypeForLayout(layoutName); 2170 if (controlType == null) 2171 return false; 2172 2173 var valueTypOfControl = 2174 TypeHelpers.GetGenericTypeArgumentFromHierarchy(controlType, typeof(InputControl<>), 0); 2175 if (valueTypOfControl == null) 2176 return false; 2177 2178 return valueType.IsAssignableFrom(valueTypOfControl); 2179 } 2180 2181 public bool IsGeneratedLayout(InternedString layout) 2182 { 2183 return layoutBuilders.ContainsKey(layout); 2184 } 2185 2186 public IEnumerable<InternedString> GetBaseLayouts(InternedString layout, bool includeSelf = true) 2187 { 2188 if (includeSelf) 2189 yield return layout; 2190 while (baseLayoutTable.TryGetValue(layout, out layout)) 2191 yield return layout; 2192 } 2193 2194 public bool IsBasedOn(InternedString parentLayout, InternedString childLayout) 2195 { 2196 var layout = childLayout; 2197 while (baseLayoutTable.TryGetValue(layout, out layout)) 2198 { 2199 if (layout == parentLayout) 2200 return true; 2201 } 2202 return false; 2203 } 2204 2205 public void AddMatcher(InternedString layout, InputDeviceMatcher matcher) 2206 { 2207 // Ignore if already added. 2208 var layoutMatcherCount = layoutMatchers.Count; 2209 for (var i = 0; i < layoutMatcherCount; ++i) 2210 if (layoutMatchers[i].deviceMatcher == matcher) 2211 return; 2212 2213 // Append. 2214 layoutMatchers.Add(new LayoutMatcher {layoutName = layout, deviceMatcher = matcher}); 2215 } 2216 } 2217 2218 // This collection is owned and managed by InputManager. 2219 internal static Collection s_Layouts; 2220 2221 public class LayoutNotFoundException : Exception 2222 { 2223 public string layout { get; } 2224 2225 public LayoutNotFoundException() 2226 { 2227 } 2228 2229 public LayoutNotFoundException(string name, string message) 2230 : base(message) 2231 { 2232 layout = name; 2233 } 2234 2235 public LayoutNotFoundException(string name) 2236 : base($"Cannot find control layout '{name}'") 2237 { 2238 layout = name; 2239 } 2240 2241 public LayoutNotFoundException(string message, Exception innerException) : 2242 base(message, innerException) 2243 { 2244 } 2245 2246 protected LayoutNotFoundException(SerializationInfo info, 2247 StreamingContext context) : base(info, context) 2248 { 2249 } 2250 } 2251 2252 // Constructs InputControlLayout instances and caches them. 2253 internal struct Cache 2254 { 2255 public Dictionary<InternedString, InputControlLayout> table; 2256 2257 public void Clear() 2258 { 2259 table = null; 2260 } 2261 2262 public InputControlLayout FindOrLoadLayout(string name, bool throwIfNotFound = true) 2263 { 2264 var internedName = new InternedString(name); 2265 2266 if (table == null) 2267 table = new Dictionary<InternedString, InputControlLayout>(); 2268 2269 var layout = s_Layouts.TryLoadLayout(internedName, table); 2270 if (layout != null) 2271 return layout; 2272 2273 // Nothing. 2274 if (throwIfNotFound) 2275 throw new LayoutNotFoundException(name); 2276 return null; 2277 } 2278 } 2279 2280 internal static Cache s_CacheInstance; 2281 internal static int s_CacheInstanceRef; 2282 2283 // Constructing InputControlLayouts is very costly as it tends to involve lots of reflection and 2284 // piecing data together. Thus, wherever possible, we want to keep layouts around for as long as 2285 // we need them yet at the same time not keep them needlessly around while we don't. 2286 // 2287 // This property makes a cache of layouts available globally yet implements a resource acquisition 2288 // based pattern to make sure we keep the cache alive only within specific execution scopes. 2289 internal static ref Cache cache 2290 { 2291 get 2292 { 2293 Debug.Assert(s_CacheInstanceRef > 0, "Must hold an instance reference"); 2294 return ref s_CacheInstance; 2295 } 2296 } 2297 2298 internal static CacheRefInstance CacheRef() 2299 { 2300 ++s_CacheInstanceRef; 2301 return new CacheRefInstance {valid = true}; 2302 } 2303 2304 internal struct CacheRefInstance : IDisposable 2305 { 2306 public bool valid; // Make sure we can distinguish default-initialized instances. 2307 public void Dispose() 2308 { 2309 if (!valid) 2310 return; 2311 2312 --s_CacheInstanceRef; 2313 if (s_CacheInstanceRef <= 0) 2314 { 2315 s_CacheInstance = default; 2316 s_CacheInstanceRef = 0; 2317 } 2318 2319 valid = false; 2320 } 2321 } 2322 } 2323}