From 38e06fce7e685232999b249ba64a56c10880be5f Mon Sep 17 00:00:00 2001 From: elexis Date: Sun, 27 Oct 2019 12:39:28 +0000 Subject: [PATCH] Rewrite FPS/Realtime/Gametime/Ceasefire counters to use object semantics using class notation, refs #5387. Rebuild the counters every 250ms instead of every frame and minimize object creation. Differential Revision: https://code.wildfiregames.com/D2391 Comments By: Stan This was SVN commit r23096. --- binaries/data/config/default.cfg | 1 + .../mods/public/gui/common/OverlayCounter.js | 47 +++++++ .../public/gui/common/OverlayCounterFPS.js | 32 +++++ .../gui/common/OverlayCounterManager.js | 119 ++++++++++++++++++ .../gui/common/OverlayCounterRealtime.js | 21 ++++ .../gui/common/functions_global_object.js | 26 ---- .../public/gui/common/functions_utility.js | 21 ++++ .../data/mods/public/gui/common/global.xml | 34 +---- .../data/mods/public/gui/options/options.js | 1 + .../mods/public/gui/pregame/MainMenuItems.js | 5 +- .../public/gui/session/DiplomacyColors.js | 1 + .../gui/session/OverlayCounterElapsedTime.js | 37 ++++++ .../OverlayCounterRemainingCeasefire.js | 28 +++++ .../data/mods/public/gui/session/messages.js | 13 +- .../data/mods/public/gui/session/session.js | 47 ------- .../data/mods/public/gui/session/session.xml | 14 --- .../data/mods/public/gui/session/styles.xml | 7 -- 17 files changed, 327 insertions(+), 127 deletions(-) create mode 100644 binaries/data/mods/public/gui/common/OverlayCounter.js create mode 100644 binaries/data/mods/public/gui/common/OverlayCounterFPS.js create mode 100644 binaries/data/mods/public/gui/common/OverlayCounterManager.js create mode 100644 binaries/data/mods/public/gui/common/OverlayCounterRealtime.js create mode 100644 binaries/data/mods/public/gui/session/OverlayCounterElapsedTime.js create mode 100644 binaries/data/mods/public/gui/session/OverlayCounterRemainingCeasefire.js diff --git a/binaries/data/config/default.cfg b/binaries/data/config/default.cfg index 5fde524857..f2ad20522f 100644 --- a/binaries/data/config/default.cfg +++ b/binaries/data/config/default.cfg @@ -169,6 +169,7 @@ fps.toggle = "Alt+F" ; Toggle frame counter realtime.toggle = "Alt+T" ; Toggle current display of computer time session.devcommands.toggle = "Alt+D" ; Toggle developer commands panel timeelapsedcounter.toggle = "F12" ; Toggle time elapsed counter +ceasefirecounter.toggle = unused ; Toggle ceasefire counter session.showstatusbars = Tab ; Toggle display of status bars session.highlightguarding = PgDn ; Toggle highlight of guarding units session.highlightguarded = PgUp ; Toggle highlight of guarded units diff --git a/binaries/data/mods/public/gui/common/OverlayCounter.js b/binaries/data/mods/public/gui/common/OverlayCounter.js new file mode 100644 index 0000000000..50b51e5625 --- /dev/null +++ b/binaries/data/mods/public/gui/common/OverlayCounter.js @@ -0,0 +1,47 @@ +/** + * This is an abstract base class managing one counter shown. + * Classes implementing this class require a Config property and may have a Hotkey property. + */ +class OverlayCounter +{ + constructor(overlayCounterManager) + { + this.overlayCounterManager = overlayCounterManager; + this.updateEnabled(); + + registerConfigChangeHandler(this.onConfigChange.bind(this)); + + if (this.Hotkey) + Engine.SetGlobalHotkey(this.Hotkey, this.toggle.bind(this)); + } + + onConfigChange(changes) + { + if (changes.has(this.Config)) + this.updateEnabled(); + } + + isEnabled() + { + return Engine.ConfigDB_GetValue("user", this.Config) == "true"; + } + + updateEnabled() + { + this.overlayCounterManager.setCounterEnabled(this, this.isEnabled()); + } + + toggle() + { + Engine.ConfigDB_CreateValue("user", this.Config, String(!this.isEnabled())); + this.updateEnabled(); + } +} + +/** + * The properties of this prototype are defined in other files. Each of them is a class + * managing a counter shown on the current page and may extend the OverlayCounter class. + */ +class OverlayCounterTypes +{ +} diff --git a/binaries/data/mods/public/gui/common/OverlayCounterFPS.js b/binaries/data/mods/public/gui/common/OverlayCounterFPS.js new file mode 100644 index 0000000000..92be8a8c48 --- /dev/null +++ b/binaries/data/mods/public/gui/common/OverlayCounterFPS.js @@ -0,0 +1,32 @@ +/** + * This counter displays the current framerate in the screen corner. + */ +OverlayCounterTypes.prototype.FPS = class extends OverlayCounter +{ + constructor(overlayCounterManager) + { + super(overlayCounterManager); + + // Tiny performance improvement + this.caption = translate(this.Caption); + + // Minimize object construction + this.fpsObject = {}; + } + + /** + * This function is called frequently and thus minimized. + */ + get() + { + this.fpsObject.fps = Engine.GetFPS(); + return sprintf(this.caption, this.fpsObject); + } +}; + +// dennis-ignore: * +OverlayCounterTypes.prototype.FPS.prototype.Caption = markForTranslation("FPS: %(fps)4s"); + +OverlayCounterTypes.prototype.FPS.prototype.Config = "overlay.fps"; + +OverlayCounterTypes.prototype.FPS.prototype.Hotkey = "fps.toggle"; diff --git a/binaries/data/mods/public/gui/common/OverlayCounterManager.js b/binaries/data/mods/public/gui/common/OverlayCounterManager.js new file mode 100644 index 0000000000..cc539e1d71 --- /dev/null +++ b/binaries/data/mods/public/gui/common/OverlayCounterManager.js @@ -0,0 +1,119 @@ +/** + * Since every GUI page can display the FPS or realtime counter, + * this manager is initialized for every GUI page. + */ +var g_OverlayCounterManager; + +class OverlayCounterManager +{ + constructor(dataCounter) + { + this.dataCounter = dataCounter; + this.lineHeight = dataCounter.size.bottom - dataCounter.size.top; + this.counters = []; + this.enabledCounters = []; + this.lastTick = undefined; + this.lastLineCount = 0; + this.resizeHandlers = []; + + for (let name of this.availableCounterNames()) + { + let counter = new OverlayCounterTypes.prototype[name](this); + this.counters.push(counter); + counter.updateEnabled(); + } + + this.dataCounter.onTick = this.onTick.bind(this); + } + + /** + * Mods may overwrite this to change the order of the counters shown. + */ + availableCounterNames() + { + return Object.keys(OverlayCounterTypes.prototype); + } + + deleteCounter(counter) + { + let filter = count => count != counter; + this.counters = this.counters.filter(filter); + this.enabledCounters = this.enabledCounters.filter(filter); + } + + /** + * This function allows enabling and disabling of timers while preserving the counter order. + */ + setCounterEnabled(counter, enabled) + { + if (enabled) + this.enabledCounters = this.counters.filter(count => + this.enabledCounters.indexOf(count) != -1 || count == counter); + else + this.enabledCounters = this.enabledCounters.filter(count => count != counter); + + // Update instantly + this.lastTick = undefined; + this.onTick(); + } + + /** + * Handlers subscribed here will be informed then the dimension of the overlay changed. + * This allows placing the buttons below the counter. + */ + registerResizeHandler(handler) + { + this.resizeHandlers.push(handler); + } + + onTick() + { + // Don't rebuild the caption every frame + let now = Date.now(); + if (now < this.lastTick + this.Delay) + return; + + this.lastTick = now; + + let lineCount = 0; + let txt = ""; + + for (let counter of this.enabledCounters) + { + let newTxt = counter.get(); + if (!newTxt) + continue; + + ++lineCount; + txt += newTxt + "\n"; + } + + if (lineCount) + this.dataCounter.caption = txt; + + // The caption changes more often than not, + // but adding or removing lines happens rarely. + if (this.lastLineCount == lineCount) + return; + + let offset = this.lineHeight * lineCount; + + if (lineCount) + { + let size = this.dataCounter.size; + size.bottom = size.top + offset; + this.dataCounter.size = size; + } + + this.dataCounter.hidden = !lineCount; + + for (let handler of this.resizeHandlers) + handler(offset); + } +} + +/** + * To minimize the computation performed every frame, this duration + * in milliseconds determines how often the caption is rebuilt. + */ +OverlayCounterManager.prototype.Delay = 250; diff --git a/binaries/data/mods/public/gui/common/OverlayCounterRealtime.js b/binaries/data/mods/public/gui/common/OverlayCounterRealtime.js new file mode 100644 index 0000000000..1281667a64 --- /dev/null +++ b/binaries/data/mods/public/gui/common/OverlayCounterRealtime.js @@ -0,0 +1,21 @@ +/** + * Shows the current time to the player in their current timezone. + */ +OverlayCounterTypes.prototype.Realtime = class extends OverlayCounter +{ + constructor(overlayCounterManager) + { + super(overlayCounterManager); + this.date = new Date(); + } + + get() + { + this.date.setTime(Date.now()); + return this.date.toLocaleTimeString(); + } +}; + +OverlayCounterTypes.prototype.Realtime.prototype.Config = "overlay.realtime"; + +OverlayCounterTypes.prototype.Realtime.prototype.Hotkey = "realtime.toggle"; diff --git a/binaries/data/mods/public/gui/common/functions_global_object.js b/binaries/data/mods/public/gui/common/functions_global_object.js index 2634e17a37..cfc8b37782 100644 --- a/binaries/data/mods/public/gui/common/functions_global_object.js +++ b/binaries/data/mods/public/gui/common/functions_global_object.js @@ -1,29 +1,3 @@ -function updateCounters() -{ - let counters = []; - - if (Engine.ConfigDB_GetValue("user", "overlay.fps") === "true") - // dennis-ignore: * - counters.push(sprintf(translate("FPS: %(fps)4s"), { "fps": Engine.GetFPS() })); - - if (Engine.ConfigDB_GetValue("user", "overlay.realtime") === "true") - counters.push((new Date()).toLocaleTimeString()); - - // If game has been started - if (typeof appendSessionCounters != "undefined") - appendSessionCounters(counters); - - let dataCounter = Engine.GetGUIObjectByName("dataCounter"); - dataCounter.caption = counters.join("\n") + "\n"; - dataCounter.hidden = !counters.length; - dataCounter.size = sprintf("%(left)s %(top)s %(right)s %(bottom)s", { - "left": "100%%-100", - "top": "40", - "right": "100%%-5", - "bottom": 40 + 14 * counters.length - }); -} - /** * Update the overlay with the most recent network warning of each client. */ diff --git a/binaries/data/mods/public/gui/common/functions_utility.js b/binaries/data/mods/public/gui/common/functions_utility.js index c819ea241e..56f5996fa5 100644 --- a/binaries/data/mods/public/gui/common/functions_utility.js +++ b/binaries/data/mods/public/gui/common/functions_utility.js @@ -8,6 +8,27 @@ var g_SoundNotifications = { "gamesetup.join": { "soundfile": "audio/interface/ui/gamesetup_join.ogg", "threshold": 0 } }; +/** + * These events are fired when the user has closed the options page. + * The handlers are provided a Set storing which config values have changed. + * TODO: This should become a GUI event sent by the engine. + */ +var g_ConfigChangeHandlers = new Set(); + +function registerConfigChangeHandler(handler) +{ + g_ConfigChangeHandlers.add(handler); +} + +/** + * @param changes - a Set of config names + */ +function fireConfigChangeHandlers(changes) +{ + for (let handler of g_ConfigChangeHandlers) + handler(changes); +} + /** * Returns translated history and gameplay data of all civs, optionally including a mock gaia civ. */ diff --git a/binaries/data/mods/public/gui/common/global.xml b/binaries/data/mods/public/gui/common/global.xml index 838a9a5dc8..9ffdaf4f83 100644 --- a/binaries/data/mods/public/gui/common/global.xml +++ b/binaries/data/mods/public/gui/common/global.xml @@ -39,43 +39,15 @@ type="text" ghost="true" z="199" - size="100%-90 40 100%-5 40" + size="100%-100 40 100%-5 54" font="mono-10" textcolor="white" text_align="right" text_valign="top" sprite="color: 0 0 0 100" - > - - updateCounters(); - - - - - - - - diff --git a/binaries/data/mods/public/gui/options/options.js b/binaries/data/mods/public/gui/options/options.js index 4c2eedc715..da33be0c72 100644 --- a/binaries/data/mods/public/gui/options/options.js +++ b/binaries/data/mods/public/gui/options/options.js @@ -243,6 +243,7 @@ function displayOptions() Engine.ConfigDB_SetChanges("user", true); g_ChangedKeys.add(option.config); + fireConfigChangeHandlers(new Set([option.config])); if (option.function) Engine[option.function](value); diff --git a/binaries/data/mods/public/gui/pregame/MainMenuItems.js b/binaries/data/mods/public/gui/pregame/MainMenuItems.js index 973384f5b1..6493882e87 100644 --- a/binaries/data/mods/public/gui/pregame/MainMenuItems.js +++ b/binaries/data/mods/public/gui/pregame/MainMenuItems.js @@ -141,7 +141,10 @@ var g_MainMenuItems = [ "caption": translate("Options"), "tooltip": translate("Adjust game settings."), "onPress": () => { - Engine.PushGuiPage("page_options.xml"); + Engine.PushGuiPage( + "page_options.xml", + {}, + fireConfigChangeHandlers); } }, { diff --git a/binaries/data/mods/public/gui/session/DiplomacyColors.js b/binaries/data/mods/public/gui/session/DiplomacyColors.js index b785348bd0..238010b29b 100644 --- a/binaries/data/mods/public/gui/session/DiplomacyColors.js +++ b/binaries/data/mods/public/gui/session/DiplomacyColors.js @@ -17,6 +17,7 @@ class DiplomacyColors registerPlayersInitHandler(this.onPlayersInit.bind(this)); registerConfigChangeHandler(this.onConfigChange.bind(this)); + registerCeasefireEndedHandler(this.onCeasefireEnded.bind(this)); } registerDiplomacyColorsChangeHandler(handler) diff --git a/binaries/data/mods/public/gui/session/OverlayCounterElapsedTime.js b/binaries/data/mods/public/gui/session/OverlayCounterElapsedTime.js new file mode 100644 index 0000000000..d8a006a293 --- /dev/null +++ b/binaries/data/mods/public/gui/session/OverlayCounterElapsedTime.js @@ -0,0 +1,37 @@ +/** + * This class shows the simulated match time below the FPS counter. + */ +OverlayCounterTypes.prototype.ElapsedTime = class extends OverlayCounter +{ + constructor(overlayCounterManager) + { + super(overlayCounterManager); + + // Performance optimization + this.caption = translate(this.Caption); + this.sprintfData = {}; + } + + get() + { + if (!g_SimState) + return ""; + + let time = timeToString(g_SimState.timeElapsed); + + let speed = Engine.GetSimRate(); + if (speed == 1) + return time; + + this.sprintfData.time = time; + this.sprintfData.speed = Engine.FormatDecimalNumberIntoString(speed); + return sprintf(this.caption, this.sprintfData); + } +}; + +// Translation: The "x" means "times", with the mathematical meaning of multiplication. +OverlayCounterTypes.prototype.ElapsedTime.prototype.Caption = markForTranslation("%(time)s (%(speed)sx)"); + +OverlayCounterTypes.prototype.ElapsedTime.prototype.Config = "gui.session.timeelapsedcounter"; + +OverlayCounterTypes.prototype.ElapsedTime.prototype.Hotkey = "timeelapsedcounter.toggle"; diff --git a/binaries/data/mods/public/gui/session/OverlayCounterRemainingCeasefire.js b/binaries/data/mods/public/gui/session/OverlayCounterRemainingCeasefire.js new file mode 100644 index 0000000000..b775d005aa --- /dev/null +++ b/binaries/data/mods/public/gui/session/OverlayCounterRemainingCeasefire.js @@ -0,0 +1,28 @@ +/** + * Adds the ceasefire counter to the global FPS and + * realtime counters shown in the top right corner. + */ +OverlayCounterTypes.prototype.RemainingCeasefire = class extends OverlayCounter +{ + constructor(overlayCounterManager) + { + super(overlayCounterManager); + registerCeasefireEndedHandler(this.onCeasefireEnded.bind(this)); + } + + onCeasefireEnded() + { + this.overlayCounterManager.deleteCounter(this); + } + + get() + { + if (!g_SimState) + return ""; + return timeToString(g_SimState.ceasefireTimeRemaining); + } +}; + +OverlayCounterTypes.prototype.RemainingCeasefire.prototype.Config = "gui.session.ceasefirecounter"; + +OverlayCounterTypes.prototype.RemainingCeasefire.prototype.Hotkey = "ceasefirecounter.toggle"; diff --git a/binaries/data/mods/public/gui/session/messages.js b/binaries/data/mods/public/gui/session/messages.js index 88c166b4d1..59d36bac40 100644 --- a/binaries/data/mods/public/gui/session/messages.js +++ b/binaries/data/mods/public/gui/session/messages.js @@ -18,6 +18,11 @@ var g_TutorialNewMessageTags = { "color": "yellow" }; */ var g_PlayerAssignmentsChangeHandlers = new Set(); +/** + * These handlers are called when the ceasefire time has run out. + */ +var g_CeasefireEndedHandlers = new Set(); + /** * Handle all netmessage types that can occur. */ @@ -151,7 +156,8 @@ var g_NotificationsTypes = "ceasefire-ended": function(notification, player) { updatePlayerData(); - g_DiplomacyColors.OnCeasefireEnded(); + for (let handler of g_CeasefireEndedHandlers) + handler(); }, "tutorial": function(notification, player) { @@ -291,6 +297,11 @@ function registerPlayerAssignmentsChangeHandler(handler) g_PlayerAssignmentsChangeHandlers.add(handler); } +function registerCeasefireEndedHandler(handler) +{ + g_CeasefireEndedHandlers.add(handler); +} + /** * Loads all known cheat commands. */ diff --git a/binaries/data/mods/public/gui/session/session.js b/binaries/data/mods/public/gui/session/session.js index 6a37e54909..b9d0bba8c1 100644 --- a/binaries/data/mods/public/gui/session/session.js +++ b/binaries/data/mods/public/gui/session/session.js @@ -159,12 +159,6 @@ var g_EntitySelectionChangeHandlers = new Set(); */ var g_HotkeyChangeHandlers = new Set(); -/** - * These events are fired when the user has closed the options page. - * The handlers are provided a Set storing which config values have changed. - */ -var g_ConfigChangeHandlers = new Set(); - /** * List of additional entities to highlight. */ @@ -353,20 +347,6 @@ function registerHotkeyChangeHandler(handler) g_HotkeyChangeHandlers.add(handler); } -function registerConfigChangeHandler(handler) -{ - g_ConfigChangeHandlers.add(handler); -} - -/** - * @param changes - a Set of config names - */ -function fireConfigChangeHandlers(changes) -{ - for (let handler of g_ConfigChangeHandlers) - handler(changes); -} - function updatePlayerData() { let simState = GetSimState(); @@ -870,30 +850,3 @@ function playAmbient() { Engine.PlayAmbientSound(pickRandom(g_Ambient), true); } - -/** - * Adds the ingame time and ceasefire counter to the global FPS and - * realtime counters shown in the top right corner. - */ -function appendSessionCounters(counters) -{ - let simState = GetSimState(); - - if (Engine.ConfigDB_GetValue("user", "gui.session.timeelapsedcounter") === "true") - { - let currentSpeed = Engine.GetSimRate(); - if (currentSpeed != 1.0) - // Translation: The "x" means "times", with the mathematical meaning of multiplication. - counters.push(sprintf(translate("%(time)s (%(speed)sx)"), { - "time": timeToString(simState.timeElapsed), - "speed": Engine.FormatDecimalNumberIntoString(currentSpeed) - })); - else - counters.push(timeToString(simState.timeElapsed)); - } - - if (simState.ceasefireActive && Engine.ConfigDB_GetValue("user", "gui.session.ceasefirecounter") === "true") - counters.push(timeToString(simState.ceasefireTimeRemaining)); - - g_ResearchProgress.setTopOffset(14 * counters.length); -} diff --git a/binaries/data/mods/public/gui/session/session.xml b/binaries/data/mods/public/gui/session/session.xml index d0e72a5a3f..36a2ae566e 100644 --- a/binaries/data/mods/public/gui/session/session.xml +++ b/binaries/data/mods/public/gui/session/session.xml @@ -41,20 +41,6 @@ - - - - - -