The Battle for Wesnoth  1.13.4+dev
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Modules Pages
synced_context.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2014 - 2016 by David White <[email protected]>
3  Part of the Battle for Wesnoth Project http://www.wesnoth.org/
4 
5  This program is free software; you can redistribute it and/or modify
6  it under the terms of the GNU General Public License as published by
7  the Free Software Foundation; either version 2 of the License, or
8  (at your option) any later version.
9  This program is distributed in the hope that it will be useful,
10  but WITHOUT ANY WARRANTY.
11 
12  See the COPYING file for more details.
13 */
14 #include "synced_context.hpp"
15 #include "synced_commands.hpp"
16 
17 #include "actions/undo.hpp"
18 #include "ai/manager.hpp"
19 #include "global.hpp"
20 #include "config.hpp"
21 #include "config_assign.hpp"
22 #include "game_classification.hpp"
23 #include "replay.hpp"
24 #include "random_new.hpp"
25 #include "random_new_synced.hpp"
27 #include "resources.hpp"
28 #include "synced_checkup.hpp"
29 #include "game_data.hpp"
30 #include "game_board.hpp"
31 #include "log.hpp"
33 #include "play_controller.hpp"
34 #include "actions/undo.hpp"
35 #include "game_end_exceptions.hpp"
36 #include "seed_rng.hpp"
37 #include "syncmp_handler.hpp"
38 #include "units/id.hpp"
39 #include "whiteboard/manager.hpp"
40 #include <boost/lexical_cast.hpp>
41 
42 #include <cassert>
43 #include <stdlib.h>
44 #include <sstream>
45 #include <iomanip>
46 static lg::log_domain log_replay("replay");
47 #define DBG_REPLAY LOG_STREAM(debug, log_replay)
48 #define LOG_REPLAY LOG_STREAM(info, log_replay)
49 #define WRN_REPLAY LOG_STREAM(warn, log_replay)
50 #define ERR_REPLAY LOG_STREAM(err, log_replay)
51 
52 
58 
59 bool synced_context::run(const std::string& commandname, const config& data, bool use_undo, bool show, synced_command::error_handler_function error_handler)
60 {
61  DBG_REPLAY << "run_in_synced_context:" << commandname << "\n";
62 
63  assert(use_undo || (!resources::undo_stack->can_redo() && !resources::undo_stack->can_undo()));
64  /*
65  use this after resources::recorder->add_synced_command
66  because set_scontext_synced sets the checkup to the last added command
67  */
70  if(it == synced_command::registry().end())
71  {
72  error_handler("commandname [" +commandname +"] not found", true);
73  }
74  else
75  {
76  bool success = it->second(data, use_undo, show, error_handler);
77  if(!success)
78  {
79  return false;
80  }
81  }
82 
83  // this might also be a good point to call resources::controller->check_victory();
84  // because before for example if someone kills all units during a moveto event they don't loose.
86  sync.do_final_checkup();
87  DBG_REPLAY << "run_in_synced_context end\n";
88  return true;
89 }
90 
91 bool synced_context::run_and_store(const std::string& commandname, const config& data, bool use_undo, bool show, synced_command::error_handler_function error_handler)
92 {
93  if(resources::controller->is_replay())
94  {
95  ERR_REPLAY << "ignored attempt to invoke a synced command during replay\n";
96  return false;
97  }
98 
99  assert(resources::recorder->at_end());
100  resources::recorder->add_synced_command(commandname, data);
101  bool success = run(commandname, data, use_undo, show, error_handler);
102  if(!success)
103  {
105  }
106  return success;
107 }
108 
109 bool synced_context::run_and_throw(const std::string& commandname, const config& data, bool use_undo, bool show, synced_command::error_handler_function error_handler)
110 {
111  bool success = run_and_store(commandname, data, use_undo, show, error_handler);
112  if(success)
113  {
115  }
116  return success;
117 }
118 
120 {
122  {
124  {
125  return run_and_throw(commandname, data, use_undo, show, error_handler);
126  }
128  ERR_REPLAY << "trying to execute action while being in a local_choice" << std::endl;
129  //we reject it because such actions usually change the gamestate badly which is not intented during a local_choice.
130  //Also we cannot invoke synced commands here, becasue multiple clients might run local choices
131  //simultaniously so it could result in invoking different synced commands simultaniously.
132  return false;
134  {
136  if(it == synced_command::registry().end())
137  {
138  error_handler("commandname [" +commandname +"] not found", true);
139  return false;
140  }
141  else
142  {
143  return it->second(data, /*use_undo*/ false, show, error_handler);
144  }
145  }
146  default:
147  assert(false && "found unknown synced_context::synced_state");
148  return false;
149  }
150 }
151 
153 {
154  ERR_REPLAY << "Unexpected Error during synced execution" << message << std::endl;
155  assert(!"Unexpected Error during synced execution, more info in stderr.");
156 }
157 
159 {
160  ERR_REPLAY << "Error during synced execution: " << message;
161 }
162 
164 {
165  DBG_REPLAY << "Ignored during synced execution: " << message;
166 }
167 
169 {
170  return state_;
171 }
172 
174 {
175  return get_synced_state() == SYNCED;
176 }
177 
179 {
180  state_ = newstate;
181 }
182 
183 namespace
184 {
185  class random_server_choice : public synced_context::server_choice
186  {
187  public:
188  /// We are in a game with no mp server and need to do this choice locally
189  virtual config local_choice() const
190  {
191  return config_of("new_seed", seed_rng::next_seed_str());
192  }
193  /// the request which is sended to the mp server.
194  virtual config request() const
195  {
196  return config();
197  }
198  virtual const char* name() const
199  {
200  return "random_seed";
201  }
202  };
203 }
205 {
206  config retv_c = synced_context::ask_server_choice(random_server_choice());
207  config::attribute_value seed_val = retv_c["new_seed"];
208 
209  return seed_val.str();
210 }
211 
213 {
214  return is_simultaneously_;
215 }
216 
218 {
219  is_simultaneously_ = false;
220 }
221 
223 {
225  is_simultaneously_ = true;
226 }
227 
229 {
230  //this method should only works in a synced context.
231  assert(is_synced());
232  //if we called the rng or if we sended data of this action over the network already, undoing is impossible.
234 }
235 
237 {
238  last_unit_id_ = id;
239 }
240 
242 {
243  //this method only works in a synced context.
244  assert(is_synced());
246 }
247 
249 {
251 }
252 
254 {
255  assert(is_simultaneously_);
257 }
258 
260 {
262  if(mode == "deterministic")
263  {
265  }
266  else
267  {
269  }
270 }
271 
273 {
275  ("request_choice", config_of
276  ("request_id", resources::controller->get_server_request_number())
277  (name(), request())
278  )
279  );
280 }
281 
282 
283 
285 {
288  assert(is_synced());
289  const bool is_mp_game = resources::controller->is_networked_mp();
290  bool did_require = false;
291 
292  DBG_REPLAY << "ask_server for random_seed\n";
293  /*
294  as soon as random or similar is involved, undoing is impossible.
295  */
297  /*
298  there might be speak or similar commands in the replay before the user input.
299  */
300  while(true) {
301 
303  bool is_replay_end = resources::recorder->at_end();
304 
305  if (is_replay_end && !is_mp_game)
306  {
307  /* The decision is ours, and it will be inserted
308  into the replay. */
309  DBG_REPLAY << "MP synchronization: local server choice\n";
311  config cfg = sch.local_choice();
312  //-1 for "server" todo: change that.
313  resources::recorder->user_input(sch.name(), cfg, -1);
314  return cfg;
315 
316  }
317  else if(is_replay_end && is_mp_game)
318  {
319  DBG_REPLAY << "MP synchronization: remote server choice\n";
320 
321  //here we can get into the situation that the decision has already been made but not received yet.
323  //FIXME: we shoudl call play_controller::play_silce or the application will freeze while waiting for a remote choice.
324 
325  /*
326  we don't want to send multiple "require_random" to the server.
327  */
328  if(!did_require)
329  {
330  sch.send_request();
331  did_require = true;
332  }
333 
334  SDL_Delay(10);
335  continue;
336 
337  }
338  else if (!is_replay_end)
339  {
340  /* The decision has already been made, and must
341  be extracted from the replay. */
342  DBG_REPLAY << "MP synchronization: replay server choice\n";
344  const config *action = resources::recorder->get_next_action();
345  if (!action)
346  {
347  replay::process_error("[" + std::string(sch.name()) + "] expected but none found\n");
349  return sch.local_choice();
350  }
351  if (!action->has_child(sch.name()))
352  {
353  replay::process_error("[" + std::string(sch.name()) + "] expected but none found, found instead:\n " + action->debug() + "\n");
354 
356  return sch.local_choice();
357  }
358  if((*action)["from_side"].str() != "server" || (*action)["side_invalid"].to_bool(false) )
359  {
360  //we can proceed without getting OOS in this case, but allowing this would allow a "player chan choose their attack results in mp" cheat
361  replay::process_error("wrong from_side or side_invalid this could mean someone wants to cheat\n");
362  }
363  return action->child(sch.name());
364  }
365  }
366 }
367 
369 {
370  undo_commands_.emplace_front(commands, ctx);
371 }
372 
374 {
375  redo_commands_.emplace_front(commands, ctx);
376 }
377 
379 {
380  undo_commands_.clear();
381 }
382 
384 {
385  redo_commands_.clear();
386 }
387 
389  : new_rng_(synced_context::get_rng_for_action())
390  , old_rng_(random_new::generator)
391 {
392  LOG_REPLAY << "set_scontext_synced_base::set_scontext_synced_base\n";
393  assert(!resources::whiteboard->has_planned_unit_map());
397  synced_context::set_last_unit_id(resources::gameboard->unit_id_manager().get_save_id());
402 }
404 {
405  LOG_REPLAY << "set_scontext_synced_base:: destructor\n";
409 }
410 
413  , new_checkup_(generate_checkup("checkup")), disabler_()
414 {
415  init();
416 }
417 
420  , new_checkup_(generate_checkup("checkup" + std::to_string(number))), disabler_()
421 {
422  init();
423 }
424 
426 {
427  if(resources::classification->oos_debug)
428  {
429  return new mp_debug_checkup();
430  }
431  else
432  {
433  return new synced_checkup(resources::recorder->get_last_real_command().child_or_add(tagname));
434  }
435 }
436 
437 /*
438  so we dont have to write the same code 3 times.
439 */
441 {
442  LOG_REPLAY << "set_scontext_synced::set_scontext_synced\n";
443  did_final_checkup_ = false;
446 }
447 
449 {
450  assert(!did_final_checkup_);
451  std::stringstream msg;
452  config co;
453  config cn = config_of
454  ("random_calls", new_rng_->get_random_calls())
455  ("next_unit_id", resources::gameboard->unit_id_manager().get_save_id() + 1);
456  if(checkup_instance->local_checkup(cn, co))
457  {
458  return;
459  }
460  if(co["random_calls"].empty())
461  {
462  msg << "cannot find random_calls check in replay" << std::endl;
463  }
464  else if(co["random_calls"] != cn["random_calls"])
465  {
466  msg << "We called random " << new_rng_->get_random_calls() << " times, but the original game called random " << co["random_calls"].to_int() << " times." << std::endl;
467  }
468  //Ignore empty next_unit_id to prevent false positives with older saves.
469  if(!co["next_unit_id"].empty() && co["next_unit_id"] != cn["next_unit_id"])
470  {
471  msg << "Our next unit id is " << cn["next_unit_id"].to_int() << " but during the original the next unit id was " << co["next_unit_id"].to_int() << std::endl;
472  }
473  if(!msg.str().empty())
474  {
475  msg << co.debug() << std::endl;
476  if(dont_throw)
477  {
478  ERR_REPLAY << msg.str() << std::flush;
479  }
480  else
481  {
482  replay::process_error(msg.str());
483  }
484  }
485  did_final_checkup_ = true;
486 }
487 
489 {
490  LOG_REPLAY << "set_scontext_synced:: destructor\n";
491  assert(checkup_instance == &*new_checkup_);
492  if(!did_final_checkup_)
493  {
494  //do_final_checkup(true);
495  }
497 }
498 
500 {
501  return new_rng_->get_random_calls();
502 }
503 
504 
506  : old_rng_(random_new::generator)
507 {
510 
511  //calling the synced rng form inside a local_choice would cause oos.
512  //TODO: should we also reset the synced checkup?
514 }
515 
517 {
521 }
522 
524  : leaver_(synced_context::is_synced() ? new leave_synced_context() : nullptr)
525 {
526 
527 }
boost::scoped_ptr< checkup > new_checkup_
static lg::log_domain log_replay("replay")
void clear()
Clears the stack of undoable (and redoable) actions.
Definition: undo.cpp:226
play_controller * controller
Definition: resources.cpp:21
static void add_undo_commands(const config &commands, const game_events::queued_event &ctx)
std::string str() const
Definition: config.cpp:353
virtual void send_to_wesnothd(const config &, const std::string &="unknown") const
static bool run_and_store(const std::string &commandname, const config &data, bool use_undo=true, bool show=true, synced_command::error_handler_function error_handler=default_error_function)
game_classification * classification
Definition: resources.cpp:37
void do_final_checkup(bool dont_throw=false)
size_t get_save_id() const
Used for saving id to savegame.
Definition: id.cpp:44
static bool run(const std::string &commandname, const config &data, bool use_undo=true, bool show=true, synced_command::error_handler_function error_handler=default_error_function)
Sets the context to 'synced', initialises random context, and calls the given function.
rng * generator
This generator is automatically synced during synced context.
Definition: random_new.cpp:52
static boost::shared_ptr< random_new::rng > get_rng_for_action()
unsigned int get_random_calls()
Provides the number of random calls to the rng in this context.
Definition: random_new.cpp:71
static map & registry()
using static function variable instead of static member variable to prevent static initialization fia...
static rng & default_instance()
Definition: random_new.cpp:65
static std::string generate_random_seed()
virtual config local_choice() const =0
We are in a game with no mp server and need to do this choice locally.
static void add_redo_commands(const config &commands, const game_events::queued_event &ctx)
static void pull_remote_choice()
STL namespace.
Replay control code.
void check_victory()
Checks to see if a side has won.
Contains the exception interfaces used to signal completion of a scenario, campaign or turn...
const std::string number
template to number regex
std::string debug() const
Definition: config.cpp:1438
GLint GLenum GLsizei GLint GLsizei const GLvoid * data
Definition: glew.h:1347
GLenum mode
Definition: glew.h:2390
Definitions for the interface to Wesnoth Markup Language (WML).
void add_synced_command(const std::string &name, const config &command)
Definition: replay.cpp:241
static synced_state get_synced_state()
static event_list undo_commands_
Actions wml to be executed when the current actio is undone.
Variant for storing WML attributes.
Definition: config.hpp:223
game_data * gamedata
Definition: resources.cpp:22
static void reset_is_simultaneously()
static void set_last_unit_id(int id)
random_new::rng * old_rng_
bool has_child(const std::string &key) const
Determine whether a config has a child or not.
Definition: config.cpp:651
boost::shared_ptr< random_new::rng > new_rng_
GLuint GLuint end
Definition: glew.h:1221
GLuint id
Definition: glew.h:1647
static void just_log_error_function(const std::string &message, bool heavy)
a function to be passed to run_in_synced_context to log the error.
static synced_state state_
virtual const char * name() const =0
static void ignore_error_function(const std::string &message, bool heavy)
a function to be passed to run_in_synced_context to ignore the error.
game_board * gameboard
Definition: resources.cpp:20
std::string next_seed_str()
Definition: seed_rng.cpp:61
#define DBG_REPLAY
checkup * checkup_instance
Managing the AIs lifecycle - headers.
static bool can_undo()
std::deque< std::pair< config, game_events::queued_event > > event_list
replay * recorder
Definition: resources.cpp:30
This checkup compares whether the results calculated during the original game match the ones calculat...
static void send_user_choice()
called from get_user_choice to send a recently made choice to the other clients.
static void pull_remote_user_input()
called from get_user_choice while waiting for a remove user choice.
static int last_unit_id_
Used to restore the unit id manager when undoing.
static bool is_simultaneously()
void show(CVideo &video, const std::string &window_id, const t_string &message, const tpoint &mouse)
Shows a tip.
Definition: tip.cpp:133
static event_list redo_commands_
Actions wml to be executed when the current actio is redone.
static void process_error(const std::string &msg)
Definition: replay.cpp:198
void maybe_throw_return_to_play_side()
static checkup * generate_checkup(const std::string &tagname)
A class to check whether the results that were calculated in the replay match the results calculated ...
random_new::rng * old_rng_
This rng is used when the normal synced rng is not available this is currently only he case at the ve...
static int get_unit_id_diff()
static std::string flush(std::ostringstream &s)
Definition: reports.cpp:86
#define LOG_REPLAY
static void msg(const char *act, debug_info &i, const char *to="", const char *result="")
Definition: debugger.cpp:112
static void send_user_choice()
static void reset_undo_commands()
static bool run_and_throw(const std::string &commandname, const config &data, bool use_undo=true, bool show=true, synced_command::error_handler_function error_handler=default_error_function)
n_unit::id_manager & unit_id_manager()
Definition: game_board.hpp:91
static bool run_in_synced_context_if_not_already(const std::string &commandname, const config &data, bool use_undo=true, bool show=true, synced_command::error_handler_function error_handler=default_error_function)
checks whether we are currently running in a synced context, and if not we enters it...
bool at_end() const
Definition: replay.cpp:611
config & child(const std::string &key, int n=0)
Returns the nth child with the given key, or a reference to an invalid config if there is none...
Definition: config.cpp:658
Various functions that implement the undoing (and redoing) of in-game commands.
Standard logging facilities (interface).
config * get_next_action()
Definition: replay.cpp:598
static void set_synced_state(synced_state newstate)
GLsizei GLenum GLuint GLuint GLsizei char * message
Definition: glew.h:2499
void revert_action()
Definition: replay.cpp:591
static void default_error_function(const std::string &message, bool heavy)
a function to be passed to run_in_synced_context to assert false on error (the default).
boost::shared_ptr< wb::manager > whiteboard
Definition: resources.cpp:36
std::function< void(const std::string &, bool)> error_handler_function
actions::undo_list * undo_stack
Definition: resources.cpp:34
virtual bool is_networked_mp() const
virtual bool local_checkup(const config &expected_data, config &real_data)=0
Compares data to the results calculated during the original game.
void undo()
Definition: replay.cpp:550
void user_input(const std::string &, const config &, int from_side)
adds a user_input to the replay
Definition: replay.cpp:251
static void set_is_simultaneously()
std::string::const_iterator iterator
Definition: tokenizer.hpp:21
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:83
REPLAY_RETURN do_replay_handle(bool one_move)
Definition: replay.cpp:677
static void reset_redo_commands()
virtual config request() const =0
the request which is sended to the mp server.
static bool is_synced()
GLsizei const GLcharARB ** string
Definition: glew.h:4503
This checkup always compares the results in from different clients in a mp game but it also causes mo...
#define ERR_REPLAY
static config ask_server_choice(const server_choice &)
void increase_server_request_number()
static bool is_simultaneously_