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}