public static string GenerateWrapperCode(InputActionAsset asset, Options options = default) { if (asset == null) { throw new ArgumentNullException(nameof(asset)); } if (string.IsNullOrEmpty(options.sourceAssetPath)) { options.sourceAssetPath = AssetDatabase.GetAssetPath(asset); } if (string.IsNullOrEmpty(options.className) && !string.IsNullOrEmpty(asset.name)) { options.className = CSharpCodeHelpers.MakeTypeName(asset.name); } if (string.IsNullOrEmpty(options.className)) { if (string.IsNullOrEmpty(options.sourceAssetPath)) { throw new ArgumentException("options.sourceAssetPath"); } options.className = CSharpCodeHelpers.MakeTypeName(Path.GetFileNameWithoutExtension(options.sourceAssetPath)); } var writer = new Writer { buffer = new StringBuilder() }; // Header. writer.WriteLine(CSharpCodeHelpers.MakeAutoGeneratedCodeHeader("com.unity.inputsystem:InputActionCodeGenerator", InputSystem.version.ToString(), options.sourceAssetPath)); // Usings. writer.WriteLine("using System;"); writer.WriteLine("using System.Collections;"); writer.WriteLine("using System.Collections.Generic;"); writer.WriteLine("using UnityEngine.InputSystem;"); writer.WriteLine("using UnityEngine.InputSystem.Utilities;"); writer.WriteLine(""); // Begin namespace. var haveNamespace = !string.IsNullOrEmpty(options.namespaceName); if (haveNamespace) { writer.WriteLine($"namespace {options.namespaceName}"); writer.BeginBlock(); } // Begin class. writer.WriteLine($"public partial class @{options.className} : IInputActionCollection2, IDisposable"); writer.BeginBlock(); writer.WriteLine($"public InputActionAsset asset {{ get; }}"); // Default constructor. writer.WriteLine($"public @{options.className}()"); writer.BeginBlock(); writer.WriteLine($"asset = InputActionAsset.FromJson(@\"{asset.ToJson().Replace("\"", "\"\"")}\");"); var maps = asset.actionMaps; var schemes = asset.controlSchemes; foreach (var map in maps) { var mapName = CSharpCodeHelpers.MakeIdentifier(map.name); writer.WriteLine($"// {map.name}"); writer.WriteLine($"m_{mapName} = asset.FindActionMap(\"{map.name}\", throwIfNotFound: true);"); foreach (var action in map.actions) { var actionName = CSharpCodeHelpers.MakeIdentifier(action.name); writer.WriteLine($"m_{mapName}_{actionName} = m_{mapName}.FindAction(\"{action.name}\", throwIfNotFound: true);"); } } writer.EndBlock(); writer.WriteLine(); writer.WriteLine("public void Dispose()"); writer.BeginBlock(); writer.WriteLine("UnityEngine.Object.Destroy(asset);"); writer.EndBlock(); writer.WriteLine(); writer.WriteLine("public InputBinding? bindingMask"); writer.BeginBlock(); writer.WriteLine("get => asset.bindingMask;"); writer.WriteLine("set => asset.bindingMask = value;"); writer.EndBlock(); writer.WriteLine(); writer.WriteLine("public ReadOnlyArray<InputDevice>? devices"); writer.BeginBlock(); writer.WriteLine("get => asset.devices;"); writer.WriteLine("set => asset.devices = value;"); writer.EndBlock(); writer.WriteLine(); writer.WriteLine("public ReadOnlyArray<InputControlScheme> controlSchemes => asset.controlSchemes;"); writer.WriteLine(); writer.WriteLine("public bool Contains(InputAction action)"); writer.BeginBlock(); writer.WriteLine("return asset.Contains(action);"); writer.EndBlock(); writer.WriteLine(); writer.WriteLine("public IEnumerator<InputAction> GetEnumerator()"); writer.BeginBlock(); writer.WriteLine("return asset.GetEnumerator();"); writer.EndBlock(); writer.WriteLine(); writer.WriteLine("IEnumerator IEnumerable.GetEnumerator()"); writer.BeginBlock(); writer.WriteLine("return GetEnumerator();"); writer.EndBlock(); writer.WriteLine(); writer.WriteLine("public void Enable()"); writer.BeginBlock(); writer.WriteLine("asset.Enable();"); writer.EndBlock(); writer.WriteLine(); writer.WriteLine("public void Disable()"); writer.BeginBlock(); writer.WriteLine("asset.Disable();"); writer.EndBlock(); writer.WriteLine("public IEnumerable<InputBinding> bindings => asset.bindings;"); writer.WriteLine(); writer.WriteLine("public InputAction FindAction(string actionNameOrId, bool throwIfNotFound = false)"); writer.BeginBlock(); writer.WriteLine("return asset.FindAction(actionNameOrId, throwIfNotFound);"); writer.EndBlock(); writer.WriteLine("public int FindBinding(InputBinding bindingMask, out InputAction action)"); writer.BeginBlock(); writer.WriteLine("return asset.FindBinding(bindingMask, out action);"); writer.EndBlock(); // Action map accessors. foreach (var map in maps) { writer.WriteLine(); writer.WriteLine($"// {map.name}"); var mapName = CSharpCodeHelpers.MakeIdentifier(map.name); var mapTypeName = CSharpCodeHelpers.MakeTypeName(mapName, "Actions"); // Caching field for action map. writer.WriteLine($"private readonly InputActionMap m_{mapName};"); writer.WriteLine(string.Format("private I{0} m_{0}CallbackInterface;", mapTypeName)); // Caching fields for all actions. foreach (var action in map.actions) { var actionName = CSharpCodeHelpers.MakeIdentifier(action.name); writer.WriteLine($"private readonly InputAction m_{mapName}_{actionName};"); } // Struct wrapping access to action set. writer.WriteLine($"public struct {mapTypeName}"); writer.BeginBlock(); // Constructor. writer.WriteLine($"private @{options.className} m_Wrapper;"); writer.WriteLine($"public {mapTypeName}(@{options.className} wrapper) {{ m_Wrapper = wrapper; }}"); // Getter for each action. foreach (var action in map.actions) { var actionName = CSharpCodeHelpers.MakeIdentifier(action.name); writer.WriteLine( $"public InputAction @{actionName} => m_Wrapper.m_{mapName}_{actionName};"); } // Action map getter. writer.WriteLine($"public InputActionMap Get() {{ return m_Wrapper.m_{mapName}; }}"); // Enable/disable methods. writer.WriteLine("public void Enable() { Get().Enable(); }"); writer.WriteLine("public void Disable() { Get().Disable(); }"); writer.WriteLine("public bool enabled => Get().enabled;"); // Implicit conversion operator. writer.WriteLine( $"public static implicit operator InputActionMap({mapTypeName} set) {{ return set.Get(); }}"); // SetCallbacks method. writer.WriteLine($"public void SetCallbacks(I{mapTypeName} instance)"); writer.BeginBlock(); ////REVIEW: this would benefit from having a single callback on InputActions rather than three different endpoints // Uninitialize existing interface. writer.WriteLine($"if (m_Wrapper.m_{mapTypeName}CallbackInterface != null)"); writer.BeginBlock(); foreach (var action in map.actions) { var actionName = CSharpCodeHelpers.MakeIdentifier(action.name); var actionTypeName = CSharpCodeHelpers.MakeTypeName(action.name); writer.WriteLine($"@{actionName}.started -= m_Wrapper.m_{mapTypeName}CallbackInterface.On{actionTypeName};"); writer.WriteLine($"@{actionName}.performed -= m_Wrapper.m_{mapTypeName}CallbackInterface.On{actionTypeName};"); writer.WriteLine($"@{actionName}.canceled -= m_Wrapper.m_{mapTypeName}CallbackInterface.On{actionTypeName};"); } writer.EndBlock(); // Initialize new interface. writer.WriteLine($"m_Wrapper.m_{mapTypeName}CallbackInterface = instance;"); writer.WriteLine("if (instance != null)"); writer.BeginBlock(); foreach (var action in map.actions) { var actionName = CSharpCodeHelpers.MakeIdentifier(action.name); var actionTypeName = CSharpCodeHelpers.MakeTypeName(action.name); writer.WriteLine($"@{actionName}.started += instance.On{actionTypeName};"); writer.WriteLine($"@{actionName}.performed += instance.On{actionTypeName};"); writer.WriteLine($"@{actionName}.canceled += instance.On{actionTypeName};"); } writer.EndBlock(); writer.EndBlock(); writer.EndBlock(); // Getter for instance of struct. writer.WriteLine($"public {mapTypeName} @{mapName} => new {mapTypeName}(this);"); } // Control scheme accessors. foreach (var scheme in schemes) { var identifier = CSharpCodeHelpers.MakeIdentifier(scheme.name); writer.WriteLine($"private int m_{identifier}SchemeIndex = -1;"); writer.WriteLine($"public InputControlScheme {identifier}Scheme"); writer.BeginBlock(); writer.WriteLine("get"); writer.BeginBlock(); writer.WriteLine($"if (m_{identifier}SchemeIndex == -1) m_{identifier}SchemeIndex = asset.FindControlSchemeIndex(\"{scheme.name}\");"); writer.WriteLine($"return asset.controlSchemes[m_{identifier}SchemeIndex];"); writer.EndBlock(); writer.EndBlock(); } // Generate interfaces. foreach (var map in maps) { var typeName = CSharpCodeHelpers.MakeTypeName(map.name); writer.WriteLine($"public interface I{typeName}Actions"); writer.BeginBlock(); foreach (var action in map.actions) { var methodName = CSharpCodeHelpers.MakeTypeName(action.name); writer.WriteLine($"void On{methodName}(InputAction.CallbackContext context);"); } writer.EndBlock(); } // End class. writer.EndBlock(); // End namespace. if (haveNamespace) { writer.EndBlock(); } return(writer.buffer.ToString()); }
/// <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()); }
/// <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()); }