From d16d899a67415dff193f5eb11a272cd99265916a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 11 Jan 2024 15:46:59 +0900 Subject: [PATCH] Remove allocation overhead from `KeyBindingContainer` key handling --- .../Input/Bindings/KeyBindingContainer.cs | 37 +++++++++++++------ 1 file changed, 26 insertions(+), 11 deletions(-) diff --git a/osu.Framework/Input/Bindings/KeyBindingContainer.cs b/osu.Framework/Input/Bindings/KeyBindingContainer.cs index da69d71c05..6fcb2ba06b 100644 --- a/osu.Framework/Input/Bindings/KeyBindingContainer.cs +++ b/osu.Framework/Input/Bindings/KeyBindingContainer.cs @@ -202,6 +202,8 @@ private bool handleRepeat(InputState state) return drawables.FirstOrDefault(d => triggerKeyBindingEvent(d, pressEvent)) != null; } + private readonly List newlyPressed = new List(); + private bool handleNewPressed(InputState state, InputKey newKey, Vector2? scrollDelta = null, bool isPrecise = false) { pressedInputKeys.Add(newKey); @@ -210,10 +212,20 @@ private bool handleNewPressed(InputState state, InputKey newKey, Vector2? scroll var pressedCombination = new KeyCombination(pressedInputKeys); bool handled = false; - var bindings = KeyBindings?.Except(pressedBindings) ?? Enumerable.Empty(); - var newlyPressed = bindings.Where(m => - m.KeyCombination.IsPressed(pressedCombination, matchingMode)); + newlyPressed.Clear(); + + if (KeyBindings != null) + { + foreach (IKeyBinding binding in KeyBindings) + { + if (pressedBindings.Contains(binding)) + continue; + + if (binding.KeyCombination.IsPressed(pressedCombination, matchingMode)) + newlyPressed.Add(binding); + } + } if (KeyCombination.IsModifierKey(newKey)) { @@ -221,11 +233,15 @@ private bool handleNewPressed(InputState state, InputKey newKey, Vector2? scroll // lambda expression is used so that the delegate is cached (see: https://github.com/dotnet/roslyn/issues/5835) // TODO: remove when we switch to .NET 7. // ReSharper disable once ConvertClosureToMethodGroup - newlyPressed = newlyPressed.Where(b => b.KeyCombination.Keys.All(key => KeyCombination.IsModifierKey(key))); + for (int i = 0; i < newlyPressed.Count; i++) + { + if (!newlyPressed[i].KeyCombination.Keys.All(key => KeyCombination.IsModifierKey(key))) + newlyPressed.RemoveAt(i--); + } } // we want to always handle bindings with more keys before bindings with less. - newlyPressed = newlyPressed.OrderByDescending(b => b.KeyCombination.Keys.Length).ToList(); + newlyPressed.Sort(static (a, b) => b.KeyCombination.Keys.Length.CompareTo(a.KeyCombination.Keys.Length)); pressedBindings.AddRange(newlyPressed); @@ -343,14 +359,13 @@ private void handleNewReleased(InputState state, InputKey releasedKey) // we don't want to consider exact matching here as we are dealing with bindings, not actions. var pressedCombination = new KeyCombination(pressedInputKeys); - var newlyReleased = pressedInputKeys.Count == 0 - ? pressedBindings.ToList() - : pressedBindings.Where(b => !b.KeyCombination.IsPressed(pressedCombination, KeyCombinationMatchingMode.Any)).ToList(); - - foreach (var binding in newlyReleased) + for (int i = 0; i < pressedBindings.Count; i++) { - pressedBindings.Remove(binding); + var binding = pressedBindings[i]; + + if (binding.KeyCombination.IsPressed(pressedCombination, KeyCombinationMatchingMode.Any)) continue; + pressedBindings.RemoveAt(i--); PropagateReleased(getInputQueue(binding).Where(d => d.IsRootedAt(this)), state, binding.GetAction()); keyBindingQueues[binding].Clear(); }