System: Move state compression/writing to worker thread
Reduce hitches when saving.
This commit is contained in:
parent
a4af88bc52
commit
5d7cb6c5dc
|
@ -6532,10 +6532,10 @@ void FullscreenUI::DoSaveState(s32 slot, bool global)
|
|||
if (!System::IsValid())
|
||||
return;
|
||||
|
||||
std::string filename(global ? System::GetGlobalSaveStateFileName(slot) :
|
||||
System::GetGameSaveStateFileName(System::GetGameSerial(), slot));
|
||||
std::string path(global ? System::GetGlobalSaveStateFileName(slot) :
|
||||
System::GetGameSaveStateFileName(System::GetGameSerial(), slot));
|
||||
Error error;
|
||||
if (!System::SaveState(filename.c_str(), &error, g_settings.create_save_state_backups, false))
|
||||
if (!System::SaveState(std::move(path), &error, g_settings.create_save_state_backups, false))
|
||||
{
|
||||
ShowToast(std::string(), fmt::format(TRANSLATE_FS("System", "Failed to save state: {}"), error.GetDescription()));
|
||||
}
|
||||
|
|
|
@ -111,7 +111,7 @@ static void HotkeySaveStateSlot(bool global, s32 slot)
|
|||
std::string path(global ? System::GetGlobalSaveStateFileName(slot) :
|
||||
System::GetGameSaveStateFileName(System::GetGameSerial(), slot));
|
||||
Error error;
|
||||
if (!System::SaveState(path.c_str(), &error, g_settings.create_save_state_backups, false))
|
||||
if (!System::SaveState(std::move(path), &error, g_settings.create_save_state_backups, false))
|
||||
{
|
||||
Host::AddIconOSDMessage(
|
||||
"SaveState", ICON_FA_EXCLAMATION_TRIANGLE,
|
||||
|
|
|
@ -389,7 +389,7 @@ void ImGuiManager::DrawPerformanceOverlay(float& position_y, float scale, float
|
|||
if (g_settings.display_show_resolution)
|
||||
{
|
||||
const u32 resolution_scale = g_gpu->GetResolutionScale();
|
||||
const auto [display_width, display_height] = g_gpu->GetFullDisplayResolution();// wrong
|
||||
const auto [display_width, display_height] = g_gpu->GetFullDisplayResolution(); // wrong
|
||||
const bool interlaced = g_gpu->IsInterlacedDisplayEnabled();
|
||||
const bool pal = g_gpu->IsInPALMode();
|
||||
text.format("{}x{} {} {} [{}x]", display_width * resolution_scale, display_height * resolution_scale,
|
||||
|
@ -1277,7 +1277,7 @@ void SaveStateSelectorUI::SaveCurrentSlot()
|
|||
if (std::string path = GetCurrentSlotPath(); !path.empty())
|
||||
{
|
||||
Error error;
|
||||
if (!System::SaveState(path.c_str(), &error, g_settings.create_save_state_backups, false))
|
||||
if (!System::SaveState(std::move(path), &error, g_settings.create_save_state_backups, false))
|
||||
{
|
||||
Host::AddIconOSDMessage("SaveState", ICON_EMOJI_WARNING,
|
||||
fmt::format(TRANSLATE_FS("OSDMessage", "Failed to save state to slot {0}:\n{1}"),
|
||||
|
|
|
@ -1663,8 +1663,8 @@ bool System::SaveResumeState(Error* error)
|
|||
return false;
|
||||
}
|
||||
|
||||
const std::string path(GetGameSaveStateFileName(s_state.running_game_serial, -1));
|
||||
return SaveState(path.c_str(), error, false, true);
|
||||
std::string path(GetGameSaveStateFileName(s_state.running_game_serial, -1));
|
||||
return SaveState(std::move(path), error, false, true);
|
||||
}
|
||||
|
||||
bool System::BootSystem(SystemBootParameters parameters, Error* error)
|
||||
|
@ -3010,7 +3010,7 @@ bool System::ReadAndDecompressStateData(std::FILE* fp, std::span<u8> dst, u32 fi
|
|||
}
|
||||
}
|
||||
|
||||
bool System::SaveState(const char* path, Error* error, bool backup_existing_save, bool ignore_memcard_busy)
|
||||
bool System::SaveState(std::string path, Error* error, bool backup_existing_save, bool ignore_memcard_busy)
|
||||
{
|
||||
if (!IsValid() || IsReplayingGPUDump())
|
||||
{
|
||||
|
@ -3029,40 +3029,62 @@ bool System::SaveState(const char* path, Error* error, bool backup_existing_save
|
|||
if (!SaveStateToBuffer(&buffer, error, 256))
|
||||
return false;
|
||||
|
||||
// TODO: Do this on a thread pool
|
||||
VERBOSE_LOG("Preparing state save took {:.2f} msec", save_timer.GetTimeMilliseconds());
|
||||
|
||||
if (backup_existing_save && FileSystem::FileExists(path))
|
||||
{
|
||||
Error backup_error;
|
||||
const std::string backup_filename = Path::ReplaceExtension(path, "bak");
|
||||
if (!FileSystem::RenamePath(path, backup_filename.c_str(), &backup_error))
|
||||
std::string osd_key = fmt::format("save_state_{}", path);
|
||||
Host::AddIconOSDMessage(osd_key, ICON_EMOJI_FLOPPY_DISK,
|
||||
fmt::format(TRANSLATE_FS("System", "Saving state to '{}'."), Path::GetFileName(path)), 60.0f);
|
||||
|
||||
QueueTaskOnThread([path = std::move(path), buffer = std::move(buffer), osd_key = std::move(osd_key),
|
||||
backup_existing_save, compression = g_settings.save_state_compression]() {
|
||||
INFO_LOG("Saving state to '{}'...", path);
|
||||
|
||||
Error lerror;
|
||||
Timer lsave_timer;
|
||||
|
||||
if (backup_existing_save && FileSystem::FileExists(path.c_str()))
|
||||
{
|
||||
ERROR_LOG("Failed to rename save state backup '{}': {}", Path::GetFileName(backup_filename),
|
||||
backup_error.GetDescription());
|
||||
const std::string backup_filename = Path::ReplaceExtension(path, "bak");
|
||||
if (!FileSystem::RenamePath(path.c_str(), backup_filename.c_str(), &lerror))
|
||||
{
|
||||
ERROR_LOG("Failed to rename save state backup '{}': {}", Path::GetFileName(backup_filename),
|
||||
lerror.GetDescription());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto fp = FileSystem::CreateAtomicRenamedFile(path, error);
|
||||
if (!fp)
|
||||
{
|
||||
Error::AddPrefixFmt(error, "Cannot open '{}': ", Path::GetFileName(path));
|
||||
return false;
|
||||
}
|
||||
auto fp = FileSystem::CreateAtomicRenamedFile(path, &lerror);
|
||||
bool result = false;
|
||||
if (fp)
|
||||
{
|
||||
if (SaveStateBufferToFile(buffer, fp.get(), &lerror, compression))
|
||||
result = FileSystem::CommitAtomicRenamedFile(fp, &lerror);
|
||||
else
|
||||
FileSystem::DiscardAtomicRenamedFile(fp);
|
||||
}
|
||||
else
|
||||
{
|
||||
lerror.AddPrefixFmt("Cannot open '{}': ", Path::GetFileName(path));
|
||||
}
|
||||
|
||||
INFO_LOG("Saving state to '{}'...", path);
|
||||
VERBOSE_LOG("Saving state took {:.2f} msec", lsave_timer.GetTimeMilliseconds());
|
||||
if (result)
|
||||
{
|
||||
Host::AddIconOSDMessage(std::move(osd_key), ICON_EMOJI_FLOPPY_DISK,
|
||||
fmt::format(TRANSLATE_FS("System", "State saved to '{}'."), Path::GetFileName(path)),
|
||||
Host::OSD_QUICK_DURATION);
|
||||
}
|
||||
else
|
||||
{
|
||||
Host::AddIconOSDMessage(std::move(osd_key), ICON_EMOJI_WARNING,
|
||||
fmt::format(TRANSLATE_FS("System", "Failed to save state to '{0}':\n{1}"),
|
||||
Path::GetFileName(path), lerror.GetDescription()),
|
||||
Host::OSD_ERROR_DURATION);
|
||||
}
|
||||
|
||||
if (!SaveStateBufferToFile(buffer, fp.get(), error, g_settings.save_state_compression))
|
||||
{
|
||||
FileSystem::DiscardAtomicRenamedFile(fp);
|
||||
return false;
|
||||
}
|
||||
System::RemoveSelfFromTaskThreads();
|
||||
});
|
||||
|
||||
Host::AddIconOSDMessage("save_state", ICON_EMOJI_FLOPPY_DISK,
|
||||
fmt::format(TRANSLATE_FS("OSDMessage", "State saved to '{}'."), Path::GetFileName(path)),
|
||||
5.0f);
|
||||
|
||||
VERBOSE_LOG("Saving state took {:.2f} msec", save_timer.GetTimeMilliseconds());
|
||||
return FileSystem::CommitAtomicRenamedFile(fp, error);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool System::SaveStateToBuffer(SaveStateBuffer* buffer, Error* error, u32 screenshot_size /* = 256 */)
|
||||
|
@ -3261,7 +3283,7 @@ u32 System::CompressAndWriteStateData(std::FILE* fp, std::span<const u8> src, Sa
|
|||
buffer.resize(buffer_size);
|
||||
|
||||
const int level =
|
||||
((method == SaveStateCompressionMode::ZstLow) ? 1 : ((method == SaveStateCompressionMode::ZstHigh) ? 19 : 0));
|
||||
((method == SaveStateCompressionMode::ZstLow) ? 1 : ((method == SaveStateCompressionMode::ZstHigh) ? 18 : 0));
|
||||
const size_t compressed_size = ZSTD_compress(buffer.data(), buffer_size, src.data(), src.size(), level);
|
||||
if (ZSTD_isError(compressed_size)) [[unlikely]]
|
||||
{
|
||||
|
@ -5399,7 +5421,7 @@ std::string System::GetGlobalSaveStateFileName(s32 slot)
|
|||
return Path::Combine(EmuFolders::SaveStates, fmt::format("savestate_{}.sav", slot));
|
||||
}
|
||||
|
||||
std::vector<SaveStateInfo> System::GetAvailableSaveStates(const char* serial)
|
||||
std::vector<SaveStateInfo> System::GetAvailableSaveStates(std::string_view serial)
|
||||
{
|
||||
std::vector<SaveStateInfo> si;
|
||||
std::string path;
|
||||
|
@ -5412,7 +5434,7 @@ std::vector<SaveStateInfo> System::GetAvailableSaveStates(const char* serial)
|
|||
si.push_back(SaveStateInfo{std::move(path), sd.ModificationTime, static_cast<s32>(slot), global});
|
||||
};
|
||||
|
||||
if (serial && std::strlen(serial) > 0)
|
||||
if (!serial.empty())
|
||||
{
|
||||
add_path(GetGameSaveStateFileName(serial, -1), -1, false);
|
||||
for (s32 i = 1; i <= PER_GAME_SAVE_STATE_SLOTS; i++)
|
||||
|
@ -5425,9 +5447,9 @@ std::vector<SaveStateInfo> System::GetAvailableSaveStates(const char* serial)
|
|||
return si;
|
||||
}
|
||||
|
||||
std::optional<SaveStateInfo> System::GetSaveStateInfo(const char* serial, s32 slot)
|
||||
std::optional<SaveStateInfo> System::GetSaveStateInfo(std::string_view serial, s32 slot)
|
||||
{
|
||||
const bool global = (!serial || serial[0] == 0);
|
||||
const bool global = serial.empty();
|
||||
std::string path = global ? GetGlobalSaveStateFileName(slot) : GetGameSaveStateFileName(serial, slot);
|
||||
|
||||
FILESYSTEM_STAT_DATA sd;
|
||||
|
@ -5468,7 +5490,7 @@ std::optional<ExtendedSaveStateInfo> System::GetExtendedSaveStateInfo(const char
|
|||
return ssi;
|
||||
}
|
||||
|
||||
void System::DeleteSaveStates(const char* serial, bool resume)
|
||||
void System::DeleteSaveStates(std::string_view serial, bool resume)
|
||||
{
|
||||
const std::vector<SaveStateInfo> states(GetAvailableSaveStates(serial));
|
||||
for (const SaveStateInfo& si : states)
|
||||
|
|
|
@ -256,7 +256,7 @@ void ResetSystem();
|
|||
|
||||
/// Loads state from the specified path.
|
||||
bool LoadState(const char* path, Error* error, bool save_undo_state);
|
||||
bool SaveState(const char* path, Error* error, bool backup_existing_save, bool ignore_memcard_busy);
|
||||
bool SaveState(std::string path, Error* error, bool backup_existing_save, bool ignore_memcard_busy);
|
||||
bool SaveResumeState(Error* error);
|
||||
|
||||
/// Runs the VM until the CPU execution is canceled.
|
||||
|
@ -358,16 +358,16 @@ std::optional<ExtendedSaveStateInfo> GetUndoSaveStateInfo();
|
|||
bool UndoLoadState();
|
||||
|
||||
/// Returns a list of save states for the specified game code.
|
||||
std::vector<SaveStateInfo> GetAvailableSaveStates(const char* serial);
|
||||
std::vector<SaveStateInfo> GetAvailableSaveStates(std::string_view serial);
|
||||
|
||||
/// Returns save state info if present. If serial is null or empty, assumes global state.
|
||||
std::optional<SaveStateInfo> GetSaveStateInfo(const char* serial, s32 slot);
|
||||
std::optional<SaveStateInfo> GetSaveStateInfo(std::string_view serial, s32 slot);
|
||||
|
||||
/// Returns save state info from opened save state stream.
|
||||
std::optional<ExtendedSaveStateInfo> GetExtendedSaveStateInfo(const char* path);
|
||||
|
||||
/// Deletes save states for the specified game code. If resume is set, the resume state is deleted too.
|
||||
void DeleteSaveStates(const char* serial, bool resume);
|
||||
void DeleteSaveStates(std::string_view serial, bool resume);
|
||||
|
||||
/// Returns the path to the memory card for the specified game, considering game settings.
|
||||
std::string GetGameMemoryCardPath(std::string_view serial, std::string_view path, u32 slot,
|
||||
|
|
|
@ -811,7 +811,7 @@ void MainWindow::populateGameListContextMenu(const GameList::Entry* entry, QWidg
|
|||
|
||||
if (!entry->serial.empty())
|
||||
{
|
||||
std::vector<SaveStateInfo> available_states(System::GetAvailableSaveStates(entry->serial.c_str()));
|
||||
std::vector<SaveStateInfo> available_states(System::GetAvailableSaveStates(entry->serial));
|
||||
const QString timestamp_format = QLocale::system().dateTimeFormat(QLocale::ShortFormat);
|
||||
const bool challenge_mode = Achievements::IsHardcoreModeActive();
|
||||
for (SaveStateInfo& ssi : available_states)
|
||||
|
@ -869,7 +869,7 @@ void MainWindow::populateGameListContextMenu(const GameList::Entry* entry, QWidg
|
|||
return;
|
||||
}
|
||||
|
||||
System::DeleteSaveStates(entry->serial.c_str(), true);
|
||||
System::DeleteSaveStates(entry->serial, true);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -881,10 +881,11 @@ static QString FormatTimestampForSaveStateMenu(u64 timestamp)
|
|||
return qtime.toString(QLocale::system().dateTimeFormat(QLocale::ShortFormat));
|
||||
}
|
||||
|
||||
void MainWindow::populateLoadStateMenu(const char* game_serial, QMenu* menu)
|
||||
void MainWindow::populateLoadStateMenu(std::string_view game_serial, QMenu* menu)
|
||||
{
|
||||
auto add_slot = [this, game_serial, menu](const QString& title, const QString& empty_title, bool global, s32 slot) {
|
||||
std::optional<SaveStateInfo> ssi = System::GetSaveStateInfo(global ? nullptr : game_serial, slot);
|
||||
auto add_slot = [this, menu](const QString& title, const QString& empty_title, const std::string_view& serial,
|
||||
s32 slot) {
|
||||
std::optional<SaveStateInfo> ssi = System::GetSaveStateInfo(serial, slot);
|
||||
|
||||
const QString menu_title =
|
||||
ssi.has_value() ? title.arg(slot).arg(FormatTimestampForSaveStateMenu(ssi->timestamp)) : empty_title.arg(slot);
|
||||
|
@ -913,28 +914,30 @@ void MainWindow::populateLoadStateMenu(const char* game_serial, QMenu* menu)
|
|||
connect(load_from_state, &QAction::triggered, g_emu_thread, &EmuThread::undoLoadState);
|
||||
menu->addSeparator();
|
||||
|
||||
if (game_serial && std::strlen(game_serial) > 0)
|
||||
if (!game_serial.empty())
|
||||
{
|
||||
for (u32 slot = 1; slot <= System::PER_GAME_SAVE_STATE_SLOTS; slot++)
|
||||
add_slot(tr("Game Save %1 (%2)"), tr("Game Save %1 (Empty)"), false, static_cast<s32>(slot));
|
||||
add_slot(tr("Game Save %1 (%2)"), tr("Game Save %1 (Empty)"), game_serial, static_cast<s32>(slot));
|
||||
|
||||
menu->addSeparator();
|
||||
}
|
||||
|
||||
std::string_view empty_serial;
|
||||
for (u32 slot = 1; slot <= System::GLOBAL_SAVE_STATE_SLOTS; slot++)
|
||||
add_slot(tr("Global Save %1 (%2)"), tr("Global Save %1 (Empty)"), true, static_cast<s32>(slot));
|
||||
add_slot(tr("Global Save %1 (%2)"), tr("Global Save %1 (Empty)"), empty_serial, static_cast<s32>(slot));
|
||||
}
|
||||
|
||||
void MainWindow::populateSaveStateMenu(const char* game_serial, QMenu* menu)
|
||||
void MainWindow::populateSaveStateMenu(std::string_view game_serial, QMenu* menu)
|
||||
{
|
||||
auto add_slot = [game_serial, menu](const QString& title, const QString& empty_title, bool global, s32 slot) {
|
||||
std::optional<SaveStateInfo> ssi = System::GetSaveStateInfo(global ? nullptr : game_serial, slot);
|
||||
auto add_slot = [menu](const QString& title, const QString& empty_title, const std::string_view& serial, s32 slot) {
|
||||
std::optional<SaveStateInfo> ssi = System::GetSaveStateInfo(serial, slot);
|
||||
|
||||
const QString menu_title =
|
||||
ssi.has_value() ? title.arg(slot).arg(FormatTimestampForSaveStateMenu(ssi->timestamp)) : empty_title.arg(slot);
|
||||
|
||||
QAction* save_action = menu->addAction(menu_title);
|
||||
connect(save_action, &QAction::triggered, [global, slot]() { g_emu_thread->saveState(global, slot); });
|
||||
connect(save_action, &QAction::triggered,
|
||||
[global = serial.empty(), slot]() { g_emu_thread->saveState(global, slot); });
|
||||
};
|
||||
|
||||
menu->clear();
|
||||
|
@ -952,16 +955,17 @@ void MainWindow::populateSaveStateMenu(const char* game_serial, QMenu* menu)
|
|||
});
|
||||
menu->addSeparator();
|
||||
|
||||
if (game_serial && std::strlen(game_serial) > 0)
|
||||
if (!game_serial.empty())
|
||||
{
|
||||
for (u32 slot = 1; slot <= System::PER_GAME_SAVE_STATE_SLOTS; slot++)
|
||||
add_slot(tr("Game Save %1 (%2)"), tr("Game Save %1 (Empty)"), false, static_cast<s32>(slot));
|
||||
add_slot(tr("Game Save %1 (%2)"), tr("Game Save %1 (Empty)"), game_serial, static_cast<s32>(slot));
|
||||
|
||||
menu->addSeparator();
|
||||
}
|
||||
|
||||
std::string_view empty_serial;
|
||||
for (u32 slot = 1; slot <= System::GLOBAL_SAVE_STATE_SLOTS; slot++)
|
||||
add_slot(tr("Global Save %1 (%2)"), tr("Global Save %1 (Empty)"), true, static_cast<s32>(slot));
|
||||
add_slot(tr("Global Save %1 (%2)"), tr("Global Save %1 (Empty)"), empty_serial, static_cast<s32>(slot));
|
||||
}
|
||||
|
||||
void MainWindow::populateChangeDiscSubImageMenu(QMenu* menu, QActionGroup* action_group)
|
||||
|
@ -1238,12 +1242,12 @@ void MainWindow::onChangeDiscMenuAboutToHide()
|
|||
|
||||
void MainWindow::onLoadStateMenuAboutToShow()
|
||||
{
|
||||
populateLoadStateMenu(s_current_game_serial.toUtf8().constData(), m_ui.menuLoadState);
|
||||
populateLoadStateMenu(s_current_game_serial.toStdString(), m_ui.menuLoadState);
|
||||
}
|
||||
|
||||
void MainWindow::onSaveStateMenuAboutToShow()
|
||||
{
|
||||
populateSaveStateMenu(s_current_game_serial.toUtf8().constData(), m_ui.menuSaveState);
|
||||
populateSaveStateMenu(s_current_game_serial.toStdString(), m_ui.menuSaveState);
|
||||
}
|
||||
|
||||
void MainWindow::onStartFullscreenUITriggered()
|
||||
|
|
|
@ -268,8 +268,8 @@ private:
|
|||
/// Fills menu with save state info and handlers.
|
||||
void populateGameListContextMenu(const GameList::Entry* entry, QWidget* parent_window, QMenu* menu);
|
||||
|
||||
void populateLoadStateMenu(const char* game_serial, QMenu* menu);
|
||||
void populateSaveStateMenu(const char* game_serial, QMenu* menu);
|
||||
void populateLoadStateMenu(std::string_view game_serial, QMenu* menu);
|
||||
void populateSaveStateMenu(std::string_view game_serial, QMenu* menu);
|
||||
|
||||
/// Fills menu with the current playlist entries. The disc index is marked as checked.
|
||||
void populateChangeDiscSubImageMenu(QMenu* menu, QActionGroup* action_group);
|
||||
|
|
|
@ -1412,7 +1412,7 @@ void EmuThread::saveState(const QString& filename, bool block_until_done /* = fa
|
|||
return;
|
||||
|
||||
Error error;
|
||||
if (!System::SaveState(filename.toUtf8().data(), &error, g_settings.create_save_state_backups, false))
|
||||
if (!System::SaveState(filename.toStdString(), &error, g_settings.create_save_state_backups, false))
|
||||
emit errorReported(tr("Error"), tr("Failed to save state: %1").arg(QString::fromStdString(error.GetDescription())));
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue