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.
This commit is contained in:
elexis 2019-10-27 12:39:28 +00:00
parent abe2ec9c75
commit 38e06fce7e
17 changed files with 327 additions and 127 deletions

View file

@ -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

View file

@ -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
{
}

View file

@ -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";

View file

@ -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;

View file

@ -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";

View file

@ -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.
*/

View file

@ -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.
*/

View file

@ -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"
>
<action on="Tick">
updateCounters();
</action>
</object>
<object name="fpsCounter"
type="text"
ghost="true"
hidden="true"
hotkey="fps.toggle"
>
<action on="Press">
Engine.ConfigDB_CreateValue("user", "overlay.fps", ""+(Engine.ConfigDB_GetValue("user", "overlay.fps")!== "true"));
</action>
</object>
<!--
==========================================
- TIME COUNTER
==========================================
-->
<object name="timeCounter"
type="text"
ghost="true"
hidden="true"
hotkey="realtime.toggle"
>
<action on="Press">
Engine.ConfigDB_CreateValue("user", "overlay.realtime", ""+(Engine.ConfigDB_GetValue("user", "overlay.realtime") !== "true"));
<action on="Load">
g_OverlayCounterManager = new OverlayCounterManager(this);
</action>
</object>

View file

@ -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);

View file

@ -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);
}
},
{

View file

@ -17,6 +17,7 @@ class DiplomacyColors
registerPlayersInitHandler(this.onPlayersInit.bind(this));
registerConfigChangeHandler(this.onConfigChange.bind(this));
registerCeasefireEndedHandler(this.onCeasefireEnded.bind(this));
}
registerDiplomacyColorsChangeHandler(handler)

View file

@ -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";

View file

@ -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";

View file

@ -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.
*/

View file

@ -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);
}

View file

@ -41,20 +41,6 @@
<!-- Hotkeys won't work properly unless outside menu -->
<include directory="gui/session/hotkeys/"/>
<!-- Time elapsed counter -->
<object size="100%-250 45 100%-140 65" type="text" name="timeElapsedCounter" style="SettingsText" hotkey="timeelapsedcounter.toggle" hidden="true">
<action on="Press">
Engine.ConfigDB_CreateValue("user", "gui.session.timeelapsedcounter", String(Engine.ConfigDB_GetValue("user", "gui.session.timeelapsedcounter") != "true"));
</action>
</object>
<!-- Ceasefire counter -->
<object size="100%-250 80 100%-140 100" type="text" name="ceasefireCounter" style="SettingsText" hotkey="ceasefirecounter.toggle" hidden="true">
<action on="Press">
Engine.ConfigDB_CreateValue("user", "gui.session.ceasefirecounter", String(Engine.ConfigDB_GetValue("user", "gui.session.ceasefirecounter") != "true"));
</action>
</object>
<!-- Network status -->
<object name="netStatus" type="text" style="netStatus" hidden="true">
<object type="button"

View file

@ -164,13 +164,6 @@
ghost="true"
/>
<style name="SettingsText"
font="sans-stroke-16"
textcolor="white"
text_align="right"
text_valign="center"
/>
<style name="dialogTitleText"
font="sans-bold-18"
textcolor="white"