using System; using System.Collections.Generic; using System.Text; using UnityEngine; namespace MSCO { /// /// 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). /// public static class PerfStats { static readonly Dictionary totals = new Dictionary(); /// Aufrufe der Harmony-Haken (Fsm.SwitchState, Fsm.Event, cInput) - zum Abschätzen ihrer Kosten. public static int SwitchStateCalls, EventCalls, InputCalls; static int frames, gcStart = -1; static double frameSeconds, worstFrame; static float started = -1f; 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; } /// Einmal pro Bild (Update). public static void Frame(float dt) { if (started < 0f) Reset(); frames++; frameSeconds += dt; if (dt > worstFrame) worstFrame = dt; // Mit -msco-perf alle 30 s, sonst im Spiel 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 : 300f; if ((autoLog || MenuHelper.InGame) && Time.realtimeSinceStartup - started >= every) { Bootstrap.Log(Report()); Reset(); } } 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>(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(); } } }