From 93e3e691f9a3a9913b56ebe71b4bd34fd21ce5c9 Mon Sep 17 00:00:00 2001 From: Filoppi Date: Fri, 12 Mar 2021 00:01:18 +0200 Subject: [PATCH] Expose Control Expression variables to mappings UI -add a way to reset their value (from the mappings UI) -fix "memory leak" where they would never be cleaned, one would be created every time you wrote a character after a "$" -fix ability to create variables with an empty string by just writing "$" (+added error for it) -Add $ operator to the UI operators list, to expose this functionality even more --- .../DolphinQt/Config/Mapping/IOWindow.cpp | 44 ++++++++++++++++++- .../Core/DolphinQt/Config/Mapping/IOWindow.h | 1 + .../ControlReference/ExpressionParser.cpp | 42 +++++++++++++++--- .../ControlReference/ExpressionParser.h | 7 ++- .../ControllerEmu/ControllerEmu.cpp | 22 ++++++++++ .../InputCommon/ControllerEmu/ControllerEmu.h | 8 +++- 6 files changed, 112 insertions(+), 12 deletions(-) diff --git a/Source/Core/DolphinQt/Config/Mapping/IOWindow.cpp b/Source/Core/DolphinQt/Config/Mapping/IOWindow.cpp index dc6f3404ea..27c17ce5fb 100644 --- a/Source/Core/DolphinQt/Config/Mapping/IOWindow.cpp +++ b/Source/Core/DolphinQt/Config/Mapping/IOWindow.cpp @@ -275,6 +275,7 @@ void IOWindow::CreateMainLayout() m_operators_combo->addItem(tr("^ Xor")); } m_operators_combo->addItem(tr("| Or")); + m_operators_combo->addItem(tr("$ User Variable")); if (m_type == Type::Input) { m_operators_combo->addItem(tr(", Comma")); @@ -305,6 +306,15 @@ void IOWindow::CreateMainLayout() m_functions_combo->addItem(QStringLiteral("max")); m_functions_combo->addItem(QStringLiteral("clamp")); + m_variables_combo = new QComboBoxWithMouseWheelDisabled(this); + m_variables_combo->addItem(tr("User Variables")); + m_variables_combo->setToolTip( + tr("User defined variables usable in the control expression.\nYou can use them to save or " + "retrieve values between\ninputs and outputs of the same parent controller.")); + m_variables_combo->insertSeparator(m_variables_combo->count()); + m_variables_combo->addItem(tr("Reset Values")); + m_variables_combo->insertSeparator(m_variables_combo->count()); + // Devices m_main_layout->addWidget(m_devices_combo); @@ -366,6 +376,8 @@ void IOWindow::CreateMainLayout() button_vbox->addWidget(m_test_button); } + button_vbox->addWidget(m_variables_combo); + button_vbox->addWidget(m_operators_combo); if (m_type == Type::Input) @@ -425,8 +437,26 @@ void IOWindow::ConnectWidgets() connect(m_expression_text, &QPlainTextEdit::textChanged, [this] { UpdateExpression(m_expression_text->toPlainText().toStdString()); }); + connect(m_variables_combo, qOverload(&QComboBox::activated), [this](int index) { + if (index == 0) + return; + + // Reset button. 1 and 3 are separators. + if (index == 2) + { + const auto lock = ControllerEmu::EmulatedController::GetStateLock(); + m_controller->ResetExpressionVariables(); + } + else + { + m_expression_text->insertPlainText(QLatin1Char('$') + m_variables_combo->currentText()); + } + + m_variables_combo->setCurrentIndex(0); + }); + connect(m_operators_combo, qOverload(&QComboBox::activated), [this](int index) { - if (0 == index) + if (index == 0) return; m_expression_text->insertPlainText(m_operators_combo->currentText().left(1)); @@ -435,7 +465,7 @@ void IOWindow::ConnectWidgets() }); connect(m_functions_combo, qOverload(&QComboBox::activated), [this](int index) { - if (0 == index) + if (index == 0) return; m_expression_text->insertPlainText(m_functions_combo->currentText() + QStringLiteral("()")); @@ -564,6 +594,16 @@ void IOWindow::UpdateExpression(std::string new_expression, UpdateMode mode) const auto status = m_reference->GetParseStatus(); m_controller->UpdateSingleControlReference(g_controller_interface, m_reference); + // This is the only place where we need to update the user variables. Keep the first 4 items. + while (m_variables_combo->count() > 4) + { + m_variables_combo->removeItem(m_variables_combo->count() - 1); + } + for (const auto& expression : m_controller->GetExpressionVariables()) + { + m_variables_combo->addItem(QString::fromStdString(expression.first)); + } + if (error) { m_parse_text->SetShouldPaintStateIndicator(false); diff --git a/Source/Core/DolphinQt/Config/Mapping/IOWindow.h b/Source/Core/DolphinQt/Config/Mapping/IOWindow.h index 8969db2a56..320ebefde4 100644 --- a/Source/Core/DolphinQt/Config/Mapping/IOWindow.h +++ b/Source/Core/DolphinQt/Config/Mapping/IOWindow.h @@ -111,6 +111,7 @@ private: // Shared actions QPushButton* m_select_button; QComboBox* m_operators_combo; + QComboBox* m_variables_combo; // Input actions QPushButton* m_detect_button; diff --git a/Source/Core/InputCommon/ControlReference/ExpressionParser.cpp b/Source/Core/InputCommon/ControlReference/ExpressionParser.cpp index 1a7ca5a8ef..6948ff07ac 100644 --- a/Source/Core/InputCommon/ControlReference/ExpressionParser.cpp +++ b/Source/Core/InputCommon/ControlReference/ExpressionParser.cpp @@ -461,20 +461,24 @@ class VariableExpression : public Expression public: VariableExpression(std::string name) : m_name(name) {} - ControlState GetValue() const override { return *m_value_ptr; } + ControlState GetValue() const override { return m_variable_ptr ? *m_variable_ptr : 0; } - void SetValue(ControlState value) override { *m_value_ptr = value; } + void SetValue(ControlState value) override + { + if (m_variable_ptr) + *m_variable_ptr = value; + } int CountNumControls() const override { return 1; } void UpdateReferences(ControlEnvironment& env) override { - m_value_ptr = env.GetVariablePtr(m_name); + m_variable_ptr = env.GetVariablePtr(m_name); } protected: const std::string m_name; - ControlState* m_value_ptr{}; + std::shared_ptr m_variable_ptr; }; class HotkeyExpression : public Expression @@ -621,9 +625,30 @@ Device::Output* ControlEnvironment::FindOutput(ControlQualifier qualifier) const return device->FindOutput(qualifier.control_name); } -ControlState* ControlEnvironment::GetVariablePtr(const std::string& name) +std::shared_ptr ControlEnvironment::GetVariablePtr(const std::string& name) { - return &m_variables[name]; + // Do not accept an empty string as key, even if the expression parser already prevents this case. + if (name.empty()) + return nullptr; + std::shared_ptr& variable = m_variables[name]; + // If new, make a shared ptr + if (!variable) + { + variable = std::make_shared(); + } + return variable; +} + +void ControlEnvironment::CleanUnusedVariables() +{ + for (auto it = m_variables.begin(); it != m_variables.end();) + { + // Don't count ourselves as reference + if (it->second.use_count() <= 1) + m_variables.erase(it++); + else + ++it; + } } ParseResult ParseResult::MakeEmptyResult() @@ -785,7 +810,10 @@ private: } case TOK_VARIABLE: { - return ParseResult::MakeSuccessfulResult(std::make_unique(tok.data)); + if (tok.data.empty()) + return ParseResult::MakeErrorResult(tok, _trans("Expected variable name.")); + else + return ParseResult::MakeSuccessfulResult(std::make_unique(tok.data)); } case TOK_LPAREN: { diff --git a/Source/Core/InputCommon/ControlReference/ExpressionParser.h b/Source/Core/InputCommon/ControlReference/ExpressionParser.h index 14670848ae..d36e00888f 100644 --- a/Source/Core/InputCommon/ControlReference/ExpressionParser.h +++ b/Source/Core/InputCommon/ControlReference/ExpressionParser.h @@ -143,7 +143,7 @@ public: class ControlEnvironment { public: - using VariableContainer = std::map; + using VariableContainer = std::map>; ControlEnvironment(const Core::DeviceContainer& container_, const Core::DeviceQualifier& default_, VariableContainer& vars) @@ -154,7 +154,10 @@ public: std::shared_ptr FindDevice(ControlQualifier qualifier) const; Core::Device::Input* FindInput(ControlQualifier qualifier) const; Core::Device::Output* FindOutput(ControlQualifier qualifier) const; - ControlState* GetVariablePtr(const std::string& name); + // Returns an existing variable by the specified name if already existing. Creates it otherwise. + std::shared_ptr GetVariablePtr(const std::string& name); + + void CleanUnusedVariables(); private: VariableContainer& m_variables; diff --git a/Source/Core/InputCommon/ControllerEmu/ControllerEmu.cpp b/Source/Core/InputCommon/ControllerEmu/ControllerEmu.cpp index a9c5844b48..498586a9f2 100644 --- a/Source/Core/InputCommon/ControllerEmu/ControllerEmu.cpp +++ b/Source/Core/InputCommon/ControllerEmu/ControllerEmu.cpp @@ -44,6 +44,8 @@ void EmulatedController::UpdateReferences(const ControllerInterface& devi) ciface::ExpressionParser::ControlEnvironment env(devi, GetDefaultDevice(), m_expression_vars); UpdateReferences(env); + + env.CleanUnusedVariables(); } void EmulatedController::UpdateReferences(ciface::ExpressionParser::ControlEnvironment& env) @@ -75,7 +77,27 @@ void EmulatedController::UpdateSingleControlReference(const ControllerInterface& ControlReference* ref) { ciface::ExpressionParser::ControlEnvironment env(devi, GetDefaultDevice(), m_expression_vars); + ref->UpdateReference(env); + + env.CleanUnusedVariables(); +} + +const ciface::ExpressionParser::ControlEnvironment::VariableContainer& +EmulatedController::GetExpressionVariables() const +{ + return m_expression_vars; +} + +void EmulatedController::ResetExpressionVariables() +{ + for (auto& var : m_expression_vars) + { + if (var.second) + { + *var.second = 0; + } + } } bool EmulatedController::IsDefaultDeviceConnected() const diff --git a/Source/Core/InputCommon/ControllerEmu/ControllerEmu.h b/Source/Core/InputCommon/ControllerEmu/ControllerEmu.h index 291e707f25..3150289bb2 100644 --- a/Source/Core/InputCommon/ControllerEmu/ControllerEmu.h +++ b/Source/Core/InputCommon/ControllerEmu/ControllerEmu.h @@ -191,6 +191,11 @@ public: // which happens while handling a hotplug event because a control reference's State() // could be called before we have finished updating the reference. [[nodiscard]] static std::unique_lock GetStateLock(); + const ciface::ExpressionParser::ControlEnvironment::VariableContainer& + GetExpressionVariables() const; + + // Resets the values while keeping the list. + void ResetExpressionVariables(); std::vector> groups; @@ -218,7 +223,8 @@ public: } protected: - // TODO: Wiimote attachment has its own member that isn't being used. + // TODO: Wiimote attachments actually end up using their parent controller value for this, + // so theirs won't be used (and thus shouldn't even exist). ciface::ExpressionParser::ControlEnvironment::VariableContainer m_expression_vars; void UpdateReferences(ciface::ExpressionParser::ControlEnvironment& env);