2009-04-18 19:00:33 +02:00
/* Copyright (C) 2009 Wildfire Games.
* This file is part of 0 A . D .
*
* 0 A . D . is free software : you can redistribute it and / or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation , either version 2 of the License , or
* ( at your option ) any later version .
*
* 0 A . D . is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
*
* You should have received a copy of the GNU General Public License
* along with 0 A . D . If not , see < http : //www.gnu.org/licenses/>.
*/
2006-04-24 01:14:18 +02:00
// FIFO queue of load 'functors' with time limit; enables displaying
// load progress without resorting to threads (complicated).
# include "precompiled.h"
# include <deque>
# include <numeric>
2006-06-02 04:10:27 +02:00
# include "lib/timer.h"
2006-04-24 01:14:18 +02:00
# include "CStr.h"
# include "Loader.h"
2006-06-30 00:59:10 +02:00
# include "LoaderThunks.h"
2006-04-24 01:14:18 +02:00
// set by LDR_EndRegistering; may be 0 during development when
// estimated task durations haven't yet been set.
static double total_estimated_duration ;
// total time spent loading so far, set by LDR_ProgressiveLoad.
// we need a persistent counter so it can be reset after each load.
// this also accumulates less errors than:
// progress += task_estimated / total_estimated.
static double estimated_duration_tally ;
// needed for report of how long each individual task took.
static double task_elapsed_time ;
// 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);
// FIRST_LOAD is used to skip the first timeslice (see LDR_ProgressiveLoad).
static enum
{
IDLE ,
REGISTERING ,
FIRST_LOAD ,
2007-12-20 21:21:45 +01:00
LOADING
2006-04-24 01:14:18 +02:00
}
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 ;
CStrW description ;
// rationale for storing as CStrW here:
// - needs to be WCS because it's user-visible and will be translated.
// - 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 estimated_duration_ms ;
// LDR_Register gets these as parameters; pack everything together.
LoadRequest ( LoadFunc func_ , void * param_ , const wchar_t * desc_ , int ms_ )
: func ( func_ ) , param ( param_ ) , description ( desc_ ) ,
estimated_duration_ms ( ms_ )
{
}
} ;
typedef std : : deque < LoadRequest > LoadRequests ;
static LoadRequests load_requests ;
// std::accumulate binary op; used by LDR_EndRegistering to sum up all
// estimated durations (for % progress calculation)
struct DurationAdder : public std : : binary_function < double , const LoadRequest & , double >
{
double operator ( ) ( double partial_result , const LoadRequest & lr ) const
{
return partial_result + lr . estimated_duration_ms * 1e-3 ;
}
} ;
// 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.
2008-07-12 12:45:11 +02:00
void LDR_BeginRegistering ( )
2006-04-24 01:14:18 +02:00
{
2011-04-30 15:01:45 +02:00
ENSURE ( state = = IDLE ) ;
2006-04-24 01:14:18 +02:00
state = REGISTERING ;
load_requests . clear ( ) ;
}
// register a task (later processed in FIFO order).
// <func>: function that will perform the actual work; see LoadFunc.
// <param>: (optional) parameter/persistent state; must be freed by func.
// <description>: user-visible description of the current task, e.g.
// "Loading Textures".
// <estimated_duration_ms>: 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).
2008-07-12 12:45:11 +02:00
void LDR_Register ( LoadFunc func , void * param , const wchar_t * description ,
2006-04-24 01:14:18 +02:00
int estimated_duration_ms )
{
2011-04-30 15:01:45 +02:00
ENSURE ( state = = REGISTERING ) ; // must be called between LDR_(Begin|End)Register
2006-04-24 01:14:18 +02:00
const LoadRequest lr ( func , param , description , estimated_duration_ms ) ;
load_requests . push_back ( lr ) ;
}
// call when finished registering tasks; subsequent calls to
// LDR_ProgressiveLoad will then work off the queued entries.
2008-07-12 12:45:11 +02:00
void LDR_EndRegistering ( )
2006-04-24 01:14:18 +02:00
{
2011-04-30 15:01:45 +02:00
ENSURE ( state = = REGISTERING ) ;
ENSURE ( ! load_requests . empty ( ) ) ;
2006-04-24 01:14:18 +02:00
state = FIRST_LOAD ;
estimated_duration_tally = 0.0 ;
task_elapsed_time = 0.0 ;
total_estimated_duration = std : : accumulate ( load_requests . begin ( ) , load_requests . end ( ) , 0.0 , DurationAdder ( ) ) ;
}
// 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.
2008-07-12 12:45:11 +02:00
void LDR_Cancel ( )
2006-04-24 01:14:18 +02:00
{
// 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.
2008-07-12 12:45:11 +02:00
state = IDLE ;
2006-04-24 01:14:18 +02:00
}
// 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
if ( time_left < = 0.0 )
return false ;
// we haven't started a request yet this timeslice. start it even if
// it's longer than time_budget to make sure there is progress.
if ( time_left = = time_budget )
return true ;
// check next task length. we want a lengthy task to happen in its own
// timeslice so that its description is displayed beforehand.
const double estimated_duration = estimated_duration_ms * 1e-3 ;
if ( time_left + estimated_duration > time_budget * 1.20 )
return false ;
return true ;
}
// process as many of the queued tasks as possible within <time_budget> [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 current progress value.
//
// return semantics:
2006-09-22 15:19:40 +02:00
// - if the final load task just completed, return INFO::ALL_COMPLETE.
// - if loading is in progress but didn't finish, return ERR::TIMED_OUT.
2006-04-24 01:14:18 +02:00
// - 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++
// 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.
2011-05-03 14:38:42 +02:00
Status LDR_ProgressiveLoad ( double time_budget , wchar_t * description , size_t max_chars , int * progress_percent )
2006-04-24 01:14:18 +02:00
{
2011-05-03 14:38:42 +02:00
Status ret ; // single exit; this is returned
2006-04-24 01:14:18 +02:00
double progress = 0.0 ; // used to set progress_percent
double time_left = time_budget ;
// don't do any work the first time around so that a graphics update
// happens before the first (probably lengthy) timeslice.
if ( state = = FIRST_LOAD )
{
state = LOADING ;
2011-12-22 15:04:32 +01:00
ret = ERR : : TIMED_OUT ; // make caller think we did something
2006-04-24 01:14:18 +02:00
// progress already set to 0.0; that'll be passed back.
goto done ;
}
// we're called unconditionally from the main loop, so this isn't
// an error; there is just nothing to do.
if ( state ! = LOADING )
2006-09-22 15:19:40 +02:00
return INFO : : OK ;
2006-04-24 01:14:18 +02:00
while ( ! load_requests . empty ( ) )
{
// get next task; abort if there's not enough time left for it.
const LoadRequest & lr = load_requests . front ( ) ;
const double estimated_duration = lr . estimated_duration_ms * 1e-3 ;
if ( ! HaveTimeForNextTask ( time_left , time_budget , lr . estimated_duration_ms ) )
{
2011-12-22 15:04:32 +01:00
ret = ERR : : TIMED_OUT ;
2006-04-24 01:14:18 +02:00
goto done ;
}
// call this task's function and bill elapsed time.
2008-01-07 21:03:19 +01:00
const double t0 = timer_Time ( ) ;
2006-04-24 01:14:18 +02:00
int status = lr . func ( lr . param , time_left ) ;
2006-06-30 00:52:50 +02:00
const bool timed_out = ldr_was_interrupted ( status ) ;
2008-01-07 21:03:19 +01:00
const double elapsed_time = timer_Time ( ) - t0 ;
2006-04-24 01:14:18 +02:00
time_left - = elapsed_time ;
task_elapsed_time + = elapsed_time ;
// either finished entirely, or failed => remove from queue.
2006-06-30 00:52:50 +02:00
if ( ! timed_out )
2006-04-24 01:14:18 +02:00
{
2015-02-14 02:45:13 +01:00
debug_printf ( " LOADER| completed %s in %g ms; estimate was %g ms \n " , utf8_from_wstring ( lr . description ) . c_str ( ) , task_elapsed_time * 1e3 , estimated_duration * 1e3 ) ;
2006-04-24 01:14:18 +02:00
task_elapsed_time = 0.0 ;
estimated_duration_tally + = estimated_duration ;
load_requests . pop_front ( ) ;
}
// calculate progress (only possible if estimates have been given)
if ( total_estimated_duration ! = 0.0 )
{
double current_estimate = estimated_duration_tally ;
// function interrupted itself; add its estimated progress.
// note: monotonicity is guaranteed since we never add more than
// its estimated_duration_ms.
2006-06-30 00:52:50 +02:00
if ( timed_out )
2006-04-24 01:14:18 +02:00
current_estimate + = estimated_duration * status / 100.0 ;
progress = current_estimate / total_estimated_duration ;
}
// do we need to continue?
// .. function interrupted itself, i.e. timed out; abort.
2006-06-30 00:52:50 +02:00
if ( timed_out )
2006-04-24 01:14:18 +02:00
{
2011-12-22 15:04:32 +01:00
ret = ERR : : TIMED_OUT ;
2006-04-24 01:14:18 +02:00
goto done ;
}
// .. failed; abort. loading will continue when we're called in
// the next iteration of the main loop.
// rationale: bail immediately instead of remembering the first
2011-04-10 07:31:18 +02:00
// error that came up so we can report all errors that happen.
2006-04-24 01:14:18 +02:00
else if ( status < 0 )
{
2011-05-03 14:38:42 +02:00
ret = ( Status ) status ;
2006-04-24 01:14:18 +02:00
goto done ;
}
2011-12-22 15:04:32 +01:00
// .. function called LDR_Cancel; abort. return OK since this is an
// intentional cancellation, not an error.
else if ( state ! = LOADING )
{
ret = INFO : : OK ;
goto done ;
}
2006-04-24 01:14:18 +02:00
// .. succeeded; continue and process next queued task.
}
// queue is empty, we just finished.
state = IDLE ;
2006-09-22 15:19:40 +02:00
ret = INFO : : ALL_COMPLETE ;
2006-04-24 01:14:18 +02:00
// set output params (there are several return points above)
done :
* progress_percent = ( int ) ( progress * 100.0 ) ;
2011-04-30 15:01:45 +02:00
ENSURE ( 0 < = * progress_percent & & * progress_percent < = 100 ) ;
2006-04-24 01:14:18 +02:00
// we want the next task, instead of what just completed:
// it will be displayed during the next load phase.
const wchar_t * new_description = L " " ; // assume finished
if ( ! load_requests . empty ( ) )
new_description = load_requests . front ( ) . description . c_str ( ) ;
wcscpy_s ( description , max_chars , new_description ) ;
2015-02-14 02:45:13 +01:00
debug_printf ( " LOADER| returning; desc=%s progress=%d \n " , utf8_from_wstring ( description ) . c_str ( ) , * progress_percent ) ;
2006-04-24 01:14:18 +02:00
return ret ;
}
// immediately process all queued load requests.
// returns 0 on success or a negative error code.
2011-05-03 14:38:42 +02:00
Status LDR_NonprogressiveLoad ( )
2006-04-24 01:14:18 +02:00
{
const double time_budget = 100.0 ;
// large enough so that individual functions won't time out
// (that'd waste time).
wchar_t description [ 100 ] ;
int progress_percent ;
for ( ; ; )
{
2011-05-03 14:38:42 +02:00
Status ret = LDR_ProgressiveLoad ( time_budget , description , ARRAY_SIZE ( description ) , & progress_percent ) ;
2006-04-24 01:14:18 +02:00
switch ( ret )
{
2006-09-22 15:19:40 +02:00
case INFO : : OK :
2009-11-03 22:46:35 +01:00
debug_warn ( L " No load in progress " ) ;
2006-09-22 15:19:40 +02:00
return INFO : : OK ;
case INFO : : ALL_COMPLETE :
return INFO : : OK ;
case ERR : : TIMED_OUT :
2006-04-24 01:14:18 +02:00
break ; // continue loading
default :
2011-05-03 14:38:42 +02:00
WARN_RETURN_STATUS_IF_ERR ( ret ) ; // failed; complain
2006-04-24 01:14:18 +02:00
}
}
}