Avoid race conditions while launching games directly from the command line (#7116)

* optimization: Load application metadata only for applications with IDs

* Load applications when necessary

This prevents loading applications when launching an application
directly from the command line (or a shortcut).
Instead, applications will be loaded after the emulation was stopped by the user.

* Show the title in the configured language when launching an application

* Rename DesiredTitleLanguage to DesiredLanguage
This commit is contained in:
TSRBerry 2024-08-03 23:31:34 +02:00 committed by GitHub
parent 3004902257
commit 263eb97f79
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 62 additions and 34 deletions

View File

@ -256,6 +256,12 @@ namespace Ryujinx
MainWindow mainWindow = new(); MainWindow mainWindow = new();
mainWindow.Show(); mainWindow.Show();
// Load the game table if no application was requested by the command line
if (CommandLineState.LaunchPathArg == null)
{
mainWindow.UpdateGameTable();
}
if (OperatingSystem.IsLinux()) if (OperatingSystem.IsLinux())
{ {
int currentVmMaxMapCount = LinuxHelper.VmMaxMapCount; int currentVmMaxMapCount = LinuxHelper.VmMaxMapCount;

View File

@ -187,7 +187,10 @@ namespace Ryujinx.UI
: IntegrityCheckLevel.None; : IntegrityCheckLevel.None;
// Instantiate GUI objects. // Instantiate GUI objects.
ApplicationLibrary = new ApplicationLibrary(_virtualFileSystem, checkLevel); ApplicationLibrary = new ApplicationLibrary(_virtualFileSystem, checkLevel)
{
DesiredLanguage = ConfigurationState.Instance.System.Language,
};
_uiHandler = new GtkHostUIHandler(this); _uiHandler = new GtkHostUIHandler(this);
_deviceExitStatus = new AutoResetEvent(false); _deviceExitStatus = new AutoResetEvent(false);
@ -325,7 +328,6 @@ namespace Ryujinx.UI
_hideUI.Label = _hideUI.Label.Replace("SHOWUIKEY", ConfigurationState.Instance.Hid.Hotkeys.Value.ShowUI.ToString()); _hideUI.Label = _hideUI.Label.Replace("SHOWUIKEY", ConfigurationState.Instance.Hid.Hotkeys.Value.ShowUI.ToString());
UpdateColumns(); UpdateColumns();
UpdateGameTable();
ConfigurationState.Instance.UI.GameDirs.Event += (sender, args) => ConfigurationState.Instance.UI.GameDirs.Event += (sender, args) =>
{ {
@ -738,7 +740,8 @@ namespace Ryujinx.UI
Thread applicationLibraryThread = new(() => Thread applicationLibraryThread = new(() =>
{ {
ApplicationLibrary.LoadApplications(ConfigurationState.Instance.UI.GameDirs, ConfigurationState.Instance.System.Language); ApplicationLibrary.DesiredLanguage = ConfigurationState.Instance.System.Language;
ApplicationLibrary.LoadApplications(ConfigurationState.Instance.UI.GameDirs);
_updatingGameTable = false; _updatingGameTable = false;
}) })

View File

@ -34,6 +34,7 @@ namespace Ryujinx.UI.App.Common
{ {
public class ApplicationLibrary public class ApplicationLibrary
{ {
public Language DesiredLanguage { get; set; }
public event EventHandler<ApplicationAddedEventArgs> ApplicationAdded; public event EventHandler<ApplicationAddedEventArgs> ApplicationAdded;
public event EventHandler<ApplicationCountUpdatedEventArgs> ApplicationCountUpdated; public event EventHandler<ApplicationCountUpdatedEventArgs> ApplicationCountUpdated;
@ -45,7 +46,6 @@ namespace Ryujinx.UI.App.Common
private readonly VirtualFileSystem _virtualFileSystem; private readonly VirtualFileSystem _virtualFileSystem;
private readonly IntegrityCheckLevel _checkLevel; private readonly IntegrityCheckLevel _checkLevel;
private Language _desiredTitleLanguage;
private CancellationTokenSource _cancellationToken; private CancellationTokenSource _cancellationToken;
private static readonly ApplicationJsonSerializerContext _serializerContext = new(JsonHelper.GetDefaultSerializerOptions()); private static readonly ApplicationJsonSerializerContext _serializerContext = new(JsonHelper.GetDefaultSerializerOptions());
@ -221,7 +221,7 @@ namespace Ryujinx.UI.App.Common
{ {
using UniqueRef<IFile> icon = new(); using UniqueRef<IFile> icon = new();
controlFs.OpenFile(ref icon.Ref, $"/icon_{_desiredTitleLanguage}.dat".ToU8Span(), OpenMode.Read).ThrowIfFailure(); controlFs.OpenFile(ref icon.Ref, $"/icon_{DesiredLanguage}.dat".ToU8Span(), OpenMode.Read).ThrowIfFailure();
using MemoryStream stream = new(); using MemoryStream stream = new();
@ -432,35 +432,40 @@ namespace Ryujinx.UI.App.Common
foreach (var data in applications) foreach (var data in applications)
{ {
ApplicationMetadata appMetadata = LoadAndSaveMetaData(data.IdString, appMetadata => // Only load metadata for applications with an ID
if (data.Id != 0)
{ {
appMetadata.Title = data.Name; ApplicationMetadata appMetadata = LoadAndSaveMetaData(data.IdString, appMetadata =>
// Only do the migration if time_played has a value and timespan_played hasn't been updated yet.
if (appMetadata.TimePlayedOld != default && appMetadata.TimePlayed == TimeSpan.Zero)
{ {
appMetadata.TimePlayed = TimeSpan.FromSeconds(appMetadata.TimePlayedOld); appMetadata.Title = data.Name;
appMetadata.TimePlayedOld = default;
}
// Only do the migration if last_played has a value and last_played_utc doesn't exist yet. // Only do the migration if time_played has a value and timespan_played hasn't been updated yet.
if (appMetadata.LastPlayedOld != default && !appMetadata.LastPlayed.HasValue) if (appMetadata.TimePlayedOld != default && appMetadata.TimePlayed == TimeSpan.Zero)
{
// Migrate from string-based last_played to DateTime-based last_played_utc.
if (DateTime.TryParse(appMetadata.LastPlayedOld, out DateTime lastPlayedOldParsed))
{ {
appMetadata.LastPlayed = lastPlayedOldParsed; appMetadata.TimePlayed = TimeSpan.FromSeconds(appMetadata.TimePlayedOld);
appMetadata.TimePlayedOld = default;
// Migration successful: deleting last_played from the metadata file.
appMetadata.LastPlayedOld = default;
} }
} // Only do the migration if last_played has a value and last_played_utc doesn't exist yet.
}); if (appMetadata.LastPlayedOld != default && !appMetadata.LastPlayed.HasValue)
{
// Migrate from string-based last_played to DateTime-based last_played_utc.
if (DateTime.TryParse(appMetadata.LastPlayedOld, out DateTime lastPlayedOldParsed))
{
appMetadata.LastPlayed = lastPlayedOldParsed;
// Migration successful: deleting last_played from the metadata file.
appMetadata.LastPlayedOld = default;
}
}
});
data.Favorite = appMetadata.Favorite;
data.TimePlayed = appMetadata.TimePlayed;
data.LastPlayed = appMetadata.LastPlayed;
}
data.Favorite = appMetadata.Favorite;
data.TimePlayed = appMetadata.TimePlayed;
data.LastPlayed = appMetadata.LastPlayed;
data.FileExtension = Path.GetExtension(applicationPath).TrimStart('.').ToUpper(); data.FileExtension = Path.GetExtension(applicationPath).TrimStart('.').ToUpper();
data.FileSize = fileSize; data.FileSize = fileSize;
data.Path = applicationPath; data.Path = applicationPath;
@ -482,13 +487,11 @@ namespace Ryujinx.UI.App.Common
controlFile.Get.Read(out _, 0, outProperty, ReadOption.None).ThrowIfFailure(); controlFile.Get.Read(out _, 0, outProperty, ReadOption.None).ThrowIfFailure();
} }
public void LoadApplications(List<string> appDirs, Language desiredTitleLanguage) public void LoadApplications(List<string> appDirs)
{ {
int numApplicationsFound = 0; int numApplicationsFound = 0;
int numApplicationsLoaded = 0; int numApplicationsLoaded = 0;
_desiredTitleLanguage = desiredTitleLanguage;
_cancellationToken = new CancellationTokenSource(); _cancellationToken = new CancellationTokenSource();
// Builds the applications list with paths to found applications // Builds the applications list with paths to found applications
@ -847,7 +850,7 @@ namespace Ryujinx.UI.App.Common
private void GetApplicationInformation(ref ApplicationControlProperty controlData, ref ApplicationData data) private void GetApplicationInformation(ref ApplicationControlProperty controlData, ref ApplicationData data)
{ {
_ = Enum.TryParse(_desiredTitleLanguage.ToString(), out TitleLanguage desiredTitleLanguage); _ = Enum.TryParse(DesiredLanguage.ToString(), out TitleLanguage desiredTitleLanguage);
if (controlData.Title.ItemsRo.Length > (int)desiredTitleLanguage) if (controlData.Title.ItemsRo.Length > (int)desiredTitleLanguage)
{ {

View File

@ -37,6 +37,7 @@ namespace Ryujinx.Ava.UI.Windows
internal static MainWindowViewModel MainWindowViewModel { get; private set; } internal static MainWindowViewModel MainWindowViewModel { get; private set; }
private bool _isLoading; private bool _isLoading;
private bool _applicationsLoadedOnce;
private UserChannelPersistence _userChannelPersistence; private UserChannelPersistence _userChannelPersistence;
private static bool _deferLoad; private static bool _deferLoad;
@ -224,7 +225,10 @@ namespace Ryujinx.Ava.UI.Windows
? IntegrityCheckLevel.ErrorOnInvalid ? IntegrityCheckLevel.ErrorOnInvalid
: IntegrityCheckLevel.None; : IntegrityCheckLevel.None;
ApplicationLibrary = new ApplicationLibrary(VirtualFileSystem, checkLevel); ApplicationLibrary = new ApplicationLibrary(VirtualFileSystem, checkLevel)
{
DesiredLanguage = ConfigurationState.Instance.System.Language,
};
// Save data created before we supported extra data in directory save data will not work properly if // Save data created before we supported extra data in directory save data will not work properly if
// given empty extra data. Luckily some of that extra data can be created using the data from the // given empty extra data. Luckily some of that extra data can be created using the data from the
@ -472,7 +476,11 @@ namespace Ryujinx.Ava.UI.Windows
ViewModel.RefreshFirmwareStatus(); ViewModel.RefreshFirmwareStatus();
LoadApplications(); // Load applications if no application was requested by the command line
if (!_deferLoad)
{
LoadApplications();
}
#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed #pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
CheckLaunchState(); CheckLaunchState();
@ -485,6 +493,12 @@ namespace Ryujinx.Ava.UI.Windows
if (MainContent.Content != content) if (MainContent.Content != content)
{ {
// Load applications while switching to the GameLibrary if we haven't done that yet
if (!_applicationsLoadedOnce && content == GameLibrary)
{
LoadApplications();
}
MainContent.Content = content; MainContent.Content = content;
} }
} }
@ -581,6 +595,7 @@ namespace Ryujinx.Ava.UI.Windows
public void LoadApplications() public void LoadApplications()
{ {
_applicationsLoadedOnce = true;
ViewModel.Applications.Clear(); ViewModel.Applications.Clear();
StatusBarView.LoadProgressBar.IsVisible = true; StatusBarView.LoadProgressBar.IsVisible = true;
@ -622,7 +637,8 @@ namespace Ryujinx.Ava.UI.Windows
Thread applicationLibraryThread = new(() => Thread applicationLibraryThread = new(() =>
{ {
ApplicationLibrary.LoadApplications(ConfigurationState.Instance.UI.GameDirs, ConfigurationState.Instance.System.Language); ApplicationLibrary.DesiredLanguage = ConfigurationState.Instance.System.Language;
ApplicationLibrary.LoadApplications(ConfigurationState.Instance.UI.GameDirs);
_isLoading = false; _isLoading = false;
}) })