use libffi::high::{ClosureMut1, FnPtr1};
use libpd_sys::{
_pdinstance, libpd_free_instance, libpd_get_instancedata, libpd_main_instance,
libpd_new_instance, libpd_num_instances, libpd_set_instance, libpd_set_instancedata,
libpd_this_instance, t_libpd_freehook,
};
use std::{any::TypeId, ffi::c_void, mem};
use crate::{error::InstanceError, functions};
type FreeHookCodePtr = *const FnPtr1<'static, *mut c_void, ()>;
#[derive(Debug, Clone, Eq)]
pub struct PdInstance {
inner: *mut _pdinstance,
number: i32,
stored_type: Option<TypeId>,
}
impl PartialEq for PdInstance {
fn eq(&self, other: &Self) -> bool {
self.number == other.number
}
}
unsafe impl Send for PdInstance {}
unsafe impl Sync for PdInstance {}
impl PdInstance {
pub fn new() -> Result<Self, InstanceError> {
if instance_count() == 0 {
functions::init()
.map_err(|err| InstanceError::InstanceFailedToCreate(err.to_string()))?;
let main_instance_ptr = unsafe { libpd_main_instance() };
unsafe {
libpd_set_instance(main_instance_ptr);
}
return Ok(Self {
inner: main_instance_ptr,
number: unsafe { (*main_instance_ptr).pd_instanceno },
stored_type: None,
});
}
let currently_set_instance_ptr = unsafe { libpd_this_instance() };
let new_instance_ptr = unsafe { libpd_new_instance() };
if new_instance_ptr.is_null() {
return Err(InstanceError::InstanceFailedToCreate(
"Returned instance pointer is null.".to_owned(),
));
}
unsafe {
libpd_set_instance(new_instance_ptr);
}
functions::init().map_err(|err| InstanceError::InstanceFailedToCreate(err.to_string()))?;
if currently_set_instance_ptr.is_null() {
let main_instance_ptr = unsafe { libpd_main_instance() };
unsafe {
libpd_set_instance(main_instance_ptr);
}
} else {
unsafe {
libpd_set_instance(currently_set_instance_ptr);
}
}
Ok(Self {
inner: new_instance_ptr,
number: unsafe { (*new_instance_ptr).pd_instanceno },
stored_type: None,
})
}
pub const fn as_ptr(&self) -> *mut _pdinstance {
self.inner
}
pub fn set_as_current(&self) {
unsafe { libpd_set_instance(self.inner) }
}
pub const fn number(&self) -> i32 {
self.number
}
pub fn system_time(&self) -> f64 {
unsafe { (*self.inner).pd_systime }
}
pub fn is_locked(&self) -> bool {
unsafe { (*self.inner).pd_islocked != 0 }
}
pub fn is_main_instance(&self) -> bool {
let main_instance = unsafe { libpd_main_instance() };
let main_instance = unsafe { &mut *main_instance };
main_instance.pd_instanceno == self.number
}
pub fn is_current_instance(&self) -> bool {
unsafe {
if libpd_this_instance().is_null() {
return false;
}
}
let current_instance = unsafe { libpd_this_instance() };
let current_instance = unsafe { &mut *current_instance };
current_instance.pd_instanceno == self.number
}
pub fn set_instance_data<T, F>(&mut self, data: T, free_hook: Option<F>)
where
T: 'static + Send + Sync,
F: FnMut(&mut T) + Send + Sync + 'static,
{
let boxed = Box::new(data);
let hook_ptr = free_hook.and_then(|mut free_hook| {
let closure: &'static mut _ = Box::leak(Box::new(move |data: *mut c_void| {
let data = unsafe { &mut *data.cast::<T>() };
free_hook(data);
}));
let callback = ClosureMut1::new(closure);
let code = callback.code_ptr() as FreeHookCodePtr;
let ptr = unsafe { *code.cast::<t_libpd_freehook>() };
mem::forget(callback);
ptr
});
unsafe {
libpd_set_instancedata(Box::into_raw(boxed).cast(), hook_ptr);
}
self.stored_type = Some(TypeId::of::<T>());
}
pub fn get_instance_data<T>(&self) -> Option<&T>
where
T: 'static + Send + Sync,
{
match self.stored_type {
Some(stored) if stored == TypeId::of::<T>() => {
let ptr = unsafe { libpd_get_instancedata() };
if ptr.is_null() {
None
} else {
Some(unsafe { &*(ptr as *const T) })
}
}
_ => None,
}
}
}
impl Drop for PdInstance {
fn drop(&mut self) {
if self.inner.is_null() || self.is_main_instance() {
return;
}
self.set_as_current();
functions::release_internal_queues();
unsafe { libpd_free_instance(self.inner) }
}
}
#[expect(
clippy::cast_sign_loss,
reason = "The instance count can not be negative."
)]
pub fn instance_count() -> usize {
unsafe { libpd_num_instances() as usize }
}