| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594 |
- //========================================================================
- // ResCache.cpp : Defines a simple resource cache
- //
- // Part of the GameCode4 Application
- //
- // GameCode4 is the sample application that encapsulates much of the source code
- // discussed in "Game Coding Complete - 4th Edition" by Mike McShaffry and David
- // "Rez" Graham, published by Charles River Media.
- // ISBN-10: 1133776574 | ISBN-13: 978-1133776574
- //
- // If this source code has found it's way to you, and you think it has helped you
- // in any way, do the authors a favor and buy a new copy of the book - there are
- // detailed explanations in it that compliment this code well. Buy a copy at Amazon.com
- // by clicking here:
- // http://www.amazon.com/gp/product/1133776574/ref=olp_product_details?ie=UTF8&me=&seller=
- //
- // There's a companion web site at http://www.mcshaffry.com/GameCode/
- //
- // The source code is managed and maintained through Google Code:
- // http://code.google.com/p/gamecode4/
- //
- // (c) Copyright 2012 Michael L. McShaffry and David Graham
- //
- // This program is free software; you can redistribute it and/or
- // modify it under the terms of the GNU Lesser GPL v3
- // as published by the Free Software Foundation.
- //
- // This program is distributed in the hope that it will be useful,
- // but WITHOUT ANY WARRANTY; without even the implied warranty of
- // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
- // http://www.gnu.org/licenses/lgpl-3.0.txt for more details.
- //
- // You should have received a copy of the GNU Lesser GPL v3
- // along with this program; if not, write to the Free Software
- // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- //
- //========================================================================
- #include "../Debugging/Logger.h"
- #include "../Unix/GameCodeStd.h"
- #include "../Utilities/String.h"
- #include <list>
- #include <map>
- #include <cctype> // for std::tolower
- #include "ResCache.h"
- // [munnellg] replaces Win32 function call from book.
- // TODO(munnellg) - make properly cross-platform
- static std::filesystem::path GetCurrentDirectory() {
- std::filesystem::path exe = std::filesystem::canonical("/proc/self/exe");
- return exe.parent_path();
- }
- //
- // Resource::Resource
- //
- Resource::Resource(const std::string &name)
- {
- m_name=name;
- std::transform(m_name.begin(), m_name.end(), m_name.begin(), (int(*)(int)) std::tolower);
- }
- //========================================================================
- // class ResourceZipFile - not discussed in the book
- //
- // This class implements the IResourceFile interface with a ZipFile.
- //
- ResourceZipFile::~ResourceZipFile()
- {
- SAFE_DELETE(m_pZipFile);
- }
- bool ResourceZipFile::VOpen()
- {
- GCC_INFO("Opening ResourceZipFile");
- m_pZipFile = GCC_NEW ZipFile;
- if (m_pZipFile)
- {
- return m_pZipFile->Init(m_resFileName.c_str());
- }
- return false;
- }
- int ResourceZipFile::VGetRawResourceSize(const Resource &r)
- {
- int resourceNum = m_pZipFile->Find(r.m_name.c_str());
- if (resourceNum == -1)
- return -1;
- return m_pZipFile->GetFileLen(resourceNum);
- }
- int ResourceZipFile::VGetRawResource(const Resource &r, char *buffer)
- {
- int size = 0;
- std::optional<int> resourceNum = m_pZipFile->Find(r.m_name.c_str());
- if (resourceNum)
- {
- size = m_pZipFile->GetFileLen(*resourceNum);
- m_pZipFile->ReadFile(*resourceNum, buffer);
- }
- return size;
- }
- int ResourceZipFile::VGetNumResources() const
- {
- return (m_pZipFile==NULL) ? 0 : m_pZipFile->GetNumFiles();
- }
- std::string ResourceZipFile::VGetResourceName(int num) const
- {
- std::string resName = "";
- if (m_pZipFile != NULL && num >= 0 && num < m_pZipFile->GetNumFiles())
- {
- resName = m_pZipFile->GetFilename(num);
- }
- return resName;
- }
- //========================================================================
- // class DevelopmentResourceZipFile - not discussed in the book
- //
- // This class fakes a ZIP file from a normal directory, and is used in the
- // editor.
- //
- DevelopmentResourceZipFile::DevelopmentResourceZipFile(const std::wstring resFileName, const Mode mode)
- : ResourceZipFile(resFileName)
- {
- m_mode=mode;
- std::filesystem::path dir = GetCurrentDirectory();
- m_AssetsDir = dir / "Assets";
- }
- int DevelopmentResourceZipFile::Find(const std::string &name)
- {
- std::string lowerCase = name;
- std::transform(lowerCase.begin(), lowerCase.end(), lowerCase.begin(), (int(*)(int)) std::tolower);
- ZipContentsMap::const_iterator i = m_DirectoryContentsMap.find(lowerCase);
- if (i==m_DirectoryContentsMap.end())
- return -1;
- return i->second;
- }
- bool DevelopmentResourceZipFile::VOpen()
- {
- GCC_INFO("Opening DevelopmentResourceZipFile");
- if (m_mode != Editor)
- {
- return ResourceZipFile::VOpen();
- }
- if (m_mode == Editor)
- {
- return ReadAssetsDirectory(m_AssetsDir);
- }
- else
- {
- // TODO(munnellg) - See if we can get this working
- // FUTURE WORK - iterate through the ZipFile contents and go grab WIN32_FIND_DATA
- // elements for each one. Then it would be possible to compare the dates/times of the
- // asset in the Zip file with the source asset.
- GCC_ASSERT(0 && "Not implemented yet");
- }
- return true;
- }
- int DevelopmentResourceZipFile::VGetRawResourceSize(const Resource &r)
- {
- int size = 0;
- if (m_mode == Editor)
- {
- int num = Find(r.m_name.c_str());
- if (num == -1)
- return -1;
- return m_AssetFileInfo[num].file_size();
- }
- return ResourceZipFile::VGetRawResourceSize(r);
- }
- int DevelopmentResourceZipFile::VGetRawResource(const Resource &r, char *buffer)
- {
- if (m_mode == Editor)
- {
- int num = Find(r.m_name.c_str());
- if (num == -1)
- return -1;
- FILE *f = fopen(m_AssetFileInfo[num].path().c_str(), "rb");
- GCC_ASSERT(f);
- size_t bytes = fread(buffer, 1, m_AssetFileInfo[num].file_size(), f);
- fclose(f);
- return bytes;
- }
- return ResourceZipFile::VGetRawResource(r, buffer);
- }
- int DevelopmentResourceZipFile::VGetNumResources() const
- {
- return (m_mode == Editor) ? m_AssetFileInfo.size() : ResourceZipFile::VGetNumResources();
- }
- std::string DevelopmentResourceZipFile::VGetResourceName(int num) const
- {
- if (m_mode == Editor)
- {
- std::filesystem::path absolutePath = m_AssetFileInfo[num].path();
- std::filesystem::path relativePath = absolutePath.lexically_relative(m_AssetsDir);
- std::string name = relativePath.string();
- std::transform(name.begin(), name.end(), name.begin(), (int(*)(int)) std::tolower);
- return name;
- }
- return ResourceZipFile::VGetResourceName(num);
- }
- bool DevelopmentResourceZipFile::ReadAssetsDirectory(std::filesystem::path dir)
- {
- for (const auto &entry : std::filesystem::directory_iterator(dir))
- {
- if (entry.is_directory())
- {
- if (!ReadAssetsDirectory(entry))
- return false;
- }
- else if (entry.is_regular_file())
- {
- std::filesystem::path relativePath = entry.path().lexically_relative(m_AssetsDir);
- std::string assetId = relativePath.string();
- std::transform(assetId.begin(), assetId.end(), assetId.begin(), (int(*)(int)) std::tolower);
- m_DirectoryContentsMap[assetId] = m_AssetFileInfo.size();
- m_AssetFileInfo.push_back(entry);
- }
- }
- return true;
- }
- //
- // ResHandle::ResHandle - Chapter 8, page 223
- //
- ResHandle::ResHandle(Resource & resource, char *buffer, unsigned int size, ResCache *pResCache)
- : m_resource(resource)
- {
- m_buffer = buffer;
- m_size = size;
- m_extra = NULL;
- m_pResCache = pResCache;
- }
- //
- // ResHandle::ResHandle - Chapter 8, page 223
- //
- ResHandle::~ResHandle()
- {
- SAFE_DELETE_ARRAY(m_buffer);
- m_pResCache->MemoryHasBeenFreed(m_size);
- }
- //
- // ResCache::ResCache - Chapter 8, page 227
- //
- ResCache::ResCache(const unsigned int sizeInMb, IResourceFile *resFile)
- {
- m_cacheSize = sizeInMb * 1024 * 1024; // total memory size
- m_allocated = 0; // total memory allocated
- m_file = resFile;
- }
- //
- // ResCache::~ResCache - Chapter 8, page 227
- //
- ResCache::~ResCache()
- {
- while (!m_lru.empty())
- {
- FreeOneResource();
- }
- SAFE_DELETE(m_file);
- }
- //
- // ResCache::Init - Chapter 8, page 227
- //
- bool ResCache::Init()
- {
- bool retValue = false;
- if ( m_file->VOpen() )
- {
- RegisterLoader(std::shared_ptr<IResourceLoader>(GCC_NEW DefaultResourceLoader()));
- retValue = true;
- }
- return retValue;
- }
- //
- // ResCache::RegisterLoader - Chapter 8, page 225
- //
- // The loaders are discussed on the page refereced above - this method simply adds the loader
- // to the resource cache.
- //
- void ResCache::RegisterLoader(std::shared_ptr<IResourceLoader> loader )
- {
- m_resourceLoaders.push_front(loader);
- }
- //
- // ResCache::GetHandle - Chapter 8, page 227
- //
- std::shared_ptr<ResHandle> ResCache::GetHandle(Resource * r)
- {
- std::shared_ptr<ResHandle> handle(Find(r));
- if (handle==NULL)
- {
- handle = Load(r);
- GCC_ASSERT(handle);
- }
- else
- {
- Update(handle);
- }
- return handle;
- }
- //
- // ResCache::Load - Chapter 8, page 228-230
- //
- std::shared_ptr<ResHandle> ResCache::Load(Resource *r)
- {
- // Create a new resource and add it to the lru list and map
- std::shared_ptr<IResourceLoader> loader;
- std::shared_ptr<ResHandle> handle;
- for (ResourceLoaders::iterator it = m_resourceLoaders.begin(); it != m_resourceLoaders.end(); ++it)
- {
- std::shared_ptr<IResourceLoader> testLoader = *it;
- if (WildcardMatch(testLoader->VGetPattern().c_str(), r->m_name.c_str()))
- {
- loader = testLoader;
- break;
- }
- }
- if (!loader)
- {
- GCC_ASSERT(loader && "Default resource loader not found!");
- return handle; // Resource not loaded!
- }
- int rawSize = m_file->VGetRawResourceSize(*r);
- if (rawSize < 0)
- {
- GCC_ASSERT(rawSize > 0 && "Resource size returned -1 - Resource not found");
- return std::shared_ptr<ResHandle>();
- }
- int allocSize = rawSize + ((loader->VAddNullZero()) ? (1) : (0));
- char *rawBuffer = loader->VUseRawFile() ? Allocate(allocSize) : GCC_NEW char[allocSize];
- // TODO(munnellg) - This is a bug. What if rawBuffer is NULL?
- memset(rawBuffer, 0, allocSize);
- if (rawBuffer == NULL || m_file->VGetRawResource(*r, rawBuffer) == 0)
- {
- // resource cache out of memory
- return std::shared_ptr<ResHandle>();
- }
- char *buffer = NULL;
- unsigned int size = 0;
- if (loader->VUseRawFile())
- {
- buffer = rawBuffer;
- handle = std::shared_ptr<ResHandle>(GCC_NEW ResHandle(*r, buffer, rawSize, this));
- }
- else
- {
- size = loader->VGetLoadedResourceSize(rawBuffer, rawSize);
- buffer = Allocate(size);
- if (rawBuffer==NULL || buffer==NULL)
- {
- // resource cache out of memory
- return std::shared_ptr<ResHandle>();
- }
- handle = std::shared_ptr<ResHandle>(GCC_NEW ResHandle(*r, buffer, size, this));
- bool success = loader->VLoadResource(rawBuffer, rawSize, handle);
- // [mrmike] - This was added after the chapter went to copy edit. It is used for those
- // resoruces that are converted to a useable format upon load, such as a compressed
- // file. If the raw buffer from the resource file isn't needed, it shouldn't take up
- // any additional memory, so we release it.
- //
- if (loader->VDiscardRawBufferAfterLoad())
- {
- SAFE_DELETE_ARRAY(rawBuffer);
- }
- if (!success)
- {
- // resource cache out of memory
- return std::shared_ptr<ResHandle>();
- }
- }
- GCC_INFO("Updating LRU queue");
- if (handle)
- {
- m_lru.push_front(handle);
- m_resources[r->m_name] = handle;
- }
- GCC_ASSERT(loader && "Default resource loader not found!");
- return handle; // ResCache is out of memory!
- }
- //
- // ResCache::Find - Chapter 8, page 228
- //
- std::shared_ptr<ResHandle> ResCache::Find(Resource * r)
- {
- ResHandleMap::iterator i = m_resources.find(r->m_name);
- if (i==m_resources.end())
- return std::shared_ptr<ResHandle>();
- return i->second;
- }
- //
- // ResCache::Update - Chapter 8, page 228
- //
- void ResCache::Update(std::shared_ptr<ResHandle> handle)
- {
- m_lru.remove(handle);
- m_lru.push_front(handle);
- }
- //
- // ResCache::Allocate - Chapter 8, page 230
- //
- char *ResCache::Allocate(unsigned int size)
- {
- if (!MakeRoom(size))
- return NULL;
- char *mem = GCC_NEW char[size];
- if (mem)
- {
- m_allocated += size;
- }
- return mem;
- }
- //
- // ResCache::FreeOneResource - Chapter 8, page 231
- //
- void ResCache::FreeOneResource()
- {
- ResHandleList::iterator gonner = m_lru.end();
- gonner--;
- std::shared_ptr<ResHandle> handle = *gonner;
- m_lru.pop_back();
- m_resources.erase(handle->m_resource.m_name);
- // Note - you can't change the resource cache size yet - the resource bits could still actually be
- // used by some sybsystem holding onto the ResHandle. Only when it goes out of scope can the memory
- // be actually free again.
- }
- //
- // ResCache::MakeRoom - Chapter 8, page 231
- //
- bool ResCache::MakeRoom(unsigned int size)
- {
- if (size > m_cacheSize)
- {
- return false;
- }
- // return null if there's no possible way to allocate the memory
- while (size > (m_cacheSize - m_allocated))
- {
- // The cache is empty, and there's still not enough room.
- if (m_lru.empty())
- return false;
- FreeOneResource();
- }
- return true;
- }
- //
- // ResCache::Free - Chapter 8, page 228
- //
- void ResCache::Free(std::shared_ptr<ResHandle> gonner)
- {
- m_lru.remove(gonner);
- m_resources.erase(gonner->m_resource.m_name);
- // Note - the resource might still be in use by something,
- // so the cache can't actually count the memory freed until the
- // ResHandle pointing to it is destroyed.
- //m_allocated -= gonner->m_resource.m_size;
- //delete gonner;
- }
- //
- // ResCache::MemoryHasBeenFreed - not described in the book
- //
- // This is called whenever the memory associated with a resource is actually freed
- //
- void ResCache::MemoryHasBeenFreed(unsigned int size)
- {
- m_allocated -= size;
- }
- //
- // ResCache::Match - not described in the book
- //
- // Searches the resource cache assets for files matching the pattern. Useful for providing a
- // a list of levels for a main menu screen, for example.
- //
- std::vector<std::string> ResCache::Match(const std::string pattern)
- {
- std::vector<std::string> matchingNames;
- if (m_file==NULL)
- return matchingNames;
- int numFiles = m_file->VGetNumResources();
- for (int i=0; i<numFiles; ++i)
- {
- std::string name = m_file->VGetResourceName(i);
- std::transform(name.begin(), name.end(), name.begin(), (int(*)(int)) std::tolower);
- if (WildcardMatch(pattern.c_str(), name.c_str()))
- {
- matchingNames.push_back(name);
- }
- }
- return matchingNames;
- }
- //
- // ResCache::Preload - Chapter 8, page 236
- //
- int ResCache::Preload(const std::string pattern, void (*progressCallback)(int, bool &))
- {
- if (m_file==NULL)
- return 0;
- int numFiles = m_file->VGetNumResources();
- int loaded = 0;
- bool cancel = false;
- for (int i=0; i<numFiles; ++i)
- {
- Resource resource(m_file->VGetResourceName(i));
- if (WildcardMatch(pattern.c_str(), resource.m_name.c_str()))
- {
- std::shared_ptr<ResHandle> handle = g_pApp->m_ResCache->GetHandle(&resource);
- ++loaded;
- }
- if (progressCallback != NULL)
- {
- progressCallback(i * 100 / numFiles, cancel);
- }
- }
- }
|