From 9aa5e05a03e6edf814cd8ca54ebe45c472685fc0 Mon Sep 17 00:00:00 2001 From: yyc12345 Date: Sun, 15 Feb 2026 23:04:55 +0800 Subject: [PATCH] feat: update bmap-rs wrapper --- .../BMapBindings/bmap-rs/src/bmap_wrapper.rs | 214 +++++++++++++++++- Assets/BMapBindings/bmap-rs/src/lib.rs | 3 + Assets/BMapBindings/bmap-rs/src/marshaler.rs | 109 +++++++++ 3 files changed, 325 insertions(+), 1 deletion(-) create mode 100644 Assets/BMapBindings/bmap-rs/src/marshaler.rs diff --git a/Assets/BMapBindings/bmap-rs/src/bmap_wrapper.rs b/Assets/BMapBindings/bmap-rs/src/bmap_wrapper.rs index 7260d59..31fabf7 100644 --- a/Assets/BMapBindings/bmap-rs/src/bmap_wrapper.rs +++ b/Assets/BMapBindings/bmap-rs/src/bmap_wrapper.rs @@ -1,3 +1,215 @@ //! The module includes all senior wrappers for BMap FFI calling in Rust style. -//! +//! //! This module is what user of this library should use. + +use crate::bmap::{self, BMBOOL, CKID, CKSTRING, PBMVOID}; +use crate::marshaler; +use std::marker::PhantomData; +use thiserror::Error as TeError; + +// region: Error and Result Types + +/// Any possible error occurs in this module. +#[derive(Debug, TeError)] +pub enum Error { + #[error("native BMap operation failed")] + BadCall, + #[error("{0}")] + Marshaler(#[from] marshaler::Error), +} + +/// The result type used in this module. +pub type Result = std::result::Result; + +// endregion + +// region: Utilities + +/// Internal used trait representing all instance created by BMap. +/// +/// Do **NOT** use this trait. +/// It can not be hidden due to the limitation of Rust trait. +pub trait AbstractPointer: Sized { + /// Internal used function for fetching instance underlying pointer. + /// + /// Do **NOT** use this function. + /// It can not be hidden due to the limitation of Rust trait. + unsafe fn get_pointer(&self) -> PBMVOID; +} + +/// Internal used trait representing all objects create by BMap file reader and writer. +/// +/// Do **NOT** use this trait. +/// It can not be hidden due to the limitation of Rust trait. +pub trait AbstractObject: AbstractPointer { + /// Internal used function for fetching object underlying ID. + /// + /// Do **NOT** use this function. + /// It can not be hidden due to the limitation of Rust trait. + unsafe fn get_ckid(&self) -> CKID; +} + +/// The representation of invalid raw pointer. +const INVALID_PTR: PBMVOID = std::ptr::null_mut(); +/// The representation of invalid CK_ID. +const INVALID_CKID: CKID = 0; + +/// The function used as callback for BMap. +/// It just writes the data in console. +unsafe extern "C" fn bmap_rs_callback(msg: CKSTRING) { + println!("[bmap-rs] {}", ""); +} + +/// The convenient macro for wrapping all BMap calling in Rust error procession pattern. +macro_rules! bmap_exec { + ($f:expr) => { + if !unsafe { $f } { + return Err(Error::BadCall); + } + }; +} + +macro_rules! arg_in { + ($v:ident) => { + $v + }; +} + +macro_rules! arg_out { + ($v:ident, $t:ty) => { + &mut $v as *mut $t + }; +} + +// endregion + +// region: BMap + +/// The BMap environment guard. +/// +/// This struct make sure that all BMap calling is executed under initialized BMap environment, +/// and shutdown BMap environment automatically when there is no more calling. +pub struct BMap {} + +impl BMap { + pub fn new() -> Result { + bmap_exec!(bmap::BMInit()); + Ok(Self {}) + } +} + +impl Drop for BMap { + fn drop(&mut self) { + // Ignore return of this function by design. + // Because using Result in Drop is inviable, + // and using `panic!` is also not suggested. + let _ = unsafe { bmap::BMDispose() }; + } +} + +// endregion + +// region: BMFileReader + +// endregion + +// region: BMFileWriter + +// endregion + +// region: CKObjects + +// region: Helper + +fn get_string_value( + o: &T, + f: unsafe extern "C" fn(PBMVOID, CKID, *mut CKSTRING) -> BMBOOL, +) -> Result> +where + T: AbstractObject, +{ + let mut data = CKSTRING::default(); + bmap_exec!(f(o.get_pointer(), o.get_ckid(), arg_out!(data, CKSTRING))); + + if data.is_null() { + Ok(None) + } else { + Ok(Some(unsafe { marshaler::from_native_string(data)? })) + } +} + +fn set_string_value( + o: &T, + f: unsafe extern "C" fn(PBMVOID, CKID, CKSTRING) -> BMBOOL, + s: Option<&str>, +) -> Result<()> +where + T: AbstractObject, +{ + let native = match s { + Some(s) => Some(unsafe { marshaler::to_native_string(s)? }), + None => None, + }; + let data = match &native { + Some(native) => unsafe { native.as_raw() }, + None => std::ptr::null_mut() as CKSTRING, + }; + bmap_exec!(f(o.get_pointer(), o.get_ckid(), arg_in!(data))); + + Ok(()) +} + +// endregion + +// region: Traits + +pub trait CKObject: AbstractObject { + fn get_name(&self) -> Result> { + get_string_value(self, bmap::BMObject_GetName) + } + fn set_name(&mut self, name: Option<&str>) -> Result<()> { + set_string_value(self, bmap::BMObject_SetName, name) + } +} + +pub trait CKTexture: CKObject {} + +pub trait CKMaterial: CKObject {} + +pub trait CKMesh: CKObject {} + +pub trait CK3dEntity: CKObject {} + +pub trait CK3dObject: CK3dEntity {} + +pub trait CKLight: CK3dEntity {} + +pub trait CKTargetLight: CKLight {} + +pub trait CKCamera: CK3dEntity {} + +pub trait CKTargetCamera: CKCamera {} + +// endregion + +// region: Structs + +pub struct BMObject {} + +pub struct BMTexture {} +pub struct BMMaterial {} +pub struct BMMesh {} +pub struct BM3dEntity {} +pub struct BM3dObject {} +pub struct BMLight {} +pub struct BMTargetLight {} +pub struct BMCamera {} +pub struct BMTargetCamera {} + +// endregion + +// endregion + +// region: BMMeshTrans + +// endregion diff --git a/Assets/BMapBindings/bmap-rs/src/lib.rs b/Assets/BMapBindings/bmap-rs/src/lib.rs index 01b3081..caf96bb 100644 --- a/Assets/BMapBindings/bmap-rs/src/lib.rs +++ b/Assets/BMapBindings/bmap-rs/src/lib.rs @@ -1,3 +1,6 @@ +//! The Rust binding to BMap. + pub mod virtools_types; pub mod bmap; +pub mod marshaler; pub mod bmap_wrapper; diff --git a/Assets/BMapBindings/bmap-rs/src/marshaler.rs b/Assets/BMapBindings/bmap-rs/src/marshaler.rs new file mode 100644 index 0000000..2d13f4e --- /dev/null +++ b/Assets/BMapBindings/bmap-rs/src/marshaler.rs @@ -0,0 +1,109 @@ +//! BMap wrapper used marshaler for string and string array. + +use crate::bmap::{CKSTRING, PCKSTRING}; +use std::ffi::{CStr, CString}; +use thiserror::Error as TeError; + +// region: Error and Result Types + +/// Any possible error occurs in this module. +#[derive(Debug, TeError)] +pub enum Error { + #[error("can not parse from native string")] + FromNative(#[from] std::str::Utf8Error), + #[error("can not format into native string")] + ToNative(#[from] std::ffi::NulError), +} + +/// The result type used in this module. +pub type Result = std::result::Result; + +// endregion + +pub unsafe fn from_native_string(ptr: CKSTRING) -> Result { + let s = unsafe { CStr::from_ptr(ptr) }; + Ok(String::from(s.to_str()?)) +} + +pub unsafe fn to_native_string(words: T) -> Result +where + T: Into>, +{ + BMString::new(words) +} + +pub struct BMString { + inner: CString, +} + +impl BMString { + fn new(words: T) -> Result + where + T: Into>, + { + Ok(Self { + inner: CString::new(words)?, + }) + } + + pub unsafe fn as_raw(&self) -> CKSTRING { + self.inner.as_ptr() as CKSTRING + } +} + +pub unsafe fn from_native_string_array(ptr: PCKSTRING) -> Result> { + let mut rv = Vec::new(); + + loop { + let item_ptr = unsafe { *ptr } as CKSTRING; + if item_ptr.is_null() { + break; + } + + let item = unsafe { from_native_string(item_ptr)? }; + rv.push(item); + } + + Ok(rv) +} + +pub unsafe fn to_native_string_array(words: &[T]) -> Result +where + T: Into> + Copy, +{ + BMStringArray::new(words) +} + +pub struct BMStringArray { + #[allow(dead_code)] + array_items: Vec, + array_body: Vec, +} + +impl BMStringArray { + fn new(words: &[T]) -> Result + where + T: Into> + Copy, + { + // Build array items + let array_items = words + .iter() + .map(|word| CString::new(*word)) + .collect::, std::ffi::NulError>>()?; + // Build array body. + // In theory, move operation will not affect data allocated on heap. + // So we can simply fetch the address of this new generated array. + let array_body = array_items + .iter() + .map(|i| i.as_ptr() as CKSTRING) + .chain(std::iter::once(std::ptr::null_mut() as CKSTRING)) + .collect::>(); + + // Return value + Ok(Self { array_items, array_body }) + } + + pub unsafe fn as_raw(&mut self) -> PCKSTRING { + self.array_body.as_ptr() as PCKSTRING + } +}