1 /*
2    Copyright (C) 2016 - 2018 by the Battle for Wesnoth Project https://www.wesnoth.org/
3 
4    This program is free software; you can redistribute it and/or modify
5    it under the terms of the GNU General Public License as published by
6    the Free Software Foundation; either version 2 of the License, or
7    (at your option) any later version.
8    This program is distributed in the hope that it will be useful,
9    but WITHOUT ANY WARRANTY.
10 
11    See the COPYING file for more details.
12 */
13 
14 /**
15  * @file
16  * Screen with logo and loading status info during program-startup.
17  */
18 
19 #define GETTEXT_DOMAIN "wesnoth-lib"
20 
21 #include "gui/dialogs/loading_screen.hpp"
22 
23 #include "cursor.hpp"
24 #include "gettext.hpp"
25 #include "gui/auxiliary/find_widget.hpp"
26 #include "gui/core/timer.hpp"
27 #include "gui/widgets/label.hpp"
28 #include "gui/widgets/settings.hpp"
29 #include "gui/widgets/window.hpp"
30 #include "log.hpp"
31 #include "preferences/general.hpp"
32 #include "utils/functional.hpp"
33 #include "video.hpp"
34 
35 #include <boost/thread.hpp>
36 
37 #include <cstdlib>
38 
39 #if defined(_MSC_VER) && _MSC_VER <= 1800
40 #include <Windows.h>
41 #endif
42 
43 static lg::log_domain log_loadscreen("loadscreen");
44 #define ERR_LS LOG_STREAM(err, log_loadscreen)
45 #define WRN_LS LOG_STREAM(warn, log_loadscreen)
46 #define LOG_LS LOG_STREAM(info, log_loadscreen)
47 #define DBG_LS LOG_STREAM(debug, log_loadscreen)
48 
49 static const std::map<loading_stage, std::string> stage_names {
50 	{ loading_stage::build_terrain,       N_("Building terrain rules") },
51 	{ loading_stage::create_cache,        N_("Reading files and creating cache") },
52 	{ loading_stage::init_display,        N_("Initializing display") },
53 	{ loading_stage::init_fonts,          N_("Reinitialize fonts for the current language") },
54 	{ loading_stage::init_teams,          N_("Initializing teams") },
55 	{ loading_stage::init_theme,          N_("Initializing display") },
56 	{ loading_stage::load_config,         N_("Loading game configuration") },
57 	{ loading_stage::load_data,           N_("Loading data files") },
58 	{ loading_stage::load_level,          N_("Loading level") },
59 	{ loading_stage::init_lua,            N_("Initializing scripting engine") },
60 	{ loading_stage::init_whiteboard,     N_("Initializing planning mode") },
61 	{ loading_stage::load_unit_types,     N_("Reading unit files") },
62 	{ loading_stage::load_units,          N_("Loading units") },
63 	{ loading_stage::refresh_addons,      N_("Searching for installed add-ons") },
64 	{ loading_stage::start_game,          N_("Starting game") },
65 	{ loading_stage::verify_cache,        N_("Verifying cache") },
66 	{ loading_stage::connect_to_server,   N_("Connecting to server") },
67 	{ loading_stage::login_response,      N_("Logging in") },
68 	{ loading_stage::waiting,             N_("Waiting for server") },
69 	{ loading_stage::redirect,            N_("Connecting to redirected server") },
70 	{ loading_stage::next_scenario,       N_("Waiting for next scenario") },
71 	{ loading_stage::download_level_data, N_("Getting game data") },
72 	{ loading_stage::download_lobby_data, N_("Downloading lobby data") },
73 };
74 
75 namespace gui2
76 {
77 namespace dialogs
78 {
REGISTER_DIALOG(loading_screen)79 REGISTER_DIALOG(loading_screen)
80 
81 loading_screen::loading_screen(std::function<void()> f)
82 	: timer_id_(0)
83 	, animation_counter_(0)
84 	, work_(f)
85 	, worker_()
86 	, cursor_setter_()
87 	, progress_stage_label_(nullptr)
88 	, animation_label_(nullptr)
89 	, current_stage_(loading_stage::none)
90 	, visible_stages_()
91 	, animation_stages_()
92 	, current_visible_stage_()
93 	, is_worker_running_(false)
94 {
95 	for(const auto& pair : stage_names) {
96 		visible_stages_[pair.first] = t_string(pair.second, "wesnoth-lib") + "...";
97 	}
98 
99 	for(int i = 0; i != 20; ++i) {
100 		std::string s(20, ' ');
101 		s[i] = '.';
102 		animation_stages_.push_back(s);
103 	}
104 
105 	current_visible_stage_ = visible_stages_.end();
106 	current_load = this;
107 }
108 
pre_show(window & window)109 void loading_screen::pre_show(window& window)
110 {
111 	if(work_) {
112 		worker_.reset(new boost::thread([this]() {
113 			is_worker_running_ = true;
114 
115 			try {
116 				work_();
117 			} catch(...) {
118 				// TODO: guard this with a mutex.
119 				exception_ = std::current_exception();
120 			}
121 
122 			is_worker_running_ = false;
123 		}));
124 	}
125 
126 	timer_id_ = add_timer(100, std::bind(&loading_screen::timer_callback, this, std::ref(window)), true);
127 	cursor_setter_.reset(new cursor::setter(cursor::WAIT));
128 	progress_stage_label_ = find_widget<label>(&window, "status", false, true);
129 	animation_label_ = find_widget<label>(&window, "test_animation", false, true);
130 
131 	window.set_enter_disabled(true);
132 	window.set_escape_disabled(true);
133 }
134 
post_show(window &)135 void loading_screen::post_show(window& /*window*/)
136 {
137 	worker_.reset();
138 	clear_timer();
139 	cursor_setter_.reset();
140 }
141 
progress(loading_stage stage)142 void loading_screen::progress(loading_stage stage)
143 {
144 	if(!current_load) {
145 		return;
146 	}
147 
148 	if(stage != loading_stage::none) {
149 		current_load->current_stage_.store(stage, std::memory_order_release);
150 	}
151 }
152 
153 loading_screen* loading_screen::current_load = nullptr;
154 
timer_callback(window & window)155 void loading_screen::timer_callback(window& window)
156 {
157 	if(!work_ || !worker_ || worker_->timed_join(boost::posix_time::milliseconds(0))) {
158 		if(exception_) {
159 			clear_timer();
160 			std::rethrow_exception(exception_);
161 		}
162 
163 		window.close();
164 	}
165 
166 	if(!work_) {
167 		return;
168 	}
169 
170 	loading_stage stage = current_stage_.load(std::memory_order_acquire);
171 
172 	if(stage != loading_stage::none && (current_visible_stage_ == visible_stages_.end() || stage != current_visible_stage_->first)) {
173 		auto iter = visible_stages_.find(stage);
174 		if(iter == visible_stages_.end()) {
175 			WRN_LS << "Stage missing description." << std::endl;
176 			return;
177 		}
178 
179 		current_visible_stage_ = iter;
180 		progress_stage_label_->set_label(iter->second);
181 	}
182 
183 	++animation_counter_;
184 	if(animation_counter_ % 2 == 0) {
185 		animation_label_->set_label(animation_stages_[(animation_counter_ / 2) % animation_stages_.size()]);
186 	}
187 }
188 
~loading_screen()189 loading_screen::~loading_screen()
190 {
191 	/* If the worker thread is running, exit the application to prevent memory corruption.
192 	 * TODO: this is still not optimal. The main problem is that this code assumes that this
193 	 * happened because the window was closed, which is not necessarily the case (other
194 	 * possibilities might be a 'dialog doesn't fit on screen' exception caused by resizing
195 	 * the window).
196 	 *
197 	 * Another approach might be to add exit points (boost::this_thread::interruption_point())
198 	 * to the worker functions (filesystem.cpp, config parsing code, etc.) and then use that
199 	 * to end the thread faster.
200 	 */
201 	if(is_worker_running_) {
202 #if defined(_MSC_VER) && _MSC_VER <= 1800
203 		HANDLE process = GetCurrentProcess();
204 		TerminateProcess(process, 0u);
205 #elif defined(_LIBCPP_VERSION) || defined(__MINGW32__)
206 		std::_Exit(0);
207 #else
208 		std::quick_exit(0);
209 #endif
210 	}
211 
212 	clear_timer();
213 	current_load = nullptr;
214 }
215 
display(std::function<void ()> f)216 void loading_screen::display(std::function<void()> f)
217 {
218 	const bool use_loadingscreen_animation = !preferences::disable_loadingscreen_animation();
219 
220 	if(current_load || CVideo::get_singleton().faked()) {
221 		f();
222 	} else if(use_loadingscreen_animation) {
223 		loading_screen(f).show();
224 	} else {
225 		loading_screen(std::function<void()>()).show();
226 		f();
227 	}
228 }
229 
clear_timer()230 void loading_screen::clear_timer()
231 {
232 	if(timer_id_ != 0) {
233 		remove_timer(timer_id_);
234 		timer_id_ = 0;
235 	}
236 }
237 
238 } // namespace dialogs
239 } // namespace gui2
240