ResourceCache.cpp 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580
  1. //
  2. // Urho3D Engine
  3. // Copyright (c) 2008-2011 Lasse Öörni
  4. //
  5. // Permission is hereby granted, free of charge, to any person obtaining a copy
  6. // of this software and associated documentation files (the "Software"), to deal
  7. // in the Software without restriction, including without limitation the rights
  8. // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  9. // copies of the Software, and to permit persons to whom the Software is
  10. // furnished to do so, subject to the following conditions:
  11. //
  12. // The above copyright notice and this permission notice shall be included in
  13. // all copies or substantial portions of the Software.
  14. //
  15. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  16. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  17. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  18. // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  19. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  20. // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  21. // THE SOFTWARE.
  22. //
  23. #include "Precompiled.h"
  24. #include "Log.h"
  25. #include "PackageFile.h"
  26. #include "ResourceCache.h"
  27. #include "ResourceEvents.h"
  28. #include "ResourceFactory.h"
  29. #include "StringUtils.h"
  30. #include "DebugNew.h"
  31. ResourceCache::ResourceCache()
  32. {
  33. LOGINFO("Resource cache created");
  34. }
  35. ResourceCache::~ResourceCache()
  36. {
  37. // At this point factories may be the only thing keeping subsystems alive; remove resources before factories so that
  38. // resources can be released cleanly
  39. mResourceGroups.clear();
  40. mFactories.clear();
  41. LOGINFO("Resource cache shut down");
  42. }
  43. void ResourceCache::addResourceFactory(ResourceFactory* factory)
  44. {
  45. if (!factory)
  46. return;
  47. mFactories.push_back(SharedPtr<ResourceFactory>(factory));
  48. }
  49. bool ResourceCache::addResourcePath(const std::string& path)
  50. {
  51. if (!directoryExists(path))
  52. {
  53. LOGERROR("Could not open directory " + path);
  54. return false;
  55. }
  56. std::string fixedPath = fixPath(path);
  57. std::string pathLower = toLower(fixedPath);
  58. // Check that the same path does not already exist
  59. for (unsigned i = 0; i < mResourcePaths.size(); ++i)
  60. {
  61. if (toLower(mResourcePaths[i]) == pathLower)
  62. return true;
  63. }
  64. mResourcePaths.push_back(fixedPath);
  65. // Scan the path for files recursively and add their hash-to-name mappings
  66. std::vector<std::string> fileNames;
  67. scanDirectory(fileNames, fixedPath, "*.*", SCAN_FILES, true);
  68. for (unsigned i = 0; i < fileNames.size(); ++i)
  69. registerHash(fileNames[i]);
  70. LOGINFO("Added resource path " + fixedPath);
  71. return true;
  72. }
  73. void ResourceCache::addPackageFile(PackageFile* package, bool addAsFirst)
  74. {
  75. if (!package)
  76. return;
  77. if (addAsFirst)
  78. mPackages.insert(mPackages.begin(), SharedPtr<PackageFile>(package));
  79. else
  80. mPackages.push_back(SharedPtr<PackageFile>(package));
  81. // Scan the package for files and add their hash-to-name mappings
  82. const std::map<std::string, PackageEntry>& entries = package->getEntries();
  83. for (std::map<std::string, PackageEntry>::const_iterator i = entries.begin(); i != entries.end(); ++i)
  84. registerHash(i->first);
  85. LOGINFO("Added resource package " + package->getName());
  86. }
  87. bool ResourceCache::addManualResource(Resource* resource)
  88. {
  89. if (!resource)
  90. {
  91. LOGERROR("Null manual resource");
  92. return false;
  93. }
  94. if (resource->getName().empty())
  95. {
  96. LOGERROR("Manual resource with empty name, can not add");
  97. return false;
  98. }
  99. resource->resetUseTimer();
  100. mResourceGroups[resource->getType()].mResources[resource->getNameHash()] = resource;
  101. updateResourceGroup(resource->getType());
  102. return true;
  103. }
  104. void ResourceCache::removeResourcePath(const std::string& path)
  105. {
  106. std::string fixedPath = toLower(fixPath(path));
  107. for (std::vector<std::string>::iterator i = mResourcePaths.begin(); i != mResourcePaths.end(); ++i)
  108. {
  109. if (toLower(*i) == fixedPath)
  110. {
  111. mResourcePaths.erase(i);
  112. return;
  113. }
  114. }
  115. }
  116. void ResourceCache::removePackageFile(PackageFile* package, bool releaseResources, bool forceRelease)
  117. {
  118. for (std::vector<SharedPtr<PackageFile> >::iterator i = mPackages.begin(); i != mPackages.end(); ++i)
  119. {
  120. if ((*i) == package)
  121. {
  122. if (releaseResources)
  123. releasePackageResources(*i, forceRelease);
  124. mPackages.erase(i);
  125. return;
  126. }
  127. }
  128. }
  129. void ResourceCache::removePackageFile(const std::string& fileName, bool releaseResources, bool forceRelease)
  130. {
  131. std::string fileNameLower = toLower(fileName);
  132. for (std::vector<SharedPtr<PackageFile> >::iterator i = mPackages.begin(); i != mPackages.end(); ++i)
  133. {
  134. if (toLower((*i)->getName()) == fileNameLower)
  135. {
  136. if (releaseResources)
  137. releasePackageResources(*i, forceRelease);
  138. mPackages.erase(i);
  139. return;
  140. }
  141. }
  142. }
  143. void ResourceCache::releaseResource(ShortStringHash type, const std::string& name, bool force)
  144. {
  145. releaseResource(type, StringHash(name), force);
  146. }
  147. void ResourceCache::releaseResource(ShortStringHash type, StringHash nameHash, bool force)
  148. {
  149. const SharedPtr<Resource>& existingRes = findResource(type, nameHash);
  150. if (!existingRes)
  151. return;
  152. // If other references exist, do not release, unless forced
  153. if ((existingRes.getRefCount() == 1) || (force))
  154. {
  155. mResourceGroups[type].mResources.erase(nameHash);
  156. updateResourceGroup(type);
  157. }
  158. }
  159. void ResourceCache::releaseResources(ShortStringHash type, bool force)
  160. {
  161. bool released = false;
  162. for (std::map<ShortStringHash, ResourceGroup>::iterator i = mResourceGroups.begin();
  163. i != mResourceGroups.end(); ++i)
  164. {
  165. if (i->first == type)
  166. {
  167. for (std::map<StringHash, SharedPtr<Resource> >::iterator j = i->second.mResources.begin();
  168. j != i->second.mResources.end();)
  169. {
  170. std::map<StringHash, SharedPtr<Resource> >::iterator current = j++;
  171. // If other references exist, do not release, unless forced
  172. if ((current->second.getRefCount() == 1) || (force))
  173. {
  174. i->second.mResources.erase(current);
  175. released = true;
  176. }
  177. }
  178. }
  179. }
  180. if (released)
  181. updateResourceGroup(type);
  182. }
  183. void ResourceCache::releaseResources(ShortStringHash type, const std::string& partialName, bool force)
  184. {
  185. std::string partialNameLower = toLower(partialName);
  186. bool released = false;
  187. for (std::map<ShortStringHash, ResourceGroup>::iterator i = mResourceGroups.begin();
  188. i != mResourceGroups.end(); ++i)
  189. {
  190. if (i->first == type)
  191. {
  192. for (std::map<StringHash, SharedPtr<Resource> >::iterator j = i->second.mResources.begin();
  193. j != i->second.mResources.end();)
  194. {
  195. std::map<StringHash, SharedPtr<Resource> >::iterator current = j++;
  196. if (current->second->getName().find(partialNameLower) != std::string::npos)
  197. {
  198. // If other references exist, do not release, unless forced
  199. if ((current->second.getRefCount() == 1) || (force))
  200. {
  201. i->second.mResources.erase(current);
  202. released = true;
  203. }
  204. }
  205. }
  206. }
  207. }
  208. if (released)
  209. updateResourceGroup(type);
  210. }
  211. void ResourceCache::releaseAllResources(bool force)
  212. {
  213. for (std::map<ShortStringHash, ResourceGroup>::iterator i = mResourceGroups.begin();
  214. i != mResourceGroups.end(); ++i)
  215. {
  216. bool released = false;
  217. for (std::map<StringHash, SharedPtr<Resource> >::iterator j = i->second.mResources.begin();
  218. j != i->second.mResources.end();)
  219. {
  220. std::map<StringHash, SharedPtr<Resource> >::iterator current = j++;
  221. // If other references exist, do not release, unless forced
  222. if ((current->second.getRefCount() == 1) || (force))
  223. {
  224. i->second.mResources.erase(current);
  225. released = true;
  226. }
  227. }
  228. if (released)
  229. updateResourceGroup(i->first);
  230. }
  231. }
  232. bool ResourceCache::reloadResource(Resource* resource)
  233. {
  234. if (!resource)
  235. return false;
  236. // Some resources are event senders/listeners, and can notify that they are being reloaded
  237. EventListener* listener = dynamic_cast<EventListener*>(resource);
  238. try
  239. {
  240. if (listener)
  241. listener->sendEvent(EVENT_RELOADSTARTED);
  242. SharedPtr<File> file = getFile(resource->getName());
  243. resource->load(*(file.getPtr()), this);
  244. resource->resetUseTimer();
  245. updateResourceGroup(resource->getType());
  246. if (listener)
  247. listener->sendEvent(EVENT_RELOADFINISHED);
  248. return true;
  249. }
  250. catch (...)
  251. {
  252. if (listener)
  253. listener->sendEvent(EVENT_RELOADFAILED);
  254. releaseResource(resource->getType(), resource->getNameHash());
  255. return false;
  256. }
  257. }
  258. void ResourceCache::setMemoryBudget(ShortStringHash type, unsigned budget)
  259. {
  260. mResourceGroups[type].mMemoryBudget = budget;
  261. }
  262. SharedPtr<File> ResourceCache::getFile(const std::string& name)
  263. {
  264. // Check first the packages
  265. for (unsigned i = 0; i < mPackages.size(); ++i)
  266. {
  267. if (mPackages[i]->exists(name))
  268. return SharedPtr<File>(new File(*mPackages[i], name));
  269. }
  270. // Then the filesystem
  271. for (unsigned i = 0; i < mResourcePaths.size(); ++i)
  272. {
  273. if (fileExists(mResourcePaths[i] + name))
  274. {
  275. // Construct the file first with full path, then rename it to not contain the resource path,
  276. // so that the file's name can be used in further getFile() calls (for example over the network)
  277. SharedPtr<File> file(new File(mResourcePaths[i] + name));
  278. file->setName(name);
  279. return file;
  280. }
  281. }
  282. EXCEPTION("Could not find resource " + name);
  283. }
  284. Resource* ResourceCache::getResource(ShortStringHash type, const std::string& name)
  285. {
  286. // Add the name to the hash map, so if this is an unknown resource, the error will not be unintelligible
  287. registerHash(name);
  288. return getResource(type, StringHash(name));
  289. }
  290. Resource* ResourceCache::getResource(ShortStringHash type, StringHash nameHash)
  291. {
  292. // If null hash, return null pointer immediately
  293. if (!nameHash)
  294. return 0;
  295. const SharedPtr<Resource>& existing = findResource(type, nameHash);
  296. if (existing)
  297. return existing;
  298. SharedPtr<Resource> resource;
  299. std::string name = hashToString(nameHash);
  300. for (unsigned i = 0; i < mFactories.size(); ++i)
  301. {
  302. resource = mFactories[i]->createResource(type, name);
  303. if (resource)
  304. break;
  305. }
  306. if (!resource)
  307. {
  308. LOGERROR("Could not load unknown resource type " + toString(type));
  309. return 0;
  310. }
  311. LOGDEBUG("Loading resource " + name);
  312. // Attempt to load the resource
  313. try
  314. {
  315. SharedPtr<File> file = getFile(name);
  316. resource->load(*(file.getPtr()), this);
  317. resource->resetUseTimer();
  318. // Store to cache
  319. mResourceGroups[type].mResources[nameHash] = resource;
  320. updateResourceGroup(type);
  321. return mResourceGroups[type].mResources[nameHash];
  322. }
  323. catch (...)
  324. {
  325. return 0;
  326. }
  327. }
  328. void ResourceCache::getResources(std::vector<Resource*>& result, ShortStringHash type) const
  329. {
  330. result.clear();
  331. std::map<ShortStringHash, ResourceGroup>::const_iterator i = mResourceGroups.find(type);
  332. if (i != mResourceGroups.end())
  333. {
  334. for (std::map<StringHash, SharedPtr<Resource> >::const_iterator j = i->second.mResources.begin();
  335. j != i->second.mResources.end(); ++j)
  336. result.push_back(j->second);
  337. }
  338. }
  339. bool ResourceCache::exists(const std::string& name) const
  340. {
  341. for (unsigned i = 0; i < mPackages.size(); ++i)
  342. {
  343. if (mPackages[i]->exists(name))
  344. return true;
  345. }
  346. for (unsigned i = 0; i < mResourcePaths.size(); ++i)
  347. {
  348. if (fileExists(mResourcePaths[i] + name))
  349. return true;
  350. }
  351. return false;
  352. }
  353. bool ResourceCache::exists(StringHash nameHash) const
  354. {
  355. std::string name = hashToString(nameHash);
  356. return exists(name);
  357. }
  358. unsigned ResourceCache::getMemoryBudget(ShortStringHash type) const
  359. {
  360. std::map<ShortStringHash, ResourceGroup>::const_iterator i = mResourceGroups.find(type);
  361. if (i != mResourceGroups.end())
  362. return i->second.mMemoryBudget;
  363. else
  364. return 0;
  365. }
  366. unsigned ResourceCache::getMemoryUse(ShortStringHash type) const
  367. {
  368. std::map<ShortStringHash, ResourceGroup>::const_iterator i = mResourceGroups.find(type);
  369. if (i != mResourceGroups.end())
  370. return i->second.mMemoryUse;
  371. else
  372. return 0;
  373. }
  374. unsigned ResourceCache::getTotalMemoryUse() const
  375. {
  376. unsigned total = 0;
  377. for (std::map<ShortStringHash, ResourceGroup>::const_iterator i = mResourceGroups.begin(); i != mResourceGroups.end(); ++i)
  378. total += i->second.mMemoryUse;
  379. return total;
  380. }
  381. const SharedPtr<Resource>& ResourceCache::findResource(ShortStringHash type, StringHash nameHash)
  382. {
  383. static const SharedPtr<Resource> noResource;
  384. std::map<ShortStringHash, ResourceGroup>::iterator i = mResourceGroups.find(type);
  385. if (i == mResourceGroups.end())
  386. return noResource;
  387. std::map<StringHash, SharedPtr<Resource> >::iterator j = i->second.mResources.find(nameHash);
  388. if (j == i->second.mResources.end())
  389. return noResource;
  390. return j->second;
  391. }
  392. void ResourceCache::releasePackageResources(PackageFile* package, bool force)
  393. {
  394. std::set<ShortStringHash> affectedGroups;
  395. const std::map<std::string, PackageEntry>& entries = package->getEntries();
  396. for (std::map<std::string, PackageEntry>::const_iterator i = entries.begin(); i != entries.end(); ++i)
  397. {
  398. StringHash nameHash(i->first);
  399. // We do not know the actual resource type, so search all type containers
  400. for (std::map<ShortStringHash, ResourceGroup>::iterator j = mResourceGroups.begin();
  401. j != mResourceGroups.end(); ++j)
  402. {
  403. std::map<StringHash, SharedPtr<Resource> >::iterator k = j->second.mResources.find(nameHash);
  404. if (k != j->second.mResources.end())
  405. {
  406. // If other references exist, do not release, unless forced
  407. if ((k->second.getRefCount() == 1) || (force))
  408. {
  409. j->second.mResources.erase(k);
  410. affectedGroups.insert(j->first);
  411. }
  412. break;
  413. }
  414. }
  415. }
  416. for (std::set<ShortStringHash>::iterator i = affectedGroups.begin(); i != affectedGroups.end(); ++i)
  417. updateResourceGroup(*i);
  418. }
  419. void ResourceCache::updateResourceGroup(ShortStringHash type)
  420. {
  421. std::map<ShortStringHash, ResourceGroup>::iterator i = mResourceGroups.find(type);
  422. if (i == mResourceGroups.end())
  423. return;
  424. for (;;)
  425. {
  426. unsigned totalSize = 0;
  427. unsigned oldestTimer = 0;
  428. std::map<StringHash, SharedPtr<Resource> >::iterator oldestResource = i->second.mResources.end();
  429. for (std::map<StringHash, SharedPtr<Resource> >::iterator j = i->second.mResources.begin();
  430. j != i->second.mResources.end(); ++j)
  431. {
  432. totalSize += j->second->getMemoryUse();
  433. unsigned useTimer = j->second->getUseTimer();
  434. if (useTimer > oldestTimer)
  435. {
  436. oldestTimer = useTimer;
  437. oldestResource = j;
  438. }
  439. }
  440. i->second.mMemoryUse = totalSize;
  441. // If memory budget defined and is exceeded, remove the oldest resource and loop again
  442. // (resources in use always return a zero timer and can not be removed)
  443. if ((i->second.mMemoryBudget) && (i->second.mMemoryUse > i->second.mMemoryBudget) &&
  444. (oldestResource != i->second.mResources.end()))
  445. {
  446. LOGDEBUG("Resource group " + oldestResource->second->getTypeName() + " over memory budget, releasing resource " +
  447. oldestResource->second->getName());
  448. i->second.mResources.erase(oldestResource);
  449. }
  450. else
  451. break;
  452. }
  453. }
  454. std::string getPreferredResourcePath(const std::string& path)
  455. {
  456. std::string fixedPath = fixPath(path);
  457. // Check for the existence of known resource subdirectories to decide if we should add the parent directory instead
  458. static const std::string checkDirs[] = {
  459. "Fonts",
  460. "Materials",
  461. "Models",
  462. "Music",
  463. "Particle",
  464. "Physics",
  465. "Scripts",
  466. "Sounds",
  467. "Shaders",
  468. "Textures",
  469. "UI",
  470. ""
  471. };
  472. bool pathHasKnownDirs = false;
  473. bool parentHasKnownDirs = false;
  474. for (unsigned i = 0; !checkDirs[i].empty(); ++i)
  475. {
  476. if (directoryExists(fixedPath + checkDirs[i]))
  477. {
  478. pathHasKnownDirs = true;
  479. break;
  480. }
  481. }
  482. if (!pathHasKnownDirs)
  483. {
  484. std::string parentPath = getParentPath(fixedPath);
  485. for (unsigned i = 0; !checkDirs[i].empty(); ++i)
  486. {
  487. if (directoryExists(parentPath + checkDirs[i]))
  488. {
  489. parentHasKnownDirs = true;
  490. break;
  491. }
  492. }
  493. // If path does not have known subdirectories, but the parent path has, use the parent instead
  494. if (parentHasKnownDirs)
  495. fixedPath = parentPath;
  496. }
  497. return fixedPath;
  498. }