use core::ffi::c_int;
use core::fmt;
use core::marker::PhantomData;
use core::time::Duration;
use std::ffi::OsStr;
use std::thread;
use bela_sys::BelaInitSettings;
use crate::application::BelaApplication;
use crate::cmdline::{self, Arguments};
use crate::cpu;
use crate::error::Error;
use crate::runtime::{Runtime, trampoline, user_data};
use crate::settings::{self, Settings};
use crate::singleton::Claim;
use crate::task;
struct InitSettings {
raw: *mut BelaInitSettings,
}
impl InitSettings {
fn alloc() -> Self {
Self {
raw: unsafe { bela_sys::Bela_InitSettings_alloc() },
}
}
#[allow(
clippy::needless_pass_by_ref_mut,
reason = "the settings are written through the pointer, so an exclusive borrow is what \
keeps a second writer out; the compiler cannot see that through a raw pointer"
)]
const fn as_mut_ptr(&mut self) -> *mut BelaInitSettings {
self.raw
}
}
impl Drop for InitSettings {
fn drop(&mut self) {
unsafe { bela_sys::Bela_InitSettings_free(self.raw) };
}
}
pub struct Bela<T: BelaApplication> {
runtime: *mut Runtime<T>,
started: bool,
_claim: Claim,
_marker: PhantomData<T>,
}
impl<T: BelaApplication> Bela<T> {
pub fn new(application: T, settings: &Settings) -> Result<Self, Error> {
Self::init(application, settings, None)
}
pub fn new_with_args<I, S>(application: T, settings: &Settings, args: I) -> Result<Self, Error>
where
I: IntoIterator<Item = S>,
S: AsRef<OsStr>,
{
let arguments = Arguments::new(args)?;
Self::init(application, settings, Some(arguments))
}
fn init(
application: T,
settings: &Settings,
mut arguments: Option<Arguments>,
) -> Result<Self, Error> {
let mut claim = Claim::take()?;
let monitoring = settings
.cpu_monitoring_cycle()
.map(cpu::check_cycle)
.transpose()?;
let mut init_settings = InitSettings::alloc();
let (ret, runtime) = unsafe {
let raw = init_settings.as_mut_ptr();
bela_sys::Bela_defaultSettings(raw);
settings.apply_to(&mut *raw);
let prepared = arguments
.as_mut()
.map_or(Ok(()), |arguments| cmdline::parse(arguments, &mut *raw))
.and_then(|()| {
settings::check_supported(&*raw, settings.cpu_monitoring_cycle(), &application)
})
.and_then(|()| cpu::apply_monitoring(monitoring));
prepared?;
let runtime = Box::into_raw(Box::new(Runtime::new(
application,
settings::render_threads(&*raw),
)));
(*raw).setup = Some(trampoline::setup::<T>);
(*raw).render_pre = Some(trampoline::render_pre::<T>);
(*raw).render = Some(trampoline::render::<T>);
(*raw).render_post = Some(trampoline::render_post::<T>);
(*raw).cleanup = Some(trampoline::cleanup::<T>);
let ret = bela_sys::Bela_initAudio(raw, user_data(runtime));
(ret, runtime)
};
drop(init_settings);
if ret != 0 {
drop(unsafe { Box::from_raw(runtime) });
claim.poison();
return Err(Error::Init(ret));
}
Ok(Self {
runtime,
started: false,
_claim: claim,
_marker: PhantomData,
})
}
pub fn start(&mut self) -> Result<(), Error> {
if self.started {
return Ok(());
}
let ret = unsafe { bela_sys::Bela_startAudio() };
if ret != 0 {
return Err(Error::Start(ret));
}
self.started = true;
Ok(())
}
pub fn stop(&mut self) {
if self.started {
task::teardown(|| self.stop_audio());
}
}
fn stop_audio(&mut self) {
if self.started {
unsafe { bela_sys::Bela_stopAudio() };
self.started = false;
}
}
#[must_use]
pub fn callback_faults(&self) -> u32 {
unsafe { &*self.runtime }.faults()
}
#[must_use]
pub fn callback_faults_while_stopping(&self) -> u32 {
unsafe { &*self.runtime }.faults_while_stopping()
}
pub fn run(application: T, settings: &Settings) -> Result<(), Error> {
Self::new(application, settings)?.until_stopped()
}
pub fn run_with_args<I, S>(application: T, settings: &Settings, args: I) -> Result<(), Error>
where
I: IntoIterator<Item = S>,
S: AsRef<OsStr>,
{
Self::new_with_args(application, settings, args)?.until_stopped()
}
pub fn until_stopped(mut self) -> Result<(), Error> {
let handler = request_stop_on_signal as extern "C" fn(c_int);
for signal in [libc::SIGINT, libc::SIGTERM, libc::SIGHUP] {
unsafe { libc::signal(signal, handler as libc::sighandler_t) };
}
self.start()?;
while !crate::stop_requested() {
thread::sleep(Duration::from_millis(10));
}
self.stop();
let while_stopping = self.callback_faults_while_stopping();
if while_stopping != 0 {
crate::rt_println!(
"bela: {while_stopping} callback(s) were refused while stopping, which is how a \
block in flight is abandoned; the last block may be short"
);
}
match self.callback_faults() {
0 => Ok(()),
faults => Err(Error::CallbackFaults(faults)),
}
}
}
impl<T: BelaApplication> fmt::Debug for Bela<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Bela")
.field("started", &self.started)
.field("callback_faults", &self.callback_faults())
.field(
"callback_faults_while_stopping",
&self.callback_faults_while_stopping(),
)
.finish_non_exhaustive()
}
}
extern "C" fn request_stop_on_signal(_signal: c_int) {
crate::request_stop();
}
impl<T: BelaApplication> Drop for Bela<T> {
fn drop(&mut self) {
task::teardown(|| {
self.stop_audio();
unsafe { bela_sys::Bela_cleanupAudio() };
});
drop(unsafe { Box::from_raw(self.runtime) });
}
}