diff --git a/fyrox-impl/src/engine/mod.rs b/fyrox-impl/src/engine/mod.rs index 6bba68271..44ff4dd6a 100644 --- a/fyrox-impl/src/engine/mod.rs +++ b/fyrox-impl/src/engine/mod.rs @@ -79,6 +79,7 @@ use raw_window_handle::HasRawWindowHandle; use std::{ffi::CString, num::NonZeroU32}; use std::ffi::OsStr; +use std::io::Cursor; use std::{ any::TypeId, collections::{HashSet, VecDeque}, @@ -94,6 +95,7 @@ use std::{ use crate::plugin::dynamic::DynamicPlugin; use crate::plugin::PluginContainer; +use fyrox_core::visitor::Visitor; use winit::{ dpi::{Position, Size}, event_loop::EventLoopWindowTarget, @@ -2298,15 +2300,24 @@ impl Engine { } } + fn register_plugin(&self, plugin: &dyn Plugin) { + *self + .serialization_context + .script_constructors + .context_type_id + .lock() = plugin.type_id(); + plugin.register(PluginRegistrationContext { + serialization_context: &self.serialization_context, + resource_manager: &self.resource_manager, + }); + } + /// Adds a new static plugin. pub fn add_plugin

(&mut self, plugin: P) where P: Plugin + 'static, { - plugin.register(PluginRegistrationContext { - serialization_context: &self.serialization_context, - resource_manager: &self.resource_manager, - }); + self.register_plugin(&plugin); self.plugins.push(PluginContainer::Static(Box::new(plugin))); } @@ -2319,17 +2330,138 @@ impl Engine { where P: AsRef + 'static, { - let plugin = PluginContainer::Dynamic(DynamicPlugin::load(path)?); - - plugin.register(PluginRegistrationContext { - serialization_context: &self.serialization_context, - resource_manager: &self.resource_manager, - }); + let dynamic = DynamicPlugin::load(path)?; + self.register_plugin(dynamic.plugin()); + let plugin = PluginContainer::Dynamic(dynamic); self.plugins.push(plugin); Ok(()) } + + /// Tries to reload all dynamic plugins registered in the engine. This method will try to "reload" + /// the scenes that have scripts from dynamic plugins. Reloading here means, that the engine will + /// serialize a scene into a binary blob, then destroy the scene, then reload all the plugins and + /// finally deserialize the scene from the binary blob. + /// + /// ## Important notes + /// + /// This method is highly dangerous and wasn't properly tested, use at your own risk! + pub async fn reload_dynamic_plugins(&mut self) { + // Search for scenes, that has scripts provided by a dynamic plugin. + // TODO: Add scene nodes here too; plugins can provide custom scene node types. + let mut scenes = FxHashSet::default(); + for plugin in self.plugins.iter() { + if let PluginContainer::Dynamic(dynamic) = plugin { + let plugin_type_id = dynamic.plugin().type_id(); + + for (handle, scene) in self.scenes.pair_iter() { + 'node_loop: for node in scene.graph.linear_iter() { + for script in node.scripts() { + let script_id = script.deref().id(); + + if let Some(constructor) = self + .serialization_context + .script_constructors + .map() + .get(&script_id) + { + if constructor.source_type_id == plugin_type_id { + scenes.insert(handle); + break 'node_loop; + } + } + } + } + } + } + } + + // Search for script constructors, that belongs to dynamic plugins. + let mut constructors = FxHashSet::default(); + for plugin in self.plugins.iter() { + if let PluginContainer::Dynamic(dynamic) = plugin { + let plugin_type_id = dynamic.plugin().type_id(); + + for (type_uuid, constructor) in + self.serialization_context.script_constructors.map().iter() + { + if constructor.source_type_id == plugin_type_id { + constructors.insert(*type_uuid); + } + } + } + } + + // Remove script constructors, that belongs to dynamic plugins. + for type_uuid in constructors.iter() { + self.serialization_context + .script_constructors + .remove(*type_uuid); + } + + // Take the scenes out of the container, serialize and destroy the initial scene. + let mut serialized_scenes = FxHashMap::default(); + for &scene_handle in scenes.iter() { + let (ticket, mut scene) = self.scenes.take_reserve(scene_handle); + let mut visitor = Visitor::new(); + scene.save("Scene", &mut visitor).unwrap(); + let mut binary_blob = Cursor::new(Vec::::new()); + visitor.save_binary_to_memory(&mut binary_blob).unwrap(); + serialized_scenes.insert(scene_handle, (ticket, binary_blob.into_inner())); + } + + // Unload the plugins. + enum TemporaryStorage { + StaticPlugin(Box), + DynamicPlugin(PathBuf), + } + let plugins = self + .plugins + .drain(..) + .map(|p| match p { + PluginContainer::Static(plugin) => TemporaryStorage::StaticPlugin(plugin), + PluginContainer::Dynamic(dynamic) => { + // Store the path of the dynamic plugin. The plugin itself will be dropped and + // thus unloaded. + TemporaryStorage::DynamicPlugin(dynamic.path().to_path_buf()) + } + }) + .collect::>(); + for plugin in plugins { + match plugin { + TemporaryStorage::StaticPlugin(plugin) => { + self.plugins.push(PluginContainer::Static(plugin)) + } + TemporaryStorage::DynamicPlugin(path) => { + let dynamic = DynamicPlugin::load(path).unwrap(); + + // Re-register the plugin. This is needed, because it might contain new script + // types (or removed ones too). + self.register_plugin(dynamic.plugin()); + + self.plugins.push(PluginContainer::Dynamic(dynamic)) + } + } + } + + // Deserialize the scenes. + for (_, (ticket, binary_blob)) in serialized_scenes { + let mut visitor = Visitor::load_from_memory(&binary_blob).unwrap(); + let loader = SceneLoader::load( + "Scene", + self.serialization_context.clone(), + self.resource_manager.clone(), + &mut visitor, + None, + ) + .unwrap(); + + let scene = loader.finish(&self.resource_manager).await; + + self.scenes.put_back(ticket, scene); + } + } } impl Drop for Engine { diff --git a/fyrox-impl/src/plugin/dynamic.rs b/fyrox-impl/src/plugin/dynamic.rs index 7a836c535..f04a7620c 100644 --- a/fyrox-impl/src/plugin/dynamic.rs +++ b/fyrox-impl/src/plugin/dynamic.rs @@ -45,4 +45,9 @@ impl DynamicPlugin { pub fn path(&self) -> &Path { &self.path } + + /// Return a reference to the plugin interface of the dynamic plugin. + pub fn plugin(&self) -> &dyn Plugin { + &*self.plugin + } } diff --git a/fyrox-impl/src/script/constructor.rs b/fyrox-impl/src/script/constructor.rs index 5e4ffbcfb..9afb8c27a 100644 --- a/fyrox-impl/src/script/constructor.rs +++ b/fyrox-impl/src/script/constructor.rs @@ -8,6 +8,7 @@ use crate::{ }, script::{Script, ScriptTrait}, }; +use std::any::{Any, TypeId}; use std::collections::BTreeMap; /// Script constructor contains all required data and methods to create script instances @@ -21,15 +22,27 @@ pub struct ScriptConstructor { /// Script source path. pub source_path: String, + + /// A type of the source of the script constructor. + pub source_type_id: TypeId, } /// A special container that is able to create nodes by their type UUID. -#[derive(Default)] pub struct ScriptConstructorContainer { + pub(crate) context_type_id: Mutex, // BTreeMap allows to have sorted list of constructors. map: Mutex>, } +impl Default for ScriptConstructorContainer { + fn default() -> Self { + Self { + context_type_id: Mutex::new(().type_id()), + map: Default::default(), + } + } +} + impl ScriptConstructorContainer { /// Creates default node constructor container with constructors for built-in engine nodes. pub fn new() -> Self { @@ -51,6 +64,7 @@ impl ScriptConstructorContainer { constructor: Box::new(|| Script::new(T::default())), name: name.to_owned(), source_path: T::source_path().to_owned(), + source_type_id: *self.context_type_id.lock(), }, );