MP Staging: some progress with getting the network processing working, as well as using the dialog for non-hosts
This commit is contained in:
parent
4488e35706
commit
75636ee17c
4 changed files with 331 additions and 45 deletions
|
@ -430,7 +430,6 @@
|
|||
id = "player_list"
|
||||
definition = "default"
|
||||
|
||||
vertical_scrollbar_mode = "always"
|
||||
horizontal_scrollbar_mode = "never"
|
||||
|
||||
[list_definition]
|
||||
|
@ -493,7 +492,7 @@
|
|||
|
||||
[button]
|
||||
id = "ok"
|
||||
definition = "default"
|
||||
definition = "large"
|
||||
label = _ "I’m Ready"
|
||||
[/button]
|
||||
[/column]
|
||||
|
@ -504,7 +503,7 @@
|
|||
|
||||
[button]
|
||||
id = "cancel"
|
||||
definition = "default"
|
||||
definition = "large"
|
||||
label = _ "Cancel"
|
||||
[/button]
|
||||
[/column]
|
||||
|
@ -616,7 +615,23 @@
|
|||
|
||||
[/row]
|
||||
|
||||
{GUI_HORIZONTAL_SPACER_LINE}
|
||||
[row]
|
||||
grow_factor = 0
|
||||
|
||||
[column]
|
||||
grow_factor = 1
|
||||
horizontal_grow = "true"
|
||||
border = "all"
|
||||
border_size = 5
|
||||
|
||||
[label]
|
||||
id = "status_label"
|
||||
definition = "default_small"
|
||||
label = _ "Waiting for players to join..."
|
||||
[/label]
|
||||
[/column]
|
||||
|
||||
[/row]
|
||||
|
||||
[row]
|
||||
grow_factor = 0
|
||||
|
|
|
@ -406,8 +406,18 @@ static std::unique_ptr<twesnothd_connection> open_connection(CVideo& video, cons
|
|||
// creating the dialogs, then, according to the dialog result, of calling other
|
||||
// of those screen functions.
|
||||
|
||||
static config& get_scenario(config& c)
|
||||
{
|
||||
if(config& scenario = c.child("scenario"))
|
||||
return scenario;
|
||||
else if(config& snapshot = c.child("snapshot"))
|
||||
return snapshot;
|
||||
else
|
||||
return c;
|
||||
}
|
||||
|
||||
static void enter_wait_mode(CVideo& video, const config& game_config, saved_game& state, twesnothd_connection* wesnothd_connection,
|
||||
bool observe, int current_turn = 0)
|
||||
lobby_info& li, bool observe, int current_turn = 0)
|
||||
{
|
||||
DBG_MP << "entering wait mode" << std::endl;
|
||||
|
||||
|
@ -415,21 +425,163 @@ static void enter_wait_mode(CVideo& video, const config& game_config, saved_game
|
|||
|
||||
gamelist.clear();
|
||||
statistics::fresh_stats();
|
||||
mp_campaign_info campaign_info(*wesnothd_connection);
|
||||
campaign_info.is_host = false;
|
||||
std::unique_ptr<mp_campaign_info> campaign_info;
|
||||
campaign_info.reset(new mp_campaign_info(*wesnothd_connection));
|
||||
campaign_info->is_host = false;
|
||||
if(preferences::skip_mp_replay() || preferences::blindfold_replay()) {
|
||||
campaign_info.skip_replay_until_turn = current_turn;
|
||||
campaign_info.skip_replay_blindfolded = preferences::blindfold_replay();
|
||||
campaign_info->skip_replay_until_turn = current_turn;
|
||||
campaign_info->skip_replay_blindfolded = preferences::blindfold_replay();
|
||||
}
|
||||
|
||||
{
|
||||
if(preferences::new_lobby()) {
|
||||
bool download_res = true;
|
||||
|
||||
assert(wesnothd_connection);
|
||||
config level;
|
||||
DBG_MP << "download_level_data()\n";
|
||||
|
||||
if(!true) {
|
||||
// Ask for the next scenario data.
|
||||
wesnothd_connection->send_data(config("load_next_scenario"));
|
||||
}
|
||||
|
||||
bool has_scenario_and_controllers = false;
|
||||
while(!has_scenario_and_controllers) {
|
||||
config revc;
|
||||
bool data_res = gui2::tnetwork_transmission::wesnothd_receive_dialog(
|
||||
video, "download level data", revc, *wesnothd_connection);
|
||||
|
||||
if(!data_res) {
|
||||
DBG_MP << "download_level_data bad results\n";
|
||||
download_res = false;
|
||||
}
|
||||
|
||||
mp::check_response(data_res, revc);
|
||||
|
||||
if(revc.child("leave_game")) {
|
||||
download_res = false;
|
||||
} else if(config& next_scenario = revc.child("next_scenario")) {
|
||||
level.swap(next_scenario);
|
||||
} else if(revc.has_attribute("version")) {
|
||||
level.swap(revc);
|
||||
has_scenario_and_controllers = true;
|
||||
} else if(config& controllers = revc.child("controllers")) {
|
||||
int index = 0;
|
||||
for(const config& controller : controllers.child_range("controller")) {
|
||||
if(config& side = get_scenario(level).child("side", index)) {
|
||||
side["is_local"] = controller["is_local"];
|
||||
}
|
||||
++index;
|
||||
}
|
||||
has_scenario_and_controllers = true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
std::cerr << "download_level_data() success.\n";
|
||||
|
||||
if(!download_res) {
|
||||
DBG_MP << "mp wait: could not download level data, quitting...";
|
||||
//set_result(QUIT);
|
||||
return;
|
||||
} else if(level["started"].to_bool()) {
|
||||
//set_result(PLAY);
|
||||
return;
|
||||
}
|
||||
|
||||
if(true) {
|
||||
state = saved_game();
|
||||
state.classification() = game_classification(level);
|
||||
|
||||
if(state.classification().campaign_type != game_classification::CAMPAIGN_TYPE::MULTIPLAYER) {
|
||||
//ERR_MP << "Mp wait recieved a game that is not a multiplayer game\n";
|
||||
}
|
||||
// Make sure that we have the same config as host, if possible.
|
||||
game_config_manager::get()->load_game_config_for_game(state.classification());
|
||||
}
|
||||
|
||||
// Add the map name to the title.
|
||||
//append_to_title(": " + get_scenario(level)["name"].t_str());
|
||||
|
||||
game_config::add_color_info(get_scenario(level));
|
||||
if(!observe) {
|
||||
//search for an appropriate vacant slot. If a description is set
|
||||
//(i.e. we're loading from a saved game), then prefer to get the side
|
||||
//with the same description as our login. Otherwise just choose the first
|
||||
//available side.
|
||||
const config *side_choice = nullptr;
|
||||
//int side_num = -1, nb_sides = 0;
|
||||
for(const config &sd : get_scenario(level).child_range("side")) {
|
||||
//std::cerr << "*** side " << nb_sides << "***\n" << sd.debug() << "***\n";
|
||||
|
||||
if(sd["controller"] == "reserved" && sd["current_player"] == preferences::login()) {
|
||||
side_choice = &sd;
|
||||
//side_num = nb_sides;
|
||||
break;
|
||||
}
|
||||
|
||||
if(sd["controller"] == "human" && sd["player_id"].empty()) {
|
||||
if(!side_choice) { // found the first empty side
|
||||
side_choice = &sd;
|
||||
//side_num = nb_sides;
|
||||
}
|
||||
|
||||
if(sd["current_player"] == preferences::login()) {
|
||||
side_choice = &sd;
|
||||
//side_num = nb_sides;
|
||||
break; // found the preferred one
|
||||
}
|
||||
}
|
||||
|
||||
if(sd["player_id"] == preferences::login()) {
|
||||
//We already own a side in this game.
|
||||
//generate_menu();
|
||||
return;
|
||||
}
|
||||
//++nb_sides;
|
||||
}
|
||||
|
||||
if(!side_choice) {
|
||||
DBG_MP << "could not find a side, all " << get_scenario(level).child_count("side") << " sides were unsuitable\n";
|
||||
//set_result(QUIT);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
mp::level_to_gamestate(level, state);
|
||||
|
||||
campaign_info.reset(new mp_campaign_info(*wesnothd_connection));
|
||||
|
||||
//campaign_info->connected_players.insert(li.users().begin(), li.users().end());
|
||||
|
||||
ng::connect_engine_ptr connect_engine(new ng::connect_engine(state, true, campaign_info.get()));
|
||||
|
||||
connect_engine->receive_from_server(level);
|
||||
|
||||
gui2::tmp_staging dlg(*connect_engine, li, wesnothd_connection);
|
||||
dlg.show(video);
|
||||
|
||||
if(dlg.get_retval() == gui2::twindow::OK) {
|
||||
campaign_controller controller(video, state, game_config, game_config_manager::get()->terrain_types());
|
||||
controller.set_mp_info(campaign_info.get());
|
||||
controller.play_game();
|
||||
}
|
||||
|
||||
//if(wesnothd_connection) {
|
||||
// wesnothd_connection->send_data(config("leave_game"));
|
||||
//}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
mp::wait ui(video, wesnothd_connection, game_config, state, gamechat, gamelist);
|
||||
|
||||
ui.join_game(observe);
|
||||
|
||||
run_lobby_loop(video, ui);
|
||||
res = ui.get_result();
|
||||
campaign_info.connected_players.insert(ui.user_list().begin(), ui.user_list().end());
|
||||
campaign_info->connected_players.insert(ui.user_list().begin(), ui.user_list().end());
|
||||
|
||||
if (res == mp::ui::PLAY) {
|
||||
ui.start_game();
|
||||
|
@ -441,7 +593,7 @@ static void enter_wait_mode(CVideo& video, const config& game_config, saved_game
|
|||
switch (res) {
|
||||
case mp::ui::PLAY: {
|
||||
campaign_controller controller(video, state, game_config, game_config_manager::get()->terrain_types());
|
||||
controller.set_mp_info(&campaign_info);
|
||||
controller.set_mp_info(campaign_info.get());
|
||||
controller.play_game();
|
||||
break;
|
||||
}
|
||||
|
@ -476,21 +628,22 @@ static bool enter_connect_mode(CVideo& video, const config& game_config,
|
|||
{
|
||||
ng::connect_engine_ptr connect_engine(new ng::connect_engine(state, true, campaign_info.get()));
|
||||
|
||||
if(preferences::new_lobby()) {
|
||||
gui2::tmp_staging dlg(game_config, *connect_engine, li);
|
||||
dlg.show(video);
|
||||
if(preferences::new_lobby()) {
|
||||
gui2::tmp_staging dlg(*connect_engine, li, wesnothd_connection);
|
||||
dlg.show(video);
|
||||
|
||||
if(dlg.get_retval() == gui2::twindow::OK) {
|
||||
campaign_controller controller(video, state, game_config, game_config_manager::get()->terrain_types());
|
||||
controller.set_mp_info(campaign_info.get());
|
||||
controller.play_game();
|
||||
if(wesnothd_connection) {
|
||||
wesnothd_connection->send_data(config("leave_game"));
|
||||
}
|
||||
}
|
||||
if(dlg.get_retval() == gui2::twindow::OK) {
|
||||
campaign_controller controller(video, state, game_config, game_config_manager::get()->terrain_types());
|
||||
controller.set_mp_info(campaign_info.get());
|
||||
controller.play_game();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
if(wesnothd_connection) {
|
||||
wesnothd_connection->send_data(config("leave_game"));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
mp::connect ui(video, wesnothd_connection, state.mp_settings().name, game_config, gamechat, gamelist,
|
||||
*connect_engine);
|
||||
|
@ -714,7 +867,7 @@ static void enter_lobby_mode(CVideo& video, const config& game_config,
|
|||
case mp::ui::JOIN:
|
||||
case mp::ui::OBSERVE:
|
||||
try {
|
||||
enter_wait_mode(video, game_config, state, wesnothd_connection, res == mp::ui::OBSERVE, current_turn);
|
||||
enter_wait_mode(video, game_config, state, wesnothd_connection, li, res == mp::ui::OBSERVE, current_turn);
|
||||
} catch(config::error& error) {
|
||||
if(!error.message.empty()) {
|
||||
gui2::show_error_message(video, error.message);
|
||||
|
|
|
@ -16,7 +16,6 @@
|
|||
#include "gui/dialogs/multiplayer/mp_staging.hpp"
|
||||
|
||||
#include "config_assign.hpp"
|
||||
#include "game_preferences.hpp"
|
||||
#include "gettext.hpp"
|
||||
#include "gui/auxiliary/field.hpp"
|
||||
#include "gui/dialogs/helper.hpp"
|
||||
|
@ -43,28 +42,39 @@
|
|||
#include "gui/widgets/toggle_panel.hpp"
|
||||
#include "gui/widgets/text_box.hpp"
|
||||
#include "game_config.hpp"
|
||||
#include "savegame.hpp"
|
||||
#include "mp_ui_alerts.hpp"
|
||||
#include "settings.hpp"
|
||||
#include "units/types.hpp"
|
||||
#include "formatter.hpp"
|
||||
#include "wesnothd_connection.hpp"
|
||||
|
||||
#ifdef GUI2_EXPERIMENTAL_LISTBOX
|
||||
#include "utils/functional.hpp"
|
||||
#endif
|
||||
|
||||
#include <boost/algorithm/string.hpp>
|
||||
|
||||
namespace gui2
|
||||
{
|
||||
|
||||
REGISTER_DIALOG(mp_staging)
|
||||
|
||||
tmp_staging::tmp_staging(const config& /*cfg*/, ng::connect_engine& connect_engine, lobby_info& lobby_info)
|
||||
tmp_staging::tmp_staging(ng::connect_engine& connect_engine, lobby_info& lobby_info, twesnothd_connection* wesnothd_connection)
|
||||
: connect_engine_(connect_engine)
|
||||
, ai_algorithms_(ai::configuration::get_available_ais())
|
||||
, lobby_info_(lobby_info)
|
||||
, wesnothd_connection_(wesnothd_connection)
|
||||
, update_timer_(0)
|
||||
{
|
||||
set_show_even_without_video(true);
|
||||
|
||||
assert(!ai_algorithms_.empty());
|
||||
}
|
||||
|
||||
tmp_staging::~tmp_staging()
|
||||
{
|
||||
if(update_timer_ != 0) {
|
||||
remove_timer(update_timer_);
|
||||
update_timer_ = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void tmp_staging::pre_show(twindow& window)
|
||||
|
@ -123,8 +133,6 @@ void tmp_staging::pre_show(twindow& window)
|
|||
//
|
||||
// AI Algorithm
|
||||
//
|
||||
assert(!ai_algorithms_.empty());
|
||||
|
||||
int selection = 0;
|
||||
|
||||
// We use an index-based loop in order to get the index of the selected option
|
||||
|
@ -155,7 +163,7 @@ void tmp_staging::pre_show(twindow& window)
|
|||
tmenu_button& controller_selection = find_widget<tmenu_button>(&row_grid, "controller", false);
|
||||
|
||||
controller_selection.set_values(controller_names, side.current_controller_index());
|
||||
controller_selection.set_active(side.controller_options().size() > 1);
|
||||
controller_selection.set_active(controller_names.size() > 1);
|
||||
controller_selection.connect_click_handler(std::bind(&tmp_staging::on_controller_select, this, std::ref(side), std::ref(row_grid)));
|
||||
|
||||
on_controller_select(side, row_grid);
|
||||
|
@ -181,7 +189,7 @@ void tmp_staging::pre_show(twindow& window)
|
|||
// As such, the index is off if there is only 1 playable team. This is a hack to make sure the menu_button
|
||||
// widget doesn't assert with the invalid initial selection. The connect_engine should be fixed once the GUI1
|
||||
// dialog is dropped
|
||||
team_selection.set_values(team_names, std::min(static_cast<int>(team_names.size() - 1), side.team()));
|
||||
team_selection.set_values(team_names, std::min<int>(team_names.size() - 1, side.team()));
|
||||
team_selection.set_active(!saved_game);
|
||||
team_selection.connect_click_handler(std::bind(&tmp_staging::on_team_select, this, std::ref(side), std::ref(team_selection)));
|
||||
|
||||
|
@ -244,14 +252,73 @@ void tmp_staging::pre_show(twindow& window)
|
|||
tchatbox& chat = find_widget<tchatbox>(&window, "chat", false);
|
||||
|
||||
chat.set_lobby_info(lobby_info_);
|
||||
chat.set_wesnothd_connection(*wesnothd_connection_);
|
||||
|
||||
chat.room_window_open("this game", true); // TODO: better title?
|
||||
chat.active_window_changed();
|
||||
|
||||
//
|
||||
// Set up player list
|
||||
//
|
||||
update_player_list(window);
|
||||
|
||||
//
|
||||
// Set up the network handling
|
||||
//
|
||||
update_timer_ = add_timer(game_config::lobby_network_timer, std::bind(&tmp_staging::network_handler, this, std::ref(window)), true);
|
||||
|
||||
network_handler(window);
|
||||
|
||||
//
|
||||
// Set up the Lua plugin context
|
||||
//
|
||||
plugins_context_.reset(new plugins_context("Multiplayer Staging"));
|
||||
|
||||
plugins_context_->set_callback("launch", [&window](const config&) { window.set_retval(twindow::OK); }, false);
|
||||
plugins_context_->set_callback("quit", [&window](const config&) { window.set_retval(twindow::CANCEL); }, false);
|
||||
plugins_context_->set_callback("chat", [&chat](const config& cfg) { chat.send_chat_message(cfg["message"], false); }, true);
|
||||
}
|
||||
|
||||
/*
|
||||
* We don't need the full widget setup as is done initially, just value setters.
|
||||
*/
|
||||
void tmp_staging::update_side_ui(twindow& window, const int i)
|
||||
{
|
||||
tgrid& row_grid = *find_widget<tlistbox>(&window, "side_list", false).get_row_grid(i);
|
||||
|
||||
ng::side_engine& side = *connect_engine_.side_engines()[i].get();
|
||||
|
||||
const int ai_i = std::find_if(ai_algorithms_.begin(), ai_algorithms_.end(), [&side](ai::description* a) {
|
||||
return a->id == side.ai_algorithm();
|
||||
}) - ai_algorithms_.begin();
|
||||
|
||||
find_widget<tmenu_button>(&row_grid, "ai_controller", false).set_selected(ai_i);
|
||||
|
||||
std::vector<config> controller_names;
|
||||
for(const auto& controller : side.controller_options()) {
|
||||
controller_names.push_back(config_of("label", controller.second));
|
||||
}
|
||||
|
||||
tmenu_button& controller_selection = find_widget<tmenu_button>(&row_grid, "controller", false);
|
||||
|
||||
controller_selection.set_values(controller_names, side.current_controller_index());
|
||||
controller_selection.set_active(controller_names.size() > 1);
|
||||
|
||||
update_leader_display(side, row_grid);
|
||||
|
||||
find_widget<tmenu_button>(&row_grid, "side_team", false).set_selected(side.team());
|
||||
find_widget<tmenu_button>(&row_grid, "side_color", false).set_selected(side.color());
|
||||
|
||||
find_widget<tslider>(&row_grid, "side_gold_slider", false).set_value(side.cfg()["gold"].to_int(100));
|
||||
find_widget<tslider>(&row_grid, "side_income_slider", false).set_value(side.cfg()["income"]);
|
||||
}
|
||||
|
||||
void tmp_staging::update_player_list(twindow& window)
|
||||
{
|
||||
tlistbox& player_list = find_widget<tlistbox>(&window, "player_list", false);
|
||||
|
||||
player_list.clear();
|
||||
|
||||
for(const auto& player : connect_engine_.connected_users()) {
|
||||
std::map<std::string, string_map> data;
|
||||
string_map item;
|
||||
|
@ -261,15 +328,6 @@ void tmp_staging::pre_show(twindow& window)
|
|||
|
||||
player_list.add_row(data);
|
||||
}
|
||||
|
||||
//
|
||||
// Set up the Lua plugin context
|
||||
//
|
||||
plugins_context_.reset(new plugins_context("Multiplayer Staging"));
|
||||
|
||||
plugins_context_->set_callback("launch", [&window](const config&) { window.set_retval(twindow::OK); }, false);
|
||||
plugins_context_->set_callback("quit", [&window](const config&) { window.set_retval(twindow::CANCEL); }, false);
|
||||
plugins_context_->set_callback("chat", [&chat](const config& cfg) { chat.send_chat_message(cfg["message"], false); }, true);
|
||||
}
|
||||
|
||||
void tmp_staging::sync_changes()
|
||||
|
@ -354,7 +412,55 @@ void tmp_staging::update_leader_display(ng::side_engine& side, tgrid& row_grid)
|
|||
// Gender
|
||||
if(current_gender != utils::unicode_em_dash) {
|
||||
const std::string gender_icon = formatter() << "icons/icon-" << current_gender << ".png";
|
||||
find_widget<timage>(&row_grid, "leader_gender", false).set_label(gender_icon);
|
||||
|
||||
timage& icon = find_widget<timage>(&row_grid, "leader_gender", false);
|
||||
|
||||
icon.set_label(gender_icon);
|
||||
icon.set_tooltip(current_gender);
|
||||
}
|
||||
}
|
||||
|
||||
void tmp_staging::network_handler(twindow& window)
|
||||
{
|
||||
config data;
|
||||
if(!wesnothd_connection_ || !wesnothd_connection_->receive_data(data)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Update chat
|
||||
find_widget<tchatbox>(&window, "chat", false).process_network_data(data);
|
||||
|
||||
// TODO: why is this needed...
|
||||
const bool was_able_to_start = connect_engine_.can_start_game();
|
||||
|
||||
bool quit_signal_recieved;
|
||||
std::tie(quit_signal_recieved, std::ignore) = connect_engine_.process_network_data(data);
|
||||
|
||||
if(quit_signal_recieved) {
|
||||
window.set_retval(twindow::CANCEL);
|
||||
}
|
||||
|
||||
// Update sides
|
||||
for(unsigned i = 0; i < connect_engine_.side_engines().size(); i++) {
|
||||
update_side_ui(window, i);
|
||||
}
|
||||
|
||||
// Update player list
|
||||
// TODO: optimally, it wouldn't regenerate the entire list every single refresh cycle
|
||||
update_player_list(window);
|
||||
|
||||
// Update status label and buttons
|
||||
// T O D O F I X T H I S S H I T
|
||||
find_widget<tlabel>(&window, "status_label", false).set_label(
|
||||
connect_engine_.can_start_game() ? "" : connect_engine_.sides_available()
|
||||
? _("Waiting for players to join...")
|
||||
: _("Waiting for players to choose factions...")
|
||||
);
|
||||
|
||||
find_widget<tbutton>(&window, "ok", false).set_active(connect_engine_.can_start_game());
|
||||
|
||||
if(!was_able_to_start && connect_engine_.can_start_game()) {
|
||||
mp_ui_alerts::ready_for_start();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -38,7 +38,9 @@ class twidget;
|
|||
class tmp_staging : public tdialog, private plugin_executor
|
||||
{
|
||||
public:
|
||||
tmp_staging(const config& cfg, ng::connect_engine& connect_engine, lobby_info& lobby_info);
|
||||
tmp_staging(ng::connect_engine& connect_engine, lobby_info& lobby_info, twesnothd_connection* wesnothd_connection = nullptr);
|
||||
|
||||
~tmp_staging();
|
||||
|
||||
private:
|
||||
/** Inherited from tdialog, implemented by REGISTER_DIALOG. */
|
||||
|
@ -50,6 +52,10 @@ private:
|
|||
/** Inherited from tdialog. */
|
||||
void post_show(twindow& window);
|
||||
|
||||
void update_side_ui(twindow& window, const int i);
|
||||
|
||||
void update_player_list(twindow& window);
|
||||
|
||||
void sync_changes();
|
||||
|
||||
void on_controller_select(ng::side_engine& side, tgrid& row_grid);
|
||||
|
@ -61,11 +67,17 @@ private:
|
|||
|
||||
void update_leader_display(ng::side_engine& side, tgrid& row_grid);
|
||||
|
||||
void network_handler(twindow& window);
|
||||
|
||||
ng::connect_engine& connect_engine_;
|
||||
|
||||
std::vector<ai::description*> ai_algorithms_;
|
||||
|
||||
lobby_info& lobby_info_;
|
||||
|
||||
twesnothd_connection* wesnothd_connection_;
|
||||
|
||||
size_t update_timer_;
|
||||
};
|
||||
|
||||
} // namespace gui2
|
||||
|
|
Loading…
Add table
Reference in a new issue