use std::cell::Cell;
use std::{ops::Deref, path::Path, sync::Arc};
use dlopen2::wrapper::{Container, WrapperApi};
use notify::{Event, RecursiveMode, Watcher};
use crate::{
R,
event::{EventNewDll, EventOldDll},
};
pub const MAX_LOAD_INDEX: usize = 10;
pub struct HotloadInner<API: WrapperApi> {
pub path: String,
pub container: [Option<Container<API>>; MAX_LOAD_INDEX],
pub load_index: usize, pub watch: Option<notify::RecommendedWatcher>,
}
impl<API: WrapperApi> HotloadInner<API> {
pub fn new(path: impl Into<String>) -> Self {
Self {
path: path.into(),
container: [None, None, None, None, None, None, None, None, None, None],
load_index: MAX_LOAD_INDEX,
watch: None,
}
}
}
pub struct Hotload<API: WrapperApi> {
pub(crate) inner: UA<HotloadInner<API>>,
file_path: &'static str,
}
impl<API: WrapperApi + 'static> Deref for Hotload<API> {
type Target = Container<API>;
fn deref(&self) -> &Self::Target {
let inner = &*self.inner;
let inner = &inner.container[inner.load_index];
inner.as_ref().expect("container not loaded")
}
}
const HOTLOAD_CACHE_DIR: &str = ".hotload_cache_dir";
impl<API: WrapperApi + 'static + Send> Hotload<API> {
pub const fn new(dll_path: &'static str) -> Self {
Self {
inner: UA::new(),
file_path: dll_path,
}
}
fn get_dir(path: &str) -> String {
let dir = {
let path = path;
let p = Path::new(&path);
let mut p = p.components().collect::<Vec<_>>();
p.pop();
p.iter()
.map(|x| x.as_os_str().to_str().unwrap_or_else(|| ""))
.collect::<Vec<_>>()
.join("/")
};
dir
}
pub fn dll_name(&self) -> String {
get_file_name(self.file_path)
.unwrap_or_else(|_| "".to_string())
.to_string()
}
pub fn init_load<F: FnMut(EventNewDll<API>, Option<EventOldDll<API>>) + Send + 'static>(
&self,
mut event_call: F,
) -> R<()> {
self.inner.set(HotloadInner::new(self.file_path));
let dir = Self::get_dir(self.file_path);
let file_name = get_file_name(&self.file_path)?;
let dir_cahe = Path::new(&dir);
let mut dir_cahe = dir_cahe.to_path_buf();
dir_cahe.push(HOTLOAD_CACHE_DIR);
_ = std::fs::create_dir(dir_cahe.clone());
let new_event = EventNewDll::new(self.inner.clone(), dir_cahe.clone())?;
event_call(new_event, None);
let inner_c = self.inner.clone();
let dir_cahe_c = dir_cahe.clone();
let mut watcher = notify::recommended_watcher(move |res: Result<Event, notify::Error>| {
let event = match res {
Ok(v) => v,
Err(e) => {
error!("watch error: {e:?}");
return;
}
};
if event
.paths
.iter()
.find(|x| {
x.file_name()
.map(|x| x.to_str().unwrap_or_else(|| "") == file_name)
.unwrap_or_else(|| false)
})
.is_none()
{
return;
}
let mut is_action = false;
match &event.kind {
notify::EventKind::Create(_e) => is_action = true,
notify::EventKind::Modify(notify::event::ModifyKind::Name(
notify::event::RenameMode::To,
)) => is_action = true,
_ => (),
};
debug!("event: {:?}; Is action load dll: {is_action}", event);
if is_action {
let load_new = match EventNewDll::new(inner_c.clone(), dir_cahe_c.clone()) {
Ok(v) => v,
Err(e) => {
error!("load new error: {e:?}");
return;
}
};
let release_old = EventOldDll::new(inner_c.clone());
event_call(load_new, Some(release_old));
}
})?;
let inner = self.inner.borrow_mut();
debug!("hoload watch path: {dir:?}");
watcher.watch(Path::new(&dir), RecursiveMode::NonRecursive)?;
inner.watch = Some(watcher);
Ok(())
}
pub fn borrow_mut(&self) -> &mut Container<API> {
let inner = self.inner.borrow_mut();
let inner = &mut inner.container[inner.load_index];
inner.as_mut().expect("container not loaded")
}
}
impl<API: WrapperApi> Hotload<API> {
pub fn force_release(&self) {
let inner = self.inner.borrow_mut();
if inner.load_index == MAX_LOAD_INDEX {
return;
}
for ele in inner.container.iter_mut() {
*ele = None;
}
inner.load_index = MAX_LOAD_INDEX;
}
}
impl<API: WrapperApi> Drop for Hotload<API> {
fn drop(&mut self) {
self.force_release();
}
}
pub struct UA<T> {
ptr: Cell<*mut T>,
}
impl<T> Deref for UA<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
self.borrow()
}
}
impl<T> Clone for UA<T> {
fn clone(&self) -> Self {
let ptr = self.ptr.get();
if !ptr.is_null() {
unsafe {
Arc::increment_strong_count(ptr as *const T);
}
}
Self {
ptr: Cell::new(ptr),
}
}
}
impl<T> UA<T> {
pub const fn new() -> Self {
Self {
ptr: Cell::new(std::ptr::null_mut()),
}
}
pub fn set(&self, u: T) {
let new_ptr = Arc::into_raw(Arc::new(u)) as *mut T;
let old_ptr = self.ptr.replace(new_ptr);
if !old_ptr.is_null() {
unsafe {
drop(Arc::from_raw(old_ptr as *const T));
}
}
}
pub fn borrow_mut(&self) -> &mut T {
let ptr = self.ptr.get();
assert!(!ptr.is_null(), "hotload dll value is not init");
unsafe { &mut *ptr }
}
pub fn borrow(&self) -> &T {
let ptr = self.ptr.get();
assert!(!ptr.is_null(), "hotload dll value is not init");
unsafe { &*ptr }
}
}
impl<T> Drop for UA<T> {
fn drop(&mut self) {
let ptr = self.ptr.get();
if !ptr.is_null() {
unsafe {
drop(Arc::from_raw(ptr as *const T));
}
}
}
}
unsafe impl<T> Sync for UA<T> {}
unsafe impl<T> Send for UA<T> {}
#[test]
fn test_ua() {
unsafe {
std::env::set_var("RUST_LOG", "debug");
}
env_logger::init();
let a: UA<i32> = UA::new();
a.set(1);
assert_eq!(*a, 1);
a.set(2);
assert_eq!(*a, 2);
assert_eq!(*a, 2);
struct T {
v: String,
}
impl T {
fn test(&self) -> i32 {
2
}
}
let a: UA<T> = UA::new();
a.set(T { v: "1".to_string() });
assert_eq!(a.test(), 2);
assert_eq!(a.test(), 2);
assert_eq!(a.v.as_str(), "1");
let a = a.clone();
assert_eq!(a.test(), 2);
assert_eq!(a.v, "1");
a.set(T {
v: "22".to_string(),
});
assert_eq!(a.test(), 2);
assert_eq!(a.v, "22");
assert_eq!(a.v, "22");
}
#[test]
fn test_ua_多线程() {
unsafe { std::env::set_var("RUST_LOG", "debug") };
env_logger::init();
let a: UA<i32> = UA::new();
a.set(1);
assert_eq!(*a, 1);
a.set(2);
assert_eq!(*a, 2);
assert_eq!(*a, 2);
struct T {
v: String,
}
impl T {
fn test(&self) -> i32 {
self.v.parse().unwrap_or_else(|_| -1)
}
}
static A: UA<T> = UA::new();
A.set(T { v: "1".to_string() });
for _ in 0..5 {
std::thread::spawn(move || {
loop {
assert_eq!(A.test(), 1);
}
});
}
for _ in 0..5 {
std::thread::spawn(move || {
loop {
assert_eq!(A.test(), 1);
}
});
}
std::thread::sleep(std::time::Duration::from_secs(9999999999));
}
pub(crate) fn get_file_name<S: AsRef<std::ffi::OsStr> + ?Sized>(s: &S) -> R<String> {
let f_old: &Path = std::path::Path::new(s);
if !std::fs::exists(f_old)? {
return Err(format!("get_file_name not exists: {:?}", s.as_ref()))?;
}
let f_name = f_old
.file_name()
.ok_or_else(|| "file name not find")?
.to_str()
.ok_or_else(|| "file name not find")?;
Ok(f_name.to_string())
}