#![warn(missing_docs)]
mod assembly;
#[macro_use]
mod macros;
#[macro_use]
mod garbage_collector;
mod marshal;
mod reflection;
mod struct_ref;
use failure::Error;
use garbage_collector::GarbageCollector;
use memory::gc::{self, GcRuntime};
use notify::{DebouncedEvent, RecommendedWatcher, RecursiveMode, Watcher};
use rustc_hash::FxHashMap;
use std::{
cell::RefCell,
collections::HashMap,
ffi, io, mem,
path::{Path, PathBuf},
ptr::NonNull,
rc::Rc,
string::ToString,
sync::{
mpsc::{channel, Receiver},
Arc,
},
time::Duration,
};
pub use crate::{
assembly::Assembly,
garbage_collector::UnsafeTypeInfo,
marshal::Marshal,
reflection::{ArgumentReflection, ReturnTypeReflection},
struct_ref::StructRef,
};
pub use abi::IntoFunctionDefinition;
pub struct RuntimeOptions {
pub library_path: PathBuf,
pub delay: Duration,
pub user_functions: Vec<(abi::FunctionDefinition, abi::FunctionDefinitionStorage)>,
}
pub struct RuntimeBuilder {
options: RuntimeOptions,
}
impl RuntimeBuilder {
pub fn new<P: Into<PathBuf>>(library_path: P) -> Self {
Self {
options: RuntimeOptions {
library_path: library_path.into(),
delay: Duration::from_millis(10),
user_functions: Default::default(),
},
}
}
pub fn set_delay(mut self, delay: Duration) -> Self {
self.options.delay = delay;
self
}
pub fn insert_fn<S: AsRef<str>, F: abi::IntoFunctionDefinition>(
mut self,
name: S,
func: F,
) -> Self {
self.options.user_functions.push(func.into(name));
self
}
pub fn spawn(self) -> Result<Rc<RefCell<Runtime>>, Error> {
Runtime::new(self.options).map(|runtime| Rc::new(RefCell::new(runtime)))
}
}
type DependencyCounter = usize;
type Dependency<T> = (T, DependencyCounter);
type DependencyMap<T> = FxHashMap<String, Dependency<T>>;
#[derive(Default)]
pub struct DispatchTable {
functions: FxHashMap<String, abi::FunctionDefinition>,
fn_dependencies: FxHashMap<String, DependencyMap<abi::FunctionPrototype>>,
}
impl DispatchTable {
pub fn get_fn(&self, fn_path: &str) -> Option<&abi::FunctionDefinition> {
self.functions.get(fn_path)
}
pub fn insert_fn<S: ToString>(
&mut self,
fn_path: S,
fn_info: abi::FunctionDefinition,
) -> Option<abi::FunctionDefinition> {
self.functions.insert(fn_path.to_string(), fn_info)
}
pub fn remove_fn<S: AsRef<str>>(&mut self, fn_path: S) -> Option<abi::FunctionDefinition> {
self.functions.remove(fn_path.as_ref())
}
pub fn add_fn_dependency<S: ToString, T: ToString>(
&mut self,
assembly_path: S,
fn_path: T,
fn_prototype: abi::FunctionPrototype,
) {
let dependencies = self
.fn_dependencies
.entry(assembly_path.to_string())
.or_default();
let (_, counter) = dependencies
.entry(fn_path.to_string())
.or_insert((fn_prototype, 0));
*counter += 1;
}
pub fn remove_fn_dependency<S: AsRef<str>, T: AsRef<str>>(
&mut self,
assembly_path: S,
fn_path: T,
) {
if let Some(dependencies) = self.fn_dependencies.get_mut(assembly_path.as_ref()) {
if let Some((key, (fn_sig, counter))) = dependencies.remove_entry(fn_path.as_ref()) {
if counter > 1 {
dependencies.insert(key, (fn_sig, counter - 1));
}
}
}
}
}
pub struct Runtime {
assemblies: HashMap<PathBuf, Assembly>,
dispatch_table: DispatchTable,
watcher: RecommendedWatcher,
watcher_rx: Receiver<DebouncedEvent>,
gc: Arc<GarbageCollector>,
_user_functions: Vec<abi::FunctionDefinitionStorage>,
}
unsafe fn get_allocator(alloc_handle: *mut ffi::c_void) -> Arc<GarbageCollector> {
Arc::from_raw(alloc_handle as *const GarbageCollector)
}
extern "C" fn new(
type_info: *const abi::TypeInfo,
alloc_handle: *mut ffi::c_void,
) -> *const *mut ffi::c_void {
let allocator = unsafe { get_allocator(alloc_handle) };
let type_info = UnsafeTypeInfo::new(unsafe { NonNull::new_unchecked(type_info as *mut _) });
let handle = allocator.alloc(type_info);
mem::forget(allocator);
handle.into()
}
impl Runtime {
pub fn new(mut options: RuntimeOptions) -> Result<Runtime, Error> {
let (tx, rx) = channel();
let mut dispatch_table = DispatchTable::default();
options.user_functions.push(IntoFunctionDefinition::into(
new as extern "C" fn(*const abi::TypeInfo, *mut ffi::c_void) -> *const *mut ffi::c_void,
"new",
));
let mut storages = Vec::with_capacity(options.user_functions.len());
for (info, storage) in options.user_functions.into_iter() {
dispatch_table.insert_fn(info.prototype.name().to_string(), info);
storages.push(storage)
}
let watcher: RecommendedWatcher = Watcher::new(tx, options.delay)?;
let mut runtime = Runtime {
assemblies: HashMap::new(),
dispatch_table,
watcher,
watcher_rx: rx,
gc: Arc::new(self::garbage_collector::GarbageCollector::default()),
_user_functions: storages,
};
runtime.add_assembly(&options.library_path)?;
Ok(runtime)
}
fn add_assembly(&mut self, library_path: &Path) -> Result<(), Error> {
let library_path = library_path.canonicalize()?;
if self.assemblies.contains_key(&library_path) {
return Err(io::Error::new(
io::ErrorKind::AlreadyExists,
"An assembly with the same name already exists.",
)
.into());
}
let mut assembly = Assembly::load(&library_path, self.gc.clone(), &self.dispatch_table)?;
for dependency in assembly.info().dependencies() {
self.add_assembly(Path::new(dependency))?;
}
assembly.link(&mut self.dispatch_table);
self.watcher
.watch(library_path.parent().unwrap(), RecursiveMode::NonRecursive)?;
self.assemblies.insert(library_path, assembly);
Ok(())
}
pub fn get_function_definition(&self, function_name: &str) -> Option<&abi::FunctionDefinition> {
self.dispatch_table.get_fn(function_name)
}
pub fn update(&mut self) -> bool {
while let Ok(event) = self.watcher_rx.try_recv() {
use notify::DebouncedEvent::*;
match event {
Write(ref path) | Rename(_, ref path) | Create(ref path) => {
if let Some(assembly) = self.assemblies.get_mut(path) {
if let Err(e) = assembly.swap(path, &mut self.dispatch_table) {
println!(
"An error occured while reloading assembly '{}': {:?}",
path.to_string_lossy(),
e
);
} else {
println!(
"Succesfully reloaded assembly: '{}'",
path.to_string_lossy()
);
return true;
}
}
}
_ => {}
}
}
false
}
pub fn gc(&self) -> &dyn GcRuntime<UnsafeTypeInfo> {
self.gc.as_ref()
}
pub fn gc_collect(&self) -> bool {
self.gc.collect()
}
pub fn gc_stats(&self) -> gc::Stats {
self.gc.stats()
}
}
pub trait RetryResultExt: Sized {
type Output;
fn retry(self) -> Self;
fn wait(self) -> Self::Output;
}
invoke_fn_impl! {
fn invoke_fn0() -> InvokeErr0;
fn invoke_fn1(a: A) -> InvokeErr1;
fn invoke_fn2(a: A, b: B) -> InvokeErr2;
fn invoke_fn3(a: A, b: B, c: C) -> InvokeErr3;
fn invoke_fn4(a: A, b: B, c: C, d: D) -> InvokeErr4;
fn invoke_fn5(a: A, b: B, c: C, d: D, e: E) -> InvokeErr5;
fn invoke_fn6(a: A, b: B, c: C, d: D, e: E, f: F) -> InvokeErr6;
fn invoke_fn7(a: A, b: B, c: C, d: D, e: E, f: F, g: G) -> InvokeErr7;
fn invoke_fn8(a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H) -> InvokeErr8;
fn invoke_fn9(a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H, i: I) -> InvokeErr9;
fn invoke_fn10(a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H, i: I, j: J) -> InvokeErr10;
fn invoke_fn11(a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H, i: I, j: J, k: K) -> InvokeErr11;
fn invoke_fn12(a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H, i: I, j: J, k: K, l: L) -> InvokeErr12;
fn invoke_fn13(a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H, i: I, j: J, k: K, l: L, m: M) -> InvokeErr13;
fn invoke_fn14(a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H, i: I, j: J, k: K, l: L, m: M, n: N) -> InvokeErr14;
fn invoke_fn15(a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H, i: I, j: J, k: K, l: L, m: M, n: N, o: O) -> InvokeErr15;
}