mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-10-01 14:58:14 +00:00
Add the basics of a played-time tracker.
This commit is contained in:
1 parent
aa4ddbe9ae
commit
31c85ae0a5
12 files changed
+186
-11
No files matched your search
@@ -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()
|
||||
|
||||
@@ -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_;
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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_;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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_);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in new issue
Block a user