Toggleable performance metrics display

This commit is contained in:
Anuken
2026-06-27 11:43:24 -04:00
parent 6b416f7187
commit c764999d75
12 changed files with 209 additions and 8 deletions
+2
View File
@@ -1427,6 +1427,7 @@ setting.fullscreen.name = Fullscreen
setting.fps.name = Show FPS & Ping
setting.console.name = Enable Console
setting.drawhitboxes.name = Display Hitboxes
setting.showperformance.name = Display Performance Metrics
setting.buildingtimestep.name = Fixed Building Timestep
setting.buildingtimestep.description = Runs buildings on a fixed timestep with constant delta,\nas opposed to every frame with variable delta.\nIf buildings cannot be updated fast enough, the game will slow down.\nIf you don't know what that means, don't touch this option.
setting.smoothcamera.name = Smooth Camera
@@ -1573,6 +1574,7 @@ keybind.zoom_minimap.name = Zoom Minimap
keybind.detach_camera.name = Toggle Free Camera
keybind.teleport_cursor.name = Teleport To Cursor (Sandbox)
keybind.debug_hitboxes.name = Toggle Debug Hitboxes
keybind.performance_metrics.name = Toggle Performance Metrics
mode.help.title = Description of modes
mode.survival.name = Survival
mode.survival.description = The normal mode. Limited resources and automatic incoming waves.\n[gray]Requires enemy spawns in the map to play.
+10
View File
@@ -269,6 +269,16 @@ public abstract class ClientLauncher extends ApplicationCore implements Platform
}
PerfCounter.update.end();
long rawUpdate = PerfCounter.update.latestValueNs();
for(var other : PerfCounter.displayedCounters){
if(other != PerfCounter.other) rawUpdate -= other.latestValueNs();
}
PerfCounter.other.add(rawUpdate);
for(var counter : PerfCounter.all){
counter.checkUpdate();
}
}
@Override
+4
View File
@@ -695,6 +695,10 @@ public class Control implements ApplicationListener, Loadable{
if(Float.isNaN(camera.position.x)) camera.position.x = world.unitWidth()/2f;
if(Float.isNaN(camera.position.y)) camera.position.y = world.unitHeight()/2f;
if(!scene.hasKeyboard()){
if(Core.input.keyTap(Binding.performanceMetrics)) Core.settings.toggle("showperformance");
}
if(state.isGame()){
input.update();
input.updateSelectQuadtree();
+11 -1
View File
@@ -463,11 +463,12 @@ public class Logic implements ApplicationListener{
protected void updateEntities(){
PerfCounter.entityUpdate.begin();
PerfCounter.entityMisc.begin();
Groups.updatePooling();
Groups.bullet.updatePhysics();
Groups.unit.updatePhysics();
Groups.all.update();
PerfCounter.entityMisc.end();
PerfCounter.unitUpdate.begin();
Groups.unit.update();
@@ -481,7 +482,12 @@ public class Logic implements ApplicationListener{
if(!state.isEditor()) Groups.build.update();
PerfCounter.buildingUpdate.end();
PerfCounter.bulletUpdate.begin();
Groups.bullet.update();
Groups.bullet.collide();
PerfCounter.bulletUpdate.begin();
PerfCounter.entityUpdate.end();
}
@@ -490,6 +496,8 @@ public class Logic implements ApplicationListener{
PerfCounter.frame.end();
PerfCounter.frame.begin();
PerfCounter.stateUpdate.begin();
Events.fire(Trigger.update);
universe.updateGlobal();
@@ -600,6 +608,8 @@ public class Logic implements ApplicationListener{
}else if(netServer.isWaitingForPlayers() && runStateCheck){
checkGameState();
}
PerfCounter.stateUpdate.end(PerfCounter.entityUpdate.latestValueNs());
}
/** @return whether the wave timer is paused due to enemies */
+58 -1
View File
@@ -1,5 +1,6 @@
package mindustry.core;
import arc.*;
import arc.math.*;
import arc.util.*;
@@ -7,34 +8,86 @@ import arc.util.*;
public enum PerfCounter{
frame,
update,
other,
stateUpdate,
entityMisc,
entityUpdate,
buildingUpdate,
powerUpdate,
unitUpdate,
bulletUpdate,
ui,
render;
public static final PerfCounter[] all = values();
public static final PerfCounter[] displayedCounters = {
powerUpdate,
buildingUpdate,
entityMisc,
bulletUpdate,
unitUpdate,
render,
ui,
stateUpdate,
other
};
static final int meanWindow = 120;
static final int refreshTimeMillis = 100;
private long lastUpdateFrame = -1;
private long valueRefreshTime;
private float refreshValue;
private long beginTime;
private long partValue;
private boolean began = false;
private WindowedMean mean = new WindowedMean(meanWindow);
public void add(long nanos){
mean.add(nanos);
lastUpdateFrame = Core.graphics.getFrameId();
}
public void begin(){
lastUpdateFrame = Core.graphics.getFrameId();
began = true;
beginTime = Time.nanos();
}
public void end(){
end(0);
}
public void end(long subtract){
if(!began) return;
lastUpdateFrame = Core.graphics.getFrameId();
began = false;
mean.add(Time.timeSinceNanos(beginTime) - subtract);
}
public void checkUpdate(){
if(lastUpdateFrame < Core.graphics.getFrameId()){
mean.add(0f);
}
}
public void beginPart(){
began = true;
beginTime = Time.nanos();
}
public void endPart(){
if(!began) return;
began = false;
mean.add(Time.timeSinceNanos(beginTime));
partValue += Time.timeSinceNanos(beginTime);
}
public void finishParts(){
lastUpdateFrame = Core.graphics.getFrameId();
mean.add(partValue);
partValue = 0;
}
/** Value with a periodic refresh interval applied, to prevent jittery UI. */
@@ -46,6 +99,10 @@ public enum PerfCounter{
return refreshValue;
}
public long latestValueNs(){
return (long)mean.latest();
}
/** Raw value without a refresh interval. This will be unstable. */
public float rawValueMs(){
return mean.rawMean() / Time.nanosPerMilli;
+3
View File
@@ -48,6 +48,7 @@ public class UI implements ApplicationListener, Loadable{
public PlayerListFragment listfrag;
public LoadingFragment loadfrag;
public HintsFragment hints;
public PerformanceFragment perffrag;
public WidgetGroup menuGroup, hudGroup;
@@ -193,6 +194,7 @@ public class UI implements ApplicationListener, Loadable{
listfrag = new PlayerListFragment();
loadfrag = new LoadingFragment();
consolefrag = new ConsoleFragment();
perffrag = new PerformanceFragment();
picker = new ColorPicker();
effects = new EffectsDialog();
@@ -243,6 +245,7 @@ public class UI implements ApplicationListener, Loadable{
listfrag.build(hudGroup);
consolefrag.build(hudGroup);
loadfrag.build(group);
perffrag.build(group);
new FadeInFragment().build(group);
}
+1 -1
View File
@@ -4,7 +4,7 @@ import mindustry.annotations.Annotations.*;
import mindustry.gen.*;
class GroupDefs<G>{
@GroupDef(value = Entityc.class, exclude = {Unitc.class, PowerGraphUpdaterc.class}, update = true) G all;
@GroupDef(value = Entityc.class, exclude = {Unitc.class, PowerGraphUpdaterc.class, Bulletc.class}, update = true) G all;
@GroupDef(value = Playerc.class, mapping = true) G player;
@GroupDef(value = Bulletc.class, spatial = true, collide = true) G bullet;
@GroupDef(value = Unitc.class, spatial = true, mapping = true) G unit;
+2 -1
View File
@@ -106,7 +106,8 @@ public class Binding{
chatScroll = KeyBind.add("chat_scroll", new Axis(KeyCode.scroll)),
chatMode = KeyBind.add("chat_mode", KeyCode.tab),
console = KeyBind.add("console", KeyCode.f8),
debugHitboxes = KeyBind.add("debug_hitboxes", KeyCode.unset)
debugHitboxes = KeyBind.add("debug_hitboxes", KeyCode.unset),
performanceMetrics = KeyBind.add("performance_metrics", KeyCode.unset)
;
//dummy static class initializer
+1 -3
View File
@@ -240,9 +240,7 @@ public class DesktopInput extends InputHandler{
boolean detached = settings.getBool("detach-camera", false);
if(!scene.hasField() && !scene.hasDialog()){
if(input.keyTap(Binding.debugHitboxes)){
Core.settings.put("drawhitboxes", !settings.getBool("drawhitboxes"));
}
if(input.keyTap(Binding.debugHitboxes)) Core.settings.toggle("drawhitboxes");
if(input.keyTap(Binding.teleportCursor) && (state.rules.editor || state.rules.infiniteResources)){
if(player.dead()){
@@ -569,6 +569,7 @@ public class SettingsMenuDialog extends BaseDialog{
dev.checkPref("console", false);
dev.checkPref("drawhitboxes", false);
dev.checkPref("showperformance", false);
if(!ios){
dev.checkPref("modcrashdisable", true);
@@ -0,0 +1,115 @@
package mindustry.ui.fragments;
import arc.*;
import arc.graphics.*;
import arc.graphics.g2d.*;
import arc.math.geom.*;
import arc.scene.*;
import arc.scene.event.*;
import arc.scene.ui.layout.*;
import arc.struct.*;
import arc.util.*;
import arc.util.pooling.*;
import mindustry.core.*;
import mindustry.graphics.*;
import mindustry.ui.*;
import static mindustry.core.PerfCounter.*;
public class PerformanceFragment{
private static final ObjectMap<PerfCounter, Color> counterToColor = ObjectMap.of(
buildingUpdate, Pal.items,
powerUpdate, Pal.powerLight,
unitUpdate, Color.royal,
ui, Pal.remove,
render, Pal.reactorPurple,
stateUpdate, Color.cyan,
other, Color.pink,
entityMisc, Color.sky,
bulletUpdate, Pal.redDust
);
private static Color color(PerfCounter counter){
return counterToColor.get(counter, Color.white);
}
public void build(Group parent){
parent.fill(t -> {
t.visible(() -> {
t.toFront();
return Core.settings.getBool("showperformance");
});
t.touchable = Touchable.disabled;
t.top().left();
var outer = t.table(Styles.black8, i -> {
i.margin(6f);
i.add(new PerfBar()).size(400f, 50f);
}).padBottom(8f);
t.row();
t.table(Styles.black8, i -> {
i.margin(3f);
i.left().top();
float fscl = 1f;
i.defaults().left();
for(PerfCounter counter : displayedCounters){
i.label(() -> counter.name() + ": " + Strings.fixed(counter.valueMs(), 1)).style(Styles.outlineLabel).color(color(counter)).with(l -> l.setFontScale(fscl)).row();
}
i.add().height(30f).row();
for(PerfCounter counter : all){
if(Structs.contains(displayedCounters, counter)) continue;
i.label(() -> counter.name() + ": " + Strings.fixed(counter.valueMs(), 1)).style(Styles.outlineLabel).with(l -> l.setFontScale(fscl)).color(color(counter)).row();
}
}).left();
outer.get().toFront();
});
}
static class PerfBar extends Element{
@Override
public void draw(){
Vec2 mouse = screenToLocalCoordinates(Core.input.mouse()).add(x, y);
float total = frame.valueMs();
float accumulator = 0f;
PerfCounter hoveredCounter = null;
Draw.color(Color.darkGray);
Fill.crect(x, y, width, height);
for(var counter : PerfCounter.displayedCounters){
float value = counter.valueMs();
Draw.color(color(counter));
float rx = x + accumulator / total * width, ry = y, rw = width * value/total, rh = height;
Fill.crect(rx, ry, rw, rh);
if(Rect.contains(rx, ry, rw, rh, mouse.x, mouse.y)){
hoveredCounter = counter;
}
accumulator += value;
}
Draw.color();
if(hoveredCounter != null){
float offset = Scl.scl(30f);
String text = Strings.insertSpaces(Strings.capitalize(hoveredCounter.name())) + ": " + Strings.fixed(hoveredCounter.valueMs(), 1);
GlyphLayout layout = Pools.obtain(GlyphLayout.class, GlyphLayout::new);
layout.setText(Fonts.outline, text);
Draw.color(0f, 0f, 0f, 0.5f);
float pad = Scl.scl(4f);
Fill.crect(mouse.x + offset - pad, mouse.y - offset - layout.height - pad, layout.width + pad*2f, layout.height + pad*2f);
Draw.color();
Fonts.outline.draw(text, mouse.x + offset, mouse.y - offset);
Pools.free(layout);
}
}
}
}
+1 -1
View File
@@ -26,4 +26,4 @@ org.gradle.caching=true
org.gradle.internal.http.socketTimeout=100000
org.gradle.internal.http.connectionTimeout=100000
android.enableR8.fullMode=false
archash=ba33c36592
archash=09e1e41ab7