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