The Battle for Wesnoth  1.17.4+dev
manager_impl.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2003 - 2022
3  by David White <dave@whitevine.net>
4  Part of the Battle for Wesnoth Project https://www.wesnoth.org/
5 
6  This program is free software; you can redistribute it and/or modify
7  it under the terms of the GNU General Public License as published by
8  the Free Software Foundation; either version 2 of the License, or
9  (at your option) any later version.
10  This program is distributed in the hope that it will be useful,
11  but WITHOUT ANY WARRANTY.
12 
13  See the COPYING file for more details.
14 */
15 
17 
18 #include "game_events/handlers.hpp"
19 #include "formula/string_utils.hpp"
20 #include "log.hpp"
22 
23 #include <boost/algorithm/string.hpp>
24 
25 #include <iostream>
26 
27 static lg::log_domain log_engine("engine");
28 #define DBG_NG LOG_STREAM(debug, log_engine)
29 #define LOG_NG LOG_STREAM(info, log_engine)
30 #define WRN_NG LOG_STREAM(warn, log_engine)
31 
32 static lg::log_domain log_event_handler("event_handler");
33 #define DBG_EH LOG_STREAM(debug, log_event_handler)
34 
35 static lg::log_domain log_wml("wml");
36 #define ERR_WML LOG_STREAM(err, log_wml)
37 #define DBG_WML LOG_STREAM(debug, log_wml)
38 
39 namespace game_events
40 {
42 {
43  if(lg::debug().dont_log(log_event_handler)) {
44  return;
45  }
46 
47  std::stringstream ss;
48 
49  for(const handler_ptr& h : active_) {
50  if(!h) {
51  continue;
52  }
53 
54  const config& cfg = h->get_config();
55  ss << "name=" << cfg["name"] << ", with id=" << cfg["id"] << "; ";
56  }
57 
58  DBG_EH << "active handlers are now " << ss.str() << "\n";
59 }
60 
61 /**
62  * Utility to standardize the event names used in by_name_.
63  * This means stripping leading and trailing spaces, and converting internal
64  * spaces to underscores.
65  */
66 std::string event_handlers::standardize_name(const std::string& name)
67 {
68  std::string retval = name;
69 
70  // Trim leading and trailing spaces.
71  boost::trim(retval);
72 
73  // Replace internal spaces with underscores.
74  boost::replace_all(retval, " ", "_");
75 
76  return retval;
77 }
78 
79 /**
80  * Read-only access to the handlers with fixed event names, by event name.
81  */
82 handler_list& event_handlers::get(const std::string& name)
83 {
84  // Empty list for the "not found" case.
85  static handler_list empty_list;
86 
87  // Look for the name in the name map.
88  auto find_it = by_name_.find(standardize_name(name));
89  return find_it == by_name_.end() ? empty_list : find_it->second;
90 }
91 
92 /**
93  * Adds an event handler.
94  * An event with a nonempty ID will not be added if an event with that
95  * ID already exists.
96  */
97 void event_handlers::add_event_handler(const config& cfg, bool is_menu_item)
98 {
99  // Someone decided to register an empty event... bail.
100  if(cfg.empty()) {
101  return;
102  }
103 
104  std::string name = cfg["name"];
105  std::string id = cfg["id"];
106 
107  if(!id.empty()) {
108  // Ignore this handler if there is already one with this ID.
109  auto find_it = id_map_.find(id);
110 
111  if(find_it != id_map_.end() && !find_it->second.expired()) {
112  DBG_EH << "ignoring event handler for name='" << name << "' with id '" << id << "'\n";
113  return;
114  }
115  }
116 
117  if(name.empty() && id.empty()) {
118  lg::log_to_chat() << "[event] is missing name or id field\n";
119  ERR_WML << "[event] is missing name or id field\n";
120  DBG_WML << "Content of that event:\n" << cfg.debug() << "\n";
121  return;
122  }
123 
124  // Make a copy of the event cfg here in order to do some standardization on the
125  // name field. Will be moved into the handler.
126  config event_cfg = cfg;
127 
128  // Split the name field...
129  std::vector<std::string> standardized_names = utils::split(name);
130 
131  if(!name.empty()) {
132  // ...and standardize each one individually. This ensures they're all valid for by-name lookup.
133  for(std::string& single_name : standardized_names) {
134  if(!utils::might_contain_variables(single_name)) {
135  single_name = standardize_name(single_name);
136  }
137  }
138 
139  assert(!standardized_names.empty());
140 
141  // Write the new name back to the config.
142  name = utils::join(standardized_names);
143  event_cfg["name"] = name;
144  }
145 
146  // Create a new handler.
147  // Do note active_ holds the main shared_ptr, and the other three containers
148  // construct weak_ptrs from the shared one.
149  DBG_EH << "inserting event handler for name=" << name << " with id=" << id << "\n";
150  active_.emplace_back(new event_handler(std::move(event_cfg), is_menu_item, standardized_names));
151 
152  //
153  // !! event_cfg is invalid past this point! DO NOT USE!
154  //
155 
156  // File by name.
158  dynamic_.emplace_back(active_.back());
159  } else {
160  for(const std::string& single_name : standardized_names) {
161  by_name_[single_name].emplace_back(active_.back());
162  }
163  }
164 
165  // File by ID.
166  if(!id.empty()) {
167  id_map_[id] = active_.back();
168  }
169 
170  log_handlers();
171 }
172 
173 /**
174  * Removes an event handler, identified by its ID.
175  * Events with empty IDs cannot be removed.
176  */
177 void event_handlers::remove_event_handler(const std::string& id)
178 {
179  if(id.empty()) {
180  return;
181  }
182 
183  DBG_EH << "removing event handler with id " << id << "\n";
184 
185  // Find the existing handler with this ID.
186  auto find_it = id_map_.find(id);
187  if(find_it != id_map_.end()) {
188  handler_ptr handler = find_it->second.lock();
189 
190  if(handler && !handler->disabled()) {
191  handler->disable();
192  }
193 
194  // Do this even if the lock failed.
195  id_map_.erase(find_it);
196 
197  // We don't delete the handler from the other lists just yet. This is to ensure
198  // the main handler list's size doesn't change when iterating over the handlers.
199  // Any disabled handlers don't get executed, and will be removed during the next
200  // cleanup pass.
201  }
202 
203  log_handlers();
204 }
205 
206 void event_handlers::clean_up_expired_handlers(const std::string& event_name)
207 {
208  // First, remove all disabled handlers from the main list.
209  auto to_remove = std::remove_if(active_.begin(), active_.end(),
210  [](handler_ptr p) { return p->disabled(); }
211  );
212 
213  active_.erase(to_remove, active_.end());
214 
215  // Then remove any now-unlockable weak_ptrs from the by-name list.
216  // Might be more than one so we split.
217  for(const std::string& name : utils::split(event_name)) {
218  get(name).remove_if(
219  [](weak_handler_ptr ptr) { return ptr.expired(); }
220  );
221  }
222 
223  // And finally remove any now-unlockable weak_ptrs from the with-variables name list.
224  dynamic_.remove_if(
225  [](weak_handler_ptr ptr) { return ptr.expired(); }
226  );
227 }
228 
230 {
231  auto find_it = id_map_.find(id);
232  if(find_it != id_map_.end() && !find_it->second.expired()) {
233  return find_it->second.lock();
234  }
235 
236  return nullptr;
237 }
238 
239 } // end namespace game_events
std::string join(const T &v, const std::string &s=",")
Generates a new string joining container items in a list.
static std::string standardize_name(const std::string &name)
Utility to standardize the event names used in by_name_.
handler_list & get(const std::string &name)
Access to the handlers with fixed event names, by event name.
void clean_up_expired_handlers(const std::string &event_name)
Removes all expired event handlers and any weak_ptrs to them.
#define h
id_map_t id_map_
Allows quick locating of handlers by id.
static lg::log_domain log_wml("wml")
#define DBG_WML
void remove_event_handler(const std::string &id)
Removes an event handler, identified by its ID.
handler_list dynamic_
Active event handlers with variables in their event names.
static lg::log_domain log_engine("engine")
std::weak_ptr< event_handler > weak_handler_ptr
Definition: fwd.hpp:26
handler_queue_t active_
Active event handlers.
logger & debug()
Definition: log.cpp:95
Domain specific events.
Definition: action_wml.cpp:87
std::string id
Text to match against addon_info.tags()
Definition: manager.cpp:215
void add_event_handler(const config &cfg, bool is_menu_item=false)
Adds an event handler.
mock_party p
map_t by_name_
Active event handlers with fixed event names, organized by event name.
bool might_contain_variables(const std::string &str)
Determines if a string might contain variables to interpolate.
std::list< weak_handler_ptr > handler_list
Definition: fwd.hpp:27
Define the handlers for the game&#39;s events mechanism.
std::vector< std::string > split(const config_attribute_value &val)
Standard logging facilities (interface).
void trim(std::string_view &s)
#define ERR_WML
static lg::log_domain log_event_handler("event_handler")
retval
Default window/dialog return values.
Definition: retval.hpp:29
std::shared_ptr< event_handler > handler_ptr
Definition: fwd.hpp:25
const handler_ptr get_event_handler_by_id(const std::string &id)
Gets an event handler, identified by its ID.
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:60
bool empty() const
Definition: config.cpp:941
std::string debug() const
Definition: config.cpp:1347
#define DBG_EH
std::stringstream & log_to_chat()
Use this to show WML errors in the ingame chat.
Definition: log.cpp:289