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
This commit is contained in:
Filoppi 2021-03-12 00:01:18 +02:00
parent 975f8e2a25
commit 93e3e691f9
6 changed files with 112 additions and 12 deletions

View File

@ -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<int>(&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<int>(&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<int>(&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);

View File

@ -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;

View File

@ -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<ControlState> 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<ControlState> 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<ControlState>& variable = m_variables[name];
// If new, make a shared ptr
if (!variable)
{
variable = std::make_shared<ControlState>();
}
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<VariableExpression>(tok.data));
if (tok.data.empty())
return ParseResult::MakeErrorResult(tok, _trans("Expected variable name."));
else
return ParseResult::MakeSuccessfulResult(std::make_unique<VariableExpression>(tok.data));
}
case TOK_LPAREN:
{

View File

@ -143,7 +143,7 @@ public:
class ControlEnvironment
{
public:
using VariableContainer = std::map<std::string, ControlState>;
using VariableContainer = std::map<std::string, std::shared_ptr<ControlState>>;
ControlEnvironment(const Core::DeviceContainer& container_, const Core::DeviceQualifier& default_,
VariableContainer& vars)
@ -154,7 +154,10 @@ public:
std::shared_ptr<Core::Device> 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<ControlState> GetVariablePtr(const std::string& name);
void CleanUnusedVariables();
private:
VariableContainer& m_variables;

View File

@ -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

View File

@ -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<std::recursive_mutex> GetStateLock();
const ciface::ExpressionParser::ControlEnvironment::VariableContainer&
GetExpressionVariables() const;
// Resets the values while keeping the list.
void ResetExpressionVariables();
std::vector<std::unique_ptr<ControlGroup>> 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);