diff options
-rw-r--r-- | sword2/layers.cpp | 4 | ||||
-rw-r--r-- | sword2/resman.cpp | 251 | ||||
-rw-r--r-- | sword2/resman.h | 39 | ||||
-rw-r--r-- | sword2/sword2.cpp | 2 |
4 files changed, 162 insertions, 134 deletions
diff --git a/sword2/layers.cpp b/sword2/layers.cpp index 79b8cd9743..d3006ff19c 100644 --- a/sword2/layers.cpp +++ b/sword2/layers.cpp @@ -46,10 +46,6 @@ void Screen::initBackground(int32 res, int32 new_palette) { assert(res); - // The resources age every time a new room is entered. - _vm->_resman->passTime(); - _vm->_resman->expireOldResources(); - _vm->_sound->clearFxQueue(); waitForFade(); diff --git a/sword2/resman.cpp b/sword2/resman.cpp index bfb5a7c7af..3ab598ad42 100644 --- a/sword2/resman.cpp +++ b/sword2/resman.cpp @@ -109,13 +109,15 @@ ResourceManager::ResourceManager(Sword2Engine *vm) { do { // item must have an #0d0a while (temp[i] != 13) { - _resourceFiles[_totalClusters][j] = temp[i]; + _resFiles[_totalClusters].fileName[j] = temp[i]; i++; j++; } // NULL terminate our extracted string - _resourceFiles[_totalClusters][j] = 0; + _resFiles[_totalClusters].fileName[j] = '\0'; + _resFiles[_totalClusters].numEntries = -1; + _resFiles[_totalClusters].entryTab = NULL; // Reset position in current slot between entries, skip the // 0x0a in the source and increase the number of clusters. @@ -168,21 +170,21 @@ ResourceManager::ResourceManager(Sword2Engine *vm) { for (i = 0; i < _totalClusters; i++) { for (j = 0; j < _totalClusters; j++) { - if (scumm_stricmp((char *) cdInf[j].clusterName, _resourceFiles[i]) == 0) + if (scumm_stricmp((char *) cdInf[j].clusterName, _resFiles[i].fileName) == 0) break; } if (j == _totalClusters) - error("%s is not in cd.inf", _resourceFiles[i]); + error("%s is not in cd.inf", _resFiles[i].fileName); - _cdTab[i] = cdInf[j].cd; + _resFiles[i].cd = cdInf[j].cd; } delete [] cdInf; debug(1, "%d resources in %d cluster files", _totalResFiles, _totalClusters); for (i = 0; i < _totalClusters; i++) - debug(2, "filename of cluster %d: -%s", i, _resourceFiles[i]); + debug(2, "filename of cluster %d: -%s", i, _resFiles[i].fileName); _resList = (Resource *) malloc(_totalResFiles * sizeof(Resource)); @@ -190,13 +192,18 @@ ResourceManager::ResourceManager(Sword2Engine *vm) { _resList[i].ptr = NULL; _resList[i].size = 0; _resList[i].refCount = 0; - _resList[i].refTime = 0; + _resList[i].prev = _resList[i].next = NULL; } - - _resTime = 0; + _cacheStart = _cacheEnd = NULL; + _usedMem = 0; } ResourceManager::~ResourceManager(void) { + Resource *res = _cacheStart; + while (res) { + _vm->_memory->memFree(res->ptr); + res = res->next; + } free(_resList); free(_resConvTable); } @@ -398,54 +405,37 @@ byte *ResourceManager::openResource(uint32 res, bool dump) { if (!_resList[res].ptr) { // Fetch the correct file and read in the correct portion. - - // points to the number of the ascii filename - uint16 parent_res_file = _resConvTable[res * 2]; - - assert(parent_res_file != 0xffff); + uint16 cluFileNum = _resConvTable[res * 2]; // points to the number of the ascii filename + assert(cluFileNum != 0xffff); // Relative resource within the file - uint16 actual_res = _resConvTable[(res * 2) + 1]; - // First we have to find the file via the _resConvTable - - debug(5, "openResource %s res %d", _resourceFiles[parent_res_file], res); + uint16 actual_res = _resConvTable[(res * 2) + 1]; + + debug(5, "openResource %s res %d", _resFiles[cluFileNum].fileName, res); // If we're loading a cluster that's only available from one // of the CDs, remember which one so that we can play the // correct music. - if (!(_cdTab[parent_res_file] & LOCAL_PERM)) - _curCd = _cdTab[parent_res_file] & 3; + if ((_resFiles[cluFileNum].cd == CD1) || (_resFiles[cluFileNum].cd == CD2)) + _curCd = _resFiles[cluFileNum].cd; // Actually, as long as the file can be found we don't really // care which CD it's on. But if we can't find it, keep asking // for the CD until we do. - File file; + File *file = openCluFile(cluFileNum); - while (!file.open(_resourceFiles[parent_res_file])) { - // If the file is supposed to be on hard disk, or we're - // playing a demo, then we're in trouble if the file - // can't be found! - - if ((_vm->_features & GF_DEMO) || (_cdTab[parent_res_file] & LOCAL_PERM)) - error("Could not find '%s'", _resourceFiles[parent_res_file]); - - getCd(_cdTab[parent_res_file] & 3); + if (_resFiles[cluFileNum].entryTab == NULL) { + // we didn't read from this file before, get its index table + readCluIndex(cluFileNum, file); } - // 1st DWORD of a cluster is an offset to the look-up table - uint32 table_offset = file.readUint32LE(); - - debug(6, "table offset = %d", table_offset); + uint32 pos = _resFiles[cluFileNum].entryTab[actual_res * 2 + 0]; + uint32 len = _resFiles[cluFileNum].entryTab[actual_res * 2 + 1]; - file.seek(table_offset + actual_res * 8, SEEK_SET); - - uint32 pos = file.readUint32LE(); - uint32 len = file.readUint32LE(); - - file.seek(pos, SEEK_SET); + file->seek(pos, SEEK_SET); debug(6, "res len %d", len); @@ -454,7 +444,7 @@ byte *ResourceManager::openResource(uint32 res, bool dump) { _resList[res].size = len; _resList[res].refCount = 0; - file.read(_resList[res].ptr, len); + file->read(_resList[res].ptr, len); if (dump) { StandardHeader *header = (StandardHeader *) _resList[res].ptr; @@ -520,15 +510,19 @@ byte *ResourceManager::openResource(uint32 res, bool dump) { } // close the cluster - file.close(); + file->close(); + delete file; + + _usedMem += len; + checkMemUsage(); #ifdef SCUMM_BIG_ENDIAN convertEndian(_resList[res].ptr, len); #endif - } + } else if (_resList[res].refCount == 0) + removeFromCacheList(_resList + res); _resList[res].refCount++; - _resList[res].refTime = _resTime; return _resList[res].ptr; } @@ -538,7 +532,8 @@ void ResourceManager::closeResource(uint32 res) { assert(_resList[res].refCount > 0); _resList[res].refCount--; - _resList[res].refTime = _resTime; + if (_resList[res].refCount == 0) + addToCacheList(_resList + res); // It's tempting to free the resource immediately when refCount // reaches zero, but that'd be a mistake. Closing a resource does not @@ -551,6 +546,73 @@ void ResourceManager::closeResource(uint32 res) { // specific memory address - was considered a bad thing. } +void ResourceManager::removeFromCacheList(Resource *res) { + if (_cacheStart == res) + _cacheStart = res->next; + + if (_cacheEnd == res) + _cacheEnd = res->prev; + + if (res->prev) + res->prev->next = res->next; + if (res->next) + res->next->prev = res->prev; + res->prev = res->next = NULL; +} + +void ResourceManager::addToCacheList(Resource *res) { + res->prev = NULL; + res->next = _cacheStart; + if (_cacheStart) + _cacheStart->prev = res; + _cacheStart = res; + if (!_cacheEnd) + _cacheEnd = res; +} + +File *ResourceManager::openCluFile(uint16 fileNum) { + File *file = new File; + while (!file->open(_resFiles[fileNum].fileName)) { + // If the file is supposed to be on hard disk, or we're + // playing a demo, then we're in trouble if the file + // can't be found! + + if ((_vm->_features & GF_DEMO) || (_resFiles[fileNum].cd & LOCAL_PERM)) + error("Could not find '%s'", _resFiles[fileNum].fileName); + + getCd(_resFiles[fileNum].cd & 3); + } + return file; +} + +void ResourceManager::readCluIndex(uint16 fileNum, File *file) { + if (_resFiles[fileNum].entryTab == NULL) { + // we didn't read from this file before, get its index table + if (file == NULL) + file = openCluFile(fileNum); + else + file->incRef(); + + // 1st DWORD of a cluster is an offset to the look-up table + uint32 table_offset = file->readUint32LE(); + debug(6, "table offset = %d", table_offset); + uint32 tableSize = file->size() - table_offset; // the table is stored at the end of the file + file->seek(table_offset); + + assert((tableSize % 8) == 0); + _resFiles[fileNum].entryTab = (uint32*)malloc(tableSize); + _resFiles[fileNum].numEntries = tableSize / 8; + file->read(_resFiles[fileNum].entryTab, tableSize); + if (file->ioFailed()) + error("unable to read index table from file %s\n", _resFiles[fileNum].fileName); +#ifdef SCUMM_BIG_ENDIAN + for (int tabCnt = 0; tabCnt < _resFiles[fileNum].numEntries * 2; tabCnt++) + _resFiles[fileNum].entryTab[tabCnt] = FROM_LE_UINT32(_resFiles[fileNum].entryTab[tabCnt]); +#endif + file->decRef(); + } +} + /** * Returns true if resource is valid, otherwise false. */ @@ -570,21 +632,6 @@ bool ResourceManager::checkValid(uint32 res) { return true; } -void ResourceManager::passTime() { - // In the original game this was called every game cycle. This allowed - // for a more exact measure of when a loaded resouce was most recently - // used. When the memory pool got too fragmented, the oldest and - // largest of the closed resources would be expelled from the cache. - - // With the new memory manager, there is no single memory block that - // can become fragmented. Therefore, it makes more sense to me to - // measure an object's age in how many rooms ago it was last used. - - // Therefore, this function is now called when a new room is loaded. - - _resTime++; -} - /** * Returns the total file length of a resource - i.e. all headers are included * too. @@ -606,67 +653,38 @@ uint32 ResourceManager::fetchLen(uint32 res) { // first we have to find the file via the _resConvTable // open the cluster file - File file; - - if (!file.open(_resourceFiles[parent_res_file])) - error("Cannot open %s", _resourceFiles[parent_res_file]); - - // 1st DWORD of a cluster is an offset to the look-up table - uint32 table_offset = file.readUint32LE(); - - // 2 dwords per resource + skip the position dword - file.seek(table_offset + (actual_res * 8) + 4, SEEK_SET); - - return file.readUint32LE(); + if (_resFiles[parent_res_file].entryTab == NULL) { + readCluIndex(parent_res_file); + } + return _resFiles[parent_res_file].entryTab[actual_res * 2 + 1]; } -// When a resource is opened, regardless of whether it was read from disk or -// from the cache, its age is zeroed. They then age every time a new room is -// entered. This function is responsible for cleaning out the resources that -// have grown too old to live. -// -// It could use a bit more tuning, I guess. I picked a max age of three for -// most resources, because so much of the game seems to consist of areas of -// about three rooms. I made an exception for SCREEN_FILE resources because -// they are so large, but maybe the exception ought to be the rule...? - -void ResourceManager::expireOldResources() { - int nuked = 0; - - for (uint i = 0; i < _totalResFiles; i++) { - if (!_resList[i].ptr || _resList[i].refCount > 0) - continue; - - StandardHeader *head = (StandardHeader *) _resList[i].ptr; - uint maxCacheAge; - - switch (head->fileType) { - case SCREEN_FILE: - // The resource will be read from disk once as soon as - // the player enters the room, and thrown away when - // the player enters a new room. - maxCacheAge = 0; - break; - default: - maxCacheAge = 3; - break; - } - - if (_resTime - _resList[i].refTime >= maxCacheAge) { - remove(i); - nuked++; +void ResourceManager::checkMemUsage(void) { + while (_usedMem > MAX_MEM_CACHE) { + // we're using up more memory than we wanted to. free some old stuff. + // Newly loaded objects are added to the start of the list, + // we start freeing from the end, to free the oldest items first + if (_cacheEnd) { + Resource *tmp = _cacheEnd; + assert((tmp->refCount == 0) && (tmp->ptr) && (tmp->next == NULL)); + removeFromCacheList(tmp); + + _vm->_memory->memFree(tmp->ptr); + tmp->ptr = NULL; + _usedMem -= tmp->size; + } else { + warning("%d bytes of memory used, but cache list is empty!\n"); + return; } } - - debug(1, "%d resources died of old age", nuked); } void ResourceManager::printConsoleClusters(void) { if (_totalClusters) { for (uint i = 0; i < _totalClusters; i++) { - Debug_Printf("%-20s ", _resourceFiles[i]); - if (!(_cdTab[i] & LOCAL_PERM)) { - switch (_cdTab[i] & 3) { + Debug_Printf("%-20s ", _resFiles[i].fileName); + if (!(_resFiles[i].cd & LOCAL_PERM)) { + switch (_resFiles[i].cd & 3) { case BOTH: Debug_Printf("CD 1 & 2\n"); break; @@ -692,7 +710,7 @@ void ResourceManager::listResources(uint minCount) { for (uint i = 0; i < _totalResFiles; i++) { if (_resList[i].ptr && _resList[i].refCount >= minCount) { StandardHeader *head = (StandardHeader *) _resList[i].ptr; - Debug_Printf("%-4d: %-35s refCount: %-3d age: %-2d\n", i, head->name, _resList[i].refCount, _resTime - _resList[i].refTime); + Debug_Printf("%-4d: %-35s refCount: %-3d\n", i, head->name, _resList[i].refCount); } } } @@ -770,9 +788,12 @@ void ResourceManager::kill(int res) { void ResourceManager::remove(int res) { if (_resList[res].ptr) { + removeFromCacheList(_resList + res); + _vm->_memory->memFree(_resList[res].ptr); _resList[res].ptr = NULL; _resList[res].refCount = 0; + _usedMem -= _resList[res].size; } } @@ -859,6 +880,10 @@ void ResourceManager::killAllObjects(bool wantInfo) { Debug_Printf("Expelled %d resources\n", nuked); } +int ResourceManager::whichCd(void) { + return _curCd; +} + void ResourceManager::getCd(int cd) { byte *textRes; diff --git a/sword2/resman.h b/sword2/resman.h index 212ed24f73..990e7f54eb 100644 --- a/sword2/resman.h +++ b/sword2/resman.h @@ -21,8 +21,11 @@ #ifndef RESMAN_H #define RESMAN_H +class File; + namespace Sword2 { +#define MAX_MEM_CACHE (8 * 1024 * 1024) // we keep up to 8 megs of resource data files in memory #define MAX_res_files 20 class Sword2Engine; @@ -31,7 +34,14 @@ struct Resource { byte *ptr; uint32 size; uint32 refCount; - uint32 refTime; + Resource *next, *prev; +}; + +struct ResourceFile { + char fileName[20]; + int32 numEntries; + uint32 *entryTab; + uint8 cd; }; class ResourceManager { @@ -45,16 +55,10 @@ public: bool checkValid(uint32 res); uint32 fetchLen(uint32 res); - void expireOldResources(void); - - void passTime(void); - // Prompts the user for the specified CD. void getCd(int cd); - int whichCd() { - return _curCd; - } + int whichCd(); void remove(int res); @@ -67,25 +71,28 @@ public: void killAll(bool wantInfo); void killAllObjects(bool wantInfo); void removeAll(void); - - Resource *_resList; - private: + File *openCluFile(uint16 fileNum); + void readCluIndex(uint16 fileNum, File *file = NULL); + void removeFromCacheList(Resource *res); + void addToCacheList(Resource *res); + void checkMemUsage(void); + Sword2Engine *_vm; int _curCd; uint32 _totalResFiles; uint32 _totalClusters; - uint32 _resTime; - // Gode generated res-id to res number/rel number conversion table uint16 *_resConvTable; + ResourceFile _resFiles[MAX_res_files]; + Resource *_resList; - char _resourceFiles[MAX_res_files][20]; - uint8 _cdTab[MAX_res_files]; // Location of each cluster. -}; + Resource *_cacheStart, *_cacheEnd; + uint32 _usedMem; // amount of used memory in bytes +}; } // End of namespace Sword2 diff --git a/sword2/sword2.cpp b/sword2/sword2.cpp index 2c1c861969..d023ebdfb5 100644 --- a/sword2/sword2.cpp +++ b/sword2/sword2.cpp @@ -289,7 +289,7 @@ int Sword2Engine::init(GameDetector &detector) { StartDialog dialog(this); - result = dialog.runModal(); + result = (dialog.runModal() != 0); // If the game is started from the beginning, the cutscene // player will kill the music for us. Otherwise, the restore |