diff options
Diffstat (limited to 'app/mac/scene.mm')
| -rw-r--r-- | app/mac/scene.mm | 171 |
1 files changed, 149 insertions, 22 deletions
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 |
