Added the feature to allow creating a TAS movie from a save state. To activate this function, start the game and use the "Start recording" command. A save state will be created at that point in time and the emulator will start recording. This results in two files, a .dtm containing the movie and a .dtm.sav which is the save state.

Changes:
* Allow events to be scheduled when the emulator is not running.  This allows the save state event to be added before the emulator starts.
* Removed the Audio back-end init flag from the save state.  This value should not be saved as it is not data relevant to guest machine.
* Allow a recording to be started at any time (apart from when a recording is already being made).
* Updated the status bar and title bar when an on-screen message is shown
* Removed the saving of PEToken from the save state as the FIFO will save this information
* Added a couple Pixel Engine interrupt states to the save state
* Added the copyright notice to the GCPadStatus.h file.

This function is preliminary.  Let us know of any bugs you find or any UI quirks.

git-svn-id: https://dolphin-emu.googlecode.com/svn/trunk@7175 8ced0084-cf51-0410-be5f-012b33b47a6e
This commit is contained in:
skidau 2011-02-15 09:07:55 +00:00
parent cf21251802
commit fb4c82fb48
11 changed files with 100 additions and 21 deletions

View File

@ -94,7 +94,7 @@ bool g_bStopping = false;
bool g_bHwInit = false;
bool g_bRealWiimote = false;
void *g_pWindowHandle = NULL;
std::string g_stateFileName;
std::thread g_EmuThread;
static std::thread cpuThread;
@ -105,7 +105,8 @@ SCoreStartupParameter g_CoreStartupParameter;
Common::Event emuThreadGoing;
Common::Event cpuRunloopQuit;
std::string GetStateFileName() { return g_stateFileName; }
void SetStateFileName(std::string val) { g_stateFileName = val; }
// Display messages and return values
@ -125,12 +126,26 @@ bool PanicAlertToVideo(const char* text, bool yes_no)
void DisplayMessage(const std::string &message, int time_in_ms)
{
SCoreStartupParameter& _CoreParameter = SConfig::GetInstance().m_LocalCoreStartupParameter;
g_video_backend->Video_AddMessage(message.c_str(), time_in_ms);
if (_CoreParameter.bRenderToMain &&
SConfig::GetInstance().m_InterfaceStatusbar) {
Host_UpdateStatusBar(message.c_str());
} else
Host_UpdateTitle(message.c_str());
}
void DisplayMessage(const char *message, int time_in_ms)
{
SCoreStartupParameter& _CoreParameter = SConfig::GetInstance().m_LocalCoreStartupParameter;
g_video_backend->Video_AddMessage(message, time_in_ms);
if (_CoreParameter.bRenderToMain &&
SConfig::GetInstance().m_InterfaceStatusbar) {
Host_UpdateStatusBar(message);
} else
Host_UpdateTitle(message);
}
void Callback_DebuggerBreak()
@ -158,6 +173,11 @@ bool IsRunningInCurrentThread()
return isRunning() && ((!cpuThread.joinable()) || cpuThread.get_id() == std::this_thread::get_id());
}
bool IsCPUThread()
{
return ((!cpuThread.joinable()) || cpuThread.get_id() == std::this_thread::get_id());
}
// This is called from the GUI thread. See the booting call schedule in
// BootManager.cpp
bool Init()
@ -258,6 +278,9 @@ void CpuThread()
#endif
}
if (!g_stateFileName.empty())
State_LoadAs(g_stateFileName);
// Enter CPU run loop. When we leave it - we are done.
CCPU::Run();

View File

@ -55,7 +55,8 @@ void Callback_CoreMessage(int Id);
std::string StopMessage(bool, std::string);
bool isRunning();
bool IsRunningInCurrentThread(); // this tells us whether we are in the cpu thread.
bool IsRunningInCurrentThread(); // this tells us whether we are running in the cpu thread.
bool IsCPUThread(); // this tells us whether we are the cpu thread.
void SetState(EState _State);
EState GetState();
@ -78,6 +79,9 @@ void Callback_CoreMessage(int Id);
void StartTrace(bool write);
void DisplayMessage(const std::string &message, int time_in_ms); // This displays messages in a user-visible way.
void DisplayMessage(const char *message, int time_in_ms); // This displays messages in a user-visible way.
std::string GetStateFileName();
void SetStateFileName(std::string val);
int SyncTrace();
void SetBlockStart(u32 addr);
@ -100,7 +104,6 @@ void Callback_CoreMessage(int Id);
extern bool g_FrameStep;
#endif
// ---------------------------
} // namespace
#endif

View File

@ -241,11 +241,11 @@ void ScheduleEvent_Threadsafe(int cyclesIntoFuture, int event_type, u64 userdata
externalEventSection.Leave();
}
// Same as ScheduleEvent_Threadsafe(0, ...) EXCEPT if we are already on the main thread
// Same as ScheduleEvent_Threadsafe(0, ...) EXCEPT if we are already on the CPU thread
// in which case the event will get handled immediately, before returning.
void ScheduleEvent_Threadsafe_Immediate(int event_type, u64 userdata)
{
if(Core::IsRunningInCurrentThread())
if(Core::IsCPUThread())
{
externalEventSection.Enter();
event_types[event_type].callback(userdata, 0);

View File

@ -51,8 +51,6 @@ DSPLLE::DSPLLE() {
void DSPLLE::DoState(PointerWrap &p)
{
p.Do(m_InitMixer);
p.Do(g_dsp.r);
p.Do(g_dsp.pc);
#if PROFILE
@ -96,7 +94,8 @@ void DSPLLE::dsp_thread(DSPLLE *lpParameter)
}
Common::AtomicStore(dsp_lle->m_cycle_count, 0);
}
Common::YieldCPU();
else
Common::YieldCPU();
}
}
@ -164,7 +163,7 @@ u16 DSPLLE::DSP_WriteControlRegister(u16 _uFlag)
UDSPControl Temp(_uFlag);
if (!m_InitMixer)
{
if (!Temp.DSPHalt && Temp.DSPInit)
if (!Temp.DSPHalt)
{
unsigned int AISampleRate, DACSampleRate;
AudioInterface::Callback_GetSampleRate(AISampleRate, DACSampleRate);

View File

@ -34,6 +34,7 @@
#else
#include <unistd.h> //truncate
#endif
#include "State.h"
Common::CriticalSection cs_frameSkip;
@ -53,6 +54,7 @@ char g_playingFile[256] = "\0";
FILE *g_recordfd = NULL;
u64 g_frameCounter = 0, g_lagCounter = 0;
bool g_bRecordingFromSaveState = false;
bool g_bPolled = false;
int g_numRerecords = 0;
@ -141,6 +143,11 @@ bool IsRecordingInput()
return (g_playMode == MODE_RECORDING);
}
bool IsRecordingInputFromSaveState()
{
return g_bRecordingFromSaveState;
}
bool IsPlayingInput()
{
return (g_playMode == MODE_PLAYING);
@ -177,17 +184,27 @@ void ChangeWiiPads()
}
}
// TODO: Add BeginRecordingFromSavestate
bool BeginRecordingInput(int controllers)
{
if(g_playMode != MODE_NONE || controllers == 0 || g_recordfd != NULL)
return false;
const char *filename = g_recordFile.c_str();
if(File::Exists(filename))
File::Delete(filename);
if (Core::isRunning())
{
std::string tmpStateFilename = g_recordFile;
tmpStateFilename.append(".sav");
const char *stateFilename = tmpStateFilename.c_str();
if(File::Exists(stateFilename))
File::Delete(stateFilename);
State_SaveAs(stateFilename);
g_bRecordingFromSaveState = true;
}
g_recordfd = fopen(filename, "wb");
if(!g_recordfd) {
PanicAlertT("Error opening file %s for recording", filename);
@ -273,8 +290,13 @@ bool PlayInput(const char *filename)
}
// Load savestate (and skip to frame data)
if(header.bFromSaveState) {
// TODO
if(header.bFromSaveState)
{
std::string stateFilename = filename;
stateFilename.append(".sav");
if(File::Exists(stateFilename.c_str()))
Core::SetStateFileName(stateFilename);
g_bRecordingFromSaveState = true;
}
/* TODO: Put this verification somewhere we have the gameID of the played game
@ -316,7 +338,8 @@ void LoadInput(const char *filename)
fread(&header, sizeof(DTMHeader), 1, t_record);
fclose(t_record);
if(header.filetype[0] != 'D' || header.filetype[1] != 'T' || header.filetype[2] != 'M' || header.filetype[3] != 0x1A) {
if(header.filetype[0] != 'D' || header.filetype[1] != 'T' || header.filetype[2] != 'M' || header.filetype[3] != 0x1A)
{
PanicAlertT("Savestate movie %s is corrupted, movie recording stopping...", filename);
strncpy(g_playingFile, "\0", 256);
EndPlayInput();
@ -469,7 +492,7 @@ void SaveRecording(const char *filename)
header.bWii = Core::g_CoreStartupParameter.bWii;
header.numControllers = g_numPads & (Core::g_CoreStartupParameter.bWii ? 0xFF : 0x0F);
header.bFromSaveState = false; // TODO: add the case where it's true
header.bFromSaveState = g_bRecordingFromSaveState;
header.frameCount = g_frameCounter;
header.lagCount = g_lagCounter;
header.numRerecords = g_rerecords;
@ -488,6 +511,15 @@ void SaveRecording(const char *filename)
File::Delete(filename);
success = File::Copy(g_recordFile.c_str(), filename);
if (success && g_bRecordingFromSaveState)
{
std::string tmpStateFilename = g_recordFile;
tmpStateFilename.append(".sav");
std::string stateFilename = filename;
stateFilename.append(".sav");
success = File::Copy(tmpStateFilename.c_str(), stateFilename.c_str());
}
if (success /* && !g_bReadOnly*/)
{
#ifdef WIN32

View File

@ -97,6 +97,7 @@ void SetPolledDevice();
bool IsAutoFiring();
bool IsRecordingInput();
bool IsRecordingInputFromSaveState();
bool IsPlayingInput();
bool IsUsingPad(int controller);

View File

@ -265,7 +265,7 @@ void SaveStateCallback(u64 userdata, int cyclesLate)
saveData->buffer = buffer;
saveData->size = sz;
if (Frame::IsRecordingInput() || Frame::IsPlayingInput())
if ((Frame::IsRecordingInput() || Frame::IsPlayingInput()) && !Frame::IsRecordingInputFromSaveState())
Frame::SaveRecording(StringFromFormat("%s.dtm", cur_filename.c_str()).c_str());
Core::DisplayMessage("Saving State...", 1000);
@ -390,7 +390,7 @@ void LoadStateCallback(u64 userdata, int cyclesLate)
if (File::Exists(StringFromFormat("%s.dtm", cur_filename.c_str()).c_str()))
Frame::LoadInput(StringFromFormat("%s.dtm", cur_filename.c_str()).c_str());
else
else if (!Frame::IsRecordingInputFromSaveState())
Frame::EndPlayInput();
state_op_in_progress = false;

View File

@ -1456,7 +1456,7 @@ void CFrame::UpdateGUI()
// Emulation
GetMenuBar()->FindItem(IDM_STOP)->Enable(Running || Paused);
GetMenuBar()->FindItem(IDM_RESET)->Enable(Running || Paused);
GetMenuBar()->FindItem(IDM_RECORD)->Enable(!Initialized);
GetMenuBar()->FindItem(IDM_RECORD)->Enable(!Frame::IsRecordingInput());
GetMenuBar()->FindItem(IDM_PLAYRECORD)->Enable(!Initialized);
GetMenuBar()->FindItem(IDM_RECORDEXPORT)->Enable(Frame::IsRecordingInput());
GetMenuBar()->FindItem(IDM_FRAMESTEP)->Enable(Running || Paused);

View File

@ -572,6 +572,7 @@ void Host_UpdateTitle(const char* title)
wxCommandEvent event(wxEVT_HOST_COMMAND, IDM_UPDATETITLE);
event.SetString(wxString::FromAscii(title));
main_frame->GetEventHandler()->AddPendingEvent(event);
Host_UpdateMainFrame();
}
void Host_UpdateBreakPointView()

View File

@ -1,6 +1,25 @@
// Copyright (C) 2003 Dolphin Project.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, version 2.0.
// This program 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 2.0 for more details.
// A copy of the GPL 2.0 should have been included with the program.
// If not, see http://www.gnu.org/licenses/
// Official SVN repository and contact information can be found at
// http://code.google.com/p/dolphin-emu/
#ifndef _GCPAD_H_INCLUDED__
#define _GCPAD_H_INCLUDED__
#include "CommonTypes.h"
#define PAD_ERR_NONE 0
#define PAD_ERR_NO_CONTROLLER -1
#define PAD_ERR_NOT_READY -2

View File

@ -135,10 +135,11 @@ void DoState(PointerWrap &p)
p.Do(m_AlphaModeConf);
p.Do(m_AlphaRead);
p.Do(m_Control);
p.Do(CommandProcessor::fifo.PEToken);
p.Do(g_bSignalTokenInterrupt);
p.Do(g_bSignalFinishInterrupt);
p.Do(interruptSetToken);
p.Do(interruptSetFinish);
p.Do(bbox);
p.Do(bbox_active);