Decompiled source of OnTogetherFrameCare v1.0.0
OnTogetherFrameCare.dll
Decompiled 3 days ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.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 EZhex1991.EZSoftBone; using HarmonyLib; using Microsoft.CodeAnalysis; using PurrNet; using SleepeyDev.OnTogetherFrameCare.Boost; using SleepeyDev.OnTogetherFrameCare.Capture; using SleepeyDev.OnTogetherFrameCare.Core; using SleepeyDev.OnTogetherFrameCare.Experiments; using SleepeyDev.OnTogetherFrameCare.Reports; using SleepeyDev.OnTogetherFrameCare.UI; using TMPro; using Unity.Collections; using Unity.Jobs.LowLevel.Unsafe; using Unity.Mathematics; using Unity.Profiling; using Unity.Profiling.LowLevel.Unsafe; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.InputSystem; using UnityEngine.InputSystem.LowLevel; using UnityEngine.InputSystem.Utilities; using UnityEngine.LowLevel; using UnityEngine.Profiling; using UnityEngine.Rendering; using UnityEngine.Rendering.RenderGraphModule; using UnityEngine.Rendering.Universal; using UnityEngine.Rendering.Universal.Internal; using UnityEngine.SceneManagement; using UnityEngine.Scripting; using UnityEngine.Splines; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("SleepeyDev")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0+9e65385f40678097a8d6e0f1db7771adb5cea0f4")] [assembly: AssemblyProduct("OnTogetherFrameCare")] [assembly: AssemblyTitle("OnTogetherFrameCare")] [assembly: AssemblyVersion("1.0.0.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 SleepeyDev.OnTogetherFrameCare { public static class ModConfig { public static ConfigEntry<bool> OutlinePass; public static ConfigEntry<float> BoostLodBiasFactor; public static ConfigEntry<bool> HideSmallFarObjects; public static ConfigEntry<float> HideSmallRadius; public static ConfigEntry<float> HideFarDistance; public static ConfigEntry<int> BoostFrameRateCap; public static ConfigEntry<bool> RemoteControllerToggleOff; public static ConfigEntry<bool> HiddenTailSoftBoneOff; public static ConfigEntry<bool> RemoteGroundCheckKinematic; public static ConfigEntry<bool> MenuLookupCache; public static ConfigEntry<bool> AmbientSoundLookupCache; public static ConfigEntry<KeyCode> ReportKey; public static ConfigEntry<float> ShortWindowSeconds; public static ConfigEntry<float> LongWindowSeconds; public static ConfigEntry<int> GroupDepth; public static ConfigEntry<int> TopGroups; public static ConfigEntry<int> TopMeshes; public static ConfigEntry<float> NoticeSeconds; public static ConfigEntry<bool> SessionLogEnabled; public static ConfigEntry<int> SessionLogKeepLaunches; public static ConfigEntry<KeyboardShortcut> RunKey; public static ConfigEntry<KeyboardShortcut> PlayersRunKey; public static ConfigEntry<float> SettleSeconds; public static ConfigEntry<float> MeasureSeconds; public static ConfigEntry<float> SmallRadius; public static ConfigEntry<float> FarDistance; public static ConfigEntry<float> LodBiasFactor; public static ConfigEntry<KeyboardShortcut> OutlinePassToggleKey; private static readonly KeyboardShortcut DefaultRunKey = new KeyboardShortcut((KeyCode)283, (KeyCode[])(object)new KeyCode[1] { (KeyCode)304 }); public static ConfigEntry<bool> MainThreadProbes; public static ConfigEntry<float> HitchMs; private static readonly KeyboardShortcut DefaultPlayersRunKey = new KeyboardShortcut((KeyCode)283, (KeyCode[])(object)new KeyCode[2] { (KeyCode)306, (KeyCode)304 }); private static readonly KeyboardShortcut DefaultOutlineToggleKey = new KeyboardShortcut((KeyCode)283, (KeyCode[])(object)new KeyCode[1] { (KeyCode)306 }); public static bool OutlinePassOn { get { if (OutlinePass != null) { return OutlinePass.Value; } return true; } } public static float LodBoostFactor { get { if (BoostLodBiasFactor == null) { return 1f; } return BoostLodBiasFactor.Value; } } public static bool HideFar { get { if (HideSmallFarObjects != null) { return HideSmallFarObjects.Value; } return false; } } public static float HideRadiusMeters { get { if (HideSmallRadius == null) { return 0.75f; } return HideSmallRadius.Value; } } public static float HideDistanceMeters { get { if (HideFarDistance == null) { return 35f; } return HideFarDistance.Value; } } public static int FrameCap { get { if (BoostFrameRateCap == null) { return 600; } return BoostFrameRateCap.Value; } } public static bool RemoteControllerToggleOffOn { get { if (RemoteControllerToggleOff != null) { return RemoteControllerToggleOff.Value; } return true; } } public static bool HiddenTailSoftBoneOffOn { get { if (HiddenTailSoftBoneOff != null) { return HiddenTailSoftBoneOff.Value; } return true; } } public static bool RemoteGroundCheckKinematicOn { get { if (RemoteGroundCheckKinematic != null) { return RemoteGroundCheckKinematic.Value; } return true; } } public static bool MenuCacheOn { get { if (MenuLookupCache != null) { return MenuLookupCache.Value; } return true; } } public static bool AmbientCacheOn { get { if (AmbientSoundLookupCache != null) { return AmbientSoundLookupCache.Value; } return true; } } public static KeyCode Key { get { //IL_0012: Unknown result type (might be due to invalid IL or missing references) if (ReportKey == null) { return (KeyCode)283; } return ReportKey.Value; } } public static float ShortWindow { get { if (ShortWindowSeconds == null) { return 5f; } return ShortWindowSeconds.Value; } } public static float LongWindow { get { if (LongWindowSeconds == null) { return 30f; } return LongWindowSeconds.Value; } } public static int Depth { get { if (GroupDepth == null) { return 3; } return GroupDepth.Value; } } public static int GroupsShown { get { if (TopGroups == null) { return 40; } return TopGroups.Value; } } public static int MeshesShown { get { if (TopMeshes == null) { return 30; } return TopMeshes.Value; } } public static float NoticeFor { get { if (NoticeSeconds == null) { return 3f; } return NoticeSeconds.Value; } } public static bool SessionLog { get { if (SessionLogEnabled != null) { return SessionLogEnabled.Value; } return true; } } public static int KeepLaunches { get { if (SessionLogKeepLaunches == null) { return 20; } return SessionLogKeepLaunches.Value; } } public static KeyboardShortcut RunShortcut { get { //IL_0012: 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) if (RunKey == null) { return DefaultRunKey; } return RunKey.Value; } } public static KeyboardShortcut PlayersRunShortcut { get { //IL_0012: 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) if (PlayersRunKey == null) { return DefaultPlayersRunKey; } return PlayersRunKey.Value; } } public static float Settle { get { if (SettleSeconds == null) { return 1.5f; } return SettleSeconds.Value; } } public static float Measure { get { if (MeasureSeconds == null) { return 3f; } return MeasureSeconds.Value; } } public static float SmallRadiusMeters { get { if (SmallRadius == null) { return 0.75f; } return SmallRadius.Value; } } public static float FarDistanceMeters { get { if (FarDistance == null) { return 35f; } return FarDistance.Value; } } public static float LodFactor { get { if (LodBiasFactor == null) { return 0.5f; } return LodBiasFactor.Value; } } public static KeyboardShortcut OutlineToggleShortcut { get { //IL_0012: 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) if (OutlinePassToggleKey == null) { return DefaultOutlineToggleKey; } return OutlinePassToggleKey.Value; } } public static bool ProbesOn { get { if (MainThreadProbes != null) { return MainThreadProbes.Value; } return true; } } public static double HitchThresholdMs => HitchRules.Clamp((HitchMs != null) ? ((double)HitchMs.Value) : 50.0); public static void Bind(ConfigFile cfg) { //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Expected O, but got Unknown //IL_0097: Unknown result type (might be due to invalid IL or missing references) //IL_00a1: Expected O, but got Unknown //IL_00cf: Unknown result type (might be due to invalid IL or missing references) //IL_00d9: Expected O, but got Unknown //IL_0103: Unknown result type (might be due to invalid IL or missing references) //IL_010d: Expected O, but got Unknown //IL_01e1: Unknown result type (might be due to invalid IL or missing references) //IL_01eb: Expected O, but got Unknown //IL_0219: Unknown result type (might be due to invalid IL or missing references) //IL_0223: Expected O, but got Unknown //IL_0245: Unknown result type (might be due to invalid IL or missing references) //IL_024f: Expected O, but got Unknown //IL_0276: Unknown result type (might be due to invalid IL or missing references) //IL_0280: Expected O, but got Unknown //IL_02a7: Unknown result type (might be due to invalid IL or missing references) //IL_02b1: Expected O, but got Unknown //IL_02df: Unknown result type (might be due to invalid IL or missing references) //IL_02e9: Expected O, but got Unknown //IL_032b: Unknown result type (might be due to invalid IL or missing references) //IL_0335: Expected O, but got Unknown //IL_0345: Unknown result type (might be due to invalid IL or missing references) //IL_0364: Unknown result type (might be due to invalid IL or missing references) //IL_03a1: Unknown result type (might be due to invalid IL or missing references) //IL_03ab: Expected O, but got Unknown //IL_03d9: Unknown result type (might be due to invalid IL or missing references) //IL_03e3: Expected O, but got Unknown //IL_0425: Unknown result type (might be due to invalid IL or missing references) //IL_042f: Expected O, but got Unknown //IL_0471: Unknown result type (might be due to invalid IL or missing references) //IL_047b: Expected O, but got Unknown //IL_04bd: Unknown result type (might be due to invalid IL or missing references) //IL_04c7: Expected O, but got Unknown //IL_04d7: Unknown result type (might be due to invalid IL or missing references) //IL_052f: Unknown result type (might be due to invalid IL or missing references) //IL_0539: Expected O, but got Unknown OutlinePass = cfg.Bind<bool>("Boost", "OutlinePass", true, "Draws the game's black outlines (and solid objects that only have an unlit pass) in a pass of their\nown right after the solid objects, so the engine can batch them instead of switching shaders\nobject by object. Every outline is kept and the picture should look the same. In dense views this gave\nabout 4x the frame rate on the test machine; how much you gain depends on your machine and the view.\nA few materials whose look depends on draw order can differ slightly (an F2 report lists them).\nCtrl+F2 switches it for the current game session only, to compare."); BoostLodBiasFactor = cfg.Bind<float>("Boost", "LodBiasFactor", 1f, new ConfigDescription("Multiplies the LOD bias the game sets. 1.0 keeps the game's own value. Lower values switch\nbuildings, props and trees to their simpler models closer to you: a modest gain (+13 fps in the\ntest view at 0.5, measured without the outline pass; with it on, this and HideSmallFarObjects\ntogether took the view from about 244 to 290 fps, far less time per frame), paid for with\nvisibly simpler models at middle distance and more noticeable switching between detail levels.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.25f, 1f), Array.Empty<object>())); HideSmallFarObjects = cfg.Bind<bool>("Boost", "HideSmallFarObjects", false, "Stops drawing small scenery objects (smaller than SmallRadius) that are farther from the camera\nthan FarDistance. Wide views gain frame rate (+38 fps in the test view without the outline pass,\nless once it is on), but small props, flowers\nand grass far away disappear, and they appear again as you come closer. Your avatar, other players,\nyour desk, customization objects, highlighted objects and 3D text are never hidden."); HideSmallRadius = cfg.Bind<float>("Boost", "SmallRadius", 0.75f, new ConfigDescription("For HideSmallFarObjects: objects whose bounding radius is below this many metres count as small.\nLarger values hide bigger things, which is more noticeable.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.1f, 3f), Array.Empty<object>())); HideFarDistance = cfg.Bind<float>("Boost", "FarDistance", 35f, new ConfigDescription("For HideSmallFarObjects: small objects whose closest point is farther than this many metres from\nthe camera are hidden (shown again once they are closer than this, hidden after 2 m more).\nSmaller values gain more and are more noticeable.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(10f, 200f), Array.Empty<object>())); BoostFrameRateCap = cfg.Bind<int>("Boost", "FrameRateCap", 600, new ConfigDescription("Caps the frame rate at this many frames per second. Saves power and heat when the game runs far above\nyour monitor's refresh rate. It applies only while vSync is off (with vSync on the display sets the\nframe rate), and a lower limit chosen in the game's settings always stays in force. 0 = no cap; values\nfrom 1 to 29 count as 30.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 1000), Array.Empty<object>())); RemoteControllerToggleOff = cfg.Bind<bool>("Boost", "RemoteControllerToggleOff", true, "Other players' avatars: the network code stops switching each avatar's CharacterController off and on around every\nmove. Nothing looks different; their collision capsule catches up with the avatar at the next physics step instead\nof every frame. Only on your computer; other players see nothing change. Measured alone with 22 other players:\nabout -0.18 ms of main thread per frame (-0.12 ms with 12 other players)."); HiddenTailSoftBoneOff = cfg.Bind<bool>("Boost", "HiddenTailSoftBoneOff", true, "Other players' avatars: the tail's soft-bone physics is paused while that avatar wears no tail (the game keeps it\nrunning for a hidden tail) and switched back on as soon as a tail is shown. Only on your computer.\nMeasured alone with 22 other players: about -0.15 ms of main thread per frame (-0.09 ms with 12 other players)."); RemoteGroundCheckKinematic = cfg.Bind<bool>("Boost", "RemoteGroundCheckKinematic", true, "Other players' avatars: the ground-check physics body stops colliding with the ground. The game reads only your own\navatar's ground check, so nothing looks or behaves differently. Only on your computer.\nMeasured alone with 22 other players: about -0.08 ms of main thread per frame (-0.01 ms with 12), within the runs' drift."); MenuLookupCache = cfg.Bind<bool>("Boost", "MenuLookupCache", true, "On the island the game's sound manager searches the whole scene for the main menu every frame. FrameCare answers from\nthe game's own cache, and searches for real after every scene load and at least once a second, so the answer is always\nthe game's. Measured with 12 other players: about -1.15 ms of main thread per frame (177 to 222 fps). Only on your computer."); AmbientSoundLookupCache = cfg.Bind<bool>("Boost", "AmbientSoundLookupCache", true, "On the island the game places the shore and river sounds at the nearest point of two curves around the island every frame,\nwhich takes about 0.17 ms. FrameCare reuses the last position while you move less than 2 cm, for at most 0.1 s, and looks\nit up for real otherwise, after every scene load and whenever a curve changes. Nothing looks different; a reused position\nis at most 0.1 s old, and its distance from you is never off by more than 2 cm. Only on your computer.\nMeasured alone: about -0.07 ms of main thread per frame in a run a player joined during, too small to tell apart from drift."); ReportKey = cfg.Bind<KeyCode>("Report", "Key", (KeyCode)283, "Saves a performance report of what is on screen right now. Ignored while you are typing.\nF1 opens Pomodoro Notify's window; Local Playlist uses F3."); ShortWindowSeconds = cfg.Bind<float>("Report", "ShortWindowSeconds", 5f, new ConfigDescription("The recent frame-rate window in a report. Stand still for this long facing what you\nwant measured, then press the key. Engine counters and timers are averaged over it too.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 30f), Array.Empty<object>())); LongWindowSeconds = cfg.Bind<float>("Report", "LongWindowSeconds", 30f, new ConfigDescription("The longer frame-rate window in a report, for a steadier average.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(5f, 60f), Array.Empty<object>())); GroupDepth = cfg.Bind<int>("Report", "GroupDepth", 3, new ConfigDescription("How many levels of the scene hierarchy name a group: 3 gives e.g.\nIsland/Buildings/PR_LibraryNew.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 6), Array.Empty<object>())); TopGroups = cfg.Bind<int>("Report", "TopGroups", 40, new ConfigDescription("How many of the most expensive groups a report lists.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(5, 500), Array.Empty<object>())); TopMeshes = cfg.Bind<int>("Report", "TopMeshes", 30, new ConfigDescription("How many of the heaviest meshes a report lists.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(5, 500), Array.Empty<object>())); NoticeSeconds = cfg.Bind<float>("Report", "NoticeSeconds", 3f, new ConfigDescription("How long the \"report saved\" notice stays on screen.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.5f, 15f), Array.Empty<object>())); SessionLogEnabled = cfg.Bind<bool>("SessionLog", "Enabled", true, "Write one line per second to BepInEx/FrameCare/sessions: frame rate, where the camera is\nand which report was taken when. One file per game launch. Off also stops the hitch log's file\n(Diagnostics / HitchMs); F2 reports still count and list hitches."); SessionLogKeepLaunches = cfg.Bind<int>("SessionLog", "KeepLaunches", 20, new ConfigDescription("How many game launches' session and hitch logs to keep in BepInEx/FrameCare/sessions. Older ones are\ndeleted once per launch, before the new files are written; this launch always counts as one. Reports are\nnever deleted. 0 = keep all.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 1000), Array.Empty<object>())); RunKey = cfg.Bind<KeyboardShortcut>("Experiments", "RunKey", DefaultRunKey, "Starts an experiment run: FrameCare switches one thing at a time (shadows, LOD, small\nobjects, grass, outlines), measures each for a few seconds, undoes it, and writes a report\ncomparing them. Keep the camera still. Press again to cancel. Either Shift key works; no Ctrl or Alt\nmay be held with it. Ignored while you are typing."); PlayersRunKey = cfg.Bind<KeyboardShortcut>("Experiments", "PlayersRunKey", DefaultPlayersRunKey, "Starts a players run: what the other players' avatars cost on the main thread. FrameCare switches one thing\nabout the remote avatars at a time (animation, network moves, physics, tail, the menu and ambient sound lookups, effects,\nshadows), measures each for a few seconds, undoes it, and writes a report. Your own avatar is never touched. The outline\npass, LOD bias and hiding small objects stay as configured; the frame-rate cap and the five busy-server boosts are\npaused. Needs other players in view. Keep the camera still.\nPress again to cancel. Ctrl+Shift+F2 by default (either Ctrl and either Shift; no Alt may be held): Alt+F2 is the\nNVIDIA overlay's Photo Mode. To use another combination write it here, e.g. \"F7 + LeftShift + LeftControl\"; avoid Alt:\nAlt+F-keys belong to the NVIDIA overlay and Left Alt+Shift switches the Windows keyboard layout.\nA combination FrameCare's other keys already use would start the scenery run instead. Ignored while you are typing."); SettleSeconds = cfg.Bind<float>("Experiments", "SettleSeconds", 1.5f, new ConfigDescription("After a step is applied, how long to wait before measuring, so the change has taken\neffect and the frame it was applied in is not counted.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.5f, 10f), Array.Empty<object>())); MeasureSeconds = cfg.Bind<float>("Experiments", "MeasureSeconds", 3f, new ConfigDescription("How long each step is measured.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 20f), Array.Empty<object>())); SmallRadius = cfg.Bind<float>("Experiments", "SmallRadius", 0.75f, new ConfigDescription("Objects whose bounding radius is below this many metres count as small (" + StepsFor(ExperimentPlan.SmallRadiusLevers) + ").", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.1f, 5f), Array.Empty<object>())); FarDistance = cfg.Bind<float>("Experiments", "FarDistance", 35f, new ConfigDescription("Objects whose closest point is farther than this many metres count as far (" + StepsFor(ExperimentPlan.FarDistanceLevers) + ").", (AcceptableValueBase)(object)new AcceptableValueRange<float>(5f, 300f), Array.Empty<object>())); LodBiasFactor = cfg.Bind<float>("Experiments", "LodBiasFactor", 0.5f, new ConfigDescription("The LOD bias is multiplied by this in " + StepsFor(ExperimentPlan.LodFactorLevers) + " (the game's High uses 1.5).", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.1f, 1f), Array.Empty<object>())); OutlinePassToggleKey = cfg.Bind<KeyboardShortcut>("Experiments", "OutlinePassToggleKey", DefaultOutlineToggleKey, "Switches the outline pass (Boost / OutlinePass) on or off for the rest of this game session, to\ncompare the picture with the game's own drawing. It never changes the config: the next game start\nfollows the config again, and so does changing OutlinePass. Not available during an experiment run.\nEither Ctrl key works; no Shift or Alt may be held with it. Ignored while you are typing."); MainThreadProbes = cfg.Bind<bool>("Diagnostics", "MainThreadProbes", true, "Puts FrameCare timestamps between the engine's player loop steps (scripts, physics, animation, UI, rendering...),\nso F2 reports, experiment reports, the session log and the hitch log can say which part of the main thread a\nframe spent its time in. They measure phases, not the engine's work inside one step, and cost a few hundredths of\na millisecond per frame (the report states the measured cost). Off: those figures are n/a and the hitch log\nfalls back to the frame time alone. Applies at once."); HitchMs = cfg.Bind<float>("Diagnostics", "HitchMs", 50f, new ConfigDescription("A frame whose main thread takes longer than this many milliseconds, counting the time since the previous frame\nended, goes into the hitch log (BepInEx/FrameCare/sessions/hitches_*.csv) with what the frame spent its time on\nand what happened just before. The file is written only while SessionLog / Enabled is on; F2 reports count\nhitches either way.\nWaiting for a frame-rate limit or vSync counts in the frame, so keep this above the limited frame time\n(above 34 ms with the game's 30 fps limit), or every frame is logged.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(20f, 1000f), Array.Empty<object>())); } private static string StepsFor(LeverId[] levers) { return ExperimentPlan.StepsUsing(ExperimentPlan.Steps(0.75f, 35f, 0.5f), levers); } } public sealed class ModRoot : MonoBehaviour { private bool _countersFailed; private bool _probesFailed; private bool _hitchesFailed; private bool _keysFailed; public static ModRoot Instance { get; private set; } public static void Create() { //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Expected O, but got Unknown //IL_0026: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)Instance != (Object)null)) { GameObject val = new GameObject("OTFC_ModRoot") { hideFlags = (HideFlags)61 }; Object.DontDestroyOnLoad((Object)val); Instance = val.AddComponent<ModRoot>(); val.AddComponent<Notice>(); SceneManager.activeSceneChanged += OnSceneChanged; SceneManager.sceneLoaded += OnSceneLoaded; SceneManager.sceneUnloaded += OnSceneUnloaded; Application.quitting += OnQuitting; Application.focusChanged += OnFocusChanged; } } private void Update() { FrameClock.Tick(); GcClock.Tick(); MouseClock.Tick(); if (!_probesFailed) { try { LoopProbe.Tick(); } catch (Exception ex) { Plugin.Log.LogError((object)("FrameCare main thread probes failed, stopping them: " + ex)); _probesFailed = true; LoopProbe.Shutdown(); } } if (!_countersFailed) { try { Counters.Tick(); } catch (Exception ex2) { Plugin.Log.LogError((object)("Engine counters failed, stopping them: " + ex2)); _countersFailed = true; } } try { SessionLog.Tick(); } catch (Exception ex3) { Plugin.Log.LogError((object)("Session log failed, stopping it: " + ex3)); SessionLog.Disable(); } if (!_hitchesFailed) { try { Hitches.Tick(); } catch (Exception ex4) { Plugin.Log.LogError((object)("FrameCare hitch log failed, stopping it: " + ex4)); _hitchesFailed = true; } } OutlinePassLever.FrameStart(); ReportService.Pump(); ExperimentRunner.Tick(); Boosts.Tick(); if (_keysFailed) { return; } try { HandleKeys(); } catch (InvalidOperationException ex5) { _keysFailed = true; Plugin.Log.LogError((object)("FrameCare cannot read its keys (legacy input is not available), so F2 and the other shortcuts are off: " + ex5.Message)); } } private static void HandleKeys() { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) KeyAction keyAction = KeyRules.Resolve(Keys.Down(ModConfig.RunShortcut), Keys.Down(ModConfig.PlayersRunShortcut), Keys.Down(ModConfig.OutlineToggleShortcut), Keys.DownAlone(ModConfig.Key)); if (keyAction == KeyAction.None || GameInfo.IsTyping()) { return; } switch (keyAction) { case KeyAction.SceneryRun: case KeyAction.PlayersRun: if (ExperimentRunner.Running) { ExperimentRunner.Cancel(); } else { ExperimentRunner.Start((keyAction != KeyAction.SceneryRun) ? RunKind.Players : RunKind.Scenery); } break; case KeyAction.OutlineToggle: if (ExperimentRunner.Running) { Notice.Show("FrameCare: an experiment is running, the outline pass toggle waits until it ends"); } else { Boosts.PressOutlineKey(); } break; default: if (ExperimentRunner.Running) { Notice.Show("FrameCare: an experiment is running, no report now (" + ExperimentRunner.CancelKey + " cancels it)"); } else { ReportService.Take(); } break; } } private static void OnSceneChanged(Scene from, Scene to) { Boosts.SceneEvent(); Counters.SceneChanged(); ExperimentRunner.Abort("the scene changed", synchronousWrite: false); Boosts.SceneChanged(); } private static void OnSceneLoaded(Scene scene, LoadSceneMode mode) { Boosts.SceneEvent(); Hitches.Event(HitchEventKind.SceneLoaded); } private static void OnSceneUnloaded(Scene scene) { Boosts.SceneEvent(); } private static void OnFocusChanged(bool focused) { try { Hitches.Event(focused ? HitchEventKind.FocusGained : HitchEventKind.FocusLost); } catch (Exception) { } } private static void OnQuitting() { ExperimentRunner.Abort("the game is quitting", synchronousWrite: true); Boosts.Shutdown("the game is quitting", unpatch: false); MouseClock.Stop(); } private void OnApplicationQuit() { ExperimentRunner.Abort("the game is quitting", synchronousWrite: true); Boosts.Shutdown("the game is quitting", unpatch: false); ScriptTimers.Stop(null); MouseClock.Stop(); SessionLog.Close(); Hitches.Close(); } private void OnDestroy() { SceneManager.activeSceneChanged -= OnSceneChanged; SceneManager.sceneLoaded -= OnSceneLoaded; SceneManager.sceneUnloaded -= OnSceneUnloaded; Application.quitting -= OnQuitting; Application.focusChanged -= OnFocusChanged; ScriptTimers.Stop(null); ExperimentRunner.Abort("FrameCare's object was destroyed", synchronousWrite: true); Boosts.Shutdown("FrameCare's object was destroyed", unpatch: true); if (OutlinePassLever.Active) { OutlinePassLever.Disable(); } LoopProbe.Shutdown(); MouseClock.Stop(); Hitches.Uninstall(); Hitches.Close(); SessionLog.Close(); SrpTiming.Uninstall(); Counters.Stop(); if ((Object)(object)Instance == (Object)(object)this) { Instance = null; } } } [BepInPlugin("com.sleepeydev.ontogether.framecare", "On-Together FrameCare", "1.0.0")] public sealed class Plugin : BaseUnityPlugin { public const string Guid = "com.sleepeydev.ontogether.framecare"; public static ManualLogSource Log { get; private set; } public static string Version { get; private set; } = "?"; private void Awake() { //IL_01a9: Unknown result type (might be due to invalid IL or missing references) //IL_01ae: Unknown result type (might be due to invalid IL or missing references) //IL_01c9: Unknown result type (might be due to invalid IL or missing references) //IL_01e0: Unknown result type (might be due to invalid IL or missing references) //IL_01f7: Unknown result type (might be due to invalid IL or missing references) Log = ((BaseUnityPlugin)this).Logger; Version = ((BaseUnityPlugin)this).Info.Metadata.Version.ToString(); CpuTimes.RecordMainThread(); try { ModConfig.Bind(((BaseUnityPlugin)this).Config); } catch (Exception ex) { Log.LogError((object)("Config binding failed - using defaults for the rest: " + ex)); } Counters.Start(); SrpTiming.Install(); Hitches.Install(); WarnSameKeys(); Boosts.Start(); ModRoot.Create(); Log.LogInfo((object)("FrameCare " + Version + " ready. Outline pass boost " + (ModConfig.OutlinePassOn ? "on" : "off") + ", LOD bias factor x" + ModConfig.LodBoostFactor.ToString("0.##", CultureInfo.InvariantCulture) + ", hiding small far objects " + (ModConfig.HideFar ? "on" : "off") + ", remote avatar boosts " + OnOff(ModConfig.RemoteControllerToggleOffOn) + "/" + OnOff(ModConfig.HiddenTailSoftBoneOffOn) + "/" + OnOff(ModConfig.RemoteGroundCheckKinematicOn) + " (controller/tail/ground check), menu lookup cache " + OnOff(ModConfig.MenuCacheOn) + ", ambient sound lookup cache " + OnOff(ModConfig.AmbientCacheOn) + ", frame-rate cap " + ((FrameCapRules.Clamp(ModConfig.FrameCap) == 0) ? "off" : FrameCapRules.Clamp(ModConfig.FrameCap).ToString(CultureInfo.InvariantCulture)) + ". Press " + ((object)ModConfig.Key/*cast due to .constrained prefix*/).ToString() + " for a performance report, " + Keys.Describe(ModConfig.RunShortcut) + " for an experiment run, " + Keys.Describe(ModConfig.PlayersRunShortcut) + " for a players run, " + Keys.Describe(ModConfig.OutlineToggleShortcut) + " to switch the outline pass for this session. Main thread probes " + (ModConfig.ProbesOn ? "on" : "off") + ", hitch log above " + ModConfig.HitchThresholdMs.ToString("0", CultureInfo.InvariantCulture) + " ms.")); } private static string OnOff(bool on) { if (!on) { return "off"; } return "on"; } private void WarnSameKeys() { //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_0065: 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) //IL_0087: Unknown result type (might be due to invalid IL or missing references) //IL_0093: Unknown result type (might be due to invalid IL or missing references) //IL_00a2: Expected I4, but got Unknown //IL_00a2: Expected I4, but got Unknown //IL_00ef: Unknown result type (might be due to invalid IL or missing references) (string, KeyboardShortcut)[] array = new(string, KeyboardShortcut)[3] { ("RunKey", ModConfig.RunShortcut), ("PlayersRunKey", ModConfig.PlayersRunShortcut), ("OutlinePassToggleKey", ModConfig.OutlineToggleShortcut) }; for (int i = 0; i < array.Length; i++) { for (int j = i + 1; j < array.Length; j++) { if (KeyRules.SameCombination((int)((KeyboardShortcut)(ref array[i].Item2)).MainKey, Keys.Families(array[i].Item2), (int)((KeyboardShortcut)(ref array[j].Item2)).MainKey, Keys.Families(array[j].Item2))) { Log.LogWarning((object)("FrameCare: Experiments / " + array[i].Item1 + " and " + array[j].Item1 + " are the same combination (" + Keys.Describe(array[i].Item2) + "); only " + array[i].Item1 + " will act. Change one in the config.")); } } } } } } namespace SleepeyDev.OnTogetherFrameCare.UI { public static class Keys { public static bool Down(KeyboardShortcut shortcut) { //IL_0002: 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_0008: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_002e: 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) //IL_0037: Expected I4, but got Unknown //IL_0044: Unknown result type (might be due to invalid IL or missing references) KeyCode mainKey = ((KeyboardShortcut)(ref shortcut)).MainKey; if ((int)mainKey == 0 || !Input.GetKeyDown(mainKey)) { return false; } ModifierFamilies modifierFamilies = ModifierFamilies.None; bool otherModifiersHeld = true; foreach (KeyCode modifier in ((KeyboardShortcut)(ref shortcut)).Modifiers) { ModifierFamilies modifierFamilies2 = KeyRules.FamilyOf((int)modifier); if (modifierFamilies2 != ModifierFamilies.None) { modifierFamilies |= modifierFamilies2; } else if (!Input.GetKey(modifier)) { otherModifiersHeld = false; } } return KeyRules.Matches(mainKeyDown: true, modifierFamilies, HeldFamilies(), otherModifiersHeld); } public static bool DownAlone(KeyCode key) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0003: Unknown result type (might be due to invalid IL or missing references) if ((int)key != 0 && Input.GetKeyDown(key)) { return HeldFamilies() == ModifierFamilies.None; } return false; } public static ModifierFamilies HeldFamilies() { ModifierFamilies modifierFamilies = ModifierFamilies.None; if (Input.GetKey((KeyCode)304) || Input.GetKey((KeyCode)303)) { modifierFamilies |= ModifierFamilies.Shift; } if (Input.GetKey((KeyCode)306) || Input.GetKey((KeyCode)305)) { modifierFamilies |= ModifierFamilies.Ctrl; } if (Input.GetKey((KeyCode)308) || Input.GetKey((KeyCode)307) || Input.GetKey((KeyCode)313)) { modifierFamilies |= ModifierFamilies.Alt; } return modifierFamilies; } public static ModifierFamilies Families(KeyboardShortcut shortcut) { //IL_0016: 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_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Expected I4, but got Unknown List<int> list = new List<int>(); foreach (KeyCode modifier in ((KeyboardShortcut)(ref shortcut)).Modifiers) { list.Add((int)modifier); } return KeyRules.Required(list); } public unsafe static string Describe(KeyboardShortcut shortcut) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0037: 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_003e: Expected I4, but got Unknown //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Unknown result type (might be due to invalid IL or missing references) List<string> list = new List<string>(); string text = KeyRules.Describe(Families(shortcut)); if (text.Length > 0) { list.Add(text); } foreach (KeyCode modifier in ((KeyboardShortcut)(ref shortcut)).Modifiers) { if (KeyRules.FamilyOf((int)modifier) == ModifierFamilies.None) { list.Add(((object)(*(KeyCode*)(&modifier))/*cast due to .constrained prefix*/).ToString()); } } list.Add(((object)((KeyboardShortcut)(ref shortcut)).MainKey/*cast due to .constrained prefix*/).ToString()); return string.Join("+", list); } } public sealed class Notice : MonoBehaviour { private sealed class Line { public string Text; public GUIStyle Style; public readonly GUIContent Content = new GUIContent(); public Vector2 Size; } private static Notice _instance; private string _text = ""; private float _hideAt; private string _status; private Texture2D _background; private GUIStyle _style; private int _styleForHeight; private readonly Line _statusLine = new Line(); private readonly Line _textLine = new Line(); public static void Show(string text) { if (!((Object)(object)_instance == (Object)null)) { _instance._text = text ?? ""; _instance._hideAt = Time.unscaledTime + ModConfig.NoticeFor; ((Behaviour)_instance).enabled = true; } } public static void SetStatus(string text) { if (!((Object)(object)_instance == (Object)null)) { _instance._status = (string.IsNullOrEmpty(text) ? null : text); if (_instance._status != null) { ((Behaviour)_instance).enabled = true; } } } private void Awake() { _instance = this; ((Behaviour)this).enabled = false; } private void Update() { if (_status == null && Time.unscaledTime >= _hideAt) { ((Behaviour)this).enabled = false; } } private void OnGUI() { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Invalid comparison between Unknown and I4 if ((int)Event.current.type == 7) { EnsureStyle(); float num = (float)Screen.height * 0.05f; if (_status != null) { num += Draw(_statusLine, _status, num) + (float)_style.padding.top; } if (Time.unscaledTime < _hideAt && _text.Length > 0) { Draw(_textLine, _text, num); } } } private float Draw(Line line, string text, float y) { //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) //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_0068: 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_0074: Unknown result type (might be due to invalid IL or missing references) //IL_008a: Unknown result type (might be due to invalid IL or missing references) if ((object)line.Text != text || line.Style != _style) { line.Text = text; line.Style = _style; line.Content.text = text; line.Size = _style.CalcSize(line.Content); } Vector2 size = line.Size; GUI.Label(new Rect(((float)Screen.width - size.x) * 0.5f, y, size.x, size.y), line.Content, _style); return size.y; } private void EnsureStyle() { //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Expected O, but got Unknown //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_00af: Unknown result type (might be due to invalid IL or missing references) //IL_00b4: Unknown result type (might be due to invalid IL or missing references) //IL_00c6: Unknown result type (might be due to invalid IL or missing references) //IL_00cd: 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_00d9: Unknown result type (might be due to invalid IL or missing references) //IL_00e3: Expected O, but got Unknown //IL_00e8: Expected O, but got Unknown //IL_0109: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_background == (Object)null) { _background = new Texture2D(1, 1, (TextureFormat)4, false) { hideFlags = (HideFlags)61 }; _background.SetPixel(0, 0, new Color(0.07f, 0.08f, 0.1f, 0.94f)); _background.Apply(); _style = null; } if (_style == null || _styleForHeight != Screen.height) { float num = Mathf.Max(1f, (float)Screen.height / 1080f); int num2 = Mathf.RoundToInt(18f * num); int num3 = Mathf.RoundToInt(10f * num); _style = new GUIStyle(GUI.skin.label) { fontSize = Mathf.RoundToInt(18f * num), alignment = (TextAnchor)4, wordWrap = false, padding = new RectOffset(num2, num2, num3, num3) }; _style.normal.background = _background; _style.normal.textColor = Color.white; _styleForHeight = Screen.height; } } private void OnDestroy() { if ((Object)(object)_background != (Object)null) { Object.Destroy((Object)(object)_background); } if ((Object)(object)_instance == (Object)(object)this) { _instance = null; } } } } namespace SleepeyDev.OnTogetherFrameCare.Reports { public sealed class ExperimentData { public int Number; public DateTime Taken; public DateTime Ended; public string Stem; public RunKind Kind; public string FrameCareVersion; public string GameVersion; public string Scene; public int Players; public string SessionLogFile; public bool HasCamera; public Vector3 CameraPosition; public float CameraYaw; public float CameraPitch; public float CameraFov; public double SettleSeconds; public double MeasureSeconds; public double RecoverSeconds; public float SmallRadius; public float FarDistance; public float LodBiasFactor; public PipelineInfo Pipeline; public int LodGroups; public string ScenerySummary; public readonly List<StepResult> Steps = new List<StepResult>(); public readonly List<string> Warnings = new List<string>(); public readonly List<string> BoostsPaused = new List<string>(); public bool FrameCareCapPaused; public int RemoteAvatars; public string PlayersSummaryStart; public string PlayersSummary; public readonly List<string> BoostsInForce = new List<string>(); public readonly List<string> Notes = new List<string>(); public string AbortReason; public int AbortedStep; public List<GameInfo.PluginEntry> Plugins; public string ProbeStatus; public bool? GcIncremental; public double GcSliceNs = double.NaN; public bool Completed => AbortReason == null; } public static class ExperimentJson { public const int SchemaVersion = 1; public static string Format(ExperimentData d) { JsonWriter jsonWriter = new JsonWriter(); jsonWriter.BeginObject(); bool flag = d.Kind == RunKind.Players; jsonWriter.Name("kind").Value("experiment"); jsonWriter.Name("run").Value(flag ? "players" : "scenery"); jsonWriter.Name("schema").Value(1L); jsonWriter.Name("report").Value(d.Number); jsonWriter.Name("taken").Value(d.Taken.ToString("yyyy-MM-ddTHH:mm:ss", CultureInfo.InvariantCulture)); jsonWriter.Name("ended").Value(d.Ended.ToString("yyyy-MM-ddTHH:mm:ss", CultureInfo.InvariantCulture)); jsonWriter.Name("frameCare").Value(d.FrameCareVersion); jsonWriter.Name("game").Value(d.GameVersion); jsonWriter.Name("scene").Value(d.Scene); jsonWriter.Name("players").Value(d.Players); jsonWriter.Name("sessionLog").Value(d.SessionLogFile); jsonWriter.Name("completed").Value(d.Completed); jsonWriter.Name("abortReason").Value(d.AbortReason); jsonWriter.Name("abortedStep").Value(d.AbortedStep); jsonWriter.Name("camera").BeginObject(); jsonWriter.Name("available").Value(d.HasCamera); jsonWriter.Name("x").Value(d.CameraPosition.x); jsonWriter.Name("y").Value(d.CameraPosition.y); jsonWriter.Name("z").Value(d.CameraPosition.z); jsonWriter.Name("yaw").Value(d.CameraYaw); jsonWriter.Name("pitch").Value(d.CameraPitch); jsonWriter.Name("fov").Value(d.CameraFov); jsonWriter.EndObject(); jsonWriter.Name("settings").BeginObject(); jsonWriter.Name("settleSeconds").Value(d.SettleSeconds); jsonWriter.Name("measureSeconds").Value(d.MeasureSeconds); jsonWriter.Name("recoverSeconds").Value(d.RecoverSeconds); jsonWriter.Name("smallRadius"); if (flag) { jsonWriter.Null(); } else { jsonWriter.Value(d.SmallRadius); } jsonWriter.Name("farDistance"); if (flag) { jsonWriter.Null(); } else { jsonWriter.Value(d.FarDistance); } jsonWriter.Name("lodBiasFactor"); if (flag) { jsonWriter.Null(); } else { jsonWriter.Value(d.LodBiasFactor); } jsonWriter.Name("maxMoveMeters").Value(0.25); jsonWriter.Name("maxTurnDegrees").Value(2.0); if (flag) { jsonWriter.Name("farHiddenMeters").Value(30.0); } if (flag) { jsonWriter.Name("farShadowMeters").Value(20.0); } jsonWriter.EndObject(); jsonWriter.Name("frameCareCapPaused").Value(d.FrameCareCapPaused); if (flag) { jsonWriter.Name("remoteAvatars").Value(d.RemoteAvatars); jsonWriter.Name("playersSummaryStart").Value(d.PlayersSummaryStart); jsonWriter.Name("playersSummary").Value(d.PlayersSummary); Strings(jsonWriter, "boostsInForce", d.BoostsInForce); jsonWriter.Name("leftOut").Value("\"Far avatars animated less often\" (every 4th frame beyond 20 m) is not in this run. The only way to animate an Animator on chosen frames is Animator.Update on a disabled Animator. Whether that still evaluates and writes the pose in this game has not been checked, it moves the evaluation from the job workers onto the main thread, and Unity 6000.0.50 and later have a known bug where switching an Animator off and on from code leaves renderers not updating. It needs an in-game check first, or its numbers could show a gain that is not real."); } PipelineInfo pipeline = d.Pipeline; jsonWriter.Name("start").BeginObject(); jsonWriter.Name("qualityIndex").Value(pipeline.QualityIndex); jsonWriter.Name("qualityName").Value(pipeline.QualityName); jsonWriter.Name("lodBias").Value(pipeline.LodBias); jsonWriter.Name("vSyncCount").Value(pipeline.VSyncCount); jsonWriter.Name("targetFrameRate").Value(pipeline.TargetFrameRate); jsonWriter.Name("refreshRate").Value(pipeline.RefreshRate); jsonWriter.Name("screenWidth").Value(pipeline.ScreenWidth); jsonWriter.Name("screenHeight").Value(pipeline.ScreenHeight); jsonWriter.Name("graphicsApi").Value(pipeline.GraphicsApi); jsonWriter.Name("gpu").Value(pipeline.GpuName); jsonWriter.Name("renderThreading").Value(pipeline.RenderThreading); jsonWriter.Name("urpAsset").Value(pipeline.AssetName); jsonWriter.Name("renderScale").Value(pipeline.RenderScale); jsonWriter.Name("msaa").Value(pipeline.Msaa); jsonWriter.Name("mainLightShadows").Value(pipeline.MainLightShadows); jsonWriter.Name("shadowDistance").Value(pipeline.ShadowDistance); jsonWriter.Name("cascadeCount").Value(pipeline.CascadeCount); jsonWriter.Name("cameraShadows").Value(pipeline.CameraShadows); jsonWriter.Name("depthPriming"); if (pipeline.DepthPriming.HasValue) { jsonWriter.Value(pipeline.DepthPriming.Value); } else { jsonWriter.Null(); } jsonWriter.Name("opaqueForwardShaderTags"); if (pipeline.OpaqueTags == null) { jsonWriter.Null(); } else { jsonWriter.BeginArray(); string[] opaqueTags = pipeline.OpaqueTags; foreach (string value in opaqueTags) { jsonWriter.Value(value); } jsonWriter.EndArray(); } jsonWriter.Name("frameCareOutlinePass").Value(pipeline.OutlinePassActive); jsonWriter.Name("frameCareOutlinePassNote").Value(pipeline.OutlinePassNote); jsonWriter.Name("lodGroups"); if (flag) { jsonWriter.Null(); } else { jsonWriter.Value(d.LodGroups); } jsonWriter.Name("scenery"); if (flag) { jsonWriter.Null(); } else { jsonWriter.Value(d.ScenerySummary); } jsonWriter.EndObject(); bool doubtful; double num = ExperimentMath.BaselineFps(d.Steps, out doubtful); double num2 = ExperimentMath.DriftPercent(d.Steps); jsonWriter.Name("baselineFps").Value(num); jsonWriter.Name("baselineTrustworthy").Value(!doubtful); jsonWriter.Name("driftPercent").Value(num2); if (flag) { jsonWriter.Name("mainBaselineMs").Value(ExperimentMath.BaselineMetric(d.Steps, "CPU Main Thread Frame Time")); jsonWriter.Name("mainDriftMs").Value(ExperimentMath.MetricDrift(d.Steps, "CPU Main Thread Frame Time")); } StepPlan[] plan = ExperimentPlan.For(d.Kind, d.SmallRadius, d.FarDistance, d.LodBiasFactor); jsonWriter.Name("steps").BeginArray(); foreach (StepResult step in d.Steps) { Step(jsonWriter, step, num, num2, flag ? d.Steps : null, d.Steps, plan); } jsonWriter.EndArray(); Strings(jsonWriter, "boostsPaused", d.BoostsPaused); Strings(jsonWriter, "warnings", d.Warnings); Strings(jsonWriter, "notes", d.Notes); jsonWriter.Name("plugins").BeginArray(); foreach (GameInfo.PluginEntry plugin in d.Plugins) { jsonWriter.BeginObject().Name("guid").Value(plugin.Guid) .Name("name") .Value(plugin.Name) .Name("version") .Value(plugin.Version) .EndObject(); } jsonWriter.EndArray(); jsonWriter.EndObject(); return jsonWriter.ToString(); } private static void Step(JsonWriter w, StepResult s, double baseline, double drift, IList<StepResult> players, IList<StepResult> all, StepPlan[] plan) { bool num = s.Status == StepStatus.Measured; w.BeginObject(); w.Name("number").Value(s.Number); w.Name("name").Value(s.Name); w.Name("whatChanged").Value(s.WhatChanged); w.Name("baseline").Value(s.IsBaseline); w.Name("status").Value(s.Status.ToString()); w.Name("trustworthy").Value(s.Trustworthy); w.Name("touched").Value(s.Touched); w.Name("touchedAvatars").Value(s.TouchedAvatars); w.Name("detail").Value(s.TouchedDetail); w.Name("error").Value(s.Error); w.Name("measureSeconds").Value(s.MeasureSeconds); if (num) { w.Name("fps").BeginObject().Name("frames") .Value(s.Fps.Frames) .Name("avgFps") .Value(s.Fps.AvgFps) .Name("onePercentLowFps") .Value(s.Fps.OnePercentLowFps) .Name("p50Ms") .Value(s.Fps.P50Ms) .Name("p95Ms") .Value(s.Fps.P95Ms) .Name("p99Ms") .Value(s.Fps.P99Ms) .Name("maxMs") .Value(s.Fps.MaxMs) .EndObject(); w.Name("deltaFps").Value(s.IsBaseline ? double.NaN : ExperimentMath.Delta(s.Fps.AvgFps, baseline)); double num2 = (s.IsBaseline ? double.NaN : ExperimentMath.DeltaPercent(s.Fps.AvgFps, baseline)); w.Name("deltaPercent").Value(num2); w.Name("withinDrift").Value(!double.IsNaN(num2) && !double.IsNaN(drift) && drift != 0.0 && Math.Abs(num2) <= Math.Abs(drift)); w.Name("cpuCores").Value(s.CpuCores); if (players != null) { w.Name("deltaMainMs").Value(s.IsBaseline ? double.NaN : ExperimentMath.MetricDelta(s, players, "CPU Main Thread Frame Time")); } w.Name("gcCollections").CountOrNull(s.GcCollections); w.Name("monoUsedMb").Value(s.MonoUsedMb); MetricValue metricValue = s.Metric("CPU Main Thread Frame Time"); w.Name("phases"); ReportJson.PhaseObject(w, s.Phases, metricValue.Available ? metricValue.Average : double.NaN, null, top: false); w.Name("phasesPartial").Value(ExperimentTable.PartialRows(s)); ExperimentTable.PhaseCheckResult phaseCheckResult = ExperimentTable.PhaseCheck(s, all, plan); w.Name("phaseCheck"); if (phaseCheckResult == null) { w.Null(); } else { w.BeginObject(); w.Name("flagged").Value(phaseCheckResult.Flagged); w.Name("targets").BeginArray(); PhaseGroup[] targets = phaseCheckResult.Targets; foreach (PhaseGroup g in targets) { w.Value(LoopPhases.RowName(g)); } w.EndArray(); w.Name("fpsDeltaPercent").Value(phaseCheckResult.FpsDeltaPercent); w.Name("targetDeltaMs").Value(phaseCheckResult.TargetDeltaMs); w.EndObject(); } if (players != null && s.Phases != null && s.Phases.Frames > 0) { w.Name("phaseDeltasMs").BeginObject(); for (int j = 0; j < 13; j++) { w.Name(LoopPhases.RowNames[j]).Value(s.IsBaseline ? double.NaN : PhaseMath.RowDelta(s, players, j)); } w.Name("Between phases").Value(s.IsBaseline ? double.NaN : PhaseMath.ExtraDelta(s, players, PhaseExtra.Gaps)); w.Name("Between frames").Value(s.IsBaseline ? double.NaN : PhaseMath.ExtraDelta(s, players, PhaseExtra.Between)); w.EndObject(); } w.Name("metrics").BeginObject(); foreach (MetricValue metric in s.Metrics) { w.Name(metric.Name); if (!metric.Available) { w.Null(); } else { w.BeginObject().Name("average").Value(metric.Average) .Name("max") .Value(metric.Max) .Name("samples") .Value(metric.Samples) .Name("unit") .Value(metric.Timer ? "ms" : "count") .EndObject(); } } w.EndObject(); } else { w.Name("fps").Null(); w.Name("metrics").Null(); } Strings(w, "notTrustworthy", s.Untrustworthy); Strings(w, "notes", s.Notes); w.EndObject(); } private static void Strings(JsonWriter w, string name, List<string> values) { w.Name(name).BeginArray(); foreach (string value in values) { w.Value(value); } w.EndArray(); } } public static class ExperimentText { private static readonly CultureInfo Inv = CultureInfo.InvariantCulture; public static string Format(ExperimentData d) { StringBuilder stringBuilder = new StringBuilder(); Header(stringBuilder, d); Settings(stringBuilder, d); Graphics(stringBuilder, d); Results(stringBuilder, d); Details(stringBuilder, d); Section(stringBuilder, "Where each step's frame went (ms, engine timers)"); stringBuilder.Append(ExperimentTable.Split(d.Steps)); Phases(stringBuilder, d); Warnings(stringBuilder, d); Section(stringBuilder, "How to read"); string[] array = ((d.Kind == RunKind.Players) ? ExperimentTable.PlayersHowToRead : ExperimentTable.HowToRead); foreach (string value in array) { stringBuilder.AppendLine(value); } Section(stringBuilder, "Mods loaded"); TextTable textTable = new TextTable("guid", "name", "version"); foreach (GameInfo.PluginEntry plugin in d.Plugins) { textTable.Row(plugin.Guid, plugin.Name, plugin.Version); } stringBuilder.Append(textTable.Render()); return stringBuilder.ToString(); } private static void Header(StringBuilder sb, ExperimentData d) { sb.Append((d.Kind == RunKind.Players) ? "FrameCare players run #" : "FrameCare experiment #").Append(d.Number.ToString("0000", Inv)).Append(" - ") .Append(d.Taken.ToString("yyyy-MM-dd HH:mm:ss", Inv)) .AppendLine(); sb.Append("On-Together ").Append(d.GameVersion).Append(" FrameCare ") .Append(d.FrameCareVersion) .Append(" scene ") .Append(d.Scene) .Append(" players in lobby: ") .Append((d.Players >= 0) ? d.Players.ToString(Inv) : "n/a") .AppendLine(); if (d.HasCamera) { sb.Append("Camera: position (").Append(F1(d.CameraPosition.x)).Append(", ") .Append(F1(d.CameraPosition.y)) .Append(", ") .Append(F1(d.CameraPosition.z)) .Append(") facing ") .Append(F0(d.CameraYaw)) .Append(" deg pitch ") .Append(F0(d.CameraPitch)) .Append(" deg FOV ") .Append(F0(d.CameraFov)) .AppendLine(); } double totalSeconds = (d.Ended - d.Taken).TotalSeconds; if (d.Completed) { sb.Append("Result: completed, ").Append(d.Steps.Count).Append(" steps in ") .Append(F0(totalSeconds)) .AppendLine(" s."); } else { string value = ((d.AbortedStep > 0 && d.AbortedStep <= d.Steps.Count) ? ("at step " + d.AbortedStep + "/" + d.Steps.Count + " (" + d.Steps[d.AbortedStep - 1].Name + ")") : "between steps"); sb.Append("Result: ABORTED ").Append(value).Append(" after ") .Append(F0(totalSeconds)) .Append(" s: ") .Append(d.AbortReason) .AppendLine("."); sb.AppendLine("This is a partial report. Everything that was applied has been restored."); } if (!string.IsNullOrEmpty(d.SessionLogFile)) { sb.Append("Session log: sessions/").Append(d.SessionLogFile).Append(" (lines tagged exp") .Append(d.Number.ToString("0000", Inv)) .AppendLine(":sN while step N was applied)"); } } private static void Settings(StringBuilder sb, ExperimentData d) { Section(sb, "Settings used"); Line(sb, "Timing", "settle " + F2(d.SettleSeconds) + " s, measure " + F2(d.MeasureSeconds) + " s, recover " + F2(d.RecoverSeconds) + " s per step"); if (d.Kind == RunKind.Players) { PlayersSettings(sb, d); return; } StepPlan[] plan = ExperimentPlan.Steps(d.SmallRadius, d.FarDistance, d.LodBiasFactor); Line(sb, "Small objects", "bounds radius < " + F2(d.SmallRadius) + " m (" + ExperimentPlan.StepsUsing(plan, ExperimentPlan.SmallRadiusLevers) + ")"); Line(sb, "Far", "closest point farther than " + F2(d.FarDistance) + " m from the camera (" + ExperimentPlan.StepsUsing(plan, ExperimentPlan.FarDistanceLevers) + ")"); Line(sb, "LOD bias factor", "x" + F2(d.LodBiasFactor) + " (" + ExperimentPlan.StepsUsing(plan, ExperimentPlan.LodFactorLevers) + ")"); Line(sb, "Camera still", "within " + F2(0.25) + " m and " + F2(2.0) + " deg of where the run started, during settle and measure"); Line(sb, "FrameCare boosts", (d.BoostsPaused.Count == 0) ? "none was on, so the baselines are the game's own rendering" : ("paused before the first step, so the baselines measure the game's own rendering, and restored when the run ended: " + string.Join("; ", d.BoostsPaused))); } private static void PlayersSettings(StringBuilder sb, ExperimentData d) { StepPlan[] plan = ExperimentPlan.PlayerSteps(30f); Line(sb, "Remote avatars", PlayerRules.RemoteAvatarsLine(d.RemoteAvatars, d.PlayersSummaryStart, d.PlayersSummary)); Line(sb, "Far", "avatar farther than " + F2(30.0) + " m from the camera (" + ExperimentPlan.StepsUsing(plan, LeverId.FarAvatarsHidden) + "), " + F2(20.0) + " m for shadows (" + ExperimentPlan.StepsUsing(plan, LeverId.FarAvatarShadowsOff) + ")"); Line(sb, "Camera still", "within " + F2(0.25) + " m and " + F2(2.0) + " deg of where the run started, during settle and measure"); Line(sb, "FrameCare boosts", "kept as configured: " + ((d.BoostsInForce.Count > 0) ? string.Join("; ", d.BoostsInForce) : "n/a")); Line(sb, "Paused for the run", (d.BoostsPaused.Count == 0) ? "nothing (none of the frame-rate cap, the remote avatar boosts, the menu lookup cache and the ambient sound lookup cache was in force)" : (string.Join("; ", d.BoostsPaused) + ", restored when the run ended")); Line(sb, "Render saver", "BlueSage's Player Render Saver was off when the run started (a players run refuses it)"); Line(sb, "Not in this run", "far avatars animated less often (see How to read)"); } private static void Graphics(StringBuilder sb, ExperimentData d) { PipelineInfo pipeline = d.Pipeline; Section(sb, "Graphics at the start of the run"); Line(sb, "Quality level", pipeline.QualityName + " (" + pipeline.QualityIndex + ") LOD bias " + F2(pipeline.LodBias) + " vSync " + pipeline.VSyncCount + " target fps " + pipeline.TargetFrameRate); Line(sb, "Screen", pipeline.ScreenWidth + "x" + pipeline.ScreenHeight + " " + pipeline.FullScreenMode + " @ " + F0(pipeline.RefreshRate) + " Hz"); Line(sb, "Graphics", pipeline.GraphicsApi + " " + pipeline.GpuName + " threading " + pipeline.RenderThreading); Line(sb, "CPU", pipeline.Cpu + " (" + pipeline.CpuThreads + " threads)"); if (pipeline.IsUrp) { Line(sb, "URP asset", pipeline.AssetName + " render scale " + F2(pipeline.RenderScale) + " MSAA " + pipeline.Msaa + "x"); Line(sb, "Opaque pass", ReportText.OpaquePassLine(pipeline)); Line(sb, "Shadows", pipeline.MainLightShadows ? ("distance " + F1(pipeline.ShadowDistance) + " m " + pipeline.CascadeCount + " cascades map " + pipeline.ShadowmapResolution + " camera shadows " + pipeline.CameraShadows) : "main light shadows off"); } else { Line(sb, "Pipeline", "not URP"); } if (d.Kind == RunKind.Scenery) { Line(sb, "Scene", d.LodGroups + " LODGroups (all step 5 can affect)"); if (!string.IsNullOrEmpty(d.ScenerySummary)) { Line(sb, "Scenery", d.ScenerySummary); } } } private static void Results(StringBuilder sb, ExperimentData d) { Section(sb, "Results"); bool flag = d.Kind == RunKind.Players; sb.Append(ExperimentTable.Results(d.Steps, flag)); bool doubtful; double num = ExperimentMath.BaselineFps(d.Steps, out doubtful); double num2 = ExperimentMath.DriftPercent(d.Steps); sb.Append("Baseline: ").Append(double.IsNaN(num) ? "n/a" : (F1(num) + " fps")).Append(doubtful ? " (mean of the measured baselines, all of them marked *: every delta is marked ~)" : " (mean of the measured baselines not marked *)") .Append(" drift start -> end: ") .Append(double.IsNaN(num2) ? "n/a" : (num2.ToString("+0.0;-0.0;0.0", Inv) + "%")) .AppendLine(); if (!double.IsNaN(num2) && num2 != 0.0) { sb.Append("Deltas marked ").Append("?").Append(" are no bigger than that drift (") .Append(Math.Abs(num2).ToString("0.0", Inv)) .AppendLine("%) and cannot be told apart from it."); } if (flag) { double d2 = ExperimentMath.BaselineMetric(d.Steps, "CPU Main Thread Frame Time"); double num3 = ExperimentMath.MetricDrift(d.Steps, "CPU Main Thread Frame Time"); sb.Append("Main thread baseline: ").Append(double.IsNaN(d2) ? "n/a" : (d2.ToString("0.00", Inv) + " ms")).Append(" drift start -> end: ") .Append(double.IsNaN(num3) ? "n/a" : (num3.ToString("+0.00;-0.00;0.00", Inv) + " ms")) .AppendLine(); if (!double.IsNaN(num3) && num3 != 0.0) { sb.Append("Δ main ms values marked ").Append("?").Append(" are no bigger than that drift (") .Append(Math.Abs(num3).ToString("0.00", Inv)) .AppendLine(" ms) and cannot be told apart from it."); } } string text = ExperimentTable.Marks(d.Steps); if (text.Length > 0) { sb.AppendLine("Not trustworthy (*):"); sb.Append(text); } } private static void Details(StringBuilder sb, ExperimentData d) { Section(sb, "Step details"); sb.Append(ExperimentTable.Details(d.Steps)); sb.AppendLine(); foreach (StepResult step in d.Steps) { if (string.IsNullOrEmpty(step.TouchedDetail) && string.IsNullOrEmpty(step.Error) && step.Notes.Count <= 0) { continue; } sb.Append(step.Number.ToString(Inv)).Append(". ").Append(step.Name) .AppendLine(":"); if (!string.IsNullOrEmpty(step.TouchedDetail)) { sb.Append(" ").AppendLine(step.TouchedDetail); } if (!string.IsNullOrEmpty(step.Error)) { sb.Append(" FAILED, undone and skipped: ").AppendLine(step.Error); } if (step.Status == StepStatus.Measured && step.MeasureSeconds > 0.0) { sb.Append(" measured over ").Append(F2(step.MeasureSeconds)).AppendLine(" s"); } foreach (string note in step.Notes) { sb.Append(" restore: ").AppendLine(note); } } foreach (string note2 in d.Notes) { sb.Append("restore: ").AppendLine(note2); } } private static void Phases(StringBuilder sb, ExperimentData d) { Section(sb, "Main thread by phase per step (ms, FrameCare's main thread probes)"); StepPlan[] plan = ExperimentPlan.For(d.Kind, d.SmallRadius, d.FarDistance, d.LodBiasFactor); sb.Append("Probes: ").AppendLine(d.ProbeStatus ?? "n/a"); sb.Append(ExperimentTable.PhaseTable(d.Steps, plan)); if (d.Kind == RunKind.Players) { sb.AppendLine(); sb.AppendLine("Against the baselines' mean (ms; below zero is a saving):"); sb.Append(ExperimentTable.PhaseDeltaTable(d.Steps, plan)); } List<string> list = ExperimentTable.PhaseChecks(d.Steps, plan); if (list.Count > 0) { sb.AppendLine("Frame rate moved without the rows the step works on (!):"); foreach (string item in list) { sb.AppendLine(item); } } List<string> list2 = ExperimentTable.PhasePartialLines(d.Steps); if (list2.Count > 0) { sb.AppendLine("Probe rows for only part of the step (partial):"); foreach (string item2 in list2) { sb.AppendLine(item2); } } string[] phaseHowToRead = ExperimentTable.PhaseHowToRead; foreach (string value in phaseHowToRead) { sb.AppendLine(value); } sb.Append("- Garbage collector: ").AppendLine(GcRules.Line(-1L, 0.0, -1L, -1L, d.GcIncremental, d.GcSliceNs).Replace("collections n/a; Mono heap n/a; ", "")); } private static void Warnings(StringBuilder sb, ExperimentData d) { Section(sb, "Warnings"); if (d.Warnings.Count == 0) { sb.AppendLine("None: no frame-rate cap, no known quality or shadow mod loaded, baselines agree."); return; } foreach (string warning in d.Warnings) { sb.Append("- ").AppendLine(warning); } } private static void Section(StringBuilder sb, string title) { sb.AppendLine(); sb.Append("== ").Append(title).AppendLine(" =="); } private static void Line(StringBuilder sb, string label, string value) { sb.Append(label.PadRight(20)).Append(value).AppendLine(); } private static string F0(double v) { return v.ToString("0", Inv); } private static string F1(double v) { return v.ToString("0.0", Inv); } private static string F2(double v) { return v.ToString("0.##", Inv); } } public sealed class ReportData { public int Number; public DateTime Taken; public string Stem; public string FrameCareVersion; public string GameVersion; public string Scene; public int Players; public bool HasCamera; public Vector3 CameraPosition; public float CameraYaw; public float CameraPitch; public float CameraFov; public double ShortWindowSeconds; public double LongWindowSeconds; public WindowStats FpsShort; public WindowStats FpsLong; public WindowStats SrpShort; public bool FrameTiming; public int FrameTimingFrames; public double CpuFrameMs; public double CpuMainThreadMs; public double CpuRenderThreadMs; public double GpuFrameMs; public PipelineInfo Pipeline; public BoostInfo Boost; public List<MetricValue> Counters; public List<MetricValue> Timers; public double FrameMs; public string FrameSplitVerdict; public SceneReport Scene3D; public List<InstancerStat> Instancers; public List<GameInfo.PluginEntry> Plugins; public int TopGroups; public int TopMeshes; public int GroupDepth; public CpuThreadsInfo CpuThreads; public PhaseWindow MainThread; public string ProbeStatus; public double PhaseCpuMainMs = double.NaN; public double SrpAvgMs = double.NaN; public double SrpP95Ms = double.NaN; public double SrpMaxMs = double.NaN; public double EngineLoopMs = double.NaN; public long GcCollections = -1L; public string GcCollectNote; public long MonoUsedBytes = -1L; public long MonoHeapBytes = -1L; public bool? GcIncremental; public double GcSliceNs = double.NaN; public ScriptTimersInfo ScriptTimers; public RemoteCensus Census; public HitchSummary Hitches; public double CaptureMs; public string SessionLogFile; } public static class ReportJson { public const int SchemaVersion = 2; public static string Format(ReportData d) { JsonWriter jsonWriter = new JsonWriter(); jsonWriter.BeginObject(); jsonWriter.Name("kind").Value("report"); jsonWriter.Name("schema").Value(2L); jsonWriter.Name("report").Value(d.Number); jsonWriter.Name("taken").Value(d.Taken.ToString("yyyy-MM-ddTHH:mm:ss", CultureInfo.InvariantCulture)); jsonWriter.Name("frameCare").Value(d.FrameCareVersion); jsonWriter.Name("game").Value(d.GameVersion); jsonWriter.Name("scene").Value(d.Scene); jsonWriter.Name("players").Value(d.Players); jsonWriter.Name("captureMs").Value(d.CaptureMs); jsonWriter.Name("sessionLog").Value(d.SessionLogFile); jsonWriter.Name("camera").BeginObject(); jsonWriter.Name("available").Value(d.HasCamera); jsonWriter.Name("x").Value(d.CameraPosition.x); jsonWriter.Name("y").Value(d.CameraPosition.y); jsonWriter.Name("z").Value(d.CameraPosition.z); jsonWriter.Name("yaw").Value(d.CameraYaw); jsonWriter.Name("pitch").Value(d.CameraPitch); jsonWriter.Name("fov").Value(d.CameraFov); jsonWriter.EndObject(); jsonWriter.Name("fps").BeginObject(); Window(jsonWriter, "short", d.ShortWindowSeconds, d.FpsShort); Window(jsonWriter, "long", d.LongWindowSeconds, d.FpsLong); Window(jsonWriter, "renderPipelineMainThread", d.ShortWindowSeconds, d.SrpShort); jsonWriter.Name("frameTiming").BeginObject(); jsonWriter.Name("available").Value(d.FrameTiming); jsonWriter.Name("frames").Value(d.FrameTimingFrames); jsonWriter.Name("cpuFrameMs").Value(d.CpuFrameMs); jsonWriter.Name("cpuMainThreadMs").Value(d.CpuMainThreadMs); jsonWriter.Name("cpuRenderThreadMs").Value(d.CpuRenderThreadMs); jsonWriter.Name("gpuFrameMs").Value(d.GpuFrameMs); jsonWriter.EndObject(); jsonWriter.EndObject(); jsonWriter.Name("frameSplit").BeginObject(); jsonWriter.Name("windowSeconds").Value(d.ShortWindowSeconds); jsonWriter.Name("avgFrameMs").Value(d.FrameMs); jsonWriter.Name("verdict").Value(d.FrameSplitVerdict); Metrics(jsonWriter, "timers", d.Timers); jsonWriter.Name("gcCollectNote").Value(d.GcCollectNote); jsonWriter.EndObject(); jsonWriter.Name("gc").BeginObject(); jsonWriter.Name("windowSeconds").Value(d.ShortWindowSeconds); jsonWriter.Name("collections").CountOrNull(d.GcCollections); jsonWriter.Name("monoUsedBytes").CountOrNull(d.MonoUsedBytes); jsonWriter.Name("monoHeapBytes").CountOrNull(d.MonoHeapBytes); jsonWriter.Name("incremental"); if (d.GcIncremental.HasValue) { jsonWriter.Value(d.GcIncremental.Value); } else { jsonWriter.Null(); } jsonWriter.Name("incrementalTimeSliceNanoseconds").Value(d.GcSliceNs); jsonWriter.EndObject(); jsonWriter.Name("mainThread"); PhaseObject(jsonWriter, d.MainThread, d.PhaseCpuMainMs, d.ProbeStatus, top: true); CpuThreadsObject(jsonWriter, d.CpuThreads); ScriptTimersObject(jsonWriter, d.ScriptTimers); CensusObject(jsonWriter, d.Census); HitchesObject(jsonWriter, d.Hitches); Pipeline(jsonWriter, d.Pipeline); BoostObject(jsonWriter, d.Boost); jsonWriter.Name("countersWindowSeconds").Value(d.ShortWindowSeconds); Metrics(jsonWriter, "counters", d.Counters); Scene(jsonWriter, d.Scene3D); jsonWriter.Name("instancers").BeginArray(); foreach (InstancerStat instancer in d.Instancers) { jsonWriter.BeginObject().Name("type").Value(instancer.Type) .Name("enabled") .Value(instancer.Enabled) .Name("instances") .Value(instancer.Instances) .Name("lists") .Value(instancer.Lists) .Name("trisEach") .Value(instancer.TrisEach) .Name("renderParams") .Value(instancer.RenderParams) .Name("shadowRenderParams") .Value(instancer.ShadowRenderParams) .Name("batches") .Value(instancer.Batches) .Name("callsPerFrame") .Value(instancer.CallsPerFrame) .EndObject(); } jsonWriter.EndArray(); jsonWriter.Name("plugins").BeginArray(); foreach (GameInfo.PluginEntry plugin in d.Plugins) { jsonWriter.BeginObject().Name("guid").Value(plugin.Guid) .Name("name") .Value(plugin.Name) .Name("version") .Value(plugin.Version) .EndObject(); } jsonWriter.EndArray(); jsonWriter.EndObject(); return jsonWriter.ToString(); } private static void Metrics(JsonWriter w, string name, List<MetricValue> values) { w.Name(name).BeginObject(); foreach (MetricValue value in values) { w.Name(value.Name); if (!value.Available) { w.Null(); } else { w.BeginObject().Name("average").Value(value.Average) .Name("max") .Value(value.Max) .Name("samples") .Value(value.Samples) .EndObject(); } } w.EndObject(); } internal static void PhaseObject(JsonWriter w, PhaseWindow p, double cpuMainMs, string status, bool top) { if (p == null || p.Frames == 0) { if (status == null) { w.Null(); } else { w.BeginObject().Name("available").Value(value: false) .Name("status") .Value(status) .EndObject(); } return; } w.BeginObject(); w.Name("available").Value(value: true); if (status != null) { w.Name("status").Value(status); } w.Name("frames").Value(p.Frames); w.Name("seconds").Value(p.Seconds); w.Name("frameAvgMs").Value(p.FrameAvgMs); w.Name("frameP95Ms").Value(p.FrameP95Ms); w.Name("frameMaxMs").Value(p.FrameMaxMs); w.Name("cpuMainThreadMs").Value(cpuMainMs); w.Name("rowsMs").Value(p.RowsMs); w.Name("workRowsMs").Value(p.WorkRowsMs); w.Name("gapsAvgMs").Value(p.GapsAvgMs); w.Name("gapsP95Ms").Value(p.GapsP95Ms); w.Name("gapsMaxMs").Value(p.GapsMaxMs); w.Name("coveredMs").Value(p.CoveredMs); w.Name("betweenFrames").Value(p.BetweenFrames); w.Name("betweenAvgMs").Value(p.BetweenAvgMs); w.Name("betweenP95Ms").Value(p.BetweenP95Ms); w.Name("betweenMaxMs").Value(p.BetweenMaxMs); w.Name("loopPeriodAvgMs").Value(p.LoopPeriodAvgMs); w.Name("unprobedAvgMs").Value(p.UnprobedAvgMs); w.Name("gaps").BeginArray(); for (int i = 0; i < p.GapAvgMs.Length && i < p.GapNames.Length; i++) { w.BeginObject().Name("name").Value(p.GapNames[i]) .Name("avgMs") .Value(p.GapAvgMs[i]) .EndObject(); } w.EndArray(); bool flag = LoopPhases.MouseClassShown(p.MouseMovingFrames); bool flag2 = LoopPhases.MouseClassShown(p.MouseStillFrames); w.Name("gapsAvgMsMouseMoving").Value(flag ? p.GapsAvgMsMoving : double.NaN); w.Name("gapsAvgMsMouseStill").Value(flag2 ? p.GapsAvgMsStill : double.NaN); w.Name("betweenAvgMsMouseMoving").Value(flag ? p.BetweenAvgMsMoving : double.NaN); w.Name("betweenAvgMsMouseStill").Value(flag2 ? p.BetweenAvgMsStill : double.NaN); w.Name("mouseEventsPerSecond").Value(p.MouseEventsPerSecond); w.Name("inputEventsPerSecond").Value(p.InputEventsPerSecond); w.Name("framesPerSecondMouseMoving").Value(p.EventFpsMoving); w.Name("inputEventsPerSecondMouseMoving").Value(InputRules.PerSecond(p.InputEventsMoving, p.EventMsMoving)); w.Name("mouseEventsPerSecondMouseMoving").Value(InputRules.PerSecond(p.MouseEventsMoving, p.EventMsMoving)); w.Name("inputEventsPerSecondMouseStill").Value(InputRules.PerSecond(p.InputEventsStill, p.EventMsStill)); w.Name("mouseSource").Value(p.MouseSource); double num = LoopPhases.Coverage(p.CoveredMs, cpuMainMs); w.Name("coverage").Value(num); w.Name("coverageBasis").Value("all rows and the time between phases, Present / wait included, over CPU Main Thread Frame Time"); w.Name("coverageWarning").Value(LoopPhases.CoverageWarning(num) != null); w.Name("probeMs").Value(p.ProbeMs); w.Name("probeNs").Value(p.ProbeMs * 1000000.0); w.Name("probesPerFrame").Value(p.ProbesPerFrame); w.Name("overheadMs").Value(p.OverheadMs); w.Name("gcCollections").Value(p.GcCollections); w.Name("ownWorkFrames").Value(p.OwnWorkFrames); w.Name("mouseMovingFrames").Value(p.MouseMovingFrames); w.Name("mouseStillFrames").Value(p.MouseStillFrames); w.Name("mouseOwnWorkLeftOut").Value(p.MouseOwnWorkLeftOut); w.Name("mouseMinFrames").Value(30L); w.Name("frameAvgMsMouseMoving").Value(LoopPhases.MouseClassShown(p.MouseMovingFrames) ? p.FrameAvgMsMoving : double.NaN); w.Name("frameAvgMsMouseStill").Value(LoopPhases.MouseClassShown(p.MouseStillFrames) ? p.FrameAvgMsStill : double.NaN); w.Name("rows").BeginArray(); for (int j = 0; j < 13; j++) { w.BeginObject().Name("name").Value(LoopPhases.RowNames[j]) .Name("avgMs") .Value(p.RowAvgMs[j]) .Name("p95Ms") .Value(p.RowP95Ms[j]) .Name("maxMs") .Value(p.RowMaxMs[j]) .Name("shareOfMainThread") .Value((double.IsNaN(cpuMainMs) || cpuMainMs <= 0.0) ? double.NaN : (p.RowAvgMs[j] / cpuMainMs)) .Name("avgMsMouseMoving") .Value(LoopPhases.MouseClassShown(p.MouseMovingFrames) ? p.RowAvgMsMoving[j] : double.NaN) .Name("avgMsMouseStill") .Value(LoopPhases.MouseClassShown(p.MouseStillFrames) ? p.RowAvgMsStill[j] : double.NaN) .EndObject(); } w.EndArray(); if (top) { w.Name("topSubsystems").BeginArray(); foreach (SubsystemTime item in p.Top) { w.BeginObject().Name("name").Value(item.Name) .Name("row") .Value(LoopPhases.RowName(item.Group)) .Name("avgMs") .Value(item.AvgMs) .Name("maxMs") .Value(item.MaxMs) .EndObject(); } w.EndArray(); } w.EndObject(); } private static void ScriptTimersObject(JsonWriter w, ScriptTimersInfo t) { w.Name("scriptTimers"); if (t == null) { w.Null(); return; } w.BeginObject(); w.Name("problem").Value(t.Problem); w.Name("frames").Value(t.Frames); w.Name("seconds").Value(t.Seconds); w.Name("insideMsPerCall").Value(t.InsideMsPerCall); w.Name("addedMsPerCall").Value(t.AddedMsPerCall); w.Name("typesFound").Value(t.TypesFound); w.Name("patchedMethods").Value(t.PatchedMethods); w.Name("patchFailed").Value(t.PatchFailed); w.Name("patchMs").Value(t.PatchMs); w.Name("searchMethods").Value(t.SearchMethods); w.Name("patchFrames").Value(t.PatchFrames); w.Name("longestFramePatchMs").Value(t.LongestFramePatchMs); w.Name("f2FramePatchMs").Value(t.F2FramePatchMs); w.Name("leftPatched").Value(t.LeftPatched); w.Name("unpatchFailed").Value(t.UnpatchFailed); w.Name("retiredSlots").Value(t.RetiredSlots); w.Name("censusMs").Value(t.CensusMs); w.Name("calibrationMs").Value(t.CalibrationMs); w.Name("startMs").Value(t.StartMs); w.Name("unpatchMs").Value(t.UnpatchMs); w.Name("unpatchFrames").Value(t.UnpatchFrames); w.Name("longestFrameUnpatchMs").Value(t.LongestFrameUnpatchMs); w.Name("groupsOutermost").Value(t.GroupTicks.Count > 0); w.Name("methods").BeginArray(); foreach (ScriptTimerResult item in ScriptTimerRules.Sorted(t)) { w.BeginObject().Name("method").Value(item.Method) .Name("assembly") .Value(item.Assembly) .Name("group") .Value(item.Group) .Name("kind") .Value(item.Kind.ToString()) .Name("types") .Value(item.Types) .Name("instances") .Value(item.Instances) .Name("enabled") .Value(item.Enabled) .Name("problem") .Value(item.Problem) .Name("note") .Value(item.Note) .Name("calls") .Value(item.Calls) .Name("callsPerFrame") .Value((item.Problem != null) ? double.NaN : ScriptTimerRules.CallsPerFrame(item.Calls, t.Frames)) .Name("msPerFrame") .Value((item.Problem != null) ? double.NaN : ScriptTimerRules.MsPerFrame(item.Ticks, item.Calls, t.Frames, t.MsPerTick, t.InsideMsPerCall)) .Name("msPerCall") .Value((item.Problem != null) ? double.NaN : ScriptTimerRules.MsPerCall(item.Ticks, item.Calls, t.MsPerTick, t.InsideMsPerCall)) .EndObject(); } w.EndArray(); w.Name("missing").BeginArray(); foreach (string item2 in t.Missing) { w.Value(item2); } w.EndArray(); w.Name("excluded").BeginObject(); foreach (KeyValuePair<string, int> item3 in t.Excluded) { w.Name(item3.Key).Value(item3.Value); } w.EndObject(); w.Name("byAssembly").BeginArray(); foreach (ScriptTimerRules.GroupTotal item4 in ScriptTimerRules.GroupTotals(t)) { w.BeginObject().Name("group").Value(item4.Group) .Name("methods") .Value(item4.Methods) .Name("callsPerFrame") .Value(item4.CallsPerFrame) .Name("msPerFrame") .Value(item4.MsPerFrame) .EndObject(); } w.EndArray(); w.Name("againstProbes").BeginArray(); TimerKind[] array = new TimerKind[4] { TimerKind.Update, TimerKind.LateUpdate, TimerKind.FixedUpdate, TimerKind.OnGUI }; for (int i = 0; i < array.Length; i++) { TimerKind kind = array[i]; int num = ScriptTimerRules.RowOf(kind); double num2 = ((num >= 0 && t.Probes != null && t.Probes.Frames > 0) ? t.Probes.RowAvgMs[num] : double.NaN); double num3 = ScriptTimerRules.KindMsPerFrame(t, kind); w.BeginObject().Name("kind").Value(kind.ToString()) .Name("probeRowMs") .Value(num2) .Name("timedOutermostMs") .Value(num3) .Name("notTimedMs") .Value(ScriptTimerRules.Residual(num2, num3)) .EndObject(); } w.EndArray(); w.Name("probeFrames").Value((t.Probes != null) ? t.Probes.Frames : 0); w.Name("sceneSearches").BeginArray(); foreach (SceneSearchResult item5 in ScriptTimerRules.SortedSearches(t)) { w.BeginObject().Name("search").Value(item5.Search) .Name("requested") .Value(item5.Requested) .Name("caller") .Value(item5.Caller) .Name("calls") .Value(item5.Calls) .Name("callsPerFrame") .Value(ScriptTimerRules.CallsPerFrame(item5.Calls, t.Frames)) .Name("msPerFrame") .Value(ScriptTimerRules.MsPerFrame(item5.Ticks, 0L, t.Frames, t.MsPerTick, double.NaN)) .Name("msPerCall") .Value(ScriptTimerRules.MsPerCall(item5.Ticks, item5.Calls, t.MsPerTick, double.NaN)) .EndObject(); } w.EndArray(); w.EndObject(); } private static void CensusObject(JsonWriter w, RemoteCensus c) { w.Name("playerCensus"); if (c == null) { w.Null(); } else { w.BeginObject().Name("problem").Value(c.Problem) .Name("remoteAvatars") .Value(c.Remote) .Name("summary") .Value(c.Summary) .Name("animators") .Value(c.Animators) .Name("alwaysAnimate") .Value(c.AlwaysAnimate) .Name("cullUpdateTransforms") .Value(c.CullUpdateTransforms) .Name("cullCompletely") .Value(c.CullCompletely) .Name("animatorsNothingRendered") .Value(c.AnimatorsNotVisible) .Name("softBones") .Value(c.SoftBones) .Name("softBonesEnabled") .Value(c.SoftBonesEnabled) .Name("tailSoftBoneWithoutTail") .Value(c.TailSoftBoneWithoutTail) .Name("controllerPatchOn") .Value(c.ControllerPatchOn) .Name("controllerPatchOff") .Value(c.ControllerPatchOff) .Name("controllerPatchProblem") .Value(c.ControllerPatchProblem) .Name("groundBodiesDynamic") .Value(c.GroundBodiesDynamic) .Name("groundBodiesKinematic") .Value(c.GroundBodiesKinematic) .Name("renderersCastingShadows") .Value(c.RenderersCastingShadows) .Name("renderersNoShadows") .Value(c.RenderersNoShadows) .Name("particleSystems") .Value(c.ParticleSystems) .Name("particlesPlaying") .Value(c.ParticlesPlaying) .Name("remoteNotAtRoot") .Value(c.NotRoot) .Name("otherNotAtRoot") .Value(c.NotRootOther) .Name("hierarchyMin") .Value(c.HierarchyMin) .Name("hierarchyMax") .Value(c.HierarchyMax) .Name("hierarchyTotal") .Value(c.HierarchyTotal) .EndObject(); } } private static void HitchesObject(JsonWriter w, HitchSummary s) { w.Name("hitches"); if (s == null) { w.Null(); return; } w.BeginObject(); w.Name("thresholdMs").Value(s.ThresholdMs); w.Name("total").Value(s.Total); w.Name("inFrameCareWork").Value(s.OwnWork); w.Name("mouseMoving").Value(s.MouseMoving); w.Name("mouseKnown").Value(s.MouseKnown); w.Name("file").Value(s.FileName); w.Name("notWritten").Value(s.NotWritten); w.Name("writeFailed").Value(s.WriteFailed); w.Name("csvOff").Value(s.CsvOff); w.Name("offNotWritten").Value(s.OffNotWritten); w.Name("note").Value(s.Note); w.Name("worst").BeginArray(); foreach (HitchRecord item in s.Worst) { w.BeginObject().Name("clock").Value(item.Clock) .Name("seconds") .Value(item.SessionSeconds) .Name("gameSeconds") .Value(item.GameSeconds) .Name("frameMs") .Value(item.FrameMs) .Name("scene") .Value(item.Scene) .Name("players") .Value(item.Players) .Name("fromProbes") .Value(item.FromProbes) .Name("frameCareWork") .Value(item.OwnWork) .Name("topPhase") .Value((HitchRules.TopKind(item, out var _) != HitchTopKind.None) ? HitchRules.TopPhaseName(item) : null) .Name("topRow") .Value((item.TopRow >= 0) ? LoopPhases.RowNames[item.TopRow] : null) .Name("periodMs") .Value(item.PeriodMs) .Name("betweenMs") .Value(item.BetweenMs) .Name("gapsMs") .Value(item.GapsMs) .Name("topGap") .Value(item.TopGap) .Name("topGapMs") .Value(item.TopGapMs) .Name("unprobedMs") .Value(item.UnprobedMs) .Name("gcFrame") .CountOrNull(item.GcFrame) .Name("gcBetween") .CountOrNull(item.GcBetween) .Name("gcInFrame") .CountOrNull(item.GcInFrame) .Name("gcSecond") .CountOrNull(item.GcSecond) .Name("mouseMoved") .CountOrNull(item.MouseMoved) .Name("mouseEvents") .CountOrNull(item.MouseEvents) .Name("inputEvents") .CountOrNull(item.InputEvents) .Name("focused") .CountOrNull(item.Focused) .Name("frameCpuShare") .Value(item.FrameCpuShare) .Name("betweenCpuShare") .Value(item.BetweenCpuShare) .Name("events") .Value(HitchRules.EventsText(item)); w.Name("topSubsystems").BeginArray(); foreach (SubsystemTime item2 in item.Top) { w.BeginObject().Name("name").Value(item2.Name) .Name("ms") .Value(item2.AvgMs) .EndObject(); } w.EndArray(); w.EndObject(); } w.EndArray(); Counts(w, "byTopPhase", s.ByTopPhase); Counts(w, "byEvent", s.ByEvent); w.EndObject(); } private static void Counts(JsonWriter w, string name, List<KeyValuePair<string, int>> counts) { w.Name(name).BeginObject(); foreach (KeyValuePair<string, int> count in counts) { w.Name(count.Key).Value(count.Value); } w.EndObject(); } private static void CpuThreadsObject(JsonWriter w, CpuThreadsInfo c) { w.Name("cpuThreads"); if (c == null) { w.Null(); return; } w.BeginObject(); w.Name("windowSeconds").Value(c.WindowSeconds); w.Name("logicalProcessors").Value(c.LogicalProcessors); w.Name("processCores").Value(c.ProcessCores); w.Name("jobWorkerCount").Value(c.JobWorkers); w.Name("jobWorkerMaximumCount").Value(c.JobWorkersMax); w.Name("renderingThreadingMode").Value(c.RenderingThreadingMode); w.Name("graphicsApi").Value(c.GraphicsApi); w.Name("bootConfigGfx").BeginArray(); foreach (string item in c.BootConfigGfx) { w.Value(item); } w.EndArray(); w.Name("gfxNote").Value(c.GfxNote); w.Name("mainThreadId").Value(c.MainThreadId); w.Name("mainThreadPercentOfCore").Value(c.MainPercent); w.Name("threads").BeginArray(); foreach (ThreadUsage thread in c.Threads) { w.BeginObject().Name("id").Value(thread.Id) .Name("name") .Value(thread.Name) .Name("group") .Value(thread.Group) .Name("main") .Value(thread.Main) .Name("startedDuringWindow") .Value(thread.New) .Name("percentOfCore") .Value(thread.PercentOfCore) .EndObject(); } w.EndArray(); w.Name("groups").BeginArray(); foreach (ThreadGroup group in c.Groups) { w.BeginObject().Name("name").Value(group.Name) .Name("threads") .Value(group.Count) .Name("totalPercent") .Value(group.TotalPercent) .Name("busiestPercent") .Value(group.BusiestPercent) .EndObject(); } w.EndArray(); w.Name("interpretation").BeginArray(); foreach (string item2 in c.Interpretation) { w.Value(item2); } w.EndArray(); w.Name("notes").BeginArray(); foreach (string note in c.Notes) { w.Value(note); } w.EndArray(); w.EndObject(); } private static void Window(JsonWriter w, string name, double seconds, WindowStats s) { w.Name(name).BeginObject().Name("seconds") .Value(seconds) .Name("frames") .Value(s.Frames) .Name("excluded") .Value(s.Excluded) .Name("avgFps") .Value(s.AvgFps) .Name("onePercentLowFps") .Value(s.OnePercentLowFps) .Name("minFps") .Value(s.MinFps) .Name("p50Ms") .Value(s.P50Ms) .Name("p95Ms") .Value(s.P95Ms) .Name("p99Ms") .Value(s.P99Ms) .Name("maxMs") .Value(s.MaxMs) .EndObject(); } private static void Pipeline(JsonWriter w, PipelineInfo p) { w.Name("pipeline").BeginObject(); w.Name("qualityIndex").Value(p.QualityIndex); w.Name("qualityName").Value(p.QualityName); w.Name("lodBias").Value(p.LodBias); w.Name("maximumLodLevel").Value(p.MaximumLodLevel); w.Name("vSyncCount").Value(p.VSyncCount); w.Name("targetFrameRate").Value(p.TargetFrameRate); w.Name("screenWidth").Value(p.ScreenWidth); w.Name("screenHeight").Value(p.ScreenHeight); w.Name("fullScreenMode").Value(p.FullScreenMode); w.Name("refreshRate").Value(p.RefreshRate); w.Name("graphicsApi").Value(p.GraphicsApi); w.Name("gpu").Value(p.GpuName); w.Name("gpuMemoryMb").Value(p.GpuMemoryMb); w.Name("cpu").Value(p.Cpu); w.Name("cpuThreads").Value(p.CpuThreads); w.Name("systemMemoryMb").Value(p.SystemMemoryMb); w.Name("renderThreading").Value(p.RenderThreading); w.Name("frameTimingEnabled").Value(p.FrameTimingEnabled); w.Name("urp").Value(p.IsUrp); w.Name("urpAsset").Value(p.AssetName); w.Name("renderingPath").Value(p.RenderingPath); w.Name("renderGraph").Value(p.RenderGraph); w.Name("mainLightShadows").Value(p.MainLightShadows); w.Name("shadowDistance").Value(p.ShadowDistance); w.Name("cascadeCount").Value(p.CascadeCount); w.Name("cascadeEnds").BeginArray(); float[] cascadeEnds = p.CascadeEnds; foreach (float num in cascadeEnds) { w.Value(num); } w.EndArray(); w.Name("shadowmapResolution").Value(p.ShadowmapResolution); w.Name("softShadows").Value(p.SoftShadows); w.Name("msaa").Value(p.Msaa); w.Name("renderScale").Value(p.RenderScale); w.Name("hdr").Value(p.Hdr); w.Name("additionalLights").Value(p.AdditionalLights); w.Name("maxAdditionalLights").Value(p.MaxAdditionalLights); w.Name("srpBatcher").Value(p.SrpBatcher); w.Name("gpuResidentDrawer").Value(p.GpuResidentDrawer); w.Name("depthPriming"); if (p.DepthPriming.HasValue) { w.Value(p.DepthPriming.Value); } else { w.Null(); } w.Name("opaqueForwardShaderTags"); if (p.OpaqueTags == null) { w.Null(); } else { w.BeginArray(); string[] opaqueTags = p.OpaqueTags; foreach (string value in opaqueTags) { w.Value(value); } w.EndArray(); } w.Name("frameCareOutlinePass").Value(p.OutlinePassActive); w.Name("frameCareOutlinePassNote").Value(p.OutlinePassNote); w.Name("cameraNear").Value(p.CameraNear); w.Name("cameraFar").Value(p.CameraFar); w.Name("cameraOcclusionCulling").Value(p.CameraOcclusionCulling); w.Name("cameraPostProcessing").Value(p.CameraPostProcessing); w.Name("cameraAntialiasing").Value(p.CameraAntialiasing); w.Name("cameraShadows").Value(p.CameraShadows); w.EndObject(); } private static void BoostObject(JsonWriter w, BoostInfo b) { w.Name("boost"); if (b == null) { w.Null(); return; } w.BeginObject(); w.Name("outlinePass").BeginObject(); w.Name("config").Value(b.OutlineConfig); w.Name("sessionOverride"); if (b.OutlineSessionOverride.HasValue) { w.Value(b.OutlineSessionOverride.Value); } else { w.Null(); } w.Name("on").Value(b.OutlineOn); w.Name("status").Value(b.OutlineStatus); w.EndObject(); w.Name("lodBias").BeginObject(); w.Name("factor").Value(b.LodFactor); w.Name("applied").Value(b.LodApplied); w.Name("gameValue").Value(b.LodGameValue); w.Name("inForce").Value(b.LodInForce); w.Name("note").Value(b.LodNote); w.EndObject(); w.Name("hideSmallFarObjects").BeginObject(); w.Name("enabled").Value(b.HideEnabled); w.Name("hidden").Value(b.Hidden); w.Name("eligible").Value(b.Eligible); w.Name("smallRadius").Value(b.SmallRadius); w.Name("farDistance").Value(b.FarDistance); w.Name("note").Value(b.HideNote); w.EndObject(); w.Name("frameRateCap").BeginObject(); w.Name("config").Value(b.CapConfig); w.Name("applied").Value(b.CapApplied); w.Name("gameValue").Value(b.CapGameValue); w.Name("inForce").Value(b.CapInForce); w.Name("vSyncCount").Value(b.CapVSync); w.Name("note").Value(b.CapNote); w.EndObject(); w.Name("remoteAvatars").BeginObject(); w.Name("tracked").Value(b.AvatarsTracked); w.Name("controllerToggleOff").BeginObject().Name("status") .Value(b.ControllerStatus) .Name("held") .Value(b.ControllerHeld) .EndObject(); w.Name("hiddenTailSoftBoneOff").BeginObject().Name("status") .Value(b.TailStatus) .Name("held") .Value(b.TailHeld) .EndObject(); w.Name("groundCheckKinematic").BeginObject().Name("status") .Value(b.GroundStatus) .Name("held") .Value(b.GroundHeld) .EndObject(); w.Name("note").Value(b.AvatarNote); w.EndObject(); w.Name("menuLookupCache").BeginObject(); w.Name("config").Value(b.MenuConfig); w.Name("on").Value(b.MenuOn); w.Name("status").Value(b.MenuStatus); w.Name("realLookups").Value(b.MenuRealLookups); w.Name("skipped").Value(b.MenuSkipped); w.Name("cached").Value(b.MenuCached); w.EndObject(); w.Name("ambientSoundLookupCache").BeginObject(); w.Name("config").Value(b.AmbientConfig); w.Name("on").Value(b.AmbientOn); w.Name("status").Value(b.AmbientStatus); w.Name("realLookups").Value(b.AmbientRealLookups); w.Name("reused").Value(b.AmbientReused); w.Name("reuseMeters").Value(0.02); w.Name("reuseSeconds").Value(0.1); w.EndObject(); w.EndObject(); } private static void Scene(JsonWriter w, SceneReport s) { w.Name("scene3d").BeginObject(); w.Name("renderers").Value(s.Renderers); w.Name("onScreen").Value(s.InView); w.Name("trisOnScreen").Value(s.TrisInView); w.Name("cameraDraws").Value(s.CameraDraws); w.Name("shadowCasters").Value(s.ShadowCasters); w.Name("shadowDraws").Value(s.ShadowDraws); w.Name("staticBatched").Value(s.StaticBatched); w.Name("playerAvatars").Value(s.PlayerRoots); Groups(w, "roots", s.Roots); Groups(w, "groups", s.Groups); w.Name("meshes").BeginArray(); foreach (MeshStat mesh in s.Meshes) { w.BeginObject().Name("name").Value(mesh.Name) .Name("trisEach") .Value(mesh.TrisEach) .Name("onScreen") .Value(mesh.InView) .Name("casters") .Value(mesh.Casters) .EndObject(); } w.EndArray(); w.Name("shaders").BeginArray(); foreach (ShaderStat shader in s.Shaders) { w.BeginObject().Name("name").Value(shader.Name) .Name("materials") .Value(shader.Materials) .Name("renderersOnScreen") .Value(shader.RenderersOnScreen) .Name("cameraDraws") .Value(shader.CameraDraws) .Name("hasShadowPass") .Value(shader.HasShadowPass) .Name("shadowPassEnabledMaterials") .Value(shader.ShadowEnabledMaterials) .Name("outlinePassMaterials") .Value(shader.OutlinePassMaterials) .Name("outlinePassEnabledMaterials") .Value(shader.OutlineEnabledMaterials); w.Name("passes").BeginArray(); foreach (string pass in shader.Passes) { w.Value(pass); } w.EndArray(); w.EndObject(); } w.EndArray(); w.Name("unlitOpaqueMaterials").Value(s.UnlitOpaqueMaterials); w.Name("unlitOrderSensitiveMaterials").BeginArray(); foreach (string item in s.UnlitOrderSensitive) { w.Value(item); } w.EndArray(); Skinned(w, "skinnedPlayers", s.PlayerSkinned); Skinned(w, "skinnedOther", s.OtherSkinned); Animators(w, "animatorsPlayers", s.PlayerAnimators); Animators(w, "animatorsOther", s.OtherAnimators); w.Name("particles").BeginObject().Name("systems") .Value(s.ParticleSystems) .Name("playing") .Value(s.ParticlesPlaying) .Name("particles") .Value(s.Particles) .Name("onScreen") .Value(s.ParticlesInView) .EndObject(); w.Name("lights").BeginArray(); foreach (LightStat light in s.Lights) { w.BeginObject().Name("type").Value(light.Type) .Name("count") .Value(light.Count) .Name("enabled") .Value(light.Enabled) .Name("castShadows") .Value(light.CastShadows) .Name("inView") .Value(light.InView) .EndObject(); } w.EndArray(); w.Name("canvases").BeginObject().Name("total") .Value(s.Canvases) .Name("worldSpace") .Value(s.WorldCanvases) .Name("worldSpaceNearView") .Value(s.WorldCanvasesInView) .Name("worldSpaceElements") .Value(s.WorldCanvasGraphics) .EndObject(); w.EndObject(); } private static void Groups(JsonWriter w, string name, List<GroupStat> groups) { w.Name(name).BeginArray(); foreach (GroupStat group in groups) { w.BeginObject().Name("key").Value(group.Key) .Name("renderers") .Value(group.Renderers) .Name("onScreen") .Value(group.InView) .Name("trisOnScreen") .Value(group.TrisInView) .Name("slotsOnScreen") .Value(group.SlotsInView) .Name("cameraDraws") .Value(group.CameraDraws) .Name("shadowCasters") .Value(group.ShadowCasters) .Name("shadowDraws") .Value(group.ShadowDraws) .Name("lodManaged") .Value(group.LodManaged) .Name("staticBatched") .Value(group.StaticBatched) .EndObject(); } w.EndArray(); } private static void Skinned(JsonWriter w, string name, SkinnedSummary s) { w.Name(name).BeginObject().Name("renderers") .Value(s.Renderers) .Name("rendered") .Value(s.Visible) .N