#![cfg_attr(
not(bela_device),
allow(
dead_code,
reason = "only the fallback main is reachable off-device; the probe code should still compile and lint"
)
)]
use core::sync::atomic::{AtomicU32, Ordering};
use std::process::ExitCode;
use std::sync::Arc;
use bela::{BelaApplication, RenderContext, SetupContext, ThreadInfo};
struct Abort;
impl BelaApplication for Abort {
type RenderState = ();
fn setup(&mut self, _context: &SetupContext) -> bool {
false
}
fn create_render_state(&mut self, _thread: ThreadInfo, _context: &SetupContext) {}
fn render(&self, _state: &mut (), _context: &mut RenderContext) {}
}
struct Idle;
impl BelaApplication for Idle {
type RenderState = ();
fn create_render_state(&mut self, _thread: ThreadInfo, _context: &SetupContext) {}
fn render(&self, _state: &mut (), _context: &mut RenderContext) {}
}
struct Count {
blocks: Arc<AtomicU32>,
}
impl BelaApplication for Count {
type RenderState = ();
fn create_render_state(&mut self, _thread: ThreadInfo, _context: &SetupContext) {}
fn render(&self, _state: &mut (), _context: &mut RenderContext) {
self.blocks.fetch_add(1, Ordering::Relaxed);
}
}
#[cfg(bela_device)]
mod probes {
use core::ffi::c_void;
use core::sync::atomic::{AtomicU32, Ordering};
use core::time::Duration;
use std::io::{self, Write};
use std::sync::Arc;
use std::thread;
use bela::bela_sys::{
Bela_InitSettings_alloc, Bela_InitSettings_free, Bela_cleanupAudio, Bela_defaultSettings,
Bela_initAudio, BelaContext,
};
use bela::{Bela, Error, Settings};
use super::{Abort, Count, Idle};
const RENDER_TIME: Duration = Duration::from_secs(1);
fn report(key: &str, value: &str) {
println!("init-failure: {key}={value}");
let _ = io::stdout().flush();
}
fn cycle_for(render_time: Duration) -> Result<u32, Error> {
let blocks = Arc::new(AtomicU32::new(0));
let app = Count {
blocks: Arc::clone(&blocks),
};
let mut bela = Bela::new(app, &Settings::new())?;
bela.start()?;
thread::sleep(render_time);
drop(bela);
Ok(blocks.load(Ordering::Relaxed))
}
fn cycle() -> Result<u32, Error> {
cycle_for(RENDER_TIME)
}
fn outcome(result: Result<u32, Error>) -> String {
match result {
Ok(0) => "up-but-silent".to_owned(),
Ok(blocks) => format!("rendered-{blocks}"),
Err(error) => format!("failed-{error:?}"),
}
}
fn abort_init() -> String {
match Bela::new(Abort, &Settings::new()) {
Err(error) => format!("failed-{error:?}"),
Ok(bela) => {
drop(bela);
"created".to_owned()
}
}
}
fn hand_back() {
report("cleanup", "calling");
unsafe { Bela_cleanupAudio() };
report("cleanup", "returned");
}
pub(crate) fn render_check(render_time: Option<Duration>) {
report(
"cycle",
&outcome(cycle_for(render_time.unwrap_or(RENDER_TIME))),
);
}
pub(crate) fn abort() {
report("abort", &abort_init());
}
pub(crate) fn abort_cleanup() {
report("abort", &abort_init());
hand_back();
}
pub(crate) fn abort_then_new() {
report("abort", &abort_init());
report("second", &outcome(cycle()));
}
pub(crate) fn abort_cleanup_then_new() {
report("abort", &abort_init());
hand_back();
report("second", &outcome(cycle()));
}
struct RawApp {
marker: [u8; 64],
cleaned: bool,
}
#[expect(
clippy::missing_const_for_fn,
reason = "a function pointer handed to C, which cannot be const-evaluated"
)]
unsafe extern "C" fn raw_setup(_context: *mut BelaContext, _user_data: *mut c_void) -> bool {
false
}
#[expect(
clippy::missing_const_for_fn,
reason = "a function pointer handed to C, which cannot be const-evaluated"
)]
unsafe extern "C" fn raw_render(_context: *mut BelaContext, _user_data: *mut c_void) {}
unsafe extern "C" fn raw_cleanup_callback(_context: *mut BelaContext, user_data: *mut c_void) {
let app = unsafe { &mut *user_data.cast::<RawApp>() };
app.cleaned = true;
report("cleanup-callback", &format!("ran-marker-{}", app.marker[0]));
}
pub(crate) fn raw_cleanup() {
let app = Box::into_raw(Box::new(RawApp {
marker: [7; 64],
cleaned: false,
}));
let ret = unsafe {
let raw = Bela_InitSettings_alloc();
Bela_defaultSettings(raw);
(*raw).setup = Some(raw_setup);
(*raw).render = Some(raw_render);
(*raw).cleanup = Some(raw_cleanup_callback);
let ret = Bela_initAudio(raw, app.cast::<c_void>());
Bela_InitSettings_free(raw);
ret
};
report("raw-init", &format!("returned-{ret}"));
report("cleanup", "calling");
unsafe { Bela_cleanupAudio() };
report("cleanup", "returned");
let app = unsafe { Box::from_raw(app) };
report(
"cleanup-callback",
if app.cleaned { "ran" } else { "never-ran" },
);
}
pub(crate) fn busy_probe(wait: Duration) {
report("busy-first", &outcome(cycle()));
thread::sleep(wait);
report("busy-second", &outcome(cycle()));
}
pub(crate) fn cycles(count: u32) {
for index in 1..=count {
report(&format!("cycle-{index}"), &outcome(cycle()));
}
report("cycles", "completed");
}
pub(crate) fn init_cycles(count: u32) {
for index in 1..=count {
let outcome = match Bela::new(Idle, &Settings::new()) {
Err(error) => format!("failed-{error:?}"),
Ok(bela) => {
drop(bela);
"built-and-dropped".to_owned()
}
};
report(&format!("init-cycle-{index}"), &outcome);
}
report("init-cycles", "completed");
}
}
#[cfg(bela_device)]
fn main() -> ExitCode {
use core::time::Duration;
use std::env::args;
let arguments: Vec<String> = args().skip(1).collect();
let probe: Vec<&str> = arguments.iter().map(String::as_str).collect();
match probe.as_slice() {
["render-check"] => probes::render_check(None),
["render-check", seconds] => {
let Ok(seconds) = seconds.parse() else {
eprintln!("render-check takes a number of seconds, not {seconds:?}");
return ExitCode::FAILURE;
};
probes::render_check(Some(Duration::from_secs(seconds)));
}
["abort"] => probes::abort(),
["abort-cleanup"] => probes::abort_cleanup(),
["raw-cleanup"] => probes::raw_cleanup(),
["abort-then-new"] => probes::abort_then_new(),
["abort-cleanup-then-new"] => probes::abort_cleanup_then_new(),
["busy-probe", seconds] => {
let Ok(seconds) = seconds.parse() else {
eprintln!("busy-probe takes a number of seconds, not {seconds:?}");
return ExitCode::FAILURE;
};
probes::busy_probe(Duration::from_secs(seconds));
}
["cycles", count] => {
let Ok(count) = count.parse() else {
eprintln!("cycles takes a count, not {count:?}");
return ExitCode::FAILURE;
};
probes::cycles(count);
}
["init-cycles", count] => {
let Ok(count) = count.parse() else {
eprintln!("init-cycles takes a count, not {count:?}");
return ExitCode::FAILURE;
};
probes::init_cycles(count);
}
_ => {
eprintln!(
"usage: init_failure (render-check [seconds] | abort | abort-cleanup\n\
\x20 | raw-cleanup | abort-then-new\n\
\x20 | abort-cleanup-then-new | busy-probe <seconds>\n\
\x20 | cycles <count> | init-cycles <count>)\n\
one probe per run: what is being measured is partly what the previous process left"
);
return ExitCode::FAILURE;
}
}
ExitCode::SUCCESS
}
#[cfg(not(bela_device))]
fn main() -> ExitCode {
eprintln!("This example must be cross-compiled for Bela Gem (aarch64-unknown-linux-gnu).");
ExitCode::FAILURE
}