Skip to content

Commit

Permalink
Merge pull request #11622 from JosJuice/tas-input-nonblocking
Browse files Browse the repository at this point in the history
DolphinQt: Rework TAS input threading
  • Loading branch information
AdmiralCurtiss committed Mar 9, 2023
2 parents a6b1b2f + 0300b44 commit 234c5dd
Show file tree
Hide file tree
Showing 14 changed files with 276 additions and 138 deletions.
4 changes: 4 additions & 0 deletions Source/Core/DolphinQt/CMakeLists.txt
Expand Up @@ -337,10 +337,14 @@ add_executable(dolphin-emu
TAS/StickWidget.h
TAS/TASCheckBox.cpp
TAS/TASCheckBox.h
TAS/TASControlState.cpp
TAS/TASControlState.h
TAS/TASInputWindow.cpp
TAS/TASInputWindow.h
TAS/TASSlider.cpp
TAS/TASSlider.h
TAS/TASSpinBox.cpp
TAS/TASSpinBox.h
TAS/WiiTASInputWindow.cpp
TAS/WiiTASInputWindow.h
ToolBar.cpp
Expand Down
4 changes: 4 additions & 0 deletions Source/Core/DolphinQt/DolphinQt.vcxproj
Expand Up @@ -207,7 +207,9 @@
<ClCompile Include="TAS\StickWidget.cpp" />
<ClCompile Include="TAS\TASCheckBox.cpp" />
<ClCompile Include="TAS\TASInputWindow.cpp" />
<ClCompile Include="TAS\TASControlState.cpp" />
<ClCompile Include="TAS\TASSlider.cpp" />
<ClCompile Include="TAS\TASSpinBox.cpp" />
<ClCompile Include="TAS\WiiTASInputWindow.cpp" />
<ClCompile Include="ToolBar.cpp" />
<ClCompile Include="Translation.cpp" />
Expand Down Expand Up @@ -242,6 +244,7 @@
<ClInclude Include="QtUtils\WrapInScrollArea.h" />
<ClInclude Include="ResourcePackManager.h" />
<ClInclude Include="Resources.h" />
<ClInclude Include="TAS\TASControlState.h" />
<ClInclude Include="TAS\TASSlider.h" />
<ClInclude Include="Translation.h" />
<ClInclude Include="WiiUpdate.h" />
Expand Down Expand Up @@ -387,6 +390,7 @@
<QtMoc Include="TAS\StickWidget.h" />
<QtMoc Include="TAS\TASCheckBox.h" />
<QtMoc Include="TAS\TASInputWindow.h" />
<QtMoc Include="TAS\TASSpinBox.h" />
<QtMoc Include="TAS\WiiTASInputWindow.h" />
<QtMoc Include="ToolBar.h" />
<QtMoc Include="Updater.h" />
Expand Down
14 changes: 6 additions & 8 deletions Source/Core/DolphinQt/TAS/GCTASInputWindow.cpp
Expand Up @@ -26,12 +26,10 @@ GCTASInputWindow::GCTASInputWindow(QWidget* parent, int controller_id)
{
setWindowTitle(tr("GameCube TAS Input %1").arg(controller_id + 1));

m_main_stick_box = CreateStickInputs(tr("Main Stick"), GCPad::MAIN_STICK_GROUP, &m_overrider,
m_x_main_stick_value, m_y_main_stick_value, 1, 1, 255, 255,
Qt::Key_F, Qt::Key_G);
m_c_stick_box =
CreateStickInputs(tr("C Stick"), GCPad::C_STICK_GROUP, &m_overrider, m_x_c_stick_value,
m_y_c_stick_value, 1, 1, 255, 255, Qt::Key_H, Qt::Key_J);
m_main_stick_box = CreateStickInputs(tr("Main Stick"), GCPad::MAIN_STICK_GROUP, &m_overrider, 1,
1, 255, 255, Qt::Key_F, Qt::Key_G);
m_c_stick_box = CreateStickInputs(tr("C Stick"), GCPad::C_STICK_GROUP, &m_overrider, 1, 1, 255,
255, Qt::Key_H, Qt::Key_J);

auto* top_layout = new QHBoxLayout;
top_layout->addWidget(m_main_stick_box);
Expand All @@ -41,11 +39,11 @@ GCTASInputWindow::GCTASInputWindow(QWidget* parent, int controller_id)

auto* l_trigger_layout =
CreateSliderValuePairLayout(tr("Left"), GCPad::TRIGGERS_GROUP, GCPad::L_ANALOG, &m_overrider,
m_l_trigger_value, 0, 0, 0, 255, Qt::Key_N, m_triggers_box);
0, 0, 0, 255, Qt::Key_N, m_triggers_box);

auto* r_trigger_layout =
CreateSliderValuePairLayout(tr("Right"), GCPad::TRIGGERS_GROUP, GCPad::R_ANALOG, &m_overrider,
m_r_trigger_value, 0, 0, 0, 255, Qt::Key_M, m_triggers_box);
0, 0, 0, 255, Qt::Key_M, m_triggers_box);

auto* triggers_layout = new QVBoxLayout;
triggers_layout->addLayout(l_trigger_layout);
Expand Down
6 changes: 0 additions & 6 deletions Source/Core/DolphinQt/TAS/GCTASInputWindow.h
Expand Up @@ -37,12 +37,6 @@ class GCTASInputWindow : public TASInputWindow
TASCheckBox* m_up_button;
TASCheckBox* m_down_button;
TASCheckBox* m_right_button;
QSpinBox* m_l_trigger_value;
QSpinBox* m_r_trigger_value;
QSpinBox* m_x_main_stick_value;
QSpinBox* m_y_main_stick_value;
QSpinBox* m_x_c_stick_value;
QSpinBox* m_y_c_stick_value;
QGroupBox* m_main_stick_box;
QGroupBox* m_c_stick_box;
QGroupBox* m_triggers_box;
Expand Down
26 changes: 24 additions & 2 deletions Source/Core/DolphinQt/TAS/TASCheckBox.cpp
Expand Up @@ -6,23 +6,34 @@
#include <QMouseEvent>

#include "Core/Movie.h"
#include "DolphinQt/QtUtils/QueueOnObject.h"
#include "DolphinQt/TAS/TASInputWindow.h"

TASCheckBox::TASCheckBox(const QString& text, TASInputWindow* parent)
: QCheckBox(text, parent), m_parent(parent)
{
setTristate(true);

connect(this, &TASCheckBox::stateChanged, this, &TASCheckBox::OnUIValueChanged);
}

bool TASCheckBox::GetValue() const
{
if (checkState() == Qt::PartiallyChecked)
Qt::CheckState check_state = static_cast<Qt::CheckState>(m_state.GetValue());

if (check_state == Qt::PartiallyChecked)
{
const u64 frames_elapsed = Movie::GetCurrentFrame() - m_frame_turbo_started;
return static_cast<int>(frames_elapsed % m_turbo_total_frames) < m_turbo_press_frames;
}

return isChecked();
return check_state != Qt::Unchecked;
}

void TASCheckBox::OnControllerValueChanged(bool new_value)
{
if (m_state.OnControllerValueChanged(new_value ? Qt::Checked : Qt::Unchecked))
QueueOnObject(this, &TASCheckBox::ApplyControllerValueChange);
}

void TASCheckBox::mousePressEvent(QMouseEvent* event)
Expand All @@ -44,3 +55,14 @@ void TASCheckBox::mousePressEvent(QMouseEvent* event)
m_turbo_total_frames = m_turbo_press_frames + m_parent->GetTurboReleaseFrames();
setCheckState(Qt::PartiallyChecked);
}

void TASCheckBox::OnUIValueChanged(int new_value)
{
m_state.OnUIValueChanged(static_cast<u16>(new_value));
}

void TASCheckBox::ApplyControllerValueChange()
{
const QSignalBlocker blocker(this);
setCheckState(static_cast<Qt::CheckState>(m_state.ApplyControllerValueChange()));
}
10 changes: 10 additions & 0 deletions Source/Core/DolphinQt/TAS/TASCheckBox.h
Expand Up @@ -5,6 +5,8 @@

#include <QCheckBox>

#include "DolphinQt/TAS/TASControlState.h"

class QMouseEvent;
class TASInputWindow;

Expand All @@ -14,13 +16,21 @@ class TASCheckBox : public QCheckBox
public:
explicit TASCheckBox(const QString& text, TASInputWindow* parent);

// Can be called from the CPU thread
bool GetValue() const;
// Must be called from the CPU thread
void OnControllerValueChanged(bool new_value);

protected:
void mousePressEvent(QMouseEvent* event) override;

private slots:
void OnUIValueChanged(int new_value);
void ApplyControllerValueChange();

private:
const TASInputWindow* m_parent;
TASControlState m_state;
int m_frame_turbo_started = 0;
int m_turbo_press_frames = 0;
int m_turbo_total_frames = 0;
Expand Down
58 changes: 58 additions & 0 deletions Source/Core/DolphinQt/TAS/TASControlState.cpp
@@ -0,0 +1,58 @@
// Copyright 2023 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#include "DolphinQt/TAS/TASControlState.h"

#include <atomic>

#include "Common/CommonTypes.h"

u16 TASControlState::GetValue() const
{
const State ui_thread_state = m_ui_thread_state.load(std::memory_order_relaxed);
const State cpu_thread_state = m_cpu_thread_state.load(std::memory_order_relaxed);

return (ui_thread_state.version != cpu_thread_state.version ? cpu_thread_state : ui_thread_state)
.value;
}

bool TASControlState::OnControllerValueChanged(u16 new_value)
{
const State cpu_thread_state = m_cpu_thread_state.load(std::memory_order_relaxed);

if (cpu_thread_state.value == new_value)
{
// The CPU thread state is already up to date with the controller. No need to do anything
return false;
}

const State new_state{static_cast<u16>(cpu_thread_state.version + 1), new_value};
m_cpu_thread_state.store(new_state, std::memory_order_relaxed);

return true;
}

void TASControlState::OnUIValueChanged(u16 new_value)
{
const State ui_thread_state = m_ui_thread_state.load(std::memory_order_relaxed);

const State new_state{ui_thread_state.version, new_value};
m_ui_thread_state.store(new_state, std::memory_order_relaxed);
}

u16 TASControlState::ApplyControllerValueChange()
{
const State ui_thread_state = m_ui_thread_state.load(std::memory_order_relaxed);
const State cpu_thread_state = m_cpu_thread_state.load(std::memory_order_relaxed);

if (ui_thread_state.version == cpu_thread_state.version)
{
// The UI thread state is already up to date with the CPU thread. No need to do anything
return ui_thread_state.value;
}
else
{
m_ui_thread_state.store(cpu_thread_state, std::memory_order_relaxed);
return cpu_thread_state.value;
}
}
43 changes: 43 additions & 0 deletions Source/Core/DolphinQt/TAS/TASControlState.h
@@ -0,0 +1,43 @@
// Copyright 2023 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#pragma once

#include <atomic>

#include "Common/CommonTypes.h"

class TASControlState
{
public:
// Call this from the CPU thread to get the current value. (This function can also safely be
// called from the UI thread, but you're effectively just getting the value the UI control has.)
u16 GetValue() const;
// Call this from the CPU thread when the controller state changes.
// If the return value is true, queue up a call to ApplyControllerChangeValue on the UI thread.
bool OnControllerValueChanged(u16 new_value);
// Call this from the UI thread when the user changes the value using the UI.
void OnUIValueChanged(u16 new_value);
// Call this from the UI thread after OnControllerValueChanged returns true,
// and set the state of the UI control to the return value.
u16 ApplyControllerValueChange();

private:
// A description of how threading is handled: The UI thread can update its copy of the state
// whenever it wants to, and must *not* increment the version when doing so. The CPU thread can
// update its copy of the state whenever it wants to, and *must* increment the version when doing
// so. When the CPU thread updates its copy of the state, the UI thread should then (possibly
// after a delay) mirror the change by copying the CPU thread's state to the UI thread's state.
// This mirroring is the only way for the version number stored in the UI thread's state to
// change. The version numbers of the two copies can be compared to check if the UI thread's view
// of what has happened on the CPU thread is up to date.

struct State
{
u16 version = 0;
u16 value = 0;
};

std::atomic<State> m_ui_thread_state;
std::atomic<State> m_cpu_thread_state;
};

0 comments on commit 234c5dd

Please sign in to comment.