diff options
| author | Hannes Schulze <haschu0103@outlook.de> | 2025-01-30 16:43:06 +0100 |
|---|---|---|
| committer | Hannes Schulze <haschu0103@outlook.de> | 2025-01-31 00:02:26 +0100 |
| commit | 307e07d6513f9524b7fcab5134494bd175465cd1 (patch) | |
| tree | ce025db2ea998bdcd8980a0ed443083f0daf8d35 /app/mac | |
| parent | 5959349f341a2380c0c3f41ec40a1e8e0d969d00 (diff) | |
handle input devices in the c++ application
Diffstat (limited to 'app/mac')
| -rw-r--r-- | app/mac/CMakeLists.txt | 7 | ||||
| -rw-r--r-- | app/mac/controller-adapter-gamepad.hh | 18 | ||||
| -rw-r--r-- | app/mac/controller-adapter-gamepad.mm | 29 | ||||
| -rw-r--r-- | app/mac/controller-adapter-keyboard.hh | 15 | ||||
| -rw-r--r-- | app/mac/controller-adapter-keyboard.mm | 44 | ||||
| -rw-r--r-- | app/mac/controller-adapter.hh | 16 | ||||
| -rw-r--r-- | app/mac/main.mm | 10 | ||||
| -rw-r--r-- | app/mac/scene.hh | 9 | ||||
| -rw-r--r-- | app/mac/scene.mm | 171 | ||||
| -rw-r--r-- | app/mac/view-controller.hh | 3 | ||||
| -rw-r--r-- | app/mac/view-controller.mm | 83 |
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; |
