mirror of
https://github.com/FyroxEngine/Fyrox.git
synced 2026-10-12 02:02:20 +00:00
experimental ability to reload dynamic plugins
This commit is contained in:
+142
-10
@@ -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<P>(&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<OsStr> + '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::<u8>::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<dyn Plugin>),
|
||||
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::<Vec<_>>();
|
||||
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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<TypeId>,
|
||||
// BTreeMap allows to have sorted list of constructors.
|
||||
map: Mutex<BTreeMap<Uuid, ScriptConstructor>>,
|
||||
}
|
||||
|
||||
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(),
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user