Skip to content

Commit cd28e1c

Browse files
authored
Merge pull request #6126 from peppy/keyboard-input-allocs
Reduce allocation overhead for keyboard / binding / button handling
2 parents 70fc208 + da75cb6 commit cd28e1c

File tree

3 files changed

+60
-19
lines changed

3 files changed

+60
-19
lines changed

osu.Framework/Input/Bindings/KeyBindingContainer.cs

+30-13
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,8 @@ private bool handleRepeat(InputState state)
202202
return drawables.FirstOrDefault(d => triggerKeyBindingEvent(d, pressEvent)) != null;
203203
}
204204

205+
private readonly List<IKeyBinding> newlyPressed = new List<IKeyBinding>();
206+
205207
private bool handleNewPressed(InputState state, InputKey newKey, Vector2? scrollDelta = null, bool isPrecise = false)
206208
{
207209
pressedInputKeys.Add(newKey);
@@ -210,21 +212,36 @@ private bool handleNewPressed(InputState state, InputKey newKey, Vector2? scroll
210212
var pressedCombination = new KeyCombination(pressedInputKeys);
211213

212214
bool handled = false;
213-
var bindings = KeyBindings?.Except(pressedBindings) ?? Enumerable.Empty<IKeyBinding>();
214-
var newlyPressed = bindings.Where(m =>
215-
m.KeyCombination.IsPressed(pressedCombination, matchingMode));
215+
216+
newlyPressed.Clear();
217+
218+
if (KeyBindings != null)
219+
{
220+
foreach (IKeyBinding binding in KeyBindings)
221+
{
222+
if (pressedBindings.Contains(binding))
223+
continue;
224+
225+
if (binding.KeyCombination.IsPressed(pressedCombination, matchingMode))
226+
newlyPressed.Add(binding);
227+
}
228+
}
216229

217230
if (KeyCombination.IsModifierKey(newKey))
218231
{
219232
// if the current key pressed was a modifier, only handle modifier-only bindings.
220233
// lambda expression is used so that the delegate is cached (see: https://github.com/dotnet/roslyn/issues/5835)
221234
// TODO: remove when we switch to .NET 7.
222235
// ReSharper disable once ConvertClosureToMethodGroup
223-
newlyPressed = newlyPressed.Where(b => b.KeyCombination.Keys.All(key => KeyCombination.IsModifierKey(key)));
236+
for (int i = 0; i < newlyPressed.Count; i++)
237+
{
238+
if (!newlyPressed[i].KeyCombination.Keys.All(key => KeyCombination.IsModifierKey(key)))
239+
newlyPressed.RemoveAt(i--);
240+
}
224241
}
225242

226243
// we want to always handle bindings with more keys before bindings with less.
227-
newlyPressed = newlyPressed.OrderByDescending(b => b.KeyCombination.Keys.Length).ToList();
244+
newlyPressed.Sort(static (a, b) => b.KeyCombination.Keys.Length.CompareTo(a.KeyCombination.Keys.Length));
228245

229246
pressedBindings.AddRange(newlyPressed);
230247

@@ -342,16 +359,16 @@ private void handleNewReleased(InputState state, InputKey releasedKey)
342359
// we don't want to consider exact matching here as we are dealing with bindings, not actions.
343360
var pressedCombination = new KeyCombination(pressedInputKeys);
344361

345-
var newlyReleased = pressedInputKeys.Count == 0
346-
? pressedBindings.ToList()
347-
: pressedBindings.Where(b => !b.KeyCombination.IsPressed(pressedCombination, KeyCombinationMatchingMode.Any)).ToList();
348-
349-
foreach (var binding in newlyReleased)
362+
for (int i = 0; i < pressedBindings.Count; i++)
350363
{
351-
pressedBindings.Remove(binding);
364+
var binding = pressedBindings[i];
352365

353-
PropagateReleased(getInputQueue(binding).Where(d => d.IsRootedAt(this)), state, binding.GetAction<T>());
354-
keyBindingQueues[binding].Clear();
366+
if (pressedInputKeys.Count == 0 || !binding.KeyCombination.IsPressed(pressedCombination, KeyCombinationMatchingMode.Any))
367+
{
368+
pressedBindings.RemoveAt(i--);
369+
PropagateReleased(getInputQueue(binding).Where(d => d.IsRootedAt(this)), state, binding.GetAction<T>());
370+
keyBindingQueues[binding].Clear();
371+
}
355372
}
356373
}
357374

osu.Framework/Input/Bindings/KeyCombination.cs

+20-5
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,25 @@ namespace osu.Framework.Input.Bindings
2929
/// Construct a new instance.
3030
/// </summary>
3131
/// <param name="keys">The keys.</param>
32-
/// <remarks>This constructor is not optimized. Hot paths are assumed to use <see cref="FromInputState(InputState, Vector2?)"/>.</remarks>
33-
public KeyCombination(IEnumerable<InputKey>? keys)
32+
public KeyCombination(ICollection<InputKey>? keys)
3433
{
35-
Keys = keys?.Any() == true ? keys.Distinct().OrderBy(k => (int)k).ToImmutableArray() : none;
34+
if (keys == null || !keys.Any())
35+
{
36+
Keys = none;
37+
return;
38+
}
39+
40+
var keyBuilder = ImmutableArray.CreateBuilder<InputKey>(keys.Count);
41+
42+
foreach (var key in keys)
43+
{
44+
if (!keyBuilder.Contains(key))
45+
keyBuilder.Add(key);
46+
}
47+
48+
keyBuilder.Sort();
49+
50+
Keys = keyBuilder.MoveToImmutable();
3651
}
3752

3853
/// <summary>
@@ -41,7 +56,7 @@ public KeyCombination(IEnumerable<InputKey>? keys)
4156
/// <param name="keys">The keys.</param>
4257
/// <remarks>This constructor is not optimized. Hot paths are assumed to use <see cref="FromInputState(InputState, Vector2?)"/>.</remarks>
4358
public KeyCombination(params InputKey[] keys)
44-
: this(keys.AsEnumerable())
59+
: this((ICollection<InputKey>)keys)
4560
{
4661
}
4762

@@ -51,7 +66,7 @@ public KeyCombination(params InputKey[] keys)
5166
/// <param name="keys">A comma-separated (KeyCode in integer) string representation of the keys.</param>
5267
/// <remarks>This constructor is not optimized. Hot paths are assumed to use <see cref="FromInputState(InputState, Vector2?)"/>.</remarks>
5368
public KeyCombination(string keys)
54-
: this(keys.Split(',', StringSplitOptions.RemoveEmptyEntries).Select(s => (InputKey)int.Parse(s)))
69+
: this(keys.Split(',', StringSplitOptions.RemoveEmptyEntries).Select(s => (InputKey)int.Parse(s)).ToArray())
5570
{
5671
}
5772

osu.Framework/Input/ButtonEventManager.cs

+10-1
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,16 @@ private void handleButtonUp(InputState state)
121121
/// <returns>The drawable which handled the event or null if none.</returns>
122122
protected Drawable? PropagateButtonEvent(IEnumerable<Drawable> drawables, UIEvent e)
123123
{
124-
var handledBy = drawables.FirstOrDefault(target => target.TriggerEvent(e));
124+
Drawable? handledBy = null;
125+
126+
foreach (Drawable target in drawables)
127+
{
128+
if (target.TriggerEvent(e))
129+
{
130+
handledBy = target;
131+
break;
132+
}
133+
}
125134

126135
if (handledBy != null)
127136
Logger.Log($"{e} handled by {handledBy}.", LoggingTarget.Runtime, LogLevel.Debug);

0 commit comments

Comments
 (0)