ResourceCache.cpp 36 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134
  1. // Copyright (c) 2008-2022 the Urho3D project
  2. // License: MIT
  3. #include "../Precompiled.h"
  4. #include "../Core/Context.h"
  5. #include "../Core/CoreEvents.h"
  6. #include "../Core/Profiler.h"
  7. #include "../Core/WorkQueue.h"
  8. #include "../IO/FileSystem.h"
  9. #include "../IO/FileWatcher.h"
  10. #include "../IO/Log.h"
  11. #include "../IO/PackageFile.h"
  12. #include "../Resource/BackgroundLoader.h"
  13. #include "../Resource/Image.h"
  14. #include "../Resource/JSONFile.h"
  15. #include "../Resource/PListFile.h"
  16. #include "../Resource/ResourceCache.h"
  17. #include "../Resource/ResourceEvents.h"
  18. #include "../Resource/XMLFile.h"
  19. #include "../DebugNew.h"
  20. #include <cstdio>
  21. namespace Urho3D
  22. {
  23. static const char* checkDirs[] =
  24. {
  25. "Fonts",
  26. "Materials",
  27. "Models",
  28. "Music",
  29. "Objects",
  30. "Particle",
  31. "PostProcess",
  32. "RenderPaths",
  33. "Scenes",
  34. "Scripts",
  35. "Sounds",
  36. "Shaders",
  37. "Techniques",
  38. "Textures",
  39. "UI",
  40. nullptr
  41. };
  42. static const SharedPtr<Resource> noResource;
  43. ResourceCache::ResourceCache(Context* context) :
  44. Object(context),
  45. autoReloadResources_(false),
  46. returnFailedResources_(false),
  47. searchPackagesFirst_(true),
  48. isRouting_(false),
  49. finishBackgroundResourcesMs_(5)
  50. {
  51. // Register Resource library object factories
  52. RegisterResourceLibrary(context_);
  53. #ifdef URHO3D_THREADING
  54. // Create resource background loader. Its thread will start on the first background request
  55. backgroundLoader_ = new BackgroundLoader(this);
  56. #endif
  57. // Subscribe BeginFrame for handling directory watchers and background loaded resource finalization
  58. SubscribeToEvent(E_BEGINFRAME, URHO3D_HANDLER(ResourceCache, HandleBeginFrame));
  59. }
  60. ResourceCache::~ResourceCache()
  61. {
  62. #ifdef URHO3D_THREADING
  63. // Shut down the background loader first
  64. backgroundLoader_.Reset();
  65. #endif
  66. }
  67. bool ResourceCache::AddResourceDir(const String& pathName, unsigned priority)
  68. {
  69. MutexLock lock(resourceMutex_);
  70. auto* fileSystem = GetSubsystem<FileSystem>();
  71. if (!fileSystem || !fileSystem->DirExists(pathName))
  72. {
  73. URHO3D_LOGERROR("Could not open directory " + pathName);
  74. return false;
  75. }
  76. // Convert path to absolute
  77. String fixedPath = SanitateResourceDirName(pathName);
  78. // Check that the same path does not already exist
  79. for (const String& resourceDir : resourceDirs_)
  80. {
  81. if (!resourceDir.Compare(fixedPath, false))
  82. return true;
  83. }
  84. if (priority < resourceDirs_.Size())
  85. resourceDirs_.Insert(priority, fixedPath);
  86. else
  87. resourceDirs_.Push(fixedPath);
  88. // If resource auto-reloading active, create a file watcher for the directory
  89. if (autoReloadResources_)
  90. {
  91. SharedPtr<FileWatcher> watcher(new FileWatcher(context_));
  92. watcher->StartWatching(fixedPath, true);
  93. fileWatchers_.Push(watcher);
  94. }
  95. URHO3D_LOGINFO("Added resource path " + fixedPath);
  96. return true;
  97. }
  98. bool ResourceCache::AddPackageFile(PackageFile* package, unsigned priority)
  99. {
  100. MutexLock lock(resourceMutex_);
  101. // Do not add packages that failed to load
  102. if (!package || !package->GetNumFiles())
  103. {
  104. URHO3D_LOGERRORF("Could not add package file %s due to load failure", package->GetName().CString());
  105. return false;
  106. }
  107. if (priority < packages_.Size())
  108. packages_.Insert(priority, SharedPtr<PackageFile>(package));
  109. else
  110. packages_.Push(SharedPtr<PackageFile>(package));
  111. URHO3D_LOGINFO("Added resource package " + package->GetName());
  112. return true;
  113. }
  114. bool ResourceCache::AddPackageFile(const String& fileName, unsigned priority)
  115. {
  116. SharedPtr<PackageFile> package(new PackageFile(context_));
  117. return package->Open(fileName) && AddPackageFile(package, priority);
  118. }
  119. bool ResourceCache::AddManualResource(Resource* resource)
  120. {
  121. if (!resource)
  122. {
  123. URHO3D_LOGERROR("Null manual resource");
  124. return false;
  125. }
  126. const String& name = resource->GetName();
  127. if (name.Empty())
  128. {
  129. URHO3D_LOGERROR("Manual resource with empty name, can not add");
  130. return false;
  131. }
  132. resource->ResetUseTimer();
  133. resourceGroups_[resource->GetType()].resources_[resource->GetNameHash()] = resource;
  134. UpdateResourceGroup(resource->GetType());
  135. return true;
  136. }
  137. void ResourceCache::RemoveResourceDir(const String& pathName)
  138. {
  139. MutexLock lock(resourceMutex_);
  140. String fixedPath = SanitateResourceDirName(pathName);
  141. for (unsigned i = 0; i < resourceDirs_.Size(); ++i)
  142. {
  143. if (!resourceDirs_[i].Compare(fixedPath, false))
  144. {
  145. resourceDirs_.Erase(i);
  146. // Remove the filewatcher with the matching path
  147. for (unsigned j = 0; j < fileWatchers_.Size(); ++j)
  148. {
  149. if (!fileWatchers_[j]->GetPath().Compare(fixedPath, false))
  150. {
  151. fileWatchers_.Erase(j);
  152. break;
  153. }
  154. }
  155. URHO3D_LOGINFO("Removed resource path " + fixedPath);
  156. return;
  157. }
  158. }
  159. }
  160. void ResourceCache::RemovePackageFile(PackageFile* package, bool releaseResources, bool forceRelease)
  161. {
  162. MutexLock lock(resourceMutex_);
  163. for (Vector<SharedPtr<PackageFile>>::Iterator i = packages_.Begin(); i != packages_.End(); ++i)
  164. {
  165. if (*i == package)
  166. {
  167. if (releaseResources)
  168. ReleasePackageResources(*i, forceRelease);
  169. URHO3D_LOGINFO("Removed resource package " + (*i)->GetName());
  170. packages_.Erase(i);
  171. return;
  172. }
  173. }
  174. }
  175. void ResourceCache::RemovePackageFile(const String& fileName, bool releaseResources, bool forceRelease)
  176. {
  177. MutexLock lock(resourceMutex_);
  178. // Compare the name and extension only, not the path
  179. String fileNameNoPath = GetFileNameAndExtension(fileName);
  180. for (Vector<SharedPtr<PackageFile>>::Iterator i = packages_.Begin(); i != packages_.End(); ++i)
  181. {
  182. if (!GetFileNameAndExtension((*i)->GetName()).Compare(fileNameNoPath, false))
  183. {
  184. if (releaseResources)
  185. ReleasePackageResources(*i, forceRelease);
  186. URHO3D_LOGINFO("Removed resource package " + (*i)->GetName());
  187. packages_.Erase(i);
  188. return;
  189. }
  190. }
  191. }
  192. void ResourceCache::ReleaseResource(StringHash type, const String& name, bool force)
  193. {
  194. StringHash nameHash(name);
  195. const SharedPtr<Resource>& existingRes = FindResource(type, nameHash);
  196. if (!existingRes)
  197. return;
  198. // If other references exist, do not release, unless forced
  199. if ((existingRes.Refs() == 1 && existingRes.WeakRefs() == 0) || force)
  200. {
  201. resourceGroups_[type].resources_.Erase(nameHash);
  202. UpdateResourceGroup(type);
  203. }
  204. }
  205. void ResourceCache::ReleaseResources(StringHash type, bool force)
  206. {
  207. bool released = false;
  208. HashMap<StringHash, ResourceGroup>::Iterator i = resourceGroups_.Find(type);
  209. if (i != resourceGroups_.End())
  210. {
  211. for (HashMap<StringHash, SharedPtr<Resource>>::Iterator j = i->second_.resources_.Begin();
  212. j != i->second_.resources_.End();)
  213. {
  214. HashMap<StringHash, SharedPtr<Resource>>::Iterator current = j++;
  215. // If other references exist, do not release, unless forced
  216. if ((current->second_.Refs() == 1 && current->second_.WeakRefs() == 0) || force)
  217. {
  218. i->second_.resources_.Erase(current);
  219. released = true;
  220. }
  221. }
  222. }
  223. if (released)
  224. UpdateResourceGroup(type);
  225. }
  226. void ResourceCache::ReleaseResources(StringHash type, const String& partialName, bool force)
  227. {
  228. bool released = false;
  229. HashMap<StringHash, ResourceGroup>::Iterator i = resourceGroups_.Find(type);
  230. if (i != resourceGroups_.End())
  231. {
  232. for (HashMap<StringHash, SharedPtr<Resource>>::Iterator j = i->second_.resources_.Begin();
  233. j != i->second_.resources_.End();)
  234. {
  235. HashMap<StringHash, SharedPtr<Resource>>::Iterator current = j++;
  236. if (current->second_->GetName().Contains(partialName))
  237. {
  238. // If other references exist, do not release, unless forced
  239. if ((current->second_.Refs() == 1 && current->second_.WeakRefs() == 0) || force)
  240. {
  241. i->second_.resources_.Erase(current);
  242. released = true;
  243. }
  244. }
  245. }
  246. }
  247. if (released)
  248. UpdateResourceGroup(type);
  249. }
  250. void ResourceCache::ReleaseResources(const String& partialName, bool force)
  251. {
  252. // Some resources refer to others, like materials to textures. Repeat the release logic as many times as necessary to ensure
  253. // these get released. This is not necessary if forcing release
  254. bool released;
  255. do
  256. {
  257. released = false;
  258. for (HashMap<StringHash, ResourceGroup>::Iterator i = resourceGroups_.Begin(); i != resourceGroups_.End(); ++i)
  259. {
  260. for (HashMap<StringHash, SharedPtr<Resource>>::Iterator j = i->second_.resources_.Begin();
  261. j != i->second_.resources_.End();)
  262. {
  263. HashMap<StringHash, SharedPtr<Resource>>::Iterator current = j++;
  264. if (current->second_->GetName().Contains(partialName))
  265. {
  266. // If other references exist, do not release, unless forced
  267. if ((current->second_.Refs() == 1 && current->second_.WeakRefs() == 0) || force)
  268. {
  269. i->second_.resources_.Erase(current);
  270. released = true;
  271. }
  272. }
  273. }
  274. if (released)
  275. UpdateResourceGroup(i->first_);
  276. }
  277. } while (released && !force);
  278. }
  279. void ResourceCache::ReleaseAllResources(bool force)
  280. {
  281. bool released;
  282. do
  283. {
  284. released = false;
  285. for (HashMap<StringHash, ResourceGroup>::Iterator i = resourceGroups_.Begin();
  286. i != resourceGroups_.End(); ++i)
  287. {
  288. for (HashMap<StringHash, SharedPtr<Resource>>::Iterator j = i->second_.resources_.Begin();
  289. j != i->second_.resources_.End();)
  290. {
  291. HashMap<StringHash, SharedPtr<Resource>>::Iterator current = j++;
  292. // If other references exist, do not release, unless forced
  293. if ((current->second_.Refs() == 1 && current->second_.WeakRefs() == 0) || force)
  294. {
  295. i->second_.resources_.Erase(current);
  296. released = true;
  297. }
  298. }
  299. if (released)
  300. UpdateResourceGroup(i->first_);
  301. }
  302. } while (released && !force);
  303. }
  304. bool ResourceCache::ReloadResource(Resource* resource)
  305. {
  306. if (!resource)
  307. return false;
  308. resource->SendEvent(E_RELOADSTARTED);
  309. bool success = false;
  310. SharedPtr<File> file = GetFile(resource->GetName());
  311. if (file)
  312. success = resource->Load(*(file.Get()));
  313. if (success)
  314. {
  315. resource->ResetUseTimer();
  316. UpdateResourceGroup(resource->GetType());
  317. resource->SendEvent(E_RELOADFINISHED);
  318. return true;
  319. }
  320. // If reloading failed, do not remove the resource from cache, to allow for a new live edit to
  321. // attempt loading again
  322. resource->SendEvent(E_RELOADFAILED);
  323. return false;
  324. }
  325. void ResourceCache::ReloadResourceWithDependencies(const String& fileName)
  326. {
  327. StringHash fileNameHash(fileName);
  328. // If the filename is a resource we keep track of, reload it
  329. const SharedPtr<Resource>& resource = FindResource(fileNameHash);
  330. if (resource)
  331. {
  332. URHO3D_LOGDEBUG("Reloading changed resource " + fileName);
  333. ReloadResource(resource);
  334. }
  335. // Always perform dependency resource check for resource loaded from XML file as it could be used in inheritance
  336. if (!resource || GetExtension(resource->GetName()) == ".xml")
  337. {
  338. // Check if this is a dependency resource, reload dependents
  339. HashMap<StringHash, HashSet<StringHash>>::ConstIterator j = dependentResources_.Find(fileNameHash);
  340. if (j != dependentResources_.End())
  341. {
  342. // Reloading a resource may modify the dependency tracking structure. Therefore collect the
  343. // resources we need to reload first
  344. Vector<SharedPtr<Resource>> dependents;
  345. dependents.Reserve(j->second_.Size());
  346. for (HashSet<StringHash>::ConstIterator k = j->second_.Begin(); k != j->second_.End(); ++k)
  347. {
  348. const SharedPtr<Resource>& dependent = FindResource(*k);
  349. if (dependent)
  350. dependents.Push(dependent);
  351. }
  352. for (const SharedPtr<Resource>& dependent : dependents)
  353. {
  354. URHO3D_LOGDEBUG("Reloading resource " + dependent->GetName() + " depending on " + fileName);
  355. ReloadResource(dependent);
  356. }
  357. }
  358. }
  359. }
  360. void ResourceCache::SetMemoryBudget(StringHash type, unsigned long long budget)
  361. {
  362. resourceGroups_[type].memoryBudget_ = budget;
  363. }
  364. void ResourceCache::SetAutoReloadResources(bool enable)
  365. {
  366. if (enable != autoReloadResources_)
  367. {
  368. if (enable)
  369. {
  370. for (const String& resourceDir : resourceDirs_)
  371. {
  372. SharedPtr<FileWatcher> watcher(new FileWatcher(context_));
  373. watcher->StartWatching(resourceDir, true);
  374. fileWatchers_.Push(watcher);
  375. }
  376. }
  377. else
  378. {
  379. fileWatchers_.Clear();
  380. }
  381. autoReloadResources_ = enable;
  382. }
  383. }
  384. void ResourceCache::AddResourceRouter(ResourceRouter* router, bool addAsFirst)
  385. {
  386. // Check for duplicate
  387. for (const SharedPtr<ResourceRouter>& resourceRouter : resourceRouters_)
  388. {
  389. if (resourceRouter == router)
  390. return;
  391. }
  392. if (addAsFirst)
  393. resourceRouters_.Insert(0, SharedPtr<ResourceRouter>(router));
  394. else
  395. resourceRouters_.Push(SharedPtr<ResourceRouter>(router));
  396. }
  397. void ResourceCache::RemoveResourceRouter(ResourceRouter* router)
  398. {
  399. for (unsigned i = 0; i < resourceRouters_.Size(); ++i)
  400. {
  401. if (resourceRouters_[i] == router)
  402. {
  403. resourceRouters_.Erase(i);
  404. return;
  405. }
  406. }
  407. }
  408. SharedPtr<File> ResourceCache::GetFile(const String& name, bool sendEventOnFailure)
  409. {
  410. MutexLock lock(resourceMutex_);
  411. String sanitatedName = SanitateResourceName(name);
  412. if (!isRouting_)
  413. {
  414. isRouting_ = true;
  415. for (const SharedPtr<ResourceRouter>& resourceRouter : resourceRouters_)
  416. resourceRouter->Route(sanitatedName, RESOURCE_GETFILE);
  417. isRouting_ = false;
  418. }
  419. if (sanitatedName.Length())
  420. {
  421. File* file = nullptr;
  422. if (searchPackagesFirst_)
  423. {
  424. file = SearchPackages(sanitatedName);
  425. if (!file)
  426. file = SearchResourceDirs(sanitatedName);
  427. }
  428. else
  429. {
  430. file = SearchResourceDirs(sanitatedName);
  431. if (!file)
  432. file = SearchPackages(sanitatedName);
  433. }
  434. if (file)
  435. return SharedPtr<File>(file);
  436. }
  437. if (sendEventOnFailure)
  438. {
  439. if (resourceRouters_.Size() && sanitatedName.Empty() && !name.Empty())
  440. URHO3D_LOGERROR("Resource request " + name + " was blocked");
  441. else
  442. URHO3D_LOGERROR("Could not find resource " + sanitatedName);
  443. if (Thread::IsMainThread())
  444. {
  445. using namespace ResourceNotFound;
  446. VariantMap& eventData = GetEventDataMap();
  447. eventData[P_RESOURCENAME] = sanitatedName.Length() ? sanitatedName : name;
  448. SendEvent(E_RESOURCENOTFOUND, eventData);
  449. }
  450. }
  451. return SharedPtr<File>();
  452. }
  453. Resource* ResourceCache::GetExistingResource(StringHash type, const String& name)
  454. {
  455. String sanitatedName = SanitateResourceName(name);
  456. if (!Thread::IsMainThread())
  457. {
  458. URHO3D_LOGERROR("Attempted to get resource " + sanitatedName + " from outside the main thread");
  459. return nullptr;
  460. }
  461. // If empty name, return null pointer immediately
  462. if (sanitatedName.Empty())
  463. return nullptr;
  464. StringHash nameHash(sanitatedName);
  465. const SharedPtr<Resource>& existing = FindResource(type, nameHash);
  466. return existing;
  467. }
  468. Resource* ResourceCache::GetResource(StringHash type, const String& name, bool sendEventOnFailure)
  469. {
  470. String sanitatedName = SanitateResourceName(name);
  471. if (!Thread::IsMainThread())
  472. {
  473. URHO3D_LOGERROR("Attempted to get resource " + sanitatedName + " from outside the main thread");
  474. return nullptr;
  475. }
  476. // If empty name, return null pointer immediately
  477. if (sanitatedName.Empty())
  478. return nullptr;
  479. StringHash nameHash(sanitatedName);
  480. #ifdef URHO3D_THREADING
  481. // Check if the resource is being background loaded but is now needed immediately
  482. backgroundLoader_->WaitForResource(type, nameHash);
  483. #endif
  484. const SharedPtr<Resource>& existing = FindResource(type, nameHash);
  485. if (existing)
  486. return existing;
  487. SharedPtr<Resource> resource;
  488. // Make sure the pointer is non-null and is a Resource subclass
  489. resource = DynamicCast<Resource>(context_->CreateObject(type));
  490. if (!resource)
  491. {
  492. URHO3D_LOGERROR("Could not load unknown resource type " + String(type));
  493. if (sendEventOnFailure)
  494. {
  495. using namespace UnknownResourceType;
  496. VariantMap& eventData = GetEventDataMap();
  497. eventData[P_RESOURCETYPE] = type;
  498. SendEvent(E_UNKNOWNRESOURCETYPE, eventData);
  499. }
  500. return nullptr;
  501. }
  502. // Attempt to load the resource
  503. SharedPtr<File> file = GetFile(sanitatedName, sendEventOnFailure);
  504. if (!file)
  505. return nullptr; // Error is already logged
  506. URHO3D_LOGDEBUG("Loading resource " + sanitatedName);
  507. resource->SetName(sanitatedName);
  508. if (!resource->Load(*(file.Get())))
  509. {
  510. // Error should already been logged by corresponding resource descendant class
  511. if (sendEventOnFailure)
  512. {
  513. using namespace LoadFailed;
  514. VariantMap& eventData = GetEventDataMap();
  515. eventData[P_RESOURCENAME] = sanitatedName;
  516. SendEvent(E_LOADFAILED, eventData);
  517. }
  518. if (!returnFailedResources_)
  519. return nullptr;
  520. }
  521. // Store to cache
  522. resource->ResetUseTimer();
  523. resourceGroups_[type].resources_[nameHash] = resource;
  524. UpdateResourceGroup(type);
  525. return resource;
  526. }
  527. bool ResourceCache::BackgroundLoadResource(StringHash type, const String& name, bool sendEventOnFailure, Resource* caller)
  528. {
  529. #ifdef URHO3D_THREADING
  530. // If empty name, fail immediately
  531. String sanitatedName = SanitateResourceName(name);
  532. if (sanitatedName.Empty())
  533. return false;
  534. // First check if already exists as a loaded resource
  535. StringHash nameHash(sanitatedName);
  536. if (FindResource(type, nameHash) != noResource)
  537. return false;
  538. return backgroundLoader_->QueueResource(type, sanitatedName, sendEventOnFailure, caller);
  539. #else
  540. // When threading not supported, fall back to synchronous loading
  541. return GetResource(type, name, sendEventOnFailure);
  542. #endif
  543. }
  544. SharedPtr<Resource> ResourceCache::GetTempResource(StringHash type, const String& name, bool sendEventOnFailure)
  545. {
  546. String sanitatedName = SanitateResourceName(name);
  547. // If empty name, return null pointer immediately
  548. if (sanitatedName.Empty())
  549. return SharedPtr<Resource>();
  550. SharedPtr<Resource> resource;
  551. // Make sure the pointer is non-null and is a Resource subclass
  552. resource = DynamicCast<Resource>(context_->CreateObject(type));
  553. if (!resource)
  554. {
  555. URHO3D_LOGERROR("Could not load unknown resource type " + String(type));
  556. if (sendEventOnFailure)
  557. {
  558. using namespace UnknownResourceType;
  559. VariantMap& eventData = GetEventDataMap();
  560. eventData[P_RESOURCETYPE] = type;
  561. SendEvent(E_UNKNOWNRESOURCETYPE, eventData);
  562. }
  563. return SharedPtr<Resource>();
  564. }
  565. // Attempt to load the resource
  566. SharedPtr<File> file = GetFile(sanitatedName, sendEventOnFailure);
  567. if (!file)
  568. return SharedPtr<Resource>(); // Error is already logged
  569. URHO3D_LOGDEBUG("Loading temporary resource " + sanitatedName);
  570. resource->SetName(file->GetName());
  571. if (!resource->Load(*(file.Get())))
  572. {
  573. // Error should already been logged by corresponding resource descendant class
  574. if (sendEventOnFailure)
  575. {
  576. using namespace LoadFailed;
  577. VariantMap& eventData = GetEventDataMap();
  578. eventData[P_RESOURCENAME] = sanitatedName;
  579. SendEvent(E_LOADFAILED, eventData);
  580. }
  581. return SharedPtr<Resource>();
  582. }
  583. return resource;
  584. }
  585. unsigned ResourceCache::GetNumBackgroundLoadResources() const
  586. {
  587. #ifdef URHO3D_THREADING
  588. return backgroundLoader_->GetNumQueuedResources();
  589. #else
  590. return 0;
  591. #endif
  592. }
  593. void ResourceCache::GetResources(Vector<Resource*>& result, StringHash type) const
  594. {
  595. result.Clear();
  596. HashMap<StringHash, ResourceGroup>::ConstIterator i = resourceGroups_.Find(type);
  597. if (i != resourceGroups_.End())
  598. {
  599. for (HashMap<StringHash, SharedPtr<Resource>>::ConstIterator j = i->second_.resources_.Begin();
  600. j != i->second_.resources_.End(); ++j)
  601. result.Push(j->second_);
  602. }
  603. }
  604. bool ResourceCache::Exists(const String& name) const
  605. {
  606. MutexLock lock(resourceMutex_);
  607. String sanitatedName = SanitateResourceName(name);
  608. if (!isRouting_)
  609. {
  610. isRouting_ = true;
  611. for (const SharedPtr<ResourceRouter>& resourceRouter : resourceRouters_)
  612. resourceRouter->Route(sanitatedName, RESOURCE_CHECKEXISTS);
  613. isRouting_ = false;
  614. }
  615. if (sanitatedName.Empty())
  616. return false;
  617. for (const SharedPtr<PackageFile>& package : packages_)
  618. {
  619. if (package->Exists(sanitatedName))
  620. return true;
  621. }
  622. FileSystem* fileSystem = GetSubsystem<FileSystem>();
  623. for (const String& resourceDir : resourceDirs_)
  624. {
  625. if (fileSystem->FileExists(resourceDir + sanitatedName))
  626. return true;
  627. }
  628. // Fallback using absolute path
  629. return fileSystem->FileExists(sanitatedName);
  630. }
  631. unsigned long long ResourceCache::GetMemoryBudget(StringHash type) const
  632. {
  633. HashMap<StringHash, ResourceGroup>::ConstIterator i = resourceGroups_.Find(type);
  634. return i != resourceGroups_.End() ? i->second_.memoryBudget_ : 0;
  635. }
  636. unsigned long long ResourceCache::GetMemoryUse(StringHash type) const
  637. {
  638. HashMap<StringHash, ResourceGroup>::ConstIterator i = resourceGroups_.Find(type);
  639. return i != resourceGroups_.End() ? i->second_.memoryUse_ : 0;
  640. }
  641. unsigned long long ResourceCache::GetTotalMemoryUse() const
  642. {
  643. unsigned long long total = 0;
  644. for (HashMap<StringHash, ResourceGroup>::ConstIterator i = resourceGroups_.Begin(); i != resourceGroups_.End(); ++i)
  645. total += i->second_.memoryUse_;
  646. return total;
  647. }
  648. String ResourceCache::GetResourceFileName(const String& name) const
  649. {
  650. FileSystem* fileSystem = GetSubsystem<FileSystem>();
  651. for (const String& resourceDir : resourceDirs_)
  652. {
  653. if (fileSystem->FileExists(resourceDir + name))
  654. return resourceDir + name;
  655. }
  656. if (IsAbsolutePath(name) && fileSystem->FileExists(name))
  657. return name;
  658. else
  659. return String();
  660. }
  661. ResourceRouter* ResourceCache::GetResourceRouter(unsigned index) const
  662. {
  663. return index < resourceRouters_.Size() ? resourceRouters_[index] : nullptr;
  664. }
  665. String ResourceCache::GetPreferredResourceDir(const String& path) const
  666. {
  667. String fixedPath = AddTrailingSlash(path);
  668. bool pathHasKnownDirs = false;
  669. bool parentHasKnownDirs = false;
  670. auto* fileSystem = GetSubsystem<FileSystem>();
  671. for (unsigned i = 0; checkDirs[i] != nullptr; ++i)
  672. {
  673. if (fileSystem->DirExists(fixedPath + checkDirs[i]))
  674. {
  675. pathHasKnownDirs = true;
  676. break;
  677. }
  678. }
  679. if (!pathHasKnownDirs)
  680. {
  681. String parentPath = GetParentPath(fixedPath);
  682. for (unsigned i = 0; checkDirs[i] != nullptr; ++i)
  683. {
  684. if (fileSystem->DirExists(parentPath + checkDirs[i]))
  685. {
  686. parentHasKnownDirs = true;
  687. break;
  688. }
  689. }
  690. // If path does not have known subdirectories, but the parent path has, use the parent instead
  691. if (parentHasKnownDirs)
  692. fixedPath = parentPath;
  693. }
  694. return fixedPath;
  695. }
  696. String ResourceCache::SanitateResourceName(const String& name) const
  697. {
  698. // Sanitate unsupported constructs from the resource name
  699. String sanitatedName = GetInternalPath(name);
  700. sanitatedName.Replace("../", "");
  701. sanitatedName.Replace("./", "");
  702. // If the path refers to one of the resource directories, normalize the resource name
  703. auto* fileSystem = GetSubsystem<FileSystem>();
  704. if (resourceDirs_.Size())
  705. {
  706. String namePath = GetPath(sanitatedName);
  707. String exePath = fileSystem->GetProgramDir().Replaced("/./", "/");
  708. for (unsigned i = 0; i < resourceDirs_.Size(); ++i)
  709. {
  710. String relativeResourcePath = resourceDirs_[i];
  711. if (relativeResourcePath.StartsWith(exePath))
  712. relativeResourcePath = relativeResourcePath.Substring(exePath.Length());
  713. if (namePath.StartsWith(resourceDirs_[i], false))
  714. namePath = namePath.Substring(resourceDirs_[i].Length());
  715. else if (namePath.StartsWith(relativeResourcePath, false))
  716. namePath = namePath.Substring(relativeResourcePath.Length());
  717. }
  718. sanitatedName = namePath + GetFileNameAndExtension(sanitatedName);
  719. }
  720. return sanitatedName.Trimmed();
  721. }
  722. String ResourceCache::SanitateResourceDirName(const String& name) const
  723. {
  724. String fixedPath = AddTrailingSlash(name);
  725. if (!IsAbsolutePath(fixedPath))
  726. fixedPath = GetSubsystem<FileSystem>()->GetCurrentDir() + fixedPath;
  727. // Sanitate away /./ construct
  728. fixedPath.Replace("/./", "/");
  729. return fixedPath.Trimmed();
  730. }
  731. void ResourceCache::StoreResourceDependency(Resource* resource, const String& dependency)
  732. {
  733. if (!resource)
  734. return;
  735. MutexLock lock(resourceMutex_);
  736. StringHash nameHash(resource->GetName());
  737. HashSet<StringHash>& dependents = dependentResources_[dependency];
  738. dependents.Insert(nameHash);
  739. }
  740. void ResourceCache::ResetDependencies(Resource* resource)
  741. {
  742. if (!resource)
  743. return;
  744. MutexLock lock(resourceMutex_);
  745. StringHash nameHash(resource->GetName());
  746. for (HashMap<StringHash, HashSet<StringHash>>::Iterator i = dependentResources_.Begin(); i != dependentResources_.End();)
  747. {
  748. HashSet<StringHash>& dependents = i->second_;
  749. dependents.Erase(nameHash);
  750. if (dependents.Empty())
  751. i = dependentResources_.Erase(i);
  752. else
  753. ++i;
  754. }
  755. }
  756. String ResourceCache::PrintMemoryUsage() const
  757. {
  758. String output = "Resource Type Cnt Avg Max Budget Total\n\n";
  759. char outputLine[256];
  760. unsigned totalResourceCt = 0;
  761. unsigned long long totalLargest = 0;
  762. unsigned long long totalAverage = 0;
  763. unsigned long long totalUse = GetTotalMemoryUse();
  764. for (HashMap<StringHash, ResourceGroup>::ConstIterator cit = resourceGroups_.Begin(); cit != resourceGroups_.End(); ++cit)
  765. {
  766. const unsigned resourceCt = cit->second_.resources_.Size();
  767. unsigned long long average = 0;
  768. if (resourceCt > 0)
  769. average = cit->second_.memoryUse_ / resourceCt;
  770. else
  771. average = 0;
  772. unsigned long long largest = 0;
  773. for (HashMap<StringHash, SharedPtr<Resource>>::ConstIterator resIt = cit->second_.resources_.Begin(); resIt != cit->second_.resources_.End(); ++resIt)
  774. {
  775. if (resIt->second_->GetMemoryUse() > largest)
  776. largest = resIt->second_->GetMemoryUse();
  777. if (largest > totalLargest)
  778. totalLargest = largest;
  779. }
  780. totalResourceCt += resourceCt;
  781. const String countString(cit->second_.resources_.Size());
  782. const String memUseString = GetFileSizeString(average);
  783. const String memMaxString = GetFileSizeString(largest);
  784. const String memBudgetString = GetFileSizeString(cit->second_.memoryBudget_);
  785. const String memTotalString = GetFileSizeString(cit->second_.memoryUse_);
  786. const String resTypeName = context_->GetTypeName(cit->first_);
  787. memset(outputLine, ' ', 256);
  788. outputLine[255] = 0;
  789. sprintf(outputLine, "%-28s %4s %9s %9s %9s %9s\n", resTypeName.CString(), countString.CString(), memUseString.CString(), memMaxString.CString(), memBudgetString.CString(), memTotalString.CString());
  790. output += ((const char*)outputLine);
  791. }
  792. if (totalResourceCt > 0)
  793. totalAverage = totalUse / totalResourceCt;
  794. const String countString(totalResourceCt);
  795. const String memUseString = GetFileSizeString(totalAverage);
  796. const String memMaxString = GetFileSizeString(totalLargest);
  797. const String memTotalString = GetFileSizeString(totalUse);
  798. memset(outputLine, ' ', 256);
  799. outputLine[255] = 0;
  800. sprintf(outputLine, "%-28s %4s %9s %9s %9s %9s\n", "All", countString.CString(), memUseString.CString(), memMaxString.CString(), "-", memTotalString.CString());
  801. output += ((const char*)outputLine);
  802. return output;
  803. }
  804. const SharedPtr<Resource>& ResourceCache::FindResource(StringHash type, StringHash nameHash)
  805. {
  806. MutexLock lock(resourceMutex_);
  807. HashMap<StringHash, ResourceGroup>::Iterator i = resourceGroups_.Find(type);
  808. if (i == resourceGroups_.End())
  809. return noResource;
  810. HashMap<StringHash, SharedPtr<Resource>>::Iterator j = i->second_.resources_.Find(nameHash);
  811. if (j == i->second_.resources_.End())
  812. return noResource;
  813. return j->second_;
  814. }
  815. const SharedPtr<Resource>& ResourceCache::FindResource(StringHash nameHash)
  816. {
  817. MutexLock lock(resourceMutex_);
  818. for (HashMap<StringHash, ResourceGroup>::Iterator i = resourceGroups_.Begin(); i != resourceGroups_.End(); ++i)
  819. {
  820. HashMap<StringHash, SharedPtr<Resource>>::Iterator j = i->second_.resources_.Find(nameHash);
  821. if (j != i->second_.resources_.End())
  822. return j->second_;
  823. }
  824. return noResource;
  825. }
  826. void ResourceCache::ReleasePackageResources(PackageFile* package, bool force)
  827. {
  828. HashSet<StringHash> affectedGroups;
  829. const HashMap<String, PackageEntry>& entries = package->GetEntries();
  830. for (HashMap<String, PackageEntry>::ConstIterator i = entries.Begin(); i != entries.End(); ++i)
  831. {
  832. StringHash nameHash(i->first_);
  833. // We do not know the actual resource type, so search all type containers
  834. for (HashMap<StringHash, ResourceGroup>::Iterator j = resourceGroups_.Begin(); j != resourceGroups_.End(); ++j)
  835. {
  836. HashMap<StringHash, SharedPtr<Resource>>::Iterator k = j->second_.resources_.Find(nameHash);
  837. if (k != j->second_.resources_.End())
  838. {
  839. // If other references exist, do not release, unless forced
  840. if ((k->second_.Refs() == 1 && k->second_.WeakRefs() == 0) || force)
  841. {
  842. j->second_.resources_.Erase(k);
  843. affectedGroups.Insert(j->first_);
  844. }
  845. break;
  846. }
  847. }
  848. }
  849. for (HashSet<StringHash>::Iterator i = affectedGroups.Begin(); i != affectedGroups.End(); ++i)
  850. UpdateResourceGroup(*i);
  851. }
  852. void ResourceCache::UpdateResourceGroup(StringHash type)
  853. {
  854. HashMap<StringHash, ResourceGroup>::Iterator i = resourceGroups_.Find(type);
  855. if (i == resourceGroups_.End())
  856. return;
  857. for (;;)
  858. {
  859. unsigned totalSize = 0;
  860. unsigned oldestTimer = 0;
  861. HashMap<StringHash, SharedPtr<Resource>>::Iterator oldestResource = i->second_.resources_.End();
  862. for (HashMap<StringHash, SharedPtr<Resource>>::Iterator j = i->second_.resources_.Begin();
  863. j != i->second_.resources_.End(); ++j)
  864. {
  865. totalSize += j->second_->GetMemoryUse();
  866. unsigned useTimer = j->second_->GetUseTimer();
  867. if (useTimer > oldestTimer)
  868. {
  869. oldestTimer = useTimer;
  870. oldestResource = j;
  871. }
  872. }
  873. i->second_.memoryUse_ = totalSize;
  874. // If memory budget defined and is exceeded, remove the oldest resource and loop again
  875. // (resources in use always return a zero timer and can not be removed)
  876. if (i->second_.memoryBudget_ && i->second_.memoryUse_ > i->second_.memoryBudget_ &&
  877. oldestResource != i->second_.resources_.End())
  878. {
  879. URHO3D_LOGDEBUG("Resource group " + oldestResource->second_->GetTypeName() + " over memory budget, releasing resource " +
  880. oldestResource->second_->GetName());
  881. i->second_.resources_.Erase(oldestResource);
  882. }
  883. else
  884. break;
  885. }
  886. }
  887. void ResourceCache::HandleBeginFrame(StringHash eventType, VariantMap& eventData)
  888. {
  889. for (unsigned i = 0; i < fileWatchers_.Size(); ++i)
  890. {
  891. String fileName;
  892. while (fileWatchers_[i]->GetNextChange(fileName))
  893. {
  894. ReloadResourceWithDependencies(fileName);
  895. // Finally send a general file changed event even if the file was not a tracked resource
  896. using namespace FileChanged;
  897. VariantMap& eventData = GetEventDataMap();
  898. eventData[P_FILENAME] = fileWatchers_[i]->GetPath() + fileName;
  899. eventData[P_RESOURCENAME] = fileName;
  900. SendEvent(E_FILECHANGED, eventData);
  901. }
  902. }
  903. // Check for background loaded resources that can be finished
  904. #ifdef URHO3D_THREADING
  905. {
  906. URHO3D_PROFILE(FinishBackgroundResources);
  907. backgroundLoader_->FinishResources(finishBackgroundResourcesMs_);
  908. }
  909. #endif
  910. }
  911. File* ResourceCache::SearchResourceDirs(const String& name)
  912. {
  913. FileSystem* fileSystem = GetSubsystem<FileSystem>();
  914. for (const String& resourceDir : resourceDirs_)
  915. {
  916. if (fileSystem->FileExists(resourceDir + name))
  917. {
  918. // Construct the file first with full path, then rename it to not contain the resource path,
  919. // so that the file's sanitatedName can be used in further GetFile() calls (for example over the network)
  920. File* file(new File(context_, resourceDir + name));
  921. file->SetName(name);
  922. return file;
  923. }
  924. }
  925. // Fallback using absolute path
  926. if (fileSystem->FileExists(name))
  927. return new File(context_, name);
  928. return nullptr;
  929. }
  930. File* ResourceCache::SearchPackages(const String& name)
  931. {
  932. for (const SharedPtr<PackageFile>& package : packages_)
  933. {
  934. if (package->Exists(name))
  935. return new File(context_, package, name);
  936. }
  937. return nullptr;
  938. }
  939. void RegisterResourceLibrary(Context* context)
  940. {
  941. Image::RegisterObject(context);
  942. JSONFile::RegisterObject(context);
  943. PListFile::RegisterObject(context);
  944. XMLFile::RegisterObject(context);
  945. }
  946. }