mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2025-04-28 12:58:05 +03:00
State: Simplify interthread communication and general cleanups.
This commit is contained in:
parent
d985f247e5
commit
26d5a69345
6 changed files with 178 additions and 257 deletions
|
@ -252,19 +252,15 @@ public final class NativeLibrary
|
|||
* Saves a game state to the slot number.
|
||||
*
|
||||
* @param slot The slot location to save state to.
|
||||
* @param wait If false, returns as early as possible.
|
||||
* If true, returns once the savestate has been written to disk.
|
||||
*/
|
||||
public static native void SaveState(int slot, boolean wait);
|
||||
public static native void SaveState(int slot);
|
||||
|
||||
/**
|
||||
* Saves a game state to the specified path.
|
||||
*
|
||||
* @param path The path to save state to.
|
||||
* @param wait If false, returns as early as possible.
|
||||
* If true, returns once the savestate has been written to disk.
|
||||
*/
|
||||
public static native void SaveStateAs(String path, boolean wait);
|
||||
public static native void SaveStateAs(String path);
|
||||
|
||||
/**
|
||||
* Loads a game state from the slot number.
|
||||
|
|
|
@ -464,16 +464,16 @@ class EmulationActivity : AppCompatActivity(), ThemeProvider {
|
|||
}
|
||||
|
||||
MENU_ACTION_TAKE_SCREENSHOT -> NativeLibrary.SaveScreenShot()
|
||||
MENU_ACTION_QUICK_SAVE -> NativeLibrary.SaveState(9, false)
|
||||
MENU_ACTION_QUICK_SAVE -> NativeLibrary.SaveState(9)
|
||||
MENU_ACTION_QUICK_LOAD -> NativeLibrary.LoadState(9)
|
||||
MENU_ACTION_SAVE_ROOT -> showSubMenu(SaveOrLoad.SAVE)
|
||||
MENU_ACTION_LOAD_ROOT -> showSubMenu(SaveOrLoad.LOAD)
|
||||
MENU_ACTION_SAVE_SLOT1 -> NativeLibrary.SaveState(0, false)
|
||||
MENU_ACTION_SAVE_SLOT2 -> NativeLibrary.SaveState(1, false)
|
||||
MENU_ACTION_SAVE_SLOT3 -> NativeLibrary.SaveState(2, false)
|
||||
MENU_ACTION_SAVE_SLOT4 -> NativeLibrary.SaveState(3, false)
|
||||
MENU_ACTION_SAVE_SLOT5 -> NativeLibrary.SaveState(4, false)
|
||||
MENU_ACTION_SAVE_SLOT6 -> NativeLibrary.SaveState(5, false)
|
||||
MENU_ACTION_SAVE_SLOT1 -> NativeLibrary.SaveState(0)
|
||||
MENU_ACTION_SAVE_SLOT2 -> NativeLibrary.SaveState(1)
|
||||
MENU_ACTION_SAVE_SLOT3 -> NativeLibrary.SaveState(2)
|
||||
MENU_ACTION_SAVE_SLOT4 -> NativeLibrary.SaveState(3)
|
||||
MENU_ACTION_SAVE_SLOT5 -> NativeLibrary.SaveState(4)
|
||||
MENU_ACTION_SAVE_SLOT6 -> NativeLibrary.SaveState(5)
|
||||
MENU_ACTION_LOAD_SLOT1 -> NativeLibrary.LoadState(0)
|
||||
MENU_ACTION_LOAD_SLOT2 -> NativeLibrary.LoadState(1)
|
||||
MENU_ACTION_LOAD_SLOT3 -> NativeLibrary.LoadState(2)
|
||||
|
|
|
@ -228,7 +228,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
|||
}
|
||||
}
|
||||
|
||||
fun saveTemporaryState() = NativeLibrary.SaveStateAs(temporaryStateFilePath, true)
|
||||
fun saveTemporaryState() = NativeLibrary.SaveStateAs(temporaryStateFilePath)
|
||||
|
||||
private val temporaryStateFilePath: String
|
||||
get() = "${requireContext().filesDir}${File.separator}temp.sav"
|
||||
|
|
|
@ -334,19 +334,17 @@ JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_eglBindAPI(J
|
|||
}
|
||||
|
||||
JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_SaveState(JNIEnv*, jclass,
|
||||
jint slot,
|
||||
jboolean wait)
|
||||
jint slot)
|
||||
{
|
||||
HostThreadLock guard;
|
||||
State::Save(Core::System::GetInstance(), slot, wait);
|
||||
State::Save(Core::System::GetInstance(), slot);
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_SaveStateAs(JNIEnv* env, jclass,
|
||||
jstring path,
|
||||
jboolean wait)
|
||||
jstring path)
|
||||
{
|
||||
HostThreadLock guard;
|
||||
State::SaveAs(Core::System::GetInstance(), GetJString(env, path), wait);
|
||||
State::SaveAs(Core::System::GetInstance(), GetJString(env, path));
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_LoadState(JNIEnv*, jclass,
|
||||
|
|
|
@ -4,11 +4,9 @@
|
|||
#include "Core/State.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <condition_variable>
|
||||
#include <filesystem>
|
||||
#include <locale>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
@ -20,12 +18,13 @@
|
|||
#include <lz4.h>
|
||||
#include <lzo/lzo1x.h>
|
||||
|
||||
#include "Common/Buffer.h"
|
||||
#include "Common/ChunkFile.h"
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Common/Contains.h"
|
||||
#include "Common/Event.h"
|
||||
#include "Common/FileUtil.h"
|
||||
#include "Common/IOFile.h"
|
||||
#include "Common/Logging/Log.h"
|
||||
#include "Common/MsgHandler.h"
|
||||
#include "Common/Thread.h"
|
||||
#include "Common/TimeUtil.h"
|
||||
|
@ -68,31 +67,23 @@ static AfterLoadCallbackFunc s_on_after_load_callback;
|
|||
|
||||
// Temporary undo state buffer
|
||||
static Common::UniqueBuffer<u8> s_undo_load_buffer;
|
||||
static std::mutex s_undo_load_buffer_mutex;
|
||||
|
||||
static std::mutex s_load_or_save_in_progress_mutex;
|
||||
// Used to estimate buffer size for the next save.
|
||||
static u32 s_last_state_size = 0;
|
||||
|
||||
struct CompressAndDumpState_args
|
||||
struct CompressAndDumpStateArgs
|
||||
{
|
||||
Common::UniqueBuffer<u8> buffer;
|
||||
std::string filename;
|
||||
std::shared_ptr<Common::Event> state_write_done_event;
|
||||
};
|
||||
|
||||
// Protects against simultaneous reads and writes to the final savestate location from multiple
|
||||
// threads.
|
||||
static std::mutex s_save_thread_mutex;
|
||||
|
||||
// Queue for compressing and writing savestates to disk.
|
||||
static Common::WorkQueueThread<CompressAndDumpState_args> s_save_thread;
|
||||
static Common::WorkQueueThread<CompressAndDumpStateArgs> s_save_thread;
|
||||
|
||||
// Keeps track of savestate writes that are currently happening, so we don't load a state while
|
||||
// another one is still saving. This is particularly important so if you save to a slot and then
|
||||
// immediately load from the same one, you don't accidentally load the state that's still at that
|
||||
// file path before the write is done.
|
||||
static std::mutex s_state_writes_in_queue_mutex;
|
||||
static size_t s_state_writes_in_queue;
|
||||
static std::condition_variable s_state_write_queue_is_empty;
|
||||
// Protect concurrent access of states on the filesystem.
|
||||
// Note: Only CPU-thread writes to filesystem states.
|
||||
// so this mutex is only needed for reads outside of CPU-thread.
|
||||
static std::mutex s_state_fs_mutex;
|
||||
|
||||
// Don't forget to increase this after doing changes on the savestate system
|
||||
constexpr u32 STATE_VERSION = 173; // Last changed in PR 10084
|
||||
|
@ -121,20 +112,24 @@ static const std::map<u32, std::pair<std::string, std::string>> s_old_versions =
|
|||
{38, {"4.0-4963", "4.0-5267"}}, {39, {"4.0-5279", "4.0-5525"}}, {40, {"4.0-5531", "4.0-5809"}},
|
||||
{41, {"4.0-5811", "4.0-5923"}}, {42, {"4.0-5925", "4.0-5946"}}};
|
||||
|
||||
enum
|
||||
{
|
||||
STATE_NONE = 0,
|
||||
STATE_SAVE = 1,
|
||||
STATE_LOAD = 2,
|
||||
};
|
||||
static constexpr bool s_use_compression = true;
|
||||
|
||||
static bool s_use_compression = true;
|
||||
|
||||
void EnableCompression(bool compression)
|
||||
// TODO: This will only actually wait if the CompressAndDump task has actually queued.
|
||||
// If Core is still inside DoState then this will return immediately.
|
||||
// It's possible to observed outdated (albeit coherent) timestamps in the UI.
|
||||
// I'm sure we have far worse actually critical race conditions elsewhere,
|
||||
// but I'd still consider this a race, so here's me documenting it.
|
||||
// There's no issue if Core was paused since DoState would happen directly in host-thread.
|
||||
static void WaitForSaveCompletion()
|
||||
{
|
||||
s_use_compression = compression;
|
||||
s_save_thread.WaitForCompletion();
|
||||
}
|
||||
|
||||
static Common::EventHook s_flush_data_hook =
|
||||
Core::FlushUnsavedDataEvent::Register(WaitForSaveCompletion, "State flush");
|
||||
|
||||
static bool ReadHeader(const std::string& filename, StateHeader& header);
|
||||
|
||||
static void DoState(Core::System& system, PointerWrap& p)
|
||||
{
|
||||
bool is_wii = system.IsWii() || system.IsMIOS();
|
||||
|
@ -201,48 +196,40 @@ static void DoState(Core::System& system, PointerWrap& p)
|
|||
#endif // USE_RETRO_ACHIEVEMENTS
|
||||
}
|
||||
|
||||
void LoadFromBuffer(Core::System& system, Common::UniqueBuffer<u8>& buffer)
|
||||
static bool LoadFromBuffer(Core::System& system, Common::UniqueBuffer<u8>& buffer)
|
||||
{
|
||||
if (NetPlay::IsNetPlayRunning())
|
||||
{
|
||||
OSD::AddMessage("Loading savestates is disabled in Netplay to prevent desyncs");
|
||||
return;
|
||||
}
|
||||
|
||||
if (AchievementManager::GetInstance().IsHardcoreModeActive())
|
||||
{
|
||||
OSD::AddMessage("Loading savestates is disabled in RetroAchievements hardcore mode");
|
||||
return;
|
||||
}
|
||||
|
||||
Core::RunOnCPUThread(
|
||||
system,
|
||||
[&] {
|
||||
u8* ptr = buffer.data();
|
||||
PointerWrap p(&ptr, buffer.size(), PointerWrap::Mode::Read);
|
||||
DoState(system, p);
|
||||
},
|
||||
true);
|
||||
u8* ptr = buffer.data();
|
||||
PointerWrap p(&ptr, buffer.size(), PointerWrap::Mode::Read);
|
||||
DoState(system, p);
|
||||
return p.IsReadMode();
|
||||
}
|
||||
|
||||
void SaveToBuffer(Core::System& system, Common::UniqueBuffer<u8>& buffer)
|
||||
static bool SaveToBuffer(Core::System& system, Common::UniqueBuffer<u8>& buffer)
|
||||
{
|
||||
Core::RunOnCPUThread(
|
||||
system,
|
||||
[&] {
|
||||
u8* ptr = nullptr;
|
||||
PointerWrap p_measure(&ptr, 0, PointerWrap::Mode::Measure);
|
||||
DoState(system, p_measure);
|
||||
// Attempt to save to our provided buffer as-is.
|
||||
// If buffer isn't large enough, PointerWrap transitions to MeasureMode,
|
||||
// and then we have our measurement for a second attempt.
|
||||
u8* ptr = buffer.data();
|
||||
PointerWrap pointer_wrap(&ptr, buffer.size(), PointerWrap::Mode::Write);
|
||||
DoState(system, pointer_wrap);
|
||||
const auto measured_size = pointer_wrap.GetOffsetFromPreviousPosition(buffer.data());
|
||||
|
||||
const size_t new_buffer_size = ptr - (u8*)(nullptr);
|
||||
if (new_buffer_size > buffer.size())
|
||||
buffer.reset(new_buffer_size);
|
||||
if (pointer_wrap.IsWriteMode())
|
||||
{
|
||||
s_last_state_size = measured_size;
|
||||
return true;
|
||||
}
|
||||
|
||||
ptr = buffer.data();
|
||||
PointerWrap p(&ptr, buffer.size(), PointerWrap::Mode::Write);
|
||||
DoState(system, p);
|
||||
},
|
||||
true);
|
||||
if (measured_size > buffer.size())
|
||||
{
|
||||
DEBUG_LOG_FMT(CORE, "SaveToBuffer: Growing buffer from size {} to measured size {}",
|
||||
buffer.size(), measured_size);
|
||||
buffer.reset(measured_size);
|
||||
return SaveToBuffer(system, buffer);
|
||||
}
|
||||
|
||||
// Buffer was large enough but we still failed for some other reason.
|
||||
return false;
|
||||
}
|
||||
|
||||
namespace
|
||||
|
@ -293,6 +280,8 @@ static std::string MakeStateFilename(int number);
|
|||
|
||||
static std::vector<SlotWithTimestamp> GetUsedSlotsWithTimestamp()
|
||||
{
|
||||
WaitForSaveCompletion();
|
||||
|
||||
std::vector<SlotWithTimestamp> result;
|
||||
StateHeader header;
|
||||
for (int i = 1; i <= (int)NUM_STATES; i++)
|
||||
|
@ -374,17 +363,18 @@ static void WriteHeadersToFile(size_t uncompressed_size, File::IOFile& f)
|
|||
// If StateExtendedHeader is amended to include more than the base, add WriteBytes() calls here.
|
||||
}
|
||||
|
||||
static void CompressAndDumpState(Core::System& system, CompressAndDumpState_args& save_args)
|
||||
static void CompressAndDumpState(Core::System& system, CompressAndDumpStateArgs& save_args)
|
||||
{
|
||||
const u8* const buffer_data = save_args.buffer.data();
|
||||
const size_t buffer_size = save_args.buffer.size();
|
||||
const std::string& filename = save_args.filename;
|
||||
|
||||
// Find free temporary filename.
|
||||
// TODO: The file exists check and the actual opening of the file should be atomic, we don't have
|
||||
// functions for that.
|
||||
|
||||
// TODO: The file exists check and the actual opening of the file should be atomic.
|
||||
// This is only an issue for multiple instances of dolphin operating on the same user folder.
|
||||
std::string temp_filename;
|
||||
size_t temp_counter = static_cast<size_t>(Common::CurrentThreadId());
|
||||
auto temp_counter = static_cast<size_t>(Common::CurrentThreadId());
|
||||
do
|
||||
{
|
||||
temp_filename = fmt::format("{}{}.tmp", filename, temp_counter);
|
||||
|
@ -413,7 +403,7 @@ static void CompressAndDumpState(Core::System& system, CompressAndDumpState_args
|
|||
const std::string dtmname = filename + ".dtm";
|
||||
|
||||
{
|
||||
std::lock_guard lk(s_save_thread_mutex);
|
||||
std::lock_guard lk{s_state_fs_mutex};
|
||||
|
||||
// Backup existing state (overwriting an existing backup, if any).
|
||||
if (File::Exists(filename))
|
||||
|
@ -460,65 +450,31 @@ static void CompressAndDumpState(Core::System& system, CompressAndDumpState_args
|
|||
Host_UpdateMainFrame();
|
||||
}
|
||||
|
||||
void SaveAs(Core::System& system, const std::string& filename, bool wait)
|
||||
void SaveAs(Core::System& system, std::string filename)
|
||||
{
|
||||
std::unique_lock lk(s_load_or_save_in_progress_mutex, std::try_to_lock);
|
||||
if (!lk)
|
||||
return;
|
||||
|
||||
Core::RunOnCPUThread(
|
||||
system,
|
||||
[&] {
|
||||
{
|
||||
std::lock_guard lk_(s_state_writes_in_queue_mutex);
|
||||
++s_state_writes_in_queue;
|
||||
}
|
||||
[&system, filename = std::move(filename)]() mutable {
|
||||
CompressAndDumpStateArgs dump_args;
|
||||
|
||||
// Measure the size of the buffer.
|
||||
u8* ptr = nullptr;
|
||||
PointerWrap p_measure(&ptr, 0, PointerWrap::Mode::Measure);
|
||||
DoState(system, p_measure);
|
||||
const size_t buffer_size = ptr - (u8*)(nullptr);
|
||||
// Try with a buffer a bit larger than the previous state.
|
||||
// This will often avoid the "Measure" step.
|
||||
const auto buffer_size_estimate = std::size_t(s_last_state_size) * 110 / 100;
|
||||
dump_args.buffer.reset(buffer_size_estimate);
|
||||
|
||||
// Then actually do the write.
|
||||
Common::UniqueBuffer<u8> current_buffer(buffer_size);
|
||||
ptr = current_buffer.data();
|
||||
PointerWrap p(&ptr, buffer_size, PointerWrap::Mode::Write);
|
||||
DoState(system, p);
|
||||
dump_args.filename = std::move(filename);
|
||||
|
||||
if (p.IsWriteMode())
|
||||
if (SaveToBuffer(system, dump_args.buffer))
|
||||
{
|
||||
Core::DisplayMessage("Saving State...", 1000);
|
||||
|
||||
std::shared_ptr<Common::Event> sync_event;
|
||||
|
||||
CompressAndDumpState_args save_args;
|
||||
save_args.buffer = std::move(current_buffer);
|
||||
save_args.filename = filename;
|
||||
if (wait)
|
||||
{
|
||||
sync_event = std::make_shared<Common::Event>();
|
||||
save_args.state_write_done_event = sync_event;
|
||||
}
|
||||
|
||||
s_save_thread.EmplaceItem(std::move(save_args));
|
||||
|
||||
if (sync_event)
|
||||
sync_event->Wait();
|
||||
s_save_thread.EmplaceItem(std::move(dump_args));
|
||||
}
|
||||
else
|
||||
{
|
||||
// someone aborted the save by changing the mode?
|
||||
{
|
||||
// Note: The worker thread takes care of this in the other branch.
|
||||
std::lock_guard lk_(s_state_writes_in_queue_mutex);
|
||||
if (--s_state_writes_in_queue == 0)
|
||||
s_state_write_queue_is_empty.notify_all();
|
||||
}
|
||||
Core::DisplayMessage("Unable to save: Internal DoState Error", 4000);
|
||||
}
|
||||
},
|
||||
true);
|
||||
false);
|
||||
}
|
||||
|
||||
static bool GetVersionFromLZO(StateHeader& header, File::IOFile& f)
|
||||
|
@ -618,18 +574,18 @@ static bool ReadStateHeaderFromFile(StateHeader& header, File::IOFile& f,
|
|||
return true;
|
||||
}
|
||||
|
||||
bool ReadHeader(const std::string& filename, StateHeader& header)
|
||||
static bool ReadHeader(const std::string& filename, StateHeader& header)
|
||||
{
|
||||
// ensure that the savestate write thread isn't moving around states while we do this
|
||||
std::lock_guard lk(s_save_thread_mutex);
|
||||
|
||||
File::IOFile f(filename, "rb");
|
||||
bool get_version_header = false;
|
||||
constexpr bool get_version_header = false;
|
||||
return ReadStateHeaderFromFile(header, f, get_version_header);
|
||||
}
|
||||
|
||||
std::string GetInfoStringOfSlot(int slot, bool translate)
|
||||
{
|
||||
WaitForSaveCompletion();
|
||||
std::lock_guard lk{s_state_fs_mutex};
|
||||
|
||||
std::string filename = MakeStateFilename(slot);
|
||||
if (!File::Exists(filename))
|
||||
return translate ? Common::GetStringT("Empty") : "Empty";
|
||||
|
@ -643,6 +599,9 @@ std::string GetInfoStringOfSlot(int slot, bool translate)
|
|||
|
||||
u64 GetUnixTimeOfSlot(int slot)
|
||||
{
|
||||
WaitForSaveCompletion();
|
||||
std::lock_guard lk{s_state_fs_mutex};
|
||||
|
||||
State::StateHeader header;
|
||||
if (!ReadHeader(MakeStateFilename(slot), header))
|
||||
return 0;
|
||||
|
@ -659,7 +618,7 @@ static bool DecompressLZ4(Common::UniqueBuffer<u8>& raw_buffer, u64 size, File::
|
|||
u64 total_bytes_read = 0;
|
||||
while (true)
|
||||
{
|
||||
s32 compressed_data_len;
|
||||
s32 compressed_data_len = 0;
|
||||
if (!f.ReadArray(&compressed_data_len, 1))
|
||||
{
|
||||
PanicAlertFmt("Could not read state data length");
|
||||
|
@ -679,8 +638,8 @@ static bool DecompressLZ4(Common::UniqueBuffer<u8>& raw_buffer, u64 size, File::
|
|||
return false;
|
||||
}
|
||||
|
||||
u32 max_decompress_size =
|
||||
static_cast<u32>(std::min((u64)LZ4_MAX_INPUT_SIZE, size - total_bytes_read));
|
||||
const auto max_decompress_size =
|
||||
static_cast<int>(std::min((u64)LZ4_MAX_INPUT_SIZE, size - total_bytes_read));
|
||||
|
||||
int bytes_read = LZ4_decompress_safe(
|
||||
compressed_data.get(), reinterpret_cast<char*>(raw_buffer.data()) + total_bytes_read,
|
||||
|
@ -699,7 +658,8 @@ static bool DecompressLZ4(Common::UniqueBuffer<u8>& raw_buffer, u64 size, File::
|
|||
{
|
||||
return true;
|
||||
}
|
||||
else if (total_bytes_read > size)
|
||||
|
||||
if (total_bytes_read > size)
|
||||
{
|
||||
PanicAlertFmtT("Internal LZ4 Error - payload size mismatch ({0} / {1}))", total_bytes_read,
|
||||
size);
|
||||
|
@ -713,7 +673,7 @@ static bool ValidateHeaders(const StateHeader& header)
|
|||
bool success = true;
|
||||
|
||||
// Game ID
|
||||
if (strncmp(SConfig::GetInstance().GetGameID().c_str(), header.legacy_header.game_id, 6))
|
||||
if (!std::ranges::equal(SConfig::GetInstance().GetGameID(), header.legacy_header.game_id))
|
||||
{
|
||||
Core::DisplayMessage(fmt::format("State belongs to a different game (ID {})",
|
||||
std::string_view{header.legacy_header.game_id,
|
||||
|
@ -722,8 +682,8 @@ static bool ValidateHeaders(const StateHeader& header)
|
|||
return false;
|
||||
}
|
||||
|
||||
// Check both the state version and the revision string
|
||||
std::string current_str = Common::GetScmRevStr();
|
||||
// Check the state version.
|
||||
// FYI: revision string isn't currently tested, that might make things annoying.
|
||||
std::string loaded_str = header.version_string;
|
||||
const u32 loaded_version = header.version_header.version_cookie - COOKIE_BASE;
|
||||
|
||||
|
@ -758,22 +718,7 @@ static bool ValidateHeaders(const StateHeader& header)
|
|||
static void LoadFileStateData(const std::string& filename, Common::UniqueBuffer<u8>& ret_data)
|
||||
{
|
||||
File::IOFile f;
|
||||
|
||||
{
|
||||
// If a state is currently saving, wait for that to end or time out.
|
||||
std::unique_lock lk(s_state_writes_in_queue_mutex);
|
||||
if (s_state_writes_in_queue != 0)
|
||||
{
|
||||
if (!s_state_write_queue_is_empty.wait_for(lk, std::chrono::seconds(3),
|
||||
[]() { return s_state_writes_in_queue == 0; }))
|
||||
{
|
||||
Core::DisplayMessage(
|
||||
"A previous state saving operation is still in progress, cancelling load.", 2000);
|
||||
return;
|
||||
}
|
||||
}
|
||||
f.Open(filename, "rb");
|
||||
}
|
||||
f.Open(filename, "rb");
|
||||
|
||||
StateHeader header;
|
||||
if (!ReadStateHeaderFromFile(header, f) || !ValidateHeaders(header))
|
||||
|
@ -837,7 +782,7 @@ static void LoadFileStateData(const std::string& filename, Common::UniqueBuffer<
|
|||
ret_data.swap(buffer);
|
||||
}
|
||||
|
||||
void LoadAs(Core::System& system, const std::string& filename)
|
||||
void LoadAs(Core::System& system, std::string filename)
|
||||
{
|
||||
if (!Core::IsRunningOrStarting(system))
|
||||
return;
|
||||
|
@ -854,18 +799,13 @@ void LoadAs(Core::System& system, const std::string& filename)
|
|||
return;
|
||||
}
|
||||
|
||||
std::unique_lock lk(s_load_or_save_in_progress_mutex, std::try_to_lock);
|
||||
if (!lk)
|
||||
return;
|
||||
|
||||
Core::RunOnCPUThread(
|
||||
system,
|
||||
[&] {
|
||||
[&system, filename = std::move(filename)] {
|
||||
// Save temp buffer for undo load state
|
||||
auto& movie = system.GetMovie();
|
||||
if (!movie.IsJustStartingRecordingInputFromSaveState())
|
||||
{
|
||||
std::lock_guard lk2(s_undo_load_buffer_mutex);
|
||||
SaveToBuffer(system, s_undo_load_buffer);
|
||||
const std::string dtmpath = File::GetUserPath(D_STATESAVES_IDX) + "undo.dtm";
|
||||
if (movie.IsMovieActive())
|
||||
|
@ -874,27 +814,26 @@ void LoadAs(Core::System& system, const std::string& filename)
|
|||
File::Delete(dtmpath);
|
||||
}
|
||||
|
||||
bool loaded = false;
|
||||
bool loadedSuccessfully = false;
|
||||
bool was_file_read = false;
|
||||
bool loaded_successfully = false;
|
||||
|
||||
// brackets here are so buffer gets freed ASAP
|
||||
{
|
||||
Common::UniqueBuffer<u8> buffer;
|
||||
|
||||
WaitForSaveCompletion();
|
||||
LoadFileStateData(filename, buffer);
|
||||
|
||||
if (!buffer.empty())
|
||||
{
|
||||
u8* ptr = buffer.data();
|
||||
PointerWrap p(&ptr, buffer.size(), PointerWrap::Mode::Read);
|
||||
DoState(system, p);
|
||||
loaded = true;
|
||||
loadedSuccessfully = p.IsReadMode();
|
||||
was_file_read = true;
|
||||
loaded_successfully = LoadFromBuffer(system, buffer);
|
||||
}
|
||||
}
|
||||
|
||||
if (loaded)
|
||||
if (was_file_read)
|
||||
{
|
||||
if (loadedSuccessfully)
|
||||
if (loaded_successfully)
|
||||
{
|
||||
std::filesystem::path tempfilename(filename);
|
||||
Core::DisplayMessage(
|
||||
|
@ -917,7 +856,7 @@ void LoadAs(Core::System& system, const std::string& filename)
|
|||
if (s_on_after_load_callback)
|
||||
s_on_after_load_callback();
|
||||
},
|
||||
true);
|
||||
false);
|
||||
}
|
||||
|
||||
void SetOnAfterLoadCallback(AfterLoadCallbackFunc callback)
|
||||
|
@ -927,25 +866,14 @@ void SetOnAfterLoadCallback(AfterLoadCallbackFunc callback)
|
|||
|
||||
void Init(Core::System& system)
|
||||
{
|
||||
s_save_thread.Reset("Savestate Worker", [&system](CompressAndDumpState_args args) {
|
||||
s_save_thread.Reset("Savestate Worker", [&system](CompressAndDumpStateArgs args) {
|
||||
CompressAndDumpState(system, args);
|
||||
|
||||
{
|
||||
std::lock_guard lk(s_state_writes_in_queue_mutex);
|
||||
if (--s_state_writes_in_queue == 0)
|
||||
s_state_write_queue_is_empty.notify_all();
|
||||
}
|
||||
|
||||
if (args.state_write_done_event)
|
||||
args.state_write_done_event->Set();
|
||||
});
|
||||
}
|
||||
|
||||
void Shutdown()
|
||||
{
|
||||
s_save_thread.Shutdown();
|
||||
|
||||
std::lock_guard lk(s_undo_load_buffer_mutex);
|
||||
s_undo_load_buffer.reset();
|
||||
}
|
||||
|
||||
|
@ -955,9 +883,9 @@ static std::string MakeStateFilename(int number)
|
|||
SConfig::GetInstance().GetGameID(), number);
|
||||
}
|
||||
|
||||
void Save(Core::System& system, int slot, bool wait)
|
||||
void Save(Core::System& system, int slot)
|
||||
{
|
||||
SaveAs(system, MakeStateFilename(slot), wait);
|
||||
SaveAs(system, MakeStateFilename(slot));
|
||||
}
|
||||
|
||||
void Load(Core::System& system, int slot)
|
||||
|
@ -967,68 +895,76 @@ void Load(Core::System& system, int slot)
|
|||
|
||||
void LoadLastSaved(Core::System& system, int i)
|
||||
{
|
||||
if (i <= 0)
|
||||
{
|
||||
Core::DisplayMessage("State doesn't exist", 2000);
|
||||
return;
|
||||
}
|
||||
Core::RunOnCPUThread(
|
||||
system,
|
||||
[&system, i] {
|
||||
std::vector<SlotWithTimestamp> used_slots = GetUsedSlotsWithTimestamp();
|
||||
if (static_cast<size_t>(i) > used_slots.size())
|
||||
{
|
||||
Core::DisplayMessage("State doesn't exist", 2000);
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<SlotWithTimestamp> used_slots = GetUsedSlotsWithTimestamp();
|
||||
if (static_cast<size_t>(i) > used_slots.size())
|
||||
{
|
||||
Core::DisplayMessage("State doesn't exist", 2000);
|
||||
return;
|
||||
}
|
||||
|
||||
std::ranges::stable_sort(used_slots, {}, &SlotWithTimestamp::timestamp);
|
||||
Load(system, (used_slots.end() - i)->slot);
|
||||
std::ranges::stable_sort(used_slots, std::ranges::greater{}, &SlotWithTimestamp::timestamp);
|
||||
Load(system, used_slots[i].slot);
|
||||
},
|
||||
false);
|
||||
}
|
||||
|
||||
// must wait for state to be written because it must know if all slots are taken
|
||||
void SaveFirstSaved(Core::System& system)
|
||||
{
|
||||
std::vector<SlotWithTimestamp> used_slots = GetUsedSlotsWithTimestamp();
|
||||
if (used_slots.size() < NUM_STATES)
|
||||
{
|
||||
// save to an empty slot
|
||||
Save(system, GetEmptySlot(used_slots), true);
|
||||
return;
|
||||
}
|
||||
Core::RunOnCPUThread(
|
||||
system,
|
||||
[&system] {
|
||||
std::vector<SlotWithTimestamp> used_slots = GetUsedSlotsWithTimestamp();
|
||||
if (used_slots.size() < NUM_STATES)
|
||||
{
|
||||
// save to an empty slot
|
||||
Save(system, GetEmptySlot(used_slots));
|
||||
return;
|
||||
}
|
||||
|
||||
// overwrite the oldest state
|
||||
std::ranges::stable_sort(used_slots, {}, &SlotWithTimestamp::timestamp);
|
||||
Save(system, used_slots.front().slot, true);
|
||||
// overwrite the oldest state
|
||||
std::ranges::stable_sort(used_slots, {}, &SlotWithTimestamp::timestamp);
|
||||
Save(system, used_slots.front().slot);
|
||||
},
|
||||
false);
|
||||
}
|
||||
|
||||
// Load the last state before loading the state
|
||||
void UndoLoadState(Core::System& system)
|
||||
{
|
||||
std::lock_guard lk(s_undo_load_buffer_mutex);
|
||||
if (!s_undo_load_buffer.empty())
|
||||
{
|
||||
auto& movie = system.GetMovie();
|
||||
if (movie.IsMovieActive())
|
||||
{
|
||||
const std::string dtmpath = File::GetUserPath(D_STATESAVES_IDX) + "undo.dtm";
|
||||
if (File::Exists(dtmpath))
|
||||
{
|
||||
LoadFromBuffer(system, s_undo_load_buffer);
|
||||
movie.LoadInput(dtmpath);
|
||||
}
|
||||
else
|
||||
{
|
||||
PanicAlertFmtT("No undo.dtm found, aborting undo load state to prevent movie desyncs");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
LoadFromBuffer(system, s_undo_load_buffer);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
PanicAlertFmtT("There is nothing to undo!");
|
||||
}
|
||||
Core::RunOnCPUThread(
|
||||
system,
|
||||
[&system] {
|
||||
if (!s_undo_load_buffer.empty())
|
||||
{
|
||||
auto& movie = system.GetMovie();
|
||||
if (movie.IsMovieActive())
|
||||
{
|
||||
const std::string dtmpath = File::GetUserPath(D_STATESAVES_IDX) + "undo.dtm";
|
||||
if (File::Exists(dtmpath))
|
||||
{
|
||||
LoadFromBuffer(system, s_undo_load_buffer);
|
||||
movie.LoadInput(dtmpath);
|
||||
}
|
||||
else
|
||||
{
|
||||
PanicAlertFmtT(
|
||||
"No undo.dtm found, aborting undo load state to prevent movie desyncs");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
LoadFromBuffer(system, s_undo_load_buffer);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
PanicAlertFmtT("There is nothing to undo!");
|
||||
}
|
||||
},
|
||||
false);
|
||||
}
|
||||
|
||||
// Load the state that the last save state overwritten on
|
||||
|
|
|
@ -10,7 +10,6 @@
|
|||
#include <string>
|
||||
#include <type_traits>
|
||||
|
||||
#include "Common/Buffer.h"
|
||||
#include "Common/CommonTypes.h"
|
||||
|
||||
namespace Core
|
||||
|
@ -81,13 +80,8 @@ struct StateExtendedHeader
|
|||
};
|
||||
|
||||
void Init(Core::System& system);
|
||||
|
||||
void Shutdown();
|
||||
|
||||
void EnableCompression(bool compression);
|
||||
|
||||
bool ReadHeader(const std::string& filename, StateHeader& header);
|
||||
|
||||
// Returns a string containing information of the savestate in the given slot
|
||||
// which can be presented to the user for identification purposes
|
||||
std::string GetInfoStringOfSlot(int slot, bool translate = true);
|
||||
|
@ -100,14 +94,11 @@ u64 GetUnixTimeOfSlot(int slot);
|
|||
// If we're in the main CPU thread then they run immediately instead
|
||||
// because some things (like Lua) need them to run immediately.
|
||||
// Slots from 0-99.
|
||||
void Save(Core::System& system, int slot, bool wait = false);
|
||||
void Save(Core::System& system, int slot);
|
||||
void Load(Core::System& system, int slot);
|
||||
|
||||
void SaveAs(Core::System& system, const std::string& filename, bool wait = false);
|
||||
void LoadAs(Core::System& system, const std::string& filename);
|
||||
|
||||
void SaveToBuffer(Core::System& system, Common::UniqueBuffer<u8>& buffer);
|
||||
void LoadFromBuffer(Core::System& system, const Common::UniqueBuffer<u8>& buffer);
|
||||
void SaveAs(Core::System& system, std::string filename);
|
||||
void LoadAs(Core::System& system, std::string filename);
|
||||
|
||||
void LoadLastSaved(Core::System& system, int i = 1);
|
||||
void SaveFirstSaved(Core::System& system);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue