Exemple #1
0
        private static void EmitControlArrayInitializers(this InputActionCodeGenerator.Writer writer, InputControl control,
                                                         string controlVariableName, Dictionary <Type, List <PropertyInfo> > controlArrayPropertyTable)
        {
            var type = control.GetType();

            if (!controlArrayPropertyTable.TryGetValue(type, out var controlArrayProperties))
            {
                controlArrayProperties          = GetControlArrayProperties(type);
                controlArrayPropertyTable[type] = controlArrayProperties;
            }

            foreach (var property in controlArrayProperties)
            {
                var array = (Array)property.GetValue(control);
                if (array == null)
                {
                    continue;
                }
                var arrayLength      = array.Length;
                var arrayElementType = array.GetType().GetElementType();
                writer.WriteLine($"{controlVariableName}.{property.Name} = new {arrayElementType.FullName.Replace('+','.')}[{arrayLength}];");

                for (var i = 0; i < arrayLength; ++i)
                {
                    var value = (InputControl)array.GetValue(i);
                    if (value == null)
                    {
                        continue;
                    }
                    writer.WriteLine($"{controlVariableName}.{property.Name}[{i}] = {MakeControlVariableName(value)};");
                }
            }
        }
Exemple #2
0
        private static void EmitControlGetterInitializers(this InputActionCodeGenerator.Writer writer, InputControl control,
                                                          string controlVariableName, Dictionary <Type, List <PropertyInfo> > controlGetterPropertyTable)
        {
            var type = control.GetType();

            if (!controlGetterPropertyTable.TryGetValue(type, out var controlGetterProperties))
            {
                controlGetterProperties          = GetControlGetterProperties(type);
                controlGetterPropertyTable[type] = controlGetterProperties;
            }

            foreach (var property in controlGetterProperties)
            {
                var value = (InputControl)property.GetValue(control);
                if (value == null)
                {
                    continue;
                }
                writer.WriteLine($"{controlVariableName}.{property.Name} = {MakeControlVariableName(value)};");
            }
        }
Exemple #3
0
        /// <summary>
        /// Generate C# code that for the given device layout called <paramref name="layoutName"/> instantly creates
        /// an <see cref="InputDevice"/> equivalent to what the input system would create by manually interpreting
        /// the given <see cref="InputControlLayout"/>.
        /// </summary>
        /// <param name="layoutName">Name of the device layout to generate code for.</param>
        /// <param name="defines">Null/empty or a valid expression for an #if conditional compilation statement.</param>
        /// <param name="namePrefix">Prefix to prepend to the type name of <paramref name="layoutName"/>.</param>
        /// <param name="visibility">C# access modifier to use with the generated class.</param>
        /// <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>
        /// <returns>C# source code for a precompiled version of the device layout.</returns>
        /// <remarks>
        /// The code generated by this method will be many times faster than the reflection-based <see cref="InputDevice"/>
        /// creation normally performed by the input system. It will also create less GC heap garbage.
        ///
        /// The downside to the generated code is that the makeup of the device is hardcoded and can no longer
        /// be changed by altering the <see cref="InputControlLayout"/> setup of the system.
        ///
        /// Note that it is possible to use this method with layouts generated on-the-fly by layout builders such as
        /// the one employed for <see cref="HID"/>. However, this must be done at compile/build time and can thus not
        /// be done for devices dynamically discovered at runtime. When this is acceptable, it is a way to dramatically
        /// speed up the creation of these devices.
        /// </remarks>
        /// <seealso cref="InputSystem.RegisterPrecompiledLayout{T}"/>
        public static string GenerateCodeForDeviceLayout(string layoutName, string defines = null, string namePrefix = "Fast", string visibility = "public", string @namespace = null)
        {
            if (string.IsNullOrEmpty(layoutName))
            {
                throw new ArgumentNullException(nameof(layoutName));
            }

            // Produce a device from the layout.
            var device = InputDevice.Build <InputDevice>(layoutName, noPrecompiledLayouts: true);

            // Get info about base type.
            var baseType          = device.GetType();
            var baseTypeName      = baseType.Name;
            var baseTypeNamespace = baseType.Namespace;

            // Begin generating code.
            var writer = new InputActionCodeGenerator.Writer
            {
                buffer = new StringBuilder()
            };

            writer.WriteLine(CSharpCodeHelpers.MakeAutoGeneratedCodeHeader("com.unity.inputsystem:InputLayoutCodeGenerator",
                                                                           InputSystem.version.ToString(),
                                                                           $"\"{layoutName}\" layout"));

            // Defines.
            if (defines != null)
            {
                writer.WriteLine($"#if {defines}");
                writer.WriteLine();
            }

            if (@namespace == null)
            {
                @namespace = baseTypeNamespace;
            }

            writer.WriteLine("using UnityEngine.InputSystem;");
            writer.WriteLine("using UnityEngine.InputSystem.LowLevel;");
            writer.WriteLine("using UnityEngine.InputSystem.Utilities;");
            writer.WriteLine("");
            writer.WriteLine("// Suppress warnings from local variables for control references");
            writer.WriteLine("// that we don't end up using.");
            writer.WriteLine("#pragma warning disable CS0219");
            writer.WriteLine("");
            if (@namespace != "")
            {
                writer.WriteLine("namespace " + @namespace);
            }
            writer.BeginBlock();

            writer.WriteLine($"{visibility} partial class {namePrefix}{baseTypeName} : {baseTypeNamespace}.{baseTypeName}");
            writer.BeginBlock();

            // "Metadata". ATM this is simply a flat, semicolon-separated list of names for layouts and processors that
            // we depend on. If any of them are touched, the precompiled layout should be considered invalidated.
            var internedLayoutName = new InternedString(layoutName);
            var allControls        = device.allControls;
            var usedControlLayouts = allControls.Select(x => x.m_Layout).Distinct().ToList();
            var layoutDependencies = string.Join(";",
                                                 usedControlLayouts.SelectMany(l => InputControlLayout.s_Layouts.GetBaseLayouts(l))
                                                 .Union(InputControlLayout.s_Layouts.GetBaseLayouts(internedLayoutName)));
            var processorDependencies = string.Join(";",
                                                    allControls.SelectMany(c => c.GetProcessors()).Select(p => InputProcessor.s_Processors.FindNameForType(p.GetType()))
                                                    .Where(n => !n.IsEmpty()).Distinct());
            var metadata = string.Join(";", processorDependencies, layoutDependencies);

            writer.WriteLine($"public const string metadata = \"{metadata}\";");

            // Constructor.
            writer.WriteLine($"public {namePrefix}{baseTypeName}()");
            writer.BeginBlock();

            var usagesForEachControl  = device.m_UsagesForEachControl;
            var usageToControl        = device.m_UsageToControl;
            var aliasesForEachControl = device.m_AliasesForEachControl;
            var controlCount          = allControls.Count;
            var usageCount            = usagesForEachControl?.Length ?? 0;
            var aliasCount            = aliasesForEachControl?.Length ?? 0;

            // Set up device control info.
            writer.WriteLine($"var builder = this.Setup({controlCount}, {usageCount}, {aliasCount})");
            writer.WriteLine($"    .WithName(\"{device.name}\")");
            writer.WriteLine($"    .WithDisplayName(\"{device.displayName}\")");
            writer.WriteLine($"    .WithChildren({device.m_ChildStartIndex}, {device.m_ChildCount})");
            writer.WriteLine($"    .WithLayout(new InternedString(\"{device.layout}\"))");
            writer.WriteLine($"    .WithStateBlock(new InputStateBlock {{ format = new FourCC({(int)device.stateBlock.format}), sizeInBits = {device.stateBlock.sizeInBits} }});");

            if (device.noisy)
            {
                writer.WriteLine("builder.IsNoisy(true);");
            }

            // Add controls to device.
            writer.WriteLine();
            foreach (var layout in usedControlLayouts)
            {
                writer.WriteLine($"var k{layout}Layout = new InternedString(\"{layout}\");");
            }

            for (var i = 0; i < controlCount; ++i)
            {
                var control             = allControls[i];
                var controlVariableName = MakeControlVariableName(control);

                writer.WriteLine("");
                writer.WriteLine($"// {control.path}");
                var parentName = "this";
                if (control.parent != device)
                {
                    parentName = MakeControlVariableName(control.parent);
                }
                writer.WriteLine($"var {controlVariableName} = {NameOfControlMethod(controlVariableName)}(k{control.layout}Layout, {parentName});");
            }

            // Initialize usages array.
            if (usageCount > 0)
            {
                writer.WriteLine();
                writer.WriteLine("// Usages.");
                for (var i = 0; i < usageCount; ++i)
                {
                    writer.WriteLine(
                        $"builder.WithControlUsage({i}, new InternedString(\"{usagesForEachControl[i]}\"), {MakeControlVariableName(usageToControl[i])});");
                }
            }

            // Initialize aliases array.
            if (aliasCount > 0)
            {
                writer.WriteLine();
                writer.WriteLine("// Aliases.");
                for (var i = 0; i < aliasCount; ++i)
                {
                    writer.WriteLine($"builder.WithControlAlias({i},  new InternedString(\"{aliasesForEachControl[i]}\"));");
                }
            }

            // Emit initializers for control getters and control arrays. This is usually what's getting set up
            // in FinishSetup(). We hardcode the look results here.
            var controlGetterProperties = new Dictionary <Type, List <PropertyInfo> >();
            var controlArrayProperties  = new Dictionary <Type, List <PropertyInfo> >();

            writer.WriteLine();
            writer.WriteLine("// Control getters/arrays.");
            writer.EmitControlArrayInitializers(device, "this", controlArrayProperties);
            writer.EmitControlGetterInitializers(device, "this", controlGetterProperties);

            for (var i = 0; i < controlCount; ++i)
            {
                var control             = allControls[i];
                var controlVariableName = MakeControlVariableName(control);

                writer.EmitControlArrayInitializers(control, controlVariableName, controlArrayProperties);
                writer.EmitControlGetterInitializers(control, controlVariableName, controlGetterProperties);
            }

            // State offset to control index map.
            if (device.m_StateOffsetToControlMap != null)
            {
                writer.WriteLine();
                writer.WriteLine("// State offset to control index map.");
                writer.WriteLine("builder.WithStateOffsetToControlIndexMap(new uint[]");
                writer.WriteLine("{");
                ++writer.indentLevel;
                var map        = device.m_StateOffsetToControlMap;
                var entryCount = map.Length;
                for (var index = 0; index < entryCount;)
                {
                    if (index != 0)
                    {
                        writer.WriteLine();
                    }
                    // 10 entries a line.
                    writer.WriteIndent();
                    for (var i = 0; i < 10 && index < entryCount; ++index, ++i)
                    {
                        writer.Write((index != 0 ? ", " : "") + map[index] + "u");
                    }
                }
                writer.WriteLine();
                --writer.indentLevel;
                writer.WriteLine("});");
            }

            writer.WriteLine();

            writer.WriteLine("builder.Finish();");
            writer.EndBlock();

            for (var i = 0; i < controlCount; ++i)
            {
                var control             = allControls[i];
                var controlType         = control.GetType();
                var controlVariableName = MakeControlVariableName(control);
                var controlFieldInits   = control.GetInitializersForPublicPrimitiveTypeFields();
                writer.WriteLine();
                EmitControlMethod(writer, controlVariableName, controlType, controlFieldInits, i, control);
            }

            writer.EndBlock();
            writer.EndBlock();

            if (defines != null)
            {
                writer.WriteLine($"#endif // {defines}");
            }

            return(writer.buffer.ToString());
        }
Exemple #4
0
        // We emit this as a separate method instead of directly inline to avoid generating a single massive constructor method
        // as these can lead to large build times with il2cpp and C++ compilers (https://fogbugz.unity3d.com/f/cases/1282090/).
        private static void EmitControlMethod(InputActionCodeGenerator.Writer writer, string controlVariableName, Type controlType,
                                              string controlFieldInits, int i, InputControl control)
        {
            var controlTypeName = controlType.FullName.Replace('+', '.');

            writer.WriteLine($"private {controlTypeName} {NameOfControlMethod(controlVariableName)}(InternedString k{control.layout}Layout, InputControl parent)");
            writer.BeginBlock();
            writer.WriteLine($"var {controlVariableName} = new {controlTypeName}{controlFieldInits};");
            writer.WriteLine($"{controlVariableName}.Setup()");
            writer.WriteLine($"    .At(this, {i})");
            writer.WriteLine("    .WithParent(parent)");
            if (control.children.Count > 0)
            {
                writer.WriteLine($"    .WithChildren({control.m_ChildStartIndex}, {control.m_ChildCount})");
            }
            writer.WriteLine($"    .WithName(\"{control.name}\")");
            writer.WriteLine($"    .WithDisplayName(\"{control.m_DisplayNameFromLayout.Replace("\\", "\\\\")}\")");
            if (!string.IsNullOrEmpty(control.m_ShortDisplayNameFromLayout))
            {
                writer.WriteLine(
                    $"    .WithShortDisplayName(\"{control.m_ShortDisplayNameFromLayout.Replace("\\", "\\\\")}\")");
            }
            writer.WriteLine($"    .WithLayout(k{control.layout}Layout)");
            if (control.usages.Count > 0)
            {
                writer.WriteLine($"    .WithUsages({control.m_UsageStartIndex}, {control.m_UsageCount})");
            }
            if (control.aliases.Count > 0)
            {
                writer.WriteLine($"    .WithAliases({control.m_AliasStartIndex}, {control.m_AliasCount})");
            }
            if (control.noisy)
            {
                writer.WriteLine("    .IsNoisy(true)");
            }
            if (control.synthetic)
            {
                writer.WriteLine("    .IsSynthetic(true)");
            }
            if (control.dontReset)
            {
                writer.WriteLine("    .DontReset(true)");
            }
            if (control is ButtonControl)
            {
                writer.WriteLine("    .IsButton(true)");
            }
            writer.WriteLine("    .WithStateBlock(new InputStateBlock");
            writer.WriteLine("    {");
            writer.WriteLine($"        format = new FourCC({(int) control.stateBlock.format}),");
            writer.WriteLine($"        byteOffset = {control.stateBlock.byteOffset},");
            writer.WriteLine($"        bitOffset = {control.stateBlock.bitOffset},");
            writer.WriteLine($"        sizeInBits = {control.stateBlock.sizeInBits}");
            writer.WriteLine("    })");
            if (control.hasDefaultState)
            {
                writer.WriteLine($"    .WithDefaultState({control.m_DefaultState})");
            }
            if (control.m_MinValue != default || control.m_MaxValue != default)
            {
                writer.WriteLine($"    .WithMinAndMax({control.m_MinValue}, {control.m_MaxValue})");
            }
            foreach (var processor in control.GetProcessors())
            {
                var isEditorWindowSpaceProcessor = processor is EditorWindowSpaceProcessor;
                if (isEditorWindowSpaceProcessor)
                {
                    writer.WriteLine("    #if UNITY_EDITOR");
                }

                var processorType = processor.GetType().FullName.Replace("+", ".");
                var valueType     = InputProcessor.GetValueTypeFromType(processor.GetType());
                var fieldInits    = processor.GetInitializersForPublicPrimitiveTypeFields();

                writer.WriteLine(
                    $"    .WithProcessor<InputProcessor<{valueType}>, {valueType}>(new {processorType}{fieldInits})");

                if (isEditorWindowSpaceProcessor)
                {
                    writer.WriteLine("    #endif");
                }
            }

            writer.WriteLine("    .Finish();");

            if (control is KeyControl key)
            {
                writer.WriteLine($"{controlVariableName}.keyCode = UnityEngine.InputSystem.Key.{key.keyCode};");
            }
            else if (control is DpadControl.DpadAxisControl dpadAxis)
            {
                writer.WriteLine($"{controlVariableName}.component = {dpadAxis.component};");
            }

            writer.WriteLine($"return {controlVariableName};");
            writer.EndBlock();
        }
Exemple #5
0
        /// <summary>
        /// Generate C# code that for the given device layout called <paramref name="layoutName"/> instantly creates
        /// an <see cref="InputDevice"/> equivalent to what the input system would create by manually interpreting
        /// the given <see cref="InputControlLayout"/>.
        /// </summary>
        /// <param name="layoutName">Name of the device layout to generate code for.</param>
        /// <param name="defines">Null/empty or a valid expression for an #if conditional compilation statement.</param>
        /// <param name="namePrefix">Prefix to prepend to the type name of <paramref name="layoutName"/>.</param>
        /// <param name="visibility">C# access modifier to use with the generated class.</param>
        /// <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>
        /// <returns>C# source code for a precompiled version of the device layout.</returns>
        /// <remarks>
        /// The code generated by this method will be many times faster than the reflection-based <see cref="InputDevice"/>
        /// creation normally performed by the input system. It will also create less GC heap garbage.
        ///
        /// The downside to the generated code is that the makeup of the device is hardcoded and can no longer
        /// be changed by altering the <see cref="InputControlLayout"/> setup of the system.
        ///
        /// Note that it is possible to use this method with layouts generated on-the-fly by layout builders such as
        /// the one employed for <see cref="HID"/>. However, this must be done at compile/build time and can thus not
        /// be done for devices dynamically discovered at runtime. When this is acceptable, it is a way to dramatically
        /// speed up the creation of these devices.
        /// </remarks>
        /// <seealso cref="InputSystem.RegisterPrecompiledLayout{T}"/>
        public static string GenerateCodeForDeviceLayout(string layoutName, string defines = null, string namePrefix = "Fast", string visibility = "public", string @namespace = null)
        {
            if (string.IsNullOrEmpty(layoutName))
            {
                throw new ArgumentNullException(nameof(layoutName));
            }

            // Produce a device from the layout.
            var device = InputDevice.Build <InputDevice>(layoutName, noPrecompiledLayouts: true);

            // Get info about base type.
            var baseType          = device.GetType();
            var baseTypeName      = baseType.Name;
            var baseTypeNamespace = baseType.Namespace;

            // Begin generating code.
            var writer = new InputActionCodeGenerator.Writer
            {
                buffer = new StringBuilder()
            };

            writer.WriteLine(CSharpCodeHelpers.MakeAutoGeneratedCodeHeader("com.unity.inputsystem:InputLayoutCodeGenerator",
                                                                           InputSystem.version.ToString(),
                                                                           $"\"{layoutName}\" layout"));

            // Defines.
            if (defines != null)
            {
                writer.WriteLine($"#if {defines}");
                writer.WriteLine();
            }

            if (@namespace == null)
            {
                @namespace = baseTypeNamespace;
            }

            writer.WriteLine("using UnityEngine.InputSystem;");
            writer.WriteLine("using UnityEngine.InputSystem.LowLevel;");
            writer.WriteLine("using UnityEngine.InputSystem.Utilities;");
            writer.WriteLine("");
            if (@namespace != "")
            {
                writer.WriteLine("namespace " + @namespace);
            }
            writer.BeginBlock();

            writer.WriteLine($"{visibility} partial class {namePrefix}{baseTypeName} : {baseTypeNamespace}.{baseTypeName}");
            writer.BeginBlock();

            // "Metadata". ATM this is simply a flat, semicolon-separated list of names for layouts and processors that
            // we depend on. If any of them are touched, the precompiled layout should be considered invalidated.
            var internedLayoutName = new InternedString(layoutName);
            var allControls        = device.allControls;
            var usedControlLayouts = allControls.Select(x => x.m_Layout).Distinct().ToList();
            var layoutDependencies = string.Join(";",
                                                 usedControlLayouts.SelectMany(l => InputControlLayout.s_Layouts.GetBaseLayouts(l))
                                                 .Union(InputControlLayout.s_Layouts.GetBaseLayouts(internedLayoutName)));
            var processorDependencies = string.Join(";",
                                                    allControls.SelectMany(c => c.GetProcessors()).Select(p => InputProcessor.s_Processors.FindNameForType(p.GetType()))
                                                    .Where(n => !n.IsEmpty()).Distinct());
            var metadata = string.Join(";", processorDependencies, layoutDependencies);

            writer.WriteLine($"public const string metadata = \"{metadata}\";");

            // Constructor.
            writer.WriteLine($"public {namePrefix}{baseTypeName}()");
            writer.BeginBlock();

            var usagesForEachControl  = device.m_UsagesForEachControl;
            var usageToControl        = device.m_UsageToControl;
            var aliasesForEachControl = device.m_AliasesForEachControl;
            var controlCount          = allControls.Count;
            var usageCount            = usagesForEachControl?.Length ?? 0;
            var aliasCount            = aliasesForEachControl?.Length ?? 0;

            // Set up device control info.
            writer.WriteLine($"var builder = this.Setup({controlCount}, {usageCount}, {aliasCount})");
            writer.WriteLine($"    .WithName(\"{device.name}\")");
            writer.WriteLine($"    .WithDisplayName(\"{device.displayName}\")");
            writer.WriteLine($"    .WithChildren({device.m_ChildStartIndex}, {device.m_ChildCount})");
            writer.WriteLine($"    .WithLayout(new InternedString(\"{device.layout}\"))");
            if (device.noisy)
            {
                writer.WriteLine("    .IsNoisy(true)");
            }
            writer.WriteLine($"    .WithStateBlock(new InputStateBlock {{ format = new FourCC({(int)device.stateBlock.format}), sizeInBits = {device.stateBlock.sizeInBits} }});");

            // Add controls to device.
            writer.WriteLine();
            foreach (var layout in usedControlLayouts)
            {
                writer.WriteLine($"var k{layout}Layout = new InternedString(\"{layout}\");");
            }
            for (var i = 0; i < controlCount; ++i)
            {
                var control             = allControls[i];
                var controlType         = control.GetType();
                var controlVariableName = MakeControlVariableName(control);
                var controlFieldInits   = control.GetInitializersForPublicPrimitiveTypeFields();

                writer.WriteLine("");
                writer.WriteLine($"// {control.path}");
                writer.WriteLine($"var {controlVariableName} = new {controlType.FullName.Replace('+', '.')}{controlFieldInits};");
                writer.WriteLine($"{controlVariableName}.Setup()");
                writer.WriteLine($"    .At(this, {i})");
                writer.WriteLine(control.parent == device
                    ? "    .WithParent(this)"
                    : $"    .WithParent({MakeControlVariableName(control.parent)})");
                if (control.children.Count > 0)
                {
                    writer.WriteLine($"    .WithChildren({control.m_ChildStartIndex}, {control.m_ChildCount})");
                }
                writer.WriteLine($"    .WithName(\"{control.name}\")");
                writer.WriteLine($"    .WithDisplayName(\"{control.m_DisplayNameFromLayout.Replace("\\", "\\\\")}\")");
                if (!string.IsNullOrEmpty(control.m_ShortDisplayNameFromLayout))
                {
                    writer.WriteLine($"    .WithShortDisplayName(\"{control.m_ShortDisplayNameFromLayout.Replace("\\", "\\\\")}\")");
                }
                writer.WriteLine($"    .WithLayout(k{control.layout}Layout)");
                if (control.usages.Count > 0)
                {
                    writer.WriteLine($"    .WithUsages({control.m_UsageStartIndex}, {control.m_UsageCount})");
                }
                if (control.aliases.Count > 0)
                {
                    writer.WriteLine($"    .WithAliases({control.m_AliasStartIndex}, {control.m_AliasCount})");
                }
                if (control.noisy)
                {
                    writer.WriteLine("    .IsNoisy(true)");
                }
                if (control.synthetic)
                {
                    writer.WriteLine("    .IsSynthetic(true)");
                }
                writer.WriteLine("    .WithStateBlock(new InputStateBlock");
                writer.WriteLine("    {");
                writer.WriteLine($"        format = new FourCC({(int)control.stateBlock.format}),");
                writer.WriteLine($"        byteOffset = {control.stateBlock.byteOffset},");
                writer.WriteLine($"        bitOffset = {control.stateBlock.bitOffset},");
                writer.WriteLine($"        sizeInBits = {control.stateBlock.sizeInBits}");
                writer.WriteLine("    })");
                if (control.hasDefaultState)
                {
                    writer.WriteLine($"    .WithDefaultState({control.m_DefaultState})");
                }
                if (control.m_MinValue != default || control.m_MaxValue != default)
                {
                    writer.WriteLine($"    .WithMinAndMax({control.m_MinValue}, {control.m_MaxValue})");
                }
                foreach (var processor in control.GetProcessors())
                {
                    var isEditorWindowSpaceProcessor = processor is EditorWindowSpaceProcessor;
                    if (isEditorWindowSpaceProcessor)
                    {
                        writer.WriteLine("    #if UNITY_EDITOR");
                    }

                    var processorType = processor.GetType().FullName.Replace("+", ".");
                    var valueType     = InputProcessor.GetValueTypeFromType(processor.GetType());
                    var fieldInits    = processor.GetInitializersForPublicPrimitiveTypeFields();

                    writer.WriteLine($"    .WithProcessor<InputProcessor<{valueType}>, {valueType}>(new {processorType}{fieldInits})");

                    if (isEditorWindowSpaceProcessor)
                    {
                        writer.WriteLine("    #endif");
                    }
                }
                writer.WriteLine("    .Finish();");

                if (control is KeyControl key)
                {
                    writer.WriteLine($"{controlVariableName}.keyCode = UnityEngine.InputSystem.Key.{key.keyCode};");
                }
                else if (control is DpadControl.DpadAxisControl dpadAxis)
                {
                    writer.WriteLine($"{controlVariableName}.component = {dpadAxis.component};");
                }
            }

            // Initialize usages array.
            if (usageCount > 0)
            {
                writer.WriteLine();
                writer.WriteLine("// Usages.");
                for (var i = 0; i < usageCount; ++i)
                {
                    writer.WriteLine(
                        $"builder.WithControlUsage({i}, new InternedString(\"{usagesForEachControl[i]}\"), {MakeControlVariableName(usageToControl[i])});");
                }
            }

            // Initialize aliases array.
            if (aliasCount > 0)
            {
                writer.WriteLine();
                writer.WriteLine("// Aliases.");
                for (var i = 0; i < aliasCount; ++i)
                {
                    writer.WriteLine($"builder.WithControlAlias({i},  new InternedString(\"{aliasesForEachControl[i]}\"));");
                }
            }

            // Emit initializers for control getters and control arrays. This is usually what's getting set up
            // in FinishSetup(). We hardcode the look results here.
            var controlGetterProperties = new Dictionary <Type, List <PropertyInfo> >();
            var controlArrayProperties  = new Dictionary <Type, List <PropertyInfo> >();

            writer.WriteLine();
            writer.WriteLine("// Control getters/arrays.");
            writer.EmitControlArrayInitializers(device, "this", controlArrayProperties);
            writer.EmitControlGetterInitializers(device, "this", controlGetterProperties);

            for (var i = 0; i < controlCount; ++i)
            {
                var control             = allControls[i];
                var controlVariableName = MakeControlVariableName(control);

                writer.EmitControlArrayInitializers(control, controlVariableName, controlArrayProperties);
                writer.EmitControlGetterInitializers(control, controlVariableName, controlGetterProperties);
            }

            writer.WriteLine("builder.Finish();");
            writer.EndBlock();

            writer.EndBlock();
            writer.EndBlock();

            if (defines != null)
            {
                writer.WriteLine($"#endif // {defines}");
            }

            return(writer.buffer.ToString());
        }