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