File size: 74,597 Bytes
18a519f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using UnityEngine.InputSystem.Controls;
using UnityEngine.InputSystem.LowLevel;
using UnityEngine.InputSystem.Utilities;
using Unity.Collections.LowLevel.Unsafe;
using UnityEngine.InputSystem.Layouts;

////REVIEW: should EvaluateMagnitude() be called EvaluateActuation() or something similar?

////REVIEW: as soon as we gain the ability to have blittable type constraints, InputControl<TValue> should be constrained such

////REVIEW: Reading and writing is asymmetric. Writing does not involve processors, reading does.

////REVIEW: While the arrays used by controls are already nicely centralized on InputDevice, InputControls still
////        hold a bunch of reference data that requires separate scanning. Can we move *all* reference data to arrays
////        on InputDevice and make InputControls reference-free? Most challenging thing probably is getting rid of
////        the InputDevice reference itself.

////REVIEW: how do we do stuff like smoothing over time?

////TODO: allow easier access to the default state such that you can easily create a state event containing only default state

////TODO: come up with a way where we do ReadValue on the most common forms/setups of controls and not have any virtual method dispatch but
////      rather go with minimal overhead directly to reading out memory
////      (this should at least cover FLT, single BIT, and INT controls; and should be able to apply the common transformations
////      as per AxisControl)

namespace UnityEngine.InputSystem
{
    /// <summary>
    /// A typed and named source of input values in a hierarchy of controls.
    /// </summary>
    /// <remarks>
    /// Controls can have children which in turn may have children. At the root of the child
    /// hierarchy is always an <see cref="InputDevice"/> (which themselves are InputControls).
    ///
    /// Controls can be looked up by their <see cref="path"/> (see <see cref="InputControlPath.TryFindControl"/>).
    ///
    /// Each control must have a unique <see cref="name"/> within the <see cref="children"/> of
    /// its <see cref="parent"/>. Multiple names can be assigned to controls using aliases (see
    /// <see cref="aliases"/>). Name lookup is case-insensitive.
    ///
    /// For display purposes, a control may have a separate <see cref="displayName"/>. This name
    /// will usually correspond to what the control is caused on the actual underlying hardware.
    /// For example, on an Xbox gamepad, the control with the name "buttonSouth" will have a display
    /// name of "A". Controls that have very long display names may also have a <see cref="shortDisplayName"/>.
    /// This is the case for the "Left Button" on the <see cref="Mouse"/>, for example, which is
    /// commonly abbreviated "LMB".
    ///
    /// In addition to names, a control may have usages associated with it (see <see cref="usages"/>).
    /// A usage indicates how a control is meant to be used. For example, a button can be assigned
    /// the "PrimaryAction" usage to indicate it is the primary action button the device. Within a
    /// device, usages have to be unique. See <see cref="CommonUsages"/> for a list of standardized usages.
    ///
    /// Controls do not actually store values. Instead, every control receives an <see cref="InputStateBlock"/>
    /// which, after the control's device has been added to the system, is used to read out values
    /// from the device's backing store. This backing store is referred to as "state" in the API
    /// as opposed to "values" which represent the data resulting from reading state. The format that
    /// each control stores state in is specific to the control. It can vary not only between controls
    /// of different types but also between controls of the same type. An <see cref="AxisControl"/>,
    /// for example, can be stored as a float or as a byte or in a number of other formats. <see cref="stateBlock"/>
    /// identifies both where the control stores its state as well as the format it stores it in.
    ///
    /// Controls are generally not created directly but are created internally by the input system
    /// from data known as "layouts" (see <see cref="InputControlLayout"/>). Each such layout describes
    /// the setup of a specific hierarchy of controls. The system internally maintains a registry of
    /// layouts and produces devices and controls from them as needed. The layout that a control has
    /// been created from can be queried using <see cref="layout"/>. For most purposes, the intricacies
    /// of the control layout mechanisms can be ignored and it is sufficient to know the names of a
    /// small set of common device layouts such as "Keyboard", "Mouse", "Gamepad", and "Touchscreen".
    ///
    /// Each control has a single, fixed value type. The type can be queried at runtime using
    /// <see cref="valueType"/>. Most types of controls are derived from <see cref="InputControl{TValue}"/>
    /// which has APIs specific to the type of value of the control (e.g. <see cref="InputControl{TValue}.ReadValue()"/>.
    ///
    /// The following example demonstrates various common operations performed on input controls:
    ///
    /// <example>
    /// <code>
    /// // Look up dpad/up control on current gamepad.
    /// var dpadUpControl = Gamepad.current["dpad/up"];
    ///
    /// // Look up the back button on the current gamepad.
    /// var backButton = Gamepad.current["{Back}"];
    ///
    /// // Look up all dpad/up controls on all gamepads in the system.
    /// using (var controls = InputSystem.FindControls("&lt;Gamepad&gt;/dpad/up"))
    ///     Debug.Log($"Found {controls.Count} controls");
    ///
    /// // Display the value of all controls on the current gamepad.
    /// foreach (var control in Gamepad.current.allControls)
    ///     Debug.Log(controls.ReadValueAsObject());
    ///
    /// // Track the value of the left stick on the current gamepad over time.
    /// var leftStickHistory = new InputStateHistory(Gamepad.current.leftStick);
    /// leftStickHistory.Enable();
    /// </code>
    /// </example>
    /// <example>
    /// </example>
    /// </remarks>
    /// <see cref="InputControl{TValue}"/>
    /// <seealso cref="InputDevice"/>
    /// <seealso cref="InputControlPath"/>
    /// <seealso cref="InputStateBlock"/>
    [DebuggerDisplay("{DebuggerDisplay(),nq}")]
    public abstract class InputControl
    {
        /// <summary>
        /// The name of the control, i.e. the final name part in its path.
        /// </summary>
        /// <remarks>
        /// Names of controls must be unique within the context of their parent.
        ///
        /// Note that this is the name of the control as assigned internally (like "buttonSouth")
        /// and not necessarily a good display name. Use <see cref="displayName"/> for
        /// getting more readable names for display purposes (where available).
        ///
        /// Lookup of names is case-insensitive.
        ///
        /// This is set from the name of the control in the layout.
        /// </remarks>
        /// <seealso cref="path"/>
        /// <seealso cref="aliases"/>
        /// <seealso cref="InputControlAttribute.name"/>
        /// <seealso cref="InputControlLayout.ControlItem.name"/>
        public string name => m_Name;

        ////TODO: protect against empty strings
        /// <summary>
        /// The text to display as the name of the control.
        /// </summary>
        /// <remarks>
        /// Note that the display name of a control may change over time. For example, when changing
        /// from a QWERTY keyboard layout to an AZERTY keyboard layout, the "q" key (which will keep
        /// that <see cref="name"/>) will change its display name from "q" to "a".
        ///
        /// By default, a control's display name will come from its layout. If it is not assigned
        /// a display name there, the display name will default to <see cref="name"/>. However, specific
        /// controls may override this behavior. <see cref="KeyControl"/>, for example, will set the
        /// display name to the actual key name corresponding to the current keyboard layout.
        ///
        /// For nested controls, the display name will include the display names of all parent controls,
        /// i.e. the display name will fully identify the control on the device. For example, the display
        /// name for the left D-Pad button on a gamepad is "D-Pad Left" and not just "Left".
        /// </remarks>
        /// <seealso cref="shortDisplayName"/>
        public string displayName
        {
            get
            {
                RefreshConfigurationIfNeeded();
                if (m_DisplayName != null)
                    return m_DisplayName;
                if (m_DisplayNameFromLayout != null)
                    return m_DisplayNameFromLayout;
                return m_Name;
            }
            // This is not public as a domain reload will wipe the change. This should really
            // come from the control itself *if* the control wants to have a custom display name
            // not driven by its layout.
            protected set => m_DisplayName = value;
        }

        /// <summary>
        /// An alternate, abbreviated <see cref="displayName"/> (for example "LMB" instead of "Left Button").
        /// </summary>
        /// <remarks>
        /// If the control has no abbreviated version, this will be null. Note that this behavior is different
        /// from <see cref="displayName"/> which will fall back to <see cref="name"/> if no display name has
        /// been assigned to the control.
        ///
        /// For nested controls, the short display name will include the short display names of all parent controls,
        /// that is, the display name will fully identify the control on the device. For example, the display
        /// name for the left D-Pad button on a gamepad is "D-Pad \u2190" and not just "\u2190". Note that if a parent
        /// control has no short name, its long name will be used instead.
        /// </remarks>
        /// <seealso cref="displayName"/>
        public string shortDisplayName
        {
            get
            {
                RefreshConfigurationIfNeeded();
                if (m_ShortDisplayName != null)
                    return m_ShortDisplayName;
                if (m_ShortDisplayNameFromLayout != null)
                    return m_ShortDisplayNameFromLayout;
                return null;
            }
            protected set => m_ShortDisplayName = value;
        }

        /// <summary>
        /// Full path all the way from the root.
        /// </summary>
        /// <remarks>
        /// This will always be the "effective" path of the control, i.e. it will not contain
        /// elements such as usages (<c>"{Back}"</c>) and other elements that can be part of
        /// control paths used for matching. Instead, this property will always be a simple
        /// linear ordering of names leading from the device at the top to the control with each
        /// element being separated by a forward slash (<c>/</c>).
        ///
        /// Allocates on first hit. Paths are not created until someone asks for them.
        ///
        /// <example>
        /// Example: "/gamepad/leftStick/x"
        /// </example>
        /// </remarks>
        /// <seealso cref="InputControlPath"/>
        public string path
        {
            get
            {
                if (m_Path == null)
                    m_Path = InputControlPath.Combine(m_Parent, m_Name);
                return m_Path;
            }
        }

        /// <summary>
        /// Layout the control is based on.
        /// </summary>
        /// <remarks>
        /// This is the layout name rather than a reference to an <see cref="InputControlLayout"/> as
        /// we only create layout instances during device creation and treat them
        /// as temporaries in general so as to not waste heap space during normal operation.
        /// </remarks>
        public string layout => m_Layout;

        /// <summary>
        /// Semicolon-separated list of variants of the control layout or "default".
        /// </summary>
        /// <example>
        /// "Lefty" when using the "Lefty" gamepad layout.
        /// </example>
        public string variants => m_Variants;

        /// <summary>
        /// The device that this control is a part of.
        /// </summary>
        /// <remarks>
        /// This is the root of the control hierarchy. For the device at the root, this
        /// will point to itself.
        /// </remarks>
        /// <seealso cref="InputDevice.allControls"/>
        public InputDevice device => m_Device;

        /// <summary>
        /// The immediate parent of the control or null if the control has no parent
        /// (which, once fully constructed) will only be the case for InputDevices).
        /// </summary>
        /// <seealso cref="children"/>
        public InputControl parent => m_Parent;

        /// <summary>
        /// List of immediate children.
        /// </summary>
        /// <remarks>
        /// Does not allocate.
        /// </remarks>
        /// <seealso cref="parent"/>
        public ReadOnlyArray<InputControl> children =>
            new ReadOnlyArray<InputControl>(m_Device.m_ChildrenForEachControl, m_ChildStartIndex, m_ChildCount);

        /// <summary>
        /// List of usage tags associated with the control.
        /// </summary>
        /// <remarks>
        /// Usages apply "semantics" to a control. Whereas the name of a control identifies a particular
        /// "endpoint" within the control hierarchy, the usages of a control identify particular roles
        /// of specific control. A simple example is <see cref="CommonUsages.Back"/> which identifies a
        /// control generally used to move backwards in the navigation history of a UI. On a keyboard,
        /// it is the escape key that generally fulfills this role whereas on a gamepad, it is generally
        /// the "B" / "Circle" button. Some devices may not have a control that generally fulfills this
        /// function and thus may not have any control with the "Back" usage.
        ///
        /// By looking up controls by usage rather than by name, it is possible to locate the correct
        /// control to use for certain standardized situation without having to know the particulars of
        /// the device or platform.
        ///
        /// <example>
        /// <code>
        /// // Bind to any control which is tagged with the "Back" usage on any device.
        /// var backAction = new InputAction(binding: "*/{Back}");
        /// </code>
        /// </example>
        ///
        /// Note that usages on devices work slightly differently than usages of controls on devices.
        /// They are also queried through this property but unlike the usages of controls, the set of
        /// usages of a device can be changed dynamically as the role of the device changes. For details,
        /// see <see cref="InputSystem.SetDeviceUsage(InputDevice,string)"/>. Controls, on the other hand,
        /// can currently only be assigned usages through layouts (<see cref="InputControlAttribute.usage"/>
        /// or <see cref="InputControlAttribute.usages"/>).
        /// </remarks>
        /// <seealso cref="InputControlAttribute.usage"/>
        /// <seealso cref="InputControlAttribute.usages"/>
        /// <seealso cref="InputSystem.SetDeviceUsage(InputDevice,string)"/>
        /// <seealso cref="InputSystem.AddDeviceUsage(InputDevice,string)"/>
        /// <seealso cref="InputSystem.RemoveDeviceUsage(InputDevice,string)"/>
        /// <seealso cref="CommonUsages"/>
        public ReadOnlyArray<InternedString> usages =>
            new ReadOnlyArray<InternedString>(m_Device.m_UsagesForEachControl, m_UsageStartIndex, m_UsageCount);

        // List of alternate names for the control.
        public ReadOnlyArray<InternedString> aliases =>
            new ReadOnlyArray<InternedString>(m_Device.m_AliasesForEachControl, m_AliasStartIndex, m_AliasCount);

        // Information about where the control stores its state.
        public InputStateBlock stateBlock => m_StateBlock;

        /// <summary>
        /// Whether the control is considered noisy.
        /// </summary>
        /// <value>True if the control produces noisy input.</value>
        /// <remarks>
        /// A control is considered "noisy" if it produces different values without necessarily requiring user
        /// interaction. A good example are sensors (see <see cref="Sensor"/>). For example, the PS4 controller
        /// which has a gyroscope sensor built into the device. Whereas sticks and buttons on the device require
        /// user interaction to produce non-default values, the gyro will produce varying values even if the
        /// device just sits there without user interaction.
        ///
        /// The value of this property is determined by the layout (<see cref="InputControlLayout"/>) that the
        /// control has been built from.
        ///
        /// Note that for devices (<see cref="InputDevice"/>) this property is true if any control on the device
        /// is marked as noisy.
        ///
        /// The primary effect of being noise is on <see cref="InputDevice.MakeCurrent"/> and
        /// on interactive rebinding (see <see cref="InputActionRebindingExtensions.RebindingOperation"/>).
        /// However, being noisy also affects automatic resetting of controls that happens when the application
        /// loses focus. While other controls are reset to their default value (except if <c>Application.runInBackground</c>
        /// is true and the device the control belongs to is marked as <see cref="InputDevice.canRunInBackground"/>),
        /// noisy controls will not be reset but rather remain at their current value. This is based on the assumption
        /// that noisy controls most often represent sensor values and snapping the last sampling value back to default
        /// will usually have undesirable effects on an application's simulation logic.
        /// </remarks>
        /// <seealso cref="InputControlLayout.ControlItem.isNoisy"/>
        /// <seealso cref="InputControlAttribute.noisy"/>
        public bool noisy
        {
            get => (m_ControlFlags & ControlFlags.IsNoisy) != 0;
            internal set
            {
                if (value)
                {
                    m_ControlFlags |= ControlFlags.IsNoisy;
                    // Making a control noisy makes all its children noisy.
                    var list = children;
                    for (var i = 0; i < list.Count; ++i)
                    {
                        if (null != list[i])
                            list[i].noisy = true;
                    }
                }
                else
                    m_ControlFlags &= ~ControlFlags.IsNoisy;
            }
        }

        /// <summary>
        /// Whether the control is considered synthetic.
        /// </summary>
        /// <value>True if the control does not represent an actual physical control on the device.</value>
        /// <remarks>
        /// A control is considered "synthetic" if it does not correspond to an actual, physical control on the
        /// device. An example for this is <see cref="Keyboard.anyKey"/> or the up/down/left/right buttons added
        /// by <see cref="StickControl"/>.
        ///
        /// The value of this property is determined by the layout (<see cref="InputControlLayout"/>) that the
        /// control has been built from.
        ///
        /// The primary effect of being synthetic is in interactive rebinding (see
        /// <see cref="InputActionRebindingExtensions.RebindingOperation"/>) where non-synthetic
        /// controls will be favored over synthetic ones. This means, for example, that if both
        /// <c>"&lt;Gamepad&gt;/leftStick/x"</c> and <c>"&lt;Gamepad&gt;/leftStick/left"</c> are
        /// suitable picks, <c>"&lt;Gamepad&gt;/leftStick/x"</c> will be favored as it represents
        /// input from an actual physical control whereas <c>"&lt;Gamepad&gt;/leftStick/left"</c>
        /// represents input from a made-up control. If, however, the "left" button is the only
        /// viable pick, it will be accepted.
        /// </remarks>
        /// <seealso cref="InputControlLayout.ControlItem.isSynthetic"/>
        /// <seealso cref="InputControlAttribute.synthetic"/>
        public bool synthetic
        {
            get => (m_ControlFlags & ControlFlags.IsSynthetic) != 0;
            internal set
            {
                if (value)
                    m_ControlFlags |= ControlFlags.IsSynthetic;
                else
                    m_ControlFlags &= ~ControlFlags.IsSynthetic;
            }
        }

        /// <summary>
        /// Fetch a control from the control's hierarchy by name.
        /// </summary>
        /// <remarks>
        /// Note that path matching is case-insensitive.
        /// </remarks>
        /// <example>
        /// <code>
        /// gamepad["leftStick"] // Returns Gamepad.leftStick
        /// gamepad["leftStick/x"] // Returns Gamepad.leftStick.x
        /// gamepad["{PrimaryAction}"] // Returns the control with PrimaryAction usage, that is, Gamepad.aButton
        /// </code>
        /// </example>
        /// <exception cref="KeyNotFoundException"><paramref name="path"/> cannot be found.</exception>
        /// <seealso cref="InputControlPath"/>
        /// <seealso cref="path"/>
        /// <seealso cref="TryGetChildControl"/>
        public InputControl this[string path]
        {
            get
            {
                var control = InputControlPath.TryFindChild(this, path);
                if (control == null)
                    throw new KeyNotFoundException(
                        $"Cannot find control '{path}' as child of '{this}'");
                return control;
            }
        }

        /// <summary>
        /// Returns the underlying value type of this control.
        /// </summary>
        /// <value>Type of values produced by the control.</value>
        /// <remarks>
        /// This is the type of values that are returned when reading the current value of a control
        /// or when reading a value of a control from an event.
        /// </remarks>
        /// <seealso cref="valueSizeInBytes"/>
        /// <seealso cref="ReadValueFromStateAsObject"/>
        public abstract Type valueType { get; }

        /// <summary>
        /// Size in bytes of values that the control returns.
        /// </summary>
        /// <seealso cref="valueType"/>
        public abstract int valueSizeInBytes { get; }

        /// <summary>
        /// Compute an absolute, normalized magnitude value that indicates the extent to which the control
        /// is actuated. Shortcut for <see cref="EvaluateMagnitude()"/>.
        /// </summary>
        /// <returns>Amount of actuation of the control or -1 if it cannot be determined.</returns>
        /// <seealso cref="EvaluateMagnitude(void*)"/>
        /// <seealso cref="EvaluateMagnitude()"/>
        public float magnitude => EvaluateMagnitude();

        /// <summary>
        /// Return a string representation of the control useful for debugging.
        /// </summary>
        /// <returns>A string representation of the control.</returns>
        public override string ToString()
        {
            return $"{layout}:{path}";
        }

        private string DebuggerDisplay()
        {
            // If the device hasn't been added, don't try to read the control's value.
            if (!device.added)
                return ToString();

            // ReadValueAsObject might throw. Revert to just ToString() in that case.
            try
            {
                return $"{layout}:{path}={this.ReadValueAsObject()}";
            }
            catch (Exception)
            {
                return ToString();
            }
        }

        ////REVIEW: The -1 behavior seems bad; probably better to just return 1 for controls that do not support finer levels of actuation
        /// <summary>
        /// Compute an absolute, normalized magnitude value that indicates the extent to which the control
        /// is actuated.
        /// </summary>
        /// <returns>Amount of actuation of the control or -1 if it cannot be determined.</returns>
        /// <remarks>
        /// Magnitudes do not make sense for all types of controls. For example, for a control that represents
        /// an enumeration of values (such as <see cref="TouchPhaseControl"/>), there is no meaningful
        /// linear ordering of values (one could derive a linear ordering through the actual enum values but
        /// their assignment may be entirely arbitrary; it is unclear whether a state of <see cref="TouchPhase.Canceled"/>
        /// has a higher or lower "magnitude" as a state of <see cref="TouchPhase.Began"/>).
        ///
        /// Controls that have no meaningful magnitude will return -1 when calling this method. Any negative
        /// return value should be considered an invalid value.
        /// </remarks>
        /// <seealso cref="EvaluateMagnitude(void*)"/>
        public unsafe float EvaluateMagnitude()
        {
            return EvaluateMagnitude(currentStatePtr);
        }

        /// <summary>
        /// Compute an absolute, normalized magnitude value that indicates the extent to which the control
        /// is actuated in the given state.
        /// </summary>
        /// <param name="statePtr">State containing the control's <see cref="stateBlock"/>.</param>
        /// <returns>Amount of actuation of the control or -1 if it cannot be determined.</returns>
        /// <seealso cref="EvaluateMagnitude()"/>
        /// <seealso cref="stateBlock"/>
        public virtual unsafe float EvaluateMagnitude(void* statePtr)
        {
            return -1;
        }

        public abstract unsafe object ReadValueFromBufferAsObject(void* buffer, int bufferSize);

        /// <summary>
        /// Read the control's final, processed value from the given state and return the value as an object.
        /// </summary>
        /// <param name="statePtr"></param>
        /// <returns>The control's value as stored in <paramref name="statePtr"/>.</returns>
        /// <remarks>
        /// This method allocates GC memory and should not be used during normal gameplay operation.
        /// </remarks>
        /// <exception cref="ArgumentNullException"><paramref name="statePtr"/> is null.</exception>
        /// <seealso cref="ReadValueFromStateIntoBuffer"/>
        public abstract unsafe object ReadValueFromStateAsObject(void* statePtr);

        /// <summary>
        /// Read the control's final, processed value from the given state and store it in the given buffer.
        /// </summary>
        /// <param name="statePtr">State to read the value for the control from.</param>
        /// <param name="bufferPtr">Buffer to store the value in.</param>
        /// <param name="bufferSize">Size of <paramref name="bufferPtr"/> in bytes. Must be at least <see cref="valueSizeInBytes"/>.
        /// If it is smaller, <see cref="ArgumentException"/> will be thrown.</param>
        /// <exception cref="ArgumentNullException"><paramref name="statePtr"/> is null, or <paramref name="bufferPtr"/> is null.</exception>
        /// <exception cref="ArgumentException"><paramref name="bufferSize"/> is smaller than <see cref="valueSizeInBytes"/>.</exception>
        /// <seealso cref="ReadValueFromStateAsObject"/>
        /// <seealso cref="WriteValueFromBufferIntoState"/>
        public abstract unsafe void ReadValueFromStateIntoBuffer(void* statePtr, void* bufferPtr, int bufferSize);

        /// <summary>
        /// Read a value from the given memory and store it as state.
        /// </summary>
        /// <param name="bufferPtr">Memory containing value.</param>
        /// <param name="bufferSize">Size of <paramref name="bufferPtr"/> in bytes. Must be at least <see cref="valueSizeInBytes"/>.</param>
        /// <param name="statePtr">State containing the control's <see cref="stateBlock"/>. Will receive the state
        /// as converted from the given value.</param>
        /// <remarks>
        /// Writing values will NOT apply processors to the given value. This can mean that when reading a value
        /// from a control after it has been written to its state, the resulting value differs from what was
        /// written.
        /// </remarks>
        /// <exception cref="NotSupportedException">The control does not support writing. This is the case, for
        /// example, that compute values (such as the magnitude of a vector).</exception>
        /// <seealso cref="ReadValueFromStateIntoBuffer"/>
        /// <seealso cref="WriteValueFromObjectIntoState"/>
        public virtual unsafe void WriteValueFromBufferIntoState(void* bufferPtr, int bufferSize, void* statePtr)
        {
            throw new NotSupportedException(
                $"Control '{this}' does not support writing");
        }

        /// <summary>
        /// Read a value object and store it as state in the given memory.
        /// </summary>
        /// <param name="value">Value for the control.</param>
        /// <param name="statePtr">State containing the control's <see cref="stateBlock"/>. Will receive
        /// the state state as converted from the given value.</param>
        /// <remarks>
        /// Writing values will NOT apply processors to the given value. This can mean that when reading a value
        /// from a control after it has been written to its state, the resulting value differs from what was
        /// written.
        /// </remarks>
        /// <exception cref="NotSupportedException">The control does not support writing. This is the case, for
        /// example, that compute values (such as the magnitude of a vector).</exception>
        /// <seealso cref="WriteValueFromBufferIntoState"/>
        public virtual unsafe void WriteValueFromObjectIntoState(object value, void* statePtr)
        {
            throw new NotSupportedException(
                $"Control '{this}' does not support writing");
        }

        /// <summary>
        /// Compare the value of the control as read from <paramref name="firstStatePtr"/> to that read from
        /// <paramref name="secondStatePtr"/> and return true if they are equal.
        /// </summary>
        /// <param name="firstStatePtr">Memory containing the control's <see cref="stateBlock"/>.</param>
        /// <param name="secondStatePtr">Memory containing the control's <see cref="stateBlock"/></param>
        /// <returns>True if the value of the control is equal in both <paramref name="firstStatePtr"/> and
        /// <paramref name="secondStatePtr"/>.</returns>
        /// <remarks>
        /// Unlike <see cref="CompareValue"/>, this method will have to do more than just compare the memory
        /// for the control in the two state buffers. It will have to read out state for the control and run
        /// the full processing machinery for the control to turn the state into a final, processed value.
        /// CompareValue is thus more costly than <see cref="CompareValue"/>.
        ///
        /// This method will apply epsilons (<see cref="Mathf.Epsilon"/>) when comparing floats.
        /// </remarks>
        /// <seealso cref="CompareValue"/>
        public abstract unsafe bool CompareValue(void* firstStatePtr, void* secondStatePtr);

        /// <summary>
        /// Try to find a child control matching the given path.
        /// </summary>
        /// <param name="path">A control path. See <see cref="InputControlPath"/>.</param>
        /// <returns>The first direct or indirect child control that matches the given <paramref name="path"/>
        /// or null if no control was found to match.</returns>
        /// <exception cref="ArgumentNullException"><paramref name="path"/> is <c>null</c> or empty.</exception>
        /// <remarks>
        /// Note that if the given path matches multiple child controls, only the first control
        /// encountered in the search will be returned.
        ///
        /// <example>
        /// <code>
        /// // Returns the leftStick control of the current gamepad.
        /// Gamepad.current.TryGetChildControl("leftStick");
        ///
        /// // Returns the X axis control of the leftStick on the current gamepad.
        /// Gamepad.current.TryGetChildControl("leftStick/x");
        ///
        /// // Returns the first control ending with "stick" in its name. Note that it
        /// // undetermined whether this is leftStick or rightStick (or even another stick
        /// // added by the given gamepad).
        /// Gamepad.current.TryGetChildControl("*stick");
        /// </code>
        /// </example>
        ///
        /// This method is equivalent to calling <see cref="InputControlPath.TryFindChild"/>.
        /// </remarks>
        public InputControl TryGetChildControl(string path)
        {
            if (string.IsNullOrEmpty(path))
                throw new ArgumentNullException(nameof(path));
            return InputControlPath.TryFindChild(this, path);
        }

        public TControl TryGetChildControl<TControl>(string path)
            where TControl : InputControl
        {
            if (string.IsNullOrEmpty(path))
                throw new ArgumentNullException(nameof(path));

            var control = TryGetChildControl(path);
            if (control == null)
                return null;

            var controlOfType = control as TControl;
            if (controlOfType == null)
                throw new InvalidOperationException(
                    $"Expected control '{path}' to be of type '{typeof(TControl).Name}' but is of type '{control.GetType().Name}' instead!");

            return controlOfType;
        }

        public InputControl GetChildControl(string path)
        {
            if (string.IsNullOrEmpty(path))
                throw new ArgumentNullException(nameof(path));

            var control = TryGetChildControl(path);
            if (control == null)
                throw new ArgumentException($"Cannot find input control '{MakeChildPath(path)}'", nameof(path));

            return control;
        }

        public TControl GetChildControl<TControl>(string path)
            where TControl : InputControl
        {
            var control = GetChildControl(path);

            if (!(control is TControl controlOfType))
                throw new ArgumentException(
                    $"Expected control '{path}' to be of type '{typeof(TControl).Name}' but is of type '{control.GetType().Name}' instead!", nameof(path));

            return controlOfType;
        }

        protected InputControl()
        {
            // Set defaults for state block setup. Subclasses may override.
            m_StateBlock.byteOffset = InputStateBlock.AutomaticOffset; // Request automatic layout by default.
        }

        /// <summary>
        /// Perform final initialization tasks after the control hierarchy has been put into place.
        /// </summary>
        /// <remarks>
        /// This method can be overridden to perform control- or device-specific setup work. The most
        /// common use case is for looking up child controls and storing them in local getters.
        ///
        /// <example>
        /// <code>
        /// public class MyDevice : InputDevice
        /// {
        ///     public ButtonControl button { get; private set; }
        ///     public AxisControl axis { get; private set; }
        ///
        ///     protected override void OnFinishSetup()
        ///     {
        ///         // Cache controls in getters.
        ///         button = GetChildControl("button");
        ///         axis = GetChildControl("axis");
        ///     }
        /// }
        /// </code>
        /// </example>
        /// </remarks>
        protected virtual void FinishSetup()
        {
        }

        /// <summary>
        /// Call <see cref="RefreshConfiguration"/> if the configuration has in the interim been invalidated
        /// by a <see cref="DeviceConfigurationEvent"/>.
        /// </summary>
        /// <remarks>
        /// This method is only relevant if you are implementing your own devices or new
        /// types of controls which are fetching configuration data from the devices (such
        /// as <see cref="KeyControl"/> which is fetching display names for individual keys
        /// from the underlying platform).
        ///
        /// This method should be called if you are accessing cached data set up by
        /// <see cref="RefreshConfiguration"/>.
        ///
        /// <example>
        /// <code>
        /// // Let's say your device has an associated orientation which it can be held with
        /// // and you want to surface both as a property and as a usage on the device.
        /// // Whenever your backend code detects a change in orientation, it should send
        /// // a DeviceConfigurationEvent to your device to signal that the configuration
        /// // of the device has changed. You can then implement RefreshConfiguration() to
        /// // read out and update the device orientation on the managed InputDevice instance.
        /// public class MyDevice : InputDevice
        /// {
        ///     public enum Orientation
        ///     {
        ///         Horizontal,
        ///         Vertical,
        ///     }
        ///
        ///     private Orientation m_Orientation;
        ///     public Orientation orientation
        ///     {
        ///         get
        ///         {
        ///             // Call RefreshOrientation if the configuration of the device has been
        ///             // invalidated since last time we initialized m_Orientation.
        ///             RefreshConfigurationIfNeeded();
        ///             return m_Orientation;
        ///         }
        ///     }
        ///     protected override void RefreshConfiguration()
        ///     {
        ///         // Fetch the current orientation from the backend. How you do this
        ///         // depends on your device. Using DeviceCommands is one way.
        ///         var fetchOrientationCommand = new FetchOrientationCommand();
        ///         ExecuteCommand(ref fetchOrientationCommand);
        ///         m_Orientation = fetchOrientation;
        ///
        ///         // Reflect the orientation on the device.
        ///         switch (m_Orientation)
        ///         {
        ///             case Orientation.Vertical:
        ///                 InputSystem.RemoveDeviceUsage(this, s_Horizontal);
        ///                 InputSystem.AddDeviceUsage(this, s_Vertical);
        ///                 break;
        ///
        ///             case Orientation.Horizontal:
        ///                 InputSystem.RemoveDeviceUsage(this, s_Vertical);
        ///                 InputSystem.AddDeviceUsage(this, s_Horizontal);
        ///                 break;
        ///         }
        ///     }
        ///
        ///     private static InternedString s_Vertical = new InternedString("Vertical");
        ///     private static InternedString s_Horizontal = new InternedString("Horizontal");
        /// }
        /// </code>
        /// </example>
        /// </remarks>
        /// <seealso cref="RefreshConfiguration"/>
        protected void RefreshConfigurationIfNeeded()
        {
            if (!isConfigUpToDate)
            {
                RefreshConfiguration();
                isConfigUpToDate = true;
            }
        }

        protected virtual void RefreshConfiguration()
        {
        }

        ////TODO: drop protected access
        protected internal InputStateBlock m_StateBlock;

        ////REVIEW: shouldn't these sit on the device?
        protected internal unsafe void* currentStatePtr => InputStateBuffers.GetFrontBufferForDevice(GetDeviceIndex());

        protected internal unsafe void* previousFrameStatePtr => InputStateBuffers.GetBackBufferForDevice(GetDeviceIndex());

        protected internal unsafe void* defaultStatePtr => InputStateBuffers.s_DefaultStateBuffer;

        /// <summary>
        /// Return the memory that holds the noise mask for the control.
        /// </summary>
        /// <value>Noise bit mask for the control.</value>
        /// <remarks>
        /// Like with all state blocks, the specific memory block for the control is found at the memory
        /// region specified by <see cref="stateBlock"/>.
        ///
        /// The noise mask can be overlaid as a bit mask over the state for the control. When doing so, all state
        /// that is noise will be masked out whereas all state that isn't will come through unmodified. In other words,
        /// any bit that is set in the noise mask indicates that the corresponding bit in the control's state memory
        /// is noise.
        /// </remarks>
        /// <seealso cref="noisy"/>
        protected internal unsafe void* noiseMaskPtr => InputStateBuffers.s_NoiseMaskBuffer;

        /// <summary>
        /// The offset of this control's state relative to its device root.
        /// </summary>
        /// <remarks>
        /// Once a device has been added to the system, its state block will get allocated
        /// in the global state buffers and the offset of the device's state block will
        /// get baked into all of the controls on the device. This property always returns
        /// the "unbaked" offset.
        /// </remarks>
        protected internal uint stateOffsetRelativeToDeviceRoot
        {
            get
            {
                var deviceStateOffset = device.m_StateBlock.byteOffset;
                Debug.Assert(deviceStateOffset <= m_StateBlock.byteOffset);
                return m_StateBlock.byteOffset - deviceStateOffset;
            }
        }

        // This data is initialized by InputDeviceBuilder.
        internal InternedString m_Name;
        internal string m_Path;
        internal string m_DisplayName; // Display name set by the control itself (may be null).
        internal string m_DisplayNameFromLayout; // Display name coming from layout (may be null).
        internal string m_ShortDisplayName; // Short display name set by the control itself (may be null).
        internal string m_ShortDisplayNameFromLayout; // Short display name coming from layout (may be null).
        internal InternedString m_Layout;
        internal InternedString m_Variants;
        internal InputDevice m_Device;
        internal InputControl m_Parent;
        internal int m_UsageCount;
        internal int m_UsageStartIndex;
        internal int m_AliasCount;
        internal int m_AliasStartIndex;
        internal int m_ChildCount;
        internal int m_ChildStartIndex;
        internal ControlFlags m_ControlFlags;

        // Value caching
        // These values will be set to true during state updates if the control has actually changed value.
        // Set to true initially so default state will be returned on the first call
        internal bool m_CachedValueIsStale = true;
        internal bool m_UnprocessedCachedValueIsStale = true;

        ////REVIEW: store these in arrays in InputDevice instead?
        internal PrimitiveValue m_DefaultState;
        internal PrimitiveValue m_MinValue;
        internal PrimitiveValue m_MaxValue;

        internal FourCC m_OptimizedControlDataType;

        /// <summary>
        /// For some types of control you can safely read/write state memory directly
        /// which is much faster than calling ReadUnprocessedValueFromState/WriteValueIntoState.
        /// This method returns a type that you can use for reading/writing the control directly,
        /// or it returns InputStateBlock.kFormatInvalid if it's not possible for this type of control.
        /// </summary>
        /// <remarks>
        /// For example, AxisControl might be a "float" in state memory, and if no processing is applied during reading (e.g. no invert/scale/etc),
        /// then you could read it as float in memory directly without calling ReadUnprocessedValueFromState, which is faster.
        /// Additionally, if you have a Vector3Control which uses 3 AxisControls as consecutive floats in memory,
        /// you can cast the Vector3Control state memory directly to Vector3 without calling ReadUnprocessedValueFromState on x/y/z axes.
        ///
        /// The value returned for any given control is computed automatically by the Input System, when the control's setup configuration changes. <see cref="InputControl.CalculateOptimizedControlDataType"/>
        /// There are some parameter changes which don't trigger a configuration change (such as the clamp, invert, normalize, and scale parameters on AxisControl),
        /// so if you modify these, the optimized data type is not automatically updated. In this situation, you should manually update it by calling <see cref="InputControl.ApplyParameterChanges"/>.
        /// </remarks>
        public FourCC optimizedControlDataType => m_OptimizedControlDataType;

        /// <summary>
        /// Calculates and returns a optimized data type that can represent a control's value in memory directly.
        /// The value then is cached in <see cref="InputControl.optimizedControlDataType"/>.
        /// This method is for internal use only, you should not call this from your own code.
        /// </summary>
        protected virtual FourCC CalculateOptimizedControlDataType()
        {
            return InputStateBlock.kFormatInvalid;
        }

        /// <summary>
        /// Apply built-in parameters changes (e.g. <see cref="AxisControl.invert"/>, others), recompute <see cref="InputControl.optimizedControlDataType"/> for impacted controls and clear cached value.
        /// </summary>
        /// <remarks>
        /// </remarks>
        public void ApplyParameterChanges()
        {
            // First we go through all children of our own hierarchy
            SetOptimizedControlDataTypeRecursively();

            // Then we go through all parents up to the root, because our own change might influence their optimization status
            // e.g. let's say we have a tree where root is Vector3 and children are three AxisControl
            // And user is calling this method on AxisControl which goes from Float to NotOptimized.
            // Then we need to also transition Vector3 to NotOptimized as well.

            var currentParent = parent;
            while (currentParent != null)
            {
                currentParent.SetOptimizedControlDataType();
                currentParent = currentParent.parent;
            }

            // Also use this method to mark cached values as stale
            MarkAsStaleRecursively();
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private void SetOptimizedControlDataType()
        {
            // setting check need to be inline so we clear optimizations if setting is disabled after the fact
            m_OptimizedControlDataType = InputSettings.optimizedControlsFeatureEnabled
                ? CalculateOptimizedControlDataType()
                : (FourCC)InputStateBlock.kFormatInvalid;
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        internal void SetOptimizedControlDataTypeRecursively()
        {
            // Need to go depth-first because CalculateOptimizedControlDataType might depend on computed values of children
            if (m_ChildCount > 0)
            {
                foreach (var inputControl in children)
                    inputControl.SetOptimizedControlDataTypeRecursively();
            }

            SetOptimizedControlDataType();
        }

        // This function exists to warn users to start using ApplyParameterChanges for edge cases that were previously not intentionally supported,
        // where control properties suddenly change underneath us without us anticipating that.
        // This is mainly to AxisControl fields being public and capable of changing at any time even if we were not anticipated such a usage pattern.
        // Also it's not clear if InputControl.stateBlock.format can potentially change at any time, likely not.
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        // Only do this check in development builds and editor in hope that it will be sufficient to catch any misuse during development.
        [Conditional("DEVELOPMENT_BUILD"), Conditional("UNITY_EDITOR")]
        internal void EnsureOptimizationTypeHasNotChanged()
        {
            if (!InputSettings.optimizedControlsFeatureEnabled)
                return;

            var currentOptimizedControlDataType = CalculateOptimizedControlDataType();
            if (currentOptimizedControlDataType != optimizedControlDataType)
            {
                Debug.LogError(
                    $"Control '{name}' / '{path}' suddenly changed optimization state due to either format " +
                    $"change or control parameters change (was '{optimizedControlDataType}' but became '{currentOptimizedControlDataType}'), " +
                    "this hinders control hot path optimization, please call control.ApplyParameterChanges() " +
                    "after the changes to the control to fix this error.");

                // Automatically fix the issue
                // Note this function is only executed in editor and development builds
                m_OptimizedControlDataType = currentOptimizedControlDataType;
            }

            if (m_ChildCount > 0)
            {
                foreach (var inputControl in children)
                    inputControl.EnsureOptimizationTypeHasNotChanged();
            }
        }

        [Flags]
        internal enum ControlFlags
        {
            ConfigUpToDate = 1 << 0,
            IsNoisy = 1 << 1,
            IsSynthetic = 1 << 2,
            IsButton = 1 << 3,
            DontReset = 1 << 4,
            SetupFinished = 1 << 5, // Can't be modified once this is set.
            UsesStateFromOtherControl = 1 << 6,
        }

        internal bool isSetupFinished
        {
            get => (m_ControlFlags & ControlFlags.SetupFinished) == ControlFlags.SetupFinished;
            set
            {
                if (value)
                    m_ControlFlags |= ControlFlags.SetupFinished;
                else
                    m_ControlFlags &= ~ControlFlags.SetupFinished;
            }
        }

        internal bool isButton
        {
            get => (m_ControlFlags & ControlFlags.IsButton) == ControlFlags.IsButton;
            set
            {
                if (value)
                    m_ControlFlags |= ControlFlags.IsButton;
                else
                    m_ControlFlags &= ~ControlFlags.IsButton;
            }
        }

        internal bool isConfigUpToDate
        {
            get => (m_ControlFlags & ControlFlags.ConfigUpToDate) == ControlFlags.ConfigUpToDate;
            set
            {
                if (value)
                    m_ControlFlags |= ControlFlags.ConfigUpToDate;
                else
                    m_ControlFlags &= ~ControlFlags.ConfigUpToDate;
            }
        }

        internal bool dontReset
        {
            get => (m_ControlFlags & ControlFlags.DontReset) == ControlFlags.DontReset;
            set
            {
                if (value)
                    m_ControlFlags |= ControlFlags.DontReset;
                else
                    m_ControlFlags &= ~ControlFlags.DontReset;
            }
        }

        internal bool usesStateFromOtherControl
        {
            get => (m_ControlFlags & ControlFlags.UsesStateFromOtherControl) == ControlFlags.UsesStateFromOtherControl;
            set
            {
                if (value)
                    m_ControlFlags |= ControlFlags.UsesStateFromOtherControl;
                else
                    m_ControlFlags &= ~ControlFlags.UsesStateFromOtherControl;
            }
        }

        internal bool hasDefaultState => !m_DefaultState.isEmpty;

        // This method exists only to not slap the internal interaction on all overrides of
        // FinishSetup().
        internal void CallFinishSetupRecursive()
        {
            var list = children;
            for (var i = 0; i < list.Count; ++i)
                list[i].CallFinishSetupRecursive();
            FinishSetup();
            SetOptimizedControlDataTypeRecursively();
        }

        internal string MakeChildPath(string path)
        {
            if (this is InputDevice)
                return path;
            return $"{this.path}/{path}";
        }

        internal void BakeOffsetIntoStateBlockRecursive(uint offset)
        {
            m_StateBlock.byteOffset += offset;

            var list = children;
            for (var i = 0; i < list.Count; ++i)
                list[i].BakeOffsetIntoStateBlockRecursive(offset);
        }

        internal int GetDeviceIndex()
        {
            var deviceIndex = m_Device.m_DeviceIndex;
            if (deviceIndex == InputDevice.kInvalidDeviceIndex)
                throw new InvalidOperationException(
                    $"Cannot query value of control '{path}' before '{device.name}' has been added to system!");
            return deviceIndex;
        }

        internal bool IsValueConsideredPressed(float value)
        {
            if (isButton)
                return ((ButtonControl)this).IsValueConsideredPressed(value);
            return value >= ButtonControl.s_GlobalDefaultButtonPressPoint;
        }

        internal virtual void AddProcessor(object first)
        {
        }

        internal void MarkAsStale()
        {
            m_CachedValueIsStale = true;
            m_UnprocessedCachedValueIsStale = true;
        }

        internal void MarkAsStaleRecursively()
        {
            MarkAsStale();

            foreach (var inputControl in children)
                inputControl.MarkAsStale();
        }

        #if UNITY_EDITOR
        internal virtual IEnumerable<object> GetProcessors()
        {
            yield return null;
        }

        #endif
    }

    /// <summary>
    /// Base class for input controls with a specific value type.
    /// </summary>
    /// <typeparam name="TValue">Type of value captured by the control. Note that this does not mean
    /// that the control has to store data in the given value format. A control that captures float
    /// values, for example, may be stored in state as byte values instead.</typeparam>
    public abstract class InputControl<TValue> : InputControl
        where TValue : struct
    {
        public override Type valueType => typeof(TValue);

        public override int valueSizeInBytes => UnsafeUtility.SizeOf<TValue>();

        /// <summary>
        /// Returns the current value of the control after processors have been applied.
        /// </summary>
        /// <returns>The controls current value.</returns>
        /// <remarks>
        /// This can only be called on devices that have been added to the system (<see cref="InputDevice.added"/>).
        ///
        /// If internal feature "USE_READ_VALUE_CACHING" is enabled, then this property implements caching
        /// to avoid applying processors when the underlying control has not changed.
        /// With this in mind, be aware of processors that use global state, such as the <see cref="Processors.AxisDeadzoneProcessor"/>.
        /// Unless the control unprocessed value has been changed, input system settings changed or <see cref="InputControl.ApplyParameterChanges()"/> invoked,
        /// the processors will not run and calls to <see cref="value"/> will return the same result as previous calls.
        ///
        /// If a processor requires to be run on every read, override <see cref="InputProcessor.cachingPolicy"/> property
        /// in the processor and set it to <see cref="InputProcessor.CachingPolicy.EvaluateOnEveryRead"/>.
        ///
        /// To improve debugging try setting "PARANOID_READ_VALUE_CACHING_CHECKS" internal feature flag to check if cache value is still consistent.
        ///
        /// Also note that this property returns the result as ref readonly. If custom control states are in use, i.e.
        /// any controls not shipped with the Input System package, be careful of accidental defensive copies
        /// <see href="https://docs.microsoft.com/en-us/dotnet/csharp/write-safe-efficient-code#avoid-defensive-copies"/>.
        /// </remarks>
        /// <seealso cref="ReadValue"/>
        public ref readonly TValue value
        {
            get
            {
#if UNITY_EDITOR
                if (InputUpdate.s_LatestUpdateType.IsEditorUpdate())
                    return ref ReadStateInEditor();
#endif

                if (
                    // if feature is disabled we re-evaluate every call
                    !InputSettings.readValueCachingFeatureEnabled
                    // if cached value is stale we re-evaluate and clear the flag
                    || m_CachedValueIsStale
                    // if a processor in stack needs to be re-evaluated, but unprocessedValue is still can be cached
                    || evaluateProcessorsEveryRead
                )
                {
                    m_CachedValue = ProcessValue(unprocessedValue);
                    m_CachedValueIsStale = false;
                }
#if DEBUG
                else if (InputSettings.paranoidReadValueCachingChecksEnabled)
                {
                    var oldUnprocessedValue = m_UnprocessedCachedValue;
                    var newUnprocessedValue = unprocessedValue;
                    var currentProcessedValue = ProcessValue(newUnprocessedValue);

                    if (CompareValue(ref newUnprocessedValue, ref oldUnprocessedValue))
                    {
                        // don't warn if unprocessedValue caching failed
                        m_CachedValue = currentProcessedValue;
                    }
                    else if (CompareValue(ref currentProcessedValue, ref m_CachedValue))
                    {
                        // processors are not behaving as expected if unprocessedValue stays the same but processedValue changed
                        var namesList = new List<string>();
                        foreach (var inputProcessor in m_ProcessorStack)
                            namesList.Add(inputProcessor.ToString());
                        var names = string.Join(", ", namesList);
                        Debug.LogError(
                            "Cached processed value unexpectedly became outdated due to InputProcessor's returning a different value, " +
                            $"new value '{currentProcessedValue}' old value '{m_CachedValue}', current processors are: {names}. " +
                            "If your processor need to be recomputed on every read please add \"public override CachingPolicy cachingPolicy => CachingPolicy.EvaluateOnEveryRead;\" to the processor.");
                        m_CachedValue = currentProcessedValue;
                    }
                }
#endif

                return ref m_CachedValue;
            }
        }

        internal unsafe ref readonly TValue unprocessedValue
        {
            get
            {
#if UNITY_EDITOR
                if (InputUpdate.s_LatestUpdateType.IsEditorUpdate())
                    return ref ReadUnprocessedStateInEditor();
#endif

                if (
                    // if feature is disabled we re-evaluate every call
                    !InputSettings.readValueCachingFeatureEnabled
                    // if cached value is stale we re-evaluate and clear the flag
                    || m_UnprocessedCachedValueIsStale
                )
                {
                    m_UnprocessedCachedValue = ReadUnprocessedValueFromState(currentStatePtr);
                    m_UnprocessedCachedValueIsStale = false;
                }
#if DEBUG
                else if (InputSettings.paranoidReadValueCachingChecksEnabled)
                {
                    var currentUnprocessedValue = ReadUnprocessedValueFromState(currentStatePtr);
                    if (CompareValue(ref currentUnprocessedValue, ref m_UnprocessedCachedValue))
                    {
                        Debug.LogError($"Cached unprocessed value unexpectedly became outdated for unknown reason, new value '{currentUnprocessedValue}' old value '{m_UnprocessedCachedValue}'.");
                        m_UnprocessedCachedValue = currentUnprocessedValue;
                    }
                }
#endif

                return ref m_UnprocessedCachedValue;
            }
        }

        /// <summary>
        /// Returns the current value of the control after processors have been applied.
        /// </summary>
        /// <returns>The controls current value.</returns>
        /// <remarks>
        /// This can only be called on devices that have been added to the system (<see cref="InputDevice.added"/>).
        ///
        /// If internal feature "USE_READ_VALUE_CACHING" is enabled, then this property implements caching
        /// to avoid applying processors when the underlying control has not changed.
        /// With this in mind, be aware of processors that use global state, such as the <see cref="Processors.AxisDeadzoneProcessor"/>.
        /// Unless the control unprocessed value has been changed, input system settings changed or <see cref="InputControl.ApplyParameterChanges()"/> invoked,
        /// the processors will not run and calls to <see cref="value"/> will return the same result as previous calls.
        ///
        /// If a processor requires to be run on every read, override <see cref="InputProcessor.cachingPolicy"/> property
        /// in the processor and set it to <see cref="InputProcessor.CachingPolicy.EvaluateOnEveryRead"/>.
        ///
        /// To improve debugging try setting "PARANOID_READ_VALUE_CACHING_CHECKS" internal feature flag to check if cache value is still consistent.
        /// <see href="https://docs.microsoft.com/en-us/dotnet/csharp/write-safe-efficient-code#avoid-defensive-copies"/>.
        /// </remarks>
        /// <seealso cref="value"/>
        public TValue ReadValue()
        {
            return value;
        }

        ////REVIEW: is 'frame' really the best wording here?
        /// <summary>
        /// Get the control's value from the previous frame (<see cref="InputControl.previousFrameStatePtr"/>).
        /// </summary>
        /// <returns>The control's value in the previous frame.</returns>
        public TValue ReadValueFromPreviousFrame()
        {
            unsafe
            {
                return ReadValueFromState(previousFrameStatePtr);
            }
        }

        /// <summary>
        /// Get the control's default value.
        /// </summary>
        /// <returns>The control's default value.</returns>
        /// <remarks>
        /// This is not necessarily equivalent to <c>default(TValue)</c>. A control's default value is determined
        /// by reading its value from the default state (<see cref="InputControl.defaultStatePtr"/>) which in turn
        /// is determined from settings in the control's registered layout (<see cref="InputControlLayout.ControlItem.defaultState"/>).
        /// </remarks>
        public TValue ReadDefaultValue()
        {
            unsafe
            {
                return ReadValueFromState(defaultStatePtr);
            }
        }

        public unsafe TValue ReadValueFromState(void* statePtr)
        {
            if (statePtr == null)
                throw new ArgumentNullException(nameof(statePtr));
            return ProcessValue(ReadUnprocessedValueFromState(statePtr));
        }

        /// <summary>
        /// Read value from provided <paramref name="statePtr"/> and apply processors. Try cache result if possible.
        /// </summary>
        /// <param name="statePtr">State pointer to read from.</param>
        /// <returns>The controls current value.</returns>
        /// <remarks>
        /// If <paramref name="statePtr"/> is "currentStatePtr", then read will be done via <see cref="value"/> property to improve performance.
        /// </remarks>
        /// <seealso cref="value"/>
        public unsafe TValue ReadValueFromStateWithCaching(void* statePtr)
        {
            return statePtr == currentStatePtr ? value : ReadValueFromState(statePtr);
        }

        /// <summary>
        /// Read value from provided <paramref name="statePtr"/>. Try cache result if possible.
        /// </summary>
        /// <param name="statePtr">State pointer to read from.</param>
        /// <returns>The controls current value.</returns>
        /// <remarks>
        /// If <paramref name="statePtr"/> is "currentStatePtr", then read will be done via <see cref="unprocessedValue"/> property to improve performance.
        /// </remarks>
        /// <seealso cref="value"/>
        public unsafe TValue ReadUnprocessedValueFromStateWithCaching(void* statePtr)
        {
            return statePtr == currentStatePtr ? unprocessedValue : ReadUnprocessedValueFromState(statePtr);
        }

        public TValue ReadUnprocessedValue()
        {
            return unprocessedValue;
        }

        public abstract unsafe TValue ReadUnprocessedValueFromState(void* statePtr);

        /// <inheritdoc />
        public override unsafe object ReadValueFromStateAsObject(void* statePtr)
        {
            return ReadValueFromState(statePtr);
        }

        /// <inheritdoc />
        public override unsafe void ReadValueFromStateIntoBuffer(void* statePtr, void* bufferPtr, int bufferSize)
        {
            if (statePtr == null)
                throw new ArgumentNullException(nameof(statePtr));
            if (bufferPtr == null)
                throw new ArgumentNullException(nameof(bufferPtr));

            var numBytes = UnsafeUtility.SizeOf<TValue>();
            if (bufferSize < numBytes)
                throw new ArgumentException(
                    $"bufferSize={bufferSize} < sizeof(TValue)={numBytes}", nameof(bufferSize));

            var value = ReadValueFromState(statePtr);
            var valuePtr = UnsafeUtility.AddressOf(ref value);

            UnsafeUtility.MemCpy(bufferPtr, valuePtr, numBytes);
        }

        public override unsafe void WriteValueFromBufferIntoState(void* bufferPtr, int bufferSize, void* statePtr)
        {
            if (bufferPtr == null)
                throw new ArgumentNullException(nameof(bufferPtr));
            if (statePtr == null)
                throw new ArgumentNullException(nameof(statePtr));

            var numBytes = UnsafeUtility.SizeOf<TValue>();
            if (bufferSize < numBytes)
                throw new ArgumentException(
                    $"bufferSize={bufferSize} < sizeof(TValue)={numBytes}", nameof(bufferSize));

            // C# won't let us use a pointer to a generically defined type. Work
            // around this by using UnsafeUtility.
            var value = default(TValue);
            var valuePtr = UnsafeUtility.AddressOf(ref value);
            UnsafeUtility.MemCpy(valuePtr, bufferPtr, numBytes);

            WriteValueIntoState(value, statePtr);
        }

        /// <inheritdoc />
        public override unsafe void WriteValueFromObjectIntoState(object value, void* statePtr)
        {
            if (statePtr == null)
                throw new ArgumentNullException(nameof(statePtr));
            if (value == null)
                throw new ArgumentNullException(nameof(value));

            // If value is not of expected type, try to convert.
            if (!(value is TValue))
                value = Convert.ChangeType(value, typeof(TValue));

            var valueOfType = (TValue)value;
            WriteValueIntoState(valueOfType, statePtr);
        }

        public virtual unsafe void WriteValueIntoState(TValue value, void* statePtr)
        {
            ////REVIEW: should we be able to even tell from layouts which controls support writing and which don't?

            throw new NotSupportedException(
                $"Control '{this}' does not support writing");
        }

        /// <inheritdoc />
        public override unsafe object ReadValueFromBufferAsObject(void* buffer, int bufferSize)
        {
            if (buffer == null)
                throw new ArgumentNullException(nameof(buffer));

            var valueSize = UnsafeUtility.SizeOf<TValue>();
            if (bufferSize < valueSize)
                throw new ArgumentException(
                    $"Expecting buffer of at least {valueSize} bytes for value of type {typeof(TValue).Name} but got buffer of only {bufferSize} bytes instead",
                    nameof(bufferSize));

            var value = default(TValue);
            var valuePtr = UnsafeUtility.AddressOf(ref value);
            UnsafeUtility.MemCpy(valuePtr, buffer, valueSize);

            return value;
        }

        private static unsafe bool CompareValue(ref TValue firstValue, ref TValue secondValue)
        {
            var firstValuePtr = UnsafeUtility.AddressOf(ref firstValue);
            var secondValuePtr = UnsafeUtility.AddressOf(ref secondValue);

            // NOTE: We're comparing raw memory of processed values here (which are guaranteed to be structs or
            //       primitives), not state. Means we don't have to take bits into account here.

            return UnsafeUtility.MemCmp(firstValuePtr, secondValuePtr, UnsafeUtility.SizeOf<TValue>()) != 0;
        }

        public override unsafe bool CompareValue(void* firstStatePtr, void* secondStatePtr)
        {
            ////REVIEW: should we first compare state here? if there's no change in state, there can be no change in value and we can skip the rest

            var firstValue = ReadValueFromState(firstStatePtr);
            var secondValue = ReadValueFromState(secondStatePtr);

            return CompareValue(ref firstValue, ref secondValue);
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public TValue ProcessValue(TValue value)
        {
            ProcessValue(ref value);
            return value;
        }

        /// <summary>
        /// Applies all control processors to the passed value.
        /// </summary>
        /// <param name="value"></param>
        /// <remarks>
        /// Use this overload when your state struct is large to avoid creating copies of the state.
        /// </remarks>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public void ProcessValue(ref TValue value)
        {
            if (m_ProcessorStack.length <= 0)
                return;

            value = m_ProcessorStack.firstValue.Process(value, this);
            if (m_ProcessorStack.additionalValues == null)
                return;

            for (var i = 0; i < m_ProcessorStack.length - 1; ++i)
                value = m_ProcessorStack.additionalValues[i].Process(value, this);
        }

        internal InlinedArray<InputProcessor<TValue>> m_ProcessorStack;

        private TValue m_CachedValue;
        private TValue m_UnprocessedCachedValue;

        #if UNITY_EDITOR
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private unsafe ref readonly TValue ReadStateInEditor()
        {
            // we don't use cached values during editor updates because editor updates cause controls to look at a
            // different block of state memory, and since the cached values are from the play mode memory, we'd
            // end up returning the wrong values.
            m_EditorValue = ReadValueFromState(currentStatePtr);
            return ref m_EditorValue;
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private unsafe ref readonly TValue ReadUnprocessedStateInEditor()
        {
            m_UnprocessedEditorValue = ReadUnprocessedValueFromState(currentStatePtr);
            return ref m_UnprocessedEditorValue;
        }

        // these fields are just to work with the fact that the 'value' property is ref readonly, so we
        // need somewhere with a known lifetime to store these so they can be returned by ref.
        private TValue m_EditorValue;
        private TValue m_UnprocessedEditorValue;
        #endif

        // Only layouts are allowed to modify the processor stack.
        internal TProcessor TryGetProcessor<TProcessor>()
            where TProcessor : InputProcessor<TValue>
        {
            if (m_ProcessorStack.length > 0)
            {
                if (m_ProcessorStack.firstValue is TProcessor processor)
                    return processor;
                if (m_ProcessorStack.additionalValues != null)
                    for (var i = 0; i < m_ProcessorStack.length - 1; ++i)
                        if (m_ProcessorStack.additionalValues[i] is TProcessor result)
                            return result;
            }
            return default;
        }

        internal override void AddProcessor(object processor)
        {
            if (!(processor is InputProcessor<TValue> processorOfType))
                throw new ArgumentException(
                    $"Cannot add processor of type '{processor.GetType().Name}' to control of type '{GetType().Name}'", nameof(processor));
            m_ProcessorStack.Append(processorOfType);
        }

        #if UNITY_EDITOR
        internal override IEnumerable<object> GetProcessors()
        {
            foreach (var processor in m_ProcessorStack)
                yield return processor;
        }

        #endif

        internal bool evaluateProcessorsEveryRead = false;

        protected override void FinishSetup()
        {
            foreach (var processor in m_ProcessorStack)
                if (processor.cachingPolicy == InputProcessor.CachingPolicy.EvaluateOnEveryRead)
                    evaluateProcessorsEveryRead = true;

            base.FinishSetup();
        }

        internal InputProcessor<TValue>[] processors => m_ProcessorStack.ToArray();
    }
}