summaryrefslogtreecommitdiff
path: root/lib/nes/app/input
diff options
context:
space:
mode:
Diffstat (limited to 'lib/nes/app/input')
-rw-r--r--lib/nes/app/input/CMakeLists.txt8
-rw-r--r--lib/nes/app/input/input-device-controller.hh38
-rw-r--r--lib/nes/app/input/input-device-keyboard.hh16
-rw-r--r--lib/nes/app/input/input-device.hh44
-rw-r--r--lib/nes/app/input/input-manager.cc102
-rw-r--r--lib/nes/app/input/input-manager.hh45
6 files changed, 253 insertions, 0 deletions
diff --git a/lib/nes/app/input/CMakeLists.txt b/lib/nes/app/input/CMakeLists.txt
new file mode 100644
index 0000000..7150745
--- /dev/null
+++ b/lib/nes/app/input/CMakeLists.txt
@@ -0,0 +1,8 @@
+target_sources(
+ nes
+ PRIVATE
+ input-device.hh
+ input-device-keyboard.hh
+ input-device-controller.hh
+ input-manager.hh
+ input-manager.cc) \ No newline at end of file
diff --git a/lib/nes/app/input/input-device-controller.hh b/lib/nes/app/input/input-device-controller.hh
new file mode 100644
index 0000000..6adb79c
--- /dev/null
+++ b/lib/nes/app/input/input-device-controller.hh
@@ -0,0 +1,38 @@
+#pragma once
+
+#include "nes/app/input/input-device.hh"
+#include "nes/common/list.hh"
+#include <string_view>
+
+namespace nes
+{
+ /// A game controller input device.
+ class input_device_controller : public input_device, public list<input_device_controller>::node
+ {
+ public:
+ enum class index
+ {
+ unused = -1,
+ player_1 = 0,
+ player_2 = 1,
+ };
+
+ /// Indicates whether the controller can be trusted to actually exist.
+ ///
+ /// If this returns false, the controller is not automatically selected on startup if there is no other
+ /// controller already selected.
+ virtual auto is_reliable() const -> bool = 0;
+
+ /// Human-readable name for the controller.
+ virtual auto get_name() const -> std::string_view = 0;
+
+ /// Get the (persistent) player index for the controller, if already set before.
+ virtual auto get_index() const -> index = 0;
+
+ /// Set the (persistent) player index for the controller.
+ virtual auto set_index(index) -> void = 0;
+
+ protected:
+ explicit input_device_controller() = default;
+ };
+} // namespace nes \ No newline at end of file
diff --git a/lib/nes/app/input/input-device-keyboard.hh b/lib/nes/app/input/input-device-keyboard.hh
new file mode 100644
index 0000000..d61417d
--- /dev/null
+++ b/lib/nes/app/input/input-device-keyboard.hh
@@ -0,0 +1,16 @@
+#pragma once
+
+#include "nes/app/input/input-device.hh"
+
+namespace nes
+{
+ /// A keyboard input device.
+ class input_device_keyboard : public input_device
+ {
+ public:
+ virtual auto read_key_escape() -> bool = 0;
+
+ protected:
+ explicit input_device_keyboard() = default;
+ };
+} // namespace nes \ No newline at end of file
diff --git a/lib/nes/app/input/input-device.hh b/lib/nes/app/input/input-device.hh
new file mode 100644
index 0000000..9ac7423
--- /dev/null
+++ b/lib/nes/app/input/input-device.hh
@@ -0,0 +1,44 @@
+#pragma once
+
+#include "nes/sys/types/button-mask.hh"
+
+namespace nes
+{
+ /// An abstract input device which can be used to play games.
+ class input_device
+ {
+ public:
+ virtual ~input_device() = default;
+
+ input_device(input_device const&) = delete;
+ input_device(input_device&&) = delete;
+ auto operator=(input_device const&) -> input_device& = delete;
+ auto operator=(input_device&&) -> input_device& = delete;
+
+ virtual auto read_key_up() -> bool = 0;
+ virtual auto read_key_down() -> bool = 0;
+ virtual auto read_key_left() -> bool = 0;
+ virtual auto read_key_right() -> bool = 0;
+ virtual auto read_key_a() -> bool = 0;
+ virtual auto read_key_b() -> bool = 0;
+ virtual auto read_key_select() -> bool = 0;
+ virtual auto read_key_start() -> bool = 0;
+
+ auto read_mask() -> button_mask
+ {
+ auto res = button_mask{};
+ if (read_key_up()) { res.add(buttons::up); }
+ if (read_key_down()) { res.add(buttons::down); }
+ if (read_key_left()) { res.add(buttons::left); }
+ if (read_key_right()) { res.add(buttons::right); }
+ if (read_key_a()) { res.add(buttons::a); }
+ if (read_key_b()) { res.add(buttons::b); }
+ if (read_key_select()) { res.add(buttons::select); }
+ if (read_key_start()) { res.add(buttons::start); }
+ return res;
+ }
+
+ protected:
+ explicit input_device() = default;
+ };
+} // namespace nes \ No newline at end of file
diff --git a/lib/nes/app/input/input-manager.cc b/lib/nes/app/input/input-manager.cc
new file mode 100644
index 0000000..1c9db9e
--- /dev/null
+++ b/lib/nes/app/input/input-manager.cc
@@ -0,0 +1,102 @@
+#include "nes/app/input/input-manager.hh"
+
+namespace nes
+{
+ namespace
+ {
+ class input_device_dummy final : public input_device
+ {
+ public:
+ explicit input_device_dummy() = default;
+
+ static auto get_instance() -> input_device_dummy&
+ {
+ static auto res = input_device_dummy{};
+ return res;
+ }
+
+ auto read_key_up() -> bool override { return false; }
+ auto read_key_down() -> bool override { return false; }
+ auto read_key_left() -> bool override { return false; }
+ auto read_key_right() -> bool override { return false; }
+ auto read_key_a() -> bool override { return false; }
+ auto read_key_b() -> bool override { return false; }
+ auto read_key_select() -> bool override { return false; }
+ auto read_key_start() -> bool override { return false; }
+ };
+ } // namespace
+
+ input_manager::input_manager(input_device_keyboard& keyboard)
+ : keyboard_{ &keyboard }
+ {
+ fill_unassigned_inputs();
+ }
+
+ auto input_manager::add_controller(input_device_controller& controller) -> void
+ {
+ controllers_.append(controller);
+ if (controller.is_reliable())
+ {
+ auto const index = controller.get_index();
+ if (index != input_device_controller::index::unused)
+ {
+ // This controller was previously used and assigned an index -> replace the current device with this
+ // (known) controller.
+ auto const i = static_cast<std::size_t>(index);
+ if (inputs_[i].controller)
+ {
+ inputs_[i].controller->set_index(input_device_controller::index::unused);
+ }
+
+ inputs_[i].controller = &controller;
+ inputs_[i].device = &controller;
+ }
+
+ // There may be another free controller now.
+ fill_unassigned_inputs();
+ }
+ }
+
+ auto input_manager::remove_controller(input_device_controller& controller) -> void
+ {
+ controllers_.remove(controller);
+ fill_unassigned_inputs();
+ }
+
+ auto input_manager::fill_unassigned_inputs() -> void
+ {
+ auto it = controllers_.begin();
+ input_device* fallback = keyboard_;
+
+ for (auto i = std::size_t{ 0 }; i < input_count; ++i)
+ {
+ if (inputs_[i].controller != nullptr) { continue; }
+
+ // This input is not assigned -> use the next available controller if available.
+ while (it != controllers_.end())
+ {
+ auto controller = *it;
+ if (controller->is_reliable() && controller->get_index() == input_device_controller::index::unused)
+ {
+ break;
+ }
+ ++it;
+ }
+
+ if (it != controllers_.end())
+ {
+ // We've found an unused controller -> assign it.
+ inputs_[i].controller = *it;
+ inputs_[i].controller->set_index(static_cast<input_device_controller::index>(i));
+ inputs_[i].device = *it;
+ }
+ else
+ {
+ // We're all out of controllers -> fall back to the keyboard (if this is the first missing controller)
+ // or nothing.
+ inputs_[i].device = fallback;
+ fallback = &input_device_dummy::get_instance(); // Only use the keyboard as a fallback once.
+ }
+ }
+ }
+} // namespace nes \ No newline at end of file
diff --git a/lib/nes/app/input/input-manager.hh b/lib/nes/app/input/input-manager.hh
new file mode 100644
index 0000000..10308c7
--- /dev/null
+++ b/lib/nes/app/input/input-manager.hh
@@ -0,0 +1,45 @@
+#pragma once
+
+#include "nes/app/input/input-device-keyboard.hh"
+#include "nes/app/input/input-device-controller.hh"
+#include "nes/common/list.hh"
+
+namespace nes
+{
+ /// Manages input devices.
+ ///
+ /// The main purpose of this class is to assign newly plugged in controllers as the primary/secondary input and fall
+ /// back to the keyboard if needed.
+ class input_manager
+ {
+ static constexpr auto input_count = std::size_t{ 2 };
+
+ input_device_keyboard* keyboard_;
+ list<input_device_controller> controllers_;
+ struct
+ {
+ input_device* device{ nullptr };
+ input_device_controller* controller{ nullptr };
+ } inputs_[input_count]{};
+
+ public:
+ explicit input_manager(input_device_keyboard&);
+
+ input_manager(input_manager const&) = delete;
+ input_manager(input_manager&&) = delete;
+ auto operator=(input_manager const&) -> input_manager& = delete;
+ auto operator=(input_manager&&) -> input_manager& = delete;
+
+ auto get_input_1() const -> input_device& { return *inputs_[0].device; }
+ auto get_input_2() const -> input_device& { return *inputs_[1].device; }
+
+ auto get_keyboard() const -> input_device_keyboard& { return *keyboard_; }
+ auto get_controllers() const -> list<input_device_controller> const& { return controllers_; }
+
+ auto add_controller(input_device_controller&) -> void;
+ auto remove_controller(input_device_controller&) -> void;
+
+ private:
+ auto fill_unassigned_inputs() -> void;
+ };
+} // namespace nes \ No newline at end of file