Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 25 additions & 4 deletions Common/TimeUtil.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,21 +36,32 @@
constexpr double micros = 1000000.0;
constexpr double nanos = 1000000000.0;


#if PPSSPP_PLATFORM(WINDOWS)

constexpr int64_t UNIX_TIME_START = 0x019DB1DED53E8000; //January 1, 1970 (start of Unix epoch) in "ticks"
constexpr double TICKS_PER_SECOND = 10000000; //a tick is 100ns

static LARGE_INTEGER frequency;
static double frequencyMult;
static LARGE_INTEGER startTime;
static LARGE_INTEGER startFileTime;

HANDLE Timer;
int SchedulerPeriodMs = 10;
INT64 QpcPerSecond;

void TimeInit() {
FILETIME ft;
GetSystemTimeAsFileTime(&ft); //returns ticks in UTC
// Copy the low and high parts of FILETIME into a LARGE_INTEGER
startFileTime.LowPart = ft.dwLowDateTime;
startFileTime.HighPart = ft.dwHighDateTime;

QueryPerformanceFrequency(&frequency);
QueryPerformanceCounter(&startTime);
QpcPerSecond = frequency.QuadPart;
frequencyMult = 1.0 / static_cast<double>(frequency.QuadPart);
frequencyMult = 1.0 / frequency.QuadPart;

// The timer will be automatically deleted on process destruction. Don't need to CloseHandle.
Timer = CreateWaitableTimerExW(NULL, NULL, CREATE_WAITABLE_TIMER_HIGH_RESOLUTION, TIMER_ALL_ACCESS);
Expand Down Expand Up @@ -85,9 +96,6 @@ double from_time_raw_relative(uint64_t 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
Expand All @@ -98,6 +106,15 @@ double time_now_unix_utc() {
return (double)(li.QuadPart - UNIX_TIME_START) / TICKS_PER_SECOND;
}

// Adds the timestamp to startTime, and converts to seconds from the unix epoch.
double time_to_unix_utc(double timestamp) {
// Copy the low and high parts of FILETIME into a LARGE_INTEGER
LARGE_INTEGER li;
li.LowPart = startFileTime.LowPart;
li.HighPart = startFileTime.HighPart;
return (double)(li.QuadPart - UNIX_TIME_START + static_cast<int64_t>(timestamp * TICKS_PER_SECOND)) / TICKS_PER_SECOND;
}

void yield() {
YieldProcessor();
}
Expand Down Expand Up @@ -227,6 +244,10 @@ double time_now_unix_utc() {
return time_now_raw();
}

double time_to_unix_utc(double t) {
return (double)tv.tv_sec + (double)tv.tv_usec * (1.0 / micros) + t;
}

Instant::Instant() {
struct timeval tv;
gettimeofday(&tv, nullptr);
Expand Down
1 change: 1 addition & 0 deletions Common/TimeUtil.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ double from_time_raw_relative(uint64_t raw_time);

// Seconds, Unix UTC time
double time_now_unix_utc();
double time_to_unix_utc(double timeNowSeconds);

// Sleep for milliseconds. Does not necessarily have millisecond granularity, especially on Windows.
// Requires a "reason" since sleeping generally should be very sparingly used. This
Expand Down
6 changes: 4 additions & 2 deletions Core/Debugger/WebSocket/GPUBufferSubscriber.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -248,14 +248,16 @@ static void GenericStreamBuffer(DebuggerRequest &req, std::function<bool(const G
if (!func(buf, &isFramebuffer)) {
return req.Fail("Could not download output");
}
_assert_(buf != nullptr);
if (!buf) {
return req.Fail("No output available");
}

if (type == "base64") {
StreamBufferToBase64(req, *buf, isFramebuffer);
} else if (type == "uri") {
StreamBufferToDataURI(req, *buf, isFramebuffer, includeAlpha, stackWidth);
} else {
_assert_(false);
return req.Fail("Unexpected output type");
}
}

Expand Down
54 changes: 29 additions & 25 deletions Core/SaveState.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -106,10 +106,10 @@ enum class OperationType {
struct Operation {
// The slot number is for visual purposes only. Set to -1 for operations where we don't display a message for example.
Operation(OperationType t, const Path &f, int slot_, Callback cb)
: type(t), filename(f), callback(cb), slot(slot_) {}
: type(t), path(f), callback(cb), slot(slot_) {}

OperationType type;
Path filename;
Path path;
Callback callback;
int slot;
};
Expand Down Expand Up @@ -245,9 +245,6 @@ int g_screenshotFailures;
}

void Rewind(Callback callback) {
if (g_netInited) {
return;
}
if (coreState == CoreState::CORE_RUNTIME_ERROR)
Core_Break(BreakReason::SavestateRewind, 0);
Enqueue(Operation(OperationType::Rewind, Path(), -1, callback));
Expand Down Expand Up @@ -414,7 +411,7 @@ int g_screenshotFailures;
Path backup = GetSysDirectory(DIRECTORY_SAVESTATE) / LOAD_UNDO_NAME;

std::string prefix(gamePrefix);
auto saveCallback = [prefix, fn, backup, slot, callback](Status status, std::string_view message) {
auto saveCallback = [prefix, fn, backup, slot, callback](Status status, std::string_view message, std::string_view metadata) {
if (status != Status::FAILURE) {
DeleteIfExists(backup);
File::Rename(backup.WithExtraExtension(".tmp"), backup);
Expand All @@ -438,7 +435,7 @@ int g_screenshotFailures;
} else {
if (callback) {
auto sy = GetI18NCategory(I18NCat::SYSTEM);
callback(Status::FAILURE, sy->T("Failed to load state. Error in the file system."));
callback(Status::FAILURE, sy->T("Failed to load state. Error in the file system."), "");
}
}
}
Expand All @@ -451,7 +448,7 @@ int g_screenshotFailures;
if (g_Config.sStateLoadUndoGame != gamePrefix) {
if (callback) {
auto sy = GetI18NCategory(I18NCat::SYSTEM);
callback(Status::FAILURE, sy->T("Error: load undo state is from a different game"));
callback(Status::FAILURE, sy->T("Error: load undo state is from a different game"), "");
}
return false;
}
Expand All @@ -463,7 +460,7 @@ int g_screenshotFailures;
} else {
if (callback) {
auto sy = GetI18NCategory(I18NCat::SYSTEM);
callback(Status::FAILURE, sy->T("Failed to load state for load undo. Error in the file system."));
callback(Status::FAILURE, sy->T("Failed to load state for load undo. Error in the file system."), "");
}
return false;
}
Expand All @@ -480,7 +477,7 @@ int g_screenshotFailures;
Path shot = GenerateSaveSlotPath(gamePrefix, slot, SCREENSHOT_EXTENSION);

std::string prefix(gamePrefix);
auto renameCallback = [fn, fnUndo, prefix, slot, callback](Status status, std::string_view message) {
auto renameCallback = [fn, fnUndo, prefix, slot, callback](Status status, std::string_view message, std::string_view metadata) {
if (status != Status::FAILURE) {
if (g_Config.bEnableStateUndo) {
DeleteIfExists(fnUndo);
Expand All @@ -494,7 +491,7 @@ int g_screenshotFailures;
File::Rename(fn.WithExtraExtension(".tmp"), fn);
}
if (callback) {
callback(status, message);
callback(status, message, "");
}
};
// Let's also create a screenshot.
Expand All @@ -508,7 +505,7 @@ int g_screenshotFailures;
} else {
if (callback) {
auto sy = GetI18NCategory(I18NCat::SYSTEM);
callback(Status::FAILURE, sy->T("Failed to save state. Error in the file system."));
callback(Status::FAILURE, sy->T("Failed to save state. Error in the file system."), "");
}
}
Rescan(gamePrefix);
Expand Down Expand Up @@ -678,8 +675,7 @@ int g_screenshotFailures;
return oldestSlot;
}

std::string GetSlotDateAsString(std::string_view gamePrefix, int slot) {
std::string fn = GenerateSaveSlotFilename(gamePrefix, slot, STATE_EXTENSION);
static std::string GetSaveFileDateAsString(const std::string &fn) {
auto iter = g_files.find(fn);
if (iter == g_files.end()) {
return "";
Expand All @@ -706,6 +702,11 @@ int g_screenshotFailures;
return buf;
}

std::string GetSlotDateAsString(std::string_view gamePrefix, int slot) {
std::string fn = GenerateSaveSlotFilename(gamePrefix, slot, STATE_EXTENSION);
return GetSaveFileDateAsString(fn);
}

std::vector<Operation> Flush() {
std::lock_guard<std::mutex> guard(mutex);
std::vector<Operation> copy = g_pendingOperations;
Expand All @@ -714,15 +715,15 @@ int g_screenshotFailures;
return copy;
}

bool HandleLoadFailure(bool wasRewinding) {
static bool HandleLoadFailure(bool wasRewinding, std::string *metadata) {
if (wasRewinding) {
WARN_LOG(Log::SaveState, "HandleLoadFailure - trying a rewind state.");
// Okay, first, let's give the next rewind state a shot - maybe we can at least not reset entirely.
// Actually, this seems like a bad thing - some systems can end up in bad states, like sceFont :(
CChunkFileReader::Error result;
do {
std::string errorString;
result = rewindStates.Restore(&errorString);
result = rewindStates.Restore(&errorString, metadata);
} while (result == CChunkFileReader::ERROR_BROKEN_STATE);

if (result == CChunkFileReader::ERROR_NONE) {
Expand Down Expand Up @@ -813,6 +814,7 @@ int g_screenshotFailures;
CChunkFileReader::Error result;
Status callbackResult;
std::string callbackMessage;
std::string callbackMetadata;
std::string title;

auto sc = GetI18NCategory(I18NCat::SCREEN);
Expand All @@ -824,12 +826,14 @@ int g_screenshotFailures;

switch (op.type) {
case OperationType::Load:
INFO_LOG(Log::SaveState, "Loading state from '%s'", op.filename.c_str());
INFO_LOG(Log::SaveState, "Loading state from '%s'", op.path.c_str());
// Use the state's latest version as a guess for saveStateInitialGitVersion.
result = CChunkFileReader::Load(op.filename, &saveStateInitialGitVersion, state, &errorString);
result = CChunkFileReader::Load(op.path, &saveStateInitialGitVersion, state, &errorString);
if (result == CChunkFileReader::ERROR_NONE) {
callbackMessage = op.slot != LOAD_UNDO_SLOT ? sc->T("Loaded State") : sc->T("State load undone");
callbackResult = TriggerLoadWarnings(callbackMessage);
callbackMetadata = SaveState::GetSaveFileDateAsString(op.path.GetFilename());

hasLoadedState = true;
Core_ResetException();

Expand All @@ -849,26 +853,26 @@ int g_screenshotFailures;
#endif
g_lastSaveTime = time_now_d();
} else if (result == CChunkFileReader::ERROR_BROKEN_STATE) {
HandleLoadFailure(false);
HandleLoadFailure(false, &callbackMetadata);
callbackMessage = std::string(i18nLoadFailure) + ": " + errorString;
ERROR_LOG(Log::SaveState, "Load state failure: %s", errorString.c_str());
callbackResult = Status::FAILURE;
} else {
callbackMessage = sc->T(errorString.c_str(), i18nLoadFailure);
callbackMessage = sc->T(errorString, i18nLoadFailure);
callbackResult = Status::FAILURE;
}
break;

case OperationType::Save:
INFO_LOG(Log::SaveState, "Saving state to '%s'", op.filename.c_str());
INFO_LOG(Log::SaveState, "Saving state to '%s'", op.path.c_str());
title = g_paramSFO.GetValueString("TITLE");
if (title.empty()) {
// Homebrew title
title = PSP_CoreParameter().fileToStart.ToVisualString();
std::size_t lslash = title.find_last_of('/');
title = title.substr(lslash + 1);
}
result = CChunkFileReader::Save(op.filename, title, PPSSPP_GIT_VERSION, state);
result = CChunkFileReader::Save(op.path, title, PPSSPP_GIT_VERSION, state);
if (result == CChunkFileReader::ERROR_NONE) {
callbackMessage = slot_prefix + std::string(sc->T("Saved State"));
callbackResult = Status::SUCCESS;
Expand Down Expand Up @@ -909,15 +913,15 @@ int g_screenshotFailures;

case OperationType::Rewind:
INFO_LOG(Log::SaveState, "Rewinding to recent savestate snapshot");
result = rewindStates.Restore(&errorString);
result = rewindStates.Restore(&errorString, &callbackMetadata);
if (result == CChunkFileReader::ERROR_NONE) {
callbackMessage = sc->T("Loaded State");
callbackResult = Status::SUCCESS;
hasLoadedState = true;
Core_ResetException();
} else if (result == CChunkFileReader::ERROR_BROKEN_STATE) {
// Cripes. Good news is, we might have more. Let's try those too, better than a reset.
if (HandleLoadFailure(true)) {
if (HandleLoadFailure(true, &callbackMetadata)) {
// Well, we did rewind, even if too much...
callbackMessage = sc->T("Loaded State");
callbackResult = Status::SUCCESS;
Expand All @@ -940,7 +944,7 @@ int g_screenshotFailures;
}

if (op.callback) {
op.callback(callbackResult, callbackMessage);
op.callback(callbackResult, callbackMessage, callbackMetadata);
}
}
if (operations.size()) {
Expand Down
2 changes: 1 addition & 1 deletion Core/SaveState.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ namespace SaveState {
WARNING,
SUCCESS,
};
typedef std::function<void(Status status, std::string_view message)> Callback;
typedef std::function<void(Status status, std::string_view message, std::string_view metadata)> Callback;

static const char * const SCREENSHOT_EXTENSION = "jpg";

Expand Down
38 changes: 27 additions & 11 deletions Core/SaveStateRewind.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
#include "Common/Thread/ThreadUtil.h"
#include "Common/Data/Text/I18n.h"
#include "Common/StringUtils.h"
#include "Core/SaveState.h"
#include "Core/SaveStateRewind.h"
#include "Core/Core.h"
Expand Down Expand Up @@ -33,16 +35,18 @@ CChunkFileReader::Error StateRingbuffer::Save() {
} else
err = SaveToRam(buffer_);

if (err == CChunkFileReader::ERROR_NONE)
ScheduleCompress(&states_[n], compressBuffer, &bases_[base_]);
else
if (err == CChunkFileReader::ERROR_NONE) {
ScheduleCompress(&states_[n].stateBuffer, compressBuffer, &bases_[base_]);
states_[n].savedTime = time_now_d();
} else {
states_[n].clear();
}

baseMapping_[n] = base_;
return err;
}

CChunkFileReader::Error StateRingbuffer::Restore(std::string *errorString) {
CChunkFileReader::Error StateRingbuffer::Restore(std::string *errorString, std::string *metadata) {
std::lock_guard<std::mutex> guard(lock_);

// No valid states left.
Expand All @@ -53,9 +57,20 @@ CChunkFileReader::Error StateRingbuffer::Restore(std::string *errorString) {
if (states_[n].empty())
return CChunkFileReader::ERROR_BAD_FILE;

auto pa = GetI18NCategory(I18NCat::PAUSE);

static std::vector<u8> buffer;
LockedDecompress(buffer, states_[n], bases_[baseMapping_[n]]);
LockedDecompress(buffer, states_[n].stateBuffer, bases_[baseMapping_[n]]);
CChunkFileReader::Error error = LoadFromRam(buffer, errorString);
*metadata = pa->T("Rewind");

if (states_[n].savedTime) {
auto di = GetI18NCategory(I18NCat::DIALOG);
metadata->append(" (");
metadata->append(ApplySafeSubstitutions(di->T("%1 seconds ago"), static_cast<int>(time_now_d() - states_[n].savedTime)));
metadata->append(")");
}

rewindLastTime_ = time_now_d();
return error;
}
Expand Down Expand Up @@ -99,16 +114,13 @@ void StateRingbuffer::LockedDecompress(std::vector<u8> &result, const std::vecto
result.clear();
result.reserve(base.size());
auto basePos = base.begin();
for (size_t i = 0; i < compressed.size(); )
{
if (compressed[i] == 0)
{
for (size_t i = 0; i < compressed.size(); ) {
if (compressed[i] == 0) {
++i;
int blockSize = std::min(BLOCK_SIZE, (int)(base.size() - result.size()));
result.insert(result.end(), basePos, basePos + blockSize);
basePos += blockSize;
} else
{
} else {
++i;
int blockSize = std::min(BLOCK_SIZE, (int)(compressed.size() - i));
result.insert(result.end(), compressed.begin() + i, compressed.begin() + i + blockSize);
Expand Down Expand Up @@ -167,4 +179,8 @@ void StateRingbuffer::NotifyState() {
rewindLastTime_ = time_now_d();
}

double StateRingbuffer::NextStateTimestamp() const {
return rewindLastTime_ + g_Config.iRewindSnapshotInterval;
}

} // namespace SaveState
Loading
Loading