Game Boy input handling

3use crate::api::GameBoyEmulatorConfig;
4use crate::interrupts::InterruptRegisters;
5use crate::sm83::InterruptType;
6use bincode::{Decode, Encode};
7use gb_config::GameBoyInputs;
8use jgenesis_common::num::GetBit;
10#[derive(Debug, Clone, Encode, Decode)]
11pub(crate) struct InputState {
12    inputs: GameBoyInputs,
13    d_pad_selected: bool,
14    buttons_selected: bool,
15    prev_joyp: u8,
16    allow_opposing_directions: bool,
17}
18
19impl InputState {
20    pub(crate) fn new(config: &GameBoyEmulatorConfig) -> Self {
21        Self {
22            inputs: GameBoyInputs::default(),
23            d_pad_selected: false,
24            buttons_selected: false,
25            prev_joyp: 0xFF,
26            allow_opposing_directions: config.allow_opposing_joypad_directions,
27        }
28    }
29
30    pub(crate) fn set_inputs(
31        &mut self,
32        inputs: GameBoyInputs,
33        interrupts: &mut InterruptRegisters,
34    ) {
35        if self.inputs == inputs {
36            return;
37        }
38
39        self.inputs = inputs;
40        self.check_for_joypad_interrupt(interrupts);
41    }
42
43    pub(crate) fn write_joyp(&mut self, value: u8, interrupts: &mut InterruptRegisters) {
44        self.buttons_selected = !value.bit(5);
45        self.d_pad_selected = !value.bit(4);
46
47        self.check_for_joypad_interrupt(interrupts);
48
49        log::trace!("JOYP write: {value:02X}");
50    }
51
52    fn check_for_joypad_interrupt(&mut self, interrupts: &mut InterruptRegisters) {
53        // Joypad interrupt triggers when any of JOYP bits 0-3 change from 1 to 0
54        let new_joyp = self.read_joyp();
55        if self.prev_joyp & 0x0F & !new_joyp != 0 {
56            interrupts.set_flag(InterruptType::Joypad);
57        }
58        self.prev_joyp = new_joyp;
59    }
60
61    pub(crate) fn read_joyp(&self) -> u8 {
62        let inputs = self.inputs.with_allow_opposing_directions(self.allow_opposing_directions);
63
64        let bit_3_inverted =
65            (self.buttons_selected && inputs.start) || (self.d_pad_selected && inputs.down);
66        let bit_2_inverted =
67            (self.buttons_selected && inputs.select) || (self.d_pad_selected && inputs.up);
68        let bit_1_inverted =
69            (self.buttons_selected && inputs.b) || (self.d_pad_selected && inputs.left);
70        let bit_0_inverted =
71            (self.buttons_selected && inputs.a) || (self.d_pad_selected && inputs.right);
72
73        0xC0 | (u8::from(!self.buttons_selected) << 5)
74            | (u8::from(!self.d_pad_selected) << 4)
75            | (u8::from(!bit_3_inverted) << 3)
76            | (u8::from(!bit_2_inverted) << 2)
77            | (u8::from(!bit_1_inverted) << 1)
78            | u8::from(!bit_0_inverted)
79    }
80
81    pub fn reload_config(&mut self, config: &GameBoyEmulatorConfig) {
82        self.allow_opposing_directions = config.allow_opposing_joypad_directions;
83    }
84}