-
Notifications
You must be signed in to change notification settings - Fork 324
/
Copy pathInputSystem.cs
4049 lines (3749 loc) · 199 KB
/
InputSystem.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using UnityEngine.InputSystem.Haptics;
using Unity.Collections.LowLevel.Unsafe;
using UnityEngine.InputSystem.Controls;
using UnityEngine.InputSystem.Layouts;
using UnityEngine.InputSystem.LowLevel;
using UnityEngine.InputSystem.DualShock;
using UnityEngine.InputSystem.EnhancedTouch;
using UnityEngine.InputSystem.HID;
using UnityEngine.InputSystem.Users;
using UnityEngine.InputSystem.XInput;
using UnityEngine.InputSystem.Utilities;
using UnityEngine.Profiling;
#if UNITY_EDITOR
using UnityEditor;
using UnityEngine.InputSystem.Editor;
using UnityEditor.Networking.PlayerConnection;
#else
using System.Linq;
using UnityEngine.Networking.PlayerConnection;
#endif
#if UNITY_EDITOR
using CustomBindingPathValidator = System.Func<string, System.Action>;
#endif
////TODO: allow aliasing processors etc
////REVIEW: rename all references to "frame" to refer to "update" instead (e.g. wasPressedThisUpdate)?
////TODO: add APIs to get to the state blocks (equivalent to what you currently get with e.g. InputSystem.devices[0].currentStatePtr)
////FIXME: modal dialogs (or anything that interrupts normal Unity operation) are likely a problem for the system as is; there's a good
//// chance the event queue will just get swamped; should be only the background queue though so I guess once it fills up we
//// simply start losing input but it won't grow infinitely
////REVIEW: make more APIs thread-safe?
////REVIEW: it'd be great to be able to set up monitors from control paths (independently of actions; or should we just use actions?)
////REVIEW: have InputSystem.onTextInput that's fired directly from the event processing loop?
//// (and allow text input events that have no associated target device? this way we don't need a keyboard to get text input)
////REVIEW: split lower-level APIs (anything mentioning events and state) off into InputSystemLowLevel API to make this API more focused?
////TODO: release native allocations when exiting
namespace UnityEngine.InputSystem
{
/// <summary>
/// This is the central hub for the input system.
/// </summary>
/// <remarks>
/// This class has the central APIs for working with the input system. You
/// can manage devices available in the system (<see cref="AddDevice{TDevice}"/>,
/// <see cref="devices"/>, <see cref="onDeviceChange"/> and related APIs) or extend
/// the input system with custom functionality (<see cref="RegisterLayout{TLayout}"/>,
/// <see cref="RegisterInteraction{T}"/>, <see cref="RegisterProcessor{T}"/>,
/// <see cref="RegisterBindingComposite{T}"/>, and related APIs).
///
/// To control haptics globally, you can use <see cref="PauseHaptics"/>, <see cref="ResumeHaptics"/>,
/// and <see cref="ResetHaptics"/>.
///
/// To enable and disable individual devices (such as <see cref="Sensor"/> devices),
/// you can use <see cref="EnableDevice"/> and <see cref="DisableDevice"/>.
///
/// The input system is initialized as part of Unity starting up. It is generally safe
/// to call the APIs here from any of Unity's script callbacks.
///
/// Note that, like most Unity APIs, most of the properties and methods in this API can only
/// be called on the main thread. However, select APIs like <see cref="QueueEvent"/> can be
/// called from threads. Where this is the case, it is stated in the documentation.
/// </remarks>
[SuppressMessage("Microsoft.Naming", "CA1724:TypeNamesShouldNotMatchNamespaces", Justification = "Options for namespaces are limited due to the legacy input class. Agreed on this as the least bad solution.")]
#if UNITY_EDITOR
[InitializeOnLoad]
#endif
public static partial class InputSystem
{
#region Layouts
/// <summary>
/// Event that is signalled when the layout setup in the system changes.
/// </summary>
/// <remarks>
/// First parameter is the name of the layout that has changed and second parameter is the
/// type of change that has occurred.
///
/// <example>
/// <code>
/// InputSystem.onLayoutChange +=
/// (name, change) =>
/// {
/// switch (change)
/// {
/// case InputControlLayoutChange.Added:
/// Debug.Log($"New layout {name} has been added");
/// break;
/// case InputControlLayoutChange.Removed:
/// Debug.Log($"Layout {name} has been removed");
/// break;
/// case InputControlLayoutChange.Replaced:
/// Debug.Log($"Layout {name} has been updated");
/// break;
/// }
/// }
/// </code>
/// </example>
/// </remarks>
/// <seealso cref="InputControlLayout"/>
public static event Action<string, InputControlLayoutChange> onLayoutChange
{
add
{
lock (s_Manager)
s_Manager.onLayoutChange += value;
}
remove
{
lock (s_Manager)
s_Manager.onLayoutChange -= value;
}
}
/// <summary>
/// Register a control layout based on a type.
/// </summary>
/// <param name="type">Type to derive a control layout from. Must be derived from <see cref="InputControl"/>.</param>
/// <param name="name">Name to use for the layout. If null or empty, the short name of the type (<c>Type.Name</c>) will be used.</param>
/// <param name="matches">Optional device matcher. If this is supplied, the layout will automatically
/// be instantiated for newly discovered devices that match the description.</param>
/// <exception cref="ArgumentNullException"><paramref name="type"/> is <c>null</c>.</exception>
/// <remarks>
/// When the layout is instantiated, the system will reflect on all public fields and properties of the type
/// which have a value type derived from <see cref="InputControl"/> or which are annotated with <see cref="InputControlAttribute"/>.
///
/// The type can be annotated with <see cref="InputControlLayoutAttribute"/> for additional options
/// but the attribute is not necessary for a type to be usable as a control layout. Note that if the type
/// does have <see cref="InputControlLayoutAttribute"/> and has set <see cref="InputControlLayoutAttribute.stateType"/>,
/// the system will <em>not</em> reflect on properties and fields in the type but do that on the given
/// state type instead.
///
/// <example>
/// <code>
/// // InputControlLayoutAttribute attribute is only necessary if you want
/// // to override default behavior that occurs when registering your device
/// // as a layout.
/// // The most common use of InputControlLayoutAttribute is to direct the system
/// // to a custom "state struct" through the `stateType` property. See below for details.
/// [InputControlLayout(displayName = "My Device", stateType = typeof(MyDeviceState))]
/// #if UNITY_EDITOR
/// [InitializeOnLoad]
/// #endif
/// public class MyDevice : InputDevice
/// {
/// public ButtonControl button { get; private set; }
/// public AxisControl axis { get; private set; }
///
/// // Register the device.
/// static MyDevice()
/// {
/// // In case you want instance of your device to automatically be created
/// // when specific hardware is detected by the Unity runtime, you have to
/// // add one or more "device matchers" (InputDeviceMatcher) for the layout.
/// // These matchers are compared to an InputDeviceDescription received from
/// // the Unity runtime when a device is connected. You can add them either
/// // using InputSystem.RegisterLayoutMatcher() or by directly specifying a
/// // matcher when registering the layout.
/// InputSystem.RegisterLayout<MyDevice>(
/// // For the sake of demonstration, let's assume your device is a HID
/// // and you want to match by PID and VID.
/// matches: new InputDeviceMatcher()
/// .WithInterface("HID")
/// .WithCapability("PID", 1234)
/// .WithCapability("VID", 5678));
/// }
///
/// // This is only to trigger the static class constructor to automatically run
/// // in the player.
/// [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
/// private static void InitializeInPlayer() {}
///
/// protected override void FinishSetup()
/// {
/// base.FinishSetup();
/// button = GetChildControl<ButtonControl>("button");
/// axis = GetChildControl<AxisControl>("axis");
/// }
/// }
///
/// // A "state struct" describes the memory format used by a device. Each device can
/// // receive and store memory in its custom format. InputControls are then connected
/// // the individual pieces of memory and read out values from them.
/// [StructLayout(LayoutKind.Explicit, Size = 32)]
/// public struct MyDeviceState : IInputStateTypeInfo
/// {
/// // In the case of a HID (which we assume for the sake of this demonstration),
/// // the format will be "HID". In practice, the format will depend on how your
/// // particular device is connected and fed into the input system.
/// // The format is a simple FourCC code that "tags" state memory blocks for the
/// // device to give a base level of safety checks on memory operations.
/// public FourCC format => return new FourCC('H', 'I', 'D');
///
/// // InputControlAttributes on fields tell the input system to create controls
/// // for the public fields found in the struct.
///
/// // Assume a 16bit field of buttons. Create one button that is tied to
/// // bit #3 (zero-based). Note that buttons do not need to be stored as bits.
/// // They can also be stored as floats or shorts, for example.
/// [InputControl(name = "button", layout = "Button", bit = 3)]
/// public ushort buttons;
///
/// // Create a floating-point axis. The name, if not supplied, is taken from
/// // the field.
/// [InputControl(layout = "Axis")]
/// public short axis;
/// }
/// </code>
/// </example>
///
/// Note that if <paramref name="matches"/> is supplied, it will immediately be matched
/// against the descriptions (<see cref="InputDeviceDescription"/>) of all available devices.
/// If it matches any description where no layout matched before, a new device will immediately
/// be created (except if suppressed by <see cref="InputSettings.supportedDevices"/>). If it
/// matches a description better (see <see cref="InputDeviceMatcher.MatchPercentage"/>) than
/// the currently used layout, the existing device will be a removed and a new device with
/// the newly registered layout will be created.
///
/// See <see cref="Controls.StickControl"/> or <see cref="Gamepad"/> for examples of layouts.
/// </remarks>
/// <seealso cref="InputControlLayout"/>
public static void RegisterLayout(Type type, string name = null, InputDeviceMatcher? matches = null)
{
if (type == null)
throw new ArgumentNullException(nameof(type));
if (string.IsNullOrEmpty(name))
name = type.Name;
s_Manager.RegisterControlLayout(name, type);
if (matches != null)
s_Manager.RegisterControlLayoutMatcher(name, matches.Value);
}
/// <summary>
/// Register a type as a control layout.
/// </summary>
/// <typeparam name="T">Type to derive a control layout from.</typeparam>
/// <param name="name">Name to use for the layout. If null or empty, the short name of the type will be used.</param>
/// <param name="matches">Optional device matcher. If this is supplied, the layout will automatically
/// be instantiated for newly discovered devices that match the description.</param>
/// <remarks>
/// This method is equivalent to calling <see cref="RegisterLayout(Type,string,InputDeviceMatcher?)"/> with
/// <c>typeof(T)</c>. See that method for details of the layout registration process.
/// </remarks>
/// <seealso cref="RegisterLayout(Type,string,InputDeviceMatcher?)"/>
public static void RegisterLayout<T>(string name = null, InputDeviceMatcher? matches = null)
where T : InputControl
{
RegisterLayout(typeof(T), name, matches);
}
/// <summary>
/// Register a layout in JSON format.
/// </summary>
/// <param name="json">JSON data describing the layout.</param>
/// <param name="name">Optional name of the layout. If null or empty, the name is taken from the "name"
/// property of the JSON data. If it is supplied, it will override the "name" property if present. If neither
/// is supplied, an <see cref="ArgumentException"/> is thrown.</param>
/// <param name="matches">Optional device matcher. If this is supplied, the layout will automatically
/// be instantiated for newly discovered devices that match the description.</param>
/// <exception cref="ArgumentNullException"><paramref name="json"/> is null or empty.</exception>
/// <exception cref="ArgumentException">No name has been supplied either through <paramref name="name"/>
/// or the "name" JSON property.</exception>
/// <remarks>
/// The JSON format makes it possible to create new device and control layouts completely
/// in data. They have to ultimately be based on a layout backed by a C# type, however (e.g.
/// <see cref="Gamepad"/>).
///
/// Note that most errors in layouts will only be detected when instantiated (i.e. when a device or control is
/// being created from a layout). The JSON data will, however, be parsed once on registration to check for a
/// device description in the layout. JSON format errors will thus be detected during registration.
///
/// <example>
/// <code>
/// InputSystem.RegisterLayout(@"
/// {
/// ""name"" : ""MyDevice"",
/// ""controls"" : [
/// {
/// ""name"" : ""myButton"",
/// ""layout"" : ""Button""
/// }
/// ]
/// }
/// );
/// </code>
/// </example>
/// </remarks>
/// <seealso cref="RemoveLayout"/>
public static void RegisterLayout(string json, string name = null, InputDeviceMatcher? matches = null)
{
s_Manager.RegisterControlLayout(json, name);
if (matches != null)
s_Manager.RegisterControlLayoutMatcher(name, matches.Value);
}
/// <summary>
/// Register a layout that applies overrides to one or more other layouts.
/// </summary>
/// <param name="json">Layout in JSON format.</param>
/// <param name="name">Optional name of the layout. If null or empty, the name is taken from the "name"
/// property of the JSON data. If it is supplied, it will override the "name" property if present. If neither
/// is supplied, an <see cref="ArgumentException"/> is thrown.</param>
/// <remarks>
/// Layout overrides are layout pieces that are applied on top of existing layouts.
/// This can be used to modify any layout in the system non-destructively. The process works the
/// same as extending an existing layout except that instead of creating a new layout
/// by merging the derived layout and the base layout, the overrides are merged
/// directly into the base layout.
///
/// The layout merging logic used for overrides, is the same as the one used for
/// derived layouts, i.e. <see cref="InputControlLayout.MergeLayout"/>.
///
/// Layouts used as overrides look the same as normal layouts and have the same format.
/// The only difference is that they are explicitly registered as overrides.
///
/// Note that unlike "normal" layouts, layout overrides have the ability to extend
/// multiple base layouts. The changes from the override will simply be merged into
/// each of the layouts it extends. Use the <c>extendMultiple</c> rather than the
/// <c>extend</c> property in JSON to give a list of base layouts instead of a single
/// one.
///
/// <example>
/// <code>
/// // Override default button press points on the gamepad triggers.
/// InputSystem.RegisterLayoutOverride(@"
/// {
/// ""name"" : ""CustomTriggerPressPoints"",
/// ""extend"" : ""Gamepad"",
/// ""controls"" : [
/// { ""name"" : ""leftTrigger"", ""parameters"" : ""pressPoint=0.25"" },
/// { ""name"" : ""rightTrigger"", ""parameters"" : ""pressPoint=0.25"" }
/// ]
/// }
/// ");
/// </code>
/// </example>
/// </remarks>
public static void RegisterLayoutOverride(string json, string name = null)
{
s_Manager.RegisterControlLayout(json, name, isOverride: true);
}
/// <summary>
/// Add an additional device matcher to an existing layout.
/// </summary>
/// <param name="layoutName">Name of the device layout that should be instantiated if <paramref name="matcher"/>
/// matches an <see cref="InputDeviceDescription"/> of a discovered device.</param>
/// <param name="matcher">Specification to match against <see cref="InputDeviceDescription"/> instances.</param>
/// <remarks>
/// Each device layout can have zero or more matchers associated with it. If any one of the
/// matchers matches a given <see cref="InputDeviceDescription"/> (see <see cref="InputDeviceMatcher.MatchPercentage"/>)
/// better than any other matcher (for the same or any other layout), then the given layout
/// will be used for the discovered device.
///
/// Note that registering a matcher may immediately lead to devices being created or recreated.
/// If <paramref name="matcher"/> matches any devices currently on the list of unsupported devices
/// (see <see cref="GetUnsupportedDevices()"/>), new <see cref="InputDevice"/>s will be created
/// using the layout called <paramref name="layoutName"/>. Also, if <paramref name="matcher"/>
/// matches the description of a device better than the matcher (if any) for the device's currently
/// used layout, the device will be recreated using the given layout.
/// </remarks>
/// <exception cref="ArgumentNullException"><paramref name="layoutName"/> is <c>null</c> or empty/</exception>
/// <exception cref="ArgumentException"><paramref name="matcher"/> is empty (<see cref="InputDeviceMatcher.empty"/>).</exception>
/// <seealso cref="RegisterLayout(Type,string,InputDeviceMatcher?)"/>
/// <seealso cref="TryFindMatchingLayout"/>
public static void RegisterLayoutMatcher(string layoutName, InputDeviceMatcher matcher)
{
s_Manager.RegisterControlLayoutMatcher(layoutName, matcher);
}
/// <summary>
/// Add an additional device matcher to the layout registered for <typeparamref name="TDevice"/>.
/// </summary>
/// <param name="matcher">A device matcher.</param>
/// <typeparam name="TDevice">Type that has been registered as a layout. See <see cref="RegisterLayout{T}"/>.</typeparam>
/// <remarks>
/// Calling this method is equivalent to calling <see cref="RegisterLayoutMatcher(string,InputDeviceMatcher)"/>
/// with the name under which <typeparamref name="TDevice"/> has been registered.
/// </remarks>
/// <exception cref="ArgumentException"><paramref name="matcher"/> is empty (<see cref="InputDeviceMatcher.empty"/>)
/// -or- <typeparamref name="TDevice"/> has not been registered as a layout.</exception>
public static void RegisterLayoutMatcher<TDevice>(InputDeviceMatcher matcher)
where TDevice : InputDevice
{
s_Manager.RegisterControlLayoutMatcher(typeof(TDevice), matcher);
}
/// <summary>
/// Register a builder that delivers an <see cref="InputControlLayout"/> instance on demand.
/// </summary>
/// <param name="buildMethod">Method to invoke to generate a layout when the layout is chosen.
/// Should not cache the layout but rather return a fresh instance every time.</param>
/// <param name="name">Name under which to register the layout. If a layout with the same
/// name is already registered, the call to this method will replace the existing layout.</param>
/// <param name="baseLayout">Name of the layout that the layout returned from <paramref name="buildMethod"/>
/// will be based on. The system needs to know this in advance in order to update devices
/// correctly if layout registrations in the system are changed.</param>
/// <param name="matches">Optional matcher for an <see cref="InputDeviceDescription"/>. If supplied,
/// it is equivalent to calling <see cref="RegisterLayoutMatcher"/>.</param>
/// <exception cref="ArgumentNullException"><paramref name="buildMethod"/> is <c>null</c> -or-
/// <paramref name="name"/> is <c>null</c> or empty.</exception>
/// <remarks>
/// Layout builders are most useful for procedurally building device layouts from metadata
/// supplied by external systems. A good example is <see cref="HID"/> where the "HID" standard
/// includes a way for input devices to describe their various inputs and outputs in the form
/// of a <see cref="UnityEngine.InputSystem.HID.HID.HIDDeviceDescriptor"/>. While not sufficient to build a perfectly robust
/// <see cref="InputDevice"/>, these descriptions are usually enough to at least make the device
/// work out-of-the-box to some extent.
///
/// The builder method would usually use <see cref="InputControlLayout.Builder"/> to build the
/// actual layout.
///
/// <example>
/// <code>
/// InputSystem.RegisterLayoutBuilder(
/// () =>
/// {
/// var builder = new InputControlLayout.Builder()
/// .WithType<MyDevice>();
/// builder.AddControl("button1").WithLayout("Button");
/// return builder.Build();
/// }, "MyCustomLayout"
/// }
/// </code>
/// </example>
///
/// Layout builders can be used in combination with <see cref="onFindLayoutForDevice"/> to
/// build layouts dynamically for devices as they are connected to the system.
///
/// Be aware that the same builder <em>must</em> not build different layouts. Each
/// layout registered in the system is considered to be immutable for as long as it
/// is registered. So, if a layout builder is registered under the name "Custom", for
/// example, then every time the builder is invoked, it must return the same identical
/// <see cref="InputControlLayout"/>.
/// </remarks>
/// <seealso cref="InputControlLayout.Builder"/>
/// <seealso cref="InputSystem.onFindLayoutForDevice"/>
public static void RegisterLayoutBuilder(Func<InputControlLayout> buildMethod, string name,
string baseLayout = null, InputDeviceMatcher? matches = null)
{
if (buildMethod == null)
throw new ArgumentNullException(nameof(buildMethod));
if (string.IsNullOrEmpty(name))
throw new ArgumentNullException(nameof(name));
s_Manager.RegisterControlLayoutBuilder(buildMethod, name, baseLayout: baseLayout);
if (matches != null)
s_Manager.RegisterControlLayoutMatcher(name, matches.Value);
}
/// <summary>
/// Register a "baked" version of a device layout.
/// </summary>
/// <typeparam name="TDevice">C# class that represents the precompiled version of the device layout that the
/// class is derived from.</typeparam>
/// <param name="metadata">Metadata automatically generated for the precompiled layout.</param>
/// <remarks>
/// This method is used to register device implementations for which their layout has been "baked" into
/// a C# class. To generate such a class, right-click a device layout in the input debugger and select
/// "Generate Precompiled Layout". This generates a C# file containing a class that represents the precompiled
/// version of the device layout. The class can be registered using this method.
///
/// Note that registering a precompiled layout will not implicitly register the "normal" version of the layout.
/// In other words, <see cref="RegisterLayout{TDevice}"/> must be called before calling this method.
///
/// <example>
/// <code>
/// // Register the non-precompiled, normal version of the layout.
/// InputSystem.RegisterLayout<MyDevice>();
///
/// // Register a precompiled version of the layout.
/// InputSystem.RegisterPrecompiledLayout<PrecompiledMyDevice>(PrecompiledMyDevice.metadata);
///
/// // This implicitly uses the precompiled version.
/// InputSystem.AddDevice<MyDevice>();
/// </code>
/// </example>
///
/// The main advantage of precompiled layouts is that instantiating them is many times faster than the default
/// device creation path. By default, when creating an <see cref="InputDevice"/>, the system will have to load
/// the <see cref="InputControlLayout"/> for the device as well as any layouts used directly or indirectly by
/// that layout. This in itself is a slow process that generates GC heap garbage and uses .NET reflection (which
/// itself may add additional permanent data to the GC heap). In addition, interpreting the layouts to construct
/// an <see cref="InputDevice"/> and populate it with <see cref="InputControl"/> children is not a fast process.
///
/// A precompiled layout, however, has all necessary construction steps "baked" into the generated code. It will
/// not use reflection and will generally generate little to no GC heap garbage.
///
/// A precompiled layout derives from the C# device class whose layout is "baked". If, for example, you generate
/// a precompiled version for <see cref="Keyboard"/>, the resulting class will be derived from <see cref="Keyboard"/>.
/// When registering the precompiled layout. If someone afterwards creates a <see cref="Keyboard"/>, the precompiled
/// version will implicitly be instantiated and thus skips the default device creation path that will construct
/// a <see cref="Keyboard"/> device from an <see cref="InputControlLayout"/> (it will thus not require the
/// <see cref="Keyboard"/> layout or any other layout it depends on to be loaded).
///
/// Note that when layout overrides (see <see cref="RegisterLayoutOverride"/>) or new versions of
/// existing layouts are registered (e.g. if you replace the built-in "Button" layout by registering
/// a new layout with that name), precompiled layouts affected by the change will automatically be
/// <em>removed</em>. This causes the system to fall back to the default device creation path which can
/// take runtime layout changes into account.
/// </remarks>
public static void RegisterPrecompiledLayout<TDevice>(string metadata)
where TDevice : InputDevice, new()
{
s_Manager.RegisterPrecompiledLayout<TDevice>(metadata);
}
/// <summary>
/// Remove an already registered layout from the system.
/// </summary>
/// <param name="name">Name of the layout to remove. Note that layout names are case-insensitive.</param>
/// <remarks>
/// Note that removing a layout also removes all devices that directly or indirectly
/// use the layout.
///
/// This method can be used to remove both control or device layouts.
/// </remarks>
public static void RemoveLayout(string name)
{
s_Manager.RemoveControlLayout(name);
}
/// <summary>
/// Try to match a description for an input device to a layout.
/// </summary>
/// <param name="deviceDescription">Description of an input device.</param>
/// <returns>Name of the layout that has been matched to the given description or null if no
/// matching layout was found.</returns>
/// <remarks>
/// This method performs the same matching process that is invoked if a device is reported
/// by the Unity runtime or using <see cref="AddDevice(InputDeviceDescription)"/>. The result
/// depends on the matches (<see cref="InputDeviceMatcher"/>) registered for the device
/// layout in the system.
///
/// <example>
/// <code>
/// var layoutName = InputSystem.TryFindMatchingLayout(
/// new InputDeviceDescription
/// {
/// interface = "XInput",
/// product = "Xbox Wired Controller",
/// manufacturer = "Microsoft"
/// }
/// );
/// </code>
/// </example>
/// </remarks>
/// <seealso cref="RegisterLayoutMatcher{TDevice}"/>
/// <seealso cref="RegisterLayoutMatcher(string,InputDeviceMatcher)"/>
public static string TryFindMatchingLayout(InputDeviceDescription deviceDescription)
{
return s_Manager.TryFindMatchingControlLayout(ref deviceDescription);
}
/// <summary>
/// Return a list with the names of all layouts that have been registered.
/// </summary>
/// <returns>A list of layout names.</returns>
/// <seealso cref="LoadLayout"/>
/// <seealso cref="ListLayoutsBasedOn"/>
/// <seealso cref="RegisterLayout(System.Type,string,Nullable{InputDeviceMatcher})"/>
public static IEnumerable<string> ListLayouts()
{
return s_Manager.ListControlLayouts();
}
/// <summary>
/// List all the layouts that are based on the given layout.
/// </summary>
/// <param name="baseLayout">Name of a registered layout.</param>
/// <exception cref="ArgumentNullException"><paramref name="baseLayout"/> is <c>null</c> or empty.</exception>
/// <returns>The names of all registered layouts based on <paramref name="baseLayout"/>.</returns>
/// <remarks>
/// The list will not include layout overrides (see <see cref="RegisterLayoutOverride"/>).
///
/// <example>
/// <code>
/// // List all gamepad layouts in the system.
/// Debug.Log(string.Join("\n", InputSystem.ListLayoutsBasedOn("Gamepad"));
/// </code>
/// </example>
/// </remarks>
public static IEnumerable<string> ListLayoutsBasedOn(string baseLayout)
{
if (string.IsNullOrEmpty(baseLayout))
throw new ArgumentNullException(nameof(baseLayout));
return s_Manager.ListControlLayouts(basedOn: baseLayout);
}
////TODO: allow loading an *unmerged* layout
/// <summary>
/// Load a registered layout.
/// </summary>
/// <param name="name">Name of the layout to load. Note that layout names are case-insensitive.</param>
/// <exception cref="ArgumentNullException"><paramref name="name"/> is <c>null</c> or empty.</exception>
/// <returns>The constructed layout instance or <c>null</c> if no layout of the given name could be found.</returns>
/// <remarks>
/// The result of this method is what's called a "fully merged" layout, i.e. a layout with
/// the information of all the base layouts as well as from all overrides merged into it. See
/// <see cref="InputControlLayout.MergeLayout"/> for details.
///
/// What this means in practice is that all inherited controls and settings will be present
/// on the layout.
///
/// <example>
/// // List all controls defined for gamepads.
/// var gamepadLayout = InputSystem.LoadLayout("Gamepad");
/// foreach (var control in gamepadLayout.controls)
/// {
/// // There may be control elements that are not introducing new controls but rather
/// // change settings on controls added indirectly by other layouts referenced from
/// // Gamepad. These are not adding new controls so we skip them here.
/// if (control.isModifyingExistingControl)
/// continue;
///
/// Debug.Log($"Control: {control.name} ({control.layout])");
/// }
/// </example>
///
/// However, note that controls which are added from other layouts referenced by the loaded layout
/// will not necessarily be visible on it (they will only if referenced by a <see cref="InputControlLayout.ControlItem"/>
/// where <see cref="InputControlLayout.ControlItem.isModifyingExistingControl"/> is <c>true</c>).
/// For example, let's assume we have the following layout which adds a device with a single stick.
///
/// <example>
/// <code>
/// InputSystem.RegisterLayout(@"
/// {
/// ""name"" : ""DeviceWithStick"",
/// ""controls"" : [
/// { ""name"" : ""stick"", ""layout"" : ""Stick"" }
/// ]
/// }
/// ");
/// </code>
/// </example>
///
/// If we load this layout, the <c>"stick"</c> control will be visible on the layout but the
/// X and Y (as well as up/down/left/right) controls added by the <c>"Stick"</c> layout will
/// not be.
/// </remarks>
/// <seealso cref="RegisterLayout(Type,string,Nullable{InputDeviceMatcher})"/>
public static InputControlLayout LoadLayout(string name)
{
if (string.IsNullOrEmpty(name))
throw new ArgumentNullException(nameof(name));
////FIXME: this will intern the name even if the operation fails
return s_Manager.TryLoadControlLayout(new InternedString(name));
}
/// <summary>
/// Load the layout registered for the given type.
/// </summary>
/// <typeparam name="TControl">An InputControl type.</typeparam>
/// <returns>The layout registered for <typeparamref name="TControl"/> or <c>null</c> if no
/// such layout exists.</returns>
/// <remarks>
/// This method is equivalent to calling <see cref="LoadLayout(string)"/> with the name
/// of the layout under which <typeparamref name="TControl"/> has been registered.
///
/// <example>
/// <code>
/// // Load the InputControlLayout generated from StickControl.
/// var stickLayout = InputSystem.LoadLayout<StickControl>();
/// </code>
/// </example>
/// </remarks>
/// <seealso cref="LoadLayout(string)"/>
public static InputControlLayout LoadLayout<TControl>()
where TControl : InputControl
{
return s_Manager.TryLoadControlLayout(typeof(TControl));
}
/// <summary>
/// Return the name of the layout that the layout registered as <paramref name="layoutName"/>
/// is based on.
/// </summary>
/// <param name="layoutName">Name of a layout as registered with a method such as <see
/// cref="RegisterLayout{T}(string,InputDeviceMatcher?)"/>. Case-insensitive.</param>
/// <returns>Name of the immediate parent layout of <paramref name="layoutName"/> or <c>null</c> if no layout
/// with the given name is registered or if it is not based on another layout or if it is a layout override.</returns>
/// <exception cref="ArgumentNullException"><paramref name="layoutName"/> is <c>null</c> or empty.</exception>
/// <remarks>
/// This method does not work for layout overrides (which can be based on multiple base layouts). To find
/// out which layouts a specific override registered with <see cref="RegisterLayoutOverride"/> is based on,
/// load the layout with <see cref="LoadLayout"/> and inspect <see cref="InputControlLayout.baseLayouts"/>.
/// This method will return <c>null</c> when <paramref name="layoutName"/> is the name of a layout override.
///
/// One advantage of this method over calling <see cref="LoadLayout"/> and looking at <see cref="InputControlLayout.baseLayouts"/>
/// is that this method does not have to actually load the layout but instead only performs a simple lookup.
///
/// <example>
/// <code>
/// // Prints "Pointer".
/// Debug.Log(InputSystem.GetNameOfBaseLayout("Mouse"));
///
/// // Also works for control layouts. Prints "Axis".
/// Debug.Log(InputSystem.GetNameOfBaseLayout("Button"));
/// </code>
/// </example>
/// </remarks>
/// <seealso cref="InputControlLayout.baseLayouts"/>
public static string GetNameOfBaseLayout(string layoutName)
{
if (string.IsNullOrEmpty(layoutName))
throw new ArgumentNullException(nameof(layoutName));
var internedLayoutName = new InternedString(layoutName);
if (InputControlLayout.s_Layouts.baseLayoutTable.TryGetValue(internedLayoutName, out var result))
return result;
return null;
}
/// <summary>
/// Check whether the first layout is based on the second.
/// </summary>
/// <param name="firstLayoutName">Name of a registered <see cref="InputControlLayout"/>.</param>
/// <param name="secondLayoutName">Name of a registered <see cref="InputControlLayout"/>.</param>
/// <returns>True if <paramref name="firstLayoutName"/> is based on <paramref name="secondLayoutName"/>.</returns>
/// <exception cref="ArgumentNullException"><paramref name="firstLayoutName"/> is <c>null</c> or empty -or-
/// <paramref name="secondLayoutName"/> is <c>null</c> or empty.</exception>
/// <remarks>
/// This is
/// <example>
/// </example>
/// </remarks>
public static bool IsFirstLayoutBasedOnSecond(string firstLayoutName, string secondLayoutName)
{
if (string.IsNullOrEmpty(firstLayoutName))
throw new ArgumentNullException(nameof(firstLayoutName));
if (string.IsNullOrEmpty(secondLayoutName))
throw new ArgumentNullException(nameof(secondLayoutName));
var internedFirstName = new InternedString(firstLayoutName);
var internedSecondName = new InternedString(secondLayoutName);
if (internedFirstName == internedSecondName)
return true;
return InputControlLayout.s_Layouts.IsBasedOn(internedSecondName, internedFirstName);
}
#endregion
#region Processors
/// <summary>
/// Register an <see cref="InputProcessor{TValue}"/> with the system.
/// </summary>
/// <param name="type">Type that implements <see cref="InputProcessor"/>.</param>
/// <param name="name">Name to use for the processor. If <c>null</c> or empty, name will be taken from the short name
/// of <paramref name="type"/> (if it ends in "Processor", that suffix will be clipped from the name). Names
/// are case-insensitive.</param>
/// <remarks>
/// Processors are used by both bindings (see <see cref="InputBinding"/>) and by controls
/// (see <see cref="InputControl"/>) to post-process input values as they are being requested
/// from calls such as <see cref="InputAction.ReadValue{TValue}"/> or <see
/// cref="InputControl{T}.ReadValue"/>.
///
/// <example>
/// <code>
/// // Let's say that we want to define a processor that adds some random jitter to its input.
/// // We have to pick a value type to operate on if we want to derive from InputProcessor<T>
/// // so we go with float here.
/// //
/// // Also, as we will need to place our call to RegisterProcessor somewhere, we add attributes
/// // to hook into Unity's initialization. This works differently in the editor and in the player,
/// // so we use both [InitializeOnLoad] and [RuntimeInitializeOnLoadMethod].
/// #if UNITY_EDITOR
/// [InitializeOnLoad]
/// #endif
/// public class JitterProcessor : InputProcessor<float>
/// {
/// // Add a parameter that defines the amount of jitter we apply.
/// // This will be editable in the Unity editor UI and can be set
/// // programmatically in code. For example:
/// //
/// // myAction.AddBinding("<Gamepad>/rightTrigger",
/// // processors: "jitter(amount=0.1)");
/// //
/// [Tooltip("Amount of jitter to apply. Will add a random value in the range [-amount..amount] "
/// + "to each input value.)]
/// public float amount;
///
/// // Process is called when an input value is read from a control. This is
/// // where we perform our jitter.
/// public override float Process(float value, InputControl control)
/// {
/// return float + Random.Range(-amount, amount);
/// }
///
/// // [InitializeOnLoad] will call the static class constructor which
/// // we use to call Register.
/// #if UNITY_EDITOR
/// static JitterProcessor()
/// {
/// Register();
/// }
/// #endif
///
/// // [RuntimeInitializeOnLoadMethod] will make sure that Register gets called
/// // in the player on startup.
/// // NOTE: This will also get called when going into play mode in the editor. In that
/// // case we get two calls to Register instead of one. We don't bother with that
/// // here. Calling RegisterProcessor twice here doesn't do any harm.
/// [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
/// static void Register()
/// {
/// // We don't supply a name here. The input system will take "JitterProcessor"
/// // and automatically snip off the "Processor" suffix thus leaving us with
/// // a name of "Jitter" (all this is case-insensitive).
/// InputSystem.RegisterProcessor<JitterProcessor>();
/// }
/// }
///
/// // It doesn't really make sense in our case as the default parameter editor is just
/// // fine (it will pick up the tooltip we defined above) but let's say we want to replace
/// // the default float edit field we get on the "amount" parameter with a slider. We can
/// // do so by defining a custom parameter editor.
/// //
/// // NOTE: We don't need to have a registration call here. The input system will automatically
/// // find our parameter editor based on the JitterProcessor type parameter we give to
/// // InputParameterEditor<T>.
/// #if UNITY_EDITOR
/// public class JitterProcessorEditor : InputParameterEditor<JitterProcessor>
/// {
/// public override void OnGUI()
/// {
/// target.amount = EditorGUILayout.Slider(m_AmountLabel, target.amount, 0, 0.25f);
/// }
///
/// private GUIContent m_AmountLabel = new GUIContent("Amount",
/// "Amount of jitter to apply. Will add a random value in the range [-amount..amount] "
/// + "to each input value.);
/// }
/// #endif
/// </code>
/// </example>
///
/// Note that it is allowed to register the same processor type multiple types with
/// different names. When doing so, the first registration is considered as the "proper"
/// name for the processor and all subsequent registrations will be considered aliases.
///
/// See the <a href="../manual/Processors.html">manual</a> for more details.
/// </remarks>
/// <seealso cref="InputProcessor{T}"/>
/// <seealso cref="InputBinding.processors"/>
/// <seealso cref="InputAction.processors"/>
/// <seealso cref="InputControlLayout.ControlItem.processors"/>
/// <seealso cref="UnityEngine.InputSystem.Editor.InputParameterEditor{TObject}"/>
public static void RegisterProcessor(Type type, string name = null)
{
if (type == null)
throw new ArgumentNullException(nameof(type));
// Default name to name of type without Processor suffix.
if (string.IsNullOrEmpty(name))
{
name = type.Name;
if (name.EndsWith("Processor"))
name = name.Substring(0, name.Length - "Processor".Length);
}
// Flush out any precompiled layout depending on the processor.
var precompiledLayouts = s_Manager.m_Layouts.precompiledLayouts;
foreach (var key in new List<InternedString>(precompiledLayouts.Keys)) // Need to keep key list stable while iterating; ToList() for some reason not available with .NET Standard 2.0 on Mono.
{
if (StringHelpers.CharacterSeparatedListsHaveAtLeastOneCommonElement(precompiledLayouts[key].metadata, name, ';'))
s_Manager.m_Layouts.precompiledLayouts.Remove(key);
}
s_Manager.processors.AddTypeRegistration(name, type);
}
/// <summary>
/// Register an <see cref="InputProcessor{TValue}"/> with the system.
/// </summary>
/// <typeparam name="T">Type that implements <see cref="InputProcessor"/>.</typeparam>
/// <param name="name">Name to use for the processor. If <c>null</c> or empty, name will be taken from the short name
/// of <typeparamref name="T"/> (if it ends in "Processor", that suffix will be clipped from the name). Names
/// are case-insensitive.</param>
/// <remarks>
/// Processors are used by both bindings (see <see cref="InputBinding"/>) and by controls
/// (see <see cref="InputControl"/>) to post-process input values as they are being requested
/// from calls such as <see cref="InputAction.ReadValue{TValue}"/> or <see
/// cref="InputControl{T}.ReadValue"/>.
///
/// <example>
/// <code>
/// // Let's say that we want to define a processor that adds some random jitter to its input.
/// // We have to pick a value type to operate on if we want to derive from InputProcessor<T>
/// // so we go with float here.
/// //
/// // Also, as we will need to place our call to RegisterProcessor somewhere, we add attributes
/// // to hook into Unity's initialization. This works differently in the editor and in the player,
/// // so we use both [InitializeOnLoad] and [RuntimeInitializeOnLoadMethod].
/// #if UNITY_EDITOR
/// [InitializeOnLoad]
/// #endif
/// public class JitterProcessor : InputProcessor<float>
/// {
/// // Add a parameter that defines the amount of jitter we apply.
/// // This will be editable in the Unity editor UI and can be set
/// // programmatically in code. For example:
/// //
/// // myAction.AddBinding("<Gamepad>/rightTrigger",
/// // processors: "jitter(amount=0.1)");
/// //
/// [Tooltip("Amount of jitter to apply. Will add a random value in the range [-amount..amount] "
/// + "to each input value.)]
/// public float amount;
///
/// // Process is called when an input value is read from a control. This is
/// // where we perform our jitter.
/// public override float Process(float value, InputControl control)
/// {
/// return float + Random.Range(-amount, amount);
/// }
///
/// // [InitializeOnLoad] will call the static class constructor which
/// // we use to call Register.
/// #if UNITY_EDITOR
/// static JitterProcessor()
/// {
/// Register();
/// }
/// #endif
///
/// // [RuntimeInitializeOnLoadMethod] will make sure that Register gets called
/// // in the player on startup.
/// // NOTE: This will also get called when going into play mode in the editor. In that
/// // case we get two calls to Register instead of one. We don't bother with that
/// // here. Calling RegisterProcessor twice here doesn't do any harm.
/// [RuntimeInitializeOnLoadMethod]
/// static void Register()
/// {
/// // We don't supply a name here. The input system will take "JitterProcessor"
/// // and automatically snip off the "Processor" suffix thus leaving us with
/// // a name of "Jitter" (all this is case-insensitive).
/// InputSystem.RegisterProcessor<JitterProcessor>();
/// }
/// }
///
/// // It doesn't really make sense in our case as the default parameter editor is just
/// // fine (it will pick up the tooltip we defined above) but let's say we want to replace
/// // the default float edit field we get on the "amount" parameter with a slider. We can
/// // do so by defining a custom parameter editor.
/// //
/// // NOTE: We don't need to have a registration call here. The input system will automatically
/// // find our parameter editor based on the JitterProcessor type parameter we give to
/// // InputParameterEditor<T>.
/// #if UNITY_EDITOR
/// public class JitterProcessorEditor : InputParameterEditor<JitterProcessor>
/// {
/// public override void OnGUI()
/// {
/// target.amount = EditorGUILayout.Slider(m_AmountLabel, target.amount, 0, 0.25f);
/// }
///
/// private GUIContent m_AmountLabel = new GUIContent("Amount",
/// "Amount of jitter to apply. Will add a random value in the range [-amount..amount] "
/// + "to each input value.);
/// }
/// #endif
/// </code>
/// </example>
///
/// Note that it is allowed to register the same processor type multiple types with
/// different names. When doing so, the first registration is considered as the "proper"
/// name for the processor and all subsequent registrations will be considered aliases.
///
/// See the <a href="../manual/Processors.html">manual</a> for more details.
/// </remarks>
/// <seealso cref="InputProcessor{T}"/>
/// <seealso cref="InputBinding.processors"/>
/// <seealso cref="InputAction.processors"/>
/// <seealso cref="InputControlLayout.ControlItem.processors"/>
/// <seealso cref="UnityEngine.InputSystem.Editor.InputParameterEditor{TObject}"/>