core, citra_qt: add frame advancing to framelimiter

Frame advancing is a commonly used TAS feature which basically means running the game frame by frame. TASers use this feature to press exact buttons at the exact frames. This commit added frame advancing to the framelimiter and two actions to the Movie menu. The default hotkey is `\` for advancing frames, and `Ctrl+A` for toggling frame advancing. The `Advance Frame` hotkey would automatically enable frame advancing if not already enabled.
This commit is contained in:
zhupengfei 2018-10-01 23:39:22 +08:00 committed by Valentin Vanelslande
parent 2477178d8d
commit 93ca0e6fd1
4 changed files with 90 additions and 0 deletions

View File

@ -352,6 +352,10 @@ void GMainWindow::InitializeHotkeys() {
Qt::ApplicationShortcut); Qt::ApplicationShortcut);
hotkey_registry.RegisterHotkey("Main Window", "Decrease Speed Limit", QKeySequence("-"), hotkey_registry.RegisterHotkey("Main Window", "Decrease Speed Limit", QKeySequence("-"),
Qt::ApplicationShortcut); Qt::ApplicationShortcut);
hotkey_registry.RegisterHotkey("Main Window", "Toggle Frame Advancing", QKeySequence("CTRL+A"),
Qt::ApplicationShortcut);
hotkey_registry.RegisterHotkey("Main Window", "Advance Frame", QKeySequence(Qt::Key_Backslash),
Qt::ApplicationShortcut);
hotkey_registry.LoadHotkeys(); hotkey_registry.LoadHotkeys();
connect(hotkey_registry.GetHotkey("Main Window", "Load File", this), &QShortcut::activated, connect(hotkey_registry.GetHotkey("Main Window", "Load File", this), &QShortcut::activated,
@ -409,6 +413,10 @@ void GMainWindow::InitializeHotkeys() {
UpdateStatusBar(); UpdateStatusBar();
} }
}); });
connect(hotkey_registry.GetHotkey("Main Window", "Toggle Frame Advancing", this),
&QShortcut::activated, ui.action_Enable_Frame_Advancing, &QAction::trigger);
connect(hotkey_registry.GetHotkey("Main Window", "Advance Frame", this), &QShortcut::activated,
ui.action_Advance_Frame, &QAction::trigger);
} }
void GMainWindow::ShowUpdaterWidgets() { void GMainWindow::ShowUpdaterWidgets() {
@ -540,6 +548,20 @@ void GMainWindow::ConnectMenuEvents() {
connect(ui.action_Play_Movie, &QAction::triggered, this, &GMainWindow::OnPlayMovie); connect(ui.action_Play_Movie, &QAction::triggered, this, &GMainWindow::OnPlayMovie);
connect(ui.action_Stop_Recording_Playback, &QAction::triggered, this, connect(ui.action_Stop_Recording_Playback, &QAction::triggered, this,
&GMainWindow::OnStopRecordingPlayback); &GMainWindow::OnStopRecordingPlayback);
connect(ui.action_Enable_Frame_Advancing, &QAction::triggered, this, [this] {
if (emulation_running) {
Core::System::GetInstance().frame_limiter.SetFrameAdvancing(
ui.action_Enable_Frame_Advancing->isChecked());
ui.action_Advance_Frame->setEnabled(ui.action_Enable_Frame_Advancing->isChecked());
}
});
connect(ui.action_Advance_Frame, &QAction::triggered, this, [this] {
if (emulation_running) {
ui.action_Enable_Frame_Advancing->setChecked(true);
ui.action_Advance_Frame->setEnabled(true);
Core::System::GetInstance().frame_limiter.AdvanceFrame();
}
});
// Help // Help
connect(ui.action_FAQ, &QAction::triggered, connect(ui.action_FAQ, &QAction::triggered,
@ -803,6 +825,9 @@ void GMainWindow::ShutdownGame() {
// TODO(bunnei): This function is not thread safe, but it's being used as if it were // TODO(bunnei): This function is not thread safe, but it's being used as if it were
Pica::g_debug_context->ClearBreakpoints(); Pica::g_debug_context->ClearBreakpoints();
// Frame advancing must be cancelled in order to release the emu thread from waiting
Core::System::GetInstance().frame_limiter.SetFrameAdvancing(false);
emit EmulationStopping(); emit EmulationStopping();
// Wait for emulation thread to complete and delete it // Wait for emulation thread to complete and delete it
@ -823,6 +848,9 @@ void GMainWindow::ShutdownGame() {
ui.action_Stop->setEnabled(false); ui.action_Stop->setEnabled(false);
ui.action_Restart->setEnabled(false); ui.action_Restart->setEnabled(false);
ui.action_Report_Compatibility->setEnabled(false); ui.action_Report_Compatibility->setEnabled(false);
ui.action_Enable_Frame_Advancing->setEnabled(false);
ui.action_Enable_Frame_Advancing->setChecked(false);
ui.action_Advance_Frame->setEnabled(false);
render_window->hide(); render_window->hide();
if (game_list->isEmpty()) if (game_list->isEmpty())
game_list_placeholder->show(); game_list_placeholder->show();
@ -1110,6 +1138,7 @@ void GMainWindow::OnStartGame() {
ui.action_Stop->setEnabled(true); ui.action_Stop->setEnabled(true);
ui.action_Restart->setEnabled(true); ui.action_Restart->setEnabled(true);
ui.action_Report_Compatibility->setEnabled(true); ui.action_Report_Compatibility->setEnabled(true);
ui.action_Enable_Frame_Advancing->setEnabled(true);
discord_rpc->Update(); discord_rpc->Update();
} }

View File

@ -114,6 +114,9 @@
<addaction name="action_Record_Movie"/> <addaction name="action_Record_Movie"/>
<addaction name="action_Play_Movie"/> <addaction name="action_Play_Movie"/>
<addaction name="action_Stop_Recording_Playback"/> <addaction name="action_Stop_Recording_Playback"/>
<addaction name="separator"/>
<addaction name="action_Enable_Frame_Advancing"/>
<addaction name="action_Advance_Frame"/>
</widget> </widget>
<widget class="QMenu" name="menu_Multiplayer"> <widget class="QMenu" name="menu_Multiplayer">
<property name="enabled"> <property name="enabled">
@ -276,6 +279,25 @@
<string>Stop Recording / Playback</string> <string>Stop Recording / Playback</string>
</property> </property>
</action> </action>
<action name="action_Enable_Frame_Advancing">
<property name="checkable">
<bool>true</bool>
</property>
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Enable Frame Advancing</string>
</property>
</action>
<action name="action_Advance_Frame">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Advance Frame</string>
</property>
</action>
<action name="action_View_Lobby"> <action name="action_View_Lobby">
<property name="enabled"> <property name="enabled">
<bool>true</bool> <bool>true</bool>

View File

@ -74,6 +74,13 @@ double PerfStats::GetLastFrameTimeScale() {
} }
void FrameLimiter::DoFrameLimiting(microseconds current_system_time_us) { void FrameLimiter::DoFrameLimiting(microseconds current_system_time_us) {
if (frame_advancing_enabled) {
// Frame advancing is enabled: wait on event instead of doing framelimiting
frame_advance_event.Wait();
frame_advance_event.Reset();
return;
}
if (!Settings::values.use_frame_limit) { if (!Settings::values.use_frame_limit) {
return; return;
} }
@ -104,4 +111,20 @@ void FrameLimiter::DoFrameLimiting(microseconds current_system_time_us) {
previous_walltime = now; previous_walltime = now;
} }
void FrameLimiter::SetFrameAdvancing(bool value) {
const bool was_enabled = frame_advancing_enabled.exchange(value);
if (was_enabled && !value) {
// Set the event to let emulation continue
frame_advance_event.Set();
}
}
void FrameLimiter::AdvanceFrame() {
if (!frame_advancing_enabled) {
// Start frame advancing
frame_advancing_enabled = true;
}
frame_advance_event.Set();
}
} // namespace Core } // namespace Core

View File

@ -4,9 +4,11 @@
#pragma once #pragma once
#include <atomic>
#include <chrono> #include <chrono>
#include <mutex> #include <mutex>
#include "common/common_types.h" #include "common/common_types.h"
#include "common/thread.h"
namespace Core { namespace Core {
@ -70,6 +72,14 @@ public:
void DoFrameLimiting(std::chrono::microseconds current_system_time_us); void DoFrameLimiting(std::chrono::microseconds current_system_time_us);
/**
* Sets whether frame advancing is enabled or not.
* Note: The frontend must cancel frame advancing before shutting down in order
* to resume the emu_thread.
*/
void SetFrameAdvancing(bool value);
void AdvanceFrame();
private: private:
/// Emulated system time (in microseconds) at the last limiter invocation /// Emulated system time (in microseconds) at the last limiter invocation
std::chrono::microseconds previous_system_time_us{0}; std::chrono::microseconds previous_system_time_us{0};
@ -78,6 +88,12 @@ private:
/// Accumulated difference between walltime and emulated time /// Accumulated difference between walltime and emulated time
std::chrono::microseconds frame_limiting_delta_err{0}; std::chrono::microseconds frame_limiting_delta_err{0};
/// Whether to use frame advancing (i.e. frame by frame)
std::atomic_bool frame_advancing_enabled;
/// Event to advance the frame when frame advancing is enabled
Common::Event frame_advance_event;
}; };
} // namespace Core } // namespace Core