| 1 | [KEEP IN SYNC WITH LOADER.H!] |
| 2 | |
| 3 | Overview |
| 4 | -------- |
| 5 | |
| 6 | "Loading" is the act of preparing a game session, including reading all |
| 7 | required data from disk. Ideally, this would be very quick, but for complex |
| 8 | maps and/or low-end machines, a duration of several seconds can be expected. |
| 9 | Having the game freeze that long is unacceptable; instead, we want to display |
| 10 | the current progress and task, which greatly increases user patience. |
| 11 | |
| 12 | |
| 13 | Allowing for Display |
| 14 | -------------------- |
| 15 | |
| 16 | To display progress, we need to periodically 'interrupt' loading. |
| 17 | Threads come to mind, but there is a problem: since OpenGL graphics calls |
| 18 | must come from the main thread, loading would have to happen in a |
| 19 | background thread. Everything would thus need to be made thread-safe, |
| 20 | which is a considerable complication. |
| 21 | |
| 22 | Therefore, we load from a single thread, and split the operation up into |
| 23 | "tasks" (as short as possible). These are typically function calls from the |
| 24 | old !InitEverything(); instead of being called directly, they are registered |
| 25 | with our queue. We are called from the main loop and process as many tasks |
| 26 | as possible within one "timeslice". |
| 27 | |
| 28 | After that, progress is updated: an estimated duration for each task |
| 29 | (derived from timings on one machine) is used to calculate headway. |
| 30 | As long as task lengths only differ by a constant factor between machines, |
| 31 | this timing is exact; even if not, only smoothness of update suffers. |
| 32 | |
| 33 | |
| 34 | Interrupting Lengthy Tasks |
| 35 | -------------------------- |
| 36 | |
| 37 | The above is sufficient for basic needs, but breaks down if tasks are long |
| 38 | (> 500ms). To fix this, we will need to modify the tasks themselves: |
| 39 | either make them coroutines, i.e. have them return to the main loop and then |
| 40 | resume where they left off, or re-enter a limited version of the main loop. |
| 41 | The former requires persistent state and careful implementation, |
| 42 | but yields significant advantages: |
| 43 | * progress calculation is easy and smooth, |
| 44 | * all services of the main loop (especially input<sup id="fn_1_back">[wiki:#fn_1 1]</sup>) are available, and |
| 45 | * complexity due to reentering the main loop is avoided. |
| 46 | |
| 47 | We therefore go with the 'coroutine' (more correctly 'generator') approach. |
| 48 | Examples of tasks that take so long and typical implementations may |
| 49 | be seen in !MapReader.cpp. |
| 50 | |
| 51 | |
| 52 | Intended Use |
| 53 | ------------ |
| 54 | |
| 55 | Replace the !InitEverything() function with the following: |
| 56 | {{{ |
| 57 | LDR_BeginRegistering(); |
| 58 | LDR_Register(..) for each sub-function<sup id="fn_2_back">[[#fn_2|2]]</sup> |
| 59 | LDR_EndRegistering(); |
| 60 | }}} |
| 61 | Then in the main loop, call LDR_ProgressiveLoad(). |
| 62 | |
| 63 | |
| 64 | == Notes == |
| 65 | <cite id="fn_1">[wiki:#fn_1_back Note 1:] </cite> input is important, since we want to be able to abort long loads or even exit the game immediately. |
| 66 | |
| 67 | <cite id="fn_2">[wiki:#fn_2_back Note 2:] </cite> !RegMemFun from !LoaderThunks.h may be used instead; it takes care of registering member functions, which would otherwise be messy. |