diff --git a/crates/molotov-core/src/assets/c_api.rs b/crates/molotov-core/src/assets/c_api.rs new file mode 100644 index 0000000..b66f545 --- /dev/null +++ b/crates/molotov-core/src/assets/c_api.rs @@ -0,0 +1,225 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +#![allow(non_snake_case)] +use super::asset::AssetFile; +use super::dir::AssetDir; +use super::manager::AssetManager; +use std::collections::HashMap; +use std::ffi::{c_void, CStr}; +use std::sync::atomic::{AtomicUsize, Ordering}; +use std::sync::{Arc, Mutex, RwLock}; + +static NEXT_ASSET_ID: AtomicUsize = AtomicUsize::new(3001); + +lazy_static::lazy_static! { + static ref ASSET_TABLE: RwLock>> = RwLock::new(HashMap::new()); + static ref DIR_TABLE: RwLock>> = RwLock::new(HashMap::new()); + pub static ref GLOBAL_ASSET_MANAGER: Arc> = Arc::new(RwLock::new(AssetManager::new())); +} + +pub fn register_test_asset(path: &str, data: Vec) -> *mut () { + if let Ok(mut mgr) = GLOBAL_ASSET_MANAGER.write() { + mgr.add_asset(path, data); + } + 1001 as *mut () +} + +#[no_mangle] +pub unsafe extern "C" fn AAssetManager_open( + _mgr: *mut (), + filename: *const libc::c_char, + _mode: i32, +) -> *mut () { + if filename.is_null() { + return std::ptr::null_mut(); + } + let name_str = match CStr::from_ptr(filename).to_str() { + Ok(s) => s, + Err(_) => return std::ptr::null_mut(), + }; + + let mgr = match GLOBAL_ASSET_MANAGER.read() { + Ok(m) => m, + Err(_) => return std::ptr::null_mut(), + }; + + if let Some(file) = mgr.open(name_str) { + let id = NEXT_ASSET_ID.fetch_add(1, Ordering::Relaxed); + if let Ok(mut table) = ASSET_TABLE.write() { + table.insert(id, Mutex::new(file)); + id as *mut () + } else { + std::ptr::null_mut() + } + } else { + std::ptr::null_mut() + } +} + +#[no_mangle] +pub unsafe extern "C" fn AAssetManager_openDir(_mgr: *mut (), dirName: *const libc::c_char) -> *mut () { + let name_str = if !dirName.is_null() { + CStr::from_ptr(dirName).to_str().unwrap_or("") + } else { + "" + }; + + let mgr = match GLOBAL_ASSET_MANAGER.read() { + Ok(m) => m, + Err(_) => return std::ptr::null_mut(), + }; + + let dir = mgr.open_dir(name_str); + let id = NEXT_ASSET_ID.fetch_add(1, Ordering::Relaxed); + if let Ok(mut table) = DIR_TABLE.write() { + table.insert(id, Mutex::new(dir)); + id as *mut () + } else { + std::ptr::null_mut() + } +} + +#[no_mangle] +pub unsafe extern "C" fn AAsset_read(asset: *mut (), buf: *mut c_void, count: usize) -> i32 { + if asset.is_null() || buf.is_null() || count == 0 { + return 0; + } + let id = asset as usize; + if let Ok(table) = ASSET_TABLE.read() { + if let Some(mutex) = table.get(&id) { + if let Ok(mut file) = mutex.lock() { + let slice = std::slice::from_raw_parts_mut(buf as *mut u8, count); + return file.read(slice) as i32; + } + } + } + 0 +} + +#[no_mangle] +pub unsafe extern "C" fn AAsset_seek(asset: *mut (), offset: i64, whence: i32) -> i64 { + if asset.is_null() { + return -1; + } + let id = asset as usize; + if let Ok(table) = ASSET_TABLE.read() { + if let Some(mutex) = table.get(&id) { + if let Ok(mut file) = mutex.lock() { + return file.seek(offset, whence); + } + } + } + -1 +} + +#[no_mangle] +pub unsafe extern "C" fn AAsset_seek64(asset: *mut (), offset: i64, whence: i32) -> i64 { + AAsset_seek(asset, offset, whence) +} + +#[no_mangle] +pub unsafe extern "C" fn AAsset_getLength(asset: *mut ()) -> i64 { + if asset.is_null() { + return 0; + } + let id = asset as usize; + if let Ok(table) = ASSET_TABLE.read() { + if let Some(mutex) = table.get(&id) { + if let Ok(file) = mutex.lock() { + return file.length(); + } + } + } + 0 +} + +#[no_mangle] +pub unsafe extern "C" fn AAsset_getLength64(asset: *mut ()) -> i64 { + AAsset_getLength(asset) +} + +#[no_mangle] +pub unsafe extern "C" fn AAsset_getRemainingLength(asset: *mut ()) -> i64 { + if asset.is_null() { + return 0; + } + let id = asset as usize; + if let Ok(table) = ASSET_TABLE.read() { + if let Some(mutex) = table.get(&id) { + if let Ok(file) = mutex.lock() { + return file.remaining_length(); + } + } + } + 0 +} + +#[no_mangle] +pub unsafe extern "C" fn AAsset_getRemainingLength64(asset: *mut ()) -> i64 { + AAsset_getRemainingLength(asset) +} + +#[no_mangle] +pub unsafe extern "C" fn AAsset_getBuffer(asset: *mut ()) -> *const c_void { + if asset.is_null() { + return std::ptr::null(); + } + let id = asset as usize; + if let Ok(table) = ASSET_TABLE.read() { + if let Some(mutex) = table.get(&id) { + if let Ok(file) = mutex.lock() { + return file.get_buffer(); + } + } + } + std::ptr::null() +} + +#[no_mangle] +pub unsafe extern "C" fn AAsset_close(asset: *mut ()) { + if !asset.is_null() { + let id = asset as usize; + if let Ok(mut table) = ASSET_TABLE.write() { + table.remove(&id); + } + } +} + +#[no_mangle] +pub unsafe extern "C" fn AAssetDir_getNextFileName(assetDir: *mut ()) -> *const libc::c_char { + if assetDir.is_null() { + return std::ptr::null(); + } + let id = assetDir as usize; + if let Ok(table) = DIR_TABLE.read() { + if let Some(mutex) = table.get(&id) { + if let Ok(mut dir) = mutex.lock() { + return dir.get_next_filename(); + } + } + } + std::ptr::null() +} + +#[no_mangle] +pub unsafe extern "C" fn AAssetDir_rewind(assetDir: *mut ()) { + if !assetDir.is_null() { + let id = assetDir as usize; + if let Ok(table) = DIR_TABLE.read() { + if let Some(mutex) = table.get(&id) { + if let Ok(mut dir) = mutex.lock() { + dir.rewind(); + } + } + } + } +} + +#[no_mangle] +pub unsafe extern "C" fn AAssetDir_close(assetDir: *mut ()) { + if !assetDir.is_null() { + let id = assetDir as usize; + if let Ok(mut table) = DIR_TABLE.write() { + table.remove(&id); + } + } +} diff --git a/crates/molotov-core/src/assets/mod.rs b/crates/molotov-core/src/assets/mod.rs index 02365ce..c3bb097 100644 --- a/crates/molotov-core/src/assets/mod.rs +++ b/crates/molotov-core/src/assets/mod.rs @@ -1,5 +1,6 @@ // SPDX-License-Identifier: GPL-3.0-or-later pub mod asset; +pub mod c_api; pub mod dir; pub mod manager; pub mod types; diff --git a/crates/molotov-core/tests/aasset_c_api_test.rs b/crates/molotov-core/tests/aasset_c_api_test.rs new file mode 100644 index 0000000..02ebc6c --- /dev/null +++ b/crates/molotov-core/tests/aasset_c_api_test.rs @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +use molotov_core::assets::c_api::*; +use std::ffi::CString; + +#[test] +fn test_aasset_c_api_lifecycle() { + let mgr_handle = register_test_asset("sounds/click.ogg", b"TEST_CLICK_AUDIO".to_vec()); + + unsafe { + let filename = CString::new("sounds/click.ogg").unwrap(); + let asset = AAssetManager_open(mgr_handle, filename.as_ptr(), 3); + assert!(!asset.is_null()); + + assert_eq!(AAsset_getLength(asset), 16); + assert_eq!(AAsset_getRemainingLength(asset), 16); + + let mut buf = [0u8; 16]; + let read_bytes = AAsset_read(asset, buf.as_mut_ptr() as *mut _, 16); + assert_eq!(read_bytes, 16); + assert_eq!(&buf, b"TEST_CLICK_AUDIO"); + + AAsset_close(asset); + // Double-close must not panic or abort + AAsset_close(asset); + } +}