This repository provides async/await support for Game Boy Advance development using the embassy executor integrated with the agb library.
- Async/await support: Write GBA games using modern async Rust
- Embassy executor integration: Leverage embassy's powerful task scheduling
- Configurable time driver: Precise timing using any of GBA's 4 hardware timers
- Async APIs: Async wrappers for display, input, and sound operations
- Full agb compatibility: Works alongside existing agb code
- Rust nightly toolchain (for
build-std) thumbv4t-none-eabitarget:rustup target add thumbv4t-none-eabi- mGBA or another GBA emulator for testing
Add the dependencies:
cargo add [email protected]
cargo add embassy-agb
cargo add [email protected]Or manually add to your Cargo.toml:
[dependencies]
agb = "0.22.6"
embassy-agb = "0.1"
embassy-executor = "0.9"Embassy-agb supports using any of the GBA's 4 hardware timers for the time driver. Timer2 is the default. To use a different timer:
cargo add embassy-agb --no-default-features --features executor,time-driver-timer0Available timer options:
time-driver-timer0- Timer0 (used by sound system)time-driver-timer1- Timer1 (used by sound system)time-driver-timer2- Timer2 (default, available for general use)time-driver-timer3- Timer3 (available for general use)
Note: Timer0 and Timer1 are also used by agb's sound system. Using Timer2 or Timer3 avoids potential conflicts.
Create a rust-toolchain.toml in your project root:
[toolchain]
channel = "nightly"
components = ["rust-src", "clippy"]Optionally, create .cargo/config.toml to set the default target:
[build]
target = "thumbv4t-none-eabi"
[unstable]
build-std = ["core", "alloc"]Create an async GBA application:
#![no_std]
#![no_main]
use embassy_agb::{time::Timer, Spawner};
#[embassy_agb::main]
async fn main(spawner: Spawner) {
let mut gba = embassy_agb::init(Default::default());
// Spawn background tasks
spawner.must_spawn(display_task(gba.display()));
spawner.must_spawn(audio_task(gba.mixer()));
// Main game loop
let mut input = gba.input();
loop {
// Wait for input asynchronously
let (button, event) = input.wait_for_any_button_press().await;
// Handle input...
// Run at 60 FPS
Timer::after_millis(16).await;
}
}
#[embassy_executor::task]
async fn display_task(mut display: embassy_agb::display::AsyncDisplay<'_>) {
loop {
// Wait for VBlank and render frame
let mut frame = display.frame().await;
// Rendering code...
}
}
#[embassy_executor::task]
async fn audio_task(mut mixer: embassy_agb::sound::AsyncMixer<'_>) {
mixer.init(agb::sound::mixer::Frequency::Hz32768);
loop {
// Process audio frame
mixer.frame().await;
}
}Embassy-agb integrates the embassy async executor with agb's hardware abstraction:
- Executor: Uses embassy's
arch-spinexecutor optimized for the GBA's ARM7TDMI processor - Time Driver: Implements embassy's time driver interface using any of GBA's 4 timers (configurable via feature flags)
- Async APIs: Provides async wrappers around agb's display, input, and sound systems
- Task Management: Supports spawning multiple concurrent tasks for different game systems
- Time Resolution: 32.768kHz tick rate for precise timing
- Frame Rate: Designed for 60 FPS game loops
- Power Efficiency: Uses
halt()instruction when no tasks are ready - Memory Overhead: Minimal overhead over synchronous agb code
Embassy-agb is designed to be fully compatible with existing agb code:
- Use
gba.agb()to access the underlyingagb::Gbainstance - Mix async and sync code as needed
- Existing agb examples can be gradually migrated to async
- agb documentation
- agbrs book
- embassy documentation
- mGBA emulator
- ⭐️ zpg6/agbrs-capture - For capturing GIFs of projects and examples
- ⭐️ zpg6/aseprite-preview - VSCode / Cursor extension for quick previews of Aseprite pixel art files (.aseprite and .ase)
Contributions are welcome! Whether it's bug fixes, feature additions, or documentation improvements, we appreciate your help in making this project better. For major changes or new features, please open an issue first to discuss what you would like to change.
