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:
parent
3004902257
commit
263eb97f79
4 changed files with 62 additions and 34 deletions
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
})
|
})
|
||||||
|
|
|
@ -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)
|
||||||
{
|
{
|
||||||
|
|
|
@ -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;
|
||||||
})
|
})
|
||||||
|
|
Reference in a new issue