Compare commits

...

4 Commits

Author SHA1 Message Date
Aneesh Maganti 924b17371d
Merge d232c689cb into 2c83a256ae 2025-01-16 10:59:50 -05:00
Aneesh Maganti d232c689cb Add locks to game metadata 2025-01-14 15:50:54 -05:00
aminoa b54bf24a4e used Common::Event, new constructor for TimeTracker 2025-01-13 16:49:46 -05:00
Admiral H. Curtiss c3086f24c9 Track Time Played (Core and QT)
Co-authored-by: aminoa <28660350+aminoa@users.noreply.github.com>
2025-01-13 16:49:31 -05:00
14 changed files with 216 additions and 6 deletions

View File

@ -541,6 +541,8 @@ add_library(core
SysConf.h
System.cpp
System.h
TimePlayed.cpp
TimePlayed.h
TitleDatabase.cpp
TitleDatabase.h
WC24PatchEngine.cpp

View File

@ -459,6 +459,8 @@ const Info<bool> MAIN_GAMELIST_COLUMN_BLOCK_SIZE{{System::Main, "GameList", "Col
false};
const Info<bool> MAIN_GAMELIST_COLUMN_COMPRESSION{{System::Main, "GameList", "ColumnCompression"},
false};
const Info<bool> MAIN_GAMELIST_COLUMN_TIME_PLAYED{{System::Main, "GameList", "ColumnTimePlayed"},
true};
const Info<bool> MAIN_GAMELIST_COLUMN_TAGS{{System::Main, "GameList", "ColumnTags"}, false};
// Main.FifoPlayer

View File

@ -295,6 +295,7 @@ extern const Info<bool> MAIN_GAMELIST_COLUMN_FILE_SIZE;
extern const Info<bool> MAIN_GAMELIST_COLUMN_FILE_FORMAT;
extern const Info<bool> MAIN_GAMELIST_COLUMN_BLOCK_SIZE;
extern const Info<bool> MAIN_GAMELIST_COLUMN_COMPRESSION;
extern const Info<bool> MAIN_GAMELIST_COLUMN_TIME_PLAYED;
extern const Info<bool> MAIN_GAMELIST_COLUMN_TAGS;
// Main.FifoPlayer

View File

@ -6,6 +6,7 @@
#include <algorithm>
#include <climits>
#include <memory>
#include <mutex>
#include <optional>
#include <sstream>
#include <string>
@ -98,6 +99,42 @@ void SConfig::LoadSettings()
Config::Load();
}
const std::string& SConfig::GetGameID() const
{
std::lock_guard<std::mutex> lock(m_metadata_lock);
return m_game_id;
}
const std::string& SConfig::GetGameTDBID() const
{
std::lock_guard<std::mutex> lock(m_metadata_lock);
return m_gametdb_id;
}
const std::string& SConfig::GetTitleName() const
{
std::lock_guard<std::mutex> lock(m_metadata_lock);
return m_title_name;
}
const std::string& SConfig::GetTitleDescription() const
{
std::lock_guard<std::mutex> lock(m_metadata_lock);
return m_title_description;
}
u64 SConfig::GetTitleID() const
{
std::lock_guard<std::mutex> lock(m_metadata_lock);
return m_title_id;
}
u16 SConfig::GetRevision() const
{
std::lock_guard<std::mutex> lock(m_metadata_lock);
return m_revision;
}
void SConfig::ResetRunningGameMetadata()
{
SetRunningGameMetadata("00000000", "", 0, 0, DiscIO::Region::Unknown);

View File

@ -4,6 +4,7 @@
#pragma once
#include <limits>
#include <mutex>
#include <optional>
#include <set>
#include <string>
@ -58,15 +59,16 @@ struct SConfig
std::string m_strSRAM;
std::string m_debugger_game_id;
// TODO: remove this as soon as the ticket view hack in IOS/ES/Views is dropped.
bool m_disc_booted_from_game_list = false;
const std::string& GetGameID() const { return m_game_id; }
const std::string& GetGameTDBID() const { return m_gametdb_id; }
const std::string& GetTitleName() const { return m_title_name; }
const std::string& GetTitleDescription() const { return m_title_description; }
u64 GetTitleID() const { return m_title_id; }
u16 GetRevision() const { return m_revision; }
const std::string& GetGameID() const;
const std::string& GetGameTDBID() const;
const std::string& GetTitleName() const;
const std::string& GetTitleDescription() const;
u64 GetTitleID() const;
u16 GetRevision() const;
void ResetRunningGameMetadata();
void SetRunningGameMetadata(const DiscIO::Volume& volume, const DiscIO::Partition& partition);
void SetRunningGameMetadata(const IOS::ES::TMDReader& tmd, DiscIO::Platform platform);
@ -114,6 +116,7 @@ private:
u64 title_id, u16 revision, DiscIO::Region region);
static SConfig* m_Instance;
mutable std::mutex m_metadata_lock;
std::string m_game_id;
std::string m_gametdb_id;

View File

@ -10,16 +10,20 @@
#include "AudioCommon/AudioCommon.h"
#include "Common/CommonTypes.h"
#include "Common/Event.h"
#include "Common/Timer.h"
#include "Core/ConfigManager.h"
#include "Core/CPUThreadConfigCallback.h"
#include "Core/Core.h"
#include "Core/Host.h"
#include "Core/PowerPC/GDBStub.h"
#include "Core/PowerPC/PowerPC.h"
#include "Core/System.h"
#include "Core/TimePlayed.h"
#include "VideoCommon/Fifo.h"
namespace CPU
{
CPUManager::CPUManager(Core::System& system) : m_system(system)
{
}
@ -63,6 +67,34 @@ void CPUManager::ExecutePendingJobs(std::unique_lock<std::mutex>& state_lock)
}
}
void CPUManager::StartTimePlayedTimer()
{
// Steady clock for greater accuracy of timing
std::chrono::steady_clock timer;
auto prev_time = timer.now();
while (true)
{
const std::string game_id = SConfig::GetInstance().GetGameID();
TimePlayed time_played(game_id);
auto curr_time = timer.now();
// Check that emulation is not paused
if (m_state == State::Running)
{
auto diff_time = std::chrono::duration_cast<std::chrono::milliseconds>(curr_time - prev_time);
time_played.AddTime(diff_time);
}
prev_time = curr_time;
if (m_state == State::PowerDown)
return;
m_time_played_finish_sync.WaitFor(std::chrono::seconds(30));
}
}
void CPUManager::Run()
{
auto& power_pc = m_system.GetPowerPC();
@ -71,6 +103,9 @@ void CPUManager::Run()
// We can't rely on PowerPC::Init doing it, since it's called from EmuThread.
PowerPC::RoundingModeUpdated(power_pc.GetPPCState());
// Start a separate time tracker thread
auto timing = std::thread(&CPUManager::StartTimePlayedTimer, this);
std::unique_lock state_lock(m_state_change_lock);
while (m_state != State::PowerDown)
{
@ -165,6 +200,11 @@ void CPUManager::Run()
break;
}
}
// m_timer_finish.notify_one();
m_time_played_finish_sync.Set();
timing.join();
state_lock.unlock();
Host_UpdateDisasmDialog();
}

View File

@ -3,12 +3,14 @@
#pragma once
#include <Common/Event.h>
#include <condition_variable>
#include <functional>
#include <mutex>
#include <queue>
namespace Common
{
class Event;
}
@ -102,6 +104,7 @@ public:
private:
void FlushStepSyncEventLocked();
void ExecutePendingJobs(std::unique_lock<std::mutex>& state_lock);
void StartTimePlayedTimer();
void RunAdjacentSystems(bool running);
bool SetStateLocked(State s);
@ -133,6 +136,7 @@ private:
bool m_state_cpu_step_instruction = false;
Common::Event* m_state_cpu_step_instruction_sync = nullptr;
std::queue<std::function<void()>> m_pending_jobs;
Common::Event m_time_played_finish_sync;
Core::System& m_system;
};

View File

@ -0,0 +1,72 @@
#pragma once
#include <string>
#include "Common/FileUtil.h"
#include "Common/IniFile.h"
#include "TimePlayed.h"
// used for QT interface - general access to time played for games
TimePlayed::TimePlayed()
{
m_game_id = "None";
ini_path = File::GetUserPath(D_CONFIG_IDX) + "TimePlayed.ini";
ini.Load(ini_path);
time_list = ini.GetOrCreateSection("Time Played");
}
void FilterUnsafeCharacters(std::string& game_id)
{
const std::string forbiddenChars = "\\/:?\"<>|";
for (auto& chr : game_id)
{
if (forbiddenChars.find(chr) != std::string::npos)
{
chr = '_';
}
}
}
TimePlayed::TimePlayed(std::string game_id)
{
// filter for unsafe characters
FilterUnsafeCharacters(game_id);
m_game_id = game_id;
ini_path = File::GetUserPath(D_CONFIG_IDX) + "TimePlayed.ini";
ini.Load(ini_path);
time_list = ini.GetOrCreateSection("Time Played");
}
void TimePlayed::AddTime(std::chrono::milliseconds time_emulated)
{
if (m_game_id == "None")
{
return;
}
u64 previous_time;
time_list->Get(m_game_id, &previous_time);
time_list->Set(m_game_id, previous_time + u64(time_emulated.count()));
ini.Save(ini_path);
}
u64 TimePlayed::GetTimePlayed()
{
if (m_game_id == "None")
{
return 0;
}
u64 previous_time;
time_list->Get(m_game_id, &previous_time);
return previous_time;
}
u64 TimePlayed::GetTimePlayed(std::string game_id)
{
FilterUnsafeCharacters(game_id);
u64 previous_time;
time_list->Get(game_id, &previous_time);
return previous_time;
}

View File

@ -0,0 +1,22 @@
#pragma once
#include "Common/CommonTypes.h"
#include "Common/IniFile.h"
class TimePlayed
{
public:
TimePlayed();
TimePlayed(std::string game_id);
void AddTime(std::chrono::milliseconds time_emulated);
u64 GetTimePlayed();
u64 GetTimePlayed(std::string game_id);
private:
std::string m_game_id;
Common::IniFile ini;
std::string ini_path;
Common::IniFile::Section* time_list;
};

View File

@ -459,6 +459,7 @@
<ClInclude Include="Core\SyncIdentifier.h" />
<ClInclude Include="Core\SysConf.h" />
<ClInclude Include="Core\System.h" />
<ClInclude Include="Core\TimePlayed.h" />
<ClInclude Include="Core\TitleDatabase.h" />
<ClInclude Include="Core\WC24PatchEngine.h" />
<ClInclude Include="Core\WiiRoot.h" />
@ -1125,6 +1126,7 @@
<ClCompile Include="Core\State.cpp" />
<ClCompile Include="Core\SysConf.cpp" />
<ClCompile Include="Core\System.cpp" />
<ClCompile Include="Core\TimePlayed.cpp" />
<ClCompile Include="Core\TitleDatabase.cpp" />
<ClCompile Include="Core\WiiRoot.cpp" />
<ClCompile Include="Core\WiiUtils.cpp" />

View File

@ -208,6 +208,7 @@ void GameList::MakeListView()
SetResizeMode(Column::FileFormat, Mode::Fixed);
SetResizeMode(Column::BlockSize, Mode::Fixed);
SetResizeMode(Column::Compression, Mode::Fixed);
SetResizeMode(Column::TimePlayed, Mode::Interactive);
SetResizeMode(Column::Tags, Mode::Interactive);
// Cells have 3 pixels of padding, so the width of these needs to be image width + 6. Banners
@ -273,6 +274,7 @@ void GameList::UpdateColumnVisibility()
SetVisiblity(Column::FileFormat, Config::Get(Config::MAIN_GAMELIST_COLUMN_FILE_FORMAT));
SetVisiblity(Column::BlockSize, Config::Get(Config::MAIN_GAMELIST_COLUMN_BLOCK_SIZE));
SetVisiblity(Column::Compression, Config::Get(Config::MAIN_GAMELIST_COLUMN_COMPRESSION));
SetVisiblity(Column::TimePlayed, Config::Get(Config::MAIN_GAMELIST_COLUMN_TIME_PLAYED));
SetVisiblity(Column::Tags, Config::Get(Config::MAIN_GAMELIST_COLUMN_TAGS));
}
@ -1005,6 +1007,7 @@ void GameList::OnColumnVisibilityToggled(const QString& row, bool visible)
{tr("File Format"), Column::FileFormat},
{tr("Block Size"), Column::BlockSize},
{tr("Compression"), Column::Compression},
{tr("Time Played"), Column::TimePlayed},
{tr("Tags"), Column::Tags},
};

View File

@ -9,6 +9,7 @@
#include <QRegularExpression>
#include "Core/Config/MainSettings.h"
#include "Core/TimePlayed.h"
#include "DiscIO/Enums.h"
@ -57,6 +58,7 @@ QVariant GameListModel::data(const QModelIndex& index, int role) const
return QVariant();
const UICommon::GameFile& game = *m_games[index.row()];
TimePlayed timer;
switch (static_cast<Column>(index.column()))
{
@ -187,6 +189,22 @@ QVariant GameListModel::data(const QModelIndex& index, int role) const
return compression.isEmpty() ? tr("No Compression") : compression;
}
break;
case Column::TimePlayed:
if (role == Qt::DisplayRole || role == SORT_ROLE)
{
std::string game_id = game.GetGameID();
std::chrono::milliseconds total_time(timer.GetTimePlayed(game_id));
std::chrono::minutes total_minutes =
std::chrono::duration_cast<std::chrono::minutes>(total_time);
std::chrono::hours total_hours = std::chrono::duration_cast<std::chrono::hours>(total_time);
// i18n: A time displayed as hours and minutes
QString formatted_time = tr("%1h %2m")
.arg(total_hours.count())
.arg(total_minutes.count() - total_hours.count() * 60);
return formatted_time;
}
break;
case Column::Tags:
if (role == Qt::DisplayRole || role == SORT_ROLE)
{
@ -232,6 +250,8 @@ QVariant GameListModel::headerData(int section, Qt::Orientation orientation, int
return tr("Block Size");
case Column::Compression:
return tr("Compression");
case Column::TimePlayed:
return tr("Time Played");
case Column::Tags:
return tr("Tags");
default:

View File

@ -58,6 +58,7 @@ public:
FileFormat,
BlockSize,
Compression,
TimePlayed,
Tags,
Count,
};

View File

@ -701,6 +701,7 @@ void MenuBar::AddListColumnsMenu(QMenu* view_menu)
{tr("File Format"), &Config::MAIN_GAMELIST_COLUMN_FILE_FORMAT},
{tr("Block Size"), &Config::MAIN_GAMELIST_COLUMN_BLOCK_SIZE},
{tr("Compression"), &Config::MAIN_GAMELIST_COLUMN_COMPRESSION},
{tr("Time Played"), &Config::MAIN_GAMELIST_COLUMN_TIME_PLAYED},
{tr("Tags"), &Config::MAIN_GAMELIST_COLUMN_TAGS}};
QActionGroup* column_group = new QActionGroup(this);