[Scummvm-cvs-logs] CVS: scummvm/sword2 layers.cpp,1.35,1.36 resman.cpp,1.106,1.107 resman.h,1.23,1.24 sword2.cpp,1.135,1.136

Robert Göffringmann lavosspawn at users.sourceforge.net
Sun Feb 20 18:30:00 CET 2005


Update of /cvsroot/scummvm/scummvm/sword2
In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv19645/sword2

Modified Files:
	layers.cpp resman.cpp resman.h sword2.cpp 
Log Message:
removed lots of unnecessary seek()s and read()s by keeping the datafiles' index tables in memory instead of accessing them over and over again, which caused major slowdowns with cd accesses.
Also, the caching of datafiles depends on the memory usage now, not on the number of screens that the player entered in the meantime.
The old behaviour made the engine run out of memory on the PS2.

Index: layers.cpp
===================================================================
RCS file: /cvsroot/scummvm/scummvm/sword2/layers.cpp,v
retrieving revision 1.35
retrieving revision 1.36
diff -u -d -r1.35 -r1.36
--- layers.cpp	19 Feb 2005 14:02:11 -0000	1.35
+++ layers.cpp	21 Feb 2005 02:29:14 -0000	1.36
@@ -46,10 +46,6 @@
 
 	assert(res);
 
-	// The resources age every time a new room is entered.
-	_vm->_resman->passTime();
-	_vm->_resman->expireOldResources();
-
 	_vm->_sound->clearFxQueue();
 	waitForFade();
 

Index: resman.cpp
===================================================================
RCS file: /cvsroot/scummvm/scummvm/sword2/resman.cpp,v
retrieving revision 1.106
retrieving revision 1.107
diff -u -d -r1.106 -r1.107
--- resman.cpp	19 Feb 2005 14:02:12 -0000	1.106
+++ resman.cpp	21 Feb 2005 02:29:15 -0000	1.107
@@ -109,13 +109,15 @@
 	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 @@
 
 	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 @@
 		_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 @@
 
 	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;
-
-		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]);
+		File *file = openCluFile(cluFileNum);
 
-			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);
-
-		file.seek(table_offset + actual_res * 8, SEEK_SET);
-
-		uint32 pos = file.readUint32LE();
-		uint32 len = file.readUint32LE();
+		uint32 pos = _resFiles[cluFileNum].entryTab[actual_res * 2 + 0];
+		uint32 len = _resFiles[cluFileNum].entryTab[actual_res * 2 + 1];
 
-		file.seek(pos, SEEK_SET);
+		file->seek(pos, SEEK_SET);
 
 		debug(6, "res len %d", len);
 
@@ -454,7 +444,7 @@
 		_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 @@
 		}
 
 		// 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 @@
 	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 @@
 	// 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 @@
 	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 @@
 	// 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;
-		}
+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);
 
-		if (_resTime - _resList[i].refTime >= maxCacheAge) {
-			remove(i);
-			nuked++;
+			_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 @@
 	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::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 @@
 		Debug_Printf("Expelled %d resources\n", nuked);
 }
 
+int ResourceManager::whichCd(void)  {
+	return _curCd;
+}
+
 void ResourceManager::getCd(int cd) {
 	byte *textRes;
 

Index: resman.h
===================================================================
RCS file: /cvsroot/scummvm/scummvm/sword2/resman.h,v
retrieving revision 1.23
retrieving revision 1.24
diff -u -d -r1.23 -r1.24
--- resman.h	17 Jan 2005 10:57:11 -0000	1.23
+++ resman.h	21 Feb 2005 02:29:17 -0000	1.24
@@ -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 @@
 	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 @@
 	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 @@
 	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
 

Index: sword2.cpp
===================================================================
RCS file: /cvsroot/scummvm/scummvm/sword2/sword2.cpp,v
retrieving revision 1.135
retrieving revision 1.136
diff -u -d -r1.135 -r1.136
--- sword2.cpp	20 Feb 2005 15:38:48 -0000	1.135
+++ sword2.cpp	21 Feb 2005 02:29:18 -0000	1.136
@@ -289,7 +289,7 @@
 
 		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





More information about the Scummvm-git-logs mailing list