暫無描述
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

InputControlLayout.cs 100KB

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