From 248225c7c43f5a0d34ef9e459029d4568367c78b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20M=C3=BCller?= Date: Tue, 21 May 2024 19:56:22 +0200 Subject: [PATCH 1/2] Refactor client input event handling Add `IInput::ConsumeEvents` function accepting a consumer `std::function` to replace the duplicate usage of the `IInput::NumEvents`, `IInput::GetEvent` and `IInput::IsEventValid` functions. Use an `std::vector` to store the current input events to support any number of input events per client update instead of at most 32. Use full `uint32_t` range for input counter instead of only using the range 0..0xFFFF. If the range is artificially reduced, then this can result in inputs being handled multiple times with high refresh rates, so the increased range should add future proofing for extremely fast devices. Split `CInput::AddEvent` function into `CInput::AddKeyEvent` and `CInput::AddTextEvent` functions for readability and to make it easier to add additional input events (i.e. touch events). Ensure double-click state is cleared at the end of each frame to prevent the double-click from being stored when no UI element consumes it. Move member variables from `IInput` interface to `CInput` implementation. Remove separate `CEditor::DispatchInputEvents` function. --- src/engine/client/input.cpp | 79 ++++++++++++++++++++++------------ src/engine/client/input.h | 19 +++++--- src/engine/input.h | 30 +++---------- src/game/client/gameclient.cpp | 9 +--- src/game/editor/editor.cpp | 24 ++--------- src/game/editor/editor.h | 1 - 6 files changed, 77 insertions(+), 85 deletions(-) diff --git a/src/engine/client/input.cpp b/src/engine/client/input.cpp index bf8bb179a..63931295a 100644 --- a/src/engine/client/input.cpp +++ b/src/engine/client/input.cpp @@ -33,19 +33,25 @@ // for platform specific features that aren't available or are broken in SDL #include -void CInput::AddEvent(char *pText, int Key, int Flags) +void CInput::AddKeyEvent(int Key, int Flags) { - if(m_NumEvents != INPUT_BUFFER_SIZE) - { - m_aInputEvents[m_NumEvents].m_Key = Key; - m_aInputEvents[m_NumEvents].m_Flags = Flags; - if(pText == nullptr) - m_aInputEvents[m_NumEvents].m_aText[0] = '\0'; - else - str_copy(m_aInputEvents[m_NumEvents].m_aText, pText); - m_aInputEvents[m_NumEvents].m_InputCount = m_InputCounter; - m_NumEvents++; - } + dbg_assert((Flags & (FLAG_PRESS | FLAG_RELEASE)) != 0 && (Flags & ~(FLAG_PRESS | FLAG_RELEASE)) == 0, "Flags invalid"); + CEvent Event; + Event.m_Key = Key; + Event.m_Flags = Flags; + Event.m_aText[0] = '\0'; + Event.m_InputCount = m_InputCounter; + m_vInputEvents.emplace_back(Event); +} + +void CInput::AddTextEvent(const char *pText) +{ + CEvent Event; + Event.m_Key = KEY_UNKNOWN; + Event.m_Flags = FLAG_TEXT; + str_copy(Event.m_aText, pText); + Event.m_InputCount = m_InputCounter; + m_vInputEvents.emplace_back(Event); } CInput::CInput() @@ -53,6 +59,7 @@ CInput::CInput() mem_zero(m_aInputCount, sizeof(m_aInputCount)); mem_zero(m_aInputState, sizeof(m_aInputState)); + m_vInputEvents.reserve(32); m_LastUpdate = 0; m_UpdateTime = 0.0f; @@ -60,8 +67,6 @@ CInput::CInput() m_InputGrabbed = false; m_MouseDoubleClick = false; - - m_NumEvents = 0; m_MouseFocus = true; m_pClipboardText = nullptr; @@ -342,11 +347,29 @@ void CInput::StopTextInput() m_vCandidates.clear(); } +void CInput::ConsumeEvents(std::function Consumer) const +{ + for(const CEvent &Event : m_vInputEvents) + { + // Only propagate valid input events + if(Event.m_InputCount == m_InputCounter) + { + Consumer(Event); + } + } +} + void CInput::Clear() { mem_zero(m_aInputState, sizeof(m_aInputState)); mem_zero(m_aInputCount, sizeof(m_aInputCount)); - m_NumEvents = 0; + m_vInputEvents.clear(); + m_MouseDoubleClick = false; +} + +float CInput::GetUpdateTime() const +{ + return m_UpdateTime; } bool CInput::KeyState(int Key) const @@ -424,24 +447,24 @@ void CInput::HandleJoystickAxisMotionEvent(const SDL_JoyAxisEvent &Event) { m_aInputState[LeftKey] = true; m_aInputCount[LeftKey] = m_InputCounter; - AddEvent(nullptr, LeftKey, IInput::FLAG_PRESS); + AddKeyEvent(LeftKey, IInput::FLAG_PRESS); } else if(Event.value > SDL_JOYSTICK_AXIS_MIN * DeadZone && m_aInputState[LeftKey]) { m_aInputState[LeftKey] = false; - AddEvent(nullptr, LeftKey, IInput::FLAG_RELEASE); + AddKeyEvent(LeftKey, IInput::FLAG_RELEASE); } if(Event.value >= SDL_JOYSTICK_AXIS_MAX * DeadZone && !m_aInputState[RightKey]) { m_aInputState[RightKey] = true; m_aInputCount[RightKey] = m_InputCounter; - AddEvent(nullptr, RightKey, IInput::FLAG_PRESS); + AddKeyEvent(RightKey, IInput::FLAG_PRESS); } else if(Event.value < SDL_JOYSTICK_AXIS_MAX * DeadZone && m_aInputState[RightKey]) { m_aInputState[RightKey] = false; - AddEvent(nullptr, RightKey, IInput::FLAG_RELEASE); + AddKeyEvent(RightKey, IInput::FLAG_RELEASE); } } @@ -461,12 +484,12 @@ void CInput::HandleJoystickButtonEvent(const SDL_JoyButtonEvent &Event) { m_aInputState[Key] = true; m_aInputCount[Key] = m_InputCounter; - AddEvent(nullptr, Key, IInput::FLAG_PRESS); + AddKeyEvent(Key, IInput::FLAG_PRESS); } else if(Event.type == SDL_JOYBUTTONUP) { m_aInputState[Key] = false; - AddEvent(nullptr, Key, IInput::FLAG_RELEASE); + AddKeyEvent(Key, IInput::FLAG_RELEASE); } } @@ -488,7 +511,7 @@ void CInput::HandleJoystickHatMotionEvent(const SDL_JoyHatEvent &Event) if(Key != HatKeys[0] && Key != HatKeys[1] && m_aInputState[Key]) { m_aInputState[Key] = false; - AddEvent(nullptr, Key, IInput::FLAG_RELEASE); + AddKeyEvent(Key, IInput::FLAG_RELEASE); } } @@ -498,7 +521,7 @@ void CInput::HandleJoystickHatMotionEvent(const SDL_JoyHatEvent &Event) { m_aInputState[CurrentKey] = true; m_aInputCount[CurrentKey] = m_InputCounter; - AddEvent(nullptr, CurrentKey, IInput::FLAG_PRESS); + AddKeyEvent(CurrentKey, IInput::FLAG_PRESS); } } } @@ -582,8 +605,8 @@ int CInput::Update() } m_LastUpdate = Now; - // keep the counter between 1..0xFFFF, 0 means not pressed - m_InputCounter = (m_InputCounter % 0xFFFF) + 1; + // keep the counter between 1..0xFFFFFFFF, 0 means not pressed + m_InputCounter = (m_InputCounter % std::numeric_limits::max()) + 1; // Ensure that we have the latest keyboard, mouse and joystick state SDL_PumpEvents(); @@ -621,7 +644,7 @@ int CInput::Update() for(int i = 0; i < Event.edit.start; i++) m_CompositionCursor = str_utf8_forward(m_aComposition, m_CompositionCursor); // Event.edit.length is currently unused on Windows and will always be 0, so we don't support selecting composition text - AddEvent(nullptr, KEY_UNKNOWN, IInput::FLAG_TEXT); + AddTextEvent(""); } else { @@ -636,7 +659,7 @@ int CInput::Update() m_aComposition[0] = '\0'; m_CompositionLength = COMP_LENGTH_INACTIVE; m_CompositionCursor = 0; - AddEvent(Event.text.text, KEY_UNKNOWN, IInput::FLAG_TEXT); + AddTextEvent(Event.text.text); break; // handle keys @@ -806,7 +829,7 @@ int CInput::Update() m_aInputState[Scancode] = 1; m_aInputCount[Scancode] = m_InputCounter; } - AddEvent(nullptr, Scancode, Action); + AddKeyEvent(Scancode, Action); } } diff --git a/src/engine/client/input.h b/src/engine/client/input.h index 00fd15c8e..7cef8f816 100644 --- a/src/engine/client/input.h +++ b/src/engine/client/input.h @@ -92,14 +92,17 @@ private: std::vector m_vCandidates; int m_CandidateSelectedIndex; - void AddEvent(char *pText, int Key, int Flags); - void Clear() override; - bool IsEventValid(const CEvent &Event) const override { return Event.m_InputCount == m_InputCounter; } + // events + std::vector m_vInputEvents; + int64_t m_LastUpdate; + float m_UpdateTime; + void AddKeyEvent(int Key, int Flags); + void AddTextEvent(const char *pText); // quick access to input - unsigned short m_aInputCount[g_MaxKeys]; // tw-KEY - unsigned char m_aInputState[g_MaxKeys]; // SDL_SCANCODE - int m_InputCounter; + uint32_t m_aInputCount[g_MaxKeys]; + unsigned char m_aInputState[g_MaxKeys]; + uint32_t m_InputCounter; void UpdateMouseState(); void UpdateJoystickState(); @@ -122,6 +125,10 @@ public: int Update() override; void Shutdown() override; + void ConsumeEvents(std::function Consumer) const override; + void Clear() override; + float GetUpdateTime() const override; + bool ModifierIsPressed() const override { return KeyState(KEY_LCTRL) || KeyState(KEY_RCTRL) || KeyState(KEY_LGUI) || KeyState(KEY_RGUI); } bool ShiftIsPressed() const override { return KeyState(KEY_LSHIFT) || KeyState(KEY_RSHIFT); } bool AltIsPressed() const override { return KeyState(KEY_LALT) || KeyState(KEY_RALT); } diff --git a/src/engine/input.h b/src/engine/input.h index 87740c2fa..2e9c56ce7 100644 --- a/src/engine/input.h +++ b/src/engine/input.h @@ -6,6 +6,9 @@ #include "kernel.h" #include +#include +#include + const int g_MaxKeys = 512; extern const char g_aaKeyStrings[g_MaxKeys][20]; @@ -23,23 +26,10 @@ public: public: int m_Flags; int m_Key; + uint32_t m_InputCount; char m_aText[INPUT_TEXT_SIZE]; - int m_InputCount; }; -protected: - enum - { - INPUT_BUFFER_SIZE = 32 - }; - - // quick access to events - size_t m_NumEvents; - CEvent m_aInputEvents[INPUT_BUFFER_SIZE]; - int64_t m_LastUpdate; - float m_UpdateTime; - -public: enum { FLAG_PRESS = 1 << 0, @@ -60,19 +50,14 @@ public: }; // events - size_t NumEvents() const { return m_NumEvents; } - virtual bool IsEventValid(const CEvent &Event) const = 0; - const CEvent &GetEvent(size_t Index) const - { - dbg_assert(Index < m_NumEvents, "Index invalid"); - return m_aInputEvents[Index]; - } + virtual void ConsumeEvents(std::function Consumer) const = 0; + virtual void Clear() = 0; /** * @return Rolling average of the time in seconds between * calls of the Update function. */ - float GetUpdateTime() const { return m_UpdateTime; } + virtual float GetUpdateTime() const = 0; // keys virtual bool ModifierIsPressed() const = 0; @@ -81,7 +66,6 @@ public: virtual bool KeyIsPressed(int Key) const = 0; virtual bool KeyPress(int Key, bool CheckCounter = false) const = 0; const char *KeyName(int Key) const { return (Key >= 0 && Key < g_MaxKeys) ? g_aaKeyStrings[Key] : g_aaKeyStrings[0]; } - virtual void Clear() = 0; // joystick class IJoystick diff --git a/src/game/client/gameclient.cpp b/src/game/client/gameclient.cpp index 64164b41d..cedf92971 100644 --- a/src/game/client/gameclient.cpp +++ b/src/game/client/gameclient.cpp @@ -385,18 +385,13 @@ void CGameClient::OnUpdate() } // handle key presses - for(size_t i = 0; i < Input()->NumEvents(); i++) - { - const IInput::CEvent &Event = Input()->GetEvent(i); - if(!Input()->IsEventValid(Event)) - continue; - + Input()->ConsumeEvents([&](const IInput::CEvent &Event) { for(auto &pComponent : m_vpInput) { if(pComponent->OnInput(Event)) break; } - } + }); if(g_Config.m_ClSubTickAiming && m_Binds.m_MouseOnAction) { diff --git a/src/game/editor/editor.cpp b/src/game/editor/editor.cpp index 767c8a6f7..f14cf5d72 100644 --- a/src/game/editor/editor.cpp +++ b/src/game/editor/editor.cpp @@ -8482,17 +8482,6 @@ void CEditor::OnMouseMove(float MouseX, float MouseY) Ui()->MapScreen(); } -void CEditor::DispatchInputEvents() -{ - for(size_t i = 0; i < Input()->NumEvents(); i++) - { - const IInput::CEvent &Event = Input()->GetEvent(i); - if(!Input()->IsEventValid(Event)) - continue; - Ui()->OnInput(Event); - } -} - void CEditor::HandleAutosave() { const float Time = Client()->GlobalTime(); @@ -8624,21 +8613,16 @@ void CEditor::OnUpdate() m_pContainerPannedLast = m_pContainerPanned; // handle key presses - for(size_t i = 0; i < Input()->NumEvents(); i++) - { - const IInput::CEvent &Event = Input()->GetEvent(i); - if(!Input()->IsEventValid(Event)) - continue; - + Input()->ConsumeEvents([&](const IInput::CEvent &Event) { for(CEditorComponent &Component : m_vComponents) { if(Component.OnInput(Event)) - break; + return; } - } + Ui()->OnInput(Event); + }); HandleCursorMovement(); - DispatchInputEvents(); HandleAutosave(); HandleWriterFinishJobs(); diff --git a/src/game/editor/editor.h b/src/game/editor/editor.h index 0801c1f04..e9172ebb0 100644 --- a/src/game/editor/editor.h +++ b/src/game/editor/editor.h @@ -465,7 +465,6 @@ public: void HandleCursorMovement(); void OnMouseMove(float MouseX, float MouseY); - void DispatchInputEvents(); void HandleAutosave(); bool PerformAutosave(); void HandleWriterFinishJobs(); From 0d9f673b03b06babec4a9887ba5c552da0eff800 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20M=C3=BCller?= Date: Mon, 27 May 2024 18:59:33 +0200 Subject: [PATCH 2/2] Include `types.h` instead of `system.h` in `input.h` --- src/engine/input.h | 3 ++- src/game/client/component.cpp | 11 +++++++++++ src/game/client/component.h | 17 ++--------------- src/game/client/components/voting.cpp | 7 +++++++ src/game/client/components/voting.h | 4 ++-- 5 files changed, 24 insertions(+), 18 deletions(-) diff --git a/src/engine/input.h b/src/engine/input.h index 2e9c56ce7..d1d926604 100644 --- a/src/engine/input.h +++ b/src/engine/input.h @@ -4,7 +4,8 @@ #define ENGINE_INPUT_H #include "kernel.h" -#include + +#include #include #include diff --git a/src/game/client/component.cpp b/src/game/client/component.cpp index 0d085f428..c682d7457 100644 --- a/src/game/client/component.cpp +++ b/src/game/client/component.cpp @@ -2,6 +2,8 @@ #include "gameclient.h" +#include + class IKernel *CComponent::Kernel() const { return m_pClient->Kernel(); } class IEngine *CComponent::Engine() const { return m_pClient->Engine(); } class IGraphics *CComponent::Graphics() const { return m_pClient->Graphics(); } @@ -27,6 +29,15 @@ class IUpdater *CComponent::Updater() const } #endif +int64_t CComponent::time() const +{ +#if defined(CONF_VIDEORECORDER) + return IVideo::Current() ? IVideo::Time() : time_get(); +#else + return time_get(); +#endif +} + float CComponent::LocalTime() const { #if defined(CONF_VIDEORECORDER) diff --git a/src/game/client/component.h b/src/game/client/component.h index be49eb991..639c8b140 100644 --- a/src/game/client/component.h +++ b/src/game/client/component.h @@ -104,25 +104,12 @@ protected: class IUpdater *Updater() const; #endif -#if defined(CONF_VIDEORECORDER) /** * Gets the current time. * @see time_get() */ - int64_t time() const - { - return IVideo::Current() ? IVideo::Time() : time_get(); - } -#else - /** - * Gets the current time. - * @see time_get() - */ - int64_t time() const - { - return time_get(); - } -#endif + int64_t time() const; + /** * Gets the local time. */ diff --git a/src/game/client/components/voting.cpp b/src/game/client/components/voting.cpp index f25dedf99..0c2fbb57e 100644 --- a/src/game/client/components/voting.cpp +++ b/src/game/client/components/voting.cpp @@ -2,6 +2,8 @@ /* If you are missing that file, acquire a complete release at teeworlds.com. */ #include "voting.h" +#include + #include #include @@ -139,6 +141,11 @@ void CVoting::Vote(int v) Client()->SendPackMsgActive(&Msg, MSGFLAG_VITAL); } +int CVoting::SecondsLeft() const +{ + return (m_Closetime - time()) / time_freq(); +} + CVoting::CVoting() { ClearOptions(); diff --git a/src/game/client/components/voting.h b/src/game/client/components/voting.h index 3e5531a6a..2d3d36d15 100644 --- a/src/game/client/components/voting.h +++ b/src/game/client/components/voting.h @@ -57,8 +57,8 @@ public: void Vote(int v); // -1 = no, 1 = yes - int SecondsLeft() { return (m_Closetime - time()) / time_freq(); } - bool IsVoting() { return m_Closetime != 0; } + int SecondsLeft() const; + bool IsVoting() const { return m_Closetime != 0; } int TakenChoice() const { return m_Voted; } const char *VoteDescription() const { return m_aDescription; } const char *VoteReason() const { return m_aReason; }