1
0

feat: update bmap-rs

This commit is contained in:
2026-02-18 20:23:54 +08:00
parent 6ea43cd82f
commit cf0966e6d3
6 changed files with 189 additions and 29 deletions

View File

@@ -1,14 +1,17 @@
//! The module includes all senior wrappers for BMap FFI calling in Rust style. //! The module includes all senior wrappers for BMap FFI calling in Rust style.
//! //!
//! This module is what user of this library should use. //! This module is what user of this library should use.
use crate::bmap::{self, BMBOOL, CKDWORD, CKID, CKSTRING, PBMVOID};
use crate::marshaler; mod marshaler;
mod utilities;
use crate::bmap::{self, BMBOOL, CKDWORD, CKID, CKINT, CKSTRING, PBMVOID};
use std::cmp::Ordering; use std::cmp::Ordering;
use std::hash::{Hash, Hasher}; use std::hash::{Hash, Hasher};
use std::iter::{ExactSizeIterator, FusedIterator, Iterator}; use std::iter::{ExactSizeIterator, FusedIterator, Iterator};
use std::marker::PhantomData; use std::marker::PhantomData;
use std::mem::MaybeUninit; use std::mem::MaybeUninit;
use std::sync::LazyLock; use std::sync::{Mutex, OnceLock};
use thiserror::Error as TeError; use thiserror::Error as TeError;
// region: Error and Result Types // region: Error and Result Types
@@ -22,6 +25,8 @@ pub enum Error {
Marshaler(#[from] marshaler::Error), Marshaler(#[from] marshaler::Error),
#[error("the length of given data iterator is too short when assigning struct array.")] #[error("the length of given data iterator is too short when assigning struct array.")]
OutOfLength, OutOfLength,
#[error("bad cast to integral value")]
BadIntCast(#[from] std::num::TryFromIntError),
} }
/// The result type used in this module. /// The result type used in this module.
@@ -227,16 +232,70 @@ macro_rules! arg_out {
/// ///
/// This struct make sure that all BMap calling is executed under initialized BMap environment, /// 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. /// and shutdown BMap environment automatically when there is no more calling.
struct BMapGuard {} pub struct BMap {}
impl BMapGuard { impl BMap {
pub fn instance() -> &'static Result<Mutex<BMap>> {
static INSTANCE: OnceLock<Result<Mutex<BMap>>> = OnceLock::new();
INSTANCE.get_or_init(|| BMap::new().map(|v| Mutex::new(v)))
}
pub fn is_available() -> bool {
Self::instance().is_ok()
}
pub fn with_bmap<F, R>(f: F) -> R
where
F: FnOnce(&mut Self) -> R,
{
let mut instance = Self::instance()
.as_ref()
.expect("bad BMap environment (not initialized)")
.lock()
.expect("failed mutex lock");
f(&mut instance)
}
}
impl BMap {
pub fn create_file_reader<'a, T>(
&'a mut self,
file_name: &str,
temp_folder: &str,
texture_folder: &str,
encodings: &[T],
) -> Result<BMFileReader<'a>>
where
T: Into<Vec<u8>> + Copy,
{
BMFileReader::new(self, file_name, temp_folder, texture_folder, encodings)
}
pub fn create_file_writer<'a, T>(
&'a mut self,
temp_folder: &str,
texture_folder: &str,
encodings: &[T],
) -> Result<BMFileWriter<'a>>
where
T: Into<Vec<u8>> + Copy,
{
BMFileWriter::new(self, temp_folder, texture_folder, encodings)
}
pub fn create_mesh_trans<'a>(&'a mut self) -> Result<BMMeshTrans<'a>> {
BMMeshTrans::new(self)
}
}
impl BMap {
fn new() -> Result<Self> { fn new() -> Result<Self> {
bmap_exec!(bmap::BMInit()); bmap_exec!(bmap::BMInit());
Ok(Self {}) Ok(Self {})
} }
} }
impl Drop for BMapGuard { impl Drop for BMap {
fn drop(&mut self) { fn drop(&mut self) {
// Ignore return of this function by design. // Ignore return of this function by design.
// Because using Result in Drop is inviable, // Because using Result in Drop is inviable,
@@ -245,16 +304,6 @@ impl Drop for BMapGuard {
} }
} }
static BMAP_SINGLETON: LazyLock<Result<BMapGuard>> = LazyLock::new(|| BMapGuard::new());
fn get_bmap_guard_singleton() -> Result<&'static BMapGuard> {
BMAP_SINGLETON.as_ref().map_err(|e| e.clone())
}
pub fn is_bmap_available() -> bool {
BMAP_SINGLETON.is_ok()
}
// endregion // endregion
// region: BMFileReader // region: BMFileReader
@@ -277,11 +326,12 @@ pub fn is_bmap_available() -> bool {
pub struct BMFileReader<'a> { pub struct BMFileReader<'a> {
handle: PBMVOID, handle: PBMVOID,
phantom: PhantomData<&'a BMapGuard>, phantom: PhantomData<&'a BMap>,
} }
impl<'a> BMFileReader<'a> { impl<'a> BMFileReader<'a> {
pub fn new<T>( fn new<T>(
_: &'a BMap,
file_name: &str, file_name: &str,
temp_folder: &str, temp_folder: &str,
texture_folder: &str, texture_folder: &str,
@@ -292,11 +342,29 @@ impl<'a> BMFileReader<'a> {
{ {
let file_name = unsafe { marshaler::to_native_string(file_name)? }; let file_name = unsafe { marshaler::to_native_string(file_name)? };
let temp_folder = unsafe { marshaler::to_native_string(temp_folder)? }; let temp_folder = unsafe { marshaler::to_native_string(temp_folder)? };
let texture_folder = unsafe { marshaler::to_native_string(texture_folder) }?; let texture_folder = unsafe { marshaler::to_native_string(texture_folder)? };
let encodings = unsafe { marshaler::to_native_string_array(encodings)? }; let encodings = unsafe { marshaler::to_native_string_array(encodings)? };
let mut file = MaybeUninit::<PBMVOID>::uninit();
bmap_exec!(bmap::BMFile_Load(
file_name.as_raw(),
temp_folder.as_raw(),
texture_folder.as_raw(),
bmap_rs_callback,
encodings.len().try_into()?,
encodings.as_raw(),
arg_out!(file.as_mut_ptr(), PBMVOID)
));
Ok(Self {
handle: unsafe { file.assume_init() },
phantom: PhantomData::<&'a BMap>,
})
} }
} }
impl<'a> BMFileReader<'a> {}
// impl<'a> BMFileReader<'a> { // impl<'a> BMFileReader<'a> {
// fn get_generic_object_count<O, FC>(&self, fc: FnProtoGetCount) -> Result<usize> { // fn get_generic_object_count<O, FC>(&self, fc: FnProtoGetCount) -> Result<usize> {
@@ -309,6 +377,12 @@ impl<'a> BMFileReader<'a> {
// } // }
impl<'a> Drop for BMFileReader<'a> {
fn drop(&mut self) {
let _ = unsafe { bmap::BMFile_Free(self.handle) };
}
}
impl<'a> AbstractPointer for BMFileReader<'a> { impl<'a> AbstractPointer for BMFileReader<'a> {
unsafe fn get_pointer(&self) -> PBMVOID { unsafe fn get_pointer(&self) -> PBMVOID {
self.handle self.handle
@@ -323,7 +397,64 @@ impl<'a> BMFileRW for BMFileReader<'a> {}
pub struct BMFileWriter<'a> { pub struct BMFileWriter<'a> {
handle: PBMVOID, handle: PBMVOID,
phantom: PhantomData<&'a BMapGuard>, phantom: PhantomData<&'a BMap>,
}
impl<'a> BMFileWriter<'a> {
fn new<T>(_: &'a BMap, temp_folder: &str, texture_folder: &str, encodings: &[T]) -> Result<Self>
where
T: Into<Vec<u8>> + Copy,
{
let temp_folder = unsafe { marshaler::to_native_string(temp_folder)? };
let texture_folder = unsafe { marshaler::to_native_string(texture_folder)? };
let encodings = unsafe { marshaler::to_native_string_array(encodings)? };
let mut file = MaybeUninit::<PBMVOID>::uninit();
bmap_exec!(bmap::BMFile_Create(
temp_folder.as_raw(),
texture_folder.as_raw(),
bmap_rs_callback,
encodings.len().try_into()?,
encodings.as_raw(),
arg_out!(file.as_mut_ptr(), PBMVOID)
));
Ok(Self {
handle: unsafe { file.assume_init() },
phantom: PhantomData::<&'a BMap>,
})
}
}
impl<'a> BMFileWriter<'a> {
pub fn save(
&self,
filename: &str,
texture_save_opt: bmap::CK_TEXTURE_SAVEOPTIONS,
compress_level: Option<CKINT>,
) -> Result<()> {
let filename = unsafe { marshaler::to_native_string(filename)? };
let use_compress = compress_level.is_some();
let compress_level = compress_level.unwrap_or(5);
bmap_exec!(bmap::BMFile_Save(
self.get_pointer(),
filename.as_raw(),
texture_save_opt,
use_compress,
compress_level
));
Ok(())
}
}
impl<'a> BMFileWriter<'a> {}
impl<'a> Drop for BMFileWriter<'a> {
fn drop(&mut self) {
let _ = unsafe { bmap::BMFile_Free(self.handle) };
}
} }
impl<'a> AbstractPointer for BMFileWriter<'a> { impl<'a> AbstractPointer for BMFileWriter<'a> {
@@ -483,12 +614,12 @@ pub trait BMMaterial: BMObject {
set_copyable_value(self, bmap::BMMaterial_SetSpecularPower, power) set_copyable_value(self, bmap::BMMaterial_SetSpecularPower, power)
} }
fn get_texture(&self) -> Result<Option<Box<dyn BMTexture>>> { // fn get_texture(&self) -> Result<Option<Box<dyn BMTexture>>> {
todo!(); // todo!();
} // }
fn set_texture(&mut self, texture: Option<&dyn BMTexture>) -> Result<()> { // fn set_texture(&mut self, texture: Option<&dyn BMTexture>) -> Result<()> {
todo!(); // todo!();
} // }
fn get_texture_border_color(&self) -> Result<bmap::VxColor> { fn get_texture_border_color(&self) -> Result<bmap::VxColor> {
let intermediary = get_copyable_value(self, bmap::BMMaterial_GetTextureBorderColor)?; let intermediary = get_copyable_value(self, bmap::BMMaterial_GetTextureBorderColor)?;
@@ -950,7 +1081,24 @@ libobj_impl_obj_trait!(BMGroupImpl, BMGroup);
pub struct BMMeshTrans<'a> { pub struct BMMeshTrans<'a> {
handle: PBMVOID, handle: PBMVOID,
phantom: PhantomData<&'a BMapGuard>, phantom: PhantomData<&'a BMap>,
}
impl<'a> BMMeshTrans<'a> {
fn new(_: &'a BMap) -> Result<Self> {
let mut trans = MaybeUninit::<PBMVOID>::uninit();
bmap_exec!(bmap::BMMeshTrans_New(arg_out!(trans.as_mut_ptr(), PBMVOID)));
Ok(Self {
handle: unsafe { trans.assume_init() },
phantom: PhantomData::<&'a BMap>,
})
}
}
impl<'a> Drop for BMMeshTrans<'a> {
fn drop(&mut self) {
let _ = unsafe { bmap::BMMeshTrans_Delete(self.handle) };
}
} }
impl<'a> AbstractPointer for BMMeshTrans<'a> { impl<'a> AbstractPointer for BMMeshTrans<'a> {

View File

@@ -0,0 +1,4 @@
//! BMap wrapper used utility stuff

View File

@@ -1,5 +1,4 @@
//! The Rust binding to BMap. //! The Rust binding to BMap.
pub mod virtools_types; pub mod virtools_types;
pub mod bmap; pub mod bmap;
pub(crate) mod marshaler;
pub mod bmap_wrapper; pub mod bmap_wrapper;

View File

@@ -0,0 +1,9 @@
use bmap_rs::bmap_wrapper as bmap;
#[test]
fn test_complete() {
assert!(bmap::BMap::is_available());
bmap::BMap::with_bmap(|b| {
let r = b.create_file_reader("", "", "", &[""]);
});
}

View File

@@ -1,7 +1,7 @@
use bmap_rs::bmap; use bmap_rs::bmap;
#[test] #[test]
fn test_init_and_dispose() { fn test_raw() {
assert!(unsafe { bmap::BMInit() }); assert!(unsafe { bmap::BMInit() });
assert!(unsafe { bmap::BMDispose() }); assert!(unsafe { bmap::BMDispose() });
} }