diff options
| author | Michael Maltese <michaeljosephmaltese@gmail.com> | 2016-10-11 17:48:38 -0700 |
|---|---|---|
| committer | Michael Maltese <michaeljosephmaltese@gmail.com> | 2017-02-07 22:59:10 -0800 |
| commit | a509f56116d308c96e38898e07d3538f26a1b7af (patch) | |
| tree | 1df255d62c9057cb3b53869afab04e737e96f0e0 /Source/Core/InputCommon/ControlReference | |
| parent | f621a6af431e53a71890f916dd812ccf987ab429 (diff) | |
InputCommon: Extract ControlReference from ControllerInterface
Better separation of concerns. Relegates `ControllerInterface` to
enumerating input controls, and the new `ControlReference` deals with
combining inputs and configuration expression parsing.
Diffstat (limited to 'Source/Core/InputCommon/ControlReference')
4 files changed, 866 insertions, 0 deletions
diff --git a/Source/Core/InputCommon/ControlReference/ControlReference.cpp b/Source/Core/InputCommon/ControlReference/ControlReference.cpp new file mode 100644 index 0000000000..4268531668 --- /dev/null +++ b/Source/Core/InputCommon/ControlReference/ControlReference.cpp @@ -0,0 +1,148 @@ +// Copyright 2016 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include "Common/Thread.h" +// For InputGateOn() +// This is a bad layering violation, but it's the cleanest +// place I could find to put it. +#include "Core/ConfigManager.h" +#include "Core/Host.h" + +#include "InputCommon/ControlReference/ControlReference.h" + +using namespace ciface::ExpressionParser; + +constexpr ControlState INPUT_DETECT_THRESHOLD = 0.55; + +bool ControlReference::InputGateOn() +{ + return SConfig::GetInstance().m_BackgroundInput || Host_RendererHasFocus() || Host_UIHasFocus(); +} + +// +// UpdateReference +// +// Updates a controlreference's binded devices/controls +// need to call this to re-parse a control reference's expression after changing it +// +void ControlReference::UpdateReference(ciface::Core::DeviceContainer& devices, + const ciface::Core::DeviceQualifier& default_device) +{ + delete parsed_expression; + parsed_expression = nullptr; + + ControlFinder finder(devices, default_device, is_input); + parse_error = ParseExpression(expression, finder, &parsed_expression); +} + +// +// InputReference :: State +// +// Gets the state of an input reference +// override function for ControlReference::State ... +// +ControlState InputReference::State(const ControlState ignore) +{ + if (parsed_expression && InputGateOn()) + return parsed_expression->GetValue() * range; + return 0.0; +} + +// +// OutputReference :: State +// +// Set the state of all binded outputs +// overrides ControlReference::State .. combined them so I could make the GUI simple / inputs == +// same as outputs one list +// I was lazy and it works so watever +// +ControlState OutputReference::State(const ControlState state) +{ + if (parsed_expression && InputGateOn()) + parsed_expression->SetValue(state); + return 0.0; +} + +// +// InputReference :: Detect +// +// Wait for input on all binded devices +// supports not detecting inputs that were held down at the time of Detect start, +// which is useful for those crazy flightsticks that have certain buttons that are always held down +// or some crazy axes or something +// upon input, return pointer to detected Control +// else return nullptr +// +ciface::Core::Device::Control* InputReference::Detect(const unsigned int ms, + ciface::Core::Device* const device) +{ + unsigned int time = 0; + std::vector<bool> states(device->Inputs().size()); + + if (device->Inputs().size() == 0) + return nullptr; + + // get starting state of all inputs, + // so we can ignore those that were activated at time of Detect start + std::vector<ciface::Core::Device::Input *>::const_iterator i = device->Inputs().begin(), + e = device->Inputs().end(); + for (std::vector<bool>::iterator state = states.begin(); i != e; ++i) + *state++ = ((*i)->GetState() > (1 - INPUT_DETECT_THRESHOLD)); + + while (time < ms) + { + device->UpdateInput(); + i = device->Inputs().begin(); + for (std::vector<bool>::iterator state = states.begin(); i != e; ++i, ++state) + { + // detected an input + if ((*i)->IsDetectable() && (*i)->GetState() > INPUT_DETECT_THRESHOLD) + { + // input was released at some point during Detect call + // return the detected input + if (false == *state) + return *i; + } + else if ((*i)->GetState() < (1 - INPUT_DETECT_THRESHOLD)) + { + *state = false; + } + } + Common::SleepCurrentThread(10); + time += 10; + } + + // no input was detected + return nullptr; +} + +// +// OutputReference :: Detect +// +// Totally different from the inputReference detect / I have them combined so it was simpler to make +// the GUI. +// The GUI doesn't know the difference between an input and an output / it's odd but I was lazy and +// it was easy +// +// set all binded outputs to <range> power for x milliseconds return false +// +ciface::Core::Device::Control* OutputReference::Detect(const unsigned int ms, + ciface::Core::Device* const device) +{ + // ignore device + + // don't hang if we don't even have any controls mapped + if (BoundCount() > 0) + { + State(1); + unsigned int slept = 0; + + // this loop is to make stuff like flashing keyboard LEDs work + while (ms > (slept += 10)) + Common::SleepCurrentThread(10); + + State(0); + } + return nullptr; +} diff --git a/Source/Core/InputCommon/ControlReference/ControlReference.h b/Source/Core/InputCommon/ControlReference/ControlReference.h new file mode 100644 index 0000000000..335675e5eb --- /dev/null +++ b/Source/Core/InputCommon/ControlReference/ControlReference.h @@ -0,0 +1,79 @@ +// Copyright 2016 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include "InputCommon/ControlReference/ExpressionParser.h" +#include "InputCommon/ControllerInterface/Device.h" + +// ControlReference +// +// These are what you create to actually use the inputs, InputReference or OutputReference. +// +// After being bound to devices and controls with UpdateReference, +// each one can link to multiple devices and controls +// when you change a ControlReference's expression, +// you must use UpdateReference on it to rebind controls +// + +class ControlReference +{ +public: + static bool InputGateOn(); + + virtual ControlState State(const ControlState state = 0) = 0; + virtual ciface::Core::Device::Control* Detect(const unsigned int ms, + ciface::Core::Device* const device) = 0; + + void UpdateReference(ciface::Core::DeviceContainer& devices, + const ciface::Core::DeviceQualifier& default_device); + + ControlState range; + std::string expression; + const bool is_input; + ciface::ExpressionParser::ExpressionParseStatus parse_error; + + virtual ~ControlReference() { delete parsed_expression; } + int BoundCount() + { + if (parsed_expression) + return parsed_expression->num_controls; + else + return 0; + } + +protected: + ControlReference(const bool _is_input) : range(1), is_input(_is_input), parsed_expression(nullptr) + { + } + ciface::ExpressionParser::Expression* parsed_expression; +}; + +// +// InputReference +// +// Control reference for inputs +// +class InputReference : public ControlReference +{ +public: + InputReference() : ControlReference(true) {} + ControlState State(const ControlState state) override; + ciface::Core::Device::Control* Detect(const unsigned int ms, + ciface::Core::Device* const device) override; +}; + +// +// OutputReference +// +// Control reference for outputs +// +class OutputReference : public ControlReference +{ +public: + OutputReference() : ControlReference(false) {} + ControlState State(const ControlState state) override; + ciface::Core::Device::Control* Detect(const unsigned int ms, + ciface::Core::Device* const device) override; +}; diff --git a/Source/Core/InputCommon/ControlReference/ExpressionParser.cpp b/Source/Core/InputCommon/ControlReference/ExpressionParser.cpp new file mode 100644 index 0000000000..47fbd001a6 --- /dev/null +++ b/Source/Core/InputCommon/ControlReference/ExpressionParser.cpp @@ -0,0 +1,567 @@ +// Copyright 2013 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include <algorithm> +#include <cassert> +#include <iostream> +#include <map> +#include <memory> +#include <string> +#include <vector> + +#include "InputCommon/ControlReference/ExpressionParser.h" + +using namespace ciface::Core; + +namespace ciface +{ +namespace ExpressionParser +{ +enum TokenType +{ + TOK_DISCARD, + TOK_INVALID, + TOK_EOF, + TOK_LPAREN, + TOK_RPAREN, + TOK_AND, + TOK_OR, + TOK_NOT, + TOK_ADD, + TOK_CONTROL, +}; + +inline std::string OpName(TokenType op) +{ + switch (op) + { + case TOK_AND: + return "And"; + case TOK_OR: + return "Or"; + case TOK_NOT: + return "Not"; + case TOK_ADD: + return "Add"; + default: + assert(false); + return ""; + } +} + +class Token +{ +public: + TokenType type; + ControlQualifier qualifier; + + Token(TokenType type_) : type(type_) {} + Token(TokenType type_, ControlQualifier qualifier_) : type(type_), qualifier(qualifier_) {} + operator std::string() + { + switch (type) + { + case TOK_DISCARD: + return "Discard"; + case TOK_EOF: + return "EOF"; + case TOK_LPAREN: + return "("; + case TOK_RPAREN: + return ")"; + case TOK_AND: + return "&"; + case TOK_OR: + return "|"; + case TOK_NOT: + return "!"; + case TOK_ADD: + return "+"; + case TOK_CONTROL: + return "Device(" + (std::string)qualifier + ")"; + case TOK_INVALID: + break; + } + + return "Invalid"; + } +}; + +class Lexer +{ +public: + std::string expr; + std::string::iterator it; + + Lexer(const std::string& expr_) : expr(expr_) { it = expr.begin(); } + bool FetchBacktickString(std::string& value, char otherDelim = 0) + { + value = ""; + while (it != expr.end()) + { + char c = *it; + ++it; + if (c == '`') + return false; + if (c > 0 && c == otherDelim) + return true; + value += c; + } + return false; + } + + Token GetFullyQualifiedControl() + { + ControlQualifier qualifier; + std::string value; + + if (FetchBacktickString(value, ':')) + { + // Found colon, this is the device name + qualifier.has_device = true; + qualifier.device_qualifier.FromString(value); + FetchBacktickString(value); + } + + qualifier.control_name = value; + + return Token(TOK_CONTROL, qualifier); + } + + Token GetBarewordsControl(char c) + { + std::string name; + name += c; + + while (it != expr.end()) + { + c = *it; + if (!isalpha(c)) + break; + name += c; + ++it; + } + + ControlQualifier qualifier; + qualifier.control_name = name; + return Token(TOK_CONTROL, qualifier); + } + + Token NextToken() + { + if (it == expr.end()) + return Token(TOK_EOF); + + char c = *it++; + switch (c) + { + case ' ': + case '\t': + case '\n': + case '\r': + return Token(TOK_DISCARD); + case '(': + return Token(TOK_LPAREN); + case ')': + return Token(TOK_RPAREN); + case '&': + return Token(TOK_AND); + case '|': + return Token(TOK_OR); + case '!': + return Token(TOK_NOT); + case '+': + return Token(TOK_ADD); + case '`': + return GetFullyQualifiedControl(); + default: + if (isalpha(c)) + return GetBarewordsControl(c); + else + return Token(TOK_INVALID); + } + } + + ExpressionParseStatus Tokenize(std::vector<Token>& tokens) + { + while (true) + { + Token tok = NextToken(); + + if (tok.type == TOK_DISCARD) + continue; + + if (tok.type == TOK_INVALID) + { + tokens.clear(); + return EXPRESSION_PARSE_SYNTAX_ERROR; + } + + tokens.push_back(tok); + + if (tok.type == TOK_EOF) + break; + } + return EXPRESSION_PARSE_SUCCESS; + } +}; + +class ExpressionNode +{ +public: + virtual ~ExpressionNode() {} + virtual ControlState GetValue() { return 0; } + virtual void SetValue(ControlState state) {} + virtual int CountNumControls() { return 0; } + virtual operator std::string() { return ""; } +}; + +class DummyExpression : public ExpressionNode +{ +public: + std::string name; + + DummyExpression(const std::string& name_) : name(name_) {} + ControlState GetValue() override { return 0.0; } + void SetValue(ControlState value) override {} + int CountNumControls() override { return 0; } + operator std::string() override { return "`" + name + "`"; } +}; + +class ControlExpression : public ExpressionNode +{ +public: + ControlQualifier qualifier; + Device::Control* control; + + ControlExpression(ControlQualifier qualifier_, std::shared_ptr<Device> device, + Device::Control* control_) + : qualifier(qualifier_), control(control_), m_device(device) + { + } + + ControlState GetValue() override { return control->ToInput()->GetState(); } + void SetValue(ControlState value) override { control->ToOutput()->SetState(value); } + int CountNumControls() override { return 1; } + operator std::string() override { return "`" + (std::string)qualifier + "`"; } +private: + std::shared_ptr<Device> m_device; +}; + +class BinaryExpression : public ExpressionNode +{ +public: + TokenType op; + ExpressionNode* lhs; + ExpressionNode* rhs; + + BinaryExpression(TokenType op_, ExpressionNode* lhs_, ExpressionNode* rhs_) + : op(op_), lhs(lhs_), rhs(rhs_) + { + } + virtual ~BinaryExpression() + { + delete lhs; + delete rhs; + } + + ControlState GetValue() override + { + ControlState lhsValue = lhs->GetValue(); + ControlState rhsValue = rhs->GetValue(); + switch (op) + { + case TOK_AND: + return std::min(lhsValue, rhsValue); + case TOK_OR: + return std::max(lhsValue, rhsValue); + case TOK_ADD: + return std::min(lhsValue + rhsValue, 1.0); + default: + assert(false); + return 0; + } + } + + void SetValue(ControlState value) override + { + // Don't do anything special with the op we have. + // Treat "A & B" the same as "A | B". + lhs->SetValue(value); + rhs->SetValue(value); + } + + int CountNumControls() override { return lhs->CountNumControls() + rhs->CountNumControls(); } + operator std::string() override + { + return OpName(op) + "(" + (std::string)(*lhs) + ", " + (std::string)(*rhs) + ")"; + } +}; + +class UnaryExpression : public ExpressionNode +{ +public: + TokenType op; + ExpressionNode* inner; + + UnaryExpression(TokenType op_, ExpressionNode* inner_) : op(op_), inner(inner_) {} + virtual ~UnaryExpression() { delete inner; } + ControlState GetValue() override + { + ControlState value = inner->GetValue(); + switch (op) + { + case TOK_NOT: + return 1.0 - value; + default: + assert(false); + return 0; + } + } + + void SetValue(ControlState value) override + { + switch (op) + { + case TOK_NOT: + inner->SetValue(1.0 - value); + break; + + default: + assert(false); + } + } + + int CountNumControls() override { return inner->CountNumControls(); } + operator std::string() override { return OpName(op) + "(" + (std::string)(*inner) + ")"; } +}; + +std::shared_ptr<Device> ControlFinder::FindDevice(ControlQualifier qualifier) +{ + if (qualifier.has_device) + return container.FindDevice(qualifier.device_qualifier); + else + return container.FindDevice(default_device); +} + +Device::Control* ControlFinder::FindControl(ControlQualifier qualifier) +{ + const std::shared_ptr<Device> device = FindDevice(qualifier); + if (!device) + return nullptr; + + if (is_input) + return device->FindInput(qualifier.control_name); + else + return device->FindOutput(qualifier.control_name); +} + +class Parser +{ +public: + Parser(std::vector<Token> tokens_, ControlFinder& finder_) : tokens(tokens_), finder(finder_) + { + m_it = tokens.begin(); + } + + ExpressionParseStatus Parse(Expression** expr_out) + { + ExpressionNode* node; + ExpressionParseStatus status = Toplevel(&node); + if (status != EXPRESSION_PARSE_SUCCESS) + return status; + + *expr_out = new Expression(node); + return EXPRESSION_PARSE_SUCCESS; + } + +private: + std::vector<Token> tokens; + std::vector<Token>::iterator m_it; + ControlFinder& finder; + + Token Chew() { return *m_it++; } + Token Peek() { return *m_it; } + bool Expects(TokenType type) + { + Token tok = Chew(); + return tok.type == type; + } + + ExpressionParseStatus Atom(ExpressionNode** expr_out) + { + Token tok = Chew(); + switch (tok.type) + { + case TOK_CONTROL: + { + std::shared_ptr<Device> device = finder.FindDevice(tok.qualifier); + Device::Control* control = finder.FindControl(tok.qualifier); + if (control == nullptr) + { + *expr_out = new DummyExpression(tok.qualifier); + return EXPRESSION_PARSE_NO_DEVICE; + } + + *expr_out = new ControlExpression(tok.qualifier, device, control); + return EXPRESSION_PARSE_SUCCESS; + } + case TOK_LPAREN: + return Paren(expr_out); + default: + return EXPRESSION_PARSE_SYNTAX_ERROR; + } + } + + bool IsUnaryExpression(TokenType type) + { + switch (type) + { + case TOK_NOT: + return true; + default: + return false; + } + } + + ExpressionParseStatus Unary(ExpressionNode** expr_out) + { + if (IsUnaryExpression(Peek().type)) + { + Token tok = Chew(); + ExpressionNode* atom_expr; + ExpressionParseStatus status = Atom(&atom_expr); + if (status == EXPRESSION_PARSE_SYNTAX_ERROR) + return status; + *expr_out = new UnaryExpression(tok.type, atom_expr); + return EXPRESSION_PARSE_SUCCESS; + } + + return Atom(expr_out); + } + + bool IsBinaryToken(TokenType type) + { + switch (type) + { + case TOK_AND: + case TOK_OR: + case TOK_ADD: + return true; + default: + return false; + } + } + + ExpressionParseStatus Binary(ExpressionNode** expr_out) + { + ExpressionParseStatus status = Unary(expr_out); + if (status == EXPRESSION_PARSE_SYNTAX_ERROR) + return status; + + while (IsBinaryToken(Peek().type)) + { + Token tok = Chew(); + ExpressionNode* unary_expr; + status = Unary(&unary_expr); + if (status == EXPRESSION_PARSE_SYNTAX_ERROR) + { + delete *expr_out; + return status; + } + + *expr_out = new BinaryExpression(tok.type, *expr_out, unary_expr); + } + + return EXPRESSION_PARSE_SUCCESS; + } + + ExpressionParseStatus Paren(ExpressionNode** expr_out) + { + ExpressionParseStatus status; + + // lparen already chewed + if ((status = Toplevel(expr_out)) != EXPRESSION_PARSE_SUCCESS) + return status; + + if (!Expects(TOK_RPAREN)) + { + delete *expr_out; + return EXPRESSION_PARSE_SYNTAX_ERROR; + } + + return EXPRESSION_PARSE_SUCCESS; + } + + ExpressionParseStatus Toplevel(ExpressionNode** expr_out) { return Binary(expr_out); } +}; + +ControlState Expression::GetValue() +{ + return node->GetValue(); +} + +void Expression::SetValue(ControlState value) +{ + node->SetValue(value); +} + +Expression::Expression(ExpressionNode* node_) +{ + node = node_; + num_controls = node->CountNumControls(); +} + +Expression::~Expression() +{ + delete node; +} + +static ExpressionParseStatus ParseExpressionInner(const std::string& str, ControlFinder& finder, + Expression** expr_out) +{ + ExpressionParseStatus status; + Expression* expr; + *expr_out = nullptr; + + if (str == "") + return EXPRESSION_PARSE_SUCCESS; + + Lexer l(str); + std::vector<Token> tokens; + status = l.Tokenize(tokens); + if (status != EXPRESSION_PARSE_SUCCESS) + return status; + + Parser p(tokens, finder); + status = p.Parse(&expr); + if (status != EXPRESSION_PARSE_SUCCESS) + return status; + + *expr_out = expr; + return EXPRESSION_PARSE_SUCCESS; +} + +ExpressionParseStatus ParseExpression(const std::string& str, ControlFinder& finder, + Expression** expr_out) +{ + // Add compatibility with old simple expressions, which are simple + // barewords control names. + + ControlQualifier qualifier; + qualifier.control_name = str; + qualifier.has_device = false; + + std::shared_ptr<Device> device = finder.FindDevice(qualifier); + Device::Control* control = finder.FindControl(qualifier); + if (control) + { + *expr_out = new Expression(new ControlExpression(qualifier, device, control)); + return EXPRESSION_PARSE_SUCCESS; + } + + return ParseExpressionInner(str, finder, expr_out); +} +} +} diff --git a/Source/Core/InputCommon/ControlReference/ExpressionParser.h b/Source/Core/InputCommon/ControlReference/ExpressionParser.h new file mode 100644 index 0000000000..2c8f5a793f --- /dev/null +++ b/Source/Core/InputCommon/ControlReference/ExpressionParser.h @@ -0,0 +1,72 @@ +// Copyright 2013 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include <memory> +#include <string> +#include "InputCommon/ControllerInterface/Device.h" + +namespace ciface +{ +namespace ExpressionParser +{ +class ControlQualifier +{ +public: + bool has_device; + Core::DeviceQualifier device_qualifier; + std::string control_name; + + ControlQualifier() : has_device(false) {} + operator std::string() + { + if (has_device) + return device_qualifier.ToString() + ":" + control_name; + else + return control_name; + } +}; + +class ControlFinder +{ +public: + ControlFinder(const Core::DeviceContainer& container_, const Core::DeviceQualifier& default_, + const bool is_input_) + : container(container_), default_device(default_), is_input(is_input_) + { + } + std::shared_ptr<Core::Device> FindDevice(ControlQualifier qualifier); + Core::Device::Control* FindControl(ControlQualifier qualifier); + +private: + const Core::DeviceContainer& container; + const Core::DeviceQualifier& default_device; + bool is_input; +}; + +class ExpressionNode; +class Expression +{ +public: + Expression() : node(nullptr) {} + Expression(ExpressionNode* node); + ~Expression(); + ControlState GetValue(); + void SetValue(ControlState state); + int num_controls; + ExpressionNode* node; +}; + +enum ExpressionParseStatus +{ + EXPRESSION_PARSE_SUCCESS = 0, + EXPRESSION_PARSE_SYNTAX_ERROR, + EXPRESSION_PARSE_NO_DEVICE, +}; + +ExpressionParseStatus ParseExpression(const std::string& expr, ControlFinder& finder, + Expression** expr_out); +} +} |
