HLE: Run Play Report event handlers in a dedicated .NET thread
This commit is contained in:
parent
a0edc5c2b0
commit
5ab50680b4
@ -2,14 +2,21 @@ using MsgPack;
|
|||||||
using Ryujinx.Horizon.Common;
|
using Ryujinx.Horizon.Common;
|
||||||
using Ryujinx.Memory;
|
using Ryujinx.Memory;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Threading;
|
||||||
|
|
||||||
namespace Ryujinx.Horizon
|
namespace Ryujinx.Horizon
|
||||||
{
|
{
|
||||||
public static class HorizonStatic
|
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<MessagePackObject> PlayReportPrinted;
|
public static event Action<MessagePackObject> PlayReport;
|
||||||
|
|
||||||
[ThreadStatic]
|
[ThreadStatic]
|
||||||
private static HorizonOptions _options;
|
private static HorizonOptions _options;
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
using Gommon;
|
||||||
using MsgPack;
|
using MsgPack;
|
||||||
using MsgPack.Serialization;
|
using MsgPack.Serialization;
|
||||||
using Ryujinx.Common.Logging;
|
using Ryujinx.Common.Logging;
|
||||||
@ -11,6 +12,7 @@ using Ryujinx.Horizon.Sdk.Sf;
|
|||||||
using Ryujinx.Horizon.Sdk.Sf.Hipc;
|
using Ryujinx.Horizon.Sdk.Sf.Hipc;
|
||||||
using System;
|
using System;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using System.Threading;
|
||||||
using ApplicationId = Ryujinx.Horizon.Sdk.Ncm.ApplicationId;
|
using ApplicationId = Ryujinx.Horizon.Sdk.Ncm.ApplicationId;
|
||||||
|
|
||||||
namespace Ryujinx.Horizon.Prepo.Ipc
|
namespace Ryujinx.Horizon.Prepo.Ipc
|
||||||
|
@ -56,7 +56,7 @@ namespace Ryujinx.Ava
|
|||||||
|
|
||||||
ConfigurationState.Instance.EnableDiscordIntegration.Event += Update;
|
ConfigurationState.Instance.EnableDiscordIntegration.Event += Update;
|
||||||
TitleIDs.CurrentApplication.Event += (_, e) => Use(e.NewValue);
|
TitleIDs.CurrentApplication.Event += (_, e) => Use(e.NewValue);
|
||||||
HorizonStatic.PlayReportPrinted += HandlePlayReport;
|
HorizonStatic.PlayReport += HandlePlayReport;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void Update(object sender, ReactiveEventArgs<bool> evnt)
|
private static void Update(object sender, ReactiveEventArgs<bool> evnt)
|
||||||
|
@ -85,128 +85,4 @@ namespace Ryujinx.Ava.Utilities
|
|||||||
_ => PlayReportFormattedValue.ForceReset
|
_ => PlayReportFormattedValue.ForceReset
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
#region Analyzer implementation
|
|
||||||
|
|
||||||
public class PlayReportAnalyzer
|
|
||||||
{
|
|
||||||
private readonly List<PlayReportGameSpec> _specs = [];
|
|
||||||
|
|
||||||
public PlayReportAnalyzer AddSpec(string titleId, Func<PlayReportGameSpec, PlayReportGameSpec> transform)
|
|
||||||
{
|
|
||||||
_specs.Add(transform(new PlayReportGameSpec { TitleIds = [titleId] }));
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public PlayReportAnalyzer AddSpec(string titleId, Action<PlayReportGameSpec> transform)
|
|
||||||
{
|
|
||||||
_specs.Add(new PlayReportGameSpec { TitleIds = [titleId] }.Apply(transform));
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public PlayReportAnalyzer AddSpec(IEnumerable<string> titleIds, Func<PlayReportGameSpec, PlayReportGameSpec> transform)
|
|
||||||
{
|
|
||||||
_specs.Add(transform(new PlayReportGameSpec { TitleIds = [..titleIds] }));
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public PlayReportAnalyzer AddSpec(IEnumerable<string> titleIds, Action<PlayReportGameSpec> 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<PlayReportValueFormatterSpec> 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
|
|
||||||
}
|
}
|
||||||
|
129
src/Ryujinx/Utilities/PlayReportAnalyzer.cs
Normal file
129
src/Ryujinx/Utilities/PlayReportAnalyzer.cs
Normal file
@ -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<PlayReportGameSpec> _specs = [];
|
||||||
|
|
||||||
|
public PlayReportAnalyzer AddSpec(string titleId, Func<PlayReportGameSpec, PlayReportGameSpec> transform)
|
||||||
|
{
|
||||||
|
_specs.Add(transform(new PlayReportGameSpec { TitleIds = [titleId] }));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PlayReportAnalyzer AddSpec(string titleId, Action<PlayReportGameSpec> transform)
|
||||||
|
{
|
||||||
|
_specs.Add(new PlayReportGameSpec { TitleIds = [titleId] }.Apply(transform));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PlayReportAnalyzer AddSpec(IEnumerable<string> titleIds, Func<PlayReportGameSpec, PlayReportGameSpec> transform)
|
||||||
|
{
|
||||||
|
_specs.Add(transform(new PlayReportGameSpec { TitleIds = [..titleIds] }));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PlayReportAnalyzer AddSpec(IEnumerable<string> titleIds, Action<PlayReportGameSpec> 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<PlayReportValueFormatterSpec> 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);
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user