Counter Strike : Global Offensive Source Code
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.

2145 lines
68 KiB

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