summaryrefslogtreecommitdiff
path: root/app/mac
diff options
context:
space:
mode:
authorHannes Schulze <haschu0103@outlook.de>2025-01-30 16:43:06 +0100
committerHannes Schulze <haschu0103@outlook.de>2025-01-31 00:02:26 +0100
commit307e07d6513f9524b7fcab5134494bd175465cd1 (patch)
treece025db2ea998bdcd8980a0ed443083f0daf8d35 /app/mac
parent5959349f341a2380c0c3f41ec40a1e8e0d969d00 (diff)
handle input devices in the c++ application
Diffstat (limited to 'app/mac')
-rw-r--r--app/mac/CMakeLists.txt7
-rw-r--r--app/mac/controller-adapter-gamepad.hh18
-rw-r--r--app/mac/controller-adapter-gamepad.mm29
-rw-r--r--app/mac/controller-adapter-keyboard.hh15
-rw-r--r--app/mac/controller-adapter-keyboard.mm44
-rw-r--r--app/mac/controller-adapter.hh16
-rw-r--r--app/mac/main.mm10
-rw-r--r--app/mac/scene.hh9
-rw-r--r--app/mac/scene.mm171
-rw-r--r--app/mac/view-controller.hh3
-rw-r--r--app/mac/view-controller.mm83
11 files changed, 154 insertions, 251 deletions
diff --git a/app/mac/CMakeLists.txt b/app/mac/CMakeLists.txt
index e0d42d0..f529e3d 100644
--- a/app/mac/CMakeLists.txt
+++ b/app/mac/CMakeLists.txt
@@ -21,9 +21,4 @@ target_sources(
scene.hh
scene.mm
view-controller.hh
- view-controller.mm
- controller-adapter.hh
- controller-adapter-gamepad.hh
- controller-adapter-gamepad.mm
- controller-adapter-keyboard.hh
- controller-adapter-keyboard.mm) \ No newline at end of file
+ view-controller.mm) \ No newline at end of file
diff --git a/app/mac/controller-adapter-gamepad.hh b/app/mac/controller-adapter-gamepad.hh
deleted file mode 100644
index c0caeed..0000000
--- a/app/mac/controller-adapter-gamepad.hh
+++ /dev/null
@@ -1,18 +0,0 @@
-#pragma once
-
-#import "controller-adapter.hh"
-
-#import <GameController/GameController.h>
-
-NS_ASSUME_NONNULL_BEGIN
-
-@interface ControllerAdapterGamepad : NSObject <ControllerAdapter>
-
-- (instancetype)initWithProfile:(GCExtendedGamepad*)profile;
-- (instancetype)init NS_UNAVAILABLE;
-
-@property (readonly) GCExtendedGamepad* profile;
-
-@end
-
-NS_ASSUME_NONNULL_END \ No newline at end of file
diff --git a/app/mac/controller-adapter-gamepad.mm b/app/mac/controller-adapter-gamepad.mm
deleted file mode 100644
index f1d8db8..0000000
--- a/app/mac/controller-adapter-gamepad.mm
+++ /dev/null
@@ -1,29 +0,0 @@
-#include "controller-adapter-gamepad.hh"
-
-@implementation ControllerAdapterGamepad
-
-- (instancetype)initWithProfile:(GCExtendedGamepad*)profile
-{
- self = [super init];
- if (self)
- {
- _profile = profile;
- }
- return self;
-}
-
-- (nes::button_mask)readPressedButtons
-{
- auto res = nes::button_mask{};
- if ([[[_profile dpad] up] isPressed]) { res.add(nes::buttons::up); }
- if ([[[_profile dpad] down] isPressed]) { res.add(nes::buttons::down); }
- if ([[[_profile dpad] left] isPressed]) { res.add(nes::buttons::left); }
- if ([[[_profile dpad] right] isPressed]) { res.add(nes::buttons::right); }
- if ([[_profile buttonA] isPressed]) { res.add(nes::buttons::a); }
- if ([[_profile buttonB] isPressed]) { res.add(nes::buttons::b); }
- if ([[_profile leftTrigger] isPressed]) { res.add(nes::buttons::select); }
- if ([[_profile rightTrigger] isPressed]) { res.add(nes::buttons::start); }
- return res;
-}
-
-@end \ No newline at end of file
diff --git a/app/mac/controller-adapter-keyboard.hh b/app/mac/controller-adapter-keyboard.hh
deleted file mode 100644
index 7c3ef84..0000000
--- a/app/mac/controller-adapter-keyboard.hh
+++ /dev/null
@@ -1,15 +0,0 @@
-#pragma once
-
-#import "controller-adapter.hh"
-
-#import <GameController/GameController.h>
-
-NS_ASSUME_NONNULL_BEGIN
-
-@interface ControllerAdapterKeyboard : NSObject <ControllerAdapter>
-
-@property (readonly, nullable) GCKeyboard* keyboard;
-
-@end
-
-NS_ASSUME_NONNULL_END \ No newline at end of file
diff --git a/app/mac/controller-adapter-keyboard.mm b/app/mac/controller-adapter-keyboard.mm
deleted file mode 100644
index ad3dda8..0000000
--- a/app/mac/controller-adapter-keyboard.mm
+++ /dev/null
@@ -1,44 +0,0 @@
-#import "controller-adapter-keyboard.hh"
-
-@implementation ControllerAdapterKeyboard
-
-- (instancetype)init
-{
- self = [super init];
- if (self)
- {
- [[NSNotificationCenter defaultCenter] addObserver:self
- selector:@selector(handleKeyboardConnected:)
- name:GCKeyboardDidConnectNotification
- object:nil];
- }
- return self;
-}
-
-- (void)dealloc
-{
- [[NSNotificationCenter defaultCenter] removeObserver:self];
-}
-
-- (void)handleKeyboardConnected:(NSNotification*)notification
-{
- _keyboard = [GCKeyboard coalescedKeyboard];
-}
-
-- (nes::button_mask)readPressedButtons
-{
- auto profile = [[self keyboard] keyboardInput];
-
- auto res = nes::button_mask{};
- if ([[profile buttonForKeyCode:GCKeyCodeUpArrow] isPressed]) { res.add(nes::buttons::up); }
- if ([[profile buttonForKeyCode:GCKeyCodeDownArrow] isPressed]) { res.add(nes::buttons::down); }
- if ([[profile buttonForKeyCode:GCKeyCodeLeftArrow] isPressed]) { res.add(nes::buttons::left); }
- if ([[profile buttonForKeyCode:GCKeyCodeRightArrow] isPressed]) { res.add(nes::buttons::right); }
- if ([[profile buttonForKeyCode:GCKeyCodeKeyZ] isPressed]) { res.add(nes::buttons::a); }
- if ([[profile buttonForKeyCode:GCKeyCodeKeyX] isPressed]) { res.add(nes::buttons::b); }
- if ([[profile buttonForKeyCode:GCKeyCodeReturnOrEnter] isPressed]) { res.add(nes::buttons::start); }
- if ([[profile buttonForKeyCode:GCKeyCodeSpacebar] isPressed]) { res.add(nes::buttons::select); }
- return res;
-}
-
-@end \ No newline at end of file
diff --git a/app/mac/controller-adapter.hh b/app/mac/controller-adapter.hh
deleted file mode 100644
index 09e8b6d..0000000
--- a/app/mac/controller-adapter.hh
+++ /dev/null
@@ -1,16 +0,0 @@
-#pragma once
-
-#import "nes/sys/types/button-mask.hh"
-
-#import <Foundation/Foundation.h>
-
-NS_ASSUME_NONNULL_BEGIN
-
-/// Adapts inputs from a GCPhysicalInputProfile to a NES button mask.
-@protocol ControllerAdapter <NSObject>
-
-- (nes::button_mask)readPressedButtons;
-
-@end
-
-NS_ASSUME_NONNULL_END \ No newline at end of file
diff --git a/app/mac/main.mm b/app/mac/main.mm
index 4e96cea..55e00b1 100644
--- a/app/mac/main.mm
+++ b/app/mac/main.mm
@@ -5,14 +5,6 @@
int main(int argc, char** argv)
{
- if (argc != 2)
- {
- std::cerr << "Usage: " << argv[0] << " <rom-path>" << std::endl;
- return 1;
- }
-
- auto filePath = [NSString stringWithUTF8String:argv[1]];
-
auto app = [NSApplication sharedApplication];
[app setActivationPolicy:NSApplicationActivationPolicyRegular];
@@ -33,7 +25,7 @@ int main(int argc, char** argv)
[mainMenu addItem:item];
[app setMainMenu:mainMenu];
- auto viewController = [[ViewController alloc] initWithFilePath:filePath];
+ auto viewController = [[ViewController alloc] init];
auto window = [NSWindow windowWithContentViewController:viewController];
[window setTitle:@"NES"];
[window setContentAspectRatio:[viewController minimumDisplaySize]];
diff --git a/app/mac/scene.hh b/app/mac/scene.hh
index ba8eb31..e4f0a45 100644
--- a/app/mac/scene.hh
+++ b/app/mac/scene.hh
@@ -1,22 +1,15 @@
#pragma once
-#import "controller-adapter.hh"
-
#import <SpriteKit/SpriteKit.h>
+#import <GameController/GameController.h>
NS_ASSUME_NONNULL_BEGIN
@interface Scene : SKScene
-- (instancetype)initWithFilePath:(NSString*)filePath NS_DESIGNATED_INITIALIZER;
-
+ (instancetype)sceneWithSize:(CGSize)size NS_UNAVAILABLE;
- (instancetype)initWithSize:(CGSize)size NS_UNAVAILABLE;
- (instancetype)initWithCoder:(NSCoder*)coder NS_UNAVAILABLE;
-- (instancetype)init NS_UNAVAILABLE;
-
-@property (nonatomic, nullable) id<ControllerAdapter> primaryController;
-@property (nonatomic, nullable) id<ControllerAdapter> secondaryController;
@end
diff --git a/app/mac/scene.mm b/app/mac/scene.mm
index 97caadf..de9c587 100644
--- a/app/mac/scene.mm
+++ b/app/mac/scene.mm
@@ -1,13 +1,14 @@
#import "scene.hh"
-#import "nes/sys/nes.hh"
+#import "nes/app/application.hh"
+#import "nes/app/input/input-device-keyboard.hh"
+#import "nes/app/input/input-device-controller.hh"
#import "nes/common/display.hh"
#import <memory>
#import <optional>
-#import <iostream>
namespace
{
- class display_impl : public nes::display
+ class display_impl final : public nes::display
{
SKSpriteNode* node_;
NSMutableData* buffer_front_;
@@ -63,16 +64,97 @@ namespace
return [NSMutableData dataWithLength:length];
}
};
+
+ class input_device_keyboard_impl final : public nes::input_device_keyboard
+ {
+ GCKeyboardInput* profile_;
+
+ public:
+ explicit input_device_keyboard_impl(GCKeyboardInput* profile)
+ : profile_{ profile }
+ {
+ }
+
+ auto set_profile(GCKeyboardInput* profile) -> void { profile_ = profile; }
+
+ auto read_key_up() -> bool override { return [[profile_ buttonForKeyCode:GCKeyCodeUpArrow] isPressed]; }
+ auto read_key_down() -> bool override { return [[profile_ buttonForKeyCode:GCKeyCodeDownArrow] isPressed]; }
+ auto read_key_left() -> bool override { return [[profile_ buttonForKeyCode:GCKeyCodeLeftArrow] isPressed]; }
+ auto read_key_right() -> bool override { return [[profile_ buttonForKeyCode:GCKeyCodeRightArrow] isPressed]; }
+ auto read_key_a() -> bool override { return [[profile_ buttonForKeyCode:GCKeyCodeKeyZ] isPressed]; }
+ auto read_key_b() -> bool override { return [[profile_ buttonForKeyCode:GCKeyCodeKeyX] isPressed]; }
+ auto read_key_select() -> bool override { return [[profile_ buttonForKeyCode:GCKeyCodeSpacebar] isPressed]; }
+ auto read_key_start() -> bool override { return [[profile_ buttonForKeyCode:GCKeyCodeReturnOrEnter] isPressed]; }
+ auto read_key_escape() -> bool override { return [[profile_ buttonForKeyCode:GCKeyCodeEscape] isPressed]; }
+ };
+
+ class input_device_controller_impl final : public nes::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_key_up() -> bool override { return [[[profile_ dpad] up] isPressed]; }
+ auto read_key_down() -> bool override { return [[[profile_ dpad] down] isPressed]; }
+ auto read_key_left() -> bool override { return [[[profile_ dpad] left] isPressed]; }
+ auto read_key_right() -> bool override { return [[[profile_ dpad] right] isPressed]; }
+ auto read_key_a() -> bool override { return [[profile_ buttonA] isPressed]; }
+ auto read_key_b() -> bool override { return [[profile_ buttonB] isPressed]; }
+ auto read_key_select() -> bool override { return [[profile_ leftTrigger] isPressed]; }
+ auto read_key_start() -> bool override { return [[profile_ rightTrigger] isPressed]; }
+ };
} // namespace
@implementation Scene
{
std::unique_ptr<display_impl> _display;
- std::unique_ptr<nes::nes> _nes;
+ std::unique_ptr<input_device_keyboard_impl> _keyboard;
+ std::vector<std::unique_ptr<input_device_controller_impl>> _controllers;
+ std::unique_ptr<nes::application> _app;
std::optional<NSTimeInterval> _lastTimestamp;
}
-- (instancetype)initWithFilePath:(NSString*)filePath
+- (instancetype)init
{
self = [super initWithSize:CGSizeMake(nes::display::width, nes::display::height)];
if (self)
@@ -84,19 +166,42 @@ namespace
[self setScaleMode:SKSceneScaleModeAspectFit];
[self addChild:node];
- auto cartridge = nes::cartridge::from_file([filePath UTF8String]);
- if (cartridge.get_status() != nes::status::success)
- {
- std::cerr << "Unable to load cartridge: " << to_string(cartridge.get_status()) << std::endl;
- std::abort();
- }
-
_display = std::make_unique<display_impl>(node);
- _nes = std::make_unique<nes::nes>(std::move(cartridge), *self->_display);
+ _keyboard = std::make_unique<input_device_keyboard_impl>([[GCKeyboard coalescedKeyboard] keyboardInput]);
+ _app = std::make_unique<nes::application>(*_display, *_keyboard);
+
+ for (GCController* controller in [GCController controllers])
+ {
+ auto gamepad = [controller extendedGamepad];
+ if (gamepad)
+ {
+ auto adapter = std::make_unique<input_device_controller_impl>(gamepad);
+ _app->add_controller(*adapter);
+ _controllers.emplace_back(std::move(adapter));
+ }
+ }
+
+ [[NSNotificationCenter defaultCenter] addObserver:self
+ selector:@selector(handleKeyboardConnected:)
+ name:GCKeyboardDidConnectNotification
+ object:nil];
+ [[NSNotificationCenter defaultCenter] addObserver:self
+ selector:@selector(handleControllerConnected:)
+ name:GCControllerDidConnectNotification
+ object:nil];
+ [[NSNotificationCenter defaultCenter] addObserver:self
+ selector:@selector(handleControllerDisconnected:)
+ name:GCControllerDidDisconnectNotification
+ object:nil];
}
return self;
}
+- (void)dealloc
+{
+ [[NSNotificationCenter defaultCenter] removeObserver:self];
+}
+
- (void)keyUp:(NSEvent*)event
{
// Handled by Game Controller
@@ -109,20 +214,42 @@ namespace
- (void)update:(NSTimeInterval)currentTime
{
- _nes->ref_controller_1().set_pressed(_primaryController ? [_primaryController readPressedButtons] : nes::button_mask{});
- _nes->ref_controller_2().set_pressed(_secondaryController ? [_secondaryController readPressedButtons] : nes::button_mask{});
-
auto const lastTimestamp = _lastTimestamp.value_or(currentTime);
_lastTimestamp = currentTime;
auto deltaMicroseconds = static_cast<std::uint64_t>((currentTime - lastTimestamp) * 1000. * 1000.);
deltaMicroseconds = std::min(deltaMicroseconds, std::uint64_t{ 50000 });
- _nes->step(std::chrono::microseconds{ deltaMicroseconds });
+ _app->frame(std::chrono::microseconds{ deltaMicroseconds });
+}
- if (_nes->get_status() != nes::status::success)
- {
- std::cerr << "Invalid state: " << to_string(_nes->get_status()) << std::endl;
- std::abort();
- }
+- (void)handleKeyboardConnected:(NSNotification*)notification
+{
+ _keyboard->set_profile([[GCKeyboard coalescedKeyboard] keyboardInput]);
+}
+
+- (void)handleControllerConnected:(NSNotification*)notification
+{
+ auto controller = (GCController*)[notification object];
+ auto gamepad = [controller extendedGamepad];
+ if (gamepad)
+ {
+ auto adapter = std::make_unique<input_device_controller_impl>(gamepad);
+ _app->add_controller(*adapter);
+ _controllers.emplace_back(std::move(adapter));
+ }
+}
+
+- (void)handleControllerDisconnected:(NSNotification*)notification
+{
+ auto controller = (GCController*)[notification object];
+ for (auto i = _controllers.size(); i > 0; --i)
+ {
+ auto& adapter = *_controllers[i - 1];
+ if (adapter.get_controller() == controller)
+ {
+ _app->remove_controller(adapter);
+ _controllers.erase(_controllers.begin() + static_cast<int>(i - 1));
+ }
+ }
}
@end \ No newline at end of file
diff --git a/app/mac/view-controller.hh b/app/mac/view-controller.hh
index 2eecd4e..bed3207 100644
--- a/app/mac/view-controller.hh
+++ b/app/mac/view-controller.hh
@@ -7,12 +7,9 @@ NS_ASSUME_NONNULL_BEGIN
@interface ViewController : GCEventViewController
-- (instancetype)initWithFilePath:(NSString*)filePath NS_DESIGNATED_INITIALIZER;
-
- (instancetype)initWithNibName:(nullable NSString*)nibNameOrNil
bundle:(nullable NSBundle*)nibBundleOrNil NS_UNAVAILABLE;
- (instancetype)initWithCoder:(NSCoder*)coder NS_UNAVAILABLE;
-- (instancetype)init NS_UNAVAILABLE;
@property (readonly) NSSize minimumDisplaySize;
@property (readonly) NSSize preferredDisplaySize;
diff --git a/app/mac/view-controller.mm b/app/mac/view-controller.mm
index eb81b6b..8695634 100644
--- a/app/mac/view-controller.mm
+++ b/app/mac/view-controller.mm
@@ -1,54 +1,23 @@
#import "view-controller.hh"
-#import "controller-adapter-keyboard.hh"
-#import "controller-adapter-gamepad.hh"
#import "scene.hh"
@implementation ViewController
{
Scene* _scene;
NSSize _sceneSize;
- ControllerAdapterKeyboard* _keyboardController;
- NSMutableArray<ControllerAdapterGamepad*>* _gamepadControllers;
}
-- (instancetype)initWithFilePath:(NSString*)filePath
+- (instancetype)init
{
self = [super initWithNibName:nil bundle:nil];
if (self)
{
- _scene = [[Scene alloc] initWithFilePath:filePath];
+ _scene = [[Scene alloc] init];
_sceneSize = [_scene size];
-
- _keyboardController = [[ControllerAdapterKeyboard alloc] init];
- _gamepadControllers = [NSMutableArray array];
- for (GCController* controller in [GCController controllers])
- {
- auto gamepad = [controller extendedGamepad];
- if (gamepad)
- {
- auto adapter = [[ControllerAdapterGamepad alloc] initWithProfile:gamepad];
- [_gamepadControllers addObject:adapter];
- }
- }
- [self updateControllers];
-
- [[NSNotificationCenter defaultCenter] addObserver:self
- selector:@selector(handleControllerConnected:)
- name:GCControllerDidConnectNotification
- object:nil];
- [[NSNotificationCenter defaultCenter] addObserver:self
- selector:@selector(handleControllerDisconnected:)
- name:GCControllerDidDisconnectNotification
- object:nil];
}
return self;
}
-- (void)dealloc
-{
- [[NSNotificationCenter defaultCenter] removeObserver:self];
-}
-
- (void)loadView
{
auto view = [[SKView alloc] init];
@@ -59,54 +28,6 @@
[self setView:view];
}
-- (void)updateControllers
-{
- // Prefer controllers, then fallback to keyboard input.
- if ([_gamepadControllers count] >= 1)
- {
- [_scene setPrimaryController:[_gamepadControllers objectAtIndex:0]];
- if ([_gamepadControllers count] >= 2)
- {
- [_scene setSecondaryController:[_gamepadControllers objectAtIndex:1]];
- }
- else
- {
- [_scene setSecondaryController:_keyboardController];
- }
- }
- else
- {
- [_scene setPrimaryController:_keyboardController];
- [_scene setSecondaryController:nil];
- }
-}
-
-- (void)handleControllerConnected:(NSNotification*)notification
-{
- auto controller = (GCController*)[notification object];
- auto gamepad = [controller extendedGamepad];
- if (gamepad)
- {
- auto adapter = [[ControllerAdapterGamepad alloc] initWithProfile:gamepad];
- [_gamepadControllers addObject:adapter];
- }
- [self updateControllers];
-}
-
-- (void)handleControllerDisconnected:(NSNotification*)notification
-{
- auto controller = (GCController*)[notification object];
- for (auto i = NSUInteger{ [_gamepadControllers count] }; i > 0; --i)
- {
- auto adapter = [_gamepadControllers objectAtIndex:(i - 1)];
- if ([[adapter profile] controller] == controller)
- {
- [_gamepadControllers removeObjectAtIndex:(i - 1)];
- }
- }
- [self updateControllers];
-}
-
- (NSSize)minimumDisplaySize
{
return _sceneSize;