The Battle for Wesnoth 1.21.0-dev
 
Loading...
Searching...
No Matches
pump.cpp
Go to the documentation of this file.
1/*
2 Copyright (C) 2003 - 2025
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"
24
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
36
37
38static 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
43static 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
49static 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.
53namespace game_events
54{
55namespace context
56{
57/** State when processing a particular flight of events or commands. */
58struct state
59{
63
64 explicit state(bool s, bool m = true)
65 : undo_disabled(m)
66 , action_canceled(false)
68 {
69 }
70};
71
72class scoped
73{
74public:
75 scoped(std::stack<context::state>& contexts, bool m = true);
76 ~scoped();
77
78private:
79 std::stack<context::state>& contexts_;
80};
81}
82
84{
85 std::vector<queued_event> events_queue;
86
87 std::stringstream wml_messages_stream;
88
89 std::stack<context::state> contexts_;
90
92
94
96 : events_queue()
98 , contexts_()
100 , my_manager(&man)
101 {
102 contexts_.emplace(false);
103 }
104};
105
106namespace
107{ // Types
108class pump_manager
109{
110public:
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
130private:
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
145namespace
146{ // Support functions
147
148pump_manager::pump_manager(pump_impl& impl)
149 : impl_(impl)
150 , x1_(resources::gamedata->get_variable("x1").to_int())
151 , x2_(resources::gamedata->get_variable("x2").to_int())
152 , y1_(resources::gamedata->get_variable("y1").to_int())
153 , y2_(resources::gamedata->get_variable("y2").to_int())
154 , queue_()
155 , pumped_count_(0) // Filled later with a swap().
156{
159}
160
161pump_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
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 */
234void 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 */
269void 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::chrono::system_clock::now(), 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 */
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
324context::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 */
386void 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
406pump_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
416void 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
534
538
539} // end namespace game_events
#define ERR_NG
A config object defines a single node in a WML file, with access to child nodes.
Definition config.hpp:157
optional_config_impl< config > optional_child(std::string_view key, int n=0)
Equivalent to mandatory_child, but returns an empty optional if the nth child was not found.
Definition config.cpp:380
void add_chat_message(const std::chrono::system_clock::time_point &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
map_location last_selected
the last location where a select event fired.
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()
display_chat_manager & get_chat_manager()
bool maybe_rebuild()
Rebuilds the screen if needs_rebuild(true) was previously called, and resets the flag.
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
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:790
static std::string _(const char *str)
Definition gettext.hpp:100
std::string id
Text to match against addon_info.tags()
Definition manager.cpp:199
static lg::log_domain log_event_handler("event_handler")
#define DBG_EH
Definition handlers.cpp:48
Define the handlers for the game's events mechanism.
Standard logging facilities (interface).
#define FORCE_LOG_TO(logger, domain)
Definition log.hpp:291
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.
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:339
logger & debug()
Definition log.cpp:357
logger & warn()
Definition log.cpp:345
std::stringstream & log_to_chat()
Use this to show WML errors in the ingame chat.
Definition log.cpp:550
logger & info()
Definition log.cpp:351
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:146
std::string_view data
Definition picture.cpp:188
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")
pump_impl & impl_
Definition pump.cpp:131
int x1_
Definition pump.cpp:132
static lg::log_domain log_event_handler("event_handler")
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.
static lg::log_domain log_wml("wml")
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
int wml_y() const
Definition location.hpp:187
int wml_x() const
Definition location.hpp:186
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