use std::cell::RefMut;
use crate::ffi::errors::FFIError;
use fxhash::FxHashMap;
use std::sync::MutexGuard;
use crate::zygote::ClonedZygote;
use crate::zygote::ZygoteState;
use crate::ffi::value::{Type, Value};
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::{Mutex, OnceLock};
use crate::zygote::{FFIRequest, FFIResponse, ZygoteStack};
static NextLibraryID: AtomicUsize = AtomicUsize::new(1);
static RegisteredLibraries: OnceLock<Mutex<FxHashMap<usize, String>>> = OnceLock::new();
#[inline(always)]
fn nextLibraryId() -> usize
{
NextLibraryID.fetch_add(1, Ordering::SeqCst)
}
#[inline(always)]
fn getRegistry() -> &'static Mutex<FxHashMap<usize, String>>
{
RegisteredLibraries.get_or_init(|| Mutex::new(FxHashMap::default()))
}
#[inline]
fn lockRegistry() -> MutexGuard<'static, FxHashMap<usize, String>>
{
getRegistry().lock().unwrap_or_else(|poisoned| poisoned.into_inner())
}
#[inline]
fn registerLibrary(id: usize, path: &str) -> ()
{
let mut registry: MutexGuard<FxHashMap<usize, String>> = lockRegistry();
registry.insert(id, path.to_string());
}
#[inline]
fn unregisterLibrary(id: usize) -> ()
{
let mut registry: MutexGuard<FxHashMap<usize, String>> = lockRegistry();
registry.remove(&id);
}
pub(super) fn sendRawRequest(request: FFIRequest) -> Result<Value, FFIError>
{
if ZygoteState.get().is_none() {
return Err(FFIError::ZygoteNotInitialized);
}
ZygoteStack.with(|stack| {
let mut mutStack: RefMut<Vec<ClonedZygote>> = stack.borrow_mut();
let zygote: &mut ClonedZygote = mutStack
.last_mut()
.ok_or(FFIError::NoActiveZygoteScope)?;
match zygote.call(request) {
Ok(FFIResponse::Ok(val)) => Ok(val),
Ok(FFIResponse::Err(err)) => Err(err),
Err(err) => Err(FFIError::ZygoteCommunicationFailed(err))
}
})
}
fn callById(
libraryId: usize,
libraryPath: &str,
functionName: &str,
args: Vec<Value>,
resultType: Type
) -> Result<Value, FFIError>
{
if ZygoteState.get().is_none() {
return Err(FFIError::ZygoteNotInitialized);
}
let registry: MutexGuard<FxHashMap<usize, String>> = lockRegistry();
if !registry.contains_key(&libraryId) {
return Err(FFIError::LibraryNotFound{ libraryPath: libraryPath.to_string() });
}
drop(registry);
sendRawRequest(FFIRequest::Call {
libraryPath: libraryPath.to_string(), functionName: functionName.to_string(), args, resultType,
})
}
#[doc(hidden)]
pub struct __Library<const Allowed: bool = false>
{
libraryId: usize,
libraryPath: String
}
impl<const Allowed: bool> __Library<Allowed>
{
#[inline(always)]
pub const fn id(&self) -> usize
{
self.libraryId
}
}
impl<const Allowed: bool> Drop for __Library<Allowed>
{
fn drop(&mut self) {
unregisterLibrary(self.libraryId)
}
}
impl __Library<true>
{
pub fn call(
&self,
functionName: &str,
args: Vec<Value>,
resultType: Type,
) -> Result<Value, FFIError>
{
callById(self.libraryId, &self.libraryPath, functionName, args, resultType)
}
pub fn load(libraryPath: &str) -> Result<Self, FFIError>
{
let libraryId: usize = nextLibraryId();
let ownedPath: String = String::from(libraryPath);
registerLibrary(libraryId, &ownedPath);
Ok(Self{ libraryId, libraryPath: ownedPath })
}
pub fn unload(self) -> Result<(), FFIError>
{
Ok(())
}
}
pub type Library = __Library<false>;
#[doc(hidden)]
pub type __FFILibrary = __Library<true>;
#[cfg(test)]
mod tests
{
use crate::ffi;
use crate::ffi::library::lockRegistry;
use crate::ffi::value::Value;
use crate::ffi::value::Type;
#[test]
fn libraryDrop() -> ()
{
let id: usize = ffi!{
let libm: Library = Library::load("libm.so.6")?;
let id: usize = libm.id();
drop(libm);
Ok(id)
}.expect("ffi block failed");
assert!(!lockRegistry().contains_key(&id));
}
#[test]
fn libraryAutoDrop() -> ()
{
let id: usize = ffi!{
let libm: Library = Library::load("libm.so.6")?;
let id: usize = libm.id();
Ok(id)
}.expect("ffi block failed");
assert!(!lockRegistry().contains_key(&id));
}
#[test]
fn libraryUnload() -> ()
{
let id: usize = ffi!{
let libm: Library = Library::load("libm.so.6")?;
let id: usize = libm.id();
libm.unload()?;
Ok(id)
}.expect("ffi block failed");
assert!(!lockRegistry().contains_key(&id));
}
#[test]
fn sqrt() -> ()
{
let result: Value = ffi!{
let libm: Library = Library::load("libm.so.6")?;
let args: Vec<Value> = vec![Value::F64(4.0)];
Ok(libm.call("sqrt", args, Type::F64)?)
}.expect("FFI call failed");
if let Value::F64(val) = result {
assert!((val - 2.0).abs() < f64::EPSILON);
} else {
panic!("Expected F64");
}
}
#[test]
fn abs() -> ()
{
let result: Value = ffi!{
let libm: Library = Library::load("libm.so.6")?;
let args: Vec<Value> = vec![Value::I32(-5)];
Ok(libm.call("abs", args, Type::I32)?)
}.expect("FFI call failed");
if let Value::I32(val) = result {
assert_eq!(val, 5);
} else {
panic!("Expected I32");
}
}
#[test]
fn multipleCallsInSingleLibrary() -> ()
{
let results: Vec<Value> = ffi!{
let mut outputs: Vec<Value> = Vec::with_capacity(10);
let libm: Library = Library::load("libm.so.6")?;
for i in 1..=10
{
let input: f64 = (i * i) as f64;
let args: Vec<Value> = vec![Value::F64(input)];
let res: Value = libm.call("sqrt", args, Type::F64)?;
outputs.push(res);
}
Ok(outputs)
}.expect("Batch FFI call failed");
assert_eq!(results.len(), 10);
for (i, val) in results.into_iter().enumerate()
{
let expected: f64 = (i + 1) as f64;
if let Value::F64(actual) = val {
assert!((actual - expected).abs() < f64::EPSILON, "Expected {}, got {}", expected, actual);
} else {
panic!("Expected Value::F64 at index {}", i);
}
}
}
#[test]
fn noneArgumentFails() -> ()
{
let result: Result<Value, _> = ffi!{
let libm: Library = Library::load("libm.so.6")?;
let args: Vec<Value> = vec![Value::None];
let res: Value = libm.call("sqrt", args, Type::F64)?;
Ok(res)
};
assert!(result.is_err(), "FFI call with Value::None should fail");
}
}