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 {source_lib_path:?}..."
186 ));
187
188 DyLibDynamicPlugin {
189 state: PluginState::Loaded(DyLibHandle::load(lib_path.as_os_str())?),
190 lib_path,
191 source_lib_path: source_lib_path.clone(),
192 _watcher: Some(watcher),
193 need_reload,
194 }
195 } else {
196 DyLibDynamicPlugin {
197 state: PluginState::Loaded(DyLibHandle::load(source_lib_path.as_os_str())?),
198 lib_path: source_lib_path.clone(),
199 source_lib_path: source_lib_path.clone(),
200 _watcher: None,
201 need_reload: Default::default(),
202 }
203 };
204 Ok(plugin)
205 }
206}
207
208impl DynamicPlugin for DyLibDynamicPlugin {
209 fn as_loaded_ref(&self) -> &dyn Plugin {
210 &*self.state.as_loaded_ref().plugin
211 }
212
213 fn as_loaded_mut(&mut self) -> &mut dyn Plugin {
214 &mut *self.state.as_loaded_mut().plugin
215 }
216
217 fn is_reload_needed_now(&self) -> bool {
218 self.need_reload.load(atomic::Ordering::Relaxed)
219 }
220
221 fn display_name(&self) -> String {
222 format!("{:?}", self.source_lib_path)
223 }
224
225 fn is_loaded(&self) -> bool {
226 matches!(self.state, PluginState::Loaded { .. })
227 }
228
229 fn reload(
230 &mut self,
231 fill_and_register: &mut dyn FnMut(&mut dyn Plugin) -> Result<(), String>,
232 ) -> Result<(), String> {
233 let PluginState::Loaded(_) = &mut self.state else {
235 return Err("cannot unload non-loaded plugin".to_string());
236 };
237
238 self.state = PluginState::Unloaded;
239
240 Log::info(format!(
241 "Plugin {:?} was unloaded successfully!",
242 self.source_lib_path
243 ));
244
245 try_copy_library(&self.source_lib_path, &self.lib_path)?;
247
248 Log::info(format!(
249 "{:?} plugin's module {} was successfully cloned to {}.",
250 self.source_lib_path,
251 self.source_lib_path.display(),
252 self.lib_path.display()
253 ));
254
255 let mut dynamic = DyLibHandle::load(&self.lib_path)?;
256
257 fill_and_register(dynamic.plugin_mut())?;
258
259 self.state = PluginState::Loaded(dynamic);
260
261 self.need_reload.store(false, atomic::Ordering::Relaxed);
262
263 Log::info(format!(
264 "Plugin {:?} was reloaded successfully!",
265 self.source_lib_path
266 ));
267
268 Ok(())
269 }
270}
271
272enum PluginState {
274 Unloaded,
276 Loaded(DyLibHandle),
278}
279
280impl PluginState {
281 pub fn as_loaded_ref(&self) -> &DyLibHandle {
283 match self {
284 PluginState::Unloaded => {
285 panic!("Cannot obtain a reference to the plugin, because it is unloaded!")
286 }
287 PluginState::Loaded(dynamic) => dynamic,
288 }
289 }
290
291 pub fn as_loaded_mut(&mut self) -> &mut DyLibHandle {
293 match self {
294 PluginState::Unloaded => {
295 panic!("Cannot obtain a reference to the plugin, because it is unloaded!")
296 }
297 PluginState::Loaded(dynamic) => dynamic,
298 }
299 }
300}
301
302fn try_copy_library(source_lib_path: &Path, lib_path: &Path) -> Result<(), String> {
303 if let Err(err) = std::fs::copy(source_lib_path, lib_path) {
304 let mut src_lib_file = File::open(source_lib_path).map_err(|e| e.to_string())?;
308 let mut src_lib_file_content = Vec::new();
309 src_lib_file
310 .read_to_end(&mut src_lib_file_content)
311 .map_err(|e| e.to_string())?;
312 let mut lib_file = File::open(lib_path).map_err(|e| e.to_string())?;
313 let mut lib_file_content = Vec::new();
314 lib_file
315 .read_to_end(&mut lib_file_content)
316 .map_err(|e| e.to_string())?;
317 if src_lib_file_content != lib_file_content {
318 return Err(format!(
319 "Unable to clone the library {} to {}. It is required, because source \
320 library has {} size, but loaded has {} size and the content does not match. \
321 Exact reason: {:?}",
322 source_lib_path.display(),
323 lib_path.display(),
324 src_lib_file_content.len(),
325 lib_file_content.len(),
326 err
327 ));
328 }
329 }
330
331 Ok(())
332}