diff --git a/src/Ryujinx.Horizon/HorizonStatic.cs b/src/Ryujinx.Horizon/HorizonStatic.cs index f08ddb3c0..562aabcc0 100644 --- a/src/Ryujinx.Horizon/HorizonStatic.cs +++ b/src/Ryujinx.Horizon/HorizonStatic.cs @@ -2,14 +2,21 @@ using MsgPack; using Ryujinx.Horizon.Common; using Ryujinx.Memory; using System; +using System.Threading; namespace Ryujinx.Horizon { public static class HorizonStatic { - internal static void HandlePlayReport(MessagePackObject report) => PlayReportPrinted?.Invoke(report); + internal static void HandlePlayReport(MessagePackObject report) => + new Thread(() => PlayReport?.Invoke(report)) + { + Name = "HLE.PlayReportEvent", + IsBackground = true, + Priority = ThreadPriority.AboveNormal + }.Start(); - public static event Action PlayReportPrinted; + public static event Action PlayReport; [ThreadStatic] private static HorizonOptions _options; diff --git a/src/Ryujinx.Horizon/Prepo/Ipc/PrepoService.cs b/src/Ryujinx.Horizon/Prepo/Ipc/PrepoService.cs index ab972d85e..2f8657e0b 100644 --- a/src/Ryujinx.Horizon/Prepo/Ipc/PrepoService.cs +++ b/src/Ryujinx.Horizon/Prepo/Ipc/PrepoService.cs @@ -1,3 +1,4 @@ +using Gommon; using MsgPack; using MsgPack.Serialization; using Ryujinx.Common.Logging; @@ -11,6 +12,7 @@ using Ryujinx.Horizon.Sdk.Sf; using Ryujinx.Horizon.Sdk.Sf.Hipc; using System; using System.Text; +using System.Threading; using ApplicationId = Ryujinx.Horizon.Sdk.Ncm.ApplicationId; namespace Ryujinx.Horizon.Prepo.Ipc diff --git a/src/Ryujinx/DiscordIntegrationModule.cs b/src/Ryujinx/DiscordIntegrationModule.cs index f55eb8d66..4ea413ea1 100644 --- a/src/Ryujinx/DiscordIntegrationModule.cs +++ b/src/Ryujinx/DiscordIntegrationModule.cs @@ -56,7 +56,7 @@ namespace Ryujinx.Ava ConfigurationState.Instance.EnableDiscordIntegration.Event += Update; TitleIDs.CurrentApplication.Event += (_, e) => Use(e.NewValue); - HorizonStatic.PlayReportPrinted += HandlePlayReport; + HorizonStatic.PlayReport += HandlePlayReport; } private static void Update(object sender, ReactiveEventArgs evnt) diff --git a/src/Ryujinx/Utilities/PlayReport.cs b/src/Ryujinx/Utilities/PlayReport.cs index f8ecef327..9049dec1b 100644 --- a/src/Ryujinx/Utilities/PlayReport.cs +++ b/src/Ryujinx/Utilities/PlayReport.cs @@ -85,128 +85,4 @@ namespace Ryujinx.Ava.Utilities _ => PlayReportFormattedValue.ForceReset }; } - - #region Analyzer implementation - - public class PlayReportAnalyzer - { - private readonly List _specs = []; - - public PlayReportAnalyzer AddSpec(string titleId, Func transform) - { - _specs.Add(transform(new PlayReportGameSpec { TitleIds = [titleId] })); - return this; - } - - public PlayReportAnalyzer AddSpec(string titleId, Action transform) - { - _specs.Add(new PlayReportGameSpec { TitleIds = [titleId] }.Apply(transform)); - return this; - } - - public PlayReportAnalyzer AddSpec(IEnumerable titleIds, Func transform) - { - _specs.Add(transform(new PlayReportGameSpec { TitleIds = [..titleIds] })); - return this; - } - - public PlayReportAnalyzer AddSpec(IEnumerable titleIds, Action transform) - { - _specs.Add(new PlayReportGameSpec { TitleIds = [..titleIds] }.Apply(transform)); - return this; - } - - public PlayReportFormattedValue Run(string runningGameId, ApplicationMetadata appMeta, MessagePackObject playReport) - { - if (!playReport.IsDictionary) - return PlayReportFormattedValue.Unhandled; - - if (!_specs.TryGetFirst(s => runningGameId.EqualsAnyIgnoreCase(s.TitleIds), out PlayReportGameSpec spec)) - return PlayReportFormattedValue.Unhandled; - - foreach (PlayReportValueFormatterSpec formatSpec in spec.Analyses.OrderBy(x => x.Priority)) - { - if (!playReport.AsDictionary().TryGetValue(formatSpec.ReportKey, out MessagePackObject valuePackObject)) - continue; - - PlayReportValue value = new() - { - Application = appMeta, - PackedValue = valuePackObject - }; - - return formatSpec.ValueFormatter(ref value); - } - - return PlayReportFormattedValue.Unhandled; - } - - } - - public class PlayReportGameSpec - { - public required string[] TitleIds { get; init; } - public List Analyses { get; } = []; - - public PlayReportGameSpec AddValueFormatter(string reportKey, PlayReportValueFormatter valueFormatter) - { - Analyses.Add(new PlayReportValueFormatterSpec - { - Priority = Analyses.Count, - ReportKey = reportKey, - ValueFormatter = valueFormatter - }); - return this; - } - - public PlayReportGameSpec AddValueFormatter(int priority, string reportKey, PlayReportValueFormatter valueFormatter) - { - Analyses.Add(new PlayReportValueFormatterSpec - { - Priority = priority, - ReportKey = reportKey, - ValueFormatter = valueFormatter - }); - return this; - } - } - - public readonly struct PlayReportValue - { - public ApplicationMetadata Application { get; init; } - - public MessagePackObject PackedValue { get; init; } - - public object BoxedValue => PackedValue.ToObject(); - } - - public struct PlayReportFormattedValue - { - public bool Handled { get; private init; } - - public bool Reset { get; private init; } - - public string FormattedString { get; private init; } - - public static implicit operator PlayReportFormattedValue(string formattedValue) - => new() { Handled = true, FormattedString = formattedValue }; - - public static PlayReportFormattedValue Unhandled => default; - public static PlayReportFormattedValue ForceReset => new() { Handled = true, Reset = true }; - - public static PlayReportValueFormatter AlwaysResets = AlwaysResetsImpl; - - private static PlayReportFormattedValue AlwaysResetsImpl(ref PlayReportValue _) => ForceReset; - } - - public struct PlayReportValueFormatterSpec - { - public required int Priority { get; init; } - public required string ReportKey { get; init; } - public PlayReportValueFormatter ValueFormatter { get; init; } - } - - public delegate PlayReportFormattedValue PlayReportValueFormatter(ref PlayReportValue value); - - #endregion } diff --git a/src/Ryujinx/Utilities/PlayReportAnalyzer.cs b/src/Ryujinx/Utilities/PlayReportAnalyzer.cs new file mode 100644 index 000000000..3760204a7 --- /dev/null +++ b/src/Ryujinx/Utilities/PlayReportAnalyzer.cs @@ -0,0 +1,129 @@ +using Gommon; +using MsgPack; +using Ryujinx.Ava.Utilities.AppLibrary; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Ryujinx.Ava.Utilities +{ + public class PlayReportAnalyzer + { + private readonly List _specs = []; + + public PlayReportAnalyzer AddSpec(string titleId, Func transform) + { + _specs.Add(transform(new PlayReportGameSpec { TitleIds = [titleId] })); + return this; + } + + public PlayReportAnalyzer AddSpec(string titleId, Action transform) + { + _specs.Add(new PlayReportGameSpec { TitleIds = [titleId] }.Apply(transform)); + return this; + } + + public PlayReportAnalyzer AddSpec(IEnumerable titleIds, Func transform) + { + _specs.Add(transform(new PlayReportGameSpec { TitleIds = [..titleIds] })); + return this; + } + + public PlayReportAnalyzer AddSpec(IEnumerable titleIds, Action transform) + { + _specs.Add(new PlayReportGameSpec { TitleIds = [..titleIds] }.Apply(transform)); + return this; + } + + public PlayReportFormattedValue Run(string runningGameId, ApplicationMetadata appMeta, MessagePackObject playReport) + { + if (!playReport.IsDictionary) + return PlayReportFormattedValue.Unhandled; + + if (!_specs.TryGetFirst(s => runningGameId.EqualsAnyIgnoreCase(s.TitleIds), out PlayReportGameSpec spec)) + return PlayReportFormattedValue.Unhandled; + + foreach (PlayReportValueFormatterSpec formatSpec in spec.Analyses.OrderBy(x => x.Priority)) + { + if (!playReport.AsDictionary().TryGetValue(formatSpec.ReportKey, out MessagePackObject valuePackObject)) + continue; + + PlayReportValue value = new() + { + Application = appMeta, + PackedValue = valuePackObject + }; + + return formatSpec.ValueFormatter(ref value); + } + + return PlayReportFormattedValue.Unhandled; + } + + } + + public class PlayReportGameSpec + { + public required string[] TitleIds { get; init; } + public List Analyses { get; } = []; + + public PlayReportGameSpec AddValueFormatter(string reportKey, PlayReportValueFormatter valueFormatter) + { + Analyses.Add(new PlayReportValueFormatterSpec + { + Priority = Analyses.Count, + ReportKey = reportKey, + ValueFormatter = valueFormatter + }); + return this; + } + + public PlayReportGameSpec AddValueFormatter(int priority, string reportKey, PlayReportValueFormatter valueFormatter) + { + Analyses.Add(new PlayReportValueFormatterSpec + { + Priority = priority, + ReportKey = reportKey, + ValueFormatter = valueFormatter + }); + return this; + } + } + + public readonly struct PlayReportValue + { + public ApplicationMetadata Application { get; init; } + + public MessagePackObject PackedValue { get; init; } + + public object BoxedValue => PackedValue.ToObject(); + } + + public struct PlayReportFormattedValue + { + public bool Handled { get; private init; } + + public bool Reset { get; private init; } + + public string FormattedString { get; private init; } + + public static implicit operator PlayReportFormattedValue(string formattedValue) + => new() { Handled = true, FormattedString = formattedValue }; + + public static PlayReportFormattedValue Unhandled => default; + public static PlayReportFormattedValue ForceReset => new() { Handled = true, Reset = true }; + + public static PlayReportValueFormatter AlwaysResets = AlwaysResetsImpl; + + private static PlayReportFormattedValue AlwaysResetsImpl(ref PlayReportValue _) => ForceReset; + } + + public struct PlayReportValueFormatterSpec + { + public required int Priority { get; init; } + public required string ReportKey { get; init; } + public PlayReportValueFormatter ValueFormatter { get; init; } + } + + public delegate PlayReportFormattedValue PlayReportValueFormatter(ref PlayReportValue value); +}