embassy_agb/
input.rs

1//! Async input system with configurable timer-based polling
2//!
3//! ## Design Decisions:
4//!
5//! 1. **Timer-based polling**: Uses Embassy timer instead of VBlank interrupt
6//!    - Reason: Avoids VBlank interrupt conflicts with display system
7//!    - Benefit: Configurable poll rate for different game requirements
8//!    - Default: 60Hz polling (16.67ms) to match VBlank rate
9//!
10//! 2. **Per-button wakers**: Each button has its own AtomicWaker in static array
11//!    - Reason: Only wake futures waiting for specific buttons that changed
12//!    - Embassy pattern: Targeted waking, not broadcast waking
13//!
14//! 3. **Configurable latency**: Poll rate can be adjusted from 30Hz to 120Hz
15//!    - Higher rates: Lower latency but more CPU usage
16//!    - Lower rates: Higher latency but better power efficiency
17//!    - Note: Button presses may not register until next poll cycle
18
19use core::future::Future;
20use core::pin::Pin;
21use core::task::{Context, Poll};
22use portable_atomic::Ordering;
23
24use agb::input::{Button, ButtonController, Tri};
25use embassy_sync::waitqueue::AtomicWaker;
26
27#[cfg(feature = "time")]
28use embassy_time;
29
30#[cfg(feature = "executor")]
31use embassy_executor;
32
33/// Keypad input register (KEYINPUT) at 0x04000130  
34const KEYPAD_INPUT: *mut u16 = 0x04000130 as *mut u16;
35
36const BUTTON_COUNT: usize = 10;
37/// Per-button wakers - following Embassy's pattern
38static BUTTON_WAKERS: [AtomicWaker; BUTTON_COUNT] = [const { AtomicWaker::new() }; BUTTON_COUNT];
39
40/// Global button state for timer-based monitoring
41static GLOBAL_BUTTON_STATE: portable_atomic::AtomicU16 = portable_atomic::AtomicU16::new(0);
42
43/// Whether the input polling task is running
44static POLLING_TASK_RUNNING: portable_atomic::AtomicBool = portable_atomic::AtomicBool::new(false);
45
46/// Input polling rate options
47#[derive(Debug, Clone, Copy)]
48pub enum PollingRate {
49    /// 30Hz - Lower latency, more power efficient
50    Hz30,
51    /// 60Hz - Default, matches VBlank rate
52    Hz60,
53    /// 90Hz - Higher responsiveness
54    Hz90,
55    /// 120Hz - Highest responsiveness, more CPU usage
56    Hz120,
57    /// Custom rate in Hz (clamped to 10-240 range)
58    Custom(u32),
59}
60
61impl PollingRate {
62    /// Get the polling rate as Hz value
63    pub fn as_hz(self) -> u32 {
64        match self {
65            PollingRate::Hz30 => 30,
66            PollingRate::Hz60 => 60,
67            PollingRate::Hz90 => 90,
68            PollingRate::Hz120 => 120,
69            PollingRate::Custom(hz) => hz.clamp(10, 240),
70        }
71    }
72}
73
74impl Default for PollingRate {
75    fn default() -> Self {
76        PollingRate::Hz60
77    }
78}
79
80/// Input polling configuration
81#[derive(Debug, Clone, Copy)]
82pub struct InputConfig {
83    /// Polling rate
84    pub poll_rate: PollingRate,
85}
86
87impl InputConfig {
88    /// Create config with specific polling rate
89    pub fn new(poll_rate: PollingRate) -> Self {
90        Self { poll_rate }
91    }
92}
93
94impl Default for InputConfig {
95    fn default() -> Self {
96        Self {
97            poll_rate: PollingRate::default(),
98        }
99    }
100}
101
102impl From<PollingRate> for InputConfig {
103    fn from(poll_rate: PollingRate) -> Self {
104        Self { poll_rate }
105    }
106}
107
108/// Convert Button to array index
109fn button_to_index(button: Button) -> Option<usize> {
110    match button {
111        Button::A => Some(0),
112        Button::B => Some(1),
113        Button::SELECT => Some(2),
114        Button::START => Some(3),
115        Button::RIGHT => Some(4),
116        Button::LEFT => Some(5),
117        Button::UP => Some(6),
118        Button::DOWN => Some(7),
119        Button::R => Some(8),
120        Button::L => Some(9),
121        _ => None,
122    }
123}
124
125/// Mark that input polling should be enabled
126fn ensure_input_initialized() {
127    if !POLLING_TASK_RUNNING.swap(true, Ordering::SeqCst) {
128        // Initialize global state on first call
129        let current = !unsafe { KEYPAD_INPUT.read_volatile() };
130        GLOBAL_BUTTON_STATE.store(current, Ordering::SeqCst);
131    }
132}
133
134/// Check for button changes and wake appropriate wakers
135fn poll_input_changes() {
136    let current = !unsafe { KEYPAD_INPUT.read_volatile() };
137    let previous = GLOBAL_BUTTON_STATE.load(Ordering::SeqCst);
138
139    if current != previous {
140        // Find which buttons changed and wake only those wakers
141        let changed = current ^ previous;
142        let buttons = [
143            Button::A,
144            Button::B,
145            Button::SELECT,
146            Button::START,
147            Button::RIGHT,
148            Button::LEFT,
149            Button::UP,
150            Button::DOWN,
151            Button::R,
152            Button::L,
153        ];
154
155        for (i, button) in buttons.iter().enumerate() {
156            let button_mask = button.bits() as u16;
157            if (changed & button_mask) != 0 {
158                // Only wake the waker for this specific button
159                BUTTON_WAKERS[i].wake();
160            }
161        }
162
163        // Update global state after waking relevant futures
164        GLOBAL_BUTTON_STATE.store(current, Ordering::SeqCst);
165    }
166}
167
168/// Background task that polls input at the configured rate
169#[cfg(all(feature = "time", feature = "executor"))]
170#[embassy_executor::task]
171pub async fn input_polling_task(config: InputConfig) {
172    let poll_interval_ms = 1000 / config.poll_rate.as_hz() as u64;
173
174    // Initialize global button state
175    let current = !unsafe { KEYPAD_INPUT.read_volatile() };
176    GLOBAL_BUTTON_STATE.store(current, Ordering::SeqCst);
177
178    loop {
179        poll_input_changes();
180        embassy_time::Timer::after(embassy_time::Duration::from_millis(poll_interval_ms)).await;
181    }
182}
183
184/// Button event types
185#[derive(Debug, Clone, Copy, PartialEq, Eq)]
186pub enum ButtonEvent {
187    /// Button was just pressed
188    Pressed,
189    /// Button was just released
190    Released,
191}
192
193/// Future that waits for a specific button event
194#[must_use = "futures do nothing unless you `.await` or poll them"]
195struct ButtonEventFuture {
196    button: Button,
197    waiting_for_press: bool,
198    completed: bool,
199}
200
201impl ButtonEventFuture {
202    fn new(button: Button, waiting_for_press: bool) -> Self {
203        Self {
204            button,
205            waiting_for_press,
206            completed: false,
207        }
208    }
209}
210
211impl Future for ButtonEventFuture {
212    type Output = ButtonEvent;
213
214    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
215        if self.completed {
216            return Poll::Ready(if self.waiting_for_press {
217                ButtonEvent::Pressed
218            } else {
219                ButtonEvent::Released
220            });
221        }
222
223        if let Some(index) = button_to_index(self.button) {
224            BUTTON_WAKERS[index].register(cx.waker());
225
226            // Check current state
227            let current = !unsafe { KEYPAD_INPUT.read_volatile() };
228            let is_pressed = (current & self.button.bits() as u16) != 0;
229
230            if self.waiting_for_press && is_pressed {
231                self.completed = true;
232                Poll::Ready(ButtonEvent::Pressed)
233            } else if !self.waiting_for_press && !is_pressed {
234                self.completed = true;
235                Poll::Ready(ButtonEvent::Released)
236            } else {
237                Poll::Pending
238            }
239        } else {
240            Poll::Ready(if self.waiting_for_press {
241                ButtonEvent::Pressed
242            } else {
243                ButtonEvent::Released
244            })
245        }
246    }
247}
248
249/// Future that waits for any button event
250#[must_use = "futures do nothing unless you `.await` or poll them"]
251struct AnyButtonEventFuture {
252    last_state: u16,
253}
254
255impl AnyButtonEventFuture {
256    fn new() -> Self {
257        let current = !unsafe { KEYPAD_INPUT.read_volatile() };
258        Self {
259            last_state: current,
260        }
261    }
262}
263
264impl Future for AnyButtonEventFuture {
265    type Output = (Button, ButtonEvent);
266
267    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
268        // Register with all button wakers
269        for waker in &BUTTON_WAKERS {
270            waker.register(cx.waker());
271        }
272
273        // Check current state
274        let current = !unsafe { KEYPAD_INPUT.read_volatile() };
275        let changed = current ^ self.last_state;
276
277        if changed != 0 {
278            // Find which button changed
279            let buttons = [
280                Button::A,
281                Button::B,
282                Button::SELECT,
283                Button::START,
284                Button::RIGHT,
285                Button::LEFT,
286                Button::UP,
287                Button::DOWN,
288                Button::R,
289                Button::L,
290            ];
291
292            for button in buttons.iter() {
293                let button_mask = button.bits() as u16;
294                if (changed & button_mask) != 0 {
295                    let is_pressed = (current & button_mask) != 0;
296
297                    // Update only the specific bit that we're handling
298                    if is_pressed {
299                        self.last_state |= button_mask;
300                    } else {
301                        self.last_state &= !button_mask;
302                    }
303
304                    return Poll::Ready((
305                        *button,
306                        if is_pressed {
307                            ButtonEvent::Pressed
308                        } else {
309                            ButtonEvent::Released
310                        },
311                    ));
312                }
313            }
314        }
315
316        Poll::Pending
317    }
318}
319
320/// Async wrapper for agb input operations
321pub struct AsyncInput {
322    controller: ButtonController,
323    _config: InputConfig,
324}
325
326impl AsyncInput {
327    pub(crate) fn new() -> Self {
328        Self::with_config(InputConfig::default())
329    }
330
331    pub(crate) fn with_config(config: InputConfig) -> Self {
332        ensure_input_initialized();
333
334        Self {
335            controller: ButtonController::new(),
336            _config: config,
337        }
338    }
339
340    /// Wait for a specific button to be pressed
341    pub async fn wait_for_button_press(&mut self, button: Button) -> ButtonEvent {
342        // If button is already pressed, wait for release first
343        let current = !unsafe { KEYPAD_INPUT.read_volatile() };
344        let is_pressed = (current & button.bits() as u16) != 0;
345
346        if is_pressed {
347            // Wait for release first
348            ButtonEventFuture::new(button, false).await;
349        }
350
351        // Now wait for press
352        ButtonEventFuture::new(button, true).await
353    }
354
355    /// Wait for any button to be pressed or released
356    pub async fn wait_for_any_button_press(&mut self) -> (Button, ButtonEvent) {
357        AnyButtonEventFuture::new().await
358    }
359
360    /// Wait for a specific button to be pressed using agb's ButtonController
361    pub async fn wait_for_button_press_polling(&mut self, button: Button) -> ButtonEvent {
362        ButtonPressFuture::new(&mut self.controller, button).await
363    }
364
365    /// Wait for any button to be pressed using agb's ButtonController
366    pub async fn wait_for_any_button_press_polling(&mut self) -> (Button, ButtonEvent) {
367        AnyButtonPressFuture::new(&mut self.controller).await
368    }
369
370    /// Get current button state (non-blocking)
371    pub fn update(&mut self) {
372        self.controller.update();
373    }
374
375    /// Check if a button is currently pressed
376    pub fn is_pressed(&self, button: Button) -> bool {
377        let current = !unsafe { KEYPAD_INPUT.read_volatile() };
378        (current & button.bits() as u16) != 0
379    }
380
381    /// Check if a button is currently pressed using agb's ButtonController
382    pub fn is_pressed_polling(&self, button: Button) -> bool {
383        self.controller.is_pressed(button)
384    }
385
386    /// Check if a button was just pressed this frame using agb's ButtonController
387    pub fn is_just_pressed_polling(&self, button: Button) -> bool {
388        self.controller.is_just_pressed(button)
389    }
390
391    /// Get the tri-state for directional inputs (non-blocking)
392    pub fn x_tri(&self) -> Tri {
393        self.controller.x_tri()
394    }
395
396    /// Get the tri-state for directional inputs (non-blocking)
397    pub fn y_tri(&self) -> Tri {
398        self.controller.y_tri()
399    }
400
401    /// Get the current button state as raw bits
402    pub(crate) fn button_state_bits(&self) -> u16 {
403        let mut bits = 0u16;
404        for button in [
405            Button::A,
406            Button::B,
407            Button::START,
408            Button::SELECT,
409            Button::LEFT,
410            Button::RIGHT,
411            Button::UP,
412            Button::DOWN,
413            Button::L,
414            Button::R,
415        ] {
416            if self.controller.is_pressed(button) {
417                bits |= button.bits() as u16;
418            }
419        }
420        bits
421    }
422}
423
424/// Future that waits for a specific button press using agb's ButtonController
425struct ButtonPressFuture<'a> {
426    controller: &'a mut ButtonController,
427    button: Button,
428    waiting_for_release: bool,
429}
430
431impl<'a> ButtonPressFuture<'a> {
432    fn new(controller: &'a mut ButtonController, button: Button) -> Self {
433        let waiting_for_release = controller.is_pressed(button);
434        Self {
435            controller,
436            button,
437            waiting_for_release,
438        }
439    }
440}
441
442impl<'a> Future for ButtonPressFuture<'a> {
443    type Output = ButtonEvent;
444
445    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
446        self.controller.update();
447
448        let is_pressed = self.controller.is_pressed(self.button);
449
450        if self.waiting_for_release {
451            if !is_pressed {
452                self.waiting_for_release = false;
453                return Poll::Ready(ButtonEvent::Released);
454            }
455        } else if is_pressed {
456            return Poll::Ready(ButtonEvent::Pressed);
457        }
458
459        // Not ready yet, wake on next frame
460        cx.waker().wake_by_ref();
461        Poll::Pending
462    }
463}
464
465/// Future that waits for any button press using agb's ButtonController
466struct AnyButtonPressFuture<'a> {
467    controller: &'a mut ButtonController,
468}
469
470impl<'a> AnyButtonPressFuture<'a> {
471    fn new(controller: &'a mut ButtonController) -> Self {
472        Self { controller }
473    }
474}
475
476impl<'a> Future for AnyButtonPressFuture<'a> {
477    type Output = (Button, ButtonEvent);
478
479    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
480        self.controller.update();
481
482        // Check all buttons for press events
483        let buttons = [
484            Button::A,
485            Button::B,
486            Button::START,
487            Button::SELECT,
488            Button::LEFT,
489            Button::RIGHT,
490            Button::UP,
491            Button::DOWN,
492            Button::L,
493            Button::R,
494        ];
495
496        for &button in &buttons {
497            if self.controller.is_just_pressed(button) {
498                return Poll::Ready((button, ButtonEvent::Pressed));
499            }
500            if self.controller.is_just_released(button) {
501                return Poll::Ready((button, ButtonEvent::Released));
502            }
503        }
504
505        // No button events, wake on next frame
506        cx.waker().wake_by_ref();
507        Poll::Pending
508    }
509}