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