use std::{cell, ffi, mem, sync::{self, atomic::{AtomicPtr, Ordering}}};
use crate::*;
mod loader;
#[derive(Clone, Copy, PartialEq, Eq, Ord, PartialOrd, Hash, Debug)]
pub enum LinkType {
Vulkan,
System(&'static [&'static str]),
}
pub struct LazyFn<F: 'static> {
pub(crate) once: sync::Once,
status: cell::UnsafeCell<Option<error::DylinkError>>,
pub(crate) addr_ptr: AtomicPtr<F>,
pub(crate) addr: cell::UnsafeCell<Option<F>>,
}
impl<F: 'static + Copy> LazyFn<F> {
#[inline]
pub const fn new(thunk: AtomicPtr<F>) -> Self {
assert!(mem::size_of::<FnPtr>() == mem::size_of::<F>());
Self {
addr_ptr: thunk,
once: sync::Once::new(),
status: cell::UnsafeCell::new(None),
addr: cell::UnsafeCell::new(None)
}
}
pub fn load(&self, fn_name: &'static ffi::CStr, link_ty: LinkType) -> Result<F> {
let str_name: &'static str = fn_name.to_str().unwrap();
self.once.call_once(|| unsafe {
let maybe = match link_ty {
LinkType::Vulkan => loader::vulkan_loader(str_name),
LinkType::System(lib_list) => {
let default_error = {
let (subject, kind) = if lib_list.len() > 1 {
(None, ErrorKind::ListNotFound)
} else {
(Some(lib_list[0]), ErrorKind::LibNotFound)
};
error::DylinkError::new(subject, kind)
};
let mut result = Err(default_error);
for lib_name in lib_list {
match loader::system_loader(ffi::OsStr::new(lib_name), str_name) {
Ok(addr) => {
result = Ok(addr);
break;
}
Err(err) => {
if let ErrorKind::FnNotFound = err.kind() {
result = Err(err);
break;
}
}
}
}
result
}
};
match maybe {
Ok(addr) => {
let addr_ptr = self.addr.get();
addr_ptr.write(Some(mem::transmute_copy(&addr)));
self.addr_ptr.store(mem::transmute(addr_ptr), Ordering::Relaxed);
}
Err(err) => {
self.status.get().write(Some(err));
}
}
});
match unsafe { (*self.status.get()).clone() } {
None => Ok(*self.as_ref()),
Some(err) => Err(err),
}
}
}
unsafe impl<F: 'static> Send for LazyFn<F> {}
unsafe impl<F: 'static> Sync for LazyFn<F> {}
impl<F: 'static> std::ops::Deref for LazyFn<F> {
type Target = F;
fn deref(&self) -> &Self::Target {
self.as_ref()
}
}
impl<F: 'static> std::convert::AsRef<F> for LazyFn<F> {
#[inline]
fn as_ref(&self) -> &F {
unsafe { self.addr_ptr.load(Ordering::Relaxed).as_ref().unwrap_unchecked() }
}
}