recording: Resolve issues discovered while testing around savestate handling

* Add a flag to explicitly indicate if the initial SS has been loaded.
* Additionally: bracket formatting / spaces -> tabs.
This commit is contained in:
Tyler Wilding 2020-06-21 00:50:02 -04:00 committed by refractionpcsx2
parent 6c80e6b93f
commit 74bba35765
8 changed files with 125 additions and 68 deletions

View File

@ -39,27 +39,51 @@ void SaveStateBase::InputRecordingFreeze()
Freeze(g_FrameCount); Freeze(g_FrameCount);
#ifndef DISABLE_RECORDING #ifndef DISABLE_RECORDING
// Explicitly set the frame change tracking variable as to not if (g_InputRecording.IsRecordingActive())
// detect loading a savestate as a frame being drawn {
g_InputRecordingControls.SetFrameCountTracker(g_FrameCount); // Explicitly set the frame change tracking variable as to not
// detect loading a savestate as a frame being drawn
g_InputRecordingControls.SetFrameCountTracker(g_FrameCount);
// If the starting savestate has been loaded (on a current-frame recording) and a save-state is loaded while recording
// or replaying the movie it is an undo operation that needs to be tracked.
//
// The reason it's also an undo operation when replaying is because the user can switch modes at any time
// and begin undoing. While this isn't impossible to resolve, it's a separate issue and not of the utmost importance (this is just interesting metadata)
if (IsLoading() && !g_InputRecording.IsSavestateInitializing())
{
g_InputRecording.GetInputRecordingData().IncrementUndoCount();
// Reloading a save-state means the internal recording frame counter may need to be adjusted
// Since we persist the g_FrameCount of the beginning of the movie, we can use it to recalculate it
u32 newFrameCounter = g_FrameCount - (g_InputRecording.GetStartingFrame());
// It is possible for someone to load a savestate outside of the original recording's context
// this should be avoided (hence the log) but I don't think there is a mechanism to reverse loading
// the save-state
// Therefore, the best we can do is limit the frame counter within the min/max of the recording
if (newFrameCounter < 0)
{
newFrameCounter = 0;
recordingConLog(L"[REC]: Warning, you loaded a savestate outside of the bounds of the original recording. This should be avoided. Savestate's framecount has been ignored.\n");
}
else if (newFrameCounter >= g_InputRecording.GetInputRecordingData().GetTotalFrames())
{
newFrameCounter = g_InputRecording.GetInputRecordingData().GetTotalFrames();
recordingConLog(L"[REC]: Warning, you loaded a savestate outside of the bounds of the original recording. This should be avoided. Savestate's framecount has been ignored.\n");
}
g_InputRecording.SetFrameCounter(newFrameCounter);
}
}
// Loading a save-state is an asynchronous task, if we are playing a recording // Loading a save-state is an asynchronous task, if we are playing a recording
// that starts from a savestate (not power-on) and the starting (pcsx2 internal) frame // that starts from a savestate (not power-on) and the starting (pcsx2 internal) frame
// marker has not been set (which comes from the save-state), we initialize it. // marker has not been set (which comes from the save-state), we initialize it.
// TODO - get rid of the -1 if (g_InputRecording.IsSavestateInitializing())
if (g_InputRecording.GetStartingFrame() == -1 && g_InputRecording.GetInputRecordingData().FromSaveState()) { {
g_InputRecording.SetStartingFrame(g_FrameCount); g_InputRecording.SetStartingFrame(g_FrameCount);
g_InputRecording.SavestateInitialized();
// TODO - make a function of my own to simplify working with the logging macros // TODO - make a function of my own to simplify working with the logging macros
recordingConLog(wxString::Format(L"[REC]: Internal Starting Frame: %d\n", g_InputRecording.GetStartingFrame())); recordingConLog(wxString::Format(L"[REC]: Internal Starting Frame: %d\n", g_InputRecording.GetStartingFrame()));
} }
// Otherwise the starting savestate has been loaded and if loaded a save-state while recording the movie
// it is an undo operation that needs to be tracked.
else if (g_InputRecording.RecordingActive() && IsLoading())
{
g_InputRecording.GetInputRecordingData().IncrementUndoCount();
// Reloading a save-state means the internal recording frame counter may need to be adjusted
// Since we persist the g_FrameCount of the beginning of the movie, we can use it to recalculate it
g_InputRecording.SetFrameCounter(g_FrameCount - (g_InputRecording.GetStartingFrame()));
}
#endif #endif
} }
@ -142,7 +166,8 @@ u32 InputRecording::GetStartingFrame()
void InputRecording::IncrementFrameCounter() void InputRecording::IncrementFrameCounter()
{ {
frameCounter++; frameCounter++;
if (state == InputRecordingMode::Recording) { if (state == InputRecordingMode::Recording)
{
GetInputRecordingData().SetTotalFrames(frameCounter); GetInputRecordingData().SetTotalFrames(frameCounter);
} }
} }
@ -152,16 +177,21 @@ bool InputRecording::IsInterruptFrame()
return fInterruptFrame; return fInterruptFrame;
} }
bool InputRecording::IsRecordingReplaying() bool InputRecording::IsRecordingActive()
{
return RecordingActive() && state == InputRecordingMode::Replaying;
}
bool InputRecording::RecordingActive()
{ {
return state != InputRecordingMode::NoneActive; return state != InputRecordingMode::NoneActive;
} }
bool InputRecording::IsSavestateInitializing()
{
return savestateInitializing;
}
bool InputRecording::IsRecordingReplaying()
{
return IsRecordingActive() && state == InputRecordingMode::Replaying;
}
wxString InputRecording::RecordingModeTitleSegment() wxString InputRecording::RecordingModeTitleSegment()
{ {
switch (state) switch (state)
@ -192,6 +222,11 @@ void InputRecording::RecordModeToggle()
} }
} }
void InputRecording::SavestateInitialized()
{
savestateInitializing = false;
}
void InputRecording::SetFrameCounter(u32 newFrameCounter) void InputRecording::SetFrameCounter(u32 newFrameCounter)
{ {
frameCounter = newFrameCounter; frameCounter = newFrameCounter;
@ -210,7 +245,8 @@ void InputRecording::Stop()
{ {
// Reset the frame counter when starting a new recording // Reset the frame counter when starting a new recording
frameCounter = 0; frameCounter = 0;
startingFrame = -1; startingFrame = 0;
savestateInitializing = false;
state = InputRecordingMode::NoneActive; state = InputRecordingMode::NoneActive;
if (inputRecordingData.Close()) if (inputRecordingData.Close())
{ {
@ -220,6 +256,7 @@ void InputRecording::Stop()
bool InputRecording::Create(wxString FileName, bool fromSaveState, wxString authorName) bool InputRecording::Create(wxString FileName, bool fromSaveState, wxString authorName)
{ {
savestateInitializing = fromSaveState;
if (!inputRecordingData.OpenNew(FileName, fromSaveState)) if (!inputRecordingData.OpenNew(FileName, fromSaveState))
{ {
return false; return false;
@ -232,6 +269,7 @@ bool InputRecording::Create(wxString FileName, bool fromSaveState, wxString auth
{ {
inputRecordingData.GetHeader().SetAuthor(authorName); inputRecordingData.GetHeader().SetAuthor(authorName);
} }
// Set Game Name // Set Game Name
inputRecordingData.GetHeader().SetGameName(resolveGameName()); inputRecordingData.GetHeader().SetGameName(resolveGameName());
// Write header contents // Write header contents
@ -243,7 +281,7 @@ bool InputRecording::Create(wxString FileName, bool fromSaveState, wxString auth
bool InputRecording::Play(wxString fileName) bool InputRecording::Play(wxString fileName)
{ {
if (RecordingActive()) if (IsRecordingActive())
Stop(); Stop();
if (!inputRecordingData.OpenExisting(fileName)) if (!inputRecordingData.OpenExisting(fileName))
@ -259,21 +297,17 @@ bool InputRecording::Play(wxString fileName)
inputRecordingData.Close(); inputRecordingData.Close();
return false; return false;
} }
FILE* ssFileCheck = wxFopen(inputRecordingData.GetFilename() + "_SaveState.p2s", "r"); if (!wxFileExists(inputRecordingData.GetFilename() + "_SaveState.p2s"))
if (ssFileCheck == NULL)
{ {
recordingConLog(wxString::Format("[REC]: Could not locate savestate file at location - %s_SaveState.p2s\n", inputRecordingData.GetFilename())); recordingConLog(wxString::Format("[REC]: Could not locate savestate file at location - %s_SaveState.p2s\n", inputRecordingData.GetFilename()));
inputRecordingData.Close(); inputRecordingData.Close();
return false; return false;
} }
fclose(ssFileCheck); savestateInitializing = true;
StateCopy_LoadFromFile(inputRecordingData.GetFilename() + "_SaveState.p2s"); StateCopy_LoadFromFile(inputRecordingData.GetFilename() + "_SaveState.p2s");
} }
else else
{
g_InputRecordingControls.Resume();
sApp.SysExecute(); sApp.SysExecute();
}
// Check if the current game matches with the one used to make the original recording // Check if the current game matches with the one used to make the original recording
if (!g_Conf->CurrentIso.IsEmpty()) if (!g_Conf->CurrentIso.IsEmpty())

View File

@ -41,8 +41,15 @@ public:
bool IsInterruptFrame(); bool IsInterruptFrame();
// If there is currently an input recording being played back or actively being recorded // If there is currently an input recording being played back or actively being recorded
bool RecordingActive(); bool IsRecordingActive();
bool IsRecordingReplaying();
// Whether or not the recording's initial save state has yet to be loaded or saved and
// the rest of the recording can be initialized
// This is not applicable to recordings from a "power-on" state
bool IsSavestateInitializing();
// If there is currently an input recording being played back
bool IsRecordingReplaying();
// String representation of the current recording mode to be interpolated into the title // String representation of the current recording mode to be interpolated into the title
wxString RecordingModeTitleSegment(); wxString RecordingModeTitleSegment();
@ -50,8 +57,12 @@ public:
// Switches between recording and replaying the active input recording file // Switches between recording and replaying the active input recording file
void RecordModeToggle(); void RecordModeToggle();
// Mark the recording's initial savestate as having been loaded or saved successfully
void SavestateInitialized();
// Set the running frame counter for the input recording to an arbitrary value // Set the running frame counter for the input recording to an arbitrary value
void SetFrameCounter(u32 newFrameCounter); void SetFrameCounter(u32 newFrameCounter);
// Store the starting internal PCSX2 g_FrameCount value // Store the starting internal PCSX2 g_FrameCount value
void SetStartingFrame(u32 newStartingFrame); void SetStartingFrame(u32 newStartingFrame);
@ -65,18 +76,20 @@ public:
void Stop(); void Stop();
private: private:
enum class InputRecordingMode { enum class InputRecordingMode
{
NoneActive, NoneActive,
Recording, Recording,
Replaying, Replaying,
}; };
// DEPRECATED: Slated for removal
bool fInterruptFrame = false; bool fInterruptFrame = false;
InputRecordingFile inputRecordingData; InputRecordingFile inputRecordingData;
bool savestateInitializing = false;
u32 startingFrame = 0;
InputRecordingMode state = InputRecording::InputRecordingMode::NoneActive; InputRecordingMode state = InputRecording::InputRecordingMode::NoneActive;
u32 frameCounter = 0; u32 frameCounter = 0;
u32 startingFrame = -1;
// Resolve the name and region of the game currently loaded using the GameDB // Resolve the name and region of the game currently loaded using the GameDB
// If the game cannot be found in the DB, the fallback is the ISO filename // If the game cannot be found in the DB, the fallback is the ISO filename

View File

@ -39,18 +39,23 @@ void InputRecordingControls::HandleFrameAdvanceAndPausing()
// As a safeguard, use the global g_FrameCount to know when the frame counter has truly changed. // As a safeguard, use the global g_FrameCount to know when the frame counter has truly changed.
// //
// NOTE - Do not mutate g_FrameCount or use it's value to set the input recording's internal frame counter // NOTE - Do not mutate g_FrameCount or use it's value to set the input recording's internal frame counter
if (frameCountTracker != g_FrameCount) { if (frameCountTracker != g_FrameCount)
{
frameCountTracker = g_FrameCount; frameCountTracker = g_FrameCount;
g_InputRecording.IncrementFrameCounter(); g_InputRecording.IncrementFrameCounter();
} else { }
if (pauseEmulation) { else
{
if (pauseEmulation)
{
emulationCurrentlyPaused = true; emulationCurrentlyPaused = true;
CoreThread.PauseSelf(); CoreThread.PauseSelf();
} }
return; return;
} }
if (g_InputRecording.IsRecordingReplaying() && g_InputRecording.GetFrameCounter() >= g_InputRecording.GetInputRecordingData().GetTotalFrames()) { if (g_InputRecording.IsRecordingReplaying() && g_InputRecording.GetFrameCounter() >= g_InputRecording.GetInputRecordingData().GetTotalFrames())
{
pauseEmulation = true; pauseEmulation = true;
} }
@ -62,7 +67,8 @@ void InputRecordingControls::HandleFrameAdvanceAndPausing()
} }
// Pause emulation if we need to (either due to frame advancing, or pause has been explicitly toggled on) // Pause emulation if we need to (either due to frame advancing, or pause has been explicitly toggled on)
if (pauseEmulation && CoreThread.IsOpen() && CoreThread.IsRunning()) { if (pauseEmulation && CoreThread.IsOpen() && CoreThread.IsRunning())
{
emulationCurrentlyPaused = true; emulationCurrentlyPaused = true;
CoreThread.PauseSelf(); CoreThread.PauseSelf();
} }
@ -70,7 +76,8 @@ void InputRecordingControls::HandleFrameAdvanceAndPausing()
void InputRecordingControls::ResumeCoreThreadIfStarted() void InputRecordingControls::ResumeCoreThreadIfStarted()
{ {
if (resumeEmulation && CoreThread.IsOpen() && CoreThread.IsPaused()) { if (resumeEmulation && CoreThread.IsOpen() && CoreThread.IsPaused())
{
CoreThread.Resume(); CoreThread.Resume();
resumeEmulation = false; resumeEmulation = false;
emulationCurrentlyPaused = false; emulationCurrentlyPaused = false;
@ -81,7 +88,7 @@ void InputRecordingControls::FrameAdvance()
{ {
if (g_InputRecording.IsRecordingReplaying() && g_InputRecording.GetFrameCounter() >= g_InputRecording.GetInputRecordingData().GetTotalFrames()) if (g_InputRecording.IsRecordingReplaying() && g_InputRecording.GetFrameCounter() >= g_InputRecording.GetInputRecordingData().GetTotalFrames())
{ {
g_InputRecording.RecordModeToggle(); g_InputRecording.RecordModeToggle();
return; return;
} }
frameAdvanceMarker = g_InputRecording.GetFrameCounter(); frameAdvanceMarker = g_InputRecording.GetFrameCounter();
@ -102,11 +109,13 @@ void InputRecordingControls::Pause()
void InputRecordingControls::PauseImmediately() void InputRecordingControls::PauseImmediately()
{ {
if (CoreThread.IsPaused()) { if (CoreThread.IsPaused())
{
return; return;
} }
Pause(); Pause();
if (pauseEmulation && CoreThread.IsOpen() && CoreThread.IsRunning()) { if (pauseEmulation && CoreThread.IsOpen() && CoreThread.IsRunning())
{
emulationCurrentlyPaused = true; emulationCurrentlyPaused = true;
CoreThread.PauseSelf(); CoreThread.PauseSelf();
} }
@ -114,8 +123,9 @@ void InputRecordingControls::PauseImmediately()
void InputRecordingControls::Resume() void InputRecordingControls::Resume()
{ {
if (g_InputRecording.IsRecordingReplaying() && g_InputRecording.GetFrameCounter() >= g_InputRecording.GetInputRecordingData().GetTotalFrames()) { if (g_InputRecording.IsRecordingReplaying() && g_InputRecording.GetFrameCounter() >= g_InputRecording.GetInputRecordingData().GetTotalFrames())
g_InputRecording.RecordModeToggle(); {
g_InputRecording.RecordModeToggle();
return; return;
} }
pauseEmulation = false; pauseEmulation = false;
@ -129,8 +139,9 @@ void InputRecordingControls::SetFrameCountTracker(u32 newFrame)
void InputRecordingControls::TogglePause() void InputRecordingControls::TogglePause()
{ {
if (pauseEmulation && g_InputRecording.IsRecordingReplaying() && g_InputRecording.GetFrameCounter() >= g_InputRecording.GetInputRecordingData().GetTotalFrames()) { if (pauseEmulation && g_InputRecording.IsRecordingReplaying() && g_InputRecording.GetFrameCounter() >= g_InputRecording.GetInputRecordingData().GetTotalFrames())
g_InputRecording.RecordModeToggle(); {
g_InputRecording.RecordModeToggle();
return; return;
} }
pauseEmulation = !pauseEmulation; pauseEmulation = !pauseEmulation;

View File

@ -38,26 +38,26 @@ public:
// Resume emulation (incase the emulation is currently paused) and pause after a single frame has passed // Resume emulation (incase the emulation is currently paused) and pause after a single frame has passed
void FrameAdvance(); void FrameAdvance();
// Returns true if the input recording has been paused, which can occur: // Returns true if the input recording has been paused, which can occur:
// - After a single frame has passed after InputRecordingControls::FrameAdvance // - After a single frame has passed after InputRecordingControls::FrameAdvance
// - Explicitly paused via InputRecordingControls::TogglePause // - Explicitly paused via InputRecordingControls::TogglePause
bool IsRecordingPaused(); bool IsRecordingPaused();
// Pause emulation at the next available Vsync // Pause emulation at the next available Vsync
void Pause(); void Pause();
// Pause emulation immediately, not waiting for the next Vsync // Pause emulation immediately, not waiting for the next Vsync
void PauseImmediately(); void PauseImmediately();
// Resume emulation when the next pcsx2 App event is handled // Resume emulation when the next pcsx2 App event is handled
void Resume(); void Resume();
void SetFrameCountTracker(u32 newFrame); void SetFrameCountTracker(u32 newFrame);
// Alternates emulation between a paused and unpaused state // Alternates emulation between a paused and unpaused state
void TogglePause(); void TogglePause();
private: private:
// Indicates if the input recording controls have explicitly paused emulation or not // Indicates if the input recording controls have explicitly paused emulation or not
bool emulationCurrentlyPaused = false; bool emulationCurrentlyPaused = false;
// Indicates on the next VSync if we are frame advancing, this value // Indicates on the next VSync if we are frame advancing, this value
// and should be cleared once a single frame has passed // and should be cleared once a single frame has passed
bool frameAdvancing = false; bool frameAdvancing = false;
// The input recording frame that frame advancing began on // The input recording frame that frame advancing began on
u32 frameAdvanceMarker = 0; u32 frameAdvanceMarker = 0;
// Used to detect if the internal PCSX2 g_FrameCount has changed // Used to detect if the internal PCSX2 g_FrameCount has changed

View File

@ -156,7 +156,7 @@ bool InputRecordingFile::OpenNew(const wxString path, bool fromSavestate)
sApp.SysExecute(); sApp.SysExecute();
return true; return true;
} }
return open(path, true); return false;
} }
bool InputRecordingFile::OpenExisting(const wxString path) bool InputRecordingFile::OpenExisting(const wxString path)

View File

@ -1059,7 +1059,7 @@ void Pcsx2App::OnProgramLogClosed( wxWindowID id )
void Pcsx2App::OnMainFrameClosed( wxWindowID id ) void Pcsx2App::OnMainFrameClosed( wxWindowID id )
{ {
#ifndef DISABLE_RECORDING #ifndef DISABLE_RECORDING
if (g_InputRecording.RecordingActive()) if (g_InputRecording.IsRecordingActive())
{ {
g_InputRecording.Stop(); g_InputRecording.Stop();
} }

View File

@ -732,7 +732,7 @@ void GSFrame::OnUpdateTitle( wxTimerEvent& evt )
#ifndef DISABLE_RECORDING #ifndef DISABLE_RECORDING
wxString title; wxString title;
wxString movieMode; wxString movieMode;
if (g_InputRecording.RecordingActive()) if (g_InputRecording.IsRecordingActive())
{ {
title = templates.RecordingTemplate; title = templates.RecordingTemplate;
title.Replace(L"${frame}", pxsFmt(L"%d", g_InputRecording.GetFrameCounter())); title.Replace(L"${frame}", pxsFmt(L"%d", g_InputRecording.GetFrameCounter()));

View File

@ -32,7 +32,7 @@
#ifndef DISABLE_RECORDING #ifndef DISABLE_RECORDING
# include "Recording/InputRecording.h" # include "Recording/InputRecording.h"
# include "Recording/InputRecordingControls.h" # include "Recording/InputRecordingControls.h"
# include "Recording/VirtualPad.h" # include "Recording/VirtualPad.h"
#endif #endif
@ -473,7 +473,6 @@ void MainEmuFrame::Menu_EnableBackupStates_Click( wxCommandEvent& )
// (1st save after the toggle keeps the old pre-toggle value).. // (1st save after the toggle keeps the old pre-toggle value)..
// wonder what that means for all the other menu checkboxes which only use AppSaveSettings... (avih) // wonder what that means for all the other menu checkboxes which only use AppSaveSettings... (avih)
AppApplySettings(); AppApplySettings();
AppSaveSettings(); AppSaveSettings();
} }
@ -530,7 +529,7 @@ void MainEmuFrame::Menu_EnableRecordingTools_Click(wxCommandEvent& event)
else else
{ {
//Properly close any currently loaded recording file before disabling //Properly close any currently loaded recording file before disabling
if (g_InputRecording.RecordingActive()) if (g_InputRecording.IsRecordingActive())
Menu_Recording_Stop_Click(event); Menu_Recording_Stop_Click(event);
GetMenuBar()->Remove(TopLevelMenu_InputRecording); GetMenuBar()->Remove(TopLevelMenu_InputRecording);
// Always turn controller logs off, but never turn it on by default // Always turn controller logs off, but never turn it on by default
@ -876,16 +875,16 @@ void MainEmuFrame::Menu_Recording_New_Click(wxCommandEvent &event)
const bool initiallyPaused = g_InputRecordingControls.IsRecordingPaused(); const bool initiallyPaused = g_InputRecordingControls.IsRecordingPaused();
if (!initiallyPaused) if (!initiallyPaused)
g_InputRecordingControls.PauseImmediately(); g_InputRecordingControls.PauseImmediately();
NewRecordingFrame* NewRecordingFrame = wxGetApp().GetNewRecordingFramePtr(); NewRecordingFrame* newRecordingFrame = wxGetApp().GetNewRecordingFramePtr();
if (NewRecordingFrame) if (newRecordingFrame)
{ {
if (NewRecordingFrame->ShowModal() == wxID_CANCEL) if (newRecordingFrame->ShowModal() == wxID_CANCEL)
{ {
if (!initiallyPaused) if (!initiallyPaused)
g_InputRecordingControls.Resume(); g_InputRecordingControls.Resume();
return; return;
} }
if (!g_InputRecording.Create(NewRecordingFrame->GetFile(), !NewRecordingFrame->GetFrom(), NewRecordingFrame->GetAuthor())) if (!g_InputRecording.Create(newRecordingFrame->GetFile(), !newRecordingFrame->GetFrom(), newRecordingFrame->GetAuthor()))
{ {
if (!initiallyPaused) if (!initiallyPaused)
g_InputRecordingControls.Resume(); g_InputRecordingControls.Resume();
@ -913,7 +912,7 @@ void MainEmuFrame::Menu_Recording_Play_Click(wxCommandEvent &event)
} }
wxString path = openFileDialog.GetPath(); wxString path = openFileDialog.GetPath();
const bool recordingLoaded = g_InputRecording.RecordingActive(); const bool recordingLoaded = g_InputRecording.IsRecordingActive();
if (!g_InputRecording.Play(path)) if (!g_InputRecording.Play(path))
{ {
if (recordingLoaded) if (recordingLoaded)