summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHannes Schulze <haschu0103@outlook.de>2025-02-04 23:11:08 +0100
committerHannes Schulze <haschu0103@outlook.de>2025-02-04 23:11:08 +0100
commit79553ab1fa3d707612e80dd03bf95ca65fa0c411 (patch)
treef44bec41548d569cfca43c44e0e4ffa7b72ed4ed
parent9c45e9c7df1efa5bc1c8e73b6eaef4726b828d3e (diff)
refactor file browser
-rw-r--r--app/mac/CMakeLists.txt4
-rw-r--r--app/mac/impl/CMakeLists.txt13
-rw-r--r--app/mac/impl/display-spritekit.hh24
-rw-r--r--app/mac/impl/display-spritekit.mm50
-rw-r--r--app/mac/impl/file-browser-posix.cc129
-rw-r--r--app/mac/impl/file-browser-posix.hh30
-rw-r--r--app/mac/impl/input-device-gc-gamepad.hh24
-rw-r--r--app/mac/impl/input-device-gc-gamepad.mm60
-rw-r--r--app/mac/impl/input-device-gc-keyboard.hh30
-rw-r--r--app/mac/impl/input-device-gc-keyboard.mm119
-rw-r--r--app/mac/impl/input-device-serial-controller.cc96
-rw-r--r--app/mac/impl/input-device-serial-controller.hh29
-rw-r--r--app/mac/scene.mm503
-rw-r--r--lib/nes/app/application.cc18
-rw-r--r--lib/nes/app/file-browser.hh20
-rw-r--r--lib/nes/app/input/input-buffer.cc2
-rw-r--r--lib/nes/app/input/input-manager.cc7
-rw-r--r--lib/nes/app/ui/action.hh26
-rw-r--r--lib/nes/app/ui/screen-browser.cc28
-rw-r--r--lib/nes/common/CMakeLists.txt3
-rw-r--r--lib/nes/common/box.hh29
-rw-r--r--lib/nes/common/debug.hh18
-rw-r--r--lib/nes/common/list.hh24
-rw-r--r--lib/nes/common/path-builder.cc14
-rw-r--r--lib/nes/common/path-builder.hh21
-rw-r--r--lib/nes/common/path-view.cc4
-rw-r--r--lib/nes/common/span.hh33
-rw-r--r--lib/nes/common/status.hh (renamed from lib/nes/sys/types/status.hh)13
-rw-r--r--lib/nes/common/string-builder.cc116
-rw-r--r--lib/nes/common/string-builder.hh37
-rw-r--r--lib/nes/sys/cartridge.cc3
-rw-r--r--lib/nes/sys/cartridge.hh2
-rw-r--r--lib/nes/sys/cpu.hh2
-rw-r--r--lib/nes/sys/mapper.hh2
-rw-r--r--lib/nes/sys/types/CMakeLists.txt3
35 files changed, 913 insertions, 623 deletions
diff --git a/app/mac/CMakeLists.txt b/app/mac/CMakeLists.txt
index e20edb5..e7471d1 100644
--- a/app/mac/CMakeLists.txt
+++ b/app/mac/CMakeLists.txt
@@ -21,4 +21,6 @@ target_sources(
scene.hh
scene.mm
view-controller.hh
- view-controller.mm) \ No newline at end of file
+ view-controller.mm)
+
+add_subdirectory(impl) \ No newline at end of file
diff --git a/app/mac/impl/CMakeLists.txt b/app/mac/impl/CMakeLists.txt
new file mode 100644
index 0000000..f8ff091
--- /dev/null
+++ b/app/mac/impl/CMakeLists.txt
@@ -0,0 +1,13 @@
+target_sources(
+ nes_app_mac
+ PRIVATE
+ display-spritekit.hh
+ display-spritekit.mm
+ input-device-gc-gamepad.hh
+ input-device-gc-gamepad.mm
+ input-device-gc-keyboard.hh
+ input-device-gc-keyboard.mm
+ input-device-serial-controller.hh
+ input-device-serial-controller.cc
+ file-browser-posix.hh
+ file-browser-posix.cc)
diff --git a/app/mac/impl/display-spritekit.hh b/app/mac/impl/display-spritekit.hh
new file mode 100644
index 0000000..64e3b03
--- /dev/null
+++ b/app/mac/impl/display-spritekit.hh
@@ -0,0 +1,24 @@
+#pragma once
+
+#import "nes/common/display.hh"
+
+#import <SpriteKit/SpriteKit.h>
+
+namespace nes::app::mac
+{
+ /// Display implementation rendering contents into a SpriteKit node.
+ class display_spritekit final : public display
+ {
+ SKSpriteNode* node_;
+ NSMutableData* buffer_front_;
+ NSMutableData* buffer_back_;
+ u8* bytes_back_;
+
+ public:
+ explicit display_spritekit(SKSpriteNode* node);
+
+ virtual auto switch_buffers() -> void override;
+ virtual auto set(u32 x, u32 y, rgb value) -> void override;
+ virtual auto get(u32 x, u32 y) const -> rgb override;
+ };
+} // namespace nes::app::mac \ No newline at end of file
diff --git a/app/mac/impl/display-spritekit.mm b/app/mac/impl/display-spritekit.mm
new file mode 100644
index 0000000..b6123d6
--- /dev/null
+++ b/app/mac/impl/display-spritekit.mm
@@ -0,0 +1,50 @@
+#import "impl/display-spritekit.hh"
+
+namespace nes::app::mac
+{
+ namespace
+ {
+ auto make_buffer() -> NSMutableData*
+ {
+ auto const length = NSUInteger{ display::width * display::height * 4 };
+ return [NSMutableData dataWithLength:length];
+ }
+ } // namespace
+
+ display_spritekit::display_spritekit(SKSpriteNode* node)
+ : node_{ node }
+ , buffer_front_{ make_buffer() }
+ , buffer_back_{ make_buffer() }
+ , bytes_back_{ static_cast<u8*>([buffer_back_ mutableBytes]) }
+ {
+ }
+
+ auto display_spritekit::switch_buffers() -> void
+ {
+ std::swap(buffer_back_, buffer_front_);
+ bytes_back_ = static_cast<u8*>([buffer_back_ mutableBytes]);
+
+ auto texture = [SKTexture textureWithData:buffer_front_ size:CGSizeMake(width, height) flipped:YES];
+ [texture setFilteringMode:SKTextureFilteringNearest];
+ [node_ setTexture:texture];
+ }
+
+ auto display_spritekit::set(u32 const x, u32 const y, rgb const value) -> void
+ {
+ auto const offset = (y * width + x) * 4;
+ bytes_back_[offset + 0] = value.r;
+ bytes_back_[offset + 1] = value.g;
+ bytes_back_[offset + 2] = value.b;
+ bytes_back_[offset + 3] = 255;
+ }
+
+ auto display_spritekit::get(u32 const x, u32 const y) const -> rgb
+ {
+ auto const offset = (y * width + x) * 4;
+ auto res = rgb{};
+ res.r = bytes_back_[offset + 0];
+ res.g = bytes_back_[offset + 1];
+ res.b = bytes_back_[offset + 2];
+ return res;
+ }
+} // namespace nes::app::mac \ No newline at end of file
diff --git a/app/mac/impl/file-browser-posix.cc b/app/mac/impl/file-browser-posix.cc
new file mode 100644
index 0000000..4c717fd
--- /dev/null
+++ b/app/mac/impl/file-browser-posix.cc
@@ -0,0 +1,129 @@
+#include "impl/file-browser-posix.hh"
+#include <string>
+#include <errno.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+namespace nes::app::mac
+{
+ file_browser_posix::file_browser_posix()
+ {
+ reopen_directory(path_.get_path());
+ }
+
+ file_browser_posix::~file_browser_posix()
+ {
+ if (it_) { closedir(it_); }
+ }
+
+ auto file_browser_posix::seek(u32 const pos) -> void
+ {
+ if (it_pos_ > pos)
+ {
+ reopen_directory(path_.get_path());
+ }
+
+ while (it_pos_ < pos)
+ {
+ if (!read_next(nullptr)) { break; }
+ }
+ }
+
+ auto file_browser_posix::read_next(item* out_item) -> bool
+ {
+ if (!it_) { return false; }
+
+ while (true)
+ {
+ errno = 0;
+ auto const entry = readdir(it_);
+ if (!entry)
+ {
+ if (errno) { perror("file_browser_posix: readdir"); }
+ return false;
+ }
+
+ auto const name = std::string_view{ entry->d_name };
+ if (name.length() > max_name_length) { continue; }
+ if (name[0] == '.') { continue; }
+
+ auto type = item_type::directory;
+ if (entry->d_type == DT_DIR) { type = item_type::directory; }
+ else if (entry->d_type == DT_REG) { type = item_type::file; }
+ else { continue; }
+
+ it_pos_ += 1;
+ if (out_item) { *out_item = item{ type, name }; }
+ return true;
+ }
+ }
+
+ auto file_browser_posix::navigate_up() -> status
+ {
+ auto new_path = path_;
+ if (auto const s = new_path.pop(); s != status::success) { return s; }
+ if (auto const s = reopen_directory(new_path.get_path()); s != status::success) { return s; }
+ path_ = new_path;
+ return status::success;
+ }
+
+ auto file_browser_posix::navigate(std::string_view const item) -> status
+ {
+ auto new_path = path_;
+ if (auto const s = new_path.push(item); s != status::success) { return s; }
+ if (auto const s = reopen_directory(new_path.get_path()); s != status::success) { return s; }
+ path_ = new_path;
+ return status::success;
+ }
+
+ auto file_browser_posix::load(std::string_view const item, span<u8> const buffer, u32* out_length) -> status
+ {
+ auto file_path = path_;
+ if (auto const s = file_path.push(item); s != status::success) { return s; }
+
+ auto const file_path_str = std::string{ file_path.get_path() };
+ auto const fd = open(file_path_str.c_str(), O_RDONLY);
+ if (fd == -1)
+ {
+ perror("file_browser_posix: open");
+ return status::error_system_error;
+ }
+
+ auto const res = read(fd, buffer.get_data(), buffer.get_length());
+ if (res < 0)
+ {
+ perror("file_browser_posix: read");
+ close(fd);
+ return status::error_system_error;
+ }
+ if (out_length) { *out_length = static_cast<u32>(res); }
+
+ auto remaining = char{};
+ if (read(fd, &remaining, 1) != 0)
+ {
+ // File was too long.
+ close(fd);
+ return status::error_invalid_ines_data;
+ }
+
+ close(fd);
+
+ return status::success;
+ }
+
+ auto file_browser_posix::reopen_directory(std::string_view const path) -> status
+ {
+ auto const path_str = std::string{ path };
+ auto const new_it = opendir(path_str.c_str());
+ if (!new_it)
+ {
+ perror("file_browser_posix: opendir");
+ return status::error_system_error;
+ }
+
+ if (it_) { closedir(it_); }
+ it_ = new_it;
+ it_pos_ = 0;
+ return status::success;
+ }
+} // namespace nes::app::mac \ No newline at end of file
diff --git a/app/mac/impl/file-browser-posix.hh b/app/mac/impl/file-browser-posix.hh
new file mode 100644
index 0000000..b760e53
--- /dev/null
+++ b/app/mac/impl/file-browser-posix.hh
@@ -0,0 +1,30 @@
+#pragma once
+
+#include "nes/app/file-browser.hh"
+#include "nes/common/path-builder.hh"
+#include <dirent.h>
+
+namespace nes::app::mac
+{
+ /// File browser implementation using the POSIX directory API.
+ class file_browser_posix final : public file_browser
+ {
+ path_buffer<1024> path_;
+ DIR* it_{ nullptr };
+ u32 it_pos_{ 0 };
+
+ public:
+ explicit file_browser_posix();
+ ~file_browser_posix() override;
+
+ auto get_path() const -> path_view override { return path_.get_components(); }
+ auto seek(u32) -> void override;
+ auto read_next(item* out_item) -> bool override;
+ auto navigate_up() -> status override;
+ auto navigate(std::string_view) -> status override;
+ auto load(std::string_view, span<u8>, u32* out_length) -> status override;
+
+ private:
+ auto reopen_directory(std::string_view path) -> status;
+ };
+} // namespace nes::app::mac \ No newline at end of file
diff --git a/app/mac/impl/input-device-gc-gamepad.hh b/app/mac/impl/input-device-gc-gamepad.hh
new file mode 100644
index 0000000..b4b4a4e
--- /dev/null
+++ b/app/mac/impl/input-device-gc-gamepad.hh
@@ -0,0 +1,24 @@
+#pragma once
+
+#import "nes/app/input/input-device-controller.hh"
+
+#import <GameController/GameController.h>
+
+namespace nes::app::mac
+{
+ /// Input device wrapping a GCExtendedGamepad.
+ class input_device_gc_gamepad final : public input_device_controller
+ {
+ GCExtendedGamepad* profile_;
+
+ public:
+ explicit input_device_gc_gamepad(GCExtendedGamepad* profile);
+
+ auto is_reliable() const -> bool override { return true; }
+ auto get_controller() const -> GCController* { return [profile_ controller]; }
+ auto get_name() const -> std::string_view override;
+ auto get_index() const -> index override;
+ auto set_index(index) -> void override;
+ auto read_buttons() -> sys::button_mask override;
+ };
+} // namespace nes::app::mac \ No newline at end of file
diff --git a/app/mac/impl/input-device-gc-gamepad.mm b/app/mac/impl/input-device-gc-gamepad.mm
new file mode 100644
index 0000000..d2f84ec
--- /dev/null
+++ b/app/mac/impl/input-device-gc-gamepad.mm
@@ -0,0 +1,60 @@
+#import "impl/input-device-gc-gamepad.hh"
+
+namespace nes::app::mac
+{
+ input_device_gc_gamepad::input_device_gc_gamepad(GCExtendedGamepad* profile)
+ : profile_{ profile }
+ {
+ }
+
+ auto input_device_gc_gamepad::get_name() const -> std::string_view
+ {
+ return [[get_controller() vendorName] UTF8String];
+ }
+
+ auto input_device_gc_gamepad::get_index() const -> index
+ {
+ switch ([get_controller() playerIndex])
+ {
+ case GCControllerPlayerIndex1:
+ return index::player_1;
+ case GCControllerPlayerIndex2:
+ return index::player_2;
+ case GCControllerPlayerIndex3:
+ case GCControllerPlayerIndex4:
+ case GCControllerPlayerIndexUnset:
+ break;
+ }
+ return index::unused;
+ }
+
+ auto input_device_gc_gamepad::set_index(index const idx) -> void
+ {
+ switch (idx)
+ {
+ case index::unused:
+ [get_controller() setPlayerIndex:GCControllerPlayerIndexUnset];
+ break;
+ case index::player_1:
+ [get_controller() setPlayerIndex:GCControllerPlayerIndex1];
+ break;
+ case index::player_2:
+ [get_controller() setPlayerIndex:GCControllerPlayerIndex2];
+ break;
+ }
+ }
+
+ auto input_device_gc_gamepad::read_buttons() -> sys::button_mask
+ {
+ auto res = sys::button_mask{};
+ if ([[[profile_ dpad] up] isPressed]) { res.add(sys::buttons::up); }
+ if ([[[profile_ dpad] down] isPressed]) { res.add(sys::buttons::down); }
+ if ([[[profile_ dpad] left] isPressed]) { res.add(sys::buttons::left); }
+ if ([[[profile_ dpad] right] isPressed]) { res.add(sys::buttons::right); }
+ if ([[profile_ buttonA] isPressed]) { res.add(sys::buttons::a); }
+ if ([[profile_ buttonB] isPressed]) { res.add(sys::buttons::b); }
+ if ([[profile_ leftTrigger] isPressed]) { res.add(sys::buttons::select); }
+ if ([[profile_ rightTrigger] isPressed]) { res.add(sys::buttons::start); }
+ return res;
+ }
+} // namespace nes::app::mac \ No newline at end of file
diff --git a/app/mac/impl/input-device-gc-keyboard.hh b/app/mac/impl/input-device-gc-keyboard.hh
new file mode 100644
index 0000000..c879dcc
--- /dev/null
+++ b/app/mac/impl/input-device-gc-keyboard.hh
@@ -0,0 +1,30 @@
+#pragma once
+
+#import "nes/app/input/input-device-keyboard.hh"
+#import "nes/app/input/input-buffer.hh"
+#import <optional>
+
+#import <GameController/GameController.h>
+
+namespace nes::app::mac
+{
+ /// Input device wrapping a GCKeyboard.
+ class input_device_gc_keyboard final : public input_device_keyboard
+ {
+ GCKeyboardInput* profile_{ nil };
+ input_buffer buffer_;
+
+ public:
+ explicit input_device_gc_keyboard(GCKeyboardInput* profile);
+ ~input_device_gc_keyboard() override;
+
+ auto set_profile(GCKeyboardInput* profile) -> void;
+
+ auto read_key(key const key) -> bool override { return buffer_.read_key(key); }
+ auto poll_event() -> input_event override { return buffer_.poll_event(); }
+
+ private:
+ auto handle_key_changed(GCKeyCode key_code, BOOL pressed) -> void;
+ auto convert_key_code(GCKeyCode key_code) -> std::optional<key>;
+ };
+} // namespace nes::app::mac \ No newline at end of file
diff --git a/app/mac/impl/input-device-gc-keyboard.mm b/app/mac/impl/input-device-gc-keyboard.mm
new file mode 100644
index 0000000..34ab98d
--- /dev/null
+++ b/app/mac/impl/input-device-gc-keyboard.mm
@@ -0,0 +1,119 @@
+#import "impl/input-device-gc-keyboard.hh"
+
+namespace nes::app::mac
+{
+ input_device_gc_keyboard::input_device_gc_keyboard(GCKeyboardInput* profile)
+ {
+ set_profile(profile);
+ }
+
+ input_device_gc_keyboard::~input_device_gc_keyboard()
+ {
+ set_profile(nil);
+ }
+
+ auto input_device_gc_keyboard::set_profile(GCKeyboardInput* profile) -> void
+ {
+ [profile_ setKeyChangedHandler:nil];
+ profile_ = profile;
+ [profile_ setKeyChangedHandler:^(GCKeyboardInput*, GCControllerButtonInput*, GCKeyCode key_code, BOOL pressed) {
+ handle_key_changed(key_code, pressed);
+ }];
+ }
+
+ auto input_device_gc_keyboard::handle_key_changed(GCKeyCode const key_code, BOOL const pressed) -> void
+ {
+ auto const key = convert_key_code(key_code);
+ if (key)
+ {
+ if (pressed)
+ {
+ buffer_.key_down(*key);
+ }
+ else
+ {
+ buffer_.key_up(*key);
+ }
+ }
+ }
+
+ auto input_device_gc_keyboard::convert_key_code(GCKeyCode const key_code) -> std::optional<key>
+ {
+ if (key_code == GCKeyCodeZero) { return key::digit_0; }
+ if (key_code == GCKeyCodeOne) { return key::digit_1; }
+ if (key_code == GCKeyCodeTwo) { return key::digit_2; }
+ if (key_code == GCKeyCodeThree) { return key::digit_3; }
+ if (key_code == GCKeyCodeFour) { return key::digit_4; }
+ if (key_code == GCKeyCodeFive) { return key::digit_5; }
+ if (key_code == GCKeyCodeSix) { return key::digit_6; }
+ if (key_code == GCKeyCodeSeven) { return key::digit_7; }
+ if (key_code == GCKeyCodeEight) { return key::digit_8; }
+ if (key_code == GCKeyCodeNine) { return key::digit_9; }
+ if (key_code == GCKeyCodeKeyA) { return key::letter_a; }
+ if (key_code == GCKeyCodeKeyB) { return key::letter_b; }
+ if (key_code == GCKeyCodeKeyC) { return key::letter_c; }
+ if (key_code == GCKeyCodeKeyD) { return key::letter_d; }
+ if (key_code == GCKeyCodeKeyE) { return key::letter_e; }
+ if (key_code == GCKeyCodeKeyF) { return key::letter_f; }
+ if (key_code == GCKeyCodeKeyG) { return key::letter_g; }
+ if (key_code == GCKeyCodeKeyH) { return key::letter_h; }
+ if (key_code == GCKeyCodeKeyI) { return key::letter_i; }
+ if (key_code == GCKeyCodeKeyJ) { return key::letter_j; }
+ if (key_code == GCKeyCodeKeyK) { return key::letter_k; }
+ if (key_code == GCKeyCodeKeyL) { return key::letter_l; }
+ if (key_code == GCKeyCodeKeyM) { return key::letter_m; }
+ if (key_code == GCKeyCodeKeyN) { return key::letter_n; }
+ if (key_code == GCKeyCodeKeyO) { return key::letter_o; }
+ if (key_code == GCKeyCodeKeyP) { return key::letter_p; }
+ if (key_code == GCKeyCodeKeyQ) { return key::letter_q; }
+ if (key_code == GCKeyCodeKeyR) { return key::letter_r; }
+ if (key_code == GCKeyCodeKeyS) { return key::letter_s; }
+ if (key_code == GCKeyCodeKeyT) { return key::letter_t; }
+ if (key_code == GCKeyCodeKeyU) { return key::letter_u; }
+ if (key_code == GCKeyCodeKeyV) { return key::letter_v; }
+ if (key_code == GCKeyCodeKeyW) { return key::letter_w; }
+ if (key_code == GCKeyCodeKeyX) { return key::letter_x; }
+ if (key_code == GCKeyCodeKeyY) { return key::letter_y; }
+ if (key_code == GCKeyCodeKeyZ) { return key::letter_z; }
+ if (key_code == GCKeyCodeEscape) { return key::escape; }
+ if (key_code == GCKeyCodeHyphen) { return key::dash; }
+ if (key_code == GCKeyCodeEqualSign) { return key::equal; }
+ if (key_code == GCKeyCodeDeleteOrBackspace) { return key::backspace; }
+ if (key_code == GCKeyCodeTab) { return key::tab; }
+ if (key_code == GCKeyCodeReturnOrEnter) { return key::enter; }
+ if (key_code == GCKeyCodeSemicolon) { return key::semicolon; }
+ if (key_code == GCKeyCodeQuote) { return key::apostrophe; }
+ if (key_code == GCKeyCodeBackslash) { return key::backslash; }
+ if (key_code == GCKeyCodeComma) { return key::comma; }
+ if (key_code == GCKeyCodePeriod) { return key::period; }
+ if (key_code == GCKeyCodeSlash) { return key::slash; }
+ if (key_code == GCKeyCodeSpacebar) { return key::space; }
+ if (key_code == GCKeyCodeCapsLock) { return key::caps_lock; }
+ if (key_code == GCKeyCodeOpenBracket) { return key::bracket_open; }
+ if (key_code == GCKeyCodeCloseBracket) { return key::bracket_close; }
+ if (key_code == GCKeyCodeLeftControl) { return key::control_left; }
+ if (key_code == GCKeyCodeRightControl) { return key::control_right; }
+ if (key_code == GCKeyCodeLeftShift) { return key::shift_left; }
+ if (key_code == GCKeyCodeRightShift) { return key::shift_right; }
+ if (key_code == GCKeyCodeLeftAlt) { return key::alt_left; }
+ if (key_code == GCKeyCodeRightAlt) { return key::alt_right; }
+ if (key_code == GCKeyCodeLeftArrow) { return key::arrow_left; }
+ if (key_code == GCKeyCodeRightArrow) { return key::arrow_right; }
+ if (key_code == GCKeyCodeUpArrow) { return key::arrow_up; }
+ if (key_code == GCKeyCodeDownArrow) { return key::arrow_down; }
+ if (key_code == GCKeyCodeF1) { return key::f1; }
+ if (key_code == GCKeyCodeF2) { return key::f2; }
+ if (key_code == GCKeyCodeF3) { return key::f3; }
+ if (key_code == GCKeyCodeF4) { return key::f4; }
+ if (key_code == GCKeyCodeF5) { return key::f5; }
+ if (key_code == GCKeyCodeF6) { return key::f6; }
+ if (key_code == GCKeyCodeF7) { return key::f7; }
+ if (key_code == GCKeyCodeF8) { return key::f8; }
+ if (key_code == GCKeyCodeF9) { return key::f9; }
+ if (key_code == GCKeyCodeF10) { return key::f10; }
+ if (key_code == GCKeyCodeF11) { return key::f11; }
+ if (key_code == GCKeyCodeF12) { return key::f12; }
+
+ return std::nullopt;
+ }
+} // namespace nes::app::mac \ No newline at end of file
diff --git a/app/mac/impl/input-device-serial-controller.cc b/app/mac/impl/input-device-serial-controller.cc
new file mode 100644
index 0000000..7a493a7
--- /dev/null
+++ b/app/mac/impl/input-device-serial-controller.cc
@@ -0,0 +1,96 @@
+#include "impl/input-device-serial-controller.hh"
+#include <stdio.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <termios.h>
+#include <sys/ioctl.h>
+
+namespace nes::app::mac
+{
+ input_device_serial_controller::input_device_serial_controller(char const* path)
+ : path_{ path }
+ {
+ fd_ = open(path, O_RDONLY | O_NOCTTY | O_NDELAY | O_NONBLOCK);
+ if (fd_ == -1)
+ {
+ perror("input_device_serial_controller: open");
+ return;
+ }
+
+ if (!set_up()) { close(); }
+ }
+
+ input_device_serial_controller::~input_device_serial_controller()
+ {
+ close();
+ }
+
+ auto input_device_serial_controller::is_reliable() const -> bool
+ {
+ return fd_ != -1;
+ }
+
+ auto input_device_serial_controller::read_buttons() -> sys::button_mask
+ {
+ if (fd_ == -1) { return sys::button_mask{}; }
+
+ // Poll for any updates until we can't read anymore.
+ while (true)
+ {
+ auto buf = u8{};
+ auto const res = read(fd_, &buf, 1);
+ if (res == -1)
+ {
+ if (errno == EAGAIN || errno == EWOULDBLOCK) { break; }
+
+ perror("read from controller");
+ close();
+ break;
+ }
+
+ current_ = sys::button_mask::from_raw_value(buf);
+ }
+
+ return current_;
+ }
+
+
+ auto input_device_serial_controller::set_up() -> bool
+ {
+ if (ioctl(fd_, TIOCEXCL) == -1)
+ {
+ perror("input_device_serial_controller: ioctl");
+ return false;
+ }
+
+ auto attr = termios{};
+ if (tcgetattr(fd_, &attr) == -1)
+ {
+ perror("input_device_serial_controller: tcgetattr");
+ return false;
+ }
+
+ cfsetispeed(&attr, B57600);
+ cfsetospeed(&attr, B57600);
+ attr.c_cflag = attr.c_cflag | CLOCAL | CREAD;
+ attr.c_cflag = (attr.c_cflag & static_cast<tcflag_t>(~CSIZE)) | CS8;
+
+ if (tcsetattr(fd_, TCSANOW, &attr) == -1)
+ {
+ perror("input_device_serial_controller: tcsetattr");
+ return false;
+ }
+
+ return true;
+ }
+
+ auto input_device_serial_controller::close() -> void
+ {
+ if (fd_ != -1)
+ {
+ ::close(fd_);
+ fd_ = -1;
+ }
+ }
+} // namespace nes::app::mac \ No newline at end of file
diff --git a/app/mac/impl/input-device-serial-controller.hh b/app/mac/impl/input-device-serial-controller.hh
new file mode 100644
index 0000000..2022ed2
--- /dev/null
+++ b/app/mac/impl/input-device-serial-controller.hh
@@ -0,0 +1,29 @@
+#pragma once
+
+#include "nes/app/input/input-device-controller.hh"
+
+namespace nes::app::mac
+{
+ /// Input device for an NES controller plugged in via serial port.
+ class input_device_serial_controller final : public input_device_controller
+ {
+ char const* path_;
+ int fd_{ -1 };
+ sys::button_mask current_{};
+ index index_{ index::unused };
+
+ public:
+ explicit input_device_serial_controller(char const* path);
+ ~input_device_serial_controller() override;
+
+ auto is_reliable() const -> bool override;
+ auto get_name() const -> std::string_view override { return path_; }
+ auto get_index() const -> index override { return index_; }
+ auto set_index(index const v) -> void override { index_ = v; }
+ auto read_buttons() -> sys::button_mask override;
+
+ private:
+ auto set_up() -> bool;
+ auto close() -> void;
+ };
+} // namespace nes::app:mac \ No newline at end of file
diff --git a/app/mac/scene.mm b/app/mac/scene.mm
index 901af64..3650156 100644
--- a/app/mac/scene.mm
+++ b/app/mac/scene.mm
@@ -1,4 +1,9 @@
#import "scene.hh"
+#import "impl/display-spritekit.hh"
+#import "impl/file-browser-posix.hh"
+#import "impl/input-device-gc-keyboard.hh"
+#import "impl/input-device-gc-gamepad.hh"
+#import "impl/input-device-serial-controller.hh"
#import "nes/app/application.hh"
#import "nes/app/input/input-device-keyboard.hh"
#import "nes/app/input/input-device-controller.hh"
@@ -8,494 +13,20 @@
#import "nes/common/path-view.hh"
#import "nes/common/path-builder.hh"
#import <memory>
-#import <iostream>
#import <optional>
-#import <fcntl.h>
-#import <unistd.h>
-#import <termios.h>
-#import <dirent.h>
-#import <sys/ioctl.h>
namespace
{
- class display_impl final : public nes::display
- {
- SKSpriteNode* node_;
- NSMutableData* buffer_front_;
- NSMutableData* buffer_back_;
- nes::u8* bytes_back_;
-
- public:
- explicit display_impl(SKSpriteNode* node)
- : node_{ node }
- , buffer_front_{ make_buffer() }
- , buffer_back_{ make_buffer() }
- , bytes_back_{ static_cast<nes::u8*>([buffer_back_ mutableBytes]) }
- {
- }
-
- ~display_impl() = default;
-
- virtual auto switch_buffers() -> void override
- {
- auto tmp = buffer_back_;
- buffer_back_ = buffer_front_;
- buffer_front_ = tmp;
- bytes_back_ = static_cast<nes::u8*>([buffer_back_ mutableBytes]);
-
- auto texture = [SKTexture textureWithData:buffer_front_ size:CGSizeMake(width, height) flipped:YES];
- [texture setFilteringMode:SKTextureFilteringNearest];
- [node_ setTexture:texture];
- }
-
- virtual auto set(nes::u32 x, nes::u32 y, nes::rgb value) -> void override
- {
- auto const offset = (y * width + x) * 4;
- bytes_back_[offset + 0] = value.r;
- bytes_back_[offset + 1] = value.g;
- bytes_back_[offset + 2] = value.b;
- bytes_back_[offset + 3] = 255;
- }
-
- virtual auto get(nes::u32 x, nes::u32 y) const -> nes::rgb override
- {
- auto const offset = (y * width + x) * 4;
- auto res = nes::rgb{};
- res.r = bytes_back_[offset + 0];
- res.g = bytes_back_[offset + 1];
- res.b = bytes_back_[offset + 2];
- return res;
- }
-
- private:
- static auto make_buffer() -> NSMutableData*
- {
- auto const length = NSUInteger{ width * height * 4 };
- return [NSMutableData dataWithLength:length];
- }
- };
-
- class input_device_keyboard_impl final : public nes::app::input_device_keyboard
- {
- GCKeyboardInput* profile_{ nil };
- nes::app::input_buffer buffer_;
-
- public:
- explicit input_device_keyboard_impl(GCKeyboardInput* profile)
- {
- set_profile(profile);
- }
-
- ~input_device_keyboard_impl()
- {
- set_profile(nil);
- }
-
- auto set_profile(GCKeyboardInput* profile) -> void
- {
- [profile_ setKeyChangedHandler:nil];
- profile_ = profile;
- [profile_ setKeyChangedHandler:^(GCKeyboardInput*, GCControllerButtonInput*, GCKeyCode key_code, BOOL pressed) {
- handle_key_changed(key_code, pressed);
- }];
- }
-
- auto read_key(nes::app::key const key) -> bool override { return buffer_.read_key(key); }
- auto poll_event() -> nes::app::input_event override { return buffer_.poll_event(); }
-
- private:
- auto handle_key_changed(GCKeyCode const key_code, BOOL const pressed) -> void
- {
- auto const key = convert_key_code(key_code);
- if (key)
- {
- if (pressed)
- {
- buffer_.key_down(*key);
- }
- else
- {
- buffer_.key_up(*key);
- }
- }
- }
-
- auto convert_key_code(GCKeyCode const key_code) -> std::optional<nes::app::key>
- {
- if (key_code == GCKeyCodeZero) { return nes::app::key::digit_0; }
- if (key_code == GCKeyCodeOne) { return nes::app::key::digit_1; }
- if (key_code == GCKeyCodeTwo) { return nes::app::key::digit_2; }
- if (key_code == GCKeyCodeThree) { return nes::app::key::digit_3; }
- if (key_code == GCKeyCodeFour) { return nes::app::key::digit_4; }
- if (key_code == GCKeyCodeFive) { return nes::app::key::digit_5; }
- if (key_code == GCKeyCodeSix) { return nes::app::key::digit_6; }
- if (key_code == GCKeyCodeSeven) { return nes::app::key::digit_7; }
- if (key_code == GCKeyCodeEight) { return nes::app::key::digit_8; }
- if (key_code == GCKeyCodeNine) { return nes::app::key::digit_9; }
- if (key_code == GCKeyCodeKeyA) { return nes::app::key::letter_a; }
- if (key_code == GCKeyCodeKeyB) { return nes::app::key::letter_b; }
- if (key_code == GCKeyCodeKeyC) { return nes::app::key::letter_c; }
- if (key_code == GCKeyCodeKeyD) { return nes::app::key::letter_d; }
- if (key_code == GCKeyCodeKeyE) { return nes::app::key::letter_e; }
- if (key_code == GCKeyCodeKeyF) { return nes::app::key::letter_f; }
- if (key_code == GCKeyCodeKeyG) { return nes::app::key::letter_g; }
- if (key_code == GCKeyCodeKeyH) { return nes::app::key::letter_h; }
- if (key_code == GCKeyCodeKeyI) { return nes::app::key::letter_i; }
- if (key_code == GCKeyCodeKeyJ) { return nes::app::key::letter_j; }
- if (key_code == GCKeyCodeKeyK) { return nes::app::key::letter_k; }
- if (key_code == GCKeyCodeKeyL) { return nes::app::key::letter_l; }
- if (key_code == GCKeyCodeKeyM) { return nes::app::key::letter_m; }
- if (key_code == GCKeyCodeKeyN) { return nes::app::key::letter_n; }
- if (key_code == GCKeyCodeKeyO) { return nes::app::key::letter_o; }
- if (key_code == GCKeyCodeKeyP) { return nes::app::key::letter_p; }
- if (key_code == GCKeyCodeKeyQ) { return nes::app::key::letter_q; }
- if (key_code == GCKeyCodeKeyR) { return nes::app::key::letter_r; }
- if (key_code == GCKeyCodeKeyS) { return nes::app::key::letter_s; }
- if (key_code == GCKeyCodeKeyT) { return nes::app::key::letter_t; }
- if (key_code == GCKeyCodeKeyU) { return nes::app::key::letter_u; }
- if (key_code == GCKeyCodeKeyV) { return nes::app::key::letter_v; }
- if (key_code == GCKeyCodeKeyW) { return nes::app::key::letter_w; }
- if (key_code == GCKeyCodeKeyX) { return nes::app::key::letter_x; }
- if (key_code == GCKeyCodeKeyY) { return nes::app::key::letter_y; }
- if (key_code == GCKeyCodeKeyZ) { return nes::app::key::letter_z; }
- if (key_code == GCKeyCodeEscape) { return nes::app::key::escape; }
- if (key_code == GCKeyCodeHyphen) { return nes::app::key::dash; }
- if (key_code == GCKeyCodeEqualSign) { return nes::app::key::equal; }
- if (key_code == GCKeyCodeDeleteOrBackspace) { return nes::app::key::backspace; }
- if (key_code == GCKeyCodeTab) { return nes::app::key::tab; }
- if (key_code == GCKeyCodeReturnOrEnter) { return nes::app::key::enter; }
- if (key_code == GCKeyCodeSemicolon) { return nes::app::key::semicolon; }
- if (key_code == GCKeyCodeQuote) { return nes::app::key::apostrophe; }
- if (key_code == GCKeyCodeBackslash) { return nes::app::key::backslash; }
- if (key_code == GCKeyCodeComma) { return nes::app::key::comma; }
- if (key_code == GCKeyCodePeriod) { return nes::app::key::period; }
- if (key_code == GCKeyCodeSlash) { return nes::app::key::slash; }
- if (key_code == GCKeyCodeSpacebar) { return nes::app::key::space; }
- if (key_code == GCKeyCodeCapsLock) { return nes::app::key::caps_lock; }
- if (key_code == GCKeyCodeOpenBracket) { return nes::app::key::bracket_open; }
- if (key_code == GCKeyCodeCloseBracket) { return nes::app::key::bracket_close; }
- if (key_code == GCKeyCodeLeftControl) { return nes::app::key::control_left; }
- if (key_code == GCKeyCodeRightControl) { return nes::app::key::control_right; }
- if (key_code == GCKeyCodeLeftShift) { return nes::app::key::shift_left; }
- if (key_code == GCKeyCodeRightShift) { return nes::app::key::shift_right; }
- if (key_code == GCKeyCodeLeftAlt) { return nes::app::key::alt_left; }
- if (key_code == GCKeyCodeRightAlt) { return nes::app::key::alt_right; }
- if (key_code == GCKeyCodeLeftArrow) { return nes::app::key::arrow_left; }
- if (key_code == GCKeyCodeRightArrow) { return nes::app::key::arrow_right; }
- if (key_code == GCKeyCodeUpArrow) { return nes::app::key::arrow_up; }
- if (key_code == GCKeyCodeDownArrow) { return nes::app::key::arrow_down; }
- if (key_code == GCKeyCodeF1) { return nes::app::key::f1; }
- if (key_code == GCKeyCodeF2) { return nes::app::key::f2; }
- if (key_code == GCKeyCodeF3) { return nes::app::key::f3; }
- if (key_code == GCKeyCodeF4) { return nes::app::key::f4; }
- if (key_code == GCKeyCodeF5) { return nes::app::key::f5; }
- if (key_code == GCKeyCodeF6) { return nes::app::key::f6; }
- if (key_code == GCKeyCodeF7) { return nes::app::key::f7; }
- if (key_code == GCKeyCodeF8) { return nes::app::key::f8; }
- if (key_code == GCKeyCodeF9) { return nes::app::key::f9; }
- if (key_code == GCKeyCodeF10) { return nes::app::key::f10; }
- if (key_code == GCKeyCodeF11) { return nes::app::key::f11; }
- if (key_code == GCKeyCodeF12) { return nes::app::key::f12; }
-
- return std::nullopt;
- }
- };
-
- class input_device_controller_impl final : public nes::app::input_device_controller
- {
- GCExtendedGamepad* profile_;
-
- public:
- explicit input_device_controller_impl(GCExtendedGamepad* profile)
- : profile_{ profile }
- {
- }
-
- auto is_reliable() const -> bool override { return true; }
- auto get_controller() const -> GCController* { return [profile_ controller]; }
- auto get_name() const -> std::string_view override { return [[get_controller() vendorName] UTF8String]; }
-
- auto get_index() const -> index override
- {
- switch ([get_controller() playerIndex])
- {
- case GCControllerPlayerIndex1:
- return index::player_1;
- case GCControllerPlayerIndex2:
- return index::player_2;
- case GCControllerPlayerIndex3:
- case GCControllerPlayerIndex4:
- case GCControllerPlayerIndexUnset:
- break;
- }
- return index::unused;
- }
-
- auto set_index(index idx) -> void override
- {
- switch (idx)
- {
- case index::unused:
- [get_controller() setPlayerIndex:GCControllerPlayerIndexUnset];
- break;
- case index::player_1:
- [get_controller() setPlayerIndex:GCControllerPlayerIndex1];
- break;
- case index::player_2:
- [get_controller() setPlayerIndex:GCControllerPlayerIndex2];
- break;
- }
- }
-
- auto read_buttons() -> nes::sys::button_mask override
- {
- auto res = nes::sys::button_mask{};
- if ([[[profile_ dpad] up] isPressed]) { res.add(nes::sys::buttons::up); }
- if ([[[profile_ dpad] down] isPressed]) { res.add(nes::sys::buttons::down); }
- if ([[[profile_ dpad] left] isPressed]) { res.add(nes::sys::buttons::left); }
- if ([[[profile_ dpad] right] isPressed]) { res.add(nes::sys::buttons::right); }
- if ([[profile_ buttonA] isPressed]) { res.add(nes::sys::buttons::a); }
- if ([[profile_ buttonB] isPressed]) { res.add(nes::sys::buttons::b); }
- if ([[profile_ leftTrigger] isPressed]) { res.add(nes::sys::buttons::select); }
- if ([[profile_ rightTrigger] isPressed]) { res.add(nes::sys::buttons::start); }
- return res;
- }
- };
-
- class input_device_controller_serial : public nes::app::input_device_controller
- {
- char const* path_;
- int fd_{ 0 };
- nes::sys::button_mask current_{};
- index index_{ index::unused };
-
- public:
- explicit input_device_controller_serial(char const* path)
- : path_{ path }
- {
- fd_ = open(path, O_RDONLY | O_NOCTTY | O_NDELAY | O_NONBLOCK);
- if (fd_ == -1)
- {
- perror("open controller");
- return;
- }
-
- if (!setup()) { close(); }
- }
-
- ~input_device_controller_serial() override
- {
- close();
- }
-
- auto read_buttons() -> nes::sys::button_mask override
- {
- if (fd_ == -1) { return nes::sys::button_mask{}; }
-
- // Poll for any updates until we can't read anymore.
- while (true)
- {
- auto buf = nes::u8{};
- auto res = read(fd_, &buf, 1);
- if (res == -1)
- {
- if (errno == EAGAIN || errno == EWOULDBLOCK) { break; }
-
- perror("read from controller");
- close();
- break;
- }
-
- current_ = nes::sys::button_mask::from_raw_value(buf);
- }
-
- return current_;
- }
-
- auto is_reliable() const -> bool override { return fd_ != -1; }
- auto get_name() const -> std::string_view override { return path_; }
- auto set_index(index const index) -> void override { index_ = index; }
- auto get_index() const -> index override { return index_; }
-
- private:
- auto close() -> void
- {
- if (fd_ != -1)
- {
- ::close(fd_);
- fd_ = -1;
- }
- }
-
- auto setup() -> bool
- {
- if (ioctl(fd_, TIOCEXCL) == -1)
- {
- perror("ioctl for controller");
- return false;
- }
-
- auto attr = termios{};
- if (tcgetattr(fd_, &attr) == -1)
- {
- perror("tcgetattr for controller");
- return false;
- }
-
- cfsetispeed(&attr, B57600);
- cfsetospeed(&attr, B57600);
- attr.c_cflag = attr.c_cflag | CLOCAL | CREAD;
- attr.c_cflag = (attr.c_cflag & static_cast<tcflag_t>(~CSIZE)) | CS8;
-
- if (tcsetattr(fd_, TCSANOW, &attr) == -1)
- {
- perror("tcsetattr for controller");
- return false;
- }
-
- return true;
- }
- };
-
- class file_browser_impl final : public nes::app::file_browser
- {
- nes::path_buffer<256> path_;
- DIR* it_{ nullptr };
- nes::u32 it_pos_{ 0 };
-
- public:
- explicit file_browser_impl()
- {
- reopen_dir();
- }
-
- ~file_browser_impl() override
- {
- closedir(it_);
- }
-
- auto get_path() const -> nes::path_view override
- {
- return path_.get_components();
- }
-
- auto seek(nes::u32 const position) -> void override
- {
- if (it_pos_ > position)
- {
- reopen_dir();
- }
-
- while (it_pos_ < position)
- {
- if (!read_next(nullptr)) { break; }
- }
- }
-
- auto read_next(item* out_item) -> bool override
- {
- if (!it_) { return false; }
-
- while (true)
- {
- errno = 0;
- auto entry = readdir(it_);
- if (!entry)
- {
- if (errno)
- {
- perror("readdir");
- std::abort();
- }
-
- return false;
- }
-
- auto const name = std::string_view{ entry->d_name };
- if (name.length() > max_name_length) { continue; }
- if (name[0] == '.') { continue; }
-
- auto type = item_type::directory;
- if (entry->d_type == DT_DIR) { type = item_type::directory; }
- else if (entry->d_type == DT_REG) { type = item_type::file; }
- else { continue; }
-
- it_pos_ += 1;
- if (out_item) { *out_item = item{ type, name }; }
- return true;
- }
- }
-
- auto navigate_up() -> void override
- {
- if (!path_.pop()) { return; }
-
- reopen_dir();
- }
-
- auto navigate(std::string_view const item) -> void override
- {
- if (!path_.push(item)) { std::abort(); }
-
- reopen_dir();
- }
-
- auto load(std::string_view const item, nes::span<nes::u8> const buffer) -> nes::u32 override
- {
- auto file_path = path_;
- if (!file_path.push(item)) { std::abort(); }
-
- auto const path = std::string{ file_path.get_path() };
- auto const fd = open(path.c_str(), O_RDONLY);
- if (fd == -1)
- {
- perror("open");
- std::abort();
- }
-
- auto const res = read(fd, buffer.get_data(), buffer.get_length());
- if (res < 0)
- {
- perror("read");
- std::abort();
- }
-
- auto remaining = char{};
- if (read(fd, &remaining, 1) != 0)
- {
- std::cerr << "Expected EOF" << std::endl;
- std::abort();
- }
-
- close(fd);
-
- return static_cast<nes::u32>(res);
- }
-
- private:
- auto reopen_dir() -> void
- {
- if (it_) { closedir(it_); }
-
- auto const path = std::string{ path_.get_path() };
- it_ = opendir(path.c_str());
- if (!it_)
- {
- perror("opendir");
- std::abort();
- }
- it_pos_ = 0;
- }
- };
+ constexpr auto const serial_controller_path = "/dev/cu.usbserial-FTB6SPL3";
} // namespace
@implementation Scene
{
- std::unique_ptr<display_impl> _display;
- std::unique_ptr<file_browser_impl> _fileBrowser;
- std::unique_ptr<input_device_keyboard_impl> _keyboard;
- std::unique_ptr<input_device_controller_serial> _serialController;
- std::vector<std::unique_ptr<input_device_controller_impl>> _controllers;
+ std::unique_ptr<nes::app::mac::display_spritekit> _display;
+ std::unique_ptr<nes::app::mac::file_browser_posix> _fileBrowser;
+ std::unique_ptr<nes::app::mac::input_device_serial_controller> _serialController;
+ std::unique_ptr<nes::app::mac::input_device_gc_keyboard> _keyboard;
+ std::vector<std::unique_ptr<nes::app::mac::input_device_gc_gamepad>> _controllers;
std::unique_ptr<nes::app::application> _app;
std::optional<NSTimeInterval> _lastTimestamp;
}
@@ -512,10 +43,10 @@ namespace
[self setScaleMode:SKSceneScaleModeAspectFit];
[self addChild:node];
- _display = std::make_unique<display_impl>(node);
- _fileBrowser = std::make_unique<file_browser_impl>();
- _keyboard = std::make_unique<input_device_keyboard_impl>([[GCKeyboard coalescedKeyboard] keyboardInput]);
- _serialController = std::make_unique<input_device_controller_serial>("/dev/cu.usbserial-FTB6SPL3");
+ _display = std::make_unique<nes::app::mac::display_spritekit>(node);
+ _fileBrowser = std::make_unique<nes::app::mac::file_browser_posix>();
+ _serialController = std::make_unique<nes::app::mac::input_device_serial_controller>(serial_controller_path);
+ _keyboard = std::make_unique<nes::app::mac::input_device_gc_keyboard>([[GCKeyboard coalescedKeyboard] keyboardInput]);
_app = std::make_unique<nes::app::application>(*_display, *_keyboard, *_fileBrowser);
_app->add_controller(*_serialController);
@@ -525,7 +56,7 @@ namespace
auto gamepad = [controller extendedGamepad];
if (gamepad)
{
- auto adapter = std::make_unique<input_device_controller_impl>(gamepad);
+ auto adapter = std::make_unique<nes::app::mac::input_device_gc_gamepad>(gamepad);
_app->add_controller(*adapter);
_controllers.emplace_back(std::move(adapter));
}
@@ -582,7 +113,7 @@ namespace
auto gamepad = [controller extendedGamepad];
if (gamepad)
{
- auto adapter = std::make_unique<input_device_controller_impl>(gamepad);
+ auto adapter = std::make_unique<nes::app::mac::input_device_gc_gamepad>(gamepad);
_app->add_controller(*adapter);
_controllers.emplace_back(std::move(adapter));
}
diff --git a/lib/nes/app/application.cc b/lib/nes/app/application.cc
index b528e61..6b7253e 100644
--- a/lib/nes/app/application.cc
+++ b/lib/nes/app/application.cc
@@ -46,7 +46,7 @@ namespace nes::app
// display_proxy::switch_buffers).
console_->step(elapsed_time);
- if (console_->get_status() != sys::status::success)
+ if (console_->get_status() != status::success)
{
std::cerr << "Invalid state: " << to_string(console_->get_status()) << std::endl;
std::abort();
@@ -93,10 +93,15 @@ namespace nes::app
case action::type::launch_game:
{
u8 buffer[sys::cartridge::max_file_size];
- auto const length = file_browser_.load(a.get_file_name(), buffer);
+ auto length = u32{ 0 };
+ if (auto const s = file_browser_.load(a.get_file_name(), buffer, &length); s != status::success)
+ {
+ std::cerr << "Unable to open cartridge file: " << to_string(console_->get_status()) << std::endl;
+ std::abort();
+ }
- console_.emplace(display_, span<u8 const>{ buffer, length });
- if (console_->get_status() != sys::status::success)
+ console_.emplace(display_, span{ buffer, length });
+ if (console_->get_status() != status::success)
{
std::cerr << "Unable to load cartridge: " << to_string(console_->get_status()) << std::endl;
std::abort();
@@ -105,6 +110,11 @@ namespace nes::app
display_.screen = nullptr;
break;
}
+ case action::type::show_error:
+ {
+ // TODO
+ break;
+ }
}
}
} // namespace nes::app \ No newline at end of file
diff --git a/lib/nes/app/file-browser.hh b/lib/nes/app/file-browser.hh
index 6ce55aa..ef5343c 100644
--- a/lib/nes/app/file-browser.hh
+++ b/lib/nes/app/file-browser.hh
@@ -2,6 +2,8 @@
#include "nes/common/types.hh"
#include "nes/common/span.hh"
+#include "nes/common/status.hh"
+#include <string_view>
namespace nes
{
@@ -10,6 +12,7 @@ namespace nes
namespace nes::app
{
+ /// A file browser that has a current directory whose items are iterated.
class file_browser
{
public:
@@ -46,12 +49,23 @@ namespace nes::app
auto operator=(file_browser const&) -> file_browser& = delete;
auto operator=(file_browser&&) -> file_browser& = delete;
+ /// Get the path components of the current working directory.
virtual auto get_path() const -> path_view = 0;
+
+ /// Go to directory entry at the given index.
virtual auto seek(u32) -> void = 0;
+
+ /// Read the directory entry at the current index.
virtual auto read_next(item* out_item) -> bool = 0;
- virtual auto navigate_up() -> void = 0;
- virtual auto navigate(std::string_view) -> void = 0;
- virtual auto load(std::string_view, span<u8>) -> u32 = 0;
+
+ /// Change the current working directory to the parent directory.
+ virtual auto navigate_up() -> status = 0;
+
+ /// Change the current working directory to an item.
+ virtual auto navigate(std::string_view) -> status = 0;
+
+ /// Load a file in the current working directory into memory.
+ virtual auto load(std::string_view, span<u8>, u32* out_length) -> status = 0;
protected:
explicit file_browser() = default;
diff --git a/lib/nes/app/input/input-buffer.cc b/lib/nes/app/input/input-buffer.cc
index e8da51c..a9ccf2b 100644
--- a/lib/nes/app/input/input-buffer.cc
+++ b/lib/nes/app/input/input-buffer.cc
@@ -23,7 +23,7 @@ namespace nes::app
auto it = event_queue_.begin();
if (it == event_queue_.end()) { return input_event{}; }
- auto const event = *it;
+ auto const event = it;
auto const k = get_key(*event);
auto const pressed = event->pressed;
event_queue_.remove(it);
diff --git a/lib/nes/app/input/input-manager.cc b/lib/nes/app/input/input-manager.cc
index 1af0f5b..ceb6690 100644
--- a/lib/nes/app/input/input-manager.cc
+++ b/lib/nes/app/input/input-manager.cc
@@ -68,8 +68,7 @@ namespace nes::app
// 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)
+ if (it->is_reliable() && it->get_index() == input_device_controller::index::unused)
{
break;
}
@@ -79,9 +78,9 @@ namespace nes::app
if (it != controllers_.end())
{
// We've found an unused controller -> assign it.
- inputs_[i].controller = *it;
+ inputs_[i].controller = &*it;
inputs_[i].controller->set_index(static_cast<input_device_controller::index>(i));
- inputs_[i].device = *it;
+ inputs_[i].device = &*it;
}
else
{
diff --git a/lib/nes/app/ui/action.hh b/lib/nes/app/ui/action.hh
index b701606..66f77f8 100644
--- a/lib/nes/app/ui/action.hh
+++ b/lib/nes/app/ui/action.hh
@@ -1,5 +1,6 @@
#pragma once
+#include "nes/common/status.hh"
#include <string_view>
namespace nes::app
@@ -16,28 +17,41 @@ namespace nes::app
go_to_settings,
go_to_help,
launch_game,
+ show_error,
};
private:
type type_{ type::none };
std::string_view file_name_;
+ status error_{ status::success };
public:
explicit action() = default;
- static auto go_to_title() -> action { return action{ type::go_to_title, "" }; }
- static auto go_to_browser() -> action { return action{ type::go_to_browser, "" }; }
- static auto go_to_settings() -> action { return action{ type::go_to_settings, "" }; }
- static auto go_to_help() -> action { return action{ type::go_to_help, "" }; }
- static auto launch_game(std::string_view const file_name) -> action { return action{ type::launch_game, file_name }; }
+ static auto go_to_title() -> action { return action{ type::go_to_title, "", status::success }; }
+ static auto go_to_browser() -> action { return action{ type::go_to_browser, "", status::success }; }
+ static auto go_to_settings() -> action { return action{ type::go_to_settings, "", status::success }; }
+ static auto go_to_help() -> action { return action{ type::go_to_help, "", status::success }; }
+
+ static auto launch_game(std::string_view const file_name) -> action
+ {
+ return action{ type::launch_game, file_name, status::success };
+ }
+
+ static auto show_error(status const error)
+ {
+ return action{ type::show_error, "", error };
+ }
auto get_type() const -> type { return type_; }
auto get_file_name() const -> std::string_view { return file_name_; }
+ auto get_error() const -> status { return error_; }
private:
- explicit action(type const type, std::string_view const file_name)
+ explicit action(type const type, std::string_view const file_name, status const error)
: type_{ type }
, file_name_{ file_name }
+ , error_{ error }
{
}
};
diff --git a/lib/nes/app/ui/screen-browser.cc b/lib/nes/app/ui/screen-browser.cc
index 6a18d3c..14ddc23 100644
--- a/lib/nes/app/ui/screen-browser.cc
+++ b/lib/nes/app/ui/screen-browser.cc
@@ -99,7 +99,10 @@ namespace nes::app
{
if (navigation_steps_ > 0)
{
- file_browser_.navigate_up();
+ if (auto const s = file_browser_.navigate_up(); s != status::success)
+ {
+ return action::show_error(s);
+ }
refresh();
navigation_steps_ -= 1;
}
@@ -115,22 +118,25 @@ namespace nes::app
{
if (selected->is_parent)
{
- file_browser_.navigate_up();
+ if (auto const s = file_browser_.navigate_up(); s != status::success)
+ {
+ return action::show_error(s);
+ }
refresh();
if (navigation_steps_ > 0) { navigation_steps_ -= 1; }
}
- else
+ else if (selected->data.get_type() == file_browser::item_type::directory)
{
- switch (selected->data.get_type())
+ if (auto const s = file_browser_.navigate(selected->data.get_name()); s != status::success)
{
- case file_browser::item_type::directory:
- file_browser_.navigate(selected->data.get_name());
- refresh();
- navigation_steps_ += 1;
- break;
- case file_browser::item_type::file:
- return action::launch_game(selected->data.get_name());
+ return action::show_error(s);
}
+ refresh();
+ navigation_steps_ += 1;
+ }
+ else if (selected->data.get_type() == file_browser::item_type::file)
+ {
+ return action::launch_game(selected->data.get_name());
}
}
}
diff --git a/lib/nes/common/CMakeLists.txt b/lib/nes/common/CMakeLists.txt
index a7c1d76..a5703ff 100644
--- a/lib/nes/common/CMakeLists.txt
+++ b/lib/nes/common/CMakeLists.txt
@@ -13,4 +13,5 @@ target_sources(
path-view.cc
path-builder.hh
path-builder.cc
- span.hh) \ No newline at end of file
+ span.hh
+ status.hh) \ No newline at end of file
diff --git a/lib/nes/common/box.hh b/lib/nes/common/box.hh
index 3a65613..7bf2848 100644
--- a/lib/nes/common/box.hh
+++ b/lib/nes/common/box.hh
@@ -1,6 +1,7 @@
#pragma once
#include "nes/common/types.hh"
+#include "nes/common/debug.hh"
namespace nes
{
@@ -28,10 +29,30 @@ namespace nes
auto operator=(box const&) -> box& = delete;
auto operator=(box&&) -> box& = delete;
- auto operator*() -> T& { return *get(); }
- auto operator*() const -> T const& { return *get(); }
- auto operator->() -> T* { return get(); }
- auto operator->() const -> T const* { return get(); }
+ auto operator*() -> T&
+ {
+ NES_ASSERT(has_value() && "expected box to be non-empty");
+ return *get();
+ }
+
+ auto operator*() const -> T const&
+ {
+ NES_ASSERT(has_value() && "expected box to be non-empty");
+ return *get();
+ }
+
+ auto operator->() -> T*
+ {
+ NES_ASSERT(has_value() && "expected box to be non-empty");
+ return get();
+ }
+
+ auto operator->() const -> T const*
+ {
+ NES_ASSERT(has_value() && "expected box to be non-empty");
+ return get();
+ }
+
operator bool() const { return has_value(); }
auto has_value() const -> bool { return initialized_; }
diff --git a/lib/nes/common/debug.hh b/lib/nes/common/debug.hh
index 409d55a..30370e9 100644
--- a/lib/nes/common/debug.hh
+++ b/lib/nes/common/debug.hh
@@ -5,11 +5,25 @@
#include <spdlog/spdlog.h>
#define NES_DEBUG_LOG(name, ...) \
- spdlog::info("[" #name "] " __VA_ARGS__)
+ spdlog::info("[" #name "] " __VA_ARGS__)
#else
#define NES_DEBUG_LOG(name, fmt, ...) \
- (void)0
+ (void)0
+
+#endif
+
+#ifdef NDEBUG
+
+#define NES_ASSERT(...) \
+ (void)0
+
+#else
+
+#include <cassert>
+
+#define NES_ASSERT(...) \
+ assert(__VA_ARGS__)
#endif \ No newline at end of file
diff --git a/lib/nes/common/list.hh b/lib/nes/common/list.hh
index b1fddf0..46f7027 100644
--- a/lib/nes/common/list.hh
+++ b/lib/nes/common/list.hh
@@ -1,5 +1,7 @@
#pragma once
+#include "nes/common/debug.hh"
+
namespace nes
{
/// An intrusive doubly-linked list.
@@ -39,15 +41,23 @@ namespace nes
auto operator==(iterator const& other) -> bool { return current_ == other.current_; }
auto operator!=(iterator const& other) -> bool { return current_ != other.current_; }
- auto operator*() const -> T* { return current_; }
+ auto operator*() const -> T&
+ {
+ NES_ASSERT(current_ != nullptr && "invalid iterator");
+ return *current_;
+ }
+
+ auto operator->() const -> T*
+ {
+ NES_ASSERT(current_ != nullptr && "invalid iterator");
+ return current_;
+ }
auto operator++() -> iterator&
{
- if (current_)
- {
- node& current_node = *current_;
- current_ = current_node.next_;
- }
+ NES_ASSERT(current_ != nullptr && "invalid iterator");
+ node& current_node = *current_;
+ current_ = current_node.next_;
return *this;
}
@@ -126,7 +136,7 @@ namespace nes
/// Remove an item from the list by its iterator.
auto remove(iterator it) -> bool
{
- if (it != end()) { return remove(**it); }
+ if (it != end()) { return remove(*it); }
return false;
}
};
diff --git a/lib/nes/common/path-builder.cc b/lib/nes/common/path-builder.cc
index 794d565..b66226f 100644
--- a/lib/nes/common/path-builder.cc
+++ b/lib/nes/common/path-builder.cc
@@ -9,28 +9,28 @@ namespace nes
if (buffer_.get_length() > 0) { buffer_[length_++] = '/'; }
}
- auto path_builder::push(std::string_view component) -> bool
+ auto path_builder::push(std::string_view component) -> status
{
// Trim leading and trailing slashes
while (!component.empty() && component[0] == '/') { component = component.substr(1); }
while (!component.empty() && component[component.length() - 1] == '/') { component = component.substr(0, component.length() - 1); }
- if (component.empty() || std::find(component.begin(), component.end(), '/') != component.end()) { return false; }
+ if (component.empty() || std::find(component.begin(), component.end(), '/') != component.end()) { return status::error_invalid_path; }
// Check if there is enough remaining capacity for "/<component>"
auto const add_leading_slash = length_ == 0 || buffer_[length_ - 1] != '/';
auto const length = component.length() + (add_leading_slash ? 1 : 0);
- if (length_ + length > buffer_.get_length()) { return false; }
+ if (length_ + length > buffer_.get_length()) { return status::error_buffer_overflow; }
// Append "/<component>"
if (add_leading_slash) { buffer_[length_++] = '/'; }
for (auto i = u32{ 0 }; i < component.length(); ++i) { buffer_[length_++] = component[i]; }
- return true;
+ return status::success;
}
- auto path_builder::pop() -> bool
+ auto path_builder::pop() -> status
{
- if (length_ == 0 || buffer_[length_ - 1] == '/') { return false; }
+ if (length_ == 0 || buffer_[length_ - 1] == '/') { return status::error_invalid_path; }
// Find the start of the last component.
auto last_start = length_ - 1;
@@ -41,6 +41,6 @@ namespace nes
// Drop everything including the trailing slash.
length_ = last_start;
- return true;
+ return status::success;
}
} // namespace nes \ No newline at end of file
diff --git a/lib/nes/common/path-builder.hh b/lib/nes/common/path-builder.hh
index b64fb28..e7980ea 100644
--- a/lib/nes/common/path-builder.hh
+++ b/lib/nes/common/path-builder.hh
@@ -3,6 +3,7 @@
#include "nes/common/types.hh"
#include "nes/common/span.hh"
#include "nes/common/path-view.hh"
+#include "nes/common/status.hh"
#include <string_view>
namespace nes
@@ -10,6 +11,9 @@ namespace nes
/// A mutable path with a fixed-length buffer.
class path_builder
{
+ template<u32 Capacity>
+ friend class path_buffer;
+
span<char> buffer_;
u32 length_{ 0 };
@@ -19,8 +23,8 @@ namespace nes
auto get_components() const -> path_view { return path_view{ get_path() }; }
auto get_path() const -> std::string_view { return std::string_view{ buffer_.get_data(), length_ }; }
- auto push(std::string_view) -> bool;
- auto pop() -> bool;
+ auto push(std::string_view) -> status;
+ auto pop() -> status;
};
/// A path builder with a buffer embedded into it.
@@ -34,5 +38,18 @@ namespace nes
: path_builder{ buffer_ }
{
}
+
+ path_buffer(path_buffer const& other)
+ : path_builder{ buffer_ }
+ {
+ std::copy_n(other.buffer_, Capacity, buffer_);
+ length_ = other.length_;
+ }
+
+ auto operator=(path_buffer const& other)
+ {
+ std::copy_n(other.buffer_, Capacity, buffer_);
+ length_ = other.length_;
+ }
};
} // namespace nes \ No newline at end of file
diff --git a/lib/nes/common/path-view.cc b/lib/nes/common/path-view.cc
index 47f198b..743c060 100644
--- a/lib/nes/common/path-view.cc
+++ b/lib/nes/common/path-view.cc
@@ -1,10 +1,12 @@
#include "nes/common/path-view.hh"
+#include "nes/common/debug.hh"
namespace nes
{
auto path_view::iterator::operator*() const -> std::string_view
{
- return current_ ? std::string_view{ current_, component_length_ } : "";
+ NES_ASSERT(current_ != nullptr && component_length_ > 0 && "invalid iterator");
+ return std::string_view{ current_, component_length_ };
}
auto path_view::iterator::operator++() -> iterator&
diff --git a/lib/nes/common/span.hh b/lib/nes/common/span.hh
index 16eb129..6779c37 100644
--- a/lib/nes/common/span.hh
+++ b/lib/nes/common/span.hh
@@ -1,6 +1,7 @@
#pragma once
#include "nes/common/types.hh"
+#include "nes/common/debug.hh"
namespace nes
{
@@ -21,6 +22,9 @@ namespace nes
{
using type = T;
};
+
+ template<typename T>
+ using remove_const_type = typename remove_const<T>::type;
} // namespace detail
/// Implementation of a container similar to std::span in C++20.
@@ -54,7 +58,7 @@ namespace nes
static_assert(Length == ActualLength || Length == detail::span_dynamic_length);
}
- span(span<typename detail::remove_const<T>::type, Length> const other)
+ span(span<detail::remove_const_type<T>, Length> const other)
: span{ other.data_, other.length_, {} }
{
}
@@ -65,12 +69,31 @@ namespace nes
auto begin() const -> iterator { return data_; }
auto end() const -> iterator { return data_ + length_; }
- auto subspan(u32 const first, u32 const length) const -> span<T> { return span{ data_ + first, length }; }
- auto subspan(u32 const first) const -> span<T> { return subspan(length_ - first); }
+ auto subspan(u32 const first, u32 const length) const -> span<T>
+ {
+ NES_ASSERT(first + length <= get_length() && "subspan out of bounds");
+ return span{ data_ + first, length };
+ }
+
+ auto subspan(u32 const first) const -> span<T>
+ {
+ NES_ASSERT(first <= get_length() && "subspan out of bounds");
+ return subspan(length_ - first);
+ }
+
template<u32 OtherLength>
- auto subspan(u32 const first) const -> span<T, OtherLength> { return span<T, OtherLength>{ data_ + first, OtherLength, {} }; }
+ auto subspan(u32 const first) const -> span<T, OtherLength>
+ {
+ static_assert(OtherLength < Length);
+ NES_ASSERT(first + OtherLength <= get_length() && "subspan out of bounds");
+ return span<T, OtherLength>{ data_ + first, OtherLength, {} };
+ }
- auto operator[](u32 const index) const -> T& { return data_[index]; }
+ auto operator[](u32 const index) const -> T&
+ {
+ NES_ASSERT(index < get_length() && "index out of bounds");
+ return data_[index];
+ }
private:
explicit span(T* data, u32 const length, detail::span_unchecked)
diff --git a/lib/nes/sys/types/status.hh b/lib/nes/common/status.hh
index 9b1e824..4b23b60 100644
--- a/lib/nes/sys/types/status.hh
+++ b/lib/nes/common/status.hh
@@ -1,6 +1,6 @@
#pragma once
-namespace nes::sys
+namespace nes
{
/// A status code.
enum class status
@@ -10,6 +10,9 @@ namespace nes::sys
error_invalid_ines_data,
error_unsupported_mapper,
error_unsupported_instruction,
+ error_buffer_overflow,
+ error_invalid_path,
+ error_invalid_format_string,
};
constexpr auto to_string(status const status) -> char const*
@@ -26,8 +29,14 @@ namespace nes::sys
return "Unsupported mapper";
case status::error_unsupported_instruction:
return "Unsupported instruction";
+ case status::error_buffer_overflow:
+ return "Buffer overflow";
+ case status::error_invalid_path:
+ return "Invalid path";
+ case status::error_invalid_format_string:
+ return "Invalid format string";
}
return "(invalid)";
}
-} // namespace nes::sys \ No newline at end of file
+} // namespace nes \ No newline at end of file
diff --git a/lib/nes/common/string-builder.cc b/lib/nes/common/string-builder.cc
index 087f0d4..7747aaa 100644
--- a/lib/nes/common/string-builder.cc
+++ b/lib/nes/common/string-builder.cc
@@ -7,70 +7,73 @@ namespace nes
{
}
- auto string_builder::append_char(char const value) -> string_builder&
+ auto string_builder::append_char(char const value) -> status
{
- if (length_ == buffer_.get_length())
- {
- is_good_ = false;
- return *this;
- }
+ if (length_ == buffer_.get_length()) { return status::error_buffer_overflow; }
buffer_[length_] = value;
length_ += 1;
- return *this;
+ return status::success;
}
- auto string_builder::append_string(std::string_view const value) -> string_builder&
+ auto string_builder::append_string(std::string_view const value) -> status
{
+ auto copy = *this;
for (auto const c : value)
{
- append_char(c);
+ if (auto const s = copy.append_char(c); s != status::success) { return s; }
}
- return *this;
+ *this = copy;
+ return status::success;
}
- auto string_builder::append_int(i64 value, number_format const format) -> string_builder&
+ auto string_builder::append_int(i64 value, number_format const format) -> status
{
+ auto copy = *this;
if (value < 0)
{
- append_char('-');
+ if (auto const s = copy.append_char('-'); s != status::success) { return s; }
value += 1; // to prevent lossy conversion to unsigned
auto abs_value = static_cast<u64>(-value);
abs_value += 1; // to correct the previous offset
- append_int(abs_value, format);
+ if (auto const s = copy.append_int(abs_value, format); s != status::success) { return s; }
}
else
{
- append_int(static_cast<u64>(value), format);
+ if (auto const s = copy.append_int(static_cast<u64>(value), format); s != status::success) { return s; }
}
- return *this;
+ *this = copy;
+ return status::success;
}
- auto string_builder::append_int(u64 value, number_format const format) -> string_builder&
+ auto string_builder::append_int(u64 value, number_format const format) -> status
{
constexpr auto max_string_length = u32{ 64 }; // worst case is a 64-bit integer in binary representation
constexpr auto digits = "0123456789abcdef";
+ auto copy = *this;
auto base = u64{ 10 };
+ auto prefix = "";
switch (format)
{
case number_format::binary:
- append_string("0b");
+ prefix = "0b";
base = 2;
break;
case number_format::octal:
- append_string("0");
+ prefix = "0";
base = 8;
break;
case number_format::decimal:
break;
case number_format::hexadecimal:
- append_string("0x");
+ prefix = "0x";
base = 16;
break;
}
+ if (auto const s = copy.append_string(prefix); s != status::success) { return s; }
char buf[max_string_length + 1] = {}; // null-terminated
auto pos = max_string_length;
@@ -89,19 +92,21 @@ namespace nes
buf[pos] = '0';
}
- append_string(&buf[pos]);
- return *this;
+ if (const auto s = copy.append_string(&buf[pos]); s != status::success) { return s; }
+
+ *this = copy;
+ return status::success;
}
- auto string_builder::append_bool(bool const value) -> string_builder&
+ auto string_builder::append_bool(bool const value) -> status
{
- append_string(value ? "true" : "false");
- return *this;
+ return append_string(value ? "true" : "false");
}
- auto string_builder::append_format(
- std::string_view const fmt, format_arg const* args, u32 const arg_count) -> string_builder&
+ auto string_builder::append_format(std::string_view const fmt, format_arg const* args, u32 const arg_count) -> status
{
+ auto copy = *this;
+
// Go through the format string, inserting the arguments for the templates as necessary.
auto current_arg = u32{ 0 };
for (auto i = u32{ 0 }; i < fmt.length(); ++i)
@@ -112,7 +117,7 @@ namespace nes
if (fmt[i] == '{') {
// escaped '{'
- append_char('{');
+ if (auto const s = copy.append_char('{'); s != status::success) { return s; }
}
else
{
@@ -128,21 +133,20 @@ namespace nes
if (fmt[i] != '}')
{
// unmatched '{'
- is_good_ = false;
- break;
+ return status::error_invalid_format_string;
}
// end of template
auto const p = std::string_view{ params, params_length };
if (current_arg < arg_count)
{
- append_format_arg(args[current_arg], p);
+ if (auto const s = copy.append_format_arg(args[current_arg], p); s != status::success) { return s; }
current_arg += 1;
}
else
{
// missing argument in parameter pack
- is_good_ = false;
+ return status::error_invalid_format_string;
}
}
}
@@ -153,39 +157,41 @@ namespace nes
if (fmt[i] == '}')
{
// escaped '}'
- append_char('}');
+ if (auto const s = copy.append_char('}'); s != status::success) { return s; }
}
else
{
// unmatched '}'
- is_good_ = false;
- break;
+ return status::error_invalid_format_string;
}
}
else
{
// normal character in the template string
- append_char(fmt[i]);
+ if (auto const s = copy.append_char(fmt[i]); s != status::success) { return s; }
}
}
if (current_arg < arg_count)
{
// not all supplied arguments were used
- is_good_ = false;
+ return status::error_invalid_format_string;
}
- return *this;
+ *this = copy;
+ return status::success;
}
- auto string_builder::append_format_arg(format_arg const arg, std::string_view const params) -> string_builder&
+ auto string_builder::append_format_arg(format_arg const arg, std::string_view const params) -> status
{
auto const parse_params = [&]
{
if (!params.empty())
{
- is_good_ = false;
+ return status::error_invalid_format_string;
}
+
+ return status::success;
};
auto const parse_params_integer = [&](number_format& out_format)
@@ -208,47 +214,43 @@ namespace nes
}
else
{
- out_format = number_format::decimal;
- is_good_ = false;
+ return status::error_invalid_format_string;
}
+
+ return status::success;
};
switch (arg.type)
{
case format_arg_type::string:
{
- parse_params();
- append_string(arg.string);
- break;
+ if (auto const s = parse_params(); s != status::success) { return s; }
+ return append_string(arg.string);
}
case format_arg_type::character:
{
- parse_params();
- append_char(arg.character);
- break;
+ if (auto const s = parse_params(); s != status::success) { return s; }
+ return append_char(arg.character);
}
case format_arg_type::signed_integer:
{
auto format = number_format{};
- parse_params_integer(format);
- append_int(arg.signed_integer, format);
- break;
+ if (auto const s = parse_params_integer(format); s != status::success) { return s; }
+ return append_int(arg.signed_integer, format);
}
case format_arg_type::unsigned_integer:
{
auto format = number_format{};
- parse_params_integer(format);
- append_int(arg.unsigned_integer, format);
- break;
+ if (auto const s = parse_params_integer(format); s != status::success) { return s; }
+ return append_int(arg.unsigned_integer, format);
}
case format_arg_type::boolean:
{
- parse_params();
- append_bool(arg.character);
- break;
+ if (auto const s = parse_params(); s != status::success) { return s; }
+ return append_bool(arg.character);
}
}
- return *this;
+ return status::error_invalid_format_string;
}
} // namespace nes \ No newline at end of file
diff --git a/lib/nes/common/string-builder.hh b/lib/nes/common/string-builder.hh
index 256c267..0298b8e 100644
--- a/lib/nes/common/string-builder.hh
+++ b/lib/nes/common/string-builder.hh
@@ -2,6 +2,7 @@
#include "nes/common/types.hh"
#include "nes/common/span.hh"
+#include "nes/common/status.hh"
#include <string_view>
namespace nes
@@ -17,9 +18,11 @@ namespace nes
/// Builds a string into a fixed-length buffer.
class string_builder
{
+ template<u32 Capacity>
+ friend class string_buffer;
+
span<char> buffer_;
u32 length_{ 0 };
- bool is_good_{ true };
enum class format_arg_type
{
@@ -65,14 +68,11 @@ namespace nes
/// Get the resulting string.
auto get_result() const -> std::string_view { return std::string_view{ buffer_.get_data(), length_ }; }
- /// Returns false after an invalid format string or buffer overflow.
- auto is_good() const -> bool { return is_good_; }
-
- auto append_char(char) -> string_builder&;
- auto append_string(std::string_view) -> string_builder&;
- auto append_int(i64, number_format = number_format::decimal) -> string_builder&;
- auto append_int(u64, number_format = number_format::decimal) -> string_builder&;
- auto append_bool(bool) -> string_builder&;
+ auto append_char(char) -> status;
+ auto append_string(std::string_view) -> status;
+ auto append_int(i64, number_format = number_format::decimal) -> status;
+ auto append_int(u64, number_format = number_format::decimal) -> status;
+ auto append_bool(bool) -> status;
/// Print a templated string.
///
@@ -82,7 +82,7 @@ namespace nes
///
/// Curly braces can be escaped by repeating them twice like {{this}}.
template<typename... Args>
- auto append_format(std::string_view const fmt, Args... args) -> string_builder&
+ auto append_format(std::string_view const fmt, Args... args) -> status
{
constexpr u32 arg_count = sizeof...(Args);
// Add another argument at the end so we don't have an empty array expression.
@@ -92,8 +92,8 @@ namespace nes
}
private:
- auto append_format(std::string_view fmt, format_arg const* args, u32 arg_count) -> string_builder&;
- auto append_format_arg(format_arg, std::string_view params) -> string_builder&;
+ auto append_format(std::string_view fmt, format_arg const* args, u32 arg_count) -> status;
+ auto append_format_arg(format_arg, std::string_view params) -> status;
};
/// A string builder with a buffer embedded into it.
@@ -107,5 +107,18 @@ namespace nes
: string_builder{ buffer_ }
{
}
+
+ string_buffer(string_buffer const& other)
+ : string_builder{ buffer_ }
+ {
+ std::copy_n(other.buffer_, Capacity, buffer_);
+ length_ = other.length_;
+ }
+
+ auto operator=(string_buffer const& other)
+ {
+ std::copy_n(other.buffer_, Capacity, buffer_);
+ length_ = other.length_;
+ }
};
} // namespace nes \ No newline at end of file
diff --git a/lib/nes/sys/cartridge.cc b/lib/nes/sys/cartridge.cc
index 14fff3d..0729107 100644
--- a/lib/nes/sys/cartridge.cc
+++ b/lib/nes/sys/cartridge.cc
@@ -1,6 +1,5 @@
#include "nes/sys/cartridge.hh"
-#include "nes/sys/types/status.hh"
-#include <fstream>
+#include "nes/common/status.hh"
#include <algorithm>
namespace nes::sys
diff --git a/lib/nes/sys/cartridge.hh b/lib/nes/sys/cartridge.hh
index 5383b56..4534e1e 100644
--- a/lib/nes/sys/cartridge.hh
+++ b/lib/nes/sys/cartridge.hh
@@ -1,7 +1,7 @@
#pragma once
-#include "nes/sys/types/status.hh"
#include "nes/sys/mapper.hh"
+#include "nes/common/status.hh"
#include "nes/common/types.hh"
#include "nes/common/span.hh"
diff --git a/lib/nes/sys/cpu.hh b/lib/nes/sys/cpu.hh
index 5d6466f..99753a7 100644
--- a/lib/nes/sys/cpu.hh
+++ b/lib/nes/sys/cpu.hh
@@ -2,7 +2,7 @@
#include "nes/sys/types/cycle-count.hh"
#include "nes/sys/types/address.hh"
-#include "nes/sys/types/status.hh"
+#include "nes/common/status.hh"
#include "nes/common/types.hh"
namespace nes::sys
diff --git a/lib/nes/sys/mapper.hh b/lib/nes/sys/mapper.hh
index 9ad88e2..48363dd 100644
--- a/lib/nes/sys/mapper.hh
+++ b/lib/nes/sys/mapper.hh
@@ -1,7 +1,7 @@
#pragma once
#include "nes/sys/types/address.hh"
-#include "nes/sys/types/status.hh"
+#include "nes/common/status.hh"
#include "nes/common/types.hh"
#include "nes/common/span.hh"
diff --git a/lib/nes/sys/types/CMakeLists.txt b/lib/nes/sys/types/CMakeLists.txt
index f51b0ae..863cf65 100644
--- a/lib/nes/sys/types/CMakeLists.txt
+++ b/lib/nes/sys/types/CMakeLists.txt
@@ -4,5 +4,4 @@ target_sources(
address.hh
button-mask.hh
cycle-count.hh
- snapshot.hh
- status.hh) \ No newline at end of file
+ snapshot.hh) \ No newline at end of file