PCSX2: Save/load slot improvements.

With these changes, saveslots will be labeled as either empty or
with the date that the file was last updated on. The menu items
for loading them are also disabled if the slot is empty.

It's possible if you are very fast to access the menu before the
slots change. It updates when the crc changes. When you save a saveslot,
the menu item also changes to show the time you told it to save until the
file is actually done saving.

Also fix an issue with backup saveslots not working properly from the
gui on first load.
This commit is contained in:
arcum42 2018-08-26 15:06:35 -07:00 committed by lightningterror
parent 6e3f41f164
commit 9cb35a8972
11 changed files with 232 additions and 106 deletions

View File

@ -343,6 +343,7 @@ set(pcsx2GuiHeaders
gui/Panels/MemoryCardPanels.h gui/Panels/MemoryCardPanels.h
gui/pxEventThread.h gui/pxEventThread.h
gui/RecentIsoList.h gui/RecentIsoList.h
gui/Saveslots.h
) )
# Warning: the declaration of the .h are mandatory in case of resources files. It will ensure the creation # Warning: the declaration of the .h are mandatory in case of resources files. It will ensure the creation

View File

@ -236,6 +236,7 @@ void SysCoreThread::GameStartingInThread()
sApp.PostAppMethod(&Pcsx2App::resetDebugger); sApp.PostAppMethod(&Pcsx2App::resetDebugger);
ApplyLoadedPatches(PPT_ONCE_ON_LOAD); ApplyLoadedPatches(PPT_ONCE_ON_LOAD);
UI_UpdateSysControls();
} }
bool SysCoreThread::StateCheckInThread() bool SysCoreThread::StateCheckInThread()

View File

@ -17,6 +17,7 @@
#include "App.h" #include "App.h"
#include "SaveState.h" #include "SaveState.h"
#include "Saveslots.h"
// -------------------------------------------------------------------------------------- // --------------------------------------------------------------------------------------
// SysExecEvent_SaveSinglePlugin // SysExecEvent_SaveSinglePlugin
@ -60,15 +61,3 @@ extern void StateCopy_SaveToFile( const wxString& file );
extern void StateCopy_LoadFromFile( const wxString& file ); extern void StateCopy_LoadFromFile( const wxString& file );
extern void StateCopy_SaveToSlot( uint num ); extern void StateCopy_SaveToSlot( uint num );
extern void StateCopy_LoadFromSlot( uint slot, bool isFromBackup = false ); extern void StateCopy_LoadFromSlot( uint slot, bool isFromBackup = false );
extern void States_registerLoadBackupMenuItem( wxMenuItem* loadBackupMenuItem );
extern bool States_isSlotUsed(int num);
extern void States_DefrostCurrentSlotBackup();
extern void States_DefrostCurrentSlot();
extern void States_FreezeCurrentSlot();
extern void States_CycleSlotForward();
extern void States_CycleSlotBackward();
extern void States_SetCurrentSlot( int slot );
extern int States_GetCurrentSlot();

View File

@ -28,6 +28,7 @@
#include "AppAccelerators.h" #include "AppAccelerators.h"
#include "svnrev.h" #include "svnrev.h"
#include "Saveslots.h"
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
wxMenu* MainEmuFrame::MakeStatesSubMenu( int baseid, int loadBackupId ) const wxMenu* MainEmuFrame::MakeStatesSubMenu( int baseid, int loadBackupId ) const
@ -36,18 +37,18 @@ wxMenu* MainEmuFrame::MakeStatesSubMenu( int baseid, int loadBackupId ) const
for (int i = 0; i < 10; i++) for (int i = 0; i < 10; i++)
{ {
// Will be changed once an iso is loaded.
mnuSubstates->Append(baseid + i + 1, wxsFormat(_("Slot %d"), i)); mnuSubstates->Append(baseid + i + 1, wxsFormat(_("Slot %d"), i));
} }
if (loadBackupId >= 0) if (loadBackupId >= 0)
{ {
mnuSubstates->AppendSeparator(); mnuSubstates->AppendSeparator();
wxMenuItem* m = mnuSubstates->Append(loadBackupId, _("Backup")); wxMenuItem* m = mnuSubstates->Append(loadBackupId, _("Backup"));
m->Enable( false ); m->Enable( false );
States_registerLoadBackupMenuItem( m );
} }
//mnuSubstates->Append( baseid - 1, _("Other...") );
return mnuSubstates; return mnuSubstates;
} }
@ -787,4 +788,3 @@ void PerPluginMenuInfo::OnLoaded()
); );
MyMenu.Enable( GetPluginMenuId_Settings(PluginId), true ); MyMenu.Enable( GetPluginMenuId_Settings(PluginId), true );
} }

View File

@ -137,6 +137,7 @@ public:
void UpdateIsoSrcSelection(); void UpdateIsoSrcSelection();
void RemoveCdvdMenu(); void RemoveCdvdMenu();
void EnableMenuItem( int id, bool enable ); void EnableMenuItem( int id, bool enable );
void SetMenuItemLabel(int id, wxString str);
void EnableCdvdPluginSubmenu(bool isEnable = true); void EnableCdvdPluginSubmenu(bool isEnable = true);
bool Destroy(); bool Destroy();

View File

@ -17,11 +17,13 @@
#include "App.h" #include "App.h"
#include "AppSaveStates.h" #include "AppSaveStates.h"
#include "ConsoleLogger.h" #include "ConsoleLogger.h"
#include "MainFrame.h"
#include "Common.h" #include "Common.h"
#include "GS.h" #include "GS.h"
#include "Elfheader.h" #include "Elfheader.h"
#include "Saveslots.h"
// -------------------------------------------------------------------------------------- // --------------------------------------------------------------------------------------
// Saveslot Section // Saveslot Section
@ -29,15 +31,7 @@
static int StatesC = 0; static int StatesC = 0;
static const int StateSlotsCount = 10; static const int StateSlotsCount = 10;
static wxMenuItem* g_loadBackupMenuItem =NULL; Saveslot saveslot_cache[10] = {{0}, {1}, {2}, {3}, {4}, {5}, {6}, {7}, {8}, {9}};
bool States_isSlotUsed(int num)
{
if (ElfCRC == 0)
return false;
else
return wxFileExists( SaveStateBase::GetFilename( num ) );
}
// FIXME : Use of the IsSavingOrLoading flag is mostly a hack until we implement a // FIXME : Use of the IsSavingOrLoading flag is mostly a hack until we implement a
// complete thread to manage queuing savestate tasks, and zipping states to disk. --air // complete thread to manage queuing savestate tasks, and zipping states to disk. --air
@ -49,20 +43,18 @@ public:
wxString GetEventName() const { return L"ClearSavingLoadingFlag"; } wxString GetEventName() const { return L"ClearSavingLoadingFlag"; }
virtual ~SysExecEvent_ClearSavingLoadingFlag() = default; virtual ~SysExecEvent_ClearSavingLoadingFlag() = default;
SysExecEvent_ClearSavingLoadingFlag() SysExecEvent_ClearSavingLoadingFlag() { }
{
}
SysExecEvent_ClearSavingLoadingFlag *Clone() const { return new SysExecEvent_ClearSavingLoadingFlag(); } SysExecEvent_ClearSavingLoadingFlag *Clone() const { return new SysExecEvent_ClearSavingLoadingFlag(); }
protected: protected:
void InvokeEvent() void InvokeEvent()
{ {
IsSavingOrLoading = false; IsSavingOrLoading = false;
UI_UpdateSysControls();
} }
}; };
void Sstates_updateLoadBackupMenuItem( bool isBeforeSave = false); void Sstates_updateLoadBackupMenuItem(bool isBeforeSave);
void States_FreezeCurrentSlot() void States_FreezeCurrentSlot()
{ {
@ -84,6 +76,11 @@ void States_FreezeCurrentSlot()
GSchangeSaveState(StatesC, SaveStateBase::GetFilename(StatesC).ToUTF8()); GSchangeSaveState(StatesC, SaveStateBase::GetFilename(StatesC).ToUTF8());
StateCopy_SaveToSlot(StatesC); StateCopy_SaveToSlot(StatesC);
// Hack: Update the saveslot saying it's filled *right now* because it's still writing the file and we don't have a timestamp.
saveslot_cache[StatesC].empty = false;
saveslot_cache[StatesC].updated = wxDateTime::Now();
saveslot_cache[StatesC].crc = ElfCRC;
GetSysExecutorThread().PostIdleEvent(SysExecEvent_ClearSavingLoadingFlag()); GetSysExecutorThread().PostIdleEvent(SysExecEvent_ClearSavingLoadingFlag());
} }
@ -106,7 +103,7 @@ void _States_DefrostCurrentSlot( bool isFromBackup )
GetSysExecutorThread().PostIdleEvent(SysExecEvent_ClearSavingLoadingFlag()); GetSysExecutorThread().PostIdleEvent(SysExecEvent_ClearSavingLoadingFlag());
Sstates_updateLoadBackupMenuItem(); Sstates_updateLoadBackupMenuItem(false);
} }
void States_DefrostCurrentSlot() void States_DefrostCurrentSlot()
@ -119,10 +116,18 @@ void States_DefrostCurrentSlotBackup()
_States_DefrostCurrentSlot(true); _States_DefrostCurrentSlot(true);
} }
// I'd keep an eye on this function, as it may still be problematic.
void States_registerLoadBackupMenuItem( wxMenuItem* loadBackupMenuItem ) void Sstates_updateLoadBackupMenuItem(bool isBeforeSave)
{ {
g_loadBackupMenuItem = loadBackupMenuItem; wxString file = SaveStateBase::GetFilename(StatesC);
if (!(isBeforeSave && g_Conf->EmuOptions.BackupSavestate))
{
file = file + L".backup";
}
sMainFrame.EnableMenuItem(MenuId_State_LoadBackup, wxFileExists(file));
sMainFrame.SetMenuItemLabel(MenuId_State_LoadBackup, wxsFormat(L"%s %d", _("Backup"), StatesC));
} }
static void OnSlotChanged() static void OnSlotChanged()
@ -132,7 +137,7 @@ static void OnSlotChanged()
if (GSchangeSaveState != NULL) if (GSchangeSaveState != NULL)
GSchangeSaveState(StatesC, SaveStateBase::GetFilename(StatesC).utf8_str()); GSchangeSaveState(StatesC, SaveStateBase::GetFilename(StatesC).utf8_str());
Sstates_updateLoadBackupMenuItem(); Sstates_updateLoadBackupMenuItem(false);
} }
int States_GetCurrentSlot() int States_GetCurrentSlot()
@ -140,18 +145,6 @@ int States_GetCurrentSlot()
return StatesC; return StatesC;
} }
void Sstates_updateLoadBackupMenuItem( bool isBeforeSave )
{
if( !g_loadBackupMenuItem ) return;
int slot = States_GetCurrentSlot();
wxString file = SaveStateBase::GetFilename( slot );
g_loadBackupMenuItem->Enable( wxFileExists( isBeforeSave && g_Conf->EmuOptions.BackupSavestate ? file : file + L".backup" ) );
wxString label;
label.Printf(L"%s %d", _("Backup"), slot );
g_loadBackupMenuItem->SetItemLabel( label );
}
void States_SetCurrentSlot(int slot) void States_SetCurrentSlot(int slot)
{ {
StatesC = std::min(std::max(slot, 0), StateSlotsCount); StatesC = std::min(std::max(slot, 0), StateSlotsCount);
@ -169,4 +162,3 @@ void States_CycleSlotBackward()
StatesC = (StatesC + StateSlotsCount - 1) % StateSlotsCount; StatesC = (StatesC + StateSlotsCount - 1) % StateSlotsCount;
OnSlotChanged(); OnSlotChanged();
} }

100
pcsx2/gui/Saveslots.h Normal file
View File

@ -0,0 +1,100 @@
/* PCSX2 - PS2 Emulator for PCs
* Copyright (C) 2002-2018 PCSX2 Dev Team
*
* PCSX2 is free software: you can redistribute it and/or modify it under the terms
* of the GNU Lesser General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with PCSX2.
* If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include "PS2Edefs.h"
#include "System.h"
#include "Elfheader.h"
class Saveslot
{
public:
u32 slot_num;
bool empty;
wxDateTime updated;
u32 crc;
Saveslot()
{
slot_num = 0;
empty = true;
updated = wxInvalidDateTime;
crc = ElfCRC;
}
Saveslot(int i)
{
slot_num = i;
empty = true;
updated = wxInvalidDateTime;
crc = ElfCRC;
}
bool isUsed()
{
return wxFileExists(SaveStateBase::GetFilename(slot_num));
}
wxDateTime GetTimestamp()
{
if (!isUsed()) return wxInvalidDateTime;
return wxDateTime(wxFileModificationTime(SaveStateBase::GetFilename(slot_num)));
}
void UpdateCache()
{
empty = !isUsed();
updated = GetTimestamp();
crc = ElfCRC;
}
wxString SlotName()
{
if (empty) return wxsFormat(_("Slot %d - Empty"), slot_num);
if (updated != wxInvalidDateTime) return wxsFormat(_("Slot %d - %s %s"), slot_num, updated.FormatDate(), updated.FormatTime());
return wxsFormat(_("Slot %d - Unknown Time"), slot_num);
}
void ConsoleDump()
{
Console.WriteLn("Slot %i information:", slot_num);
Console.WriteLn("Internal CRC = %i; Current CRC = %i.", crc, ElfCRC);
if (empty)
Console.WriteLn("Slot cache says it is empty.");
else
Console.WriteLn("Slot cache says it is used.");
if (updated != wxInvalidDateTime)
Console.WriteLn(wxsFormat(_("Write time is %s %s."), updated.FormatDate(), updated.FormatTime()));
if (isUsed())
Console.WriteLn(wxsFormat(_("The disk has a file on it dated %s %s."), GetTimestamp().FormatDate(), GetTimestamp().FormatTime()));
}
};
extern Saveslot saveslot_cache[10];
extern void States_DefrostCurrentSlotBackup();
extern void States_DefrostCurrentSlot();
extern void States_FreezeCurrentSlot();
extern void States_CycleSlotForward();
extern void States_CycleSlotBackward();
extern void States_SetCurrentSlot(int slot);
extern int States_GetCurrentSlot();
extern void Sstates_updateLoadBackupMenuItem(bool isBeforeSave);

View File

@ -476,6 +476,7 @@ protected:
void CleanupEvent() void CleanupEvent()
{ {
UI_UpdateSysControls();
} }
}; };
@ -674,6 +675,7 @@ void StateCopy_SaveToSlot( uint num )
Console.Indent().WriteLn( Color_StrongGreen, L"filename: %s", WX_STR(file) ); Console.Indent().WriteLn( Color_StrongGreen, L"filename: %s", WX_STR(file) );
StateCopy_SaveToFile( file ); StateCopy_SaveToFile( file );
UI_UpdateSysControls();
} }
void StateCopy_LoadFromSlot( uint slot, bool isFromBackup ) void StateCopy_LoadFromSlot( uint slot, bool isFromBackup )
@ -690,4 +692,5 @@ void StateCopy_LoadFromSlot( uint slot, bool isFromBackup )
Console.Indent().WriteLn( Color_StrongGreen, L"filename: %s", WX_STR(file) ); Console.Indent().WriteLn( Color_StrongGreen, L"filename: %s", WX_STR(file) );
StateCopy_LoadFromFile( file ); StateCopy_LoadFromFile( file );
UI_UpdateSysControls();
} }

View File

@ -32,27 +32,57 @@ void MainEmuFrame::EnableMenuItem( int id, bool enable )
item->Enable(enable); item->Enable(enable);
} }
void MainEmuFrame::SetMenuItemLabel(int id, wxString str)
{
if (wxMenuItem *item = m_menubar.FindItem(id))
item->SetItemLabel(str);
}
static void _SaveLoadStuff(bool enabled) static void _SaveLoadStuff(bool enabled)
{ {
sMainFrame.EnableMenuItem(MenuId_Sys_LoadStates, enabled); sMainFrame.EnableMenuItem(MenuId_Sys_LoadStates, enabled);
sMainFrame.EnableMenuItem(MenuId_Sys_SaveStates, enabled); sMainFrame.EnableMenuItem(MenuId_Sys_SaveStates, enabled);
for (int i = 0; i < 10; i++)
{
int load_menu_item = MenuId_State_Load01 + i + 1;
int save_menu_item = MenuId_State_Save01 + i + 1;
// If the cache is out of sync with the actual files, we need to update things. First update, the cache'll be blank, and this will populate everything.
if (saveslot_cache[i].empty == saveslot_cache[i].isUsed())
{
// If there is actually a file there, or the cache was for a different game, we force an update.
// If the cache says there's a saveslot for the current game that there isn't a file for, writing it is done in a different thread,
// so it might not be written yet. Which is why I cache to begin with.
if (saveslot_cache[i].isUsed() || (saveslot_cache[i].crc != ElfCRC))
{
saveslot_cache[i].UpdateCache();
}
}
sMainFrame.EnableMenuItem(load_menu_item, !saveslot_cache[i].empty);
sMainFrame.SetMenuItemLabel(load_menu_item, saveslot_cache[i].SlotName());
sMainFrame.SetMenuItemLabel(save_menu_item, saveslot_cache[i].SlotName());
}
Sstates_updateLoadBackupMenuItem(false);
} }
// Updates the enable/disable status of all System related controls: menus, toolbars, // Updates the enable/disable status of all System related controls: menus, toolbars,
// etc. Typically called by SysEvtHandler whenever the message pump becomes idle. // etc. Typically called by SysEvtHandler whenever the message pump becomes idle.
void UI_UpdateSysControls() void UI_UpdateSysControls()
{ {
if( wxGetApp().Rpc_TryInvokeAsync( &UI_UpdateSysControls ) ) return; if (wxGetApp().Rpc_TryInvokeAsync(&UI_UpdateSysControls))
return;
sApp.PostAction(CoreThreadStatusEvent(CoreThread_Indeterminate)); sApp.PostAction(CoreThreadStatusEvent(CoreThread_Indeterminate));
//_SaveLoadStuff( true );
_SaveLoadStuff(SysHasValidState()); _SaveLoadStuff(SysHasValidState());
} }
void UI_DisableSysShutdown() void UI_DisableSysShutdown()
{ {
if( wxGetApp().Rpc_TryInvokeAsync( &UI_DisableSysShutdown ) ) return; if (wxGetApp().Rpc_TryInvokeAsync(&UI_DisableSysShutdown))
return;
sMainFrame.EnableMenuItem(MenuId_Sys_Shutdown, false); sMainFrame.EnableMenuItem(MenuId_Sys_Shutdown, false);
sMainFrame.EnableMenuItem(MenuId_IsoBrowse, !g_Conf->AskOnBoot); sMainFrame.EnableMenuItem(MenuId_IsoBrowse, !g_Conf->AskOnBoot);
@ -61,7 +91,8 @@ void UI_DisableSysShutdown()
void UI_EnableSysShutdown() void UI_EnableSysShutdown()
{ {
if( wxGetApp().Rpc_TryInvokeAsync( &UI_EnableSysShutdown ) ) return; if (wxGetApp().Rpc_TryInvokeAsync(&UI_EnableSysShutdown))
return;
sMainFrame.EnableMenuItem(MenuId_Sys_Shutdown, true); sMainFrame.EnableMenuItem(MenuId_Sys_Shutdown, true);
} }
@ -69,7 +100,8 @@ void UI_EnableSysShutdown()
void UI_DisableSysActions() void UI_DisableSysActions()
{ {
if( wxGetApp().Rpc_TryInvokeAsync( &UI_DisableSysActions ) ) return; if (wxGetApp().Rpc_TryInvokeAsync(&UI_DisableSysActions))
return;
sMainFrame.EnableMenuItem(MenuId_Sys_Shutdown, false); sMainFrame.EnableMenuItem(MenuId_Sys_Shutdown, false);
@ -78,7 +110,8 @@ void UI_DisableSysActions()
void UI_EnableSysActions() void UI_EnableSysActions()
{ {
if( wxGetApp().Rpc_TryInvokeAsync( &UI_EnableSysActions ) ) return; if (wxGetApp().Rpc_TryInvokeAsync(&UI_EnableSysActions))
return;
sMainFrame.EnableMenuItem(MenuId_Sys_Shutdown, true); sMainFrame.EnableMenuItem(MenuId_Sys_Shutdown, true);
sMainFrame.EnableMenuItem(MenuId_IsoBrowse, true); sMainFrame.EnableMenuItem(MenuId_IsoBrowse, true);
@ -89,14 +122,16 @@ void UI_EnableSysActions()
void UI_DisableStateActions() void UI_DisableStateActions()
{ {
if( wxGetApp().Rpc_TryInvokeAsync( &UI_DisableStateActions ) ) return; if (wxGetApp().Rpc_TryInvokeAsync(&UI_DisableStateActions))
return;
_SaveLoadStuff(false); _SaveLoadStuff(false);
} }
void UI_EnableStateActions() void UI_EnableStateActions()
{ {
if( wxGetApp().Rpc_TryInvokeAsync( &UI_EnableStateActions ) ) return; if (wxGetApp().Rpc_TryInvokeAsync(&UI_EnableStateActions))
return;
_SaveLoadStuff(true); _SaveLoadStuff(true);
} }

View File

@ -427,6 +427,7 @@
<ClInclude Include="..\..\GameDatabase.h" /> <ClInclude Include="..\..\GameDatabase.h" />
<ClInclude Include="..\..\Gif_Unit.h" /> <ClInclude Include="..\..\Gif_Unit.h" />
<ClInclude Include="..\..\gui\AppGameDatabase.h" /> <ClInclude Include="..\..\gui\AppGameDatabase.h" />
<ClInclude Include="..\..\gui\Saveslots.h" />
<ClInclude Include="..\..\gui\Debugger\BreakpointWindow.h" /> <ClInclude Include="..\..\gui\Debugger\BreakpointWindow.h" />
<ClInclude Include="..\..\gui\Debugger\CtrlDisassemblyView.h" /> <ClInclude Include="..\..\gui\Debugger\CtrlDisassemblyView.h" />
<ClInclude Include="..\..\gui\Debugger\CtrlMemView.h" /> <ClInclude Include="..\..\gui\Debugger\CtrlMemView.h" />

View File

@ -1197,6 +1197,9 @@
<ClInclude Include="..\..\gui\RecentIsoList.h"> <ClInclude Include="..\..\gui\RecentIsoList.h">
<Filter>AppHost\Include</Filter> <Filter>AppHost\Include</Filter>
</ClInclude> </ClInclude>
<ClInclude Include="..\..\gui\Saveslots.h">
<Filter>AppHost\Include</Filter>
</ClInclude>
<ClInclude Include="..\cheats\cheats.h"> <ClInclude Include="..\cheats\cheats.h">
<Filter>AppHost\Cheats</Filter> <Filter>AppHost\Cheats</Filter>
</ClInclude> </ClInclude>