diff --git a/projects/mtg/include/CacheEngine.h b/projects/mtg/include/CacheEngine.h index f150f65e2..f604a74fd 100644 --- a/projects/mtg/include/CacheEngine.h +++ b/projects/mtg/include/CacheEngine.h @@ -90,6 +90,7 @@ public: UnthreadedCardRetriever(WCache& inCache) : CardRetrieverBase(inCache) { + DebugTrace("Unthreaded version"); } virtual ~UnthreadedCardRetriever() @@ -115,6 +116,7 @@ public: ThreadedCardRetriever(WCache& inCache) : CardRetrieverBase(inCache), mProcessing(true) { + DebugTrace("Threaded Version"); mWorkerThread = boost::thread(ThreadProc, this); } @@ -186,7 +188,9 @@ protected: // not sure this is necessary, adding it to potentially prevent SIGHUP on the psp // rumour has it that if a worker thread doesn't allow the main thread a chance to run, it can hang the unit +#ifdef PSPENV boost::this_thread::sleep(boost::posix_time::milliseconds(10)); +#endif } boost::this_thread::sleep(kIdleTime); diff --git a/projects/mtg/include/WResourceManager.h b/projects/mtg/include/WResourceManager.h index 01ff1c722..4348c40c1 100644 --- a/projects/mtg/include/WResourceManager.h +++ b/projects/mtg/include/WResourceManager.h @@ -9,6 +9,10 @@ #include "WCachedResource.h" #include "WFont.h" +#include + +#include "Threading.h" + #define HUGE_CACHE_LIMIT 20000000 // Size of the cache for Windows and Linux #define SAMPLES_CACHE_SIZE 1500000 // Size in bytes of the cached samples #define PSI_CACHE_SIZE 500000 // Size in bytes of the cached particles @@ -56,7 +60,7 @@ enum ENUM_RETRIEVE_STYLE enum ENUM_CACHE_SUBTYPE { CACHE_NORMAL = (1<<0), //Use default values. Not really a flag. - CACHE_EXISTING = (1<<1), //Retrieve it only if it already exists + //CACHE_EXISTING = (1<<1), //Retrieve it only if it already exists //Because these bits only modify how a cached resource's Attempt() is called, //We can use them over and over for each resource type. @@ -89,12 +93,14 @@ class WCache { public: friend class WResourceManager; + friend class ThreadedCardRetriever; + friend class UnthreadedCardRetriever; WCache(); ~WCache(); cacheItem* Retrieve(int id, const string& filename, int style = RETRIEVE_NORMAL, int submode = CACHE_NORMAL); //Primary interface function. - bool Release(cacheActual* actual); //Releases an item, and deletes it if unlocked. + bool Release(cacheActual* actual); //Releases an item, and deletes it if unlocked. bool RemoveMiss(int id=0); //Removes a cache miss. bool RemoveOldest(); //Remove oldest unlocked item. bool Cleanup(); //Repeats RemoveOldest() until cache fits in size limits @@ -102,18 +108,63 @@ public: void Refresh(); //Refreshes all cache items. unsigned int Flatten(); //Ensures that the times don't loop. Returns new lastTime. void Resize(unsigned long size, int items); //Sets new limits, then enforces them. Lock safe, so not a "hard limit". + protected: - bool RemoveItem(cacheItem * item, bool force = true); //Removes an item, deleting it. if(force), ignores locks / permanent - bool UnlinkCache(cacheItem * item); //Removes an item from our cache, does not delete it. Use with care. - bool Delete(cacheItem * item); //SAFE_DELETE and garbage collect. If maxCached == 0, nullify first. (This means you have to free that cacheActual later!) + + cacheItem* LoadIntoCache(int id, const string& filename, int submode, int style = RETRIEVE_NORMAL); + + /* + ** Attempts a new cache item, progressively clearing cache if it fails. + */ + cacheItem* AttemptNew(const string& filename, int submode); + + bool RemoveItem(cacheItem* item, bool force = true); //Removes an item, deleting it. if(force), ignores locks / permanent + bool UnlinkCache(cacheItem* item); //Removes an item from our cache, does not delete it. Use with care. + bool Delete(cacheItem* item); //SAFE_DELETE and garbage collect. If maxCached == 0, nullify first. (This means you have to free that cacheActual later!) cacheItem* Get(int id, const string& filename, int style = RETRIEVE_NORMAL, int submode = CACHE_NORMAL); //Subordinate to Retrieve. - cacheItem* AttemptNew(const string& filename, int submode); //Attempts a new cache item, progressively clearing cache if it fails. - int makeID(int id, const string& filename, int submode); //Makes an ID appropriate to the submode. + int makeID(int id, const string& filename, int submode); //Makes an ID appropriate to the submode. - map ids; - map cache; - map managed; //Cache can be arbitrarily large, so managed items are seperate. + inline bool RequiresMissCleanup() + { + return (cacheItems < cache.size() /*&& cache.size() - cacheItems > MAX_CACHE_MISSES*/); + } + + inline bool RequiresOldItemCleanup() + { + if (cacheItems > MAX_CACHE_OBJECTS || cacheItems > maxCached || cacheSize > maxCacheSize) + { + std::ostringstream stream; + if (cacheItems > MAX_CACHE_OBJECTS) + { + stream << "CacheItems exceeded 300, cacheItems = " << cacheItems; + } + else if (cacheItems > maxCached) + { + stream << "CacheItems (" << cacheItems << ") exceeded arbitrary limit of " << maxCached; + } + else + { + stream << "Cache size (" << cacheSize << ") exceeded max value of " << maxCacheSize; + } + + LOG(stream.str().c_str()); + return true; + } + +#if PSPENV + if (ramAvailableLineareMax() < MIN_LINEAR_RAM) + { + DebugTrace("Memory below minimum threshold!!"); + return true; + } +#endif + return false; + } + + map ids; + map cache; + map managed; //Cache can be arbitrarily large, so managed items are separate. unsigned long totalSize; unsigned long cacheSize; @@ -123,6 +174,11 @@ protected: unsigned int cacheItems; int mError; + + // mutex meant for the cache map + boost::mutex mCacheMutex; + // mutex meant to protect against unthread-safe calls into JFileSystem, etc. + boost::mutex mLoadFunctionMutex; }; struct WManagedQuad @@ -152,6 +208,8 @@ public: virtual ~WResourceManager(); + bool IsThreaded(); + void Unmiss(string filename); JQuadPtr RetrieveCard(MTGCard * card, int style = RETRIEVE_NORMAL,int submode = CACHE_NORMAL); JSample * RetrieveSample(const string& filename, int style = RETRIEVE_NORMAL, int submode = CACHE_NORMAL); @@ -224,8 +282,8 @@ private: */ WResourceManager(); - bool bThemedCards; //Does the theme have a "sets" directory for overwriting cards? - void FlattenTimes(); //To prevent bad cache timing on int overflow + bool bThemedCards; //Does the theme have a "sets" directory for overwriting cards? + void FlattenTimes(); //To prevent bad cache timing on int overflow //For cached stuff WCache textureWCache; diff --git a/projects/mtg/src/CardGui.cpp b/projects/mtg/src/CardGui.cpp index 9f1cde3a0..2d8dc221c 100644 --- a/projects/mtg/src/CardGui.cpp +++ b/projects/mtg/src/CardGui.cpp @@ -776,15 +776,7 @@ void CardGui::RenderBig(MTGCard* card, const Pos& pos) return; } - //No card found, attempt to render the thumbnail instead (better than nothing, even if it gets super stretched) - JQuadPtr q; - if ((q = WResourceManager::Instance()->RetrieveCard(card, CACHE_THUMB))) - { - float scale = pos.actZ * 250 / q->mHeight; - q->SetColor(ARGB(255,255,255,255)); - renderer->RenderQuad(q.get(), x, pos.actY, pos.actT, scale, scale); - return; - } + DebugTrace("Unable to fetch image: " << card->getImageName()); // If we come here, we do not have the picture. AlternateRender(card, pos); diff --git a/projects/mtg/src/GameApp.cpp b/projects/mtg/src/GameApp.cpp index 6cadd2543..2c0743857 100644 --- a/projects/mtg/src/GameApp.cpp +++ b/projects/mtg/src/GameApp.cpp @@ -91,6 +91,7 @@ void GameApp::Create() //_CrtSetBreakAlloc(368); LOG("starting Game"); + WResourceManager::Instance()->ResetCacheLimits(); //Find the Res folder wagic::ifstream mfile("Res.txt"); string resPath; diff --git a/projects/mtg/src/GameStateDeckViewer.cpp b/projects/mtg/src/GameStateDeckViewer.cpp index f858def24..d7909b320 100644 --- a/projects/mtg/src/GameStateDeckViewer.cpp +++ b/projects/mtg/src/GameStateDeckViewer.cpp @@ -21,8 +21,7 @@ #include "SimpleMenu.h" #include "utils.h" -// This is pending a change by Wil regarding graphics threads -#define GRAPHICS_NO_THREADING + //!! helper function; this is probably handled somewhere in the code already. // If not, should be placed in general library @@ -1314,63 +1313,64 @@ void GameStateDeckViewer::renderCard(int id, float rotation) int alpha = (int) (255 * (scale + 1.0 - max_scale)); if (!card) return; - -#ifdef GRAPHICS_NO_THREADING - - JQuadPtr backQuad = WResourceManager::Instance()->GetQuad("back"); - JQuadPtr quad; - - int cacheError = CACHE_ERROR_NONE; - - if (!options[Options::DISABLECARDS].number) + + if (!WResourceManager::Instance()->IsThreaded()) { - quad = WResourceManager::Instance()->RetrieveCard(card, RETRIEVE_EXISTING); - cacheError = WResourceManager::Instance()->RetrieveError(); - if (!quad.get() && cacheError != CACHE_ERROR_404) + JQuadPtr backQuad = WResourceManager::Instance()->GetQuad("back"); + JQuadPtr quad; + + int cacheError = CACHE_ERROR_NONE; + + if (!options[Options::DISABLECARDS].number) { - if (last_user_activity > (abs(2 - id) + 1) * NO_USER_ACTIVITY_SHOWCARD_DELAY) - quad = WResourceManager::Instance()->RetrieveCard(card); - else + quad = WResourceManager::Instance()->RetrieveCard(card, RETRIEVE_EXISTING); + cacheError = WResourceManager::Instance()->RetrieveError(); + if (!quad.get() && cacheError != CACHE_ERROR_404) { - quad = backQuad; + if (last_user_activity > (abs(2 - id) + 1) * NO_USER_ACTIVITY_SHOWCARD_DELAY) + quad = WResourceManager::Instance()->RetrieveCard(card); + else + { + quad = backQuad; + } } } - } - if (quad.get()) - { - if (quad == backQuad) + if (quad.get()) { - quad->SetColor(ARGB(255,255,255,255)); - float _scale = scale * (285 / quad->mHeight); - JRenderer::GetInstance()->RenderQuad(quad.get(), x, y, 0.0f, _scale, _scale); + if (quad == backQuad) + { + quad->SetColor(ARGB(255,255,255,255)); + float _scale = scale * (285 / quad->mHeight); + JRenderer::GetInstance()->RenderQuad(quad.get(), x, y, 0.0f, _scale, _scale); + } + else + { + Pos pos = Pos(x, y, scale * 285 / 250, 0.0, 255); + CardGui::DrawCard(card, pos); + } } else { Pos pos = Pos(x, y, scale * 285 / 250, 0.0, 255); - CardGui::DrawCard(card, pos); + CardGui::DrawCard(card, pos, DrawMode::kText); + if (!options[Options::DISABLECARDS].number) quad = WResourceManager::Instance()->RetrieveCard(card, CACHE_THUMB); + if (quad.get()) + { + float _scale = 285 * scale / quad->mHeight; + quad->SetColor(ARGB(40,255,255,255)); + JRenderer::GetInstance()->RenderQuad(quad.get(), x, y, 0, _scale, _scale); + } } } else { + int mode = !options[Options::DISABLECARDS].number ? DrawMode::kNormal : DrawMode::kText; + Pos pos = Pos(x, y, scale * 285 / 250, 0.0, 255); - CardGui::DrawCard(card, pos, DrawMode::kText); - if (!options[Options::DISABLECARDS].number) quad = WResourceManager::Instance()->RetrieveCard(card, CACHE_THUMB); - if (quad.get()) - { - float _scale = 285 * scale / quad->mHeight; - quad->SetColor(ARGB(40,255,255,255)); - JRenderer::GetInstance()->RenderQuad(quad.get(), x, y, 0, _scale, _scale); - } + CardGui::DrawCard(card, pos, mode); } -#else - int mode = !options[Options::DISABLECARDS].number ? DrawMode::kNormal : DrawMode::kText; - - Pos pos = Pos(x, y, scale * 285 / 250, 0.0, 255); - CardGui::DrawCard(card, pos, mode); -#endif - int quadAlpha = alpha; if (!displayed_deck->count(card)) quadAlpha /= 2; quadAlpha = 255 - quadAlpha; @@ -1421,6 +1421,19 @@ void GameStateDeckViewer::Render() order[2] = 1; } + // even though we want to draw the cards in a particular z order for layering, we want to prefetch them + // in a different order, ie the center card should appear first, then the adjacent ones + if (WResourceManager::Instance()->IsThreaded()) + { + WResourceManager::Instance()->RetrieveCard(cardIndex[0]); + WResourceManager::Instance()->RetrieveCard(cardIndex[3]); + WResourceManager::Instance()->RetrieveCard(cardIndex[4]); + WResourceManager::Instance()->RetrieveCard(cardIndex[2]); + WResourceManager::Instance()->RetrieveCard(cardIndex[5]); + WResourceManager::Instance()->RetrieveCard(cardIndex[1]); + WResourceManager::Instance()->RetrieveCard(cardIndex[6]); + } + renderCard(6, mRotation); renderCard(5, mRotation); renderCard(4, mRotation); diff --git a/projects/mtg/src/WDataSrc.cpp b/projects/mtg/src/WDataSrc.cpp index 5c6d5347b..7f693fc67 100644 --- a/projects/mtg/src/WDataSrc.cpp +++ b/projects/mtg/src/WDataSrc.cpp @@ -56,13 +56,14 @@ WSrcCards::WSrcCards(float delay) JQuadPtr WSrcCards::getImage(int offset) { -#if defined WIN32 || defined LINUX //Loading delay only on PSP. -#else - if (mDelay && mLastInput < mDelay) - { - return WResourceManager::Instance()->RetrieveCard(getCard(offset), RETRIEVE_EXISTING); - } -#endif + if (!WResourceManager::Instance()->IsThreaded()) + { + if (mDelay && mLastInput < mDelay) + { + return WResourceManager::Instance()->RetrieveCard(getCard(offset), RETRIEVE_EXISTING); + } + } + return WResourceManager::Instance()->RetrieveCard(getCard(offset)); } diff --git a/projects/mtg/src/WResourceManager.cpp b/projects/mtg/src/WResourceManager.cpp index 9a57616bd..5060f033d 100644 --- a/projects/mtg/src/WResourceManager.cpp +++ b/projects/mtg/src/WResourceManager.cpp @@ -1,9 +1,7 @@ #include "PrecompiledHeader.h" -#include - -#include "utils.h" #include "GameOptions.h" +#include "CacheEngine.h" #include "WResourceManager.h" #include "StyleManager.h" @@ -12,9 +10,8 @@ #include #endif #include "WFont.h" -#include -//#define FORCE_LOW_CACHE_MEMORY +#define FORCE_LOW_CACHE_MEMORY const unsigned int kConstrainedCacheLimit = 8 * 1024 * 1024; extern bool neofont; @@ -25,6 +22,9 @@ namespace const std::string kExtension_png(".png"); const std::string kExtension_gbk(".gbk"); const std::string kExtension_font(".font"); + + const std::string kGenericCard("back.jpg"); + const std::string kGenericThumbCard("back_thumb.jpg"); } WResourceManager* WResourceManager::sInstance = NULL; @@ -48,10 +48,9 @@ void WResourceManager::DebugRender() { JRenderer* renderer = JRenderer::GetInstance(); WFont * font = WResourceManager::Instance()->GetWFont(Fonts::MAIN_FONT); - font->SetColor(ARGB(255,255,255,255)); - 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)); @@ -70,11 +69,11 @@ void WResourceManager::DebugRender() textureWCache.cacheSize, textureWCache.maxCacheSize, man); font->DrawString(buf, 10, 5); -#if defined (WIN32) || defined (LINUX) || defined (IOS) -#else - int maxLinear = ramAvailableLineareMax(); - int ram = ramAvailable(); - sprintf(buf, "Ram : linear max: %i - total : %i\n", maxLinear, ram); +#if PSPENV + //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 @@ -180,6 +179,14 @@ WResourceManager::WResourceManager() lastError = CACHE_ERROR_NONE; bThemedCards = false; + + LOG("Calling CacheEngine::Create"); + +#ifdef PSPENV + CacheEngine::Create(textureWCache); +#else + CacheEngine::Create(textureWCache); +#endif } WResourceManager::~WResourceManager() @@ -187,9 +194,15 @@ WResourceManager::~WResourceManager() LOG("==Destroying WResourceManager=="); RemoveWFonts(); + CacheEngine::Terminate(); LOG("==Successfully Destroyed WResourceManager=="); } +bool WResourceManager::IsThreaded() +{ + return CacheEngine::IsThreaded(); +} + JQuadPtr WResourceManager::RetrieveCard(MTGCard * card, int style, int submode) { //Cards are never, ever resource managed, so just check cache. @@ -197,9 +210,7 @@ JQuadPtr WResourceManager::RetrieveCard(MTGCard * card, int style, int submode) submode = submode | TEXTURE_SUB_CARD; - string filename = setlist[card->setId]; - filename += "/"; - string filename1 = filename + card->getImageName(); + string filename = setlist[card->setId] + "/" + card->getImageName(); int id = card->getMTGId(); //Aliases. @@ -209,40 +220,8 @@ JQuadPtr WResourceManager::RetrieveCard(MTGCard * card, int style, int submode) style = RETRIEVE_NORMAL; } - //Hack to allow either ID or card name as a filename for a given card. - // When missing the first attempt (for example [id].jpg), the cache assigns a "404" to the card's image cache, - // Preventing us to try a second time with [name].jpg. - //To bypass this, we first check if the card was ever marked as "null". If not, it means it's the first time we're looking for it - // In that case, we "unmiss" it after trying the [id].jpg, in order to give a chance to the [name.jpg] - bool canUnmiss = false; - { - JQuadPtr tempQuad = RetrieveQuad(filename1, 0, 0, 0, 0, "", RETRIEVE_EXISTING, submode | TEXTURE_SUB_5551, id); - lastError = textureWCache.mError; - if (!tempQuad && lastError != CACHE_ERROR_404) - { - canUnmiss = true; - } - } - JQuadPtr jq = RetrieveQuad(filename1, 0, 0, 0, 0, "", style, submode | TEXTURE_SUB_5551, id); - if (!jq) - { - if (canUnmiss) - { - int mId = id; - //To differentiate between cached thumbnails and the real thing. - if (submode & TEXTURE_SUB_THUMB) - { - if (mId < 0) - mId -= THUMBNAILS_OFFSET; - else - mId += THUMBNAILS_OFFSET; - } - textureWCache.RemoveMiss(mId); - } - filename1 = filename + card->data->getName() + ".jpg"; - jq = RetrieveQuad(filename1, 0, 0, 0, 0, "", style, submode | TEXTURE_SUB_5551, id); + JQuadPtr jq = RetrieveQuad(filename, 0, 0, 0, 0, "", style, submode | TEXTURE_SUB_5551, id); - } lastError = textureWCache.mError; if (jq) { @@ -353,11 +332,7 @@ JQuadPtr WResourceManager::RetrieveQuad(const string& filename, float offX, floa if (!resname.size()) resname = filename; //No quad, but we have a managed texture for this! - WCachedTexture * jtex = NULL; - if (style == RETRIEVE_MANAGE || style == RETRIEVE_EXISTING) - jtex = textureWCache.Retrieve(id, filename, style, submode); - else - jtex = textureWCache.Retrieve(id, filename, RETRIEVE_NORMAL, submode); + WCachedTexture* jtex = textureWCache.Retrieve(id, filename, style, submode); lastError = textureWCache.mError; @@ -939,13 +914,15 @@ void WResourceManager::ResetCacheLimits() textureWCache.Resize(HUGE_CACHE_LIMIT,MAX_CACHE_OBJECTS); #endif #else - unsigned int ram = ramAvailable(); - unsigned int myNewSize = ram - OPERATIONAL_SIZE + textureWCache.totalSize; + static unsigned int ram(ramAvailable() / 2); + unsigned int myNewSize = ram - OPERATIONAL_SIZE; if (myNewSize < TEXTURES_CACHE_MINSIZE) { - fprintf(stderr, "Error, Not enough RAM for Cache: %i - total Ram: %i\n", myNewSize, ram); + DebugTrace( "Error, Not enough RAM for Cache: " << myNewSize << " - total Ram: " << ram); } - textureWCache.Resize(myNewSize, MAX_CACHE_OBJECTS); + textureWCache.Resize(MIN(myNewSize, HUGE_CACHE_LIMIT), MAX_CACHE_OBJECTS); + + DebugTrace("Texture cache resized to " << myNewSize); #endif return; } @@ -990,6 +967,11 @@ bool WCache::RemoveOldest() 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; @@ -1024,6 +1006,12 @@ void WCache::Resize(unsigned long size, int items) { maxCacheSize = size; +#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 @@ -1033,12 +1021,6 @@ void WCache::Resize(unsigned long size, int items) template cacheItem* WCache::AttemptNew(const string& filename, int submode) { - if (submode & CACHE_EXISTING) - { //Should never get this far. - mError = CACHE_ERROR_NOT_CACHED; - return NULL; - } - cacheItem* item = NEW cacheItem; if (!item) { @@ -1053,25 +1035,22 @@ cacheItem* WCache::AttemptNew(const string& filename, in //No such file. Fail if (mError == CACHE_ERROR_404) { + DebugTrace("AttemptNew failed to load. Deleting cache item " << ToHex(item)); SAFE_DELETE(item); return NULL; } - //Probably not enough memory: cleanup and try again - Cleanup(); - mError = CACHE_ERROR_NONE; - if (!item->Attempt(filename, submode, mError) || !item->isGood()) + else { + DebugTrace("AttemptNew failed to load (not a 404 error). Deleting cache item " << ToHex(item)); SAFE_DELETE(item); mError = CACHE_ERROR_BAD; - return NULL; + return NULL; } } //Success! Enforce cache limits, then return. mError = CACHE_ERROR_NONE; - item->lock(); - Cleanup(); - item->unlock(); + return item; } @@ -1079,13 +1058,8 @@ template cacheItem* WCache::Retrieve(int id, const string& filename, int style, int submode) { //Check cache. - cacheItem * tc = NULL; mError = CACHE_ERROR_NONE; //Reset error status. - - if (style == RETRIEVE_EXISTING || style == RETRIEVE_RESOURCE) - tc = Get(id, filename, style, submode | CACHE_EXISTING); - else - tc = Get(id, filename, style, submode); + cacheItem* tc = Get(id, filename, style, submode); //Retrieve resource only works on permanent items. if (style == RETRIEVE_RESOURCE && tc && !tc->isPermanent()) @@ -1153,14 +1127,9 @@ int WCache::makeID(int id, const string& filename, int s } } - //To differentiate between cached thumbnails and the real thing. if (submode & TEXTURE_SUB_THUMB) - { - if (mId < 0) - mId -= THUMBNAILS_OFFSET; - else - mId += THUMBNAILS_OFFSET; - } + mId += THUMBNAILS_OFFSET; + return mId; } @@ -1179,38 +1148,73 @@ cacheItem* WCache::Get(int id, const string& filename, i return it->second; //A hit. } - //Failed to find managed resource and won't create one. Record a miss. - if (style == RETRIEVE_RESOURCE) - { - managed[lookup] = NULL; - return NULL; - } - //Not managed, so look in cache. if (style != RETRIEVE_MANAGE) { + boost::mutex::scoped_lock lock(mCacheMutex); + //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; + if (!it->second) + { + mError = CACHE_ERROR_404; + DebugTrace("cache hit, no item??"); + //assert(false); + } return it->second; //A hit, or maybe a miss. } } - //Didn't exist in cache. - if (submode & CACHE_EXISTING) + // not hit in the cache, respect the RETRIEVE_EXISTING flag if present + if (style == RETRIEVE_EXISTING) { - mError = CACHE_ERROR_NOT_CACHED; return NULL; } - //Space in cache, make new texture - cacheItem * item = AttemptNew(filename, submode); + // 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(mLoadFunctionMutex); + cacheItem* item = AttemptNew(filename, submode); + //assert(item); if (style == RETRIEVE_MANAGE) { - if (mError == CACHE_ERROR_404 || item) managed[lookup] = item; //Record a hit or miss. + if (mError == CACHE_ERROR_404 || item) + { + managed[id] = item; //Record a hit or miss. + } if (item) { item->deadbolt(); //Make permanent. @@ -1218,10 +1222,25 @@ cacheItem* WCache::Get(int id, const string& filename, i } else { - if (mError == CACHE_ERROR_404 || item) cache[lookup] = item; + if (mError == CACHE_ERROR_404 || item) + { + boost::mutex::scoped_lock lock(mCacheMutex); + cache[id] = item; + DebugTrace("inserted item ptr " << ToHex(item) << " at index " << id); + } } - if (!item) return NULL; //Failure + 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(); @@ -1234,6 +1253,12 @@ cacheItem* WCache::Get(int id, const string& filename, i 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; } @@ -1292,24 +1317,31 @@ WCache::~WCache() template bool WCache::Cleanup() { - while (cacheItems < cache.size() && cache.size() - cacheItems > MAX_CACHE_MISSES) + bool result = true; + // this looks redundant, but the idea is, don't grab the mutex if there's no work to do + if (RequiresMissCleanup()) { - RemoveMiss(); - } - - while (cacheItems > MAX_CACHE_OBJECTS || cacheItems > maxCached || cacheSize > maxCacheSize -#if defined WIN32 || defined LINUX || defined (IOS) -#else - || ramAvailableLineareMax() < MIN_LINEAR_RAM -#endif - ) - { - if (!RemoveOldest()) + boost::mutex::scoped_lock lock(mCacheMutex); + while (RequiresMissCleanup()) { - return false; + RemoveMiss(); } } - return true; + + if (RequiresOldItemCleanup()) + { + boost::mutex::scoped_lock lock(mCacheMutex); + while (RequiresOldItemCleanup()) + { + if (!RemoveOldest()) + { + result = false; + break; + } + } + } + + return result; } bool WCacheSort::operator()(const WResource * l, const WResource * r) @@ -1425,6 +1457,7 @@ bool WCache::Delete(cacheItem * item) cacheItems--; + DebugTrace("Deleting cache item " << ToHex(item)); SAFE_DELETE(item); return true; } diff --git a/projects/mtg/src/utils.cpp b/projects/mtg/src/utils.cpp index 064d2bc14..555d8168a 100644 --- a/projects/mtg/src/utils.cpp +++ b/projects/mtg/src/utils.cpp @@ -7,6 +7,10 @@ #include "WFont.h" #include +#ifdef PSPENV +#include "pspsdk.h" +#endif + namespace wagic { #ifdef TRACK_FILE_USAGE_STATS @@ -162,6 +166,10 @@ u32 ramAvailableLineareMax(void) size = 0; sizeblock = RAM_BLOCK; +#ifdef PSPENV + int disableInterrupts = pspSdkDisableInterrupts(); +#endif + // Check loop while (sizeblock) { @@ -184,6 +192,10 @@ u32 ramAvailableLineareMax(void) free(ram); } +#ifdef PSPENV + pspSdkEnableInterrupts(disableInterrupts); +#endif + return size; } @@ -197,6 +209,10 @@ u32 ramAvailable(void) size = 0; count = 0; +#ifdef PSPENV + int disableInterrupts = pspSdkDisableInterrupts(); +#endif + // Check loop for (;;) { @@ -233,6 +249,9 @@ u32 ramAvailable(void) free(ram); } +#ifdef PSPENV + pspSdkEnableInterrupts(disableInterrupts); +#endif return size; }