LCOV - code coverage report
Current view: top level - source/ps - Loader.cpp (source / functions) Hit Total Coverage
Test: 0 A.D. test coverage report Lines: 2 103 1.9 %
Date: 2023-01-19 00:18:29 Functions: 2 13 15.4 %

          Line data    Source code
       1             : /* Copyright (C) 2019 Wildfire Games.
       2             :  * This file is part of 0 A.D.
       3             :  *
       4             :  * 0 A.D. is free software: you can redistribute it and/or modify
       5             :  * it under the terms of the GNU General Public License as published by
       6             :  * the Free Software Foundation, either version 2 of the License, or
       7             :  * (at your option) any later version.
       8             :  *
       9             :  * 0 A.D. is distributed in the hope that it will be useful,
      10             :  * but WITHOUT ANY WARRANTY; without even the implied warranty of
      11             :  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      12             :  * GNU General Public License for more details.
      13             :  *
      14             :  * You should have received a copy of the GNU General Public License
      15             :  * along with 0 A.D.  If not, see <http://www.gnu.org/licenses/>.
      16             :  */
      17             : 
      18             : // FIFO queue of load 'functors' with time limit; enables displaying
      19             : // load progress without resorting to threads (complicated).
      20             : 
      21             : #include "precompiled.h"
      22             : 
      23             : #include <deque>
      24             : #include <numeric>
      25             : 
      26             : #include "lib/timer.h"
      27             : #include "CStr.h"
      28             : #include "Loader.h"
      29             : #include "LoaderThunks.h"
      30             : 
      31             : 
      32             : // set by LDR_EndRegistering; may be 0 during development when
      33             : // estimated task durations haven't yet been set.
      34             : static double total_estimated_duration;
      35             : 
      36             : // total time spent loading so far, set by LDR_ProgressiveLoad.
      37             : // we need a persistent counter so it can be reset after each load.
      38             : // this also accumulates less errors than:
      39             : // progress += task_estimated / total_estimated.
      40             : static double estimated_duration_tally;
      41             : 
      42             : // needed for report of how long each individual task took.
      43             : static double task_elapsed_time;
      44             : 
      45             : // main purpose is to indicate whether a load is in progress, so that
      46             : // LDR_ProgressiveLoad can return 0 iff loading just completed.
      47             : // the REGISTERING state allows us to detect 2 simultaneous loads (bogus);
      48             : // FIRST_LOAD is used to skip the first timeslice (see LDR_ProgressiveLoad).
      49             : static enum
      50             : {
      51             :     IDLE,
      52             :     REGISTERING,
      53             :     FIRST_LOAD,
      54             :     LOADING
      55             : }
      56             : state = IDLE;
      57             : 
      58             : 
      59             : // holds all state for one load request; stored in queue.
      60           0 : struct LoadRequest
      61             : {
      62             :     // member documentation is in LDR_Register (avoid duplication).
      63             : 
      64             :     LoadFunc func;
      65             : 
      66             :     // MemFun_t<T> instance
      67             :     std::shared_ptr<void> param;
      68             : 
      69             :     // Translatable string shown to the player.
      70             :     CStrW description;
      71             : 
      72             :     int estimated_duration_ms;
      73             : 
      74             :     // LDR_Register gets these as parameters; pack everything together.
      75           0 :     LoadRequest(LoadFunc func_, std::shared_ptr<void> param_, const wchar_t* desc_, int ms_)
      76           0 :         : func(func_), param(param_), description(desc_),
      77           0 :           estimated_duration_ms(ms_)
      78             :     {
      79           0 :     }
      80             : };
      81             : 
      82             : typedef std::deque<LoadRequest> LoadRequests;
      83           1 : static LoadRequests load_requests;
      84             : 
      85             : // call before starting to register load requests.
      86             : // this routine is provided so we can prevent 2 simultaneous load operations,
      87             : // which is bogus. that can happen by clicking the load button quickly,
      88             : // or issuing via console while already loading.
      89           0 : void LDR_BeginRegistering()
      90             : {
      91           0 :     ENSURE(state == IDLE);
      92             : 
      93           0 :     state = REGISTERING;
      94           0 :     load_requests.clear();
      95           0 : }
      96             : 
      97             : 
      98             : // register a task (later processed in FIFO order).
      99             : // <func>: function that will perform the actual work; see LoadFunc.
     100             : // <param>: (optional) parameter/persistent state.
     101             : // <description>: user-visible description of the current task, e.g.
     102             : //   "Loading Textures".
     103             : // <estimated_duration_ms>: used to calculate progress, and when checking
     104             : //   whether there is enough of the time budget left to process this task
     105             : //   (reduces timeslice overruns, making the main loop more responsive).
     106           0 : void LDR_Register(LoadFunc func, std::shared_ptr<void> param, const wchar_t* description,
     107             :     int estimated_duration_ms)
     108             : {
     109           0 :     ENSURE(state == REGISTERING);   // must be called between LDR_(Begin|End)Register
     110             : 
     111           0 :     const LoadRequest lr(func, param, description, estimated_duration_ms);
     112           0 :     load_requests.push_back(lr);
     113           0 : }
     114             : 
     115             : 
     116             : // call when finished registering tasks; subsequent calls to
     117             : // LDR_ProgressiveLoad will then work off the queued entries.
     118           0 : void LDR_EndRegistering()
     119             : {
     120           0 :     ENSURE(state == REGISTERING);
     121           0 :     ENSURE(!load_requests.empty());
     122             : 
     123           0 :     state = FIRST_LOAD;
     124           0 :     estimated_duration_tally = 0.0;
     125           0 :     task_elapsed_time = 0.0;
     126           0 :     total_estimated_duration = std::accumulate(load_requests.begin(), load_requests.end(), 0.0,
     127           0 :         [](double partial_result, const LoadRequest& lr) -> double { return partial_result + lr.estimated_duration_ms * 1e-3; });
     128           0 : }
     129             : 
     130             : 
     131             : // immediately cancel this load; no further tasks will be processed.
     132             : // used to abort loading upon user request or failure.
     133             : // note: no special notification will be returned by LDR_ProgressiveLoad.
     134           0 : void LDR_Cancel()
     135             : {
     136             :     // the queue doesn't need to be emptied now; that'll happen during the
     137             :     // next LDR_StartRegistering. for now, it is sufficient to set the
     138             :     // state, so that LDR_ProgressiveLoad is a no-op.
     139           0 :     state = IDLE;
     140           0 : }
     141             : 
     142             : // helper routine for LDR_ProgressiveLoad.
     143             : // tries to prevent starting a long task when at the end of a timeslice.
     144           0 : static bool HaveTimeForNextTask(double time_left, double time_budget, int estimated_duration_ms)
     145             : {
     146             :     // have already exceeded our time budget
     147           0 :     if(time_left <= 0.0)
     148           0 :         return false;
     149             : 
     150             :     // we haven't started a request yet this timeslice. start it even if
     151             :     // it's longer than time_budget to make sure there is progress.
     152           0 :     if(time_left == time_budget)
     153           0 :         return true;
     154             : 
     155             :     // check next task length. we want a lengthy task to happen in its own
     156             :     // timeslice so that its description is displayed beforehand.
     157           0 :     const double estimated_duration = estimated_duration_ms*1e-3;
     158           0 :     if(time_left+estimated_duration > time_budget*1.20)
     159           0 :         return false;
     160             : 
     161           0 :     return true;
     162             : }
     163             : 
     164             : 
     165             : // process as many of the queued tasks as possible within <time_budget> [s].
     166             : // if a task is lengthy, the budget may be exceeded. call from the main loop.
     167             : //
     168             : // passes back a description of the next task that will be undertaken
     169             : // ("" if finished) and the current progress value.
     170             : //
     171             : // return semantics:
     172             : // - if the final load task just completed, return INFO::ALL_COMPLETE.
     173             : // - if loading is in progress but didn't finish, return ERR::TIMED_OUT.
     174             : // - if not currently loading (no-op), return 0.
     175             : // - any other value indicates a failure; the request has been de-queued.
     176             : //
     177             : // string interface rationale: for better interoperability, we avoid C++
     178             : // std::wstring and PS CStr. since the registered description may not be
     179             : // persistent, we can't just store a pointer. returning a pointer to
     180             : // our copy of the description doesn't work either, since it's freed when
     181             : // the request is de-queued. that leaves writing into caller's buffer.
     182           0 : Status LDR_ProgressiveLoad(double time_budget, wchar_t* description, size_t max_chars, int* progress_percent)
     183             : {
     184             :     Status ret; // single exit; this is returned
     185           0 :     double progress = 0.0;  // used to set progress_percent
     186           0 :     double time_left = time_budget;
     187             : 
     188             :     // don't do any work the first time around so that a graphics update
     189             :     // happens before the first (probably lengthy) timeslice.
     190           0 :     if(state == FIRST_LOAD)
     191             :     {
     192           0 :         state = LOADING;
     193             : 
     194           0 :         ret = ERR::TIMED_OUT;   // make caller think we did something
     195             :         // progress already set to 0.0; that'll be passed back.
     196           0 :         goto done;
     197             :     }
     198             : 
     199             :     // we're called unconditionally from the main loop, so this isn't
     200             :     // an error; there is just nothing to do.
     201           0 :     if(state != LOADING)
     202           0 :         return INFO::OK;
     203             : 
     204           0 :     while(!load_requests.empty())
     205             :     {
     206             :         // get next task; abort if there's not enough time left for it.
     207           0 :         const LoadRequest& lr = load_requests.front();
     208           0 :         const double estimated_duration = lr.estimated_duration_ms*1e-3;
     209           0 :         if(!HaveTimeForNextTask(time_left, time_budget, lr.estimated_duration_ms))
     210             :         {
     211           0 :             ret = ERR::TIMED_OUT;
     212           0 :             goto done;
     213             :         }
     214             : 
     215             :         // call this task's function and bill elapsed time.
     216           0 :         const double t0 = timer_Time();
     217           0 :         int status = lr.func(lr.param, time_left);
     218           0 :         const bool timed_out = ldr_was_interrupted(status);
     219           0 :         const double elapsed_time = timer_Time() - t0;
     220           0 :         time_left -= elapsed_time;
     221           0 :         task_elapsed_time += elapsed_time;
     222             : 
     223             :         // either finished entirely, or failed => remove from queue.
     224           0 :         if(!timed_out)
     225             :         {
     226           0 :             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);
     227           0 :             task_elapsed_time = 0.0;
     228           0 :             estimated_duration_tally += estimated_duration;
     229           0 :             load_requests.pop_front();
     230             :         }
     231             : 
     232             :         // calculate progress (only possible if estimates have been given)
     233           0 :         if(total_estimated_duration != 0.0)
     234             :         {
     235           0 :             double current_estimate = estimated_duration_tally;
     236             : 
     237             :             // function interrupted itself; add its estimated progress.
     238             :             // note: monotonicity is guaranteed since we never add more than
     239             :             //   its estimated_duration_ms.
     240           0 :             if(timed_out)
     241           0 :                 current_estimate += estimated_duration * status/100.0;
     242             : 
     243           0 :             progress = current_estimate / total_estimated_duration;
     244             :         }
     245             : 
     246             :         // do we need to continue?
     247             :         // .. function interrupted itself, i.e. timed out; abort.
     248           0 :         if(timed_out)
     249             :         {
     250           0 :             ret = ERR::TIMED_OUT;
     251           0 :             goto done;
     252             :         }
     253             :         // .. failed; abort. loading will continue when we're called in
     254             :         //    the next iteration of the main loop.
     255             :         //    rationale: bail immediately instead of remembering the first
     256             :         //    error that came up so we can report all errors that happen.
     257           0 :         else if(status < 0)
     258             :         {
     259           0 :             ret = (Status)status;
     260           0 :             goto done;
     261             :         }
     262             :         // .. function called LDR_Cancel; abort. return OK since this is an
     263             :         //    intentional cancellation, not an error.
     264           0 :         else if(state != LOADING)
     265             :         {
     266           0 :             ret = INFO::OK;
     267           0 :             goto done;
     268             :         }
     269             :         // .. succeeded; continue and process next queued task.
     270             :     }
     271             : 
     272             :     // queue is empty, we just finished.
     273           0 :     state = IDLE;
     274           0 :     ret = INFO::ALL_COMPLETE;
     275             : 
     276             : 
     277             :     // set output params (there are several return points above)
     278           0 : done:
     279           0 :     *progress_percent = (int)(progress * 100.0);
     280           0 :     ENSURE(0 <= *progress_percent && *progress_percent <= 100);
     281             : 
     282             :     // we want the next task, instead of what just completed:
     283             :     // it will be displayed during the next load phase.
     284           0 :     const wchar_t* new_description = L""; // assume finished
     285           0 :     if(!load_requests.empty())
     286           0 :         new_description = load_requests.front().description.c_str();
     287           0 :     wcscpy_s(description, max_chars, new_description);
     288             : 
     289           0 :     debug_printf("LOADER| returning; desc=%s progress=%d\n", utf8_from_wstring(description).c_str(), *progress_percent);
     290             : 
     291           0 :     return ret;
     292             : }
     293             : 
     294             : 
     295             : // immediately process all queued load requests.
     296             : // returns 0 on success or a negative error code.
     297           0 : Status LDR_NonprogressiveLoad()
     298             : {
     299           0 :     const double time_budget = 100.0;
     300             :         // large enough so that individual functions won't time out
     301             :         // (that'd waste time).
     302             :     wchar_t description[100];
     303             :     int progress_percent;
     304             : 
     305             :     for(;;)
     306             :     {
     307           0 :         Status ret = LDR_ProgressiveLoad(time_budget, description, ARRAY_SIZE(description), &progress_percent);
     308           0 :         switch(ret)
     309             :         {
     310           0 :         case INFO::OK:
     311           0 :             debug_warn(L"No load in progress");
     312           0 :             return INFO::OK;
     313           0 :         case INFO::ALL_COMPLETE:
     314           0 :             return INFO::OK;
     315           0 :         case ERR::TIMED_OUT:
     316           0 :             break;          // continue loading
     317           0 :         default:
     318           0 :             WARN_RETURN_STATUS_IF_ERR(ret); // failed; complain
     319             :         }
     320           0 :     }
     321           3 : }

Generated by: LCOV version 1.13