Add the basics of a played-time tracker.

This commit is contained in:
Henrik Rydgård committed 2023-11-26 19:15:38 +01:00
1 parent aa4ddbe9ae
commit 31c85ae0a5
12 files changed
+186 -11

No files matched your search

+10 -6
View File
@@ -189,6 +189,15 @@ void Section::Clear() {
lines_.clear();
}
bool Section::GetKeys(std::vector<std::string> &keys) const {
keys.clear();
for (auto liter = lines_.begin(); liter != lines_.end(); ++liter) {
if (!liter->Key().empty())
keys.push_back(std::string(liter->Key()));
}
return true;
}
ParsedIniLine *Section::GetLine(const char *key) {
for (auto &line : lines_) {
if (equalsNoCase(line.Key(), key))
@@ -482,12 +491,7 @@ bool IniFile::GetKeys(const char* sectionName, std::vector<std::string>& keys) c
const Section *section = GetSection(sectionName);
if (!section)
return false;
keys.clear();
for (auto liter = section->lines_.begin(); liter != section->lines_.end(); ++liter) {
if (!liter->Key().empty())
keys.push_back(std::string(liter->Key()));
}
return true;
return section->GetKeys(keys);
}
void IniFile::SortSections()
+3
View File
@@ -105,6 +105,9 @@ public:
bool Get(const char* key, double* value, double defaultValue = false) const;
bool Get(const char* key, std::vector<std::string>& values) const;
// Return a list of all keys in this section
bool GetKeys(std::vector<std::string> &keys) const;
bool operator < (const Section& other) const {
return name_ < other.name_;
}
+1 -1
View File
@@ -337,7 +337,7 @@ void GLQueueRunner::RunInitSteps(const FastVec<GLRInitStep> &steps, bool skipGLC
std::vector<std::string_view> lines;
SplitString(errorString, '\n', lines);
for (auto line : lines) {
ERROR_LOG(G3D, "%.*s", line.size(), line.data());
ERROR_LOG(G3D, "%.*s", (int)line.size(), line.data());
}
if (errorCallback_) {
std::string desc = StringFromFormat("Shader compilation failed: %s", step.create_shader.stage == GL_VERTEX_SHADER ? "vertex" : "fragment");
+1 -1
View File
@@ -31,7 +31,7 @@ bool RequestHeader::GetParamValue(const char *param_name, std::string *value) co
for (size_t i = 0; i < v.size(); i++) {
std::vector<std::string_view> parts;
SplitString(v[i], '=', parts);
DEBUG_LOG(IO, "Param: %.*s Value: %.*s", parts[0].size(), parts[0].data(), parts[1].size(), parts[1].data());
DEBUG_LOG(IO, "Param: %.*s Value: %.*s", (int)parts[0].size(), parts[0].data(), (int)parts[1].size(), parts[1].data());
if (parts[0] == param_name) {
*value = parts[1];
return true;
+31 -2
View File
@@ -68,6 +68,20 @@ double from_time_raw_relative(uint64_t raw_time) {
return from_time_raw(raw_time);
}
double time_now_unix_utc() {
const int64_t UNIX_TIME_START = 0x019DB1DED53E8000; //January 1, 1970 (start of Unix epoch) in "ticks"
const double TICKS_PER_SECOND = 10000000; //a tick is 100ns
FILETIME ft;
GetSystemTimeAsFileTime(&ft); //returns ticks in UTC
// Copy the low and high parts of FILETIME into a LARGE_INTEGER
LARGE_INTEGER li;
li.LowPart = ft.dwLowDateTime;
li.HighPart = ft.dwHighDateTime;
//Convert ticks since 1/1/1970 into seconds
return (double)(li.QuadPart - UNIX_TIME_START) / TICKS_PER_SECOND;
}
void yield() {
YieldProcessor();
}
@@ -99,6 +113,12 @@ double from_time_raw_relative(uint64_t raw_time) {
return (double)raw_time * (1.0 / nanos);
}
double time_now_unix_utc() {
struct timespec tp;
clock_gettime(CLOCK_REALTIME, &tp);
return tp.tv_sec * 1000000000ULL + tp.tv_nsec;
}
void yield() {
#if PPSSPP_ARCH(X86) || PPSSPP_ARCH(AMD64)
_mm_pause();
@@ -120,9 +140,14 @@ double time_now_d() {
return (double)(tv.tv_sec - start) + (double)tv.tv_usec * (1.0 / micros);
}
// Fake, but usable in a pinch. Don't, though.
uint64_t time_now_raw() {
return (uint64_t)(time_now_d() * nanos);
static time_t start;
struct timeval tv;
gettimeofday(&tv, nullptr);
if (start == 0) {
start = tv.tv_sec;
}
return (double)tv.tv_sec + (double)tv.tv_usec * (1.0 / micros);
}
double from_time_raw(uint64_t raw_time) {
@@ -135,6 +160,10 @@ double from_time_raw_relative(uint64_t raw_time) {
void yield() {}
double time_now_unix_utc() {
return time_now_raw();
}
#endif
void sleep_ms(int ms) {
+3
View File
@@ -13,6 +13,9 @@ uint64_t time_now_raw();
double from_time_raw(uint64_t raw_time);
double from_time_raw_relative(uint64_t raw_time);
// Seconds, Unix UTC time
double time_now_unix_utc();
// Sleep. Does not necessarily have millisecond granularity, especially on Windows.
void sleep_ms(int ms);
+90
View File
@@ -1157,6 +1157,10 @@ void Config::Load(const char *iniFileName, const char *controllerIniFilename) {
}
}
// Time tracking
Section *playTime = iniFile.GetOrCreateSection("PlayTime");
playTimeTracker_.Load(playTime);
auto pinnedPaths = iniFile.GetOrCreateSection("PinnedPaths")->ToMap();
vPinnedPaths.clear();
for (auto it = pinnedPaths.begin(), end = pinnedPaths.end(); it != end; ++it) {
@@ -1317,6 +1321,10 @@ bool Config::Save(const char *saveReason) {
if (LogManager::GetInstance())
LogManager::GetInstance()->SaveConfig(log);
// Time tracking
Section *playTime = iniFile.GetOrCreateSection("PlayTime");
playTimeTracker_.Save(playTime);
if (!iniFile.Save(iniFilename_)) {
ERROR_LOG(LOADER, "Error saving config (%s)- can't write ini '%s'", saveReason, iniFilename_.c_str());
return false;
@@ -1827,3 +1835,85 @@ int Config::GetPSPLanguage() {
return g_Config.iLanguage;
}
}
void PlayTimeTracker::Start(std::string gameId) {
if (gameId.empty()) {
return;
}
auto iter = tracker_.find(std::string(gameId));
if (iter != tracker_.end()) {
iter->second.lastTimePlayed = time_now_unix_utc();
iter->second.startTime = time_now_d();
return;
}
PlayTime playTime;
playTime.lastTimePlayed = time_now_unix_utc();
playTime.totalTimePlayed = 0.0;
playTime.startTime = time_now_d();
tracker_[gameId] = playTime;
}
void PlayTimeTracker::Stop(std::string gameId) {
_dbg_assert_(!gameId.empty());
auto iter = tracker_.find(std::string(gameId));
if (iter != tracker_.end()) {
if (iter->second.startTime != 0.0) {
iter->second.totalTimePlayed += time_now_d() - iter->second.startTime;
}
iter->second.lastTimePlayed = time_now_unix_utc();
iter->second.startTime = 0.0;
return;
}
// Shouldn't happen, ignore this case.
WARN_LOG(SYSTEM, "GameTimeTracker::Stop called without corresponding GameTimeTracker::Start");
}
void PlayTimeTracker::Load(const Section *section) {
tracker_.clear();
std::vector<std::string> keys;
section->GetKeys(keys);
for (auto key : keys) {
std::string value;
if (!section->Get(key.c_str(), &value, nullptr)) {
continue;
}
// Parse the string.
PlayTime gameTime{};
if (2 == sscanf(value.c_str(), "%d,%llu", &gameTime.totalTimePlayed, &gameTime.lastTimePlayed)) {
tracker_[key] = gameTime;
}
}
}
void PlayTimeTracker::Save(Section *section) {
for (auto iter : tracker_) {
std::string formatted = StringFromFormat("%d,%llu", iter.second.totalTimePlayed, iter.second.lastTimePlayed);
section->Set(iter.first.c_str(), formatted);
}
}
bool PlayTimeTracker::GetPlayedTimeString(const std::string &gameId, std::string *str) const {
auto ga = GetI18NCategory(I18NCat::GAME);
auto iter = tracker_.find(gameId);
if (iter == tracker_.end()) {
return false;
}
int totalSeconds = iter->second.totalTimePlayed;
int seconds = totalSeconds % 60;
totalSeconds /= 60;
int minutes = totalSeconds % 60;
totalSeconds /= 60;
int hours = totalSeconds;
*str = StringFromFormat(ga->T("Play time: %02d:%02d:%02d"), hours, minutes, seconds);
return true;
}
+25
View File
@@ -37,6 +37,28 @@ namespace http {
struct UrlEncoder;
struct ConfigPrivate;
class Section;
class PlayTimeTracker {
public:
struct PlayTime {
int totalTimePlayed;
double startTime; // time_now_d() time
uint64_t lastTimePlayed; // UTC Unix time for portability.
};
void Start(std::string gameId);
void Stop(std::string gameId);
void Load(const Section *section);
void Save(Section *section);
bool GetPlayedTimeString(const std::string &gameId, std::string *str) const;
private:
std::map<std::string, PlayTime> tracker_;
};
struct Config {
public:
Config();
@@ -578,6 +600,8 @@ public:
// Applies the Auto setting if set. Returns an enum value from PSP_SYSTEMPARAM_LANGUAGE_*.
int GetPSPLanguage();
PlayTimeTracker &TimeTracker() { return playTimeTracker_; }
protected:
void LoadStandardControllerIni();
void LoadLangValuesMapping();
@@ -592,6 +616,7 @@ private:
std::string gameIdTitle_;
std::vector<std::string> recentIsos;
std::map<std::string, std::pair<std::string, int>> langValuesMapping_;
PlayTimeTracker playTimeTracker_;
Path iniFilename_;
Path controllerIniFilename_;
Path searchPath_;
+7
View File
@@ -409,13 +409,20 @@ void EmuScreen::bootComplete() {
loadingViewColor_->Divert(0x00FFFFFF, 0.2f);
loadingViewVisible_->Divert(UI::V_INVISIBLE, 0.2f);
std::string gameID = g_paramSFO.GetValueString("DISC_ID");
g_Config.TimeTracker().Start(gameID);
}
EmuScreen::~EmuScreen() {
std::string gameID = g_paramSFO.GetValueString("DISC_ID");
g_Config.TimeTracker().Stop(gameID);
// If we were invalid, it would already be shutdown.
if (!invalid_ || bootPending_) {
PSP_Shutdown();
}
g_OSD.ClearAchievementStuff();
SetExtraAssertInfo(nullptr);
+12
View File
@@ -127,6 +127,9 @@ void GameScreen::CreateViews() {
tvInstallDataSize_->SetVisibility(V_GONE);
tvRegion_ = infoLayout->Add(new TextView("", ALIGN_LEFT, true, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT)));
tvRegion_->SetShadow(true);
tvPlayTime_ = infoLayout->Add(new TextView("", ALIGN_LEFT, true, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT)));
tvPlayTime_->SetShadow(true);
tvPlayTime_->SetVisibility(V_GONE);
tvCRC_ = infoLayout->Add(new TextView("", ALIGN_LEFT, true, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT)));
tvCRC_->SetShadow(true);
tvCRC_->SetVisibility(Reporting::HasCRC(gamePath_) ? V_VISIBLE : V_GONE);
@@ -138,6 +141,7 @@ void GameScreen::CreateViews() {
tvSaveDataSize_ = nullptr;
tvInstallDataSize_ = nullptr;
tvRegion_ = nullptr;
tvPlayTime_ = nullptr;
tvCRC_ = nullptr;
tvID_ = nullptr;
}
@@ -305,6 +309,14 @@ void GameScreen::render() {
}
}
if (tvPlayTime_) {
std::string str;
if (g_Config.TimeTracker().GetPlayedTimeString(info->id, &str)) {
tvPlayTime_->SetText(str);
tvPlayTime_->SetVisibility(UI::V_VISIBLE);
}
}
if (tvCRC_ && Reporting::HasCRC(gamePath_)) {
auto rp = GetI18NCategory(I18NCat::REPORTING);
uint32_t crcVal = Reporting::RetrieveCRC(gamePath_);
+1
View File
@@ -72,6 +72,7 @@ private:
UI::TextView *tvSaveDataSize_ = nullptr;
UI::TextView *tvInstallDataSize_ = nullptr;
UI::TextView *tvRegion_ = nullptr;
UI::TextView *tvPlayTime_ = nullptr;
UI::TextView *tvCRC_ = nullptr;
UI::TextView *tvID_ = nullptr;
NoticeView *tvVerified_ = nullptr;
+2 -1
View File
@@ -1217,12 +1217,13 @@ extern "C" jboolean Java_org_ppsspp_ppsspp_NativeApp_keyUp(JNIEnv *, jclass, jin
return NativeKey(keyInput);
}
// TODO: Make a batched interface, since we get these in batches on the Android side.
// TODO: Make a batched interface, since we get these in batches on the Java side.
extern "C" void Java_org_ppsspp_ppsspp_NativeApp_joystickAxis(
JNIEnv *env, jclass, jint deviceId, jint axisId, jfloat value) {
if (!renderer_inited)
return;
// These are dirty-filtered on the Java side.
AxisInput axis;
axis.deviceId = (InputDeviceID)deviceId;
axis.axisId = (InputAxis)axisId;