Skip to content

Commit daf6b10

Browse files
committed
optimize input button tracking
This replaces `ButtonStates` dictionaries with `PressedButtons` sets, which... - reduces allocations (since most buttons aren't down at any given time); - reduces iteration (and a yield generator in `GamePadStateBuilder.GetPressedGamePadButtons()`) for filling pressed buttons; - and simplifies change tracking.
1 parent 3474f49 commit daf6b10

3 files changed

Lines changed: 75 additions & 75 deletions

File tree

src/SMAPI/Framework/Input/GamePadStateBuilder.cs

Lines changed: 38 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,11 @@ internal class GamePadStateBuilder : IInputStateBuilder<GamePadStateBuilder, Gam
1919
private const float RightThumbstickDeadZone = 0.9f;
2020

2121
/// <summary>The underlying controller state.</summary>
22+
/// <remarks>This value is null if it needs to be regenerated for overrides. Most code should call <see cref="GetState"/> instead.</remarks>
2223
private GamePadState? State;
2324

24-
/// <summary>The current button states.</summary>
25-
private readonly Dictionary<Buttons, ButtonState> ButtonStates = [];
25+
/// <summary>The pressed buttons.</summary>
26+
private readonly HashSet<Buttons> PressedButtons = [];
2627

2728
/// <summary>The left trigger value.</summary>
2829
private float LeftTrigger;
@@ -33,7 +34,7 @@ internal class GamePadStateBuilder : IInputStateBuilder<GamePadStateBuilder, Gam
3334
/// <summary>The left thumbstick position.</summary>
3435
private Vector2 LeftStickPos;
3536

36-
/// <summary>The left thumbstick position.</summary>
37+
/// <summary>The right thumbstick position.</summary>
3738
private Vector2 RightStickPos;
3839

3940

@@ -45,30 +46,31 @@ public void Reset(GamePadState state)
4546
{
4647
this.State = state;
4748

49+
// reset tracked values
50+
this.PressedButtons.Clear();
4851
if (state.IsConnected)
4952
{
5053
GamePadDPad pad = state.DPad;
5154
GamePadButtons buttons = state.Buttons;
5255
GamePadTriggers triggers = state.Triggers;
5356
GamePadThumbSticks sticks = state.ThumbSticks;
5457

55-
var states = this.ButtonStates;
56-
states.Clear();
57-
states[Buttons.DPadUp] = pad.Up;
58-
states[Buttons.DPadDown] = pad.Down;
59-
states[Buttons.DPadLeft] = pad.Left;
60-
states[Buttons.DPadRight] = pad.Right;
61-
states[Buttons.A] = buttons.A;
62-
states[Buttons.B] = buttons.B;
63-
states[Buttons.X] = buttons.X;
64-
states[Buttons.Y] = buttons.Y;
65-
states[Buttons.LeftStick] = buttons.LeftStick;
66-
states[Buttons.RightStick] = buttons.RightStick;
67-
states[Buttons.LeftShoulder] = buttons.LeftShoulder;
68-
states[Buttons.RightShoulder] = buttons.RightShoulder;
69-
states[Buttons.Back] = buttons.Back;
70-
states[Buttons.Start] = buttons.Start;
71-
states[Buttons.BigButton] = buttons.BigButton;
58+
HashSet<Buttons> pressed = this.PressedButtons;
59+
AddIfPressed(pressed, Buttons.DPadUp, pad.Up);
60+
AddIfPressed(pressed, Buttons.DPadDown, pad.Down);
61+
AddIfPressed(pressed, Buttons.DPadLeft, pad.Left);
62+
AddIfPressed(pressed, Buttons.DPadRight, pad.Right);
63+
AddIfPressed(pressed, Buttons.A, buttons.A);
64+
AddIfPressed(pressed, Buttons.B, buttons.B);
65+
AddIfPressed(pressed, Buttons.X, buttons.X);
66+
AddIfPressed(pressed, Buttons.Y, buttons.Y);
67+
AddIfPressed(pressed, Buttons.LeftStick, buttons.LeftStick);
68+
AddIfPressed(pressed, Buttons.RightStick, buttons.RightStick);
69+
AddIfPressed(pressed, Buttons.LeftShoulder, buttons.LeftShoulder);
70+
AddIfPressed(pressed, Buttons.RightShoulder, buttons.RightShoulder);
71+
AddIfPressed(pressed, Buttons.Back, buttons.Back);
72+
AddIfPressed(pressed, Buttons.Start, buttons.Start);
73+
AddIfPressed(pressed, Buttons.BigButton, buttons.BigButton);
7274

7375
this.LeftTrigger = triggers.Left;
7476
this.RightTrigger = triggers.Right;
@@ -77,13 +79,18 @@ public void Reset(GamePadState state)
7779
}
7880
else
7981
{
80-
this.ButtonStates.Clear();
81-
8282
this.LeftTrigger = 0;
8383
this.RightTrigger = 0;
8484
this.LeftStickPos = Vector2.Zero;
8585
this.RightStickPos = Vector2.Zero;
8686
}
87+
88+
return;
89+
static void AddIfPressed(HashSet<Buttons> pressed, Buttons button, ButtonState state)
90+
{
91+
if (state == ButtonState.Pressed)
92+
pressed.Add(button);
93+
}
8794
}
8895

8996
/// <summary>Override the state for a button.</summary>
@@ -92,7 +99,7 @@ public void Reset(GamePadState state)
9299
public void OverrideButton(Buttons button, SButtonState state)
93100
{
94101
bool isDown = state.IsDown();
95-
bool changed = false;
102+
bool changed;
96103

97104
switch (button)
98105
{
@@ -134,22 +141,16 @@ public void OverrideButton(Buttons button, SButtonState state)
134141

135142
// buttons
136143
default:
137-
{
138-
ButtonState newState = isDown ? ButtonState.Pressed : ButtonState.Released;
139-
140-
if (!this.ButtonStates.TryGetValue(button, out ButtonState oldState) || newState != oldState)
141-
{
142-
this.ButtonStates[button] = newState;
143-
changed = true;
144-
}
145-
}
144+
changed = isDown
145+
? this.PressedButtons.Add(button)
146+
: this.PressedButtons.Remove(button);
146147
break;
147148
}
148149

149150
if (changed)
150151
this.State = null;
151-
return;
152152

153+
return;
153154
[SuppressMessage("ReSharper", "CompareOfFloatsByEqualityOperator", Justification = "Floating points not an issue for the specific values we're checking.")]
154155
static bool Set(ref float field, int newValue)
155156
{
@@ -167,7 +168,7 @@ static bool Set(ref float field, int newValue)
167168
public void FillPressedButtons(HashSet<SButton> set)
168169
{
169170
// buttons
170-
foreach (Buttons button in this.GetPressedGamePadButtons())
171+
foreach (Buttons button in this.PressedButtons)
171172
set.Add(button.ToSButton());
172173

173174
// triggers
@@ -208,21 +209,9 @@ public GamePadState GetState()
208209
rightThumbStick: this.RightStickPos,
209210
leftTrigger: this.LeftTrigger,
210211
rightTrigger: this.RightTrigger,
211-
buttons: this.GetPressedGamePadButtons().ToArray()
212+
buttons: this.PressedButtons.Count > 0
213+
? this.PressedButtons.ToArray()
214+
: []
212215
);
213216
}
214-
215-
216-
/*********
217-
** Private methods
218-
*********/
219-
/// <summary>Get the pressed gamepad buttons.</summary>
220-
private IEnumerable<Buttons> GetPressedGamePadButtons()
221-
{
222-
foreach ((Buttons button, ButtonState state) in this.ButtonStates)
223-
{
224-
if (state == ButtonState.Pressed)
225-
yield return button;
226-
}
227-
}
228217
}

src/SMAPI/Framework/Input/KeyboardStateBuilder.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ internal class KeyboardStateBuilder : IInputStateBuilder<KeyboardStateBuilder, K
1111
** Fields
1212
*********/
1313
/// <summary>The underlying keyboard state.</summary>
14+
/// <remarks>This value is null if it needs to be regenerated for overrides. Most code should call <see cref="GetState"/> instead.</remarks>
1415
private KeyboardState? State;
1516

1617
/// <summary>The pressed buttons.</summary>
@@ -25,6 +26,7 @@ public void Reset(KeyboardState state)
2526
{
2627
this.State = state;
2728

29+
// reset tracked values
2830
this.PressedButtons.Clear();
2931
foreach (Keys button in state.GetPressedKeys())
3032
this.PressedButtons.Add(button);

src/SMAPI/Framework/Input/MouseStateBuilder.cs

Lines changed: 35 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,11 @@ internal class MouseStateBuilder : IInputStateBuilder<MouseStateBuilder, MouseSt
1010
** Fields
1111
*********/
1212
/// <summary>The underlying mouse state.</summary>
13+
/// <remarks>This value is null if it needs to be regenerated for overrides. Most code should call <see cref="GetState"/> instead.</remarks>
1314
private MouseState? State;
1415

15-
/// <summary>The current button states.</summary>
16-
private readonly Dictionary<SButton, ButtonState> ButtonStates = [];
16+
/// <summary>The pressed buttons.</summary>
17+
private readonly HashSet<SButton> PressedButtons = [];
1718

1819
/// <summary>The mouse wheel scroll value.</summary>
1920
private int ScrollWheelValue;
@@ -37,43 +38,44 @@ public void Reset(MouseState state)
3738
{
3839
this.State = state;
3940

40-
var states = this.ButtonStates;
41-
states.Clear();
42-
states[SButton.MouseLeft] = state.LeftButton;
43-
states[SButton.MouseMiddle] = state.MiddleButton;
44-
states[SButton.MouseRight] = state.RightButton;
45-
states[SButton.MouseX1] = state.XButton1;
46-
states[SButton.MouseX2] = state.XButton2;
41+
// reset tracked buttons
42+
this.PressedButtons.Clear();
43+
AddIfPressed(this.PressedButtons, SButton.MouseLeft, state.LeftButton);
44+
AddIfPressed(this.PressedButtons, SButton.MouseMiddle, state.MiddleButton);
45+
AddIfPressed(this.PressedButtons, SButton.MouseRight, state.RightButton);
46+
AddIfPressed(this.PressedButtons, SButton.MouseX1, state.XButton1);
47+
AddIfPressed(this.PressedButtons, SButton.MouseX2, state.XButton2);
4748

4849
this.X = state.X;
4950
this.Y = state.Y;
5051
this.ScrollWheelValue = state.ScrollWheelValue;
52+
53+
return;
54+
static void AddIfPressed(HashSet<SButton> pressed, SButton button, ButtonState state)
55+
{
56+
if (state == ButtonState.Pressed)
57+
pressed.Add(button);
58+
}
5159
}
5260

5361
/// <summary>Override the state for a button.</summary>
5462
/// <param name="button">The button to override.</param>
5563
/// <param name="state">The new state to set.</param>
5664
public void OverrideButton(SButton button, SButtonState state)
5765
{
58-
ButtonState newState = state.IsDown()
59-
? ButtonState.Pressed
60-
: ButtonState.Released;
66+
bool changed = state.IsDown()
67+
? this.PressedButtons.Add(button)
68+
: this.PressedButtons.Remove(button);
6169

62-
if (this.ButtonStates.TryGetValue(button, out ButtonState oldState) && oldState != newState)
63-
{
70+
if (changed)
6471
this.State = null;
65-
this.ButtonStates[button] = newState;
66-
}
6772
}
6873

6974
/// <inheritdoc />
7075
public void FillPressedButtons(HashSet<SButton> set)
7176
{
72-
foreach (var pair in this.ButtonStates)
73-
{
74-
if (pair.Value == ButtonState.Pressed)
75-
set.Add(pair.Key);
76-
}
77+
foreach (SButton button in this.PressedButtons)
78+
set.Add(button);
7779
}
7880

7981
/// <inheritdoc />
@@ -83,11 +85,18 @@ public MouseState GetState()
8385
x: this.X,
8486
y: this.Y,
8587
scrollWheel: this.ScrollWheelValue,
86-
leftButton: this.ButtonStates[SButton.MouseLeft],
87-
middleButton: this.ButtonStates[SButton.MouseMiddle],
88-
rightButton: this.ButtonStates[SButton.MouseRight],
89-
xButton1: this.ButtonStates[SButton.MouseX1],
90-
xButton2: this.ButtonStates[SButton.MouseX2]
88+
leftButton: GetButtonState(this.PressedButtons, SButton.MouseLeft),
89+
middleButton: GetButtonState(this.PressedButtons, SButton.MouseMiddle),
90+
rightButton: GetButtonState(this.PressedButtons, SButton.MouseRight),
91+
xButton1: GetButtonState(this.PressedButtons, SButton.MouseX1),
92+
xButton2: GetButtonState(this.PressedButtons, SButton.MouseX2)
9193
);
94+
95+
static ButtonState GetButtonState(HashSet<SButton> pressed, SButton button)
96+
{
97+
return pressed.Contains(button)
98+
? ButtonState.Pressed
99+
: ButtonState.Released;
100+
}
92101
}
93102
}

0 commit comments

Comments
 (0)