Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of MySix Core v1.0.2
MySix.Core.dll
Decompiled 4 hours agousing System; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")] [assembly: AssemblyVersion("0.0.0.0")] namespace MySix { internal static class SuiteVersion { public const string Value = "1.0.2"; } } namespace MySix.Core { public static class Compat { public static bool TakeOwnership(ZDO zdo) { if (zdo == null) { return false; } zdo.SetOwner(ZDOMan.GetSessionID()); return zdo.IsOwner(); } } internal static class ConfigSync { private const string RpcRequest = "MySix_ConfigRequest"; private const string RpcValues = "MySix_ConfigValues"; private const string RpcSet = "MySix_ConfigSet"; private const string RpcReply = "MySix_ConfigReply"; private static bool _registered; private static bool _requested; internal static void Register() { if (!_registered && ZRoutedRpc.instance != null) { ZRoutedRpc.instance.Register<ZPackage>("MySix_ConfigRequest", (Action<long, ZPackage>)OnRequest); ZRoutedRpc.instance.Register<ZPackage>("MySix_ConfigValues", (Action<long, ZPackage>)OnValues); ZRoutedRpc.instance.Register<ZPackage>("MySix_ConfigSet", (Action<long, ZPackage>)OnSet); ZRoutedRpc.instance.Register<string>("MySix_ConfigReply", (Action<long, string>)OnReply); _registered = true; } } internal static void Reset() { _registered = false; _requested = false; Settings.ClearRemoteValues(); } internal static void RequestIfNeeded() { //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Expected O, but got Unknown if (!_requested && _registered && !((Object)(object)ZNet.instance == (Object)null) && !ZNet.instance.IsServer()) { ZNetPeer serverPeer = ZNet.instance.GetServerPeer(); if (serverPeer != null) { _requested = true; ZRoutedRpc.instance.InvokeRoutedRPC(serverPeer.m_uid, "MySix_ConfigRequest", new object[1] { (object)new ZPackage() }); CorePlugin.Log.LogInfo((object)"Asked the server for MySix settings"); } } } private static void OnRequest(long sender, ZPackage _) { if (!((Object)(object)ZNet.instance == (Object)null) && ZNet.instance.IsServer()) { SendValuesTo(sender); } } internal static void BroadcastValues() { if ((Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer()) { return; } foreach (ZNetPeer item in from p in ZNet.instance.GetPeers() where p != null && !p.m_server select p) { SendValuesTo(item.m_uid); } } private static void SendValuesTo(long peerId) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Expected O, but got Unknown ZPackage val = new ZPackage(); List<SettingBase> list = Settings.ServerOwned.ToList(); val.Write(list.Count); foreach (SettingBase item in list) { val.Write(item.FullName); val.Write(item.ValueText); } ZRoutedRpc.instance.InvokeRoutedRPC(peerId, "MySix_ConfigValues", new object[1] { val }); } private static void OnValues(long sender, ZPackage pkg) { if ((Object)(object)ZNet.instance == (Object)null || ZNet.instance.IsServer() || pkg == null) { return; } int num = pkg.ReadInt(); int num2 = 0; for (int i = 0; i < num; i++) { string fullName = pkg.ReadString(); string text = pkg.ReadString(); SettingBase settingBase = Settings.Find(fullName); if (settingBase != null) { settingBase.ApplyRemoteText(text); num2++; } } CorePlugin.Log.LogInfo((object)$"Applied {num2} server setting(s)"); } internal static bool RequestSet(string fullName, string value, out string error) { //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Expected O, but got Unknown error = null; if (!_registered || (Object)(object)ZNet.instance == (Object)null || ZNet.instance.IsServer()) { error = "not connected to a server"; return false; } ZNetPeer serverPeer = ZNet.instance.GetServerPeer(); if (serverPeer == null) { error = "no server connection"; return false; } ZPackage val = new ZPackage(); val.Write(fullName); val.Write(value); ZRoutedRpc.instance.InvokeRoutedRPC(serverPeer.m_uid, "MySix_ConfigSet", new object[1] { val }); return true; } private static void OnSet(long sender, ZPackage pkg) { if ((Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer() || pkg == null) { return; } string text = pkg.ReadString(); string text2 = pkg.ReadString(); if (!IsAdmin(sender)) { CorePlugin.Log.LogWarning((object)$"Refused setting change from non-admin peer {sender}: {text} = {text2}"); Reply(sender, "You must be a server admin to change MySix settings."); return; } SettingBase settingBase = Settings.Find(text); if (settingBase == null) { Reply(sender, "Unknown setting: " + text); return; } if (settingBase.Scope != Scope.Server) { Reply(sender, settingBase.FullName + " is a per-player setting; change it locally."); return; } if (!settingBase.TrySetFromText(text2, out var error)) { Reply(sender, "Invalid value for " + settingBase.FullName + ": " + error); return; } CorePlugin.Log.LogInfo((object)$"Admin peer {sender} set {settingBase.FullName} = {settingBase.ValueText}"); Reply(sender, settingBase.FullName + " = " + settingBase.ValueText); BroadcastValues(); } private static bool IsAdmin(long peerId) { ZNetPeer peer = ZNet.instance.GetPeer(peerId); if (peer?.m_socket == null) { return false; } return ZNet.instance.IsAdmin(peer.m_socket.GetHostName()); } private static void Reply(long peerId, string message) { ZRoutedRpc.instance.InvokeRoutedRPC(peerId, "MySix_ConfigReply", new object[1] { message }); } private static void OnReply(long sender, string message) { CorePlugin.Log.LogInfo((object)("Server: " + message)); if ((Object)(object)Console.instance != (Object)null) { ((Terminal)Console.instance).AddString("MySix: " + message); } } } internal static class ConsoleCommands { [Serializable] [CompilerGenerated] private sealed class <>c { public static readonly <>c <>9 = new <>c(); public static ConsoleEventFailable <>9__2_0; public static ConsoleOptionsFetcher <>9__2_1; public static Func<SettingBase, string> <>9__3_0; public static Func<SettingBase, string> <>9__5_0; public static Func<IGrouping<string, SettingBase>, string> <>9__5_1; internal object <RegisterIfNeeded>b__2_0(ConsoleEventArgs args) { return Run(args); } internal List<string> <RegisterIfNeeded>b__2_1() { return Options(); } internal string <Options>b__3_0(SettingBase s) { return s.ShortName; } internal string <PrintList>b__5_0(SettingBase s) { return s.Feature; } internal string <PrintList>b__5_1(IGrouping<string, SettingBase> g) { return g.Key; } } private static bool _registered; private static readonly FieldRef<bool> TerminalInitialized = AccessTools.StaticFieldRefAccess<bool>(AccessTools.Field(typeof(Terminal), "m_terminalInitialized")); internal static void RegisterIfNeeded() { //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Expected O, but got Unknown //IL_006c: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Unknown result type (might be due to invalid IL or missing references) //IL_0069: Expected O, but got Unknown if (_registered || !TerminalInitialized.Invoke()) { return; } _registered = true; object obj = <>c.<>9__2_0; if (obj == null) { ConsoleEventFailable val = (ConsoleEventArgs args) => Run(args); <>c.<>9__2_0 = val; obj = (object)val; } object obj2 = <>c.<>9__2_1; if (obj2 == null) { ConsoleOptionsFetcher val2 = () => Options(); <>c.<>9__2_1 = val2; obj2 = (object)val2; } new ConsoleCommand("mysix", "MySix mods: list settings, or 'get <setting>' / 'set <setting> <value>'", (ConsoleEventFailable)obj, false, false, false, false, true, false, (ConsoleOptionsFetcher)obj2, false, false, false); CorePlugin.Log.LogInfo((object)"Console command 'mysix' registered"); } private static List<string> Options() { List<string> list = new List<string>(); list.Add("get"); list.Add("set"); list.Add("list"); list.AddRange(Settings.Known.Select((SettingBase s) => s.ShortName)); return list; } private static object Run(ConsoleEventArgs args) { Terminal context = args.Context; switch ((args.Length > 1) ? args[1].ToLowerInvariant() : "list") { case "list": PrintList(context); return true; case "get": { if (args.Length < 3) { return Print(context, "usage: mysix get <setting>"); } SettingBase settingBase2 = Settings.Find(args[2]); if (settingBase2 == null) { return Print(context, "unknown setting: " + args[2]); } return Print(context, Describe(settingBase2)); } case "set": { if (args.Length < 4) { return Print(context, "usage: mysix set <setting> <value>"); } SettingBase settingBase = Settings.Find(args[2]); if (settingBase == null) { return Print(context, "unknown setting: " + args[2]); } string text = string.Join(" ", args.Args.Skip(3).ToArray()); bool flag = (Object)(object)ZNet.instance != (Object)null && !ZNet.instance.IsServer(); if (settingBase.Scope == Scope.Server && flag) { if (!ConfigSync.RequestSet(settingBase.FullName, text, out var error)) { return Print(context, "could not reach the server: " + error); } return Print(context, "asked the server to set " + settingBase.FullName + " to " + text); } if (!settingBase.TrySetFromText(text, out var error2)) { return Print(context, "invalid value for " + settingBase.FullName + ": " + error2); } if (settingBase.Scope == Scope.Server) { ConfigSync.BroadcastValues(); } return Print(context, settingBase.FullName + " = " + settingBase.ValueText); } default: return Print(context, "usage: mysix [list | get <setting> | set <setting> <value>]"); } } private static void PrintList(Terminal ctx) { List<SettingBase> list = Settings.Known.ToList(); if (list.Count == 0) { Print(ctx, "MySix: no features registered any settings"); return; } Print(ctx, string.Format("MySix {0} - {1} setting(s):", "1.0.2", list.Count)); foreach (IGrouping<string, SettingBase> item in (from s in list group s by s.Feature).OrderBy<IGrouping<string, SettingBase>, string>((IGrouping<string, SettingBase> g) => g.Key, StringComparer.OrdinalIgnoreCase)) { Print(ctx, " " + item.Key); foreach (SettingBase item2 in item) { Print(ctx, " " + item2.ShortName + " = " + item2.ValueText + (item2.IsRemote ? " (from server)" : "")); } } Print(ctx, "use 'mysix get <setting>' for details, 'mysix set <setting> <value>' to change one"); } private static string Describe(SettingBase s) { return s.FullName + " = " + s.ValueText + " [" + s.TypeHint + ", default " + s.DefaultText + ", " + ((s.Scope == Scope.Server) ? "server-owned" : "per-player") + (s.IsRemote ? ", currently from the server" : "") + "]" + (string.IsNullOrEmpty(s.Description) ? "" : (Environment.NewLine + " " + s.Description)); } private static bool Print(Terminal ctx, string text) { if ((Object)(object)ctx != (Object)null) { ctx.AddString(text); } else { CorePlugin.Log.LogInfo((object)text); } return true; } } public enum Role { Unknown, DedicatedServer, Host, Client } [BepInPlugin("com.mysix.core", "MySix Core", "1.0.2")] public class CorePlugin : BaseUnityPlugin { public const string PluginGuid = "com.mysix.core"; public const string PluginName = "MySix Core"; public const string PluginVersion = "1.0.2"; public const int ApiVersion = 1; private static bool _readyRaised; private ConfigEntry<float> _cycleSeconds; private ConfigEntry<int> _maxObjectsPerFrame; public static CorePlugin Instance { get; private set; } public static ManualLogSource Log { get; private set; } public static Role Role { get; private set; } public static bool IsServerSide { get { if (Role != Role.DedicatedServer) { return Role == Role.Host; } return true; } } public static bool IsPlayerSide { get { if (Role != Role.Host) { return Role == Role.Client; } return true; } } public static bool IsRemoteClient => Role == Role.Client; public WorldScanner Scanner { get; } = new WorldScanner(); public static event Action<Role> Ready; private void Awake() { Instance = this; Log = ((BaseUnityPlugin)this).Logger; _cycleSeconds = ((BaseUnityPlugin)this).Config.Bind<float>("Scanner", "CycleSeconds", 600f, "How long one full pass over the world takes on the server. Work is spread evenly across it."); _maxObjectsPerFrame = ((BaseUnityPlugin)this).Config.Bind<int>("Scanner", "MaxObjectsPerFrame", 2000, "Safety cap on objects inspected in a single frame (only matters if a pass falls behind)."); Settings.Register<float>("MySix Core", _cycleSeconds, Scope.Server, "Seconds for one full world pass on the server."); Settings.Register<int>("MySix Core", _maxObjectsPerFrame, Scope.Server, "Maximum objects inspected per frame."); ((BaseUnityPlugin)this).Logger.LogInfo((object)string.Format("{0} {1} loaded (API {2})", "MySix Core", "1.0.2", 1)); } public static bool RequireApi(string featureName, int requiredApiVersion) { if (requiredApiVersion <= 1) { return true; } ManualLogSource log = Log; if (log != null) { log.LogError((object)($"{featureName} needs MySix Core API {requiredApiVersion}, but this is Core " + string.Format("{0} (API {1}). Update MySix.Core.dll; {2} is disabled.", "1.0.2", 1, featureName))); } return false; } private void Update() { ZNet instance = ZNet.instance; if ((Object)(object)instance == (Object)null) { if (Role != Role.Unknown) { Role = Role.Unknown; _readyRaised = false; ConfigSync.Reset(); } return; } if (Role == Role.Unknown) { Role = (instance.IsDedicated() ? Role.DedicatedServer : (instance.IsServer() ? Role.Host : Role.Client)); } ConfigSync.Register(); ConsoleCommands.RegisterIfNeeded(); if (!_readyRaised) { _readyRaised = true; try { CorePlugin.Ready?.Invoke(Role); } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogError((object)("Ready handler failed: " + ex)); } } if (IsRemoteClient) { ConfigSync.RequestIfNeeded(); } if (!IsServerSide || !Scanner.HasConsumers) { return; } try { Scanner.Tick(Time.realtimeSinceStartup, _cycleSeconds.Value, _maxObjectsPerFrame.Value); } catch (Exception ex2) { ((BaseUnityPlugin)this).Logger.LogError((object)("World scan failed: " + ex2)); } } public static void PushSettingsToPlayers() { ConfigSync.BroadcastValues(); } internal static bool TakeOwnership(ZDO zdo) { zdo.SetOwner(ZDOMan.GetSessionID()); return zdo.IsOwner(); } } public enum Scope { Server, Player } public abstract class SettingBase { public string Feature { get; protected set; } public string Key { get; protected set; } public string Description { get; protected set; } public Scope Scope { get; protected set; } public string FullName => Feature + "." + Key; public string ShortName => Feature.Replace("MySix", "").Replace(" ", "") + "." + Key; public bool IsRemote { get; internal set; } public abstract string ValueText { get; } public abstract string DefaultText { get; } public abstract string TypeHint { get; } public abstract bool TrySetFromText(string text, out string error); internal abstract void ApplyRemoteText(string text); internal abstract void ClearRemote(); } public sealed class Setting<T> : SettingBase { private readonly ConfigEntry<T> _entry; private T _remote; public T Value { get { if (!base.IsRemote) { return _entry.Value; } return _remote; } } public override string ValueText => Format(Value); public override string DefaultText => Format((T)((ConfigEntryBase)_entry).DefaultValue); public override string TypeHint => Describe(typeof(T)); public event Action<Setting<T>> Changed; internal Setting(string feature, string key, ConfigEntry<T> entry, Scope scope, string description) { base.Feature = feature; base.Key = key; object obj = description; if (obj == null) { ConfigDescription description2 = ((ConfigEntryBase)entry).Description; obj = ((description2 != null) ? description2.Description : null) ?? ""; } base.Description = (string)obj; base.Scope = scope; _entry = entry; _entry.SettingChanged += delegate { if (!base.IsRemote) { Raise(); } }; } public override bool TrySetFromText(string text, out string error) { if (!TryParse(text, out var value, out error)) { return false; } base.IsRemote = false; _entry.Value = value; Raise(); error = null; return true; } internal override void ApplyRemoteText(string text) { if (!TryParse(text, out var value, out var error)) { CorePlugin.Log.LogWarning((object)("Ignoring bad value for " + base.FullName + " from server: " + error)); return; } bool num = !base.IsRemote || !EqualityComparer<T>.Default.Equals(_remote, value); _remote = value; base.IsRemote = true; if (num) { Raise(); } } internal override void ClearRemote() { if (base.IsRemote) { base.IsRemote = false; Raise(); } } private void Raise() { try { this.Changed?.Invoke(this); } catch (Exception arg) { CorePlugin.Log.LogError((object)$"{base.FullName} change handler failed: {arg}"); } } private static string Format(T v) { if (!(v is float num)) { if (!(v is bool)) { return Convert.ToString(v, CultureInfo.InvariantCulture); } object obj = v; if (!(bool)((obj is bool) ? obj : null)) { return "false"; } return "true"; } return num.ToString("0.###", CultureInfo.InvariantCulture); } private static bool TryParse(string text, out T value, out string error) { value = default(T); error = null; text = (text ?? "").Trim(); Type typeFromHandle = typeof(T); try { if (typeFromHandle == typeof(bool)) { if (text.Equals("true", StringComparison.OrdinalIgnoreCase) || text == "1") { value = (T)(object)true; return true; } if (text.Equals("false", StringComparison.OrdinalIgnoreCase) || text == "0") { value = (T)(object)false; return true; } error = "expected true or false"; return false; } if (typeFromHandle == typeof(int)) { if (!int.TryParse(text, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result)) { error = "expected a whole number"; return false; } value = (T)(object)result; return true; } if (typeFromHandle == typeof(float)) { if (!float.TryParse(text, NumberStyles.Float, CultureInfo.InvariantCulture, out var result2)) { error = "expected a number"; return false; } value = (T)(object)result2; return true; } if (typeFromHandle.IsEnum) { string[] names = Enum.GetNames(typeFromHandle); foreach (string text2 in names) { if (text2.Equals(text, StringComparison.OrdinalIgnoreCase)) { value = (T)Enum.Parse(typeFromHandle, text2); return true; } } error = "expected one of: " + string.Join(", ", Enum.GetNames(typeFromHandle)); return false; } if (typeFromHandle == typeof(string)) { value = (T)(object)text; return true; } error = "unsupported setting type " + typeFromHandle.Name; return false; } catch (Exception ex) { error = ex.Message; return false; } } private static string Describe(Type t) { if (!(t == typeof(bool))) { if (!(t == typeof(int))) { if (!(t == typeof(float))) { if (!t.IsEnum) { return "text"; } return string.Join("|", Enum.GetNames(t)); } return "number"; } return "whole number"; } return "true/false"; } } public static class Settings { private static readonly Dictionary<string, SettingBase> All = new Dictionary<string, SettingBase>(StringComparer.OrdinalIgnoreCase); public static IEnumerable<SettingBase> Known => All.Values.OrderBy<SettingBase, string>((SettingBase s) => s.FullName, StringComparer.OrdinalIgnoreCase); public static IEnumerable<SettingBase> ServerOwned => Known.Where((SettingBase s) => s.Scope == Scope.Server); public static IEnumerable<string> Names => Known.Select((SettingBase s) => s.FullName); public static Setting<T> Register<T>(string feature, ConfigEntry<T> entry, Scope scope, string description = null) { if (entry == null) { throw new ArgumentNullException("entry"); } Setting<T> setting = new Setting<T>(feature, ((ConfigEntryBase)entry).Definition.Key, entry, scope, description); if (All.ContainsKey(setting.FullName)) { CorePlugin.Log.LogWarning((object)("Setting registered twice: " + setting.FullName)); } All[setting.FullName] = setting; return setting; } public static SettingBase Find(string fullName) { if (All.TryGetValue(fullName ?? "", out var value)) { return value; } List<SettingBase> list = All.Values.Where((SettingBase x) => string.Equals(x.ShortName, fullName, StringComparison.OrdinalIgnoreCase)).ToList(); if (list.Count == 1) { return list[0]; } List<SettingBase> list2 = All.Values.Where((SettingBase x) => string.Equals(x.Key, fullName, StringComparison.OrdinalIgnoreCase)).ToList(); if (list2.Count != 1) { return null; } return list2[0]; } internal static void ClearRemoteValues() { foreach (SettingBase value in All.Values) { value.ClearRemote(); } } } public interface IWorldScanConsumer { void BeginPass(); void Visit(ZDO zdo, int prefabHash); void EndPass(float realtimeNow); } public class WorldScanner { private static readonly FieldRef<ZDOMan, List<ZDO>[]> ObjectsBySector = AccessTools.FieldRefAccess<ZDOMan, List<ZDO>[]>("m_objectsBySector"); private static readonly FieldRef<ZDOMan, Dictionary<ZDOID, ZDO>> ObjectsById = AccessTools.FieldRefAccess<ZDOMan, Dictionary<ZDOID, ZDO>>("m_objectsByID"); private readonly List<IWorldScanConsumer> _consumers = new List<IWorldScanConsumer>(); private readonly Stopwatch _slice = new Stopwatch(); private int _sector = -1; private float _passStart; private double _passWorkMs; private double _passMaxSliceMs; private int _passFrames; private int _passObjects; public int CompletedPasses { get; private set; } public bool WorldLoaded { get; private set; } public bool HasConsumers => _consumers.Count > 0; public void Add(IWorldScanConsumer consumer) { if (consumer != null && !_consumers.Contains(consumer)) { _consumers.Add(consumer); } } public void FinishPassNow() { _passStart = -1.7014117E+38f; } public void Tick(float now, float cycleSeconds, int maxObjectsPerFrame) { if (_consumers.Count == 0 || ZDOMan.instance == null) { return; } List<ZDO>[] array = ObjectsBySector.Invoke(ZDOMan.instance); if (array == null) { return; } if (!WorldLoaded) { if (ObjectsById.Invoke(ZDOMan.instance).Count == 0) { return; } WorldLoaded = true; } if (_sector < 0) { _sector = 0; _passStart = now; _passWorkMs = (_passMaxSliceMs = 0.0); _passFrames = (_passObjects = 0); foreach (IWorldScanConsumer consumer in _consumers) { consumer.BeginPass(); } } float num = Math.Max(1f, cycleSeconds); double num2 = (now - _passStart) / num; int num3 = ((num2 >= 1.0) ? array.Length : ((int)(num2 * (double)array.Length))); if (_sector >= num3 && _sector < array.Length) { return; } _slice.Restart(); int num4 = Math.Max(100, maxObjectsPerFrame); int num5 = 0; while (_sector < array.Length && _sector < num3 && num5 < num4) { List<ZDO> list = array[_sector++]; if (list == null) { continue; } for (int i = 0; i < list.Count; i++) { ZDO val = list[i]; if (val != null && val.IsValid()) { int prefab = val.GetPrefab(); for (int j = 0; j < _consumers.Count; j++) { _consumers[j].Visit(val, prefab); } } } num5 += list.Count; } _passObjects += num5; bool flag = _sector >= array.Length; if (flag) { _sector = -1; foreach (IWorldScanConsumer consumer2 in _consumers) { consumer2.EndPass(now); } } double totalMilliseconds = _slice.Elapsed.TotalMilliseconds; _slice.Reset(); _passWorkMs += totalMilliseconds; _passFrames++; if (totalMilliseconds > _passMaxSliceMs) { _passMaxSliceMs = totalMilliseconds; } if (flag) { if (CompletedPasses == 0 || _passMaxSliceMs > 8.0) { CorePlugin.Log.Log((LogLevel)((_passMaxSliceMs > 8.0) ? 4 : 16), (object)($"World pass over {_passObjects} objects took {now - _passStart:0}s: {_passFrames} frames, " + $"{_passWorkMs:0.0} ms total work, slowest frame {_passMaxSliceMs:0.00} ms")); } CompletedPasses++; } } } }