Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of FPSDoctor v0.18.145
FPSDoctor.dll
Decompiled 6 days ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Reflection; using System.Reflection.Emit; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using System.Text; using System.Threading; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using BepInEx.Logging; using FPSDoctor.FidelityFX; using FPSDoctor.FidelityFX.FSR3; using FPSDoctor.Optimizations; using HarmonyLib; using Microsoft.CodeAnalysis; using TMPro; using Unity.Collections; using Unity.Profiling; using Unity.Profiling.LowLevel.Unsafe; using UnityEngine; using UnityEngine.Events; using UnityEngine.Experimental.Rendering; using UnityEngine.LowLevel; using UnityEngine.PostProcessing; using UnityEngine.Profiling; using UnityEngine.Rendering; using UnityEngine.SceneManagement; using UnityEngine.Scripting; using UnityEngine.UI; using UnityStandardAssets.ImageEffects; using Valheim.SettingsGui; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: AssemblyCompany("FPSDoctor")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("0.18.145.0")] [assembly: AssemblyInformationalVersion("0.18.145+32191ee1b53bfd3437eebe31f71b91fb884f3c92")] [assembly: AssemblyProduct("FPSDoctor")] [assembly: AssemblyTitle("FPSDoctor")] [assembly: AssemblyVersion("0.18.145.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace FPSDoctor { internal sealed class Benchmark : MonoBehaviour { internal sealed class Experiment { public string Name; public Action Apply; public Action Revert; public float ExtraWarmup; } private sealed class Result { public int N; public double FrameMs; public double BusyMs; public double SetPass; public double Draws; public static Result Mean(Result a, Result b) { return new Result { FrameMs = (a.FrameMs + b.FrameMs) / 2.0, BusyMs = (a.BusyMs + b.BusyMs) / 2.0, SetPass = (a.SetPass + b.SetPass) / 2.0, Draws = (a.Draws + b.Draws) / 2.0 }; } public string Line(string name, Result reference) { string text = ((reference == null) ? "" : $"{(FrameMs / reference.FrameMs - 1.0) * 100.0,7:+0.0;-0.0}%"); string text2 = ((SetPass <= 0.0) ? " n/a" : ((reference == null) ? $"{SetPass,8:0}" : $"{SetPass - reference.SetPass,8:+0;-0;0}")); string text3 = ((Draws <= 0.0) ? " n/a" : ((reference == null) ? $"{Draws,8:0}" : $"{Draws - reference.Draws,8:+0;-0;0}")); return $"{FrameMs,10:0.00}{BusyMs,10:0.00}{1000.0 / FrameMs,8:0}{text,14}{text2}{text3} {name}\n"; } } private const float Warmup = 1.5f; private const float Measure = 5f; private bool _wasGhost; private bool _wasGod; private static readonly FieldRef<Character, Vector3> _moveDir = AccessTools.FieldRefAccess<Character, Vector3>("m_moveDir"); public static string Progress = ""; private GUIStyle _style; private const int Rounds = 2; private static ProfilerRecorder _spRec; private static ProfilerRecorder _dcRec; private static float _lastHp = -1f; private static bool _healthChanged; public static bool Running { get; private set; } public static void Patch(Harmony harmony) { //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Expected O, but got Unknown HarmonyMethod val = new HarmonyMethod(typeof(Benchmark), "SkipWhileRunning", (Type[])null); harmony.Patch((MethodBase)AccessTools.Method(typeof(PlayerController), "FixedUpdate", (Type[])null, (Type[])null), val, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); harmony.Patch((MethodBase)AccessTools.Method(typeof(PlayerController), "LateUpdate", (Type[])null, (Type[])null), val, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } private static bool SkipWhileRunning() { if (!Running) { return !RouteBench.Playing; } return false; } private void Shield(bool on) { //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Unknown result type (might be due to invalid IL or missing references) Player localPlayer = Player.m_localPlayer; if (!((Object)(object)localPlayer == (Object)null)) { if (on) { _wasGhost = ((Character)localPlayer).InGhostMode(); _wasGod = ((Character)localPlayer).InGodMode(); localPlayer.SetGhostMode(true); localPlayer.SetGodMode(true); _moveDir.Invoke((Character)(object)localPlayer) = Vector3.zero; } else { localPlayer.SetGhostMode(_wasGhost); localPlayer.SetGodMode(_wasGod); } } } private void Update() { if (!Running && !RouteBench.Playing && !((Object)(object)ZNetScene.instance == (Object)null) && !((Object)(object)Player.m_localPlayer == (Object)null)) { if (Plugin.DevKey(Plugin.DeepScanKey)) { ((MonoBehaviour)this).StartCoroutine(Run(DeepScan.BuildExperiments(), 1, "FPSDoctor DEEP SCAN: what loads the game the most", sortByImpact: true, DeepScan.AppendCensus)); } else if (Plugin.DevKey(Plugin.WeatherScanKey)) { ((MonoBehaviour)this).StartCoroutine(Run(DeepScan.BuildWeatherExperiments(), 1, "FPSDoctor WEATHER SCAN: what each biome's weather costs", sortByImpact: true, null)); } else if (Plugin.DevKey(Plugin.LogicScanKey)) { ((MonoBehaviour)this).StartCoroutine(LogicScan()); } else if (Plugin.DevKey(Plugin.ShadowScanKey)) { ((MonoBehaviour)this).StartCoroutine(Run(DeepScan.BuildShadowExperiments(), 3, "FPSDoctor SHADOW SCAN: what the shadows cost", sortByImpact: true, null)); } else if (Plugin.DevKey(Plugin.BenchKey)) { ((MonoBehaviour)this).StartCoroutine(Run(BuildExperiments(), 2, "FPSDoctor benchmark", sortByImpact: false, null)); } } } private IEnumerator LogicScan() { yield return Run(DeepScan.BuildLogicExperiments(), 2, "FPSDoctor LOGIC SCAN: drawing or game logic?", sortByImpact: false, null); if ((Object)(object)ProfilerOverlay.Instance != (Object)null) { Running = true; Progress = "profiling the game systems (12 s)..."; yield return ProfilerOverlay.Instance.TimedDump(12f); Running = false; Progress = ""; } } private IEnumerator Run(List<Experiment> exps, int rounds, string title, bool sortByImpact, Action<StringBuilder> extra) { Running = true; Shield(on: true); EnvMan env = EnvMan.instance; string heldEnv = (((Object)(object)env != (Object)null) ? env.m_debugEnv : null); bool heldTod = (Object)(object)env != (Object)null && env.m_debugTimeOfDay; float heldTime = (((Object)(object)env != (Object)null) ? env.m_debugTime : 0.5f); string current = (((Object)(object)env != (Object)null) ? "Clear, noon" : null); if ((Object)(object)env != (Object)null) { env.m_debugEnv = "Clear"; env.m_debugTimeOfDay = true; env.m_debugTime = 0.5f; } if ((Object)(object)env != (Object)null) { env.ForceInstantEnvironmentSwitch(); Progress = "setting clear noon..."; yield return (object)new WaitForSecondsRealtime(4f); } StringBuilder sb = new StringBuilder(); sb.AppendFormat("\n{0} ({1} experiments x {2} rounds, {3:0.#}s phases, baselines between)\n", title, exps.Count, rounds, 6.5f); sb.Append(" ms/frame busy ms FPS vs baseline setpass draws experiment (setpass/draws: absolute for the baseline, change for experiments)\n"); _lastHp = -1f; _healthChanged = false; Result baseline = new Result(); yield return MeasurePhase("baseline", baseline); sb.Append(baseline.Line("baseline", null)); Dictionary<Experiment, Result> sumExp = new Dictionary<Experiment, Result>(); Dictionary<Experiment, Result> sumRef = new Dictionary<Experiment, Result>(); for (int round = 0; round < rounds; round++) { foreach (Experiment e in exps) { Result r = new Result(); bool applied = false; try { e.Apply(); applied = true; } catch (Exception ex) { sb.AppendFormat(" {0}: failed to apply: {1}\n", e.Name, ex.Message); } if (applied) { yield return MeasurePhase(e.Name, r, e.ExtraWarmup); try { e.Revert(); } catch (Exception ex2) { Plugin.Log.LogWarning((object)("Benchmark: revert of " + e.Name + " failed: " + ex2.Message)); } } Result after = new Result(); yield return MeasurePhase("baseline", after); if (applied) { Accumulate(sumExp, e, r); Accumulate(sumRef, e, Result.Mean(baseline, after)); } baseline = after; } } List<Experiment> list = new List<Experiment>(exps); if (sortByImpact) { list.Sort(delegate(Experiment x, Experiment y) { Result value2; double num = (sumExp.TryGetValue(x, out value2) ? (Scale(value2).FrameMs / Scale(sumRef[x]).FrameMs) : 1.0); Result value4; double value3 = (sumExp.TryGetValue(y, out value4) ? (Scale(value4).FrameMs / Scale(sumRef[y]).FrameMs) : 1.0); return num.CompareTo(value3); }); } foreach (Experiment item in list) { if (sumExp.TryGetValue(item, out var value)) { sb.Append(Scale(value).Line(item.Name, Scale(sumRef[item]))); } } sb.Append("(negative % = faster than baseline; 'busy' = main thread time per frame)\n"); if (_healthChanged) { sb.Append("WARNING: you took damage during the benchmark (combat?). Results are unreliable, run it again in a quiet spot.\n"); } sb.Append("\nState after benchmark (everything back on):\n"); PieceStats.Append(sb); PieceRenderer.AppendBlockDiagnostics(sb); foreach (Optimization item2 in OptimizationManager.All) { sb.AppendFormat(" {0} [cost {1:0.000} ms/frame]: {2}\n", item2.Name, item2.CostMs, item2.Active ? item2.Status() : "off"); } if (extra != null) { try { extra(sb); } catch (Exception ex3) { sb.Append("census failed: " + ex3.Message + "\n"); } } if ((Object)(object)env != (Object)null) { env.m_debugEnv = heldEnv ?? ""; env.m_debugTimeOfDay = heldTod; env.m_debugTime = heldTime; } Shield(on: false); if (current != null) { sb.AppendLine("(conditions during the run: " + current + "; controls frozen, ghost + god mode)"); } Plugin.Log.LogMessage((object)sb.ToString()); Progress = "benchmark done, results in LogOutput.log"; Running = false; yield return (object)new WaitForSecondsRealtime(4f); if (!Running) { Progress = ""; } } private void OnGUI() { //IL_0084: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Expected O, but got Unknown //IL_0059: Unknown result type (might be due to invalid IL or missing references) string text = ((!string.IsNullOrEmpty(Progress)) ? Progress : RouteBench.Progress); if (!string.IsNullOrEmpty(text)) { if (_style == null) { _style = new GUIStyle(GUI.skin.box) { fontSize = 18, alignment = (TextAnchor)4 }; _style.normal.textColor = Color.yellow; } GUI.Box(new Rect((float)Screen.width / 2f - 260f, 40f, 520f, 36f), text, _style); } } private static void Accumulate(Dictionary<Experiment, Result> into, Experiment e, Result r) { if (!into.TryGetValue(e, out var value)) { value = (into[e] = new Result()); } value.FrameMs += r.FrameMs; value.BusyMs += r.BusyMs; value.SetPass += r.SetPass; value.Draws += r.Draws; value.N++; } private static Result Scale(Result r) { return new Result { FrameMs = r.FrameMs / (double)r.N, BusyMs = r.BusyMs / (double)r.N, SetPass = r.SetPass / (double)r.N, Draws = r.Draws / (double)r.N }; } private IEnumerator MeasurePhase(string name, Result into, float extraWarmup = 0f) { Progress = "benchmark: " + name + " (don't move)"; float t = 0f; while (t < 1.5f + extraWarmup) { t += Time.unscaledDeltaTime; yield return null; } FrameSplit.ResetAccumulators(); if (!((ProfilerRecorder)(ref _spRec)).Valid) { try { _spRec = ProfilerRecorder.StartNew(ProfilerCategory.Render, "SetPass Calls Count", 1, (ProfilerRecorderOptions)24); } catch { } } if (!((ProfilerRecorder)(ref _dcRec)).Valid) { try { _dcRec = ProfilerRecorder.StartNew(ProfilerCategory.Render, "Draw Calls Count", 1, (ProfilerRecorderOptions)24); } catch { } } double sum = 0.0; double sp = 0.0; double dc = 0.0; int n = 0; int nsp = 0; t = 0f; while (t < 5f) { yield return null; Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer != (Object)null) { float health = ((Character)localPlayer).GetHealth(); if (_lastHp >= 0f && health < _lastHp - 0.01f) { _healthChanged = true; } _lastHp = health; } t += Time.unscaledDeltaTime; sum += (double)Time.unscaledDeltaTime * 1000.0; n++; if (((ProfilerRecorder)(ref _spRec)).Valid && ((ProfilerRecorder)(ref _spRec)).Count > 0 && ((ProfilerRecorder)(ref _dcRec)).Valid && ((ProfilerRecorder)(ref _dcRec)).Count > 0) { sp += (double)((ProfilerRecorder)(ref _spRec)).LastValue; dc += (double)((ProfilerRecorder)(ref _dcRec)).LastValue; nsp++; } } into.FrameMs = sum / (double)Math.Max(1, n); into.SetPass = ((nsp > 0) ? (sp / (double)nsp) : 0.0); into.Draws = ((nsp > 0) ? (dc / (double)nsp) : 0.0); into.BusyMs = FrameSplit.AverageBusySinceReset(); } private static List<Experiment> BuildExperiments() { //IL_0204: Unknown result type (might be due to invalid IL or missing references) //IL_0209: Unknown result type (might be due to invalid IL or missing references) List<Experiment> list = new List<Experiment>(); bool master = Optimization.MasterOn; list.Add(new Experiment { Name = (master ? "FPSDoctor optimizations OFF" : "FPSDoctor optimizations ON"), Apply = delegate { OptimizationManager.SetMaster(!master); }, Revert = delegate { OptimizationManager.SetMaster(master); } }); foreach (Optimization item in OptimizationManager.All) { Optimization opt = item; if (opt.Active && opt.Benchmarkable) { list.Add(new Experiment { Name = "only " + opt.Name + " OFF", Apply = delegate { opt.Suspended = true; OptimizationManager.SafeRefresh(opt); }, Revert = delegate { opt.Suspended = false; OptimizationManager.SafeRefresh(opt); } }); } } foreach (Optimization item2 in OptimizationManager.All) { OcclusionCulling occ = item2 as OcclusionCulling; if (occ != null && !item2.Active) { list.Add(new Experiment { Name = "OcclusionCulling forced ON (Auto has it off here)", Apply = delegate { OcclusionCulling.ForcedOn = true; OptimizationManager.SafeRefresh(occ); }, Revert = delegate { OcclusionCulling.ForcedOn = false; OptimizationManager.SafeRefresh(occ); } }); } } list.Add(new Experiment { Name = "only chunk meshes OFF (pieces instanced instead of merged)", Apply = delegate { PieceRenderer.SetCellsSuspended(suspended: true); }, Revert = delegate { PieceRenderer.SetCellsSuspended(suspended: false); } }); ShadowQuality shadows = QualitySettings.shadows; list.Add(new Experiment { Name = "all shadows off (upper bound for shadow work)", Apply = delegate { QualitySettings.shadows = (ShadowQuality)0; }, Revert = delegate { //IL_0001: Unknown result type (might be due to invalid IL or missing references) QualitySettings.shadows = shadows; } }); List<Renderer> hidden = new List<Renderer>(); list.Add(new Experiment { Name = "build pieces hidden (upper bound for piece rendering)", Apply = delegate { SuspendPieces(suspend: true); hidden.Clear(); foreach (WearNTear allInstance in WearNTear.GetAllInstances()) { if ((Object)(object)allInstance != (Object)null) { Renderer[] componentsInChildren = ((Component)allInstance).GetComponentsInChildren<Renderer>(); foreach (Renderer val in componentsInChildren) { if (val.enabled) { val.enabled = false; hidden.Add(val); } } } } }, Revert = delegate { foreach (Renderer item3 in hidden) { if ((Object)(object)item3 != (Object)null) { item3.enabled = true; } } hidden.Clear(); SuspendPieces(suspend: false); } }); List<(Renderer, ShadowCastingMode)> shadowOff = new List<(Renderer, ShadowCastingMode)>(); list.Add(new Experiment { Name = "build piece shadows off (upper bound for piece shadows)", Apply = delegate { //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) SuspendPieces(suspend: true); shadowOff.Clear(); foreach (WearNTear allInstance2 in WearNTear.GetAllInstances()) { if ((Object)(object)allInstance2 != (Object)null) { Renderer[] componentsInChildren = ((Component)allInstance2).GetComponentsInChildren<Renderer>(); foreach (Renderer val in componentsInChildren) { if ((int)val.shadowCastingMode != 0) { shadowOff.Add((val, val.shadowCastingMode)); val.shadowCastingMode = (ShadowCastingMode)0; } } } } }, Revert = delegate { //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) foreach (var (val, shadowCastingMode) in shadowOff) { if ((Object)(object)val != (Object)null) { val.shadowCastingMode = shadowCastingMode; } } shadowOff.Clear(); SuspendPieces(suspend: false); } }); return list; } private static void SuspendPieces(bool suspend) { foreach (Optimization item in OptimizationManager.All) { if (item is PieceRenderer) { item.Suspended = suspend; OptimizationManager.SafeRefresh(item); } } } } internal static class BufferPoolBudget { public static long PerType = 33554432L; public static void SetByRam(int ramMb) { PerType = (long)((ramMb >= 24000) ? 256 : ((ramMb >= 12000) ? 128 : 64)) << 20; } } internal static class BufferPool<T> { private const int MinShift = 10; private const int Classes = 88; private static readonly Stack<T[]>[] _bins; private static readonly long[] _lastUse; private static long _clock; private static readonly int ElemBytes; private static long _kept; public static int Hits; public static int Misses; public static long DroppedBytes; private static long MaxKeptBytes => BufferPoolBudget.PerType; public static long KeptBytes => Interlocked.Read(in _kept) * ElemBytes; static BufferPool() { _bins = new Stack<T[]>[88]; _lastUse = new long[88]; ElemBytes = Marshal.SizeOf(typeof(T)); for (int i = 0; i < 88; i++) { _bins[i] = new Stack<T[]>(); } } private static int Class(int n, out int size) { if (n <= 1024) { size = 1024; return 0; } int num = 0; for (uint num2 = (uint)(n - 1); num2 > 1; num2 >>= 1) { num++; } int num3 = 1 << num - 2; int num4 = (n + num3 - 1) / num3; size = num4 * num3; return (num - 10) * 4 + (num4 - 4); } public static T[] Rent(int n) { int size; int num = Class(n, out size); if (num >= 88) { PieceRenderer.CountAlloc((long)n * (long)ElemBytes); return new T[n]; } Stack<T[]> stack = _bins[num]; _lastUse[num] = Interlocked.Increment(ref _clock); lock (stack) { if (stack.Count > 0) { T[] array = stack.Pop(); Interlocked.Add(ref _kept, -array.Length); Interlocked.Increment(ref Hits); return array; } } Interlocked.Increment(ref Misses); PieceRenderer.CountAlloc((long)size * (long)ElemBytes); return new T[size]; } private static void Drop(long length) { Interlocked.Add(ref DroppedBytes, length * ElemBytes); } private static bool EvictOldest(int except) { int num = -1; for (int i = 0; i < 88; i++) { if (i != except && _bins[i].Count > 0 && (num < 0 || _lastUse[i] < _lastUse[num])) { num = i; } } if (num < 0) { return false; } Stack<T[]> stack = _bins[num]; T[] array; lock (stack) { if (stack.Count == 0) { return true; } array = stack.Pop(); } Interlocked.Add(ref _kept, -array.Length); Drop(array.Length); return true; } public static void Return(T[] a) { if (a == null) { return; } int size; int num = Class(a.Length, out size); if (num >= 88 || size != a.Length) { return; } if ((long)a.Length * (long)ElemBytes > MaxKeptBytes / 4) { Drop(a.Length); return; } _lastUse[num] = Interlocked.Increment(ref _clock); while (Interlocked.Read(in _kept) * ElemBytes + (long)a.Length * (long)ElemBytes > MaxKeptBytes) { if (!EvictOldest(num)) { Drop(a.Length); return; } } Interlocked.Add(ref _kept, a.Length); Stack<T[]> stack = _bins[num]; lock (stack) { stack.Push(a); } } } internal sealed class BuildBench : MonoBehaviour { private sealed class Variant { public string Name; public bool KeepParked; public Action Apply; public Action Revert; } private sealed class Result { public string Name; public float Created = -1f; public float Solved = -1f; public float Total; public bool TimedOut; public int Frames; public int Over33; public int Over50; public int Gcs; public int Pieces; public double FrameAvg; public double Fps; public double Low1; public double Worst; public double CaptureMs; public double SolveMs; public double PaceMs; public long Fell; public long Made; public long PaceMade; public long PaceTicks; public long Woken; public long Held; public int WouldFall; public float Near = -1f; public float Near90 = -1f; public float Near99 = -1f; public double InstMs; public long CatchUp; public long JobFrames; public long TailFrames; public double TailSolverMs; } public static bool HidePieces; private bool _abort; private static readonly FieldRef<ZNetScene, Dictionary<ZDO, ZNetView>> _instances = AccessTools.FieldRefAccess<ZNetScene, Dictionary<ZDO, ZNetView>>("m_instances"); private static readonly HashSet<ZDOID> _wouldFall = new HashSet<ZDOID>(); private static float _passStart; private static int _wouldLogged; private static readonly FieldRef<WearNTear, float> _support = AccessTools.FieldRefAccess<WearNTear, float>("m_support"); private static readonly Func<WearNTear, float> _minSupport = AccessTools.MethodDelegate<Func<WearNTear, float>>(AccessTools.Method(typeof(WearNTear), "GetMinSupport", (Type[])null, (Type[])null), (object)null, true); private static readonly Predicate<ZDO> _isPiece = (ZDO z) => z != null && LoadGuard.IsPiece(z.GetPrefab()); private const float NearRadius = 30f; public static bool Running { get; private set; } private static bool WearDamagePrefix(WearNTear __instance, HitData hitData, ref bool __result) { //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Unknown result type (might be due to invalid IL or missing references) //IL_0073: Unknown result type (might be due to invalid IL or missing references) //IL_009b: Unknown result type (might be due to invalid IL or missing references) //IL_00ff: Unknown result type (might be due to invalid IL or missing references) if (!Running || hitData != null || (Object)(object)__instance == (Object)null) { return true; } ZNetView component = ((Component)__instance).GetComponent<ZNetView>(); ZDOID item = (((Object)(object)component != (Object)null && component.GetZDO() != null) ? component.GetZDO().m_uid : ZDOID.None); if (_wouldFall.Add(item) && _wouldLogged < 40) { _wouldLogged++; Vector3 position = ((Component)__instance).transform.position; Plugin.Log.LogInfo((object)(string.Format("BuildBench: would have broken (wear / support) {0} at {1:F1}, ", ((Object)__instance).name.Replace("(Clone)", ""), position) + $"{Time.realtimeSinceStartup - _passStart:0.0} s into the pass: " + $"support {_support.Invoke(__instance):0.#} (min {_minSupport(__instance):0.#}), solver: {SupportSolver.SourceOf(__instance)}, " + $"area loading {LoadGuard.Why(position)}, building still coming {SupportSolver.BuildingLoading(__instance)}, health {__instance.GetHealthPercentage() * 100f:0}%; " + "chain: " + SupportSolver.ChainOf(__instance))); } __result = false; return false; } public static void Patch(Harmony harmony) { //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Expected O, but got Unknown //IL_00a2: Unknown result type (might be due to invalid IL or missing references) //IL_00af: Expected O, but got Unknown harmony.Patch((MethodBase)AccessTools.Method(typeof(WearNTear), "ApplyDamage", (Type[])null, (Type[])null), new HarmonyMethod(typeof(BuildBench), "WearDamagePrefix", (Type[])null) { priority = 800 }, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); harmony.Patch((MethodBase)AccessTools.Method(typeof(ZDOMan), "FindSectorObjects", new Type[4] { typeof(Vector2s), typeof(SimulationDistance), typeof(List<ZDO>), typeof(List<ZDO>) }, (Type[])null), (HarmonyMethod)null, new HarmonyMethod(typeof(BuildBench), "FindPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } private static void FindPostfix(List<ZDO> sectorObjects, List<ZDO> distantSectorObjects) { if (HidePieces) { sectorObjects?.RemoveAll(_isPiece); distantSectorObjects?.RemoveAll(_isPiece); } } private void Update() { if (!((Object)(object)ZNetScene.instance == (Object)null) && !((Object)(object)Player.m_localPlayer == (Object)null) && Plugin.DevKey(Plugin.BuildBenchKey)) { if (Running) { _abort = true; Msg("Бенчмарк построек: останавливаю после этого шага"); } else if (!Benchmark.Running && !RouteBench.Playing) { ((MonoBehaviour)this).StartCoroutine(Run()); } } } private static void Msg(string s) { if ((Object)(object)MessageHud.instance != (Object)null) { MessageHud.instance.ShowMessage((MessageType)1, s, 0, (Sprite)null, false, true); } Plugin.Log.LogInfo((object)("BuildBench: " + s)); } private static int PiecesOnScene() { //IL_0000: Unknown result type (might be due to invalid IL or missing references) int near; return PiecesOnScene(Vector3.zero, -1f, out near); } private static int PiecesOnScene(Vector3 at, float radius, out int near) { //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Unknown result type (might be due to invalid IL or missing references) int num = 0; near = 0; float num2 = radius * radius; foreach (KeyValuePair<ZDO, ZNetView> item in _instances.Invoke(ZNetScene.instance)) { if (item.Key == null || !LoadGuard.IsPiece(item.Key.GetPrefab())) { continue; } num++; if (radius > 0f) { Vector3 position = item.Key.GetPosition(); float num3 = position.x - at.x; float num4 = position.z - at.z; if (num3 * num3 + num4 * num4 < num2) { near++; } } } return num; } private static void SetPacing(int order, float budgetMs) { ObjectPacing.Order = order; ObjectPacing.ForceBudgetMs = budgetMs; } private List<Variant> Variants() { List<Variant> list = new List<Variant>(); Optimization pacing = OptimizationManager.All.Find((Optimization o) => o.Name == "ObjectPacing"); Action revert = delegate { SetPacing(0, 0f); }; list.Add(new Variant { Name = "по опорам, 4 мс за кадр", Apply = delegate { SetPacing(0, 4f); }, Revert = revert }); list.Add(new Variant { Name = "по опорам, 6 мс за кадр", Apply = delegate { SetPacing(0, 6f); }, Revert = revert }); list.Add(new Variant { Name = "по опорам, 8 мс за кадр", Apply = delegate { SetPacing(0, 8f); }, Revert = revert }); list.Add(new Variant { Name = "как есть (обычные настройки)", Apply = delegate { }, Revert = delegate { } }); list.Add(new Variant { Name = "возврат (узлы решателя сохранены)", KeepParked = true, Apply = delegate { }, Revert = delegate { } }); if (pacing != null) { list.Add(new Variant { Name = "порядок и темп создания игры (без ObjectPacing)", Apply = delegate { pacing.Suspended = true; }, Revert = delegate { pacing.Suspended = false; } }); } return list; } private IEnumerator Run() { Running = true; _abort = false; Player p = Player.m_localPlayer; bool ghost = ((Character)p).InGhostMode(); bool god = ((Character)p).InGodMode(); p.SetGhostMode(true); p.SetGodMode(true); Optimization scanSkip = OptimizationManager.All.Find((Optimization o) => o.Name == "ObjectScanSkip"); bool scanWas = scanSkip?.Suspended ?? false; if (scanSkip != null) { scanSkip.Suspended = true; } List<Result> results = new List<Result>(); Vector3 where = ((Component)p).transform.position; int nearBefore; int before = PiecesOnScene(where, 30f, out nearBefore); int num = SupportSolver.SaveLinks(); Plugin.Log.LogInfo((object)$"BuildBench: supporters saved for {num} pieces before the passes"); Msg($"Бенчмарк построек: {before} деталей на сцене. Не двигайся. Alt+F1 ещё раз - остановить."); yield return (object)new WaitForSeconds(2f); List<Variant> variants = Variants(); for (int vi = 0; vi < variants.Count; vi++) { if (_abort) { break; } Variant v = variants[vi]; Result r = new Result { Name = v.Name }; Msg($"Проход {vi + 1}/{variants.Count}: {v.Name} - выгружаю постройки"); HidePieces = true; float t0 = Time.realtimeSinceStartup; while (PiecesOnScene() > 0 && Time.realtimeSinceStartup - t0 < 30f) { yield return (object)new WaitForSeconds(0.25f); } yield return (object)new WaitForSeconds(1.5f); if (!v.KeepParked) { SupportSolver.Clear(); } LoadGuard.ResetForBench(); GC.Collect(); yield return (object)new WaitForSeconds(1f); v.Apply(); Msg($"Проход {vi + 1}/{variants.Count}: {v.Name} - загружаю"); List<float> frames = new List<float>(4096); int gc0 = GC.CollectionCount(0); double cap0 = SupportSolver.CaptureMs; double sol0 = SupportSolver.SolveMs; long fell0 = SupportSolver.DestroyedCount; long made0 = LoadGuard.CreatedPieces; long cu0 = WearSleep._catchUpFrames; long pm0 = ObjectPacing.MadeTotal; long pt0 = ObjectPacing.TicksWithWork; long wk0 = ObjectPacing.Woken; long hd0 = SupportSolver.BuildingHeld; double pms0 = ObjectPacing.CreateMs; double ims0 = ObjectPacing.InstantiateMs; _wouldFall.Clear(); _wouldLogged = 0; HidePieces = false; float start = Time.realtimeSinceStartup; float lastCheck = 0f; float quietSince = -1f; float lastTail = -10f; long tf0 = WearSleep.SolverFrames; double tm0 = WearSleep.SolverMsTotal; _passStart = start; while (true) { yield return null; frames.Add(Time.unscaledDeltaTime * 1000f); float num2 = Time.realtimeSinceStartup - start; if (r.Created >= 0f && r.Solved < 0f) { r.TailFrames++; if (SupportSolver.JobRunning) { r.JobFrames++; } if (num2 - lastTail >= 2f) { lastTail = num2; double num3 = ((WearSleep.SolverFrames > tf0) ? ((WearSleep.SolverMsTotal - tm0) / (double)(WearSleep.SolverFrames - tf0)) : 0.0); Plugin.Log.LogInfo((object)($"BuildBench tail {num2:0.0} s: solver {num3:0.00} ms a frame (allowed now {WearSleep.Allowed:0.0}), " + $"catching up {WearSleep._catchUpFrames - cu0} frames so far, build pieces waiting to be created {ObjectPacing.PiecesWaiting}, " + $"backlog {SupportSolver.Backlog}; {SupportSolver.NowText()}")); tf0 = WearSleep.SolverFrames; tm0 = WearSleep.SolverMsTotal; } } if (!(num2 - lastCheck >= 0.5f)) { continue; } lastCheck = num2; int near; int num4 = PiecesOnScene(where, 30f, out near); if (r.Created < 0f && (float)num4 >= (float)before * 0.995f) { r.Created = num2; } if (r.Near90 < 0f && (float)near >= (float)nearBefore * 0.9f) { r.Near90 = num2; } if (r.Near99 < 0f && (float)near >= (float)nearBefore * 0.99f) { r.Near99 = num2; } if (r.Near < 0f && near >= nearBefore) { r.Near = num2; } int num5 = SupportSolver.WorkLeft(); if (r.Created >= 0f && r.Solved < 0f && num5 == 0) { r.Solved = num2; } if (r.Created >= 0f && r.Solved >= 0f) { if (quietSince < 0f) { quietSince = num2; } if (num2 - quietSince > 2f) { break; } } if (num2 > 200f) { r.TimedOut = true; break; } if (_abort) { break; } r.Pieces = num4; } r.Total = Time.realtimeSinceStartup - start; v.Revert(); r.Gcs = GC.CollectionCount(0) - gc0; r.CaptureMs = SupportSolver.CaptureMs - cap0; r.SolveMs = SupportSolver.SolveMs - sol0; r.Fell = SupportSolver.DestroyedCount - fell0; r.Made = LoadGuard.CreatedPieces - made0; r.WouldFall = _wouldFall.Count; r.PaceMade = ObjectPacing.MadeTotal - pm0; r.PaceTicks = ObjectPacing.TicksWithWork - pt0; r.PaceMs = ObjectPacing.CreateMs - pms0; r.Woken = ObjectPacing.Woken - wk0; r.Held = SupportSolver.BuildingHeld - hd0; r.CatchUp = WearSleep._catchUpFrames - cu0; r.InstMs = ObjectPacing.InstantiateMs - ims0; r.Frames = frames.Count; if (frames.Count > 0) { double num6 = 0.0; foreach (float item in frames) { num6 += (double)item; if (item > 33.3f) { r.Over33++; } if (item > 50f) { r.Over50++; } } r.FrameAvg = num6 / (double)frames.Count; r.Fps = 1000.0 / r.FrameAvg; List<float> list = new List<float>(frames); list.Sort(); r.Worst = list[list.Count - 1]; int num7 = Math.Max(1, list.Count / 100); double num8 = 0.0; for (int num9 = list.Count - num7; num9 < list.Count; num9++) { num8 += (double)list[num9]; } r.Low1 = 1000.0 / (num8 / (double)num7); } results.Add(r); Msg($"Проход {vi + 1}: все детали за {Fmt(r.Created)}, посчитано за {Fmt(r.Solved)}, упало бы {r.WouldFall}, {r.Fps:0} FPS, худший кадр {r.Worst:0} мс"); yield return (object)new WaitForSeconds(2f); } HidePieces = false; if (scanSkip != null) { scanSkip.Suspended = scanWas; } p.SetGhostMode(ghost); p.SetGodMode(god); Running = false; Write(results, before, where, nearBefore); } private static string Fmt(float t) { if (!(t < 0f)) { return t.ToString("0.0") + " с"; } return "не дождался"; } private static string F(float t) { if (!(t < 0f)) { return t.ToString("0.0"); } return "-"; } private static void Write(List<Result> results, int before, Vector3 where, int nearBefore) { //IL_0029: Unknown result type (might be due to invalid IL or missing references) StringBuilder stringBuilder = new StringBuilder(); stringBuilder.AppendLine(string.Format("FPSDoctor building benchmark - {0:yyyy-MM-dd HH:mm} - FPSDoctor {1} - at {2:F0}, {3} build pieces on the scene", DateTime.Now, "0.18.145", where, before)); stringBuilder.AppendLine("Each pass: the base's pieces unloaded, the building systems reset to a clean start, the variant set, the pieces loaded again as on arrival."); stringBuilder.AppendLine($"Near: until 90% / 99% / every piece within {30f:0} m horizontally ({nearBefore} pieces) is back. Created: until every piece is back on the scene. Solved: until the solver has nothing left to work out."); stringBuilder.AppendLine("Would fall: unique pieces the wear pass would have broken (no damage is done while the benchmark runs; the first ones are in the log with their chain). Held: wear passes under the minimum held because the rest of the building was still coming."); stringBuilder.AppendLine(">33 / >50: frames over 33 / 50 ms per 1000 frames. GC/min: garbage collections a minute. Create: objects made by ObjectPacing, its ms per object (incl. picking), its ticks a second, pieces made right after their supporter."); stringBuilder.AppendLine(); stringBuilder.AppendLine(string.Format("{0,-36} {1,16} {2,8} {3,7} {4,6} {5,5} {6,6} {7,6} {8,6} {9,6} {10,6} {11,8} {12,8} {13,7} {14,7} {15,6} {16,6} {17,6}", "pass", "near 90/99/all", "created", "solved", "fall", "held", "FPS", "1%low", "worst", ">33", ">50", "GC/min", "geom ms", "solv ms", "made", "ms/obj", "tick/s", "woken")); foreach (Result result in results) { double num = ((result.Frames > 0) ? (1000.0 / (double)result.Frames) : 0.0); double num2 = Math.Max(0.01, (double)result.Total / 60.0); stringBuilder.AppendLine(string.Format("{0,-36} {1,16} {2,8} {3,7} {4,6} {5,5} {6,6:0} {7,6:0} {8,6:0} {9,6:0} {10,6:0.0} {11,8:0.0} {12,8:0} {13,7:0} {14,7} {15,6:0.000} {16,6:0.0} {17,6}{18}", result.Name, F(result.Near90) + "/" + F(result.Near99) + "/" + F(result.Near), (result.Created < 0f) ? "-" : result.Created.ToString("0.0"), (result.Solved < 0f) ? "-" : result.Solved.ToString("0.0"), result.WouldFall, result.Held, result.Fps, result.Low1, result.Worst, (double)result.Over33 * num, (double)result.Over50 * num, (double)result.Gcs / num2, result.CaptureMs, result.SolveMs, result.PaceMade, (result.PaceMade > 0) ? (result.PaceMs / (double)result.PaceMade) : 0.0, (result.Total > 0f) ? ((float)result.PaceTicks / result.Total) : 0f, result.Woken, result.TimedOut ? " (stopped at 200 s)" : "")); } stringBuilder.AppendLine(); stringBuilder.AppendLine("Creating an object: ms per object inside the game's CreateObject (clone, components' Awake, registering) / the rest (picking, waking, bookkeeping):"); foreach (Result result2 in results) { if (result2.PaceMade > 0) { stringBuilder.AppendLine($" {result2.Name}: {result2.InstMs / (double)result2.PaceMade:0.000} / {(result2.PaceMs - result2.InstMs) / (double)result2.PaceMade:0.000}"); } } stringBuilder.AppendLine(); stringBuilder.AppendLine("Tail (everything created, the solver still at it): frames, of them with a drop job running, frames the solver caught up (>= SolverCatchUpMs) over the whole pass. Details every 2 s in the log ('BuildBench tail')."); foreach (Result result3 in results) { stringBuilder.AppendLine($" {result3.Name}: tail {result3.TailFrames} frames, drop job running in {result3.JobFrames}, catching up {result3.CatchUp} frames"); } string text = stringBuilder.ToString(); Plugin.Log.LogInfo((object)("\n" + text)); try { string text2 = Path.Combine(Paths.BepInExRootPath, "FPSDoctor-sessions"); Directory.CreateDirectory(text2); string path = Path.Combine(text2, $"buildbench_{DateTime.Now:yyyy-MM-dd_HH-mm}.txt"); File.WriteAllText(path, text); Msg("Бенчмарк построек готов: " + Path.GetFileName(path)); } catch (Exception ex) { Plugin.Log.LogWarning((object)("BuildBench: report not written: " + ex.Message)); } } } internal static class DeepScan { private enum Group { Vegetation, Rocks, Terrain, Water, Characters, Other } private static readonly string[] HeavyWeathers = new string[4] { "Ashlands_storm", "Twilight_SnowStorm", "SnowStorm", "Ashlands_misty" }; private static Group GroupOf(Renderer r) { if (r is SkinnedMeshRenderer) { return Group.Characters; } if ((Object)(object)((Component)r).GetComponent<Heightmap>() != (Object)null) { return Group.Terrain; } Material sharedMaterial = r.sharedMaterial; string text = (((Object)(object)sharedMaterial != (Object)null && (Object)(object)sharedMaterial.shader != (Object)null) ? ((Object)sharedMaterial.shader).name : ""); if (text.IndexOf("Water", StringComparison.OrdinalIgnoreCase) >= 0) { return Group.Water; } if (text.IndexOf("Vegetation", StringComparison.OrdinalIgnoreCase) >= 0 || text.IndexOf("Tree", StringComparison.OrdinalIgnoreCase) >= 0 || text.IndexOf("Leaf", StringComparison.OrdinalIgnoreCase) >= 0 || text.IndexOf("Bush", StringComparison.OrdinalIgnoreCase) >= 0) { return Group.Vegetation; } if (text.IndexOf("Rock", StringComparison.OrdinalIgnoreCase) >= 0) { return Group.Rocks; } if (text.IndexOf("Heightmap", StringComparison.OrdinalIgnoreCase) >= 0 || text.IndexOf("Terrain", StringComparison.OrdinalIgnoreCase) >= 0) { return Group.Terrain; } return Group.Other; } private static bool IsPiece(Renderer r) { if (!PieceRenderer.CellRenderers.Contains(r)) { return (Object)(object)((Component)r).GetComponentInParent<WearNTear>() != (Object)null; } return true; } private static Benchmark.Experiment HideGroup(string name, Group g) { List<Renderer> hidden = new List<Renderer>(); return new Benchmark.Experiment { Name = name, Apply = delegate { hidden.Clear(); Renderer[] array = Object.FindObjectsByType<Renderer>((FindObjectsSortMode)0); foreach (Renderer val in array) { if (val.enabled && !val.forceRenderingOff && ((Component)val).gameObject.activeInHierarchy && !(val is ParticleSystemRenderer) && !(val is LineRenderer) && !(val is TrailRenderer) && !IsPiece(val) && GroupOf(val) == g) { val.forceRenderingOff = true; hidden.Add(val); } } }, Revert = delegate { foreach (Renderer item in hidden) { if ((Object)(object)item != (Object)null) { item.forceRenderingOff = false; } } hidden.Clear(); } }; } private static Benchmark.Experiment ShadowGroup(string name, Func<Renderer, bool> filter) { List<(Renderer, ShadowCastingMode)> changed = new List<(Renderer, ShadowCastingMode)>(); return new Benchmark.Experiment { Name = name, Apply = delegate { //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Unknown result type (might be due to invalid IL or missing references) changed.Clear(); Renderer[] array = Object.FindObjectsByType<Renderer>((FindObjectsSortMode)0); foreach (Renderer val in array) { if (val.enabled && (int)val.shadowCastingMode != 0 && !(val is ParticleSystemRenderer) && filter(val)) { changed.Add((val, val.shadowCastingMode)); val.shadowCastingMode = (ShadowCastingMode)0; } } Plugin.Log.LogInfo((object)$"Deep scan: '{name}' affects {changed.Count} renderers"); }, Revert = delegate { //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) foreach (var (val, shadowCastingMode) in changed) { if ((Object)(object)val != (Object)null) { val.shadowCastingMode = shadowCastingMode; } } changed.Clear(); } }; } private static Benchmark.Experiment Toggle<T>(string name, Func<T, bool> isOn, Action<T, bool> set) where T : Object { List<T> changed = new List<T>(); return new Benchmark.Experiment { Name = name, Apply = delegate { changed.Clear(); T[] array = Object.FindObjectsByType<T>((FindObjectsSortMode)0); foreach (T val in array) { if (isOn(val)) { set(val, arg2: false); changed.Add(val); } } }, Revert = delegate { foreach (T item in changed) { if ((Object)(object)item != (Object)null) { set(item, arg2: true); } } changed.Clear(); } }; } private static Benchmark.Experiment Setting(string name, Action apply, Action revert) { return new Benchmark.Experiment { Name = name, Apply = apply, Revert = revert }; } public static List<Benchmark.Experiment> BuildShadowExperiments() { //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Unknown result type (might be due to invalid IL or missing references) List<Benchmark.Experiment> list = new List<Benchmark.Experiment>(); ShadowQuality shadows = QualitySettings.shadows; list.Add(Setting("quality: all shadows off", delegate { QualitySettings.shadows = (ShadowQuality)0; }, delegate { //IL_0001: Unknown result type (might be due to invalid IL or missing references) QualitySettings.shadows = shadows; })); AddShadowDetail(list); return list; } private static void AddShadowDetail(List<Benchmark.Experiment> list) { //IL_0441: Unknown result type (might be due to invalid IL or missing references) //IL_0446: Unknown result type (might be due to invalid IL or missing references) //IL_0452: Unknown result type (might be due to invalid IL or missing references) list.Add(ShadowGroup("shadows: trees & bushes cast none", (Renderer r) => !IsPiece(r) && GroupOf(r) == Group.Vegetation)); list.Add(ShadowGroup("shadows: rocks & cliffs cast none", (Renderer r) => !IsPiece(r) && GroupOf(r) == Group.Rocks)); list.Add(ShadowGroup("shadows: terrain casts none", (Renderer r) => GroupOf(r) == Group.Terrain)); list.Add(ShadowGroup("shadows: characters & creatures cast none", (Renderer r) => r is SkinnedMeshRenderer)); list.Add(ShadowGroup("shadows: everything else casts none (items, props, locations)", (Renderer r) => !IsPiece(r) && !(r is SkinnedMeshRenderer) && GroupOf(r) == Group.Other)); Benchmark.Experiment experiment = Setting("[test] build piece chunk cells 64 m instead of 16 m (fewer, bigger merged meshes)", delegate { PieceRenderer.SetCellSizeOverride(64); }, delegate { PieceRenderer.SetCellSizeOverride(0); }); experiment.ExtraWarmup = 4f; list.Add(experiment); Benchmark.Experiment experiment2 = Setting("[test] merged piece shadows off (one shadow per cell x material again)", delegate { PieceRenderer.SetMergedShadowsSuspended(suspended: true); }, delegate { PieceRenderer.SetMergedShadowsSuspended(suspended: false); }); experiment2.ExtraWarmup = 4f; list.Add(experiment2); Benchmark.Experiment experiment3 = Setting("[test] build piece chunk cells 256 m (one merged mesh per material for the whole base)", delegate { PieceRenderer.SetCellSizeOverride(256); }, delegate { PieceRenderer.SetCellSizeOverride(0); }); experiment3.ExtraWarmup = 5f; list.Add(experiment3); foreach (Optimization item in OptimizationManager.All) { if (item is MaterialSort || item is GrassBatching || item is WearNTearSmoothing || item is GpuOcclusion || item is DoorSleep || item is AnimatorCulling || item is SupportCache || item is VegetationDetail || item is IdleParticles) { Optimization opt = item; list.Add(Setting("[test] " + opt.Name + " off", delegate { opt.Suspended = true; opt.Refresh(); }, delegate { opt.Suspended = false; opt.Refresh(); })); } } list.Add(Setting("[test] build piece shadow proxies off (detailed piece shadows)", delegate { PieceRenderer.SetProxiesSuspended(suspended: true); }, delegate { PieceRenderer.SetProxiesSuspended(suspended: false); })); List<Renderer> cellShadows = new List<Renderer>(); list.Add(Setting("shadows: build pieces cast none", delegate { //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Invalid comparison between Unknown and I4 PieceRenderer.NoShadows = true; cellShadows.Clear(); foreach (Renderer cellRenderer in PieceRenderer.CellRenderers) { if ((Object)(object)cellRenderer != (Object)null && (int)cellRenderer.shadowCastingMode == 3 && !cellRenderer.forceRenderingOff) { cellRenderer.forceRenderingOff = true; cellShadows.Add(cellRenderer); } } }, delegate { PieceRenderer.NoShadows = false; foreach (Renderer item2 in cellShadows) { if ((Object)(object)item2 != (Object)null) { item2.forceRenderingOff = false; } } cellShadows.Clear(); })); List<InstanceRenderer> grass = new List<InstanceRenderer>(); list.Add(Setting("shadows: grass & clutter cast none", delegate { //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) grass.Clear(); InstanceRenderer[] array = Object.FindObjectsByType<InstanceRenderer>((FindObjectsSortMode)0); foreach (InstanceRenderer val in array) { if ((int)val.m_shadowCasting != 0) { val.m_shadowCasting = (ShadowCastingMode)0; grass.Add(val); } } Plugin.Log.LogInfo((object)$"Deep scan: {grass.Count} grass/clutter renderers were casting shadows"); }, delegate { //IL_0021: Unknown result type (might be due to invalid IL or missing references) foreach (InstanceRenderer item3 in grass) { if ((Object)(object)item3 != (Object)null) { item3.m_shadowCasting = (ShadowCastingMode)1; } } grass.Clear(); })); int cascades = QualitySettings.shadowCascades; if (cascades > 2) { list.Add(Setting($"shadows: {cascades} -> 2 cascades", delegate { QualitySettings.shadowCascades = 2; }, delegate { QualitySettings.shadowCascades = cascades; })); } if (cascades > 1) { list.Add(Setting($"shadows: {cascades} -> 1 cascade", delegate { QualitySettings.shadowCascades = 1; }, delegate { QualitySettings.shadowCascades = cascades; })); } ShadowResolution shadowRes = QualitySettings.shadowResolution; list.Add(Setting($"shadows: resolution {shadowRes} -> Low", delegate { QualitySettings.shadowResolution = (ShadowResolution)0; }, delegate { //IL_0001: Unknown result type (might be due to invalid IL or missing references) QualitySettings.shadowResolution = shadowRes; })); } public static List<Benchmark.Experiment> BuildLogicExperiments() { string[] obj = ((!TestWorld.Active) ? new string[6] { "objects: EVERYTHING hidden", "objects: build pieces hidden", "objects: build pieces' scripts paused", "engine: physics simulation paused", "engine: animations off", "[reference] FPSDoctor optimizations OFF" } : new string[12] { "objects: EVERYTHING hidden", "effects: post-processing off", "engine: game UI hidden", "objects: water hidden", "quality: all shadows off", "quality: 3D render resolution halved", "objects: characters & creatures hidden", "engine: all sound sources off", "engine: physics simulation paused", "engine: animations off", "effects: fog off", "[reference] FPSDoctor optimizations OFF" }); List<Benchmark.Experiment> list = BuildExperiments(); List<Benchmark.Experiment> list2 = new List<Benchmark.Experiment>(); string[] array = obj; foreach (string value in array) { foreach (Benchmark.Experiment item in list) { if (item.Name.StartsWith(value, StringComparison.Ordinal)) { list2.Add(item); break; } } } if (TestWorld.Active) { AddPostEffects(list2); } return list2; } public static List<Benchmark.Experiment> BuildWeatherExperiments() { List<Benchmark.Experiment> list = new List<Benchmark.Experiment>(); EnvMan instance = EnvMan.instance; if ((Object)(object)instance == (Object)null) { return list; } foreach (var item2 in WeatherTour.All()) { string item = item2.Biome; string env = item2.Env; EnvSetup setup = instance.m_environments.Find((EnvSetup e) => e != null && e.m_name == env); if (setup == null || setup.m_psystems == null || setup.m_psystems.Length == 0) { continue; } string name = env; list.Add(new Benchmark.Experiment { Name = "weather: " + item + " - " + env, Apply = delegate { WeatherTour.Force(name); }, Revert = delegate { LogParticles(setup); WeatherTour.Force("Clear"); }, ExtraWarmup = 3f }); List<GameObject> off = new List<GameObject>(); list.Add(new Benchmark.Experiment { Name = "weather: " + env + " with its particles off", Apply = delegate { WeatherTour.Force(name); off.Clear(); GameObject[] psystems = setup.m_psystems; foreach (GameObject val in psystems) { if ((Object)(object)val != (Object)null && val.activeSelf) { val.SetActive(false); off.Add(val); } } }, Revert = delegate { foreach (GameObject item3 in off) { if ((Object)(object)item3 != (Object)null) { item3.SetActive(true); } } off.Clear(); WeatherTour.Force("Clear"); }, ExtraWarmup = 3f }); if (Array.IndexOf(HeavyWeathers, env) < 0) { continue; } List<(ParticleSystemRenderer, float)> sizes = new List<(ParticleSystemRenderer, float)>(); list.Add(new Benchmark.Experiment { Name = "weather: " + env + " with particles at most 10% of the screen", Apply = delegate { WeatherTour.Force(name); sizes.Clear(); GameObject[] psystems = setup.m_psystems; foreach (GameObject val in psystems) { if ((Object)(object)val != (Object)null) { ParticleSystemRenderer[] componentsInChildren = val.GetComponentsInChildren<ParticleSystemRenderer>(true); foreach (ParticleSystemRenderer val2 in componentsInChildren) { if (val2.maxParticleSize > 0.1f) { sizes.Add((val2, val2.maxParticleSize)); val2.maxParticleSize = 0.1f; } } } } }, Revert = delegate { foreach (var (val, maxParticleSize) in sizes) { if ((Object)(object)val != (Object)null) { val.maxParticleSize = maxParticleSize; } } sizes.Clear(); WeatherTour.Force("Clear"); }, ExtraWarmup = 3f }); List<(ParticleSystem, float, int)> rates = new List<(ParticleSystem, float, int)>(); list.Add(new Benchmark.Experiment { Name = "weather: " + env + " with half the particles", Apply = delegate { //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) WeatherTour.Force(name); rates.Clear(); GameObject[] psystems = setup.m_psystems; foreach (GameObject val in psystems) { if ((Object)(object)val != (Object)null) { ParticleSystem[] componentsInChildren = val.GetComponentsInChildren<ParticleSystem>(true); foreach (ParticleSystem val2 in componentsInChildren) { EmissionModule emission = val2.emission; MainModule main = val2.main; rates.Add((val2, ((EmissionModule)(ref emission)).rateOverTimeMultiplier, ((MainModule)(ref main)).maxParticles)); ((EmissionModule)(ref emission)).rateOverTimeMultiplier = ((EmissionModule)(ref emission)).rateOverTimeMultiplier * 0.5f; ((MainModule)(ref main)).maxParticles = Mathf.Max(1, ((MainModule)(ref main)).maxParticles / 2); } } } }, Revert = delegate { //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Unknown result type (might be due to invalid IL or missing references) foreach (var (val, rateOverTimeMultiplier, maxParticles) in rates) { if ((Object)(object)val != (Object)null) { EmissionModule emission = val.emission; ((EmissionModule)(ref emission)).rateOverTimeMultiplier = rateOverTimeMultiplier; MainModule main = val.main; ((MainModule)(ref main)).maxParticles = maxParticles; } } rates.Clear(); WeatherTour.Force("Clear"); }, ExtraWarmup = 3f }); } return list; } private static void LogParticles(EnvSetup setup) { //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_00a4: Unknown result type (might be due to invalid IL or missing references) //IL_00a9: Unknown result type (might be due to invalid IL or missing references) //IL_00c4: Unknown result type (might be due to invalid IL or missing references) //IL_00c9: Unknown result type (might be due to invalid IL or missing references) //IL_00d9: Unknown result type (might be due to invalid IL or missing references) //IL_00de: Unknown result type (might be due to invalid IL or missing references) //IL_00ee: Unknown result type (might be due to invalid IL or missing references) //IL_00f3: Unknown result type (might be due to invalid IL or missing references) //IL_013b: Unknown result type (might be due to invalid IL or missing references) //IL_0140: Unknown result type (might be due to invalid IL or missing references) //IL_018c: Unknown result type (might be due to invalid IL or missing references) StringBuilder stringBuilder = new StringBuilder("Weather " + setup.m_name + " particles:"); GameObject[] psystems = setup.m_psystems; foreach (GameObject val in psystems) { if (!((Object)(object)val == (Object)null)) { ParticleSystem[] componentsInChildren = val.GetComponentsInChildren<ParticleSystem>(true); foreach (ParticleSystem val2 in componentsInChildren) { MainModule main = val2.main; ParticleSystemRenderer component = ((Component)val2).GetComponent<ParticleSystemRenderer>(); object[] obj = new object[5] { ((Object)val).name, ((Object)val2).name, val2.particleCount, ((MainModule)(ref main)).maxParticles, null }; EmissionModule emission = val2.emission; obj[4] = ((EmissionModule)(ref emission)).rateOverTimeMultiplier; string text = string.Format("\n {0}/{1}: live {2}, max {3}, rate {4:0}/s, ", obj); MinMaxCurve val3 = ((MainModule)(ref main)).startSize; object arg = ((MinMaxCurve)(ref val3)).constantMin; val3 = ((MainModule)(ref main)).startSize; object arg2 = ((MinMaxCurve)(ref val3)).constantMax; val3 = ((MainModule)(ref main)).startLifetime; stringBuilder.Append(text + $"size {arg:0.##}-{arg2:0.##}, life {((MinMaxCurve)(ref val3)).constantMax:0.#} s, " + string.Format("screen cap {0:0.##}, mode {1}, ", ((Object)(object)component != (Object)null) ? component.maxParticleSize : 0f, ((Object)(object)component != (Object)null) ? ((object)component.renderMode/*cast due to .constrained prefix*/).ToString() : "-") + string.Format("shader {0}, sim {1}", ((Object)(object)component != (Object)null && (Object)(object)((Renderer)component).sharedMaterial != (Object)null) ? ((Object)((Renderer)component).sharedMaterial.shader).name : "-", ((MainModule)(ref main)).simulationSpace)); } } } Plugin.Log.LogInfo((object)stringBuilder.ToString()); } private static void AddPostEffects(List<Benchmark.Experiment> list) { //IL_0229: Unknown result type (might be due to invalid IL or missing references) //IL_022e: Unknown result type (might be due to invalid IL or missing references) //IL_023b: Unknown result type (might be due to invalid IL or missing references) //IL_023d: Unknown result type (might be due to invalid IL or missing references) //IL_0247: Unknown result type (might be due to invalid IL or missing references) //IL_0265: Unknown result type (might be due to invalid IL or missing references) //IL_0274: Unknown result type (might be due to invalid IL or missing references) //IL_0289: Unknown result type (might be due to invalid IL or missing references) //IL_028e: Unknown result type (might be due to invalid IL or missing references) //IL_029c: Unknown result type (might be due to invalid IL or missing references) Camera mainCamera = Utils.GetMainCamera(); if ((Object)(object)mainCamera == (Object)null) { return; } PostProcessingBehaviour component = ((Component)mainCamera).GetComponent<PostProcessingBehaviour>(); PostProcessingProfile val = (((Object)(object)component != (Object)null) ? component.profile : null); if ((Object)(object)val != (Object)null) { (string, PostProcessingModel)[] obj = new(string, PostProcessingModel)[14] { ("ambient occlusion (SSAO)", (PostProcessingModel)(object)val.ambientOcclusion), ("screen-space reflections", (PostProcessingModel)(object)val.screenSpaceReflection), ("motion blur", (PostProcessingModel)(object)val.motionBlur), ("antialiasing", (PostProcessingModel)(object)val.antialiasing), ("eye adaptation", (PostProcessingModel)(object)val.eyeAdaptation), ("depth of field", (PostProcessingModel)(object)val.depthOfField), ("bloom", (PostProcessingModel)(object)val.bloom), ("color grading", (PostProcessingModel)(object)val.colorGrading), ("chromatic aberration", (PostProcessingModel)(object)val.chromaticAberration), ("grain", (PostProcessingModel)(object)val.grain), ("vignette", (PostProcessingModel)(object)val.vignette), ("dithering", (PostProcessingModel)(object)val.dithering), ("user LUT", (PostProcessingModel)(object)val.userLut), ("post fog", (PostProcessingModel)(object)val.fog) }; StringBuilder stringBuilder = new StringBuilder(); (string, PostProcessingModel)[] array = obj; for (int i = 0; i < array.Length; i++) { (string, PostProcessingModel) tuple = array[i]; string item = tuple.Item1; PostProcessingModel item2 = tuple.Item2; if (item2 != null && item2.enabled) { stringBuilder.Append(item).Append(", "); PostProcessingModel mm = item2; list.Add(Setting("post: " + item + " off", delegate { mm.enabled = false; }, delegate { mm.enabled = true; })); } } Settings settings = val.ambientOcclusion.settings; Plugin.Log.LogInfo((object)($"Post-processing stack: on = {stringBuilder}AO samples {settings.sampleCount}, downsampling {settings.downsampling}, " + $"radius {settings.radius}, intensity {settings.intensity}; AA {val.antialiasing.settings.method}; camera depth {mainCamera.depthTextureMode}")); } Behaviour[] components = ((Component)mainCamera).GetComponents<Behaviour>(); foreach (Behaviour val2 in components) { if (!((Object)(object)val2 == (Object)null) && val2.enabled && !(val2 is PostProcessingBehaviour) && IsImageEffect(val2)) { Behaviour bb = val2; list.Add(Setting("post: " + ((object)val2).GetType().Name + " off", delegate { bb.enabled = false; }, delegate { bb.enabled = true; })); } } } public static List<Benchmark.Experiment> BuildExperiments() { //IL_030d: Unknown result type (might be due to invalid IL or missing references) //IL_0312: Unknown result type (might be due to invalid IL or missing references) //IL_0434: Unknown result type (might be due to invalid IL or missing references) //IL_0439: Unknown result type (might be due to invalid IL or missing references) //IL_0534: Unknown result type (might be due to invalid IL or missing references) //IL_0539: Unknown result type (might be due to invalid IL or missing references) List<Benchmark.Experiment> list = new List<Benchmark.Experiment>(); bool master = Optimization.MasterOn; list.Add(Setting("[reference] FPSDoctor optimizations OFF", delegate { OptimizationManager.SetMaster(on: false); }, delegate { OptimizationManager.SetMaster(master); })); List<Renderer> pieceHidden = new List<Renderer>(); list.Add(Setting("objects: build pieces hidden", delegate { PieceRenderer.SkipDraw = true; pieceHidden.Clear(); Renderer[] array = Object.FindObjectsByType<Renderer>((FindObjectsSortMode)0); foreach (Renderer val in array) { if (val.enabled && !val.forceRenderingOff && IsPiece(val)) { val.forceRenderingOff = true; pieceHidden.Add(val); } } }, delegate { PieceRenderer.SkipDraw = false; foreach (Renderer item in pieceHidden) { if ((Object)(object)item != (Object)null) { item.forceRenderingOff = false; } } pieceHidden.Clear(); })); List<Renderer> allHidden = new List<Renderer>(); List<InstanceRenderer> grassOff = new List<InstanceRenderer>(); list.Add(Setting("objects: EVERYTHING hidden (only game logic, physics, engine left)", delegate { PieceRenderer.SkipDraw = true; allHidden.Clear(); grassOff.Clear(); Renderer[] array = Object.FindObjectsByType<Renderer>((FindObjectsSortMode)0); foreach (Renderer val in array) { if (val.enabled && !val.forceRenderingOff) { val.forceRenderingOff = true; allHidden.Add(val); } } InstanceRenderer[] array2 = Object.FindObjectsByType<InstanceRenderer>((FindObjectsSortMode)0); foreach (InstanceRenderer val2 in array2) { if (((Behaviour)val2).enabled) { ((Behaviour)val2).enabled = false; grassOff.Add(val2); } } }, delegate { PieceRenderer.SkipDraw = false; foreach (Renderer item2 in allHidden) { if ((Object)(object)item2 != (Object)null) { item2.forceRenderingOff = false; } } foreach (InstanceRenderer item3 in grassOff) { if ((Object)(object)item3 != (Object)null) { ((Behaviour)item3).enabled = true; } } allHidden.Clear(); grassOff.Clear(); })); List<WearNTearUpdater> updaters = new List<WearNTearUpdater>(); list.Add(Setting("objects: build pieces' scripts paused (wear/support pass off, still drawn)", delegate { updaters.Clear(); WearNTearUpdater[] array = Object.FindObjectsByType<WearNTearUpdater>((FindObjectsSortMode)0); foreach (WearNTearUpdater val in array) { if (((Behaviour)val).enabled) { ((Behaviour)val).enabled = false; updaters.Add(val); } } }, delegate { foreach (WearNTearUpdater item4 in updaters) { if ((Object)(object)item4 != (Object)null) { ((Behaviour)item4).enabled = true; } } updaters.Clear(); })); list.Add(HideGroup("objects: trees & bushes hidden", Group.Vegetation)); list.Add(HideGroup("objects: rocks & cliffs hidden", Group.Rocks)); list.Add(HideGroup("objects: terrain hidden", Group.Terrain)); list.Add(HideGroup("objects: water hidden", Group.Water)); list.Add(HideGroup("objects: characters & creatures hidden", Group.Characters)); list.Add(HideGroup("objects: everything else hidden (items, props, dungeon/location meshes)", Group.Other)); list.Add(Toggle<InstanceRenderer>("objects: grass & ground clutter off", (Func<InstanceRenderer, bool>)((InstanceRenderer x) => ((Behaviour)x).enabled), (Action<InstanceRenderer, bool>)delegate(InstanceRenderer x, bool on) { ((Behaviour)x).enabled = on; })); list.Add(Toggle<ParticleSystemRenderer>("effects: particles off (smoke, fire, mist, sparks)", (Func<ParticleSystemRenderer, bool>)((ParticleSystemRenderer x) => ((Renderer)x).enabled), (Action<ParticleSystemRenderer, bool>)delegate(ParticleSystemRenderer x, bool on) { ((Renderer)x).enabled = on; })); list.Add(Toggle<Light>("effects: point & spot lights off (fires, torches, candles)", (Func<Light, bool>)((Light x) => ((Behaviour)x).enabled && ((int)x.type == 2 || (int)x.type == 0)), (Action<Light, bool>)delegate(Light x, bool on) { ((Behaviour)x).enabled = on; })); list.Add(Toggle<ReflectionProbe>("effects: reflection probes off", (Func<ReflectionProbe, bool>)((ReflectionProbe x) => ((Behaviour)x).enabled), (Action<ReflectionProbe, bool>)delegate(ReflectionProbe x, bool on) { ((Behaviour)x).enabled = on; })); bool fog = RenderSettings.fog; list.Add(Setting("effects: fog off", delegate { RenderSettings.fog = false; }, delegate { RenderSettings.fog = fog; })); List<Behaviour> postFx = new List<Behaviour>(); list.Add(Setting("effects: post-processing off (SSAO, bloom, sun shafts, color grading...)", delegate { postFx.Clear(); Camera mainCamera = Utils.GetMainCamera(); if (!((Object)(object)mainCamera == (Object)null)) { Behaviour[] components = ((Component)mainCamera).GetComponents<Behaviour>(); foreach (Behaviour val in components) { if ((Object)(object)val != (Object)null && val.enabled && IsImageEffect(val)) { val.enabled = false; postFx.Add(val); } } } }, delegate { foreach (Behaviour item5 in postFx) { if ((Object)(object)item5 != (Object)null) { item5.enabled = true; } } postFx.Clear(); })); ShadowQuality shadows = QualitySettings.shadows; list.Add(Setting("quality: all shadows off", delegate { QualitySettings.shadows = (ShadowQuality)0; }, delegate { //IL_0001: Unknown result type (might be due to invalid IL or missing references) QualitySettings.shadows = shadows; })); AddShadowDetail(list); float shadowDist = QualitySettings.shadowDistance; list.Add(Setting($"quality: shadow distance {shadowDist:0} -> {shadowDist / 2f:0} m", delegate { QualitySettings.shadowDistance = shadowDist / 2f; }, delegate { QualitySettings.shadowDistance = shadowDist; })); float lodBias = QualitySettings.lodBias; list.Add(Setting($"quality: LOD distance halved (bias {lodBias:0.##} -> {lodBias / 2f:0.##})", delegate { QualitySettings.lodBias = lodBias / 2f; }, delegate { QualitySettings.lodBias = lodBias; })); int mip = QualitySettings.globalTextureMipmapLimit; list.Add(Setting("quality: textures at half resolution", delegate { QualitySettings.globalTextureMipmapLimit = mip + 1; }, delegate { QualitySettings.globalTextureMipmapLimit = mip; })); AnisotropicFiltering aniso = QualitySettings.anisotropicFiltering; list.Add(Setting("quality: anisotropic filtering off", delegate { QualitySettings.anisotropicFiltering = (AnisotropicFiltering)0; }, delegate { //IL_0001: Unknown result type (might be due to invalid IL or missing references) QualitySettings.anisotropicFiltering = aniso; })); bool autoRes = UpscaledFrameBuffer.m_autoTargetResolution; uint resV = UpscaledFrameBuffer.m_targetResolutionVertical; list.Add(Setting($"quality: 3D render resolution halved ({Screen.height}p -> {Screen.height / 2}p, GPU fill-rate test)", delegate { UpscaledFrameBuffer.m_autoTargetResolution = false; UpscaledFrameBuffer.m_targetResolutionVertical = (uint)(Screen.height / 2); }, delegate { UpscaledFrameBuffer.m_autoTargetResolution = autoRes; UpscaledFrameBuffer.m_targetResolutionVertical = resV; })); list.Add(Toggle<Animator>("engine: animations off (characters, doors, windmills...)", (Func<Animator, bool>)((Animator x) => ((Behaviour)x).enabled), (Action<Animator, bool>)delegate(Animator x, bool on) { ((Behaviour)x).enabled = on; })); SimulationMode sim = Physics.simulationMode; list.Add(Setting("engine: physics simulation paused", delegate { Physics.simulationMode = (SimulationMode)2; }, delegate { //IL_0001: Unknown result type (might be due to invalid IL or missing references) Physics.simulationMode = sim; })); list.Add(Setting("engine: audio paused", delegate { AudioListener.pause = true; }, delegate { AudioListener.pause = false; })); list.Add(Toggle<AudioSource>("engine: all sound sources off (what the sounds really cost)", (Func<AudioSource, bool>)((AudioSource x) => ((Behaviour)x).enabled), (Action<AudioSource, bool>)delegate(AudioSource x, bool on) { ((Behaviour)x).enabled = on; })); list.Add(Toggle<Canvas>("engine: game UI hidden (HUD, minimap, hotbar)", (Func<Canvas, bool>)((Canvas x) => ((Behaviour)x).enabled && x.isRootCanvas), (Action<Canvas, bool>)delegate(Canvas x, bool on) { ((Behaviour)x).enabled = on; })); return list; } private static bool IsImageEffect(Behaviour b) { if (b is Camera || b is AudioListener || b is FlareLayer) { return false; } Type type = ((object)b).GetType(); if (type.Assembly == typeof(DeepScan).Assembly) { return false; } if (type.GetMethod("OnRenderImage", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) != null) { return true; } string name = type.Name; if (name.IndexOf("PostProcess", StringComparison.OrdinalIgnoreCase) < 0 && name.IndexOf("Occlusion", StringComparison.OrdinalIgnoreCase) < 0 && name.IndexOf("SunShaft", StringComparison.OrdinalIgnoreCase) < 0) { return name.IndexOf("Bloom", StringComparison.OrdinalIgnoreCase) >= 0; } return true; } public static void AppendCensus(StringBuilder sb) { //IL_0376: Unknown result type (might be due to invalid IL or missing references) sb.Append("\nWhat is drawn right now (visible renderers, triangles per group):\n"); Dictionary<string, long> tris = new Dictionary<string, long>(); Dictionary<string, int> dictionary = new Dictionary<string, int>(); Renderer[] array = Object.FindObjectsByType<Renderer>((FindObjectsSortMode)0); foreach (Renderer val in array) { if (!val.enabled || val.forceRenderingOff || !val.isVisible) { continue; } SkinnedMeshRenderer val2 = (SkinnedMeshRenderer)(object)((val is SkinnedMeshRenderer) ? val : null); object obj; if (val2 == null) { MeshFilter component = ((Component)val).GetComponent<MeshFilter>(); obj = ((component != null) ? component.sharedMesh : null); } else { obj = val2.sharedMesh; } Mesh val3 = (Mesh)obj; string key = ((val is ParticleSystemRenderer) ? "particles" : (IsPiece(val) ? "build pieces" : GroupOf(val).ToString().ToLowerInvariant())); dictionary.TryGetValue(key, out var value); dictionary[key] = value + 1; if (!((Object)(object)val3 == (Object)null)) { long num = 0L; for (int j = 0; j < val3.subMeshCount; j++) { num += val3.GetIndexCount(j) / 3; } tris.TryGetValue(key, out var value2); tris[key] = value2 + num; } } long value4; foreach (KeyValuePair<string, int> item in dictionary.OrderByDescending((KeyValuePair<string, int> k) => (!tris.TryGetValue(k.Key, out value4)) ? 0 : value4)) { sb.AppendFormat(" {0,-14} {1,6} renderers {2,9:N0} triangles\n", item.Key, item.Value, tris.TryGetValue(item.Key, out var value3) ? value3 : 0); } sb.Append(" (FPSDoctor chunk meshes count all LOD levels incl. hidden ones, so their triangles are an upper bound)\n"); Texture2D[] array2 = Resources.FindObjectsOfTypeAll<Texture2D>(); long num2 = 0L; List<(Texture2D, long)> list = new List<(Texture2D, long)>(); Texture2D[] array3 = array2; foreach (Texture2D val4 in array3) { if (!((Object)(object)val4 == (Object)null)) { long num3 = EstimateBytes(val4); num2 += num3; list.Add((val4, num3)); } } long num4 = 0L; RenderTexture[] array4 = Resources.FindObjectsOfTypeAll<RenderTexture>(); RenderTexture[] array5 = array4; foreach (RenderTexture val5 in array5) { if ((Object)(object)val5 != (Object)null) { num4 += (long)((Texture)val5).width * (long)((Texture)val5).height * Math.Max(1, val5.volumeDepth) * 8; } } sb.AppendFormat("\nTextures loaded: {0} (~{1:N0} MB est.), render targets: {2} (~{3:N0} MB est.)\n", array2.Length, num2 / 1048576, array4.Length, num4 / 1048576); sb.Append(" biggest textures:\n"); foreach (var (val6, num5) in list.OrderByDescending(((Texture2D, long) x) => x.Item2).Take(15)) { sb.AppendFormat(" {0,7:N1} MB {1}x{2} {3} {4}\n", (double)num5 / 1048576.0, ((Texture)val6).width, ((Texture)val6).height, val6.format, ((Object)val6).name); } Mesh[] array6 = Resources.FindObjectsOfTypeAll<Mesh>(); long num6 = 0L; Mesh[] array7 = array6; foreach (Mesh val7 in array7) { if ((Object)(object)val7 != (Object)null) { num6 += val7.vertexCount; } } sb.AppendFormat("Meshes loaded: {0} ({1:N0} vertices total), materials: {2}, shaders: {3}\n", array6.Length, num6, Resources.FindObjectsOfTypeAll<Material>().Length, Resources.FindObjectsOfTypeAll<Shader>().Length); ParticleSystem[] array8 = Object.FindObjectsByType<ParticleSystem>((FindObjectsSortMode)0); sb.AppendFormat("Particle systems: {0} ({1} playing, {2:N0} live particles); lights: {3} enabled\n", array8.Length, array8.Count((ParticleSystem p) => p.isPlaying), ((IEnumerable<ParticleSystem>)array8).Sum((Func<ParticleSystem, long>)((ParticleSystem p) => p.particleCount)), Object.FindObjectsByType<Light>((FindObjectsSortMode)0).Count((Light l) => ((Behaviour)l).isActiveAndEnabled)); } private static long EstimateBytes(Texture2D t) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0009: Invalid comparison between Unknown and I4 //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Expected I4, but got Unknown //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Invalid comparison between Unknown and I4 //IL_0071: Unknown result type (might be due to invalid IL or missing references) //IL_0074: Invalid comparison between Unknown and I4 //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Invalid comparison between Unknown and I4 //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_0079: Invalid comparison between Unknown and I4 TextureFormat format = t.format; int num; if ((int)format <= 3) { if ((int)format == 1) { goto IL_0081; } if ((int)format != 3) { goto IL_009c; } num = 24; } else { switch (format - 10) { default: if ((int)format == 62) { goto IL_0097; } if ((int)format == 63) { goto IL_0081; } goto IL_009c; case 0: case 16: case 18: break; case 2: case 14: case 15: case 17: case 19: goto IL_0081; case 7: goto IL_008a; case 10: goto IL_008f; case 5: goto IL_0097; case 1: case 3: case 4: case 6: case 8: case 9: case 11: case 12: case 13: goto IL_009c; } num = 4; } goto IL_009f; IL_009f: long num2 = (long)((Texture)t).width * (long)((Texture)t).height * num / 8; if (((Texture)t).mipmapCount <= 1) { return num2; } return num2 * 4 / 3; IL_008a: num = 64; goto IL_009f; IL_008f: num = 128; goto IL_009f; IL_0081: num = 8; goto IL_009f; IL_009c: num = 32; goto IL_009f; IL_0097: num = 16; goto IL_009f; } } internal sealed class EngineProfile : MonoBehaviour { private sealed class Entry { public ProfilerRecorder Rec; public string Name; public string Category; public ProfilerMarkerDataUnit Unit; public double Sum; public double Calls; public long Max; } private const float Seconds = 10f; private bool _running; private void Update() { if (!_running && Plugin.DevKey(Plugin.EngineProfileKey)) { ((MonoBehaviour)this).StartCoroutine(Run()); } } private IEnumerator Run() { _running = true; List<ProfilerRecorderHandle> list = new List<ProfilerRecorderHandle>(); ProfilerRecorderHandle.GetAvailable(list); List<Entry> entries = new List<Entry>(); foreach (ProfilerRecorderHandle item in list) { ProfilerRecorderDescription description = ProfilerRecorderHandle.GetDescription(item); if ((int)((ProfilerRecorderDescription)(ref description)).UnitType == 1 || (int)((ProfilerRecorderDescription)(ref description)).UnitType == 3 || (int)((ProfilerRecorderDescription)(ref description)).UnitType == 2) { ProfilerRecorder rec; try { rec = new ProfilerRecorder(item, 1, (ProfilerRecorderOptions)25); } catch { continue; } if (!((ProfilerRecorder)(ref rec)).Valid) { ((ProfilerRecorder)(ref rec)).Dispose(); continue; } Entry obj2 = new Entry { Rec = rec, Name = ((ProfilerRecorderDescription)(ref description)).Name }; ProfilerCategory category = ((ProfilerRecorderDescription)(ref description)).Category; obj2.Category = ((ProfilerCategory)(ref category)).Name; obj2.Unit = ((ProfilerRecorderDescription)(ref description)).UnitType; entries.Add(obj2); } } bool dev = Debug.isDebugBuild; Plugin.Log.LogInfo((object)($"Engine profile: recording {entries.Count} of {list.Count} markers/counters for {10f:0} s " + "(" + (dev ? "development player: everything" : "release player: only what it exposes") + "). Stand still.")); yield return null; yield return null; int frames = 0; float until = Time.unscaledTime + 10f; double frameMs = 0.0; while (Time.unscaledTime < until) { yield return null; frames++; frameMs += (double)Time.unscaledDeltaTime * 1000.0; foreach (Entry item2 in entries) { if (((ProfilerRecorder)(ref item2.Rec)).Count != 0) { long lastValue = ((ProfilerRecorder)(ref item2.Rec)).LastValue; item2.Sum += lastValue; if (lastValue > item2.Max) { item2.Max = lastValue; } if ((int)item2.Unit == 1) { double calls = item2.Calls; ProfilerRecorderSample sample = ((ProfilerRecorder)(ref item2.Rec)).GetSample(0); item2.Calls = calls + (double)((ProfilerRecorderSample)(ref sample)).Count; } } } } foreach (Entry item3 in entries) { ((ProfilerRecorder)(ref item3.Rec)).Dispose(); } StringBuilder stringBuilder = new StringBuilder(); stringBuilder.AppendLine($"FPSDoctor ENGINE PROFILE: {frames} frames, {frameMs / (double)frames:0.00} ms/frame ({1000.0 * (double)frames / frameMs:0} FPS), " + string.Format("{0}, {1} markers/counters", dev ? "development player" : "release player", entries.Count)); List<Entry> list2 = (from e in entries where (int)e.Unit == 1 && e.Sum > 0.0 orderby e.Sum descending select e).Take(90).ToList(); stringBuilder.AppendLine(" ms/frame max ms calls/frame category / marker"); foreach (Entry item4 in list2) { stringBuilder.AppendLine($" {item4.Sum / (double)frames / 1000000.0,8:0.000} {(double)item4.Max / 1000000.0,7:0.00} {item4.Calls / (double)frames,10:0.0} {item4.Category} / {item4.Name}"); } stringBuilder.AppendLine("By category (sum of its markers - nested ones counted twice, for comparison only):"); foreach (IGrouping<string, Entry> item5 in from e in entries where (int)e.Unit == 1 && e.Sum > 0.0 group e by e.Category into g orderby g.Sum((Entry e) => e.Sum) descending select g) { stringBuilder.AppendLine($" {item5.Sum((Entry e) => e.Sum) / (double)frames / 1000000.0,8:0.000} {item5.Key} ({item5.Count()} markers)"); } stringBuilder.AppendLine("Counters (average per frame):"); foreach (Entry item6 in (from e in entries where (int)e.Unit != 1 && e.Sum > 0.0 orderby e.Category, e.Name select e).Take(120)) { double num = item6.Sum / (double)frames; string arg = (((int)item6.Unit == 2) ? $"{num / 1048576.0:0.00} MB" : $"{num:0.#}"); stringBuilder.AppendLine($" {arg,14} {item6.Category} / {item6.Name}"); } Plugin.Log.LogMessage((object)("\n" + stringBuilder)); _running = false; } } internal static class EngineSplit { [StructLayout(LayoutKind.Sequential, Size = 1)] private struct Marker { } private static PlayerLoopSystem _original; private static bool _installed; private static readonly List<string> Labels = new List<string>(); private static long[] _ticks = new long[0]; private static long[] _frameTicks = new long[0]; private static long[] _prevTicks = new long[0]; private static int _tickFrame = -1; private static int _users; private static int[] _calls = new int[0]; private static long _last; private static int _current = -1; private static int _frames; public static readonly List<(string Label, double Ms, double Calls)> Rows = new List<(string, double, double)>(); private static readonly int[] _topIdx = new int[4]; private static readonly float[] _topMs = new float[4]; public static bool Installed => _installed; public static void Install() { //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_007f: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_01cf: Unknown result type (might be due to invalid IL or missing references) //IL_0097: Unknown result type (might be due to invalid IL or missing references) //IL_00a5: Unknown result type (might be due to invalid IL or missing references) //IL_00cf: Unknown result type (might be due to invalid IL or missing references) //IL_00d4: Unknown result type (might be due to invalid IL or missing references) //IL_00df: Unknown result type (might be due to invalid IL or missing references) //IL_00f5: Unknown result type (might be due to invalid IL or missing references) //IL_0106: Unknown result type (might be due to invalid IL or missing references) //IL_0112: Unknown result type (might be due to invalid IL or missing references) //IL_013d: Unknown result type (might be due to invalid IL or missing references) //IL_0157: Unknown result type (might be due to invalid IL or missing references) //IL_0159: Unknown result type (might be due to invalid IL or missing references) _users++; if (_installed) { Array.Clear(_ticks, 0, _ticks.Length); Array.Clear(_calls, 0, _calls.Length); _frames = 0; return; } try { _original = PlayerLoop.GetCurrentPlayerLoop(); PlayerLoopSystem original = _original; Labels.Clear(); PlayerLoopSystem[] subSystemList = original.subSystemList; if (subSystemList == null) { return; } PlayerLoopSystem[] array = (PlayerLoopSystem[])(object)new PlayerLoopSystem[subSystemList.Length]; for (int i = 0; i < subSystemList.Length; i++) { PlayerLoopSystem val = subSystemList[i]; string text = ((val.type != null) ? val.type.Name : "?"); object obj = ((object)val.subSystemList) ?? ((object)new PlayerLoopSystem[0]); List<PlayerLoopSystem> list = new List<PlayerLoopSystem>(((Array)obj).Length * 2 + 1); PlayerLoopSystem[] array2 = (PlayerLoopSystem[])obj; foreach (PlayerLoopSystem val2 in array2) { list.Add(MakeMarker(text + "." + ((val2.type != null) ? val2.type.Name : "?"))); list.Add(val2); } list.Add(MakeMarker((i == subSystemList.Length - 1) ? "(between frames: outside the player loop)" : "(between phases)")); val.subSystemList = list.ToArray(); array[i] = val; } original.subSystemList = array; _ticks = new long[Labels.Count]; _frameTicks = new long[Labels.Count]; _prevTicks = new long[Labels.Count]; _calls = new int[Labels.Count]; _current = -1; _frames = 0; PlayerLoop.SetPlayerLoop(original); _installed = true; } catch (Exception ex) { Plugin.Log.LogWarning((object)("EngineSplit: install failed: " + ex.Message)); } } public static void Uninstall() { //IL_0025: Unknown result type (might be due to invalid IL or missing references) if (_users > 0) { _users--; } if (_users <= 0 && _installed) { try { PlayerLoop.SetPlayerLoop(_original); } catch (Exception ex) { Plugin.Log.LogWarning((object)("EngineSplit: restore failed: " + ex.Message)); } _installed = false; } } private static PlayerLoopSystem MakeMarker(string label) { //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Expected O, but got Unknown //IL_004d: Unknown result type (might be due to invalid IL or missing references) int index = Labels.Count; Labels.Add(label); return new PlayerLoopSystem { type = typeof(Marker), updateDelegate = (UpdateFunction)delegate { long timestamp = Stopwatch.GetTimestamp(); if (_current >= 0) { _ticks[_current] += timestamp - _last; _frameTicks[_current] += timestamp - _last; } _last = timestamp; int frameCount = Time.frameCount; if (frameCount != _tickFrame) { _tickFrame = frameCount; long[] prevTicks = _prevTicks; _prevTicks = _frameTicks; _frameTicks = prevTicks; Array.Clear(_frameTicks, 0, _frameTicks.Length); } _current = index; _calls[index]++; } }; } public static int LastFrameTop(int[] idx, float[] ms) { if (!_installed) { return 0; } double num = 1000.0 / (double)Stopwatch.Frequency; int i; for (i = 0; i < idx.Length; i++) { int num2 = -1; for (int j = 0; j < _prevTicks.Length; j++) { if (_prevTicks[j] <= 0) { continue; } bool flag = false; for (int k = 0; k < i; k++) { if (idx[k] == j) { flag = true; break; } } if (!flag && (num2 < 0 || _prevTicks[j] > _prevTicks[num2])) { num2 = j; } } if (num2 < 0) { break; } idx[i] = num2; ms[i] = (float)((double)_prevTicks[num2] * num); } return i; } public static void AddLastFrame(ref double[] into) { if (_installed) { if (into == null || into.Length < _prevTicks.Length) { Array.Resize(ref into, _prevTicks.Length); } double num = 1000.0 / (double)Stopwatch.Frequency; for (int i = 0; i < _prevTicks.Length; i++) { into[i] += (double)_prevTicks[i] * num; } } } public static string Label(int i) { if (i < 0 || i >= Labels.Count) { return "?"; } return Short(Labels[i]); } public static void Frame(bool spike, StringBuilder spikeLine) { if (!_installed) { return; } _frames++; if (spike && spikeLine != null) { spikeLine.Append(" | engine:"); int num = LastFrameTop(_topIdx, _topMs); for (int i = 0; i < num; i++) { spikeLine.AppendFormat(" {0} {1:0.00}", Short(Labels[_topIdx[i]]), _topMs[i]); } } } private static string Short(string label) { if (!label.StartsWith("(")) { return label; } return "outside loop"; } public static void FinishWindow() { Rows.Clear(); if (!_installed || _frames == 0) { return; } double num = 1000.0 / (double)Stopwatch.Frequency; for (int i = 0; i < _ticks.Length; i++) { double num2 = (double)_ticks[i] * num / (double)_frames; if (num2 >= 0.02) { Rows.Add((Labels[i], num2, (double)_calls[i] / (double)_frames)); } _ticks[i] = 0L; _calls[i] = 0; } _frames = 0; Rows.Sort(((string Label, double Ms, double Calls) a, (string Label, double Ms, double Calls) b) => b.Ms.CompareTo(a.Ms)); } public static void Append(StringBuilder sb, int max) { if (Rows.Count == 0) { return; } double num = 0.0; foreach (var row in Rows) { num += row.Ms; } sb.AppendFormat("Engine frame breakdown (Unity player loop steps, {0:0.00} ms/frame total):\n", num); for (int i = 0; i < Rows.Count && i < max; i++) { sb.AppendFormat("{0,9:0.000}{1,8:0.#}x {2}\n", Rows[i].Ms, Rows[i].Calls, Rows[i].Label); } } } internal static class FrameBudget { private static ConfigEntry<int> _targetFps; private static ConfigEntry<float> _share; private static int _frame = -1; private static double _left; public static double LastFrameAllowed; public static double LastFrameUsed; private static double _used; private static int _nextFrame = -1; private static int _curFrame = -1; private static double _nextMs; private static double _curMs; public static double TargetMs { get { //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) int num = ((_targetFps != null) ? _targetFps.Value : 0); if (num <= 0) { double num2 = 0.0; try { Resolution currentResolution = Screen.currentResolution; RefreshRate refreshRateRatio = ((Resolution)(ref currentResolution)).refreshRateRatio; num2 = ((RefreshRate)(ref refreshRateRatio)).value; } catch { } num = ((num2 >= 30.0) ? ((int)num2) : 60); } return 1000.0 / (double)num; } } public static double Reserved { get { int frameCount = Time.frameCount; if (_nextFrame == frameCount) { return _nextMs; } if (_curFrame == frameCount) { return _curMs; } return 0.0; } } public static void Bind(ConfigFile cfg) { //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Expected O, but got Unknown //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Expected O, but got Unknown _targetFps = cfg.Bind<int>("Budget", "TargetFps", 0, new ConfigDescription("The frame rate the background work budget is planned for. 0 = the monitor's refresh rate (60 if unknown).", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 500), Array.Empty<object>())); _share = cfg.Bind<float>("Budget", "Share", 0.2f, new ConfigDescription("Share of the target frame time all background work together may take (building support, event checks, object creation).", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.05f, 0.5f), Array.Empty<object>())); } private static void Roll() { int frameCount = Time.frameCount; if (frameCount != _frame) { LastFrameUsed = _used; _frame = frameCount; _used = 0.0; _left = TargetMs * (double)((_share != null) ? _share.Value : 0.2f); LastFrameAllowed = _left; } } public static double Allow(double wantMs, double floorMs = 0.3) { Roll(); return Math.Max(floorMs, Math.Min(wantMs, _left)); } public static void Reserve(double ms) { int frameCount = Time.frameCount; int num = frameCount + 1; if (_nextFrame != num) { if (_nextFrame == frameCount) { _curFrame = _nextFrame; _curMs = _nextMs; } _nextFrame = num; _nextMs = 0.0; } if (ms > _nextMs) { _nextMs = ms; } } public static void Spent(double ms) { Roll(); _left -= ms; _used += ms; } } [DefaultExecutionOrder(-32000)] internal sealed class FrameSplit : MonoBehaviour { private static readonly WaitForEndOfFrame EndOfFrame = new WaitForEndOfFrame(); private long _frameStart; private long _lastEof; private int _startedFrame = -1; private double _sumBusy; private double _sumWait; private int _n; private double _benchBusy; private int _benchN; public static double BusyMs = -1.0; public static double WaitMs = -1.0; public static double LastBusyMs = -1.0; public static double LastWaitMs = -1.0; public static float WaitShare; private double _lastBusy; private static FrameSplit _instance; private void OnEnable() { _instance = this; ((MonoBehaviour)this).StartCoroutine(EofLoop()); } private void FixedUpdate() { MarkStart(); } private void Update() { MarkStart(); } private void MarkStart() { if (_startedFrame == Time.frameCount) { return; } _startedFrame = Time.frameCount; _frameStart = Stopwatch.GetTimestamp(); if (_lastEof != 0L) { double num = (LastWaitMs = (double)(_frameStart - _lastEof) * 1000.0 / (double)Stopwatch.Frequency); _sumWait += num; double num2 = num + _lastBusy; if (num2 > 0.0) { WaitShare = WaitShare * 0.98f + (float)(num / num2) * 0.02f; } } } private IEnumerator EofLoop() { while (true) { yield return EndOfFrame; long timestamp = Stopwatch.GetTimestamp(); if (_startedFrame == Time.frameCount && _frameStart != 0L) { double num = (LastBusyMs = (_lastBusy = (double)(timestamp - _frameStart) * 1000.0 / (double)Stopwatch.Frequency)); _sumBusy += num; _n++; _benchBusy += num; _benchN++; } _lastEof = timestamp; } } public static void ResetAccumulators() { if (!((Object)(object)_instance == (Object)null)) { _instance._benchBusy = 0.0; _instance._benchN = 0; } } public static double AverageBusySinceReset() { if (!((Object)(object)_instance == (Object)null) && _instance._benchN != 0) { return _instance._benchBusy / (double)_instance._benchN; } return -1.0; } public static void FinishWindow() { FrameSplit instance = _instance; if ((Object)(object)instance == (Object)null || instance._n == 0) { BusyMs = (WaitMs = -1.0); return; } BusyMs = insta