From b42c3ce21db249d5e3bc04b4f73202e757da317c Mon Sep 17 00:00:00 2001 From: MonsterDruide1 <5958456@gmail.com> Date: Fri, 18 Jun 2021 16:15:42 +0200 Subject: input_common/tas: Base playback & recording system The base playback system supports up to 8 controllers (specified by `PLAYER_NUMBER` in `tas_input.h`), which all change their inputs simulataneously when `TAS::UpdateThread` is called. The recording system uses the controller debugger to read the state of the first controller and forwards that data to the TASing system for recording. Currently, this process sadly is not frame-perfect and pixel-accurate. Co-authored-by: Naii-the-Baf Co-authored-by: Narr-the-Reg --- src/yuzu/debugger/controller.cpp | 22 +++++++++++++++++++++- src/yuzu/debugger/controller.h | 24 +++++++++++++++++++++++- 2 files changed, 44 insertions(+), 2 deletions(-) (limited to 'src/yuzu/debugger') diff --git a/src/yuzu/debugger/controller.cpp b/src/yuzu/debugger/controller.cpp index c1fc69578..822d033d1 100644 --- a/src/yuzu/debugger/controller.cpp +++ b/src/yuzu/debugger/controller.cpp @@ -6,10 +6,13 @@ #include #include #include "common/settings.h" +#include "input_common/main.h" +#include "input_common/tas/tas_input.h" #include "yuzu/configuration/configure_input_player_widget.h" #include "yuzu/debugger/controller.h" -ControllerDialog::ControllerDialog(QWidget* parent) : QWidget(parent, Qt::Dialog) { +ControllerDialog::ControllerDialog(QWidget* parent, InputCommon::InputSubsystem* input_subsystem_) + : QWidget(parent, Qt::Dialog), input_subsystem{input_subsystem_} { setObjectName(QStringLiteral("Controller")); setWindowTitle(tr("Controller P1")); resize(500, 350); @@ -38,6 +41,9 @@ void ControllerDialog::refreshConfiguration() { constexpr std::size_t player = 0; widget->SetPlayerInputRaw(player, players[player].buttons, players[player].analogs); widget->SetControllerType(players[player].controller_type); + ControllerCallback callback{[this](ControllerInput input) { InputController(input); }}; + widget->SetCallBack(callback); + widget->repaint(); widget->SetConnectedStatus(players[player].connected); } @@ -67,3 +73,17 @@ void ControllerDialog::hideEvent(QHideEvent* ev) { widget->SetConnectedStatus(false); QWidget::hideEvent(ev); } + +void ControllerDialog::RefreshTasFile() { + input_subsystem->GetTas()->RefreshTasFile(); +} + +void ControllerDialog::InputController(ControllerInput input) { + u32 buttons = 0; + int index = 0; + for (bool btn : input.button_values) { + buttons += (btn ? 1 : 0) << index; + index++; + } + input_subsystem->GetTas()->RecordInput(buttons, input.axis_values); +} \ No newline at end of file diff --git a/src/yuzu/debugger/controller.h b/src/yuzu/debugger/controller.h index c54750070..659923e1b 100644 --- a/src/yuzu/debugger/controller.h +++ b/src/yuzu/debugger/controller.h @@ -4,18 +4,36 @@ #pragma once +#include #include +#include "common/settings.h" class QAction; class QHideEvent; class QShowEvent; class PlayerControlPreview; +namespace InputCommon { +class InputSubsystem; +} + +struct ControllerInput { + std::array, Settings::NativeAnalog::NUM_STICKS_HID> axis_values{}; + std::array button_values{}; + bool changed{}; +}; + +struct ControllerCallback { + std::function input; + std::function update; +}; + class ControllerDialog : public QWidget { Q_OBJECT public: - explicit ControllerDialog(QWidget* parent = nullptr); + explicit ControllerDialog(QWidget* parent = nullptr, + InputCommon::InputSubsystem* input_subsystem_ = nullptr); /// Returns a QAction that can be used to toggle visibility of this dialog. QAction* toggleViewAction(); @@ -26,6 +44,10 @@ protected: void hideEvent(QHideEvent* ev) override; private: + void RefreshTasFile(); + void InputController(ControllerInput input); QAction* toggle_view_action = nullptr; + QFileSystemWatcher* watcher = nullptr; PlayerControlPreview* widget; + InputCommon::InputSubsystem* input_subsystem; }; -- cgit v1.2.3 From c01a872c8efa90065b6ba1a74079ddf6ec12058f Mon Sep 17 00:00:00 2001 From: german77 Date: Sat, 19 Jun 2021 14:38:49 -0500 Subject: config: Move TAS options to it's own menu --- src/common/fs/fs_paths.h | 2 +- src/common/fs/path_util.cpp | 2 +- src/common/fs/path_util.h | 3 +- src/core/hle/service/apm/apm_interface.cpp | 2 +- src/input_common/main.cpp | 6 +- src/input_common/tas/tas_input.cpp | 189 ++++++++++++--------- src/input_common/tas/tas_input.h | 26 ++- src/yuzu/CMakeLists.txt | 3 + src/yuzu/configuration/configure_filesystem.cpp | 9 - src/yuzu/configuration/configure_filesystem.h | 1 - src/yuzu/configuration/configure_filesystem.ui | 49 ------ .../configure_input_player_widget.cpp | 4 +- src/yuzu/configuration/configure_tas.cpp | 84 +++++++++ src/yuzu/configuration/configure_tas.h | 38 +++++ src/yuzu/configuration/configure_tas.ui | 143 ++++++++++++++++ src/yuzu/debugger/controller.h | 1 - src/yuzu/main.cpp | 67 +++++--- src/yuzu/main.h | 1 + src/yuzu/main.ui | 6 + 19 files changed, 452 insertions(+), 184 deletions(-) create mode 100644 src/yuzu/configuration/configure_tas.cpp create mode 100644 src/yuzu/configuration/configure_tas.h create mode 100644 src/yuzu/configuration/configure_tas.ui (limited to 'src/yuzu/debugger') diff --git a/src/common/fs/fs_paths.h b/src/common/fs/fs_paths.h index 84968b8e0..5d447f108 100644 --- a/src/common/fs/fs_paths.h +++ b/src/common/fs/fs_paths.h @@ -21,7 +21,7 @@ #define SCREENSHOTS_DIR "screenshots" #define SDMC_DIR "sdmc" #define SHADER_DIR "shader" -#define TAS_DIR "scripts" +#define TAS_DIR "tas" // yuzu-specific files diff --git a/src/common/fs/path_util.cpp b/src/common/fs/path_util.cpp index 97d026eb8..43b79bd6d 100644 --- a/src/common/fs/path_util.cpp +++ b/src/common/fs/path_util.cpp @@ -116,7 +116,7 @@ private: GenerateYuzuPath(YuzuPath::ScreenshotsDir, yuzu_path / SCREENSHOTS_DIR); GenerateYuzuPath(YuzuPath::SDMCDir, yuzu_path / SDMC_DIR); GenerateYuzuPath(YuzuPath::ShaderDir, yuzu_path / SHADER_DIR); - GenerateYuzuPath(YuzuPath::TASFile, yuzu_path / TAS_DIR); + GenerateYuzuPath(YuzuPath::TASDir, yuzu_path / TAS_DIR); } ~PathManagerImpl() = default; diff --git a/src/common/fs/path_util.h b/src/common/fs/path_util.h index 6079de4c6..52e4670e2 100644 --- a/src/common/fs/path_util.h +++ b/src/common/fs/path_util.h @@ -23,8 +23,7 @@ enum class YuzuPath { ScreenshotsDir, // Where yuzu screenshots are stored. SDMCDir, // Where the emulated SDMC is stored. ShaderDir, // Where shaders are stored. - - TASFile, // Where the current script file is stored. + TASDir, // Where the current script file is stored. }; /** diff --git a/src/core/hle/service/apm/apm_interface.cpp b/src/core/hle/service/apm/apm_interface.cpp index 724483107..520ccfa88 100644 --- a/src/core/hle/service/apm/apm_interface.cpp +++ b/src/core/hle/service/apm/apm_interface.cpp @@ -121,7 +121,7 @@ void APM_Sys::SetCpuBoostMode(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_APM, "called, mode={:08X}", mode); - Settings::values.is_cpu_boosted = (static_cast(mode) == 1); + Settings::values.is_cpu_boosted = (mode == CpuBoostMode::Full); controller.SetFromCpuBoostMode(mode); IPC::ResponseBuilder rb{ctx, 2}; diff --git a/src/input_common/main.cpp b/src/input_common/main.cpp index 4f170493e..3b9906b53 100644 --- a/src/input_common/main.cpp +++ b/src/input_common/main.cpp @@ -5,6 +5,7 @@ #include #include #include "common/param_package.h" +#include "common/settings.h" #include "input_common/analog_from_button.h" #include "input_common/gcadapter/gc_adapter.h" #include "input_common/gcadapter/gc_poller.h" @@ -114,8 +115,11 @@ struct InputSubsystem::Impl { std::vector devices = { Common::ParamPackage{{"display", "Any"}, {"class", "any"}}, Common::ParamPackage{{"display", "Keyboard/Mouse"}, {"class", "keyboard"}}, - Common::ParamPackage{{"display", "TAS"}, {"class", "tas"}}, }; + if (Settings::values.tas_enable) { + devices.push_back( + Common::ParamPackage{{"display", "TAS Controller"}, {"class", "tas"}}); + } #ifdef HAVE_SDL2 auto sdl_devices = sdl->GetInputDevices(); devices.insert(devices.end(), sdl_devices.begin(), sdl_devices.end()); diff --git a/src/input_common/tas/tas_input.cpp b/src/input_common/tas/tas_input.cpp index 7320a7004..6efa1234a 100644 --- a/src/input_common/tas/tas_input.cpp +++ b/src/input_common/tas/tas_input.cpp @@ -67,14 +67,13 @@ void Tas::LoadTasFile(size_t player_index) { if (!commands[player_index].empty()) { commands[player_index].clear(); } - std::string file = Common::FS::ReadStringFromFile( - Common::FS::GetYuzuPathString(Common::FS::YuzuPath::TASFile) + "script0-" + - std::to_string(player_index + 1) + ".txt", - Common::FS::FileType::BinaryFile); + std::string file = + Common::FS::ReadStringFromFile(Common::FS::GetYuzuPathString(Common::FS::YuzuPath::TASDir) + + "script0-" + std::to_string(player_index + 1) + ".txt", + Common::FS::FileType::BinaryFile); std::stringstream command_line(file); std::string line; - int frameNo = 0; - TASCommand empty = {.buttons = 0, .l_axis = {0.f, 0.f}, .r_axis = {0.f, 0.f}}; + int frame_no = 0; while (std::getline(command_line, line, '\n')) { if (line.empty()) { continue; @@ -94,9 +93,9 @@ void Tas::LoadTasFile(size_t player_index) { continue; } - while (frameNo < std::stoi(seglist.at(0))) { - commands[player_index].push_back(empty); - frameNo++; + while (frame_no < std::stoi(seglist.at(0))) { + commands[player_index].push_back({}); + frame_no++; } TASCommand command = { @@ -105,30 +104,29 @@ void Tas::LoadTasFile(size_t player_index) { .r_axis = ReadCommandAxis(seglist.at(3)), }; commands[player_index].push_back(command); - frameNo++; + frame_no++; } - LOG_INFO(Input, "TAS file loaded! {} frames", frameNo); + LOG_INFO(Input, "TAS file loaded! {} frames", frame_no); } void Tas::WriteTasFile() { LOG_DEBUG(Input, "WriteTasFile()"); - std::string output_text = ""; - for (int frame = 0; frame < (signed)record_commands.size(); frame++) { + std::string output_text; + for (size_t frame = 0; frame < record_commands.size(); frame++) { if (!output_text.empty()) { output_text += "\n"; } - TASCommand line = record_commands.at(frame); + const TASCommand& line = record_commands[frame]; output_text += std::to_string(frame) + " " + WriteCommandButtons(line.buttons) + " " + WriteCommandAxis(line.l_axis) + " " + WriteCommandAxis(line.r_axis); } - size_t bytesWritten = Common::FS::WriteStringToFile( - Common::FS::GetYuzuPathString(Common::FS::YuzuPath::TASFile) + "record.txt", + const size_t bytes_written = Common::FS::WriteStringToFile( + Common::FS::GetYuzuPathString(Common::FS::YuzuPath::TASDir) + "record.txt", Common::FS::FileType::TextFile, output_text); - if (bytesWritten == output_text.size()) { + if (bytes_written == output_text.size()) { LOG_INFO(Input, "TAS file written to file!"); - } - else { - LOG_ERROR(Input, "Writing the TAS-file has failed! {} / {} bytes written", bytesWritten, + } else { + LOG_ERROR(Input, "Writing the TAS-file has failed! {} / {} bytes written", bytes_written, output_text.size()); } } @@ -142,30 +140,33 @@ void Tas::RecordInput(u32 buttons, const std::array, 2>& last_input = {buttons, FlipY(axes[0]), FlipY(axes[1])}; } -std::tuple Tas::GetStatus() { +std::tuple Tas::GetStatus() const { TasState state; - if (Settings::values.tas_record) { - return {TasState::RECORDING, record_commands.size(), record_commands.size()}; - } else if (Settings::values.tas_enable) { - state = TasState::RUNNING; + if (is_recording) { + return {TasState::Recording, 0, record_commands.size()}; + } + + if (is_running) { + state = TasState::Running; } else { - state = TasState::STOPPED; + state = TasState::Stopped; } return {state, current_command, script_length}; } static std::string DebugButtons(u32 buttons) { - return "{ " + TasInput::Tas::ButtonsToString(buttons) + " }"; + return fmt::format("{{ {} }}", TasInput::Tas::ButtonsToString(buttons)); } static std::string DebugJoystick(float x, float y) { - return "[ " + std::to_string(x) + "," + std::to_string(y) + " ]"; + return fmt::format("[ {} , {} ]", std::to_string(x), std::to_string(y)); } static std::string DebugInput(const TasData& data) { - return "{ " + DebugButtons(data.buttons) + " , " + DebugJoystick(data.axis[0], data.axis[1]) + - " , " + DebugJoystick(data.axis[2], data.axis[3]) + " }"; + return fmt::format("{{ {} , {} , {} }}", DebugButtons(data.buttons), + DebugJoystick(data.axis[0], data.axis[1]), + DebugJoystick(data.axis[2], data.axis[3])); } static std::string DebugInputs(const std::array& arr) { @@ -180,66 +181,54 @@ static std::string DebugInputs(const std::array& arr) { } void Tas::UpdateThread() { - if (update_thread_running) { - if (Settings::values.pause_tas_on_load && Settings::values.is_cpu_boosted) { - for (size_t i = 0; i < PLAYER_NUMBER; i++) { - tas_data[i].buttons = 0; - tas_data[i].axis = {}; - } - } + if (!update_thread_running) { + return; + } - if (Settings::values.tas_record) { - record_commands.push_back(last_input); - } - if (!Settings::values.tas_record && !record_commands.empty()) { - WriteTasFile(); - Settings::values.tas_reset = true; - refresh_tas_fle = true; - record_commands.clear(); - } - if (Settings::values.tas_reset) { - current_command = 0; - if (refresh_tas_fle) { - LoadTasFiles(); - refresh_tas_fle = false; - } - Settings::values.tas_reset = false; + if (is_recording) { + record_commands.push_back(last_input); + } + if (!is_recording && !record_commands.empty()) { + WriteTasFile(); + needs_reset = true; + refresh_tas_fle = true; + record_commands.clear(); + } + if (needs_reset) { + current_command = 0; + if (refresh_tas_fle) { LoadTasFiles(); - LOG_DEBUG(Input, "tas_reset done"); + refresh_tas_fle = false; } - if (Settings::values.tas_enable) { - if ((signed)current_command < script_length) { - LOG_INFO(Input, "Playing TAS {}/{}", current_command, script_length); - size_t frame = current_command++; - for (size_t i = 0; i < PLAYER_NUMBER; i++) { - if (frame < commands[i].size()) { - TASCommand command = commands[i][frame]; - tas_data[i].buttons = command.buttons; - auto [l_axis_x, l_axis_y] = command.l_axis; - tas_data[i].axis[0] = l_axis_x; - tas_data[i].axis[1] = l_axis_y; - auto [r_axis_x, r_axis_y] = command.r_axis; - tas_data[i].axis[2] = r_axis_x; - tas_data[i].axis[3] = r_axis_y; - } else { - tas_data[i].buttons = 0; - tas_data[i].axis = {}; - } - } - } else { - Settings::values.tas_enable = false; - current_command = 0; - for (size_t i = 0; i < PLAYER_NUMBER; i++) { - tas_data[i].buttons = 0; - tas_data[i].axis = {}; + needs_reset = false; + LoadTasFiles(); + LOG_DEBUG(Input, "tas_reset done"); + } + if (is_running) { + if (current_command < script_length) { + LOG_INFO(Input, "Playing TAS {}/{}", current_command, script_length); + size_t frame = current_command++; + for (size_t i = 0; i < PLAYER_NUMBER; i++) { + if (frame < commands[i].size()) { + TASCommand command = commands[i][frame]; + tas_data[i].buttons = command.buttons; + auto [l_axis_x, l_axis_y] = command.l_axis; + tas_data[i].axis[0] = l_axis_x; + tas_data[i].axis[1] = l_axis_y; + auto [r_axis_x, r_axis_y] = command.r_axis; + tas_data[i].axis[2] = r_axis_x; + tas_data[i].axis[3] = r_axis_y; + } else { + tas_data[i] = {}; } } } else { - for (size_t i = 0; i < PLAYER_NUMBER; i++) { - tas_data[i].buttons = 0; - tas_data[i].axis = {}; - } + is_running = Settings::values.tas_loop; + current_command = 0; + tas_data.fill({}); } + } else { + tas_data.fill({}); } LOG_DEBUG(Input, "TAS inputs: {}", DebugInputs(tas_data)); } @@ -284,8 +273,9 @@ std::string Tas::WriteCommandAxis(TasAnalog data) const { } std::string Tas::WriteCommandButtons(u32 data) const { - if (data == 0) + if (data == 0) { return "NONE"; + } std::string line; u32 index = 0; @@ -307,6 +297,37 @@ std::string Tas::WriteCommandButtons(u32 data) const { return line; } +void Tas::StartStop() { + is_running = !is_running; +} + +void Tas::Reset() { + needs_reset = true; +} + +void Tas::Record() { + is_recording = !is_recording; +<<<<<<< HEAD +======= + return is_recording; +} + +void Tas::SaveRecording(bool overwrite_file) { + if (is_recording) { + return; + } + if (record_commands.empty()) { + return; + } + WriteTasFile("record.txt"); + if (overwrite_file) { + WriteTasFile("script0-1.txt"); + } + needs_reset = true; + record_commands.clear(); +>>>>>>> 773d268db (config: disable pause on load) +} + InputCommon::ButtonMapping Tas::GetButtonMappingForDevice( const Common::ParamPackage& params) const { // This list is missing ZL/ZR since those are not considered buttons. diff --git a/src/input_common/tas/tas_input.h b/src/input_common/tas/tas_input.h index 8ee70bcaf..49ef10ff9 100644 --- a/src/input_common/tas/tas_input.h +++ b/src/input_common/tas/tas_input.h @@ -14,14 +14,14 @@ namespace TasInput { -constexpr int PLAYER_NUMBER = 8; +constexpr size_t PLAYER_NUMBER = 8; using TasAnalog = std::pair; enum class TasState { - RUNNING, - RECORDING, - STOPPED, + Running, + Recording, + Stopped, }; enum class TasButton : u32 { @@ -114,8 +114,19 @@ public: void LoadTasFiles(); void RecordInput(u32 buttons, const std::array, 2>& axes); void UpdateThread(); - std::tuple GetStatus(); + void StartStop(); + void Reset(); + void Record(); + + /** + * Returns the current status values of TAS playback/recording + * @return Tuple of + * TasState indicating the current state out of Running, Recording or Stopped ; + * Current playback progress or amount of frames (so far) for Recording ; + * Total length of script file currently loaded or amount of frames (so far) for Recording + */ + std::tuple GetStatus() const; InputCommon::ButtonMapping GetButtonMappingForDevice(const Common::ParamPackage& params) const; InputCommon::AnalogMapping GetAnalogMappingForDevice(const Common::ParamPackage& params) const; [[nodiscard]] const TasData& GetTasState(std::size_t pad) const; @@ -137,9 +148,12 @@ private: std::array tas_data; bool update_thread_running{true}; bool refresh_tas_fle{false}; + bool is_recording{false}; + bool is_running{false}; + bool needs_reset{false}; std::array, PLAYER_NUMBER> commands{}; std::vector record_commands{}; - std::size_t current_command{0}; + size_t current_command{0}; TASCommand last_input{}; // only used for recording }; } // namespace TasInput diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt index 19ba0dbba..b6dda283d 100644 --- a/src/yuzu/CMakeLists.txt +++ b/src/yuzu/CMakeLists.txt @@ -108,6 +108,9 @@ add_executable(yuzu configuration/configure_system.cpp configuration/configure_system.h configuration/configure_system.ui + configuration/configure_tas.cpp + configuration/configure_tas.h + configuration/configure_tas.ui configuration/configure_touch_from_button.cpp configuration/configure_touch_from_button.h configuration/configure_touch_from_button.ui diff --git a/src/yuzu/configuration/configure_filesystem.cpp b/src/yuzu/configuration/configure_filesystem.cpp index 4636d476e..013de02db 100644 --- a/src/yuzu/configuration/configure_filesystem.cpp +++ b/src/yuzu/configuration/configure_filesystem.cpp @@ -26,8 +26,6 @@ ConfigureFilesystem::ConfigureFilesystem(QWidget* parent) [this] { SetDirectory(DirectoryTarget::Dump, ui->dump_path_edit); }); connect(ui->load_path_button, &QToolButton::pressed, this, [this] { SetDirectory(DirectoryTarget::Load, ui->load_path_edit); }); - connect(ui->tas_path_button, &QToolButton::pressed, this, - [this] { SetDirectory(DirectoryTarget::TAS, ui->tas_path_edit); }); connect(ui->reset_game_list_cache, &QPushButton::pressed, this, &ConfigureFilesystem::ResetMetadata); @@ -51,8 +49,6 @@ void ConfigureFilesystem::setConfiguration() { QString::fromStdString(Common::FS::GetYuzuPathString(Common::FS::YuzuPath::DumpDir))); ui->load_path_edit->setText( QString::fromStdString(Common::FS::GetYuzuPathString(Common::FS::YuzuPath::LoadDir))); - ui->tas_path_edit->setText( - QString::fromStdString(Common::FS::GetYuzuPathString(Common::FS::YuzuPath::TASFile))); ui->gamecard_inserted->setChecked(Settings::values.gamecard_inserted.GetValue()); ui->gamecard_current_game->setChecked(Settings::values.gamecard_current_game.GetValue()); @@ -74,11 +70,9 @@ void ConfigureFilesystem::applyConfiguration() { ui->dump_path_edit->text().toStdString()); Common::FS::SetYuzuPath(Common::FS::YuzuPath::LoadDir, ui->load_path_edit->text().toStdString()); - Common::FS::SetYuzuPath(Common::FS::YuzuPath::TASFile, ui->tas_path_edit->text().toStdString()); Settings::values.gamecard_inserted = ui->gamecard_inserted->isChecked(); Settings::values.gamecard_current_game = ui->gamecard_current_game->isChecked(); - Settings::values.pause_tas_on_load = ui->tas_pause_on_load->isChecked(); Settings::values.dump_exefs = ui->dump_exefs->isChecked(); Settings::values.dump_nso = ui->dump_nso->isChecked(); @@ -104,9 +98,6 @@ void ConfigureFilesystem::SetDirectory(DirectoryTarget target, QLineEdit* edit) case DirectoryTarget::Load: caption = tr("Select Mod Load Directory..."); break; - case DirectoryTarget::TAS: - caption = tr("Select TAS Directory..."); - break; } QString str; diff --git a/src/yuzu/configuration/configure_filesystem.h b/src/yuzu/configuration/configure_filesystem.h index 86dab8684..2147cd405 100644 --- a/src/yuzu/configuration/configure_filesystem.h +++ b/src/yuzu/configuration/configure_filesystem.h @@ -32,7 +32,6 @@ private: Gamecard, Dump, Load, - TAS, }; void SetDirectory(DirectoryTarget target, QLineEdit* edit); diff --git a/src/yuzu/configuration/configure_filesystem.ui b/src/yuzu/configuration/configure_filesystem.ui index 8ac7250fd..62b9abc7a 100644 --- a/src/yuzu/configuration/configure_filesystem.ui +++ b/src/yuzu/configuration/configure_filesystem.ui @@ -219,55 +219,6 @@ - - - - TAS Directories - - - - - - Path - - - - - - - ... - - - - - - - - - - Qt::Horizontal - - - QSizePolicy::Maximum - - - - 60 - 20 - - - - - - - - Pause TAS execution during loads (SMO - 1.3) - - - - - - diff --git a/src/yuzu/configuration/configure_input_player_widget.cpp b/src/yuzu/configuration/configure_input_player_widget.cpp index e649e2169..e4383676a 100644 --- a/src/yuzu/configuration/configure_input_player_widget.cpp +++ b/src/yuzu/configuration/configure_input_player_widget.cpp @@ -232,7 +232,7 @@ void PlayerControlPreview::UpdateInput() { axis_values[Settings::NativeAnalog::RStick].value.x(), axis_values[Settings::NativeAnalog::RStick].value.y()}; input.button_values = button_values; - if (controller_callback.input != NULL) { + if (controller_callback.input != nullptr) { controller_callback.input(std::move(input)); } @@ -242,7 +242,7 @@ void PlayerControlPreview::UpdateInput() { } void PlayerControlPreview::SetCallBack(ControllerCallback callback_) { - controller_callback = callback_; + controller_callback = std::move(callback_); } void PlayerControlPreview::paintEvent(QPaintEvent* event) { diff --git a/src/yuzu/configuration/configure_tas.cpp b/src/yuzu/configuration/configure_tas.cpp new file mode 100644 index 000000000..f2f91d84a --- /dev/null +++ b/src/yuzu/configuration/configure_tas.cpp @@ -0,0 +1,84 @@ +// Copyright 2021 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include +#include "common/fs/fs.h" +#include "common/fs/path_util.h" +#include "common/settings.h" +#include "ui_configure_tas.h" +#include "yuzu/configuration/configure_tas.h" +#include "yuzu/uisettings.h" + +ConfigureTasDialog::ConfigureTasDialog(QWidget* parent) + : QDialog(parent), ui(std::make_unique()) { + + ui->setupUi(this); + + setFocusPolicy(Qt::ClickFocus); + setWindowTitle(tr("TAS Configuration")); + + connect(ui->tas_path_button, &QToolButton::pressed, this, + [this] { SetDirectory(DirectoryTarget::TAS, ui->tas_path_edit); }); + + LoadConfiguration(); +} + +ConfigureTasDialog::~ConfigureTasDialog() = default; + +void ConfigureTasDialog::LoadConfiguration() { + ui->tas_path_edit->setText( + QString::fromStdString(Common::FS::GetYuzuPathString(Common::FS::YuzuPath::TASDir))); + ui->tas_enable->setChecked(Settings::values.tas_enable); + ui->tas_control_swap->setChecked(Settings::values.tas_swap_controllers); + ui->tas_loop_script->setChecked(Settings::values.tas_loop); + ui->tas_pause_on_load->setChecked(Settings::values.pause_tas_on_load); +} + +void ConfigureTasDialog::ApplyConfiguration() { + Common::FS::SetYuzuPath(Common::FS::YuzuPath::TASDir, ui->tas_path_edit->text().toStdString()); + Settings::values.tas_enable = ui->tas_enable->isChecked(); + Settings::values.tas_swap_controllers = ui->tas_control_swap->isChecked(); + Settings::values.tas_loop = ui->tas_loop_script->isChecked(); + Settings::values.pause_tas_on_load = ui->tas_pause_on_load->isChecked(); +} + +void ConfigureTasDialog::SetDirectory(DirectoryTarget target, QLineEdit* edit) { + QString caption; + + switch (target) { + case DirectoryTarget::TAS: + caption = tr("Select TAS Load Directory..."); + break; + } + + QString str = QFileDialog::getExistingDirectory(this, caption, edit->text()); + + if (str.isNull() || str.isEmpty()) { + return; + } + + if (str.back() != QChar::fromLatin1('/')) { + str.append(QChar::fromLatin1('/')); + } + + edit->setText(str); +} + +void ConfigureTasDialog::changeEvent(QEvent* event) { + if (event->type() == QEvent::LanguageChange) { + RetranslateUI(); + } + + QDialog::changeEvent(event); +} + +void ConfigureTasDialog::RetranslateUI() { + ui->retranslateUi(this); +} + +void ConfigureTasDialog::HandleApplyButtonClicked() { + UISettings::values.configuration_applied = true; + ApplyConfiguration(); +} diff --git a/src/yuzu/configuration/configure_tas.h b/src/yuzu/configuration/configure_tas.h new file mode 100644 index 000000000..1546bf16f --- /dev/null +++ b/src/yuzu/configuration/configure_tas.h @@ -0,0 +1,38 @@ +// Copyright 2021 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include + +namespace Ui { +class ConfigureTas; +} + +class ConfigureTasDialog : public QDialog { + Q_OBJECT + +public: + explicit ConfigureTasDialog(QWidget* parent); + ~ConfigureTasDialog() override; + + /// Save all button configurations to settings file + void ApplyConfiguration(); + +private: + enum class DirectoryTarget { + TAS, + }; + + void LoadConfiguration(); + + void SetDirectory(DirectoryTarget target, QLineEdit* edit); + + void changeEvent(QEvent* event) override; + void RetranslateUI(); + + void HandleApplyButtonClicked(); + + std::unique_ptr ui; +}; diff --git a/src/yuzu/configuration/configure_tas.ui b/src/yuzu/configuration/configure_tas.ui new file mode 100644 index 000000000..906e073ff --- /dev/null +++ b/src/yuzu/configuration/configure_tas.ui @@ -0,0 +1,143 @@ + + + ConfigureTas + + + + 0 + 0 + 800 + 300 + + + + Dialog + + + + + + + + TAS Settings + + + + + + Enable TAS features + + + + + + + false + + + Automatic controller profile swapping + + + + + + + Loop script + + + + + + + false + + + Pause execution during loads + + + + + + + + + + + + + + TAS Directories + + + + + + Path + + + + + + + ... + + + + + + + + + + Qt::Horizontal + + + QSizePolicy::Maximum + + + + 60 + 20 + + + + + + + + + + + + + + 0 + 0 + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + ConfigureTas + accept() + + + buttonBox + rejected() + ConfigureTas + reject() + + + diff --git a/src/yuzu/debugger/controller.h b/src/yuzu/debugger/controller.h index 659923e1b..f2f6653f7 100644 --- a/src/yuzu/debugger/controller.h +++ b/src/yuzu/debugger/controller.h @@ -25,7 +25,6 @@ struct ControllerInput { struct ControllerCallback { std::function input; - std::function update; }; class ControllerDialog : public QWidget { diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index 0ee0fd8cd..820e31fa7 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -19,6 +19,7 @@ #include "common/nvidia_flags.h" #include "configuration/configure_input.h" #include "configuration/configure_per_game.h" +#include "configuration/configure_tas.h" #include "configuration/configure_vibration.h" #include "core/file_sys/vfs.h" #include "core/file_sys/vfs_real.h" @@ -750,6 +751,11 @@ void GMainWindow::InitializeWidgets() { statusBar()->addPermanentWidget(label); } + tas_label = new QLabel(); + tas_label->setObjectName(QStringLiteral("TASlabel")); + tas_label->setFocusPolicy(Qt::NoFocus); + statusBar()->insertPermanentWidget(0, tas_label); + // Setup Dock button dock_status_button = new QPushButton(); dock_status_button->setObjectName(QStringLiteral("TogglableStatusBarButton")); @@ -826,12 +832,6 @@ void GMainWindow::InitializeWidgets() { }); statusBar()->insertPermanentWidget(0, renderer_status_button); - tas_label = new QLabel(); - tas_label->setObjectName(QStringLiteral("TASlabel")); - tas_label->setText(tr("TAS not running")); - tas_label->setFocusPolicy(Qt::NoFocus); - statusBar()->insertPermanentWidget(0, tas_label); - statusBar()->setVisible(true); setStyleSheet(QStringLiteral("QStatusBar::item{border: none;}")); } @@ -1024,18 +1024,11 @@ void GMainWindow::InitializeHotkeys() { } }); connect(hotkey_registry.GetHotkey(main_window, QStringLiteral("TAS Start/Stop"), this), - &QShortcut::activated, this, [&] { - Settings::values.tas_enable = !Settings::values.tas_enable; - LOG_INFO(Frontend, "Tas enabled {}", Settings::values.tas_enable); - }); - + &QShortcut::activated, this, [&] { input_subsystem->GetTas()->StartStop(); }); connect(hotkey_registry.GetHotkey(main_window, QStringLiteral("TAS Reset"), this), - &QShortcut::activated, this, [&] { Settings::values.tas_reset = true; }); + &QShortcut::activated, this, [&] { input_subsystem->GetTas()->Reset(); }); connect(hotkey_registry.GetHotkey(main_window, QStringLiteral("TAS Record"), this), - &QShortcut::activated, this, [&] { - Settings::values.tas_record = !Settings::values.tas_record; - LOG_INFO(Frontend, "Tas recording {}", Settings::values.tas_record); - }); + &QShortcut::activated, this, [&] { input_subsystem->GetTas()->Record(); }); } void GMainWindow::SetDefaultUIGeometry() { @@ -1154,6 +1147,7 @@ void GMainWindow::ConnectMenuEvents() { connect(ui.action_Open_FAQ, &QAction::triggered, this, &GMainWindow::OnOpenFAQ); connect(ui.action_Restart, &QAction::triggered, this, [this] { BootGame(QString(game_path)); }); connect(ui.action_Configure, &QAction::triggered, this, &GMainWindow::OnConfigure); + connect(ui.action_Configure_Tas, &QAction::triggered, this, &GMainWindow::OnConfigureTas); connect(ui.action_Configure_Current_Game, &QAction::triggered, this, &GMainWindow::OnConfigurePerGame); @@ -2720,6 +2714,19 @@ void GMainWindow::OnConfigure() { UpdateStatusButtons(); } +void GMainWindow::OnConfigureTas() { + const auto& system = Core::System::GetInstance(); + ConfigureTasDialog dialog(this); + const auto result = dialog.exec(); + + if (result != QDialog::Accepted && !UISettings::values.configuration_applied) { + Settings::RestoreGlobalState(system.IsPoweredOn()); + return; + } else if (result == QDialog::Accepted) { + dialog.ApplyConfiguration(); + } +} + void GMainWindow::OnConfigurePerGame() { const u64 title_id = Core::System::GetInstance().CurrentProcess()->GetTitleID(); OpenPerGameConfiguration(title_id, game_path.toStdString()); @@ -2898,14 +2905,14 @@ void GMainWindow::UpdateWindowTitle(std::string_view title_name, std::string_vie static std::string GetTasStateDescription(TasInput::TasState state) { switch (state) { - case TasInput::TasState::RUNNING: - return "Running"; - case TasInput::TasState::RECORDING: - return "Recording"; - case TasInput::TasState::STOPPED: - return "Stopped"; - default: - return "INVALID STATE"; + case TasInput::TasState::Running: + return "Running"; + case TasInput::TasState::Recording: + return "Recording"; + case TasInput::TasState::Stopped: + return "Stopped"; + default: + return "INVALID STATE"; } } @@ -2915,8 +2922,16 @@ void GMainWindow::UpdateStatusBar() { return; } - auto [tas_status, current_tas_frame, total_tas_frames] = input_subsystem->GetTas()->GetStatus(); - tas_label->setText(tr("%1 TAS %2/%3").arg(tr(GetTasStateDescription(tas_status).c_str())).arg(current_tas_frame).arg(total_tas_frames)); + if (Settings::values.tas_enable) { + auto [tas_status, current_tas_frame, total_tas_frames] = + input_subsystem->GetTas()->GetStatus(); + tas_label->setText(tr("%1 TAS %2/%3") + .arg(tr(GetTasStateDescription(tas_status).c_str())) + .arg(current_tas_frame) + .arg(total_tas_frames)); + } else { + tas_label->clear(); + } auto& system = Core::System::GetInstance(); auto results = system.GetAndResetPerfStats(); diff --git a/src/yuzu/main.h b/src/yuzu/main.h index edca661ac..867a0003c 100644 --- a/src/yuzu/main.h +++ b/src/yuzu/main.h @@ -259,6 +259,7 @@ private slots: void OnMenuInstallToNAND(); void OnMenuRecentFile(); void OnConfigure(); + void OnConfigureTas(); void OnConfigurePerGame(); void OnLoadAmiibo(); void OnOpenYuzuFolder(); diff --git a/src/yuzu/main.ui b/src/yuzu/main.ui index 048870687..31c1a20f3 100644 --- a/src/yuzu/main.ui +++ b/src/yuzu/main.ui @@ -72,6 +72,7 @@ + @@ -294,6 +295,11 @@ &Capture Screenshot + + + Configure &TAS... + + false -- cgit v1.2.3 From f078b15565c8cab08587b8f8629d878615705cfb Mon Sep 17 00:00:00 2001 From: MonsterDruide1 <5958456@gmail.com> Date: Sun, 20 Jun 2021 00:04:34 +0200 Subject: input_common/tas: Fallback to simple update --- src/common/settings.h | 7 ++- src/core/hle/service/apm/apm_interface.cpp | 2 - src/core/hle/service/vi/vi.cpp | 3 -- src/input_common/tas/tas_input.cpp | 47 ++++++++--------- src/input_common/tas/tas_input.h | 59 ++++------------------ .../configure_input_player_widget.cpp | 3 ++ src/yuzu/debugger/controller.cpp | 16 +++--- src/yuzu/debugger/controller.h | 3 +- src/yuzu/main.cpp | 21 +++----- src/yuzu/main.h | 1 + 10 files changed, 60 insertions(+), 102 deletions(-) (limited to 'src/yuzu/debugger') diff --git a/src/common/settings.h b/src/common/settings.h index 7333a64dc..cf12c325c 100644 --- a/src/common/settings.h +++ b/src/common/settings.h @@ -14,7 +14,6 @@ #include #include -#include #include "common/common_types.h" #include "common/settings_input.h" @@ -515,9 +514,9 @@ struct Values { BasicSetting pause_tas_on_load { false, "pause_tas_on_load" }; BasicSetting tas_enable{ false, "tas_enable" }; - BasicSetting tas_reset{ false, "tas_reset" }; - BasicSetting tas_record{ false, "tas_record" }; - BasicSetting is_cpu_boxted{ false, " BasicSetting is_cpu_boxted{ false, "cpuBoosted" }; + BasicSetting tas_loop{ false, "tas_loop" }; + BasicSetting tas_swap_controllers{ false, "tas_swap_controllers" }; + BasicSetting is_cpu_boosted{ false, "is_cpu_boosted" }; " }; BasicSetting mouse_panning{false, "mouse_panning"}; diff --git a/src/core/hle/service/apm/apm_interface.cpp b/src/core/hle/service/apm/apm_interface.cpp index 520ccfa88..e58bad083 100644 --- a/src/core/hle/service/apm/apm_interface.cpp +++ b/src/core/hle/service/apm/apm_interface.cpp @@ -3,7 +3,6 @@ // Refer to the license.txt file included. #include "common/logging/log.h" -#include "common/settings.h" #include "core/hle/ipc_helpers.h" #include "core/hle/service/apm/apm.h" #include "core/hle/service/apm/apm_controller.h" @@ -121,7 +120,6 @@ void APM_Sys::SetCpuBoostMode(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_APM, "called, mode={:08X}", mode); - Settings::values.is_cpu_boosted = (mode == CpuBoostMode::Full); controller.SetFromCpuBoostMode(mode); IPC::ResponseBuilder rb{ctx, 2}; diff --git a/src/core/hle/service/vi/vi.cpp b/src/core/hle/service/vi/vi.cpp index f4eac0bca..8e8fc40ca 100644 --- a/src/core/hle/service/vi/vi.cpp +++ b/src/core/hle/service/vi/vi.cpp @@ -32,8 +32,6 @@ #include "core/hle/service/vi/vi_s.h" #include "core/hle/service/vi/vi_u.h" -#include "input_common/tas/tas_input.h" - namespace Service::VI { constexpr ResultCode ERR_OPERATION_FAILED{ErrorModule::VI, 1}; @@ -597,7 +595,6 @@ private: IGBPQueueBufferResponseParcel response{1280, 720}; ctx.WriteBuffer(response.Serialize()); - Settings::values.input_subsystem->GetTas()->UpdateThread(); break; } case TransactionId::Query: { diff --git a/src/input_common/tas/tas_input.cpp b/src/input_common/tas/tas_input.cpp index 6efa1234a..baeb18c22 100644 --- a/src/input_common/tas/tas_input.cpp +++ b/src/input_common/tas/tas_input.cpp @@ -2,13 +2,8 @@ // Licensed under GPLv2+ // Refer to the license.txt file included. -#include #include -#include -#include #include -#include -#include #include "common/fs/file.h" #include "common/fs/fs_types.h" @@ -43,27 +38,25 @@ constexpr std::array, 20> text_to_tas_but }; Tas::Tas() { + if (!Settings::values.tas_enable) { + return; + } LoadTasFiles(); } -Tas::~Tas() { - update_thread_running = false; -} +Tas::~Tas() = default; -void Tas::RefreshTasFile() { - refresh_tas_fle = true; -} void Tas::LoadTasFiles() { script_length = 0; - for (size_t i = 0; i < PLAYER_NUMBER; i++) { + for (size_t i = 0; i < commands.size(); i++) { LoadTasFile(i); if (commands[i].size() > script_length) { script_length = commands[i].size(); } } } + void Tas::LoadTasFile(size_t player_index) { - LOG_DEBUG(Input, "LoadTasFile()"); if (!commands[player_index].empty()) { commands[player_index].clear(); } @@ -110,7 +103,6 @@ void Tas::LoadTasFile(size_t player_index) { } void Tas::WriteTasFile() { - LOG_DEBUG(Input, "WriteTasFile()"); std::string output_text; for (size_t frame = 0; frame < record_commands.size(); frame++) { if (!output_text.empty()) { @@ -131,13 +123,13 @@ void Tas::WriteTasFile() { } } -static std::pair FlipY(std::pair old) { +std::pair Tas::FlipAxisY(std::pair old) { auto [x, y] = old; return {x, -y}; } void Tas::RecordInput(u32 buttons, const std::array, 2>& axes) { - last_input = {buttons, FlipY(axes[0]), FlipY(axes[1])}; + last_input = {buttons, FlipAxisY(axes[0]), FlipAxisY(axes[1])}; } std::tuple Tas::GetStatus() const { @@ -155,21 +147,21 @@ std::tuple Tas::GetStatus() const { return {state, current_command, script_length}; } -static std::string DebugButtons(u32 buttons) { +std::string Tas::DebugButtons(u32 buttons) const { return fmt::format("{{ {} }}", TasInput::Tas::ButtonsToString(buttons)); } -static std::string DebugJoystick(float x, float y) { +std::string Tas::DebugJoystick(float x, float y) const { return fmt::format("[ {} , {} ]", std::to_string(x), std::to_string(y)); } -static std::string DebugInput(const TasData& data) { +std::string Tas::DebugInput(const TasData& data) const { return fmt::format("{{ {} , {} , {} }}", DebugButtons(data.buttons), DebugJoystick(data.axis[0], data.axis[1]), DebugJoystick(data.axis[2], data.axis[3])); } -static std::string DebugInputs(const std::array& arr) { +std::string Tas::DebugInputs(const std::array& arr) const { std::string returns = "[ "; for (size_t i = 0; i < arr.size(); i++) { returns += DebugInput(arr[i]); @@ -180,8 +172,17 @@ static std::string DebugInputs(const std::array& arr) { return returns + "]"; } +std::string Tas::ButtonsToString(u32 button) const { + std::string returns; + for (auto [text_button, tas_button] : text_to_tas_button) { + if ((button & static_cast(tas_button)) != 0) + returns += fmt::format(", {}", text_button.substr(4)); + } + return returns.empty() ? "" : returns.substr(2); +} + void Tas::UpdateThread() { - if (!update_thread_running) { + if (!Settings::values.tas_enable) { return; } @@ -206,9 +207,9 @@ void Tas::UpdateThread() { } if (is_running) { if (current_command < script_length) { - LOG_INFO(Input, "Playing TAS {}/{}", current_command, script_length); + LOG_DEBUG(Input, "Playing TAS {}/{}", current_command, script_length); size_t frame = current_command++; - for (size_t i = 0; i < PLAYER_NUMBER; i++) { + for (size_t i = 0; i < commands.size(); i++) { if (frame < commands[i].size()) { TASCommand command = commands[i][frame]; tas_data[i].buttons = command.buttons; diff --git a/src/input_common/tas/tas_input.h b/src/input_common/tas/tas_input.h index 49ef10ff9..e011e559e 100644 --- a/src/input_common/tas/tas_input.h +++ b/src/input_common/tas/tas_input.h @@ -5,8 +5,6 @@ #pragma once #include -#include -#include #include "common/common_types.h" #include "core/frontend/input.h" @@ -65,53 +63,6 @@ public: Tas(); ~Tas(); - static std::string ButtonsToString(u32 button) { - std::string returns; - if ((button & static_cast(TasInput::TasButton::BUTTON_A)) != 0) - returns += ", A"; - if ((button & static_cast(TasInput::TasButton::BUTTON_B)) != 0) - returns += ", B"; - if ((button & static_cast(TasInput::TasButton::BUTTON_X)) != 0) - returns += ", X"; - if ((button & static_cast(TasInput::TasButton::BUTTON_Y)) != 0) - returns += ", Y"; - if ((button & static_cast(TasInput::TasButton::STICK_L)) != 0) - returns += ", STICK_L"; - if ((button & static_cast(TasInput::TasButton::STICK_R)) != 0) - returns += ", STICK_R"; - if ((button & static_cast(TasInput::TasButton::TRIGGER_L)) != 0) - returns += ", TRIGGER_L"; - if ((button & static_cast(TasInput::TasButton::TRIGGER_R)) != 0) - returns += ", TRIGGER_R"; - if ((button & static_cast(TasInput::TasButton::TRIGGER_ZL)) != 0) - returns += ", TRIGGER_ZL"; - if ((button & static_cast(TasInput::TasButton::TRIGGER_ZR)) != 0) - returns += ", TRIGGER_ZR"; - if ((button & static_cast(TasInput::TasButton::BUTTON_PLUS)) != 0) - returns += ", PLUS"; - if ((button & static_cast(TasInput::TasButton::BUTTON_MINUS)) != 0) - returns += ", MINUS"; - if ((button & static_cast(TasInput::TasButton::BUTTON_LEFT)) != 0) - returns += ", LEFT"; - if ((button & static_cast(TasInput::TasButton::BUTTON_UP)) != 0) - returns += ", UP"; - if ((button & static_cast(TasInput::TasButton::BUTTON_RIGHT)) != 0) - returns += ", RIGHT"; - if ((button & static_cast(TasInput::TasButton::BUTTON_DOWN)) != 0) - returns += ", DOWN"; - if ((button & static_cast(TasInput::TasButton::BUTTON_SL)) != 0) - returns += ", SL"; - if ((button & static_cast(TasInput::TasButton::BUTTON_SR)) != 0) - returns += ", SR"; - if ((button & static_cast(TasInput::TasButton::BUTTON_HOME)) != 0) - returns += ", HOME"; - if ((button & static_cast(TasInput::TasButton::BUTTON_CAPTURE)) != 0) - returns += ", CAPTURE"; - return returns.empty() ? "" : returns.substr(2); - } - - void RefreshTasFile(); - void LoadTasFiles(); void RecordInput(u32 buttons, const std::array, 2>& axes); void UpdateThread(); @@ -137,6 +88,7 @@ private: TasAnalog l_axis{}; TasAnalog r_axis{}; }; + void LoadTasFiles(); void LoadTasFile(size_t player_index); void WriteTasFile(); TasAnalog ReadCommandAxis(const std::string& line) const; @@ -144,9 +96,16 @@ private: std::string WriteCommandButtons(u32 data) const; std::string WriteCommandAxis(TasAnalog data) const; + std::pair FlipAxisY(std::pair old); + + std::string DebugButtons(u32 buttons) const; + std::string DebugJoystick(float x, float y) const; + std::string DebugInput(const TasData& data) const; + std::string DebugInputs(const std::array& arr) const; + std::string ButtonsToString(u32 button) const; + size_t script_length{0}; std::array tas_data; - bool update_thread_running{true}; bool refresh_tas_fle{false}; bool is_recording{false}; bool is_running{false}; diff --git a/src/yuzu/configuration/configure_input_player_widget.cpp b/src/yuzu/configuration/configure_input_player_widget.cpp index e4383676a..b905fc73d 100644 --- a/src/yuzu/configuration/configure_input_player_widget.cpp +++ b/src/yuzu/configuration/configure_input_player_widget.cpp @@ -175,6 +175,9 @@ void PlayerControlPreview::ResetInputs() { } void PlayerControlPreview::UpdateInput() { + if (controller_callback.update != nullptr) { + controller_callback.update(std::move(true)); + } if (!is_enabled && !mapping_active) { return; } diff --git a/src/yuzu/debugger/controller.cpp b/src/yuzu/debugger/controller.cpp index 822d033d1..a745699bf 100644 --- a/src/yuzu/debugger/controller.cpp +++ b/src/yuzu/debugger/controller.cpp @@ -41,7 +41,8 @@ void ControllerDialog::refreshConfiguration() { constexpr std::size_t player = 0; widget->SetPlayerInputRaw(player, players[player].buttons, players[player].analogs); widget->SetControllerType(players[player].controller_type); - ControllerCallback callback{[this](ControllerInput input) { InputController(input); }}; + ControllerCallback callback{[this](ControllerInput input) { InputController(input); }, + [this](bool update) { UpdateController(update); }}; widget->SetCallBack(callback); widget->repaint(); widget->SetConnectedStatus(players[player].connected); @@ -74,10 +75,6 @@ void ControllerDialog::hideEvent(QHideEvent* ev) { QWidget::hideEvent(ev); } -void ControllerDialog::RefreshTasFile() { - input_subsystem->GetTas()->RefreshTasFile(); -} - void ControllerDialog::InputController(ControllerInput input) { u32 buttons = 0; int index = 0; @@ -86,4 +83,11 @@ void ControllerDialog::InputController(ControllerInput input) { index++; } input_subsystem->GetTas()->RecordInput(buttons, input.axis_values); -} \ No newline at end of file +} + +void ControllerDialog::UpdateController(bool update) { + if (!update) { + return; + } + input_subsystem->GetTas()->UpdateThread(); +} diff --git a/src/yuzu/debugger/controller.h b/src/yuzu/debugger/controller.h index f2f6653f7..448d24b67 100644 --- a/src/yuzu/debugger/controller.h +++ b/src/yuzu/debugger/controller.h @@ -25,6 +25,7 @@ struct ControllerInput { struct ControllerCallback { std::function input; + std::function update; }; class ControllerDialog : public QWidget { @@ -43,8 +44,8 @@ protected: void hideEvent(QHideEvent* ev) override; private: - void RefreshTasFile(); void InputController(ControllerInput input); + void UpdateController(bool update); QAction* toggle_view_action = nullptr; QFileSystemWatcher* watcher = nullptr; PlayerControlPreview* widget; diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index 820e31fa7..6c2835a2f 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -196,7 +196,6 @@ GMainWindow::GMainWindow() config{std::make_unique()}, vfs{std::make_shared()}, provider{std::make_unique()} { Common::Log::Initialize(); - Settings::values.inputSubsystem = input_subsystem; LoadTranslation(); setAcceptDrops(true); @@ -2903,16 +2902,17 @@ void GMainWindow::UpdateWindowTitle(std::string_view title_name, std::string_vie } } -static std::string GetTasStateDescription(TasInput::TasState state) { - switch (state) { +QString GMainWindow::GetTasStateDescription() const { + auto [tas_status, current_tas_frame, total_tas_frames] = input_subsystem->GetTas()->GetStatus(); + switch (tas_status) { case TasInput::TasState::Running: - return "Running"; + return tr("TAS state: Running %1/%2").arg(current_tas_frame).arg(total_tas_frames); case TasInput::TasState::Recording: - return "Recording"; + return tr("TAS state: Recording %1").arg(total_tas_frames); case TasInput::TasState::Stopped: - return "Stopped"; + return tr("TAS state: Idle %1/%2").arg(current_tas_frame).arg(total_tas_frames); default: - return "INVALID STATE"; + return tr("INVALID TAS STATE"); } } @@ -2923,12 +2923,7 @@ void GMainWindow::UpdateStatusBar() { } if (Settings::values.tas_enable) { - auto [tas_status, current_tas_frame, total_tas_frames] = - input_subsystem->GetTas()->GetStatus(); - tas_label->setText(tr("%1 TAS %2/%3") - .arg(tr(GetTasStateDescription(tas_status).c_str())) - .arg(current_tas_frame) - .arg(total_tas_frames)); + tas_label->setText(GetTasStateDescription()); } else { tas_label->clear(); } diff --git a/src/yuzu/main.h b/src/yuzu/main.h index 867a0003c..610b59ee5 100644 --- a/src/yuzu/main.h +++ b/src/yuzu/main.h @@ -301,6 +301,7 @@ private: void OpenURL(const QUrl& url); void LoadTranslation(); void OpenPerGameConfiguration(u64 title_id, const std::string& file_name); + QString GetTasStateDescription() const; Ui::MainWindow ui; -- cgit v1.2.3 From 5401cf6eb583092ed144d0f30fb6221a0ab25fed Mon Sep 17 00:00:00 2001 From: german77 Date: Fri, 9 Jul 2021 23:30:58 -0500 Subject: input_common/tas: new update method --- src/yuzu/bootmanager.cpp | 2 ++ src/yuzu/configuration/configure_input_player_widget.cpp | 4 ---- src/yuzu/debugger/controller.cpp | 10 +--------- src/yuzu/debugger/controller.h | 2 -- src/yuzu/main.cpp | 3 +-- 5 files changed, 4 insertions(+), 17 deletions(-) (limited to 'src/yuzu/debugger') diff --git a/src/yuzu/bootmanager.cpp b/src/yuzu/bootmanager.cpp index 2e0ade815..1519a46ed 100644 --- a/src/yuzu/bootmanager.cpp +++ b/src/yuzu/bootmanager.cpp @@ -36,6 +36,7 @@ #include "input_common/keyboard.h" #include "input_common/main.h" #include "input_common/mouse/mouse_input.h" +#include "input_common/tas/tas_input.h" #include "video_core/renderer_base.h" #include "video_core/video_core.h" #include "yuzu/bootmanager.h" @@ -312,6 +313,7 @@ GRenderWindow::~GRenderWindow() { } void GRenderWindow::OnFrameDisplayed() { + input_subsystem->GetTas()->UpdateThread(); if (!first_frame) { first_frame = true; emit FirstFrameDisplayed(); diff --git a/src/yuzu/configuration/configure_input_player_widget.cpp b/src/yuzu/configuration/configure_input_player_widget.cpp index 38c59263c..da328d904 100644 --- a/src/yuzu/configuration/configure_input_player_widget.cpp +++ b/src/yuzu/configuration/configure_input_player_widget.cpp @@ -237,10 +237,6 @@ void PlayerControlPreview::UpdateInput() { } } - if (controller_callback.update != nullptr) { - controller_callback.update(true); - } - if (mapping_active) { blink_counter = (blink_counter + 1) % 50; } diff --git a/src/yuzu/debugger/controller.cpp b/src/yuzu/debugger/controller.cpp index a745699bf..296000ed5 100644 --- a/src/yuzu/debugger/controller.cpp +++ b/src/yuzu/debugger/controller.cpp @@ -41,8 +41,7 @@ void ControllerDialog::refreshConfiguration() { constexpr std::size_t player = 0; widget->SetPlayerInputRaw(player, players[player].buttons, players[player].analogs); widget->SetControllerType(players[player].controller_type); - ControllerCallback callback{[this](ControllerInput input) { InputController(input); }, - [this](bool update) { UpdateController(update); }}; + ControllerCallback callback{[this](ControllerInput input) { InputController(input); }}; widget->SetCallBack(callback); widget->repaint(); widget->SetConnectedStatus(players[player].connected); @@ -84,10 +83,3 @@ void ControllerDialog::InputController(ControllerInput input) { } input_subsystem->GetTas()->RecordInput(buttons, input.axis_values); } - -void ControllerDialog::UpdateController(bool update) { - if (!update) { - return; - } - input_subsystem->GetTas()->UpdateThread(); -} diff --git a/src/yuzu/debugger/controller.h b/src/yuzu/debugger/controller.h index 448d24b67..7742db58b 100644 --- a/src/yuzu/debugger/controller.h +++ b/src/yuzu/debugger/controller.h @@ -25,7 +25,6 @@ struct ControllerInput { struct ControllerCallback { std::function input; - std::function update; }; class ControllerDialog : public QWidget { @@ -45,7 +44,6 @@ protected: private: void InputController(ControllerInput input); - void UpdateController(bool update); QAction* toggle_view_action = nullptr; QFileSystemWatcher* watcher = nullptr; PlayerControlPreview* widget; diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index 7d12fcca7..ea77caad5 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -31,8 +31,6 @@ #include "core/hle/service/am/applet_oe.h" #include "core/hle/service/am/applets/applets.h" -#include "input_common/tas/tas_input.h" - // These are wrappers to avoid the calls to CreateDirectory and CreateFile because of the Windows // defines. static FileSys::VirtualDir VfsFilesystemCreateDirectoryWrapper( @@ -105,6 +103,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual #include "core/perf_stats.h" #include "core/telemetry_session.h" #include "input_common/main.h" +#include "input_common/tas/tas_input.h" #include "util/overlay_dialog.h" #include "video_core/gpu.h" #include "video_core/renderer_base.h" -- cgit v1.2.3 From 75d8ec1e9f474ce6c2bfc0b8ebe574ca44f9f3d8 Mon Sep 17 00:00:00 2001 From: german77 Date: Sun, 25 Jul 2021 20:52:19 -0500 Subject: UI: Relocate tas menu and add brief description --- src/input_common/main.cpp | 2 +- src/input_common/tas/tas_input.cpp | 88 ++++++++++++++++---------- src/input_common/tas/tas_input.h | 48 +++++++------- src/yuzu/configuration/configure_tas.cpp | 2 +- src/yuzu/configuration/configure_tas.ui | 47 +++++++++++++- src/yuzu/configuration/configure_vibration.cpp | 2 +- src/yuzu/debugger/controller.cpp | 2 +- src/yuzu/main.cpp | 21 ++++-- src/yuzu/main.h | 2 +- src/yuzu/main.ui | 2 +- 10 files changed, 148 insertions(+), 68 deletions(-) (limited to 'src/yuzu/debugger') diff --git a/src/input_common/main.cpp b/src/input_common/main.cpp index 3b9906b53..18d7d8817 100644 --- a/src/input_common/main.cpp +++ b/src/input_common/main.cpp @@ -117,7 +117,7 @@ struct InputSubsystem::Impl { Common::ParamPackage{{"display", "Keyboard/Mouse"}, {"class", "keyboard"}}, }; if (Settings::values.tas_enable) { - devices.push_back( + devices.emplace_back( Common::ParamPackage{{"display", "TAS Controller"}, {"class", "tas"}}); } #ifdef HAVE_SDL2 diff --git a/src/input_common/tas/tas_input.cpp b/src/input_common/tas/tas_input.cpp index 877d35088..1598092b6 100644 --- a/src/input_common/tas/tas_input.cpp +++ b/src/input_common/tas/tas_input.cpp @@ -40,12 +40,15 @@ constexpr std::array, 20> text_to_tas_but Tas::Tas() { if (!Settings::values.tas_enable) { + needs_reset = true; return; } LoadTasFiles(); } -Tas::~Tas() = default; +Tas::~Tas() { + Stop(); +}; void Tas::LoadTasFiles() { script_length = 0; @@ -184,6 +187,9 @@ std::string Tas::ButtonsToString(u32 button) const { void Tas::UpdateThread() { if (!Settings::values.tas_enable) { + if (is_running) { + Stop(); + } return; } @@ -196,34 +202,35 @@ void Tas::UpdateThread() { LoadTasFiles(); LOG_DEBUG(Input, "tas_reset done"); } - if (is_running) { - if (current_command < script_length) { - LOG_DEBUG(Input, "Playing TAS {}/{}", current_command, script_length); - size_t frame = current_command++; - for (size_t i = 0; i < commands.size(); i++) { - if (frame < commands[i].size()) { - TASCommand command = commands[i][frame]; - tas_data[i].buttons = command.buttons; - auto [l_axis_x, l_axis_y] = command.l_axis; - tas_data[i].axis[0] = l_axis_x; - tas_data[i].axis[1] = l_axis_y; - auto [r_axis_x, r_axis_y] = command.r_axis; - tas_data[i].axis[2] = r_axis_x; - tas_data[i].axis[3] = r_axis_y; - } else { - tas_data[i] = {}; - } - } - } else { - is_running = Settings::values.tas_loop.GetValue(); - current_command = 0; - tas_data.fill({}); - if (!is_running) { - SwapToStoredController(); + + if (!is_running) { + tas_data.fill({}); + return; + } + if (current_command < script_length) { + LOG_DEBUG(Input, "Playing TAS {}/{}", current_command, script_length); + size_t frame = current_command++; + for (size_t i = 0; i < commands.size(); i++) { + if (frame < commands[i].size()) { + TASCommand command = commands[i][frame]; + tas_data[i].buttons = command.buttons; + auto [l_axis_x, l_axis_y] = command.l_axis; + tas_data[i].axis[0] = l_axis_x; + tas_data[i].axis[1] = l_axis_y; + auto [r_axis_x, r_axis_y] = command.r_axis; + tas_data[i].axis[2] = r_axis_x; + tas_data[i].axis[3] = r_axis_y; + } else { + tas_data[i] = {}; } } } else { + is_running = Settings::values.tas_loop.GetValue(); + current_command = 0; tas_data.fill({}); + if (!is_running) { + SwapToStoredController(); + } } LOG_DEBUG(Input, "TAS inputs: {}", DebugInputs(tas_data)); } @@ -237,8 +244,8 @@ TasAnalog Tas::ReadCommandAxis(const std::string& line) const { seglist.push_back(segment); } - const float x = std::stof(seglist.at(0)) / 32767.f; - const float y = std::stof(seglist.at(1)) / 32767.f; + const float x = std::stof(seglist.at(0)) / 32767.0f; + const float y = std::stof(seglist.at(1)) / 32767.0f; return {x, y}; } @@ -293,14 +300,22 @@ std::string Tas::WriteCommandButtons(u32 data) const { } void Tas::StartStop() { - is_running = !is_running; + if (!Settings::values.tas_enable) { + return; + } if (is_running) { - SwapToTasController(); + Stop(); } else { - SwapToStoredController(); + is_running = true; + SwapToTasController(); } } +void Tas::Stop() { + is_running = false; + SwapToStoredController(); +} + void Tas::SwapToTasController() { if (!Settings::values.tas_swap_controllers) { return; @@ -315,7 +330,8 @@ void Tas::SwapToTasController() { continue; } - auto tas_param = Common::ParamPackage{{"pad", static_cast(index)}}; + Common::ParamPackage tas_param; + tas_param.Set("pad", static_cast(index)); auto button_mapping = GetButtonMappingForDevice(tas_param); auto analog_mapping = GetAnalogMappingForDevice(tas_param); auto& buttons = player.buttons; @@ -328,25 +344,33 @@ void Tas::SwapToTasController() { analogs[i] = analog_mapping[static_cast(i)].Serialize(); } } + is_old_input_saved = true; Settings::values.is_device_reload_pending.store(true); } void Tas::SwapToStoredController() { - if (!Settings::values.tas_swap_controllers) { + if (!is_old_input_saved) { return; } auto& players = Settings::values.players.GetValue(); for (std::size_t index = 0; index < players.size(); index++) { players[index] = player_mappings[index]; } + is_old_input_saved = false; Settings::values.is_device_reload_pending.store(true); } void Tas::Reset() { + if (!Settings::values.tas_enable) { + return; + } needs_reset = true; } bool Tas::Record() { + if (!Settings::values.tas_enable) { + return true; + } is_recording = !is_recording; return is_recording; } diff --git a/src/input_common/tas/tas_input.h b/src/input_common/tas/tas_input.h index 52d000db4..3e2db8f00 100644 --- a/src/input_common/tas/tas_input.h +++ b/src/input_common/tas/tas_input.h @@ -13,8 +13,8 @@ /* To play back TAS scripts on Yuzu, select the folder with scripts in the configuration menu below -Emulation -> Configure TAS. The file itself has normal text format and has to be called -script0-1.txt for controller 1, script0-2.txt for controller 2 and so forth (with max. 8 players). +Tools -> Configure TAS. The file itself has normal text format and has to be called script0-1.txt +for controller 1, script0-2.txt for controller 2 and so forth (with max. 8 players). A script file has the same format as TAS-nx uses, so final files will look like this: @@ -56,26 +56,26 @@ enum class TasState { }; enum class TasButton : u32 { - BUTTON_A = 0x000001, - BUTTON_B = 0x000002, - BUTTON_X = 0x000004, - BUTTON_Y = 0x000008, - STICK_L = 0x000010, - STICK_R = 0x000020, - TRIGGER_L = 0x000040, - TRIGGER_R = 0x000080, - TRIGGER_ZL = 0x000100, - TRIGGER_ZR = 0x000200, - BUTTON_PLUS = 0x000400, - BUTTON_MINUS = 0x000800, - BUTTON_LEFT = 0x001000, - BUTTON_UP = 0x002000, - BUTTON_RIGHT = 0x004000, - BUTTON_DOWN = 0x008000, - BUTTON_SL = 0x010000, - BUTTON_SR = 0x020000, - BUTTON_HOME = 0x040000, - BUTTON_CAPTURE = 0x080000, + BUTTON_A = 1U << 0, + BUTTON_B = 1U << 1, + BUTTON_X = 1U << 2, + BUTTON_Y = 1U << 3, + STICK_L = 1U << 4, + STICK_R = 1U << 5, + TRIGGER_L = 1U << 6, + TRIGGER_R = 1U << 7, + TRIGGER_ZL = 1U << 8, + TRIGGER_ZR = 1U << 9, + BUTTON_PLUS = 1U << 10, + BUTTON_MINUS = 1U << 11, + BUTTON_LEFT = 1U << 12, + BUTTON_UP = 1U << 13, + BUTTON_RIGHT = 1U << 14, + BUTTON_DOWN = 1U << 15, + BUTTON_SL = 1U << 16, + BUTTON_SR = 1U << 17, + BUTTON_HOME = 1U << 18, + BUTTON_CAPTURE = 1U << 19, }; enum class TasAxes : u8 { @@ -105,6 +105,9 @@ public: // Sets the flag to start or stop the TAS command excecution and swaps controllers profiles void StartStop(); + // Stop the TAS and reverts any controller profile + void Stop(); + // Sets the flag to reload the file and start from the begining in the next update void Reset(); @@ -219,6 +222,7 @@ private: size_t script_length{0}; std::array tas_data; + bool is_old_input_saved{false}; bool is_recording{false}; bool is_running{false}; bool needs_reset{false}; diff --git a/src/yuzu/configuration/configure_tas.cpp b/src/yuzu/configuration/configure_tas.cpp index 00d6c1ba5..b666b175a 100644 --- a/src/yuzu/configuration/configure_tas.cpp +++ b/src/yuzu/configuration/configure_tas.cpp @@ -55,7 +55,7 @@ void ConfigureTasDialog::SetDirectory(DirectoryTarget target, QLineEdit* edit) { QString str = QFileDialog::getExistingDirectory(this, caption, edit->text()); - if (str.isNull() || str.isEmpty()) { + if (str.isEmpty()) { return; } diff --git a/src/yuzu/configuration/configure_tas.ui b/src/yuzu/configuration/configure_tas.ui index 445904d8f..8a3ecb834 100644 --- a/src/yuzu/configuration/configure_tas.ui +++ b/src/yuzu/configuration/configure_tas.ui @@ -19,7 +19,50 @@ - TAS Settings + TAS + + + + + + Reads controller input from scripts in the same format as TAS-nx scripts. For a more detailed explanation please consult the FAQ on the yuzu website. + + + true + + + + + + + To check which hotkeys control the playback/recording, please refer to the Hotkey settings (General -> Hotkeys). + + + true + + + + + + + WARNING: This is an experimental feature. It will not play back scripts frame perfectly with the current, imperfect syncing method. + + + true + + + + + + + + + + + + + + Settings @@ -63,7 +106,7 @@ - TAS Directories + Script Directory diff --git a/src/yuzu/configuration/configure_vibration.cpp b/src/yuzu/configuration/configure_vibration.cpp index 9d92c4949..46a0f3025 100644 --- a/src/yuzu/configuration/configure_vibration.cpp +++ b/src/yuzu/configuration/configure_vibration.cpp @@ -99,7 +99,7 @@ void ConfigureVibration::SetVibrationDevices(std::size_t player_index) { const auto guid = param.Get("guid", ""); const auto port = param.Get("port", ""); - if (engine.empty() || engine == "keyboard" || engine == "mouse") { + if (engine.empty() || engine == "keyboard" || engine == "mouse" || engine == "tas") { continue; } diff --git a/src/yuzu/debugger/controller.cpp b/src/yuzu/debugger/controller.cpp index 296000ed5..5a844409b 100644 --- a/src/yuzu/debugger/controller.cpp +++ b/src/yuzu/debugger/controller.cpp @@ -78,7 +78,7 @@ void ControllerDialog::InputController(ControllerInput input) { u32 buttons = 0; int index = 0; for (bool btn : input.button_values) { - buttons += (btn ? 1 : 0) << index; + buttons |= (btn ? 1U : 0U) << index; index++; } input_subsystem->GetTas()->RecordInput(buttons, input.axis_values); diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index ea77caad5..3c2824362 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -1022,18 +1022,25 @@ void GMainWindow::InitializeHotkeys() { } }); connect(hotkey_registry.GetHotkey(main_window, QStringLiteral("TAS Start/Stop"), this), - &QShortcut::activated, this, [&] { input_subsystem->GetTas()->StartStop(); }); + &QShortcut::activated, this, [&] { + if (!emulation_running) { + return; + } + input_subsystem->GetTas()->StartStop(); + }); connect(hotkey_registry.GetHotkey(main_window, QStringLiteral("TAS Reset"), this), &QShortcut::activated, this, [&] { input_subsystem->GetTas()->Reset(); }); connect(hotkey_registry.GetHotkey(main_window, QStringLiteral("TAS Record"), this), &QShortcut::activated, this, [&] { + if (!emulation_running) { + return; + } bool is_recording = input_subsystem->GetTas()->Record(); if (!is_recording) { - QMessageBox::StandardButton reply; - reply = QMessageBox::question(this, tr("TAS Recording"), - tr("Overwrite file of player 1?"), - QMessageBox::Yes | QMessageBox::No); - input_subsystem->GetTas()->SaveRecording(reply == QMessageBox::Yes); + const auto res = QMessageBox::question(this, tr("TAS Recording"), + tr("Overwrite file of player 1?"), + QMessageBox::Yes | QMessageBox::No); + input_subsystem->GetTas()->SaveRecording(res == QMessageBox::Yes); } }); } @@ -1487,6 +1494,8 @@ void GMainWindow::ShutdownGame() { game_list->show(); } game_list->SetFilterFocus(); + tas_label->clear(); + input_subsystem->GetTas()->Stop(); render_window->removeEventFilter(render_window); render_window->setAttribute(Qt::WA_Hover, false); diff --git a/src/yuzu/main.h b/src/yuzu/main.h index 610b59ee5..36eed6103 100644 --- a/src/yuzu/main.h +++ b/src/yuzu/main.h @@ -320,7 +320,7 @@ private: QLabel* emu_speed_label = nullptr; QLabel* game_fps_label = nullptr; QLabel* emu_frametime_label = nullptr; - QLabel* TASlabel; + QLabel* tas_label = nullptr; QPushButton* gpu_accuracy_button = nullptr; QPushButton* renderer_status_button = nullptr; QPushButton* dock_status_button = nullptr; diff --git a/src/yuzu/main.ui b/src/yuzu/main.ui index 31c1a20f3..653c010d8 100644 --- a/src/yuzu/main.ui +++ b/src/yuzu/main.ui @@ -72,7 +72,6 @@ - @@ -101,6 +100,7 @@ + -- cgit v1.2.3