InputControlLayout.cs 93 KB

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