Skip to content

Commit

Permalink
Fixed keybinds created from C++ mods executing multiple times
Browse files Browse the repository at this point in the history
This only happened if the hot-reload feature was used to reload all mods.

This was fixed by introducing CppUserModBase::register_keydown_event.
It's identical to the one in UE4SSProgram, except it keeps track of which C++ mod registered the event so that it can be "unregistered" when the mod is reloaded.

Mods that continue to use the UE4SSProgram version will continue to have the same problem.

Updated changelog
  • Loading branch information
UE4SS authored and Buckminsterfullerene02 committed Apr 21, 2024
1 parent cb5823d commit 658e17e
Show file tree
Hide file tree
Showing 7 changed files with 80 additions and 11 deletions.
4 changes: 4 additions & 0 deletions UE4SS/include/Mod/CppUserModBase.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#include <Common.hpp>
#include <File/Macros.hpp>
#include <GUI/GUITab.hpp>
#include <Input/Handler.hpp>

namespace RC
{
Expand Down Expand Up @@ -126,5 +127,8 @@ namespace RC

protected:
RC_UE4SS_API auto register_tab(std::wstring_view tab_name, GUI::GUITab::RenderFunctionType) -> void;
RC_UE4SS_API auto register_keydown_event(Input::Key, const Input::EventCallbackCallable&, uint8_t custom_data = 0) -> void;
RC_UE4SS_API auto register_keydown_event(Input::Key, const Input::Handler::ModifierKeyArray&, const Input::EventCallbackCallable&, uint8_t custom_data = 0)
-> void;
};
} // namespace RC
22 changes: 19 additions & 3 deletions UE4SS/include/UE4SSProgram.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,21 @@ namespace RC
uint64_t safety_padding[8]{0};
};

struct KeyDownEventData
{
// Custom data from the C++ mod.
// The 'custom_data' variable to UE4SSProgram::register_keydown_event will be used to determine the type of custom_data2.
uint8_t custom_data{};

// The C++ mod that created this event.
CppUserModBase* mod{};
};

class UE4SSProgram : public MProgram
{
public:
friend class CppUserModBase; // m_input_handler

public:
constexpr static wchar_t m_settings_file_name[] = L"UE4SS-settings.ini";
constexpr static wchar_t m_log_file_name[] = L"UE4SS.log";
Expand Down Expand Up @@ -213,9 +226,12 @@ namespace RC

public:
// API pass-through for use outside the private scope of UE4SSProgram
RC_UE4SS_API auto register_keydown_event(Input::Key, const Input::EventCallbackCallable&, uint8_t custom_data = 0) -> void;
RC_UE4SS_API auto register_keydown_event(Input::Key, const Input::Handler::ModifierKeyArray&, const Input::EventCallbackCallable&, uint8_t custom_data = 0)
-> void;
RC_UE4SS_API auto register_keydown_event(Input::Key, const Input::EventCallbackCallable&, uint8_t custom_data = 0, void* custom_data2 = nullptr) -> void;
RC_UE4SS_API auto register_keydown_event(Input::Key,
const Input::Handler::ModifierKeyArray&,
const Input::EventCallbackCallable&,
uint8_t custom_data = 0,
void* custom_data2 = nullptr) -> void;
RC_UE4SS_API auto is_keydown_event_registered(Input::Key) -> bool;
RC_UE4SS_API auto is_keydown_event_registered(Input::Key, const Input::Handler::ModifierKeyArray&) -> bool;

Expand Down
38 changes: 38 additions & 0 deletions UE4SS/src/Mod/CppUserModBase.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,49 @@ namespace RC
}
}
GUITabs.clear();

auto& key_events = UE4SSProgram::get_program().m_input_handler.get_events();
std::erase_if(key_events, [&](Input::KeySet& input_event) -> bool {
bool were_all_events_registered_from_this_mod = true;
for (auto& [key, vector_of_key_data] : input_event.key_data)
{
std::erase_if(vector_of_key_data, [&](Input::KeyData& key_data) -> bool {
// custom_data == 1: Bind came from Lua, and custom_data2 is nullptr.
// custom_data == 2: Bind came from C++, and custom_data2 is a pointer to KeyDownEventData. Must free it.
auto event_data = static_cast<KeyDownEventData*>(key_data.custom_data2);
if (key_data.custom_data == 2 && event_data && event_data->mod == this)
{
delete event_data;
return true;
}
else
{
were_all_events_registered_from_this_mod = false;
return false;
}
});
}

return were_all_events_registered_from_this_mod;
});
}

auto CppUserModBase::register_tab(std::wstring_view tab_name, GUI::GUITab::RenderFunctionType render_function) -> void
{
auto& tab = GUITabs.emplace_back(std::make_shared<GUI::GUITab>(tab_name, render_function, this));
UE4SSProgram::get_program().add_gui_tab(tab);
}

auto CppUserModBase::register_keydown_event(Input::Key key, const Input::EventCallbackCallable& callback, uint8_t custom_data) -> void
{
UE4SSProgram::get_program().register_keydown_event(key, callback, 2, new KeyDownEventData{custom_data, this});
}

auto CppUserModBase::register_keydown_event(Input::Key key,
const Input::Handler::ModifierKeyArray& callback,
const Input::EventCallbackCallable& modifier_keys,
uint8_t custom_data) -> void
{
UE4SSProgram::get_program().register_keydown_event(key, callback, modifier_keys, 2, new KeyDownEventData{custom_data, this});
}
} // namespace RC
11 changes: 7 additions & 4 deletions UE4SS/src/UE4SSProgram.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1273,6 +1273,8 @@ namespace RC
for (auto& [key, vector_of_key_data] : input_event.key_data)
{
std::erase_if(vector_of_key_data, [&](Input::KeyData& key_data) -> bool {
// custom_data == 1: Bind came from Lua, and custom_data2 is nullptr.
// custom_data == 2: Bind came from C++, and custom_data2 is a pointer to KeyDownEventData. Must free it.
if (key_data.custom_data == 1)
{
return true;
Expand Down Expand Up @@ -1443,17 +1445,18 @@ namespace RC
return m_queued_events.empty();
}

auto UE4SSProgram::register_keydown_event(Input::Key key, const Input::EventCallbackCallable& callback, uint8_t custom_data) -> void
auto UE4SSProgram::register_keydown_event(Input::Key key, const Input::EventCallbackCallable& callback, uint8_t custom_data, void* custom_data2) -> void
{
m_input_handler.register_keydown_event(key, callback, custom_data);
m_input_handler.register_keydown_event(key, callback, custom_data, custom_data2);
}

auto UE4SSProgram::register_keydown_event(Input::Key key,
const Input::Handler::ModifierKeyArray& modifier_keys,
const Input::EventCallbackCallable& callback,
uint8_t custom_data) -> void
uint8_t custom_data,
void* custom_data2) -> void
{
m_input_handler.register_keydown_event(key, modifier_keys, callback, custom_data);
m_input_handler.register_keydown_event(key, modifier_keys, callback, custom_data, custom_data2);
}

auto UE4SSProgram::is_keydown_event_registered(Input::Key key) -> bool
Expand Down
3 changes: 3 additions & 0 deletions assets/Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ TBD
### Lua API

### C++ API
Key binds created with `UE4SSProgram::register_keydown_event` end up being duplicated upon mod hot-reload.
To fix this, `CppUserModBase::register_keydown_event` has been introduced.
It's used exactly the same way except without the `UE4SSProgram::` part.

### Experimental

Expand Down
6 changes: 4 additions & 2 deletions deps/first/Input/include/Input/Handler.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ namespace RC::Input
std::vector<ModifierKey> required_modifier_keys{};
std::vector<EventCallbackCallable> callbacks{};
uint8_t custom_data{};
void* custom_data2{};
bool requires_modifier_keys{};
bool is_down{};
};
Expand Down Expand Up @@ -73,10 +74,11 @@ namespace RC::Input

public:
auto process_event() -> void;
auto register_keydown_event(Input::Key, EventCallbackCallable, uint8_t custom_data = 0) -> void;
auto register_keydown_event(Input::Key, EventCallbackCallable, uint8_t custom_data = 0, void* custom_data2 = nullptr) -> void;

using ModifierKeyArray = std::array<Input::ModifierKey, max_modifier_keys>;
auto register_keydown_event(Input::Key, const ModifierKeyArray&, const EventCallbackCallable&, uint8_t custom_data = 0) -> void;
auto register_keydown_event(Input::Key, const ModifierKeyArray&, const EventCallbackCallable&, uint8_t custom_data = 0, void* custom_data2 = nullptr)
-> void;

auto is_keydown_event_registered(Input::Key) -> bool;
auto is_keydown_event_registered(Input::Key, const ModifierKeyArray&) -> bool;
Expand Down
7 changes: 5 additions & 2 deletions deps/first/Input/src/Handler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ namespace RC::Input
}
}

auto Handler::register_keydown_event(Input::Key key, EventCallbackCallable callback, uint8_t custom_data) -> void
auto Handler::register_keydown_event(Input::Key key, EventCallbackCallable callback, uint8_t custom_data, void* custom_data2) -> void
{
KeySet& key_set = [&]() -> KeySet& {
for (auto& key_set : m_key_sets)
Expand All @@ -176,9 +176,11 @@ namespace RC::Input
KeyData& key_data = key_set.key_data[key].emplace_back();
key_data.callbacks.emplace_back(callback);
key_data.custom_data = custom_data;
key_data.custom_data2 = custom_data2;
}

auto Handler::register_keydown_event(Input::Key key, const ModifierKeyArray& modifier_keys, const EventCallbackCallable& callback, uint8_t custom_data) -> void
auto Handler::register_keydown_event(
Input::Key key, const ModifierKeyArray& modifier_keys, const EventCallbackCallable& callback, uint8_t custom_data, void* custom_data2) -> void
{
KeySet& key_set = [&]() -> KeySet& {
for (auto& key_set : m_key_sets)
Expand All @@ -195,6 +197,7 @@ namespace RC::Input
KeyData& key_data = key_set.key_data[key].emplace_back();
key_data.callbacks.emplace_back(callback);
key_data.custom_data = custom_data;
key_data.custom_data2 = custom_data2;
key_data.requires_modifier_keys = true;

for (const auto& modifier_key : modifier_keys)
Expand Down

0 comments on commit 658e17e

Please sign in to comment.