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