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