From 8ac0a92a34408cd066a6f91e7a9738eeb2d2910d Mon Sep 17 00:00:00 2001 From: xezon <4720891+xezon@users.noreply.github.com> Date: Sun, 2 Nov 2025 11:31:27 +0100 Subject: [PATCH 1/8] perf(mapcache): Simplify and improve implementation of MapCache to prevent expensive reoccurring redundant map cache reads --- .../GameEngine/Include/GameClient/MapUtil.h | 44 ++- .../Source/Common/INI/INIMapCache.cpp | 1 + .../GameEngine/Source/GameClient/MapUtil.cpp | 303 +++++++++--------- 3 files changed, 177 insertions(+), 171 deletions(-) diff --git a/GeneralsMD/Code/GameEngine/Include/GameClient/MapUtil.h b/GeneralsMD/Code/GameEngine/Include/GameClient/MapUtil.h index 94eb59ce6e..720a9eba53 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameClient/MapUtil.h +++ b/GeneralsMD/Code/GameEngine/Include/GameClient/MapUtil.h @@ -75,9 +75,10 @@ class MapMetaData AsciiString m_nameLookupTag; Region3D m_extent; Int m_numPlayers; - Bool m_isMultiplayer; + Bool m_isMultiplayer; Bool m_isOfficial; + Bool m_doesExist; ///< Flag to indicate whether the map physically exists. Should be true. UnsignedInt m_filesize; UnsignedInt m_CRC; @@ -89,10 +90,20 @@ class MapMetaData AsciiString m_fileName; }; +// TheSuperHackers @performance xezon 02/11/2025 Simplifies and improves the implementation of MapCache +// to prevent expensive reoccurring redundant map cache reads. + class MapCache : public std::map { + typedef std::set MapNameSet; + public: - MapCache() {} + MapCache() + : m_hasTriedCreatingStandardMapCacheINI(FALSE) + , m_hasLoadedStandardMapCacheINI(FALSE) + , m_hasLoadedUserMapCacheINI(FALSE) + {} + void updateCache( void ); AsciiString getMapDir() const; @@ -105,22 +116,27 @@ class MapCache : public std::map void addShippingMap(AsciiString mapName) { mapName.toLower(); m_allowedMaps.insert(mapName); } private: - Bool clearUnseenMaps( AsciiString dirName ); - void loadStandardMaps(void); - Bool loadUserMaps(void); // returns true if we needed to (re)parse a map -// Bool addMap( AsciiString dirName, AsciiString fname, WinTimeStamp timestamp, -// UnsignedInt filesize, Bool isOfficial ); ///< returns true if it had to (re)parse the map - Bool addMap( AsciiString dirName, AsciiString fname, FileInfo *fileInfo, Bool isOfficial); ///< returns true if it had to (re)parse the map - void writeCacheINI( Bool userDir ); - - static const char * m_mapCacheName; - std::map m_seen; - - std::set m_allowedMaps; + void prepareUnseenMaps(const AsciiString &mapDir); + Bool clearUnseenMaps(const AsciiString &mapDir); + void loadMapsFromMapCacheINI(const AsciiString &mapDir); + Bool loadMapsFromDisk(const AsciiString &mapDir, Bool isOfficial, Bool filterByAllowedMaps = FALSE); // returns true if we needed to (re)parse a map + Bool addMap(const AsciiString &mapDir, const AsciiString &fname, const AsciiString &lowerFname, FileInfo &fileInfo, Bool isOfficial); ///< returns true if it had to (re)parse the map + void writeCacheINI(const AsciiString &mapDir); + + static const char *m_mapCacheName; + + MapNameSet m_allowedMaps; + Bool m_hasTriedCreatingStandardMapCacheINI; + Bool m_hasLoadedStandardMapCacheINI; + Bool m_hasLoadedUserMapCacheINI; }; extern MapCache *TheMapCache; extern TechAndSupplyImages TheSupplyAndTechImageLocations; + +// TheSuperHackers @refactor xezon 28/11/2025 Refactors the map list population implementation +// by breaking it into smaller pieces to make it more maintainable. + Int populateMapListbox( GameWindow *listbox, Bool useSystemMaps, Bool isMultiplayer, AsciiString mapToSelect = AsciiString::TheEmptyString ); /// Read a list of maps from the run directory and fill in the listbox. Return the selected index Int populateMapListboxNoReset( GameWindow *listbox, Bool useSystemMaps, Bool isMultiplayer, AsciiString mapToSelect = AsciiString::TheEmptyString ); /// Read a list of maps from the run directory and fill in the listbox. Return the selected index Bool isValidMap( AsciiString mapName, Bool isMultiplayer ); /// Validate a map diff --git a/GeneralsMD/Code/GameEngine/Source/Common/INI/INIMapCache.cpp b/GeneralsMD/Code/GameEngine/Source/Common/INI/INIMapCache.cpp index b7c2fef7bd..988228cf5c 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/INI/INIMapCache.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/INI/INIMapCache.cpp @@ -131,6 +131,7 @@ void INI::parseMapCacheDefinition( INI* ini ) md.m_extent = mdr.m_extent; md.m_isOfficial = mdr.m_isOfficial != 0; + md.m_doesExist = TRUE; md.m_isMultiplayer = mdr.m_isMultiplayer != 0; md.m_numPlayers = mdr.m_numPlayers; md.m_filesize = mdr.m_filesize; diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/MapUtil.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/MapUtil.cpp index 884c988024..478d9a0cd3 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/MapUtil.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/MapUtil.cpp @@ -329,18 +329,8 @@ AsciiString MapCache::getMapExtension() const return AsciiString("map"); } -void MapCache::writeCacheINI( Bool userDir ) +void MapCache::writeCacheINI( const AsciiString &mapDir ) { - AsciiString mapDir; - if (!userDir || TheGlobalData->m_buildMapCache) - { - mapDir = getMapDir(); - } - else - { - mapDir = getUserMapDir(); - } - AsciiString filepath = mapDir; filepath.concat('\\'); @@ -352,18 +342,17 @@ void MapCache::writeCacheINI( Bool userDir ) if (fp == NULL) { return; } + fprintf(fp, "; FILE: %s /////////////////////////////////////////////////////////////\n", filepath.str()); fprintf(fp, "; This INI file is auto-generated - do not modify\n"); fprintf(fp, "; /////////////////////////////////////////////////////////////////////////////\n"); - mapDir.toLower(); MapCache::iterator it = begin(); - MapMetaData md; for (; it != end(); ++it) { if (it->first.startsWithNoCase(mapDir.str())) { - md = it->second; + const MapMetaData &md = it->second; fprintf(fp, "\nMapCache %s\n", AsciiStringToQuotedPrintable(it->first.str()).str()); fprintf(fp, " fileSize = %u\n", md.m_filesize); fprintf(fp, " fileCRC = %u\n", md.m_CRC); @@ -385,13 +374,13 @@ void MapCache::writeCacheINI( Bool userDir ) #endif Coord3D pos; - WaypointMap::iterator itw = md.m_waypoints.begin(); + WaypointMap::const_iterator itw = md.m_waypoints.begin(); for (; itw != md.m_waypoints.end(); ++itw) { pos = itw->second; fprintf(fp, " %s = X:%2.2f Y:%2.2f Z:%2.2f\n", itw->first.str(), pos.x, pos.y, pos.z); } - Coord3DList::iterator itc3d = md.m_techPositions.begin(); + Coord3DList::const_iterator itc3d = md.m_techPositions.begin(); for (; itc3d != md.m_techPositions.end(); ++itc3d) { pos = *itc3d; @@ -419,190 +408,191 @@ void MapCache::updateCache( void ) { setFPMode(); - TheFileSystem->createDirectory(getUserMapDir()); + const AsciiString mapDir = getMapDir(); + const AsciiString userMapDir = getUserMapDir(); - if (loadUserMaps()) + // Create the standard map cache if required. Is only relevant for Mod developers. + // TheSuperHackers @tweak This step is done before loading any other map caches to not poison the cached state. + if (!m_hasTriedCreatingStandardMapCacheINI) { - writeCacheINI( TRUE ); - } - loadStandardMaps(); // we shall overwrite info from matching user maps to prevent munkees from getting rowdy :) #if defined(RTS_DEBUG) - if (TheLocalFileSystem->doesFileExist(getMapDir().str())) + // only create the map cache file if "Maps" folder exists + const Bool buildMapCache = TheLocalFileSystem->doesFileExist(mapDir.str()); +#else + const Bool buildMapCache = TheGlobalData->m_buildMapCache; +#endif + if (buildMapCache) + { + const Bool isOfficial = TRUE; + const Bool filterByAllowedMaps = !m_allowedMaps.empty(); + + if (loadMapsFromDisk(mapDir, isOfficial, filterByAllowedMaps)) + { + writeCacheINI(mapDir); + } + } + m_hasTriedCreatingStandardMapCacheINI = TRUE; + } + + // Load user map cache first. + if (!m_hasLoadedUserMapCacheINI) { - // only create the map cache file if "Maps" exist - Bool wasBuildMapCache = TheGlobalData->m_buildMapCache; - TheWritableGlobalData->m_buildMapCache = true; - loadUserMaps(); - TheWritableGlobalData->m_buildMapCache = wasBuildMapCache; - writeCacheINI( FALSE ); + loadMapsFromMapCacheINI(userMapDir); + m_hasLoadedUserMapCacheINI = TRUE; + } + + // Load user maps from disk and update any discrepancies from the map cache. + if (loadMapsFromDisk(userMapDir, FALSE)) + { + writeCacheINI(userMapDir); + m_hasLoadedStandardMapCacheINI = FALSE; + } + + // Load standard maps from map cache last. + // This overwrites matching user maps to prevent munkees getting rowdy :) + if (!m_hasLoadedStandardMapCacheINI) + { + loadMapsFromMapCacheINI(mapDir); + m_hasLoadedStandardMapCacheINI = TRUE; } -#endif } -Bool MapCache::clearUnseenMaps( AsciiString dirName ) +void MapCache::prepareUnseenMaps( const AsciiString &mapDir ) { - dirName.toLower(); - Bool erasedSomething = FALSE; + MapCache::iterator it = begin(); + for (; it != end(); ++it) + { + const AsciiString &mapName = it->first; + + if (mapName.startsWithNoCase(mapDir.str())) + { + it->second.m_doesExist = FALSE; + } + } +} - std::map::iterator it = m_seen.begin(); +Bool MapCache::clearUnseenMaps( const AsciiString &mapDir ) +{ + Bool erasedSomething = FALSE; - for (; it != m_seen.end(); ++it) + MapCache::iterator it = begin(); + while (it != end()) { - AsciiString mapName = it->first; - if (it->second == FALSE && mapName.startsWithNoCase(dirName.str())) + const AsciiString &mapName = it->first; + const MapMetaData &mapData = it->second; + + if (mapName.startsWithNoCase(mapDir.str()) && !mapData.m_doesExist) { - // not seen in the dir - clear it out. - erase(mapName); + it = erase(it); erasedSomething = TRUE; } + else + { + ++it; + } } + return erasedSomething; } -void MapCache::loadStandardMaps(void) +void MapCache::loadMapsFromMapCacheINI( const AsciiString &mapDir ) { INI ini; AsciiString fname; - fname.format("%s\\%s", getMapDir().str(), m_mapCacheName); -#if defined(RTS_DEBUG) - File *fp = TheFileSystem->openFile(fname.str(), File::READ); - if (fp != NULL) + fname.format("%s\\%s", mapDir.str(), m_mapCacheName); + + if (TheFileSystem->doesFileExist(fname.str())) { - fp->close(); - fp = NULL; -#endif ini.load( fname, INI_LOAD_OVERWRITE, NULL ); -#if defined(RTS_DEBUG) } -#endif } -Bool MapCache::loadUserMaps() +Bool MapCache::loadMapsFromDisk( const AsciiString &mapDir, Bool isOfficial, Bool filterByAllowedMaps ) { - // Read in map list from disk - AsciiString mapDir; - if (TheGlobalData->m_buildMapCache) - { - mapDir = getMapDir(); - } - else - { - mapDir = getUserMapDir(); - - INI ini; - AsciiString fname; - fname.format("%s\\%s", mapDir.str(), m_mapCacheName); - File *fp = TheFileSystem->openFile(fname.str(), File::READ); - if (fp) - { - fp->close(); - ini.load( fname, INI_LOAD_OVERWRITE, NULL ); - } - - } - - // mark all as unseen - m_seen.clear(); - MapCache::iterator it = begin(); - for (; it != end(); ++it) - { - m_seen[it->first] = FALSE; - } + prepareUnseenMaps(mapDir); - FilenameList filenameList; - FilenameListIter iter; + FilenameList filepathList; + FilenameListIter filepathIt; AsciiString toplevelPattern; toplevelPattern.format("%s\\", mapDir.str()); - Bool parsedAMap = FALSE; + Bool mapListChanged = FALSE; AsciiString filenamepattern; filenamepattern.format("*.%s", getMapExtension().str()); - TheFileSystem->getFileListInDirectory(toplevelPattern, filenamepattern, filenameList, TRUE); + TheFileSystem->getFileListInDirectory(toplevelPattern, filenamepattern, filepathList, TRUE); - iter = filenameList.begin(); + filepathIt = filepathList.begin(); - for (; iter != filenameList.end(); ++iter) { + for (; filepathIt != filepathList.end(); ++filepathIt) + { FileInfo fileInfo; - AsciiString tempfilename; - tempfilename = (*iter); - tempfilename.toLower(); + AsciiString filepathLower = *filepathIt; + filepathLower.toLower(); - const char *s = tempfilename.reverseFind('\\'); - if (!s) + const char *szFilenameLower = filepathLower.reverseFind('\\'); + if (!szFilenameLower) { DEBUG_CRASH(("Couldn't find \\ in map name!")); + continue; } - else + + AsciiString endingStr; + AsciiString filenameLower = szFilenameLower+1; + filenameLower.truncateBy(strlen(mapExtension)); + + if (filterByAllowedMaps && m_allowedMaps.find(filenameLower) == m_allowedMaps.end()) { - AsciiString endingStr; - AsciiString fname = s+1; - fname.truncateBy(strlen(mapExtension)); + DEBUG_CRASH(("Map '%s' has been filtered out", filenameLower.str())); + continue; + } - endingStr.format("%s\\%s%s", fname.str(), fname.str(), mapExtension); + endingStr.format("%s\\%s%s", filenameLower.str(), filenameLower.str(), mapExtension); - Bool skipMap = FALSE; - if (TheGlobalData->m_buildMapCache) - { - std::set::const_iterator sit = m_allowedMaps.find(fname); - if (m_allowedMaps.size() != 0 && sit == m_allowedMaps.end()) - { - //DEBUG_LOG(("Skipping map: '%s'", fname.str())); - skipMap = TRUE; - } - else - { - //DEBUG_LOG(("Parsing map: '%s'", fname.str())); - } - } + if (!filepathLower.endsWithNoCase(endingStr.str())) + { + DEBUG_CRASH(("Found map '%s' in wrong spot (%s)", filenameLower.str(), filepathLower.str())); + continue; + } - if (!skipMap) - { - if (!tempfilename.endsWithNoCase(endingStr.str())) - { - DEBUG_CRASH(("Found map '%s' in wrong spot (%s)", fname.str(), tempfilename.str())); - } - else - { - if (TheFileSystem->getFileInfo(tempfilename, &fileInfo)) { - m_seen[tempfilename] = TRUE; - parsedAMap |= addMap(mapDir, *iter, &fileInfo, TheGlobalData->m_buildMapCache); - } else { - DEBUG_CRASH(("Could not get file info for map %s", (*iter).str())); - } - } - } + if (!TheFileSystem->getFileInfo(*filepathIt, &fileInfo)) + { + DEBUG_CRASH(("Could not get file info for map %s", filepathIt->str())); + continue; + } + + MapCache::iterator it = find(filepathLower); + if (it != end()) + { + it->second.m_doesExist = TRUE; + } + else + { + mapListChanged |= addMap(mapDir, *filepathIt, filepathLower, fileInfo, isOfficial); } } - // clean out unseen maps if (clearUnseenMaps(mapDir)) - return TRUE; + { + mapListChanged = TRUE; + } - return parsedAMap; + return mapListChanged; } -//Bool MapCache::addMap( AsciiString dirName, AsciiString fname, WinTimeStamp timestamp, UnsignedInt filesize, Bool isOfficial ) -Bool MapCache::addMap( AsciiString dirName, AsciiString fname, FileInfo *fileInfo, Bool isOfficial) +Bool MapCache::addMap( + const AsciiString &mapDir, + const AsciiString &fname, + const AsciiString &lowerFname, + FileInfo &fileInfo, + Bool isOfficial) { - if (fileInfo == NULL) { - return FALSE; - } - - AsciiString lowerFname; - lowerFname = fname; - lowerFname.toLower(); MapCache::iterator it = find(lowerFname); - - MapMetaData md; - UnsignedInt filesize = fileInfo->sizeLow; - if (it != end()) { - // Found the map in our cache. Check to see if it has changed. - md = it->second; + // Found the map in our cache. Check to see if it has changed. + const MapMetaData& md = it->second; - if ((md.m_filesize == filesize) && - (md.m_CRC != 0)) + if (md.m_filesize == fileInfo.sizeLow && md.m_CRC != 0) { // Force a lookup so that we don't display the English localization in all builds. if (md.m_nameLookupTag.isEmpty()) @@ -633,9 +623,9 @@ Bool MapCache::addMap( AsciiString dirName, AsciiString fname, FileInfo *fileInf return FALSE; // OK, it checks out. } DEBUG_LOG(("%s didn't match file in MapCache", fname.str())); - DEBUG_LOG(("size: %d / %d", filesize, md.m_filesize)); - DEBUG_LOG(("time1: %d / %d", fileInfo->timestampHigh, md.m_timestamp.m_highTimeStamp)); - DEBUG_LOG(("time2: %d / %d", fileInfo->timestampLow, md.m_timestamp.m_lowTimeStamp)); + DEBUG_LOG(("size: %d / %d", fileInfo.sizeLow, md.m_filesize)); + DEBUG_LOG(("time1: %d / %d", fileInfo.timestampHigh, md.m_timestamp.m_highTimeStamp)); + DEBUG_LOG(("time2: %d / %d", fileInfo.timestampLow, md.m_timestamp.m_lowTimeStamp)); // DEBUG_LOG(("size: %d / %d", filesize, md.m_filesize)); // DEBUG_LOG(("time1: %d / %d", timestamp.m_highTimeStamp, md.m_timestamp.m_highTimeStamp)); // DEBUG_LOG(("time2: %d / %d", timestamp.m_lowTimeStamp, md.m_timestamp.m_lowTimeStamp)); @@ -646,14 +636,16 @@ Bool MapCache::addMap( AsciiString dirName, AsciiString fname, FileInfo *fileInf loadMap(fname); // Just load for querying the data, since we aren't playing this map. // The map is now loaded. Pick out what we need. + MapMetaData md; md.m_fileName = lowerFname; - md.m_filesize = filesize; + md.m_filesize = fileInfo.sizeLow; md.m_isOfficial = isOfficial; + md.m_doesExist = TRUE; md.m_waypoints.update(); md.m_numPlayers = md.m_waypoints.m_numStartSpots; md.m_isMultiplayer = (md.m_numPlayers >= 2); - md.m_timestamp.m_highTimeStamp = fileInfo->timestampHigh; - md.m_timestamp.m_lowTimeStamp = fileInfo->timestampLow; + md.m_timestamp.m_highTimeStamp = fileInfo.timestampHigh; + md.m_timestamp.m_lowTimeStamp = fileInfo.timestampLow; md.m_supplyPositions = m_supplyPositions; md.m_techPositions = m_techPositions; md.m_CRC = calcCRC(fname); @@ -661,6 +653,7 @@ Bool MapCache::addMap( AsciiString dirName, AsciiString fname, FileInfo *fileInf Bool exists = false; AsciiString munkee = worldDict.getAsciiString(TheKey_mapName, &exists); md.m_nameLookupTag = munkee; + if (!exists || munkee.isEmpty()) { DEBUG_LOG(("Missing TheKey_mapName!")); @@ -678,7 +671,7 @@ Bool MapCache::addMap( AsciiString dirName, AsciiString fname, FileInfo *fileInf else { AsciiString stringFileName; - stringFileName.format("%s\\%s", dirName.str(), fname.str()); + stringFileName.format("%s\\%s", mapDir.str(), fname.str()); stringFileName.truncateBy(4); stringFileName.concat("\\map.str"); TheGameText->initMapStringFile(stringFileName); @@ -733,24 +726,20 @@ Bool WouldMapTransfer( const AsciiString& mapName ) } //------------------------------------------------------------------------------------------------- -// TheSuperHackers @refactor Massively refactors the map list population function by breaking it into smaller pieces. - typedef std::set > MapNameList; typedef std::map MapDisplayToFileNameList; static void buildMapListForNumPlayers(MapNameList &outMapNames, MapDisplayToFileNameList &outFileNames, Int numPlayers) { - DEBUG_LOG(("Adding maps with %d players", numPlayers)); MapCache::iterator it = TheMapCache->begin(); for (; it != TheMapCache->end(); ++it) { - const MapMetaData &md = it->second; - if (md.m_numPlayers == numPlayers) + const MapMetaData &mapData = it->second; + if (mapData.m_numPlayers == numPlayers) { outMapNames.insert(it->second.m_displayName); outFileNames[it->second.m_displayName] = it->first; - DEBUG_LOG(("Adding map '%s' to temp cache.", it->first.str())); } } } From abe3010a69f2b1d073f414a5f0136af5eb7fc250 Mon Sep 17 00:00:00 2001 From: xezon <4720891+xezon@users.noreply.github.com> Date: Sun, 2 Nov 2025 12:05:57 +0100 Subject: [PATCH 2/8] Fix indentation --- .../Code/GameEngine/Source/GameClient/MapUtil.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/MapUtil.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/MapUtil.cpp index 478d9a0cd3..c76fa6d1ae 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/MapUtil.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/MapUtil.cpp @@ -423,13 +423,13 @@ void MapCache::updateCache( void ) #endif if (buildMapCache) { - const Bool isOfficial = TRUE; - const Bool filterByAllowedMaps = !m_allowedMaps.empty(); + const Bool isOfficial = TRUE; + const Bool filterByAllowedMaps = !m_allowedMaps.empty(); - if (loadMapsFromDisk(mapDir, isOfficial, filterByAllowedMaps)) - { - writeCacheINI(mapDir); - } + if (loadMapsFromDisk(mapDir, isOfficial, filterByAllowedMaps)) + { + writeCacheINI(mapDir); + } } m_hasTriedCreatingStandardMapCacheINI = TRUE; } From 510edacd94873a26846d09b4d2ab9ca3b86ea3f4 Mon Sep 17 00:00:00 2001 From: xezon <4720891+xezon@users.noreply.github.com> Date: Sun, 2 Nov 2025 12:16:49 +0100 Subject: [PATCH 3/8] Simplify function loadMapsFromDisk --- .../Code/GameEngine/Source/GameClient/MapUtil.cpp | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/MapUtil.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/MapUtil.cpp index c76fa6d1ae..ce472ff873 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/MapUtil.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/MapUtil.cpp @@ -560,15 +560,7 @@ Bool MapCache::loadMapsFromDisk( const AsciiString &mapDir, Bool isOfficial, Boo continue; } - MapCache::iterator it = find(filepathLower); - if (it != end()) - { - it->second.m_doesExist = TRUE; - } - else - { - mapListChanged |= addMap(mapDir, *filepathIt, filepathLower, fileInfo, isOfficial); - } + mapListChanged |= addMap(mapDir, *filepathIt, filepathLower, fileInfo, isOfficial); } if (clearUnseenMaps(mapDir)) @@ -619,6 +611,9 @@ Bool MapCache::addMap( (*this)[lowerFname].m_displayName.concat(extension); } } + + it->second.m_doesExist = TRUE; + // DEBUG_LOG(("MapCache::addMap - found match for map %s", lowerFname.str())); return FALSE; // OK, it checks out. } From 40a23257c25283bbe55413d813a2abac0aed0ba0 Mon Sep 17 00:00:00 2001 From: xezon <4720891+xezon@users.noreply.github.com> Date: Sun, 2 Nov 2025 12:25:22 +0100 Subject: [PATCH 4/8] Fix VC6 compile error --- .../Code/GameEngine/Source/GameClient/MapUtil.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/MapUtil.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/MapUtil.cpp index ce472ff873..e9c1c964b1 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/MapUtil.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/MapUtil.cpp @@ -478,18 +478,19 @@ Bool MapCache::clearUnseenMaps( const AsciiString &mapDir ) MapCache::iterator it = begin(); while (it != end()) { + MapCache::iterator next = it; + ++next; + const AsciiString &mapName = it->first; const MapMetaData &mapData = it->second; if (mapName.startsWithNoCase(mapDir.str()) && !mapData.m_doesExist) { - it = erase(it); + erase(it); erasedSomething = TRUE; } - else - { - ++it; - } + + it = next; } return erasedSomething; From c1be4ed7761a24a574baa627571be6c248159e97 Mon Sep 17 00:00:00 2001 From: xezon <4720891+xezon@users.noreply.github.com> Date: Sun, 2 Nov 2025 13:00:35 +0100 Subject: [PATCH 5/8] Protect m_mapCacheName from changing --- GeneralsMD/Code/GameEngine/Include/GameClient/MapUtil.h | 2 +- GeneralsMD/Code/GameEngine/Source/GameClient/MapUtil.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/GeneralsMD/Code/GameEngine/Include/GameClient/MapUtil.h b/GeneralsMD/Code/GameEngine/Include/GameClient/MapUtil.h index 720a9eba53..10a380ae9b 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameClient/MapUtil.h +++ b/GeneralsMD/Code/GameEngine/Include/GameClient/MapUtil.h @@ -123,7 +123,7 @@ class MapCache : public std::map Bool addMap(const AsciiString &mapDir, const AsciiString &fname, const AsciiString &lowerFname, FileInfo &fileInfo, Bool isOfficial); ///< returns true if it had to (re)parse the map void writeCacheINI(const AsciiString &mapDir); - static const char *m_mapCacheName; + static const char *const m_mapCacheName; MapNameSet m_allowedMaps; Bool m_hasTriedCreatingStandardMapCacheINI; diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/MapUtil.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/MapUtil.cpp index e9c1c964b1..f549d38bdc 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/MapUtil.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/MapUtil.cpp @@ -310,7 +310,7 @@ void WaypointMap::update( void ) m_numStartSpots = max(1, m_numStartSpots); } -const char * MapCache::m_mapCacheName = "MapCache.ini"; +const char *const MapCache::m_mapCacheName = "MapCache.ini"; AsciiString MapCache::getMapDir() const { From 46cd3145929e6dd17d19647fc63eb74431897bc7 Mon Sep 17 00:00:00 2001 From: xezon <4720891+xezon@users.noreply.github.com> Date: Mon, 10 Nov 2025 18:44:12 +0100 Subject: [PATCH 6/8] Invert meaning of booleans in MapCache class --- .../Code/GameEngine/Include/GameClient/MapUtil.h | 12 ++++++------ .../Code/GameEngine/Source/GameClient/MapUtil.cpp | 14 +++++++------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/GeneralsMD/Code/GameEngine/Include/GameClient/MapUtil.h b/GeneralsMD/Code/GameEngine/Include/GameClient/MapUtil.h index 10a380ae9b..408333c479 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameClient/MapUtil.h +++ b/GeneralsMD/Code/GameEngine/Include/GameClient/MapUtil.h @@ -99,9 +99,9 @@ class MapCache : public std::map public: MapCache() - : m_hasTriedCreatingStandardMapCacheINI(FALSE) - , m_hasLoadedStandardMapCacheINI(FALSE) - , m_hasLoadedUserMapCacheINI(FALSE) + : m_doCreateStandardMapCacheINI(TRUE) + , m_doLoadStandardMapCacheINI(TRUE) + , m_doLoadUserMapCacheINI(TRUE) {} void updateCache( void ); @@ -126,9 +126,9 @@ class MapCache : public std::map static const char *const m_mapCacheName; MapNameSet m_allowedMaps; - Bool m_hasTriedCreatingStandardMapCacheINI; - Bool m_hasLoadedStandardMapCacheINI; - Bool m_hasLoadedUserMapCacheINI; + Bool m_doCreateStandardMapCacheINI; + Bool m_doLoadStandardMapCacheINI; + Bool m_doLoadUserMapCacheINI; }; extern MapCache *TheMapCache; diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/MapUtil.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/MapUtil.cpp index f549d38bdc..f892e3e1b6 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/MapUtil.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/MapUtil.cpp @@ -413,7 +413,7 @@ void MapCache::updateCache( void ) // Create the standard map cache if required. Is only relevant for Mod developers. // TheSuperHackers @tweak This step is done before loading any other map caches to not poison the cached state. - if (!m_hasTriedCreatingStandardMapCacheINI) + if (m_doCreateStandardMapCacheINI) { #if defined(RTS_DEBUG) // only create the map cache file if "Maps" folder exists @@ -431,29 +431,29 @@ void MapCache::updateCache( void ) writeCacheINI(mapDir); } } - m_hasTriedCreatingStandardMapCacheINI = TRUE; + m_doCreateStandardMapCacheINI = FALSE; } // Load user map cache first. - if (!m_hasLoadedUserMapCacheINI) + if (m_doLoadUserMapCacheINI) { loadMapsFromMapCacheINI(userMapDir); - m_hasLoadedUserMapCacheINI = TRUE; + m_doLoadUserMapCacheINI = FALSE; } // Load user maps from disk and update any discrepancies from the map cache. if (loadMapsFromDisk(userMapDir, FALSE)) { writeCacheINI(userMapDir); - m_hasLoadedStandardMapCacheINI = FALSE; + m_doLoadStandardMapCacheINI = TRUE; } // Load standard maps from map cache last. // This overwrites matching user maps to prevent munkees getting rowdy :) - if (!m_hasLoadedStandardMapCacheINI) + if (m_doLoadStandardMapCacheINI) { loadMapsFromMapCacheINI(mapDir); - m_hasLoadedStandardMapCacheINI = TRUE; + m_doLoadStandardMapCacheINI = FALSE; } } From e3a1af9b1a7953aecc0e963e4af2ed674ed261ea Mon Sep 17 00:00:00 2001 From: xezon <4720891+xezon@users.noreply.github.com> Date: Mon, 10 Nov 2025 18:45:48 +0100 Subject: [PATCH 7/8] Rename munkee --- GeneralsMD/Code/GameEngine/Source/GameClient/MapUtil.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/MapUtil.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/MapUtil.cpp index f892e3e1b6..6c1a0ca28a 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/MapUtil.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/MapUtil.cpp @@ -647,10 +647,10 @@ Bool MapCache::addMap( md.m_CRC = calcCRC(fname); Bool exists = false; - AsciiString munkee = worldDict.getAsciiString(TheKey_mapName, &exists); - md.m_nameLookupTag = munkee; + AsciiString nameLookupTag = worldDict.getAsciiString(TheKey_mapName, &exists); + md.m_nameLookupTag = nameLookupTag; - if (!exists || munkee.isEmpty()) + if (!exists || nameLookupTag.isEmpty()) { DEBUG_LOG(("Missing TheKey_mapName!")); AsciiString tempdisplayname; @@ -671,7 +671,7 @@ Bool MapCache::addMap( stringFileName.truncateBy(4); stringFileName.concat("\\map.str"); TheGameText->initMapStringFile(stringFileName); - md.m_displayName = TheGameText->fetch(munkee); + md.m_displayName = TheGameText->fetch(nameLookupTag); if (md.m_numPlayers >= 2) { UnicodeString extension; From e609fce88c00646daabac2ace2058823b255289f Mon Sep 17 00:00:00 2001 From: xezon <4720891+xezon@users.noreply.github.com> Date: Mon, 10 Nov 2025 19:17:28 +0100 Subject: [PATCH 8/8] Replicate in Generals --- .../GameEngine/Include/GameClient/MapUtil.h | 44 ++- .../Source/Common/INI/INIMapCache.cpp | 1 + .../GameEngine/Source/GameClient/MapUtil.cpp | 309 +++++++++--------- 3 files changed, 178 insertions(+), 176 deletions(-) diff --git a/Generals/Code/GameEngine/Include/GameClient/MapUtil.h b/Generals/Code/GameEngine/Include/GameClient/MapUtil.h index 4a55b63804..5db7a1ede1 100644 --- a/Generals/Code/GameEngine/Include/GameClient/MapUtil.h +++ b/Generals/Code/GameEngine/Include/GameClient/MapUtil.h @@ -75,9 +75,10 @@ class MapMetaData AsciiString m_nameLookupTag; Region3D m_extent; Int m_numPlayers; - Bool m_isMultiplayer; + Bool m_isMultiplayer; Bool m_isOfficial; + Bool m_doesExist; ///< Flag to indicate whether the map physically exists. Should be true. UnsignedInt m_filesize; UnsignedInt m_CRC; @@ -89,10 +90,20 @@ class MapMetaData AsciiString m_fileName; }; +// TheSuperHackers @performance xezon 02/11/2025 Simplifies and improves the implementation of MapCache +// to prevent expensive reoccurring redundant map cache reads. + class MapCache : public std::map { + typedef std::set MapNameSet; + public: - MapCache() {} + MapCache() + : m_doCreateStandardMapCacheINI(TRUE) + , m_doLoadStandardMapCacheINI(TRUE) + , m_doLoadUserMapCacheINI(TRUE) + {} + void updateCache( void ); AsciiString getMapDir() const; @@ -105,22 +116,27 @@ class MapCache : public std::map void addShippingMap(AsciiString mapName) { mapName.toLower(); m_allowedMaps.insert(mapName); } private: - Bool clearUnseenMaps( AsciiString dirName ); - void loadStandardMaps(void); - Bool loadUserMaps(void); // returns true if we needed to (re)parse a map -// Bool addMap( AsciiString dirName, AsciiString fname, WinTimeStamp timestamp, -// UnsignedInt filesize, Bool isOfficial ); ///< returns true if it had to (re)parse the map - Bool addMap( AsciiString dirName, AsciiString fname, FileInfo *fileInfo, Bool isOfficial); ///< returns true if it had to (re)parse the map - void writeCacheINI( Bool userDir ); - - static const char * m_mapCacheName; - std::map m_seen; - - std::set m_allowedMaps; + void prepareUnseenMaps(const AsciiString &mapDir); + Bool clearUnseenMaps(const AsciiString &mapDir); + void loadMapsFromMapCacheINI(const AsciiString &mapDir); + Bool loadMapsFromDisk(const AsciiString &mapDir, Bool isOfficial, Bool filterByAllowedMaps = FALSE); // returns true if we needed to (re)parse a map + Bool addMap(const AsciiString &mapDir, const AsciiString &fname, const AsciiString &lowerFname, FileInfo &fileInfo, Bool isOfficial); ///< returns true if it had to (re)parse the map + void writeCacheINI(const AsciiString &mapDir); + + static const char *const m_mapCacheName; + + MapNameSet m_allowedMaps; + Bool m_doCreateStandardMapCacheINI; + Bool m_doLoadStandardMapCacheINI; + Bool m_doLoadUserMapCacheINI; }; extern MapCache *TheMapCache; extern TechAndSupplyImages TheSupplyAndTechImageLocations; + +// TheSuperHackers @refactor xezon 28/11/2025 Refactors the map list population implementation +// by breaking it into smaller pieces to make it more maintainable. + Int populateMapListbox( GameWindow *listbox, Bool useSystemMaps, Bool isMultiplayer, AsciiString mapToSelect = AsciiString::TheEmptyString ); /// Read a list of maps from the run directory and fill in the listbox. Return the selected index Int populateMapListboxNoReset( GameWindow *listbox, Bool useSystemMaps, Bool isMultiplayer, AsciiString mapToSelect = AsciiString::TheEmptyString ); /// Read a list of maps from the run directory and fill in the listbox. Return the selected index Bool isValidMap( AsciiString mapName, Bool isMultiplayer ); /// Validate a map diff --git a/Generals/Code/GameEngine/Source/Common/INI/INIMapCache.cpp b/Generals/Code/GameEngine/Source/Common/INI/INIMapCache.cpp index 7b23e4caf1..9141b62774 100644 --- a/Generals/Code/GameEngine/Source/Common/INI/INIMapCache.cpp +++ b/Generals/Code/GameEngine/Source/Common/INI/INIMapCache.cpp @@ -131,6 +131,7 @@ void INI::parseMapCacheDefinition( INI* ini ) md.m_extent = mdr.m_extent; md.m_isOfficial = mdr.m_isOfficial != 0; + md.m_doesExist = TRUE; md.m_isMultiplayer = mdr.m_isMultiplayer != 0; md.m_numPlayers = mdr.m_numPlayers; md.m_filesize = mdr.m_filesize; diff --git a/Generals/Code/GameEngine/Source/GameClient/MapUtil.cpp b/Generals/Code/GameEngine/Source/GameClient/MapUtil.cpp index 3e53947f38..80c37925bd 100644 --- a/Generals/Code/GameEngine/Source/GameClient/MapUtil.cpp +++ b/Generals/Code/GameEngine/Source/GameClient/MapUtil.cpp @@ -310,7 +310,7 @@ void WaypointMap::update( void ) m_numStartSpots = max(1, m_numStartSpots); } -const char * MapCache::m_mapCacheName = "MapCache.ini"; +const char *const MapCache::m_mapCacheName = "MapCache.ini"; AsciiString MapCache::getMapDir() const { @@ -329,18 +329,8 @@ AsciiString MapCache::getMapExtension() const return AsciiString("map"); } -void MapCache::writeCacheINI( Bool userDir ) +void MapCache::writeCacheINI( const AsciiString &mapDir ) { - AsciiString mapDir; - if (!userDir || TheGlobalData->m_buildMapCache) - { - mapDir = getMapDir(); - } - else - { - mapDir = getUserMapDir(); - } - AsciiString filepath = mapDir; filepath.concat('\\'); @@ -352,18 +342,17 @@ void MapCache::writeCacheINI( Bool userDir ) if (fp == NULL) { return; } + fprintf(fp, "; FILE: %s /////////////////////////////////////////////////////////////\n", filepath.str()); fprintf(fp, "; This INI file is auto-generated - do not modify\n"); fprintf(fp, "; /////////////////////////////////////////////////////////////////////////////\n"); - mapDir.toLower(); MapCache::iterator it = begin(); - MapMetaData md; for (; it != end(); ++it) { if (it->first.startsWithNoCase(mapDir.str())) { - md = it->second; + const MapMetaData &md = it->second; fprintf(fp, "\nMapCache %s\n", AsciiStringToQuotedPrintable(it->first.str()).str()); fprintf(fp, " fileSize = %u\n", md.m_filesize); fprintf(fp, " fileCRC = %u\n", md.m_CRC); @@ -385,13 +374,13 @@ void MapCache::writeCacheINI( Bool userDir ) #endif Coord3D pos; - WaypointMap::iterator itw = md.m_waypoints.begin(); + WaypointMap::const_iterator itw = md.m_waypoints.begin(); for (; itw != md.m_waypoints.end(); ++itw) { pos = itw->second; fprintf(fp, " %s = X:%2.2f Y:%2.2f Z:%2.2f\n", itw->first.str(), pos.x, pos.y, pos.z); } - Coord3DList::iterator itc3d = md.m_techPositions.begin(); + Coord3DList::const_iterator itc3d = md.m_techPositions.begin(); for (; itc3d != md.m_techPositions.end(); ++itc3d) { pos = *itc3d; @@ -419,190 +408,184 @@ void MapCache::updateCache( void ) { setFPMode(); - TheFileSystem->createDirectory(getUserMapDir()); + const AsciiString mapDir = getMapDir(); + const AsciiString userMapDir = getUserMapDir(); - if (loadUserMaps()) + // Create the standard map cache if required. Is only relevant for Mod developers. + // TheSuperHackers @tweak This step is done before loading any other map caches to not poison the cached state. + if (m_doCreateStandardMapCacheINI) { - writeCacheINI( TRUE ); - } - loadStandardMaps(); // we shall overwrite info from matching user maps to prevent munkees from getting rowdy :) #if defined(RTS_DEBUG) - if (TheLocalFileSystem->doesFileExist(getMapDir().str())) + // only create the map cache file if "Maps" folder exists + const Bool buildMapCache = TheLocalFileSystem->doesFileExist(mapDir.str()); +#else + const Bool buildMapCache = TheGlobalData->m_buildMapCache; +#endif + if (buildMapCache) + { + const Bool isOfficial = TRUE; + const Bool filterByAllowedMaps = !m_allowedMaps.empty(); + + if (loadMapsFromDisk(mapDir, isOfficial, filterByAllowedMaps)) + { + writeCacheINI(mapDir); + } + } + m_doCreateStandardMapCacheINI = FALSE; + } + + // Load user map cache first. + if (m_doLoadUserMapCacheINI) { - // only create the map cache file if "Maps" exist - Bool wasBuildMapCache = TheGlobalData->m_buildMapCache; - TheWritableGlobalData->m_buildMapCache = true; - loadUserMaps(); - TheWritableGlobalData->m_buildMapCache = wasBuildMapCache; - writeCacheINI( FALSE ); + loadMapsFromMapCacheINI(userMapDir); + m_doLoadUserMapCacheINI = FALSE; + } + + // Load user maps from disk and update any discrepancies from the map cache. + if (loadMapsFromDisk(userMapDir, FALSE)) + { + writeCacheINI(userMapDir); + m_doLoadStandardMapCacheINI = TRUE; + } + + // Load standard maps from map cache last. + // This overwrites matching user maps to prevent munkees getting rowdy :) + if (m_doLoadStandardMapCacheINI) + { + loadMapsFromMapCacheINI(mapDir); + m_doLoadStandardMapCacheINI = FALSE; } -#endif } -Bool MapCache::clearUnseenMaps( AsciiString dirName ) +void MapCache::prepareUnseenMaps( const AsciiString &mapDir ) { - dirName.toLower(); - Bool erasedSomething = FALSE; + MapCache::iterator it = begin(); + for (; it != end(); ++it) + { + const AsciiString &mapName = it->first; - std::map::iterator it = m_seen.begin(); + if (mapName.startsWithNoCase(mapDir.str())) + { + it->second.m_doesExist = FALSE; + } + } +} + +Bool MapCache::clearUnseenMaps( const AsciiString &mapDir ) +{ + Bool erasedSomething = FALSE; - for (; it != m_seen.end(); ++it) + MapCache::iterator it = begin(); + while (it != end()) { - AsciiString mapName = it->first; - if (it->second == FALSE && mapName.startsWithNoCase(dirName.str())) + MapCache::iterator next = it; + ++next; + + const AsciiString &mapName = it->first; + const MapMetaData &mapData = it->second; + + if (mapName.startsWithNoCase(mapDir.str()) && !mapData.m_doesExist) { - // not seen in the dir - clear it out. - erase(mapName); + erase(it); erasedSomething = TRUE; } + + it = next; } + return erasedSomething; } -void MapCache::loadStandardMaps(void) +void MapCache::loadMapsFromMapCacheINI( const AsciiString &mapDir ) { INI ini; AsciiString fname; - fname.format("%s\\%s", getMapDir().str(), m_mapCacheName); -#if defined(RTS_DEBUG) - File *fp = TheFileSystem->openFile(fname.str(), File::READ); - if (fp != NULL) + fname.format("%s\\%s", mapDir.str(), m_mapCacheName); + + if (TheFileSystem->doesFileExist(fname.str())) { - fp->close(); - fp = NULL; -#endif ini.load( fname, INI_LOAD_OVERWRITE, NULL ); -#if defined(RTS_DEBUG) } -#endif } -Bool MapCache::loadUserMaps() +Bool MapCache::loadMapsFromDisk( const AsciiString &mapDir, Bool isOfficial, Bool filterByAllowedMaps ) { - // Read in map list from disk - AsciiString mapDir; - if (TheGlobalData->m_buildMapCache) - { - mapDir = getMapDir(); - } - else - { - mapDir = getUserMapDir(); - - INI ini; - AsciiString fname; - fname.format("%s\\%s", mapDir.str(), m_mapCacheName); - File *fp = TheFileSystem->openFile(fname.str(), File::READ); - if (fp) - { - fp->close(); - ini.load( fname, INI_LOAD_OVERWRITE, NULL ); - } - - } + prepareUnseenMaps(mapDir); - // mark all as unseen - m_seen.clear(); - MapCache::iterator it = begin(); - for (; it != end(); ++it) - { - m_seen[it->first] = FALSE; - } - - FilenameList filenameList; - FilenameListIter iter; + FilenameList filepathList; + FilenameListIter filepathIt; AsciiString toplevelPattern; toplevelPattern.format("%s\\", mapDir.str()); - Bool parsedAMap = FALSE; + Bool mapListChanged = FALSE; AsciiString filenamepattern; filenamepattern.format("*.%s", getMapExtension().str()); - TheFileSystem->getFileListInDirectory(toplevelPattern, filenamepattern, filenameList, TRUE); + TheFileSystem->getFileListInDirectory(toplevelPattern, filenamepattern, filepathList, TRUE); - iter = filenameList.begin(); + filepathIt = filepathList.begin(); - for (; iter != filenameList.end(); ++iter) { + for (; filepathIt != filepathList.end(); ++filepathIt) + { FileInfo fileInfo; - AsciiString tempfilename; - tempfilename = (*iter); - tempfilename.toLower(); + AsciiString filepathLower = *filepathIt; + filepathLower.toLower(); - const char *s = tempfilename.reverseFind('\\'); - if (!s) + const char *szFilenameLower = filepathLower.reverseFind('\\'); + if (!szFilenameLower) { DEBUG_CRASH(("Couldn't find \\ in map name!")); + continue; } - else + + AsciiString endingStr; + AsciiString filenameLower = szFilenameLower+1; + filenameLower.truncateBy(strlen(mapExtension)); + + if (filterByAllowedMaps && m_allowedMaps.find(filenameLower) == m_allowedMaps.end()) { - AsciiString endingStr; - AsciiString fname = s+1; - fname.truncateBy(strlen(mapExtension)); + DEBUG_CRASH(("Map '%s' has been filtered out", filenameLower.str())); + continue; + } - endingStr.format("%s\\%s%s", fname.str(), fname.str(), mapExtension); + endingStr.format("%s\\%s%s", filenameLower.str(), filenameLower.str(), mapExtension); - Bool skipMap = FALSE; - if (TheGlobalData->m_buildMapCache) - { - std::set::const_iterator sit = m_allowedMaps.find(fname); - if (m_allowedMaps.size() != 0 && sit == m_allowedMaps.end()) - { - //DEBUG_LOG(("Skipping map: '%s'", fname.str())); - skipMap = TRUE; - } - else - { - //DEBUG_LOG(("Parsing map: '%s'", fname.str())); - } - } + if (!filepathLower.endsWithNoCase(endingStr.str())) + { + DEBUG_CRASH(("Found map '%s' in wrong spot (%s)", filenameLower.str(), filepathLower.str())); + continue; + } - if (!skipMap) - { - if (!tempfilename.endsWithNoCase(endingStr.str())) - { - DEBUG_CRASH(("Found map '%s' in wrong spot (%s)", fname.str(), tempfilename.str())); - } - else - { - if (TheFileSystem->getFileInfo(tempfilename, &fileInfo)) { - m_seen[tempfilename] = TRUE; - parsedAMap |= addMap(mapDir, *iter, &fileInfo, TheGlobalData->m_buildMapCache); - } else { - DEBUG_CRASH(("Could not get file info for map %s", (*iter).str())); - } - } - } + if (!TheFileSystem->getFileInfo(*filepathIt, &fileInfo)) + { + DEBUG_CRASH(("Could not get file info for map %s", filepathIt->str())); + continue; } + + mapListChanged |= addMap(mapDir, *filepathIt, filepathLower, fileInfo, isOfficial); } - // clean out unseen maps if (clearUnseenMaps(mapDir)) - return TRUE; + { + mapListChanged = TRUE; + } - return parsedAMap; + return mapListChanged; } -//Bool MapCache::addMap( AsciiString dirName, AsciiString fname, WinTimeStamp timestamp, UnsignedInt filesize, Bool isOfficial ) -Bool MapCache::addMap( AsciiString dirName, AsciiString fname, FileInfo *fileInfo, Bool isOfficial) +Bool MapCache::addMap( + const AsciiString &mapDir, + const AsciiString &fname, + const AsciiString &lowerFname, + FileInfo &fileInfo, + Bool isOfficial) { - if (fileInfo == NULL) { - return FALSE; - } - - AsciiString lowerFname; - lowerFname = fname; - lowerFname.toLower(); MapCache::iterator it = find(lowerFname); - - MapMetaData md; - UnsignedInt filesize = fileInfo->sizeLow; - if (it != end()) { - // Found the map in our cache. Check to see if it has changed. - md = it->second; + // Found the map in our cache. Check to see if it has changed. + const MapMetaData& md = it->second; - if ((md.m_filesize == filesize) && - (md.m_CRC != 0)) + if (md.m_filesize == fileInfo.sizeLow && md.m_CRC != 0) { // Force a lookup so that we don't display the English localization in all builds. if (md.m_nameLookupTag.isEmpty()) @@ -629,13 +612,16 @@ Bool MapCache::addMap( AsciiString dirName, AsciiString fname, FileInfo *fileInf (*this)[lowerFname].m_displayName.concat(extension); } } + + it->second.m_doesExist = TRUE; + // DEBUG_LOG(("MapCache::addMap - found match for map %s", lowerFname.str())); return FALSE; // OK, it checks out. } DEBUG_LOG(("%s didn't match file in MapCache", fname.str())); - DEBUG_LOG(("size: %d / %d", filesize, md.m_filesize)); - DEBUG_LOG(("time1: %d / %d", fileInfo->timestampHigh, md.m_timestamp.m_highTimeStamp)); - DEBUG_LOG(("time2: %d / %d", fileInfo->timestampLow, md.m_timestamp.m_lowTimeStamp)); + DEBUG_LOG(("size: %d / %d", fileInfo.sizeLow, md.m_filesize)); + DEBUG_LOG(("time1: %d / %d", fileInfo.timestampHigh, md.m_timestamp.m_highTimeStamp)); + DEBUG_LOG(("time2: %d / %d", fileInfo.timestampLow, md.m_timestamp.m_lowTimeStamp)); // DEBUG_LOG(("size: %d / %d", filesize, md.m_filesize)); // DEBUG_LOG(("time1: %d / %d", timestamp.m_highTimeStamp, md.m_timestamp.m_highTimeStamp)); // DEBUG_LOG(("time2: %d / %d", timestamp.m_lowTimeStamp, md.m_timestamp.m_lowTimeStamp)); @@ -646,22 +632,25 @@ Bool MapCache::addMap( AsciiString dirName, AsciiString fname, FileInfo *fileInf loadMap(fname); // Just load for querying the data, since we aren't playing this map. // The map is now loaded. Pick out what we need. + MapMetaData md; md.m_fileName = lowerFname; - md.m_filesize = filesize; + md.m_filesize = fileInfo.sizeLow; md.m_isOfficial = isOfficial; + md.m_doesExist = TRUE; md.m_waypoints.update(); md.m_numPlayers = md.m_waypoints.m_numStartSpots; md.m_isMultiplayer = (md.m_numPlayers >= 2); - md.m_timestamp.m_highTimeStamp = fileInfo->timestampHigh; - md.m_timestamp.m_lowTimeStamp = fileInfo->timestampLow; + md.m_timestamp.m_highTimeStamp = fileInfo.timestampHigh; + md.m_timestamp.m_lowTimeStamp = fileInfo.timestampLow; md.m_supplyPositions = m_supplyPositions; md.m_techPositions = m_techPositions; md.m_CRC = calcCRC(fname); Bool exists = false; - AsciiString munkee = worldDict.getAsciiString(TheKey_mapName, &exists); - md.m_nameLookupTag = munkee; - if (!exists || munkee.isEmpty()) + AsciiString nameLookupTag = worldDict.getAsciiString(TheKey_mapName, &exists); + md.m_nameLookupTag = nameLookupTag; + + if (!exists || nameLookupTag.isEmpty()) { DEBUG_LOG(("Missing TheKey_mapName!")); AsciiString tempdisplayname; @@ -678,11 +667,11 @@ Bool MapCache::addMap( AsciiString dirName, AsciiString fname, FileInfo *fileInf else { AsciiString stringFileName; - stringFileName.format("%s\\%s", dirName.str(), fname.str()); + stringFileName.format("%s\\%s", mapDir.str(), fname.str()); stringFileName.truncateBy(4); stringFileName.concat("\\map.str"); TheGameText->initMapStringFile(stringFileName); - md.m_displayName = TheGameText->fetch(munkee); + md.m_displayName = TheGameText->fetch(nameLookupTag); if (md.m_numPlayers >= 2) { UnicodeString extension; @@ -733,24 +722,20 @@ Bool WouldMapTransfer( const AsciiString& mapName ) } //------------------------------------------------------------------------------------------------- -// TheSuperHackers @refactor Massively refactors the map list population function by breaking it into smaller pieces. - typedef std::set > MapNameList; typedef std::map MapDisplayToFileNameList; static void buildMapListForNumPlayers(MapNameList &outMapNames, MapDisplayToFileNameList &outFileNames, Int numPlayers) { - DEBUG_LOG(("Adding maps with %d players", numPlayers)); MapCache::iterator it = TheMapCache->begin(); for (; it != TheMapCache->end(); ++it) { - const MapMetaData &md = it->second; - if (md.m_numPlayers == numPlayers) + const MapMetaData &mapData = it->second; + if (mapData.m_numPlayers == numPlayers) { outMapNames.insert(it->second.m_displayName); outFileNames[it->second.m_displayName] = it->first; - DEBUG_LOG(("Adding map '%s' to temp cache.", it->first.str())); } } }