Team Fortress 2 Source Code as on 22/4/2020
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1978 lines
61 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Queued Loading of map resources. !!!!Specifically!!! designed for the map loading process.
  4. //
  5. // Not designed for application startup or gameplay time. Layered on top of async i/o.
  6. // Queued loading is allowed during the map load process until full connection only,
  7. // but can complete the remaining low priority jobs during the game render.
  8. // The normal loading path can run in units of seconds if it does not have to do I/O,
  9. // which is why this system runs first and gets all the data in memory unhindered
  10. // by dependency blocking. The I/O delivery process achieves its speed by having all the I/O
  11. // requests at once, performing the I/O, and handing the actual consumption
  12. // of the I/O buffer to another available core/thread (via job pool) for computation work.
  13. // The I/O (should be all unbuffered) is then only throttled by physical transfer rates.
  14. //
  15. // The Load process is broken into three phases. The first phase build up I/O requests.
  16. // The second phase fulfills only the high priority I/O requests. This gets the critical
  17. // data in memory, that has to be there for the normal load path to query, or the renderer
  18. // to run (i.e. models and shaders). The third phase is the normal load process.
  19. // The low priority jobs run concurrently with the normal load process. Low priority jobs
  20. // are those that have been specially built such that the game or loading can operate unblocked
  21. // without the actual data (i.e. d3d texture bits).
  22. //
  23. // Phase 1: The reslist is parsed into seperate lists based on handled extensions. Each list
  24. // call its own loader which in turn generates its own dictionaries and I/O requests through
  25. // "AddJob". A single reslist entry could cause a laoder to request multiple jobs. ( i.e. models )
  26. // A loader marks its jobs as high or low priority.
  27. // Phase 2: The I/O requests are sorted (which achieves seek offset order) and
  28. // async i/o commences. Phase 2 does not end until all the high priority jobs
  29. // are complete. This ensures critical data is resident.
  30. // Phase 3: The !!!NORMAL!!! loading path can commence. The legacy loading path then
  31. // is not expected to do I/O (it can, but that's a hole in the reslist), as all of the data
  32. // that it queries, should be resident.
  33. //
  34. // Late added jobs are non-optimal (should have been in reslist), warned, but handled.
  35. //
  36. //===========================================================================//
  37. #include "basefilesystem.h"
  38. #include "tier0/vprof.h"
  39. #include "tier0/tslist.h"
  40. #include "tier1/utlbuffer.h"
  41. #include "tier1/convar.h"
  42. #include "tier1/KeyValues.h"
  43. #include "tier1/utllinkedlist.h"
  44. #include "tier1/utlstring.h"
  45. #include "tier1/UtlSortVector.h"
  46. #include "tier1/utldict.h"
  47. #include "basefilesystem.h"
  48. #include "tier0/icommandline.h"
  49. #include "vstdlib/jobthread.h"
  50. #include "filesystem/IQueuedLoader.h"
  51. #include "tier2/tier2.h"
  52. #include "characterset.h"
  53. #if !defined( _X360 )
  54. #include "xbox/xboxstubs.h"
  55. #endif
  56. // memdbgon must be the last include file in a .cpp file!!!
  57. #include "tier0/memdbgon.h"
  58. #define PRIORITY_HIGH 1
  59. #define PRIORITY_NORMAL 0
  60. #define PRIORITY_LOW -1
  61. // main thread has reason to block and wait for thread pool to finish jobs
  62. #define MAIN_THREAD_YIELD_TIME 20
  63. // discrete stages in the preload process to tick the progress bar
  64. #define PROGRESS_START 0.10f
  65. #define PROGRESS_GOTRESLIST 0.12f
  66. #define PROGRESS_PARSEDRESLIST 0.15f
  67. #define PROGRESS_CREATEDRESOURCES 0.20f
  68. #define PROGRESS_PREPURGE 0.22f
  69. #define PROGRESS_IO 0.25f // up to 1.0
  70. struct FileJob_t
  71. {
  72. FileJob_t()
  73. {
  74. Q_memset( this, 0, sizeof( FileJob_t ) );
  75. }
  76. FileNameHandle_t m_hFilename;
  77. QueuedLoaderCallback_t m_pCallback;
  78. FSAsyncControl_t m_hAsyncControl;
  79. void *m_pContext;
  80. void *m_pContext2;
  81. void *m_pTargetData;
  82. int m_nBytesToRead;
  83. unsigned int m_nStartOffset;
  84. LoaderPriority_t m_Priority;
  85. unsigned int m_SubmitTime;
  86. unsigned int m_FinishTime;
  87. int m_SubmitTag;
  88. int m_nActualBytesRead;
  89. LoaderError_t m_LoaderError;
  90. unsigned int m_ThreadId;
  91. unsigned int m_bFinished : 1;
  92. unsigned int m_bFreeTargetAfterIO : 1;
  93. unsigned int m_bFileExists : 1;
  94. unsigned int m_bClaimed : 1;
  95. };
  96. // dummy stubbed progress interface
  97. class CDummyProgress : public ILoaderProgress
  98. {
  99. void BeginProgress() {}
  100. void UpdateProgress( float progress ) {}
  101. void EndProgress() {}
  102. };
  103. static CDummyProgress s_DummyProgress;
  104. class CQueuedLoader : public CTier2AppSystem< IQueuedLoader >
  105. {
  106. typedef CTier2AppSystem< IQueuedLoader > BaseClass;
  107. public:
  108. CQueuedLoader();
  109. virtual ~CQueuedLoader();
  110. // Inherited from IAppSystem
  111. virtual InitReturnVal_t Init();
  112. virtual void Shutdown();
  113. // IQueuedLoader
  114. virtual void InstallLoader( ResourcePreload_t type, IResourcePreload *pLoader );
  115. virtual void InstallProgress( ILoaderProgress *pProgress );
  116. // Set bOptimizeReload if you want appropriate data (such as static prop lighting)
  117. // to persist - rather than being purged and reloaded - when going from map A to map A.
  118. virtual bool BeginMapLoading( const char *pMapName, bool bLoadForHDR, bool bOptimizeMapReload );
  119. virtual void EndMapLoading( bool bAbort );
  120. virtual bool AddJob( const LoaderJob_t *pLoaderJob );
  121. virtual void AddMapResource( const char *pFilename );
  122. virtual void DynamicLoadMapResource( const char *pFilename, DynamicResourceCallback_t pCallback, void *pContext, void *pContext2 );
  123. virtual void QueueDynamicLoadFunctor( CFunctor* pFunctor );
  124. virtual bool CompleteDynamicLoad();
  125. virtual void QueueCleanupDynamicLoadFunctor( CFunctor* pFunctor );
  126. virtual bool CleanupDynamicLoad();
  127. virtual bool ClaimAnonymousJob( const char *pFilename, QueuedLoaderCallback_t pCallback, void *pContext, void *pContext2 );
  128. virtual bool ClaimAnonymousJob( const char *pFilename, void **pData, int *pDataSize, LoaderError_t *pError );
  129. virtual bool IsMapLoading() const;
  130. virtual bool IsSameMapLoading() const;
  131. virtual bool IsFinished() const;
  132. virtual bool IsBatching() const;
  133. virtual bool IsDynamic() const;
  134. virtual int GetSpewDetail() const;
  135. char *GetFilename( const FileNameHandle_t hFilename, char *pBuff, int nBuffSize );
  136. FileNameHandle_t FindFilename( const char *pFilename );
  137. void SpewInfo();
  138. // submit any queued jobs to the async loader, called by main or async thread to get more work
  139. void SubmitPendingJobs();
  140. void PurgeAll();
  141. private:
  142. class CFileJobsLessFunc
  143. {
  144. public:
  145. int GetLayoutOrderForFilename( const char *pFilename );
  146. bool Less( FileJob_t* const &pFileJobLHS, FileJob_t* const &pFileJobRHS, void *pCtx );
  147. };
  148. class CResourceNameLessFunc
  149. {
  150. public:
  151. bool Less( const FileNameHandle_t &hFilenameLHS, const FileNameHandle_t &hFilenameRHS, void *pCtx );
  152. };
  153. typedef CUtlSortVector< FileNameHandle_t, CResourceNameLessFunc > ResourceList_t;
  154. static void BuildResources( IResourcePreload *pLoader, ResourceList_t *pList, float *pBuildTime );
  155. static void BuildMaterialResources( IResourcePreload *pLoader, ResourceList_t *pList, float *pBuildTime );
  156. void PurgeQueue();
  157. void CleanQueue();
  158. void SubmitBatchedJobs();
  159. void SubmitBatchedJobsAndWait();
  160. void ParseResourceList( CUtlBuffer &resourceList );
  161. void GetJobRequests();
  162. void PurgeUnreferencedResources();
  163. void AddResourceToTable( const char *pFilename );
  164. bool m_bStarted;
  165. bool m_bActive;
  166. bool m_bBatching;
  167. bool m_bDynamic;
  168. bool m_bCanBatch;
  169. bool m_bLoadForHDR;
  170. bool m_bDoProgress;
  171. bool m_bSameMap;
  172. int m_nSubmitCount;
  173. unsigned int m_StartTime;
  174. unsigned int m_EndTime;
  175. char m_szMapNameToCompareSame[MAX_PATH];
  176. DynamicResourceCallback_t m_pfnDynamicCallback;
  177. CUtlString m_DynamicFileName;
  178. void* m_pDynamicContext;
  179. void* m_pDynamicContext2;
  180. CThreadFastMutex m_FunctorQueueMutex;
  181. CUtlVector< CFunctor* > m_FunctorQueue;
  182. CUtlVector< CFunctor* > m_CleanupFunctorQueue;
  183. CUtlFilenameSymbolTable m_Filenames;
  184. CTSList< FileJob_t* > m_PendingJobs;
  185. CTSList< FileJob_t* > m_BatchedJobs;
  186. CUtlLinkedList< FileJob_t* > m_SubmittedJobs;
  187. CUtlDict< FileJob_t*, int > m_AnonymousJobs;
  188. CUtlSymbolTable m_AdditionalResources;
  189. CUtlSortVector< FileNameHandle_t, CResourceNameLessFunc > m_ResourceNames[RESOURCEPRELOAD_COUNT];
  190. IResourcePreload *m_pLoaders[RESOURCEPRELOAD_COUNT];
  191. float m_LoaderTimes[RESOURCEPRELOAD_COUNT];
  192. ILoaderProgress *m_pProgress;
  193. CThreadFastMutex m_Mutex;
  194. };
  195. static CQueuedLoader g_QueuedLoader;
  196. EXPOSE_SINGLE_INTERFACE_GLOBALVAR( CQueuedLoader, IQueuedLoader, QUEUEDLOADER_INTERFACE_VERSION, g_QueuedLoader );
  197. class CResourcePreloadAnonymous : public IResourcePreload
  198. {
  199. virtual bool CreateResource( const char *pName )
  200. {
  201. // create an anonymous job to get the data in memory, claimed during load, or auto-freed
  202. LoaderJob_t loaderJob;
  203. loaderJob.m_pFilename = pName;
  204. loaderJob.m_pPathID = "GAME";
  205. loaderJob.m_Priority = LOADERPRIORITY_DURINGPRELOAD;
  206. g_QueuedLoader.AddJob( &loaderJob );
  207. return true;
  208. }
  209. virtual void PurgeUnreferencedResources() {}
  210. virtual void OnEndMapLoading( bool bAbort ) {}
  211. virtual void PurgeAll() {}
  212. };
  213. static CResourcePreloadAnonymous s_ResourcePreloadAnonymous;
  214. const char *g_ResourceLoaderNames[RESOURCEPRELOAD_COUNT] =
  215. {
  216. "???", // RESOURCEPRELOAD_UNKNOWN
  217. "Sounds", // RESOURCEPRELOAD_SOUND
  218. "Materials", // RESOURCEPRELOAD_MATERIAL
  219. "Models", // RESOURCEPRELOAD_MODEL
  220. "Cubemaps", // RESOURCEPRELOAD_CUBEMAP
  221. "PropLighting", // RESOURCEPRELOAD_STATICPROPLIGHTING
  222. "Anonymous", // RESOURCEPRELOAD_ANONYMOUS
  223. };
  224. static CInterlockedInt g_nActiveJobs;
  225. static CInterlockedInt g_nQueuedJobs;
  226. static CInterlockedInt g_nHighPriorityJobs; // tracks jobs that must finish during preload
  227. static CInterlockedInt g_nJobsToFinishBeforePlay; // tracks jobs that must finish before gameplay
  228. static CInterlockedInt g_nIOMemory; // tracks I/O data from async delivery until consumed
  229. static CInterlockedInt g_nAnonymousIOMemory; // tracks anonymous I/O data from async delivery until consumed
  230. static CInterlockedInt g_SuspendIO; // used to throttle the I/O
  231. static int g_nIOMemoryPeak;
  232. static int g_nAnonymousIOMemoryPeak;
  233. static int g_nHighIOSuspensionMark;
  234. static int g_nLowIOSuspensionMark;
  235. ConVar loader_spew_info( "loader_spew_info", "0", 0, "0:Off, 1:Timing, 2:Completions, 3:Late Completions, 4:Purges, -1:All " );
  236. // Kyle says: this is here only to change the DLL size to force clients to update! This should be removed
  237. // by whoever sees this comment after we've shipped a DLL using it!
  238. ConVar loader_sped_info_ex( "loader_spew_info_ex", "0", 0, "(internal)" );
  239. CON_COMMAND( loader_dump_table, "" )
  240. {
  241. g_QueuedLoader.SpewInfo();
  242. }
  243. //-----------------------------------------------------------------------------
  244. // Constructor
  245. //-----------------------------------------------------------------------------
  246. CQueuedLoader::CQueuedLoader() : BaseClass( false )
  247. {
  248. m_bStarted = false;
  249. m_bActive = false;
  250. m_bBatching = false;
  251. m_bDynamic = false;
  252. m_bCanBatch = false;
  253. m_bLoadForHDR = false;
  254. m_bDoProgress = false;
  255. m_bSameMap = false;
  256. m_nSubmitCount = 0;
  257. m_pfnDynamicCallback = NULL;
  258. m_pDynamicContext = NULL;
  259. m_pDynamicContext2 = NULL;
  260. m_szMapNameToCompareSame[0] = '\0';
  261. m_pProgress = &s_DummyProgress;
  262. V_memset( m_pLoaders, 0, sizeof( m_pLoaders ) );
  263. // set resource dictionaries sort context
  264. for ( int i = 0; i < RESOURCEPRELOAD_COUNT; i++ )
  265. {
  266. m_ResourceNames[i].SetLessContext( (void *)i );
  267. }
  268. InstallLoader( RESOURCEPRELOAD_ANONYMOUS, &s_ResourcePreloadAnonymous );
  269. }
  270. //-----------------------------------------------------------------------------
  271. // Destructor
  272. //-----------------------------------------------------------------------------
  273. CQueuedLoader::~CQueuedLoader()
  274. {
  275. }
  276. //-----------------------------------------------------------------------------
  277. // Computation job to build out objects
  278. //-----------------------------------------------------------------------------
  279. void CQueuedLoader::BuildResources( IResourcePreload *pLoader, ResourceList_t *pList, float *pBuildTime )
  280. {
  281. float t0 = Plat_FloatTime();
  282. if ( pLoader )
  283. {
  284. pList->RedoSort();
  285. for ( int i = 0; i < pList->Count(); i++ )
  286. {
  287. char szFilename[MAX_PATH];
  288. g_QueuedLoader.GetFilename( pList->Element( i ), szFilename, sizeof( szFilename ) );
  289. if ( szFilename[0] )
  290. {
  291. if ( !pLoader->CreateResource( szFilename ) )
  292. {
  293. Warning( "QueuedLoader: Failed to create resource %s\n", szFilename );
  294. }
  295. }
  296. }
  297. }
  298. // finished with list
  299. pList->Purge();
  300. *pBuildTime = Plat_FloatTime() - t0;
  301. }
  302. //-----------------------------------------------------------------------------
  303. // Computation job to build out material objects
  304. //-----------------------------------------------------------------------------
  305. void CQueuedLoader::BuildMaterialResources( IResourcePreload *pLoader, ResourceList_t *pList, float *pBuildTime )
  306. {
  307. float t0 = Plat_FloatTime();
  308. char szLastFilename[MAX_PATH];
  309. szLastFilename[0] = '\0';
  310. // ensure cubemaps are first
  311. pList->RedoSort();
  312. // run a clean operation to cull the non-patched env_cubemap materials, which are not built directly
  313. for ( int i = 0; i < pList->Count(); i++ )
  314. {
  315. char szFilename[MAX_PATH];
  316. char *pFilename = g_QueuedLoader.GetFilename( pList->Element( i ), szFilename, sizeof( szFilename ) );
  317. if ( !V_stristr( pFilename, "maps\\" ) )
  318. {
  319. // list is sorted, first non-cubemap marks end of relevant list
  320. break;
  321. }
  322. // skip past maps/mapname/
  323. pFilename += 5;
  324. pFilename = strchr( pFilename, '\\' ) + 1;
  325. // back up until end of material name is found, need to strip off _%d_%d_%d.vmt
  326. char *pEndFilename = V_stristr( pFilename, ".vmt" );
  327. if ( !pEndFilename )
  328. {
  329. pEndFilename = pFilename + strlen( pFilename );
  330. }
  331. int numUnderscores = 3;
  332. while ( pEndFilename != pFilename && numUnderscores > 0 )
  333. {
  334. pEndFilename--;
  335. if ( pEndFilename[0] == '_' )
  336. {
  337. numUnderscores--;
  338. }
  339. }
  340. if ( numUnderscores == 0 )
  341. {
  342. *pEndFilename = '\0';
  343. if ( !V_strcmp( szLastFilename, pFilename ) )
  344. {
  345. // same cubemap material base already processed, skip it
  346. continue;
  347. }
  348. V_strncpy( szLastFilename, pFilename, sizeof( szLastFilename ) );
  349. strcat( pFilename, ".vmt" );
  350. FileNameHandle_t hFilename = g_QueuedLoader.FindFilename( pFilename );
  351. if ( hFilename )
  352. {
  353. pList->Remove( hFilename );
  354. }
  355. }
  356. }
  357. // process clean list
  358. BuildResources( pLoader, pList, pBuildTime );
  359. *pBuildTime = Plat_FloatTime() - t0;
  360. }
  361. //-----------------------------------------------------------------------------
  362. // Called by multiple worker threads. Throttle the I/O to ensure too many
  363. // buffers don't flood the work queue. Anonymous I/O is allowed to grow unbounded.
  364. //-----------------------------------------------------------------------------
  365. void AdjustAsyncIOSpeed()
  366. {
  367. if ( g_QueuedLoader.IsDynamic() == true )
  368. {
  369. return;
  370. }
  371. // throttle back the I/O to keep the pending buffers from exhausting memory
  372. if ( g_SuspendIO == 0 )
  373. {
  374. if ( g_nIOMemory >= g_nHighIOSuspensionMark && g_nActiveJobs != 0 )
  375. {
  376. // protect against another worker thread
  377. if ( g_SuspendIO.AssignIf( 0, 1 ) )
  378. {
  379. if ( g_QueuedLoader.GetSpewDetail() )
  380. {
  381. Msg( "QueuedLoader: Suspending I/O at %.2f MB\n", (float)g_nIOMemory / ( 1024.0f * 1024.0f ) );
  382. }
  383. g_pFullFileSystem->AsyncSuspend();
  384. }
  385. }
  386. }
  387. else if ( g_SuspendIO == 1 )
  388. {
  389. if ( g_nIOMemory <= g_nLowIOSuspensionMark )
  390. {
  391. // protect against another worker thread
  392. if ( g_SuspendIO.AssignIf( 1, 0 ) )
  393. {
  394. if ( g_QueuedLoader.GetSpewDetail() )
  395. {
  396. Msg( "QueuedLoader: Resuming I/O at %.2f MB\n", (float)g_nIOMemory / ( 1024.0f * 1024.0f ) );
  397. }
  398. g_pFullFileSystem->AsyncResume();
  399. }
  400. }
  401. }
  402. }
  403. //-----------------------------------------------------------------------------
  404. // Computation job to do work after IO, runs callback
  405. //-----------------------------------------------------------------------------
  406. void IOComputationJob( FileJob_t *pFileJob, void *pData, int nSize, LoaderError_t loaderError )
  407. {
  408. int spewDetail = g_QueuedLoader.GetSpewDetail();
  409. if ( spewDetail & ( LOADER_DETAIL_COMPLETIONS|LOADER_DETAIL_LATECOMPLETIONS ) )
  410. {
  411. const char *pLateString = "";
  412. if ( !g_QueuedLoader.IsMapLoading() )
  413. {
  414. // completed outside of load process
  415. pLateString = "(Late) ";
  416. }
  417. if ( ( spewDetail & LOADER_DETAIL_COMPLETIONS ) || ( ( spewDetail & LOADER_DETAIL_LATECOMPLETIONS ) && pLateString[0] ) )
  418. {
  419. char szFilename[MAX_PATH];
  420. g_QueuedLoader.GetFilename( pFileJob->m_hFilename, szFilename, sizeof( szFilename ) );
  421. Msg( "QueuedLoader: Computation:%8.8x, Size:%7d %s%s\n", ThreadGetCurrentId(), nSize, pLateString, szFilename );
  422. }
  423. }
  424. if ( loaderError != LOADERERROR_NONE && pFileJob->m_bFileExists )
  425. {
  426. char szFilename[MAX_PATH];
  427. g_QueuedLoader.GetFilename( pFileJob->m_hFilename, szFilename, sizeof( szFilename ) );
  428. Warning( "QueuedLoader:: I/O Error on %s\n", szFilename );
  429. }
  430. pFileJob->m_nActualBytesRead = nSize;
  431. pFileJob->m_LoaderError = loaderError;
  432. if ( !pFileJob->m_pCallback )
  433. {
  434. // absent callback means resource loader want this system to delay buffer until ready for it
  435. if ( !pFileJob->m_pTargetData )
  436. {
  437. // track it for later, unclaimed buffers will get freed
  438. pFileJob->m_pTargetData = pData;
  439. }
  440. }
  441. else
  442. {
  443. // regardless of error, call job callback so caller can do cleanup of their context
  444. pFileJob->m_pCallback( pFileJob->m_pContext, pFileJob->m_pContext2, pData, nSize, loaderError );
  445. if ( pFileJob->m_bFreeTargetAfterIO && pData )
  446. {
  447. // free our data only
  448. g_pFullFileSystem->FreeOptimalReadBuffer( pData );
  449. }
  450. // memory has been consumed
  451. g_nIOMemory -= nSize;
  452. }
  453. // mark as completed
  454. pFileJob->m_bFinished = true;
  455. pFileJob->m_FinishTime = Plat_MSTime();
  456. pFileJob->m_ThreadId = ThreadGetCurrentId();
  457. if ( pFileJob->m_Priority == LOADERPRIORITY_DURINGPRELOAD )
  458. {
  459. g_nHighPriorityJobs--;
  460. }
  461. else if ( pFileJob->m_Priority == LOADERPRIORITY_BEFOREPLAY )
  462. {
  463. g_nJobsToFinishBeforePlay--;
  464. }
  465. g_nQueuedJobs--;
  466. if ( g_nQueuedJobs == 0 && ( spewDetail & LOADER_DETAIL_TIMING ) )
  467. {
  468. Msg( "QueuedLoader: Finished I/O of all queued jobs!\n" );
  469. }
  470. AdjustAsyncIOSpeed();
  471. }
  472. //-----------------------------------------------------------------------------
  473. // Computation job to do work after anonymous job was asynchronously claimed, runs callback.
  474. //-----------------------------------------------------------------------------
  475. void FinishAnonymousJob( FileJob_t *pFileJob, QueuedLoaderCallback_t pCallback, void *pContext, void *pContext2 )
  476. {
  477. // regardless of error, call job callback so caller can do cleanup of their context
  478. pCallback( pContext, pContext2, pFileJob->m_pTargetData, pFileJob->m_nActualBytesRead, pFileJob->m_LoaderError );
  479. if ( pFileJob->m_bFreeTargetAfterIO && pFileJob->m_pTargetData )
  480. {
  481. // free our data only
  482. g_pFullFileSystem->FreeOptimalReadBuffer( pFileJob->m_pTargetData );
  483. pFileJob->m_pTargetData = NULL;
  484. }
  485. pFileJob->m_bClaimed = true;
  486. // memory has been consumed
  487. g_nAnonymousIOMemory -= pFileJob->m_nActualBytesRead;
  488. }
  489. //-----------------------------------------------------------------------------
  490. // Callback from I/O job thread. Purposely lightweight as possible to keep i/o from stalling.
  491. //-----------------------------------------------------------------------------
  492. void IOAsyncCallback( const FileAsyncRequest_t &asyncRequest, int numReadBytes, FSAsyncStatus_t asyncStatus )
  493. {
  494. FileJob_t *pFileJob = (FileJob_t *)asyncRequest.pContext;
  495. // interpret the async error
  496. LoaderError_t loaderError;
  497. switch ( asyncStatus )
  498. {
  499. case FSASYNC_OK:
  500. loaderError = LOADERERROR_NONE;
  501. break;
  502. case FSASYNC_ERR_FILEOPEN:
  503. loaderError = LOADERERROR_FILEOPEN;
  504. break;
  505. default:
  506. loaderError = LOADERERROR_READING;
  507. }
  508. // track how much i/o data is in flight, consumption will decrement
  509. if ( !pFileJob->m_pCallback )
  510. {
  511. // anonymous io memory is tracked seperatley
  512. g_nAnonymousIOMemory += numReadBytes;
  513. if ( g_nAnonymousIOMemory > g_nAnonymousIOMemoryPeak )
  514. {
  515. g_nAnonymousIOMemoryPeak = g_nAnonymousIOMemory;
  516. }
  517. }
  518. else
  519. {
  520. g_nIOMemory += numReadBytes;
  521. if ( g_nIOMemory > g_nIOMemoryPeak )
  522. {
  523. g_nIOMemoryPeak = g_nIOMemory;
  524. }
  525. }
  526. // have data or error, do callback as a computation job
  527. if ( !g_QueuedLoader.IsDynamic() )
  528. {
  529. g_pThreadPool->QueueCall( IOComputationJob, pFileJob, asyncRequest.pData, numReadBytes, loaderError )->Release();
  530. }
  531. else
  532. {
  533. g_QueuedLoader.QueueDynamicLoadFunctor( CreateFunctor( IOComputationJob, pFileJob, asyncRequest.pData, numReadBytes, loaderError ) );
  534. }
  535. // don't let the i/o starve, possibly get some more work from the pending queue
  536. g_QueuedLoader.SubmitPendingJobs();
  537. // possibly goes to zero atomically, AFTER submission
  538. // prevents contention between main thread
  539. --g_nActiveJobs;
  540. }
  541. //-----------------------------------------------------------------------------
  542. // Public method to filename dictionary
  543. //-----------------------------------------------------------------------------
  544. char *CQueuedLoader::GetFilename( const FileNameHandle_t hFilename, char *pBuff, int nBuffSize )
  545. {
  546. m_Filenames.String( hFilename, pBuff, nBuffSize );
  547. return pBuff;
  548. }
  549. //-----------------------------------------------------------------------------
  550. // Public method to filename dictionary
  551. //-----------------------------------------------------------------------------
  552. FileNameHandle_t CQueuedLoader::FindFilename( const char *pFilename )
  553. {
  554. return m_Filenames.FindFileName( pFilename );
  555. }
  556. //-----------------------------------------------------------------------------
  557. // Sort function for resource names.
  558. //-----------------------------------------------------------------------------
  559. bool CQueuedLoader::CResourceNameLessFunc::Less( const FileNameHandle_t &hFilenameLHS, const FileNameHandle_t &hFilenameRHS, void *pCtx )
  560. {
  561. switch ( (int)pCtx )
  562. {
  563. case RESOURCEPRELOAD_MATERIAL:
  564. {
  565. // Cubemap materials are expected to be at top of list
  566. char szNameLHS[MAX_PATH];
  567. char szNameRHS[MAX_PATH];
  568. const char *pNameLHS = g_QueuedLoader.GetFilename( hFilenameLHS, szNameLHS, sizeof( szNameLHS ) );
  569. const char *pNameRHS = g_QueuedLoader.GetFilename( hFilenameRHS, szNameRHS, sizeof( szNameRHS ) );
  570. bool bIsCubemapLHS = V_stristr( pNameLHS, "maps\\" ) != NULL;
  571. bool bIsCubemapRHS = V_stristr( pNameRHS, "maps\\" ) != NULL;
  572. if ( bIsCubemapLHS != bIsCubemapRHS )
  573. {
  574. return ( bIsCubemapLHS == true && bIsCubemapRHS == false );
  575. }
  576. return ( V_stricmp( pNameLHS, pNameRHS ) < 0 );
  577. }
  578. break;
  579. default:
  580. // sort not really needed, just use numeric handles
  581. return ( hFilenameLHS < hFilenameRHS );
  582. }
  583. }
  584. //-----------------------------------------------------------------------------
  585. // Resolve filenames to expected disc layout order as...
  586. // bsp, graphs, platform, hl2, episodic, ep2, tf, portal, non-zip
  587. // see XGD layout.
  588. //-----------------------------------------------------------------------------
  589. int CQueuedLoader::CFileJobsLessFunc::GetLayoutOrderForFilename( const char *pFilename )
  590. {
  591. bool bIsLocalizedZip = false;
  592. if ( XBX_IsLocalized() )
  593. {
  594. if ( V_stristr( pFilename, "\\zip" ) && V_stristr( pFilename, XBX_GetLanguageString() ) )
  595. {
  596. bIsLocalizedZip = true;
  597. }
  598. }
  599. int order;
  600. if ( V_stristr( pFilename, "\\maps\\" ) )
  601. {
  602. // bsp's and graphs on the opposite layer, these must be topmost
  603. // the queued loader is expecting to do these first, all at once
  604. // this allows for a single layer switch
  605. if ( V_stristr( pFilename, "\\graphs\\" ) )
  606. {
  607. order = 1;
  608. }
  609. else
  610. {
  611. order = 0;
  612. }
  613. }
  614. else if ( V_stristr( pFilename, "\\platform\\zip" ) )
  615. {
  616. order = 2;
  617. }
  618. else if ( V_stristr( pFilename, "\\hl2\\zip" ) )
  619. {
  620. order = 3;
  621. }
  622. else if ( V_stristr( pFilename, "\\episodic\\zip" ) )
  623. {
  624. order = 4;
  625. }
  626. else if ( V_stristr( pFilename, "\\ep2\\zip" ) )
  627. {
  628. order = 5;
  629. }
  630. else if ( V_stristr( pFilename, "\\tf\\zip" ) )
  631. {
  632. order = 6;
  633. }
  634. else if ( V_stristr( pFilename, "\\portal\\zip" ) )
  635. {
  636. order = 7;
  637. }
  638. else
  639. {
  640. // other
  641. order = 8;
  642. }
  643. // localized zips have same relative sort order, but after all other zips
  644. return bIsLocalizedZip ? 10*order : order;
  645. }
  646. //-----------------------------------------------------------------------------
  647. // Sort function, high priority jobs sort first, then offset, then zip
  648. //-----------------------------------------------------------------------------
  649. bool CQueuedLoader::CFileJobsLessFunc::Less( FileJob_t* const &pFileJobLHS, FileJob_t* const &pFileJobRHS, void *pCtx )
  650. {
  651. if ( pFileJobLHS->m_Priority != pFileJobRHS->m_Priority )
  652. {
  653. // higher priorities sort to top
  654. return ( pFileJobLHS->m_Priority > pFileJobRHS->m_Priority );
  655. }
  656. if ( pFileJobLHS->m_hFilename == pFileJobRHS->m_hFilename )
  657. {
  658. // same file (zip), sort by offset
  659. return pFileJobLHS->m_nStartOffset < pFileJobRHS->m_nStartOffset;
  660. }
  661. char szFilenameLHS[MAX_PATH];
  662. char szFilenameRHS[MAX_PATH];
  663. g_QueuedLoader.GetFilename( pFileJobLHS->m_hFilename, szFilenameLHS, sizeof( szFilenameLHS ) );
  664. g_QueuedLoader.GetFilename( pFileJobRHS->m_hFilename, szFilenameRHS, sizeof( szFilenameRHS ) );
  665. // resolve filename to match disk layout of zips
  666. int layoutLHS = GetLayoutOrderForFilename( szFilenameLHS );
  667. int layoutRHS = GetLayoutOrderForFilename( szFilenameRHS );
  668. if ( layoutLHS != layoutRHS )
  669. {
  670. return layoutLHS < layoutRHS;
  671. }
  672. return CaselessStringLessThan( szFilenameLHS, szFilenameRHS );
  673. }
  674. //-----------------------------------------------------------------------------
  675. // Dump the queue contents to the file system.
  676. //-----------------------------------------------------------------------------
  677. void CQueuedLoader::SubmitPendingJobs()
  678. {
  679. // prevents contention between I/O and main thread attempting to submit
  680. if ( ThreadInMainThread() && g_nActiveJobs != 0 && m_bDynamic == false )
  681. {
  682. // main thread can only kick start work if the I/O is idle
  683. // once the I/O is kicked off, the I/O thread is responsible for continual draining
  684. return;
  685. }
  686. else if ( !ThreadInMainThread() && g_nActiveJobs != 1 && m_bDynamic == false )
  687. {
  688. // I/O thread requests more work, but will only fall through and get some when it expects to go idle
  689. // I/O thread still has jobs and doesn't need any more yet
  690. return;
  691. }
  692. CTSList<FileJob_t *>::Node_t *pNode = m_PendingJobs.Detach();
  693. if ( !pNode )
  694. {
  695. return;
  696. }
  697. // used by spew to indicate submission blocks
  698. m_nSubmitCount++;
  699. // sort entries
  700. CUtlSortVector< FileJob_t*, CFileJobsLessFunc > sortedFiles( 0, 128 );
  701. while ( pNode )
  702. {
  703. FileJob_t *pFileJob = pNode->elem;
  704. sortedFiles.InsertNoSort( pFileJob );
  705. CTSList<FileJob_t *>::Node_t *pNext = (CTSList<FileJob_t *>::Node_t*)pNode->Next;
  706. delete pNode;
  707. pNode = pNext;
  708. }
  709. sortedFiles.RedoSort();
  710. FileAsyncRequest_t asyncRequest;
  711. asyncRequest.pfnCallback = IOAsyncCallback;
  712. char szFilename[MAX_PATH];
  713. for ( int i = 0; i<sortedFiles.Count(); i++ )
  714. {
  715. FileJob_t *pFileJob = sortedFiles[i];
  716. pFileJob->m_SubmitTag = m_nSubmitCount;
  717. pFileJob->m_SubmitTime = Plat_MSTime();
  718. m_SubmittedJobs.AddToTail( pFileJob );
  719. // build an async request
  720. if ( pFileJob->m_Priority == LOADERPRIORITY_DURINGPRELOAD )
  721. {
  722. // must finish during preload
  723. asyncRequest.priority = PRIORITY_HIGH;
  724. g_nHighPriorityJobs++;
  725. }
  726. else if ( pFileJob->m_Priority == LOADERPRIORITY_BEFOREPLAY )
  727. {
  728. // must finish before gameplay
  729. asyncRequest.priority = PRIORITY_NORMAL;
  730. g_nJobsToFinishBeforePlay++;
  731. }
  732. else
  733. {
  734. // can finish during gameplay, normal priority
  735. asyncRequest.priority = PRIORITY_NORMAL;
  736. }
  737. // async will allocate unless caller provided a target
  738. // loader always takes ownership of buffer
  739. asyncRequest.pData = pFileJob->m_pTargetData;
  740. asyncRequest.flags = pFileJob->m_pTargetData ? 0 : FSASYNC_FLAGS_ALLOCNOFREE;
  741. asyncRequest.nOffset = pFileJob->m_nStartOffset;
  742. asyncRequest.nBytes = pFileJob->m_nBytesToRead;
  743. asyncRequest.pszFilename = GetFilename( pFileJob->m_hFilename, szFilename, sizeof( szFilename ) );
  744. asyncRequest.pContext = (void *)pFileJob;
  745. if ( pFileJob->m_bFileExists )
  746. {
  747. // start the valid async request
  748. g_nActiveJobs++;
  749. g_pFullFileSystem->AsyncRead( asyncRequest, &pFileJob->m_hAsyncControl );
  750. }
  751. else
  752. {
  753. // prevent dragging the i/o system down for known failures
  754. // still need to do callback so subsystems can do the right thing based on file absence
  755. if ( IsDynamic() )
  756. QueueDynamicLoadFunctor( CreateFunctor( IOComputationJob, pFileJob, pFileJob->m_pTargetData, 0, LOADERERROR_FILEOPEN ) );
  757. else
  758. g_pThreadPool->QueueCall( IOComputationJob, pFileJob, pFileJob->m_pTargetData, 0, LOADERERROR_FILEOPEN )->Release();
  759. }
  760. }
  761. }
  762. //-----------------------------------------------------------------------------
  763. // Add to queue
  764. //-----------------------------------------------------------------------------
  765. bool CQueuedLoader::AddJob( const LoaderJob_t *pLoaderJob )
  766. {
  767. if ( !m_bActive )
  768. {
  769. return false;
  770. }
  771. Assert( pLoaderJob && pLoaderJob->m_pFilename );
  772. if ( m_bCanBatch && !m_bBatching )
  773. {
  774. // should have been part of pre-load batch
  775. DevWarning( "QueuedLoader: Late Queued Job: %s\n", pLoaderJob->m_pFilename );
  776. }
  777. // anonymous jobs lack callbacks and are heavily restricted to ensure their stability
  778. // the caller is expected to claim these before load ends (which auto-purges them)
  779. if ( !pLoaderJob->m_pCallback && pLoaderJob->m_Priority == LOADERPRIORITY_ANYTIME )
  780. {
  781. Assert( 0 );
  782. DevWarning( "QueuedLoader: Ignoring Anonymous Job: %s\n", pLoaderJob->m_pFilename );
  783. return false;
  784. }
  785. MEM_ALLOC_CREDIT();
  786. // all bsp based files get forced to a higher priority in order to achieve a clustered sort
  787. // the bsp files are not going to be anywhere near the zips, thus we don't want head thrashing
  788. bool bFileIsFromBSP;
  789. bool bExists = false;
  790. char *pFullPath;
  791. char szFullPath[MAX_PATH];
  792. if ( V_IsAbsolutePath( pLoaderJob->m_pFilename ) )
  793. {
  794. // an absolute path is trusted, take as is
  795. pFullPath = (char *)pLoaderJob->m_pFilename;
  796. bFileIsFromBSP = V_stristr( pFullPath, ".bsp" ) != NULL;
  797. bExists = true;
  798. }
  799. else
  800. {
  801. // must resolve now, all submitted paths must be absolute for proper sort which achieves seek linearization
  802. // a resolved absolute file ensures its existence
  803. PathTypeFilter_t pathFilter = FILTER_NONE;
  804. if ( IsX360() && ( g_pFullFileSystem->GetDVDMode() == DVDMODE_STRICT ) )
  805. {
  806. if ( V_stristr( pLoaderJob->m_pFilename, ".bsp" ) || V_stristr( pLoaderJob->m_pFilename, ".ain" ) )
  807. {
  808. // only the bsp/ain are allowed to be external
  809. pathFilter = FILTER_CULLPACK;
  810. }
  811. else
  812. {
  813. // all files are expected to be in zip
  814. pathFilter = FILTER_CULLNONPACK;
  815. }
  816. }
  817. PathTypeQuery_t pathType;
  818. g_pFullFileSystem->RelativePathToFullPath( pLoaderJob->m_pFilename, pLoaderJob->m_pPathID, szFullPath, sizeof( szFullPath ), pathFilter, &pathType );
  819. bExists = V_IsAbsolutePath( szFullPath );
  820. pFullPath = szFullPath;
  821. bFileIsFromBSP = ( (pathType & PATH_IS_MAPPACKFILE) != 0 );
  822. }
  823. // create a file job
  824. FileJob_t *pFileJob = new FileJob_t;
  825. pFileJob->m_hFilename = m_Filenames.FindOrAddFileName( pFullPath );
  826. pFileJob->m_bFileExists = bExists;
  827. pFileJob->m_pCallback = pLoaderJob->m_pCallback;
  828. pFileJob->m_pContext = pLoaderJob->m_pContext;
  829. pFileJob->m_pContext2 = pLoaderJob->m_pContext2;
  830. pFileJob->m_pTargetData = pLoaderJob->m_pTargetData;
  831. pFileJob->m_nBytesToRead = pLoaderJob->m_nBytesToRead;
  832. pFileJob->m_nStartOffset = pLoaderJob->m_nStartOffset;
  833. pFileJob->m_Priority = bFileIsFromBSP ? LOADERPRIORITY_DURINGPRELOAD : pLoaderJob->m_Priority;
  834. if ( pLoaderJob->m_pTargetData )
  835. {
  836. // never free caller's buffer, if they provide, they have to free it
  837. pFileJob->m_bFreeTargetAfterIO = false;
  838. }
  839. else
  840. {
  841. // caller can take over ownership, otherwise it gets freed after I/O
  842. pFileJob->m_bFreeTargetAfterIO = ( pLoaderJob->m_bPersistTargetData == false );
  843. }
  844. if ( !pLoaderJob->m_pCallback )
  845. {
  846. // track anonymous jobs
  847. AUTO_LOCK( m_Mutex );
  848. char szFixedName[MAX_PATH];
  849. V_strncpy( szFixedName, pLoaderJob->m_pFilename, sizeof( szFixedName ) );
  850. V_FixSlashes( szFixedName );
  851. m_AnonymousJobs.Insert( szFixedName, pFileJob );
  852. }
  853. g_nQueuedJobs++;
  854. if ( m_bBatching )
  855. {
  856. m_BatchedJobs.PushItem( pFileJob );
  857. }
  858. else
  859. {
  860. m_PendingJobs.PushItem( pFileJob );
  861. SubmitPendingJobs();
  862. }
  863. return true;
  864. }
  865. //-----------------------------------------------------------------------------
  866. // Allows an external system to append to a map's reslist. The next map load
  867. // will append these specified files. Unhandled resources will just get
  868. // quietly discarded. An external system could use this to patch a hole
  869. // or prevent a purge.
  870. //-----------------------------------------------------------------------------
  871. void CQueuedLoader::AddMapResource( const char *pFilename )
  872. {
  873. if ( !pFilename || !pFilename[0] )
  874. {
  875. // pointless
  876. return;
  877. }
  878. // normalize the provided name as a filename
  879. char szFilename[MAX_PATH];
  880. V_strncpy( szFilename, pFilename, sizeof( szFilename ) );
  881. V_FixSlashes( szFilename );
  882. V_strlower( szFilename );
  883. if ( m_AdditionalResources.Find( szFilename ) != UTL_INVAL_SYMBOL )
  884. {
  885. // already added
  886. return;
  887. }
  888. m_AdditionalResources.AddString( szFilename );
  889. }
  890. //-----------------------------------------------------------------------------
  891. // Asynchronous claim for an anonymous job.
  892. // This allows loaders with deep dependencies to get their data in flight, and then claim it
  893. // when the they are in a state to consume it.
  894. //-----------------------------------------------------------------------------
  895. bool CQueuedLoader::ClaimAnonymousJob( const char *pFilename, QueuedLoaderCallback_t pCallback, void *pContext, void *pContext2 )
  896. {
  897. Assert( ThreadInMainThread() );
  898. Assert( pFilename && pCallback && !m_bBatching );
  899. char szFixedName[MAX_PATH];
  900. V_strncpy( szFixedName, pFilename, sizeof( szFixedName ) );
  901. V_FixSlashes( szFixedName );
  902. pFilename = szFixedName;
  903. int iIndex = m_AnonymousJobs.Find( pFilename );
  904. if ( iIndex == m_AnonymousJobs.InvalidIndex() )
  905. {
  906. // unknown
  907. DevWarning( "QueuedLoader: Anonymous Job '%s' not found\n", pFilename );
  908. return false;
  909. }
  910. // caller is claiming
  911. FileJob_t *pFileJob = m_AnonymousJobs[iIndex];
  912. if ( !pFileJob->m_bFinished )
  913. {
  914. // unfinished shouldn't happen and caller can't have it
  915. // anonymous jobs and their claims are very restrictive in such a way to provide stability
  916. // this dead job will get auto-cleaned at end of map loading
  917. Assert( 0 );
  918. return false;
  919. }
  920. m_AnonymousJobs.RemoveAt( iIndex );
  921. g_pThreadPool->QueueCall( FinishAnonymousJob, pFileJob, pCallback, pContext, pContext2 )->Release();
  922. return true;
  923. }
  924. //-----------------------------------------------------------------------------
  925. // Synchronous claim for an anonymous job. This allows loaders
  926. // with deep dependencies to get their data in flight, and then claim it
  927. // when the they are in a state to consume it.
  928. //-----------------------------------------------------------------------------
  929. bool CQueuedLoader::ClaimAnonymousJob( const char *pFilename, void **pData, int *pDataSize, LoaderError_t *pError )
  930. {
  931. Assert( ThreadInMainThread() );
  932. Assert( pFilename && !m_bBatching );
  933. char szFixedName[MAX_PATH];
  934. V_strncpy( szFixedName, pFilename, sizeof( szFixedName ) );
  935. V_FixSlashes( szFixedName );
  936. pFilename = szFixedName;
  937. int iIndex = m_AnonymousJobs.Find( pFilename );
  938. if ( iIndex == m_AnonymousJobs.InvalidIndex() )
  939. {
  940. // unknown
  941. DevWarning( "QueuedLoader: Anonymous Job '%s' not found\n", pFilename );
  942. return false;
  943. }
  944. // caller is claiming
  945. FileJob_t *pFileJob = m_AnonymousJobs[iIndex];
  946. if ( !pFileJob->m_bFinished )
  947. {
  948. // unfinished shouldn't happen and caller can't have it
  949. // anonymous jobs and their claims are very restrictive in such a way to provide stability
  950. // this dead job will get auto-cleaned at end of map loading
  951. Assert( 0 );
  952. return false;
  953. }
  954. pFileJob->m_bClaimed = true;
  955. m_AnonymousJobs.RemoveAt( iIndex );
  956. *pData = pFileJob->m_pTargetData;
  957. *pDataSize = pFileJob->m_LoaderError == LOADERERROR_NONE ? pFileJob->m_nActualBytesRead : 0;
  958. if ( pError )
  959. {
  960. *pError = pFileJob->m_LoaderError;
  961. }
  962. // caller owns the data, regardless of how the job was setup
  963. pFileJob->m_pTargetData = NULL;
  964. // memory has been consumed
  965. g_nAnonymousIOMemory -= pFileJob->m_nActualBytesRead;
  966. return true;
  967. }
  968. //-----------------------------------------------------------------------------
  969. // End of batching. Moves jobs into pending queue and submits but does not wait
  970. //-----------------------------------------------------------------------------
  971. void CQueuedLoader::SubmitBatchedJobs()
  972. {
  973. // end of batching
  974. m_bBatching = false;
  975. CTSList<FileJob_t *>::Node_t *pNode = m_BatchedJobs.Detach();
  976. if ( !pNode )
  977. {
  978. return;
  979. }
  980. // must wait for any initial i/o to finish
  981. // i/o thread must stop in order to submit all the batched jobs atomically
  982. // and get an accurate accounting of high priority jobs
  983. while ( g_nActiveJobs != 0 )
  984. {
  985. g_pThreadPool->Yield( MAIN_THREAD_YIELD_TIME );
  986. }
  987. // dump batched jobs to pending jobs
  988. while ( pNode )
  989. {
  990. FileJob_t *pFileJob = pNode->elem;
  991. m_PendingJobs.PushItem( pFileJob );
  992. CTSList<FileJob_t *>::Node_t *pNext = (CTSList<FileJob_t *>::Node_t*)pNode->Next;
  993. delete pNode;
  994. pNode = pNext;
  995. }
  996. SubmitPendingJobs();
  997. if ( GetSpewDetail() )
  998. {
  999. Msg( "QueuedLoader: High Priority Jobs: %d\n", (int)g_nHighPriorityJobs );
  1000. }
  1001. }
  1002. //-----------------------------------------------------------------------------
  1003. // End of batching. High priority jobs are guaranteed completed before function returns.
  1004. //-----------------------------------------------------------------------------
  1005. void CQueuedLoader::SubmitBatchedJobsAndWait()
  1006. {
  1007. SubmitBatchedJobs();
  1008. // finish only the high priority jobs
  1009. // high priority jobs are expected to be complete at the conclusion of batching
  1010. int total = g_nHighPriorityJobs;
  1011. while ( g_nHighPriorityJobs != 0 )
  1012. {
  1013. float t = (float)( total - g_nHighPriorityJobs ) / (float)total;
  1014. m_pProgress->UpdateProgress( PROGRESS_IO + t * ( 1.0f - PROGRESS_IO ) );
  1015. // yield some time
  1016. g_pThreadPool->Yield( MAIN_THREAD_YIELD_TIME );
  1017. }
  1018. }
  1019. //-----------------------------------------------------------------------------
  1020. // Clean queue of stale entries. Active entries are skipped.
  1021. //-----------------------------------------------------------------------------
  1022. void CQueuedLoader::CleanQueue()
  1023. {
  1024. for ( int i = 0; i<RESOURCEPRELOAD_COUNT; i++ )
  1025. {
  1026. m_ResourceNames[i].Purge();
  1027. }
  1028. m_BatchedJobs.Purge();
  1029. int iIndex = m_SubmittedJobs.Head();
  1030. while ( iIndex != m_SubmittedJobs.InvalidIndex() )
  1031. {
  1032. int iNext = m_SubmittedJobs.Next( iIndex );
  1033. FileJob_t *pFileJob = m_SubmittedJobs[iIndex];
  1034. if ( pFileJob->m_bFinished )
  1035. {
  1036. // job is complete, safe to free
  1037. m_SubmittedJobs.Free( iIndex );
  1038. g_pFullFileSystem->AsyncRelease( pFileJob->m_hAsyncControl );
  1039. delete pFileJob;
  1040. }
  1041. iIndex = iNext;
  1042. }
  1043. m_Filenames.RemoveAll();
  1044. }
  1045. //-----------------------------------------------------------------------------
  1046. // Abandon queue
  1047. //-----------------------------------------------------------------------------
  1048. void CQueuedLoader::PurgeQueue()
  1049. {
  1050. }
  1051. //-----------------------------------------------------------------------------
  1052. // Spew info abut queued load
  1053. //-----------------------------------------------------------------------------
  1054. void CQueuedLoader::SpewInfo()
  1055. {
  1056. Msg( "Queued Loader:\n\n" );
  1057. int totalClaimed = 0;
  1058. int totalUnclaimed = 0;
  1059. if ( IsFinished() || m_bDynamic == true )
  1060. {
  1061. // can only access submitted jobs safely when io thread complete
  1062. int lastPriority = -1;
  1063. int iIndex = m_SubmittedJobs.Head();
  1064. while ( iIndex != m_SubmittedJobs.InvalidIndex() )
  1065. {
  1066. FileJob_t *pFileJob = m_SubmittedJobs[iIndex];
  1067. int asyncDuration = -1;
  1068. if ( pFileJob->m_FinishTime )
  1069. {
  1070. asyncDuration = pFileJob->m_FinishTime - pFileJob->m_SubmitTime;
  1071. }
  1072. if ( pFileJob->m_Priority != lastPriority )
  1073. {
  1074. switch ( pFileJob->m_Priority )
  1075. {
  1076. case LOADERPRIORITY_DURINGPRELOAD:
  1077. Msg( "---- FINISH DURING PRELOAD ( HIGH PRIORITY )----\n" );
  1078. break;
  1079. case LOADERPRIORITY_BEFOREPLAY:
  1080. Msg( "---- FINISH BEFORE GAMEPLAY ( NORMAL PRIORITY )----\n" );
  1081. break;
  1082. case LOADERPRIORITY_ANYTIME:
  1083. Msg( "---- FINISH ANYTIME ( NORMAL PRIORITY )----\n" );
  1084. break;
  1085. }
  1086. lastPriority = pFileJob->m_Priority;
  1087. }
  1088. char szAnonymousString[MAX_PATH];
  1089. const char *pAnonymousStatus = "";
  1090. if ( !pFileJob->m_pCallback )
  1091. {
  1092. V_snprintf( szAnonymousString, sizeof( szAnonymousString ), "(%s) ", pFileJob->m_bClaimed ? "Claimed" : "Unclaimed" );
  1093. pAnonymousStatus = szAnonymousString;
  1094. if ( pFileJob->m_bClaimed )
  1095. {
  1096. totalClaimed += pFileJob->m_nActualBytesRead;
  1097. }
  1098. else
  1099. {
  1100. totalUnclaimed += pFileJob->m_nActualBytesRead;
  1101. }
  1102. }
  1103. char szFilename[MAX_PATH];
  1104. Msg( "Submit:%5dms AsyncDuration:%5dms Tag:%d Thread:%8.8x Size:%7d %s%s\n",
  1105. pFileJob->m_SubmitTime - m_StartTime,
  1106. asyncDuration,
  1107. pFileJob->m_SubmitTag,
  1108. pFileJob->m_ThreadId,
  1109. pFileJob->m_nActualBytesRead,
  1110. pAnonymousStatus,
  1111. GetFilename( pFileJob->m_hFilename, szFilename, sizeof( szFilename ) ) );
  1112. iIndex = m_SubmittedJobs.Next( iIndex );
  1113. }
  1114. Msg( "%d Total Jobs\n", m_SubmittedJobs.Count() );
  1115. }
  1116. Msg( "%d Queued Jobs\n", (int)g_nQueuedJobs );
  1117. Msg( "%d Active Jobs\n", (int)g_nActiveJobs );
  1118. Msg( "Peak IO Memory: %.2f MB\n", (float)g_nIOMemoryPeak / ( 1024.0f * 1024.0f ) );
  1119. Msg( "Peak Anonymous IO Memory: %.2f MB\n", (float)g_nAnonymousIOMemoryPeak / ( 1024.0f * 1024.0f ) );
  1120. Msg( " Total Anonymous Claimed: %d\n", totalClaimed );
  1121. Msg( " Total Anonymous Unclaimed: %d\n", totalUnclaimed );
  1122. if ( m_EndTime )
  1123. {
  1124. Msg( "Queuing Duration: %dms\n", m_EndTime - m_StartTime );
  1125. }
  1126. }
  1127. //-----------------------------------------------------------------------------
  1128. // Initialization
  1129. //-----------------------------------------------------------------------------
  1130. InitReturnVal_t CQueuedLoader::Init()
  1131. {
  1132. InitReturnVal_t nRetVal = BaseClass::Init();
  1133. if ( nRetVal != INIT_OK )
  1134. {
  1135. return nRetVal;
  1136. }
  1137. return INIT_OK;
  1138. }
  1139. //-----------------------------------------------------------------------------
  1140. // Shutdown
  1141. //-----------------------------------------------------------------------------
  1142. void CQueuedLoader::Shutdown()
  1143. {
  1144. BaseClass::Shutdown();
  1145. }
  1146. //-----------------------------------------------------------------------------
  1147. // Install a type specific interface from managing system.
  1148. //-----------------------------------------------------------------------------
  1149. void CQueuedLoader::InstallLoader( ResourcePreload_t type, IResourcePreload *pLoader )
  1150. {
  1151. m_pLoaders[type] = pLoader;
  1152. }
  1153. void CQueuedLoader::InstallProgress( ILoaderProgress *pProgress )
  1154. {
  1155. m_pProgress = pProgress;
  1156. }
  1157. //-----------------------------------------------------------------------------
  1158. // Invoke the loader systems to purge dead resources
  1159. //-----------------------------------------------------------------------------
  1160. void CQueuedLoader::PurgeUnreferencedResources()
  1161. {
  1162. ResourcePreload_t purgeOrder[RESOURCEPRELOAD_COUNT];
  1163. // the purge operations require a specific order (models and cubemaps before materials)
  1164. int numPurges = 0;
  1165. purgeOrder[numPurges++] = RESOURCEPRELOAD_SOUND;
  1166. purgeOrder[numPurges++] = RESOURCEPRELOAD_STATICPROPLIGHTING;
  1167. purgeOrder[numPurges++] = RESOURCEPRELOAD_MODEL;
  1168. purgeOrder[numPurges++] = RESOURCEPRELOAD_CUBEMAP;
  1169. purgeOrder[numPurges++] = RESOURCEPRELOAD_MATERIAL;
  1170. // iterate according to order
  1171. for ( int i = 0; i < numPurges; i++ )
  1172. {
  1173. ResourcePreload_t loader = purgeOrder[i];
  1174. if ( m_pLoaders[loader] )
  1175. {
  1176. m_pLoaders[loader]->PurgeUnreferencedResources();
  1177. }
  1178. }
  1179. m_pProgress->UpdateProgress( PROGRESS_PREPURGE );
  1180. }
  1181. //-----------------------------------------------------------------------------
  1182. // Invoke the loader systems to purge all resources, if possible
  1183. //-----------------------------------------------------------------------------
  1184. void CQueuedLoader::PurgeAll()
  1185. {
  1186. ResourcePreload_t purgeOrder[RESOURCEPRELOAD_COUNT];
  1187. // the purge operations require a specific order (models and cubemaps before materials)
  1188. int numPurges = 0;
  1189. purgeOrder[numPurges++] = RESOURCEPRELOAD_SOUND;
  1190. purgeOrder[numPurges++] = RESOURCEPRELOAD_STATICPROPLIGHTING;
  1191. purgeOrder[numPurges++] = RESOURCEPRELOAD_MODEL;
  1192. purgeOrder[numPurges++] = RESOURCEPRELOAD_CUBEMAP;
  1193. purgeOrder[numPurges++] = RESOURCEPRELOAD_MATERIAL;
  1194. // iterate according to order
  1195. for ( int i = 0; i < numPurges; i++ )
  1196. {
  1197. ResourcePreload_t loader = purgeOrder[i];
  1198. if ( m_pLoaders[loader] )
  1199. {
  1200. m_pLoaders[loader]->PurgeAll();
  1201. }
  1202. }
  1203. *m_szMapNameToCompareSame = 0;
  1204. }
  1205. //-----------------------------------------------------------------------------
  1206. // Invoke the loader systems to request i/o jobs, which are batched.
  1207. //-----------------------------------------------------------------------------
  1208. void CQueuedLoader::GetJobRequests()
  1209. {
  1210. COM_TimestampedLog( "CQueuedLoader::GetJobRequests - Start" );
  1211. // causes the batch queue to fill with i/o requests
  1212. m_bCanBatch = true;
  1213. m_bBatching = true;
  1214. float t0 = Plat_FloatTime();
  1215. if ( !IsPC() && !m_bDynamic )
  1216. {
  1217. // cubemap textures must be first to install correctly before their cubemap materials are built (and precache the cubmeap textures)
  1218. // cannot be overlapped, must run serially
  1219. BuildResources( m_pLoaders[RESOURCEPRELOAD_CUBEMAP], &m_ResourceNames[RESOURCEPRELOAD_CUBEMAP], &m_LoaderTimes[RESOURCEPRELOAD_CUBEMAP] );
  1220. // Overlapping these is not critical in any way, total time is currently < 2 seconds.
  1221. // These operations flood calls (AddJob) back into the queued loader (which has to mutex its lists),
  1222. // so in fact it's slightly slower to queue these at this stage. As these routines age they may become more heavyweight.
  1223. CJob *jobs[5];
  1224. jobs[0] = g_pThreadPool->QueueCall( BuildResources, m_pLoaders[RESOURCEPRELOAD_SOUND], &m_ResourceNames[RESOURCEPRELOAD_SOUND], &m_LoaderTimes[RESOURCEPRELOAD_SOUND] );
  1225. jobs[1] = g_pThreadPool->QueueCall( BuildMaterialResources, m_pLoaders[RESOURCEPRELOAD_MATERIAL], &m_ResourceNames[RESOURCEPRELOAD_MATERIAL], &m_LoaderTimes[RESOURCEPRELOAD_MATERIAL] );
  1226. jobs[2] = g_pThreadPool->QueueCall( BuildResources, m_pLoaders[RESOURCEPRELOAD_STATICPROPLIGHTING], &m_ResourceNames[RESOURCEPRELOAD_STATICPROPLIGHTING], &m_LoaderTimes[RESOURCEPRELOAD_STATICPROPLIGHTING] );
  1227. jobs[3] = g_pThreadPool->QueueCall( BuildResources, m_pLoaders[RESOURCEPRELOAD_MODEL], &m_ResourceNames[RESOURCEPRELOAD_MODEL], &m_LoaderTimes[RESOURCEPRELOAD_MODEL] );
  1228. jobs[4] = g_pThreadPool->QueueCall( BuildResources, m_pLoaders[RESOURCEPRELOAD_ANONYMOUS], &m_ResourceNames[RESOURCEPRELOAD_ANONYMOUS], &m_LoaderTimes[RESOURCEPRELOAD_ANONYMOUS] );
  1229. // all jobs must finish
  1230. float flLastUpdateT = -1000.0f;
  1231. // Update as if this takes 2 seconds
  1232. float flDelta = ( PROGRESS_CREATEDRESOURCES - PROGRESS_PARSEDRESLIST ) * 0.03 / 2.0f;
  1233. float flProgress = PROGRESS_PARSEDRESLIST;
  1234. while( true )
  1235. {
  1236. bool bIsDone = true;
  1237. for ( int i=0; i<ARRAYSIZE( jobs ); i++ )
  1238. {
  1239. if ( !jobs[i]->IsFinished() )
  1240. {
  1241. bIsDone = false;
  1242. break;
  1243. }
  1244. }
  1245. if ( bIsDone )
  1246. break;
  1247. // Can't sleep; that will allow this thread to be used by the thread pool
  1248. float newt = Plat_FloatTime();
  1249. if ( newt - flLastUpdateT > .03 )
  1250. {
  1251. m_pProgress->UpdateProgress( flProgress );
  1252. flProgress = clamp( flProgress + flDelta, PROGRESS_PARSEDRESLIST, PROGRESS_CREATEDRESOURCES );
  1253. // Necessary to take into account any waits for vsync
  1254. flLastUpdateT = Plat_FloatTime();
  1255. }
  1256. }
  1257. for ( int i=0; i<ARRAYSIZE( jobs ); i++ )
  1258. {
  1259. jobs[i]->Release();
  1260. }
  1261. }
  1262. else
  1263. {
  1264. BuildResources( m_pLoaders[RESOURCEPRELOAD_CUBEMAP], &m_ResourceNames[RESOURCEPRELOAD_CUBEMAP], &m_LoaderTimes[RESOURCEPRELOAD_CUBEMAP] );
  1265. BuildResources( m_pLoaders[RESOURCEPRELOAD_SOUND], &m_ResourceNames[RESOURCEPRELOAD_SOUND], &m_LoaderTimes[RESOURCEPRELOAD_SOUND] );
  1266. BuildMaterialResources( m_pLoaders[RESOURCEPRELOAD_MATERIAL], &m_ResourceNames[RESOURCEPRELOAD_MATERIAL], &m_LoaderTimes[RESOURCEPRELOAD_MATERIAL] );
  1267. BuildResources( m_pLoaders[RESOURCEPRELOAD_STATICPROPLIGHTING], &m_ResourceNames[RESOURCEPRELOAD_STATICPROPLIGHTING], &m_LoaderTimes[RESOURCEPRELOAD_STATICPROPLIGHTING] );
  1268. BuildResources( m_pLoaders[RESOURCEPRELOAD_MODEL], &m_ResourceNames[RESOURCEPRELOAD_MODEL], &m_LoaderTimes[RESOURCEPRELOAD_MODEL] );
  1269. BuildResources( m_pLoaders[RESOURCEPRELOAD_ANONYMOUS], &m_ResourceNames[RESOURCEPRELOAD_ANONYMOUS], &m_LoaderTimes[RESOURCEPRELOAD_ANONYMOUS] );
  1270. }
  1271. if ( g_QueuedLoader.GetSpewDetail() & LOADER_DETAIL_TIMING )
  1272. {
  1273. for ( int i = RESOURCEPRELOAD_UNKNOWN+1; i<RESOURCEPRELOAD_COUNT; i++ )
  1274. {
  1275. Msg( "QueuedLoader: %s Creating: %.2f seconds\n", g_ResourceLoaderNames[i], m_LoaderTimes[i] );
  1276. }
  1277. Msg( "QueuedLoader: Total Creating: %.2f seconds\n", Plat_FloatTime() - t0 );
  1278. }
  1279. m_pProgress->UpdateProgress( PROGRESS_CREATEDRESOURCES );
  1280. COM_TimestampedLog( "CQueuedLoader::GetJobRequests - End" );
  1281. }
  1282. void CQueuedLoader::AddResourceToTable( const char *pFilename )
  1283. {
  1284. const char *pExt = V_GetFileExtension( pFilename );
  1285. if ( !pExt )
  1286. {
  1287. // unknown
  1288. // all resources are identified by their extension
  1289. return;
  1290. }
  1291. const char *pTypeDir = NULL;
  1292. const char *pName = pFilename;
  1293. ResourcePreload_t type = RESOURCEPRELOAD_UNKNOWN;
  1294. if ( !V_stricmp( pExt, "wav" ) )
  1295. {
  1296. type = RESOURCEPRELOAD_SOUND;
  1297. pTypeDir = "sound\\";
  1298. }
  1299. else if ( !V_stricmp( pExt, "vmt" ) )
  1300. {
  1301. type = RESOURCEPRELOAD_MATERIAL;
  1302. pTypeDir = "materials\\";
  1303. }
  1304. else if ( !V_stricmp( pExt, "vtf" ) )
  1305. {
  1306. if ( V_stristr( pFilename, "maps\\" ) )
  1307. {
  1308. // only want cubemap textures
  1309. if ( !m_bLoadForHDR && V_stristr( pFilename, ".hdr." ) )
  1310. {
  1311. return;
  1312. }
  1313. else if ( m_bLoadForHDR && !V_stristr( pFilename, ".hdr." ) )
  1314. {
  1315. return;
  1316. }
  1317. type = RESOURCEPRELOAD_CUBEMAP;
  1318. pTypeDir = "materials\\";
  1319. }
  1320. else
  1321. {
  1322. return;
  1323. }
  1324. }
  1325. else if ( !V_stricmp( pExt, "mdl" ) )
  1326. {
  1327. type = RESOURCEPRELOAD_MODEL;
  1328. pTypeDir = "models\\";
  1329. }
  1330. else if ( !V_stricmp( pExt, "vhv" ) )
  1331. {
  1332. // want static props only
  1333. pName = V_stristr( pFilename, "sp_" );
  1334. if ( !pName )
  1335. {
  1336. return;
  1337. }
  1338. if ( !m_bLoadForHDR && V_stristr( pFilename, "_hdr_" ) )
  1339. {
  1340. return;
  1341. }
  1342. else if ( m_bLoadForHDR && !V_stristr( pFilename, "_hdr_" ) )
  1343. {
  1344. return;
  1345. }
  1346. type = RESOURCEPRELOAD_STATICPROPLIGHTING;
  1347. }
  1348. else
  1349. {
  1350. // unknown, ignored
  1351. return;
  1352. }
  1353. if ( pTypeDir )
  1354. {
  1355. // want object name only
  1356. // skip past game/type directory prefixing
  1357. const char *pDir = V_stristr( pName, pTypeDir );
  1358. if ( pDir )
  1359. {
  1360. pName = pDir + strlen( pTypeDir );
  1361. }
  1362. }
  1363. FileNameHandle_t hFilename = m_Filenames.FindOrAddFileName( pName );
  1364. m_ResourceNames[type].InsertNoSort( hFilename );
  1365. }
  1366. //-----------------------------------------------------------------------------
  1367. // Parse the raw resource list into resource dictionaries
  1368. //-----------------------------------------------------------------------------
  1369. void CQueuedLoader::ParseResourceList( CUtlBuffer &resourceList )
  1370. {
  1371. // parse resource list into known types
  1372. characterset_t breakSet;
  1373. CharacterSetBuild( &breakSet, "" );
  1374. char szToken[MAX_PATH];
  1375. for ( ;; )
  1376. {
  1377. int nTokenSize = resourceList.ParseToken( &breakSet, szToken, sizeof( szToken ) );
  1378. if ( nTokenSize <= 0 )
  1379. {
  1380. break;
  1381. }
  1382. AddResourceToTable( szToken );
  1383. }
  1384. // add any additional resources
  1385. // duplicates don't need to be culled, loaders are supposed to handle resources that already exist
  1386. for ( int i = 0; i < m_AdditionalResources.GetNumStrings(); i++ )
  1387. {
  1388. if ( g_QueuedLoader.GetSpewDetail() )
  1389. {
  1390. Msg( "QueuedLoader: Appending: %s\n", m_AdditionalResources.String( i ) );
  1391. }
  1392. AddResourceToTable( m_AdditionalResources.String( i ) );
  1393. }
  1394. if ( g_QueuedLoader.GetSpewDetail() )
  1395. {
  1396. for ( int i = RESOURCEPRELOAD_UNKNOWN+1; i < RESOURCEPRELOAD_COUNT; i++ )
  1397. {
  1398. Msg( "QueuedLoader: %s: %d Entries\n", g_ResourceLoaderNames[i], m_ResourceNames[i].Count() );
  1399. }
  1400. }
  1401. m_pProgress->UpdateProgress( PROGRESS_PARSEDRESLIST );
  1402. }
  1403. //-----------------------------------------------------------------------------
  1404. // Mark the start of the queued loading process.
  1405. //-----------------------------------------------------------------------------
  1406. bool CQueuedLoader::BeginMapLoading( const char *pMapName, bool bLoadForHDR, bool bOptimizeMapReload )
  1407. {
  1408. if ( IsPC() )
  1409. {
  1410. return false;
  1411. }
  1412. if ( CommandLine()->FindParm( "-noqueuedload" ) || ( g_pFullFileSystem->GetDVDMode() != DVDMODE_STRICT ) )
  1413. {
  1414. return false;
  1415. }
  1416. if ( m_bStarted )
  1417. {
  1418. // already started, shouldn't be started more than once
  1419. Assert( 0 );
  1420. return true;
  1421. }
  1422. COM_TimestampedLog( "CQueuedLoader::BeginMapLoading" );
  1423. // set the IO throttle markers based on available memory
  1424. // these safety watermarks throttle the i/o from flooding memory, when the cores cannot keep up
  1425. // the delta must be larger than any single operation, otherwise deadlock
  1426. // markers that are too close will cause excessive suspension
  1427. size_t usedMemory, freeMemory;
  1428. MemAlloc_GlobalMemoryStatus( &usedMemory, &freeMemory );
  1429. if ( freeMemory >= 64*1024*1024 )
  1430. {
  1431. // lots of available memory, can afford to have let the i/o get ahead
  1432. g_nHighIOSuspensionMark = 10*1024*1024;
  1433. g_nLowIOSuspensionMark = 2*1024*1024;
  1434. }
  1435. else
  1436. {
  1437. // low memory, suspend the i/o more frequently
  1438. g_nHighIOSuspensionMark = 5*1024*1024;
  1439. g_nLowIOSuspensionMark = 1*1024*1024;
  1440. }
  1441. if ( GetSpewDetail() )
  1442. {
  1443. Msg( "QueuedLoader: Suspend I/O at [%.2f,%.2f] MB\n", (float)g_nLowIOSuspensionMark/(1024.0f*1024.0f), (float)g_nHighIOSuspensionMark/(1024.0f*1024.0f) );
  1444. }
  1445. m_bStarted = true;
  1446. m_bDynamic = false;
  1447. m_bLoadForHDR = bLoadForHDR;
  1448. // map pak will be accessed asynchronously throughout loading and into game frame
  1449. g_pFullFileSystem->BeginMapAccess();
  1450. // remove any prior stale entries
  1451. CleanQueue();
  1452. Assert( m_SubmittedJobs.Count() == 0 && g_nActiveJobs == 0 && g_nQueuedJobs == 0 );
  1453. m_bActive = true;
  1454. m_nSubmitCount = 0;
  1455. m_StartTime = Plat_MSTime();
  1456. m_EndTime = 0;
  1457. m_bCanBatch = false;
  1458. m_bBatching = false;
  1459. m_bDoProgress = false;
  1460. g_nIOMemory = 0;
  1461. g_nAnonymousIOMemory = 0;
  1462. g_nIOMemoryPeak = 0;
  1463. g_nAnonymousIOMemoryPeak = 0;
  1464. m_bSameMap = bOptimizeMapReload && ( V_stricmp( pMapName, m_szMapNameToCompareSame ) == 0 );
  1465. if ( m_bSameMap )
  1466. {
  1467. // Data will persist (so reloading a map is v. fast)
  1468. }
  1469. else
  1470. {
  1471. // Full load of the new map's data
  1472. V_strncpy( m_szMapNameToCompareSame, pMapName, sizeof( m_szMapNameToCompareSame ) );
  1473. }
  1474. m_pProgress->BeginProgress();
  1475. m_pProgress->UpdateProgress( PROGRESS_START );
  1476. // load this map's resource list before any other i/o
  1477. char szBaseName[MAX_PATH];
  1478. char szFilename[MAX_PATH];
  1479. V_FileBase( pMapName, szBaseName, sizeof( szBaseName ) );
  1480. V_snprintf( szFilename, sizeof( szFilename ), "reslists_xbox/%s%s.lst", szBaseName, GetPlatformExt() );
  1481. MEM_ALLOC_CREDIT();
  1482. CUtlBuffer resListBuffer( 0, 0, CUtlBuffer::TEXT_BUFFER );
  1483. if ( !g_pFullFileSystem->ReadFile( szFilename, "GAME", resListBuffer, 0, 0 ) )
  1484. {
  1485. // very bad, a valid reslist is critical
  1486. DevWarning( "QueuedLoader: Failed to get reslist '%s', Non-Optimal Loading.\n", szFilename );
  1487. m_bActive = false;
  1488. return false;
  1489. }
  1490. if ( XBX_IsLocalized() )
  1491. {
  1492. // find optional localized reslist fixup
  1493. V_snprintf( szFilename, sizeof( szFilename ), "reslists_xbox/%s%s.lst", XBX_GetLanguageString(), GetPlatformExt() );
  1494. CUtlBuffer localizedBuffer( 0, 0, CUtlBuffer::TEXT_BUFFER );
  1495. if ( g_pFullFileSystem->ReadFile( szFilename, "GAME", localizedBuffer, 0, 0 ) )
  1496. {
  1497. // append it
  1498. resListBuffer.EnsureCapacity( resListBuffer.TellPut() + localizedBuffer.TellPut() );
  1499. resListBuffer.Put( localizedBuffer.PeekGet(), localizedBuffer.TellPut() );
  1500. }
  1501. }
  1502. m_pProgress->UpdateProgress( PROGRESS_GOTRESLIST );
  1503. // due to its size, the bsp load is a lengthy i/o operation
  1504. // this causes a non-batched async i/o operation to commence immediately
  1505. if ( !m_pLoaders[RESOURCEPRELOAD_MODEL]->CreateResource( pMapName ) )
  1506. {
  1507. // very bad, a valid bsp is critical
  1508. DevWarning( "QueuedLoader: Failed to mount BSP '%s', Non-Optimal Loading.\n", pMapName );
  1509. m_bActive = false;
  1510. return false;
  1511. }
  1512. // parse the raw resource list into loader specific dictionaries
  1513. ParseResourceList( resListBuffer );
  1514. // run the distributed precache loaders, generating a batch of i/o requests
  1515. GetJobRequests();
  1516. // event each loader to discard dead resources
  1517. PurgeUnreferencedResources();
  1518. // sort and start async fulfilling the i/o requests
  1519. // waits for all "must complete" jobs to finish
  1520. SubmitBatchedJobsAndWait();
  1521. // progress is only relevant during preload
  1522. // normal load process takes over any progress bar
  1523. // disable progress tracking to prevent any late queued operation from updating
  1524. m_pProgress->EndProgress();
  1525. return m_bActive;
  1526. }
  1527. //-----------------------------------------------------------------------------
  1528. // Signal the end of the queued loading process, i/o will still be in progress.
  1529. //-----------------------------------------------------------------------------
  1530. void CQueuedLoader::EndMapLoading( bool bAbort )
  1531. {
  1532. if ( !m_bStarted )
  1533. {
  1534. // already stopped or never started
  1535. return;
  1536. }
  1537. /////////////////////////////////////////////////////
  1538. // TBD: Cannot abort!!!! feature has not been done //
  1539. /////////////////////////////////////////////////////
  1540. bAbort = false;
  1541. if ( m_bActive )
  1542. {
  1543. if ( bAbort )
  1544. {
  1545. PurgeQueue();
  1546. }
  1547. else
  1548. {
  1549. // finish all outstanding priority jobs
  1550. SubmitPendingJobs();
  1551. while ( g_nHighPriorityJobs != 0 || g_nJobsToFinishBeforePlay != 0 )
  1552. {
  1553. // yield some time
  1554. g_pThreadPool->Yield( MAIN_THREAD_YIELD_TIME );
  1555. }
  1556. }
  1557. m_EndTime = Plat_MSTime();
  1558. m_bActive = false;
  1559. // transmit the end map event
  1560. for ( int i = RESOURCEPRELOAD_UNKNOWN+1; i < RESOURCEPRELOAD_COUNT; i++ )
  1561. {
  1562. if ( m_pLoaders[i] )
  1563. {
  1564. m_pLoaders[i]->OnEndMapLoading( bAbort );
  1565. }
  1566. }
  1567. // free any unclaimed anonymous buffers
  1568. int iIndex = m_AnonymousJobs.First();
  1569. while ( iIndex != m_AnonymousJobs.InvalidIndex() )
  1570. {
  1571. FileJob_t *pFileJob = m_AnonymousJobs[iIndex];
  1572. if ( pFileJob->m_bFreeTargetAfterIO && pFileJob->m_pTargetData )
  1573. {
  1574. g_pFullFileSystem->FreeOptimalReadBuffer( pFileJob->m_pTargetData );
  1575. pFileJob->m_pTargetData = NULL;
  1576. }
  1577. g_nAnonymousIOMemory -= pFileJob->m_nActualBytesRead;
  1578. iIndex = m_AnonymousJobs.Next( iIndex );
  1579. }
  1580. m_AnonymousJobs.Purge();
  1581. if ( g_nIOMemory || g_nAnonymousIOMemory )
  1582. {
  1583. // expected to be zero, otherwise logic flaw
  1584. DevWarning( "CQueuedLoader: Unclaimed I/O memory: total:%d anonymous:%d\n", (int)g_nIOMemory, (int)g_nAnonymousIOMemory );
  1585. g_nIOMemory = 0;
  1586. g_nAnonymousIOMemory = 0;
  1587. }
  1588. // no longer needed
  1589. m_AdditionalResources.RemoveAll();
  1590. }
  1591. g_pFullFileSystem->EndMapAccess();
  1592. m_bStarted = false;
  1593. }
  1594. //-----------------------------------------------------------------------------
  1595. // Returns true if loader is accepting queue requests.
  1596. //-----------------------------------------------------------------------------
  1597. bool CQueuedLoader::IsMapLoading() const
  1598. {
  1599. return m_bActive;
  1600. }
  1601. //-----------------------------------------------------------------------------
  1602. // Returns true if loader is working on same map as last load
  1603. //-----------------------------------------------------------------------------
  1604. bool CQueuedLoader::IsSameMapLoading() const
  1605. {
  1606. return m_bActive && m_bSameMap;
  1607. }
  1608. //-----------------------------------------------------------------------------
  1609. // Returns true if the loader is idle, indicates all i/o and work has completed.
  1610. //-----------------------------------------------------------------------------
  1611. bool CQueuedLoader::IsFinished() const
  1612. {
  1613. return ( m_bActive == false && g_nActiveJobs == 0 && g_nQueuedJobs == 0 );
  1614. }
  1615. //-----------------------------------------------------------------------------
  1616. // Returns true if loader is batching
  1617. //-----------------------------------------------------------------------------
  1618. bool CQueuedLoader::IsBatching() const
  1619. {
  1620. return m_bBatching;
  1621. }
  1622. //-----------------------------------------------------------------------------
  1623. // Returns true if loader is batching
  1624. //-----------------------------------------------------------------------------
  1625. bool CQueuedLoader::IsDynamic() const
  1626. {
  1627. return m_bDynamic;
  1628. }
  1629. int CQueuedLoader::GetSpewDetail() const
  1630. {
  1631. int spewDetail = loader_spew_info.GetInt();
  1632. if ( spewDetail <= 0 )
  1633. {
  1634. return spewDetail;
  1635. }
  1636. return 1 << ( spewDetail - 1 );
  1637. }
  1638. void CQueuedLoader::DynamicLoadMapResource( const char *pFilename, DynamicResourceCallback_t pCallback, void *pContext, void *pContext2 )
  1639. {
  1640. Assert( m_bActive == false );
  1641. m_bActive = true;
  1642. m_bDynamic = true;
  1643. m_DynamicFileName = pFilename;
  1644. m_pfnDynamicCallback = pCallback;
  1645. m_pDynamicContext = pContext;
  1646. m_pDynamicContext2 = pContext2;
  1647. CleanQueue();
  1648. AddResourceToTable( m_DynamicFileName );
  1649. // run the distributed precache loaders, generating a batch of i/o requests
  1650. GetJobRequests();
  1651. // sort and start async fulfilling the i/o requests
  1652. Assert( m_bBatching && g_nActiveJobs == 0 );
  1653. SubmitBatchedJobs();
  1654. Assert( !m_bBatching );
  1655. }
  1656. void CQueuedLoader::QueueDynamicLoadFunctor( CFunctor* pFunctor )
  1657. {
  1658. AUTO_LOCK( m_FunctorQueueMutex );
  1659. m_FunctorQueue.AddToTail( pFunctor );
  1660. }
  1661. bool CQueuedLoader::CompleteDynamicLoad()
  1662. {
  1663. Assert( m_bActive && m_bDynamic && !m_bBatching );
  1664. bool bDone = true;
  1665. if ( m_bDynamic )
  1666. {
  1667. CUtlVector< CFunctor* > functors;
  1668. {
  1669. AUTO_LOCK( m_FunctorQueueMutex );
  1670. functors.Swap( m_FunctorQueue );
  1671. }
  1672. FOR_EACH_VEC( functors, i )
  1673. {
  1674. ( *functors[i] )();
  1675. functors[i]->Release();
  1676. }
  1677. {
  1678. AUTO_LOCK( m_FunctorQueueMutex );
  1679. bDone = m_FunctorQueue.Count() == 0 && g_nQueuedJobs == 0 && g_nActiveJobs == 0;
  1680. }
  1681. if ( bDone )
  1682. {
  1683. if ( m_pfnDynamicCallback )
  1684. {
  1685. ( *m_pfnDynamicCallback )( m_DynamicFileName, m_pDynamicContext, m_pDynamicContext2 );
  1686. }
  1687. m_DynamicFileName.Clear();
  1688. m_bActive = false;
  1689. m_bDynamic = false;
  1690. }
  1691. }
  1692. return bDone;
  1693. }
  1694. void CQueuedLoader::QueueCleanupDynamicLoadFunctor( CFunctor* pFunctor )
  1695. {
  1696. Assert( ThreadInMainThread() );
  1697. m_CleanupFunctorQueue.AddToTail( pFunctor );
  1698. }
  1699. bool CQueuedLoader::CleanupDynamicLoad()
  1700. {
  1701. Assert( ThreadInMainThread() );
  1702. FOR_EACH_VEC( m_CleanupFunctorQueue, i )
  1703. {
  1704. ( *m_CleanupFunctorQueue[i] )();
  1705. m_CleanupFunctorQueue[i]->Release();
  1706. }
  1707. m_CleanupFunctorQueue.Purge();
  1708. return true;
  1709. }