Index: ps/trunk/source/ps/Loader.cpp =================================================================== --- ps/trunk/source/ps/Loader.cpp (nonexistent) +++ ps/trunk/source/ps/Loader.cpp (revision 2027) @@ -0,0 +1,222 @@ +#include "precompiled.h" + +#include + +#include "lib.h" // error codes +#include "timer.h" +#include "loader.h" +#include "CStr.h" + +// note: not thread-safe! + + +// need to maintain this counter ourselves so we can reset after each load. +static int progress_percent = 0; + +// main purpose is to indicate whether a load is in progress, so that +// LDR_ProgressiveLoad can return 0 iff loading just completed. +// the REGISTERING state allows us to detect 2 simultaneous loads (bogus). +static enum +{ + IDLE, + REGISTERING, + LOADING, +} +state = IDLE; + + +// holds all state for one load request; stored in queue. +struct LoadRequest +{ + // member documentation is in LDR_Register (avoid duplication). + + LoadFunc func; + void* param; + + const CStrW description; + // rationale: + // - don't just store a pointer - the caller's string may be volatile. + // - the module interface must work in C, so we get/set as wchar_t*. + + int progress_percent_after_completion; + + int estimated_duration_ms; + + // LDR_Register gets these as parameters; pack everything together. + LoadRequest(LoadFunc func_, void* param_, const wchar_t* desc_, int pc_, int ms_) + : func(func_), param(param_), description(desc_), + progress_percent_after_completion(pc_), estimated_duration_ms(ms_) + { + } +}; + +typedef std::deque LoadRequests; +static LoadRequests load_requests; + + +// call before starting to register load requests. +// 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. +int LDR_BeginRegistering() +{ + if(state != IDLE) + return -1; + + state = REGISTERING; + load_requests.clear(); + return 0; +} + + +// register a load request (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". +// : optional; if non-zero, progress is +// set to this value after the current task completes. +// must increase monotonically. +// : optional; if non-zero, this task will be +// postponed until the next LDR_ProgressiveLoad timeslice if there's not +// much time left. this reduces overruns of the timeslice => main loop is +// more responsive. +int LDR_Register(LoadFunc func, void* param, const wchar_t* description, + int progress_percent_after_completion, int estimated_duration_ms) +{ + if(state != REGISTERING) + return -1; + + const LoadRequest lr(func, param, description, + progress_percent_after_completion, estimated_duration_ms); + load_requests.push_back(lr); + return 0; +} + + +// call when finished registering load requests; subsequent calls to +// LDR_ProgressiveLoad will then work off the queued entries. +int LDR_EndRegistering() +{ + if(state != REGISTERING) + return -1; + + state = LOADING; + progress_percent = 0; + return 0; +} + + +// immediately cancel the load. note: no special notification will be +// returned by LDR_ProgressiveLoad. +int LDR_Cancel() +{ + // note: calling during registering doesn't make sense - that + // should be an atomic sequence of begin, register [..], end. + if(state != LOADING) + return -1; + + state = IDLE; + // the queue doesn't need to be emptied now; that'll happen during the + // next LDR_StartRegistering. for now, it is sufficient to set the + // state, so that LDR_ProgressiveLoad is a no-op. + return 0; +} + + +// helper routine for LDR_ProgressiveLoad. +// tries to prevent starting a long task when at the end of a timeslice. +static bool HaveTimeForNextTask(double time_left, double time_budget, int estimated_duration_ms) +{ + // have already exceeded our time budget => stop for now. + if(time_left <= 0.0) + return false; + + // we've already used up more than 60% + // (if it's less than that, we won't check the next task length) + if(time_left < 0.40*time_budget) + { + const double estimated_duration = estimated_duration_ms * 1e-3; + // .. next task is expected to be long - do it next call + if(estimated_duration > time_left + time_budget*0.20) + return false; + } + + return true; +} + + +// 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. +// +// passes back a description of the last task undertaken and the progress +// value established by the last request to complete. +// +// 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. +// - any other value indicates a failure; the request has been de-queued. +// +// string interface rationale: for better interoperability, we avoid C++ +// std::wstring and PS CStr. since the registered description may not be +// persistent, we can't just store a pointer. returning a pointer to +// our copy of the description doesn't work either, since it's freed when +// the request is de-queued. that leaves writing into caller's buffer. +int LDR_ProgressiveLoad(double time_budget, wchar_t* current_description, + size_t max_chars, int* progress_percent_) +{ + // we're called unconditionally from the main loop, so this isn't + // an error; there is just nothing to do. + if(state != LOADING) + return 1; + + const double end_time = get_time() + time_budget; + + // in case it's never set below (because all LoadRequests have + // progress_percent_after_completion = 0) + *progress_percent_ = progress_percent; + + // (function will return immediately on failure or timeout) + while(!load_requests.empty()) + { + const double time_left = end_time - get_time(); + const LoadRequest& lr = load_requests.front(); + + // latch description of the current task now (it may be removed below) + wcscpy_s(current_description, max_chars, lr.description); + + // do actual work of loading + int ret = lr.func(lr.param, time_left); + // .. either finished entirely, or failed => remove from queue + if(ret != ERR_TIMED_OUT) + load_requests.pop_front(); + // .. failed or timed out => abort immediately; loading will + // continue when we're called in the next iteration of the main loop. + // rationale: bail immediately instead of remembering the first error + // that came up, so that we report can all errors that happen. + if(ret != 0) + return ret; + // .. completed normally: + + // update progress + const int new_pc = lr.progress_percent_after_completion; + if(new_pc) + { + assert(new_pc > progress_percent); + *progress_percent_ = progress_percent = new_pc; + } + + // check if we're out of time; take into account next task length. + // note: do this at the end of the loop to make sure there's + // progress even if the timer is low-resolution (=> time_left = 0). + if(!HaveTimeForNextTask(time_left, time_budget, lr.estimated_duration_ms)) + return ERR_TIMED_OUT; + } + + // queue is empty, we just finished. + state = IDLE; + assert(progress_percent == 100); + return 0; +} Index: ps/trunk/source/ps/Loader.h =================================================================== --- ps/trunk/source/ps/Loader.h (nonexistent) +++ ps/trunk/source/ps/Loader.h (revision 2027) @@ -0,0 +1,65 @@ +#include + + +// call before starting to register load requests. +// 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. +// it receives a param (see below) and the exact time remaining [s]. +// +// return semantics: +// - if the work can be split into smaller subtasks, process those until +// is reached or exceeded and then return ERR_TIMED_OUT. +// - if the entire task was successfully completed, return 0: +// the load request will then be de-queued. +// - any other return value indicates failure and causes +// LDR_ProgressiveLoad to immediately abort and return that. +typedef int (*LoadFunc)(void* param, double time_left); + +// register a load request (later processed in FIFO order). +// : function that will perform the actual work; see LoadFunc above. +// : (optional) parameter/persistent state; must be freed by func. +// : user-visible description of the current task, e.g. +// "Loading map". +// : optional; if non-zero, progress is +// set to this value after the current task completes. +// must increase monotonically. +// : optional; if non-zero, this task will be +// postponed until the next LDR_ProgressiveLoad timeslice if there's not +// much time left. this reduces overruns of the timeslice => main loop is +// more responsive. +extern int LDR_Register(LoadFunc func, void* param, const wchar_t* description, + int progress_percent_after_completion = 0, int estimated_duration_ms = 0); + +// call when finished registering load requests; 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. +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. +// +// passes back a description of the last task undertaken and the progress +// value established by the last request to complete. +// +// 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. +// - any other value indicates a failure; the request has been de-queued. +// +// string interface rationale: for better interoperability, we avoid C++ +// std::wstring and PS CStr. since the registered description may not be +// persistent, we can't just store a pointer. returning a pointer to +// our copy of the description doesn't work either, since it's freed when +// the request is de-queued. that leaves writing into caller's buffer. +extern int LDR_ProgressiveLoad(double time_budget, wchar_t* current_description, + size_t max_chars, int* progress_percent);