summaryrefslogtreecommitdiff
path: root/app/mac/scene.mm
diff options
context:
space:
mode:
Diffstat (limited to 'app/mac/scene.mm')
-rw-r--r--app/mac/scene.mm171
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