diff --git a/projects/mtg/bin/daily_build/template.exe b/projects/mtg/bin/daily_build/template.exe index b92eb3ae1..cf9b5532a 100644 Binary files a/projects/mtg/bin/daily_build/template.exe and b/projects/mtg/bin/daily_build/template.exe differ diff --git a/projects/mtg/include/WCachedResource.h b/projects/mtg/include/WCachedResource.h index 0659c1e2b..7df830f17 100644 --- a/projects/mtg/include/WCachedResource.h +++ b/projects/mtg/include/WCachedResource.h @@ -21,6 +21,7 @@ public: virtual unsigned long size()=0; //Size of cached item in bytes. virtual bool isGood()=0; //Return true if this has data. virtual bool isLocked(); //Is the resource locked? + virtual bool isTrash(); //Is the resource locked? virtual void lock(); //Lock it. virtual void unlock(bool force = false); //Unlock it. Forcing a lock will also remove "permanent" status. @@ -29,6 +30,7 @@ public: void hit(); //Update resource's last used time. protected: + string id; //Our lookup value. int loadedMode; //What submode settings were we loaded with? (For refresh) unsigned int lastTime; //When was the last time we were hit? unsigned char locks; //Remember to unlock when we're done using locked stuff, or else this'll be useless. diff --git a/projects/mtg/include/WResourceManager.h b/projects/mtg/include/WResourceManager.h index 26220f027..a204430f9 100644 --- a/projects/mtg/include/WResourceManager.h +++ b/projects/mtg/include/WResourceManager.h @@ -6,17 +6,21 @@ #include "MTGDeck.h" #include "MTGCard.h" #include "WCachedResource.h" +#include //Soft limits. -#define HUGE_CACHE_LIMIT 10000000 -#define HUGE_CACHE_ITEMS 400 +//For values higher than ~8000000, we run the danger of hitting our reserved space in deck editor. +#define HUGE_CACHE_LIMIT 8000000 +#define LARGE_CACHE_LIMIT 6000000 +#define SMALL_CACHE_LIMIT 3000000 -#define LARGE_CACHE_LIMIT 5000000 -#define LARGE_CACHE_ITEMS 300 - -#define SMALL_CACHE_LIMIT 1000000 -#define SMALL_CACHE_ITEMS 200 +#define HUGE_CACHE_ITEMS 200 +#define LARGE_CACHE_ITEMS 150 +#define SMALL_CACHE_ITEMS 100 +//We keep a certain amount of space reserved for non-cache use. +//This value was chosen to guarantee space for image loading. +#define CACHE_SPACE_RESERVED (512*512*sizeof(PIXEL_TYPE)) //Hard Limits. #define MAX_CACHE_OBJECTS HUGE_CACHE_ITEMS @@ -30,6 +34,7 @@ enum ENUM_WRES_INFO{ WRES_MAX_LOCK = 250, //Maximum number of locks for a resource. WRES_PERMANENT = 251, //Resource is permanent (ie, managed) WRES_UNDERLOCKED = 252, //Resource was released too many times. + WRES_TRASH = 253, //Resource is trash, and can be recycled. }; enum ENUM_RETRIEVE_STYLE{ @@ -60,12 +65,13 @@ enum ENUM_CACHE_SUBTYPE{ enum ENUM_CACHE_ERROR{ CACHE_ERROR_NONE = 0, - CACHE_ERROR_NOT_CACHED = CACHE_ERROR_NONE, - CACHE_ERROR_404, - CACHE_ERROR_BAD, //Something went wrong with item->attempt() - CACHE_ERROR_BAD_ALLOC, //Couldn't allocate item - CACHE_ERROR_LOST, + CACHE_ERROR_NOT_CACHED, CACHE_ERROR_NOT_MANAGED, + CACHE_ERROR_404, + CACHE_ERROR_BAD, //Something went wrong with item->attempt() + CACHE_ERROR_BAD_ALLOC, //Couldn't allocate item + CACHE_ERROR_FULL, //Cache is at maxCached. + CACHE_ERROR_LOST, }; template @@ -89,19 +95,21 @@ public: 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* Get(string id, int style = RETRIEVE_NORMAL, int submode = CACHE_NORMAL); //Subordinate to Retrieve. - cacheItem* AttemptNew(string filename, int submode); //Attempts a new cache item, progressively clearing cache if it fails. - cacheItem* Recycle(); //Returns a cache item from the trash. + bool RemoveItem(cacheItem* item, bool force = true); //Removes an item, deleting it. if(force), ignores locks / permanent + bool Delete(cacheItem* item); //Garbage collect. If maxCached == 0, nullify first. (This means you have to free that cacheActual later!) + + bool AttemptNew(cacheItem* item, int submode); //Attempts to load item, progressively clearing mCache if it fails. + cacheItem* Get(string id, int style = RETRIEVE_NORMAL, int submode = CACHE_NORMAL); //Subordinate to Retrieve. Guarenteed isGood(). + cacheItem* Recycle(); //Returns a cache item from the trash, or (worst possible case) pops a new one onto mCache. + + void RecordMiss(string miss); string makeID(string filename, int submode); //Makes an ID appropriate to the submode. string makeFilename(string id, int submode); //Makes a filename from an ID. - map cache; - map managed; //Cache can be arbitrarily large, so managed items are seperate. - vector garbage; //Garbage collection. + cacheItem mCached[MAX_CACHE_OBJECTS]; + list mManaged; //Cache and managed items are seperate to improve performance. + list mMisses; unsigned long totalSize; unsigned long cacheSize; @@ -138,6 +146,8 @@ public: JQuad * RetrieveTempQuad(string filename); hgeParticleSystemInfo * RetrievePSI(string filename, JQuad * texture, int style = RETRIEVE_NORMAL, int submode = CACHE_NORMAL); + int RetrieveError(); //Returns the error from the last call to ANY retrieve function. + void Release(JTexture * tex); void Release(JQuad * quad); void Release(JSample * sample); @@ -202,6 +212,7 @@ private: //Statistics of record. unsigned int lastTime; + int lastError; }; extern WResourceManager resources; diff --git a/projects/mtg/src/GameStateDeckViewer.cpp b/projects/mtg/src/GameStateDeckViewer.cpp index 22688afd0..17f4b3599 100644 --- a/projects/mtg/src/GameStateDeckViewer.cpp +++ b/projects/mtg/src/GameStateDeckViewer.cpp @@ -548,8 +548,12 @@ void GameStateDeckViewer::renderCard(int id, float rotation){ JQuad * quad = NULL; int showName = 0; +#ifdef DEBUG_CACHE + quad = resources.RetrieveCard(card,RETRIEVE_NORMAL); +#else quad = resources.RetrieveCard(card,RETRIEVE_EXISTING); - if (!quad){ +#endif + if (!quad && resources.RetrieveError() != CACHE_ERROR_404){ if(last_user_activity > (abs(2-id) + 1)* NO_USER_ACTIVITY_SHOWCARD_DELAY) quad = resources.RetrieveCard(card); else{ diff --git a/projects/mtg/src/OptionItem.cpp b/projects/mtg/src/OptionItem.cpp index 97eba2b9a..79f4522e8 100644 --- a/projects/mtg/src/OptionItem.cpp +++ b/projects/mtg/src/OptionItem.cpp @@ -526,7 +526,6 @@ void OptionsList::save(){ for (int i = 0; i < nbitems; i++){ listItems[i]->setData(); } - ::options.save(); } void OptionsList::Update(float dt){ @@ -652,6 +651,8 @@ void OptionsMenu::save(){ for(int x=0;xsave(); + + ::options.save(); } bool OptionsMenu::isTab(string name){ diff --git a/projects/mtg/src/ShopItem.cpp b/projects/mtg/src/ShopItem.cpp index fbb7f9732..fbfa384ae 100644 --- a/projects/mtg/src/ShopItem.cpp +++ b/projects/mtg/src/ShopItem.cpp @@ -384,7 +384,7 @@ void ShopItems::ButtonPressed(int controllerId, int controlId){ tempDeck->remove(it->first); int rarity = (int) tempDeck->database->getCardById(it->first)->getRarity(); - tempDeck->addRandomCards(dupes,&rarity); + tempDeck->addRandomCards(dupes,sets,1,rarity); it = tempDeck->cards.begin(); } diff --git a/projects/mtg/src/WCachedResource.cpp b/projects/mtg/src/WCachedResource.cpp index 19ec5cdb0..17495e465 100644 --- a/projects/mtg/src/WCachedResource.cpp +++ b/projects/mtg/src/WCachedResource.cpp @@ -16,13 +16,16 @@ WResource::~WResource(){ return; } WResource::WResource(){ - locks = WRES_UNLOCKED; + locks = WRES_TRASH; lastTime = resources.nowTime(); loadedMode = 0; } bool WResource::isLocked(){ - return (locks != WRES_UNLOCKED); + return (locks != WRES_UNLOCKED && locks != WRES_TRASH); +} +bool WResource::isTrash(){ + return (locks == WRES_TRASH); } bool WResource::isPermanent(){ @@ -171,8 +174,10 @@ WTrackedQuad * WCachedTexture::GetTrackedQuad(float offX, float offY, float widt tq = *gtq; garbageTQs.erase(gtq); } - else + else{ tq = NEW WTrackedQuad(resname); + tq->unlock(true); + } } if(tq == NULL) @@ -244,6 +249,9 @@ unsigned long WCachedTexture::size(){ } bool WCachedTexture::isGood(){ + if(locks == WRES_TRASH) + return false; + if(!texture) return false; @@ -321,6 +329,9 @@ bool WCachedTexture::Attempt(string filename, int submode, int & error){ return false; } + if(locks == WRES_TRASH) + locks = WRES_UNLOCKED; + error = CACHE_ERROR_NONE; return true; } @@ -330,6 +341,11 @@ void WCachedTexture::Nullify(){ texture = NULL; } void WCachedTexture::Trash(){ + id = ""; + locks = WRES_TRASH; +#ifdef DEBUG_CACHE + OutputDebugString("WCachedTexture::Trash()\n"); +#endif SAFE_DELETE(texture); vector::iterator it; @@ -355,6 +371,11 @@ void WCachedSample::Nullify(){ } void WCachedSample::Trash(){ + id = ""; + locks = WRES_TRASH; +#ifdef DEBUG_CACHE + OutputDebugString("WCachedSample::Trash()\n"); +#endif SAFE_DELETE(sample); } @@ -388,6 +409,9 @@ unsigned long WCachedSample::size(){ } bool WCachedSample::isGood(){ + if(locks == WRES_TRASH) + return false; + if(!sample || !sample->mSample) return false; @@ -412,12 +436,18 @@ bool WCachedSample::Attempt(string filename, int submode, int & error){ return false; } + if(locks == WRES_TRASH) + locks = WRES_UNLOCKED; + return true; } //WCachedParticles bool WCachedParticles::isGood(){ + if(locks == WRES_TRASH) + return false; + if(!particles) return false; return true; @@ -463,6 +493,10 @@ bool WCachedParticles::Attempt(string filename, int submode, int & error){ particles->sprite=NULL; error = CACHE_ERROR_NONE; + + if(locks == WRES_TRASH) + locks = WRES_UNLOCKED; + return true; } @@ -489,6 +523,11 @@ void WCachedParticles::Nullify(){ } void WCachedParticles::Trash(){ + id = ""; + locks = WRES_TRASH; +#ifdef DEBUG_CACHE + OutputDebugString("WCachedParticles::Trash()\n"); +#endif SAFE_DELETE(particles); } @@ -498,6 +537,11 @@ void WTrackedQuad::Nullify() { } void WTrackedQuad::Trash(){ + id = ""; + locks = WRES_TRASH; +#ifdef DEBUG_CACHE + OutputDebugString("WTrackedQuad::Trash()\n"); +#endif resname.clear(); SAFE_DELETE(quad); } @@ -510,6 +554,9 @@ unsigned long WTrackedQuad::size() { return sizeof(JQuad); } bool WTrackedQuad::isGood(){ + if(locks == WRES_TRASH) + return false; + return (quad != NULL); } WTrackedQuad::WTrackedQuad(string _resname) { diff --git a/projects/mtg/src/WResourceManager.cpp b/projects/mtg/src/WResourceManager.cpp index 3b892dbb6..ddb507f67 100644 --- a/projects/mtg/src/WResourceManager.cpp +++ b/projects/mtg/src/WResourceManager.cpp @@ -35,6 +35,9 @@ bool WResourceManager::RemoveOldest(){ } //WResourceManager +int WResourceManager::RetrieveError(){ + return lastError; +} void WResourceManager::DebugRender(){ JRenderer* renderer = JRenderer::GetInstance(); JLBFont * font = resources.GetJLBFont(Constants::MAIN_FONT); @@ -54,17 +57,13 @@ void WResourceManager::DebugRender(){ 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+%u (of %u) items (%u misses), Pixels: %lu (of %lu) + %lu", - textureWCache.cacheItems, textureWCache.managed.size(),textureWCache.maxCached, - misses,textureWCache.cacheSize,textureWCache.maxCacheSize,man); + textureWCache.cacheItems, textureWCache.mManaged.size(),textureWCache.maxCached, + textureWCache.mMisses.size(),textureWCache.cacheSize,textureWCache.maxCacheSize,man); font->DrawString(buf, 10,5); @@ -111,11 +110,11 @@ unsigned long WResourceManager::SizeManaged(){ unsigned int WResourceManager::Count(){ unsigned int count = 0; count += textureWCache.cacheItems; - count += textureWCache.managed.size(); + count += textureWCache.mManaged.size(); count += sampleWCache.cacheItems; - count += sampleWCache.managed.size(); + count += sampleWCache.mManaged.size(); count += psiWCache.cacheItems; - count += psiWCache.managed.size(); + count += psiWCache.mManaged.size(); return count; } unsigned int WResourceManager::CountCached(){ @@ -127,9 +126,9 @@ unsigned int WResourceManager::CountCached(){ } unsigned int WResourceManager::CountManaged(){ unsigned int count = 0; - count += textureWCache.managed.size(); - count += sampleWCache.managed.size(); - count += psiWCache.managed.size(); + count += textureWCache.mManaged.size(); + count += sampleWCache.mManaged.size(); + count += psiWCache.mManaged.size(); return count; } @@ -179,6 +178,7 @@ WResourceManager::WResourceManager(){ sampleWCache.Resize(SMALL_CACHE_LIMIT,MAX_CACHED_SAMPLES); textureWCache.Resize(LARGE_CACHE_LIMIT,MAX_CACHE_OBJECTS); lastTime = 0; + lastError = CACHE_ERROR_NONE; } WResourceManager::~WResourceManager(){ LOG("==Destroying WResourceManager=="); @@ -317,6 +317,8 @@ JQuad * WResourceManager::RetrieveQuad(string filename, float offX, float offY, else jtex = textureWCache.Retrieve(filename,RETRIEVE_NORMAL,submode); + lastError = textureWCache.mError; + //Somehow, jtex wasn't promoted. if(style == RETRIEVE_MANAGE && jtex && !jtex->isPermanent()) return NULL; @@ -359,14 +361,14 @@ void WResourceManager::Release(JQuad * quad){ if(!quad) return; - map::iterator it; - for(it = textureWCache.cache.begin();it!=textureWCache.cache.end();it++){ - if(it->second && it->second->ReleaseQuad(quad)) + int i; + for(i = 0; i < MAX_CACHE_OBJECTS;i++){ + if(textureWCache.mCached[i].isGood() && textureWCache.mCached[i].ReleaseQuad(quad)) break; } - if(it != textureWCache.cache.end() && it->second) - textureWCache.RemoveItem(it->second,false); //won't remove locked. + if(i != MAX_CACHE_OBJECTS) + textureWCache.RemoveItem(&textureWCache.mCached[i],false); //won't remove locked. } void WResourceManager::ClearMisses(){ @@ -438,8 +440,11 @@ JTexture * WResourceManager::RetrieveTexture(string filename, int style, int sub case CACHE_ERROR_NOT_MANAGED: debugMessage = "Resource not managed: "; break; + case CACHE_ERROR_NOT_CACHED: + debugMessage = "Resource not cached: "; + break; case CACHE_ERROR_LOST: - debugMessage = "Resource went bad, potential memory leak: "; + debugMessage = "Resource went bad: "; break; default: debugMessage = "Unspecified error: "; @@ -447,6 +452,7 @@ JTexture * WResourceManager::RetrieveTexture(string filename, int style, int sub debugMessage += filename; } #endif + lastError = textureWCache.mError; return NULL; } @@ -466,21 +472,26 @@ JTexture* WResourceManager::GetTexture(const string &textureName){ } JTexture* WResourceManager::GetTexture(int id){ - map::iterator it; - JTexture *jtex = NULL; - if(id == INVALID_ID) + if(id == INVALID_ID || id < 0 || id > (int) textureWCache.mManaged.size()) 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; - } + int pos = 0; + list::iterator i; + for(i = textureWCache.mManaged.begin();i!=textureWCache.mManaged.end();i++){ + if(pos == id) + break; + pos++; } - return jtex; + if(i == textureWCache.mManaged.end()) + return NULL; + + WCachedTexture * wct = &(*i); + if(wct->isGood()) + return wct->Actual(); + + return NULL; } hgeParticleSystemInfo * WResourceManager::RetrievePSI(string filename, JQuad * texture, int style, int submode){ @@ -497,27 +508,33 @@ hgeParticleSystemInfo * WResourceManager::RetrievePSI(string filename, JQuad * t return i; } + lastError = psiWCache.mError; return NULL; } JSample * WResourceManager::RetrieveSample(string filename, int style, int submode){ //Check cache. This just tracks misses. return NULL; - WCachedSample * tc = NULL; - tc = sampleWCache.Get(filename,submode); + WCachedSample * it; + it = sampleWCache.Get(filename,submode); + + if(it == NULL) + return NULL; //Sample exists! Get it. - if(tc && tc->isGood()){ - JSample * js = tc->Actual(); + if(it->isGood()){ + JSample * js = it->Actual(); + + it->Nullify(); //Samples are freed when played, not managed by us. + it->Trash(); - //Samples are freed when played, so remove this. Because maxCached is 0, this will Nullify() first. - sampleWCache.RemoveItem(tc,true); //Adjust sizes accordingly. sampleWCache.cacheSize = 0; sampleWCache.totalSize = 0; return js; } + lastError = sampleWCache.mError; return NULL; } @@ -822,10 +839,12 @@ void WResourceManager::CacheForState(int state){ sampleWCache.Resize(SMALL_CACHE_LIMIT,MAX_CACHED_SAMPLES); Cleanup(); break; - //Deck editor and shop are entirely cache safe, so give it near infinite resources. + //Shop is almost entirely cache safe, so give it near infinite resources. case GAME_STATE_SHOP: + textureWCache.Resize(HUGE_CACHE_LIMIT,HUGE_CACHE_ITEMS); + break; case GAME_STATE_DECK_VIEWER: - textureWCache.Resize(HUGE_CACHE_LIMIT,HUGE_CACHE_ITEMS); + textureWCache.Resize(HUGE_CACHE_LIMIT,HUGE_CACHE_ITEMS); break; //Anything unknown, use large cache. default: @@ -915,83 +934,47 @@ void WResourceManager::Refresh(){ //WCache template bool WCache::RemoveOldest(){ - typename map ::iterator oldest = cache.end(); + int oldest = -1; - 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; + for(int i = 0;i< MAX_CACHE_OBJECTS;i++){ + if(mCached[i].isGood() && !mCached[i].isLocked() + && (oldest == -1 || mCached[i].lastTime < mCached[oldest].lastTime)) + oldest = i; } - if(oldest != cache.end() && oldest->second && !oldest->second->isLocked()){ + if(oldest != -1 && mCached[oldest].isGood() && !mCached[oldest].isLocked()){ #if defined DEBUG_CACHE - lastExpired = oldest->first; + lastExpired = mCached[oldest].id; #endif - Delete(oldest->second); - cache.erase(oldest); + Delete(&mCached[oldest]); return true; } return false; - } template void WCache::Clear(){ - typename map::iterator it, next; + typename list::iterator it; - for(it = cache.begin(); it != cache.end();it=next){ - next = it; - next++; - - if(it->second) - Delete(it->second); - cache.erase(it); - } - for(it = managed.begin(); it != managed.end();it=next){ - next = it; - next++; - - if(!it->second) - managed.erase(it); + for(int i = 0; i < MAX_CACHE_OBJECTS;i++){ + if(!mCached[i].isTrash()) + Delete(&mCached[i]); } } template void WCache::ClearUnlocked(){ - typename map::iterator it, next; + typename list::iterator it; - 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); - } + for(int i = 0; i < MAX_CACHE_OBJECTS;i++){ + if(mCached[i].isGood() && !mCached[i].isLocked()) + Delete(&mCached[i]); } } template void WCache::ClearMisses(){ - typename map::iterator it, next; - - for(it = cache.begin(); it != cache.end();it=next){ - next = it; - next++; - - if(!it->second) - cache.erase(it); - } - for(it = managed.begin(); it != managed.end();it=next){ - next = it; - next++; - - if(!it->second) - managed.erase(it); - } + mMisses.clear(); } template void WCache::Resize(unsigned long size, int items){ @@ -1005,159 +988,146 @@ void WCache::Resize(unsigned long size, int items){ template cacheItem* WCache::Recycle(){ - typename vector::iterator it = garbage.begin(); - if(it == garbage.end()) + + int i; + for(i = 0; i < MAX_CACHE_OBJECTS;i++){ + if(mCached[i].isTrash()) + break; + } + + if(i == MAX_CACHE_OBJECTS){ + mError = CACHE_ERROR_FULL; return NULL; + } - cacheItem * item = (*it); - garbage.erase(it); - - return item; + return &mCached[i]; } template -cacheItem* WCache::AttemptNew(string filename, int submode){ +bool WCache::AttemptNew(cacheItem* item, int submode){ if(submode & CACHE_EXISTING){ //Should never get this far. mError = CACHE_ERROR_NOT_CACHED; - return NULL; + return false; } - cacheItem* item = NULL; - - item = Recycle(); + //We weren't passed a valid item, so get one. + if(item == NULL || !item->isTrash()) + item = Recycle(); + //Weren't able to get an item. Fail. if(item == NULL){ - try{ - item = NEW cacheItem; - mError = CACHE_ERROR_NONE; + mError = CACHE_ERROR_BAD_ALLOC; + return false; + } + + //Keep a bit of memory in reserve. + if(CACHE_SPACE_RESERVED > 0){ + char * test = (char*)malloc(CACHE_SPACE_RESERVED);; + int tries = 0; + while(!test && tries++ < MAX_CACHE_ATTEMPTS){ + if(!RemoveOldest()) + break; + + test = (char*)malloc(CACHE_SPACE_RESERVED); } - catch(std::bad_alloc){ - SAFE_DELETE(item); + + if(test){ + free(test); + test = NULL; + } + else{ + resources.ClearUnlocked(); + test = (char*)malloc(CACHE_SPACE_RESERVED); + + if(test){ + free(test); + test = NULL; + } + else{ + mError = CACHE_ERROR_BAD_ALLOC; + return false; + } } } - if(item == NULL || !item->Attempt(filename,submode,mError)){ + string filename = makeFilename(item->id,submode); + + if(!item->Attempt(filename,submode,mError)){ //No such file. Fail. - if(item && mError == CACHE_ERROR_404){ - if(garbage.size() < MAX_CACHE_GARBAGE){ - item->Trash(); - garbage.push_back(item); - } - else - SAFE_DELETE(item); - - return NULL; + if(!item->isGood() && mError == CACHE_ERROR_404){ + item->Trash(); + return false; } - for(int attempt=0;attempt<10;attempt++){ + //Make several attempts, removing bits of the cache. + for(int attempt=0;attemptAttempt(filename,submode,mError)) + if(item->Attempt(filename,submode,mError)) break; + + //Failed attempt. Trash this. + item->Trash(); } - - //Still no result, so clear cache entirely, then try again. - if(!item || !item->isGood()){ - ClearUnlocked(); - if(!item) item = NEW(cacheItem); + if(!item->isGood()){ + Cleanup(); item->Attempt(filename,submode,mError); } - - //Worst cache scenerio. Clear every cache we've got. - if(!item || !item->isGood()){ - resources.ClearUnlocked(); - if(!item){ - try{ - item = NEW(cacheItem); - } - catch(std::bad_alloc){ - mError = CACHE_ERROR_BAD_ALLOC; - SAFE_DELETE(item); - return NULL; - } - } + //Still no result, so clear cache entirely, then try again. + if(!item->isGood()){ + ClearUnlocked(); item->Attempt(filename,submode,mError); } } - if(item && !item->isGood()){ - if(garbage.size() < MAX_CACHE_GARBAGE){ - item->Trash(); - garbage.push_back(item); - } - else - SAFE_DELETE(item); + //Final failure. Trash it. + if(!item->isGood()){ + item->Trash(); mError = CACHE_ERROR_BAD; - return NULL; + return false; } else mError = CACHE_ERROR_NONE; + //Strictly enforce limits. item->lock(); Cleanup(); item->unlock(); - return item; + + return true; } template cacheItem * WCache::Retrieve(string filename, int style, int submode){ //Check cache. - cacheItem * tc = NULL; + cacheItem* item; if(style == RETRIEVE_EXISTING || style == RETRIEVE_RESOURCE) - tc = Get(filename,style,submode|CACHE_EXISTING); + item = Get(filename,style,submode|CACHE_EXISTING); else - tc = Get(filename,style,submode); + item = Get(filename,style,submode); + + //Get failed. + if(item == NULL || mError != CACHE_ERROR_NONE){ + return NULL; + } //Retrieve resource only works on permanent items. - if(style == RETRIEVE_RESOURCE && tc && !tc->isPermanent()){ + if(style == RETRIEVE_RESOURCE && !item->isPermanent()){ mError = CACHE_ERROR_NOT_MANAGED; return NULL; } - + //Perform lock or unlock on entry. - if(tc){ - if(style == RETRIEVE_LOCK) tc->lock(); - else if(style == RETRIEVE_UNLOCK) tc->unlock(); - else if(style == RETRIEVE_MANAGE && !tc->isPermanent()) { - //Unlink the managed resource from the cache. - UnlinkCache(tc); - - //Post it in managed resources. - managed[makeID(filename,submode)] = tc; - tc->deadbolt(); - } - } + if(style == RETRIEVE_LOCK) item->lock(); + else if(style == RETRIEVE_UNLOCK) item->unlock(); + else if(style == RETRIEVE_MANAGE) item->deadbolt(); - //Resource exists! - if(tc){ - if(tc->isGood()){ - tc->hit(); - return tc; //Everything fine. - } - else{ - //Something went wrong. - Delete(tc); - } - } - - //Record managed failure. Cache failure is recorded in Get(). - if(style == RETRIEVE_MANAGE || style == RETRIEVE_RESOURCE) - managed[makeID(filename,submode)] = NULL; - - return NULL; + //All is well! + item->hit(); + return item; } template string WCache::makeID(string id, int submode){ @@ -1179,150 +1149,165 @@ string WCache::makeFilename(string id, int submode){ return id; } +template +void WCache::RecordMiss(string miss){ + list::iterator it; + for(it=mMisses.begin();it!=mMisses.end();it++){ + if(*it == miss) + return; + } + + mMisses.push_back(miss); +} + template -cacheItem * WCache::Get(string id, int style, int submode){ - typename map::iterator it; +cacheItem* WCache::Get(string id, int style, int submode){ + typename list::iterator it; + cacheItem * item = NULL; + int i = 0; string lookup = makeID(id,submode); + + //Start with no errors. + mError = CACHE_ERROR_NONE; + + + //Check for misses. + list::iterator miss; + for(miss = mMisses.begin();miss != mMisses.end();miss++){ + if(*miss == lookup){ + mError = CACHE_ERROR_404; + return NULL; + } + } //Check for managed resources first. Always - it = managed.end(); - for(it = managed.begin();it!=managed.end();it++){ - if(it->first == lookup) + for(it = mManaged.begin();it!=mManaged.end();it++){ + if(it->id == lookup) break; } //Something is managed. - if(it != managed.end()) { - if(!it->second && style == RETRIEVE_RESOURCE) - return NULL; //A miss. - else - return it->second; //A hit. + if(it != mManaged.end() && it->isGood()) { + mError = CACHE_ERROR_NONE; + return &(*it); } //Failed to find managed resource and won't create one. Record a miss. else if(style == RETRIEVE_RESOURCE){ - managed[lookup] = NULL; + mError = CACHE_ERROR_NOT_MANAGED; return NULL; } //Not managed, so look in cache. - if(it == managed.end() && style != RETRIEVE_MANAGE && style != RETRIEVE_RESOURCE ){ - it = cache.end(); - for(it = cache.begin();it!=cache.end();it++){ - if(it->first == lookup) + if(style != RETRIEVE_RESOURCE ){ + for(i= 0;iid == lookup) break; } + + if(i >= MAX_CACHE_OBJECTS) + item = NULL; + //Well, we've found something... - if(it != cache.end()) { - if(!it->second && (submode & CACHE_EXISTING)) - return NULL; //A miss. - else - return it->second; //A hit. + if(item != NULL && item->isGood()) { + mError = CACHE_ERROR_NONE; + return item; //A hit. + } + //Didn't find anything. Die here, if we must. + else if(style == RETRIEVE_EXISTING || submode & CACHE_EXISTING){ + mError = CACHE_ERROR_NOT_CACHED; + return NULL; } } - - cacheItem * item = NULL; - if(style != RETRIEVE_MANAGE) - item = cache[lookup]; //We don't know about this one yet. - - //Found something. - if(item){ - //Item went bad? - if(!item->isGood()){ + //If we've got an item that went bad, trash it so we can recycle it. + if(item != NULL && !item->isGood() && !item->isTrash()){ + item->Trash(); + } - //If we're allowed, attempt to revive it. - if(!(submode & CACHE_EXISTING)) - item->Attempt(id,submode,mError); - - //Still bad, so remove it and return NULL - if(submode & CACHE_EXISTING || !item->isGood()){ - if(!item->isLocked()){ - RemoveItem(item); //Delete it. - mError = CACHE_ERROR_BAD; - } - //Worst case scenerio. Hopefully never happens.... hasn't so far. - else{ - item->Nullify(); //We're giving up on anything allocated here. - mError = CACHE_ERROR_LOST; //This is a potential memory leak, but might prevent a crash. - } - return NULL; + //Give us a managed item + if(style == RETRIEVE_MANAGE){ + //This was formerly in cache, so promote it + if(item != NULL){ + if(item->isGood()){ + mManaged.push_front(*item); + item->Trash(); + mError = CACHE_ERROR_NONE; + it = mManaged.begin(); + return &(*it); + } + else{ + //It went bad, so trash it and manage something new. + item->Trash(); } } - - //Alright, everythings fine! - mError = CACHE_ERROR_NONE; - return item; - } - - //Didn't exist in cache. - if(submode & CACHE_EXISTING ){ - RemoveMiss(lookup); - mError = CACHE_ERROR_NOT_CACHED; - return NULL; + else{ + cacheItem temp; + mManaged.push_front(temp); + it = mManaged.begin(); + item = &(*it); + } } + //Give us a cached item. else{ - //Space in cache, make new texture - item = AttemptNew(id,submode); + if(item != NULL && !item->isTrash()) { + mError = CACHE_ERROR_LOST; + return NULL; //Something went wrong. TODO: Proper error message. + } - //Couldn't make GOOD new item. - if(item && !item->isGood()) - { - if(garbage.size() < MAX_CACHE_GARBAGE){ - item->Trash(); - garbage.push_back(item); - } - else - SAFE_DELETE(item); - } + item = Recycle(); } - - if(style == RETRIEVE_MANAGE){ - if(item){ - managed[lookup] = item; //Record a hit. - item->deadbolt(); //Make permanent. - } - else if(mError == CACHE_ERROR_404) - managed[lookup] = item; //File not found. Record a miss - } - else{ - if(!item && mError != CACHE_ERROR_404) - RemoveMiss(lookup); - else - cache[lookup] = item; - } - //Succeeded in making a new item. - if(item){ + if(mError != CACHE_ERROR_NONE) + return NULL; + + //If we've reached this point, we've got an item to fill. + item->id = lookup; //Asign the new lookup value. + AttemptNew(item,submode); //Try to fill it. + + //No errors, so good! + if(item->isGood()){ unsigned long isize = item->size(); totalSize += isize; - mError = CACHE_ERROR_NONE; if(style != RETRIEVE_MANAGE){ cacheItems++; cacheSize += isize; } + mError = CACHE_ERROR_NONE; return item; } - + //Failed. Record miss. + else if(mError == CACHE_ERROR_404){ + RecordMiss(lookup); + } + //Failure. + if(item && !item->isGood()) + item->Trash(); + + mError = CACHE_ERROR_BAD; return NULL; } template void WCache::Refresh(){ - typename map::iterator it; ClearUnlocked(); - for(it = cache.begin();it!=cache.end();it++){ - if(it->second){ - it->second->Refresh(makeFilename(it->first,it->second->loadedMode)); + + for(int i = 0;i < MAX_CACHE_OBJECTS;i++){ + cacheItem * item = &mCached[i]; + if(item->isGood()){ + item->Refresh(makeFilename(item->id,item->loadedMode)); } } - for(it = managed.begin();it!=managed.end();it++){ - if(it->second){ - it->second->Refresh(makeFilename(it->first,it->second->loadedMode)); + typename list::iterator it; + for(it = mManaged.begin();it!=mManaged.end();it++){ + if(it->isGood()){ + it->Refresh(makeFilename(it->id,it->loadedMode)); } } + mMisses.clear(); } template @@ -1339,38 +1324,17 @@ WCache::WCache(){ template WCache::~WCache(){ - typename map::iterator it; - - for(it=cache.begin();it!=cache.end();it++){ - if(!it->second) - continue; - - //Delete(it->second); - SAFE_DELETE(it->second); - } - - for(it=managed.begin();it!=managed.end();it++){ - if(!it->second) - continue; - - //Delete(it->second); - SAFE_DELETE(it->second); - } - - typename vector::iterator g; - for(g=garbage.begin();g!=garbage.end();g++){ - SAFE_DELETE(*g); - } + //Vectors take care of themselves. } template bool WCache::Cleanup(){ - while(cacheItems < cache.size() && cache.size() - cacheItems > MAX_CACHE_MISSES){ + while(mMisses.size() > MAX_CACHE_MISSES){ RemoveMiss(); } - while (cacheItems > MAX_CACHE_OBJECTS || cacheItems > maxCached || cacheSize > maxCacheSize ){ + while (cacheItems > MAX_CACHE_OBJECTS - 1 || cacheItems > maxCached || cacheSize > maxCacheSize ){ if (!RemoveOldest()) return false; } @@ -1382,15 +1346,18 @@ unsigned int WCache::Flatten(){ unsigned int youngest = 65535; unsigned int oldest = 0; - for (typename map::iterator it = cache.begin(); it != cache.end(); ++it){ - if(!it->second) continue; - if(it->second->lastTime < youngest) youngest = it->second->lastTime; - if(it->second->lastTime > oldest) oldest = it->second->lastTime; + for (int i = 0; i < MAX_CACHE_OBJECTS; i++){ + cacheItem * it = &mCached[i]; + if(!it->isGood()) continue; + if(it->lastTime < youngest) youngest = it->lastTime; + if(it->lastTime > oldest) oldest = it->lastTime; } - for (typename map::iterator it = cache.begin(); it != cache.end(); ++it){ - if(!it->second) continue; - it->second->lastTime -= youngest; + for (int i = 0; i < MAX_CACHE_OBJECTS; i++){ + cacheItem * it = &mCached[i]; + + if(!it->isGood()) continue; + it->lastTime -= youngest; } return (oldest - youngest); @@ -1398,16 +1365,16 @@ unsigned int WCache::Flatten(){ template bool WCache::RemoveMiss(string id){ - typename map::iterator it = cache.end(); + typename list::iterator it = mMisses.end(); - for(it = cache.begin();it!=cache.end();it++){ - if((id == "" || it->first == id) && it->second == NULL) + for(it = mMisses.begin();it!=mMisses.end();it++){ + if((id == "" || *it == id)) break; } - if(it != cache.end()) + if(it != mMisses.end()) { - cache.erase(it); + mMisses.erase(it); return true; } @@ -1415,23 +1382,9 @@ bool WCache::RemoveMiss(string id){ } 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())){ - -#if defined DEBUG_CACHE - lastRemoved = it->first; -#endif - Delete(it->second); - cache.erase(it); +bool WCache::RemoveItem(cacheItem* item, bool force){ + if(item && !item->isLocked()){ + Delete(item); return true; } @@ -1439,57 +1392,32 @@ bool WCache::RemoveItem(cacheItem * item, bool force){ } 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) +bool WCache::Delete(cacheItem* item){ +//No need to delete this. + if(item == NULL || item->isTrash()) return false; + #ifdef DEBUG_CACHE char buf[512]; - sprintf(buf,"Cache: Delete [%d]\n",(int)item); - //OutputDebugString(buf); + sprintf(buf,"WCache::Delete %s\n",item->id.c_str()); + OutputDebugString(buf); #endif + if(maxCached == 0) item->Nullify(); - unsigned long isize = item->size(); - totalSize -= isize; - cacheSize -= isize; + unsigned long isize = item->size(); + totalSize -= isize; + cacheSize -= isize; + #ifdef DEBUG_CACHE if(cacheItems == 0) OutputDebugString("cacheItems out of sync.\n"); #endif - cacheItems--; + cacheItems--; - if(garbage.size() > MAX_CACHE_GARBAGE) - SAFE_DELETE(item); - else{ - item->Trash(); - garbage.push_back(item); - } + item->Trash(); return true; } @@ -1498,27 +1426,29 @@ 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)) + int i; + + for(i = 0;i < MAX_CACHE_OBJECTS;i++){ + if(!mCached[i].isGood()) + continue; + + if(mCached[i].compare(actual)) break; } - if(it == cache.end()) + if(i == MAX_CACHE_OBJECTS) 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. + //Release one lock. + mCached[i].unlock(); + + //Still locked, won't delete, not technically a failure. + if(mCached[i].isLocked()) + return true; #if defined DEBUG_CACHE - lastReleased = it->first; + lastReleased = mCached[i].id; #endif - Delete(it->second); - } - - //Released! - cache.erase(it); + Delete(&mCached[i]); return true; } \ No newline at end of file