veloren/voxygen/anim/src/dyn_lib.rs

248 lines
7.9 KiB
Rust
Raw Normal View History

2020-06-17 07:49:14 +00:00
use lazy_static::lazy_static;
use libloading::Library;
use notify::{immediate_watcher, EventKind, RecursiveMode, Watcher};
2020-06-17 07:49:14 +00:00
use std::{
process::{Command, Stdio},
sync::{mpsc, Mutex},
thread,
time::Duration,
2020-06-17 07:49:14 +00:00
};
2020-06-25 13:07:34 +00:00
use find_folder::Search;
use hashbrown::HashSet;
use std::{env, path::PathBuf};
use tracing::{debug, error, info};
#[cfg(target_os = "windows")]
const COMPILED_FILE: &str = "voxygen_anim.dll";
#[cfg(target_os = "windows")]
const ACTIVE_FILE: &str = "voxygen_anim_active.dll";
2020-06-25 13:07:34 +00:00
#[cfg(not(target_os = "windows"))]
const COMPILED_FILE: &str = "libvoxygen_anim.so";
2020-06-25 13:07:34 +00:00
#[cfg(not(target_os = "windows"))]
const ACTIVE_FILE: &str = "libvoxygen_anim_active.so";
// This option is required as `hotreload()` moves the `LoadedLib`.
2020-06-17 07:49:14 +00:00
lazy_static! {
2020-06-25 13:07:34 +00:00
pub static ref LIB: Mutex<Option<LoadedLib>> = Mutex::new(Some(LoadedLib::compile_load()));
2020-06-17 07:49:14 +00:00
}
2020-06-25 13:07:34 +00:00
/// LoadedLib holds a loaded dynamic library and the location of library file
/// with the appropriate OS specific name and extension i.e.
/// `libvoxygen_anim_active.dylib`, `voxygen_anim_active.dll`.
///
/// # NOTE
/// DOES NOT WORK ON MACOS, due to some limitations with hot-reloading the
/// `.dylib`.
2020-06-17 07:49:14 +00:00
pub struct LoadedLib {
/// Loaded library.
2020-06-17 07:49:14 +00:00
pub lib: Library,
/// Path to the library.
pub lib_path: PathBuf,
2020-06-17 07:49:14 +00:00
}
impl LoadedLib {
2020-06-25 13:07:34 +00:00
/// Compile and load the dynamic library
///
/// This is necessary because the very first time you use hot reloading you
/// wont have the library, so you can't load it until you have compiled it!
2020-06-25 13:07:34 +00:00
fn compile_load() -> Self {
#[cfg(target_os = "macos")]
error!("The hot reloading feature does not work on macos.");
2020-06-17 07:49:14 +00:00
// Compile
if !compile() {
panic!("Animation compile failed.");
} else {
info!("Animation compile succeeded.");
}
copy(&LoadedLib::determine_path());
2020-06-25 13:07:34 +00:00
Self::load()
2020-06-17 07:49:14 +00:00
}
2020-06-25 13:07:34 +00:00
/// Load a library from disk.
///
/// Currently this is pretty fragile, it gets the path of where it thinks
/// the dynamic library should be and tries to load it. It will panic if it
/// is missing.
2020-06-17 07:49:14 +00:00
fn load() -> Self {
let lib_path = LoadedLib::determine_path();
// Try to load the library.
let lib = match Library::new(lib_path.clone()) {
Ok(lib) => lib,
Err(e) => panic!(
"Tried to load dynamic library from {:?}, but it could not be found. The first \
reason might be that you need to uncomment a line in `voxygen/anim/Cargo.toml` \
to build the library required for hot reloading. The second is we may require a \
special case for your OS so we can find it. {:?}",
lib_path, e
),
};
Self { lib, lib_path }
}
/// Determine the path to the dynamic library based on the path of the
/// current executable.
fn determine_path() -> PathBuf {
let current_exe = env::current_exe();
2020-06-25 13:07:34 +00:00
// If we got the current_exe, we need to go up a level and then down
// in to debug (in case we were in release or another build dir).
let mut lib_path = match current_exe {
2020-06-25 13:07:34 +00:00
Ok(mut path) => {
// Remove the filename to get the directory.
path.pop();
// Search for the debug directory.
let dir = Search::ParentsThenKids(1, 1)
.of(path)
.for_folder("debug")
.expect(
"Could not find the debug build directory relative to the current \
executable.",
);
debug!(?dir, "Found the debug build directory.");
dir
},
Err(e) => {
panic!(
"Could not determine the path of the current executable, this is needed to \
2020-06-25 13:07:34 +00:00
hotreload the dynamic library. {:?}",
e
);
},
};
// Determine the platform specific path and push it onto our already
// established target/debug dir.
lib_path.push(ACTIVE_FILE);
lib_path
2020-06-17 07:49:14 +00:00
}
}
/// Initialise a watcher.
///
/// The assumption is that this is run from the voxygen crate's root directory
/// as it will watch the relative path `anim` for any changes to `.rs`
/// files. Upon noticing changes it will wait a moment and then recompile.
2020-06-17 07:49:14 +00:00
pub fn init() {
2020-06-25 13:07:34 +00:00
// Make sure first compile is done by accessing the lazy_static and then
// immediately dropping (because we don't actually need it).
drop(LIB.lock());
// TODO: use crossbeam
let (reload_send, reload_recv) = mpsc::channel();
2020-06-17 07:49:14 +00:00
// Start watcher
let mut watcher = immediate_watcher(move |res| event_fn(res, &reload_send)).unwrap();
watcher.watch("anim", RecursiveMode::Recursive).unwrap();
2020-06-17 07:49:14 +00:00
// Start reloader that watcher signals
// "Debounces" events since I can't find the option to do this in the latest
// `notify`
thread::spawn(move || {
let mut modified_paths = HashSet::new();
while let Ok(path) = reload_recv.recv() {
modified_paths.insert(path);
2020-06-19 05:31:15 +00:00
// Wait for any additional modify events before reloading
while let Ok(path) = reload_recv.recv_timeout(Duration::from_millis(300)) {
modified_paths.insert(path);
}
info!(
?modified_paths,
"Hot reloading animations because files in `anim` modified."
);
hotreload();
}
});
2020-06-17 07:49:14 +00:00
// Let the watcher live forever
std::mem::forget(watcher);
}
/// Event function to hotreload the dynamic library
///
/// This is called by the watcher to filter for modify events on `.rs` files
/// before sending them back.
fn event_fn(res: notify::Result<notify::Event>, sender: &mpsc::Sender<String>) {
2020-06-17 07:49:14 +00:00
match res {
Ok(event) => match event.kind {
EventKind::Modify(_) => {
event
2020-06-17 07:49:14 +00:00
.paths
.iter()
.filter(|p| p.extension().map(|e| e == "rs").unwrap_or(false))
.map(|p| p.to_string_lossy().into_owned())
// Signal reloader
.for_each(|p| { let _ = sender.send(p); });
2020-06-17 07:49:14 +00:00
},
_ => {},
},
2020-06-25 13:07:34 +00:00
Err(e) => error!(?e, "Animation hotreload watcher error."),
2020-06-17 07:49:14 +00:00
}
}
/// Hotreload the dynamic library
///
/// This will reload the dynamic library by first internally calling compile
/// and then reloading the library.
fn hotreload() {
2020-06-25 13:07:34 +00:00
// Do nothing if recompile failed.
if compile() {
let mut lock = LIB.lock().unwrap();
// Close lib.
2020-06-25 13:07:34 +00:00
let loaded_lib = lock.take().unwrap();
loaded_lib.lib.close().unwrap();
copy(&loaded_lib.lib_path);
// Open new lib.
*lock = Some(LoadedLib::load());
info!("Updated animations.");
2020-06-17 07:49:14 +00:00
}
}
/// Recompile the anim package
///
/// Returns `false` if the compile failed.
fn compile() -> bool {
let output = Command::new("cargo")
.stderr(Stdio::inherit())
.stdout(Stdio::inherit())
.arg("build")
.arg("--package")
.arg("veloren-voxygen-anim")
.output()
.unwrap();
output.status.success()
}
/// Copy the lib file, so we have an `_active` copy.
///
/// We do this for all OS's although it is only strictly necessary for windows.
/// The reason we do this is to make the code easier to understand and debug.
fn copy(lib_path: &PathBuf) {
2020-06-25 13:07:34 +00:00
// Use the platform specific names.
let lib_compiled_path = lib_path.with_file_name(COMPILED_FILE);
let lib_output_path = lib_path.with_file_name(ACTIVE_FILE);
// Get the path to where the lib was compiled to.
debug!(?lib_compiled_path, ?lib_output_path, "Moving.");
2020-06-25 13:07:34 +00:00
// Copy the library file from where it is output, to where we are going to
// load it from i.e. lib_path.
std::fs::copy(lib_compiled_path, lib_output_path).expect("Failed to rename dynamic library.");
2020-06-17 07:49:14 +00:00
}