mirror of
https://git.ryujinx.app/projects/Kenji-NX.git
synced 2026-10-04 23:08:53 +02:00
UI: LoadGuestApplication asynchronous cancellation
Fixed LoadGuestApplication hanging when cancelled. Since startup procedure has technically changed, we should consider testing this with a variety of game formats to ensure regressions do not occur.
This commit is contained in:
+63
-32
@@ -4,6 +4,8 @@ using Avalonia.Controls.ApplicationLifetimes;
|
||||
using Avalonia.Input;
|
||||
using Avalonia.Threading;
|
||||
using Gommon;
|
||||
using LibHac.Common;
|
||||
using LibHac.Ns;
|
||||
using LibHac.Tools.FsSystem;
|
||||
using Ryujinx.Audio.Backends.Apple;
|
||||
using Ryujinx.Audio.Backends.Dummy;
|
||||
@@ -64,7 +66,7 @@ using VSyncMode = Ryujinx.Common.Configuration.VSyncMode;
|
||||
|
||||
namespace Ryujinx.Ava
|
||||
{
|
||||
internal class AppHost
|
||||
internal class AppHost : IDisposable
|
||||
{
|
||||
private const int CursorHideIdleTime = 5; // Hide Cursor seconds.
|
||||
private const float MaxResolutionScale = 4.0f; // Max resolution hotkeys can scale to before wrapping.
|
||||
@@ -453,7 +455,7 @@ namespace Ryujinx.Ava
|
||||
|
||||
SaveBitmapAsPng(bitmapToSave, path);
|
||||
|
||||
Logger.Notice.Print(LogClass.Application, $"Screenshot saved to {path}", "Screenshot");
|
||||
Logger.Notice.Print(LogClass.Application, $"Screenshot saved to '{path}'.", "Screenshot");
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -592,27 +594,37 @@ namespace Ryujinx.Ava
|
||||
|
||||
_isActive = false;
|
||||
|
||||
// NOTE: The render loop is allowed to stay alive until the renderer itself is disposed, as it may handle resource dispose.
|
||||
// We only need to wait for all commands submitted during the main gpu loop to be processed.
|
||||
_gpuDoneEvent.WaitOne();
|
||||
_gpuDoneEvent.Dispose();
|
||||
|
||||
DisplaySleep.Restore();
|
||||
|
||||
NpadManager.Dispose();
|
||||
TouchScreenManager.Dispose();
|
||||
Device.Dispose();
|
||||
|
||||
DisposeGpu();
|
||||
// NOTE: The render loop is allowed to stay alive until the renderer itself is disposed, as it may handle resource dispose.
|
||||
// We only need to wait for all commands submitted during the main gpu loop to be processed.
|
||||
// If the GPU has no work and is cancelled, we need to handle that as well.
|
||||
|
||||
WaitHandle.WaitAny(new[] { _gpuDoneEvent, _gpuCancellationTokenSource.Token.WaitHandle });
|
||||
_gpuCancellationTokenSource.Dispose();
|
||||
|
||||
// Waiting for work to be finished before we dispose.
|
||||
if (_renderingStarted)
|
||||
{
|
||||
Device.Gpu.WaitUntilGpuReady();
|
||||
}
|
||||
|
||||
_gpuDoneEvent.Dispose();
|
||||
|
||||
DisposeGpu();
|
||||
AppExit?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
private void Dispose()
|
||||
// MUST be public to inherit from IDisposable
|
||||
public void Dispose()
|
||||
{
|
||||
if (Device.Processes != null)
|
||||
{
|
||||
MainWindowViewModel.UpdateGameMetadata(Device.Processes.ActiveApplication.ProgramIdText);
|
||||
MainWindowViewModel.UpdateGameMetadata(Device.Processes.ActiveApplication?.ProgramIdText);
|
||||
}
|
||||
|
||||
ConfigurationState.Instance.System.IgnoreMissingServices.Event -= UpdateIgnoreMissingServicesState;
|
||||
@@ -629,7 +641,6 @@ namespace Ryujinx.Ava
|
||||
_topLevel.PointerExited -= TopLevel_PointerExited;
|
||||
|
||||
_gpuCancellationTokenSource.Cancel();
|
||||
_gpuCancellationTokenSource.Dispose();
|
||||
|
||||
_chrono.Stop();
|
||||
}
|
||||
@@ -654,6 +665,12 @@ namespace Ryujinx.Ava
|
||||
}
|
||||
else
|
||||
{
|
||||
// No use waiting on something that never started work
|
||||
if (_renderingStarted)
|
||||
{
|
||||
Device.Gpu.WaitUntilGpuReady();
|
||||
}
|
||||
|
||||
Device.DisposeGpu();
|
||||
}
|
||||
}
|
||||
@@ -668,7 +685,7 @@ namespace Ryujinx.Ava
|
||||
_cursorState = CursorStates.ForceChangeCursor;
|
||||
}
|
||||
|
||||
public async Task<bool> LoadGuestApplication()
|
||||
public async Task LoadGuestApplication(CancellationTokenSource cts, BlitStruct<ApplicationControlProperty>? customNacpData = null)
|
||||
{
|
||||
InitializeSwitchInstance();
|
||||
MainWindow.UpdateGraphicsConfig();
|
||||
@@ -696,7 +713,8 @@ namespace Ryujinx.Ava
|
||||
await UserErrorDialog.ShowUserErrorDialog(userError);
|
||||
Device.Dispose();
|
||||
|
||||
return false;
|
||||
cts.Cancel();
|
||||
throw new OperationCanceledException(cts.Token);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -705,10 +723,11 @@ namespace Ryujinx.Ava
|
||||
await UserErrorDialog.ShowUserErrorDialog(userError);
|
||||
Device.Dispose();
|
||||
|
||||
return false;
|
||||
cts.Cancel();
|
||||
throw new OperationCanceledException(cts.Token);
|
||||
}
|
||||
|
||||
// Tell the user that we installed a firmware for them.
|
||||
// Tell the user that we installed firmware for them.
|
||||
if (userError == UserError.NoFirmware)
|
||||
{
|
||||
firmwareVersion = ContentManager.GetCurrentFirmwareVersion();
|
||||
@@ -728,7 +747,8 @@ namespace Ryujinx.Ava
|
||||
await UserErrorDialog.ShowUserErrorDialog(userError);
|
||||
Device.Dispose();
|
||||
|
||||
return false;
|
||||
cts.Cancel();
|
||||
throw new OperationCanceledException(cts.Token);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -744,7 +764,8 @@ namespace Ryujinx.Ava
|
||||
{
|
||||
Device.Dispose();
|
||||
|
||||
return false;
|
||||
cts.Cancel();
|
||||
throw new OperationCanceledException(cts.Token);
|
||||
}
|
||||
}
|
||||
else if (Directory.Exists(ApplicationPath))
|
||||
@@ -762,20 +783,24 @@ namespace Ryujinx.Ava
|
||||
|
||||
if (!Device.LoadCart(ApplicationPath, romFsFiles[0]))
|
||||
{
|
||||
await ContentDialogHelper.CreateErrorDialog(
|
||||
"Please specify an unpacked game directory with a valid exefs or NSO/NRO.");
|
||||
Device.Dispose();
|
||||
|
||||
return false;
|
||||
cts.Cancel();
|
||||
throw new OperationCanceledException(cts.Token);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Info?.Print(LogClass.Application, "Loading as cart WITHOUT RomFS.");
|
||||
|
||||
if (!Device.LoadCart(ApplicationPath))
|
||||
{
|
||||
await ContentDialogHelper.CreateErrorDialog(
|
||||
"Please specify an unpacked game directory with a valid exefs or NSO/NRO.");
|
||||
Device.Dispose();
|
||||
|
||||
return false;
|
||||
cts.Cancel();
|
||||
throw new OperationCanceledException(cts.Token);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -791,7 +816,8 @@ namespace Ryujinx.Ava
|
||||
{
|
||||
Device.Dispose();
|
||||
|
||||
return false;
|
||||
cts.Cancel();
|
||||
throw new OperationCanceledException(cts.Token);
|
||||
}
|
||||
|
||||
break;
|
||||
@@ -804,7 +830,8 @@ namespace Ryujinx.Ava
|
||||
{
|
||||
Device.Dispose();
|
||||
|
||||
return false;
|
||||
cts.Cancel();
|
||||
throw new OperationCanceledException(cts.Token);
|
||||
}
|
||||
|
||||
break;
|
||||
@@ -818,7 +845,8 @@ namespace Ryujinx.Ava
|
||||
{
|
||||
Device.Dispose();
|
||||
|
||||
return false;
|
||||
cts.Cancel();
|
||||
throw new OperationCanceledException(cts.Token);
|
||||
}
|
||||
|
||||
break;
|
||||
@@ -833,7 +861,8 @@ namespace Ryujinx.Ava
|
||||
{
|
||||
Device.Dispose();
|
||||
|
||||
return false;
|
||||
cts.Cancel();
|
||||
throw new OperationCanceledException(cts.Token);
|
||||
}
|
||||
}
|
||||
catch (ArgumentOutOfRangeException)
|
||||
@@ -842,7 +871,8 @@ namespace Ryujinx.Ava
|
||||
|
||||
Device.Dispose();
|
||||
|
||||
return false;
|
||||
cts.Cancel();
|
||||
throw new OperationCanceledException(cts.Token);
|
||||
}
|
||||
|
||||
break;
|
||||
@@ -851,11 +881,12 @@ namespace Ryujinx.Ava
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Application, "Please specify a valid XCI/NCA/NSP/PFS0/NRO file.");
|
||||
Logger.Warning?.Print(LogClass.Application, "Please specify a valid XCI/NCA/NSP/PFS0/NSO/NRO file.");
|
||||
|
||||
Device.Dispose();
|
||||
|
||||
return false;
|
||||
cts.Cancel();
|
||||
throw new OperationCanceledException(cts.Token);
|
||||
}
|
||||
|
||||
ApplicationMetadata appMeta = ApplicationLibrary.LoadAndSaveMetaData(Device.Processes.ActiveApplication.ProgramIdText,
|
||||
@@ -863,8 +894,6 @@ namespace Ryujinx.Ava
|
||||
);
|
||||
|
||||
DiscordIntegrationModule.SwitchToPlayingState(appMeta, Device.Processes.ActiveApplication);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
internal void Resume()
|
||||
@@ -873,7 +902,7 @@ namespace Ryujinx.Ava
|
||||
|
||||
_viewModel.IsPaused = false;
|
||||
_viewModel.Title = TitleHelper.ActiveApplicationTitle(Device?.Processes.ActiveApplication, Program.Version);
|
||||
Logger.Info?.Print(LogClass.Emulation, "Emulation was resumed");
|
||||
Logger.Info?.Print(LogClass.Emulation, "Emulation was resumed.");
|
||||
}
|
||||
|
||||
internal void Pause()
|
||||
@@ -882,7 +911,7 @@ namespace Ryujinx.Ava
|
||||
|
||||
_viewModel.IsPaused = true;
|
||||
_viewModel.Title = TitleHelper.ActiveApplicationTitle(Device?.Processes.ActiveApplication, Program.Version, LocaleManager.Instance[LocaleKeys.Paused]);
|
||||
Logger.Info?.Print(LogClass.Emulation, "Emulation was paused");
|
||||
Logger.Info?.Print(LogClass.Emulation, "Emulation was paused.");
|
||||
}
|
||||
|
||||
private void InitializeSwitchInstance()
|
||||
@@ -1119,7 +1148,9 @@ namespace Ryujinx.Ava
|
||||
// Make sure all commands in the run loop are fully executed before leaving the loop.
|
||||
if (Device.Gpu.Renderer is ThreadedRenderer threaded)
|
||||
{
|
||||
Logger.Info?.PrintMsg(LogClass.Gpu, "Flushing threaded commands...");
|
||||
threaded.FlushThreadedCommands();
|
||||
Logger.Info?.PrintMsg(LogClass.Gpu, "Flushed!");
|
||||
}
|
||||
|
||||
_gpuDoneEvent.Set();
|
||||
|
||||
Reference in New Issue
Block a user