rhai_dylib/module_resolvers/
libloading.rs1use super::{locked_read, locked_write};
2use crate::loader::libloading::Libloading;
3use crate::loader::Loader;
4
5#[cfg(target_os = "linux")]
6const DYLIB_EXTENSION: &str = "so";
7#[cfg(target_os = "macos")]
8const DYLIB_EXTENSION: &str = "dylib";
9#[cfg(target_os = "windows")]
10const DYLIB_EXTENSION: &str = "dll";
11
12pub struct DylibModuleResolver {
14 base_path: Option<std::path::PathBuf>,
16 loader: rhai::Locked<Libloading>,
18 cache_enabled: bool,
20 cache: rhai::Locked<std::collections::BTreeMap<std::path::PathBuf, rhai::Shared<rhai::Module>>>,
22}
23
24impl Default for DylibModuleResolver {
25 fn default() -> Self {
26 Self {
27 base_path: None,
28 loader: Libloading::new().into(),
29 cache_enabled: true,
30 cache: rhai::Locked::new(std::collections::BTreeMap::new()),
31 }
32 }
33}
34
35impl DylibModuleResolver {
36 #[must_use]
38 pub fn new() -> Self {
39 Self::default()
40 }
41
42 pub fn enable_cache(&mut self, enable: bool) -> &mut Self {
44 self.cache_enabled = enable;
45 self
46 }
47
48 #[must_use]
50 pub const fn is_cache_enabled(&self) -> bool {
51 self.cache_enabled
52 }
53
54 #[must_use]
70 pub fn with_path(path: impl Into<std::path::PathBuf>) -> Self {
71 Self {
72 base_path: Some(path.into()),
73 ..Default::default()
74 }
75 }
76
77 #[must_use]
79 pub fn get_file_path(
80 &self,
81 path: &str,
82 source_path: Option<&std::path::Path>,
83 ) -> std::path::PathBuf {
84 let path = std::path::Path::new(path);
85
86 let mut file_path;
87
88 if path.is_relative() {
89 file_path = self
90 .base_path
91 .clone()
92 .or_else(|| source_path.map(Into::into))
93 .unwrap_or_default();
94 file_path.push(path);
95 } else {
96 file_path = path.into();
97 }
98
99 file_path.set_extension(DYLIB_EXTENSION);
100
101 file_path
102 }
103
104 #[allow(clippy::needless_pass_by_value)]
106 fn impl_resolve(
107 &self,
108 global: Option<&mut rhai::GlobalRuntimeState>,
109 source: Option<&str>,
110 path: &str,
111 position: rhai::Position,
112 ) -> Result<rhai::Shared<rhai::Module>, Box<rhai::EvalAltResult>> {
113 let source_path = global
115 .as_ref()
116 .and_then(|g| g.source())
117 .or(source)
118 .and_then(|p| std::path::Path::new(p).parent());
119
120 let path = self.get_file_path(path, source_path);
121
122 if !path.exists() {
123 return Err(Box::new(rhai::EvalAltResult::ErrorModuleNotFound(
124 path.to_str()
125 .map_or_else(String::default, std::string::ToString::to_string),
126 position,
127 )));
128 }
129
130 if self.is_cache_enabled() {
131 let module = { locked_read(&self.cache).get(&path).cloned() };
132
133 if let Some(module) = module {
134 Ok(module)
135 } else {
136 let module = locked_write(&self.loader).load(path.as_path())?;
137 locked_write(&self.cache).insert(path, module.clone());
138
139 Ok(module)
140 }
141 } else {
142 locked_write(&self.loader).load(path.as_path())
143 }
144 }
145}
146
147impl rhai::ModuleResolver for DylibModuleResolver {
148 fn resolve(
149 &self,
150 _: &rhai::Engine,
151 source: Option<&str>,
152 path: &str,
153 position: rhai::Position,
154 ) -> Result<rhai::Shared<rhai::Module>, Box<rhai::EvalAltResult>> {
155 self.impl_resolve(None, source, path, position)
156 }
157
158 fn resolve_raw(
159 &self,
160 _: &rhai::Engine,
161 global: &mut rhai::GlobalRuntimeState,
162 _: &mut rhai::Scope,
163 path: &str,
164 position: rhai::Position,
165 ) -> Result<rhai::Shared<rhai::Module>, Box<rhai::EvalAltResult>> {
166 self.impl_resolve(Some(global), None, path, position)
167 }
168
169 fn resolve_ast(
172 &self,
173 _: &rhai::Engine,
174 _: Option<&str>,
175 _: &str,
176 _: rhai::Position,
177 ) -> Option<Result<rhai::AST, Box<rhai::EvalAltResult>>> {
178 None
179 }
180}
181
182#[cfg(test)]
183mod tests {
184 use super::*;
185
186 #[test]
187 fn new() {
188 let mut r = DylibModuleResolver::new();
189 let rp = DylibModuleResolver::with_path("./scripts");
190
191 r.enable_cache(false);
192 assert!(!r.is_cache_enabled());
193 assert!(rp.is_cache_enabled());
194 }
195
196 #[test]
197 fn file_path_resolution() {
198 let r = DylibModuleResolver::new();
199
200 let relative = r.get_file_path("mylib", None);
201
202 #[cfg(target_os = "linux")]
203 assert_eq!(relative, std::path::PathBuf::from("mylib.so"));
204 #[cfg(target_os = "windows")]
205 assert_eq!(relative, std::path::PathBuf::from("mylib.dll"));
206 #[cfg(target_os = "macos")]
207 assert_eq!(relative, std::path::PathBuf::from("mylib.dylib"));
208
209 let source = r.get_file_path("mylib", Some(std::path::Path::new("source")));
210
211 #[cfg(target_os = "linux")]
212 assert_eq!(source, std::path::PathBuf::from("source/mylib.so"));
213 #[cfg(target_os = "windows")]
214 assert_eq!(source, std::path::PathBuf::from("source/mylib.dll"));
215 #[cfg(target_os = "macos")]
216 assert_eq!(source, std::path::PathBuf::from("source/mylib.dylib"));
217 }
218
219 #[test]
220 fn file_path_resolution_with_path() {
221 let rp = DylibModuleResolver::with_path("scripts");
222
223 let relative = rp.get_file_path("mylib", None);
224 #[cfg(target_os = "linux")]
225 assert_eq!(relative, std::path::PathBuf::from("scripts/mylib.so"));
226 #[cfg(target_os = "windows")]
227 assert_eq!(relative, std::path::PathBuf::from("scripts/mylib.dll"));
228 #[cfg(target_os = "macos")]
229 assert_eq!(relative, std::path::PathBuf::from("scripts/mylib.dylib"));
230
231 let absolute = rp.get_file_path("/usr/local/lib/mylib", None);
233 #[cfg(target_os = "linux")]
234 assert_eq!(
235 absolute,
236 std::path::PathBuf::from("/usr/local/lib/mylib.so")
237 );
238 }
239}