rhai/src/module/resolvers/file.rs

341 lines
10 KiB
Rust
Raw Normal View History

2021-11-27 16:20:05 +01:00
#![cfg(not(feature = "no_std"))]
2022-01-12 01:12:28 +01:00
#![cfg(not(target_family = "wasm"))]
2021-11-27 16:20:05 +01:00
2021-11-13 15:36:23 +01:00
use crate::func::native::shared_write_lock;
2021-12-25 16:49:14 +01:00
use crate::{
2021-12-27 06:30:44 +01:00
Engine, Identifier, Module, ModuleResolver, Position, RhaiResultOf, Scope, Shared, ERR,
2021-12-25 16:49:14 +01:00
};
2021-11-08 04:35:46 +01:00
2021-04-17 09:15:54 +02:00
use std::{
2021-03-23 05:13:53 +01:00
collections::BTreeMap,
2020-12-26 06:05:57 +01:00
io::Error as IoError,
path::{Path, PathBuf},
2020-11-08 16:00:03 +01:00
};
pub const RHAI_SCRIPT_EXTENSION: &str = "rhai";
/// A [module][Module] resolution service that loads [module][Module] script files from the file system.
///
/// ## Caching
///
/// Resolved [Modules][Module] are cached internally so script files are not reloaded and recompiled
/// for subsequent requests.
///
/// Use [`clear_cache`][FileModuleResolver::clear_cache] or
/// [`clear_cache_for_path`][FileModuleResolver::clear_cache_for_path] to clear the internal cache.
///
/// ## Namespace
///
/// When a function within a script file module is called, all functions defined within the same
/// script are available, evan `private` ones. In other words, functions defined in a module script
/// can always cross-call each other.
///
2020-10-27 04:30:38 +01:00
/// # Example
///
/// ```
/// use rhai::Engine;
/// use rhai::module_resolvers::FileModuleResolver;
///
/// // Create a new 'FileModuleResolver' loading scripts from the 'scripts' subdirectory
/// // with file extension '.x'.
/// let resolver = FileModuleResolver::new_with_path_and_extension("./scripts", "x");
///
/// let mut engine = Engine::new();
///
2020-12-26 06:05:57 +01:00
/// engine.set_module_resolver(resolver);
/// ```
#[derive(Debug)]
pub struct FileModuleResolver {
base_path: Option<PathBuf>,
extension: Identifier,
cache_enabled: bool,
2020-11-27 16:37:59 +01:00
#[cfg(not(feature = "sync"))]
2021-04-17 09:15:54 +02:00
cache: std::cell::RefCell<BTreeMap<PathBuf, Shared<Module>>>,
2020-11-27 16:37:59 +01:00
#[cfg(feature = "sync")]
2021-04-17 09:15:54 +02:00
cache: std::sync::RwLock<BTreeMap<PathBuf, Shared<Module>>>,
}
impl FileModuleResolver {
/// Create a new [`FileModuleResolver`] with the current directory as base path.
///
/// The default extension is `.rhai`.
///
/// # Example
///
/// ```
/// use rhai::Engine;
/// use rhai::module_resolvers::FileModuleResolver;
///
/// // Create a new 'FileModuleResolver' loading scripts from the current directory
/// // with file extension '.rhai' (the default).
/// let resolver = FileModuleResolver::new();
///
/// let mut engine = Engine::new();
/// engine.set_module_resolver(resolver);
/// ```
#[inline(always)]
2021-06-12 16:47:43 +02:00
#[must_use]
pub fn new() -> Self {
Self::new_with_extension(RHAI_SCRIPT_EXTENSION)
}
2020-11-20 09:52:28 +01:00
/// Create a new [`FileModuleResolver`] with a specific base path.
///
2021-01-02 16:30:10 +01:00
/// The default extension is `.rhai`.
///
2020-10-27 04:30:38 +01:00
/// # Example
///
/// ```
/// use rhai::Engine;
/// use rhai::module_resolvers::FileModuleResolver;
///
/// // Create a new 'FileModuleResolver' loading scripts from the 'scripts' subdirectory
/// // with file extension '.rhai' (the default).
/// let resolver = FileModuleResolver::new_with_path("./scripts");
///
/// let mut engine = Engine::new();
2020-12-26 06:05:57 +01:00
/// engine.set_module_resolver(resolver);
/// ```
2020-10-08 16:25:50 +02:00
#[inline(always)]
2021-06-12 16:47:43 +02:00
#[must_use]
2020-12-26 06:05:57 +01:00
pub fn new_with_path(path: impl Into<PathBuf>) -> Self {
Self::new_with_path_and_extension(path, RHAI_SCRIPT_EXTENSION)
}
/// Create a new [`FileModuleResolver`] with a file extension.
///
2020-10-27 04:30:38 +01:00
/// # Example
///
/// ```
/// use rhai::Engine;
/// use rhai::module_resolvers::FileModuleResolver;
///
/// // Create a new 'FileModuleResolver' loading scripts with file extension '.rhai' (the default).
/// let resolver = FileModuleResolver::new_with_extension("rhai");
///
/// let mut engine = Engine::new();
2020-12-26 06:05:57 +01:00
/// engine.set_module_resolver(resolver);
/// ```
2020-10-08 16:25:50 +02:00
#[inline(always)]
2021-06-12 16:47:43 +02:00
#[must_use]
pub fn new_with_extension(extension: impl Into<Identifier>) -> Self {
Self {
base_path: None,
extension: extension.into(),
cache_enabled: true,
2021-11-27 07:24:06 +01:00
cache: BTreeMap::new().into(),
}
}
/// Create a new [`FileModuleResolver`] with a specific base path and file extension.
2021-01-02 16:30:10 +01:00
///
2020-10-27 04:30:38 +01:00
/// # Example
///
/// ```
/// use rhai::Engine;
/// use rhai::module_resolvers::FileModuleResolver;
///
/// // Create a new 'FileModuleResolver' loading scripts from the 'scripts' subdirectory
/// // with file extension '.x'.
/// let resolver = FileModuleResolver::new_with_path_and_extension("./scripts", "x");
///
/// let mut engine = Engine::new();
2020-12-26 06:05:57 +01:00
/// engine.set_module_resolver(resolver);
/// ```
2020-10-08 16:25:50 +02:00
#[inline(always)]
2021-06-12 16:47:43 +02:00
#[must_use]
pub fn new_with_path_and_extension(
path: impl Into<PathBuf>,
extension: impl Into<Identifier>,
) -> Self {
Self {
base_path: Some(path.into()),
extension: extension.into(),
cache_enabled: true,
2021-11-27 07:24:06 +01:00
cache: BTreeMap::new().into(),
}
}
2020-12-26 06:05:57 +01:00
/// Get the base path for script files.
#[inline(always)]
2021-06-12 16:47:43 +02:00
#[must_use]
pub fn base_path(&self) -> Option<&Path> {
self.base_path.as_ref().map(PathBuf::as_ref)
2020-12-26 06:05:57 +01:00
}
/// Set the base path for script files.
#[inline(always)]
2020-12-28 02:49:54 +01:00
pub fn set_base_path(&mut self, path: impl Into<PathBuf>) -> &mut Self {
self.base_path = Some(path.into());
2020-12-26 06:05:57 +01:00
self
}
/// Get the script file extension.
#[inline(always)]
2021-06-12 16:47:43 +02:00
#[must_use]
2020-12-26 06:05:57 +01:00
pub fn extension(&self) -> &str {
&self.extension
}
/// Set the script file extension.
#[inline(always)]
pub fn set_extension(&mut self, extension: impl Into<Identifier>) -> &mut Self {
2020-12-26 06:05:57 +01:00
self.extension = extension.into();
self
}
/// Enable/disable the cache.
#[inline(always)]
pub fn enable_cache(&mut self, enable: bool) -> &mut Self {
self.cache_enabled = enable;
self
}
/// Is the cache enabled?
#[inline(always)]
2021-06-12 16:47:43 +02:00
#[must_use]
pub fn is_cache_enabled(&self) -> bool {
self.cache_enabled
}
/// Is a particular path cached?
#[inline]
2021-06-12 16:47:43 +02:00
#[must_use]
2021-11-27 16:04:45 +01:00
pub fn is_cached(&self, path: impl AsRef<str>, source_path: Option<&str>) -> bool {
if !self.cache_enabled {
return false;
}
2021-11-27 16:04:45 +01:00
let file_path = self.get_file_path(path.as_ref(), source_path);
2021-11-08 04:35:46 +01:00
shared_write_lock(&self.cache).contains_key(&file_path)
}
2020-12-26 06:05:57 +01:00
/// Empty the internal cache.
#[inline]
2021-06-12 16:47:43 +02:00
pub fn clear_cache(&mut self) -> &mut Self {
2021-11-08 04:35:46 +01:00
shared_write_lock(&self.cache).clear();
2021-06-12 16:47:43 +02:00
self
2020-12-26 06:05:57 +01:00
}
2021-01-02 16:30:10 +01:00
/// Remove the specified path from internal cache.
///
/// The next time this path is resolved, the script file will be loaded once again.
#[inline]
2021-06-12 16:47:43 +02:00
#[must_use]
pub fn clear_cache_for_path(
&mut self,
2021-11-27 16:04:45 +01:00
path: impl AsRef<str>,
source_path: Option<impl AsRef<str>>,
) -> Option<Shared<Module>> {
2022-01-01 10:38:32 +01:00
let file_path = self.get_file_path(path.as_ref(), source_path.as_ref().map(<_>::as_ref));
2021-11-08 04:35:46 +01:00
shared_write_lock(&self.cache)
.remove_entry(&file_path)
2021-11-08 04:35:46 +01:00
.map(|(_, v)| v)
2020-12-26 06:05:57 +01:00
}
/// Construct a full file path.
2021-06-12 16:47:43 +02:00
#[must_use]
fn get_file_path(&self, path: &str, source_path: Option<&str>) -> PathBuf {
let path = Path::new(path);
let mut file_path;
if path.is_relative() {
file_path = self
.base_path
.clone()
.or_else(|| source_path.map(|p| p.into()))
.unwrap_or_default();
file_path.push(path);
} else {
file_path = path.into();
}
file_path.set_extension(self.extension.as_str()); // Force extension
file_path
}
}
impl ModuleResolver for FileModuleResolver {
fn resolve(
&self,
engine: &Engine,
source_path: Option<&str>,
path: &str,
pos: Position,
2021-12-25 16:49:14 +01:00
) -> RhaiResultOf<Shared<Module>> {
// Load relative paths from source if there is no base path specified
let source_path =
source_path.and_then(|p| Path::new(p).parent().map(|p| p.to_string_lossy()));
// Construct the script file path
let file_path = self.get_file_path(path, source_path.as_ref().map(|p| p.as_ref()));
// See if it is cached
if self.is_cache_enabled() {
#[cfg(not(feature = "sync"))]
let c = self.cache.borrow();
#[cfg(feature = "sync")]
let c = self.cache.read().unwrap();
2020-11-07 16:33:21 +01:00
if let Some(module) = c.get(&file_path) {
2021-01-03 06:30:01 +01:00
return Ok(module.clone());
2020-11-08 16:00:03 +01:00
}
2021-01-03 06:30:01 +01:00
}
// Load the script file and compile it
let scope = Scope::new();
2021-01-03 06:30:01 +01:00
let mut ast = engine
.compile_file(file_path.clone())
.map_err(|err| match *err {
2021-12-27 05:27:31 +01:00
ERR::ErrorSystem(_, err) if err.is::<IoError>() => {
Box::new(ERR::ErrorModuleNotFound(path.to_string(), pos))
2021-01-03 06:30:01 +01:00
}
2021-12-27 05:27:31 +01:00
_ => Box::new(ERR::ErrorInModule(path.to_string(), err, pos)),
2020-11-08 16:00:03 +01:00
})?;
2020-11-07 16:33:21 +01:00
ast.set_source(path);
2021-01-03 06:30:01 +01:00
// Make a module from the AST
let m: Shared<Module> = Module::eval_ast_as_new(scope, &ast, engine)
2021-12-27 05:27:31 +01:00
.map_err(|err| Box::new(ERR::ErrorInModule(path.to_string(), err, pos)))?
2021-01-03 06:30:01 +01:00
.into();
// Put it into the cache
if self.is_cache_enabled() {
2021-11-08 04:35:46 +01:00
shared_write_lock(&self.cache).insert(file_path, m.clone());
}
2021-01-03 06:30:01 +01:00
Ok(m)
}
2021-01-09 09:52:22 +01:00
/// Resolve an `AST` based on a path string.
///
/// The file system is accessed during each call; the internal cache is by-passed.
2021-01-09 09:52:22 +01:00
fn resolve_ast(
&self,
engine: &Engine,
source_path: Option<&str>,
2021-01-09 09:52:22 +01:00
path: &str,
pos: Position,
2021-12-25 16:49:14 +01:00
) -> Option<RhaiResultOf<crate::AST>> {
2021-01-09 09:52:22 +01:00
// Construct the script file path
let file_path = self.get_file_path(path, source_path);
2021-01-09 09:52:22 +01:00
// Load the script file and compile it
2021-06-29 15:58:05 +02:00
Some(
engine
.compile_file(file_path)
.map(|mut ast| {
ast.set_source(path);
ast
})
.map_err(|err| match *err {
2021-12-27 05:27:31 +01:00
ERR::ErrorSystem(_, err) if err.is::<IoError>() => {
ERR::ErrorModuleNotFound(path.to_string(), pos).into()
2021-06-29 15:58:05 +02:00
}
2021-12-27 05:27:31 +01:00
_ => ERR::ErrorInModule(path.to_string(), err, pos).into(),
2021-06-29 15:58:05 +02:00
}),
)
2021-01-09 09:52:22 +01:00
}
}