mirror of
https://github.com/GreemDev/Ryujinx.git
synced 2025-02-22 21:30:14 +01:00
PlayReport: Add Sparse Multi Value formatters
This commit is contained in:
parent
aa8ba8b503
commit
2c8edaf89e
@ -126,14 +126,16 @@ namespace Ryujinx.Ava
|
||||
if (!TitleIDs.CurrentApplication.Value.HasValue) return;
|
||||
if (_discordPresencePlaying is null) return;
|
||||
|
||||
Analyzer.FormattedValue formattedValue =
|
||||
FormattedValue formattedValue =
|
||||
PlayReports.Analyzer.Format(TitleIDs.CurrentApplication.Value, _currentApp, playReport);
|
||||
|
||||
if (!formattedValue.Handled) return;
|
||||
|
||||
_discordPresencePlaying.Details = formattedValue.Reset
|
||||
? $"Playing {_currentApp.Title}"
|
||||
: formattedValue.FormattedString;
|
||||
_discordPresencePlaying.Details = TruncateToByteLength(
|
||||
formattedValue.Reset
|
||||
? $"Playing {_currentApp.Title}"
|
||||
: formattedValue.FormattedString
|
||||
);
|
||||
|
||||
if (_discordClient.CurrentPresence.Details.Equals(_discordPresencePlaying.Details))
|
||||
return; //don't trigger an update if the set presence Details are identical to current
|
||||
|
@ -78,7 +78,7 @@ namespace Ryujinx.Ava.Utilities.PlayReport
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Runs the configured <see cref="GameSpec.FormatterSpec"/> for the specified game title ID.
|
||||
/// </summary>
|
||||
@ -98,261 +98,48 @@ namespace Ryujinx.Ava.Utilities.PlayReport
|
||||
if (!_specs.TryGetFirst(s => runningGameId.EqualsAnyIgnoreCase(s.TitleIds), out GameSpec spec))
|
||||
return FormattedValue.Unhandled;
|
||||
|
||||
foreach (GameSpec.FormatterSpec formatSpec in spec.SimpleValueFormatters.OrderBy(x => x.Priority))
|
||||
foreach (FormatterSpec formatSpec in spec.SimpleValueFormatters.OrderBy(x => x.Priority))
|
||||
{
|
||||
if (!playReport.AsDictionary().TryGetValue(formatSpec.ReportKey, out MessagePackObject valuePackObject))
|
||||
continue;
|
||||
|
||||
return formatSpec.ValueFormatter(new Value
|
||||
{
|
||||
Application = appMeta, PackedValue = valuePackObject
|
||||
});
|
||||
return formatSpec.Formatter(new Value { Application = appMeta, PackedValue = valuePackObject });
|
||||
}
|
||||
|
||||
foreach (GameSpec.MultiFormatterSpec formatSpec in spec.MultiValueFormatters.OrderBy(x => x.Priority))
|
||||
|
||||
foreach (MultiFormatterSpec formatSpec in spec.MultiValueFormatters.OrderBy(x => x.Priority))
|
||||
{
|
||||
List<MessagePackObject> packedObjects = [];
|
||||
foreach (var reportKey in formatSpec.ReportKeys)
|
||||
{
|
||||
if (!playReport.AsDictionary().TryGetValue(reportKey, out MessagePackObject valuePackObject))
|
||||
continue;
|
||||
|
||||
|
||||
packedObjects.Add(valuePackObject);
|
||||
}
|
||||
|
||||
|
||||
if (packedObjects.Count != formatSpec.ReportKeys.Length)
|
||||
return FormattedValue.Unhandled;
|
||||
|
||||
return formatSpec.ValueFormatter(packedObjects
|
||||
|
||||
return formatSpec.Formatter(packedObjects
|
||||
.Select(packObject => new Value { Application = appMeta, PackedValue = packObject })
|
||||
.ToArray());
|
||||
}
|
||||
|
||||
foreach (SparseMultiFormatterSpec formatSpec in spec.SparseMultiValueFormatters.OrderBy(x => x.Priority))
|
||||
{
|
||||
Dictionary<string, Value> packedObjects = [];
|
||||
foreach (var reportKey in formatSpec.ReportKeys)
|
||||
{
|
||||
if (!playReport.AsDictionary().TryGetValue(reportKey, out MessagePackObject valuePackObject))
|
||||
continue;
|
||||
|
||||
packedObjects.Add(reportKey, new Value { Application = appMeta, PackedValue = valuePackObject });
|
||||
}
|
||||
|
||||
return formatSpec.Formatter(packedObjects);
|
||||
}
|
||||
|
||||
return FormattedValue.Unhandled;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A potential formatted value returned by a <see cref="ValueFormatter"/>.
|
||||
/// </summary>
|
||||
public readonly struct FormattedValue
|
||||
{
|
||||
/// <summary>
|
||||
/// Was any handler able to match anything in the Play Report?
|
||||
/// </summary>
|
||||
public bool Handled { get; private init; }
|
||||
|
||||
/// <summary>
|
||||
/// Did the handler request the caller of the <see cref="Analyzer"/> to reset the existing value?
|
||||
/// </summary>
|
||||
public bool Reset { get; private init; }
|
||||
|
||||
/// <summary>
|
||||
/// The formatted value, only present if <see cref="Handled"/> is true, and <see cref="Reset"/> is false.
|
||||
/// </summary>
|
||||
public string FormattedString { get; private init; }
|
||||
|
||||
/// <summary>
|
||||
/// The intended path of execution for having a string to return: simply return the string.
|
||||
/// This implicit conversion will make the struct for you.<br/><br/>
|
||||
///
|
||||
/// If the input is null, <see cref="Unhandled"/> is returned.
|
||||
/// </summary>
|
||||
/// <param name="formattedValue">The formatted string value.</param>
|
||||
/// <returns>The automatically constructed <see cref="FormattedValue"/> struct.</returns>
|
||||
public static implicit operator FormattedValue(string formattedValue)
|
||||
=> formattedValue is not null
|
||||
? new FormattedValue { Handled = true, FormattedString = formattedValue }
|
||||
: Unhandled;
|
||||
|
||||
/// <summary>
|
||||
/// Return this to tell the caller there is no value to return.
|
||||
/// </summary>
|
||||
public static FormattedValue Unhandled => default;
|
||||
|
||||
/// <summary>
|
||||
/// Return this to suggest the caller reset the value it's using the <see cref="Analyzer"/> for.
|
||||
/// </summary>
|
||||
public static FormattedValue ForceReset => new() { Handled = true, Reset = true };
|
||||
|
||||
/// <summary>
|
||||
/// A delegate singleton you can use to always return <see cref="ForceReset"/> in a <see cref="ValueFormatter"/>.
|
||||
/// </summary>
|
||||
public static readonly ValueFormatter AlwaysResets = _ => ForceReset;
|
||||
|
||||
/// <summary>
|
||||
/// A delegate factory you can use to always return the specified
|
||||
/// <paramref name="formattedValue"/> in a <see cref="ValueFormatter"/>.
|
||||
/// </summary>
|
||||
/// <param name="formattedValue">The string to always return for this delegate instance.</param>
|
||||
public static ValueFormatter AlwaysReturns(string formattedValue) => _ => formattedValue;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A mapping of title IDs to value formatter specs.
|
||||
///
|
||||
/// <remarks>Generally speaking, use the <see cref="Analyzer"/>.AddSpec(...) methods instead of creating this class yourself.</remarks>
|
||||
/// </summary>
|
||||
public class GameSpec
|
||||
{
|
||||
public required string[] TitleIds { get; init; }
|
||||
public List<FormatterSpec> SimpleValueFormatters { get; } = [];
|
||||
public List<MultiFormatterSpec> MultiValueFormatters { get; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
/// <param name="reportKey">The key name to match.</param>
|
||||
/// <param name="valueFormatter">The function which can return a potential formatted value.</param>
|
||||
/// <returns>The current <see cref="GameSpec"/>, for chaining convenience.</returns>
|
||||
public GameSpec AddValueFormatter(string reportKey, ValueFormatter valueFormatter)
|
||||
{
|
||||
SimpleValueFormatters.Add(new FormatterSpec
|
||||
{
|
||||
Priority = SimpleValueFormatters.Count, ReportKey = reportKey, ValueFormatter = valueFormatter
|
||||
});
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
/// <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="valueFormatter">The function which can return a potential formatted value.</param>
|
||||
/// <returns>The current <see cref="GameSpec"/>, for chaining convenience.</returns>
|
||||
public GameSpec AddValueFormatter(int priority, string reportKey,
|
||||
ValueFormatter valueFormatter)
|
||||
{
|
||||
SimpleValueFormatters.Add(new FormatterSpec
|
||||
{
|
||||
Priority = priority, ReportKey = reportKey, ValueFormatter = valueFormatter
|
||||
});
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add a multi-value formatter to the current <see cref="GameSpec"/>
|
||||
/// matching a specific set of keys that could exist in a Play Report for the previously specified title IDs.
|
||||
/// </summary>
|
||||
/// <param name="reportKeys">The key names to match.</param>
|
||||
/// <param name="valueFormatter">The function which can format the values.</param>
|
||||
/// <returns>The current <see cref="GameSpec"/>, for chaining convenience.</returns>
|
||||
public GameSpec AddMultiValueFormatter(string[] reportKeys, MultiValueFormatter valueFormatter)
|
||||
{
|
||||
MultiValueFormatters.Add(new MultiFormatterSpec
|
||||
{
|
||||
Priority = SimpleValueFormatters.Count, ReportKeys = reportKeys, ValueFormatter = valueFormatter
|
||||
});
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add a multi-value formatter at a specific priority to the current <see cref="GameSpec"/>
|
||||
/// matching a specific set of keys that could exist in a Play Report for the previously specified title IDs.
|
||||
/// </summary>
|
||||
/// <param name="priority">The resolution priority of this value formatter. Higher resolves sooner.</param>
|
||||
/// <param name="reportKeys">The key names to match.</param>
|
||||
/// <param name="valueFormatter">The function which can format the values.</param>
|
||||
/// <returns>The current <see cref="GameSpec"/>, for chaining convenience.</returns>
|
||||
public GameSpec AddMultiValueFormatter(int priority, string[] reportKeys,
|
||||
MultiValueFormatter valueFormatter)
|
||||
{
|
||||
MultiValueFormatters.Add(new MultiFormatterSpec
|
||||
{
|
||||
Priority = priority, ReportKeys = reportKeys, ValueFormatter = valueFormatter
|
||||
});
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A struct containing the data for a mapping of a key in a Play Report to a formatter for its potential value.
|
||||
/// </summary>
|
||||
public struct FormatterSpec
|
||||
{
|
||||
public required int Priority { get; init; }
|
||||
public required string ReportKey { get; init; }
|
||||
public ValueFormatter ValueFormatter { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A struct containing the data for a mapping of an arbitrary key set in a Play Report to a formatter for their potential values.
|
||||
/// </summary>
|
||||
public struct MultiFormatterSpec
|
||||
{
|
||||
public required int Priority { get; init; }
|
||||
public required string[] ReportKeys { get; init; }
|
||||
public MultiValueFormatter ValueFormatter { get; init; }
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The input data to a <see cref="ValueFormatter"/>,
|
||||
/// containing the currently running application's <see cref="ApplicationMetadata"/>,
|
||||
/// and the matched <see cref="MessagePackObject"/> from the Play Report.
|
||||
/// </summary>
|
||||
public class Value
|
||||
{
|
||||
/// <summary>
|
||||
/// The currently running application's <see cref="ApplicationMetadata"/>.
|
||||
/// </summary>
|
||||
public ApplicationMetadata Application { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// The matched value from the Play Report.
|
||||
/// </summary>
|
||||
public MessagePackObject PackedValue { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Access the <see cref="PackedValue"/> as its underlying .NET type.<br/>
|
||||
///
|
||||
/// Does not seem to work well with comparing numeric types,
|
||||
/// so use XValue properties for that.
|
||||
/// </summary>
|
||||
public object BoxedValue => PackedValue.ToObject();
|
||||
|
||||
#region AsX accessors
|
||||
|
||||
public bool BooleanValue => PackedValue.AsBoolean();
|
||||
public byte ByteValye => PackedValue.AsByte();
|
||||
public sbyte SByteValye => PackedValue.AsSByte();
|
||||
public short ShortValye => PackedValue.AsInt16();
|
||||
public ushort UShortValye => PackedValue.AsUInt16();
|
||||
public int IntValye => PackedValue.AsInt32();
|
||||
public uint UIntValye => PackedValue.AsUInt32();
|
||||
public long LongValye => PackedValue.AsInt64();
|
||||
public ulong ULongValye => PackedValue.AsUInt64();
|
||||
public float FloatValue => PackedValue.AsSingle();
|
||||
public double DoubleValue => PackedValue.AsDouble();
|
||||
public string StringValue => PackedValue.AsString();
|
||||
public Span<byte> BinaryValue => PackedValue.AsBinary();
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The delegate type that powers single value formatters.<br/>
|
||||
/// Takes in the result value from the Play Report, and outputs:
|
||||
/// <br/>
|
||||
/// a formatted string,
|
||||
/// <br/>
|
||||
/// a signal that nothing was available to handle it,
|
||||
/// <br/>
|
||||
/// OR a signal to reset the value that the caller is using the <see cref="Analyzer"/> for.
|
||||
/// </summary>
|
||||
public delegate Analyzer.FormattedValue ValueFormatter(Value value);
|
||||
|
||||
/// <summary>
|
||||
/// The delegate type that powers multiple value formatters.<br/>
|
||||
/// Takes in the result value from the Play Report, and outputs:
|
||||
/// <br/>
|
||||
/// a formatted string,
|
||||
/// <br/>
|
||||
/// a signal that nothing was available to handle it,
|
||||
/// <br/>
|
||||
/// OR a signal to reset the value that the caller is using the <see cref="Analyzer"/> for.
|
||||
/// </summary>
|
||||
public delegate Analyzer.FormattedValue MultiValueFormatter(Value[] value);
|
||||
}
|
||||
|
42
src/Ryujinx/Utilities/PlayReport/Delegates.cs
Normal file
42
src/Ryujinx/Utilities/PlayReport/Delegates.cs
Normal file
@ -0,0 +1,42 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Ryujinx.Ava.Utilities.PlayReport
|
||||
{
|
||||
/// <summary>
|
||||
/// The delegate type that powers single value formatters.<br/>
|
||||
/// Takes in the result value from the Play Report, and outputs:
|
||||
/// <br/>
|
||||
/// a formatted string,
|
||||
/// <br/>
|
||||
/// a signal that nothing was available to handle it,
|
||||
/// <br/>
|
||||
/// OR a signal to reset the value that the caller is using the <see cref="Analyzer"/> for.
|
||||
/// </summary>
|
||||
public delegate FormattedValue ValueFormatter(Value value);
|
||||
|
||||
/// <summary>
|
||||
/// The delegate type that powers multiple value formatters.<br/>
|
||||
/// Takes in the result values from the Play Report, and outputs:
|
||||
/// <br/>
|
||||
/// a formatted string,
|
||||
/// <br/>
|
||||
/// a signal that nothing was available to handle it,
|
||||
/// <br/>
|
||||
/// OR a signal to reset the value that the caller is using the <see cref="Analyzer"/> for.
|
||||
/// </summary>
|
||||
public delegate FormattedValue MultiValueFormatter(Value[] value);
|
||||
|
||||
/// <summary>
|
||||
/// The delegate type that powers multiple value formatters.
|
||||
/// The dictionary passed to this delegate is sparsely populated;
|
||||
/// that is, not every key specified in the Play Report needs to match for this to be used.<br/>
|
||||
/// Takes in the result values from the Play Report, and outputs:
|
||||
/// <br/>
|
||||
/// a formatted string,
|
||||
/// <br/>
|
||||
/// a signal that nothing was available to handle it,
|
||||
/// <br/>
|
||||
/// OR a signal to reset the value that the caller is using the <see cref="Analyzer"/> for.
|
||||
/// </summary>
|
||||
public delegate FormattedValue SparseMultiValueFormatter(Dictionary<string, Value> values);
|
||||
}
|
@ -1,6 +1,4 @@
|
||||
using static Ryujinx.Ava.Utilities.PlayReport.Analyzer;
|
||||
|
||||
namespace Ryujinx.Ava.Utilities.PlayReport
|
||||
namespace Ryujinx.Ava.Utilities.PlayReport
|
||||
{
|
||||
public static class PlayReports
|
||||
{
|
||||
@ -10,7 +8,7 @@ namespace Ryujinx.Ava.Utilities.PlayReport
|
||||
spec => spec
|
||||
.AddValueFormatter("IsHardMode", BreathOfTheWild_MasterMode)
|
||||
// reset to normal status when switching between normal & master mode in title screen
|
||||
.AddValueFormatter("AoCVer", FormattedValue.AlwaysResets)
|
||||
.AddValueFormatter("AoCVer", FormattedValue.SingleAlwaysResets)
|
||||
)
|
||||
.AddSpec(
|
||||
"0100f2c0115b6000",
|
||||
|
140
src/Ryujinx/Utilities/PlayReport/Specs.cs
Normal file
140
src/Ryujinx/Utilities/PlayReport/Specs.cs
Normal file
@ -0,0 +1,140 @@
|
||||
using FluentAvalonia.Core;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Ryujinx.Ava.Utilities.PlayReport
|
||||
{
|
||||
/// <summary>
|
||||
/// A mapping of title IDs to value formatter specs.
|
||||
///
|
||||
/// <remarks>Generally speaking, use the <see cref="Analyzer"/>.AddSpec(...) methods instead of creating this class yourself.</remarks>
|
||||
/// </summary>
|
||||
public class GameSpec
|
||||
{
|
||||
public required string[] TitleIds { get; init; }
|
||||
public List<FormatterSpec> SimpleValueFormatters { get; } = [];
|
||||
public List<MultiFormatterSpec> MultiValueFormatters { get; } = [];
|
||||
public List<SparseMultiFormatterSpec> SparseMultiValueFormatters { get; } = [];
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
/// <param name="reportKey">The key name to match.</param>
|
||||
/// <param name="valueFormatter">The function which can return a potential formatted value.</param>
|
||||
/// <returns>The current <see cref="GameSpec"/>, for chaining convenience.</returns>
|
||||
public GameSpec AddValueFormatter(string reportKey, ValueFormatter valueFormatter)
|
||||
=> AddValueFormatter(SimpleValueFormatters.Count, reportKey, valueFormatter);
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
/// <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="valueFormatter">The function which can return a potential formatted value.</param>
|
||||
/// <returns>The current <see cref="GameSpec"/>, for chaining convenience.</returns>
|
||||
public GameSpec AddValueFormatter(int priority, string reportKey,
|
||||
ValueFormatter valueFormatter)
|
||||
{
|
||||
SimpleValueFormatters.Add(new FormatterSpec
|
||||
{
|
||||
Priority = priority, ReportKey = reportKey, Formatter = valueFormatter
|
||||
});
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add a multi-value formatter to the current <see cref="GameSpec"/>
|
||||
/// matching a specific set of keys that could exist in a Play Report for the previously specified title IDs.
|
||||
/// </summary>
|
||||
/// <param name="reportKeys">The key names to match.</param>
|
||||
/// <param name="valueFormatter">The function which can format the values.</param>
|
||||
/// <returns>The current <see cref="GameSpec"/>, for chaining convenience.</returns>
|
||||
public GameSpec AddMultiValueFormatter(string[] reportKeys, MultiValueFormatter valueFormatter)
|
||||
=> AddMultiValueFormatter(MultiValueFormatters.Count, reportKeys, valueFormatter);
|
||||
|
||||
/// <summary>
|
||||
/// Add a multi-value formatter at a specific priority to the current <see cref="GameSpec"/>
|
||||
/// matching a specific set of keys that could exist in a Play Report for the previously specified title IDs.
|
||||
/// </summary>
|
||||
/// <param name="priority">The resolution priority of this value formatter. Higher resolves sooner.</param>
|
||||
/// <param name="reportKeys">The key names to match.</param>
|
||||
/// <param name="valueFormatter">The function which can format the values.</param>
|
||||
/// <returns>The current <see cref="GameSpec"/>, for chaining convenience.</returns>
|
||||
public GameSpec AddMultiValueFormatter(int priority, string[] reportKeys,
|
||||
MultiValueFormatter valueFormatter)
|
||||
{
|
||||
MultiValueFormatters.Add(new MultiFormatterSpec
|
||||
{
|
||||
Priority = priority, ReportKeys = reportKeys, Formatter = valueFormatter
|
||||
});
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add a multi-value formatter to the current <see cref="GameSpec"/>
|
||||
/// matching a specific set of keys that could exist in a Play Report for the previously specified title IDs.
|
||||
/// <br/><br/>
|
||||
/// The 'Sparse' multi-value formatters do not require every key to be present.
|
||||
/// If you need this requirement, use <see cref="AddMultiValueFormatter(string[], Ryujinx.Ava.Utilities.PlayReport.MultiValueFormatter)"/>.
|
||||
/// </summary>
|
||||
/// <param name="reportKeys">The key names to match.</param>
|
||||
/// <param name="valueFormatter">The function which can format the values.</param>
|
||||
/// <returns>The current <see cref="GameSpec"/>, for chaining convenience.</returns>
|
||||
public GameSpec AddSparseMultiValueFormatter(string[] reportKeys, SparseMultiValueFormatter valueFormatter)
|
||||
=> AddSparseMultiValueFormatter(SparseMultiValueFormatters.Count, reportKeys, valueFormatter);
|
||||
|
||||
/// <summary>
|
||||
/// Add a multi-value formatter at a specific priority to the current <see cref="GameSpec"/>
|
||||
/// matching a specific set of keys that could exist in a Play Report for the previously specified title IDs.
|
||||
/// <br/><br/>
|
||||
/// The 'Sparse' multi-value formatters do not require every key to be present.
|
||||
/// If you need this requirement, use <see cref="AddMultiValueFormatter(int, string[], Ryujinx.Ava.Utilities.PlayReport.MultiValueFormatter)"/>.
|
||||
/// </summary>
|
||||
/// <param name="priority">The resolution priority of this value formatter. Higher resolves sooner.</param>
|
||||
/// <param name="reportKeys">The key names to match.</param>
|
||||
/// <param name="valueFormatter">The function which can format the values.</param>
|
||||
/// <returns>The current <see cref="GameSpec"/>, for chaining convenience.</returns>
|
||||
public GameSpec AddSparseMultiValueFormatter(int priority, string[] reportKeys,
|
||||
SparseMultiValueFormatter valueFormatter)
|
||||
{
|
||||
SparseMultiValueFormatters.Add(new SparseMultiFormatterSpec
|
||||
{
|
||||
Priority = priority, ReportKeys = reportKeys, Formatter = valueFormatter
|
||||
});
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A struct containing the data for a mapping of a key in a Play Report to a formatter for its potential value.
|
||||
/// </summary>
|
||||
public struct FormatterSpec
|
||||
{
|
||||
public required int Priority { get; init; }
|
||||
public required string ReportKey { get; init; }
|
||||
public ValueFormatter Formatter { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A struct containing the data for a mapping of an arbitrary key set in a Play Report to a formatter for their potential values.
|
||||
/// </summary>
|
||||
public struct MultiFormatterSpec
|
||||
{
|
||||
public required int Priority { get; init; }
|
||||
public required string[] ReportKeys { get; init; }
|
||||
public MultiValueFormatter Formatter { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A struct containing the data for a mapping of an arbitrary key set in a Play Report to a formatter for their sparsely populated potential values.
|
||||
/// </summary>
|
||||
public struct SparseMultiFormatterSpec
|
||||
{
|
||||
public required int Priority { get; init; }
|
||||
public required string[] ReportKeys { get; init; }
|
||||
public SparseMultiValueFormatter Formatter { get; init; }
|
||||
}
|
||||
}
|
130
src/Ryujinx/Utilities/PlayReport/Value.cs
Normal file
130
src/Ryujinx/Utilities/PlayReport/Value.cs
Normal file
@ -0,0 +1,130 @@
|
||||
using MsgPack;
|
||||
using Ryujinx.Ava.Utilities.AppLibrary;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Ava.Utilities.PlayReport
|
||||
{
|
||||
/// <summary>
|
||||
/// The input data to a <see cref="ValueFormatter"/>,
|
||||
/// containing the currently running application's <see cref="ApplicationMetadata"/>,
|
||||
/// and the matched <see cref="MessagePackObject"/> from the Play Report.
|
||||
/// </summary>
|
||||
public class Value
|
||||
{
|
||||
/// <summary>
|
||||
/// The currently running application's <see cref="ApplicationMetadata"/>.
|
||||
/// </summary>
|
||||
public ApplicationMetadata Application { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// The matched value from the Play Report.
|
||||
/// </summary>
|
||||
public MessagePackObject PackedValue { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Access the <see cref="PackedValue"/> as its underlying .NET type.<br/>
|
||||
///
|
||||
/// Does not seem to work well with comparing numeric types,
|
||||
/// so use XValue properties for that.
|
||||
/// </summary>
|
||||
public object BoxedValue => PackedValue.ToObject();
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
object boxed = BoxedValue;
|
||||
return boxed == null
|
||||
? "null"
|
||||
: boxed.ToString();
|
||||
}
|
||||
|
||||
#region AsX accessors
|
||||
|
||||
public bool BooleanValue => PackedValue.AsBoolean();
|
||||
public byte ByteValue => PackedValue.AsByte();
|
||||
public sbyte SByteValue => PackedValue.AsSByte();
|
||||
public short ShortValue => PackedValue.AsInt16();
|
||||
public ushort UShortValue => PackedValue.AsUInt16();
|
||||
public int IntValue => PackedValue.AsInt32();
|
||||
public uint UIntValue => PackedValue.AsUInt32();
|
||||
public long LongValue => PackedValue.AsInt64();
|
||||
public ulong ULongValue => PackedValue.AsUInt64();
|
||||
public float FloatValue => PackedValue.AsSingle();
|
||||
public double DoubleValue => PackedValue.AsDouble();
|
||||
public string StringValue => PackedValue.AsString();
|
||||
public Span<byte> BinaryValue => PackedValue.AsBinary();
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A potential formatted value returned by a <see cref="ValueFormatter"/>.
|
||||
/// </summary>
|
||||
public readonly struct FormattedValue
|
||||
{
|
||||
/// <summary>
|
||||
/// Was any handler able to match anything in the Play Report?
|
||||
/// </summary>
|
||||
public bool Handled { get; private init; }
|
||||
|
||||
/// <summary>
|
||||
/// Did the handler request the caller of the <see cref="Analyzer"/> to reset the existing value?
|
||||
/// </summary>
|
||||
public bool Reset { get; private init; }
|
||||
|
||||
/// <summary>
|
||||
/// The formatted value, only present if <see cref="Handled"/> is true, and <see cref="Reset"/> is false.
|
||||
/// </summary>
|
||||
public string FormattedString { get; private init; }
|
||||
|
||||
/// <summary>
|
||||
/// The intended path of execution for having a string to return: simply return the string.
|
||||
/// This implicit conversion will make the struct for you.<br/><br/>
|
||||
///
|
||||
/// If the input is null, <see cref="Unhandled"/> is returned.
|
||||
/// </summary>
|
||||
/// <param name="formattedValue">The formatted string value.</param>
|
||||
/// <returns>The automatically constructed <see cref="FormattedValue"/> struct.</returns>
|
||||
public static implicit operator FormattedValue(string formattedValue)
|
||||
=> formattedValue is not null
|
||||
? new FormattedValue { Handled = true, FormattedString = formattedValue }
|
||||
: Unhandled;
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
if (!Handled)
|
||||
return "<Unhandled>";
|
||||
|
||||
if (Reset)
|
||||
return "<Reset>";
|
||||
|
||||
return FormattedString;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return this to tell the caller there is no value to return.
|
||||
/// </summary>
|
||||
public static FormattedValue Unhandled => default;
|
||||
|
||||
/// <summary>
|
||||
/// Return this to suggest the caller reset the value it's using the <see cref="Analyzer"/> for.
|
||||
/// </summary>
|
||||
public static FormattedValue ForceReset => new() { Handled = true, Reset = true };
|
||||
|
||||
/// <summary>
|
||||
/// A delegate singleton you can use to always return <see cref="ForceReset"/> in a <see cref="ValueFormatter"/>.
|
||||
/// </summary>
|
||||
public static readonly ValueFormatter SingleAlwaysResets = _ => ForceReset;
|
||||
|
||||
/// <summary>
|
||||
/// A delegate singleton you can use to always return <see cref="ForceReset"/> in a <see cref="MultiValueFormatter"/>.
|
||||
/// </summary>
|
||||
public static readonly MultiValueFormatter MultiAlwaysResets = _ => ForceReset;
|
||||
|
||||
/// <summary>
|
||||
/// A delegate factory you can use to always return the specified
|
||||
/// <paramref name="formattedValue"/> in a <see cref="ValueFormatter"/>.
|
||||
/// </summary>
|
||||
/// <param name="formattedValue">The string to always return for this delegate instance.</param>
|
||||
public static ValueFormatter AlwaysReturns(string formattedValue) => _ => formattedValue;
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user