Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Hotkeys: refactored how hotkey_commands are stored and managed (WML hotkeys only) #6728

Merged
merged 2 commits into from
May 26, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
18 changes: 10 additions & 8 deletions src/game_events/menu_item.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ wml_menu_item::wml_menu_item(const std::string& id, const config& cfg)
: item_id_(id)
, event_name_(make_item_name(id))
, hotkey_id_(make_item_hotkey(id))
, hotkey_record_()
, image_(cfg["image"].str())
, description_(cfg["description"].t_str())
, needs_select_(cfg["needs_select"].to_bool(false))
Expand All @@ -92,6 +93,7 @@ wml_menu_item::wml_menu_item(const std::string& id, const vconfig& definition)
: item_id_(id)
, event_name_(make_item_name(id))
, hotkey_id_(make_item_hotkey(id))
, hotkey_record_()
, image_()
, description_()
, needs_select_(false)
Expand All @@ -106,7 +108,7 @@ wml_menu_item::wml_menu_item(const std::string& id, const vconfig& definition)
{
// On the off-chance that update() doesn't do it, add the hotkey here.
// (Update can always modify it.)
hotkey::add_wml_hotkey(hotkey_id_, description_, default_hotkey_);
hotkey_record_.emplace(hotkey_id_, description_, default_hotkey_);

// Apply WML.
update(definition);
Expand Down Expand Up @@ -189,7 +191,7 @@ void wml_menu_item::fire_event(const map_location& event_hex, const game_data& d
}
}

void wml_menu_item::finish_handler() const
void wml_menu_item::finish_handler()
{
if(!command_.empty()) {
assert(resources::game_events);
Expand All @@ -198,11 +200,11 @@ void wml_menu_item::finish_handler() const

// Hotkey support
if(use_hotkey_) {
hotkey::remove_wml_hotkey(hotkey_id_);
hotkey_record_.reset();
}
}

void wml_menu_item::init_handler() const
void wml_menu_item::init_handler()
{
// If this menu item has a [command], add a handler for it.
if(!command_.empty()) {
Expand All @@ -212,7 +214,7 @@ void wml_menu_item::init_handler() const

// Hotkey support
if(use_hotkey_) {
hotkey::add_wml_hotkey(hotkey_id_, description_, default_hotkey_);
hotkey_record_.emplace(hotkey_id_, description_, default_hotkey_);
}
}

Expand Down Expand Up @@ -321,15 +323,15 @@ void wml_menu_item::update(const vconfig& vcfg)

if(use_hotkey_ && !old_use_hotkey) {
// The hotkey needs to be enabled.
hotkey::add_wml_hotkey(hotkey_id_, description_, default_hotkey_);
hotkey_record_.emplace(hotkey_id_, description_, default_hotkey_);

} else if(use_hotkey_ && hotkey_updated) {
// The hotkey needs to be updated.
hotkey::add_wml_hotkey(hotkey_id_, description_, default_hotkey_);
hotkey_record_.emplace(hotkey_id_, description_, default_hotkey_);

} else if(!use_hotkey_ && old_use_hotkey) {
// The hotkey needs to be disabled.
hotkey::remove_wml_hotkey(hotkey_id_);
hotkey_record_.reset();
}
}

Expand Down
10 changes: 8 additions & 2 deletions src/game_events/menu_item.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,11 @@
#pragma once

#include "config.hpp"
#include "hotkey/hotkey_command.hpp"
#include "variable.hpp"

#include <optional>

class filter_context;
class game_data;
struct map_location;
Expand Down Expand Up @@ -103,10 +106,10 @@ class wml_menu_item
void fire_event(const map_location& event_hex, const game_data& data) const;

/** Removes the implicit event handler for an inlined [command]. */
void finish_handler() const;
void finish_handler();

/** Initializes the implicit event handler for an inlined [command]. */
void init_handler() const;
void init_handler();

/**
* The text to put in a menu for this item.
Expand Down Expand Up @@ -150,6 +153,9 @@ class wml_menu_item
/** The id for this item's hotkey; based on the item's id. */
const std::string hotkey_id_;

/** Controls the lifetime of the associate hotkey's hotkey_command. */
std::optional<hotkey::wml_hotkey_record> hotkey_record_;

/** The image to display in the menu next to this item's description. */
std::string image_;

Expand Down
2 changes: 1 addition & 1 deletion src/gui/dialogs/preferences_dialog.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -795,7 +795,7 @@ listbox& preferences_dialog::setup_hotkey_list()
const std::string eh = "<span color='#0f0'>" + _("editor_hotkeys^E") + "</span>";
const std::string mh = "<span color='#0f0'>" + _("mainmenu_hotkeys^M") + "</span>";

for(const auto& hotkey_item : hotkey::get_hotkey_commands()) {
for(const auto& [id, hotkey_item] : hotkey::get_hotkey_commands()) {
if(hotkey_item.hidden) {
continue;
}
Expand Down
114 changes: 30 additions & 84 deletions src/hotkey/hotkey_command.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -324,11 +324,7 @@ const std::set<HOTKEY_COMMAND> toggle_commands {
};

// Contains copies of master_hotkey_list and all current active wml menu hotkeys
// TODO: Maybe known_hotkeys is not a fitting name anymore.
std::vector<hotkey::hotkey_command> known_hotkeys;

// Index map for known_hotkeys. Since known_hotkeys begins with master_hotkey_list, they are also indexes for master_hotkey_list.
std::map<std::string, std::size_t> command_map;
std::map<std::string_view, hotkey::hotkey_command> registered_hotkeys;

hk_scopes scope_active(0);
} // end anon namespace
Expand Down Expand Up @@ -373,75 +369,40 @@ bool is_scope_active(hk_scopes s)

const hotkey_command& get_hotkey_command(const std::string& command)
{
if(command_map.find(command) == command_map.end()) {
try {
return registered_hotkeys.at(command);
} catch(const std::out_of_range&) {
return get_hotkey_null();
}

return known_hotkeys[command_map[command]];
}

const std::vector<hotkey_command>& get_hotkey_commands()
const std::map<std::string_view, hotkey::hotkey_command>& get_hotkey_commands()
{
return known_hotkeys;
}

// Returns whether a hotkey was deleted.
bool remove_wml_hotkey(const std::string& id)
{
const hotkey::hotkey_command& command = get_hotkey_command(id);

if(command.command == hotkey::HOTKEY_NULL) {
LOG_G << "remove_wml_hotkey: command with id=" + id + " doesn't exist\n";
return false;
} else if(command.command != hotkey::HOTKEY_WML) {
LOG_G << "remove_wml_hotkey: command with id=" + id + " cannot be removed because it is no wml menu hotkey\n";
return false;
} else {
LOG_G << "removing wml hotkey with id=" + id + "\n";

// Iterate in reverse since it's almost certain the appropriate hotkey will be near
// the end, since this function is used to removed WML hotkeys, which are added after
// the built-ins.
for(auto itor = known_hotkeys.rbegin(); itor != known_hotkeys.rend(); ++itor) {
if(itor->id == id) {
known_hotkeys.erase(std::next(itor).base());
break;
}
}

// command_map might be all wrong now, so we need to rebuild.
command_map.clear();

for(std::size_t index = 0; index < known_hotkeys.size(); ++index) {
command_map[known_hotkeys[index].id] = index;
}

return true;
}
return registered_hotkeys;
}

bool has_hotkey_command(const std::string& id)
{
return get_hotkey_command(id).command != hotkey::HOTKEY_NULL;
}

void add_wml_hotkey(const std::string& id, const t_string& description, const config& default_hotkey)
wml_hotkey_record::wml_hotkey_record(const std::string& id, const t_string& description, const config& default_hotkey)
: cleanup_()
{
if(id == "null") {
LOG_G << "Couldn't add wml hotkey with null id and description = '" << description << "'.\n";
return;
}

if(has_hotkey_command(id)) {
LOG_G << "Hotkey with id '" << id << "' already exists. Deleting the old hotkey_command.\n";
remove_wml_hotkey(id);
}

DBG_G << "Added wml hotkey with id = '" << id << "' and description = '" << description << "'.\n";

known_hotkeys.emplace_back(hotkey::HOTKEY_WML, id, description, false, false, scope_game, HKCAT_CUSTOM, t_string(""));
const auto& [iter, inserted] = registered_hotkeys.try_emplace(
id, hotkey::HOTKEY_WML, id, description, false, false, scope_game, HKCAT_CUSTOM, t_string(""));

command_map[id] = known_hotkeys.size() - 1;
if(inserted) {
DBG_G << "Added wml hotkey with id = '" << id << "' and description = '" << description << "'.\n";
} else {
LOG_G << "Hotkey with id '" << id << "' already exists.\n";
return;
}

if(!default_hotkey.empty() && !has_hotkey_item(id)) {
hotkey::hotkey_ptr new_item = hotkey::load_from_config(default_hotkey);
Expand All @@ -454,6 +415,16 @@ void add_wml_hotkey(const std::string& id, const t_string& description, const co
ERR_CF << "failed to add default hotkey with id=" + id;
}
}

// Record the cleanup handler
cleanup_ = [i = iter] { registered_hotkeys.erase(i); };
Vultraz marked this conversation as resolved.
Show resolved Hide resolved
}

wml_hotkey_record::~wml_hotkey_record()
{
if(cleanup_) {
cleanup_();
}
}

hotkey_command::hotkey_command(const hotkey_command_temp& temp_command)
Expand Down Expand Up @@ -511,7 +482,7 @@ bool hotkey_command::null() const

const hotkey_command& hotkey_command::get_command_by_command(hotkey::HOTKEY_COMMAND command)
{
for(hotkey_command& cmd : known_hotkeys) {
for(auto& [id, cmd] : registered_hotkeys) {
if(cmd.command == command) {
return cmd;
}
Expand All @@ -526,17 +497,7 @@ const hotkey_command& hotkey_command::get_command_by_command(hotkey::HOTKEY_COMM

const hotkey_command& get_hotkey_null()
{
// It is the last entry in that array, and the indexes in master_hotkey_list and known_hotkeys are the same.
return known_hotkeys[master_hotkey_list.size() - 1];
}

void delete_all_wml_hotkeys()
{
while(known_hotkeys.back().command == hotkey::HOTKEY_WML) {
command_map.erase(known_hotkeys.back().id);

known_hotkeys.pop_back();
}
return registered_hotkeys.at("null");
}

const std::string& get_description(const std::string& command)
Expand All @@ -552,29 +513,14 @@ const std::string& get_tooltip(const std::string& command)

void init_hotkey_commands()
{
known_hotkeys.clear();

// Reserve enough space for the built-in hotkeys and 20 extra spaces for WML hotkeys. This is
// to avoid reallocation of this huge vector when any of the latter are added. 20 is honestly
// overkill, since there's really no reason to ever have near that many hotkey-enabled WML menu
// items, but it doesn't cost us anything to have extra.
known_hotkeys.reserve(master_hotkey_list.size() + 20);
registered_hotkeys.clear();

std::size_t i = 0;
for(const hotkey_command_temp& cmd : master_hotkey_list) {
// Initialize the full hotkey from the temp data.
auto& h = known_hotkeys.emplace_back(cmd);

// Note the known_hotkeys index associated with this command.
command_map[h.id] = i++;
registered_hotkeys.try_emplace(cmd.id, cmd);
}
}

void clear_hotkey_commands()
{
command_map.clear();
}

HOTKEY_COMMAND get_id(const std::string& command)
{
return get_hotkey_command(command).command;
Expand Down
32 changes: 21 additions & 11 deletions src/hotkey/hotkey_command.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#include "tstring.hpp"

#include <bitset>
#include <functional>
#include <list>
#include <map>
#include <vector>
Expand Down Expand Up @@ -235,7 +236,7 @@ struct hotkey_command
/** Constructs a new command from a temporary static hotkey object. */
hotkey_command(const hotkey_command_temp& temp_command);

/** @todo: remove this with c++20. We can use aggregate initialization with emplace_back.*/
/** @todo: see if we can remove this with c++20. Aggregate initialization with try_emplace?*/
hotkey_command(HOTKEY_COMMAND cmd, const std::string& id, const t_string& desc, bool hidden, bool toggle, hk_scopes scope, HOTKEY_CATEGORY category, const t_string& tooltip);

hotkey_command(const hotkey_command&) = default;
Expand Down Expand Up @@ -292,7 +293,7 @@ class scope_changer {
* returns a container that contains all currently active hotkey_commands.
* everything that wants a hotkey, must be in this container
*/
const std::vector<hotkey_command>& get_hotkey_commands();
const std::map<std::string_view, hotkey::hotkey_command>& get_hotkey_commands();

/** returns the hotkey_command with the given name */
const hotkey_command& get_hotkey_command(const std::string& command);
Expand All @@ -309,23 +310,32 @@ bool is_scope_active(hk_scopes s);
bool has_hotkey_command(const std::string& id);

/**
* adds a new wml hotkey to the list, but only if there is no hotkey with that id yet on the list.
* the object that is created here will be deleted in "delete_all_wml_hotkeys()"
* RAII helper class to control the lifetime of a WML hotkey_command.
*/
void add_wml_hotkey(const std::string& id, const t_string& description, const config& default_hotkey);
class wml_hotkey_record
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could do with documentation, and a better name. "Record" seems almost as generic as "Object" in this instance.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What name would you suggest?

{
public:
wml_hotkey_record() = default;

/** Don't allow copying so objects don't get erased early. */
wml_hotkey_record(const wml_hotkey_record&) = delete;
const wml_hotkey_record& operator=(const wml_hotkey_record&) = delete;

/** Registers a hotkey_command for a WML hotkey with the given ID if one does not already exist. */
wml_hotkey_record(const std::string& id, const t_string& description, const config& default_hotkey);

/** deletes all wml hotkeys, should be called after a game has ended */
void delete_all_wml_hotkeys();
/** removes a wml hotkey with the given id, returns true if the deletion was successful */
bool remove_wml_hotkey(const std::string& id);
~wml_hotkey_record();

private:
/** Handles removing the associated hotkey_command on this object's destruction. */
std::function<void()> cleanup_{};
};

const std::string& get_description(const std::string& command);
const std::string& get_tooltip(const std::string& command);

void init_hotkey_commands();

void clear_hotkey_commands();

/** returns get_hotkey_command(command).id */
HOTKEY_COMMAND get_id(const std::string& command);
}
2 changes: 0 additions & 2 deletions src/hotkey/hotkey_manager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,7 @@ void manager::init()

void manager::wipe()
{
clear_hotkey_commands();
clear_hotkeys();
delete_all_wml_hotkeys();
}

manager::~manager()
Expand Down
1 change: 0 additions & 1 deletion src/play_controller.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,6 @@ play_controller::play_controller(const config& level, saved_game& state_of_game,
play_controller::~play_controller()
{
unit_types.remove_scenario_fixes();
hotkey::delete_all_wml_hotkeys();
clear_resources();
}

Expand Down