From 383001b57043aff74d76d2fe7713083f2c53e7d0 Mon Sep 17 00:00:00 2001 From: Rene Damm Date: Fri, 23 Aug 2019 11:47:07 +0200 Subject: [PATCH 1/2] CHANGE: Make ToHumanReadableString() respect display names. --- .../Tests/InputSystem/CoreTests_Controls.cs | 13 +- Packages/com.unity.inputsystem/CHANGELOG.md | 5 + .../Controls/InputControlLayout.cs | 44 +++++ .../InputSystem/Controls/InputControlPath.cs | 170 ++++++++++++++---- .../InputSystem/Devices/InputDeviceBuilder.cs | 29 ++- .../ControlPicker/InputControlDropdownItem.cs | 28 ++- .../Editor/EditorInputControlLayoutCache.cs | 48 ++--- .../InputSystem/InputManager.cs | 9 + .../Plugins/XInput/XInputController.cs | 11 ++ 9 files changed, 272 insertions(+), 85 deletions(-) diff --git a/Assets/Tests/InputSystem/CoreTests_Controls.cs b/Assets/Tests/InputSystem/CoreTests_Controls.cs index e6c0b213e0..e389c4e4b8 100644 --- a/Assets/Tests/InputSystem/CoreTests_Controls.cs +++ b/Assets/Tests/InputSystem/CoreTests_Controls.cs @@ -996,11 +996,20 @@ public void Controls_DisplayNameForNestedControls_IncludesNameOfParentControl() public void Controls_CanTurnControlPathIntoHumanReadableText() { Assert.That(InputControlPath.ToHumanReadableString("*/{PrimaryAction}"), Is.EqualTo("PrimaryAction [Any]")); - Assert.That(InputControlPath.ToHumanReadableString("/leftStick"), Is.EqualTo("leftStick [Gamepad]")); - Assert.That(InputControlPath.ToHumanReadableString("/leftStick/x"), Is.EqualTo("leftStick/x [Gamepad]")); + Assert.That(InputControlPath.ToHumanReadableString("/leftStick"), Is.EqualTo("Left Stick [Gamepad]")); + Assert.That(InputControlPath.ToHumanReadableString("/leftStick/x"), Is.EqualTo("Left Stick/X [Gamepad]")); Assert.That(InputControlPath.ToHumanReadableString("{LeftHand}/position"), Is.EqualTo("position [LeftHand XRController]")); Assert.That(InputControlPath.ToHumanReadableString("*/leftStick"), Is.EqualTo("leftStick [Any]")); Assert.That(InputControlPath.ToHumanReadableString("*/{PrimaryMotion}/x"), Is.EqualTo("PrimaryMotion/x [Any]")); + Assert.That(InputControlPath.ToHumanReadableString("/buttonSouth"), Is.EqualTo("Button South [Gamepad]")); + Assert.That(InputControlPath.ToHumanReadableString("/buttonSouth"), Is.EqualTo("A [Xbox Controller]")); + + Assert.That( + InputControlPath.ToHumanReadableString("/buttonSouth", + InputControlPath.HumanReadableStringOptions.OmitDevice), Is.EqualTo("Button South")); + Assert.That( + InputControlPath.ToHumanReadableString("*/{PrimaryAction}", + InputControlPath.HumanReadableStringOptions.OmitDevice), Is.EqualTo("PrimaryAction")); } [Test] diff --git a/Packages/com.unity.inputsystem/CHANGELOG.md b/Packages/com.unity.inputsystem/CHANGELOG.md index 200f3f64d0..c523b5821f 100755 --- a/Packages/com.unity.inputsystem/CHANGELOG.md +++ b/Packages/com.unity.inputsystem/CHANGELOG.md @@ -15,11 +15,16 @@ however, it has to be formatted properly to pass verification tests. #### Actions +- Binding paths now show the same way in the action editor UI as they do in the control picker. + * For example, where before a binding to `/buttonSouth` was shown as `rightShoulder [XInputController]`, the same binding will now show as `A [Xbox Controller]`. + ### Changed - `InputUser.onUnpairedDeviceUsed` now receives a 2nd argument which is the event that triggered the callback. * Also, the callback is now triggered __BEFORE__ the given event is processed rather than after the event has already been written to the device. This allows updating the pairing state of the system before input is processed. * In practice, this means that, for example, if the user switches from keyboard&mouse to gamepad, the initial input that triggered the switch will get picked up right away. +- `InputControlPath.ToHumanReadableString` now takes display names from registered `InputControlLayout` instances into account. + * This means that the method can now be used to generate strings to display in rebinding UIs. #### Actions diff --git a/Packages/com.unity.inputsystem/InputSystem/Controls/InputControlLayout.cs b/Packages/com.unity.inputsystem/InputSystem/Controls/InputControlLayout.cs index 90358ab593..a86a557c13 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Controls/InputControlLayout.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Controls/InputControlLayout.cs @@ -343,6 +343,7 @@ public ControlItem this[string path] if (string.IsNullOrEmpty(path)) throw new ArgumentNullException(nameof(path)); + // Does not use FindControl so that we don't force-intern the given path string. if (m_Controls != null) { for (var i = 0; i < m_Controls.Length; ++i) @@ -1825,5 +1826,48 @@ public InputControlLayout FindOrLoadLayout(string name) throw new LayoutNotFoundException(name); } } + + internal static Cache s_CacheInstance; + internal static int s_CacheInstanceRef; + + // Constructing InputControlLayouts is very costly as it tends to involve lots of reflection and + // piecing data together. Thus, wherever possible, we want to keep layouts around for as long as + // we need them yet at the same time not keep them needlessly around while we don't. + // + // This property makes a cache of layouts available globally yet implements a resource acquisition + // based pattern to make sure we keep the cache alive only within specific execution scopes. + internal static ref Cache cache + { + get + { + Debug.Assert(s_CacheInstanceRef > 0, "Must hold an instance reference"); + return ref s_CacheInstance; + } + } + + internal static CacheRefInstance CacheRef() + { + ++s_CacheInstanceRef; + return new CacheRefInstance {valid = true}; + } + + internal struct CacheRefInstance : IDisposable + { + public bool valid; // Make sure we can distinguish default-initialized instances. + public void Dispose() + { + if (!valid) + return; + + --s_CacheInstanceRef; + if (s_CacheInstanceRef <= 0) + { + s_CacheInstance = default; + s_CacheInstanceRef = 0; + } + + valid = false; + } + } } } diff --git a/Packages/com.unity.inputsystem/InputSystem/Controls/InputControlPath.cs b/Packages/com.unity.inputsystem/InputSystem/Controls/InputControlPath.cs index 76a45908f2..73eeb75c71 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Controls/InputControlPath.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Controls/InputControlPath.cs @@ -113,12 +113,60 @@ public static string Combine(InputControl parent, string path) return $"{parent.path}/{path}"; } + /// + /// Options for customizing the behavior of . + /// + [Flags] + public enum HumanReadableStringOptions + { + /// + /// The default behavior. + /// + None = 0, + + /// + /// Do not mention the device of the control. For example, instead of "A [Gamepad]", + /// return just "A". + /// + OmitDevice = 1 << 1, + } + + ////TODO: factor out the part that looks up an InputControlLayout.ControlItem from a given path + //// and make that available as a stand-alone API + ////TODO: add option to customize path separation character /// /// Create a human readable string from the given control path. /// /// A control path such as "<XRController>{LeftHand}/position". - /// A string such as "leftStick/x [Gamepad]". - public static string ToHumanReadableString(string path) + /// Customize the resulting string. + /// A string such as "Left Stick/X [Gamepad]". + /// + /// This function is most useful for turning binding paths (see ) + /// into strings that can be displayed in UIs (such as rebinding screens). It is used by + /// the Unity editor itself to display binding paths in the UI. + /// + /// The method uses display names (see , + /// , and ) + /// where possible. For example, if a "<XInputController>/buttonSouth" will be returned as + /// "A [Xbox Controller]" as the display name of is "XBox Controller" + /// and the display name of its "buttonSouth" control is "A". + /// + /// Note that these lookups depend on the currently registered control layouts (see ) and different strings may thus be returned for the same control + /// path depending on the layouts registered with the system. + /// + /// + /// + /// InputControlPath.ToHumanReadableString("*/{PrimaryAction"); // -> "PrimaryAction [Any]" + /// InputControlPath.ToHumanReadableString("<Gamepad>/buttonSouth"); // -> "Button South [Gamepad]" + /// InputControlPath.ToHumanReadableString("<XInputController>/buttonSouth"); // -> "A [Xbox Controller]" + /// InputControlPath.ToHumanReadableString("<Gamepad>/leftStick/x"); // -> "Left Stick/X [Gamepad]" + /// + /// + /// + /// + public static string ToHumanReadableString(string path, + HumanReadableStringOptions options = HumanReadableStringOptions.None) { if (string.IsNullOrEmpty(path)) return string.Empty; @@ -126,38 +174,45 @@ public static string ToHumanReadableString(string path) var buffer = new StringBuilder(); var parser = new PathParser(path); - ////REVIEW: ideally, we'd use display names of controls rather than the control paths directly from the path - - // First level is taken to be device. - if (parser.MoveToNextComponent()) + // For display names of controls and devices, we need to look at InputControlLayouts. + // If none is in place here, we establish a temporary layout cache while we go through + // the path. If one is in place already, we reuse what's already there. + using (InputControlLayout.CacheRef()) { - var device = parser.current.ToHumanReadableString(); - - // Any additional levels (if present) are taken to form a control path on the device. - var isFirstControlLevel = true; - while (parser.MoveToNextComponent()) + // First level is taken to be device. + if (parser.MoveToNextComponent()) { - if (!isFirstControlLevel) - buffer.Append('/'); + // Keep track of which control layout we're on (if any) as we're crawling + // down the path. + var device = parser.current.ToHumanReadableString(null, out var currentLayoutName); - buffer.Append(parser.current.ToHumanReadableString()); - isFirstControlLevel = false; - } + // Any additional levels (if present) are taken to form a control path on the device. + var isFirstControlLevel = true; + while (parser.MoveToNextComponent()) + { + if (!isFirstControlLevel) + buffer.Append('/'); - if (!string.IsNullOrEmpty(device)) - { - buffer.Append(" ["); - buffer.Append(device); - buffer.Append(']'); + buffer.Append(parser.current.ToHumanReadableString( + currentLayoutName, out currentLayoutName)); + isFirstControlLevel = false; + } + + if ((options & HumanReadableStringOptions.OmitDevice) == 0 && !string.IsNullOrEmpty(device)) + { + buffer.Append(" ["); + buffer.Append(device); + buffer.Append(']'); + } } - } - // If we didn't manage to figure out a display name, default to displaying - // the path as is. - if (buffer.Length == 0) - return path; + // If we didn't manage to figure out a display name, default to displaying + // the path as is. + if (buffer.Length == 0) + return path; - return buffer.ToString(); + return buffer.ToString(); + } } public static string[] TryGetDeviceUsages(string path) @@ -171,7 +226,7 @@ public static string[] TryGetDeviceUsages(string path) if (parser.current.usages != null && parser.current.usages.Length > 0) { - return Array.ConvertAll(parser.current.usages, i => { return i.ToString(); }); + return Array.ConvertAll(parser.current.usages, i => { return i.ToString(); }); } return null; @@ -219,11 +274,6 @@ public static string TryGetDeviceLayout(string path) ////TODO: return Substring and use path parser; should get rid of allocations // From the given control path, try to determine the control layout being used. - // - // NOTE: This function will only use information available in the path itself or - // in layouts referenced by the path. It will not look at actual devices - // in the system. This is to make the behavior predictable and not dependent - // on whether you currently have the right device connected or not. // NOTE: Allocates! public static string TryGetControlLayout(string path) { @@ -956,8 +1006,10 @@ internal struct ParsedPathComponent public bool isWildcard => name == Wildcard; public bool isDoubleWildcard => name == DoubleWildcard; - public string ToHumanReadableString() + public string ToHumanReadableString(string parentLayoutName, out string referencedLayoutName) { + referencedLayoutName = null; + var result = string.Empty; if (isWildcard) result += "Any"; @@ -987,18 +1039,60 @@ public string ToHumanReadableString() if (!layout.isEmpty) { + referencedLayoutName = layout.ToString(); + + // Where possible, use the displayName of the given layout rather than + // just the internal layout name. + string layoutString; + var referencedLayout = InputControlLayout.cache.FindOrLoadLayout(referencedLayoutName); + if (referencedLayout != null && !string.IsNullOrEmpty(referencedLayout.m_DisplayName)) + layoutString = referencedLayout.m_DisplayName; + else + layoutString = ToHumanReadableString(layout); + if (!string.IsNullOrEmpty(result)) - result += ' ' + ToHumanReadableString(layout); + result += ' ' + layoutString; else - result += ToHumanReadableString(layout); + result += layoutString; } if (!name.isEmpty && !isWildcard) { + // If we have a layout from a preceding path component, try to find + // the control by name on the layout. If we find it, use it's display + // name rather than the name referenced in the binding. + string nameString = null; + if (!string.IsNullOrEmpty(parentLayoutName)) + { + // NOTE: This produces a fully merged layout. We should thus pick up display names + // from base layouts automatically wherever applicable. + var parentLayout = InputControlLayout.cache.FindOrLoadLayout(new InternedString(parentLayoutName)); + if (parentLayout != null) + { + var controlName = new InternedString(name.ToString()); + var control = parentLayout.FindControl(controlName); + if (control != null) + { + if (!string.IsNullOrEmpty(control.Value.displayName)) + nameString = control.Value.displayName; + + // If we don't have an explicit part in the component, take + // remember the name of the layout referenced by the control name so + // that path components further down the line can keep looking up their + // display names. + if (string.IsNullOrEmpty(referencedLayoutName)) + referencedLayoutName = control.Value.layout; + } + } + } + + if (nameString == null) + nameString = ToHumanReadableString(name); + if (!string.IsNullOrEmpty(result)) - result += ' ' + ToHumanReadableString(name); + result += ' ' + nameString; else - result += ToHumanReadableString(name); + result += nameString; } if (!displayName.isEmpty) diff --git a/Packages/com.unity.inputsystem/InputSystem/Devices/InputDeviceBuilder.cs b/Packages/com.unity.inputsystem/InputSystem/Devices/InputDeviceBuilder.cs index d20d08c2f8..b93fb17cd9 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Devices/InputDeviceBuilder.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Devices/InputDeviceBuilder.cs @@ -43,11 +43,13 @@ namespace UnityEngine.InputSystem.Layouts /// Existing controls may be reused while at the same time the hierarchy and even the device instance /// itself may change. /// - internal struct InputDeviceBuilder + internal struct InputDeviceBuilder : IDisposable { public void Setup(InternedString layout, InternedString variants, InputDeviceDescription deviceDescription = default) { + m_LayoutCacheRef = InputControlLayout.CacheRef(); + InstantiateLayout(layout, variants, new InternedString(), null); FinalizeControlHierarchy(); @@ -67,12 +69,16 @@ public InputDevice Finish() return device; } - internal InputDevice m_Device; + public void Dispose() + { + m_LayoutCacheRef.Dispose(); + } + + private InputDevice m_Device; - // We construct layouts lazily as we go but keep them cached while we - // set up hierarchies so that we don't re-construct the same Button layout - // 256 times for a keyboard. - private InputControlLayout.Cache m_LayoutCache; + // Make sure the global layout cache sticks around for at least as long + // as the device builder so that we don't load layouts over and over. + private InputControlLayout.CacheRefInstance m_LayoutCacheRef; // Table mapping (lower-cased) control paths to control layouts that contain // overrides for the control at the given path. @@ -725,7 +731,8 @@ private static void SetFormat(InputControl control, InputControlLayout.ControlIt private InputControlLayout FindOrLoadLayout(string name) { - return m_LayoutCache.FindOrLoadLayout(name); + Debug.Assert(InputControlLayout.s_CacheInstanceRef > 0, "Should have acquired layout cache reference"); + return InputControlLayout.cache.FindOrLoadLayout(name); } private static void ComputeStateLayout(InputControl control) @@ -896,7 +903,7 @@ internal static ref InputDeviceBuilder instance } } - public static RefInstance Ref() + internal static RefInstance Ref() { Debug.Assert(s_Instance.m_Device == null, "InputDeviceBuilder is already in use! Cannot use the builder recursively"); @@ -911,8 +918,12 @@ internal struct RefInstance : IDisposable public void Dispose() { --s_InstanceRef; - if (s_InstanceRef == 0) + if (s_InstanceRef <= 0) + { + s_Instance.Dispose(); s_Instance = default; + s_InstanceRef = 0; + } else // Make sure we reset when there is an exception. s_Instance.Reset(); diff --git a/Packages/com.unity.inputsystem/InputSystem/Editor/ControlPicker/InputControlDropdownItem.cs b/Packages/com.unity.inputsystem/InputSystem/Editor/ControlPicker/InputControlDropdownItem.cs index 84e432c5de..b0d7baf76f 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Editor/ControlPicker/InputControlDropdownItem.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Editor/ControlPicker/InputControlDropdownItem.cs @@ -10,6 +10,7 @@ internal abstract class InputControlDropdownItem : AdvancedDropdownItem protected string m_ControlPath; protected string m_Device; protected string m_Usage; + protected bool m_Searchable; public string controlPath => m_ControlPath; @@ -26,7 +27,23 @@ public virtual string controlPathWithDevice } } - public override string searchableName => m_SearchableName ?? string.Empty; + public override string searchableName + { + get + { + // ToHumanReadableString is expensive, especially given that we build the whole tree + // every time the control picker comes up. Build searchable names only on demand + // to save some time. + if (m_SearchableName == null) + { + if (m_Searchable) + m_SearchableName = InputControlPath.ToHumanReadableString(controlPathWithDevice); + else + m_SearchableName = string.Empty; + } + return m_SearchableName; + } + } protected InputControlDropdownItem(string name) : base(name) {} @@ -54,7 +71,7 @@ public UsageDropdownItem(string usage) m_Device = "*"; m_ControlPath = usage; id = controlPathWithDevice.GetHashCode(); - m_SearchableName = InputControlPath.ToHumanReadableString(controlPathWithDevice); + m_Searchable = true; } } @@ -68,8 +85,7 @@ public DeviceDropdownItem(InputControlLayout layout, string usage = null, bool s if (usage != null) name += " (" + usage + ")"; id = name.GetHashCode(); - if (searchable) - m_SearchableName = InputControlPath.ToHumanReadableString(controlPathWithDevice); + m_Searchable = searchable; } } @@ -80,6 +96,7 @@ public ControlDropdownItem(ControlDropdownItem parent, string controlName, strin { m_Device = device; m_Usage = usage; + m_Searchable = searchable; if (parent != null) m_ControlPath = $"{parent.controlPath}/{controlName}"; @@ -90,9 +107,6 @@ public ControlDropdownItem(ControlDropdownItem parent, string controlName, strin id = controlPathWithDevice.GetHashCode(); indent = parent?.indent + 1 ?? 0; - - if (searchable) - m_SearchableName = InputControlPath.ToHumanReadableString(controlPathWithDevice); } } } diff --git a/Packages/com.unity.inputsystem/InputSystem/Editor/EditorInputControlLayoutCache.cs b/Packages/com.unity.inputsystem/InputSystem/Editor/EditorInputControlLayoutCache.cs index c85a62b1ba..e03bdde672 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Editor/EditorInputControlLayoutCache.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Editor/EditorInputControlLayoutCache.cs @@ -29,7 +29,7 @@ public static IEnumerable allLayouts get { Refresh(); - return s_Cache.table.Values; + return InputControlLayout.cache.table.Values; } } @@ -51,7 +51,7 @@ public static IEnumerable allControlLayouts { Refresh(); foreach (var name in s_ControlLayouts) - yield return s_Cache.FindOrLoadLayout(name.ToString()); + yield return InputControlLayout.cache.FindOrLoadLayout(name.ToString()); } } @@ -61,7 +61,7 @@ public static IEnumerable allDeviceLayouts { Refresh(); foreach (var name in s_DeviceLayouts) - yield return s_Cache.FindOrLoadLayout(name.ToString()); + yield return InputControlLayout.cache.FindOrLoadLayout(name.ToString()); } } @@ -71,31 +71,17 @@ public static IEnumerable allProductLayouts { Refresh(); foreach (var name in s_ProductLayouts) - yield return s_Cache.FindOrLoadLayout(name.ToString()); + yield return InputControlLayout.cache.FindOrLoadLayout(name.ToString()); } } - /// - /// Event that is triggered whenever the layout setup in the system changes. - /// - public static event Action onRefresh - { - add - { - if (s_RefreshListeners == null) - s_RefreshListeners = new List(); - s_RefreshListeners.Add(value); - } - remove => s_RefreshListeners?.Remove(value); - } - public static InputControlLayout TryGetLayout(string layoutName) { if (string.IsNullOrEmpty(layoutName)) throw new ArgumentException("Layout name cannot be null or empty", nameof(layoutName)); Refresh(); - return s_Cache.FindOrLoadLayout(layoutName); + return InputControlLayout.cache.FindOrLoadLayout(layoutName); } public static Type GetValueType(string layoutName) @@ -205,7 +191,7 @@ public struct ControlSearchResult internal static void Clear() { s_LayoutRegistrationVersion = 0; - s_Cache.table?.Clear(); + s_LayoutCacheRef.Dispose(); s_Usages.Clear(); s_ControlLayouts.Clear(); s_DeviceLayouts.Clear(); @@ -223,6 +209,15 @@ private static void Refresh() Clear(); + if (!s_LayoutCacheRef.valid) + { + // In the editor, we keep a permanent reference on the global layout + // cache. Means that in th editor, we always have all layouts loaded in full + // at all times whereas in the player, we load layouts only while we need + // them and then release them again. + s_LayoutCacheRef = InputControlLayout.CacheRef(); + } + var layoutNames = new List(); manager.ListControlLayouts(layoutNames); @@ -239,7 +234,7 @@ private static void Refresh() // Load and store all layouts. foreach (var layoutName in layoutNames) { - var layout = s_Cache.FindOrLoadLayout(layoutName); + var layout = InputControlLayout.cache.FindOrLoadLayout(layoutName); ScanLayout(layout); if (layout.isControlLayout) @@ -254,7 +249,7 @@ private static void Refresh() // a layout that has one over to the product list. foreach (var name in s_DeviceLayouts) { - var layout = s_Cache.FindOrLoadLayout(name); + var layout = InputControlLayout.cache.FindOrLoadLayout(name); if (layout.m_BaseLayouts.length > 1) throw new NotImplementedException(); @@ -268,7 +263,7 @@ private static void Refresh() break; } - var baseLayout = s_Cache.FindOrLoadLayout(baseLayoutName); + var baseLayout = InputControlLayout.cache.FindOrLoadLayout(baseLayoutName); if (baseLayout.m_BaseLayouts.length > 1) throw new NotImplementedException(); baseLayoutName = baseLayout.baseLayouts.FirstOrDefault(); @@ -279,15 +274,10 @@ private static void Refresh() s_DeviceLayouts.ExceptWith(s_ProductLayouts); s_LayoutRegistrationVersion = manager.m_LayoutRegistrationVersion; - - if (s_RefreshListeners != null) - foreach (var listener in s_RefreshListeners) - listener(); } private static int s_LayoutRegistrationVersion; - private static InputControlLayout.Cache s_Cache; - private static List s_RefreshListeners; + private static InputControlLayout.CacheRefInstance s_LayoutCacheRef; private static readonly HashSet s_ControlLayouts = new HashSet(); private static readonly HashSet s_DeviceLayouts = new HashSet(); diff --git a/Packages/com.unity.inputsystem/InputSystem/InputManager.cs b/Packages/com.unity.inputsystem/InputSystem/InputManager.cs index 001c1c45a9..7bcace3afe 100644 --- a/Packages/com.unity.inputsystem/InputSystem/InputManager.cs +++ b/Packages/com.unity.inputsystem/InputSystem/InputManager.cs @@ -429,6 +429,11 @@ private void PerformLayoutPostRegistration(InternedString layoutName, InlinedArr { ++m_LayoutRegistrationVersion; + // Force-clear layout cache. Don't clear reference count so that + // the cache gets cleared out properly when released in case someone + // is using it ATM. + InputControlLayout.s_CacheInstance = default; + // For layouts that aren't overrides, add the name of the base // layout to the lookup table. if (!isOverride && baseLayouts.length > 0) @@ -1590,6 +1595,10 @@ internal void UninstallGlobals() if (ReferenceEquals(InputBindingComposite.s_Composites.table, m_Composites.table)) InputBindingComposite.s_Composites = new TypeTable(); + // Clear layout cache. + InputControlLayout.s_CacheInstance = default; + InputControlLayout.s_CacheInstanceRef = 0; + // Detach from runtime. if (m_Runtime != null) { diff --git a/Packages/com.unity.inputsystem/InputSystem/Plugins/XInput/XInputController.cs b/Packages/com.unity.inputsystem/InputSystem/Plugins/XInput/XInputController.cs index b9dc6a838e..327daf0a01 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Plugins/XInput/XInputController.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Plugins/XInput/XInputController.cs @@ -20,6 +20,17 @@ namespace UnityEngine.InputSystem.XInput [InputControlLayout(displayName = "Xbox Controller")] public class XInputController : Gamepad { + // Change the display names for the buttons to conform to Xbox conventions. + [InputControl(name = "buttonSouth", displayName = "A")] + [InputControl(name = "buttonEast", displayName = "B")] + [InputControl(name = "buttonWest", displayName = "X")] + [InputControl(name = "buttonNorth", displayName = "Y")] + [InputControl(name = "leftShoulder", displayName = "Left Bumper")] + [InputControl(name = "rightShoulder", displayName = "Right Bumper")] + // This follows Xbox One conventions; on Xbox 360, this is start=start and select=back. + [InputControl(name = "start", displayName = "Menu")] + [InputControl(name = "select", displayName = "View")] + public ButtonControl menu { get; private set; } public ButtonControl view { get; private set; } From 77c829ad1c67bd51e40eb52a0e3488a1a90e3775 Mon Sep 17 00:00:00 2001 From: Rene Damm Date: Fri, 23 Aug 2019 12:04:52 +0200 Subject: [PATCH 2/2] FIX: Typos. --- .../InputSystem/Controls/InputControlPath.cs | 6 +++--- .../InputSystem/Editor/EditorInputControlLayoutCache.cs | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Packages/com.unity.inputsystem/InputSystem/Controls/InputControlPath.cs b/Packages/com.unity.inputsystem/InputSystem/Controls/InputControlPath.cs index 73eeb75c71..b53489bfe2 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Controls/InputControlPath.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Controls/InputControlPath.cs @@ -147,7 +147,7 @@ public enum HumanReadableStringOptions /// /// The method uses display names (see , /// , and ) - /// where possible. For example, if a "<XInputController>/buttonSouth" will be returned as + /// where possible. For example, "<XInputController>/buttonSouth" will be returned as /// "A [Xbox Controller]" as the display name of is "XBox Controller" /// and the display name of its "buttonSouth" control is "A". /// @@ -1059,7 +1059,7 @@ public string ToHumanReadableString(string parentLayoutName, out string referenc if (!name.isEmpty && !isWildcard) { // If we have a layout from a preceding path component, try to find - // the control by name on the layout. If we find it, use it's display + // the control by name on the layout. If we find it, use its display // name rather than the name referenced in the binding. string nameString = null; if (!string.IsNullOrEmpty(parentLayoutName)) @@ -1076,7 +1076,7 @@ public string ToHumanReadableString(string parentLayoutName, out string referenc if (!string.IsNullOrEmpty(control.Value.displayName)) nameString = control.Value.displayName; - // If we don't have an explicit part in the component, take + // If we don't have an explicit part in the component, // remember the name of the layout referenced by the control name so // that path components further down the line can keep looking up their // display names. diff --git a/Packages/com.unity.inputsystem/InputSystem/Editor/EditorInputControlLayoutCache.cs b/Packages/com.unity.inputsystem/InputSystem/Editor/EditorInputControlLayoutCache.cs index e03bdde672..6d41c54879 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Editor/EditorInputControlLayoutCache.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Editor/EditorInputControlLayoutCache.cs @@ -212,7 +212,7 @@ private static void Refresh() if (!s_LayoutCacheRef.valid) { // In the editor, we keep a permanent reference on the global layout - // cache. Means that in th editor, we always have all layouts loaded in full + // cache. Means that in the editor, we always have all layouts loaded in full // at all times whereas in the player, we load layouts only while we need // them and then release them again. s_LayoutCacheRef = InputControlLayout.CacheRef();