use bitflags::bitflags;
use ffi::VSPluginFunction;
use rustsynth_sys::{self as ffi, VSPluginConfigFlags};
use std::{
ffi::{c_void, CStr, CString, NulError},
marker::PhantomData,
ptr::{self, NonNull},
};
use thiserror::Error;
use crate::{
api::API,
core::CoreRef,
map::{Map, MapError, MapRef},
};
#[derive(Error, Debug)]
pub enum PluginError {
#[error("Function '{0}' not found in plugin")]
FunctionNotFound(String),
#[error("Plugin function call failed: {0}")]
FunctionCallFailed(#[from] PluginFunctionError),
#[error("Error: {0}")]
InvokeError(String),
#[error("Output map error: {0}")]
OutputMapError(MapError),
#[error("CString conversion error: {0}")]
CStringConversion(#[from] NulError),
#[error("Failed to register function")]
RegistrationFailed,
}
#[derive(Debug, Clone, Copy)]
pub struct Plugin<'core> {
handle: NonNull<ffi::VSPlugin>,
_owner: PhantomData<&'core ()>,
}
unsafe impl Send for Plugin<'_> {}
unsafe impl Sync for Plugin<'_> {}
impl<'core> Plugin<'core> {
#[inline]
pub const unsafe fn from_ptr(ptr: *mut ffi::VSPlugin) -> Self {
Plugin {
handle: NonNull::new_unchecked(ptr),
_owner: PhantomData,
}
}
#[inline]
#[must_use]
pub const fn as_ptr(&self) -> *mut ffi::VSPlugin {
self.handle.as_ptr()
}
#[must_use]
pub fn path(&self) -> Option<String> {
let ptr = unsafe { API::get_cached().get_plugin_path(self.as_ptr()) };
if ptr.is_null() {
None
} else {
Some(unsafe { CStr::from_ptr(ptr).to_string_lossy().into_owned() })
}
}
#[must_use]
pub fn id(&self) -> Option<String> {
let ptr = unsafe { API::get_cached().get_plugin_id(self.as_ptr()) };
if ptr.is_null() {
None
} else {
Some(unsafe { CStr::from_ptr(ptr).to_string_lossy().into_owned() })
}
}
#[must_use]
pub fn namespace(&self) -> Option<String> {
let ptr = unsafe { API::get_cached().get_plugin_ns(self.as_ptr()) };
if ptr.is_null() {
None
} else {
Some(unsafe { CStr::from_ptr(ptr).to_string_lossy().into_owned() })
}
}
#[must_use]
pub fn name(&self) -> Option<String> {
let ptr = unsafe { API::get_cached().get_plugin_name(self.as_ptr()) };
if ptr.is_null() {
None
} else {
Some(unsafe { CStr::from_ptr(ptr).to_string_lossy().into_owned() })
}
}
#[inline]
#[must_use]
pub fn version(&self) -> i32 {
unsafe { API::get_cached().get_plugin_version(self.as_ptr()) }
}
#[must_use]
pub fn function(&self, name: &str) -> Option<PluginFunction<'_>> {
let name_ptr = CString::new(name).ok()?;
unsafe {
let ptr =
API::get_cached().get_plugin_function_by_name(name_ptr.as_ptr(), self.as_ptr());
if ptr.is_null() {
None
} else {
Some(PluginFunction::from_ptr(ptr, self))
}
}
}
#[must_use]
pub const fn functions(&'_ self) -> PluginFunctions<'_> {
PluginFunctions {
function: None,
plugin: self,
}
}
fn next_function(&self, function: Option<PluginFunction<'_>>) -> Option<PluginFunction<'_>> {
unsafe {
let function = if let Some(value) = function {
value.as_ptr()
} else {
ptr::null_mut()
};
let ptr = API::get_cached().get_next_plugin_function(function, self.as_ptr());
if ptr.is_null() {
None
} else {
Some(PluginFunction::from_ptr(ptr, self))
}
}
}
pub fn invoke(&self, name: &str, args: &Map<'core>) -> PluginResult<Map<'core>> {
let func = self
.function(name)
.ok_or(PluginError::FunctionNotFound(name.to_string()))?;
Ok(func.call(args)?)
}
pub fn try_invoke(&self, name: &str, args: &Map<'core>) -> PluginResult<Map<'core>> {
let func = self
.function(name)
.ok_or_else(|| PluginError::FunctionNotFound(name.to_string()))?;
let ret = func.call(args)?;
if let Ok(err) = ret.error() {
return Err(PluginError::InvokeError(err.to_string()));
}
Ok(ret)
}
pub fn invoke_no_args(&self, name: &str) -> PluginResult<Map<'core>> {
let empty_map = Map::new().map_err(PluginError::OutputMapError)?;
self.try_invoke(name, &empty_map)
}
pub fn try_invoke_no_args(&self, name: &str) -> PluginResult<Map<'core>> {
let empty_map = Map::new().map_err(PluginError::OutputMapError)?;
self.try_invoke(name, &empty_map)
}
pub fn register_function(
&self,
name: &str,
args: &str,
ret_type: &str,
func: PublicFunction,
) -> PluginResult<()> {
let name_c = CString::new(name)?;
let args_c = CString::new(args)?;
let ret_type_c = CString::new(ret_type)?;
let user_data: Box<PublicFunction> = Box::new(func);
let user_data_ptr = Box::into_raw(user_data).cast::<c_void>();
let res = unsafe {
API::get_cached().register_function(
name_c.as_ptr(),
args_c.as_ptr(),
ret_type_c.as_ptr(),
Some(public_function),
user_data_ptr,
self.handle.as_ptr(),
)
};
if res == 0 {
Ok(())
} else {
Err(PluginError::RegistrationFailed)
}
}
}
unsafe extern "C" fn public_function(
in_map: *const ffi::VSMap,
out_map: *mut ffi::VSMap,
user_data: *mut c_void,
core: *mut ffi::VSCore,
_vs_api: *const ffi::VSAPI,
) {
if in_map.is_null() || user_data.is_null() || core.is_null() {
return;
}
let user_data = unsafe { Box::from_raw(user_data.cast::<PublicFunction>()) };
let in_map = unsafe { MapRef::from_ptr(in_map) };
let out_map = unsafe { MapRef::from_ptr_mut(out_map) };
let core = unsafe { CoreRef::from_ptr(core) };
(user_data)(in_map, out_map, core);
}
pub type PublicFunction = fn(in_map: &MapRef<'_>, out_map: &mut MapRef<'_>, core: CoreRef);
bitflags! {
pub struct PluginConfigFlags: i32 {
const MODIFIABLE = 1;
const NONE = 0;
}
}
impl PluginConfigFlags {
#[must_use]
pub const fn as_ffi(&self) -> ffi::VSPluginConfigFlags {
VSPluginConfigFlags(self.bits() as u32)
}
}
#[derive(Debug, Clone, Copy)]
pub struct PluginFunctions<'core> {
function: Option<PluginFunction<'core>>,
plugin: &'core Plugin<'core>,
}
impl<'core> Iterator for PluginFunctions<'core> {
type Item = PluginFunction<'core>;
fn next(&mut self) -> Option<Self::Item> {
self.function = self.plugin.next_function(self.function);
self.function
}
}
#[derive(Debug, Clone, Copy)]
pub struct PluginFunction<'a> {
ptr: NonNull<ffi::VSPluginFunction>,
plugin: &'a Plugin<'a>,
}
impl<'a> PluginFunction<'a> {
pub const unsafe fn from_ptr(ptr: *mut VSPluginFunction, plugin: &'a Plugin<'a>) -> Self {
PluginFunction {
ptr: NonNull::new_unchecked(ptr),
plugin,
}
}
#[must_use]
pub const fn as_ptr(&self) -> *mut ffi::VSPluginFunction {
self.ptr.as_ptr()
}
#[must_use]
pub fn get_name(&self) -> Option<String> {
let ptr = unsafe { API::get_cached().get_plugin_function_name(self.ptr.as_ptr()) };
if ptr.is_null() {
None
} else {
Some(unsafe { CStr::from_ptr(ptr).to_string_lossy().into_owned() })
}
}
#[must_use]
pub fn get_arguments(&self) -> Option<String> {
let ptr = unsafe { API::get_cached().get_plugin_function_arguments(self.ptr.as_ptr()) };
if ptr.is_null() {
None
} else {
Some(unsafe { CStr::from_ptr(ptr).to_string_lossy().into_owned() })
}
}
#[must_use]
pub fn get_return_type(&self) -> Option<String> {
let ptr = unsafe { API::get_cached().get_plugin_function_return_type(self.ptr.as_ptr()) };
if ptr.is_null() {
None
} else {
Some(unsafe { CStr::from_ptr(ptr).to_string_lossy().into_owned() })
}
}
pub fn call<'map>(&self, args: &Map<'map>) -> Result<Map<'map>, PluginFunctionError> {
let name = self.get_name().ok_or(PluginFunctionError::NoName)?;
let name_c = CString::new(name).map_err(PluginFunctionError::NulError)?;
unsafe {
Ok(Map::from_ptr(API::get_cached().invoke(
self.plugin.as_ptr(),
name_c.as_ptr(),
&*args.as_ptr(),
)))
}
}
pub fn call_no_args(&self) -> Result<Map<'_>, PluginFunctionError> {
let empty_map = Map::new().map_err(|_| PluginFunctionError::CreateMapFailed)?;
self.call(&empty_map)
}
}
pub type PluginResult<T> = Result<T, PluginError>;
#[derive(thiserror::Error, Debug)]
pub enum PluginFunctionError {
#[error("Fuction has no name")]
NoName,
#[error("Nul error in string: {0}")]
NulError(#[from] NulError),
#[error("Function call failed")]
CallFailed,
#[error("Failed to create output map")]
CreateMapFailed,
}