ResourceCache.cpp 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598
  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 "Context.h"
  25. #include "FileSystem.h"
  26. #include "Image.h"
  27. #include "Log.h"
  28. #include "PackageFile.h"
  29. #include "ResourceCache.h"
  30. #include "ResourceEvents.h"
  31. #include "XMLFile.h"
  32. #include "DebugNew.h"
  33. static const String checkDirs[] = {
  34. "Fonts",
  35. "Materials",
  36. "Models",
  37. "Music",
  38. "Particle",
  39. "Scripts",
  40. "Sounds",
  41. "Shaders",
  42. "Textures",
  43. "UI",
  44. ""
  45. };
  46. static const String noName;
  47. static const SharedPtr<Resource> noResource;
  48. OBJECTTYPESTATIC(ResourceCache);
  49. ResourceCache::ResourceCache(Context* context) :
  50. Object(context)
  51. {
  52. }
  53. ResourceCache::~ResourceCache()
  54. {
  55. }
  56. bool ResourceCache::AddResourceDir(const String& pathName)
  57. {
  58. FileSystem* fileSystem = GetSubsystem<FileSystem>();
  59. if (!fileSystem || !fileSystem->DirExists(pathName))
  60. {
  61. LOGERROR("Could not open directory " + pathName);
  62. return false;
  63. }
  64. String fixedPath = AddTrailingSlash(pathName);
  65. // Check that the same path does not already exist
  66. for (unsigned i = 0; i < resourceDirs_.Size(); ++i)
  67. {
  68. if (!resourceDirs_[i].Compare(fixedPath, false))
  69. return true;
  70. }
  71. resourceDirs_.Push(fixedPath);
  72. // Scan the path for files recursively and add their hash-to-name mappings
  73. Vector<String> fileNames;
  74. fileSystem->ScanDir(fileNames, fixedPath, "*.*", SCAN_FILES, true);
  75. for (unsigned i = 0; i < fileNames.Size(); ++i)
  76. StoreNameHash(fileNames[i]);
  77. LOGINFO("Added resource path " + fixedPath);
  78. return true;
  79. }
  80. void ResourceCache::AddPackageFile(PackageFile* package, bool addAsFirst)
  81. {
  82. // Do not add packages that failed to load
  83. if (!package || !package->GetNumFiles())
  84. return;
  85. if (addAsFirst)
  86. packages_.Insert(packages_.Begin(), SharedPtr<PackageFile>(package));
  87. else
  88. packages_.Push(SharedPtr<PackageFile>(package));
  89. // Scan the package for files and add their hash-to-name mappings
  90. const Map<String, PackageEntry>& entries = package->GetEntries();
  91. for (Map<String, PackageEntry>::ConstIterator i = entries.Begin(); i != entries.End(); ++i)
  92. StoreNameHash(i->first_);
  93. LOGINFO("Added resource package " + package->GetName());
  94. }
  95. bool ResourceCache::AddManualResource(Resource* resource)
  96. {
  97. if (!resource)
  98. {
  99. LOGERROR("Null manual resource");
  100. return false;
  101. }
  102. const String& name = resource->GetName();
  103. if (name.Empty())
  104. {
  105. LOGERROR("Manual resource with empty name, can not add");
  106. return false;
  107. }
  108. StoreNameHash(name);
  109. resource->ResetUseTimer();
  110. resourceGroups_[resource->GetType()].resources_[resource->GetNameHash()] = resource;
  111. UpdateResourceGroup(resource->GetType());
  112. return true;
  113. }
  114. void ResourceCache::RemoveResourceDir(const String& path)
  115. {
  116. String fixedPath = AddTrailingSlash(path);
  117. for (Vector<String>::Iterator i = resourceDirs_.Begin(); i != resourceDirs_.End(); ++i)
  118. {
  119. if (!i->Compare(path, false))
  120. {
  121. resourceDirs_.Erase(i);
  122. return;
  123. }
  124. }
  125. }
  126. void ResourceCache::RemovePackageFile(PackageFile* package, bool ReleaseResources, bool forceRelease)
  127. {
  128. for (Vector<SharedPtr<PackageFile> >::Iterator i = packages_.Begin(); i != packages_.End(); ++i)
  129. {
  130. if ((*i) == package)
  131. {
  132. if (ReleaseResources)
  133. ReleasePackageResources(*i, forceRelease);
  134. packages_.Erase(i);
  135. return;
  136. }
  137. }
  138. }
  139. void ResourceCache::RemovePackageFile(const String& fileName, bool ReleaseResources, bool forceRelease)
  140. {
  141. // Compare the name and extension only, not the path
  142. String fileNameNoPath = GetFileNameAndExtension(fileName);
  143. for (Vector<SharedPtr<PackageFile> >::Iterator i = packages_.Begin(); i != packages_.End(); ++i)
  144. {
  145. if (!GetFileNameAndExtension((*i)->GetName()).Compare(fileNameNoPath, false))
  146. {
  147. if (ReleaseResources)
  148. ReleasePackageResources(*i, forceRelease);
  149. packages_.Erase(i);
  150. return;
  151. }
  152. }
  153. }
  154. void ResourceCache::ReleaseResource(ShortStringHash type, const String& name, bool force)
  155. {
  156. ReleaseResource(type, StringHash(name), force);
  157. }
  158. void ResourceCache::ReleaseResource(ShortStringHash type, StringHash nameHash, bool force)
  159. {
  160. const SharedPtr<Resource>& existingRes = FindResource(type, nameHash);
  161. if (!existingRes)
  162. return;
  163. // If other references exist, do not release, unless forced
  164. if (existingRes.Refs() == 1 || force)
  165. {
  166. resourceGroups_[type].resources_.Erase(nameHash);
  167. UpdateResourceGroup(type);
  168. }
  169. }
  170. void ResourceCache::ReleaseResources(ShortStringHash type, bool force)
  171. {
  172. bool released = false;
  173. for (Map<ShortStringHash, ResourceGroup>::Iterator i = resourceGroups_.Begin();
  174. i != resourceGroups_.End(); ++i)
  175. {
  176. if (i->first_ == type)
  177. {
  178. for (Map<StringHash, SharedPtr<Resource> >::Iterator j = i->second_.resources_.Begin();
  179. j != i->second_.resources_.End();)
  180. {
  181. Map<StringHash, SharedPtr<Resource> >::Iterator current = j++;
  182. // If other references exist, do not release, unless forced
  183. if (current->second_.Refs() == 1 || force)
  184. {
  185. i->second_.resources_.Erase(current);
  186. released = true;
  187. }
  188. }
  189. }
  190. }
  191. if (released)
  192. UpdateResourceGroup(type);
  193. }
  194. void ResourceCache::ReleaseResources(ShortStringHash type, const String& partialName, bool force)
  195. {
  196. bool released = false;
  197. for (Map<ShortStringHash, ResourceGroup>::Iterator i = resourceGroups_.Begin();
  198. i != resourceGroups_.End(); ++i)
  199. {
  200. if (i->first_ == type)
  201. {
  202. for (Map<StringHash, SharedPtr<Resource> >::Iterator j = i->second_.resources_.Begin();
  203. j != i->second_.resources_.End();)
  204. {
  205. Map<StringHash, SharedPtr<Resource> >::Iterator current = j++;
  206. if (current->second_->GetName().Find(partialName) != String::NPOS)
  207. {
  208. // If other references exist, do not release, unless forced
  209. if (current->second_.Refs() == 1 || force)
  210. {
  211. i->second_.resources_.Erase(current);
  212. released = true;
  213. }
  214. }
  215. }
  216. }
  217. }
  218. if (released)
  219. UpdateResourceGroup(type);
  220. }
  221. void ResourceCache::ReleaseAllResources(bool force)
  222. {
  223. for (Map<ShortStringHash, ResourceGroup>::Iterator i = resourceGroups_.Begin();
  224. i != resourceGroups_.End(); ++i)
  225. {
  226. bool released = false;
  227. for (Map<StringHash, SharedPtr<Resource> >::Iterator j = i->second_.resources_.Begin();
  228. j != i->second_.resources_.End();)
  229. {
  230. Map<StringHash, SharedPtr<Resource> >::Iterator current = j++;
  231. // If other references exist, do not release, unless forced
  232. if (current->second_.Refs() == 1 || force)
  233. {
  234. i->second_.resources_.Erase(current);
  235. released = true;
  236. }
  237. }
  238. if (released)
  239. UpdateResourceGroup(i->first_);
  240. }
  241. }
  242. bool ResourceCache::ReloadResource(Resource* resource)
  243. {
  244. if (!resource)
  245. return false;
  246. resource->SendEvent(E_RELOADSTARTED);
  247. bool success = false;
  248. SharedPtr<File> file = GetFile(resource->GetName());
  249. if (file)
  250. success = resource->Load(*(file.RawPtr()));
  251. if (success)
  252. {
  253. resource->ResetUseTimer();
  254. UpdateResourceGroup(resource->GetType());
  255. resource->SendEvent(E_RELOADFINISHED);
  256. return true;
  257. }
  258. // If reloading failed, remove the resource from cache
  259. resource->SendEvent(E_RELOADFAILED);
  260. ReleaseResource(resource->GetType(), resource->GetNameHash());
  261. return false;
  262. }
  263. void ResourceCache::SetMemoryBudget(ShortStringHash type, unsigned budget)
  264. {
  265. resourceGroups_[type].memoryBudget_ = budget;
  266. }
  267. SharedPtr<File> ResourceCache::GetFile(const String& name)
  268. {
  269. // Check first the packages
  270. for (unsigned i = 0; i < packages_.Size(); ++i)
  271. {
  272. if (packages_[i]->Exists(name))
  273. return SharedPtr<File>(new File(context_, packages_[i], name));
  274. }
  275. // Then the filesystem
  276. FileSystem* fileSystem = GetSubsystem<FileSystem>();
  277. if (fileSystem)
  278. {
  279. for (unsigned i = 0; i < resourceDirs_.Size(); ++i)
  280. {
  281. if (fileSystem->FileExists(resourceDirs_[i] + name))
  282. {
  283. // Construct the file first with full path, then rename it to not contain the resource path,
  284. // so that the file's name can be used in further GetFile() calls (for example over the network)
  285. SharedPtr<File> file(new File(context_, resourceDirs_[i] + name));
  286. file->SetName(name);
  287. return file;
  288. }
  289. }
  290. }
  291. LOGERROR("Could not find resource " + name);
  292. return SharedPtr<File>();
  293. }
  294. Resource* ResourceCache::GetResource(ShortStringHash type, const String& name)
  295. {
  296. // Add the name to the hash map, so if this is an unknown resource, the error will not be unintelligible
  297. StoreNameHash(name);
  298. return GetResource(type, StringHash(name));
  299. }
  300. Resource* ResourceCache::GetResource(ShortStringHash type, StringHash nameHash)
  301. {
  302. // If null hash, return null pointer immediately
  303. if (!nameHash)
  304. return 0;
  305. const SharedPtr<Resource>& existing = FindResource(type, nameHash);
  306. if (existing)
  307. return existing;
  308. SharedPtr<Resource> resource;
  309. const String& name = GetResourceName(nameHash);
  310. if (name.Empty())
  311. {
  312. LOGERROR("Could not load unknown resource " + String(nameHash));
  313. return 0;
  314. }
  315. // Make sure the pointer is non-null and is a Resource subclass
  316. resource = DynamicCast<Resource>(context_->CreateObject(type));
  317. if (!resource)
  318. {
  319. LOGERROR("Could not load unknown resource type " + String(type));
  320. return 0;
  321. }
  322. // Attempt to load the resource
  323. SharedPtr<File> file = GetFile(name);
  324. if (!file)
  325. return 0;
  326. LOGDEBUG("Loading resource " + name);
  327. resource->SetName(file->GetName());
  328. if (!resource->Load(*(file.RawPtr())))
  329. return 0;
  330. // Store to cache
  331. resource->ResetUseTimer();
  332. resourceGroups_[type].resources_[nameHash] = resource;
  333. UpdateResourceGroup(type);
  334. return resource;
  335. }
  336. void ResourceCache::GetResources(PODVector<Resource*>& result, ShortStringHash type) const
  337. {
  338. result.Clear();
  339. Map<ShortStringHash, ResourceGroup>::ConstIterator i = resourceGroups_.Find(type);
  340. if (i != resourceGroups_.End())
  341. {
  342. for (Map<StringHash, SharedPtr<Resource> >::ConstIterator j = i->second_.resources_.Begin();
  343. j != i->second_.resources_.End(); ++j)
  344. result.Push(j->second_);
  345. }
  346. }
  347. bool ResourceCache::Exists(const String& name) const
  348. {
  349. for (unsigned i = 0; i < packages_.Size(); ++i)
  350. {
  351. if (packages_[i]->Exists(name))
  352. return true;
  353. }
  354. FileSystem* fileSystem = GetSubsystem<FileSystem>();
  355. if (fileSystem)
  356. {
  357. for (unsigned i = 0; i < resourceDirs_.Size(); ++i)
  358. {
  359. if (fileSystem->FileExists(resourceDirs_[i] + name))
  360. return true;
  361. }
  362. }
  363. return false;
  364. }
  365. bool ResourceCache::Exists(StringHash nameHash) const
  366. {
  367. return Exists(GetResourceName(nameHash));
  368. }
  369. unsigned ResourceCache::GetMemoryBudget(ShortStringHash type) const
  370. {
  371. Map<ShortStringHash, ResourceGroup>::ConstIterator i = resourceGroups_.Find(type);
  372. if (i != resourceGroups_.End())
  373. return i->second_.memoryBudget_;
  374. else
  375. return 0;
  376. }
  377. unsigned ResourceCache::GetMemoryUse(ShortStringHash type) const
  378. {
  379. Map<ShortStringHash, ResourceGroup>::ConstIterator i = resourceGroups_.Find(type);
  380. if (i != resourceGroups_.End())
  381. return i->second_.memoryUse_;
  382. else
  383. return 0;
  384. }
  385. unsigned ResourceCache::GetTotalMemoryUse() const
  386. {
  387. unsigned total = 0;
  388. for (Map<ShortStringHash, ResourceGroup>::ConstIterator i = resourceGroups_.Begin(); i != resourceGroups_.End(); ++i)
  389. total += i->second_.memoryUse_;
  390. return total;
  391. }
  392. const String& ResourceCache::GetResourceName(StringHash nameHash) const
  393. {
  394. Map<StringHash, String>::ConstIterator i = hashToName_.Find(nameHash);
  395. if (i == hashToName_.End())
  396. return noName;
  397. else
  398. return i->second_;
  399. }
  400. String ResourceCache::GetPreferredResourceDir(const String& path)
  401. {
  402. String fixedPath = AddTrailingSlash(path);
  403. bool pathHasKnownDirs = false;
  404. bool parentHasKnownDirs = false;
  405. FileSystem* fileSystem = GetSubsystem<FileSystem>();
  406. // If no filesystem, can not check directory existence, so just return the original path
  407. if (!fileSystem)
  408. return fixedPath;
  409. for (unsigned i = 0; !checkDirs[i].Empty(); ++i)
  410. {
  411. if (fileSystem->DirExists(fixedPath + checkDirs[i]))
  412. {
  413. pathHasKnownDirs = true;
  414. break;
  415. }
  416. }
  417. if (!pathHasKnownDirs)
  418. {
  419. String parentPath = GetParentPath(fixedPath);
  420. for (unsigned i = 0; !checkDirs[i].Empty(); ++i)
  421. {
  422. if (fileSystem->DirExists(parentPath + checkDirs[i]))
  423. {
  424. parentHasKnownDirs = true;
  425. break;
  426. }
  427. }
  428. // If path does not have known subdirectories, but the parent path has, use the parent instead
  429. if (parentHasKnownDirs)
  430. fixedPath = parentPath;
  431. }
  432. return fixedPath;
  433. }
  434. const SharedPtr<Resource>& ResourceCache::FindResource(ShortStringHash type, StringHash nameHash)
  435. {
  436. Map<ShortStringHash, ResourceGroup>::Iterator i = resourceGroups_.Find(type);
  437. if (i == resourceGroups_.End())
  438. return noResource;
  439. Map<StringHash, SharedPtr<Resource> >::Iterator j = i->second_.resources_.Find(nameHash);
  440. if (j == i->second_.resources_.End())
  441. return noResource;
  442. return j->second_;
  443. }
  444. void ResourceCache::ReleasePackageResources(PackageFile* package, bool force)
  445. {
  446. HashSet<ShortStringHash> affectedGroups;
  447. const Map<String, PackageEntry>& entries = package->GetEntries();
  448. for (Map<String, PackageEntry>::ConstIterator i = entries.Begin(); i != entries.End(); ++i)
  449. {
  450. StringHash nameHash(i->first_);
  451. // We do not know the actual resource type, so search all type containers
  452. for (Map<ShortStringHash, ResourceGroup>::Iterator j = resourceGroups_.Begin();
  453. j != resourceGroups_.End(); ++j)
  454. {
  455. Map<StringHash, SharedPtr<Resource> >::Iterator k = j->second_.resources_.Find(nameHash);
  456. if (k != j->second_.resources_.End())
  457. {
  458. // If other references exist, do not release, unless forced
  459. if (k->second_.Refs() == 1 || force)
  460. {
  461. j->second_.resources_.Erase(k);
  462. affectedGroups.Insert(j->first_);
  463. }
  464. break;
  465. }
  466. }
  467. }
  468. for (HashSet<ShortStringHash>::Iterator i = affectedGroups.Begin(); i != affectedGroups.End(); ++i)
  469. UpdateResourceGroup(*i);
  470. }
  471. void ResourceCache::UpdateResourceGroup(ShortStringHash type)
  472. {
  473. Map<ShortStringHash, ResourceGroup>::Iterator i = resourceGroups_.Find(type);
  474. if (i == resourceGroups_.End())
  475. return;
  476. for (;;)
  477. {
  478. unsigned totalSize = 0;
  479. unsigned oldestTimer = 0;
  480. Map<StringHash, SharedPtr<Resource> >::Iterator oldestResource = i->second_.resources_.End();
  481. for (Map<StringHash, SharedPtr<Resource> >::Iterator j = i->second_.resources_.Begin();
  482. j != i->second_.resources_.End(); ++j)
  483. {
  484. totalSize += j->second_->GetMemoryUse();
  485. unsigned useTimer = j->second_->GetUseTimer();
  486. if (useTimer > oldestTimer)
  487. {
  488. oldestTimer = useTimer;
  489. oldestResource = j;
  490. }
  491. }
  492. i->second_.memoryUse_ = totalSize;
  493. // If memory budget defined and is exceeded, remove the oldest resource and loop again
  494. // (resources in use always return a zero timer and can not be removed)
  495. if (i->second_.memoryBudget_ && i->second_.memoryUse_ > i->second_.memoryBudget_ &&
  496. oldestResource != i->second_.resources_.End())
  497. {
  498. LOGDEBUG("Resource group " + oldestResource->second_->GetTypeName() + " over memory budget, releasing resource " +
  499. oldestResource->second_->GetName());
  500. i->second_.resources_.Erase(oldestResource);
  501. }
  502. else
  503. break;
  504. }
  505. }
  506. void ResourceCache::StoreNameHash(const String& name)
  507. {
  508. if (name.Empty())
  509. return;
  510. hashToName_[StringHash(name)] = name;
  511. }
  512. void RegisterResourceLibrary(Context* context)
  513. {
  514. Image::RegisterObject(context);
  515. XMLFile::RegisterObject(context);
  516. }