[KEEP IN SYNC WITH LOADER.H!]
"Loading" is the act of preparing a game session, including reading all required data from disk. Ideally, this would be very quick, but for complex maps and/or low-end machines, a duration of several seconds can be expected. Having the game freeze that long is unacceptable; instead, we want to display the current progress and task, which greatly increases user patience.
Allowing for Display
To display progress, we need to periodically 'interrupt' loading. Threads come to mind, but there is a problem: since OpenGL graphics calls must come from the main thread, loading would have to happen in a background thread. Everything would thus need to be made thread-safe, which is a considerable complication.
Therefore, we load from a single thread, and split the operation up into "tasks" (as short as possible). These are typically function calls from the old InitEverything(); instead of being called directly, they are registered with our queue. We are called from the main loop and process as many tasks as possible within one "timeslice".
After that, progress is updated: an estimated duration for each task (derived from timings on one machine) is used to calculate headway. As long as task lengths only differ by a constant factor between machines, this timing is exact; even if not, only smoothness of update suffers.
Interrupting Lengthy Tasks
The above is sufficient for basic needs, but breaks down if tasks are long (> 500ms). To fix this, we will need to modify the tasks themselves: either make them coroutines, i.e. have them return to the main loop and then resume where they left off, or re-enter a limited version of the main loop. The former requires persistent state and careful implementation, but yields significant advantages:
- progress calculation is easy and smooth,
- all services of the main loop (especially input (1)) are available, and
- complexity due to reentering the main loop is avoided.
We therefore go with the 'coroutine' (more correctly 'generator') approach. Examples of tasks that take so long and typical implementations may be seen in MapReader.cpp.
Replace the InitEverything() function with the following:
LDR_BeginRegistering(); LDR_Register(..) for each sub-function (2) LDR_EndRegistering();
Then in the main loop, call LDR_ProgressiveLoad().
- input is important, since we want to be able to abort long loads or even exit the game immediately.
- RegMemFun from LoaderThunks.h may be used instead; it takes care of registering member functions, which would otherwise be messy.