Compare commits

...

4 commits

Author SHA1 Message Date
mqudsi
62d55dcd4e Merge branch 'ExcludePausedTime' into 'master'
Exclude time spent with emulator paused from play time

See merge request [ryubing/ryujinx!55](https://git.ryujinx.app/ryubing/ryujinx/-/merge_requests/55)
2025-06-18 01:19:08 -05:00
yeager
b1cde5fd97 Updated Swedish translation (ryubing/ryujinx!66)
See merge request ryubing/ryujinx!66
2025-06-17 13:05:39 -05:00
Mahmoud Al-Qudsi
32ae313d77 Rework updates of application metadata gameplay hours
Rely on `AppHost` to more accurately track active gameplay time instead
of doing naive DateTime.UtcNow subtraction in UpdatePre/UpdatePostGame()
calls.
2025-06-11 15:52:28 -05:00
Mahmoud Al-Qudsi
b5c82f8a5b Exclude time spent with emulator paused from play time
With this change, the play time reported internally by a game's gameplay
timer should match (or be much closer to matching) what Ryujinx displays
in the application library.

Aside from this being closer to the natural expectation of what "hours
played" would take into account (as by definition time spent paused is
time spent not playing), this also brings us closer to the behavior of
other emulators and game libraries.
2025-06-11 15:22:05 -05:00
4 changed files with 20 additions and 21 deletions

View file

@ -2089,7 +2089,7 @@
"pl_PL": "Całkowity czas gry: {0}", "pl_PL": "Całkowity czas gry: {0}",
"pt_BR": "Tempo total de jogo: {0}", "pt_BR": "Tempo total de jogo: {0}",
"ru_RU": "", "ru_RU": "",
"sv_SE": "", "sv_SE": "Total speltid: {0}",
"th_TH": "", "th_TH": "",
"tr_TR": "Toplam Oyun Süresi: {0}", "tr_TR": "Toplam Oyun Süresi: {0}",
"uk_UA": "", "uk_UA": "",
@ -7214,7 +7214,7 @@
"pl_PL": "", "pl_PL": "",
"pt_BR": "Configuração encontrada:\n\nNome:\t{0}\nGUID:\t{1}\n\n Aguardando conexão do controle...", "pt_BR": "Configuração encontrada:\n\nNome:\t{0}\nGUID:\t{1}\n\n Aguardando conexão do controle...",
"ru_RU": "", "ru_RU": "",
"sv_SE": "", "sv_SE": "Konfiguration hittad:\n\nNamn:\t{0}\nGUID:\t{1}\n\n Väntar på anslutning till kontroller...",
"th_TH": "", "th_TH": "",
"tr_TR": "", "tr_TR": "",
"uk_UA": "", "uk_UA": "",
@ -13139,7 +13139,7 @@
"pl_PL": "", "pl_PL": "",
"pt_BR": "Falha em atualizar a versão do Ryujinx recebida do servidor de atualização.", "pt_BR": "Falha em atualizar a versão do Ryujinx recebida do servidor de atualização.",
"ru_RU": "", "ru_RU": "",
"sv_SE": "", "sv_SE": "Det gick inte att konvertera Ryujinx-versionen som mottogs från uppdateringsservern.",
"th_TH": "", "th_TH": "",
"tr_TR": "", "tr_TR": "",
"uk_UA": "", "uk_UA": "",
@ -16564,7 +16564,7 @@
"pl_PL": "", "pl_PL": "",
"pt_BR": "Se esta opção está ativada nas configurações customizadas, as configurações globais de entrada serão usadas.\n\nNas configurações globais: você pode ativar ou desativá-las se necessário; está configuração será herdada por qualquer nova configuração customizada criada.", "pt_BR": "Se esta opção está ativada nas configurações customizadas, as configurações globais de entrada serão usadas.\n\nNas configurações globais: você pode ativar ou desativá-las se necessário; está configuração será herdada por qualquer nova configuração customizada criada.",
"ru_RU": "Если эта опция включена в пользовательских настройках, будет использована глобальная конфигурация ввода.\n\nВ глобальных настройках: переключите эту опцию по своему усмотрению, это будет унаследовано для вновь созданых пользовательских конфигураций", "ru_RU": "Если эта опция включена в пользовательских настройках, будет использована глобальная конфигурация ввода.\n\nВ глобальных настройках: переключите эту опцию по своему усмотрению, это будет унаследовано для вновь созданых пользовательских конфигураций",
"sv_SE": "", "sv_SE": "Om det här alternativet är aktiverat i anpassade inställningar kommer den globala inmatningskonfigurationen att användas.\n\nI de globala inställningarna: du kan aktivera eller inaktivera det efter behov; den här inställningen kommer att ärvas av alla nya anpassade konfigurationer som skapas.",
"th_TH": "", "th_TH": "",
"tr_TR": "", "tr_TR": "",
"uk_UA": "", "uk_UA": "",
@ -23439,7 +23439,7 @@
"pl_PL": "", "pl_PL": "",
"pt_BR": "Ver Registro", "pt_BR": "Ver Registro",
"ru_RU": "", "ru_RU": "",
"sv_SE": "", "sv_SE": "Visa ändringslogg",
"th_TH": "ด", "th_TH": "ด",
"tr_TR": "", "tr_TR": "",
"uk_UA": "", "uk_UA": "",
@ -24539,7 +24539,7 @@
"pl_PL": "Gry i Aplikacje", "pl_PL": "Gry i Aplikacje",
"pt_BR": "Jogos e Aplicativos", "pt_BR": "Jogos e Aplicativos",
"ru_RU": "Игры и Приложения", "ru_RU": "Игры и Приложения",
"sv_SE": "Spel och Applikationer", "sv_SE": "Spel och applikationer",
"th_TH": "", "th_TH": "",
"tr_TR": "Oyunlar ve Uygulamalar", "tr_TR": "Oyunlar ve Uygulamalar",
"uk_UA": "Ігри та Додатки", "uk_UA": "Ігри та Додатки",
@ -24589,7 +24589,7 @@
"pl_PL": "Problemy i Cechy", "pl_PL": "Problemy i Cechy",
"pt_BR": "Problemas e Características", "pt_BR": "Problemas e Características",
"ru_RU": "Проблемы и Особенности", "ru_RU": "Проблемы и Особенности",
"sv_SE": "Problem och Egenskaper", "sv_SE": "Problem och egenskaper",
"th_TH": "", "th_TH": "",
"tr_TR": "Sorunlar ve Özellikler", "tr_TR": "Sorunlar ve Özellikler",
"uk_UA": "Проблеми та Особливості", "uk_UA": "Проблеми та Особливості",

View file

@ -75,6 +75,7 @@ namespace Ryujinx.Ava.Systems
private readonly long _ticksPerFrame; private readonly long _ticksPerFrame;
private readonly Stopwatch _chrono; private readonly Stopwatch _chrono;
private readonly Stopwatch _playTimer;
private long _ticks; private long _ticks;
private readonly AccountManager _accountManager; private readonly AccountManager _accountManager;
@ -175,6 +176,7 @@ namespace Ryujinx.Ava.Systems
_chrono = new Stopwatch(); _chrono = new Stopwatch();
_ticksPerFrame = Stopwatch.Frequency / TargetFps; _ticksPerFrame = Stopwatch.Frequency / TargetFps;
_playTimer = new Stopwatch();
if (ApplicationPath.StartsWith("@SystemContent")) if (ApplicationPath.StartsWith("@SystemContent"))
{ {
@ -565,6 +567,7 @@ namespace Ryujinx.Ava.Systems
public void Stop() public void Stop()
{ {
_isActive = false; _isActive = false;
_playTimer.Stop();
} }
private void Exit() private void Exit()
@ -616,7 +619,7 @@ namespace Ryujinx.Ava.Systems
private void Dispose() private void Dispose()
{ {
if (Device.Processes != null) if (Device.Processes != null)
MainWindowViewModel.UpdateGameMetadata(Device.Processes.ActiveApplication.ProgramIdText); MainWindowViewModel.UpdateGameMetadata(Device.Processes.ActiveApplication.ProgramIdText, _playTimer.Elapsed);
ConfigurationState.Instance.System.IgnoreMissingServices.Event -= UpdateIgnoreMissingServicesState; ConfigurationState.Instance.System.IgnoreMissingServices.Event -= UpdateIgnoreMissingServicesState;
ConfigurationState.Instance.Graphics.AspectRatio.Event -= UpdateAspectRatioState; ConfigurationState.Instance.Graphics.AspectRatio.Event -= UpdateAspectRatioState;
@ -635,6 +638,7 @@ namespace Ryujinx.Ava.Systems
_gpuCancellationTokenSource.Dispose(); _gpuCancellationTokenSource.Dispose();
_chrono.Stop(); _chrono.Stop();
_playTimer.Stop();
} }
public void DisposeGpu() public void DisposeGpu()
@ -868,6 +872,7 @@ namespace Ryujinx.Ava.Systems
ApplicationLibrary.LoadAndSaveMetaData(Device.Processes.ActiveApplication.ProgramIdText, ApplicationLibrary.LoadAndSaveMetaData(Device.Processes.ActiveApplication.ProgramIdText,
appMetadata => appMetadata.UpdatePreGame() appMetadata => appMetadata.UpdatePreGame()
); );
_playTimer.Start();
return true; return true;
} }
@ -877,6 +882,7 @@ namespace Ryujinx.Ava.Systems
Device?.System.TogglePauseEmulation(false); Device?.System.TogglePauseEmulation(false);
_viewModel.IsPaused = false; _viewModel.IsPaused = false;
_playTimer.Start();
_viewModel.Title = TitleHelper.ActiveApplicationTitle(Device?.Processes.ActiveApplication, Program.Version, !ConfigurationState.Instance.ShowOldUI); _viewModel.Title = TitleHelper.ActiveApplicationTitle(Device?.Processes.ActiveApplication, Program.Version, !ConfigurationState.Instance.ShowOldUI);
Logger.Info?.Print(LogClass.Emulation, "Emulation was resumed"); Logger.Info?.Print(LogClass.Emulation, "Emulation was resumed");
} }
@ -886,6 +892,7 @@ namespace Ryujinx.Ava.Systems
Device?.System.TogglePauseEmulation(true); Device?.System.TogglePauseEmulation(true);
_viewModel.IsPaused = true; _viewModel.IsPaused = true;
_playTimer.Stop();
_viewModel.Title = TitleHelper.ActiveApplicationTitle(Device?.Processes.ActiveApplication, Program.Version, !ConfigurationState.Instance.ShowOldUI, LocaleManager.Instance[LocaleKeys.Paused]); _viewModel.Title = TitleHelper.ActiveApplicationTitle(Device?.Processes.ActiveApplication, Program.Version, !ConfigurationState.Instance.ShowOldUI, LocaleManager.Instance[LocaleKeys.Paused]);
Logger.Info?.Print(LogClass.Emulation, "Emulation was paused"); Logger.Info?.Print(LogClass.Emulation, "Emulation was paused");
} }

View file

@ -33,19 +33,11 @@ namespace Ryujinx.Ava.Systems.AppLibrary
/// <summary> /// <summary>
/// Updates <see cref="LastPlayed"/> and <see cref="TimePlayed"/>. Call this after a game ends. /// Updates <see cref="LastPlayed"/> and <see cref="TimePlayed"/>. Call this after a game ends.
/// </summary> /// </summary>
public void UpdatePostGame() /// <param name="playTime">The active gameplay time this past session.</param>
public void UpdatePostGame(TimeSpan playTime)
{ {
DateTime? prevLastPlayed = LastPlayed;
UpdatePreGame(); UpdatePreGame();
TimePlayed += playTime;
if (!prevLastPlayed.HasValue)
{
return;
}
TimeSpan diff = DateTime.UtcNow - prevLastPlayed.Value;
double newTotalSeconds = TimePlayed.Add(diff).TotalSeconds;
TimePlayed = TimeSpan.FromSeconds(Math.Round(newTotalSeconds, MidpointRounding.AwayFromZero));
} }
} }
} }

View file

@ -1688,8 +1688,8 @@ namespace Ryujinx.Ava.UI.ViewModels
RendererHostControl.Focus(); RendererHostControl.Focus();
}); });
public static void UpdateGameMetadata(string titleId) public static void UpdateGameMetadata(string titleId, TimeSpan playTime)
=> ApplicationLibrary.LoadAndSaveMetaData(titleId, appMetadata => appMetadata.UpdatePostGame()); => ApplicationLibrary.LoadAndSaveMetaData(titleId, appMetadata => appMetadata.UpdatePostGame(playTime));
public void RefreshFirmwareStatus() public void RefreshFirmwareStatus()
{ {