From 4297d2fea2228ff4afe2a7c244fb8b3f1a97491a Mon Sep 17 00:00:00 2001 From: MonsterDruide1 <5958456@gmail.com> Date: Fri, 18 Jun 2021 16:32:46 +0200 Subject: core: Hacky TAS syncing & load pausing To keep the TAS inputs synced to the game speed even through lag spikes and loading zones, deeper access is required. First, the `TAS::UpdateThread` has to be executed exactly once per frame. This is done by connecting it to the service method the game calls to pass parameters to the GPU: `Service::VI::QueueBuffer`. Second, the loading time of new subareas and/or kingdoms (SMO) can vary. To counteract that, the `CPU_BOOST_MODE` can be detected: In the `APM`-interface, the call to enabling/disabling the boost mode can be caught and forwarded to the TASing system, which can pause the script execution if neccessary and enabled in the settings. --- src/input_common/tas/tas_input.cpp | 142 +++++++++++++++++++++++-------------- src/input_common/tas/tas_input.h | 58 ++++++--------- 2 files changed, 107 insertions(+), 93 deletions(-) (limited to 'src/input_common/tas') diff --git a/src/input_common/tas/tas_input.cpp b/src/input_common/tas/tas_input.cpp index 343641945..7320a7004 100644 --- a/src/input_common/tas/tas_input.cpp +++ b/src/input_common/tas/tas_input.cpp @@ -19,6 +19,29 @@ namespace TasInput { +constexpr std::array, 20> text_to_tas_button = { + std::pair{"KEY_A", TasButton::BUTTON_A}, + {"KEY_B", TasButton::BUTTON_B}, + {"KEY_X", TasButton::BUTTON_X}, + {"KEY_Y", TasButton::BUTTON_Y}, + {"KEY_LSTICK", TasButton::STICK_L}, + {"KEY_RSTICK", TasButton::STICK_R}, + {"KEY_L", TasButton::TRIGGER_L}, + {"KEY_R", TasButton::TRIGGER_R}, + {"KEY_PLUS", TasButton::BUTTON_PLUS}, + {"KEY_MINUS", TasButton::BUTTON_MINUS}, + {"KEY_DLEFT", TasButton::BUTTON_LEFT}, + {"KEY_DUP", TasButton::BUTTON_UP}, + {"KEY_DRIGHT", TasButton::BUTTON_RIGHT}, + {"KEY_DDOWN", TasButton::BUTTON_DOWN}, + {"KEY_SL", TasButton::BUTTON_SL}, + {"KEY_SR", TasButton::BUTTON_SR}, + {"KEY_CAPTURE", TasButton::BUTTON_CAPTURE}, + {"KEY_HOME", TasButton::BUTTON_HOME}, + {"KEY_ZL", TasButton::TRIGGER_ZL}, + {"KEY_ZR", TasButton::TRIGGER_ZR}, +}; + Tas::Tas() { LoadTasFiles(); } @@ -31,29 +54,31 @@ void Tas::RefreshTasFile() { refresh_tas_fle = true; } void Tas::LoadTasFiles() { - scriptLength = 0; - for (int i = 0; i < PLAYER_NUMBER; i++) { + script_length = 0; + for (size_t i = 0; i < PLAYER_NUMBER; i++) { LoadTasFile(i); - if (newCommands[i].size() > scriptLength) - scriptLength = newCommands[i].size(); + if (commands[i].size() > script_length) { + script_length = commands[i].size(); + } } } -void Tas::LoadTasFile(int playerIndex) { +void Tas::LoadTasFile(size_t player_index) { LOG_DEBUG(Input, "LoadTasFile()"); - if (!newCommands[playerIndex].empty()) { - newCommands[playerIndex].clear(); + 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(playerIndex + 1) + ".txt", + 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}}; while (std::getline(command_line, line, '\n')) { - if (line.empty()) + if (line.empty()) { continue; + } LOG_DEBUG(Input, "Loading line: {}", line); std::smatch m; @@ -65,11 +90,12 @@ void Tas::LoadTasFile(int playerIndex) { seglist.push_back(segment); } - if (seglist.size() < 4) + if (seglist.size() < 4) { continue; + } while (frameNo < std::stoi(seglist.at(0))) { - newCommands[playerIndex].push_back(empty); + commands[player_index].push_back(empty); frameNo++; } @@ -78,7 +104,7 @@ void Tas::LoadTasFile(int playerIndex) { .l_axis = ReadCommandAxis(seglist.at(2)), .r_axis = ReadCommandAxis(seglist.at(3)), }; - newCommands[playerIndex].push_back(command); + commands[player_index].push_back(command); frameNo++; } LOG_INFO(Input, "TAS file loaded! {} frames", frameNo); @@ -87,84 +113,89 @@ void Tas::LoadTasFile(int playerIndex) { void Tas::WriteTasFile() { LOG_DEBUG(Input, "WriteTasFile()"); std::string output_text = ""; - for (int frame = 0; frame < (signed)recordCommands.size(); frame++) { - if (!output_text.empty()) + for (int frame = 0; frame < (signed)record_commands.size(); frame++) { + if (!output_text.empty()) { output_text += "\n"; - TASCommand line = recordCommands.at(frame); + } + TASCommand line = record_commands.at(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", Common::FS::FileType::TextFile, output_text); - if (bytesWritten == output_text.size()) + if (bytesWritten == output_text.size()) { LOG_INFO(Input, "TAS file written to file!"); - else + } + else { LOG_ERROR(Input, "Writing the TAS-file has failed! {} / {} bytes written", bytesWritten, output_text.size()); + } } -void Tas::RecordInput(u32 buttons, std::array, 2> axes) { - lastInput = {buttons, flipY(axes[0]), flipY(axes[1])}; -} - -std::pair Tas::flipY(std::pair old) const { +static std::pair FlipY(std::pair old) { auto [x, y] = old; return {x, -y}; } -std::string Tas::GetStatusDescription() { +void Tas::RecordInput(u32 buttons, const std::array, 2>& axes) { + last_input = {buttons, FlipY(axes[0]), FlipY(axes[1])}; +} + +std::tuple Tas::GetStatus() { + TasState state; if (Settings::values.tas_record) { - return "Recording TAS: " + std::to_string(recordCommands.size()); - } - if (Settings::values.tas_enable) { - return "Playing TAS: " + std::to_string(current_command) + "/" + - std::to_string(scriptLength); + return {TasState::RECORDING, record_commands.size(), record_commands.size()}; + } else if (Settings::values.tas_enable) { + state = TasState::RUNNING; + } else { + state = TasState::STOPPED; } - return "TAS not running: " + std::to_string(current_command) + "/" + - std::to_string(scriptLength); + + return {state, current_command, script_length}; } -std::string debugButtons(u32 buttons) { - return "{ " + TasInput::Tas::buttonsToString(buttons) + " }"; +static std::string DebugButtons(u32 buttons) { + return "{ " + TasInput::Tas::ButtonsToString(buttons) + " }"; } -std::string debugJoystick(float x, float y) { +static std::string DebugJoystick(float x, float y) { return "[ " + std::to_string(x) + "," + std::to_string(y) + " ]"; } -std::string debugInput(TasData data) { - return "{ " + debugButtons(data.buttons) + " , " + debugJoystick(data.axis[0], data.axis[1]) + - " , " + debugJoystick(data.axis[2], data.axis[3]) + " }"; +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]) + " }"; } -std::string debugInputs(std::array arr) { +static std::string DebugInputs(const std::array& arr) { std::string returns = "[ "; for (size_t i = 0; i < arr.size(); i++) { - returns += debugInput(arr[i]); - if (i != arr.size() - 1) + returns += DebugInput(arr[i]); + if (i != arr.size() - 1) { returns += " , "; + } } return returns + "]"; } void Tas::UpdateThread() { if (update_thread_running) { - if (Settings::values.pauseTasOnLoad && Settings::values.cpuBoosted) { - for (int i = 0; i < PLAYER_NUMBER; i++) { + 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 (Settings::values.tas_record) { - recordCommands.push_back(lastInput); + record_commands.push_back(last_input); } - if (!Settings::values.tas_record && !recordCommands.empty()) { + if (!Settings::values.tas_record && !record_commands.empty()) { WriteTasFile(); Settings::values.tas_reset = true; refresh_tas_fle = true; - recordCommands.clear(); + record_commands.clear(); } if (Settings::values.tas_reset) { current_command = 0; @@ -177,12 +208,12 @@ void Tas::UpdateThread() { LOG_DEBUG(Input, "tas_reset done"); } if (Settings::values.tas_enable) { - if ((signed)current_command < scriptLength) { - LOG_INFO(Input, "Playing TAS {}/{}", current_command, scriptLength); + if ((signed)current_command < script_length) { + LOG_INFO(Input, "Playing TAS {}/{}", current_command, script_length); size_t frame = current_command++; - for (int i = 0; i < PLAYER_NUMBER; i++) { - if (frame < newCommands[i].size()) { - TASCommand command = newCommands[i][frame]; + 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; @@ -198,22 +229,22 @@ void Tas::UpdateThread() { } else { Settings::values.tas_enable = false; current_command = 0; - for (int i = 0; i < PLAYER_NUMBER; i++) { + for (size_t i = 0; i < PLAYER_NUMBER; i++) { tas_data[i].buttons = 0; tas_data[i].axis = {}; } } } else { - for (int i = 0; i < PLAYER_NUMBER; i++) { + for (size_t i = 0; i < PLAYER_NUMBER; i++) { tas_data[i].buttons = 0; tas_data[i].axis = {}; } } } - LOG_DEBUG(Input, "TAS inputs: {}", debugInputs(tas_data)); + LOG_DEBUG(Input, "TAS inputs: {}", DebugInputs(tas_data)); } -TasAnalog Tas::ReadCommandAxis(const std::string line) const { +TasAnalog Tas::ReadCommandAxis(const std::string& line) const { std::stringstream linestream(line); std::string segment; std::vector seglist; @@ -228,7 +259,7 @@ TasAnalog Tas::ReadCommandAxis(const std::string line) const { return {x, y}; } -u32 Tas::ReadCommandButtons(const std::string data) const { +u32 Tas::ReadCommandButtons(const std::string& data) const { std::stringstream button_text(data); std::string line; u32 buttons = 0; @@ -262,8 +293,9 @@ std::string Tas::WriteCommandButtons(u32 data) const { if ((data & 1) == 1) { for (auto [text, tas_button] : text_to_tas_button) { if (tas_button == static_cast(1 << index)) { - if (line.size() > 0) + if (line.size() > 0) { line += ";"; + } line += text; break; } diff --git a/src/input_common/tas/tas_input.h b/src/input_common/tas/tas_input.h index 0a152a04f..8ee70bcaf 100644 --- a/src/input_common/tas/tas_input.h +++ b/src/input_common/tas/tas_input.h @@ -12,11 +12,17 @@ #include "core/frontend/input.h" #include "input_common/main.h" -#define PLAYER_NUMBER 8 - namespace TasInput { -using TasAnalog = std::tuple; +constexpr int PLAYER_NUMBER = 8; + +using TasAnalog = std::pair; + +enum class TasState { + RUNNING, + RECORDING, + STOPPED, +}; enum class TasButton : u32 { BUTTON_A = 0x000001, @@ -41,29 +47,6 @@ enum class TasButton : u32 { BUTTON_CAPTURE = 0x080000, }; -static const std::array, 20> text_to_tas_button = { - std::pair{"KEY_A", TasButton::BUTTON_A}, - {"KEY_B", TasButton::BUTTON_B}, - {"KEY_X", TasButton::BUTTON_X}, - {"KEY_Y", TasButton::BUTTON_Y}, - {"KEY_LSTICK", TasButton::STICK_L}, - {"KEY_RSTICK", TasButton::STICK_R}, - {"KEY_L", TasButton::TRIGGER_L}, - {"KEY_R", TasButton::TRIGGER_R}, - {"KEY_PLUS", TasButton::BUTTON_PLUS}, - {"KEY_MINUS", TasButton::BUTTON_MINUS}, - {"KEY_DLEFT", TasButton::BUTTON_LEFT}, - {"KEY_DUP", TasButton::BUTTON_UP}, - {"KEY_DRIGHT", TasButton::BUTTON_RIGHT}, - {"KEY_DDOWN", TasButton::BUTTON_DOWN}, - {"KEY_SL", TasButton::BUTTON_SL}, - {"KEY_SR", TasButton::BUTTON_SR}, - {"KEY_CAPTURE", TasButton::BUTTON_CAPTURE}, - {"KEY_HOME", TasButton::BUTTON_HOME}, - {"KEY_ZL", TasButton::TRIGGER_ZL}, - {"KEY_ZR", TasButton::TRIGGER_ZR}, -}; - enum class TasAxes : u8 { StickX, StickY, @@ -82,7 +65,7 @@ public: Tas(); ~Tas(); - static std::string buttonsToString(u32 button) { + static std::string ButtonsToString(u32 button) { std::string returns; if ((button & static_cast(TasInput::TasButton::BUTTON_A)) != 0) returns += ", A"; @@ -124,14 +107,14 @@ public: returns += ", HOME"; if ((button & static_cast(TasInput::TasButton::BUTTON_CAPTURE)) != 0) returns += ", CAPTURE"; - return returns.length() != 0 ? returns.substr(2) : ""; + return returns.empty() ? "" : returns.substr(2); } void RefreshTasFile(); void LoadTasFiles(); - void RecordInput(u32 buttons, std::array, 2> axes); + void RecordInput(u32 buttons, const std::array, 2>& axes); void UpdateThread(); - std::string GetStatusDescription(); + std::tuple GetStatus(); InputCommon::ButtonMapping GetButtonMappingForDevice(const Common::ParamPackage& params) const; InputCommon::AnalogMapping GetAnalogMappingForDevice(const Common::ParamPackage& params) const; @@ -143,21 +126,20 @@ private: TasAnalog l_axis{}; TasAnalog r_axis{}; }; - void LoadTasFile(int playerIndex); + void LoadTasFile(size_t player_index); void WriteTasFile(); - TasAnalog ReadCommandAxis(const std::string line) const; - u32 ReadCommandButtons(const std::string line) const; + TasAnalog ReadCommandAxis(const std::string& line) const; + u32 ReadCommandButtons(const std::string& line) const; std::string WriteCommandButtons(u32 data) const; std::string WriteCommandAxis(TasAnalog data) const; - std::pair flipY(std::pair old) const; - size_t scriptLength{0}; + size_t script_length{0}; std::array tas_data; bool update_thread_running{true}; bool refresh_tas_fle{false}; - std::array, PLAYER_NUMBER> newCommands{}; - std::vector recordCommands{}; + std::array, PLAYER_NUMBER> commands{}; + std::vector record_commands{}; std::size_t current_command{0}; - TASCommand lastInput{}; // only used for recording + TASCommand last_input{}; // only used for recording }; } // namespace TasInput -- cgit v1.2.3