Pyrogenesis  13997
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages
Loader.cpp
Go to the documentation of this file.
1 /* Copyright (C) 2009 Wildfire Games.
2  * This file is part of 0 A.D.
3  *
4  * 0 A.D. 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  *
9  * 0 A.D. is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
16  */
17 
18 // FIFO queue of load 'functors' with time limit; enables displaying
19 // load progress without resorting to threads (complicated).
20 
21 #include "precompiled.h"
22 
23 #include <deque>
24 #include <numeric>
25 
26 #include "lib/timer.h"
27 #include "CStr.h"
28 #include "Loader.h"
29 #include "LoaderThunks.h"
30 
31 
32 // set by LDR_EndRegistering; may be 0 during development when
33 // estimated task durations haven't yet been set.
35 
36 // total time spent loading so far, set by LDR_ProgressiveLoad.
37 // we need a persistent counter so it can be reset after each load.
38 // this also accumulates less errors than:
39 // progress += task_estimated / total_estimated.
41 
42 // needed for report of how long each individual task took.
43 static double task_elapsed_time;
44 
45 // main purpose is to indicate whether a load is in progress, so that
46 // LDR_ProgressiveLoad can return 0 iff loading just completed.
47 // the REGISTERING state allows us to detect 2 simultaneous loads (bogus);
48 // FIRST_LOAD is used to skip the first timeslice (see LDR_ProgressiveLoad).
49 static enum
50 {
55 }
56 state = IDLE;
57 
58 
59 // holds all state for one load request; stored in queue.
61 {
62  // member documentation is in LDR_Register (avoid duplication).
63 
65  void* param;
66 
67  CStrW description;
68  // rationale for storing as CStrW here:
69  // - needs to be WCS because it's user-visible and will be translated.
70  // - don't just store a pointer - the caller's string may be volatile.
71  // - the module interface must work in C, so we get/set as wchar_t*.
72 
74 
75  // LDR_Register gets these as parameters; pack everything together.
76  LoadRequest(LoadFunc func_, void* param_, const wchar_t* desc_, int ms_)
77  : func(func_), param(param_), description(desc_),
79  {
80  }
81 };
82 
83 typedef std::deque<LoadRequest> LoadRequests;
85 
86 // std::accumulate binary op; used by LDR_EndRegistering to sum up all
87 // estimated durations (for % progress calculation)
88 struct DurationAdder: public std::binary_function<double, const LoadRequest&, double>
89 {
90  double operator()(double partial_result, const LoadRequest& lr) const
91  {
92  return partial_result + lr.estimated_duration_ms*1e-3;
93  }
94 };
95 
96 
97 // call before starting to register load requests.
98 // this routine is provided so we can prevent 2 simultaneous load operations,
99 // which is bogus. that can happen by clicking the load button quickly,
100 // or issuing via console while already loading.
102 {
103  ENSURE(state == IDLE);
104 
105  state = REGISTERING;
106  load_requests.clear();
107 }
108 
109 
110 // register a task (later processed in FIFO order).
111 // <func>: function that will perform the actual work; see LoadFunc.
112 // <param>: (optional) parameter/persistent state; must be freed by func.
113 // <description>: user-visible description of the current task, e.g.
114 // "Loading Textures".
115 // <estimated_duration_ms>: used to calculate progress, and when checking
116 // whether there is enough of the time budget left to process this task
117 // (reduces timeslice overruns, making the main loop more responsive).
118 void LDR_Register(LoadFunc func, void* param, const wchar_t* description,
119  int estimated_duration_ms)
120 {
121  ENSURE(state == REGISTERING); // must be called between LDR_(Begin|End)Register
122 
123  const LoadRequest lr(func, param, description, estimated_duration_ms);
124  load_requests.push_back(lr);
125 }
126 
127 
128 // call when finished registering tasks; subsequent calls to
129 // LDR_ProgressiveLoad will then work off the queued entries.
131 {
133  ENSURE(!load_requests.empty());
134 
135  state = FIRST_LOAD;
137  task_elapsed_time = 0.0;
138  total_estimated_duration = std::accumulate(load_requests.begin(), load_requests.end(), 0.0, DurationAdder());
139 }
140 
141 
142 // immediately cancel this load; no further tasks will be processed.
143 // used to abort loading upon user request or failure.
144 // note: no special notification will be returned by LDR_ProgressiveLoad.
146 {
147  // the queue doesn't need to be emptied now; that'll happen during the
148  // next LDR_StartRegistering. for now, it is sufficient to set the
149  // state, so that LDR_ProgressiveLoad is a no-op.
150  state = IDLE;
151 }
152 
153 // helper routine for LDR_ProgressiveLoad.
154 // tries to prevent starting a long task when at the end of a timeslice.
155 static bool HaveTimeForNextTask(double time_left, double time_budget, int estimated_duration_ms)
156 {
157  // have already exceeded our time budget
158  if(time_left <= 0.0)
159  return false;
160 
161  // we haven't started a request yet this timeslice. start it even if
162  // it's longer than time_budget to make sure there is progress.
163  if(time_left == time_budget)
164  return true;
165 
166  // check next task length. we want a lengthy task to happen in its own
167  // timeslice so that its description is displayed beforehand.
168  const double estimated_duration = estimated_duration_ms*1e-3;
169  if(time_left+estimated_duration > time_budget*1.20)
170  return false;
171 
172  return true;
173 }
174 
175 
176 // process as many of the queued tasks as possible within <time_budget> [s].
177 // if a task is lengthy, the budget may be exceeded. call from the main loop.
178 //
179 // passes back a description of the next task that will be undertaken
180 // ("" if finished) and the current progress value.
181 //
182 // return semantics:
183 // - if the final load task just completed, return INFO::ALL_COMPLETE.
184 // - if loading is in progress but didn't finish, return ERR::TIMED_OUT.
185 // - if not currently loading (no-op), return 0.
186 // - any other value indicates a failure; the request has been de-queued.
187 //
188 // string interface rationale: for better interoperability, we avoid C++
189 // std::wstring and PS CStr. since the registered description may not be
190 // persistent, we can't just store a pointer. returning a pointer to
191 // our copy of the description doesn't work either, since it's freed when
192 // the request is de-queued. that leaves writing into caller's buffer.
193 Status LDR_ProgressiveLoad(double time_budget, wchar_t* description, size_t max_chars, int* progress_percent)
194 {
195  Status ret; // single exit; this is returned
196  double progress = 0.0; // used to set progress_percent
197  double time_left = time_budget;
198 
199  // don't do any work the first time around so that a graphics update
200  // happens before the first (probably lengthy) timeslice.
201  if(state == FIRST_LOAD)
202  {
203  state = LOADING;
204 
205  ret = ERR::TIMED_OUT; // make caller think we did something
206  // progress already set to 0.0; that'll be passed back.
207  goto done;
208  }
209 
210  // we're called unconditionally from the main loop, so this isn't
211  // an error; there is just nothing to do.
212  if(state != LOADING)
213  return INFO::OK;
214 
215  while(!load_requests.empty())
216  {
217  // get next task; abort if there's not enough time left for it.
218  const LoadRequest& lr = load_requests.front();
219  const double estimated_duration = lr.estimated_duration_ms*1e-3;
220  if(!HaveTimeForNextTask(time_left, time_budget, lr.estimated_duration_ms))
221  {
222  ret = ERR::TIMED_OUT;
223  goto done;
224  }
225 
226  // call this task's function and bill elapsed time.
227  const double t0 = timer_Time();
228  int status = lr.func(lr.param, time_left);
229  const bool timed_out = ldr_was_interrupted(status);
230  const double elapsed_time = timer_Time() - t0;
231  time_left -= elapsed_time;
232  task_elapsed_time += elapsed_time;
233 
234  // either finished entirely, or failed => remove from queue.
235  if(!timed_out)
236  {
237  debug_printf(L"LOADER| completed %ls in %g ms; estimate was %g ms\n", lr.description.c_str(), task_elapsed_time*1e3, estimated_duration*1e3);
238  task_elapsed_time = 0.0;
239  estimated_duration_tally += estimated_duration;
240  load_requests.pop_front();
241  }
242 
243  // calculate progress (only possible if estimates have been given)
244  if(total_estimated_duration != 0.0)
245  {
246  double current_estimate = estimated_duration_tally;
247 
248  // function interrupted itself; add its estimated progress.
249  // note: monotonicity is guaranteed since we never add more than
250  // its estimated_duration_ms.
251  if(timed_out)
252  current_estimate += estimated_duration * status/100.0;
253 
254  progress = current_estimate / total_estimated_duration;
255  }
256 
257  // do we need to continue?
258  // .. function interrupted itself, i.e. timed out; abort.
259  if(timed_out)
260  {
261  ret = ERR::TIMED_OUT;
262  goto done;
263  }
264  // .. failed; abort. loading will continue when we're called in
265  // the next iteration of the main loop.
266  // rationale: bail immediately instead of remembering the first
267  // error that came up so we can report all errors that happen.
268  else if(status < 0)
269  {
270  ret = (Status)status;
271  goto done;
272  }
273  // .. function called LDR_Cancel; abort. return OK since this is an
274  // intentional cancellation, not an error.
275  else if(state != LOADING)
276  {
277  ret = INFO::OK;
278  goto done;
279  }
280  // .. succeeded; continue and process next queued task.
281  }
282 
283  // queue is empty, we just finished.
284  state = IDLE;
285  ret = INFO::ALL_COMPLETE;
286 
287 
288  // set output params (there are several return points above)
289 done:
290  *progress_percent = (int)(progress * 100.0);
291  ENSURE(0 <= *progress_percent && *progress_percent <= 100);
292 
293  // we want the next task, instead of what just completed:
294  // it will be displayed during the next load phase.
295  const wchar_t* new_description = L""; // assume finished
296  if(!load_requests.empty())
297  new_description = load_requests.front().description.c_str();
298  wcscpy_s(description, max_chars, new_description);
299 
300  debug_printf(L"LOADER| returning; desc=%ls progress=%d\n", description, *progress_percent);
301 
302  return ret;
303 }
304 
305 
306 // immediately process all queued load requests.
307 // returns 0 on success or a negative error code.
309 {
310  const double time_budget = 100.0;
311  // large enough so that individual functions won't time out
312  // (that'd waste time).
313  wchar_t description[100];
314  int progress_percent;
315 
316  for(;;)
317  {
318  Status ret = LDR_ProgressiveLoad(time_budget, description, ARRAY_SIZE(description), &progress_percent);
319  switch(ret)
320  {
321  case INFO::OK:
322  debug_warn(L"No load in progress");
323  return INFO::OK;
324  case INFO::ALL_COMPLETE:
325  return INFO::OK;
326  case ERR::TIMED_OUT:
327  break; // continue loading
328  default:
329  WARN_RETURN_STATUS_IF_ERR(ret); // failed; complain
330  }
331  }
332 }
const Status OK
Definition: status.h:386
Status LDR_NonprogressiveLoad()
Definition: Loader.cpp:308
void LDR_Cancel()
Definition: Loader.cpp:145
const Status TIMED_OUT
Definition: status.h:411
int(* LoadFunc)(void *param, double time_left)
Definition: Loader.h:119
static double estimated_duration_tally
Definition: Loader.cpp:40
const Status ALL_COMPLETE
Definition: status.h:400
#define ARRAY_SIZE(name)
int wcscpy_s(wchar_t *dst, size_t max_dst_chars, const wchar_t *src)
static double total_estimated_duration
Definition: Loader.cpp:34
Definition: Loader.cpp:51
#define WARN_RETURN_STATUS_IF_ERR(expression)
Definition: status.h:287
#define ENSURE(expr)
ensure the expression &lt;expr&gt; evaluates to non-zero.
Definition: debug.h:282
CStrW description
Definition: Loader.cpp:67
i64 Status
Error handling system.
Definition: status.h:171
void * param
Definition: Loader.cpp:65
double timer_Time()
Definition: timer.cpp:98
static bool HaveTimeForNextTask(double time_left, double time_budget, int estimated_duration_ms)
Definition: Loader.cpp:155
int estimated_duration_ms
Definition: Loader.cpp:73
LoadFunc func
Definition: Loader.cpp:64
static bool ldr_was_interrupted(int ret)
Definition: LoaderThunks.h:42
void LDR_EndRegistering()
Definition: Loader.cpp:130
Status LDR_ProgressiveLoad(double time_budget, wchar_t *description, size_t max_chars, int *progress_percent)
Definition: Loader.cpp:193
std::deque< LoadRequest > LoadRequests
Definition: Loader.cpp:83
static double task_elapsed_time
Definition: Loader.cpp:43
#define debug_warn(expr)
display the error dialog with the given text.
Definition: debug.h:324
LoadRequest(LoadFunc func_, void *param_, const wchar_t *desc_, int ms_)
Definition: Loader.cpp:76
static LoadRequests load_requests
Definition: Loader.cpp:84
void LDR_Register(LoadFunc func, void *param, const wchar_t *description, int estimated_duration_ms)
Definition: Loader.cpp:118
void LDR_BeginRegistering()
Definition: Loader.cpp:101
void debug_printf(const wchar_t *fmt,...)
write a formatted string to the debug channel, subject to filtering (see below).
Definition: debug.cpp:142
static enum @41 state
double operator()(double partial_result, const LoadRequest &lr) const
Definition: Loader.cpp:90