use lv2_core::extension::ExtensionDescriptor;
use lv2_core::feature::*;
use lv2_core::plugin::{Plugin, PluginInstance};
use std::fmt;
use std::marker::PhantomData;
use std::mem;
use std::mem::ManuallyDrop;
use std::os::raw::*;
use std::ptr;
use urid::*;
#[derive(PartialEq, Eq, Clone, Copy)]
pub enum ScheduleError<T> {
Unknown(T),
NoSpace(T),
NoCallback(T),
}
impl<T> fmt::Debug for ScheduleError<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
ScheduleError::Unknown(..) => "Unknown(..)".fmt(f),
ScheduleError::NoSpace(..) => "NoSpace(..)".fmt(f),
ScheduleError::NoCallback(..) => "NoCallback(..)".fmt(f),
}
}
}
impl<T> fmt::Display for ScheduleError<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
ScheduleError::Unknown(..) => "unknown error".fmt(f),
ScheduleError::NoSpace(..) => "not enough space".fmt(f),
ScheduleError::NoCallback(..) => "no callback".fmt(f),
}
}
}
#[repr(transparent)]
pub struct Schedule<'a, P> {
internal: &'a lv2_sys::LV2_Worker_Schedule,
phantom: PhantomData<*const P>,
}
unsafe impl<'a, P> UriBound for Schedule<'a, P> {
const URI: &'static [u8] = lv2_sys::LV2_WORKER__schedule;
}
unsafe impl<'a, P> Feature for Schedule<'a, P> {
unsafe fn from_feature_ptr(feature: *const c_void, class: ThreadingClass) -> Option<Self> {
if class == ThreadingClass::Audio {
(feature as *const lv2_sys::LV2_Worker_Schedule)
.as_ref()
.map(|internal| Self {
internal,
phantom: PhantomData::<*const P>,
})
} else {
panic!("The Worker Schedule feature is only allowed in the audio threading class");
}
}
}
impl<'a, P: Worker> Schedule<'a, P> {
pub fn schedule_work(&self, worker_data: P::WorkData) -> Result<(), ScheduleError<P::WorkData>>
where
P::WorkData: 'static + Send,
{
let worker_data = ManuallyDrop::new(worker_data);
let size = mem::size_of_val(&worker_data) as u32;
let ptr = &worker_data as *const _ as *const c_void;
let schedule_work = if let Some(schedule_work) = self.internal.schedule_work {
schedule_work
} else {
return Err(ScheduleError::NoCallback(ManuallyDrop::into_inner(
worker_data,
)));
};
match unsafe { (schedule_work)(self.internal.handle, size, ptr) } {
lv2_sys::LV2_Worker_Status_LV2_WORKER_SUCCESS => Ok(()),
lv2_sys::LV2_Worker_Status_LV2_WORKER_ERR_UNKNOWN => Err(ScheduleError::Unknown(
ManuallyDrop::into_inner(worker_data),
)),
lv2_sys::LV2_Worker_Status_LV2_WORKER_ERR_NO_SPACE => Err(ScheduleError::NoSpace(
ManuallyDrop::into_inner(worker_data),
)),
_ => Err(ScheduleError::Unknown(ManuallyDrop::into_inner(
worker_data,
))),
}
}
}
#[derive(PartialEq, Eq, Clone, Copy)]
pub enum RespondError<T> {
Unknown(T),
NoSpace(T),
NoCallback(T),
}
impl<T> fmt::Debug for RespondError<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
RespondError::Unknown(..) => "Unknown(..)".fmt(f),
RespondError::NoSpace(..) => "NoSpace(..)".fmt(f),
RespondError::NoCallback(..) => "NoCallback(..)".fmt(f),
}
}
}
impl<T> fmt::Display for RespondError<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
RespondError::Unknown(..) => "unknown error".fmt(f),
RespondError::NoSpace(..) => "not enough space".fmt(f),
RespondError::NoCallback(..) => "no callback".fmt(f),
}
}
}
pub struct ResponseHandler<P: Worker> {
response_function: lv2_sys::LV2_Worker_Respond_Function,
respond_handle: lv2_sys::LV2_Worker_Respond_Handle,
phantom: PhantomData<P>,
}
impl<P: Worker> ResponseHandler<P> {
pub fn respond(
&self,
response_data: P::ResponseData,
) -> Result<(), RespondError<P::ResponseData>>
where
P::WorkData: 'static + Send,
{
let response_data = ManuallyDrop::new(response_data);
let size = mem::size_of_val(&response_data) as u32;
let ptr = &response_data as *const _ as *const c_void;
let response_function = if let Some(response_function) = self.response_function {
response_function
} else {
return Err(RespondError::NoCallback(ManuallyDrop::into_inner(
response_data,
)));
};
match unsafe { (response_function)(self.respond_handle, size, ptr) } {
lv2_sys::LV2_Worker_Status_LV2_WORKER_SUCCESS => Ok(()),
lv2_sys::LV2_Worker_Status_LV2_WORKER_ERR_UNKNOWN => Err(RespondError::Unknown(
ManuallyDrop::into_inner(response_data),
)),
lv2_sys::LV2_Worker_Status_LV2_WORKER_ERR_NO_SPACE => Err(RespondError::NoSpace(
ManuallyDrop::into_inner(response_data),
)),
_ => Err(RespondError::Unknown(ManuallyDrop::into_inner(
response_data,
))),
}
}
}
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
pub enum WorkerError {
Unknown,
NoSpace,
}
pub trait Worker: Plugin {
type WorkData: 'static + Send;
type ResponseData: 'static + Send;
fn work(
response_handler: &ResponseHandler<Self>,
data: Self::WorkData,
) -> Result<(), WorkerError>;
fn work_response(
&mut self,
_data: Self::ResponseData,
_features: &mut Self::AudioFeatures,
) -> Result<(), WorkerError> {
Ok(())
}
fn end_run(&mut self, _features: &mut Self::AudioFeatures) -> Result<(), WorkerError> {
Ok(())
}
}
pub struct WorkerDescriptor<P: Worker> {
plugin: PhantomData<P>,
}
unsafe impl<P: Worker> UriBound for WorkerDescriptor<P> {
const URI: &'static [u8] = lv2_sys::LV2_WORKER__interface;
}
impl<P: Worker> WorkerDescriptor<P> {
unsafe extern "C" fn extern_work(
_handle: lv2_sys::LV2_Handle,
response_function: lv2_sys::LV2_Worker_Respond_Function,
respond_handle: lv2_sys::LV2_Worker_Respond_Handle,
size: u32,
data: *const c_void,
) -> lv2_sys::LV2_Worker_Status {
let response_handler = ResponseHandler {
response_function,
respond_handle,
phantom: PhantomData::<P>,
};
let worker_data =
ptr::read_unaligned(data as *const mem::ManuallyDrop<<P as Worker>::WorkData>);
let worker_data = mem::ManuallyDrop::into_inner(worker_data);
if size as usize != mem::size_of_val(&worker_data) {
return lv2_sys::LV2_Worker_Status_LV2_WORKER_ERR_UNKNOWN;
}
match P::work(&response_handler, worker_data) {
Ok(()) => lv2_sys::LV2_Worker_Status_LV2_WORKER_SUCCESS,
Err(WorkerError::Unknown) => lv2_sys::LV2_Worker_Status_LV2_WORKER_ERR_UNKNOWN,
Err(WorkerError::NoSpace) => lv2_sys::LV2_Worker_Status_LV2_WORKER_ERR_NO_SPACE,
}
}
unsafe extern "C" fn extern_work_response(
handle: lv2_sys::LV2_Handle,
size: u32,
body: *const c_void,
) -> lv2_sys::LV2_Worker_Status {
let plugin_instance =
if let Some(plugin_instance) = (handle as *mut PluginInstance<P>).as_mut() {
plugin_instance
} else {
return lv2_sys::LV2_Worker_Status_LV2_WORKER_ERR_UNKNOWN;
};
let response_data =
ptr::read_unaligned(body as *const mem::ManuallyDrop<<P as Worker>::ResponseData>);
let response_data = mem::ManuallyDrop::into_inner(response_data);
if size as usize != mem::size_of_val(&response_data) {
return lv2_sys::LV2_Worker_Status_LV2_WORKER_ERR_UNKNOWN;
}
let (instance, features) = plugin_instance.audio_class_handle();
match instance.work_response(response_data, features) {
Ok(()) => lv2_sys::LV2_Worker_Status_LV2_WORKER_SUCCESS,
Err(WorkerError::Unknown) => lv2_sys::LV2_Worker_Status_LV2_WORKER_ERR_UNKNOWN,
Err(WorkerError::NoSpace) => lv2_sys::LV2_Worker_Status_LV2_WORKER_ERR_NO_SPACE,
}
}
unsafe extern "C" fn extern_end_run(handle: lv2_sys::LV2_Handle) -> lv2_sys::LV2_Worker_Status {
if let Some(plugin_instance) = (handle as *mut PluginInstance<P>).as_mut() {
let (instance, features) = plugin_instance.audio_class_handle();
match instance.end_run(features) {
Ok(()) => lv2_sys::LV2_Worker_Status_LV2_WORKER_SUCCESS,
Err(WorkerError::Unknown) => lv2_sys::LV2_Worker_Status_LV2_WORKER_ERR_UNKNOWN,
Err(WorkerError::NoSpace) => lv2_sys::LV2_Worker_Status_LV2_WORKER_ERR_NO_SPACE,
}
} else {
lv2_sys::LV2_Worker_Status_LV2_WORKER_ERR_UNKNOWN
}
}
}
impl<P: Worker> ExtensionDescriptor for WorkerDescriptor<P> {
type ExtensionInterface = lv2_sys::LV2_Worker_Interface;
const INTERFACE: &'static lv2_sys::LV2_Worker_Interface = &lv2_sys::LV2_Worker_Interface {
work: Some(Self::extern_work),
work_response: Some(Self::extern_work_response),
end_run: Some(Self::extern_end_run),
};
}
#[cfg(test)]
mod tests {
use super::*;
use lv2_core::prelude::*;
use lv2_sys::*;
use std::fmt;
use std::mem;
use std::ops;
use std::ptr;
struct HasDrop {
drop_count: u32,
drop_limit: u32,
}
impl HasDrop {
fn new(val: u32) -> Self {
Self {
drop_count: 0,
drop_limit: val,
}
}
}
impl ops::Drop for HasDrop {
fn drop(&mut self) {
if self.drop_count >= self.drop_limit {
panic!("Dropped more than {} time", self.drop_limit);
} else {
self.drop_count += 1;
}
}
}
impl fmt::Display for HasDrop {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "HasDrop variable")
}
}
#[derive(PortCollection)]
struct Ports {}
struct TestDropWorker;
unsafe impl<'a> UriBound for TestDropWorker {
const URI: &'static [u8] = b"not relevant\0";
}
impl Plugin for TestDropWorker {
type Ports = Ports;
type InitFeatures = ();
type AudioFeatures = ();
fn new(_plugin_info: &PluginInfo, _features: &mut Self::InitFeatures) -> Option<Self> {
Some(Self {})
}
fn run(&mut self, _ports: &mut Ports, _features: &mut Self::InitFeatures, _: u32) {}
}
impl Worker for TestDropWorker {
type WorkData = HasDrop;
type ResponseData = HasDrop;
fn work(
_response_handler: &ResponseHandler<Self>,
_data: HasDrop,
) -> Result<(), WorkerError> {
Ok(())
}
fn work_response(
&mut self,
_data: HasDrop,
_features: &mut Self::AudioFeatures,
) -> Result<(), WorkerError> {
Ok(())
}
}
extern "C" fn extern_schedule(
_handle: LV2_Worker_Schedule_Handle,
_size: u32,
_data: *const c_void,
) -> LV2_Worker_Status {
LV2_Worker_Status_LV2_WORKER_SUCCESS
}
extern "C" fn faulty_schedule(
_handle: LV2_Worker_Schedule_Handle,
_size: u32,
_data: *const c_void,
) -> LV2_Worker_Status {
LV2_Worker_Status_LV2_WORKER_ERR_UNKNOWN
}
extern "C" fn extern_respond(
_handle: LV2_Worker_Respond_Handle,
_size: u32,
_data: *const c_void,
) -> LV2_Worker_Status {
LV2_Worker_Status_LV2_WORKER_SUCCESS
}
extern "C" fn faulty_respond(
_handle: LV2_Worker_Respond_Handle,
_size: u32,
_data: *const c_void,
) -> LV2_Worker_Status {
LV2_Worker_Status_LV2_WORKER_ERR_UNKNOWN
}
#[test]
fn schedule_must_not_drop() {
let hd = HasDrop::new(0);
let internal = lv2_sys::LV2_Worker_Schedule {
handle: ptr::null_mut(),
schedule_work: Some(extern_schedule),
};
let schedule = Schedule {
internal: &internal,
phantom: PhantomData::<*const TestDropWorker>,
};
let _ = schedule.schedule_work(hd);
}
#[test]
#[should_panic(expected = "Dropped")]
fn schedule_must_enable_drop_on_error() {
let hd = HasDrop::new(0);
let internal = lv2_sys::LV2_Worker_Schedule {
handle: ptr::null_mut(),
schedule_work: Some(faulty_schedule),
};
let schedule = Schedule {
internal: &internal,
phantom: PhantomData::<*const TestDropWorker>,
};
let _ = schedule.schedule_work(hd);
}
#[test]
fn respond_must_not_drop() {
let hd = HasDrop::new(0);
let respond = ResponseHandler {
response_function: Some(extern_respond),
respond_handle: ptr::null_mut(),
phantom: PhantomData::<TestDropWorker>,
};
let _ = respond.respond(hd);
}
#[test]
#[should_panic(expected = "Dropped")]
fn respond_must_enable_drop_on_error() {
let hd = HasDrop::new(0);
let respond = ResponseHandler {
response_function: Some(faulty_respond),
respond_handle: ptr::null_mut(),
phantom: PhantomData::<TestDropWorker>,
};
let _ = respond.respond(hd);
}
#[test]
#[should_panic(expected = "Dropped")]
fn extern_work_should_drop() {
let hd = mem::ManuallyDrop::new(HasDrop::new(0));
let ptr_hd = &hd as *const _ as *const c_void;
let size = mem::size_of_val(&hd) as u32;
let mut tdw = TestDropWorker {};
let ptr_tdw = &mut tdw as *mut _ as *mut c_void;
unsafe {
WorkerDescriptor::<TestDropWorker>::extern_work(
ptr_tdw,
Some(extern_respond),
ptr::null_mut(),
size,
ptr_hd,
);
}
}
#[test]
fn extern_work_should_not_drop_twice() {
let hd = mem::ManuallyDrop::new(HasDrop::new(1));
let ptr_hd = &hd as *const _ as *const c_void;
let size = mem::size_of_val(&hd) as u32;
let mut tdw = TestDropWorker {};
let ptr_tdw = &mut tdw as *mut _ as *mut c_void;
unsafe {
WorkerDescriptor::<TestDropWorker>::extern_work(
ptr_tdw,
Some(extern_respond),
ptr::null_mut(),
size,
ptr_hd,
);
}
}
#[test]
#[should_panic(expected = "Dropped")]
fn extern_work_response_should_drop() {
let hd = mem::ManuallyDrop::new(HasDrop::new(0));
let ptr_hd = &hd as *const _ as *const c_void;
let size = mem::size_of_val(&hd) as u32;
let mut tdw = TestDropWorker {};
let ptr_tdw = &mut tdw as *mut _ as *mut c_void;
unsafe {
WorkerDescriptor::<TestDropWorker>::extern_work_response(ptr_tdw, size, ptr_hd);
}
}
#[test]
fn extern_work_response_should_not_drop_twice() {
let hd = mem::ManuallyDrop::new(HasDrop::new(1));
let ptr_hd = &hd as *const _ as *const c_void;
let size = mem::size_of_val(&hd) as u32;
let mut tdw = TestDropWorker {};
let ptr_tdw = &mut tdw as *mut _ as *mut c_void;
unsafe {
WorkerDescriptor::<TestDropWorker>::extern_work_response(ptr_tdw, size, ptr_hd);
}
}
}