Pyrogenesis  trunk
Loader.h
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 #ifndef INCLUDED_LOADER
22 #define INCLUDED_LOADER
23 
24 #include <wchar.h>
25 
26 /*
27 
28 [KEEP IN SYNC WITH WIKI!]
29 
30 Overview
31 --------
32 
33 "Loading" is the act of preparing a game session, including reading all
34 required data from disk. Ideally, this would be very quick, but for complex
35 maps and/or low-end machines, a duration of several seconds can be expected.
36 Having the game freeze that long is unacceptable; instead, we want to display
37 the current progress and task, which greatly increases user patience.
38 
39 
40 Allowing for Display
41 --------------------
42 
43 To display progress, we need to periodically 'interrupt' loading.
44 Threads come to mind, but there is a problem: since OpenGL graphics calls
45 must come from the main thread, loading would have to happen in a
46 background thread. Everything would thus need to be made thread-safe,
47 which is a considerable complication.
48 
49 Therefore, we load from a single thread, and split the operation up into
50 "tasks" (as short as possible). These are typically function calls from the
51 old InitEverything(); instead of being called directly, they are registered
52 with our queue. We are called from the main loop and process as many tasks
53 as possible within one "timeslice".
54 
55 After that, progress is updated: an estimated duration for each task
56 (derived from timings on one machine) is used to calculate headway.
57 As long as task lengths only differ by a constant factor between machines,
58 this timing is exact; even if not, only smoothness of update suffers.
59 
60 
61 Interrupting Lengthy Tasks
62 --------------------------
63 
64 The above is sufficient for basic needs, but breaks down if tasks are long
65 (> 500ms). To fix this, we will need to modify the tasks themselves:
66 either make them coroutines, i.e. have them return to the main loop and then
67 resume where they left off, or re-enter a limited version of the main loop.
68 The former requires persistent state and careful implementation,
69 but yields significant advantages:
70 - progress calculation is easy and smooth,
71 - all services of the main loop (especially input*) are available, and
72 - complexity due to reentering the main loop is avoided.
73 
74 * input is important, since we want to be able to abort long loads or
75 even exit the game immediately.
76 
77 We therefore go with the 'coroutine' (more correctly 'generator') approach.
78 Examples of tasks that take so long and typical implementations may
79 be seen in MapReader.cpp.
80 
81 
82 Intended Use
83 ------------
84 
85 Replace the InitEverything() function with the following:
86  LDR_BeginRegistering();
87  LDR_Register(..) for each sub-function (*)
88  LDR_EndRegistering();
89 Then in the main loop, call LDR_ProgressiveLoad().
90 
91 * RegMemFun from LoaderThunks.h may be used instead; it takes care of
92 registering member functions, which would otherwise be messy.
93 
94 */
95 
96 
97 // NOTE: this module is not thread-safe!
98 
99 
100 // call before starting to register tasks.
101 // this routine is provided so we can prevent 2 simultaneous load operations,
102 // which is bogus. that can happen by clicking the load button quickly,
103 // or issuing via console while already loading.
104 extern void LDR_BeginRegistering();
105 
106 
107 // callback function of a task; performs the actual work.
108 // it receives a param (see below) and the exact time remaining [s].
109 //
110 // return semantics:
111 // - if the entire task was successfully completed, return 0;
112 // it will then be de-queued.
113 // - if the work can be split into smaller subtasks, process those until
114 // <time_left> is reached or exceeded and then return an estimate
115 // of progress in percent (<= 100, otherwise it's a warning;
116 // != 0, or it's treated as "finished")
117 // - on failure, return a negative error code or 'warning' (see above);
118 // LDR_ProgressiveLoad will abort immediately and return that.
119 typedef int (*LoadFunc)(void* param, double time_left);
120 
121 // register a task (later processed in FIFO order).
122 // <func>: function that will perform the actual work; see LoadFunc.
123 // <param>: (optional) parameter/persistent state; must be freed by func.
124 // <description>: user-visible description of the current task, e.g.
125 // "Loading Textures".
126 // <estimated_duration_ms>: used to calculate progress, and when checking
127 // whether there is enough of the time budget left to process this task
128 // (reduces timeslice overruns, making the main loop more responsive).
129 extern void LDR_Register(LoadFunc func, void* param, const wchar_t* description,
130  int estimated_duration_ms);
131 
132 
133 // call when finished registering tasks; subsequent calls to
134 // LDR_ProgressiveLoad will then work off the queued entries.
135 extern void LDR_EndRegistering();
136 
137 
138 // immediately cancel this load; no further tasks will be processed.
139 // used to abort loading upon user request or failure.
140 // note: no special notification will be returned by LDR_ProgressiveLoad.
141 extern void LDR_Cancel();
142 
143 
144 // process as many of the queued tasks as possible within <time_budget> [s].
145 // if a task is lengthy, the budget may be exceeded. call from the main loop.
146 //
147 // passes back a description of the next task that will be undertaken
148 // ("" if finished) and the current progress value.
149 //
150 // return semantics:
151 // - if the final load task just completed, return INFO::ALL_COMPLETE.
152 // - if loading is in progress but didn't finish, return ERR::TIMED_OUT.
153 // - if not currently loading (no-op), return 0.
154 // - any other value indicates a failure; the request has been de-queued.
155 //
156 // string interface rationale: for better interoperability, we avoid C++
157 // std::wstring and PS CStr. since the registered description may not be
158 // persistent, we can't just store a pointer. returning a pointer to
159 // our copy of the description doesn't work either, since it's freed when
160 // the request is de-queued. that leaves writing into caller's buffer.
161 extern Status LDR_ProgressiveLoad(double time_budget, wchar_t* next_description, size_t max_chars, int* progress_percent);
162 
163 // immediately process all queued load requests.
164 // returns 0 on success or a negative error code.
166 
167 
168 // boilerplate check-if-timed-out and return-progress-percent code.
169 // completed_jobs and total_jobs are ints and must be updated by caller.
170 // assumes presence of a local variable (double)<end_time>
171 // (as returned by timer_Time()) that indicates the time at which to abort.
172 #define LDR_CHECK_TIMEOUT(completed_jobs, total_jobs)\
173  if(timer_Time() > end_time)\
174  {\
175  size_t progress_percent = ((completed_jobs)*100 / (total_jobs));\
176  /* 0 means "finished", so don't return that! */\
177  if(progress_percent == 0)\
178  progress_percent = 1;\
179  ENSURE(0 < progress_percent && progress_percent <= 100);\
180  return (int)progress_percent;\
181  }
182 
183 #endif // #ifndef INCLUDED_LOADER
void LDR_Cancel()
Definition: Loader.cpp:145
void LDR_Register(LoadFunc func, void *param, const wchar_t *description, int estimated_duration_ms)
Definition: Loader.cpp:118
i64 Status
Error handling system.
Definition: status.h:171
void LDR_BeginRegistering()
Definition: Loader.cpp:101
Status LDR_NonprogressiveLoad()
Definition: Loader.cpp:308
void LDR_EndRegistering()
Definition: Loader.cpp:130
int(* LoadFunc)(void *param, double time_left)
Definition: Loader.h:119
Status LDR_ProgressiveLoad(double time_budget, wchar_t *next_description, size_t max_chars, int *progress_percent)
Definition: Loader.cpp:193