From 7bce8206d5df5243e7888300938a0f0d637cd2a4 Mon Sep 17 00:00:00 2001 From: Evan Husted Date: Mon, 10 Feb 2025 13:59:25 -0600 Subject: [PATCH 1/7] misc: chore: small cleanups --- src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs | 4 ++-- .../Utilities/PlayReport/PlayReports.Formatters.cs | 14 ++++++++++---- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs b/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs index 499eedc8d..113c00a5a 100644 --- a/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs +++ b/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs @@ -1147,10 +1147,10 @@ namespace Ryujinx.Ava.UI.ViewModels List dirs = result.Select(it => it.Path.LocalPath).ToList(); int numAdded = onDirsSelected(dirs, out int numRemoved); - string msg = String.Join("\r\n", new string[] { + string msg = string.Join("\n", string.Format(LocaleManager.Instance[localeMessageRemovedKey], numRemoved), string.Format(LocaleManager.Instance[localeMessageAddedKey], numAdded) - }); + ); await Dispatcher.UIThread.InvokeAsync(async () => { diff --git a/src/Ryujinx/Utilities/PlayReport/PlayReports.Formatters.cs b/src/Ryujinx/Utilities/PlayReport/PlayReports.Formatters.cs index f0d7f87ba..cabace6bb 100644 --- a/src/Ryujinx/Utilities/PlayReport/PlayReports.Formatters.cs +++ b/src/Ryujinx/Utilities/PlayReport/PlayReports.Formatters.cs @@ -1,4 +1,5 @@ -using System; +using Gommon; +using System; using System.Buffers.Binary; using System.Collections.Generic; using System.Linq; @@ -281,9 +282,14 @@ namespace Ryujinx.Ava.Utilities.PlayReport players = players.OrderBy(p => p.Rank ?? int.MaxValue).ToList(); return players.Count > 4 - ? $"{players.Count} Players - " + string.Join(", ", - players.Take(3).Select(p => $"{p.Character}({p.PlayerNumber}){RankMedal(p.Rank)}")) - : string.Join(", ", players.Select(p => $"{p.Character}({p.PlayerNumber}){RankMedal(p.Rank)}")); + ? $"{players.Count} Players - { + players.Take(3) + .Select(p => $"{p.Character}({p.PlayerNumber}){RankMedal(p.Rank)}") + .JoinToString(", ") + }" + : players + .Select(p => $"{p.Character}({p.PlayerNumber}){RankMedal(p.Rank)}") + .JoinToString(", "); string RankMedal(int? rank) => rank switch { From f3cf03495dd2fc3aefbbd3b050f6ab9e41903e7e Mon Sep 17 00:00:00 2001 From: Evan Husted Date: Mon, 10 Feb 2025 14:25:47 -0600 Subject: [PATCH 2/7] misc: add the ability to ignore UI logs when using trace & debug log levels --- src/Ryujinx/Assets/locales.json | 50 +++++++++++++++++++ src/Ryujinx/UI/Helpers/LoggerAdapter.cs | 7 +++ .../UI/ViewModels/SettingsViewModel.cs | 3 ++ .../Views/Settings/SettingsLoggingView.axaml | 4 ++ .../Configuration/ConfigurationFileFormat.cs | 7 ++- .../ConfigurationState.Migration.cs | 3 +- .../Configuration/ConfigurationState.Model.cs | 6 +++ .../Configuration/ConfigurationState.cs | 2 + 8 files changed, 80 insertions(+), 2 deletions(-) diff --git a/src/Ryujinx/Assets/locales.json b/src/Ryujinx/Assets/locales.json index 04ecfe8da..ab114a893 100644 --- a/src/Ryujinx/Assets/locales.json +++ b/src/Ryujinx/Assets/locales.json @@ -5747,6 +5747,31 @@ "zh_TW": "啟用客體日誌" } }, + { + "ID": "SettingsTabLoggingEnableAvaloniaLogs", + "Translations": { + "ar_SA": "", + "de_DE": "", + "el_GR": "", + "en_US": "Enable UI Logs", + "es_ES": "", + "fr_FR": "", + "he_IL": "", + "it_IT": "", + "ja_JP": "", + "ko_KR": "", + "no_NO": "", + "pl_PL": "", + "pt_BR": "", + "ru_RU": "", + "sv_SE": "", + "th_TH": "", + "tr_TR": "", + "uk_UA": "", + "zh_CN": "", + "zh_TW": "" + } + }, { "ID": "SettingsTabLoggingEnableFsAccessLogs", "Translations": { @@ -16722,6 +16747,31 @@ "zh_TW": "謹慎使用" } }, + { + "ID": "AvaloniaLogTooltip", + "Translations": { + "ar_SA": "", + "de_DE": "", + "el_GR": "", + "en_US": "Prints Avalonia (UI) log messages in the console.", + "es_ES": "", + "fr_FR": "", + "he_IL": "", + "it_IT": "", + "ja_JP": "", + "ko_KR": "", + "no_NO": "", + "pl_PL": "", + "pt_BR": "", + "ru_RU": "", + "sv_SE": "", + "th_TH": "", + "tr_TR": "", + "uk_UA": "", + "zh_CN": "", + "zh_TW": "" + } + }, { "ID": "OpenGlLogLevel", "Translations": { diff --git a/src/Ryujinx/UI/Helpers/LoggerAdapter.cs b/src/Ryujinx/UI/Helpers/LoggerAdapter.cs index 928d1fc31..ba317e74a 100644 --- a/src/Ryujinx/UI/Helpers/LoggerAdapter.cs +++ b/src/Ryujinx/UI/Helpers/LoggerAdapter.cs @@ -1,6 +1,7 @@ using Avalonia.Logging; using Avalonia.Utilities; using Gommon; +using Ryujinx.Ava.Utilities.Configuration; using Ryujinx.Common.Logging; using System; using System.Text; @@ -14,13 +15,19 @@ namespace Ryujinx.Ava.UI.Helpers internal class LoggerAdapter : ILogSink { + private static bool _avaloniaLogsEnabled = ConfigurationState.Instance.Logger.EnableAvaloniaLog; + public static void Register() { AvaLogger.Sink = new LoggerAdapter(); + ConfigurationState.Instance.Logger.EnableAvaloniaLog.Event + += (_, e) => _avaloniaLogsEnabled = e.NewValue; } private static RyuLogger.Log? GetLog(AvaLogLevel level, string area) { + if (!_avaloniaLogsEnabled) return null; + return level switch { AvaLogLevel.Verbose => RyuLogger.Debug, diff --git a/src/Ryujinx/UI/ViewModels/SettingsViewModel.cs b/src/Ryujinx/UI/ViewModels/SettingsViewModel.cs index bd649602c..0b3f8dcc6 100644 --- a/src/Ryujinx/UI/ViewModels/SettingsViewModel.cs +++ b/src/Ryujinx/UI/ViewModels/SettingsViewModel.cs @@ -204,6 +204,7 @@ namespace Ryujinx.Ava.UI.ViewModels public bool EnableTrace { get; set; } public bool EnableGuest { get; set; } public bool EnableFsAccessLog { get; set; } + public bool EnableAvaloniaLog { get; set; } public bool EnableDebug { get; set; } public bool IsOpenAlEnabled { get; set; } public bool IsSoundIoEnabled { get; set; } @@ -560,6 +561,7 @@ namespace Ryujinx.Ava.UI.ViewModels EnableGuest = config.Logger.EnableGuest; EnableDebug = config.Logger.EnableDebug; EnableFsAccessLog = config.Logger.EnableFsAccessLog; + EnableAvaloniaLog = config.Logger.EnableAvaloniaLog; FsGlobalAccessLogMode = config.System.FsGlobalAccessLogMode; OpenglDebugLevel = (int)config.Logger.GraphicsDebugLevel.Value; @@ -679,6 +681,7 @@ namespace Ryujinx.Ava.UI.ViewModels config.Logger.EnableGuest.Value = EnableGuest; config.Logger.EnableDebug.Value = EnableDebug; config.Logger.EnableFsAccessLog.Value = EnableFsAccessLog; + config.Logger.EnableAvaloniaLog.Value = EnableAvaloniaLog; config.System.FsGlobalAccessLogMode.Value = FsGlobalAccessLogMode; config.Logger.GraphicsDebugLevel.Value = (GraphicsDebugLevel)OpenglDebugLevel; diff --git a/src/Ryujinx/UI/Views/Settings/SettingsLoggingView.axaml b/src/Ryujinx/UI/Views/Settings/SettingsLoggingView.axaml index 7ab49c0ae..d28f0ffa7 100644 --- a/src/Ryujinx/UI/Views/Settings/SettingsLoggingView.axaml +++ b/src/Ryujinx/UI/Views/Settings/SettingsLoggingView.axaml @@ -74,6 +74,10 @@ ToolTip.Tip="{ext:Locale DebugLogTooltip}"> + + + /// The current version of the file format /// - public const int CurrentVersion = 63; + public const int CurrentVersion = 64; /// /// Version of the configuration file format @@ -111,6 +111,11 @@ namespace Ryujinx.Ava.Utilities.Configuration /// Enables printing FS access log messages /// public bool LoggingEnableFsAccessLog { get; set; } + + /// + /// Enables log messages from Avalonia + /// + public bool LoggingEnableAvalonia { get; set; } /// /// Controls which log messages are written to the log targets diff --git a/src/Ryujinx/Utilities/Configuration/ConfigurationState.Migration.cs b/src/Ryujinx/Utilities/Configuration/ConfigurationState.Migration.cs index 0fb3fb754..36f42d8b3 100644 --- a/src/Ryujinx/Utilities/Configuration/ConfigurationState.Migration.cs +++ b/src/Ryujinx/Utilities/Configuration/ConfigurationState.Migration.cs @@ -430,7 +430,8 @@ namespace Ryujinx.Ava.Utilities.Configuration } }), (62, static cff => cff.RainbowSpeed = 1f), - (63, static cff => cff.MatchSystemTime = false) + (63, static cff => cff.MatchSystemTime = false), + (64, static cff => cff.LoggingEnableAvalonia = false) ); } } diff --git a/src/Ryujinx/Utilities/Configuration/ConfigurationState.Model.cs b/src/Ryujinx/Utilities/Configuration/ConfigurationState.Model.cs index 2d6e2aa7f..8fbe20e05 100644 --- a/src/Ryujinx/Utilities/Configuration/ConfigurationState.Model.cs +++ b/src/Ryujinx/Utilities/Configuration/ConfigurationState.Model.cs @@ -254,6 +254,11 @@ namespace Ryujinx.Ava.Utilities.Configuration /// Enables printing FS access log messages /// public ReactiveObject EnableFsAccessLog { get; private set; } + + /// + /// Enables log messages from Avalonia + /// + public ReactiveObject EnableAvaloniaLog { get; private set; } /// /// Controls which log messages are written to the log targets @@ -281,6 +286,7 @@ namespace Ryujinx.Ava.Utilities.Configuration EnableTrace = new ReactiveObject(); EnableGuest = new ReactiveObject(); EnableFsAccessLog = new ReactiveObject(); + EnableAvaloniaLog = new ReactiveObject(); FilteredClasses = new ReactiveObject(); EnableFileLog = new ReactiveObject(); EnableFileLog.LogChangesToValue(nameof(EnableFileLog)); diff --git a/src/Ryujinx/Utilities/Configuration/ConfigurationState.cs b/src/Ryujinx/Utilities/Configuration/ConfigurationState.cs index 4ab77a60f..f8fbc90d8 100644 --- a/src/Ryujinx/Utilities/Configuration/ConfigurationState.cs +++ b/src/Ryujinx/Utilities/Configuration/ConfigurationState.cs @@ -46,6 +46,7 @@ namespace Ryujinx.Ava.Utilities.Configuration LoggingEnableTrace = Logger.EnableTrace, LoggingEnableGuest = Logger.EnableGuest, LoggingEnableFsAccessLog = Logger.EnableFsAccessLog, + LoggingEnableAvalonia = Logger.EnableAvaloniaLog, LoggingFilteredClasses = Logger.FilteredClasses, LoggingGraphicsDebugLevel = Logger.GraphicsDebugLevel, SystemLanguage = System.Language, @@ -165,6 +166,7 @@ namespace Ryujinx.Ava.Utilities.Configuration Logger.EnableTrace.Value = false; Logger.EnableGuest.Value = true; Logger.EnableFsAccessLog.Value = false; + Logger.EnableAvaloniaLog.Value = false; Logger.FilteredClasses.Value = []; Logger.GraphicsDebugLevel.Value = GraphicsDebugLevel.None; System.Language.Value = Language.AmericanEnglish; From b6b391b2cf1ebdd752f04fbd0b5f6cde1ad17495 Mon Sep 17 00:00:00 2001 From: Evan Husted Date: Mon, 10 Feb 2025 14:34:12 -0600 Subject: [PATCH 3/7] misc: chore: [ci skip] Remove unused 'using' directives from solution --- src/Ryujinx.Graphics.Vulkan/Vendor.cs | 1 - src/Ryujinx.HLE/HOS/Applets/Error/ErrorApplet.cs | 1 - src/Ryujinx.Horizon/HorizonStatic.cs | 1 - src/Ryujinx/DiscordIntegrationModule.cs | 2 -- src/Ryujinx/UI/Controls/ApplicationDataView.axaml.cs | 5 +---- src/Ryujinx/UI/Controls/ApplicationListView.axaml.cs | 1 - src/Ryujinx/UI/Views/Settings/SettingsSystemView.axaml.cs | 1 - src/Ryujinx/Utilities/PlayReport/Analyzer.cs | 1 - src/Ryujinx/Utilities/PlayReport/MatchedValues.cs | 1 - src/Ryujinx/Utilities/PlayReport/PlayReports.cs | 7 +------ src/Ryujinx/Utilities/PlayReport/Specs.cs | 3 +-- 11 files changed, 3 insertions(+), 21 deletions(-) diff --git a/src/Ryujinx.Graphics.Vulkan/Vendor.cs b/src/Ryujinx.Graphics.Vulkan/Vendor.cs index 87c6407cd..550ba0221 100644 --- a/src/Ryujinx.Graphics.Vulkan/Vendor.cs +++ b/src/Ryujinx.Graphics.Vulkan/Vendor.cs @@ -1,5 +1,4 @@ using Silk.NET.Vulkan; -using System.Text.RegularExpressions; namespace Ryujinx.Graphics.Vulkan { diff --git a/src/Ryujinx.HLE/HOS/Applets/Error/ErrorApplet.cs b/src/Ryujinx.HLE/HOS/Applets/Error/ErrorApplet.cs index b41bc60b1..2faff6e61 100644 --- a/src/Ryujinx.HLE/HOS/Applets/Error/ErrorApplet.cs +++ b/src/Ryujinx.HLE/HOS/Applets/Error/ErrorApplet.cs @@ -15,7 +15,6 @@ using System.IO; using System.Linq; using System.Runtime.InteropServices; using System.Text; -using System.Text.RegularExpressions; namespace Ryujinx.HLE.HOS.Applets.Error { diff --git a/src/Ryujinx.Horizon/HorizonStatic.cs b/src/Ryujinx.Horizon/HorizonStatic.cs index eb9dd4e31..a936a5a2d 100644 --- a/src/Ryujinx.Horizon/HorizonStatic.cs +++ b/src/Ryujinx.Horizon/HorizonStatic.cs @@ -1,4 +1,3 @@ -using MsgPack; using Ryujinx.Horizon.Common; using Ryujinx.Horizon.Prepo.Types; using Ryujinx.Memory; diff --git a/src/Ryujinx/DiscordIntegrationModule.cs b/src/Ryujinx/DiscordIntegrationModule.cs index 1f820a223..47fc8ad69 100644 --- a/src/Ryujinx/DiscordIntegrationModule.cs +++ b/src/Ryujinx/DiscordIntegrationModule.cs @@ -1,6 +1,5 @@ using DiscordRPC; using Gommon; -using MsgPack; using Ryujinx.Ava.Utilities; using Ryujinx.Ava.Utilities.AppLibrary; using Ryujinx.Ava.Utilities.Configuration; @@ -11,7 +10,6 @@ using Ryujinx.HLE; using Ryujinx.HLE.Loaders.Processes; using Ryujinx.Horizon; using Ryujinx.Horizon.Prepo.Types; -using System.Linq; using System.Text; namespace Ryujinx.Ava diff --git a/src/Ryujinx/UI/Controls/ApplicationDataView.axaml.cs b/src/Ryujinx/UI/Controls/ApplicationDataView.axaml.cs index e85e1188e..57dba3228 100644 --- a/src/Ryujinx/UI/Controls/ApplicationDataView.axaml.cs +++ b/src/Ryujinx/UI/Controls/ApplicationDataView.axaml.cs @@ -1,12 +1,9 @@ -using Avalonia; -using Avalonia.Controls; +using Avalonia.Controls; using Avalonia.Input.Platform; using Avalonia.Interactivity; using Avalonia.Styling; using FluentAvalonia.UI.Controls; -using Ryujinx.Ava; using Ryujinx.Ava.Common.Locale; -using Ryujinx.Ava.UI.Controls; using Ryujinx.Ava.UI.Helpers; using Ryujinx.Ava.UI.ViewModels; using Ryujinx.Ava.UI.Windows; diff --git a/src/Ryujinx/UI/Controls/ApplicationListView.axaml.cs b/src/Ryujinx/UI/Controls/ApplicationListView.axaml.cs index 7c6b0cf15..fb38fdb72 100644 --- a/src/Ryujinx/UI/Controls/ApplicationListView.axaml.cs +++ b/src/Ryujinx/UI/Controls/ApplicationListView.axaml.cs @@ -7,7 +7,6 @@ using Ryujinx.Ava.UI.ViewModels; using Ryujinx.Ava.Utilities.AppLibrary; using Ryujinx.Ava.Utilities.Compat; using System; -using System.Globalization; using System.Linq; namespace Ryujinx.Ava.UI.Controls diff --git a/src/Ryujinx/UI/Views/Settings/SettingsSystemView.axaml.cs b/src/Ryujinx/UI/Views/Settings/SettingsSystemView.axaml.cs index 3878c19d5..2c9eac28c 100644 --- a/src/Ryujinx/UI/Views/Settings/SettingsSystemView.axaml.cs +++ b/src/Ryujinx/UI/Views/Settings/SettingsSystemView.axaml.cs @@ -1,5 +1,4 @@ using Avalonia.Controls; -using Avalonia.Interactivity; using Ryujinx.Ava.UI.ViewModels; using TimeZone = Ryujinx.Ava.UI.Models.TimeZone; diff --git a/src/Ryujinx/Utilities/PlayReport/Analyzer.cs b/src/Ryujinx/Utilities/PlayReport/Analyzer.cs index 0b5284673..51047cc32 100644 --- a/src/Ryujinx/Utilities/PlayReport/Analyzer.cs +++ b/src/Ryujinx/Utilities/PlayReport/Analyzer.cs @@ -1,5 +1,4 @@ using Gommon; -using MsgPack; using Ryujinx.Ava.Utilities.AppLibrary; using System; using System.Collections.Generic; diff --git a/src/Ryujinx/Utilities/PlayReport/MatchedValues.cs b/src/Ryujinx/Utilities/PlayReport/MatchedValues.cs index 3086a9d65..46cae678a 100644 --- a/src/Ryujinx/Utilities/PlayReport/MatchedValues.cs +++ b/src/Ryujinx/Utilities/PlayReport/MatchedValues.cs @@ -1,7 +1,6 @@ using MsgPack; using Ryujinx.Ava.Utilities.AppLibrary; using System.Collections.Generic; -using System.Linq; namespace Ryujinx.Ava.Utilities.PlayReport { diff --git a/src/Ryujinx/Utilities/PlayReport/PlayReports.cs b/src/Ryujinx/Utilities/PlayReport/PlayReports.cs index 5f6ba3446..d5568962e 100644 --- a/src/Ryujinx/Utilities/PlayReport/PlayReports.cs +++ b/src/Ryujinx/Utilities/PlayReport/PlayReports.cs @@ -1,9 +1,4 @@ -using System; -using System.Buffers.Binary; -using System.Collections.Generic; -using System.Linq; - -namespace Ryujinx.Ava.Utilities.PlayReport +namespace Ryujinx.Ava.Utilities.PlayReport { public static partial class PlayReports { diff --git a/src/Ryujinx/Utilities/PlayReport/Specs.cs b/src/Ryujinx/Utilities/PlayReport/Specs.cs index b81a599a2..5c8126a99 100644 --- a/src/Ryujinx/Utilities/PlayReport/Specs.cs +++ b/src/Ryujinx/Utilities/PlayReport/Specs.cs @@ -1,5 +1,4 @@ -using FluentAvalonia.Core; -using MsgPack; +using MsgPack; using Ryujinx.Ava.Utilities.AppLibrary; using System; using System.Collections.Generic; From 1129ab0e8c3a6211a76a0dd875dadf35f8e7b268 Mon Sep 17 00:00:00 2001 From: Evan Husted Date: Mon, 10 Feb 2025 15:44:58 -0600 Subject: [PATCH 4/7] misc: chore: Remove unused property in ApplicationData --- .../Utilities/AppLibrary/ApplicationData.cs | 31 ++++--------------- 1 file changed, 6 insertions(+), 25 deletions(-) diff --git a/src/Ryujinx/Utilities/AppLibrary/ApplicationData.cs b/src/Ryujinx/Utilities/AppLibrary/ApplicationData.cs index e893c4c21..02bf46c94 100644 --- a/src/Ryujinx/Utilities/AppLibrary/ApplicationData.cs +++ b/src/Ryujinx/Utilities/AppLibrary/ApplicationData.cs @@ -59,7 +59,7 @@ namespace Ryujinx.Ava.Utilities.AppLibrary public string TimePlayedString => ValueFormatUtils.FormatTimeSpan(TimePlayed); - public bool HasPlayedPreviously => TimePlayedString != string.Empty; + public bool HasPlayedPreviously => TimePlayed.TotalSeconds > 1; public string LastPlayedString => ValueFormatUtils.FormatDateTime(LastPlayed)?.Replace(" ", "\n"); @@ -78,25 +78,6 @@ namespace Ryujinx.Ava.Utilities.AppLibrary public LocaleKeys? PlayabilityStatus => Compatibility.Convert(x => x.Status).OrElse(null); - public string CompatibilityToolTip - { - get - { - StringBuilder sb = new(LocalizedStatusTooltip); - - string formattedCompatibilityLabels = FormattedCompatibilityLabels; - if (!string.IsNullOrEmpty(formattedCompatibilityLabels)) - { - sb.AppendLine() - .AppendLine() - .Append(formattedCompatibilityLabels); - } - - return sb.ToString(); - } - } - - public string LocalizedStatusTooltip => Compatibility.Convert(x => #pragma warning disable CS8509 It is exhaustive for all possible values this can contain. @@ -120,16 +101,16 @@ namespace Ryujinx.Ava.Utilities.AppLibrary public static string GetBuildId(VirtualFileSystem virtualFileSystem, IntegrityCheckLevel checkLevel, string titleFilePath) { - using FileStream file = new(titleFilePath, FileMode.Open, FileAccess.Read); - - Nca mainNca = null; - Nca patchNca = null; - if (!System.IO.Path.Exists(titleFilePath)) { Logger.Error?.Print(LogClass.Application, $"File \"{titleFilePath}\" does not exist."); return string.Empty; } + + using FileStream file = new(titleFilePath, FileMode.Open, FileAccess.Read); + + Nca mainNca = null; + Nca patchNca = null; string extension = System.IO.Path.GetExtension(titleFilePath).ToLower(); From 55fdb3f6b2dc091d8ae36ed82f88cc14d400aed8 Mon Sep 17 00:00:00 2001 From: Evan Husted Date: Mon, 10 Feb 2025 15:45:09 -0600 Subject: [PATCH 5/7] headless: Default to Vulkan --- src/Ryujinx/Headless/Options.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Ryujinx/Headless/Options.cs b/src/Ryujinx/Headless/Options.cs index 1b10145fe..a57863d5d 100644 --- a/src/Ryujinx/Headless/Options.cs +++ b/src/Ryujinx/Headless/Options.cs @@ -387,7 +387,7 @@ namespace Ryujinx.Headless [Option("graphics-shaders-dump-path", Required = false, HelpText = "Dumps shaders in this local directory. (Developer only)")] public string GraphicsShadersDumpPath { get; set; } - [Option("graphics-backend", Required = false, Default = GraphicsBackend.OpenGl, HelpText = "Change Graphics Backend to use.")] + [Option("graphics-backend", Required = false, Default = GraphicsBackend.Vulkan, HelpText = "Change Graphics Backend to use.")] public GraphicsBackend GraphicsBackend { get; set; } [Option("preferred-gpu-vendor", Required = false, Default = "", HelpText = "When using the Vulkan backend, prefer using the GPU from the specified vendor.")] From faacec980117cad9b43dd7fa40300d20b3f12162 Mon Sep 17 00:00:00 2001 From: FluffyOMC <45863583+FluffyOMC@users.noreply.github.com> Date: Mon, 10 Feb 2025 16:53:44 -0500 Subject: [PATCH 6/7] JIT Cache Regions + HLE SoNoSigpipe BSD socket mapping (#615) Instead of one big 2048MB JIT Cache that'd crash the emulator when maxed out, we now have it where we add 256MB JIT Cache regions when needed, helping reduce allocated memory where games don't use the JIT cache for it, and helping bigger games that DO need JIT cache bigger than 2048MB! ![image](https://github.com/user-attachments/assets/ff17dc48-6028-4377-8c73-746ab21ab83b) (SSBU goes past the 2048MB JIT Cache limit that would normally crash Ryujinx ^) Also I added a BSD socket that Baba is You's networking for downloading custom levels uses. --- src/ARMeilleure/Memory/ReservedRegion.cs | 2 + src/ARMeilleure/Translation/Cache/JitCache.cs | 112 ++++++++++++------ .../LightningJit/Cache/JitCache.cs | 94 +++++++++------ .../LightningJit/Cache/NoWxCache.cs | 2 +- .../Sockets/Bsd/Impl/WinSockHelper.cs | 1 + 5 files changed, 142 insertions(+), 69 deletions(-) diff --git a/src/ARMeilleure/Memory/ReservedRegion.cs b/src/ARMeilleure/Memory/ReservedRegion.cs index a3ebd610d..dfe17c933 100644 --- a/src/ARMeilleure/Memory/ReservedRegion.cs +++ b/src/ARMeilleure/Memory/ReservedRegion.cs @@ -7,6 +7,7 @@ namespace ARMeilleure.Memory public const int DefaultGranularity = 65536; // Mapping granularity in Windows. public IJitMemoryBlock Block { get; } + public IJitMemoryAllocator Allocator { get; } public nint Pointer => Block.Pointer; @@ -21,6 +22,7 @@ namespace ARMeilleure.Memory granularity = DefaultGranularity; } + Allocator = allocator; Block = allocator.Reserve(maxSize); _maxSize = maxSize; _sizeGranularity = granularity; diff --git a/src/ARMeilleure/Translation/Cache/JitCache.cs b/src/ARMeilleure/Translation/Cache/JitCache.cs index d7e8201d8..0ede558b6 100644 --- a/src/ARMeilleure/Translation/Cache/JitCache.cs +++ b/src/ARMeilleure/Translation/Cache/JitCache.cs @@ -2,6 +2,8 @@ using ARMeilleure.CodeGen; using ARMeilleure.CodeGen.Unwinding; using ARMeilleure.Memory; using ARMeilleure.Native; +using Humanizer; +using Ryujinx.Common.Logging; using Ryujinx.Memory; using System; using System.Collections.Generic; @@ -18,9 +20,8 @@ namespace ARMeilleure.Translation.Cache private static readonly int _pageMask = _pageSize - 1; private const int CodeAlignment = 4; // Bytes. - private const int CacheSize = 2047 * 1024 * 1024; + private const int CacheSize = 256 * 1024 * 1024; - private static ReservedRegion _jitRegion; private static JitCacheInvalidation _jitCacheInvalidator; private static CacheMemoryAllocator _cacheAllocator; @@ -30,6 +31,9 @@ namespace ARMeilleure.Translation.Cache private static readonly Lock _lock = new(); private static bool _initialized; + private static readonly List _jitRegions = new(); + private static int _activeRegionIndex = 0; + [SupportedOSPlatform("windows")] [LibraryImport("kernel32.dll", SetLastError = true)] public static partial nint FlushInstructionCache(nint hProcess, nint lpAddress, nuint dwSize); @@ -48,7 +52,9 @@ namespace ARMeilleure.Translation.Cache return; } - _jitRegion = new ReservedRegion(allocator, CacheSize); + var firstRegion = new ReservedRegion(allocator, CacheSize); + _jitRegions.Add(firstRegion); + _activeRegionIndex = 0; if (!OperatingSystem.IsWindows() && !OperatingSystem.IsMacOS()) { @@ -59,7 +65,9 @@ namespace ARMeilleure.Translation.Cache if (OperatingSystem.IsWindows()) { - JitUnwindWindows.InstallFunctionTableHandler(_jitRegion.Pointer, CacheSize, _jitRegion.Pointer + Allocate(_pageSize)); + JitUnwindWindows.InstallFunctionTableHandler( + firstRegion.Pointer, CacheSize, firstRegion.Pointer + Allocate(_pageSize) + ); } _initialized = true; @@ -75,8 +83,8 @@ namespace ARMeilleure.Translation.Cache Debug.Assert(_initialized); int funcOffset = Allocate(code.Length); - - nint funcPtr = _jitRegion.Pointer + funcOffset; + ReservedRegion targetRegion = _jitRegions[_activeRegionIndex]; + nint funcPtr = targetRegion.Pointer + funcOffset; if (OperatingSystem.IsMacOS() && RuntimeInformation.ProcessArchitecture == Architecture.Arm64) { @@ -90,9 +98,9 @@ namespace ARMeilleure.Translation.Cache } else { - ReprotectAsWritable(funcOffset, code.Length); + ReprotectAsWritable(targetRegion, funcOffset, code.Length); Marshal.Copy(code, 0, funcPtr, code.Length); - ReprotectAsExecutable(funcOffset, code.Length); + ReprotectAsExecutable(targetRegion, funcOffset, code.Length); if (OperatingSystem.IsWindows() && RuntimeInformation.ProcessArchitecture == Architecture.Arm64) { @@ -116,52 +124,83 @@ namespace ARMeilleure.Translation.Cache { Debug.Assert(_initialized); - int funcOffset = (int)(pointer.ToInt64() - _jitRegion.Pointer.ToInt64()); - - if (TryFind(funcOffset, out CacheEntry entry, out int entryIndex) && entry.Offset == funcOffset) + foreach (var region in _jitRegions) { - _cacheAllocator.Free(funcOffset, AlignCodeSize(entry.Size)); - _cacheEntries.RemoveAt(entryIndex); + if (pointer.ToInt64() < region.Pointer.ToInt64() || + pointer.ToInt64() >= (region.Pointer + CacheSize).ToInt64()) + { + continue; + } + + int funcOffset = (int)(pointer.ToInt64() - region.Pointer.ToInt64()); + + if (TryFind(funcOffset, out CacheEntry entry, out int entryIndex) && entry.Offset == funcOffset) + { + _cacheAllocator.Free(funcOffset, AlignCodeSize(entry.Size)); + _cacheEntries.RemoveAt(entryIndex); + } + + return; } } } - private static void ReprotectAsWritable(int offset, int size) + private static void ReprotectAsWritable(ReservedRegion region, int offset, int size) { int endOffs = offset + size; - int regionStart = offset & ~_pageMask; int regionEnd = (endOffs + _pageMask) & ~_pageMask; - _jitRegion.Block.MapAsRwx((ulong)regionStart, (ulong)(regionEnd - regionStart)); + region.Block.MapAsRwx((ulong)regionStart, (ulong)(regionEnd - regionStart)); } - private static void ReprotectAsExecutable(int offset, int size) + private static void ReprotectAsExecutable(ReservedRegion region, int offset, int size) { int endOffs = offset + size; - int regionStart = offset & ~_pageMask; int regionEnd = (endOffs + _pageMask) & ~_pageMask; - _jitRegion.Block.MapAsRx((ulong)regionStart, (ulong)(regionEnd - regionStart)); + region.Block.MapAsRx((ulong)regionStart, (ulong)(regionEnd - regionStart)); } private static int Allocate(int codeSize) { codeSize = AlignCodeSize(codeSize); - int allocOffset = _cacheAllocator.Allocate(codeSize); - - if (allocOffset < 0) + for (int i = _activeRegionIndex; i < _jitRegions.Count; i++) { - throw new OutOfMemoryException("JIT Cache exhausted."); + int allocOffset = _cacheAllocator.Allocate(codeSize); + + if (allocOffset >= 0) + { + _jitRegions[i].ExpandIfNeeded((ulong)allocOffset + (ulong)codeSize); + _activeRegionIndex = i; + return allocOffset; + } } - _jitRegion.ExpandIfNeeded((ulong)allocOffset + (ulong)codeSize); + int exhaustedRegion = _activeRegionIndex; + var newRegion = new ReservedRegion(_jitRegions[0].Allocator, CacheSize); + _jitRegions.Add(newRegion); + _activeRegionIndex = _jitRegions.Count - 1; + + int newRegionNumber = _activeRegionIndex; - return allocOffset; + Logger.Warning?.Print(LogClass.Cpu, $"JIT Cache Region {exhaustedRegion} exhausted, creating new Cache Region {newRegionNumber} ({((newRegionNumber + 1) * CacheSize).Bytes()} Total Allocation)."); + + _cacheAllocator = new CacheMemoryAllocator(CacheSize); + + int allocOffsetNew = _cacheAllocator.Allocate(codeSize); + if (allocOffsetNew < 0) + { + throw new OutOfMemoryException("Failed to allocate in new Cache Region!"); + } + + newRegion.ExpandIfNeeded((ulong)allocOffsetNew + (ulong)codeSize); + return allocOffsetNew; } + private static int AlignCodeSize(int codeSize) { return checked(codeSize + (CodeAlignment - 1)) & ~(CodeAlignment - 1); @@ -185,18 +224,21 @@ namespace ARMeilleure.Translation.Cache { lock (_lock) { - int index = _cacheEntries.BinarySearch(new CacheEntry(offset, 0, default)); - - if (index < 0) + foreach (var region in _jitRegions) { - index = ~index - 1; - } + int index = _cacheEntries.BinarySearch(new CacheEntry(offset, 0, default)); - if (index >= 0) - { - entry = _cacheEntries[index]; - entryIndex = index; - return true; + if (index < 0) + { + index = ~index - 1; + } + + if (index >= 0) + { + entry = _cacheEntries[index]; + entryIndex = index; + return true; + } } } diff --git a/src/Ryujinx.Cpu/LightningJit/Cache/JitCache.cs b/src/Ryujinx.Cpu/LightningJit/Cache/JitCache.cs index 5849401ab..c994d424e 100644 --- a/src/Ryujinx.Cpu/LightningJit/Cache/JitCache.cs +++ b/src/Ryujinx.Cpu/LightningJit/Cache/JitCache.cs @@ -1,4 +1,6 @@ using ARMeilleure.Memory; +using Humanizer; +using Ryujinx.Common.Logging; using Ryujinx.Memory; using System; using System.Collections.Generic; @@ -15,9 +17,8 @@ namespace Ryujinx.Cpu.LightningJit.Cache private static readonly int _pageMask = _pageSize - 1; private const int CodeAlignment = 4; // Bytes. - private const int CacheSize = 2047 * 1024 * 1024; + private const int CacheSize = 256 * 1024 * 1024; - private static ReservedRegion _jitRegion; private static JitCacheInvalidation _jitCacheInvalidator; private static CacheMemoryAllocator _cacheAllocator; @@ -26,6 +27,8 @@ namespace Ryujinx.Cpu.LightningJit.Cache private static readonly Lock _lock = new(); private static bool _initialized; + private static readonly List _jitRegions = new(); + private static int _activeRegionIndex = 0; [SupportedOSPlatform("windows")] [LibraryImport("kernel32.dll", SetLastError = true)] @@ -45,7 +48,9 @@ namespace Ryujinx.Cpu.LightningJit.Cache return; } - _jitRegion = new ReservedRegion(allocator, CacheSize); + var firstRegion = new ReservedRegion(allocator, CacheSize); + _jitRegions.Add(firstRegion); + _activeRegionIndex = 0; if (!OperatingSystem.IsWindows() && !OperatingSystem.IsMacOS()) { @@ -65,8 +70,8 @@ namespace Ryujinx.Cpu.LightningJit.Cache Debug.Assert(_initialized); int funcOffset = Allocate(code.Length); - - nint funcPtr = _jitRegion.Pointer + funcOffset; + ReservedRegion targetRegion = _jitRegions[_activeRegionIndex]; + nint funcPtr = targetRegion.Pointer + funcOffset; if (OperatingSystem.IsMacOS() && RuntimeInformation.ProcessArchitecture == Architecture.Arm64) { @@ -80,18 +85,11 @@ namespace Ryujinx.Cpu.LightningJit.Cache } else { - ReprotectAsWritable(funcOffset, code.Length); - code.CopyTo(new Span((void*)funcPtr, code.Length)); - ReprotectAsExecutable(funcOffset, code.Length); + ReprotectAsWritable(targetRegion, funcOffset, code.Length); + Marshal.Copy(code.ToArray(), 0, funcPtr, code.Length); + ReprotectAsExecutable(targetRegion, funcOffset, code.Length); - if (OperatingSystem.IsWindows() && RuntimeInformation.ProcessArchitecture == Architecture.Arm64) - { - FlushInstructionCache(Process.GetCurrentProcess().Handle, funcPtr, (nuint)code.Length); - } - else - { - _jitCacheInvalidator?.Invalidate(funcPtr, (ulong)code.Length); - } + _jitCacheInvalidator?.Invalidate(funcPtr, (ulong)code.Length); } Add(funcOffset, code.Length); @@ -106,50 +104,80 @@ namespace Ryujinx.Cpu.LightningJit.Cache { Debug.Assert(_initialized); - int funcOffset = (int)(pointer.ToInt64() - _jitRegion.Pointer.ToInt64()); - - if (TryFind(funcOffset, out CacheEntry entry, out int entryIndex) && entry.Offset == funcOffset) + foreach (var region in _jitRegions) { - _cacheAllocator.Free(funcOffset, AlignCodeSize(entry.Size)); - _cacheEntries.RemoveAt(entryIndex); + if (pointer.ToInt64() < region.Pointer.ToInt64() || + pointer.ToInt64() >= (region.Pointer + CacheSize).ToInt64()) + { + continue; + } + + int funcOffset = (int)(pointer.ToInt64() - region.Pointer.ToInt64()); + + if (TryFind(funcOffset, out CacheEntry entry, out int entryIndex) && entry.Offset == funcOffset) + { + _cacheAllocator.Free(funcOffset, AlignCodeSize(entry.Size)); + _cacheEntries.RemoveAt(entryIndex); + } + + return; } } } - private static void ReprotectAsWritable(int offset, int size) + private static void ReprotectAsWritable(ReservedRegion region, int offset, int size) { int endOffs = offset + size; - int regionStart = offset & ~_pageMask; int regionEnd = (endOffs + _pageMask) & ~_pageMask; - _jitRegion.Block.MapAsRwx((ulong)regionStart, (ulong)(regionEnd - regionStart)); + region.Block.MapAsRwx((ulong)regionStart, (ulong)(regionEnd - regionStart)); } - private static void ReprotectAsExecutable(int offset, int size) + private static void ReprotectAsExecutable(ReservedRegion region, int offset, int size) { int endOffs = offset + size; - int regionStart = offset & ~_pageMask; int regionEnd = (endOffs + _pageMask) & ~_pageMask; - _jitRegion.Block.MapAsRx((ulong)regionStart, (ulong)(regionEnd - regionStart)); + region.Block.MapAsRx((ulong)regionStart, (ulong)(regionEnd - regionStart)); } private static int Allocate(int codeSize) { codeSize = AlignCodeSize(codeSize); - int allocOffset = _cacheAllocator.Allocate(codeSize); - - if (allocOffset < 0) + for (int i = _activeRegionIndex; i < _jitRegions.Count; i++) { - throw new OutOfMemoryException("JIT Cache exhausted."); + int allocOffset = _cacheAllocator.Allocate(codeSize); + + if (allocOffset >= 0) + { + _jitRegions[i].ExpandIfNeeded((ulong)allocOffset + (ulong)codeSize); + _activeRegionIndex = i; + return allocOffset; + } } - _jitRegion.ExpandIfNeeded((ulong)allocOffset + (ulong)codeSize); + int exhaustedRegion = _activeRegionIndex; + var newRegion = new ReservedRegion(_jitRegions[0].Allocator, CacheSize); + _jitRegions.Add(newRegion); + _activeRegionIndex = _jitRegions.Count - 1; + + int newRegionNumber = _activeRegionIndex; - return allocOffset; + Logger.Warning?.Print(LogClass.Cpu, $"JIT Cache Region {exhaustedRegion} exhausted, creating new Cache Region {newRegionNumber} ({((newRegionNumber + 1) * CacheSize).Bytes()} Total Allocation)."); + + _cacheAllocator = new CacheMemoryAllocator(CacheSize); + + int allocOffsetNew = _cacheAllocator.Allocate(codeSize); + if (allocOffsetNew < 0) + { + throw new OutOfMemoryException("Failed to allocate in new Cache Region!"); + } + + newRegion.ExpandIfNeeded((ulong)allocOffsetNew + (ulong)codeSize); + return allocOffsetNew; } private static int AlignCodeSize(int codeSize) diff --git a/src/Ryujinx.Cpu/LightningJit/Cache/NoWxCache.cs b/src/Ryujinx.Cpu/LightningJit/Cache/NoWxCache.cs index 1bbf70182..65d297c28 100644 --- a/src/Ryujinx.Cpu/LightningJit/Cache/NoWxCache.cs +++ b/src/Ryujinx.Cpu/LightningJit/Cache/NoWxCache.cs @@ -12,7 +12,7 @@ namespace Ryujinx.Cpu.LightningJit.Cache { private const int CodeAlignment = 4; // Bytes. private const int SharedCacheSize = 2047 * 1024 * 1024; - private const int LocalCacheSize = 128 * 1024 * 1024; + private const int LocalCacheSize = 256 * 1024 * 1024; // How many calls to the same function we allow until we pad the shared cache to force the function to become available there // and allow the guest to take the fast path. diff --git a/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Impl/WinSockHelper.cs b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Impl/WinSockHelper.cs index 3db2712f3..018bb8f14 100644 --- a/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Impl/WinSockHelper.cs +++ b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Impl/WinSockHelper.cs @@ -150,6 +150,7 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Impl { BsdSocketOption.SoLinger, SocketOptionName.Linger }, { BsdSocketOption.SoOobInline, SocketOptionName.OutOfBandInline }, { BsdSocketOption.SoReusePort, SocketOptionName.ReuseAddress }, + { BsdSocketOption.SoNoSigpipe, SocketOptionName.DontLinger }, { BsdSocketOption.SoSndBuf, SocketOptionName.SendBuffer }, { BsdSocketOption.SoRcvBuf, SocketOptionName.ReceiveBuffer }, { BsdSocketOption.SoSndLoWat, SocketOptionName.SendLowWater }, From 6ab899f621a01f2077aadc2d172ff9f9a3312566 Mon Sep 17 00:00:00 2001 From: Evan Husted Date: Mon, 10 Feb 2025 16:44:18 -0600 Subject: [PATCH 7/7] misc: chore: [ci skip] Use explicit types & target-typed new --- src/ARMeilleure/Translation/Cache/JitCache.cs | 6 +++--- src/ARMeilleure/Translation/PTC/PtcProfiler.cs | 16 +++++++--------- src/Ryujinx.Cpu/LightningJit/Cache/JitCache.cs | 6 +++--- src/Ryujinx/Utilities/PlayReport/Specs.cs | 4 ++-- 4 files changed, 15 insertions(+), 17 deletions(-) diff --git a/src/ARMeilleure/Translation/Cache/JitCache.cs b/src/ARMeilleure/Translation/Cache/JitCache.cs index 0ede558b6..bc75e7d3f 100644 --- a/src/ARMeilleure/Translation/Cache/JitCache.cs +++ b/src/ARMeilleure/Translation/Cache/JitCache.cs @@ -52,7 +52,7 @@ namespace ARMeilleure.Translation.Cache return; } - var firstRegion = new ReservedRegion(allocator, CacheSize); + ReservedRegion firstRegion = new(allocator, CacheSize); _jitRegions.Add(firstRegion); _activeRegionIndex = 0; @@ -124,7 +124,7 @@ namespace ARMeilleure.Translation.Cache { Debug.Assert(_initialized); - foreach (var region in _jitRegions) + foreach (ReservedRegion region in _jitRegions) { if (pointer.ToInt64() < region.Pointer.ToInt64() || pointer.ToInt64() >= (region.Pointer + CacheSize).ToInt64()) @@ -224,7 +224,7 @@ namespace ARMeilleure.Translation.Cache { lock (_lock) { - foreach (var region in _jitRegions) + foreach (ReservedRegion _ in _jitRegions) { int index = _cacheEntries.BinarySearch(new CacheEntry(offset, 0, default)); diff --git a/src/ARMeilleure/Translation/PTC/PtcProfiler.cs b/src/ARMeilleure/Translation/PTC/PtcProfiler.cs index de0b78dbe..f3aaa58ff 100644 --- a/src/ARMeilleure/Translation/PTC/PtcProfiler.cs +++ b/src/ARMeilleure/Translation/PTC/PtcProfiler.cs @@ -144,17 +144,15 @@ namespace ARMeilleure.Translation.PTC public List GetBlacklistedFunctions() { - List funcs = new List(); + List funcs = []; - foreach (var profiledFunc in ProfiledFuncs) + foreach ((ulong ptr, FuncProfile funcProfile) in ProfiledFuncs) { - if (profiledFunc.Value.Blacklist) - { - if (!funcs.Contains(profiledFunc.Key)) - { - funcs.Add(profiledFunc.Key); - } - } + if (!funcProfile.Blacklist) + continue; + + if (!funcs.Contains(ptr)) + funcs.Add(ptr); } return funcs; diff --git a/src/Ryujinx.Cpu/LightningJit/Cache/JitCache.cs b/src/Ryujinx.Cpu/LightningJit/Cache/JitCache.cs index c994d424e..ccf8ad964 100644 --- a/src/Ryujinx.Cpu/LightningJit/Cache/JitCache.cs +++ b/src/Ryujinx.Cpu/LightningJit/Cache/JitCache.cs @@ -48,7 +48,7 @@ namespace Ryujinx.Cpu.LightningJit.Cache return; } - var firstRegion = new ReservedRegion(allocator, CacheSize); + ReservedRegion firstRegion = new(allocator, CacheSize); _jitRegions.Add(firstRegion); _activeRegionIndex = 0; @@ -104,7 +104,7 @@ namespace Ryujinx.Cpu.LightningJit.Cache { Debug.Assert(_initialized); - foreach (var region in _jitRegions) + foreach (ReservedRegion region in _jitRegions) { if (pointer.ToInt64() < region.Pointer.ToInt64() || pointer.ToInt64() >= (region.Pointer + CacheSize).ToInt64()) @@ -160,7 +160,7 @@ namespace Ryujinx.Cpu.LightningJit.Cache } int exhaustedRegion = _activeRegionIndex; - var newRegion = new ReservedRegion(_jitRegions[0].Allocator, CacheSize); + ReservedRegion newRegion = new(_jitRegions[0].Allocator, CacheSize); _jitRegions.Add(newRegion); _activeRegionIndex = _jitRegions.Count - 1; diff --git a/src/Ryujinx/Utilities/PlayReport/Specs.cs b/src/Ryujinx/Utilities/PlayReport/Specs.cs index 5c8126a99..f1b94de68 100644 --- a/src/Ryujinx/Utilities/PlayReport/Specs.cs +++ b/src/Ryujinx/Utilities/PlayReport/Specs.cs @@ -152,7 +152,7 @@ namespace Ryujinx.Ava.Utilities.PlayReport public override bool GetData(Horizon.Prepo.Types.PlayReport playReport, out object result) { List packedObjects = []; - foreach (var reportKey in ReportKeys) + foreach (string reportKey in ReportKeys) { if (!playReport.ReportData.AsDictionary().TryGetValue(reportKey, out MessagePackObject valuePackObject)) { @@ -176,7 +176,7 @@ namespace Ryujinx.Ava.Utilities.PlayReport public override bool GetData(Horizon.Prepo.Types.PlayReport playReport, out object result) { Dictionary packedObjects = []; - foreach (var reportKey in ReportKeys) + foreach (string reportKey in ReportKeys) { if (!playReport.ReportData.AsDictionary().TryGetValue(reportKey, out MessagePackObject valuePackObject)) continue;