use crate::context::Context;
pub unsafe trait BelaApplication: Send {
fn setup(&mut self, _context: &mut Context) -> bool {
true
}
fn render(&mut self, context: &mut Context);
fn cleanup(&mut self, _context: &mut Context) {}
}
#[cfg_attr(
not(bela_device),
allow(
dead_code,
reason = "only called by the device-gated system module; still unit-tested on the host"
)
)]
pub mod trampoline {
use core::ffi::c_void;
use bela_sys::BelaContext;
use super::BelaApplication;
use crate::context::Context;
pub unsafe extern "C" fn setup<T: BelaApplication>(
context: *mut BelaContext,
user_data: *mut c_void,
) -> bool {
let app = unsafe { &mut *user_data.cast::<T>() };
let context = unsafe { Context::from_mut_ptr(context) };
app.setup(context)
}
pub unsafe extern "C" fn render<T: BelaApplication>(
context: *mut BelaContext,
user_data: *mut c_void,
) {
let app = unsafe { &mut *user_data.cast::<T>() };
let context = unsafe { Context::from_mut_ptr(context) };
app.render(context);
}
pub unsafe extern "C" fn cleanup<T: BelaApplication>(
context: *mut BelaContext,
user_data: *mut c_void,
) {
let app = unsafe { &mut *user_data.cast::<T>() };
let context = unsafe { Context::from_mut_ptr(context) };
app.cleanup(context);
}
}
#[cfg(test)]
mod tests {
use core::ffi::c_void;
use core::mem;
use bela_sys::BelaContext;
use super::*;
#[derive(Default)]
struct TestApp {
setup_ok: bool,
setup_calls: u32,
render_calls: u32,
cleanup_calls: u32,
frames_seen: u32,
}
unsafe impl BelaApplication for TestApp {
fn setup(&mut self, _context: &mut Context) -> bool {
self.setup_calls += 1;
self.setup_ok
}
fn render(&mut self, context: &mut Context) {
self.render_calls += 1;
self.frames_seen = context.as_sys().audioFrames;
}
fn cleanup(&mut self, _context: &mut Context) {
self.cleanup_calls += 1;
}
}
fn test_context() -> BelaContext {
let mut context: BelaContext = unsafe { mem::zeroed() };
context.audioFrames = 64;
context
}
#[test]
fn trampolines_forward_to_the_application() {
let mut context = test_context();
let mut app = TestApp {
setup_ok: true,
..TestApp::default()
};
let user_data = (&raw mut app).cast::<c_void>();
unsafe {
assert!(trampoline::setup::<TestApp>(&raw mut context, user_data));
trampoline::render::<TestApp>(&raw mut context, user_data);
trampoline::render::<TestApp>(&raw mut context, user_data);
trampoline::cleanup::<TestApp>(&raw mut context, user_data);
}
assert_eq!(app.setup_calls, 1);
assert_eq!(app.render_calls, 2);
assert_eq!(app.cleanup_calls, 1);
assert_eq!(app.frames_seen, 64);
}
#[test]
fn setup_failure_is_reported() {
let mut context = test_context();
let mut app = TestApp::default();
let user_data = (&raw mut app).cast::<c_void>();
let ok = unsafe { trampoline::setup::<TestApp>(&raw mut context, user_data) };
assert!(!ok);
assert_eq!(app.setup_calls, 1);
}
#[test]
fn default_setup_and_cleanup_are_provided() {
struct RenderOnly;
unsafe impl BelaApplication for RenderOnly {
fn render(&mut self, _context: &mut Context) {}
}
let mut context = test_context();
let mut app = RenderOnly;
let user_data = (&raw mut app).cast::<c_void>();
unsafe {
assert!(trampoline::setup::<RenderOnly>(&raw mut context, user_data));
trampoline::cleanup::<RenderOnly>(&raw mut context, user_data);
}
}
}