embassy_agb/
lib.rs

1#![no_std]
2// This appears to be needed for testing to work
3#![cfg_attr(any(test, feature = "testing"), no_main)]
4#![cfg_attr(any(test, feature = "testing"), feature(custom_test_frameworks))]
5#![cfg_attr(
6    any(test, feature = "testing"),
7    test_runner(agb::test_runner::test_runner)
8)]
9#![cfg_attr(
10    any(test, feature = "testing"),
11    reexport_test_harness_main = "test_main"
12)]
13#![warn(missing_docs)]
14
15//! # Embassy async support for agb
16//!
17//! This crate provides async/await support for Game Boy Advance development using the embassy executor.
18//! It integrates with the existing agb library to provide async APIs for display, input, sound, and timing.
19//!
20//! ## Features
21//!
22//! - Async display operations (VBlank waiting, DMA transfers)
23//! - Async input handling (button press events) with automatic polling
24//! - Async sound mixing
25//! - Embassy time integration with GBA timers
26//! - Task spawning and management
27//! - Automatic power management via Halt mode
28//!
29//! ## Example
30//!
31//! ```rust,no_run
32//! #![no_std]
33//! #![no_main]
34//!
35//! use embassy_agb::Spawner;
36//! use embassy_agb::agb::sound::mixer::Frequency;
37//! use agb::include_wav;
38//!
39//! static JUMP: agb::sound::mixer::SoundData = include_wav!("jump.wav");
40//!
41//! #[embassy_agb::main]
42//! async fn main(_spawner: Spawner) -> ! {
43//!     let mut gba = embassy_agb::init(Default::default());
44//!     
45//!     // Get peripherals with convenient frame handling
46//!     let mut peripherals = gba.peripherals(Frequency::Hz10512);
47//!     
48//!     loop {
49//!         // wait_frame() returns events that occurred during the frame
50//!         let events = peripherals.wait_frame().await;
51//!         
52//!         // Check button events from the frame context
53//!         if events.is_pressed(agb::input::Button::A) {
54//!             peripherals.play_sound(&JUMP);
55//!         }
56//!         
57//!         // Or access peripherals directly for continuous state
58//!         if peripherals.input.is_pressed(agb::input::Button::LEFT) {
59//!             // Move left...
60//!         }
61//!         
62//!         // Use frame counter for animations
63//!         let animation_frame = (events.frame_count / 8) as usize;
64//!     }
65//! }
66//! ```
67
68// Include generated code
69include!(concat!(env!("OUT_DIR"), "/_generated.rs"));
70
71#[cfg(feature = "executor")]
72pub use embassy_executor::Spawner;
73
74// Re-export our macros
75pub use embassy_agb_macros::{main, task};
76
77#[cfg(feature = "time")]
78pub use embassy_time as time;
79
80#[cfg(feature = "time")]
81pub use embassy_time::{Duration, Instant, Ticker, Timer};
82
83pub use embassy_futures as futures;
84pub use embassy_sync as sync;
85
86// Re-export agb for convenience
87pub use agb;
88
89/// Configuration types for embassy-agb
90pub mod config;
91pub use config::*;
92
93#[cfg(feature = "_time-driver")]
94mod time_driver;
95
96#[cfg(feature = "executor")]
97mod executor;
98#[cfg(feature = "executor")]
99pub use executor::*;
100
101/// Async display utilities
102pub mod display;
103pub mod input;
104/// Async sound utilities
105pub mod sound;
106/// Utility functions and macros
107pub mod utils;
108
109/// Internal utilities (do not use directly)
110#[doc(hidden)]
111pub mod _internal;
112
113/// Initialize the embassy-agb HAL with the given configuration.
114///
115/// This function must be called once before using any embassy-agb functionality.
116/// It initializes the underlying agb library and sets up embassy integration.
117///
118/// # Example
119///
120/// ```rust,no_run
121/// let gba = embassy_agb::init(Default::default());
122/// ```
123pub fn init(config: Config) -> InitializedGba {
124    // Get the agb instance from internal storage (set by macro)
125    let gba = unsafe { _internal::get_agb_instance() };
126
127    // Configure the time driver with user settings
128    #[cfg(feature = "_time-driver")]
129    time_driver::configure_timer_frequency(config.timer.overflow_amount);
130
131    // Take peripherals
132    let peripherals = Peripherals::take();
133
134    InitializedGba {
135        gba,
136        peripherals,
137        _config: config,
138    }
139}
140
141/// The initialized GBA with embassy integration
142pub struct InitializedGba {
143    gba: &'static mut agb::Gba,
144    #[allow(dead_code)]
145    peripherals: Peripherals,
146    _config: Config,
147}
148
149impl InitializedGba {
150    /// Get a convenient peripheral wrapper with automatic frame handling (recommended)
151    ///
152    /// This is the recommended high-level API for most games. It returns a `GbaPeripherals`
153    /// struct that bundles display, mixer, and input together with automatic per-frame updates.
154    ///
155    /// **Features:**
156    /// - Direct field access: `peripherals.display`, `peripherals.mixer`, `peripherals.input`
157    /// - Automatic frame handling: `wait_frame()` handles input updates, audio mixing, and VBlank
158    /// - Frame events: Returns button presses, releases, and frame counter
159    /// - Convenience methods: `play_sound()` and `play_sound_high_priority()`
160    ///
161    /// For advanced use cases requiring finer control, see [`split()`](Self::split).
162    ///
163    /// # Example
164    ///
165    /// ```rust,no_run
166    /// # use embassy_agb::agb::sound::mixer::Frequency;
167    /// # use agb::include_wav;
168    /// # static JUMP: agb::sound::mixer::SoundData = include_wav!("jump.wav");
169    /// # async fn example() {
170    /// let mut gba = embassy_agb::init(Default::default());
171    /// let mut peripherals = gba.peripherals(Frequency::Hz10512);
172    ///
173    /// loop {
174    ///     let events = peripherals.wait_frame().await;
175    ///     
176    ///     if events.is_pressed(agb::input::Button::A) {
177    ///         peripherals.play_sound(&JUMP);
178    ///     }
179    /// }
180    /// # }
181    /// ```
182    pub fn peripherals(
183        &mut self,
184        mixer_frequency: agb::sound::mixer::Frequency,
185    ) -> GbaPeripherals<'_> {
186        GbaPeripherals::new(
187            &mut self.gba,
188            mixer_frequency,
189            input::InputConfig::default(),
190        )
191    }
192
193    /// Get peripherals with custom input polling configuration
194    ///
195    /// Same as [`peripherals()`](Self::peripherals) but allows customizing the input
196    /// polling rate (default is 60Hz).
197    pub fn peripherals_with_input_config(
198        &mut self,
199        mixer_frequency: agb::sound::mixer::Frequency,
200        input_config: input::InputConfig,
201    ) -> GbaPeripherals<'_> {
202        GbaPeripherals::new(&mut self.gba, mixer_frequency, input_config)
203    }
204
205    /// Split the GBA into display, mixer, and input peripherals
206    ///
207    /// This is the lower-level API that gives you separate components.
208    /// Consider using [`peripherals()`](InitializedGba::peripherals) for a more convenient API.
209    ///
210    /// # Example
211    ///
212    /// ```rust,no_run
213    /// # use embassy_agb::agb::sound::mixer::Frequency;
214    /// # async fn example() {
215    /// let mut gba = embassy_agb::init(Default::default());
216    /// let (mut mixer, display, mut input) = gba.split(Frequency::Hz10512);
217    ///
218    /// loop {
219    ///     input.update();
220    ///     mixer.frame();
221    ///     display.wait_for_vblank().await;
222    /// }
223    /// # }
224    /// ```
225    pub fn split(
226        &mut self,
227        mixer_frequency: agb::sound::mixer::Frequency,
228    ) -> (
229        sound::AsyncMixer<'_>,
230        display::AsyncDisplay<'_>,
231        input::AsyncInput,
232    ) {
233        let mixer = sound::AsyncMixer::new(&mut self.gba.mixer, mixer_frequency);
234        let display = display::AsyncDisplay::new(&mut self.gba.graphics);
235        let input = input::AsyncInput::new();
236        (mixer, display, input)
237    }
238
239    /// Split the GBA into display, mixer, and input with custom input configuration
240    pub fn split_with_input_config(
241        &mut self,
242        mixer_frequency: agb::sound::mixer::Frequency,
243        input_config: input::InputConfig,
244    ) -> (
245        sound::AsyncMixer<'_>,
246        display::AsyncDisplay<'_>,
247        input::AsyncInput,
248    ) {
249        let mixer = sound::AsyncMixer::new(&mut self.gba.mixer, mixer_frequency);
250        let display = display::AsyncDisplay::new(&mut self.gba.graphics);
251        let input = input::AsyncInput::with_config(input_config);
252        (mixer, display, input)
253    }
254
255    /// Get the display peripheral for async operations
256    pub fn display(&mut self) -> display::AsyncDisplay<'_> {
257        display::AsyncDisplay::new(&mut self.gba.graphics)
258    }
259
260    /// Get the mixer peripheral for async operations
261    ///
262    /// Note: If you need to use display or input after creating the mixer,
263    /// use [`split()`](InitializedGba::split) instead to avoid borrow checker issues.
264    pub fn mixer(&mut self, frequency: agb::sound::mixer::Frequency) -> sound::AsyncMixer<'_> {
265        sound::AsyncMixer::new(&mut self.gba.mixer, frequency)
266    }
267
268    /// Get the input peripheral for async operations
269    pub fn input(&mut self) -> input::AsyncInput {
270        input::AsyncInput::new()
271    }
272
273    /// Get the input peripheral for async operations with custom configuration
274    pub fn input_with_config(&mut self, config: input::InputConfig) -> input::AsyncInput {
275        input::AsyncInput::with_config(config)
276    }
277
278    /// Get access to the underlying agb::Gba for compatibility
279    pub fn agb(&mut self) -> &mut agb::Gba {
280        self.gba
281    }
282}
283
284/// Frame events returned by [`GbaPeripherals::wait_frame()`]
285///
286/// Provides information about what happened during the frame.
287///
288/// ## What's Included
289///
290/// - **Button presses**: Buttons that transitioned from released to pressed
291/// - **Button releases**: Buttons that transitioned from pressed to released
292/// - **Frame counter**: Auto-incrementing counter for animations and timing
293#[derive(Debug, Clone, Copy, Default)]
294pub struct FrameEvents {
295    /// Bit flags for buttons that were just pressed this frame
296    pressed: u16,
297    /// Bit flags for buttons that were just released this frame  
298    released: u16,
299    /// Frame counter (wraps at u32::MAX)
300    pub frame_count: u32,
301}
302
303impl FrameEvents {
304    /// Check if a specific button was just pressed this frame
305    pub fn is_pressed(&self, button: agb::input::Button) -> bool {
306        (self.pressed & button.bits() as u16) != 0
307    }
308
309    /// Check if a specific button was just released this frame
310    pub fn is_released(&self, button: agb::input::Button) -> bool {
311        (self.released & button.bits() as u16) != 0
312    }
313
314    /// Check if any button was pressed this frame
315    pub fn any_pressed(&self) -> bool {
316        self.pressed != 0
317    }
318
319    /// Check if any button was released this frame
320    pub fn any_released(&self) -> bool {
321        self.released != 0
322    }
323
324    /// Get all buttons that were pressed this frame as a bitmask
325    pub fn pressed_buttons(&self) -> u16 {
326        self.pressed
327    }
328
329    /// Get all buttons that were released this frame as a bitmask
330    pub fn released_buttons(&self) -> u16 {
331        self.released
332    }
333}
334
335/// High-level peripheral wrapper with automatic frame handling
336///
337/// This struct bundles the GBA's display, sound mixer, and input together with
338/// automatic per-frame updates. It follows Embassy's design pattern of providing
339/// direct field access plus convenient helper methods.
340///
341/// ## Design
342///
343/// - **Public fields**: Access `display`, `mixer`, and `input` directly
344/// - **Frame synchronization**: `wait_frame()` handles all per-frame updates automatically
345/// - **Event-driven**: Returns frame events (button presses/releases, frame counter)
346/// - **Zero overhead**: Just a thin wrapper with smart defaults
347///
348/// ## Usage Pattern
349///
350/// 1. Call `wait_frame()` once per game loop iteration
351/// 2. Handle button events from the returned `FrameEvents`
352/// 3. Access peripherals directly when needed (e.g., `peripherals.mixer`)
353/// 4. Use convenience methods like `play_sound()` for common operations
354///
355/// # Example
356///
357/// ```rust,no_run
358/// # use embassy_agb::agb::sound::mixer::{Frequency, SoundChannel};
359/// # use agb::include_wav;
360/// # static JUMP: agb::sound::mixer::SoundData = include_wav!("jump.wav");
361/// # async fn example() {
362/// let mut gba = embassy_agb::init(Default::default());
363/// let mut peripherals = gba.peripherals(Frequency::Hz10512);
364///
365/// loop {
366///     // wait_frame() returns events that occurred during the frame
367///     let events = peripherals.wait_frame().await;
368///     
369///     // Check button events from the returned context
370///     if events.is_pressed(agb::input::Button::A) {
371///         peripherals.play_sound(&JUMP);
372///     }
373///     
374///     // Access peripherals directly for continuous state
375///     if peripherals.input.is_pressed(agb::input::Button::LEFT) {
376///         // Move left...
377///     }
378///     
379///     // Use frame counter for animations
380///     let anim_frame = (events.frame_count / 8) as usize;
381/// }
382/// # }
383/// ```
384pub struct GbaPeripherals<'a> {
385    /// Display peripheral for VBlank and rendering
386    pub display: display::AsyncDisplay<'a>,
387    /// Sound mixer for audio playback
388    pub mixer: sound::AsyncMixer<'a>,
389    /// Input peripheral for button handling
390    pub input: input::AsyncInput,
391    frame_count: u32,
392    prev_button_state: u16,
393}
394
395impl<'a> GbaPeripherals<'a> {
396    fn new(
397        gba: &'a mut agb::Gba,
398        mixer_frequency: agb::sound::mixer::Frequency,
399        input_config: input::InputConfig,
400    ) -> Self {
401        Self {
402            mixer: sound::AsyncMixer::new(&mut gba.mixer, mixer_frequency),
403            display: display::AsyncDisplay::new(&mut gba.graphics),
404            input: input::AsyncInput::with_config(input_config),
405            frame_count: 0,
406            prev_button_state: 0,
407        }
408    }
409
410    /// Wait for the next frame, automatically handling all per-frame updates
411    ///
412    /// This method:
413    /// 1. Updates input state (detects button changes)
414    /// 2. Processes one frame of audio mixing
415    /// 3. Waits for VBlank (~16.7ms at 60Hz)
416    /// 4. Returns frame events (button changes, frame count, etc.)
417    ///
418    /// Call this once per frame in your game loop.
419    ///
420    /// # Example
421    ///
422    /// ```rust,no_run
423    /// # async fn example(mut peripherals: embassy_agb::GbaPeripherals<'_>) {
424    /// loop {
425    ///     let events = peripherals.wait_frame().await;
426    ///     
427    ///     if events.is_pressed(agb::input::Button::A) {
428    ///         // Button A was just pressed this frame
429    ///     }
430    ///     
431    ///     // Use frame_count for animations
432    ///     let animation_frame = (events.frame_count / 8) as usize;
433    /// }
434    /// # }
435    /// ```
436    pub async fn wait_frame(&mut self) -> FrameEvents {
437        self.input.update();
438
439        // Get current button state as raw bits
440        let current_state = self.input.button_state_bits();
441
442        // Calculate button changes
443        let pressed = current_state & !self.prev_button_state;
444        let released = !current_state & self.prev_button_state;
445
446        self.prev_button_state = current_state;
447
448        self.mixer.frame();
449        self.display.wait_for_vblank().await;
450
451        let events = FrameEvents {
452            pressed,
453            released,
454            frame_count: self.frame_count,
455        };
456
457        self.frame_count = self.frame_count.wrapping_add(1);
458
459        events
460    }
461
462    /// Play a sound effect with default priority
463    ///
464    /// Convenience method that creates a `SoundChannel` and plays it through the mixer.
465    /// Returns `Ok(channel_id)` if the sound starts playing, or `Err(SoundError)`
466    /// if all channels are busy.
467    ///
468    /// # Example
469    ///
470    /// ```rust,no_run
471    /// # use agb::include_wav;
472    /// # static JUMP: agb::sound::mixer::SoundData = include_wav!("jump.wav");
473    /// # async fn example(mut peripherals: embassy_agb::GbaPeripherals<'_>) {
474    /// let events = peripherals.wait_frame().await;
475    ///
476    /// if events.is_pressed(agb::input::Button::A) {
477    ///     peripherals.play_sound(&JUMP);
478    /// }
479    /// # }
480    /// ```
481    pub fn play_sound(
482        &mut self,
483        sound: &'static agb::sound::mixer::SoundData,
484    ) -> Result<agb::sound::mixer::ChannelId, sound::SoundError> {
485        let channel = agb::sound::mixer::SoundChannel::new(*sound);
486        self.mixer.play_sound(channel)
487    }
488
489    /// Play a sound effect with high priority
490    ///
491    /// High priority sounds will replace low priority sounds if all channels are busy.
492    /// Use this for important sounds like background music or critical sound effects.
493    ///
494    /// # Example
495    ///
496    /// ```rust,no_run
497    /// # use agb::include_wav;
498    /// # static BGM: agb::sound::mixer::SoundData = include_wav!("music.wav");
499    /// # async fn example(mut peripherals: embassy_agb::GbaPeripherals<'_>) {
500    /// // Play background music with high priority so it doesn't get interrupted
501    /// peripherals.play_sound_high_priority(&BGM);
502    /// # }
503    /// ```
504    pub fn play_sound_high_priority(
505        &mut self,
506        sound: &'static agb::sound::mixer::SoundData,
507    ) -> Result<agb::sound::mixer::ChannelId, sound::SoundError> {
508        let channel = agb::sound::mixer::SoundChannel::new_high_priority(*sound);
509        self.mixer.play_sound(channel)
510    }
511}
512
513/// Enable automatic input polling with the given polling rate.
514///
515/// This function should be called once at startup to automatically spawn
516/// the input polling task. If not called, input methods will still work
517/// but will use polling-based approach instead of interrupt-driven.
518///
519/// # Example
520///
521/// ```rust,no_run
522/// use embassy_agb::input::PollingRate;
523///
524/// #[embassy_agb::main]
525/// async fn main(spawner: Spawner) -> ! {
526///     let mut gba = embassy_agb::init(Default::default());
527///     
528///     // Enable automatic input polling at 60Hz
529///     embassy_agb::enable_input_polling(&spawner, PollingRate::Hz60);
530///     
531///     let mut input = gba.input();
532///     // ... rest of your code
533/// }
534/// ```
535#[cfg(all(feature = "time", feature = "executor"))]
536pub fn enable_input_polling(spawner: &Spawner, rate: input::PollingRate) {
537    let config = input::InputConfig::from(rate);
538    spawner.must_spawn(input::input_polling_task(config));
539}