fixup wml menu items: track the lifetime of the manager

testing revealed that assertions may fail regarding the lifetime
of the manager of wml menu items in some cases. this commit adds
a pointer to each which tracks its manager's life time and prevents
dangling pointers.
This commit is contained in:
Chris Beck 2014-12-24 01:45:28 -05:00
parent d4440b34cd
commit 6fc1ac1bb2
6 changed files with 60 additions and 41 deletions

View file

@ -32,6 +32,8 @@
#include "util.hpp"
#include <boost/foreach.hpp>
#include <boost/make_shared.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/unordered_map.hpp>
#include <iostream>
@ -99,6 +101,7 @@ manager::manager(const config& cfg, const boost::shared_ptr<t_context> & res)
, used_items_()
, pump_(new game_events::t_pump(*this, res))
, resources_(res)
, me_(boost::make_shared<manager * const>(this))
{
BOOST_FOREACH(const config &ev, cfg.child_range("event")) {
add_event_handler(ev);
@ -126,7 +129,7 @@ manager::manager(const config& cfg, const boost::shared_ptr<t_context> & res)
}
// Create the event handlers for menu items.
resources_->gamedata->get_wml_menu_items().init_handlers();
resources_->gamedata->get_wml_menu_items().init_handlers(me_);
}
manager::~manager() {}

View file

@ -115,6 +115,7 @@ namespace game_events {
boost::scoped_ptr<game_events::t_pump> pump_;
boost::shared_ptr<t_context> resources_;
boost::shared_ptr<manager* const> me_;
public:
/// Note that references will be maintained,

View file

@ -37,7 +37,6 @@
#include "../preferences.hpp"
#include "../replay.hpp"
#include "../replay_helper.hpp"
#include "../resources.hpp"
#include "../synced_context.hpp"
#include "../terrain_filter.hpp"
@ -92,7 +91,8 @@ wml_menu_item::wml_menu_item(const std::string& id, const config & cfg) :
command_(cfg.child_or_empty("command")),
default_hotkey_(cfg.child_or_empty("default_hotkey")),
use_hotkey_(cfg["use_hotkey"].to_bool(true)),
use_wml_menu_(cfg["use_hotkey"].str() != "only")
use_wml_menu_(cfg["use_hotkey"].str() != "only"),
my_manager_()
{
}
@ -146,7 +146,8 @@ wml_menu_item::wml_menu_item(const std::string& id, const vconfig & definition,
command_(original.command_),
default_hotkey_(original.default_hotkey_),
use_hotkey_(original.use_hotkey_),
use_wml_menu_(original.use_wml_menu_)
use_wml_menu_(original.use_wml_menu_),
my_manager_()
{
// Apply WML.
update(definition);
@ -221,24 +222,29 @@ void wml_menu_item::fire_event(const map_location & event_hex, const game_data &
void wml_menu_item::finish_handler() const
{
if ( !command_.empty() ) {
assert(resources::game_events);
resources::game_events->remove_event_handler(command_["id"]);
if (boost::shared_ptr<manager * const> man = my_manager_.lock()) {
(**man).remove_event_handler(command_["id"]);
} else {
LOG_NG << "Tried to finish handler for a wml menu item, but the manager could not be found.\n";
}
}
// Hotkey support
if ( use_hotkey_ )
if ( use_hotkey_ ) {
hotkey::remove_wml_hotkey(hotkey_id_);
}
}
/**
* Initializes the implicit event handler for an inlined [command].
*/
void wml_menu_item::init_handler() const
void wml_menu_item::init_handler(const boost::shared_ptr<manager * const> & man) const
{
// If this menu item has a [command], add a handler for it.
if ( !command_.empty() ) {
assert(resources::game_events);
resources::game_events->add_event_handler(command_, true);
assert(man);
my_manager_ = man;
(**man).add_event_handler(command_, true);
}
// Hotkey support
@ -349,37 +355,39 @@ void wml_menu_item::update(const vconfig & vcfg)
*/
void wml_menu_item::update_command(const config & new_command)
{
// If there is an old command, remove it from the event handlers.
if ( !command_.empty() ) {
assert(resources::game_events);
manager::iteration iter(event_name_, *resources::game_events);
while ( handler_ptr hand = *iter ) {
if ( hand->is_menu_item() ) {
LOG_NG << "Removing command for " << event_name_ << ".\n";
resources::game_events->remove_event_handler(command_["id"].str());
if (boost::shared_ptr<manager * const> man = my_manager_.lock()) {
// If there is an old command, remove it from the event handlers.
if ( !command_.empty() ) {
manager::iteration iter(event_name_, **man);
while ( handler_ptr hand = *iter ) {
if ( hand->is_menu_item() ) {
LOG_NG << "Removing command for " << event_name_ << ".\n";
(**man).remove_event_handler(command_["id"].str());
}
++iter;
}
++iter;
}
}
// Update our stored command.
if ( new_command.empty() )
command_.clear();
else {
command_ = new_command;
// Update our stored command.
if ( new_command.empty() )
command_.clear();
else {
command_ = new_command;
// Set some fields required by event processing.
config::attribute_value & event_id = command_["id"];
if ( event_id.empty() && !item_id_.empty() ) {
event_id = item_id_;
// Set some fields required by event processing.
config::attribute_value & event_id = command_["id"];
if ( event_id.empty() && !item_id_.empty() ) {
event_id = item_id_;
}
command_["name"] = event_name_;
command_["first_time_only"] = false;
// Register the event.
LOG_NG << "Setting command for " << event_name_ << " to:\n" << command_;
(**man).add_event_handler(command_, true);
}
command_["name"] = event_name_;
command_["first_time_only"] = false;
// Register the event.
LOG_NG << "Setting command for " << event_name_ << " to:\n" << command_;
assert(resources::game_events);
resources::game_events->add_event_handler(command_, true);
} else {
ERR_NG << "Tried to set a command for a menu item, but the manager could not be found\n";
}
}

View file

@ -24,6 +24,9 @@
#include "../tstring.hpp"
#include "../variable.hpp"
#include <boost/shared_ptr.hpp>
#include <boost/weak_ptr.hpp>
class filter_context;
class game_data;
struct map_location;
@ -31,6 +34,7 @@ class unit_map;
namespace game_events
{
class manager;
class wml_menu_item
{
@ -57,7 +61,7 @@ public:
/// Removes the implicit event handler for an inlined [command].
void finish_handler() const;
/// Initializes the implicit event handler for an inlined [command].
void init_handler() const;
void init_handler(const boost::shared_ptr<manager * const> &) const;
/// The text to put in a menu for this item.
/// This will be either translated text or a hotkey identifier.
std::string menu_text() const
@ -98,6 +102,9 @@ private: // Data
bool use_hotkey_;
/// If true, allow using the menu to trigger this item.
bool use_wml_menu_;
/// A link back to my manager
mutable boost::weak_ptr<manager * const> my_manager_;
};
} // end namespace game_events

View file

@ -133,7 +133,7 @@ std::vector<std::pair<boost::shared_ptr<const wml_menu_item>, std::string> > wmi
/**
* Initializes the implicit event handlers for inlined [command]s.
*/
void wmi_container::init_handlers() const
void wmi_container::init_handlers(const boost::shared_ptr<manager * const> & man) const
{
// Applying default hotkeys here currently does not work because
// the hotkeys are reset by play_controler::init_managers() ->
@ -149,7 +149,7 @@ void wmi_container::init_handlers() const
// Loop through each menu item.
BOOST_FOREACH( const item_ptr & wmi, *this ) {
// If this menu item has a [command], add a handler for it.
wmi->init_handler();
wmi->init_handler(man);
// Count the menu items (for the diagnostic message).
++wmi_count;
}

View file

@ -38,7 +38,7 @@ class vconfig;
namespace game_events
{
class wml_menu_item;
class manager;
/// A container of wml_menu_item.
class wmi_container{
@ -85,7 +85,7 @@ public:
return get_items(hex, gamedata, fc, units, begin(), end());
}
/// Initializes the implicit event handlers for inlined [command]s.
void init_handlers() const;
void init_handlers(const boost::shared_ptr<manager * const> &) const;
void to_config(config& cfg) const;
/// Updates or creates (as appropriate) the menu item with the given @a id.
void set_item(const std::string& id, const vconfig& menu_item);