misc: chore: Move Play Report analyzer into a dedicated namespace and remove the PlayReport name prefix on types

This commit is contained in:
Evan Husted 2025-02-05 19:27:44 -06:00
parent 5e5e180fea
commit c638a7daf8
3 changed files with 56 additions and 60 deletions

View File

@ -4,16 +4,12 @@ using MsgPack;
using Ryujinx.Ava.Utilities; using Ryujinx.Ava.Utilities;
using Ryujinx.Ava.Utilities.AppLibrary; using Ryujinx.Ava.Utilities.AppLibrary;
using Ryujinx.Ava.Utilities.Configuration; using Ryujinx.Ava.Utilities.Configuration;
using Ryujinx.Ava.Utilities.PlayReport;
using Ryujinx.Common; using Ryujinx.Common;
using Ryujinx.Common.Helper;
using Ryujinx.Common.Logging; using Ryujinx.Common.Logging;
using Ryujinx.HLE; using Ryujinx.HLE;
using Ryujinx.HLE.Loaders.Processes; using Ryujinx.HLE.Loaders.Processes;
using Ryujinx.Horizon; using Ryujinx.Horizon;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text; using System.Text;
namespace Ryujinx.Ava namespace Ryujinx.Ava
@ -130,8 +126,8 @@ namespace Ryujinx.Ava
if (!TitleIDs.CurrentApplication.Value.HasValue) return; if (!TitleIDs.CurrentApplication.Value.HasValue) return;
if (_discordPresencePlaying is null) return; if (_discordPresencePlaying is null) return;
PlayReportAnalyzer.FormattedValue formattedValue = Analyzer.FormattedValue formattedValue =
PlayReport.Analyzer.Format(TitleIDs.CurrentApplication.Value, _currentApp, playReport); PlayReports.Analyzer.Format(TitleIDs.CurrentApplication.Value, _currentApp, playReport);
if (!formattedValue.Handled) return; if (!formattedValue.Handled) return;

View File

@ -6,27 +6,27 @@ using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.Linq; using System.Linq;
namespace Ryujinx.Ava.Utilities namespace Ryujinx.Ava.Utilities.PlayReport
{ {
/// <summary> /// <summary>
/// The entrypoint for the Play Report analysis system. /// The entrypoint for the Play Report analysis system.
/// </summary> /// </summary>
public class PlayReportAnalyzer public class Analyzer
{ {
private readonly List<PlayReportGameSpec> _specs = []; private readonly List<GameSpec> _specs = [];
/// <summary> /// <summary>
/// Add an analysis spec matching a specific game by title ID, with the provided spec configuration. /// Add an analysis spec matching a specific game by title ID, with the provided spec configuration.
/// </summary> /// </summary>
/// <param name="titleId">The ID of the game to listen to Play Reports in.</param> /// <param name="titleId">The ID of the game to listen to Play Reports in.</param>
/// <param name="transform">The configuration function for the analysis spec.</param> /// <param name="transform">The configuration function for the analysis spec.</param>
/// <returns>The current <see cref="PlayReportAnalyzer"/>, for chaining convenience.</returns> /// <returns>The current <see cref="Analyzer"/>, for chaining convenience.</returns>
public PlayReportAnalyzer AddSpec(string titleId, Func<PlayReportGameSpec, PlayReportGameSpec> transform) public Analyzer AddSpec(string titleId, Func<GameSpec, GameSpec> transform)
{ {
Guard.Ensure(ulong.TryParse(titleId, NumberStyles.HexNumber, null, out _), Guard.Ensure(ulong.TryParse(titleId, NumberStyles.HexNumber, null, out _),
$"Cannot use a non-hexadecimal string as the Title ID for a {nameof(PlayReportGameSpec)}."); $"Cannot use a non-hexadecimal string as the Title ID for a {nameof(GameSpec)}.");
_specs.Add(transform(new PlayReportGameSpec { TitleIds = [titleId] })); _specs.Add(transform(new GameSpec { TitleIds = [titleId] }));
return this; return this;
} }
@ -35,13 +35,13 @@ namespace Ryujinx.Ava.Utilities
/// </summary> /// </summary>
/// <param name="titleId">The ID of the game to listen to Play Reports in.</param> /// <param name="titleId">The ID of the game to listen to Play Reports in.</param>
/// <param name="transform">The configuration function for the analysis spec.</param> /// <param name="transform">The configuration function for the analysis spec.</param>
/// <returns>The current <see cref="PlayReportAnalyzer"/>, for chaining convenience.</returns> /// <returns>The current <see cref="Analyzer"/>, for chaining convenience.</returns>
public PlayReportAnalyzer AddSpec(string titleId, Action<PlayReportGameSpec> transform) public Analyzer AddSpec(string titleId, Action<GameSpec> transform)
{ {
Guard.Ensure(ulong.TryParse(titleId, NumberStyles.HexNumber, null, out _), Guard.Ensure(ulong.TryParse(titleId, NumberStyles.HexNumber, null, out _),
$"Cannot use a non-hexadecimal string as the Title ID for a {nameof(PlayReportGameSpec)}."); $"Cannot use a non-hexadecimal string as the Title ID for a {nameof(GameSpec)}.");
_specs.Add(new PlayReportGameSpec { TitleIds = [titleId] }.Apply(transform)); _specs.Add(new GameSpec { TitleIds = [titleId] }.Apply(transform));
return this; return this;
} }
@ -50,15 +50,15 @@ namespace Ryujinx.Ava.Utilities
/// </summary> /// </summary>
/// <param name="titleIds">The IDs of the games to listen to Play Reports in.</param> /// <param name="titleIds">The IDs of the games to listen to Play Reports in.</param>
/// <param name="transform">The configuration function for the analysis spec.</param> /// <param name="transform">The configuration function for the analysis spec.</param>
/// <returns>The current <see cref="PlayReportAnalyzer"/>, for chaining convenience.</returns> /// <returns>The current <see cref="Analyzer"/>, for chaining convenience.</returns>
public PlayReportAnalyzer AddSpec(IEnumerable<string> titleIds, public Analyzer AddSpec(IEnumerable<string> titleIds,
Func<PlayReportGameSpec, PlayReportGameSpec> transform) Func<GameSpec, GameSpec> transform)
{ {
string[] tids = titleIds.ToArray(); string[] tids = titleIds.ToArray();
Guard.Ensure(tids.All(x => ulong.TryParse(x, NumberStyles.HexNumber, null, out _)), Guard.Ensure(tids.All(x => ulong.TryParse(x, NumberStyles.HexNumber, null, out _)),
$"Cannot use a non-hexadecimal string as the Title ID for a {nameof(PlayReportGameSpec)}."); $"Cannot use a non-hexadecimal string as the Title ID for a {nameof(GameSpec)}.");
_specs.Add(transform(new PlayReportGameSpec { TitleIds = [..tids] })); _specs.Add(transform(new GameSpec { TitleIds = [..tids] }));
return this; return this;
} }
@ -67,20 +67,20 @@ namespace Ryujinx.Ava.Utilities
/// </summary> /// </summary>
/// <param name="titleIds">The IDs of the games to listen to Play Reports in.</param> /// <param name="titleIds">The IDs of the games to listen to Play Reports in.</param>
/// <param name="transform">The configuration function for the analysis spec.</param> /// <param name="transform">The configuration function for the analysis spec.</param>
/// <returns>The current <see cref="PlayReportAnalyzer"/>, for chaining convenience.</returns> /// <returns>The current <see cref="Analyzer"/>, for chaining convenience.</returns>
public PlayReportAnalyzer AddSpec(IEnumerable<string> titleIds, Action<PlayReportGameSpec> transform) public Analyzer AddSpec(IEnumerable<string> titleIds, Action<GameSpec> transform)
{ {
string[] tids = titleIds.ToArray(); string[] tids = titleIds.ToArray();
Guard.Ensure(tids.All(x => ulong.TryParse(x, NumberStyles.HexNumber, null, out _)), Guard.Ensure(tids.All(x => ulong.TryParse(x, NumberStyles.HexNumber, null, out _)),
$"Cannot use a non-hexadecimal string as the Title ID for a {nameof(PlayReportGameSpec)}."); $"Cannot use a non-hexadecimal string as the Title ID for a {nameof(GameSpec)}.");
_specs.Add(new PlayReportGameSpec { TitleIds = [..tids] }.Apply(transform)); _specs.Add(new GameSpec { TitleIds = [..tids] }.Apply(transform));
return this; return this;
} }
/// <summary> /// <summary>
/// Runs the configured <see cref="PlayReportGameSpec.FormatterSpec"/> for the specified game title ID. /// Runs the configured <see cref="GameSpec.FormatterSpec"/> for the specified game title ID.
/// </summary> /// </summary>
/// <param name="runningGameId">The game currently running.</param> /// <param name="runningGameId">The game currently running.</param>
/// <param name="appMeta">The Application metadata information, including localized game name and play time information.</param> /// <param name="appMeta">The Application metadata information, including localized game name and play time information.</param>
@ -95,15 +95,15 @@ namespace Ryujinx.Ava.Utilities
if (!playReport.IsDictionary) if (!playReport.IsDictionary)
return FormattedValue.Unhandled; return FormattedValue.Unhandled;
if (!_specs.TryGetFirst(s => runningGameId.EqualsAnyIgnoreCase(s.TitleIds), out PlayReportGameSpec spec)) if (!_specs.TryGetFirst(s => runningGameId.EqualsAnyIgnoreCase(s.TitleIds), out GameSpec spec))
return FormattedValue.Unhandled; return FormattedValue.Unhandled;
foreach (PlayReportGameSpec.FormatterSpec formatSpec in spec.SimpleValueFormatters.OrderBy(x => x.Priority)) foreach (GameSpec.FormatterSpec formatSpec in spec.SimpleValueFormatters.OrderBy(x => x.Priority))
{ {
if (!playReport.AsDictionary().TryGetValue(formatSpec.ReportKey, out MessagePackObject valuePackObject)) if (!playReport.AsDictionary().TryGetValue(formatSpec.ReportKey, out MessagePackObject valuePackObject))
continue; continue;
return formatSpec.ValueFormatter(new PlayReportValue return formatSpec.ValueFormatter(new Value
{ {
Application = appMeta, PackedValue = valuePackObject Application = appMeta, PackedValue = valuePackObject
}); });
@ -123,7 +123,7 @@ namespace Ryujinx.Ava.Utilities
public bool Handled { get; private init; } public bool Handled { get; private init; }
/// <summary> /// <summary>
/// Did the handler request the caller of the <see cref="PlayReportAnalyzer"/> to reset the existing value? /// Did the handler request the caller of the <see cref="Analyzer"/> to reset the existing value?
/// </summary> /// </summary>
public bool Reset { get; private init; } public bool Reset { get; private init; }
@ -151,7 +151,7 @@ namespace Ryujinx.Ava.Utilities
public static FormattedValue Unhandled => default; public static FormattedValue Unhandled => default;
/// <summary> /// <summary>
/// Return this to suggest the caller reset the value it's using the <see cref="PlayReportAnalyzer"/> for. /// Return this to suggest the caller reset the value it's using the <see cref="Analyzer"/> for.
/// </summary> /// </summary>
public static FormattedValue ForceReset => new() { Handled = true, Reset = true }; public static FormattedValue ForceReset => new() { Handled = true, Reset = true };
@ -172,21 +172,21 @@ namespace Ryujinx.Ava.Utilities
/// <summary> /// <summary>
/// A mapping of title IDs to value formatter specs. /// A mapping of title IDs to value formatter specs.
/// ///
/// <remarks>Generally speaking, use the <see cref="PlayReportAnalyzer"/>.AddSpec(...) methods instead of creating this class yourself.</remarks> /// <remarks>Generally speaking, use the <see cref="Analyzer"/>.AddSpec(...) methods instead of creating this class yourself.</remarks>
/// </summary> /// </summary>
public class PlayReportGameSpec public class GameSpec
{ {
public required string[] TitleIds { get; init; } public required string[] TitleIds { get; init; }
public List<FormatterSpec> SimpleValueFormatters { get; } = []; public List<FormatterSpec> SimpleValueFormatters { get; } = [];
/// <summary> /// <summary>
/// Add a value formatter to the current <see cref="PlayReportGameSpec"/> /// Add a value formatter to the current <see cref="GameSpec"/>
/// matching a specific key that could exist in a Play Report for the previously specified title IDs. /// matching a specific key that could exist in a Play Report for the previously specified title IDs.
/// </summary> /// </summary>
/// <param name="reportKey">The key name to match.</param> /// <param name="reportKey">The key name to match.</param>
/// <param name="valueFormatter">The function which can return a potential formatted value.</param> /// <param name="valueFormatter">The function which can return a potential formatted value.</param>
/// <returns>The current <see cref="PlayReportGameSpec"/>, for chaining convenience.</returns> /// <returns>The current <see cref="GameSpec"/>, for chaining convenience.</returns>
public PlayReportGameSpec AddValueFormatter(string reportKey, PlayReportValueFormatter valueFormatter) public GameSpec AddValueFormatter(string reportKey, PlayReportValueFormatter valueFormatter)
{ {
SimpleValueFormatters.Add(new FormatterSpec SimpleValueFormatters.Add(new FormatterSpec
{ {
@ -196,14 +196,14 @@ namespace Ryujinx.Ava.Utilities
} }
/// <summary> /// <summary>
/// Add a value formatter at a specific priority to the current <see cref="PlayReportGameSpec"/> /// Add a value formatter at a specific priority to the current <see cref="GameSpec"/>
/// matching a specific key that could exist in a Play Report for the previously specified title IDs. /// matching a specific key that could exist in a Play Report for the previously specified title IDs.
/// </summary> /// </summary>
/// <param name="priority">The resolution priority of this value formatter. Higher resolves sooner.</param> /// <param name="priority">The resolution priority of this value formatter. Higher resolves sooner.</param>
/// <param name="reportKey">The key name to match.</param> /// <param name="reportKey">The key name to match.</param>
/// <param name="valueFormatter">The function which can return a potential formatted value.</param> /// <param name="valueFormatter">The function which can return a potential formatted value.</param>
/// <returns>The current <see cref="PlayReportGameSpec"/>, for chaining convenience.</returns> /// <returns>The current <see cref="GameSpec"/>, for chaining convenience.</returns>
public PlayReportGameSpec AddValueFormatter(int priority, string reportKey, public GameSpec AddValueFormatter(int priority, string reportKey,
PlayReportValueFormatter valueFormatter) PlayReportValueFormatter valueFormatter)
{ {
SimpleValueFormatters.Add(new FormatterSpec SimpleValueFormatters.Add(new FormatterSpec
@ -229,7 +229,7 @@ namespace Ryujinx.Ava.Utilities
/// containing the currently running application's <see cref="ApplicationMetadata"/>, /// containing the currently running application's <see cref="ApplicationMetadata"/>,
/// and the matched <see cref="MessagePackObject"/> from the Play Report. /// and the matched <see cref="MessagePackObject"/> from the Play Report.
/// </summary> /// </summary>
public class PlayReportValue public class Value
{ {
/// <summary> /// <summary>
/// The currently running application's <see cref="ApplicationMetadata"/>. /// The currently running application's <see cref="ApplicationMetadata"/>.
@ -276,7 +276,7 @@ namespace Ryujinx.Ava.Utilities
/// <br/> /// <br/>
/// a signal that nothing was available to handle it, /// a signal that nothing was available to handle it,
/// <br/> /// <br/>
/// OR a signal to reset the value that the caller is using the <see cref="PlayReportAnalyzer"/> for. /// OR a signal to reset the value that the caller is using the <see cref="Analyzer"/> for.
/// </summary> /// </summary>
public delegate PlayReportAnalyzer.FormattedValue PlayReportValueFormatter(PlayReportValue value); public delegate Analyzer.FormattedValue PlayReportValueFormatter(Value value);
} }

View File

@ -1,16 +1,16 @@
using PlayReportFormattedValue = Ryujinx.Ava.Utilities.PlayReportAnalyzer.FormattedValue; using static Ryujinx.Ava.Utilities.PlayReport.Analyzer;
namespace Ryujinx.Ava.Utilities namespace Ryujinx.Ava.Utilities.PlayReport
{ {
public static class PlayReport public static class PlayReports
{ {
public static PlayReportAnalyzer Analyzer { get; } = new PlayReportAnalyzer() public static Analyzer Analyzer { get; } = new Analyzer()
.AddSpec( .AddSpec(
"01007ef00011e000", "01007ef00011e000",
spec => spec spec => spec
.AddValueFormatter("IsHardMode", BreathOfTheWild_MasterMode) .AddValueFormatter("IsHardMode", BreathOfTheWild_MasterMode)
// reset to normal status when switching between normal & master mode in title screen // reset to normal status when switching between normal & master mode in title screen
.AddValueFormatter("AoCVer", PlayReportFormattedValue.AlwaysResets) .AddValueFormatter("AoCVer", FormattedValue.AlwaysResets)
) )
.AddSpec( .AddSpec(
"0100f2c0115b6000", "0100f2c0115b6000",
@ -40,10 +40,10 @@ namespace Ryujinx.Ava.Utilities
.AddValueFormatter("team_circle", PokemonSVUnionCircle) .AddValueFormatter("team_circle", PokemonSVUnionCircle)
); );
private static PlayReportFormattedValue BreathOfTheWild_MasterMode(PlayReportValue value) private static FormattedValue BreathOfTheWild_MasterMode(Value value)
=> value.BoxedValue is 1 ? "Playing Master Mode" : PlayReportFormattedValue.ForceReset; => value.BoxedValue is 1 ? "Playing Master Mode" : FormattedValue.ForceReset;
private static PlayReportFormattedValue TearsOfTheKingdom_CurrentField(PlayReportValue value) => private static FormattedValue TearsOfTheKingdom_CurrentField(Value value) =>
value.DoubleValue switch value.DoubleValue switch
{ {
> 800d => "Exploring the Sky Islands", > 800d => "Exploring the Sky Islands",
@ -51,16 +51,16 @@ namespace Ryujinx.Ava.Utilities
_ => "Roaming Hyrule" _ => "Roaming Hyrule"
}; };
private static PlayReportFormattedValue SuperMarioOdyssey_AssistMode(PlayReportValue value) private static FormattedValue SuperMarioOdyssey_AssistMode(Value value)
=> value.BoxedValue is 1 ? "Playing in Assist Mode" : "Playing in Regular Mode"; => value.BoxedValue is 1 ? "Playing in Assist Mode" : "Playing in Regular Mode";
private static PlayReportFormattedValue SuperMarioOdysseyChina_AssistMode(PlayReportValue value) private static FormattedValue SuperMarioOdysseyChina_AssistMode(Value value)
=> value.BoxedValue is 1 ? "Playing in 帮助模式" : "Playing in 普通模式"; => value.BoxedValue is 1 ? "Playing in 帮助模式" : "Playing in 普通模式";
private static PlayReportFormattedValue SuperMario3DWorldOrBowsersFury(PlayReportValue value) private static FormattedValue SuperMario3DWorldOrBowsersFury(Value value)
=> value.BoxedValue is 0 ? "Playing Super Mario 3D World" : "Playing Bowser's Fury"; => value.BoxedValue is 0 ? "Playing Super Mario 3D World" : "Playing Bowser's Fury";
private static PlayReportFormattedValue MarioKart8Deluxe_Mode(PlayReportValue value) private static FormattedValue MarioKart8Deluxe_Mode(Value value)
=> value.StringValue switch => value.StringValue switch
{ {
// Single Player // Single Player
@ -85,13 +85,13 @@ namespace Ryujinx.Ava.Utilities
"Battle" => "Battle Mode", "Battle" => "Battle Mode",
"RaceStart" => "Selecting a Course", "RaceStart" => "Selecting a Course",
"Race" => "Racing", "Race" => "Racing",
_ => PlayReportFormattedValue.ForceReset _ => FormattedValue.ForceReset
}; };
private static PlayReportFormattedValue PokemonSVUnionCircle(PlayReportValue value) private static FormattedValue PokemonSVUnionCircle(Value value)
=> value.BoxedValue is 0 ? "Playing Alone" : "Playing in a group"; => value.BoxedValue is 0 ? "Playing Alone" : "Playing in a group";
private static PlayReportFormattedValue PokemonSVArea(PlayReportValue value) private static FormattedValue PokemonSVArea(Value value)
=> value.StringValue switch => value.StringValue switch
{ {
// Base Game Locations // Base Game Locations
@ -122,7 +122,7 @@ namespace Ryujinx.Ava.Utilities
"a_w26" => "East Paldean Sea", "a_w26" => "East Paldean Sea",
"a_w27" => "Nouth Paldean Sea", "a_w27" => "Nouth Paldean Sea",
//TODO DLC Locations //TODO DLC Locations
_ => PlayReportFormattedValue.ForceReset _ => FormattedValue.ForceReset
}; };
} }
} }