The Battle for Wesnoth  1.17.10+dev
lobby_info.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2009 - 2022
3  by Tomasz Sniatowski <kailoran@gmail.com>
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 "addon/manager.hpp" // for installed_addons
19 #include "gettext.hpp"
20 #include "log.hpp"
21 #include "map/exception.hpp"
22 #include "map/map.hpp"
23 #include "mp_ui_alerts.hpp"
24 #include "preferences/game.hpp"
25 #include "wesnothd_connection.hpp"
26 
27 #include <iterator>
28 
29 static lg::log_domain log_engine("engine");
30 #define WRN_NG LOG_STREAM(warn, log_engine)
31 
32 static lg::log_domain log_lobby("lobby");
33 #define DBG_LB LOG_STREAM(debug, log_lobby)
34 #define WRN_LB LOG_STREAM(warn, log_lobby)
35 #define ERR_LB LOG_STREAM(err, log_lobby)
36 #define SCOPE_LB log_scope2(log_lobby, __func__)
37 
38 namespace mp
39 {
41  : installed_addons_()
42  , gamelist_()
43  , gamelist_initialized_(false)
44  , games_by_id_()
45  , games_()
46  , users_()
47  , game_filters_()
48  , game_filter_invert_()
49  , games_visibility_()
50 {
52 }
53 
55 {
56  // This function does not refer to an addon database, it calls filesystem functions.
57  // For the sanity of the mp lobby, this list should be fixed for the entire lobby session,
58  // even if the user changes the contents of the addon directory in the meantime.
60 }
61 
62 void do_notify(notify_mode mode, const std::string& sender, const std::string& message)
63 {
64  switch(mode) {
68  mp::ui_alerts::private_message(true, sender, message);
69  break;
71  mp::ui_alerts::friend_message(true, sender, message);
72  break;
74  mp::ui_alerts::server_message(true, sender, message);
75  break;
78  break;
81  break;
83  mp::ui_alerts::public_message(true, sender, message);
84  break;
86  mp::ui_alerts::game_created(sender, message);
87  break;
88  default:
89  break;
90  }
91 }
92 
93 namespace
94 {
95 std::string dump_games_map(const lobby_info::game_info_map& games)
96 {
97  std::stringstream ss;
98  for(const auto& v : games) {
99  const game_info& game = v.second;
100  ss << "G" << game.id << "(" << game.name << ") " << game.display_status_string() << " ";
101  }
102 
103  ss << "\n";
104  return ss.str();
105 }
106 
107 std::string dump_games_config(const config& gamelist)
108 {
109  std::stringstream ss;
110  for(const auto& c : gamelist.child_range("game")) {
111  ss << "g" << c["id"] << "(" << c["name"] << ") " << c[config::diff_track_attribute] << " ";
112  }
113 
114  ss << "\n";
115  return ss.str();
116 }
117 
118 } // end anonymous namespace
119 
121 {
122  SCOPE_LB;
123 
124  gamelist_ = data;
125  gamelist_initialized_ = true;
126 
127  games_by_id_.clear();
128 
129  for(const auto& c : gamelist_.child("gamelist").child_range("game")) {
131  games_by_id_.emplace(game.id, std::move(game));
132  }
133 
134  DBG_LB << dump_games_map(games_by_id_);
135  DBG_LB << dump_games_config(gamelist_.child("gamelist"));
136 
138 }
139 
141 {
142  if(!process_gamelist_diff_impl(data)) {
143  // the gamelist is now corrupted, stop further processing and wait for a fresh list.
144  gamelist_initialized_ = false;
145  return false;
146  } else {
147  return true;
148  }
149 }
150 
152 {
153  SCOPE_LB;
154  if(!gamelist_initialized_) {
155  return false;
156  }
157 
158  DBG_LB << "prediff " << dump_games_config(gamelist_.child("gamelist"));
159 
160  try {
161  gamelist_.apply_diff(data, true);
162  } catch(const config::error& e) {
163  ERR_LB << "Error while applying the gamelist diff: '" << e.message << "' Getting a new gamelist.";
164  return false;
165  }
166 
167  DBG_LB << "postdiff " << dump_games_config(gamelist_.child("gamelist"));
168  DBG_LB << dump_games_map(games_by_id_);
169 
170  for(config& c : gamelist_.child("gamelist").child_range("game")) {
171  DBG_LB << "data process: " << c["id"] << " (" << c[config::diff_track_attribute] << ")";
172 
173  const int game_id = c["id"];
174  if(game_id == 0) {
175  ERR_LB << "game with id 0 in gamelist config";
176  return false;
177  }
178 
179  auto current_i = games_by_id_.find(game_id);
180 
181  const std::string& diff_result = c[config::diff_track_attribute];
182 
183  if(diff_result == "new" || diff_result == "modified") {
184  // note: at this point (1.14.3) the server never sends a 'modified' and instead
185  // just sends a 'delete' followed by a 'new', it still works becasue the delete doesn't
186  // delete the element and just marks it as game_info::DELETED so that game_info::DELETED
187  // is replaced by game_info::UPDATED below. See also
188  // https://github.com/wesnoth/wesnoth/blob/1.14/src/server/server.cpp#L149
189  if(current_i == games_by_id_.end()) {
190  games_by_id_.emplace(game_id, game_info(c, installed_addons_));
191  continue;
192  }
193 
194  // Had a game with that id, so update it and mark it as such
195  current_i->second = game_info(c, installed_addons_);
196  current_i->second.display_status = game_info::disp_status::UPDATED;
197  } else if(diff_result == "deleted") {
198  if(current_i == games_by_id_.end()) {
199  WRN_LB << "Would have to delete a game that I don't have: " << game_id;
200  continue;
201  }
202 
203  if(current_i->second.display_status == game_info::disp_status::NEW) {
204  // This means the game never made it through to the user interface,
205  // so just deleting it is fine.
206  games_by_id_.erase(current_i);
207  } else {
208  current_i->second.display_status = game_info::disp_status::DELETED;
209  }
210  }
211  }
212 
213  DBG_LB << dump_games_map(games_by_id_);
214 
215  try {
217  } catch(const config::error& e) {
218  ERR_LB << "Error while applying the gamelist diff (2): '" << e.message << "' Getting a new gamelist.";
219  return false;
220  }
221 
222  DBG_LB << "postclean " << dump_games_config(gamelist_.child("gamelist"));
223 
225  return true;
226 }
227 
229 {
230  SCOPE_LB;
231 
232  users_.clear();
233  for(const auto& c : gamelist_.child_range("user")) {
234  user_info& ui = users_.emplace_back(c);
235 
236  if(ui.game_id == 0) {
237  continue;
238  }
239 
241  if(!g) {
242  WRN_NG << "User " << ui.name << " has unknown game_id: " << ui.game_id;
243  continue;
244  }
245 
246  switch(ui.get_relation()) {
248  g->has_friends = true;
249  break;
251  g->has_ignored = true;
252  break;
253  default:
254  break;
255  }
256  }
257 
258  std::stable_sort(users_.begin(), users_.end());
259 }
260 
261 std::function<void()> lobby_info::begin_state_sync()
262 {
263  // First, update the list of game pointers to reflect any changes made to games_by_id_.
264  // This guarantees anything that calls games() before post cleanup has valid pointers,
265  // since there will likely have been changes to games_by_id_ caused by network traffic.
267 
268  return [this]() {
269  DBG_LB << "lobby_info, second state sync stage";
270  DBG_LB << "games_by_id_ size: " << games_by_id_.size();
271 
272  auto i = games_by_id_.begin();
273 
274  while(i != games_by_id_.end()) {
275  if(i->second.display_status == game_info::disp_status::DELETED) {
276  i = games_by_id_.erase(i);
277  } else {
278  i->second.display_status = game_info::disp_status::CLEAN;
279  ++i;
280  }
281  }
282 
283  DBG_LB << " -> " << games_by_id_.size();
284 
286 
287  // Now that both containers are again in sync, update the visibility mask. We want to do
288  // this last since the filer functions are expensive.
290  };
291 }
292 
294 {
295  auto i = games_by_id_.find(id);
296  return i == games_by_id_.end() ? nullptr : &i->second;
297 }
298 
300 {
301  auto i = games_by_id_.find(id);
302  return i == games_by_id_.end() ? nullptr : &i->second;
303 }
304 
305 user_info* lobby_info::get_user(const std::string& name)
306 {
307  for(auto& user : users_) {
308  if(user.name == name) {
309  return &user;
310  }
311  }
312 
313  return nullptr;
314 }
315 
317 {
318  games_.reserve(games_by_id_.size());
319  games_.clear();
320 
321  for(auto& v : games_by_id_) {
322  games_.push_back(&v.second);
323  }
324 
325  // Reset the visibility mask. Its size should then match games_'s and all its bits be true.
326  games_visibility_.resize(games_.size());
327  games_visibility_.reset();
328  games_visibility_.flip();
329 }
330 
332 {
333  for(const auto& filter_func : game_filters_) {
334  if(!game_filter_invert_(filter_func(game))) {
335  return false;
336  }
337  }
338 
339  return true;
340 }
341 
343 {
344  // Since games_visibility_ is a visibility mask over games_,
345  // they need to be the same size or we'll end up with issues.
346  assert(games_visibility_.size() == games_.size());
347 
348  for(unsigned i = 0; i < games_.size(); ++i) {
350  }
351 }
352 
353 } // end namespace mp
#define SCOPE_LB
Definition: lobby_info.cpp:36
void public_message(bool is_lobby, const std::string &sender, const std::string &message)
config & child(config_key_type 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:402
const config & gamelist() const
Returns the raw game list config data.
Definition: lobby_info.hpp:58
user_info * get_user(const std::string &name)
Returns info on the user with the given name, or nullptr if they don&#39;t eixst.
Definition: lobby_info.cpp:305
bool is_game_visible(const game_info &)
Returns whether the game would be visible after the game filters are applied.
Definition: lobby_info.cpp:331
static lg::log_domain log_engine("engine")
This class represents the info a client has about a game on the server.
Definition: lobby_data.hpp:65
#define DBG_LB
Definition: lobby_info.cpp:33
void private_message(bool is_lobby, const std::string &sender, const std::string &message)
void player_leaves(bool is_lobby)
child_itors child_range(config_key_type key)
Definition: config.cpp:344
std::map< int, game_info > game_info_map
Definition: lobby_info.hpp:36
std::string_view data
Definition: picture.cpp:206
void clear_diff_track(const config &diff)
Clear any tracking info from a previous apply_diff call with tracking.
Definition: config.cpp:1188
std::function< bool(bool)> game_filter_invert_
Definition: lobby_info.hpp:150
std::vector< game_filter_func > game_filters_
Definition: lobby_info.hpp:148
void friend_message(bool is_lobby, const std::string &sender, const std::string &message)
std::string name
Definition: lobby_data.hpp:74
void process_gamelist(const config &data)
Process a full game list.
Definition: lobby_info.cpp:120
void apply_game_filter()
Generates a new list of games that match the current filter functions and inversion setting...
Definition: lobby_info.cpp:342
std::vector< std::string > installed_addons_
Definition: lobby_info.hpp:136
game_info * get_game_by_id(int id)
Returns info on a game with the given game ID.
Definition: lobby_info.cpp:293
void make_games_vector()
Generates a new vector of game pointers from the ID/game map.
Definition: lobby_info.cpp:316
Main entry points of multiplayer mode.
Definition: lobby_data.cpp:52
void do_notify(notify_mode mode, const std::string &sender, const std::string &message)
Definition: lobby_info.cpp:62
user_relation get_relation() const
Definition: lobby_data.cpp:75
#define WRN_LB
Definition: lobby_info.cpp:34
boost::dynamic_bitset games_visibility_
Definition: lobby_info.hpp:152
void game_created(const std::string &scenario, const std::string &name)
static lg::log_domain log_lobby("lobby")
std::vector< std::string > installed_addons()
Retrieves the names of all installed add-ons.
Definition: manager.cpp:178
game_info_map games_by_id_
Definition: lobby_info.hpp:142
void apply_diff(const config &diff, bool track=false)
A function to apply a diff config onto this config object.
Definition: config.cpp:1125
std::function< void()> begin_state_sync()
Updates the game pointer list and returns a second stage cleanup function to be called after any acti...
Definition: lobby_info.cpp:261
bool gamelist_initialized_
Definition: lobby_info.hpp:140
std::string name
Definition: lobby_data.hpp:54
void player_joins(bool is_lobby)
const char * display_status_string() const
Definition: lobby_data.cpp:485
std::size_t i
Definition: function.cpp:967
#define WRN_NG
Definition: lobby_info.cpp:30
const std::vector< game_info * > & games() const
Definition: lobby_info.hpp:98
double g
Definition: astarsearch.cpp:65
bool process_gamelist_diff(const config &data)
Process a gamelist diff.
Definition: lobby_info.cpp:140
void refresh_installed_addons_cache()
Definition: lobby_info.cpp:54
void process_userlist()
Definition: lobby_info.cpp:228
This class represents the information a client has about another player.
Definition: lobby_data.hpp:32
static const char * diff_track_attribute
The name of the attribute used for tracking diff changes.
Definition: config.hpp:771
Standard logging facilities (interface).
void server_message(bool is_lobby, const std::string &sender, const std::string &message)
std::string message
Definition: exceptions.hpp:30
notify_mode
Definition: lobby_info.hpp:155
#define e
#define ERR_LB
Definition: lobby_info.cpp:35
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:60
std::vector< user_info > users_
Definition: lobby_info.hpp:146
std::vector< game_info * > games_
Definition: lobby_info.hpp:144
mock_char c
bool process_gamelist_diff_impl(const config &data)
Definition: lobby_info.cpp:151