roplat 0.2.0

roplat: just a robot operation system
Documentation
//! 不透明数据类型支持
//!
//! 不透明数据是只在单一语言内使用的数据结构。
//! 当数据来自非 Rust 语言时,需要在 Rust 中注册一个"傀儡结构"用于调度。
//! 傀儡结构持有指向实际不透明数据的指针,并在 Drop 时通过 FFI 回调释放资源。

use std::ffi::c_void;
use std::fmt;

/// 不透明数据的释放函数类型
pub type DestroyFn = unsafe extern "C" fn(*mut c_void);

/// Rust 端的不透明数据傀儡结构
///
/// 封装一个来自外部语言的不透明指针,确保在 Drop 时正确释放。
/// 这是实现跨语言不透明数据通讯的核心类型。
pub struct OpaqueData {
    /// 指向不透明数据的指针
    ptr: *mut c_void,
    /// 释放函数(由创建语言提供)
    destroy_fn: Option<DestroyFn>,
    /// 数据大小(用于调度和内存管理)
    size: usize,
    /// 类型标识(用于运行时类型检查)
    type_id: &'static str,
}

// Safety: OpaqueData 独占其内部指针,外部语言保证数据的线程安全
unsafe impl Send for OpaqueData {}
unsafe impl Sync for OpaqueData {}

impl OpaqueData {
    /// 创建新的不透明数据包装
    ///
    /// # Safety
    /// - `ptr` 必须指向由对应语言分配的有效内存
    /// - `destroy_fn` 必须能正确释放 `ptr` 指向的内存
    /// - 调用者保证不透明数据的线程安全性
    pub unsafe fn new(
        ptr: *mut c_void,
        destroy_fn: DestroyFn,
        size: usize,
        type_id: &'static str,
    ) -> Self {
        Self { ptr, destroy_fn: Some(destroy_fn), size, type_id }
    }

    /// 创建一个无析构函数的不透明数据(由外部语言管理生命周期)
    ///
    /// # Safety
    /// - `ptr` 必须在 OpaqueData 生命周期内保持有效
    pub unsafe fn from_raw(ptr: *mut c_void, size: usize, type_id: &'static str) -> Self {
        Self { ptr, destroy_fn: None, size, type_id }
    }

    /// 获取内部指针
    pub fn as_ptr(&self) -> *const c_void {
        self.ptr
    }

    /// 获取可变内部指针
    pub fn as_mut_ptr(&mut self) -> *mut c_void {
        self.ptr
    }

    /// 数据大小
    pub fn size(&self) -> usize {
        self.size
    }

    /// 类型标识
    pub fn type_id(&self) -> &'static str {
        self.type_id
    }

    /// 是否为空指针
    pub fn is_null(&self) -> bool {
        self.ptr.is_null()
    }

    /// 将指针解释为特定类型的引用
    ///
    /// # Safety
    /// - 内部指针必须指向类型 T 的有效实例
    /// - T 的内存布局必须与不透明数据兼容
    pub unsafe fn cast_ref<T>(&self) -> &T {
        unsafe { &*(self.ptr as *const T) }
    }

    /// 将指针解释为特定类型的可变引用
    ///
    /// # Safety
    /// - 内部指针必须指向类型 T 的有效实例
    pub unsafe fn cast_mut<T>(&mut self) -> &mut T {
        unsafe { &mut *(self.ptr as *mut T) }
    }
}

impl Drop for OpaqueData {
    fn drop(&mut self) {
        if !self.ptr.is_null()
            && let Some(destroy) = self.destroy_fn
        {
            unsafe {
                destroy(self.ptr);
            }
        }
    }
}

impl fmt::Debug for OpaqueData {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_struct("OpaqueData")
            .field("ptr", &self.ptr)
            .field("size", &self.size)
            .field("type_id", &self.type_id)
            .field("has_destroy", &self.destroy_fn.is_some())
            .finish()
    }
}

/// 带类型信息的不透明数据包装
///
/// 提供一层泛型封装,将 OpaqueData 与 Rust 端的类型 T 关联起来。
/// T 通常是一个标记类型(marker type),用于在编译期区分不同的不透明数据。
pub struct TypedOpaque<T> {
    inner: OpaqueData,
    _marker: std::marker::PhantomData<T>,
}

impl<T> TypedOpaque<T> {
    /// 从 OpaqueData 包装
    pub fn from_opaque(data: OpaqueData) -> Self {
        Self { inner: data, _marker: std::marker::PhantomData }
    }

    /// 获取内部 OpaqueData
    pub fn into_inner(self) -> OpaqueData {
        self.inner
    }

    /// 获取内部引用
    pub fn inner(&self) -> &OpaqueData {
        &self.inner
    }

    /// 获取内部可变引用
    pub fn inner_mut(&mut self) -> &mut OpaqueData {
        &mut self.inner
    }
}

impl<T> fmt::Debug for TypedOpaque<T> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        self.inner.fmt(f)
    }
}

// ============================================================
// C FFI 接口用于外部语言创建和操作不透明数据
// ============================================================

/// C FFI:创建不透明数据包装
///
/// # Safety
/// - `ptr` 和 `destroy_fn` 同 OpaqueData::new
/// - `type_id` 必须是有效的以 null 结尾的 C 字符串,且在逻辑上为 'static
#[unsafe(no_mangle)]
pub unsafe extern "C" fn roplat_opaque_create(
    ptr: *mut c_void,
    destroy_fn: DestroyFn,
    size: usize,
    type_id: *const std::ffi::c_char,
) -> *mut OpaqueData {
    let type_id_str = if type_id.is_null() {
        "unknown"
    } else {
        // 将 C 字符串转为 &'static str
        // Safety: 我们将其泄漏为 'static,因为 type_id 只用于标识,不需要释放
        let c_str = unsafe { std::ffi::CStr::from_ptr(type_id) };
        let owned = c_str.to_string_lossy().into_owned();
        Box::leak(owned.into_boxed_str())
    };

    let opaque = unsafe { OpaqueData::new(ptr, destroy_fn, size, type_id_str) };
    Box::into_raw(Box::new(opaque))
}

/// C FFI:释放不透明数据包装(同时调用内部释放函数)
///
/// # Safety
/// - `opaque` 必须是 `roplat_opaque_create` 返回的指针
#[unsafe(no_mangle)]
pub unsafe extern "C" fn roplat_opaque_destroy(opaque: *mut OpaqueData) {
    if !opaque.is_null() {
        unsafe {
            drop(Box::from_raw(opaque));
        }
    }
}

/// C FFI:获取不透明数据的内部指针
///
/// # Safety
/// - `opaque` 必须指向有效的 OpaqueData
#[unsafe(no_mangle)]
pub unsafe extern "C" fn roplat_opaque_get_ptr(opaque: *const OpaqueData) -> *mut c_void {
    if opaque.is_null() {
        std::ptr::null_mut()
    } else {
        unsafe { (*opaque).ptr }
    }
}

/// C FFI:获取不透明数据大小
///
/// # Safety
/// - `opaque` 必须指向有效的 OpaqueData
#[unsafe(no_mangle)]
pub unsafe extern "C" fn roplat_opaque_get_size(opaque: *const OpaqueData) -> usize {
    if opaque.is_null() {
        0
    } else {
        unsafe { (*opaque).size }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_opaque_data_lifecycle() {
        use std::sync::atomic::{AtomicBool, Ordering};

        static DESTROYED: AtomicBool = AtomicBool::new(false);

        unsafe extern "C" fn my_destroy(ptr: *mut c_void) {
            unsafe {
                let _ = Box::from_raw(ptr as *mut i32);
            }
            DESTROYED.store(true, Ordering::SeqCst);
        }

        {
            let data = Box::into_raw(Box::new(42i32));
            let opaque = unsafe { OpaqueData::new(data as *mut c_void, my_destroy, 4, "test_i32") };

            assert_eq!(opaque.type_id(), "test_i32");
            assert_eq!(opaque.size(), 4);
            assert!(!opaque.is_null());

            unsafe {
                assert_eq!(*opaque.cast_ref::<i32>(), 42);
            }
        }

        assert!(DESTROYED.load(Ordering::SeqCst));
    }

    #[test]
    fn test_typed_opaque() {
        struct MyMarker;

        let data = Box::into_raw(Box::new(vec![1, 2, 3]));
        unsafe extern "C" fn destroy_vec(ptr: *mut c_void) {
            unsafe {
                let _ = Box::from_raw(ptr as *mut Vec<i32>);
            }
        }

        let opaque = unsafe {
            OpaqueData::new(
                data as *mut c_void,
                destroy_vec,
                std::mem::size_of::<Vec<i32>>(),
                "Vec<i32>",
            )
        };

        let typed = TypedOpaque::<MyMarker>::from_opaque(opaque);
        assert_eq!(typed.inner().type_id(), "Vec<i32>");

        unsafe {
            let v = typed.inner().cast_ref::<Vec<i32>>();
            assert_eq!(v.as_slice(), &[1, 2, 3]);
        }
    }

    #[test]
    fn test_ffi_interface() {
        use std::sync::atomic::{AtomicBool, Ordering};

        static FFI_DESTROYED: AtomicBool = AtomicBool::new(false);

        unsafe extern "C" fn ffi_destroy(ptr: *mut c_void) {
            unsafe {
                let _ = Box::from_raw(ptr as *mut f64);
            }
            FFI_DESTROYED.store(true, Ordering::SeqCst);
        }

        let value = Box::into_raw(Box::new(std::f64::consts::PI));
        let type_id = std::ffi::CString::new("f64_value").unwrap();

        unsafe {
            let opaque =
                roplat_opaque_create(value as *mut c_void, ffi_destroy, 8, type_id.as_ptr());

            assert!(!opaque.is_null());
            assert_eq!(roplat_opaque_get_size(opaque), 8);

            let ptr = roplat_opaque_get_ptr(opaque);
            assert_eq!(*(ptr as *const f64), std::f64::consts::PI);

            roplat_opaque_destroy(opaque);
        }

        assert!(FFI_DESTROYED.load(Ordering::SeqCst));
    }
}