#![allow(unsafe_code)]
use std::marker::PhantomData;
use crate::{
boundary::{BorrowedStr, OwnedBytes, PluginError, PluginErrorCode, PluginResult, Slice},
panic::{guard, guard_infallible},
};
#[repr(C)]
pub struct CustomDataHandle {
_opaque: [u8; 0],
}
#[repr(C)]
#[derive(Clone, Copy)]
pub struct PluginCustomDataRef {
type_name: BorrowedStr<'static>,
vtable: *const CustomDataVTable,
handle: *const CustomDataHandle,
}
impl PluginCustomDataRef {
#[must_use]
pub unsafe fn from_raw_parts(
type_name: BorrowedStr<'static>,
vtable: *const CustomDataVTable,
handle: *const CustomDataHandle,
) -> Self {
Self {
type_name,
vtable,
handle,
}
}
#[must_use]
pub fn type_name(&self) -> &str {
unsafe { self.type_name.as_str() }
}
#[must_use]
pub fn is<T>(&self) -> bool
where
T: PluginCustomData + PartialEq + Clone,
{
self.vtable == custom_data_vtable::<T>()
}
#[must_use]
pub fn downcast_ref<T>(&self) -> Option<&T>
where
T: PluginCustomData + PartialEq + Clone,
{
if self.handle.is_null() || !self.is::<T>() {
return None;
}
Some(unsafe { &*self.handle.cast::<T>() })
}
}
#[repr(C)]
pub struct CustomDataVTable {
pub type_name: Option<unsafe extern "C" fn() -> BorrowedStr<'static>>,
pub schema_ipc: Option<unsafe extern "C" fn() -> PluginResult<OwnedBytes>>,
pub from_json: Option<
unsafe extern "C" fn(payload: BorrowedStr<'_>) -> PluginResult<*mut CustomDataHandle>,
>,
pub encode_batch: Option<
unsafe extern "C" fn(
handles: Slice<'_, *const CustomDataHandle>,
) -> PluginResult<OwnedBytes>,
>,
pub decode_batch: Option<
unsafe extern "C" fn(
ipc_bytes: Slice<'_, u8>,
metadata: Slice<'_, MetadataEntry<'_>>,
) -> PluginResult<OwnedBytes>,
>,
pub ts_event: Option<unsafe extern "C" fn(handle: *const CustomDataHandle) -> u64>,
pub ts_init: Option<unsafe extern "C" fn(handle: *const CustomDataHandle) -> u64>,
pub to_json:
Option<unsafe extern "C" fn(handle: *const CustomDataHandle) -> PluginResult<OwnedBytes>>,
pub clone_handle:
Option<unsafe extern "C" fn(handle: *const CustomDataHandle) -> *mut CustomDataHandle>,
pub drop_handle: Option<unsafe extern "C" fn(handle: *mut CustomDataHandle)>,
pub eq_handles: Option<
unsafe extern "C" fn(lhs: *const CustomDataHandle, rhs: *const CustomDataHandle) -> bool,
>,
}
#[repr(C)]
#[derive(Clone, Copy)]
pub struct MetadataEntry<'a> {
pub key: BorrowedStr<'a>,
pub value: BorrowedStr<'a>,
}
pub trait PluginCustomData: 'static + Send + Sync + Sized {
const TYPE_NAME: &'static str;
fn ts_event(&self) -> u64;
fn ts_init(&self) -> u64;
fn to_json(&self) -> anyhow::Result<Vec<u8>>;
fn from_json(payload: &[u8]) -> anyhow::Result<Self>;
fn schema_ipc() -> anyhow::Result<Vec<u8>>;
fn encode_batch(items: &[&Self]) -> anyhow::Result<Vec<u8>>;
fn decode_batch(ipc_bytes: &[u8], metadata: &[(String, String)]) -> anyhow::Result<Vec<Self>>;
fn equals(&self, other: &Self) -> bool
where
Self: PartialEq,
{
self == other
}
#[must_use]
fn clone_value(&self) -> Self
where
Self: Clone,
{
Clone::clone(self)
}
}
#[must_use]
pub fn custom_data_vtable<T>() -> *const CustomDataVTable
where
T: PluginCustomData + PartialEq + Clone,
{
&VTableTag::<T>::VTABLE
}
struct VTableTag<T>(PhantomData<T>);
impl<T> VTableTag<T>
where
T: PluginCustomData + PartialEq + Clone,
{
const VTABLE: CustomDataVTable = CustomDataVTable {
type_name: Some(type_name_thunk::<T>),
schema_ipc: Some(schema_ipc_thunk::<T>),
from_json: Some(from_json_thunk::<T>),
encode_batch: Some(encode_batch_thunk::<T>),
decode_batch: Some(decode_batch_thunk::<T>),
ts_event: Some(ts_event_thunk::<T>),
ts_init: Some(ts_init_thunk::<T>),
to_json: Some(to_json_thunk::<T>),
clone_handle: Some(clone_handle_thunk::<T>),
drop_handle: Some(drop_handle_thunk::<T>),
eq_handles: Some(eq_handles_thunk::<T>),
};
}
unsafe extern "C" fn type_name_thunk<T: PluginCustomData>() -> BorrowedStr<'static> {
BorrowedStr::from_str(T::TYPE_NAME)
}
unsafe extern "C" fn schema_ipc_thunk<T: PluginCustomData>() -> PluginResult<OwnedBytes> {
guard(|| {
T::schema_ipc()
.map(OwnedBytes::from_vec)
.map_err(|e| PluginError::new(PluginErrorCode::SerializationFailed, e.to_string()))
})
}
unsafe extern "C" fn from_json_thunk<T: PluginCustomData>(
payload: BorrowedStr<'_>,
) -> PluginResult<*mut CustomDataHandle> {
guard(|| {
let bytes = unsafe { payload.as_str() }.as_bytes();
T::from_json(bytes)
.map(|v| Box::into_raw(Box::new(v)).cast::<CustomDataHandle>())
.map_err(|e| PluginError::new(PluginErrorCode::SerializationFailed, e.to_string()))
})
}
unsafe extern "C" fn encode_batch_thunk<T: PluginCustomData>(
handles: Slice<'_, *const CustomDataHandle>,
) -> PluginResult<OwnedBytes> {
guard(|| {
let raw = unsafe { handles.as_slice() };
let items: Vec<&T> = raw
.iter()
.map(|p| {
unsafe { &*p.cast::<T>() }
})
.collect();
T::encode_batch(&items)
.map(OwnedBytes::from_vec)
.map_err(|e| PluginError::new(PluginErrorCode::SerializationFailed, e.to_string()))
})
}
unsafe extern "C" fn decode_batch_thunk<T: PluginCustomData>(
ipc_bytes: Slice<'_, u8>,
metadata: Slice<'_, MetadataEntry<'_>>,
) -> PluginResult<OwnedBytes> {
guard(|| {
let bytes = unsafe { ipc_bytes.as_slice() };
let entries = unsafe { metadata.as_slice() };
let md: Vec<(String, String)> = entries
.iter()
.map(|e| {
let k = unsafe { e.key.as_str() }.to_string();
let v = unsafe { e.value.as_str() }.to_string();
(k, v)
})
.collect();
let values: Vec<T> = T::decode_batch(bytes, &md)
.map_err(|e| PluginError::new(PluginErrorCode::SerializationFailed, e.to_string()))?;
let handles: Vec<*mut CustomDataHandle> = values
.into_iter()
.map(|v| Box::into_raw(Box::new(v)).cast::<CustomDataHandle>())
.collect();
let mut handles = std::mem::ManuallyDrop::new(handles);
let ptr = handles.as_mut_ptr().cast::<u8>();
let elem_size = std::mem::size_of::<*mut CustomDataHandle>();
let len = handles.len() * elem_size;
let cap = handles.capacity() * elem_size;
Ok(OwnedBytes {
ptr,
len,
cap,
drop_fn: Some(drop_handle_array),
})
})
}
unsafe extern "C" fn drop_handle_array(ptr: *mut u8, len: usize, cap: usize) {
if ptr.is_null() {
return;
}
let elem_size = std::mem::size_of::<*mut CustomDataHandle>();
if elem_size == 0 {
return;
}
let len_count = len / elem_size;
let cap_count = cap / elem_size;
unsafe {
let _ = Vec::from_raw_parts(ptr.cast::<*mut CustomDataHandle>(), len_count, cap_count);
}
}
unsafe extern "C" fn ts_event_thunk<T: PluginCustomData>(handle: *const CustomDataHandle) -> u64 {
guard_infallible("ts_event", || {
let value = unsafe { &*handle.cast::<T>() };
value.ts_event()
})
}
unsafe extern "C" fn ts_init_thunk<T: PluginCustomData>(handle: *const CustomDataHandle) -> u64 {
guard_infallible("ts_init", || {
let value = unsafe { &*handle.cast::<T>() };
value.ts_init()
})
}
unsafe extern "C" fn to_json_thunk<T: PluginCustomData>(
handle: *const CustomDataHandle,
) -> PluginResult<OwnedBytes> {
guard(|| {
let value = unsafe { &*handle.cast::<T>() };
value
.to_json()
.map(OwnedBytes::from_vec)
.map_err(|e| PluginError::new(PluginErrorCode::SerializationFailed, e.to_string()))
})
}
unsafe extern "C" fn clone_handle_thunk<T: PluginCustomData + Clone>(
handle: *const CustomDataHandle,
) -> *mut CustomDataHandle {
guard_infallible("clone_handle", || {
let value = unsafe { &*handle.cast::<T>() };
let cloned = value.clone_value();
Box::into_raw(Box::new(cloned)).cast::<CustomDataHandle>()
})
}
unsafe extern "C" fn drop_handle_thunk<T: PluginCustomData>(handle: *mut CustomDataHandle) {
if handle.is_null() {
return;
}
guard_infallible("drop_handle", || {
unsafe {
drop(Box::from_raw(handle.cast::<T>()));
}
});
}
unsafe extern "C" fn eq_handles_thunk<T: PluginCustomData + PartialEq>(
lhs: *const CustomDataHandle,
rhs: *const CustomDataHandle,
) -> bool {
guard_infallible("eq_handles", || {
let lhs = unsafe { &*lhs.cast::<T>() };
let rhs = unsafe { &*rhs.cast::<T>() };
lhs.equals(rhs)
})
}