Keep the GUI alive when closing a game (#888)
* Keep the GUI alive when closing a game Make HLE.Switch init when starting a game and dispose it when closing the GlScreen. This also make HLE in charge of disposing the audio and gpu backend. * Address Ac_k's comments * Make sure to dispose the Discord module and use GTK quit method Also update Discord Precense when closing a game. * Make sure to dispose MainWindow * Address gdk's comments
This commit is contained in:
parent
b4b2b8b162
commit
d6b9babe1d
16 changed files with 284 additions and 215 deletions
|
@ -88,5 +88,23 @@ namespace Ryujinx.Configuration
|
|||
|
||||
DiscordClient?.SetPresence(DiscordPresence);
|
||||
}
|
||||
|
||||
public static void SwitchToMainMenu()
|
||||
{
|
||||
DiscordPresence.Details = "Main Menu";
|
||||
DiscordPresence.State = "Idling";
|
||||
DiscordPresence.Assets.LargeImageKey = "ryujinx";
|
||||
DiscordPresence.Assets.LargeImageText = LargeDescription;
|
||||
DiscordPresence.Assets.SmallImageKey = null;
|
||||
DiscordPresence.Assets.SmallImageText = null;
|
||||
DiscordPresence.Timestamps = new Timestamps(DateTime.UtcNow);
|
||||
|
||||
DiscordClient?.SetPresence(DiscordPresence);
|
||||
}
|
||||
|
||||
public static void Exit()
|
||||
{
|
||||
DiscordClient?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -43,7 +43,6 @@ namespace Ryujinx
|
|||
ConfigurationState.Instance.ToFileFormat().SaveConfig(configurationPath);
|
||||
}
|
||||
|
||||
|
||||
Profile.Initialize();
|
||||
|
||||
Application.Init();
|
||||
|
|
|
@ -7,6 +7,7 @@ using LibHac.FsSystem.NcaUtils;
|
|||
using LibHac.Ncm;
|
||||
using LibHac.Spl;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Configuration.System;
|
||||
using Ryujinx.HLE.FileSystem;
|
||||
using Ryujinx.HLE.Loaders.Npdm;
|
||||
using System;
|
||||
|
@ -20,7 +21,6 @@ using Utf8Json;
|
|||
using Utf8Json.Resolvers;
|
||||
|
||||
using RightsId = LibHac.Fs.RightsId;
|
||||
using TitleLanguage = Ryujinx.HLE.HOS.SystemState.TitleLanguage;
|
||||
|
||||
namespace Ryujinx.Ui
|
||||
{
|
||||
|
@ -34,15 +34,15 @@ namespace Ryujinx.Ui
|
|||
private static readonly byte[] _nroIcon = GetResourceBytes("Ryujinx.Ui.assets.NROIcon.png");
|
||||
private static readonly byte[] _nsoIcon = GetResourceBytes("Ryujinx.Ui.assets.NSOIcon.png");
|
||||
|
||||
private static Keyset _keySet;
|
||||
private static TitleLanguage _desiredTitleLanguage;
|
||||
private static Keyset _keySet;
|
||||
private static Language _desiredTitleLanguage;
|
||||
|
||||
public static void LoadApplications(List<string> appDirs, Keyset keySet, TitleLanguage desiredTitleLanguage, FileSystemClient fsClient = null, VirtualFileSystem vfs = null)
|
||||
public static void LoadApplications(List<string> appDirs, VirtualFileSystem virtualFileSystem, Language desiredTitleLanguage)
|
||||
{
|
||||
int numApplicationsFound = 0;
|
||||
int numApplicationsLoaded = 0;
|
||||
|
||||
_keySet = keySet;
|
||||
_keySet = virtualFileSystem.KeySet;
|
||||
_desiredTitleLanguage = desiredTitleLanguage;
|
||||
|
||||
// Builds the applications list with paths to found applications
|
||||
|
@ -346,11 +346,11 @@ namespace Ryujinx.Ui
|
|||
filter.SetUserId(new UserId(1, 0));
|
||||
filter.SetTitleId(new TitleId(titleIdNum));
|
||||
|
||||
Result result = fsClient.FindSaveDataWithFilter(out SaveDataInfo saveDataInfo, SaveDataSpaceId.User, ref filter);
|
||||
Result result = virtualFileSystem.FsClient.FindSaveDataWithFilter(out SaveDataInfo saveDataInfo, SaveDataSpaceId.User, ref filter);
|
||||
|
||||
if (result.IsSuccess())
|
||||
{
|
||||
saveDataPath = Path.Combine(vfs.GetNandPath(), $"user/save/{saveDataInfo.SaveDataId:x16}");
|
||||
saveDataPath = Path.Combine(virtualFileSystem.GetNandPath(), $"user/save/{saveDataInfo.SaveDataId:x16}");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -45,14 +45,20 @@ namespace Ryujinx.Ui
|
|||
private ProfileWindowManager _profileWindow;
|
||||
#endif
|
||||
|
||||
public GlScreen(Switch device, Renderer renderer)
|
||||
public GlScreen(Switch device)
|
||||
: base(1280, 720,
|
||||
new GraphicsMode(), "Ryujinx", 0,
|
||||
DisplayDevice.Default, 3, 3,
|
||||
GraphicsContextFlags.ForwardCompatible)
|
||||
{
|
||||
_device = device;
|
||||
_renderer = renderer;
|
||||
_device = device;
|
||||
|
||||
if (!(device.Gpu.Renderer is Renderer))
|
||||
{
|
||||
throw new NotSupportedException($"GPU renderer must be an OpenGL renderer when using GlScreen!");
|
||||
}
|
||||
|
||||
_renderer = (Renderer)device.Gpu.Renderer;
|
||||
|
||||
_primaryController = new Input.NpadController(ConfigurationState.Instance.Hid.JoystickControls);
|
||||
|
||||
|
@ -108,7 +114,6 @@ namespace Ryujinx.Ui
|
|||
}
|
||||
|
||||
_device.DisposeGpu();
|
||||
_renderer.Dispose();
|
||||
}
|
||||
|
||||
public void MainLoop()
|
||||
|
|
|
@ -3,8 +3,10 @@ using JsonPrettyPrinterPlus;
|
|||
using Ryujinx.Audio;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Configuration;
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using Ryujinx.Graphics.OpenGL;
|
||||
using Ryujinx.HLE.FileSystem;
|
||||
using Ryujinx.HLE.FileSystem.Content;
|
||||
using Ryujinx.Profiler;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
|
@ -22,11 +24,10 @@ namespace Ryujinx.Ui
|
|||
{
|
||||
public class MainWindow : Window
|
||||
{
|
||||
private static HLE.Switch _device;
|
||||
private static VirtualFileSystem _virtualFileSystem;
|
||||
private static ContentManager _contentManager;
|
||||
|
||||
private static Renderer _renderer;
|
||||
|
||||
private static IAalOutput _audioOut;
|
||||
private static HLE.Switch _emulationContext;
|
||||
|
||||
private static GlScreen _screen;
|
||||
|
||||
|
@ -75,29 +76,29 @@ namespace Ryujinx.Ui
|
|||
|
||||
_gameTable.ButtonReleaseEvent += Row_Clicked;
|
||||
|
||||
// First we check that a migration isn't needed. (because VirtualFileSystem will create the new directory otherwise)
|
||||
bool continueWithStartup = Migration.PromptIfMigrationNeededForStartup(this, out bool migrationNeeded);
|
||||
if (!continueWithStartup)
|
||||
{
|
||||
End();
|
||||
End(null);
|
||||
}
|
||||
|
||||
_renderer = new Renderer();
|
||||
|
||||
_audioOut = InitializeAudioEngine();
|
||||
|
||||
// TODO: Initialization and dispose of HLE.Switch when starting/stoping emulation.
|
||||
_device = InitializeSwitchInstance();
|
||||
_virtualFileSystem = new VirtualFileSystem();
|
||||
_contentManager = new ContentManager(_virtualFileSystem);
|
||||
|
||||
if (migrationNeeded)
|
||||
{
|
||||
bool migrationSuccessful = Migration.DoMigrationForStartup(this, _device);
|
||||
bool migrationSuccessful = Migration.DoMigrationForStartup(this, _virtualFileSystem);
|
||||
|
||||
if (!migrationSuccessful)
|
||||
{
|
||||
End();
|
||||
End(null);
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure that everything is loaded.
|
||||
_virtualFileSystem.Reload();
|
||||
|
||||
_treeView = _gameTable;
|
||||
|
||||
ApplyTheme();
|
||||
|
@ -199,7 +200,9 @@ namespace Ryujinx.Ui
|
|||
|
||||
private HLE.Switch InitializeSwitchInstance()
|
||||
{
|
||||
HLE.Switch instance = new HLE.Switch(_renderer, _audioOut);
|
||||
_virtualFileSystem.Reload();
|
||||
|
||||
HLE.Switch instance = new HLE.Switch(_virtualFileSystem, _contentManager, InitializeRenderer(), InitializeAudioEngine());
|
||||
|
||||
instance.Initialize();
|
||||
|
||||
|
@ -218,8 +221,7 @@ namespace Ryujinx.Ui
|
|||
_tableStore.Clear();
|
||||
|
||||
await Task.Run(() => ApplicationLibrary.LoadApplications(ConfigurationState.Instance.Ui.GameDirs,
|
||||
_device.System.KeySet, _device.System.State.DesiredTitleLanguage, _device.System.FsClient,
|
||||
_device.FileSystem));
|
||||
_virtualFileSystem, ConfigurationState.Instance.System.Language));
|
||||
|
||||
_updatingGameTable = false;
|
||||
}
|
||||
|
@ -234,8 +236,10 @@ namespace Ryujinx.Ui
|
|||
{
|
||||
Logger.RestartTime();
|
||||
|
||||
HLE.Switch device = InitializeSwitchInstance();
|
||||
|
||||
// TODO: Move this somewhere else + reloadable?
|
||||
Ryujinx.Graphics.Gpu.GraphicsConfig.ShadersDumpPath = ConfigurationState.Instance.Graphics.ShadersDumpPath;
|
||||
Graphics.Gpu.GraphicsConfig.ShadersDumpPath = ConfigurationState.Instance.Graphics.ShadersDumpPath;
|
||||
|
||||
if (Directory.Exists(path))
|
||||
{
|
||||
|
@ -249,12 +253,12 @@ namespace Ryujinx.Ui
|
|||
if (romFsFiles.Length > 0)
|
||||
{
|
||||
Logger.PrintInfo(LogClass.Application, "Loading as cart with RomFS.");
|
||||
_device.LoadCart(path, romFsFiles[0]);
|
||||
device.LoadCart(path, romFsFiles[0]);
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.PrintInfo(LogClass.Application, "Loading as cart WITHOUT RomFS.");
|
||||
_device.LoadCart(path);
|
||||
device.LoadCart(path);
|
||||
}
|
||||
}
|
||||
else if (File.Exists(path))
|
||||
|
@ -263,22 +267,22 @@ namespace Ryujinx.Ui
|
|||
{
|
||||
case ".xci":
|
||||
Logger.PrintInfo(LogClass.Application, "Loading as XCI.");
|
||||
_device.LoadXci(path);
|
||||
device.LoadXci(path);
|
||||
break;
|
||||
case ".nca":
|
||||
Logger.PrintInfo(LogClass.Application, "Loading as NCA.");
|
||||
_device.LoadNca(path);
|
||||
device.LoadNca(path);
|
||||
break;
|
||||
case ".nsp":
|
||||
case ".pfs0":
|
||||
Logger.PrintInfo(LogClass.Application, "Loading as NSP.");
|
||||
_device.LoadNsp(path);
|
||||
device.LoadNsp(path);
|
||||
break;
|
||||
default:
|
||||
Logger.PrintInfo(LogClass.Application, "Loading as homebrew.");
|
||||
try
|
||||
{
|
||||
_device.LoadProgram(path);
|
||||
device.LoadProgram(path);
|
||||
}
|
||||
catch (ArgumentOutOfRangeException)
|
||||
{
|
||||
|
@ -290,13 +294,15 @@ namespace Ryujinx.Ui
|
|||
else
|
||||
{
|
||||
Logger.PrintWarning(LogClass.Application, "Please specify a valid XCI/NCA/NSP/PFS0/NRO file.");
|
||||
End();
|
||||
End(device);
|
||||
}
|
||||
|
||||
_emulationContext = device;
|
||||
|
||||
#if MACOS_BUILD
|
||||
CreateGameWindow();
|
||||
CreateGameWindow(device);
|
||||
#else
|
||||
new Thread(CreateGameWindow).Start();
|
||||
new Thread(() => CreateGameWindow(device)).Start();
|
||||
#endif
|
||||
|
||||
_gameLoaded = true;
|
||||
|
@ -305,28 +311,55 @@ namespace Ryujinx.Ui
|
|||
_firmwareInstallFile.Sensitive = false;
|
||||
_firmwareInstallDirectory.Sensitive = false;
|
||||
|
||||
DiscordIntegrationModule.SwitchToPlayingState(_device.System.TitleIdText, _device.System.TitleName);
|
||||
DiscordIntegrationModule.SwitchToPlayingState(device.System.TitleIdText, device.System.TitleName);
|
||||
|
||||
ApplicationLibrary.LoadAndSaveMetaData(_device.System.TitleIdText, appMetadata =>
|
||||
ApplicationLibrary.LoadAndSaveMetaData(device.System.TitleIdText, appMetadata =>
|
||||
{
|
||||
appMetadata.LastPlayed = DateTime.UtcNow.ToString();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private static void CreateGameWindow()
|
||||
private void CreateGameWindow(HLE.Switch device)
|
||||
{
|
||||
_device.Hid.InitializePrimaryController(ConfigurationState.Instance.Hid.ControllerType);
|
||||
device.Hid.InitializePrimaryController(ConfigurationState.Instance.Hid.ControllerType);
|
||||
|
||||
using (_screen = new GlScreen(_device, _renderer))
|
||||
using (_screen = new GlScreen(device))
|
||||
{
|
||||
_screen.MainLoop();
|
||||
}
|
||||
|
||||
End();
|
||||
device.Dispose();
|
||||
|
||||
_emulationContext = null;
|
||||
_screen = null;
|
||||
_gameLoaded = false;
|
||||
|
||||
DiscordIntegrationModule.SwitchToMainMenu();
|
||||
|
||||
Application.Invoke(delegate
|
||||
{
|
||||
_stopEmulation.Sensitive = false;
|
||||
_firmwareInstallFile.Sensitive = true;
|
||||
_firmwareInstallDirectory.Sensitive = true;
|
||||
});
|
||||
}
|
||||
|
||||
private static void UpdateGameMetadata(string titleId)
|
||||
{
|
||||
if (_gameLoaded)
|
||||
{
|
||||
ApplicationLibrary.LoadAndSaveMetaData(titleId, appMetadata =>
|
||||
{
|
||||
DateTime lastPlayedDateTime = DateTime.Parse(appMetadata.LastPlayed);
|
||||
double sessionTimePlayed = DateTime.UtcNow.Subtract(lastPlayedDateTime).TotalSeconds;
|
||||
|
||||
appMetadata.TimePlayed += Math.Round(sessionTimePlayed, MidpointRounding.AwayFromZero);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private static void End()
|
||||
private void End(HLE.Switch device)
|
||||
{
|
||||
if (_ending)
|
||||
{
|
||||
|
@ -335,22 +368,23 @@ namespace Ryujinx.Ui
|
|||
|
||||
_ending = true;
|
||||
|
||||
if (_gameLoaded)
|
||||
if (device != null)
|
||||
{
|
||||
ApplicationLibrary.LoadAndSaveMetaData(_device.System.TitleIdText, appMetadata =>
|
||||
{
|
||||
DateTime lastPlayedDateTime = DateTime.Parse(appMetadata.LastPlayed);
|
||||
double sessionTimePlayed = DateTime.UtcNow.Subtract(lastPlayedDateTime).TotalSeconds;
|
||||
|
||||
appMetadata.TimePlayed += Math.Round(sessionTimePlayed, MidpointRounding.AwayFromZero);
|
||||
});
|
||||
UpdateGameMetadata(device.System.TitleIdText);
|
||||
}
|
||||
|
||||
Dispose();
|
||||
|
||||
Profile.FinishProfiling();
|
||||
_device?.Dispose();
|
||||
_audioOut?.Dispose();
|
||||
device?.Dispose();
|
||||
DiscordIntegrationModule.Exit();
|
||||
Logger.Shutdown();
|
||||
Environment.Exit(0);
|
||||
Application.Quit();
|
||||
}
|
||||
|
||||
private static IRenderer InitializeRenderer()
|
||||
{
|
||||
return new Renderer();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -427,7 +461,7 @@ namespace Ryujinx.Ui
|
|||
|
||||
if (treeIter.UserData == IntPtr.Zero) return;
|
||||
|
||||
GameTableContextMenu contextMenu = new GameTableContextMenu(_tableStore, treeIter, _device.System.FsClient);
|
||||
GameTableContextMenu contextMenu = new GameTableContextMenu(_tableStore, treeIter, _virtualFileSystem.FsClient);
|
||||
contextMenu.ShowAll();
|
||||
contextMenu.PopupAtPointer(null);
|
||||
}
|
||||
|
@ -477,20 +511,18 @@ namespace Ryujinx.Ui
|
|||
private void Exit_Pressed(object sender, EventArgs args)
|
||||
{
|
||||
_screen?.Exit();
|
||||
End();
|
||||
End(_emulationContext);
|
||||
}
|
||||
|
||||
private void Window_Close(object sender, DeleteEventArgs args)
|
||||
{
|
||||
_screen?.Exit();
|
||||
End();
|
||||
End(_emulationContext);
|
||||
}
|
||||
|
||||
private void StopEmulation_Pressed(object sender, EventArgs args)
|
||||
{
|
||||
// TODO: Write logic to kill running game
|
||||
|
||||
_gameLoaded = false;
|
||||
_screen?.Exit();
|
||||
}
|
||||
|
||||
private void Installer_File_Pressed(object o, EventArgs args)
|
||||
|
@ -525,7 +557,7 @@ namespace Ryujinx.Ui
|
|||
|
||||
private void RefreshFirmwareLabel()
|
||||
{
|
||||
var currentFirmware = _device.System.GetCurrentFirmwareVersion();
|
||||
var currentFirmware = _contentManager.GetCurrentFirmwareVersion();
|
||||
|
||||
GLib.Idle.Add(new GLib.IdleHandler(() =>
|
||||
{
|
||||
|
@ -547,7 +579,7 @@ namespace Ryujinx.Ui
|
|||
|
||||
fileChooser.Dispose();
|
||||
|
||||
var firmwareVersion = _device.System.VerifyFirmwarePackage(filename);
|
||||
var firmwareVersion = _contentManager.VerifyFirmwarePackage(filename);
|
||||
|
||||
if (firmwareVersion == null)
|
||||
{
|
||||
|
@ -566,7 +598,7 @@ namespace Ryujinx.Ui
|
|||
return;
|
||||
}
|
||||
|
||||
var currentVersion = _device.System.GetCurrentFirmwareVersion();
|
||||
var currentVersion = _contentManager.GetCurrentFirmwareVersion();
|
||||
|
||||
string dialogMessage = $"System version {firmwareVersion.VersionString} will be installed.";
|
||||
|
||||
|
@ -606,7 +638,7 @@ namespace Ryujinx.Ui
|
|||
|
||||
try
|
||||
{
|
||||
_device.System.InstallFirmware(filename);
|
||||
_contentManager.InstallFirmware(filename);
|
||||
|
||||
GLib.Idle.Add(new GLib.IdleHandler(() =>
|
||||
{
|
||||
|
|
|
@ -1,21 +1,20 @@
|
|||
using Gtk;
|
||||
using LibHac;
|
||||
using Ryujinx.HLE.FileSystem;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
using Switch = Ryujinx.HLE.Switch;
|
||||
|
||||
namespace Ryujinx.Ui
|
||||
{
|
||||
internal class Migration
|
||||
{
|
||||
private Switch Device { get; }
|
||||
private VirtualFileSystem _virtualFileSystem;
|
||||
|
||||
public Migration(Switch device)
|
||||
public Migration(VirtualFileSystem virtualFileSystem)
|
||||
{
|
||||
Device = device;
|
||||
_virtualFileSystem = virtualFileSystem;
|
||||
}
|
||||
|
||||
public static bool PromptIfMigrationNeededForStartup(Window parentWindow, out bool isMigrationNeeded)
|
||||
|
@ -48,11 +47,11 @@ namespace Ryujinx.Ui
|
|||
return dialogResponse == (int)ResponseType.Yes;
|
||||
}
|
||||
|
||||
public static bool DoMigrationForStartup(Window parentWindow, Switch device)
|
||||
public static bool DoMigrationForStartup(MainWindow parentWindow, VirtualFileSystem virtualFileSystem)
|
||||
{
|
||||
try
|
||||
{
|
||||
Migration migration = new Migration(device);
|
||||
Migration migration = new Migration(virtualFileSystem);
|
||||
int saveCount = migration.Migrate();
|
||||
|
||||
using MessageDialog dialogSuccess = new MessageDialog(parentWindow, DialogFlags.Modal, MessageType.Info, ButtonsType.Ok, null)
|
||||
|
@ -64,9 +63,6 @@ namespace Ryujinx.Ui
|
|||
|
||||
dialogSuccess.Run();
|
||||
|
||||
// Reload key set after migration to be sure to catch the keys in the system directory.
|
||||
device.System.LoadKeySet();
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (HorizonResultException ex)
|
||||
|
@ -80,6 +76,9 @@ namespace Ryujinx.Ui
|
|||
// Returns the number of saves migrated
|
||||
public int Migrate()
|
||||
{
|
||||
// Make sure FsClient is initialized
|
||||
_virtualFileSystem.Reload();
|
||||
|
||||
string appDataPath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
|
||||
|
||||
string oldBasePath = Path.Combine(appDataPath, "RyuFs");
|
||||
|
@ -89,7 +88,7 @@ namespace Ryujinx.Ui
|
|||
|
||||
CopyRyuFs(oldBasePath, newBasePath);
|
||||
|
||||
SaveImporter importer = new SaveImporter(oldSaveDir, Device.System.FsClient);
|
||||
SaveImporter importer = new SaveImporter(oldSaveDir, _virtualFileSystem.FsClient);
|
||||
|
||||
return importer.Import();
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue