Decompiled source of FPSDoctor v0.18.145

FPSDoctor.dll

Decompiled 6 days ago
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