暫無描述
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.

InputLayoutCodeGenerator.cs 23KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467
  1. #if UNITY_EDITOR
  2. using System;
  3. using System.Collections.Generic;
  4. using System.IO;
  5. using System.Linq;
  6. using System.Reflection;
  7. using System.Runtime.InteropServices;
  8. using System.Text;
  9. using Unity.Collections.LowLevel.Unsafe;
  10. using UnityEngine.InputSystem.Controls;
  11. using UnityEngine.InputSystem.Layouts;
  12. using UnityEngine.InputSystem.Processors;
  13. using UnityEngine.InputSystem.Utilities;
  14. namespace UnityEngine.InputSystem.Editor
  15. {
  16. internal static class InputLayoutCodeGenerator
  17. {
  18. public static string GenerateCodeFileForDeviceLayout(string layoutName, string fileName, string prefix = "Fast")
  19. {
  20. string defines = null;
  21. string @namespace = null;
  22. var visibility = "public";
  23. // If the file already exists, read out the changes we preserve.
  24. if (File.Exists(fileName))
  25. {
  26. var lines = File.ReadLines(fileName).Take(50).ToList();
  27. // Read out #defines.
  28. for (var i = 0; i < (lines.Count - 1); ++i)
  29. {
  30. var line = lines[i].Trim();
  31. if (line.StartsWith("#if "))
  32. defines = line.Substring("#if ".Length);
  33. else if (line.StartsWith("namespace "))
  34. @namespace = line.Substring("namespace ".Length);
  35. }
  36. if (lines.Any(x => x.Contains("internal partial class " + prefix)))
  37. visibility = "internal";
  38. }
  39. return GenerateCodeForDeviceLayout(layoutName,
  40. defines: defines, visibility: visibility, @namespace: @namespace, namePrefix: prefix);
  41. }
  42. /// <summary>
  43. /// Generate C# code that for the given device layout called <paramref name="layoutName"/> instantly creates
  44. /// an <see cref="InputDevice"/> equivalent to what the input system would create by manually interpreting
  45. /// the given <see cref="InputControlLayout"/>.
  46. /// </summary>
  47. /// <param name="layoutName">Name of the device layout to generate code for.</param>
  48. /// <param name="defines">Null/empty or a valid expression for an #if conditional compilation statement.</param>
  49. /// <param name="namePrefix">Prefix to prepend to the type name of <paramref name="layoutName"/>.</param>
  50. /// <param name="visibility">C# access modifier to use with the generated class.</param>
  51. /// <param name="namespace">Namespace to put the generated class in. If <c>null</c>, namespace of type behind <paramref name="layoutName"/> will be used.</param>
  52. /// <returns>C# source code for a precompiled version of the device layout.</returns>
  53. /// <remarks>
  54. /// The code generated by this method will be many times faster than the reflection-based <see cref="InputDevice"/>
  55. /// creation normally performed by the input system. It will also create less GC heap garbage.
  56. ///
  57. /// The downside to the generated code is that the makeup of the device is hardcoded and can no longer
  58. /// be changed by altering the <see cref="InputControlLayout"/> setup of the system.
  59. ///
  60. /// Note that it is possible to use this method with layouts generated on-the-fly by layout builders such as
  61. /// the one employed for <see cref="HID"/>. However, this must be done at compile/build time and can thus not
  62. /// be done for devices dynamically discovered at runtime. When this is acceptable, it is a way to dramatically
  63. /// speed up the creation of these devices.
  64. /// </remarks>
  65. /// <seealso cref="InputSystem.RegisterPrecompiledLayout{T}"/>
  66. public static unsafe string GenerateCodeForDeviceLayout(string layoutName, string defines = null, string namePrefix = "Fast", string visibility = "public", string @namespace = null)
  67. {
  68. if (string.IsNullOrEmpty(layoutName))
  69. throw new ArgumentNullException(nameof(layoutName));
  70. // Produce a device from the layout.
  71. var device = InputDevice.Build<InputDevice>(layoutName, noPrecompiledLayouts: true);
  72. // Get info about base type.
  73. var baseType = device.GetType();
  74. var baseTypeName = baseType.Name;
  75. var baseTypeNamespace = baseType.Namespace;
  76. // Begin generating code.
  77. var writer = new InputActionCodeGenerator.Writer
  78. {
  79. buffer = new StringBuilder()
  80. };
  81. writer.WriteLine(CSharpCodeHelpers.MakeAutoGeneratedCodeHeader("com.unity.inputsystem:InputLayoutCodeGenerator",
  82. InputSystem.version.ToString(),
  83. $"\"{layoutName}\" layout"));
  84. // Defines.
  85. if (defines != null)
  86. {
  87. writer.WriteLine($"#if {defines}");
  88. writer.WriteLine();
  89. }
  90. if (@namespace == null)
  91. @namespace = baseTypeNamespace;
  92. writer.WriteLine("using UnityEngine.InputSystem;");
  93. writer.WriteLine("using UnityEngine.InputSystem.LowLevel;");
  94. writer.WriteLine("using UnityEngine.InputSystem.Utilities;");
  95. writer.WriteLine("");
  96. writer.WriteLine("// Suppress warnings from local variables for control references");
  97. writer.WriteLine("// that we don't end up using.");
  98. writer.WriteLine("#pragma warning disable CS0219");
  99. writer.WriteLine("");
  100. if (!string.IsNullOrEmpty(@namespace))
  101. {
  102. writer.WriteLine("namespace " + @namespace);
  103. writer.BeginBlock();
  104. }
  105. if (string.IsNullOrEmpty(baseTypeNamespace))
  106. writer.WriteLine($"{visibility} partial class {namePrefix}{baseTypeName} : {baseTypeName}");
  107. else
  108. writer.WriteLine($"{visibility} partial class {namePrefix}{baseTypeName} : {baseTypeNamespace}.{baseTypeName}");
  109. writer.BeginBlock();
  110. // "Metadata". ATM this is simply a flat, semicolon-separated list of names for layouts and processors that
  111. // we depend on. If any of them are touched, the precompiled layout should be considered invalidated.
  112. var internedLayoutName = new InternedString(layoutName);
  113. var allControls = device.allControls;
  114. var usedControlLayouts = allControls.Select(x => x.m_Layout).Distinct().ToList();
  115. var layoutDependencies = string.Join(";",
  116. usedControlLayouts.SelectMany(l => InputControlLayout.s_Layouts.GetBaseLayouts(l))
  117. .Union(InputControlLayout.s_Layouts.GetBaseLayouts(internedLayoutName)));
  118. var processorDependencies = string.Join(";",
  119. allControls.SelectMany(c => c.GetProcessors()).Select(p => InputProcessor.s_Processors.FindNameForType(p.GetType()))
  120. .Where(n => !n.IsEmpty()).Distinct());
  121. var metadata = string.Join(";", processorDependencies, layoutDependencies);
  122. writer.WriteLine($"public const string metadata = \"{metadata}\";");
  123. // Constructor.
  124. writer.WriteLine($"public {namePrefix}{baseTypeName}()");
  125. writer.BeginBlock();
  126. var usagesForEachControl = device.m_UsagesForEachControl;
  127. var usageToControl = device.m_UsageToControl;
  128. var aliasesForEachControl = device.m_AliasesForEachControl;
  129. var controlCount = allControls.Count;
  130. var usageCount = usagesForEachControl?.Length ?? 0;
  131. var aliasCount = aliasesForEachControl?.Length ?? 0;
  132. // Set up device control info.
  133. writer.WriteLine($"var builder = this.Setup({controlCount}, {usageCount}, {aliasCount})");
  134. writer.WriteLine($" .WithName(\"{device.name}\")");
  135. writer.WriteLine($" .WithDisplayName(\"{device.displayName}\")");
  136. writer.WriteLine($" .WithChildren({device.m_ChildStartIndex}, {device.m_ChildCount})");
  137. writer.WriteLine($" .WithLayout(new InternedString(\"{device.layout}\"))");
  138. writer.WriteLine($" .WithStateBlock(new InputStateBlock {{ format = new FourCC({(int)device.stateBlock.format}), sizeInBits = {device.stateBlock.sizeInBits} }});");
  139. if (device.noisy)
  140. writer.WriteLine("builder.IsNoisy(true);");
  141. // Add controls to device.
  142. writer.WriteLine();
  143. foreach (var layout in usedControlLayouts)
  144. writer.WriteLine($"var k{layout}Layout = new InternedString(\"{layout}\");");
  145. for (var i = 0; i < controlCount; ++i)
  146. {
  147. var control = allControls[i];
  148. var controlVariableName = MakeControlVariableName(control);
  149. writer.WriteLine("");
  150. writer.WriteLine($"// {control.path}");
  151. var parentName = "this";
  152. if (control.parent != device)
  153. parentName = MakeControlVariableName(control.parent);
  154. writer.WriteLine($"var {controlVariableName} = {NameOfControlMethod(controlVariableName)}(k{control.layout}Layout, {parentName});");
  155. }
  156. // Initialize usages array.
  157. if (usageCount > 0)
  158. {
  159. writer.WriteLine();
  160. writer.WriteLine("// Usages.");
  161. for (var i = 0; i < usageCount; ++i)
  162. writer.WriteLine(
  163. $"builder.WithControlUsage({i}, new InternedString(\"{usagesForEachControl[i]}\"), {MakeControlVariableName(usageToControl[i])});");
  164. }
  165. // Initialize aliases array.
  166. if (aliasCount > 0)
  167. {
  168. writer.WriteLine();
  169. writer.WriteLine("// Aliases.");
  170. for (var i = 0; i < aliasCount; ++i)
  171. writer.WriteLine($"builder.WithControlAlias({i}, new InternedString(\"{aliasesForEachControl[i]}\"));");
  172. }
  173. // Emit initializers for control getters and control arrays. This is usually what's getting set up
  174. // in FinishSetup(). We hardcode the look results here.
  175. var controlGetterProperties = new Dictionary<Type, List<PropertyInfo>>();
  176. var controlArrayProperties = new Dictionary<Type, List<PropertyInfo>>();
  177. writer.WriteLine();
  178. writer.WriteLine("// Control getters/arrays.");
  179. writer.EmitControlArrayInitializers(device, "this", controlArrayProperties);
  180. writer.EmitControlGetterInitializers(device, "this", controlGetterProperties);
  181. for (var i = 0; i < controlCount; ++i)
  182. {
  183. var control = allControls[i];
  184. var controlVariableName = MakeControlVariableName(control);
  185. writer.EmitControlArrayInitializers(control, controlVariableName, controlArrayProperties);
  186. writer.EmitControlGetterInitializers(control, controlVariableName, controlGetterProperties);
  187. }
  188. // State offset to control index map.
  189. if (device.m_StateOffsetToControlMap != null)
  190. {
  191. writer.WriteLine();
  192. writer.WriteLine("// State offset to control index map.");
  193. writer.WriteLine("builder.WithStateOffsetToControlIndexMap(new uint[]");
  194. writer.WriteLine("{");
  195. ++writer.indentLevel;
  196. var map = device.m_StateOffsetToControlMap;
  197. var entryCount = map.Length;
  198. for (var index = 0; index < entryCount;)
  199. {
  200. if (index != 0)
  201. writer.WriteLine();
  202. // 10 entries a line.
  203. writer.WriteIndent();
  204. for (var i = 0; i < 10 && index < entryCount; ++index, ++i)
  205. writer.Write((index != 0 ? ", " : "") + map[index] + "u");
  206. }
  207. writer.WriteLine();
  208. --writer.indentLevel;
  209. writer.WriteLine("});");
  210. }
  211. writer.WriteLine();
  212. if (device.m_ControlTreeNodes != null)
  213. {
  214. if (device.m_ControlTreeIndices == null)
  215. throw new InvalidOperationException(
  216. $"Control tree indicies was null. Ensure the '{device.displayName}' device was created without errors.");
  217. writer.WriteLine("builder.WithControlTree(new byte[]");
  218. writer.WriteLine("{");
  219. ++writer.indentLevel;
  220. writer.WriteLine("// Control tree nodes as bytes");
  221. var nodePtr = (byte*)UnsafeUtility.AddressOf(ref device.m_ControlTreeNodes[0]);
  222. var byteCount = device.m_ControlTreeNodes.Length * UnsafeUtility.SizeOf<InputDevice.ControlBitRangeNode>();
  223. for (var i = 0; i < byteCount;)
  224. {
  225. if (i != 0)
  226. writer.WriteLine();
  227. writer.WriteIndent();
  228. for (var j = 0; j < 30 && i < byteCount; j++, i++)
  229. {
  230. writer.Write((i != 0 ? ", " : "") + *(nodePtr + i));
  231. }
  232. }
  233. writer.WriteLine();
  234. --writer.indentLevel;
  235. writer.WriteLine("}, new ushort[]");
  236. ++writer.indentLevel;
  237. writer.WriteLine("{");
  238. ++writer.indentLevel;
  239. writer.WriteLine("// Control tree node indicies");
  240. writer.WriteLine();
  241. for (var i = 0; i < device.m_ControlTreeIndices.Length;)
  242. {
  243. if (i != 0)
  244. writer.WriteLine();
  245. writer.WriteIndent();
  246. for (var j = 0; j < 30 && i < device.m_ControlTreeIndices.Length; j++, i++)
  247. {
  248. writer.Write((i != 0 ? ", " : "") + device.m_ControlTreeIndices[i]);
  249. }
  250. }
  251. writer.WriteLine();
  252. --writer.indentLevel;
  253. writer.WriteLine("});");
  254. --writer.indentLevel;
  255. }
  256. writer.WriteLine();
  257. writer.WriteLine("builder.Finish();");
  258. writer.EndBlock();
  259. for (var i = 0; i < controlCount; ++i)
  260. {
  261. var control = allControls[i];
  262. var controlType = control.GetType();
  263. var controlVariableName = MakeControlVariableName(control);
  264. var controlFieldInits = control.GetInitializersForPublicPrimitiveTypeFields();
  265. writer.WriteLine();
  266. EmitControlMethod(writer, controlVariableName, controlType, controlFieldInits, i, control);
  267. }
  268. writer.EndBlock();
  269. if (!string.IsNullOrEmpty(@namespace))
  270. writer.EndBlock();
  271. if (defines != null)
  272. writer.WriteLine($"#endif // {defines}");
  273. return writer.buffer.ToString();
  274. }
  275. private static string NameOfControlMethod(string controlVariableName)
  276. {
  277. return $"Initialize_{controlVariableName}";
  278. }
  279. // We emit this as a separate method instead of directly inline to avoid generating a single massive constructor method
  280. // as these can lead to large build times with il2cpp and C++ compilers (https://fogbugz.unity3d.com/f/cases/1282090/).
  281. private static void EmitControlMethod(InputActionCodeGenerator.Writer writer, string controlVariableName, Type controlType,
  282. string controlFieldInits, int i, InputControl control)
  283. {
  284. var controlTypeName = controlType.FullName.Replace('+', '.');
  285. writer.WriteLine($"private {controlTypeName} {NameOfControlMethod(controlVariableName)}(InternedString k{control.layout}Layout, InputControl parent)");
  286. writer.BeginBlock();
  287. writer.WriteLine($"var {controlVariableName} = new {controlTypeName}{controlFieldInits};");
  288. writer.WriteLine($"{controlVariableName}.Setup()");
  289. writer.WriteLine($" .At(this, {i})");
  290. writer.WriteLine(" .WithParent(parent)");
  291. if (control.children.Count > 0)
  292. writer.WriteLine($" .WithChildren({control.m_ChildStartIndex}, {control.m_ChildCount})");
  293. writer.WriteLine($" .WithName(\"{control.name}\")");
  294. writer.WriteLine($" .WithDisplayName(\"{control.m_DisplayNameFromLayout.Replace("\\", "\\\\")}\")");
  295. if (!string.IsNullOrEmpty(control.m_ShortDisplayNameFromLayout))
  296. writer.WriteLine(
  297. $" .WithShortDisplayName(\"{control.m_ShortDisplayNameFromLayout.Replace("\\", "\\\\")}\")");
  298. writer.WriteLine($" .WithLayout(k{control.layout}Layout)");
  299. if (control.usages.Count > 0)
  300. writer.WriteLine($" .WithUsages({control.m_UsageStartIndex}, {control.m_UsageCount})");
  301. if (control.aliases.Count > 0)
  302. writer.WriteLine($" .WithAliases({control.m_AliasStartIndex}, {control.m_AliasCount})");
  303. if (control.noisy)
  304. writer.WriteLine(" .IsNoisy(true)");
  305. if (control.synthetic)
  306. writer.WriteLine(" .IsSynthetic(true)");
  307. if (control.dontReset)
  308. writer.WriteLine(" .DontReset(true)");
  309. if (control is ButtonControl)
  310. writer.WriteLine(" .IsButton(true)");
  311. writer.WriteLine(" .WithStateBlock(new InputStateBlock");
  312. writer.WriteLine(" {");
  313. writer.WriteLine($" format = new FourCC({(int) control.stateBlock.format}),");
  314. writer.WriteLine($" byteOffset = {control.stateBlock.byteOffset},");
  315. writer.WriteLine($" bitOffset = {control.stateBlock.bitOffset},");
  316. writer.WriteLine($" sizeInBits = {control.stateBlock.sizeInBits}");
  317. writer.WriteLine(" })");
  318. if (control.hasDefaultState)
  319. writer.WriteLine($" .WithDefaultState({control.m_DefaultState})");
  320. if (control.m_MinValue != default || control.m_MaxValue != default)
  321. writer.WriteLine($" .WithMinAndMax({control.m_MinValue}, {control.m_MaxValue})");
  322. foreach (var processor in control.GetProcessors())
  323. {
  324. var isEditorWindowSpaceProcessor = processor is EditorWindowSpaceProcessor;
  325. if (isEditorWindowSpaceProcessor)
  326. writer.WriteLine(" #if UNITY_EDITOR");
  327. var processorType = processor.GetType().FullName.Replace("+", ".");
  328. var valueType = InputProcessor.GetValueTypeFromType(processor.GetType());
  329. var fieldInits = processor.GetInitializersForPublicPrimitiveTypeFields();
  330. writer.WriteLine(
  331. $" .WithProcessor<InputProcessor<{valueType}>, {valueType}>(new {processorType}{fieldInits})");
  332. if (isEditorWindowSpaceProcessor)
  333. writer.WriteLine(" #endif");
  334. }
  335. writer.WriteLine(" .Finish();");
  336. if (control is KeyControl key)
  337. writer.WriteLine($"{controlVariableName}.keyCode = UnityEngine.InputSystem.Key.{key.keyCode};");
  338. else if (control is DpadControl.DpadAxisControl dpadAxis)
  339. writer.WriteLine($"{controlVariableName}.component = {dpadAxis.component};");
  340. writer.WriteLine($"return {controlVariableName};");
  341. writer.EndBlock();
  342. }
  343. private static string MakeControlVariableName(InputControl control)
  344. {
  345. return "ctrl" + CSharpCodeHelpers.MakeIdentifier(control.path);
  346. }
  347. private static void EmitControlGetterInitializers(this InputActionCodeGenerator.Writer writer, InputControl control,
  348. string controlVariableName, Dictionary<Type, List<PropertyInfo>> controlGetterPropertyTable)
  349. {
  350. var type = control.GetType();
  351. if (!controlGetterPropertyTable.TryGetValue(type, out var controlGetterProperties))
  352. {
  353. controlGetterProperties = GetControlGetterProperties(type);
  354. controlGetterPropertyTable[type] = controlGetterProperties;
  355. }
  356. foreach (var property in controlGetterProperties)
  357. {
  358. var value = (InputControl)property.GetValue(control);
  359. if (value == null)
  360. continue;
  361. writer.WriteLine($"{controlVariableName}.{property.Name} = {MakeControlVariableName(value)};");
  362. }
  363. }
  364. private static void EmitControlArrayInitializers(this InputActionCodeGenerator.Writer writer, InputControl control,
  365. string controlVariableName, Dictionary<Type, List<PropertyInfo>> controlArrayPropertyTable)
  366. {
  367. var type = control.GetType();
  368. if (!controlArrayPropertyTable.TryGetValue(type, out var controlArrayProperties))
  369. {
  370. controlArrayProperties = GetControlArrayProperties(type);
  371. controlArrayPropertyTable[type] = controlArrayProperties;
  372. }
  373. foreach (var property in controlArrayProperties)
  374. {
  375. var array = (Array)property.GetValue(control);
  376. if (array == null)
  377. continue;
  378. var arrayLength = array.Length;
  379. var arrayElementType = array.GetType().GetElementType();
  380. writer.WriteLine($"{controlVariableName}.{property.Name} = new {arrayElementType.FullName.Replace('+','.')}[{arrayLength}];");
  381. for (var i = 0; i < arrayLength; ++i)
  382. {
  383. var value = (InputControl)array.GetValue(i);
  384. if (value == null)
  385. continue;
  386. writer.WriteLine($"{controlVariableName}.{property.Name}[{i}] = {MakeControlVariableName(value)};");
  387. }
  388. }
  389. }
  390. private static List<PropertyInfo> GetControlGetterProperties(Type type)
  391. {
  392. return type.GetProperties(BindingFlags.Instance | BindingFlags.Public)
  393. .Where(x => typeof(InputControl).IsAssignableFrom(x.PropertyType) && x.CanRead && x.CanWrite &&
  394. x.GetIndexParameters().LengthSafe() == 0 && x.Name != "device" && x.Name != "parent").ToList();
  395. }
  396. private static List<PropertyInfo> GetControlArrayProperties(Type type)
  397. {
  398. return type.GetProperties(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public)
  399. .Where(x => x.PropertyType.IsArray && typeof(InputControl).IsAssignableFrom(x.PropertyType.GetElementType()) && x.CanRead && x.CanWrite &&
  400. x.GetIndexParameters().LengthSafe() == 0).ToList();
  401. }
  402. }
  403. }
  404. #endif // UNITY_EDITOR