ResCache.cpp 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594
  1. //========================================================================
  2. // ResCache.cpp : Defines a simple resource cache
  3. //
  4. // Part of the GameCode4 Application
  5. //
  6. // GameCode4 is the sample application that encapsulates much of the source code
  7. // discussed in "Game Coding Complete - 4th Edition" by Mike McShaffry and David
  8. // "Rez" Graham, published by Charles River Media.
  9. // ISBN-10: 1133776574 | ISBN-13: 978-1133776574
  10. //
  11. // If this source code has found it's way to you, and you think it has helped you
  12. // in any way, do the authors a favor and buy a new copy of the book - there are
  13. // detailed explanations in it that compliment this code well. Buy a copy at Amazon.com
  14. // by clicking here:
  15. // http://www.amazon.com/gp/product/1133776574/ref=olp_product_details?ie=UTF8&me=&seller=
  16. //
  17. // There's a companion web site at http://www.mcshaffry.com/GameCode/
  18. //
  19. // The source code is managed and maintained through Google Code:
  20. // http://code.google.com/p/gamecode4/
  21. //
  22. // (c) Copyright 2012 Michael L. McShaffry and David Graham
  23. //
  24. // This program is free software; you can redistribute it and/or
  25. // modify it under the terms of the GNU Lesser GPL v3
  26. // as published by the Free Software Foundation.
  27. //
  28. // This program is distributed in the hope that it will be useful,
  29. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  30. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
  31. // http://www.gnu.org/licenses/lgpl-3.0.txt for more details.
  32. //
  33. // You should have received a copy of the GNU Lesser GPL v3
  34. // along with this program; if not, write to the Free Software
  35. // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  36. //
  37. //========================================================================
  38. #include "../Debugging/Logger.h"
  39. #include "../Unix/GameCodeStd.h"
  40. #include "../Utilities/String.h"
  41. #include <list>
  42. #include <map>
  43. #include <cctype> // for std::tolower
  44. #include "ResCache.h"
  45. // [munnellg] replaces Win32 function call from book.
  46. // TODO(munnellg) - make properly cross-platform
  47. static std::filesystem::path GetCurrentDirectory() {
  48. std::filesystem::path exe = std::filesystem::canonical("/proc/self/exe");
  49. return exe.parent_path();
  50. }
  51. //
  52. // Resource::Resource
  53. //
  54. Resource::Resource(const std::string &name)
  55. {
  56. m_name=name;
  57. std::transform(m_name.begin(), m_name.end(), m_name.begin(), (int(*)(int)) std::tolower);
  58. }
  59. //========================================================================
  60. // class ResourceZipFile - not discussed in the book
  61. //
  62. // This class implements the IResourceFile interface with a ZipFile.
  63. //
  64. ResourceZipFile::~ResourceZipFile()
  65. {
  66. SAFE_DELETE(m_pZipFile);
  67. }
  68. bool ResourceZipFile::VOpen()
  69. {
  70. GCC_INFO("Opening ResourceZipFile");
  71. m_pZipFile = GCC_NEW ZipFile;
  72. if (m_pZipFile)
  73. {
  74. return m_pZipFile->Init(m_resFileName.c_str());
  75. }
  76. return false;
  77. }
  78. int ResourceZipFile::VGetRawResourceSize(const Resource &r)
  79. {
  80. int resourceNum = m_pZipFile->Find(r.m_name.c_str());
  81. if (resourceNum == -1)
  82. return -1;
  83. return m_pZipFile->GetFileLen(resourceNum);
  84. }
  85. int ResourceZipFile::VGetRawResource(const Resource &r, char *buffer)
  86. {
  87. int size = 0;
  88. std::optional<int> resourceNum = m_pZipFile->Find(r.m_name.c_str());
  89. if (resourceNum)
  90. {
  91. size = m_pZipFile->GetFileLen(*resourceNum);
  92. m_pZipFile->ReadFile(*resourceNum, buffer);
  93. }
  94. return size;
  95. }
  96. int ResourceZipFile::VGetNumResources() const
  97. {
  98. return (m_pZipFile==NULL) ? 0 : m_pZipFile->GetNumFiles();
  99. }
  100. std::string ResourceZipFile::VGetResourceName(int num) const
  101. {
  102. std::string resName = "";
  103. if (m_pZipFile != NULL && num >= 0 && num < m_pZipFile->GetNumFiles())
  104. {
  105. resName = m_pZipFile->GetFilename(num);
  106. }
  107. return resName;
  108. }
  109. //========================================================================
  110. // class DevelopmentResourceZipFile - not discussed in the book
  111. //
  112. // This class fakes a ZIP file from a normal directory, and is used in the
  113. // editor.
  114. //
  115. DevelopmentResourceZipFile::DevelopmentResourceZipFile(const std::wstring resFileName, const Mode mode)
  116. : ResourceZipFile(resFileName)
  117. {
  118. m_mode=mode;
  119. std::filesystem::path dir = GetCurrentDirectory();
  120. m_AssetsDir = dir / "Assets";
  121. }
  122. int DevelopmentResourceZipFile::Find(const std::string &name)
  123. {
  124. std::string lowerCase = name;
  125. std::transform(lowerCase.begin(), lowerCase.end(), lowerCase.begin(), (int(*)(int)) std::tolower);
  126. ZipContentsMap::const_iterator i = m_DirectoryContentsMap.find(lowerCase);
  127. if (i==m_DirectoryContentsMap.end())
  128. return -1;
  129. return i->second;
  130. }
  131. bool DevelopmentResourceZipFile::VOpen()
  132. {
  133. GCC_INFO("Opening DevelopmentResourceZipFile");
  134. if (m_mode != Editor)
  135. {
  136. return ResourceZipFile::VOpen();
  137. }
  138. if (m_mode == Editor)
  139. {
  140. return ReadAssetsDirectory(m_AssetsDir);
  141. }
  142. else
  143. {
  144. // TODO(munnellg) - See if we can get this working
  145. // FUTURE WORK - iterate through the ZipFile contents and go grab WIN32_FIND_DATA
  146. // elements for each one. Then it would be possible to compare the dates/times of the
  147. // asset in the Zip file with the source asset.
  148. GCC_ASSERT(0 && "Not implemented yet");
  149. }
  150. return true;
  151. }
  152. int DevelopmentResourceZipFile::VGetRawResourceSize(const Resource &r)
  153. {
  154. int size = 0;
  155. if (m_mode == Editor)
  156. {
  157. int num = Find(r.m_name.c_str());
  158. if (num == -1)
  159. return -1;
  160. return m_AssetFileInfo[num].file_size();
  161. }
  162. return ResourceZipFile::VGetRawResourceSize(r);
  163. }
  164. int DevelopmentResourceZipFile::VGetRawResource(const Resource &r, char *buffer)
  165. {
  166. if (m_mode == Editor)
  167. {
  168. int num = Find(r.m_name.c_str());
  169. if (num == -1)
  170. return -1;
  171. FILE *f = fopen(m_AssetFileInfo[num].path().c_str(), "rb");
  172. GCC_ASSERT(f);
  173. size_t bytes = fread(buffer, 1, m_AssetFileInfo[num].file_size(), f);
  174. fclose(f);
  175. return bytes;
  176. }
  177. return ResourceZipFile::VGetRawResource(r, buffer);
  178. }
  179. int DevelopmentResourceZipFile::VGetNumResources() const
  180. {
  181. return (m_mode == Editor) ? m_AssetFileInfo.size() : ResourceZipFile::VGetNumResources();
  182. }
  183. std::string DevelopmentResourceZipFile::VGetResourceName(int num) const
  184. {
  185. if (m_mode == Editor)
  186. {
  187. std::filesystem::path absolutePath = m_AssetFileInfo[num].path();
  188. std::filesystem::path relativePath = absolutePath.lexically_relative(m_AssetsDir);
  189. std::string name = relativePath.string();
  190. std::transform(name.begin(), name.end(), name.begin(), (int(*)(int)) std::tolower);
  191. return name;
  192. }
  193. return ResourceZipFile::VGetResourceName(num);
  194. }
  195. bool DevelopmentResourceZipFile::ReadAssetsDirectory(std::filesystem::path dir)
  196. {
  197. for (const auto &entry : std::filesystem::directory_iterator(dir))
  198. {
  199. if (entry.is_directory())
  200. {
  201. if (!ReadAssetsDirectory(entry))
  202. return false;
  203. }
  204. else if (entry.is_regular_file())
  205. {
  206. std::filesystem::path relativePath = entry.path().lexically_relative(m_AssetsDir);
  207. std::string assetId = relativePath.string();
  208. std::transform(assetId.begin(), assetId.end(), assetId.begin(), (int(*)(int)) std::tolower);
  209. m_DirectoryContentsMap[assetId] = m_AssetFileInfo.size();
  210. m_AssetFileInfo.push_back(entry);
  211. }
  212. }
  213. return true;
  214. }
  215. //
  216. // ResHandle::ResHandle - Chapter 8, page 223
  217. //
  218. ResHandle::ResHandle(Resource & resource, char *buffer, unsigned int size, ResCache *pResCache)
  219. : m_resource(resource)
  220. {
  221. m_buffer = buffer;
  222. m_size = size;
  223. m_extra = NULL;
  224. m_pResCache = pResCache;
  225. }
  226. //
  227. // ResHandle::ResHandle - Chapter 8, page 223
  228. //
  229. ResHandle::~ResHandle()
  230. {
  231. SAFE_DELETE_ARRAY(m_buffer);
  232. m_pResCache->MemoryHasBeenFreed(m_size);
  233. }
  234. //
  235. // ResCache::ResCache - Chapter 8, page 227
  236. //
  237. ResCache::ResCache(const unsigned int sizeInMb, IResourceFile *resFile)
  238. {
  239. m_cacheSize = sizeInMb * 1024 * 1024; // total memory size
  240. m_allocated = 0; // total memory allocated
  241. m_file = resFile;
  242. }
  243. //
  244. // ResCache::~ResCache - Chapter 8, page 227
  245. //
  246. ResCache::~ResCache()
  247. {
  248. while (!m_lru.empty())
  249. {
  250. FreeOneResource();
  251. }
  252. SAFE_DELETE(m_file);
  253. }
  254. //
  255. // ResCache::Init - Chapter 8, page 227
  256. //
  257. bool ResCache::Init()
  258. {
  259. bool retValue = false;
  260. if ( m_file->VOpen() )
  261. {
  262. RegisterLoader(std::shared_ptr<IResourceLoader>(GCC_NEW DefaultResourceLoader()));
  263. retValue = true;
  264. }
  265. return retValue;
  266. }
  267. //
  268. // ResCache::RegisterLoader - Chapter 8, page 225
  269. //
  270. // The loaders are discussed on the page refereced above - this method simply adds the loader
  271. // to the resource cache.
  272. //
  273. void ResCache::RegisterLoader(std::shared_ptr<IResourceLoader> loader )
  274. {
  275. m_resourceLoaders.push_front(loader);
  276. }
  277. //
  278. // ResCache::GetHandle - Chapter 8, page 227
  279. //
  280. std::shared_ptr<ResHandle> ResCache::GetHandle(Resource * r)
  281. {
  282. std::shared_ptr<ResHandle> handle(Find(r));
  283. if (handle==NULL)
  284. {
  285. handle = Load(r);
  286. GCC_ASSERT(handle);
  287. }
  288. else
  289. {
  290. Update(handle);
  291. }
  292. return handle;
  293. }
  294. //
  295. // ResCache::Load - Chapter 8, page 228-230
  296. //
  297. std::shared_ptr<ResHandle> ResCache::Load(Resource *r)
  298. {
  299. // Create a new resource and add it to the lru list and map
  300. std::shared_ptr<IResourceLoader> loader;
  301. std::shared_ptr<ResHandle> handle;
  302. for (ResourceLoaders::iterator it = m_resourceLoaders.begin(); it != m_resourceLoaders.end(); ++it)
  303. {
  304. std::shared_ptr<IResourceLoader> testLoader = *it;
  305. if (WildcardMatch(testLoader->VGetPattern().c_str(), r->m_name.c_str()))
  306. {
  307. loader = testLoader;
  308. break;
  309. }
  310. }
  311. if (!loader)
  312. {
  313. GCC_ASSERT(loader && "Default resource loader not found!");
  314. return handle; // Resource not loaded!
  315. }
  316. int rawSize = m_file->VGetRawResourceSize(*r);
  317. if (rawSize < 0)
  318. {
  319. GCC_ASSERT(rawSize > 0 && "Resource size returned -1 - Resource not found");
  320. return std::shared_ptr<ResHandle>();
  321. }
  322. int allocSize = rawSize + ((loader->VAddNullZero()) ? (1) : (0));
  323. char *rawBuffer = loader->VUseRawFile() ? Allocate(allocSize) : GCC_NEW char[allocSize];
  324. // TODO(munnellg) - This is a bug. What if rawBuffer is NULL?
  325. memset(rawBuffer, 0, allocSize);
  326. if (rawBuffer == NULL || m_file->VGetRawResource(*r, rawBuffer) == 0)
  327. {
  328. // resource cache out of memory
  329. return std::shared_ptr<ResHandle>();
  330. }
  331. char *buffer = NULL;
  332. unsigned int size = 0;
  333. if (loader->VUseRawFile())
  334. {
  335. buffer = rawBuffer;
  336. handle = std::shared_ptr<ResHandle>(GCC_NEW ResHandle(*r, buffer, rawSize, this));
  337. }
  338. else
  339. {
  340. size = loader->VGetLoadedResourceSize(rawBuffer, rawSize);
  341. buffer = Allocate(size);
  342. if (rawBuffer==NULL || buffer==NULL)
  343. {
  344. // resource cache out of memory
  345. return std::shared_ptr<ResHandle>();
  346. }
  347. handle = std::shared_ptr<ResHandle>(GCC_NEW ResHandle(*r, buffer, size, this));
  348. bool success = loader->VLoadResource(rawBuffer, rawSize, handle);
  349. // [mrmike] - This was added after the chapter went to copy edit. It is used for those
  350. // resoruces that are converted to a useable format upon load, such as a compressed
  351. // file. If the raw buffer from the resource file isn't needed, it shouldn't take up
  352. // any additional memory, so we release it.
  353. //
  354. if (loader->VDiscardRawBufferAfterLoad())
  355. {
  356. SAFE_DELETE_ARRAY(rawBuffer);
  357. }
  358. if (!success)
  359. {
  360. // resource cache out of memory
  361. return std::shared_ptr<ResHandle>();
  362. }
  363. }
  364. GCC_INFO("Updating LRU queue");
  365. if (handle)
  366. {
  367. m_lru.push_front(handle);
  368. m_resources[r->m_name] = handle;
  369. }
  370. GCC_ASSERT(loader && "Default resource loader not found!");
  371. return handle; // ResCache is out of memory!
  372. }
  373. //
  374. // ResCache::Find - Chapter 8, page 228
  375. //
  376. std::shared_ptr<ResHandle> ResCache::Find(Resource * r)
  377. {
  378. ResHandleMap::iterator i = m_resources.find(r->m_name);
  379. if (i==m_resources.end())
  380. return std::shared_ptr<ResHandle>();
  381. return i->second;
  382. }
  383. //
  384. // ResCache::Update - Chapter 8, page 228
  385. //
  386. void ResCache::Update(std::shared_ptr<ResHandle> handle)
  387. {
  388. m_lru.remove(handle);
  389. m_lru.push_front(handle);
  390. }
  391. //
  392. // ResCache::Allocate - Chapter 8, page 230
  393. //
  394. char *ResCache::Allocate(unsigned int size)
  395. {
  396. if (!MakeRoom(size))
  397. return NULL;
  398. char *mem = GCC_NEW char[size];
  399. if (mem)
  400. {
  401. m_allocated += size;
  402. }
  403. return mem;
  404. }
  405. //
  406. // ResCache::FreeOneResource - Chapter 8, page 231
  407. //
  408. void ResCache::FreeOneResource()
  409. {
  410. ResHandleList::iterator gonner = m_lru.end();
  411. gonner--;
  412. std::shared_ptr<ResHandle> handle = *gonner;
  413. m_lru.pop_back();
  414. m_resources.erase(handle->m_resource.m_name);
  415. // Note - you can't change the resource cache size yet - the resource bits could still actually be
  416. // used by some sybsystem holding onto the ResHandle. Only when it goes out of scope can the memory
  417. // be actually free again.
  418. }
  419. //
  420. // ResCache::MakeRoom - Chapter 8, page 231
  421. //
  422. bool ResCache::MakeRoom(unsigned int size)
  423. {
  424. if (size > m_cacheSize)
  425. {
  426. return false;
  427. }
  428. // return null if there's no possible way to allocate the memory
  429. while (size > (m_cacheSize - m_allocated))
  430. {
  431. // The cache is empty, and there's still not enough room.
  432. if (m_lru.empty())
  433. return false;
  434. FreeOneResource();
  435. }
  436. return true;
  437. }
  438. //
  439. // ResCache::Free - Chapter 8, page 228
  440. //
  441. void ResCache::Free(std::shared_ptr<ResHandle> gonner)
  442. {
  443. m_lru.remove(gonner);
  444. m_resources.erase(gonner->m_resource.m_name);
  445. // Note - the resource might still be in use by something,
  446. // so the cache can't actually count the memory freed until the
  447. // ResHandle pointing to it is destroyed.
  448. //m_allocated -= gonner->m_resource.m_size;
  449. //delete gonner;
  450. }
  451. //
  452. // ResCache::MemoryHasBeenFreed - not described in the book
  453. //
  454. // This is called whenever the memory associated with a resource is actually freed
  455. //
  456. void ResCache::MemoryHasBeenFreed(unsigned int size)
  457. {
  458. m_allocated -= size;
  459. }
  460. //
  461. // ResCache::Match - not described in the book
  462. //
  463. // Searches the resource cache assets for files matching the pattern. Useful for providing a
  464. // a list of levels for a main menu screen, for example.
  465. //
  466. std::vector<std::string> ResCache::Match(const std::string pattern)
  467. {
  468. std::vector<std::string> matchingNames;
  469. if (m_file==NULL)
  470. return matchingNames;
  471. int numFiles = m_file->VGetNumResources();
  472. for (int i=0; i<numFiles; ++i)
  473. {
  474. std::string name = m_file->VGetResourceName(i);
  475. std::transform(name.begin(), name.end(), name.begin(), (int(*)(int)) std::tolower);
  476. if (WildcardMatch(pattern.c_str(), name.c_str()))
  477. {
  478. matchingNames.push_back(name);
  479. }
  480. }
  481. return matchingNames;
  482. }
  483. //
  484. // ResCache::Preload - Chapter 8, page 236
  485. //
  486. int ResCache::Preload(const std::string pattern, void (*progressCallback)(int, bool &))
  487. {
  488. if (m_file==NULL)
  489. return 0;
  490. int numFiles = m_file->VGetNumResources();
  491. int loaded = 0;
  492. bool cancel = false;
  493. for (int i=0; i<numFiles; ++i)
  494. {
  495. Resource resource(m_file->VGetResourceName(i));
  496. if (WildcardMatch(pattern.c_str(), resource.m_name.c_str()))
  497. {
  498. std::shared_ptr<ResHandle> handle = g_pApp->m_ResCache->GetHandle(&resource);
  499. ++loaded;
  500. }
  501. if (progressCallback != NULL)
  502. {
  503. progressCallback(i * 100 / numFiles, cancel);
  504. }
  505. }
  506. }