diff --git a/source/ps/Loader.cpp b/source/ps/Loader.cpp index 8ae2a38959..7557c4064d 100644 --- a/source/ps/Loader.cpp +++ b/source/ps/Loader.cpp @@ -95,11 +95,11 @@ int LDR_BeginRegistering() } -// register a load request (later processed in FIFO order). +// register a task (later processed in FIFO order). // : function that will perform the actual work; see LoadFunc. // : (optional) parameter/persistent state; must be freed by func. // : user-visible description of the current task, e.g. -// "Loading map". +// "Loading Textures". // : used to calculate progress, and when checking // whether there is enough of the time budget left to process this task // (reduces timeslice overruns, making the main loop more responsive). @@ -120,7 +120,7 @@ int LDR_Register(LoadFunc func, void* param, const wchar_t* description, } -// call when finished registering load requests; subsequent calls to +// call when finished registering tasks; subsequent calls to // LDR_ProgressiveLoad will then work off the queued entries. int LDR_EndRegistering() { @@ -138,8 +138,9 @@ int LDR_EndRegistering() } -// immediately cancel the load. note: no special notification will be -// returned by LDR_ProgressiveLoad. +// immediately cancel this load; no further tasks will be processed. +// used to abort loading upon user request or failure. +// note: no special notification will be returned by LDR_ProgressiveLoad. int LDR_Cancel() { // note: calling during registering doesn't make sense - that @@ -178,18 +179,16 @@ static bool HaveTimeForNextTask(double time_left, double time_budget, int estima } -// process as many of the queued load requests as possible within -// [s]. if a request is lengthy, the budget may be exceeded. -// call from the main loop. +// process as many of the queued tasks as possible within [s]. +// if a task is lengthy, the budget may be exceeded. call from the main loop. // // passes back a description of the next task that will be undertaken -// ("" if finished) and the progress value established by the -// last request to complete. +// ("" if finished) and the current progress value. // // return semantics: // - if loading just completed, return 0. // - if loading is in progress but didn't finish, return ERR_TIMED_OUT. -// - if not currently loading (no-op), return 1. +// - if not currently loading (no-op), return > 0. // - any other value indicates a failure; the request has been de-queued. // // string interface rationale: for better interoperability, we avoid C++ diff --git a/source/ps/Loader.h b/source/ps/Loader.h index f68a963507..10c01ebbaf 100644 --- a/source/ps/Loader.h +++ b/source/ps/Loader.h @@ -4,49 +4,109 @@ // Jan Wassenberg, initial implementation finished 2005-03-21 // jan@wildfiregames.com - -// intended use: -// replace the InitEverything function with the following: -// LDR_BeginRegistering() -// LDR_Register() for each sub-function (*) -// LDR_EndRegistering() -// then in the main loop, call LDR_ProgressiveLoad(). -// -// *: splitting up InitEverything is required so that control returns to -// the main loop occasionally; that allows displaying the progress. -// note that we can't interrupt loading without threads (complex). -// -// this module is not thread-safe! - +#ifndef LOADER_H_INCLUDED +#define LOADER_H_INCLUDED #include +/* -// call before starting to register load requests. +[KEEP IN SYNC WITH WIKI!] + +Overview +-------- + +"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*) are available, and +- complexity due to reentering the main loop is avoided. + +* input is important, since we want to be able to abort long loads or +even exit the game immediately. + +Examples of tasks that take so long, and typical 'coroutine' (more correcly +'generator') implementations may be seen in MapReader.cpp. + + +Intended Use +------------ + +Replace the InitEverything() function with the following: + LDR_BeginRegistering(); + LDR_Register(..) for each sub-function (*) + LDR_EndRegistering(); +Then in the main loop, call LDR_ProgressiveLoad(). + +* RegMemFun from LoaderThunks.h may be used instead; it takes care of +registering member functions, which would otherwise be messy. + +*/ + + +// NOTE: this module is not thread-safe! + + +// call before starting to register tasks. // this routine is provided so we can prevent 2 simultaneous load operations, // which is bogus. that can happen by clicking the load button quickly, // or issuing via console while already loading. extern int LDR_BeginRegistering(); -// callback function of a load request; performs the actual work. +// callback function of a task; performs the actual work. // it receives a param (see below) and the exact time remaining [s]. // // return semantics: -// - if the entire task was successfully completed, return 0: -// the load request will then be de-queued. +// - if the entire task was successfully completed, return 0; +// it will then be de-queued. // - if the work can be split into smaller subtasks, process those until // is reached or exceeded and then return an estimate -// of progress in percent (> 0 or it's treated as "finished"). +// of progress in percent (!= 0, or it's treated as "finished"). // - on failure, return a negative error code; LDR_ProgressiveLoad // will abort immediately and return that. typedef int (*LoadFunc)(void* param, double time_left); -// register a load request (later processed in FIFO order). +// register a task (later processed in FIFO order). // : function that will perform the actual work; see LoadFunc. // : (optional) parameter/persistent state; must be freed by func. // : user-visible description of the current task, e.g. -// "Loading map". +// "Loading Textures". // : used to calculate progress, and when checking // whether there is enough of the time budget left to process this task // (reduces timeslice overruns, making the main loop more responsive). @@ -54,28 +114,27 @@ extern int LDR_Register(LoadFunc func, void* param, const wchar_t* description, int estimated_duration_ms); -// call when finished registering load requests; subsequent calls to +// call when finished registering tasks; subsequent calls to // LDR_ProgressiveLoad will then work off the queued entries. extern int LDR_EndRegistering(); -// immediately cancel the load. note: no special notification will be -// returned by LDR_ProgressiveLoad. +// immediately cancel this load; no further tasks will be processed. +// used to abort loading upon user request or failure. +// note: no special notification will be returned by LDR_ProgressiveLoad. extern int LDR_Cancel(); -// process as many of the queued load requests as possible within -// [s]. if a request is lengthy, the budget may be exceeded. -// call from the main loop. +// process as many of the queued tasks as possible within [s]. +// if a task is lengthy, the budget may be exceeded. call from the main loop. // // passes back a description of the next task that will be undertaken -// ("" if finished) and the progress value established by the -// last request to complete. +// ("" if finished) and the current progress value. // // return semantics: // - if loading just completed, return 0. // - if loading is in progress but didn't finish, return ERR_TIMED_OUT. -// - if not currently loading (no-op), return 1. +// - if not currently loading (no-op), return > 0. // - any other value indicates a failure; the request has been de-queued. // // string interface rationale: for better interoperability, we avoid C++ @@ -87,7 +146,7 @@ extern int LDR_ProgressiveLoad(double time_budget, wchar_t* next_description, size_t max_chars, int* progress_percent); // immediately process all queued load requests. -// returns 0 on success, something else on failure. +// returns 0 on success or a negative error code. extern int LDR_NonprogressiveLoad(); @@ -105,3 +164,5 @@ extern int LDR_NonprogressiveLoad(); assert2(0 < progress_percent && progress_percent <= 100);\ return progress_percent;\ } + +#endif // #ifndef LOADER_H_INCLUDED