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())
|
if (!System::IsValid())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
std::string filename(global ? System::GetGlobalSaveStateFileName(slot) :
|
std::string path(global ? System::GetGlobalSaveStateFileName(slot) :
|
||||||
System::GetGameSaveStateFileName(System::GetGameSerial(), slot));
|
System::GetGameSaveStateFileName(System::GetGameSerial(), slot));
|
||||||
Error error;
|
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()));
|
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) :
|
std::string path(global ? System::GetGlobalSaveStateFileName(slot) :
|
||||||
System::GetGameSaveStateFileName(System::GetGameSerial(), slot));
|
System::GetGameSaveStateFileName(System::GetGameSerial(), slot));
|
||||||
Error error;
|
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(
|
Host::AddIconOSDMessage(
|
||||||
"SaveState", ICON_FA_EXCLAMATION_TRIANGLE,
|
"SaveState", ICON_FA_EXCLAMATION_TRIANGLE,
|
||||||
|
|
|
@ -389,7 +389,7 @@ void ImGuiManager::DrawPerformanceOverlay(float& position_y, float scale, float
|
||||||
if (g_settings.display_show_resolution)
|
if (g_settings.display_show_resolution)
|
||||||
{
|
{
|
||||||
const u32 resolution_scale = g_gpu->GetResolutionScale();
|
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 interlaced = g_gpu->IsInterlacedDisplayEnabled();
|
||||||
const bool pal = g_gpu->IsInPALMode();
|
const bool pal = g_gpu->IsInPALMode();
|
||||||
text.format("{}x{} {} {} [{}x]", display_width * resolution_scale, display_height * resolution_scale,
|
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())
|
if (std::string path = GetCurrentSlotPath(); !path.empty())
|
||||||
{
|
{
|
||||||
Error error;
|
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,
|
Host::AddIconOSDMessage("SaveState", ICON_EMOJI_WARNING,
|
||||||
fmt::format(TRANSLATE_FS("OSDMessage", "Failed to save state to slot {0}:\n{1}"),
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::string path(GetGameSaveStateFileName(s_state.running_game_serial, -1));
|
std::string path(GetGameSaveStateFileName(s_state.running_game_serial, -1));
|
||||||
return SaveState(path.c_str(), error, false, true);
|
return SaveState(std::move(path), error, false, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool System::BootSystem(SystemBootParameters parameters, Error* error)
|
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())
|
if (!IsValid() || IsReplayingGPUDump())
|
||||||
{
|
{
|
||||||
|
@ -3029,40 +3029,62 @@ bool System::SaveState(const char* path, Error* error, bool backup_existing_save
|
||||||
if (!SaveStateToBuffer(&buffer, error, 256))
|
if (!SaveStateToBuffer(&buffer, error, 256))
|
||||||
return false;
|
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))
|
std::string osd_key = fmt::format("save_state_{}", path);
|
||||||
{
|
Host::AddIconOSDMessage(osd_key, ICON_EMOJI_FLOPPY_DISK,
|
||||||
Error backup_error;
|
fmt::format(TRANSLATE_FS("System", "Saving state to '{}'."), Path::GetFileName(path)), 60.0f);
|
||||||
const std::string backup_filename = Path::ReplaceExtension(path, "bak");
|
|
||||||
if (!FileSystem::RenamePath(path, backup_filename.c_str(), &backup_error))
|
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),
|
const std::string backup_filename = Path::ReplaceExtension(path, "bak");
|
||||||
backup_error.GetDescription());
|
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);
|
auto fp = FileSystem::CreateAtomicRenamedFile(path, &lerror);
|
||||||
if (!fp)
|
bool result = false;
|
||||||
{
|
if (fp)
|
||||||
Error::AddPrefixFmt(error, "Cannot open '{}': ", Path::GetFileName(path));
|
{
|
||||||
return false;
|
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))
|
System::RemoveSelfFromTaskThreads();
|
||||||
{
|
});
|
||||||
FileSystem::DiscardAtomicRenamedFile(fp);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
Host::AddIconOSDMessage("save_state", ICON_EMOJI_FLOPPY_DISK,
|
return true;
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool System::SaveStateToBuffer(SaveStateBuffer* buffer, Error* error, u32 screenshot_size /* = 256 */)
|
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);
|
buffer.resize(buffer_size);
|
||||||
|
|
||||||
const int level =
|
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);
|
const size_t compressed_size = ZSTD_compress(buffer.data(), buffer_size, src.data(), src.size(), level);
|
||||||
if (ZSTD_isError(compressed_size)) [[unlikely]]
|
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));
|
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::vector<SaveStateInfo> si;
|
||||||
std::string path;
|
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});
|
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);
|
add_path(GetGameSaveStateFileName(serial, -1), -1, false);
|
||||||
for (s32 i = 1; i <= PER_GAME_SAVE_STATE_SLOTS; i++)
|
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;
|
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);
|
std::string path = global ? GetGlobalSaveStateFileName(slot) : GetGameSaveStateFileName(serial, slot);
|
||||||
|
|
||||||
FILESYSTEM_STAT_DATA sd;
|
FILESYSTEM_STAT_DATA sd;
|
||||||
|
@ -5468,7 +5490,7 @@ std::optional<ExtendedSaveStateInfo> System::GetExtendedSaveStateInfo(const char
|
||||||
return ssi;
|
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));
|
const std::vector<SaveStateInfo> states(GetAvailableSaveStates(serial));
|
||||||
for (const SaveStateInfo& si : states)
|
for (const SaveStateInfo& si : states)
|
||||||
|
|
|
@ -256,7 +256,7 @@ void ResetSystem();
|
||||||
|
|
||||||
/// Loads state from the specified path.
|
/// Loads state from the specified path.
|
||||||
bool LoadState(const char* path, Error* error, bool save_undo_state);
|
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);
|
bool SaveResumeState(Error* error);
|
||||||
|
|
||||||
/// Runs the VM until the CPU execution is canceled.
|
/// Runs the VM until the CPU execution is canceled.
|
||||||
|
@ -358,16 +358,16 @@ std::optional<ExtendedSaveStateInfo> GetUndoSaveStateInfo();
|
||||||
bool UndoLoadState();
|
bool UndoLoadState();
|
||||||
|
|
||||||
/// Returns a list of save states for the specified game code.
|
/// 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.
|
/// 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.
|
/// Returns save state info from opened save state stream.
|
||||||
std::optional<ExtendedSaveStateInfo> GetExtendedSaveStateInfo(const char* path);
|
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.
|
/// 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.
|
/// 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,
|
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())
|
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 QString timestamp_format = QLocale::system().dateTimeFormat(QLocale::ShortFormat);
|
||||||
const bool challenge_mode = Achievements::IsHardcoreModeActive();
|
const bool challenge_mode = Achievements::IsHardcoreModeActive();
|
||||||
for (SaveStateInfo& ssi : available_states)
|
for (SaveStateInfo& ssi : available_states)
|
||||||
|
@ -869,7 +869,7 @@ void MainWindow::populateGameListContextMenu(const GameList::Entry* entry, QWidg
|
||||||
return;
|
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));
|
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) {
|
auto add_slot = [this, menu](const QString& title, const QString& empty_title, const std::string_view& serial,
|
||||||
std::optional<SaveStateInfo> ssi = System::GetSaveStateInfo(global ? nullptr : game_serial, slot);
|
s32 slot) {
|
||||||
|
std::optional<SaveStateInfo> ssi = System::GetSaveStateInfo(serial, slot);
|
||||||
|
|
||||||
const QString menu_title =
|
const QString menu_title =
|
||||||
ssi.has_value() ? title.arg(slot).arg(FormatTimestampForSaveStateMenu(ssi->timestamp)) : empty_title.arg(slot);
|
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);
|
connect(load_from_state, &QAction::triggered, g_emu_thread, &EmuThread::undoLoadState);
|
||||||
menu->addSeparator();
|
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++)
|
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();
|
menu->addSeparator();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string_view empty_serial;
|
||||||
for (u32 slot = 1; slot <= System::GLOBAL_SAVE_STATE_SLOTS; slot++)
|
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) {
|
auto add_slot = [menu](const QString& title, const QString& empty_title, const std::string_view& serial, s32 slot) {
|
||||||
std::optional<SaveStateInfo> ssi = System::GetSaveStateInfo(global ? nullptr : game_serial, slot);
|
std::optional<SaveStateInfo> ssi = System::GetSaveStateInfo(serial, slot);
|
||||||
|
|
||||||
const QString menu_title =
|
const QString menu_title =
|
||||||
ssi.has_value() ? title.arg(slot).arg(FormatTimestampForSaveStateMenu(ssi->timestamp)) : empty_title.arg(slot);
|
ssi.has_value() ? title.arg(slot).arg(FormatTimestampForSaveStateMenu(ssi->timestamp)) : empty_title.arg(slot);
|
||||||
|
|
||||||
QAction* save_action = menu->addAction(menu_title);
|
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();
|
menu->clear();
|
||||||
|
@ -952,16 +955,17 @@ void MainWindow::populateSaveStateMenu(const char* game_serial, QMenu* menu)
|
||||||
});
|
});
|
||||||
menu->addSeparator();
|
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++)
|
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();
|
menu->addSeparator();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string_view empty_serial;
|
||||||
for (u32 slot = 1; slot <= System::GLOBAL_SAVE_STATE_SLOTS; slot++)
|
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)
|
void MainWindow::populateChangeDiscSubImageMenu(QMenu* menu, QActionGroup* action_group)
|
||||||
|
@ -1238,12 +1242,12 @@ void MainWindow::onChangeDiscMenuAboutToHide()
|
||||||
|
|
||||||
void MainWindow::onLoadStateMenuAboutToShow()
|
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()
|
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()
|
void MainWindow::onStartFullscreenUITriggered()
|
||||||
|
|
|
@ -268,8 +268,8 @@ private:
|
||||||
/// Fills menu with save state info and handlers.
|
/// Fills menu with save state info and handlers.
|
||||||
void populateGameListContextMenu(const GameList::Entry* entry, QWidget* parent_window, QMenu* menu);
|
void populateGameListContextMenu(const GameList::Entry* entry, QWidget* parent_window, QMenu* menu);
|
||||||
|
|
||||||
void populateLoadStateMenu(const char* game_serial, QMenu* menu);
|
void populateLoadStateMenu(std::string_view game_serial, QMenu* menu);
|
||||||
void populateSaveStateMenu(const char* 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.
|
/// Fills menu with the current playlist entries. The disc index is marked as checked.
|
||||||
void populateChangeDiscSubImageMenu(QMenu* menu, QActionGroup* action_group);
|
void populateChangeDiscSubImageMenu(QMenu* menu, QActionGroup* action_group);
|
||||||
|
|
|
@ -1412,7 +1412,7 @@ void EmuThread::saveState(const QString& filename, bool block_until_done /* = fa
|
||||||
return;
|
return;
|
||||||
|
|
||||||
Error error;
|
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())));
|
emit errorReported(tr("Error"), tr("Failed to save state: %1").arg(QString::fromStdString(error.GetDescription())));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue