From 15f150341ac465bf450b65d18295fb1d5e43547f Mon Sep 17 00:00:00 2001 From: Vantha Date: Thu, 12 Feb 2026 17:39:27 +0100 Subject: [PATCH] Split g_TemplateData into per-player cache Previously, all templates were stored in the same cache. This was problematic because the data returned by the GuiInterface depends on the player passed (since it applies player modifications). The reason this didn't cause a lot of issues is because most of the time GetTemplateData is called without the player parameter, in which case the viewed player is always fallen back to. In a few other cases the viewed player is even passed directly to it, which has the same effect. Creating one cache for each player is the best way to ensure the stored template data is consistent and what one would expect. Also, this allows us to stop having to reset the cache when the viewed player is changed. --- .../data/mods/public/gui/session/session.js | 46 +++++++++---------- .../simulation/components/GuiInterface.js | 16 +++---- 2 files changed, 31 insertions(+), 31 deletions(-) diff --git a/binaries/data/mods/public/gui/session/session.js b/binaries/data/mods/public/gui/session/session.js index 468e36a847..ce9ef99b27 100644 --- a/binaries/data/mods/public/gui/session/session.js +++ b/binaries/data/mods/public/gui/session/session.js @@ -226,13 +226,23 @@ function GetEntityState(entId) */ function GetTemplateData(templateName, player) { - if (!(templateName in g_TemplateData)) + const targetPlayer = player || g_ViewedPlayer; + let cache = g_TemplateData[targetPlayer]; + if (!cache) { - const template = Engine.GuiInterfaceCall("GetTemplateData", { "templateName": templateName, "player": player }); - translateObjectKeys(template, ["specific", "generic", "tooltip"]); - g_TemplateData[templateName] = deepfreeze(template); + cache = {}; + g_TemplateData[targetPlayer] = cache; } - return g_TemplateData[templateName]; + + let templateData = cache[templateName]; + if (!templateData) + { + templateData = Engine.GuiInterfaceCall("GetTemplateData", { "templateName": templateName, "player": targetPlayer }); + translateObjectKeys(templateData, ["specific", "generic", "tooltip"]); + deepfreeze(templateData); + cache[templateName] = templateData; + } + return templateData; } function GetTechnologyData(technologyName, civ) @@ -285,7 +295,6 @@ async function init(initData, hotloadData) g_DiplomacyColors = new DiplomacyColors(); g_PlayerViewControl = new PlayerViewControl(); g_PlayerViewControl.registerViewedPlayerChangeHandler(g_DiplomacyColors.updateDisplayedPlayerColors.bind(g_DiplomacyColors)); - g_PlayerViewControl.registerViewedPlayerChangeHandler(resetTemplates); g_DiplomacyColors.registerDiplomacyColorsChangeHandler(g_PlayerViewControl.rebuild.bind(g_PlayerViewControl)); g_PauseControl = new PauseControl(); g_PlayerViewControl.registerPreViewedPlayerChangeHandler(removeStatusBarDisplay); @@ -495,16 +504,6 @@ function initializeMusic() } -function resetTemplates() -{ - // Update GUI and clear player-dependent cache - g_TemplateData = {}; - Engine.GuiInterfaceCall("ResetTemplateModified"); - - // TODO: do this more selectively - onSimulationUpdate(); -} - /** * Returns true if the player with that ID is in observermode. */ @@ -660,15 +659,16 @@ function onTick() function onSimulationUpdate() { + g_EntityStates = {}; + g_SimState = undefined; + // Templates change depending on technologies and auras, so they have to be reloaded after such a change. // g_TechnologyData data never changes, so it shouldn't be deleted. - g_EntityStates = {}; - if (Engine.GuiInterfaceCall("IsTemplateModified")) - { - g_TemplateData = {}; - Engine.GuiInterfaceCall("ResetTemplateModified"); - } - g_SimState = undefined; + const players = Engine.GuiInterfaceCall("GetPlayersWithModifiedTemplates"); + for (const player of players) + g_TemplateData[player] = {}; + if (players.size) + Engine.GuiInterfaceCall("ResetPlayersWithModifiedTemplates"); // Some changes may require re-rendering the selection. if (Engine.GuiInterfaceCall("IsSelectionDirty")) diff --git a/binaries/data/mods/public/simulation/components/GuiInterface.js b/binaries/data/mods/public/simulation/components/GuiInterface.js index 1fa1571ca7..c61db4b7a4 100644 --- a/binaries/data/mods/public/simulation/components/GuiInterface.js +++ b/binaries/data/mods/public/simulation/components/GuiInterface.js @@ -36,7 +36,7 @@ GuiInterface.prototype.Init = function() this.entsRallyPointsDisplayed = []; this.entsWithAuraAndStatusBars = new Set(); this.enabledVisualRangeOverlayTypes = {}; - this.templateModified = {}; + this.playersWithModifiedTemplates = new Set(); this.selectionDirty = {}; this.obstructionSnap = new ObstructionSnap(); }; @@ -713,18 +713,18 @@ GuiInterface.prototype.GetNeededResources = function(player, data) */ GuiInterface.prototype.OnTemplateModification = function(msg) { - this.templateModified[msg.player] = true; + this.playersWithModifiedTemplates.add(msg.player); this.selectionDirty[msg.player] = true; }; -GuiInterface.prototype.IsTemplateModified = function(player) +GuiInterface.prototype.GetPlayersWithModifiedTemplates = function(player) { - return this.templateModified[player] || false; + return this.playersWithModifiedTemplates; }; -GuiInterface.prototype.ResetTemplateModified = function() +GuiInterface.prototype.ResetPlayersWithModifiedTemplates = function() { - this.templateModified = {}; + this.playersWithModifiedTemplates.clear(); }; /** @@ -2132,8 +2132,8 @@ GuiInterface.prototype.exposedFunctions = { "GetTraderNumber": 1, "GetTradingGoods": 1, - "IsTemplateModified": 1, - "ResetTemplateModified": 1, + "GetPlayersWithModifiedTemplates": 1, + "ResetPlayersWithModifiedTemplates": 1, "IsSelectionDirty": 1, "ResetSelectionDirty": 1 };