Merge branch 'kevinglasson/hotfix-anim-hotreloading-compile-error' into 'master'

Refactor and document hotreloading feature

See merge request veloren/veloren!1102
This commit is contained in:
Songtronix 2020-06-30 12:44:53 +00:00
commit 97a446f2f9
3 changed files with 154 additions and 64 deletions

1
Cargo.lock generated
View File

@ -4590,6 +4590,7 @@ dependencies = [
name = "veloren-voxygen-anim" name = "veloren-voxygen-anim"
version = "0.6.0" version = "0.6.0"
dependencies = [ dependencies = [
"find_folder",
"lazy_static", "lazy_static",
"libloading 0.6.2", "libloading 0.6.2",
"notify", "notify",

View File

@ -11,7 +11,7 @@ name = "voxygen_anim"
# crate-type = ["lib", "cdylib"] # crate-type = ["lib", "cdylib"]
[features] [features]
use-dyn-lib = ["libloading", "notify", "lazy_static", "tracing"] use-dyn-lib = ["libloading", "notify", "lazy_static", "tracing", "find_folder"]
be-dyn-lib = [] be-dyn-lib = []
default = ["be-dyn-lib"] default = ["be-dyn-lib"]
@ -23,3 +23,4 @@ libloading = { version = "0.6.2", optional = true }
notify = { version = "5.0.0-pre.2", optional = true } notify = { version = "5.0.0-pre.2", optional = true }
lazy_static = { version = "1.4.0", optional = true } lazy_static = { version = "1.4.0", optional = true }
tracing = { version = "0.1", optional = true } tracing = { version = "0.1", optional = true }
find_folder = { version = "0.3.0", optional = true}

View File

@ -7,46 +7,133 @@ use std::{
thread, thread,
time::Duration, time::Duration,
}; };
use tracing::{error, warn};
use find_folder::Search;
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";
#[cfg(not(target_os = "windows"))]
const COMPILED_FILE: &str = "libvoxygen_anim.so";
#[cfg(not(target_os = "windows"))]
const ACTIVE_FILE: &str = "libvoxygen_anim_active.so";
// This option is required as `hotreload()` moves the `LoadedLib`.
lazy_static! { lazy_static! {
pub static ref LIB: Mutex<Option<LoadedLib>> = Mutex::new(Some(LoadedLib::compile_load())); pub static ref LIB: Mutex<Option<LoadedLib>> = Mutex::new(Some(LoadedLib::compile_load()));
} }
/// 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`.
pub struct LoadedLib { pub struct LoadedLib {
/// Loaded library.
pub lib: Library, pub lib: Library,
/// Path to the library.
pub lib_path: PathBuf,
} }
impl LoadedLib { impl LoadedLib {
/// 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!
fn compile_load() -> Self { fn compile_load() -> Self {
// Compile #[cfg(target_os = "macos")]
compile(); error!("The hot reloading feature does not work on macos.");
#[cfg(target_os = "windows")] // Compile
copy(); if !compile() {
panic!("Animation compile failed.");
} else {
info!("Animation compile succeeded.");
}
copy(&LoadedLib::determine_path());
Self::load() Self::load()
} }
/// 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.
fn load() -> Self { fn load() -> Self {
#[cfg(target_os = "windows")] let lib_path = LoadedLib::determine_path();
let lib = Library::new("../target/debug/voxygen_anim_active.dll").expect(
"To use hot animation reloading you need to uncomment a line in \ // Try to load the library.
`voxygen/src/anim/Cargo.toml`.", let lib = match Library::new(lib_path.clone()) {
); Ok(lib) => lib,
#[cfg(not(target_os = "windows"))] Err(e) => panic!(
let lib = Library::new("../target/debug/libvoxygen_anim.so").expect( "Tried to load dynamic library from {:?}, but it could not be found. The first \
"To use hot animation reloading you need to uncomment a line in \ reason might be that you need to uncomment a line in \
`voxygen/src/anim/Cargo.toml`.", `voxygen/src/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();
// 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 {
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.",
); );
Self { lib } debug!(?dir, "Found the debug build directory.");
dir
},
Err(e) => {
panic!(
"Could not determine the path of the current executable, this is needed to \
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
} }
} }
// Starts up watcher /// Initialise a watcher.
///
/// The assumption is that this is run from the voxygen crate's root directory
/// as it will watch the relative path `src/anim` for any changes to `.rs`
/// files. Upon noticing changes it will wait a moment and then recompile.
pub fn init() { pub fn init() {
// Make sure first compile is done // 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()); drop(LIB.lock());
// TODO: use crossbeam // TODO: use crossbeam
@ -69,13 +156,12 @@ pub fn init() {
modified_paths.insert(path); modified_paths.insert(path);
} }
warn!( info!(
?modified_paths, ?modified_paths,
"Hot reloading animations because these files were modified" "Hot reloading animations because files in `src/anim` modified."
); );
// Reload hotreload();
reload();
} }
}); });
@ -83,8 +169,10 @@ pub fn init() {
std::mem::forget(watcher); std::mem::forget(watcher);
} }
// Recompiles and hotreloads the lib if the source has been changed /// Event function to hotreload the dynamic library
// Note: designed with voxygen dir as working dir, could be made more flexible ///
/// 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>) { fn event_fn(res: notify::Result<notify::Event>, sender: &mpsc::Sender<String>) {
match res { match res {
Ok(event) => match event.kind { Ok(event) => match event.kind {
@ -99,33 +187,34 @@ fn event_fn(res: notify::Result<notify::Event>, sender: &mpsc::Sender<String>) {
}, },
_ => {}, _ => {},
}, },
Err(e) => error!("Animation hotreload watcher error: {:?}", e), Err(e) => error!(?e, "Animation hotreload watcher error."),
} }
} }
fn reload() { /// Hotreload the dynamic library
// Stop if recompile failed ///
if !compile() { /// This will reload the dynamic library by first internally calling compile
return; /// and then reloading the library.
} fn hotreload() {
// Do nothing if recompile failed.
if compile() {
let mut lock = LIB.lock().unwrap(); let mut lock = LIB.lock().unwrap();
// Close lib // Close lib.
lock.take().unwrap().lib.close().unwrap(); let loaded_lib = lock.take().unwrap();
loaded_lib.lib.close().unwrap();
copy(&loaded_lib.lib_path);
// Rename lib file on windows // Open new lib.
// Called after closing lib so file will be unlocked
#[cfg(target_os = "windows")]
copy();
// Open new lib
*lock = Some(LoadedLib::load()); *lock = Some(LoadedLib::load());
warn!("Updated animations"); info!("Updated animations.");
}
} }
// Returns false if compile failed /// Recompile the anim package
///
/// Returns `false` if the compile failed.
fn compile() -> bool { fn compile() -> bool {
let output = Command::new("cargo") let output = Command::new("cargo")
.stderr(Stdio::inherit()) .stderr(Stdio::inherit())
@ -136,23 +225,22 @@ fn compile() -> bool {
.output() .output()
.unwrap(); .unwrap();
// If compile failed output.status.success()
if !output.status.success() {
error!("Failed to compile anim crate");
false
} else {
warn!("Animation recompile success!!");
true
}
} }
// Copy lib file if on windows since loading the lib locks the file blocking /// Copy the lib file, so we have an `_active` copy.
// future compilation ///
#[cfg(target_os = "windows")] /// We do this for all OS's although it is only strictly necessary for windows.
fn copy() { /// The reason we do this is to make the code easier to understand and debug.
std::fs::copy( fn copy(lib_path: &PathBuf) {
"../target/debug/voxygen_anim.dll", // Use the platform specific names.
"../target/debug/voxygen_anim_active.dll", let lib_compiled_path = lib_path.with_file_name(COMPILED_FILE);
) let lib_output_path = lib_path.with_file_name(ACTIVE_FILE);
.expect("Failed to rename animations dll");
// Get the path to where the lib was compiled to.
debug!(?lib_compiled_path, ?lib_output_path, "Moving.");
// 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.");
} }