92 lines
4.2 KiB
C#
92 lines
4.2 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Text;
|
|
using UnityEngine;
|
|
|
|
namespace MSCO
|
|
{
|
|
/// <summary>
|
|
/// Laufzeit-Statistik: wie viele ms pro Bild jeder MSCO-Teil braucht, FPS und Garbage-Collections. Konsole (F7):
|
|
/// "perf" bzw. "perf reset". Mit der Startoption -msco-perf wird alle 30 s eine Zeile in MSCO\MSCO_Loader.txt
|
|
/// geschrieben (zum Messen ohne Konsole).
|
|
/// </summary>
|
|
public static class PerfStats
|
|
{
|
|
static readonly Dictionary<string, double> totals = new Dictionary<string, double>();
|
|
/// <summary>Aufrufe der Harmony-Haken (Fsm.SwitchState, Fsm.Event, cInput) - zum Abschätzen ihrer Kosten.</summary>
|
|
public static int SwitchStateCalls, EventCalls, InputCalls;
|
|
static int frames, gcStart = -1;
|
|
static double frameSeconds, worstFrame;
|
|
static float started = -1f;
|
|
static int level = -1;
|
|
static int reports;
|
|
static readonly bool autoLog = Array.IndexOf(Environment.GetCommandLineArgs(), "-msco-perf") >= 0;
|
|
|
|
public static void Add(string part, double ms)
|
|
{
|
|
double t;
|
|
totals.TryGetValue(part, out t);
|
|
totals[part] = t + ms;
|
|
}
|
|
|
|
/// <summary>Einmal pro Bild (Update).</summary>
|
|
public static void Frame(float dt)
|
|
{
|
|
// Neue Szene (Menü -> Spiel): neu anfangen, sonst zählt das Laden mit
|
|
if (started < 0f || Application.loadedLevel != level) { level = Application.loadedLevel; reports = 0; Reset(); }
|
|
frames++;
|
|
frameSeconds += dt;
|
|
if (dt > worstFrame) worstFrame = dt;
|
|
// Mit -msco-perf alle 30 s, sonst im Spiel nach 2 Minuten und dann alle 5 Minuten ein Bericht in MSCO_Loader.txt -
|
|
// so lässt sich bei "wenig FPS" nachsehen, ob MSCO, eine Mod oder das Spiel selbst die Zeit braucht
|
|
float every = autoLog ? 30f : reports == 0 ? 120f : 300f;
|
|
if ((autoLog || MenuHelper.InGame) && Time.realtimeSinceStartup - started >= every)
|
|
{
|
|
Bootstrap.Log(Report());
|
|
reports++;
|
|
Reset();
|
|
}
|
|
}
|
|
|
|
/// <summary>Beim Beenden: was seit dem letzten Bericht gemessen wurde (mindestens 20 s im Spiel).</summary>
|
|
public static void FinalReport()
|
|
{
|
|
if (frames > 0 && MenuHelper.InGame && Time.realtimeSinceStartup - started >= 20f) Bootstrap.Log(Report());
|
|
}
|
|
|
|
public static void Reset()
|
|
{
|
|
totals.Clear();
|
|
frames = 0;
|
|
frameSeconds = 0;
|
|
worstFrame = 0;
|
|
started = Time.realtimeSinceStartup;
|
|
gcStart = GC.CollectionCount(0);
|
|
SwitchStateCalls = EventCalls = InputCalls = 0;
|
|
}
|
|
|
|
public static string Report()
|
|
{
|
|
if (frames == 0) return "PERF: no frames measured yet";
|
|
double seconds = Math.Max(0.001, Time.realtimeSinceStartup - started);
|
|
double msco = 0;
|
|
foreach (double v in totals.Values) msco += v;
|
|
var parts = new List<KeyValuePair<string, double>>(totals);
|
|
parts.Sort((a, b) => b.Value.CompareTo(a.Value));
|
|
var sb = new StringBuilder();
|
|
sb.Append("PERF ").Append(seconds.ToString("0")).Append(" s (").Append(MenuHelper.Scene).Append("): ")
|
|
.Append((frames / frameSeconds).ToString("0")).Append(" fps, ")
|
|
.Append((frameSeconds * 1000.0 / frames).ToString("0.0")).Append(" ms/frame (worst ")
|
|
.Append((worstFrame * 1000.0).ToString("0")).Append(" ms), MSCO ")
|
|
.Append((msco / frames).ToString("0.00")).Append(" ms/frame, GC ")
|
|
.Append(((GC.CollectionCount(0) - gcStart) * 60.0 / seconds).ToString("0.0")).Append("/min")
|
|
.Append(", hooks/frame: state ").Append((SwitchStateCalls / (double)frames).ToString("0"))
|
|
.Append(" event ").Append((EventCalls / (double)frames).ToString("0"))
|
|
.Append(" input ").Append((InputCalls / (double)frames).ToString("0"));
|
|
for (int i = 0; i < parts.Count && i < 8; i++)
|
|
sb.Append(i == 0 ? " | " : ", ").Append(parts[i].Key).Append(' ').Append((parts[i].Value / frames).ToString("0.000"));
|
|
return sb.ToString();
|
|
}
|
|
}
|
|
}
|