#include "PrecompiledHeader.h" #include "WResourceManagerImpl.h" #include "StyleManager.h" #include "CacheEngine.h" #if defined (WIN32) #include #include #endif #include "WFont.h" //#define FORCE_LOW_CACHE_MEMORY const unsigned int kConstrainedCacheLimit = 8 * 1024 * 1024; extern bool neofont; int idCounter = OTHERS_OFFSET; namespace { const std::string kExtension_png(".png"); const std::string kExtension_gbk(".gbk"); const std::string kExtension_font(".font"); // mutex meant for the cache map boost::mutex sCacheMutex; // mutex meant to protect against unthread-safe calls into JFileSystem, etc. boost::mutex sLoadFunctionMutex; } WResourceManager* WResourceManager::sInstance = NULL; WResourceManager* WResourceManager::Instance() { if (sInstance == NULL) { sInstance = NEW ResourceManagerImpl; } return sInstance; } void WResourceManager::Terminate() { if (sInstance) SAFE_DELETE(sInstance); } int ResourceManagerImpl::RetrieveError() { return lastError; } bool ResourceManagerImpl::RemoveOldest() { if (sampleWCache.RemoveOldest()) return true; if (textureWCache.RemoveOldest()) return true; if (psiWCache.RemoveOldest()) return true; return false; } //ResourceManagerImpl void ResourceManagerImpl::DebugRender() { JRenderer* renderer = JRenderer::GetInstance(); WFont * font = ResourceManagerImpl::Instance()->GetWFont(Fonts::MAIN_FONT); if (!font || !renderer) return; font->SetColor(ARGB(255,255,255,255)); font->SetScale(DEFAULT_MAIN_FONT_SCALE); renderer->FillRect(0, 0, SCREEN_WIDTH, 40, ARGB(128,155,0,0)); renderer->FillRect(0, SCREEN_HEIGHT - 20, SCREEN_WIDTH, 40, ARGB(128,155,0,0)); char buf[512]; unsigned long man = 0; unsigned int misses = 0; if (textureWCache.cacheItems < textureWCache.cache.size()) misses = textureWCache.cache.size() - textureWCache.cacheItems; if (textureWCache.totalSize > textureWCache.cacheSize) man = textureWCache.totalSize - textureWCache.cacheSize; sprintf(buf, "Textures %u+%llu (of %u) items (%u misses), Pixels: %lu (of %lu) + %lu", textureWCache.cacheItems, (long long unsigned int) textureWCache.managed.size(), textureWCache.maxCached, misses, textureWCache.cacheSize, textureWCache.maxCacheSize, man); font->DrawString(buf, 10, 5); #ifdef PSP //deliberately off - these functions aren't thread safe! //int maxLinear = ramAvailableLineareMax(); //int ram = ramAvailable(); //sprintf(buf, "Ram : linear max: %i - total : %i sceSize : %i\n", maxLinear, ram, sceSize); //font->DrawString(buf, 10, 20); #endif sprintf(buf, "Time: %u. Total Size: %lu (%lu cached, %lu managed). ", lastTime, Size(), SizeCached(), SizeManaged()); font->DrawString(buf, SCREEN_WIDTH - 10, SCREEN_HEIGHT - 15, JGETEXT_RIGHT); #ifdef DEBUG_CACHE if(debugMessage.size()) font->DrawString(debugMessage.c_str(), SCREEN_WIDTH-10,SCREEN_HEIGHT-25,JGETEXT_RIGHT); #endif } unsigned long ResourceManagerImpl::Size() { unsigned long res = 0; res += textureWCache.totalSize; res += sampleWCache.totalSize; res += psiWCache.totalSize; return res; } unsigned long ResourceManagerImpl::SizeCached() { unsigned long res = 0; res += textureWCache.cacheSize; res += sampleWCache.cacheSize; res += psiWCache.cacheSize; return res; } unsigned long ResourceManagerImpl::SizeManaged() { unsigned long res = 0; if (textureWCache.totalSize > textureWCache.cacheSize) res += textureWCache.totalSize - textureWCache.cacheSize; if (sampleWCache.totalSize > sampleWCache.cacheSize) res += sampleWCache.totalSize - sampleWCache.cacheSize; if (psiWCache.totalSize > psiWCache.cacheSize) res += psiWCache.totalSize - psiWCache.cacheSize; return res; } unsigned int ResourceManagerImpl::Count() { unsigned int count = 0; count += textureWCache.cacheItems; count += textureWCache.managed.size(); count += sampleWCache.cacheItems; count += sampleWCache.managed.size(); count += psiWCache.cacheItems; count += psiWCache.managed.size(); return count; } unsigned int ResourceManagerImpl::CountCached() { unsigned int count = 0; count += textureWCache.cacheItems; count += sampleWCache.cacheItems; count += psiWCache.cacheItems; return count; } unsigned int ResourceManagerImpl::CountManaged() { unsigned int count = 0; count += textureWCache.managed.size(); count += sampleWCache.managed.size(); count += psiWCache.managed.size(); return count; } unsigned int ResourceManagerImpl::nowTime() { if (lastTime > MAX_CACHE_TIME) FlattenTimes(); return ++lastTime; } void ResourceManagerImpl::FlattenTimes() { unsigned int t; lastTime = sampleWCache.Flatten(); t = textureWCache.Flatten(); if (t > lastTime) lastTime = t; t = psiWCache.Flatten(); if (t > lastTime) lastTime = t; } ResourceManagerImpl::ResourceManagerImpl() { DebugTrace("Init ResourceManagerImpl : " << this); #ifdef DEBUG_CACHE menuCached = 0; #endif psiWCache.Resize(PSI_CACHE_SIZE, 20); sampleWCache.Resize(SAMPLES_CACHE_SIZE, MAX_CACHED_SAMPLES); textureWCache.Resize(TEXTURES_CACHE_MINSIZE, MAX_CACHE_OBJECTS); lastTime = 0; lastError = CACHE_ERROR_NONE; bThemedCards = false; LOG("Calling CacheEngine::Create"); #ifdef PSP CacheEngine::Create(textureWCache); #else CacheEngine::Create(textureWCache); #endif } ResourceManagerImpl::~ResourceManagerImpl() { LOG("==Destroying ResourceManagerImpl=="); RemoveWFonts(); CacheEngine::Terminate(); LOG("==Successfully Destroyed ResourceManagerImpl=="); } bool ResourceManagerImpl::IsThreaded() { return CacheEngine::IsThreaded(); } JQuadPtr ResourceManagerImpl::RetrieveCard(MTGCard * card, int style, int submode) { //Cards are never, ever resource managed, so just check cache. if (!card || options[Options::DISABLECARDS].number) return JQuadPtr(); submode = submode | TEXTURE_SUB_CARD; static std::ostringstream filename; filename.str(""); filename << setlist[card->setId] << "/" << card->getImageName(); int id = card->getMTGId(); //Aliases. if (style == RETRIEVE_THUMB) { submode = submode | TEXTURE_SUB_THUMB; style = RETRIEVE_NORMAL; } JQuadPtr jq = RetrieveQuad(filename.str(), 0, 0, 0, 0, "", style, submode | TEXTURE_SUB_5551, id); lastError = textureWCache.mError; if (jq) { jq->SetHotSpot(static_cast (jq->mTex->mWidth / 2), static_cast (jq->mTex->mHeight / 2)); return jq; } return JQuadPtr(); } int ResourceManagerImpl::AddQuadToManaged(const WManagedQuad& inQuad) { int id = mIDLookupMap.size(); mIDLookupMap.insert(make_pair(id, inQuad.resname)); mManagedQuads.insert(make_pair(inQuad.resname, inQuad)); return id; } int ResourceManagerImpl::CreateQuad(const string &quadName, const string &textureName, float x, float y, float width, float height) { if (!quadName.size() || !textureName.size()) return INVALID_ID; if (GetQuad(quadName) != NULL) { assert(false); return ALREADY_EXISTS; } WCachedTexture * jtex = textureWCache.Retrieve(0, textureName, RETRIEVE_MANAGE); lastError = textureWCache.mError; int id = INVALID_ID; //Somehow, jtex wasn't promoted. if (RETRIEVE_MANAGE && jtex && !jtex->isPermanent()) return id; if (jtex) { JQuadPtr quad = jtex->GetQuad(x, y, width, height, quadName); if (quad.get()) { jtex->deadbolt(); WManagedQuad mq; mq.resname = quadName; mq.texture = jtex; id = AddQuadToManaged(mq); } } assert(id != INVALID_ID); return id; } JQuadPtr ResourceManagerImpl::GetQuad(const string &quadName) { JQuadPtr result; ManagedQuadMap::const_iterator found = mManagedQuads.find(quadName); if (found != mManagedQuads.end()) { result = found->second.texture->GetQuad(quadName); } return result; } JQuadPtr ResourceManagerImpl::GetQuad(int id) { JQuadPtr result; if (id < 0 || id >= (int) mManagedQuads.size()) return result; IDLookupMap::const_iterator key = mIDLookupMap.find(id); if (key != mIDLookupMap.end()) { WCachedTexture* jtex = mManagedQuads[key->second].texture; if (jtex) { result = jtex->GetQuad(key->second); } } return result; } JQuadPtr ResourceManagerImpl::RetrieveTempQuad(const string& filename, int submode) { return RetrieveQuad(filename, 0, 0, 0, 0, "temporary", RETRIEVE_NORMAL, submode); } JQuadPtr ResourceManagerImpl::RetrieveQuad(const string& filename, float offX, float offY, float width, float height, string resname, int style, int submode, int id) { //Lookup managed resources, but only with a real resname. if (resname.size() && (style == RETRIEVE_MANAGE || style == RETRIEVE_RESOURCE)) { JQuadPtr quad = GetQuad(resname); if (quad.get() || style == RETRIEVE_RESOURCE) return quad; } //Aliases. if (style == RETRIEVE_THUMB) { submode = submode | TEXTURE_SUB_THUMB; style = RETRIEVE_NORMAL; } //Resname defaults to filename. if (!resname.size()) resname = filename; //No quad, but we have a managed texture for this! WCachedTexture* jtex = textureWCache.Retrieve(id, filename, style, submode); lastError = textureWCache.mError; //Somehow, jtex wasn't promoted. if (style == RETRIEVE_MANAGE && jtex && !jtex->isPermanent()) return JQuadPtr(); //Make this quad, overwriting any similarly resname'd quads. if (jtex) { JQuadPtr quad = jtex->GetQuad(offX, offY, width, height, resname); if (!quad.get()) return quad; if (style == RETRIEVE_MANAGE && resname != "") { WManagedQuad mq; mq.resname = resname; mq.texture = jtex; AddQuadToManaged(mq); } if (style == RETRIEVE_LOCK) jtex->lock(); else if (style == RETRIEVE_UNLOCK) jtex->unlock(); else if (style == RETRIEVE_MANAGE) jtex->deadbolt(); return quad; } //Texture doesn't exist, so no quad. return JQuadPtr(); } void ResourceManagerImpl::Release(JTexture * tex) { if (!tex) return; //Copied direct from WCache::Release(). This is quick and dirty. map::iterator it; for (it = textureWCache.cache.begin(); it != textureWCache.cache.end(); it++) { if (it->second && it->second->compare(tex)) break; } if (it == textureWCache.cache.end()) return; //Not here, can't release. if (it->second) { it->second->unlock(); //Release one lock. if (it->second->locks != WRES_UNLOCKED) //Normally we'd call isLocked, but this way ignores quads. return; //Locked } textureWCache.Delete(it->second); textureWCache.cache.erase(it); return; //Released! } void ResourceManagerImpl::ClearUnlocked() { textureWCache.ClearUnlocked(); sampleWCache.ClearUnlocked(); psiWCache.ClearUnlocked(); } void ResourceManagerImpl::Release(JSample * sample) { if (!sample) return; sampleWCache.Release(sample); } JTexture * ResourceManagerImpl::RetrieveTexture(const string& filename, int style, int submode) { //Aliases. if (style == RETRIEVE_THUMB) { submode = submode | TEXTURE_SUB_THUMB; style = RETRIEVE_NORMAL; } WCachedTexture* res = textureWCache.Retrieve(0, filename, style, submode); lastError = textureWCache.mError; if (res) { //a non-null result will always be good. JTexture * t = res->Actual(); return t; } #ifdef DEBUG_CACHE else { switch(textureWCache.mError) { case CACHE_ERROR_NONE: debugMessage = "Not in cache: "; break; case CACHE_ERROR_404: debugMessage = "File not found: "; break; case CACHE_ERROR_BAD_ALLOC: debugMessage = "Out of memory: "; break; case CACHE_ERROR_BAD: debugMessage = "Cache bad: "; break; case CACHE_ERROR_NOT_MANAGED: debugMessage = "Resource not managed: "; break; case CACHE_ERROR_LOST: debugMessage = "Resource went bad, potential memory leak: "; break; default: debugMessage = "Unspecified error: "; } debugMessage += filename; } #endif return NULL; } int ResourceManagerImpl::CreateTexture(const string &textureName) { JTexture * jtex = RetrieveTexture(textureName, RETRIEVE_MANAGE); if (jtex) return (int) jtex->mTexId; //Because it's unsigned on windows/linux. return INVALID_ID; } JTexture* ResourceManagerImpl::GetTexture(const string &textureName) { JTexture * jtex = RetrieveTexture(textureName, RETRIEVE_RESOURCE); return jtex; } JTexture* ResourceManagerImpl::GetTexture(int id) { map::iterator it; JTexture *jtex = NULL; if (id == INVALID_ID) return NULL; for (it = textureWCache.managed.begin(); it != textureWCache.managed.end(); it++) { if (it->second) { jtex = it->second->Actual(); if (id == (int) jtex->mTexId) return jtex; } } return jtex; } hgeParticleSystemInfo * ResourceManagerImpl::RetrievePSI(const string& filename, JQuad * texture, int style, int submode) { if (!texture) return NULL; WCachedParticles * res = psiWCache.Retrieve(0, filename, style, submode); lastError = psiWCache.mError; if (res) //A non-null result will always be good. { hgeParticleSystemInfo * i = res->Actual(); i->sprite = texture; return i; } return NULL; } JSample * ResourceManagerImpl::RetrieveSample(const string& filename, int style, int submode) { WCachedSample * tc = NULL; tc = sampleWCache.Retrieve(0, filename, style, submode); lastError = sampleWCache.mError; //Sample exists! Get it. if (tc && tc->isGood()) { JSample * js = tc->Actual(); return js; } return NULL; } string ResourceManagerImpl::graphicsFile(const string& filename) { char buf[512]; //Check the theme folder. string theme = options[Options::ACTIVE_THEME].str; //Check for a theme style renaming: if (filename != "style.txt") { WStyle * ws = options.getStyle(); if (ws) { sprintf(buf, "themes/%s/%s", theme.c_str(), ws->stylized(filename).c_str()); if (fileOK(buf)) return buf; } } if (theme != "" && theme != "Default") { sprintf(buf, "themes/%s/%s", theme.c_str(), filename.c_str()); if (fileOK(buf)) return buf; } /* //FIXME Put back when we're using modes. //Failure. Check mode graphics string mode = options[Options::ACTIVE_MODE].str; if(mode != "" && mode != "Default"){ sprintf(buf,"modes/%s/graphics/%s",mode.c_str(),filename.c_str()); if(fileOK(buf,true)) return buf; } */ //Failure. Check graphics char graphdir[512]; sprintf(graphdir, "graphics/%s", filename.c_str()); if (fileOK(graphdir)) return graphdir; //Failure. Check sets. sprintf(buf, "sets/%s", filename.c_str()); if (fileOK(buf)) return buf; //Failure. Check raw faile. sprintf(buf, "%s", filename.c_str()); if (fileOK(buf)) return buf; //Complete abject failure. Probably a crash... return graphdir; } string ResourceManagerImpl::avatarFile(const string& filename) { char buf[512]; //Check the profile folder. string profile = options[Options::ACTIVE_PROFILE].str; if (profile != "" && profile != "Default") { sprintf(buf, "profiles/%s/%s", profile.c_str(), filename.c_str()); if (fileOK(buf)) return buf; } else { sprintf(buf, "player/%s", filename.c_str()); if (fileOK(buf)) return buf; } //Check the theme folder. string theme = options[Options::ACTIVE_THEME].str; if (theme != "" && theme != "Default") { sprintf(buf, "themes/%s/%s", theme.c_str(), filename.c_str()); if (fileOK(buf)) return buf; } /* //FIXME Put back when we're using modes. //Failure. Check mode graphics string mode = options[Options::ACTIVE_MODE].str; if(mode != "" && mode != "Default"){ sprintf(buf,"modes/%s/graphics/%s",mode.c_str(),filename.c_str()); if(fileOK(buf,true)) return buf; } */ //Failure. Check Baka sprintf(buf, "ai/baka/avatars/%s", filename.c_str()); if (fileOK(buf)) return buf; //Failure. Check graphics sprintf(buf, "graphics/%s", filename.c_str()); if (fileOK(buf)) return buf; //Failure. Check raw faile. sprintf(buf, "%s", filename.c_str()); if (fileOK(buf)) return buf; //Complete abject failure. Probably a crash... return ""; } string ResourceManagerImpl::cardFile(const string& filename) { char buf[512]; string::size_type i = 0; string set; JFileSystem* fs = JFileSystem::GetInstance(); //Check the theme folder. string theme = options[Options::ACTIVE_THEME].str; if (theme != "" && theme != "Default") { //Does this theme use custom cards? if (bThemedCards) { //Check zipped first. Discover set name. for (i = 0; i < filename.size(); i++) { if (filename[i] == '\\' || filename[i] == '/') break; } if (i != filename.size()) set = filename.substr(0, i); if (set.size()) { char zipname[512]; sprintf(zipname, "themes/%s/sets/%s/%s.zip", theme.c_str(), set.c_str(), set.c_str()); if (fs->AttachZipFile(zipname)) return filename.substr(i + 1); } sprintf(buf, "themes/%s/sets/%s", theme.c_str(), filename.c_str()); if (fileOK(buf)) return buf; //Themed, unzipped. } } //FIXME Put back when we're using modes. /* //Failure. Check mode string mode = options[Options::ACTIVE_MODE].str; if(mode != "" && mode != "Default"){ sprintf(buf,"modes/%s/sets/%s",mode.c_str(),filename.c_str()); if(fileOK(buf,true)) return buf; } */ //Failure. Assume it's in a zip file? if (!set.size()) { //Didn't fill "set" string, so do it now. for (i = 0; i < filename.size(); i++) { if (filename[i] == '\\' || filename[i] == '/') break; } if (i != filename.size()) set = filename.substr(0, i); } if (set.size()) { char zipname[512]; sprintf(zipname, "sets/%s/%s.zip", set.c_str(), set.c_str()); if (fs->AttachZipFile(zipname)) return filename.substr(i + 1); } //Failure. Check for unzipped file in sets char defdir[512]; sprintf(defdir, "sets/%s", filename.c_str()); if (fileOK(defdir)) return defdir; //Complete failure. return ""; } string ResourceManagerImpl::musicFile(const string& filename) { char buf[512]; //Check the theme folder. string theme = options[Options::ACTIVE_THEME].str; if (theme != "" && theme != "Default") { sprintf(buf, "themes/%s/sound/%s", theme.c_str(), filename.c_str()); if (fileOK(buf)) return buf; } /* //FIXME Put back when we're using modes. //Failure. Check mode string mode = options[Options::ACTIVE_MODE].str; if(mode != "" && mode != "Default"){ sprintf(buf,"modes/%s/sound/%s",mode.c_str(),filename.c_str()); if(fileOK(buf,true)) return buf; }*/ //Failure. Check sound char defdir[512]; sprintf(defdir, "sound/%s", filename.c_str()); if (fileOK(defdir)) return defdir; //Failure. Check raw file. sprintf(defdir, "%s", filename.c_str()); if (fileOK(defdir)) return defdir; //Complete abject failure. Probably a crash... return ""; } string ResourceManagerImpl::sfxFile(const string& filename) { char buf[512]; //Check the theme folder. string theme = options[Options::ACTIVE_THEME].str; if (theme != "" && theme != "Default") { sprintf(buf, "themes/%s/sound/sfx/%s", theme.c_str(), filename.c_str()); if (fileOK(buf)) return buf; } /* //FIXME: Put back when we're using modes. //Failure. Check mode string mode = options[Options::ACTIVE_MODE].str; if(mode != "" && mode != "Default"){ sprintf(buf,"modes/%s/sound/sfx/%s",mode.c_str(),filename.c_str()); if(fileOK(buf,true)) return buf; } */ //Failure. Check sound char defdir[512]; sprintf(defdir, "sound/sfx/%s", filename.c_str()); if (fileOK(defdir)) return defdir; //Complete abject failure. Probably a crash... return ""; } bool ResourceManagerImpl::dirOK(const string& dirname) { return JFileSystem::GetInstance()->DirExists(dirname); } bool ResourceManagerImpl::fileOK(const string& filename) { return JFileSystem::GetInstance()->FileExists(filename); } void ResourceManagerImpl::InitFonts(const std::string& inLang) { unsigned int idOffset = 0; if (inLang.compare("cn") == 0) { mFontFileExtension = kExtension_gbk; LoadWFont("simon", 12, Fonts::MAIN_FONT); LoadWFont("f3", 16, Fonts::MENU_FONT); LoadWFont("magic", 16, Fonts::MAGIC_FONT); LoadWFont("smallface", 12, Fonts::SMALLFACE_FONT); idOffset = Fonts::kSingleByteFontOffset; } if (inLang.compare("jp") == 0) { mFontFileExtension = kExtension_font; LoadWFont("simon", 12, Fonts::MAIN_FONT); LoadWFont("f3", 16, Fonts::MENU_FONT); LoadWFont("magic", 16, Fonts::MAGIC_FONT); LoadWFont("smallface", 12, Fonts::SMALLFACE_FONT); idOffset = Fonts::kSingleByteFontOffset; } mFontFileExtension = kExtension_png; LoadWFont("simon", 11, Fonts::MAIN_FONT + idOffset); GetWFont(Fonts::MAIN_FONT)->SetTracking(-1); LoadWFont("f3", 16, Fonts::MENU_FONT + idOffset); LoadWFont("magic", 16, Fonts::MAGIC_FONT + idOffset); LoadWFont("smallface", 7, Fonts::SMALLFACE_FONT + idOffset); } int ResourceManagerImpl::ReloadWFonts() { RemoveWFonts(); string lang = options[Options::LANG].str; std::transform(lang.begin(), lang.end(), lang.begin(), ::tolower); InitFonts(lang); return 1; } WFont* ResourceManagerImpl::LoadWFont(const string& inFontname, int inFontHeight, int inFontID) { WFont* font = GetWFont(inFontID); if (font) { return font; } string mFontName = inFontname + mFontFileExtension; string path = graphicsFile(mFontName); if (mFontFileExtension == kExtension_font) font = NEW WUFont(inFontID, path.c_str(), inFontHeight, true); else if (mFontFileExtension == kExtension_gbk) font = NEW WGBKFont(inFontID, path.c_str(), inFontHeight, true); else font = NEW WLBFont(inFontID, path.c_str(), inFontHeight, true); mWFontMap[inFontID] = font; return font; } WFont* ResourceManagerImpl::GetWFont(int id) { WFont* font = NULL; FontMap::iterator iter = mWFontMap.find(id); if (iter != mWFontMap.end()) { font = iter->second; } return font; } void ResourceManagerImpl::RemoveWFonts() { for (FontMap::iterator font = mWFontMap.begin(); font != mWFontMap.end(); ++font) { delete font->second; } mWFontMap.clear(); } void ResourceManagerImpl::ResetCacheLimits() { #if defined PSP unsigned int ram = ramAvailable(); unsigned int myNewSize = ram - OPERATIONAL_SIZE + textureWCache.totalSize; if (myNewSize < TEXTURES_CACHE_MINSIZE) { DebugTrace( "Error, Not enough RAM for Cache: " << myNewSize << " - total Ram: " << ram); } textureWCache.Resize(MIN(myNewSize, HUGE_CACHE_LIMIT), MAX_CACHE_OBJECTS); #else #ifdef FORCE_LOW_CACHE_MEMORY textureWCache.Resize(kConstrainedCacheLimit, MAX_CACHE_OBJECTS); #else unsigned long cacheSize = options["cachesize"].number ? (unsigned long)(options["cachesize"].number) * 1024 * 1024 : HUGE_CACHE_LIMIT; textureWCache.Resize(cacheSize,MAX_CACHE_OBJECTS); #endif #endif return; } JMusic * ResourceManagerImpl::ssLoadMusic(const char *fileName) { string file = musicFile(fileName); if (!file.size()) return NULL; return JSoundSystem::GetInstance()->LoadMusic(file.c_str()); } void ResourceManagerImpl::Refresh() { //Really easy cache relinking. ReloadWFonts(); sampleWCache.Refresh(); textureWCache.Refresh(); psiWCache.Refresh(); //Check for card images in theme. bThemedCards = false; if (!options[Options::ACTIVE_THEME].isDefault()) { char buf[512]; sprintf(buf, "themes/%s/sets", options[Options::ACTIVE_THEME].str.c_str()); if (dirOK(buf)) bThemedCards = true; } } //WCache template bool WCache::RemoveOldest() { typename map::iterator oldest = cache.end(); for (typename map::iterator it = cache.begin(); it != cache.end(); ++it) { if (it->second && !it->second->isLocked() && (oldest == cache.end() || it->second->lastTime < oldest->second->lastTime)) oldest = it; } if (oldest != cache.end() && oldest->second && !oldest->second->isLocked()) { #ifdef DEBUG_CACHE std::ostringstream stream; stream << "erasing from cache: " << oldest->second->mFilename << " " << oldest->first; LOG(stream.str().c_str()); #endif Delete(oldest->second); cache.erase(oldest); return true; } return false; } template void WCache::ClearUnlocked() { typename map::iterator it, next; for (it = cache.begin(); it != cache.end(); it = next) { next = it; next++; if (it->second && !it->second->isLocked()) { Delete(it->second); cache.erase(it); } else if (!it->second) { cache.erase(it); } } } template void WCache::Resize(unsigned long size, int items) { maxCacheSize = size; DebugTrace(typeid(cacheActual).name() << " cache resized to " << size << " bytes"); #ifdef DEBUG_CACHE std::ostringstream stream; stream << "Max cache limit resized to " << size << ", items limit reset to " << items; LOG(stream.str().c_str()); #endif if (items > MAX_CACHE_OBJECTS || items < 1) maxCached = MAX_CACHE_OBJECTS; else maxCached = items; } template cacheItem* WCache::AttemptNew(const string& filename, int submode) { cacheItem* item = NEW cacheItem; if (!item) { mError = CACHE_ERROR_BAD_ALLOC; return NULL; } mError = CACHE_ERROR_NONE; if (!item->Attempt(filename, submode, mError) || !item->isGood()) { //No such file. Fail if (mError == CACHE_ERROR_404) { DebugTrace("AttemptNew failed to load. Deleting cache item " << ToHex(item)); SAFE_DELETE(item); return NULL; } else { DebugTrace("AttemptNew failed to load (not a 404 error). Deleting cache item " << ToHex(item)); SAFE_DELETE(item); mError = CACHE_ERROR_BAD; return NULL; } } //Success! Enforce cache limits, then return. mError = CACHE_ERROR_NONE; return item; } template cacheItem* WCache::Retrieve(int id, const string& filename, int style, int submode) { //Check cache. mError = CACHE_ERROR_NONE; //Reset error status. cacheItem* tc = Get(id, filename, style, submode); //Retrieve resource only works on permanent items. if (style == RETRIEVE_RESOURCE && tc && !tc->isPermanent()) { mError = CACHE_ERROR_NOT_MANAGED; return NULL; } //Perform lock or unlock on entry. if (tc) { switch (style) { case RETRIEVE_LOCK: tc->lock(); break; case RETRIEVE_UNLOCK: tc->unlock(); break; case RETRIEVE_MANAGE: if (!tc->isPermanent()) { //Unlink the managed resource from the cache. UnlinkCache(tc); //Post it in managed resources. managed[makeID(id, filename, submode)] = tc; tc->deadbolt(); } break; } } //Resource exists! if (tc) { if (tc->isGood()) { tc->hit(); return tc; //Everything fine. } //Something went wrong. RemoveItem(tc); mError = CACHE_ERROR_BAD; } //Record managed failure. Cache failure is recorded in Get(). if ((style == RETRIEVE_MANAGE || style == RETRIEVE_RESOURCE) && mError == CACHE_ERROR_404) managed[makeID(id, filename, submode)] = NULL; return NULL; } template int WCache::makeID(int id, const string& filename, int submode) { int mId = id; if (!mId) { mId = ids[filename]; if (!mId) { mId = idCounter++; ids[filename] = mId; } } if (submode & TEXTURE_SUB_THUMB) mId += THUMBNAILS_OFFSET; return mId; } template cacheItem* WCache::Get(int id, const string& filename, int style, int submode) { typename map::iterator it; int lookup = makeID(id, filename, submode); //Check for managed resources first. Always it = managed.find(lookup); //Something is managed. if (it != managed.end()) { return it->second; //A hit. } //Not managed, so look in cache. if (style != RETRIEVE_MANAGE) { boost::mutex::scoped_lock lock(sCacheMutex); //DebugTrace("Cache lock acquired, looking up index " << lookup); it = cache.find(lookup); //Well, we've found something... if (it != cache.end()) { if (!it->second) { mError = CACHE_ERROR_404; //DebugTrace("cache hit, no item??"); //assert(false); } return it->second; //A hit, or maybe a miss. } } // not hit in the cache, respect the RETRIEVE_EXISTING flag if present if (style == RETRIEVE_EXISTING) { return NULL; } // no hit in cache, clear space before attempting to load a new one // note: Cleanup() should ONLY be ever called on the main (UI) thread! Cleanup(); // check if we're doing a card lookup if (submode & TEXTURE_SUB_CARD) { // processing a cache miss, return a generic card & queue up an async read // side note: using a string reference here to a global predefined string, as basic_string is not thread safe for allocations! const std::string& cardPath = (submode & TEXTURE_SUB_THUMB) ? kGenericThumbCard : kGenericCard; int genericCardId = makeID(0, cardPath, CACHE_NORMAL); it = managed.find(genericCardId); assert(it != managed.end()); CacheEngine::Instance()->QueueRequest(filename, submode, lookup); return it->second; } //Space in cache, make new texture return LoadIntoCache(lookup, filename, submode, style); } template cacheItem* WCache::LoadIntoCache(int id, const string& filename, int submode, int style) { // note: my original implementation only had one lock (the cache mutex lock) - I eventually // added this second one, as locking at the Get() call means that the main thread is blocked on doing a simple cache // check. If you're hitting the system hard (like, paging up in the deck editor which forces 7 cards to load simultaneously), // we'd block the UI thread for a long period at this point. So I moved the cache mutex to lock specifically only attempts to touch // the shared cache container, and this separate lock was added to insulate us against thread safety issues in JGE. In particular, // JFileSystem is particularly unsafe, as it assumes that we have only one zip loaded at a time... rather than add mutexes in JGE, // I've kept it local to here. boost::mutex::scoped_lock functionLock(sLoadFunctionMutex); cacheItem* item = AttemptNew(filename, submode); //assert(item); if (style == RETRIEVE_MANAGE) { if (mError == CACHE_ERROR_404 || item) { managed[id] = item; //Record a hit or miss. } if (item) { item->deadbolt(); //Make permanent. } } else { if (mError == CACHE_ERROR_404 || item) { boost::mutex::scoped_lock lock(sCacheMutex); cache[id] = item; DebugTrace("inserted item ptr " << ToHex(item) << " at index " << id); } } if (item == NULL) { DebugTrace("Can't locate "); if (submode & TEXTURE_SUB_THUMB) { DebugTrace("thumbnail "); } DebugTrace(filename); return NULL; //Failure } //Succeeded in making a new item. unsigned long isize = item->size(); totalSize += isize; mError = CACHE_ERROR_NONE; if (style != RETRIEVE_MANAGE) { cacheItems++; cacheSize += isize; } #ifdef DEBUG_CACHE std::ostringstream stream; stream << "Cache insert: " << filename << " " << id << ", cacheItem count: " << cacheItems << ", cacheSize is now: " << cacheSize; LOG(stream.str().c_str()); #endif return item; } template void WCache::Refresh() { typename map::iterator it; ClearUnlocked(); for (it = cache.begin(); it != cache.end(); it++) { if (it->second) { it->second->Refresh(); } } for (it = managed.begin(); it != managed.end(); it++) { if (it->second) { it->second->Refresh(); } } } template WCache::WCache() { cacheSize = 0; totalSize = 0; maxCacheSize = TEXTURES_CACHE_MINSIZE; maxCached = MAX_CACHE_OBJECTS; cacheItems = 0; mError = CACHE_ERROR_NONE; } template WCache::~WCache() { typename map::iterator it; //Delete from cache & managed for (it = cache.begin(); it != cache.end(); it++) { SAFE_DELETE(it->second); } for (it = managed.begin(); it != managed.end(); it++) { SAFE_DELETE(it->second); } } template bool WCache::Cleanup() { bool result = true; // this looks redundant, but the idea is, don't grab the mutex if there's no work to do if (RequiresOldItemCleanup()) { boost::mutex::scoped_lock lock(sCacheMutex); while (RequiresOldItemCleanup()) { if (!RemoveOldest()) { result = false; break; } } } return result; } bool WCacheSort::operator()(const WResource * l, const WResource * r) { if (!l || !r) return false; return (l->lastTime < r->lastTime); } template unsigned int WCache::Flatten() { vector items; unsigned int oldest = 0; unsigned int lastSet = 0; if (!cache.size()) return 0; for (typename map::iterator it = cache.begin(); it != cache.end(); ++it) { if (!it->second) continue; items.push_back(it->second); } sort(items.begin(), items.end(), WCacheSort()); for (typename vector::iterator it = items.begin(); it != items.end(); ++it) { assert((*it) && (*it)->lastTime > lastSet); lastSet = (*it)->lastTime; (*it)->lastTime = ++oldest; } return oldest + 1; } template bool WCache::RemoveMiss(int id) { typename map::iterator it = cache.end(); for (it = cache.begin(); it != cache.end(); it++) { if ((id == 0 || it->first == id) && it->second == NULL) break; } if (it != cache.end()) { cache.erase(it); return true; } return false; } template bool WCache::RemoveItem(cacheItem * item, bool force) { typename map::iterator it; if (item == NULL) return false; //Use RemoveMiss to remove cache misses, not this. for (it = cache.begin(); it != cache.end(); it++) { if (it->second == item) break; } if (it != cache.end() && it->second && (force || !it->second->isLocked())) { Delete(it->second); cache.erase(it); return true; } return false; } template bool WCache::UnlinkCache(cacheItem * item) { typename map::iterator it = cache.end(); if (item == NULL) return false; //Use RemoveMiss to remove cache misses, not this. for (it = cache.begin(); it != cache.end(); it++) { if (it->second == item) break; } if (it != cache.end() && it->second) { it->second = NULL; unsigned long isize = item->size(); cacheSize -= isize; cacheItems--; cache.erase(it); return true; } return false; } template bool WCache::Delete(cacheItem * item) { if (!item) return false; unsigned long isize = item->size(); totalSize -= isize; cacheSize -= isize; #ifdef DEBUG_CACHE if(cacheItems == 0) DebugTrace("cacheItems out of sync."); #endif cacheItems--; DebugTrace("Deleting cache item " << ToHex(item) << ", cache reduced by " << isize << " bytes"); SAFE_DELETE(item); return true; } template bool WCache::Release(cacheActual* actual) { if (!actual) return false; typename map::iterator it; for (it = cache.begin(); it != cache.end(); it++) { if (it->second && it->second->compare(actual)) break; } if (it == cache.end()) return false; //Not here, can't release. if (it->second) { it->second->unlock(); //Release one lock. if (it->second->isLocked()) return true; //Still locked, won't delete, not technically a failure. } //Released! Delete(it->second); cache.erase(it); return true; }