The Battle for Wesnoth  1.17.21+dev
pump.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2003 - 2023
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 
16 /**
17  * @file
18  * Handles the current state of WML-events. This includes raising and firing,
19  * as well as tracking the context for event firing.
20  */
21 
22 #include "game_events/pump.hpp"
23 #include "game_events/handlers.hpp"
24 
25 #include "display_chat_manager.hpp"
26 #include "game_config.hpp"
27 #include "gettext.hpp"
28 #include "log.hpp"
29 #include "play_controller.hpp"
30 #include "resources.hpp"
32 #include "side_filter.hpp"
33 #include "units/map.hpp"
34 #include "units/unit.hpp"
35 #include "variable.hpp"
36 #include "video.hpp" // only for faked
37 #include "whiteboard/manager.hpp"
38 
39 #include <iomanip>
40 
41 static lg::log_domain log_engine("engine");
42 #define DBG_NG LOG_STREAM(debug, log_engine)
43 #define LOG_NG LOG_STREAM(info, log_engine)
44 #define ERR_NG LOG_STREAM(err, log_engine)
45 
46 static lg::log_domain log_wml("wml");
47 #define DBG_WML LOG_STREAM(debug, log_wml)
48 #define LOG_WML LOG_STREAM(info, log_wml)
49 #define WRN_WML LOG_STREAM(warn, log_wml)
50 #define ERR_WML LOG_STREAM(err, log_wml)
51 
52 static lg::log_domain log_event_handler("event_handler");
53 #define DBG_EH LOG_STREAM(debug, log_event_handler)
54 
55 // This file is in the game_events namespace.
56 namespace game_events
57 {
58 namespace context
59 {
60 /** State when processing a particular flight of events or commands. */
61 struct state
62 {
66 
67  explicit state(bool s, bool m = true)
68  : undo_disabled(m)
69  , action_canceled(false)
70  , skip_messages(s)
71  {
72  }
73 };
74 
75 class scoped
76 {
77 public:
78  scoped(std::stack<context::state>& contexts, bool m = true);
79  ~scoped();
80 
81 private:
82  std::stack<context::state>& contexts_;
83 };
84 }
85 
86 struct pump_impl
87 {
88  std::vector<queued_event> events_queue;
89 
90  std::stringstream wml_messages_stream;
91 
92  std::stack<context::state> contexts_;
93 
94  unsigned instance_count;
95 
97 
99  : events_queue()
101  , contexts_()
102  , instance_count(0)
103  , my_manager(&man)
104  {
105  contexts_.emplace(false);
106  }
107 };
108 
109 namespace
110 { // Types
111 class pump_manager
112 {
113 public:
114  pump_manager(pump_impl&);
115  ~pump_manager();
116 
117  /** Allows iteration through the queued events. */
118  queued_event& next()
119  {
120  return queue_[pumped_count_++];
121  }
122  /** Indicates the iteration is over. */
123  bool done() const
124  {
125  return pumped_count_ >= queue_.size();
126  }
127 
128  unsigned count() const
129  {
130  return impl_.instance_count;
131  }
132 
133 private:
134  pump_impl& impl_;
135  int x1_, x2_, y1_, y2_;
136 
137  /**
138  * Tracks the events to process.
139  * This isolates these events from any events that might be generated during the processing.
140  */
141  std::vector<queued_event> queue_;
142 
143  /** Tracks how many events have been processed. */
144  std::size_t pumped_count_;
145 };
146 } // end anonymous namespace (types)
147 
148 namespace
149 { // Support functions
150 
151 pump_manager::pump_manager(pump_impl& impl)
152  : impl_(impl)
153  , x1_(resources::gamedata->get_variable("x1"))
154  , x2_(resources::gamedata->get_variable("x2"))
155  , y1_(resources::gamedata->get_variable("y1"))
156  , y2_(resources::gamedata->get_variable("y2"))
157  , queue_()
158  , pumped_count_(0) // Filled later with a swap().
159 {
160  queue_.swap(impl_.events_queue);
162 }
163 
164 pump_manager::~pump_manager()
165 {
167 
168  // Not sure what the correct thing to do is here. In princple,
169  // discarding all events (i.e. clearing events_queue) seems like
170  // the right thing to do in the face of an exception. However, the
171  // previous functionality preserved the queue, so for now we will
172  // restore it.
173  if(!done()) {
174  // The remaining events get inserted at the beginning of events_queue.
175  std::vector<queued_event> temp;
176  impl_.events_queue.swap(temp);
177  impl_.events_queue.insert(impl_.events_queue.end(), queue_.begin() + pumped_count_, queue_.end());
178  impl_.events_queue.insert(impl_.events_queue.end(), temp.begin(), temp.end());
179  }
180 
181  // Restore the old values of the game variables.
186 }
187 }
188 
189 /**
190  * Processes an event through a single event handler.
191  * This includes checking event filters, but not checking that the event
192  * name matches.
193  *
194  * @param[in,out] handler_p The handler to offer the event to.
195  * This may be reset during processing.
196  * @param[in] ev The event information.
197  */
199 {
200  DBG_EH << "processing event " << ev.name << " with id=" << ev.id;
201 
202  // We currently never pass a null pointer to this function, but to
203  // guard against future modifications:
204  if(!handler_p) {
205  return;
206  }
207 
208  unit_map& units = resources::gameboard->units();
209  scoped_xy_unit first_unit("unit", ev.loc1, units);
210  scoped_xy_unit second_unit("second_unit", ev.loc2, units);
211  scoped_weapon_info first_weapon("weapon", ev.data.optional_child("first"));
212  scoped_weapon_info second_weapon("second_weapon", ev.data.optional_child("second"));
213 
214  if(!handler_p->filter_event(ev)) {
215  return;
216  }
217 
218  // The event hasn't been filtered out, so execute the handler.
219  context::scoped evc(impl_->contexts_);
220  assert(resources::lua_kernel != nullptr);
221  handler_p->handle_event(ev, *resources::lua_kernel);
222  // NOTE: handler_p may be null at this point!
223 
224  if(ev.name == "select") {
226  }
227 
228  if(game_display::get_singleton() != nullptr) {
230  }
231 }
232 
233 /**
234  * Helper function for show_wml_messages(), which gathers
235  * the messages from a stringstream.
236  */
237 void wml_event_pump::fill_wml_messages_map(std::map<std::string, int>& msg_map, std::stringstream& source)
238 {
239  while(true) {
240  std::string msg;
241  std::getline(source, msg);
242 
243  if(source.eof()) {
244  break;
245  }
246 
247  if(msg.empty()) {
248  continue;
249  }
250 
251  if(msg_map.find(msg) == msg_map.end()) {
252  msg_map[msg] = 1;
253  } else {
254  msg_map[msg]++;
255  }
256  }
257 
258  // Make sure the eof flag is cleared otherwise no new messages are shown
259  source.clear();
260 }
261 
262 /**
263  * Shows a summary of messages/errors generated so far by WML.
264  * Identical messages are shown once, with (between parentheses)
265  * the number of times that message was encountered.
266  * The order in which the messages are shown does not need
267  * to be the order in which these messages are encountered.
268  *
269  * @param source The source to be parsed before being displayed.
270  * @param caption The text to display before each message parsed from @a source.
271  */
272 void wml_event_pump::show_wml_messages(std::stringstream& source, const std::string& caption)
273 {
274  // Get all unique messages in messages,
275  // with the number of encounters for these messages
276  std::map<std::string, int> messages;
277  fill_wml_messages_map(messages, source);
278 
279  // Show the messages collected
280  for(std::map<std::string, int>::const_iterator itor = messages.begin(); itor != messages.end(); ++itor) {
281  std::stringstream msg;
282  msg << itor->first;
283  if(itor->second > 1) {
284  msg << " (" << itor->second << ")";
285  }
286 
288  std::time(nullptr), caption, 0, msg.str(), events::chat_handler::MESSAGE_PUBLIC, false);
289  }
290 }
291 
292 /**
293  * Shows a summary of the errors encountered in WML so far,
294  * to avoid a lot of the same messages to be shown.
295  * Identical messages are shown once, with (between parentheses)
296  * the number of times that message was encountered.
297  * The order in which the messages are shown does not need
298  * to be the order in which these messages are encountered.
299  * Messages are always written to std::cerr.
300  */
302 {
304 }
305 
306 /**
307  * Shows a summary of the messages generated so far by WML.
308  * Identical messages are shown once, with (between parentheses)
309  * the number of times that message was encountered.
310  * The order in which the messages are shown does not need
311  * to be the order in which these messages are encountered.
312  */
314 {
315  show_wml_messages(impl_->wml_messages_stream, "WML: ");
316 }
317 
319  lg::logger& logger, const std::string& prefix, const std::string& message, bool in_chat)
320 {
321  FORCE_LOG_TO(logger, log_wml) << message;
322  if(in_chat) {
323  impl_->wml_messages_stream << prefix << message << std::endl;
324  }
325 }
326 
327 context::scoped::scoped(std::stack<context::state>& contexts, bool m)
328  : contexts_(contexts)
329 {
330  // The default context at least should always be on the stack
331  assert(contexts_.size() > 0);
332 
333  bool skip_messages = (contexts_.size() > 1) && contexts_.top().skip_messages;
334  contexts_.emplace(skip_messages, m);
335 }
336 
338 {
339  assert(contexts_.size() > 1);
340  bool undo_disabled = contexts_.top().undo_disabled;
341  bool action_canceled = contexts_.top().action_canceled;
342 
343  contexts_.pop();
344  contexts_.top().undo_disabled |= undo_disabled;
345  contexts_.top().action_canceled |= action_canceled;
346 }
347 
349 {
350  assert(impl_->contexts_.size() > 0);
351  return impl_->contexts_.top().undo_disabled;
352 }
353 
355 {
356  assert(impl_->contexts_.size() > 0);
357  impl_->contexts_.top().undo_disabled = b;
358 }
359 
361 {
362  assert(impl_->contexts_.size() > 0);
363  return impl_->contexts_.top().action_canceled;
364 }
365 
367 {
368  assert(impl_->contexts_.size() > 0);
369  impl_->contexts_.top().action_canceled = true;
370 }
371 
372 
374 {
375  assert(impl_->contexts_.size() > 0);
376  return impl_->contexts_.top().skip_messages;
377 }
378 
380 {
381  assert(impl_->contexts_.size() > 0);
382  impl_->contexts_.top().skip_messages = b;
383 }
384 
385 /**
386  * Helper function which determines whether a wml_message text can
387  * really be pushed into the wml_messages_stream, and does it.
388  */
389 void wml_event_pump::put_wml_message(const std::string& logger, const std::string& message, bool in_chat)
390 {
391  if(logger == "err" || logger == "error") {
392  put_wml_message(lg::err(), _("Error: "), message, in_chat);
393  } else if(logger == "warn" || logger == "wrn" || logger == "warning") {
394  put_wml_message(lg::warn(), _("Warning: "), message, in_chat);
395  } else if((logger == "debug" || logger == "dbg") && !lg::debug().dont_log(log_wml)) {
396  put_wml_message(lg::debug(), _("Debug: "), message, in_chat);
397  } else if(!lg::info().dont_log(log_wml)) {
398  put_wml_message(lg::info(), _("Info: "), message, in_chat);
399  }
400 }
401 
403  const std::string& event, const entity_location& loc1, const entity_location& loc2, const config& data)
404 {
405  raise(event, loc1, loc2, data);
406  return (*this)();
407 }
408 
409 pump_result_t wml_event_pump::fire(const std::string& event,
410  const std::string& id,
411  const entity_location& loc1,
412  const entity_location& loc2,
413  const config& data)
414 {
415  raise(event, id, loc1, loc2, data);
416  return (*this)();
417 }
418 
419 void wml_event_pump::raise(const std::string& event,
420  const std::string& id,
421  const entity_location& loc1,
422  const entity_location& loc2,
423  const config& data)
424 {
425  if(game_display::get_singleton() == nullptr)
426  return;
427 
428  DBG_EH << "raising event name=" << event << ", id=" << id;
429 
430  impl_->events_queue.emplace_back(event, id, loc1, loc2, data);
431 }
432 
434 {
435  // Quick aborts:
436  if(game_display::get_singleton() == nullptr) {
437  return pump_result_t();
438  }
439 
440  assert(resources::lua_kernel != nullptr);
441  if(impl_->events_queue.empty()) {
442  DBG_EH << "Processing queued events, but none found.";
443  return pump_result_t();
444  }
445 
446  if(impl_->instance_count >= game_config::max_loop) {
447  ERR_NG << "game_events pump waiting to process new events because "
448  << "recursion level would exceed maximum: "
450  return pump_result_t();
451  }
452 
453  if(!lg::debug().dont_log(log_event_handler)) {
454  std::stringstream ss;
455  for(const queued_event& ev : impl_->events_queue) {
456  ss << "name=" << ev.name << ", "
457  << "id=" << ev.id << "; ";
458  }
459  DBG_EH << "processing queued events: " << ss.str();
460  }
461 
462  // Ensure the whiteboard doesn't attempt to build its future unit map
463  // while events are being processed.
464  wb::real_map real_unit_map;
465 
466  pump_manager pump_instance(*impl_);
467  context::scoped evc(impl_->contexts_, false);
468  // Loop through the events we need to process.
469  while(!pump_instance.done()) {
470  queued_event& ev = pump_instance.next();
471 
472  if(ev.name.empty() && ev.id.empty()) {
473  continue;
474  }
475 
476  const std::string& event_name = ev.name;
477  const std::string& event_id = ev.id;
478 
479  // Clear the unit cache, since the best clearing time is hard to figure out
480  // due to status changes by WML. Every event will flush the cache.
482 
483  { // Block for context::scoped
484  context::scoped inner_evc(impl_->contexts_, false);
486  }
487 
488  assert(impl_->my_manager);
489 
494 
495  if(event_id.empty()) {
496  // Handle events of this name.
497  impl_->my_manager->execute_on_events(event_name, [&](game_events::manager&, handler_ptr& ptr) {
498  DBG_EH << "processing event " << event_name << " with id=" << ptr->id();
499 
500  // Let this handler process our event.
501  process_event(ptr, ev);
502  });
503  } else {
504  // Get the handler directly via ID
505  handler_ptr cur_handler = impl_->my_manager->get_event_handler_by_id(event_id);
506 
507  if(cur_handler) {
508  DBG_EH << "processing event " << event_name << " with id=" << cur_handler->id();
509  process_event(cur_handler, ev);
510  }
511  }
512 
513  // Flush messages when finished iterating over event_handlers.
514  flush_messages();
515  }
516 
517  // Notify the whiteboard of any event.
518  // This is used to track when moves, recruits, etc. happen.
519  resources::whiteboard->on_gamestate_change();
520 
521  return std::tuple(undo_disabled(), action_canceled());
522 }
523 
525 {
526  // Dialogs can only be shown if the display is not fake
528  show_wml_errors();
529  show_wml_messages();
530  }
531 }
532 
534  : impl_(new pump_impl(man))
535 {
536 }
537 
539 {
540 }
541 
542 } // end namespace game_events
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:161
optional_config_impl< config > optional_child(config_key_type key, int n=0)
Euivalent to mandatory_child, but returns an empty optional if the nth child was not found.
Definition: config.cpp:389
void add_chat_message(const std::time_t &time, const std::string &speaker, int side, const std::string &msg, events::chat_handler::MESSAGE_TYPE type, bool bell)
virtual const unit_map & units() const override
Definition: game_board.hpp:113
map_location last_selected
the last location where a select event fired.
Definition: game_data.hpp:127
config::attribute_value & get_variable(const std::string &varname)
throws invalid_variablename_exception if varname is no valid variable name.
Definition: game_data.cpp:66
static game_display * get_singleton()
bool maybe_rebuild()
Rebuilds the screen if needs_rebuild(true) was previously called, and resets the flag.
display_chat_manager & get_chat_manager()
std::stack< context::state > & contexts_
Definition: pump.cpp:82
scoped(std::stack< context::state > &contexts, bool m=true)
Definition: pump.cpp:327
The game event manager loads the scenario configuration object, and ensures that events are handled a...
Definition: manager.hpp:47
void set_action_canceled()
Sets whether or not wml wants to abort the currently executed user action.
Definition: pump.cpp:366
void show_wml_errors()
Shows a summary of the errors encountered in WML so far, to avoid a lot of the same messages to be sh...
Definition: pump.cpp:301
bool undo_disabled()
Context: The general environment within which events are processed.
Definition: pump.cpp:348
bool context_skip_messages()
Returns whether or not we are skipping messages.
Definition: pump.cpp:373
void flush_messages()
Flushes WML messages and errors.
Definition: pump.cpp:524
const std::unique_ptr< pump_impl > impl_
Definition: pump.hpp:75
void show_wml_messages()
Shows a summary of the messages generated so far by WML.
Definition: pump.cpp:313
bool action_canceled()
Returns whether or not wml wants to abort the currently executed user action.
Definition: pump.cpp:360
pump_result_t fire(const std::string &event, const entity_location &loc1=entity_location::null_entity, const entity_location &loc2=entity_location::null_entity, const config &data=config())
Function to fire an event.
Definition: pump.cpp:402
void raise(const std::string &event, const std::string &id, const entity_location &loc1=entity_location::null_entity, const entity_location &loc2=entity_location::null_entity, const config &data=config())
Definition: pump.cpp:419
pump_result_t operator()()
Definition: pump.cpp:433
void set_undo_disabled(bool mutated)
[allow_undo] implementation
Definition: pump.cpp:354
void process_event(handler_ptr &handler_p, const queued_event &ev)
Processes an event through a single event handler.
Definition: pump.cpp:198
wml_event_pump(manager &)
Definition: pump.cpp:533
void fill_wml_messages_map(std::map< std::string, int > &msg_map, std::stringstream &source)
Helper function for show_wml_messages(), which gathers the messages from a stringstream.
Definition: pump.cpp:237
void put_wml_message(const std::string &logger, const std::string &message, bool in_chat)
Helper function which determines whether a wml_message text can really be pushed into the wml_message...
Definition: pump.cpp:389
bool run_event(const game_events::queued_event &)
Executes the game_events.on_event function.
Container associating units to locations.
Definition: map.hpp:99
static void clear_status_caches()
Clear this unit status cache for all units.
Definition: unit.cpp:679
static std::string _(const char *str)
Definition: gettext.hpp:93
std::string id
Text to match against addon_info.tags()
Definition: manager.cpp:215
Define the handlers for the game's events mechanism.
Standard logging facilities (interface).
#define FORCE_LOG_TO(logger, domain)
Definition: log.hpp:257
const std::size_t max_loop
The maximum number of hexes on a map and items in an array and also used as maximum in wml loops.
Definition: game_config.cpp:70
Domain specific events.
std::tuple< bool, bool > pump_result_t
Definition: fwd.hpp:29
std::shared_ptr< event_handler > handler_ptr
Definition: fwd.hpp:25
logger & err()
Definition: log.cpp:226
logger & debug()
Definition: log.cpp:244
logger & warn()
Definition: log.cpp:232
std::stringstream & log_to_chat()
Use this to show WML errors in the ingame chat.
Definition: log.cpp:468
logger & info()
Definition: log.cpp:238
game_board * gameboard
Definition: resources.cpp:21
game_data * gamedata
Definition: resources.cpp:23
game_lua_kernel * lua_kernel
Definition: resources.cpp:26
std::shared_ptr< wb::manager > whiteboard
Definition: resources.cpp:34
bool headless()
The game is running headless.
Definition: video.cpp:142
static void msg(const char *act, debug_info &i, const char *to="", const char *result="")
Definition: debugger.cpp:110
std::string_view data
Definition: picture.cpp:199
int x2_
Definition: pump.cpp:135
std::vector< queued_event > queue_
Tracks the events to process.
Definition: pump.cpp:141
int y1_
Definition: pump.cpp:135
static lg::log_domain log_engine("engine")
#define ERR_NG
Definition: pump.cpp:44
pump_impl & impl_
Definition: pump.cpp:134
int x1_
Definition: pump.cpp:135
static lg::log_domain log_event_handler("event_handler")
#define DBG_EH
Definition: pump.cpp:53
int y2_
Definition: pump.cpp:135
static lg::log_domain log_wml("wml")
std::size_t pumped_count_
Tracks how many events have been processed.
Definition: pump.cpp:144
Define the game's event mechanism.
State when processing a particular flight of events or commands.
Definition: pump.cpp:62
state(bool s, bool m=true)
Definition: pump.cpp:67
const map_location & filter_loc() const
std::stack< context::state > contexts_
Definition: pump.cpp:92
manager * my_manager
Definition: pump.cpp:96
pump_impl(manager &man)
Definition: pump.cpp:98
unsigned instance_count
Definition: pump.cpp:94
std::stringstream wml_messages_stream
Definition: pump.cpp:90
std::vector< queued_event > events_queue
Definition: pump.cpp:88
entity_location loc1
Definition: pump.hpp:65
entity_location loc2
Definition: pump.hpp:66
std::string name
Definition: pump.hpp:63
int wml_y() const
Definition: location.hpp:154
int wml_x() const
Definition: location.hpp:153
Ensures that the real unit map is active for the duration of the struct's life.
Definition: manager.hpp:284
static map_location::DIRECTION s
#define b