Skip to content

Conversation

@xezon
Copy link

@xezon xezon commented Nov 2, 2025

This change simplifies and improves the implementation of MapCache to prevent expensive reoccurring redundant map cache reads. It also removes DEBUG_LOG in buildMapListForNumPlayers because it is a spammy log that adds stalling.

Originally, the implementation in MapCache::updateCache was a total munkee disaster. Very complicated and inefficient. This change turned out to be half refactor, half performance optimization.

Problems in MapCache were:

  • Standard and User Map Cache INI loaded on every call to MapCache::updateCache, even if already loaded
  • Too much different logic cramped into MapCache::loadUserMaps
  • Overcomplicated logic around TheGlobalData->m_buildMapCache
  • Overcomplicated and inefficient logic around std::map<AsciiString, Bool> m_seen
  • Standard Maps Cache built in two code paths
  • Standard Maps Cache built after existing Cache was already loaded, causing poisened Map Cache generation

All this is fixed.

Performance measurement

Measured in optimized vs2022 build, with RTS_DEBUG.

Map List Click Original Time Optimized Time (This Change)
System Maps 203 ms 62 ms
User Maps 313 ms 62 ms

TODO

  • Replicate in Generals

…event expensive reoccurring redundant map cache reads
@xezon xezon added GUI For graphical user interface Major Severity: Minor < Major < Critical < Blocker Performance Is a performance concern Gen Relates to Generals ZH Relates to Zero Hour Refactor Edits the code with insignificant behavior changes, is never user facing labels Nov 2, 2025
Copy link

@Mauller Mauller left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good overall, only a few points to help clear up the logic.


MapCache::iterator it = begin();
MapMetaData md;
for (; it != end(); ++it)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be clearer and more standardised to make all of the iterator based for loops into while loops instead.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I intentionally did not do that because while loops should be the exception. Because it is far easier to create infinite loops by placing a continue in them, when not accompanied with iterator change.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is fair.

TheFileSystem->createDirectory(mapDir);

filepath.concat(m_mapCacheName);
FILE *fp = fopen(filepath.str(), "w");
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we are rewriting this, should we make use of the game file system like we did with the recorder class instead of raw C file handling? It might make some of the file handling a bit cleaner.

could be someting for a followup PR at some point.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe yes. I did not specifically touch the file handling stuff, just nearby code.

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)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could be more concise and easier to understand the logic in places to name these variables with the action instead of past tense of the action so;

m_CreateStandardMapCache
m_LoadUserMapCache
m_LoadStandardMapCache

Then just invert the logic. Could also drop the INI from the naming of things since we could make the map cache binary data in future.

With the current naming it makes logic look strange in places.

}

// Load standard maps from map cache last.
// This overwrites matching user maps to prevent munkees getting rowdy :)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Those damned dirty apes!

{
// not seen in the dir - clear it out.
erase(mapName);
erase(it);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is kind of annoying that stlport map does not support returning an interator from the erase function.

}

Bool MapCache::loadUserMaps()
Bool MapCache::loadMapsFromDisk( const AsciiString &mapDir, Bool isOfficial, Bool filterByAllowedMaps )
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The nesting in this function was filth, nice to see that flattened out and making more sense.


Bool exists = false;
AsciiString munkee = worldDict.getAsciiString(TheKey_mapName, &exists);
md.m_nameLookupTag = munkee;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we keep the munkee or give it a more appropriate name?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Gen Relates to Generals GUI For graphical user interface Major Severity: Minor < Major < Critical < Blocker Performance Is a performance concern Refactor Edits the code with insignificant behavior changes, is never user facing ZH Relates to Zero Hour

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants