fyrox_impl/plugin/
dylib.rs1use crate::core::notify::RecommendedWatcher;
24use crate::{
25 core::{
26 log::Log,
27 notify::{self, EventKind, RecursiveMode, Watcher},
28 },
29 plugin::Plugin,
30};
31use std::{
32 ffi::OsStr,
33 fs::File,
34 io::Read,
35 path::{Path, PathBuf},
36 sync::{
37 atomic::{self, AtomicBool},
38 Arc,
39 },
40};
41
42use crate::plugin::DynamicPlugin;
43
44pub struct DyLibHandle {
48 pub(super) plugin: Box<dyn Plugin>,
49 #[allow(dead_code)]
52 #[cfg(any(unix, windows))]
53 lib: libloading::Library,
54}
55
56#[cfg(any(unix, windows))]
57type PluginEntryPoint = fn() -> Box<dyn Plugin>;
58
59impl DyLibHandle {
60 pub fn load<P>(#[allow(unused_variables)] path: P) -> Result<Self, String>
62 where
63 P: AsRef<OsStr>,
64 {
65 #[cfg(any(unix, windows))]
66 unsafe {
67 let lib = libloading::Library::new(path).map_err(|e| e.to_string())?;
68
69 let entry = lib
70 .get::<PluginEntryPoint>("fyrox_plugin".as_bytes())
71 .map_err(|e| e.to_string())?;
72
73 Ok(Self {
74 plugin: entry(),
75 lib,
76 })
77 }
78
79 #[cfg(not(any(unix, windows)))]
80 {
81 panic!("Unsupported platform!")
82 }
83 }
84
85 pub fn plugin(&self) -> &dyn Plugin {
87 &*self.plugin
88 }
89
90 pub(crate) fn plugin_mut(&mut self) -> &mut dyn Plugin {
92 &mut *self.plugin
93 }
94}
95
96pub struct DyLibDynamicPlugin {
98 state: PluginState,
100 lib_path: PathBuf,
102 source_lib_path: PathBuf,
106 _watcher: Option<RecommendedWatcher>,
110 need_reload: Arc<AtomicBool>,
113}
114
115impl DyLibDynamicPlugin {
116 pub fn new<P>(
128 path: P,
129 reload_when_changed: bool,
130 use_relative_paths: bool,
131 ) -> Result<Self, String>
132 where
133 P: AsRef<Path> + 'static,
134 {
135 let source_lib_path = if use_relative_paths {
136 let exe_folder = std::env::current_exe()
137 .map_err(|e| e.to_string())?
138 .parent()
139 .map(|p| p.to_path_buf())
140 .unwrap_or_default();
141
142 exe_folder.join(path.as_ref())
143 } else {
144 path.as_ref().to_path_buf()
145 };
146
147 let plugin = if reload_when_changed {
148 let mut suffix = std::env::current_exe()
154 .ok()
155 .and_then(|p| p.file_stem().map(|s| s.to_owned()))
156 .unwrap_or_default();
157 suffix.push(".module");
158 let lib_path = source_lib_path.with_extension(suffix);
159 try_copy_library(&source_lib_path, &lib_path)?;
160
161 let need_reload = Arc::new(AtomicBool::new(false));
162 let need_reload_clone = need_reload.clone();
163 let source_lib_path_clone = source_lib_path.clone();
164
165 let mut watcher =
166 notify::recommended_watcher(move |event: notify::Result<notify::Event>| {
167 if let Ok(event) = event {
168 if let EventKind::Modify(_) | EventKind::Create(_) = event.kind {
169 need_reload_clone.store(true, atomic::Ordering::Relaxed);
170
171 Log::warn(format!(
172 "Plugin {} was changed. Performing hot reloading...",
173 source_lib_path_clone.display()
174 ))
175 }
176 }
177 })
178 .map_err(|e| e.to_string())?;
179
180 watcher
181 .watch(&source_lib_path, RecursiveMode::NonRecursive)
182 .map_err(|e| e.to_string())?;
183
184 Log::info(format!(
185 "Watching for changes in plugin {:?}...",
186 source_lib_path
187 ));
188
189 DyLibDynamicPlugin {
190 state: PluginState::Loaded(DyLibHandle::load(lib_path.as_os_str())?),
191 lib_path,
192 source_lib_path: source_lib_path.clone(),
193 _watcher: Some(watcher),
194 need_reload,
195 }
196 } else {
197 DyLibDynamicPlugin {
198 state: PluginState::Loaded(DyLibHandle::load(source_lib_path.as_os_str())?),
199 lib_path: source_lib_path.clone(),
200 source_lib_path: source_lib_path.clone(),
201 _watcher: None,
202 need_reload: Default::default(),
203 }
204 };
205 Ok(plugin)
206 }
207}
208
209impl DynamicPlugin for DyLibDynamicPlugin {
210 fn as_loaded_ref(&self) -> &dyn Plugin {
211 &*self.state.as_loaded_ref().plugin
212 }
213
214 fn as_loaded_mut(&mut self) -> &mut dyn Plugin {
215 &mut *self.state.as_loaded_mut().plugin
216 }
217
218 fn is_reload_needed_now(&self) -> bool {
219 self.need_reload.load(atomic::Ordering::Relaxed)
220 }
221
222 fn display_name(&self) -> String {
223 format!("{:?}", self.source_lib_path)
224 }
225
226 fn is_loaded(&self) -> bool {
227 matches!(self.state, PluginState::Loaded { .. })
228 }
229
230 fn reload(
231 &mut self,
232 fill_and_register: &mut dyn FnMut(&mut dyn Plugin) -> Result<(), String>,
233 ) -> Result<(), String> {
234 let PluginState::Loaded(_) = &mut self.state else {
236 return Err("cannot unload non-loaded plugin".to_string());
237 };
238
239 self.state = PluginState::Unloaded;
240
241 Log::info(format!(
242 "Plugin {:?} was unloaded successfully!",
243 self.source_lib_path
244 ));
245
246 try_copy_library(&self.source_lib_path, &self.lib_path)?;
248
249 Log::info(format!(
250 "{:?} plugin's module {} was successfully cloned to {}.",
251 self.source_lib_path,
252 self.source_lib_path.display(),
253 self.lib_path.display()
254 ));
255
256 let mut dynamic = DyLibHandle::load(&self.lib_path)?;
257
258 fill_and_register(dynamic.plugin_mut())?;
259
260 self.state = PluginState::Loaded(dynamic);
261
262 self.need_reload.store(false, atomic::Ordering::Relaxed);
263
264 Log::info(format!(
265 "Plugin {:?} was reloaded successfully!",
266 self.source_lib_path
267 ));
268
269 Ok(())
270 }
271}
272
273enum PluginState {
275 Unloaded,
277 Loaded(DyLibHandle),
279}
280
281impl PluginState {
282 pub fn as_loaded_ref(&self) -> &DyLibHandle {
284 match self {
285 PluginState::Unloaded => {
286 panic!("Cannot obtain a reference to the plugin, because it is unloaded!")
287 }
288 PluginState::Loaded(dynamic) => dynamic,
289 }
290 }
291
292 pub fn as_loaded_mut(&mut self) -> &mut DyLibHandle {
294 match self {
295 PluginState::Unloaded => {
296 panic!("Cannot obtain a reference to the plugin, because it is unloaded!")
297 }
298 PluginState::Loaded(dynamic) => dynamic,
299 }
300 }
301}
302
303fn try_copy_library(source_lib_path: &Path, lib_path: &Path) -> Result<(), String> {
304 if let Err(err) = std::fs::copy(source_lib_path, lib_path) {
305 let mut src_lib_file = File::open(source_lib_path).map_err(|e| e.to_string())?;
309 let mut src_lib_file_content = Vec::new();
310 src_lib_file
311 .read_to_end(&mut src_lib_file_content)
312 .map_err(|e| e.to_string())?;
313 let mut lib_file = File::open(lib_path).map_err(|e| e.to_string())?;
314 let mut lib_file_content = Vec::new();
315 lib_file
316 .read_to_end(&mut lib_file_content)
317 .map_err(|e| e.to_string())?;
318 if src_lib_file_content != lib_file_content {
319 return Err(format!(
320 "Unable to clone the library {} to {}. It is required, because source \
321 library has {} size, but loaded has {} size and the content does not match. \
322 Exact reason: {:?}",
323 source_lib_path.display(),
324 lib_path.display(),
325 src_lib_file_content.len(),
326 lib_file_content.len(),
327 err
328 ));
329 }
330 }
331
332 Ok(())
333}