FileCacheManager.cpp 41 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927
  1. /*
  2. * Copyright (c) Contributors to the Open 3D Engine Project.
  3. * For complete copyright and license terms please see the LICENSE at the root of this distribution.
  4. *
  5. * SPDX-License-Identifier: Apache-2.0 OR MIT
  6. *
  7. */
  8. #include <FileCacheManager.h>
  9. #include <AzCore/Console/ILogger.h>
  10. #include <AzCore/Debug/Profiler.h>
  11. #include <AzCore/IO/FileIO.h>
  12. #include <AzCore/IO/IStreamer.h>
  13. #include <AzCore/IO/Path/Path.h>
  14. #include <AzCore/IO/Streamer/FileRequest.h>
  15. #include <AzCore/std/string/conversions.h>
  16. #include <AzCore/std/parallel/binary_semaphore.h>
  17. #include <AzCore/StringFunc/StringFunc.h>
  18. #include <AudioAllocators.h>
  19. #include <IAudioSystemImplementation.h>
  20. #include <SoundCVars.h>
  21. #include <AudioSystem_Traits_Platform.h>
  22. #if !defined(AUDIO_RELEASE)
  23. // Debug Draw
  24. #include <AzFramework/Entity/EntityDebugDisplayBus.h>
  25. #endif // !AUDIO_RELEASE
  26. namespace Audio
  27. {
  28. ///////////////////////////////////////////////////////////////////////////////////////////////////
  29. enum EAudioFileFlags : TATLEnumFlagsType
  30. {
  31. eAFF_NOTFOUND = AUDIO_BIT(0),
  32. eAFF_CACHED = AUDIO_BIT(1),
  33. eAFF_MEMALLOCFAIL = AUDIO_BIT(2),
  34. eAFF_REMOVABLE = AUDIO_BIT(3),
  35. eAFF_LOADING = AUDIO_BIT(4),
  36. eAFF_USE_COUNTED = AUDIO_BIT(5),
  37. eAFF_NEEDS_RESET_TO_MANUAL_LOADING = AUDIO_BIT(6),
  38. eAFF_LOCALIZED = AUDIO_BIT(7),
  39. };
  40. ///////////////////////////////////////////////////////////////////////////////////////////////
  41. CFileCacheManager::CFileCacheManager(TATLPreloadRequestLookup& preloadRequests)
  42. : m_preloadRequests(preloadRequests)
  43. , m_currentByteTotal(0)
  44. , m_maxByteTotal(0)
  45. {
  46. }
  47. ///////////////////////////////////////////////////////////////////////////////////////////////
  48. CFileCacheManager::~CFileCacheManager()
  49. {
  50. }
  51. ///////////////////////////////////////////////////////////////////////////////////////////////
  52. void CFileCacheManager::Initialize()
  53. {
  54. AllocateHeap(static_cast<size_t>(Audio::CVars::s_FileCacheManagerMemorySize), "AudioFileCacheManager");
  55. AudioFileCacheManagerNotficationBus::Handler::BusConnect();
  56. }
  57. ///////////////////////////////////////////////////////////////////////////////////////////////
  58. void CFileCacheManager::Release()
  59. {
  60. AudioFileCacheManagerNotficationBus::Handler::BusDisconnect();
  61. // Should we check here for any lingering files?
  62. // ATL unloads everything before getting here, but a stop-gap could be safer.
  63. }
  64. ///////////////////////////////////////////////////////////////////////////////////////////////
  65. void CFileCacheManager::Update()
  66. {
  67. AZ_PROFILE_FUNCTION(Audio);
  68. AudioFileCacheManagerNotficationBus::ExecuteQueuedEvents();
  69. UpdatePreloadRequestsStatus();
  70. }
  71. ///////////////////////////////////////////////////////////////////////////////////////////////
  72. void CFileCacheManager::AllocateHeap(const size_t size, [[maybe_unused]] const char* const usage)
  73. {
  74. if (size > 0)
  75. {
  76. m_maxByteTotal = size << 10;
  77. }
  78. }
  79. ///////////////////////////////////////////////////////////////////////////////////////////////
  80. TAudioFileEntryID CFileCacheManager::TryAddFileCacheEntry(const AZ::rapidxml::xml_node<char>* fileXmlNode, const EATLDataScope dataScope, bool autoLoad)
  81. {
  82. TAudioFileEntryID fileEntryId = INVALID_AUDIO_FILE_ENTRY_ID;
  83. SATLAudioFileEntryInfo fileEntryInfo;
  84. EAudioRequestStatus result = EAudioRequestStatus::None;
  85. AudioSystemImplementationRequestBus::BroadcastResult(result, &AudioSystemImplementationRequestBus::Events::ParseAudioFileEntry, fileXmlNode, &fileEntryInfo);
  86. if (result == EAudioRequestStatus::Success)
  87. {
  88. const char* fileLocation = nullptr;
  89. AudioSystemImplementationRequestBus::BroadcastResult(fileLocation, &AudioSystemImplementationRequestBus::Events::GetAudioFileLocation, &fileEntryInfo);
  90. AZStd::string filePath;
  91. AZ::StringFunc::AssetDatabasePath::Join(fileLocation, fileEntryInfo.sFileName, filePath);
  92. auto newAudioFileEntry = azcreate(CATLAudioFileEntry, (filePath.c_str(), fileEntryInfo.pImplData), Audio::AudioSystemAllocator);
  93. if (newAudioFileEntry)
  94. {
  95. newAudioFileEntry->m_memoryBlockAlignment = fileEntryInfo.nMemoryBlockAlignment;
  96. if (fileEntryInfo.bLocalized)
  97. {
  98. newAudioFileEntry->m_flags.AddFlags(eAFF_LOCALIZED);
  99. }
  100. fileEntryId = AudioStringToID<TAudioFileEntryID>(newAudioFileEntry->m_filePath.c_str());
  101. auto it = m_audioFileEntries.find(fileEntryId);
  102. if (it == m_audioFileEntries.end())
  103. {
  104. if (!autoLoad)
  105. {
  106. // Can now be ref-counted and therefore manually unloaded.
  107. newAudioFileEntry->m_flags.AddFlags(eAFF_USE_COUNTED);
  108. }
  109. newAudioFileEntry->m_dataScope = dataScope;
  110. AZStd::to_lower(newAudioFileEntry->m_filePath.begin(), newAudioFileEntry->m_filePath.end());
  111. auto fileIO = AZ::IO::FileIOBase::GetInstance();
  112. if (AZ::u64 fileSize = 0;
  113. fileIO->Size(newAudioFileEntry->m_filePath.c_str(), fileSize) && fileSize != 0)
  114. {
  115. newAudioFileEntry->m_fileSize = fileSize;
  116. newAudioFileEntry->m_flags.ClearFlags(eAFF_NOTFOUND);
  117. }
  118. m_audioFileEntries[fileEntryId] = newAudioFileEntry;
  119. }
  120. else
  121. {
  122. if (autoLoad && it->second->m_flags.AreAnyFlagsActive(eAFF_USE_COUNTED))
  123. {
  124. // This file entry is upgraded from "manual loading" to "auto loading" but needs a reset to "manual loading" again!
  125. it->second->m_flags.AddFlags(eAFF_NEEDS_RESET_TO_MANUAL_LOADING);
  126. it->second->m_flags.ClearFlags(eAFF_USE_COUNTED);
  127. AZLOG_DEBUG("FileCacheManager - Upgraded file entry from 'Manual' to 'Auto' loading: %s", it->second->m_filePath.c_str());
  128. }
  129. // Entry already exists, free the memory!
  130. AudioSystemImplementationRequestBus::Broadcast(&AudioSystemImplementationRequestBus::Events::DeleteAudioFileEntryData, newAudioFileEntry->m_implData);
  131. azdestroy(newAudioFileEntry, Audio::AudioSystemAllocator);
  132. }
  133. }
  134. }
  135. return fileEntryId;
  136. }
  137. ///////////////////////////////////////////////////////////////////////////////////////////////
  138. bool CFileCacheManager::TryRemoveFileCacheEntry(const TAudioFileEntryID audioFileID, const EATLDataScope dataScope)
  139. {
  140. bool success = false;
  141. const TAudioFileEntries::iterator iter(m_audioFileEntries.find(audioFileID));
  142. if (iter != m_audioFileEntries.end())
  143. {
  144. CATLAudioFileEntry* const audioFileEntry = iter->second;
  145. if (audioFileEntry->m_dataScope == dataScope)
  146. {
  147. UncacheFileCacheEntryInternal(audioFileEntry, true, true);
  148. AudioSystemImplementationRequestBus::Broadcast(&AudioSystemImplementationRequestBus::Events::DeleteAudioFileEntryData, audioFileEntry->m_implData);
  149. azdestroy(audioFileEntry, Audio::AudioSystemAllocator);
  150. m_audioFileEntries.erase(iter);
  151. }
  152. else if (dataScope == eADS_LEVEL_SPECIFIC && audioFileEntry->m_flags.AreAnyFlagsActive(eAFF_NEEDS_RESET_TO_MANUAL_LOADING))
  153. {
  154. audioFileEntry->m_flags.AddFlags(eAFF_USE_COUNTED);
  155. audioFileEntry->m_flags.ClearFlags(eAFF_NEEDS_RESET_TO_MANUAL_LOADING);
  156. AZLOG_DEBUG("FileCacheManager - Downgraded file entry from 'Auto' to 'Manual' loading: %s", audioFileEntry->m_filePath.c_str());
  157. }
  158. }
  159. return success;
  160. }
  161. ///////////////////////////////////////////////////////////////////////////////////////////////
  162. void CFileCacheManager::UpdateLocalizedFileCacheEntries()
  163. {
  164. for (auto& audioFileEntryPair : m_audioFileEntries)
  165. {
  166. CATLAudioFileEntry* const audioFileEntry = audioFileEntryPair.second;
  167. if (audioFileEntry && audioFileEntry->m_flags.AreAnyFlagsActive(eAFF_LOCALIZED))
  168. {
  169. if (audioFileEntry->m_flags.AreAnyFlagsActive(eAFF_CACHED | eAFF_LOADING))
  170. {
  171. // The file needs to be unloaded first.
  172. const AZ::u32 useCount = audioFileEntry->m_useCount;
  173. audioFileEntry->m_useCount = 0; // Needed to uncache without an error.
  174. UncacheFile(audioFileEntry);
  175. UpdateLocalizedFileEntryData(audioFileEntry);
  176. TryCacheFileCacheEntryInternal(audioFileEntry, audioFileEntryPair.first, true, true, useCount);
  177. }
  178. else
  179. {
  180. // The file is not cached or loading, it is safe to update the corresponding CATLAudioFileEntry data.
  181. UpdateLocalizedFileEntryData(audioFileEntry);
  182. }
  183. }
  184. }
  185. }
  186. ///////////////////////////////////////////////////////////////////////////////////////////////
  187. EAudioRequestStatus CFileCacheManager::TryLoadRequest(const TAudioPreloadRequestID preloadRequestID, const bool loadSynchronously, const bool autoLoadOnly)
  188. {
  189. bool fullSuccess = false;
  190. bool fullFailure = true;
  191. auto itPreload = m_preloadRequests.find(preloadRequestID);
  192. if (itPreload != m_preloadRequests.end())
  193. {
  194. CATLPreloadRequest* const preloadRequest = itPreload->second;
  195. if (!preloadRequest->m_cFileEntryIDs.empty() && (!autoLoadOnly || (autoLoadOnly && preloadRequest->m_bAutoLoad)))
  196. {
  197. fullSuccess = true;
  198. for (auto fileId : preloadRequest->m_cFileEntryIDs)
  199. {
  200. auto itFileEntry = m_audioFileEntries.find(fileId);
  201. if (itFileEntry != m_audioFileEntries.end())
  202. {
  203. const bool tempResult = TryCacheFileCacheEntryInternal(itFileEntry->second, fileId, loadSynchronously);
  204. fullSuccess = (fullSuccess && tempResult);
  205. fullFailure = (fullFailure && !tempResult);
  206. }
  207. }
  208. }
  209. if (fullSuccess && preloadRequest->m_allLoaded)
  210. {
  211. // Notify to handlers that the preload is already loaded/cached.
  212. AudioPreloadNotificationBus::Event(preloadRequestID, &AudioPreloadNotificationBus::Events::OnAudioPreloadCached);
  213. }
  214. }
  215. return (
  216. fullSuccess ? EAudioRequestStatus::Success
  217. : (fullFailure ? EAudioRequestStatus::Failure : EAudioRequestStatus::PartialSuccess));
  218. }
  219. ///////////////////////////////////////////////////////////////////////////////////////////////
  220. EAudioRequestStatus CFileCacheManager::TryUnloadRequest(const TAudioPreloadRequestID preloadRequestID)
  221. {
  222. bool fullSuccess = false;
  223. bool fullFailure = true;
  224. auto itPreload = m_preloadRequests.find(preloadRequestID);
  225. if (itPreload != m_preloadRequests.end())
  226. {
  227. CATLPreloadRequest* const preloadRequest = itPreload->second;
  228. if (!preloadRequest->m_cFileEntryIDs.empty())
  229. {
  230. fullSuccess = true;
  231. for (auto fileId : preloadRequest->m_cFileEntryIDs)
  232. {
  233. auto itFileEntry = m_audioFileEntries.find(fileId);
  234. if (itFileEntry != m_audioFileEntries.end())
  235. {
  236. const bool tempResult = UncacheFileCacheEntryInternal(itFileEntry->second, true);
  237. fullSuccess = (fullSuccess && tempResult);
  238. fullFailure = (fullFailure && !tempResult);
  239. }
  240. }
  241. }
  242. if (fullSuccess && !preloadRequest->m_allLoaded)
  243. {
  244. // Notify to handlers the the preload is already unloaded.
  245. AudioPreloadNotificationBus::Event(preloadRequestID, &AudioPreloadNotificationBus::Events::OnAudioPreloadUncached);
  246. }
  247. }
  248. return (
  249. fullSuccess ? EAudioRequestStatus::Success
  250. : (fullFailure ? EAudioRequestStatus::Failure : EAudioRequestStatus::PartialSuccess));
  251. }
  252. ///////////////////////////////////////////////////////////////////////////////////////////////
  253. EAudioRequestStatus CFileCacheManager::UnloadDataByScope(const EATLDataScope dataScope)
  254. {
  255. for (auto it = m_audioFileEntries.begin(); it != m_audioFileEntries.end(); )
  256. {
  257. CATLAudioFileEntry* const audioFileEntry = it->second;
  258. if (audioFileEntry && audioFileEntry->m_dataScope == dataScope)
  259. {
  260. if (UncacheFileCacheEntryInternal(audioFileEntry, true, true))
  261. {
  262. it = m_audioFileEntries.erase(it);
  263. continue;
  264. }
  265. }
  266. ++it;
  267. }
  268. return EAudioRequestStatus::Success;
  269. }
  270. ///////////////////////////////////////////////////////////////////////////////////////////////
  271. bool CFileCacheManager::UncacheFileCacheEntryInternal(CATLAudioFileEntry* const audioFileEntry, const bool now, const bool ignoreUsedCount /* = false */)
  272. {
  273. bool success = false;
  274. // In any case decrement the used count.
  275. if (audioFileEntry->m_useCount > 0)
  276. {
  277. --audioFileEntry->m_useCount;
  278. }
  279. if (audioFileEntry->m_useCount < 1 || ignoreUsedCount)
  280. {
  281. // Must be cached to proceed.
  282. if (audioFileEntry->m_flags.AreAnyFlagsActive(eAFF_CACHED))
  283. {
  284. // Only "use-counted" files can become removable!
  285. if (audioFileEntry->m_flags.AreAnyFlagsActive(eAFF_USE_COUNTED))
  286. {
  287. audioFileEntry->m_flags.AddFlags(eAFF_REMOVABLE);
  288. }
  289. if (now || ignoreUsedCount)
  290. {
  291. UncacheFile(audioFileEntry);
  292. }
  293. }
  294. else if (audioFileEntry->m_flags.AreAnyFlagsActive(eAFF_LOADING | eAFF_MEMALLOCFAIL))
  295. {
  296. AZLOG_DEBUG("FileCacheManager - Trying to remove a loading or mem-failed entry '%s'", audioFileEntry->m_filePath.c_str());
  297. // Reset the entry in case it's still loading or was a memory allocation fail.
  298. UncacheFile(audioFileEntry);
  299. }
  300. // The file was either properly uncached, queued for uncache or not cached at all.
  301. success = true;
  302. }
  303. return success;
  304. }
  305. #if !defined(AUDIO_RELEASE)
  306. ///////////////////////////////////////////////////////////////////////////////////////////////
  307. void CFileCacheManager::DrawDebugInfo(AzFramework::DebugDisplayRequests& debugDisplay, const float posX, const float posY)
  308. {
  309. if (CVars::s_debugDrawOptions.AreAllFlagsActive(static_cast<AZ::u32>(DebugDraw::Options::FileCacheInfo)))
  310. {
  311. const auto frameTime = AZStd::chrono::steady_clock::now();
  312. const float entryDrawSize = 0.8f;
  313. const float entryStepSize = 15.0f;
  314. float positionY = posY + 20.0f;
  315. float positionX = posX + 20.0f;
  316. float time = 0.0f;
  317. float ratio = 0.0f;
  318. float originalAlpha = 0.7f;
  319. // The colors.
  320. AZ::Color white{ 1.0f, 1.0f, 1.0f, originalAlpha }; // file is use-counted
  321. AZ::Color cyan{ 0.0f, 1.0f, 1.0f, originalAlpha }; // file is global scope
  322. AZ::Color orange{ 1.0f, 0.5f, 0.0f, originalAlpha }; // header color
  323. AZ::Color green{ 0.0f, 1.0f, 0.0f, originalAlpha }; // file is removable
  324. AZ::Color red{ 1.0f, 0.0f, 0.0f, originalAlpha }; // memory allocation failed
  325. AZ::Color redish{ 0.7f, 0.0f, 0.0f, originalAlpha }; // file not found
  326. AZ::Color blue{ 0.1f, 0.2f, 0.8f, originalAlpha }; // file is loading
  327. AZ::Color yellow{ 1.0f, 1.0f, 0.0f, originalAlpha }; // file is level scope
  328. AZ::Color darkish{ 0.3f, 0.3f, 0.3f, originalAlpha }; // file is not loaded
  329. const bool displayAll = CVars::s_fcmDrawOptions.GetRawFlags() == 0;
  330. const bool displayGlobals = CVars::s_fcmDrawOptions.AreAllFlagsActive(static_cast<AZ::u32>(FileCacheManagerDebugDraw::Options::Global));
  331. const bool displayLevels = CVars::s_fcmDrawOptions.AreAllFlagsActive(static_cast<AZ::u32>(FileCacheManagerDebugDraw::Options::LevelSpecific));
  332. const bool displayUseCounted =
  333. CVars::s_fcmDrawOptions.AreAllFlagsActive(static_cast<AZ::u32>(FileCacheManagerDebugDraw::Options::UseCounted));
  334. const bool displayLoaded = CVars::s_fcmDrawOptions.AreAllFlagsActive(static_cast<AZ::u32>(FileCacheManagerDebugDraw::Options::Loaded));
  335. // The text
  336. AZStd::string str = AZStd::string::format(
  337. "File Cache Mgr (%zu of %zu KiB) [Total Entries: %zu]", m_currentByteTotal >> 10, m_maxByteTotal >> 10,
  338. m_audioFileEntries.size());
  339. debugDisplay.SetColor(orange);
  340. debugDisplay.Draw2dTextLabel(posX, positionY, entryDrawSize, str.c_str());
  341. positionY += entryStepSize;
  342. for (auto& audioFileEntryPair : m_audioFileEntries)
  343. {
  344. AZ::Color& color = white;
  345. CATLAudioFileEntry* const audioFileEntry = audioFileEntryPair.second;
  346. bool isGlobal = (audioFileEntry->m_dataScope == eADS_GLOBAL);
  347. bool isLevel = (audioFileEntry->m_dataScope == eADS_LEVEL_SPECIFIC);
  348. bool isUseCounted = audioFileEntry->m_flags.AreAnyFlagsActive(eAFF_USE_COUNTED);
  349. bool isLoaded = audioFileEntry->m_flags.AreAnyFlagsActive(eAFF_CACHED);
  350. if (displayAll || (displayGlobals && isGlobal) || (displayLevels && isLevel) || (displayUseCounted && isUseCounted) || (displayLoaded && isLoaded))
  351. {
  352. if (audioFileEntry->m_flags.AreAnyFlagsActive(eAFF_LOADING))
  353. {
  354. color = blue;
  355. }
  356. else if (audioFileEntry->m_flags.AreAnyFlagsActive(eAFF_MEMALLOCFAIL))
  357. {
  358. color = red;
  359. }
  360. else if (audioFileEntry->m_flags.AreAnyFlagsActive(eAFF_REMOVABLE))
  361. {
  362. color = green;
  363. }
  364. else if (!isLoaded)
  365. {
  366. color = darkish;
  367. }
  368. else if (audioFileEntry->m_flags.AreAnyFlagsActive(eAFF_NOTFOUND))
  369. {
  370. color = redish;
  371. }
  372. else if (isGlobal)
  373. {
  374. color = cyan;
  375. }
  376. else if (isLevel)
  377. {
  378. color = yellow;
  379. }
  380. //else isUseCounted
  381. using duration_sec = AZStd::chrono::duration<float>;
  382. time = AZStd::chrono::duration_cast<duration_sec>(frameTime - audioFileEntry->m_timeCached).count();
  383. ratio = time / 5.0f;
  384. originalAlpha = color.GetA();
  385. color.SetA(originalAlpha * AZ::GetClamp(ratio, 0.2f, 1.0f));
  386. bool kiloBytes = false;
  387. size_t fileSize = audioFileEntry->m_fileSize;
  388. if (fileSize >= 1024)
  389. {
  390. fileSize >>= 10;
  391. kiloBytes = true;
  392. }
  393. // Format: "relative/path/filename.ext (230 KiB) [2]"
  394. str = AZStd::string::format(
  395. "%s (%zu %s) [%u]", audioFileEntry->m_filePath.c_str(), fileSize, kiloBytes ? "KiB" : "Bytes",
  396. audioFileEntry->m_useCount);
  397. debugDisplay.SetColor(color);
  398. debugDisplay.Draw2dTextLabel(positionX, positionY, entryDrawSize, str.c_str());
  399. color.SetA(originalAlpha);
  400. positionY += entryStepSize;
  401. }
  402. }
  403. }
  404. }
  405. #endif // !AUDIO_RELEASE
  406. ///////////////////////////////////////////////////////////////////////////////////////////////
  407. bool CFileCacheManager::DoesRequestFitInternal(const size_t requestSize)
  408. {
  409. // Make sure these unsigned values don't flip around.
  410. AZ_Assert(m_currentByteTotal <= m_maxByteTotal, "FileCacheManager DoesRequestFitInternal - Unsigned wraparound detected!");
  411. bool success = false;
  412. if (requestSize <= (m_maxByteTotal - m_currentByteTotal))
  413. {
  414. // Here the requested size is available without the need of first cleaning up.
  415. success = true;
  416. }
  417. else
  418. {
  419. // Determine how much memory would get freed if all eAFF_REMOVABLE files get thrown out.
  420. // We however skip files that are already queued for unload. The request will get queued up in that case.
  421. size_t possibleMemoryGain = 0;
  422. // Check the single file entries for removability.
  423. for (auto& audioFileEntryPair : m_audioFileEntries)
  424. {
  425. CATLAudioFileEntry* const audioFileEntry = audioFileEntryPair.second;
  426. if (audioFileEntry && audioFileEntry->m_flags.AreAllFlagsActive(eAFF_CACHED | eAFF_REMOVABLE))
  427. {
  428. possibleMemoryGain += audioFileEntry->m_fileSize;
  429. }
  430. }
  431. const size_t maxAvailableSize = (m_maxByteTotal - (m_currentByteTotal - possibleMemoryGain));
  432. if (requestSize <= maxAvailableSize)
  433. {
  434. // Here we need to cleanup first before allowing the new request to be allocated.
  435. TryToUncacheFiles();
  436. // We should only indicate success if there's actually really enough room for the new entry!
  437. success = (m_maxByteTotal - m_currentByteTotal) >= requestSize;
  438. }
  439. }
  440. return success;
  441. }
  442. ///////////////////////////////////////////////////////////////////////////////////////////////
  443. void CFileCacheManager::FinishAsyncStreamRequest(AZ::IO::FileRequestHandle request)
  444. {
  445. auto streamer = AZ::Interface<AZ::IO::IStreamer>::Get();
  446. AZ_Assert(streamer, "FileCacheManager - AZ::IO::Streamer is not available.")
  447. // Find the file entry that matches the request handle...
  448. auto fileEntryIter = AZStd::find_if(m_audioFileEntries.begin(), m_audioFileEntries.end(),
  449. [&request] (const AZStd::pair<TAudioFileEntryID, CATLAudioFileEntry*>& data) -> bool
  450. {
  451. return (data.second->m_asyncStreamRequest == request);
  452. }
  453. );
  454. // If found, we finish processing the async file load request...
  455. if (fileEntryIter != m_audioFileEntries.end())
  456. {
  457. void* buffer{};
  458. AZ::u64 numBytesRead{};
  459. [[maybe_unused]] bool result = streamer->GetReadRequestResult(request, buffer, numBytesRead);
  460. AZ_Assert(result, "FileCacheManager - Unable to retrieve read information from the file request. "
  461. "This can happen if the callback was assigned to a request that didn't read.");
  462. CATLAudioFileEntry* audioFileEntry = fileEntryIter->second;
  463. AZ_Assert(audioFileEntry, "FileCacheManager - Audio file entry is null!");
  464. AZ_Assert(buffer == audioFileEntry->m_memoryBlock, "FileCacheManager - The memory buffer doesn't match the file entry memory block!");
  465. FinishCachingFileInternal(audioFileEntry, numBytesRead, streamer->GetRequestStatus(request));
  466. }
  467. }
  468. ///////////////////////////////////////////////////////////////////////////////////////////////
  469. bool CFileCacheManager::FinishCachingFileInternal(CATLAudioFileEntry* const audioFileEntry, [[maybe_unused]] AZ::IO::SizeType bytesRead,
  470. AZ::IO::IStreamerTypes::RequestStatus requestState)
  471. {
  472. AZ_PROFILE_FUNCTION(Audio);
  473. bool success = false;
  474. audioFileEntry->m_asyncStreamRequest.reset();
  475. switch (requestState)
  476. {
  477. case AZ::IO::IStreamerTypes::RequestStatus::Completed:
  478. {
  479. AZ_Assert(
  480. bytesRead == audioFileEntry->m_fileSize,
  481. "FileCacheManager - Sync Streamed Read completed, but bytes read does not match file size!");
  482. if (audioFileEntry->m_flags.AreAnyFlagsActive(eAFF_LOADING))
  483. {
  484. audioFileEntry->m_flags.AddFlags(eAFF_CACHED);
  485. audioFileEntry->m_flags.ClearFlags(eAFF_LOADING);
  486. #if !defined(AUDIO_RELEASE)
  487. audioFileEntry->m_timeCached = AZStd::chrono::steady_clock::now();
  488. #endif // !AUDIO_RELEASE
  489. SATLAudioFileEntryInfo fileEntryInfo;
  490. fileEntryInfo.nMemoryBlockAlignment = audioFileEntry->m_memoryBlockAlignment;
  491. fileEntryInfo.pFileData = audioFileEntry->m_memoryBlock;
  492. fileEntryInfo.nSize = audioFileEntry->m_fileSize;
  493. fileEntryInfo.pImplData = audioFileEntry->m_implData;
  494. AZ::IO::PathView filePath{ audioFileEntry->m_filePath };
  495. fileEntryInfo.sFileName = filePath.Filename().Native().data();
  496. AudioSystemImplementationRequestBus::Broadcast(&AudioSystemImplementationRequestBus::Events::RegisterInMemoryFile, &fileEntryInfo);
  497. success = true;
  498. AZLOG_DEBUG("FileCacheManager - File Cached: '%s'", fileEntryInfo.sFileName);
  499. }
  500. break;
  501. }
  502. case AZ::IO::IStreamerTypes::RequestStatus::Failed:
  503. {
  504. AZLOG_ERROR("FileCacheManager - Async file stream '%s' failed during operation!", audioFileEntry->m_filePath.c_str());
  505. UncacheFileCacheEntryInternal(audioFileEntry, true, true);
  506. break;
  507. }
  508. case AZ::IO::IStreamerTypes::RequestStatus::Canceled:
  509. {
  510. AZLOG_DEBUG("FileCacheManager - Async file stream '%s' was canceled by user!", audioFileEntry->m_filePath.c_str());
  511. UncacheFileCacheEntryInternal(audioFileEntry, true, true);
  512. break;
  513. }
  514. default:
  515. {
  516. break;
  517. }
  518. }
  519. return success;
  520. }
  521. ///////////////////////////////////////////////////////////////////////////////////////////////
  522. void CFileCacheManager::UpdatePreloadRequestsStatus()
  523. {
  524. // Run through the list of preload requests and their fileEntryIDs.
  525. // Check the fileEntries for the CACHED flags and accumulate the 'allLoaded' and 'anyLoaded' status of each preload request.
  526. // If the result is different than what is stored on the preload request, update it and send a notification of
  527. // either cached or uncached.
  528. for (auto& preloadPair : m_preloadRequests)
  529. {
  530. CATLPreloadRequest* preloadRequest = preloadPair.second;
  531. bool wasLoaded = preloadRequest->m_allLoaded;
  532. bool allLoaded = !preloadRequest->m_cFileEntryIDs.empty();
  533. bool anyLoaded = false;
  534. for (auto fileId : preloadRequest->m_cFileEntryIDs)
  535. {
  536. bool cached = false;
  537. auto iter = m_audioFileEntries.find(fileId);
  538. if (iter != m_audioFileEntries.end())
  539. {
  540. cached = iter->second->m_flags.AreAnyFlagsActive(eAFF_CACHED);
  541. }
  542. allLoaded = (allLoaded && cached);
  543. anyLoaded = (anyLoaded || cached);
  544. }
  545. if (allLoaded != wasLoaded && allLoaded)
  546. {
  547. // Loaded now...
  548. preloadRequest->m_allLoaded = allLoaded;
  549. AudioPreloadNotificationBus::Event(preloadPair.first, &AudioPreloadNotificationBus::Events::OnAudioPreloadCached);
  550. }
  551. if (anyLoaded != wasLoaded && !anyLoaded)
  552. {
  553. // Unloaded now...
  554. preloadRequest->m_allLoaded = anyLoaded;
  555. AudioPreloadNotificationBus::Event(preloadPair.first, &AudioPreloadNotificationBus::Events::OnAudioPreloadUncached);
  556. }
  557. }
  558. }
  559. ///////////////////////////////////////////////////////////////////////////////////////////////
  560. bool CFileCacheManager::AllocateMemoryBlockInternal(CATLAudioFileEntry* const audioFileEntry)
  561. {
  562. AZ_PROFILE_FUNCTION(Audio);
  563. // Must not have valid memory yet.
  564. AZ_Assert(!audioFileEntry->m_memoryBlock, "FileCacheManager AllocateMemoryBlockInternal - Memory appears to be set already!");
  565. audioFileEntry->m_memoryBlock = AZ::AllocatorInstance<AudioBankAllocator>::Get().Allocate(
  566. audioFileEntry->m_fileSize,
  567. audioFileEntry->m_memoryBlockAlignment,
  568. 0,
  569. audioFileEntry->m_filePath.c_str(),
  570. __FILE__, __LINE__);
  571. if (!audioFileEntry->m_memoryBlock)
  572. {
  573. // Memory block is either full or too fragmented, let's try to throw everything out that can be removed and allocate again.
  574. TryToUncacheFiles();
  575. // And try again
  576. audioFileEntry->m_memoryBlock = AZ::AllocatorInstance<AudioBankAllocator>::Get().Allocate(
  577. audioFileEntry->m_fileSize,
  578. audioFileEntry->m_memoryBlockAlignment,
  579. 0,
  580. audioFileEntry->m_filePath.c_str(),
  581. __FILE__, __LINE__);
  582. }
  583. return (audioFileEntry->m_memoryBlock != nullptr);
  584. }
  585. ///////////////////////////////////////////////////////////////////////////////////////////////
  586. void CFileCacheManager::UncacheFile(CATLAudioFileEntry* const audioFileEntry)
  587. {
  588. if (audioFileEntry->m_asyncStreamRequest)
  589. {
  590. auto streamer = AZ::Interface<AZ::IO::IStreamer>::Get();
  591. AZ::IO::FileRequestPtr request = streamer->Cancel(audioFileEntry->m_asyncStreamRequest);
  592. AZStd::binary_semaphore wait;
  593. streamer->SetRequestCompleteCallback(
  594. request,
  595. [&wait](AZ::IO::FileRequestHandle)
  596. {
  597. wait.release();
  598. });
  599. streamer->QueueRequest(request);
  600. wait.acquire();
  601. audioFileEntry->m_asyncStreamRequest.reset();
  602. }
  603. if (audioFileEntry->m_memoryBlock)
  604. {
  605. SATLAudioFileEntryInfo fileEntryInfo;
  606. fileEntryInfo.nMemoryBlockAlignment = audioFileEntry->m_memoryBlockAlignment;
  607. fileEntryInfo.pFileData = audioFileEntry->m_memoryBlock;
  608. fileEntryInfo.nSize = audioFileEntry->m_fileSize;
  609. fileEntryInfo.pImplData = audioFileEntry->m_implData;
  610. AZ::IO::PathView filePath{ audioFileEntry->m_filePath };
  611. fileEntryInfo.sFileName = filePath.Filename().Native().data();
  612. EAudioRequestStatus result = EAudioRequestStatus::None;
  613. AudioSystemImplementationRequestBus::BroadcastResult(result, &AudioSystemImplementationRequestBus::Events::UnregisterInMemoryFile, &fileEntryInfo);
  614. if (result == EAudioRequestStatus::Success)
  615. {
  616. AZLOG_DEBUG("FileCacheManager - File Uncached: '%s'", fileEntryInfo.sFileName);
  617. }
  618. else
  619. {
  620. AZLOG_NOTICE("FileCacheManager - Unable to uncache file '%s'", fileEntryInfo.sFileName);
  621. return;
  622. }
  623. }
  624. AZ::AllocatorInstance<AudioBankAllocator>::Get().DeAllocate(
  625. audioFileEntry->m_memoryBlock,
  626. audioFileEntry->m_fileSize,
  627. audioFileEntry->m_memoryBlockAlignment
  628. );
  629. audioFileEntry->m_flags.ClearFlags(eAFF_CACHED | eAFF_REMOVABLE);
  630. m_currentByteTotal -= audioFileEntry->m_fileSize;
  631. AZ_Warning("FileCacheManager", audioFileEntry->m_useCount == 0, "Use-count of file '%s' is non-zero while uncaching it! Use Count: %d", audioFileEntry->m_filePath.c_str(), audioFileEntry->m_useCount);
  632. audioFileEntry->m_useCount = 0;
  633. #if !defined(AUDIO_RELEASE)
  634. audioFileEntry->m_timeCached = AZStd::chrono::steady_clock::time_point();
  635. #endif // !AUDIO_RELEASE
  636. }
  637. ///////////////////////////////////////////////////////////////////////////////////////////////
  638. void CFileCacheManager::TryToUncacheFiles()
  639. {
  640. for (auto& audioFileEntryPair : m_audioFileEntries)
  641. {
  642. CATLAudioFileEntry* const audioFileEntry = audioFileEntryPair.second;
  643. if (audioFileEntry && audioFileEntry->m_flags.AreAllFlagsActive(eAFF_CACHED | eAFF_REMOVABLE))
  644. {
  645. UncacheFileCacheEntryInternal(audioFileEntry, true);
  646. }
  647. }
  648. }
  649. ///////////////////////////////////////////////////////////////////////////////////////////////
  650. void CFileCacheManager::UpdateLocalizedFileEntryData(CATLAudioFileEntry* const audioFileEntry)
  651. {
  652. static SATLAudioFileEntryInfo fileEntryInfo;
  653. fileEntryInfo.bLocalized = true;
  654. fileEntryInfo.nSize = 0;
  655. fileEntryInfo.pFileData = nullptr;
  656. fileEntryInfo.nMemoryBlockAlignment = 0;
  657. AZ::IO::FixedMaxPath filePath{ audioFileEntry->m_filePath };
  658. AZStd::string_view fileName{ filePath.Filename().Native() };
  659. fileEntryInfo.pImplData = audioFileEntry->m_implData;
  660. fileEntryInfo.sFileName = fileName.data();
  661. const char* fileLocation = nullptr;
  662. AudioSystemImplementationRequestBus::BroadcastResult(fileLocation, &AudioSystemImplementationRequestBus::Events::GetAudioFileLocation, &fileEntryInfo);
  663. if (fileLocation && fileLocation[0] != '\0')
  664. {
  665. audioFileEntry->m_filePath.assign(fileLocation);
  666. audioFileEntry->m_filePath.append(fileName.data(), fileName.size());
  667. }
  668. else
  669. {
  670. AZ_WarningOnce("FileCacheManager", fileLocation != nullptr, "GetAudioFileLocation returned null when getting a localized file path! Path will not be changed.");
  671. }
  672. AZStd::to_lower(audioFileEntry->m_filePath.begin(), audioFileEntry->m_filePath.end());
  673. AZ::u64 fileSize = 0;
  674. auto fileIO = AZ::IO::FileIOBase::GetInstance();
  675. fileIO->Size(audioFileEntry->m_filePath.c_str(), fileSize);
  676. audioFileEntry->m_fileSize = fileSize;
  677. AZ_Assert(audioFileEntry->m_fileSize != 0, "FileCacheManager - UpdateLocalizedFileEntryData expected file size to be greater than zero!");
  678. }
  679. ///////////////////////////////////////////////////////////////////////////////////////////////
  680. bool CFileCacheManager::TryCacheFileCacheEntryInternal(
  681. CATLAudioFileEntry* const audioFileEntry,
  682. [[maybe_unused]] const TAudioFileEntryID fileEntryId,
  683. [[maybe_unused]] const bool loadSynchronously,
  684. const bool overrideUseCount /* = false */,
  685. const AZ::u32 useCount /* = 0 */)
  686. {
  687. AZ_PROFILE_FUNCTION(Audio);
  688. bool success = false;
  689. if (!audioFileEntry->m_filePath.empty() && !audioFileEntry->m_flags.AreAnyFlagsActive(eAFF_CACHED | eAFF_LOADING))
  690. {
  691. if (DoesRequestFitInternal(audioFileEntry->m_fileSize) && AllocateMemoryBlockInternal(audioFileEntry))
  692. {
  693. auto streamer = AZ::Interface<AZ::IO::IStreamer>::Get();
  694. AZ_Assert(streamer, "FileCacheManager - Streamer should be ready!");
  695. audioFileEntry->m_flags.AddFlags(eAFF_LOADING);
  696. if (loadSynchronously)
  697. {
  698. AZ::IO::FileRequestPtr request = streamer->Read(
  699. audioFileEntry->m_filePath.c_str(),
  700. audioFileEntry->m_memoryBlock,
  701. audioFileEntry->m_fileSize,
  702. audioFileEntry->m_fileSize,
  703. AZ::IO::IStreamerTypes::s_deadlineNow,
  704. AZ::IO::IStreamerTypes::s_priorityHigh);
  705. AZStd::binary_semaphore wait;
  706. streamer->SetRequestCompleteCallback(
  707. request,
  708. [&wait](AZ::IO::FileRequestHandle)
  709. {
  710. wait.release();
  711. });
  712. streamer->QueueRequest(request);
  713. wait.acquire();
  714. AZ::IO::IStreamerTypes::RequestStatus status = streamer->GetRequestStatus(request);
  715. if (FinishCachingFileInternal(audioFileEntry, audioFileEntry->m_fileSize, status))
  716. {
  717. m_currentByteTotal += audioFileEntry->m_fileSize;
  718. success = true;
  719. }
  720. }
  721. else
  722. {
  723. if (!audioFileEntry->m_asyncStreamRequest)
  724. {
  725. audioFileEntry->m_asyncStreamRequest = streamer->CreateRequest();
  726. }
  727. streamer->Read(
  728. audioFileEntry->m_asyncStreamRequest,
  729. audioFileEntry->m_filePath.c_str(),
  730. audioFileEntry->m_memoryBlock,
  731. audioFileEntry->m_fileSize,
  732. audioFileEntry->m_fileSize,
  733. AZ::IO::IStreamerTypes::s_noDeadline,
  734. AZ::IO::IStreamerTypes::s_priorityHigh);
  735. streamer->SetRequestCompleteCallback(
  736. audioFileEntry->m_asyncStreamRequest,
  737. [](AZ::IO::FileRequestHandle request)
  738. {
  739. AZ_PROFILE_FUNCTION(Audio);
  740. AudioFileCacheManagerNotficationBus::QueueBroadcast(
  741. &AudioFileCacheManagerNotficationBus::Events::FinishAsyncStreamRequest,
  742. request);
  743. });
  744. streamer->QueueRequest(audioFileEntry->m_asyncStreamRequest);
  745. // Increase total size even though async request is processing...
  746. m_currentByteTotal += audioFileEntry->m_fileSize;
  747. success = true;
  748. }
  749. }
  750. else
  751. {
  752. // Cannot have a valid memory block!
  753. AZ_Assert(audioFileEntry->m_memoryBlock == nullptr,
  754. "FileCacheManager - Memory block should be null after memory allocation failure!");
  755. // This unfortunately is a total memory allocation fail.
  756. audioFileEntry->m_flags.AddFlags(eAFF_MEMALLOCFAIL);
  757. // The user should be made aware of it.
  758. AZLOG_ERROR(
  759. "FileCacheManager - Could not cache '%s' - out of memory or fragmented memory!", audioFileEntry->m_filePath.c_str());
  760. }
  761. }
  762. else if (audioFileEntry->m_flags.AreAnyFlagsActive(eAFF_CACHED | eAFF_LOADING))
  763. {
  764. AZLOG_DEBUG(
  765. "FileCacheManager - Skipping '%s' - it's either already loaded or currently loading!", audioFileEntry->m_filePath.c_str());
  766. success = true;
  767. }
  768. else if (audioFileEntry->m_flags.AreAnyFlagsActive(eAFF_NOTFOUND))
  769. {
  770. AZLOG_WARN(
  771. "FileCacheManager - Could not cache '%s' - file was not found at that location!", audioFileEntry->m_filePath.c_str());
  772. }
  773. // Increment the used count on manually-loaded files.
  774. if (audioFileEntry->m_flags.AreAnyFlagsActive(eAFF_USE_COUNTED) && audioFileEntry->m_flags.AreAnyFlagsActive(eAFF_CACHED | eAFF_LOADING))
  775. {
  776. if (overrideUseCount)
  777. {
  778. audioFileEntry->m_useCount = useCount;
  779. }
  780. else
  781. {
  782. ++audioFileEntry->m_useCount;
  783. }
  784. // Make sure to handle the eAFCS_REMOVABLE flag according to the m_useCount count.
  785. if (audioFileEntry->m_useCount != 0)
  786. {
  787. audioFileEntry->m_flags.ClearFlags(eAFF_REMOVABLE);
  788. }
  789. else
  790. {
  791. audioFileEntry->m_flags.AddFlags(eAFF_REMOVABLE);
  792. }
  793. }
  794. return success;
  795. }
  796. } // namespace Audio