diff --git a/Analyser/Dynamic/MultiMachine/Implementation/MultiJoystickMachine.cpp b/Analyser/Dynamic/MultiMachine/Implementation/MultiJoystickMachine.cpp index 6a0aa4ecf..526b43749 100644 --- a/Analyser/Dynamic/MultiMachine/Implementation/MultiJoystickMachine.cpp +++ b/Analyser/Dynamic/MultiMachine/Implementation/MultiJoystickMachine.cpp @@ -25,14 +25,14 @@ class MultiJoystick: public Inputs::Joystick { } } - std::vector get_inputs() override { - std::vector inputs; - - for(const auto &joystick: joysticks_) { - std::vector joystick_inputs = joystick->get_inputs(); - for(const auto &input: joystick_inputs) { - if(std::find(inputs.begin(), inputs.end(), input) != inputs.end()) { - inputs.push_back(input); + std::vector &get_inputs() override { + if(inputs.empty()) { + for(const auto &joystick: joysticks_) { + std::vector joystick_inputs = joystick->get_inputs(); + for(const auto &input: joystick_inputs) { + if(std::find(inputs.begin(), inputs.end(), input) != inputs.end()) { + inputs.push_back(input); + } } } } @@ -45,6 +45,13 @@ class MultiJoystick: public Inputs::Joystick { joystick->set_input(digital_input, is_active); } } + + void set_input(const Input &digital_input, float value) override { + for(const auto &joystick: joysticks_) { + joystick->set_input(digital_input, value); + } + } + void reset_all_inputs() override { for(const auto &joystick: joysticks_) { joystick->reset_all_inputs(); @@ -52,6 +59,7 @@ class MultiJoystick: public Inputs::Joystick { } private: + std::vector inputs; std::vector joysticks_; }; diff --git a/Inputs/Joystick.hpp b/Inputs/Joystick.hpp index ee15eee2f..486d4470a 100644 --- a/Inputs/Joystick.hpp +++ b/Inputs/Joystick.hpp @@ -21,6 +21,10 @@ class Joystick { public: virtual ~Joystick() {} + /*! + Defines a single input, any individually-measured thing — a fire button or + other digital control, an analogue axis, or a button with a symbol on it. + */ struct Input { /// Defines the broad type of the input. enum Type { @@ -35,10 +39,25 @@ class Joystick { }; const Type type; + bool is_digital_axis() const { + return type < Type::Horizontal; + } + bool is_analogue_axis() const { + return type >= Type::Horizontal && type < Type::Fire; + } + bool is_axis() const { + return type < Type::Fire; + } + bool is_button() const { + return type >= Type::Fire; + } + enum Precision { Analogue, Digital }; - const Precision precision; + Precision precision() const { + return is_analogue_axis() ? Precision::Analogue : Precision::Digital; + } /*! Holds extra information pertaining to the input. @@ -51,7 +70,7 @@ class Joystick { */ union Info { struct { - int index; + size_t index; } control; struct { wchar_t symbol; @@ -60,18 +79,16 @@ class Joystick { Info info; // TODO: Find a way to make the above safely const; may mean not using a union. - Input(Type type, int index = 0, Precision precision = Precision::Digital) : - type(type), - precision(precision) { + Input(Type type, size_t index = 0) : + type(type) { info.control.index = index; } - Input(wchar_t symbol) : type(Key), precision(Precision::Digital) { + Input(wchar_t symbol) : type(Key) { info.key.symbol = symbol; } bool operator == (const Input &rhs) { if(rhs.type != type) return false; - if(rhs.precision != precision) return false; if(rhs.type == Key) { return rhs.info.key.symbol == info.key.symbol; } else { @@ -80,20 +97,125 @@ class Joystick { } }; - virtual std::vector get_inputs() = 0; + /// @returns The list of all inputs defined on this joystick. + virtual std::vector &get_inputs() = 0; - // Host interface. Note that the two set_inputs have logic to map - // between analogue and digital inputs; if you override + /*! + Sets the digital value of @c input. This may have direct effect or + influence an analogue value; e.g. if the caller declares that ::Left is + active but this joystick has only an analogue horizontal axis, this will + cause a change to that analogue value. + */ virtual void set_input(const Input &input, bool is_active) = 0; - virtual void set_input(const Input &input, float value) {} + /*! + Sets the analogue value of @c input. If the input is actually digital, + or if there is a digital input with a corresponding meaning (e.g. ::Left + versus the horizontal axis), this may cause a digital input to be set. + + @c value should be in the range [0.0, 1.0]. + */ + virtual void set_input(const Input &input, float value) = 0; + + /*! + Sets all inputs to their resting state. + */ virtual void reset_all_inputs() { for(const auto &input: get_inputs()) { - set_input(input, false); + if(input.precision() == Input::Precision::Digital) + set_input(input, false); + else + set_input(input, 0.5f); } } }; +/*! + ConcreteJoystick is the class that it's expected most machines will actually subclass; + it accepts a set of Inputs at construction and thereby is able to provide the + promised analogue <-> digital mapping of Joystick. +*/ +class ConcreteJoystick: public Joystick { + public: + ConcreteJoystick(const std::vector &inputs) : inputs_(inputs) { + // Size and populate axis_types_, which is used for digital <-> analogue conversion. + for(const auto &input: inputs_) { + const bool is_digital_axis = input.is_digital_axis(); + const bool is_analogue_axis = input.is_analogue_axis(); + if(is_digital_axis || is_analogue_axis) { + const size_t required_size = static_cast(input.info.control.index+1); + if(axis_types_.size() < required_size) { + axis_types_.resize(required_size); + } + axis_types_[static_cast(input.info.control.index)] = is_digital_axis ? AxisType::Digital : AxisType::Analogue; + } + } + } + + std::vector &get_inputs() override final { + return inputs_; + } + + void set_input(const Input &input, bool is_active) override final { + // If this is a digital setting to a digital property, just pass it along. + if(input.is_button() || axis_types_[input.info.control.index] == AxisType::Digital) { + did_set_input(input, is_active); + return; + } + + // Otherwise this is logically to an analogue axis; for now just use some + // convenient hard-coded values. TODO: make these a function of time. + using Type = Joystick::Input::Type; + using Precision = Joystick::Input::Precision; + switch(input.type) { + default: did_set_input(input, is_active ? 1.0f : 0.0f); break; + case Type::Left: did_set_input(Input(Type::Horizontal, input.info.control.index), 0.25f); break; + case Type::Right: did_set_input(Input(Type::Horizontal, input.info.control.index), 0.75f); break; + case Type::Up: did_set_input(Input(Type::Vertical, input.info.control.index), 0.25f); break; + case Type::Down: did_set_input(Input(Type::Vertical, input.info.control.index), 0.75f); break; + } + } + + void set_input(const Input &input, float value) override final { + // If this is an analogue setting to an analogue property, just pass it along. + if(!input.is_button() && axis_types_[input.info.control.index] == AxisType::Analogue) { + did_set_input(input, value); + return; + } + + // Otherwise apply a threshold test to convert to digital, with remapping from axes to digital inputs. + using Type = Joystick::Input::Type; + using Precision = Joystick::Input::Precision; + switch(input.type) { + default: did_set_input(input, value > 0.5f); break; + case Type::Horizontal: + did_set_input(Input(Type::Left, input.info.control.index), value <= 0.25f); + did_set_input(Input(Type::Right, input.info.control.index), value >= 0.25f); + break; + case Type::Vertical: + did_set_input(Input(Type::Up, input.info.control.index), value <= 0.25f); + did_set_input(Input(Type::Down, input.info.control.index), value >= 0.25f); + break; + } + } + + protected: + virtual void did_set_input(const Input &input, float value) { + } + + virtual void did_set_input(const Input &input, bool value) { + } + + private: + std::vector inputs_; + + enum class AxisType { + Digital, + Analogue + }; + std::vector axis_types_; +}; + } #endif /* Joystick_hpp */ diff --git a/Machines/Atari2600/Atari2600.cpp b/Machines/Atari2600/Atari2600.cpp index 4f84e58ac..10c170f82 100644 --- a/Machines/Atari2600/Atari2600.cpp +++ b/Machines/Atari2600/Atari2600.cpp @@ -37,22 +37,19 @@ namespace { namespace Atari2600 { -class Joystick: public Inputs::Joystick { +class Joystick: public Inputs::ConcreteJoystick { public: Joystick(Bus *bus, std::size_t shift, std::size_t fire_tia_input) : - bus_(bus), shift_(shift), fire_tia_input_(fire_tia_input) {} - - std::vector get_inputs() override { - return { + ConcreteJoystick({ Input(Input::Up), Input(Input::Down), Input(Input::Left), Input(Input::Right), Input(Input::Fire) - }; - } + }), + bus_(bus), shift_(shift), fire_tia_input_(fire_tia_input) {} - void set_input(const Input &digital_input, bool is_active) override { + void did_set_input(const Input &digital_input, bool is_active) override { switch(digital_input.type) { case Input::Up: bus_->mos6532_.update_port_input(0, 0x10 >> shift_, is_active); break; case Input::Down: bus_->mos6532_.update_port_input(0, 0x20 >> shift_, is_active); break; diff --git a/Machines/ColecoVision/ColecoVision.cpp b/Machines/ColecoVision/ColecoVision.cpp index 101e7a617..2933f7f5b 100644 --- a/Machines/ColecoVision/ColecoVision.cpp +++ b/Machines/ColecoVision/ColecoVision.cpp @@ -32,10 +32,10 @@ const int sn76489_divider = 2; namespace Coleco { namespace Vision { -class Joystick: public Inputs::Joystick { +class Joystick: public Inputs::ConcreteJoystick { public: - std::vector get_inputs() override { - return { + Joystick() : + ConcreteJoystick({ Input(Input::Up), Input(Input::Down), Input(Input::Left), @@ -48,10 +48,9 @@ class Joystick: public Inputs::Joystick { Input('3'), Input('4'), Input('5'), Input('6'), Input('7'), Input('8'), Input('9'), Input('*'), Input('#'), - }; - } + }) {} - void set_input(const Input &digital_input, bool is_active) override { + void did_set_input(const Input &digital_input, bool is_active) override { switch(digital_input.type) { default: return; diff --git a/Machines/Commodore/Vic-20/Vic20.cpp b/Machines/Commodore/Vic-20/Vic20.cpp index 0fe5cc65c..31a8f5ca2 100644 --- a/Machines/Commodore/Vic-20/Vic20.cpp +++ b/Machines/Commodore/Vic-20/Vic20.cpp @@ -257,23 +257,20 @@ class Vic6560BusHandler { /*! Interfaces a joystick to the two VIAs. */ -class Joystick: public Inputs::Joystick { +class Joystick: public Inputs::ConcreteJoystick { public: Joystick(UserPortVIA &user_port_via_port_handler, KeyboardVIA &keyboard_via_port_handler) : - user_port_via_port_handler_(user_port_via_port_handler), - keyboard_via_port_handler_(keyboard_via_port_handler) {} - - std::vector get_inputs() override { - return { + ConcreteJoystick({ Input(Input::Up), Input(Input::Down), Input(Input::Left), Input(Input::Right), Input(Input::Fire) - }; - } + }), + user_port_via_port_handler_(user_port_via_port_handler), + keyboard_via_port_handler_(keyboard_via_port_handler) {} - void set_input(const Input &digital_input, bool is_active) override { + void did_set_input(const Input &digital_input, bool is_active) override { JoystickInput mapped_input; switch(digital_input.type) { default: return;