reload prefabs with plugin data when doing hot reloading

- this change applies the same method (serialize -> unload -> deserialize) for prefabs as for ordinary scenes
This commit is contained in:
Dmitry Stepanov
2024-05-16 10:58:55 +03:00
parent d29897646b
commit 19d1af059c
2 changed files with 271 additions and 194 deletions
+221
View File
@@ -0,0 +1,221 @@
use crate::{
asset::manager::ResourceManager,
core::{
log::Log,
pool::{Handle, PayloadContainer, Ticket},
visitor::{Visit, VisitError, Visitor, VisitorFlags},
},
engine::SerializationContext,
gui::constructor::WidgetConstructorContainer,
plugin::Plugin,
scene::{
base::visit_opt_script,
node::{container::NodeContainer, Node},
Scene,
},
script::Script,
};
use std::{ops::Deref, sync::Arc};
pub struct ScriptState {
index: usize,
binary_blob: Vec<u8>,
}
pub struct NodeState {
node: Handle<Node>,
ticket: Option<Ticket<Node>>,
binary_blob: Vec<u8>,
scripts: Vec<ScriptState>,
}
pub struct SceneState {
pub scene: Handle<Scene>,
nodes: Vec<NodeState>,
}
impl SceneState {
pub fn try_create_from_plugin(
scene_handle: Handle<Scene>,
scene: &mut Scene,
serialization_context: &SerializationContext,
plugin: &dyn Plugin,
) -> Result<Option<Self>, String> {
let mut scene_state = Self {
scene: scene_handle,
nodes: Default::default(),
};
for index in 0..scene.graph.capacity() {
let handle = scene.graph.handle_from_index(index);
let Some(node) = scene.graph.try_get_mut(handle) else {
continue;
};
let mut node_state = NodeState {
node: handle,
ticket: None,
binary_blob: Default::default(),
scripts: Default::default(),
};
if is_node_belongs_to_plugin(serialization_context, node, plugin) {
// The entire node belongs to plugin, serialize it entirely.
// Take the node out of the graph first.
let (ticket, node) = scene.graph.take_reserve(handle);
let mut container = NodeContainer::new(node);
let mut visitor = make_writing_visitor();
container
.visit("Node", &mut visitor)
.map_err(|e| e.to_string())?;
node_state.binary_blob = visitor.save_binary_to_vec().map_err(|e| e.to_string())?;
node_state.ticket = Some(ticket);
} else {
// The node does not belong to the plugin, try to check its scripts.
for (script_index, record) in node.scripts.iter_mut().enumerate() {
if let Some(script) = record.script.as_ref() {
if is_script_belongs_to_plugin(serialization_context, script, plugin) {
// Take the script out of the node and serialize it. The script will be
// dropped and destroyed.
let mut script = record.script.take();
let mut visitor = make_writing_visitor();
visit_opt_script("Script", &mut script, &mut visitor)
.map_err(|e| e.to_string())?;
let binary_blob =
visitor.save_binary_to_vec().map_err(|e| e.to_string())?;
node_state.scripts.push(ScriptState {
index: script_index,
binary_blob,
})
}
}
}
}
if !node_state.binary_blob.is_empty() || !node_state.scripts.is_empty() {
scene_state.nodes.push(node_state);
}
}
if !scene_state.nodes.is_empty() {
Ok(Some(scene_state))
} else {
Ok(None)
}
}
pub fn deserialize_into_scene(
self,
scene: &mut Scene,
serialization_context: &Arc<SerializationContext>,
resource_manager: &ResourceManager,
widget_constructors: &Arc<WidgetConstructorContainer>,
) -> Result<(), String> {
let script_message_sender = scene.graph.script_message_sender.clone();
for node_state in self.nodes {
let node = &mut scene.graph[node_state.node];
if node_state.binary_blob.is_empty() {
// Only scripts needs to be reloaded.
for script in node_state.scripts {
let mut visitor = make_reading_visitor(
&script.binary_blob,
serialization_context,
resource_manager,
widget_constructors,
)
.map_err(|e| e.to_string())?;
let mut opt_script: Option<Script> = None;
visit_opt_script("Script", &mut opt_script, &mut visitor)
.map_err(|e| e.to_string())?;
node.scripts[script.index].script = opt_script;
Log::info(format!(
"Script {} of node {} was successfully deserialized.",
script.index, node_state.node
));
}
} else {
let mut visitor = make_reading_visitor(
&node_state.binary_blob,
serialization_context,
resource_manager,
widget_constructors,
)
.map_err(|e| e.to_string())?;
let mut container = NodeContainer::default();
container
.visit("Node", &mut visitor)
.map_err(|e| e.to_string())?;
if let Some(mut new_node) = container.take() {
new_node.script_message_sender = Some(script_message_sender.clone());
*node = new_node;
Log::info(format!(
"Node {} was successfully deserialized.",
node_state.node
));
}
}
}
Ok(())
}
}
pub fn make_writing_visitor() -> Visitor {
let mut visitor = Visitor::new();
visitor.flags = VisitorFlags::SERIALIZE_EVERYTHING;
visitor
}
pub fn make_reading_visitor(
binary_blob: &[u8],
serialization_context: &Arc<SerializationContext>,
resource_manager: &ResourceManager,
widget_constructors: &Arc<WidgetConstructorContainer>,
) -> Result<Visitor, VisitError> {
let mut visitor = Visitor::load_from_memory(binary_blob)?;
visitor.blackboard.register(serialization_context.clone());
visitor
.blackboard
.register(Arc::new(resource_manager.clone()));
visitor.blackboard.register(widget_constructors.clone());
Ok(visitor)
}
fn is_script_belongs_to_plugin(
serialization_context: &SerializationContext,
script: &Script,
plugin: &dyn Plugin,
) -> bool {
let script_id = script.deref().id();
if let Some(constructor) = serialization_context
.script_constructors
.map()
.get(&script_id)
{
if constructor.assembly_name == plugin.assembly_name() {
return true;
}
}
false
}
fn is_node_belongs_to_plugin(
serialization_context: &SerializationContext,
node: &Node,
plugin: &dyn Plugin,
) -> bool {
let node_id = (*node).id();
if let Some(constructor) = serialization_context.node_constructors.map().get(&node_id) {
if constructor.assembly_name == plugin.assembly_name() {
return true;
}
}
false
}
+50 -194
View File
@@ -7,6 +7,8 @@ pub mod error;
pub mod executor;
pub mod task;
mod hotreload;
use crate::{
asset::{
event::ResourceEvent,
@@ -97,13 +99,9 @@ use std::{
use crate::plugin::dynamic::DynamicPlugin;
use crate::plugin::{DynamicPluginState, PluginContainer};
use crate::scene::base::visit_opt_script;
use crate::scene::node::container::NodeContainer;
use fyrox_core::futures::future::join_all;
use fyrox_core::notify;
use fyrox_core::notify::{EventKind, RecursiveMode, Watcher};
use fyrox_core::pool::{PayloadContainer, Ticket};
use fyrox_core::visitor::{Visit, Visitor, VisitorFlags};
use fyrox_resource::state::ResourceState;
use fyrox_ui::constructor::WidgetConstructorContainer;
use fyrox_ui::UiContainer;
@@ -2583,61 +2581,6 @@ impl Engine {
Ok(&**self.plugins.last().unwrap())
}
fn is_script_belongs_to_plugin(
serialization_context: &SerializationContext,
script: &Script,
plugin: &dyn Plugin,
) -> bool {
let script_id = script.deref().id();
if let Some(constructor) = serialization_context
.script_constructors
.map()
.get(&script_id)
{
if constructor.assembly_name == plugin.assembly_name() {
return true;
}
}
false
}
fn is_node_belongs_to_plugin(
serialization_context: &SerializationContext,
node: &Node,
plugin: &dyn Plugin,
) -> bool {
let node_id = (*node).id();
if let Some(constructor) = serialization_context.node_constructors.map().get(&node_id) {
if constructor.assembly_name == plugin.assembly_name() {
return true;
}
}
false
}
fn make_writing_visitor() -> Visitor {
let mut visitor = Visitor::new();
visitor.flags = VisitorFlags::SERIALIZE_EVERYTHING;
visitor
}
fn make_reading_visitor(
binary_blob: &[u8],
serialization_context: &Arc<SerializationContext>,
resource_manager: &ResourceManager,
widget_constructors: &Arc<WidgetConstructorContainer>,
) -> Result<Visitor, VisitError> {
let mut visitor = Visitor::load_from_memory(binary_blob)?;
visitor.blackboard.register(serialization_context.clone());
visitor
.blackboard
.register(Arc::new(resource_manager.clone()));
visitor.blackboard.register(widget_constructors.clone());
Ok(visitor)
}
/// Tries to reload a specified plugin. This method tries to perform least invasive reloading, by
/// only detaching parts from the scenes and engine internals, that belongs to reloadable plugin.
pub fn reload_plugin(
@@ -2669,93 +2612,15 @@ impl Engine {
let plugin_assembly_name = state.as_loaded_ref().plugin().assembly_name();
// Collect all the data that belongs to the plugin
struct ScriptState {
index: usize,
binary_blob: Vec<u8>,
}
struct NodeState {
node: Handle<Node>,
ticket: Option<Ticket<Node>>,
binary_blob: Vec<u8>,
scripts: Vec<ScriptState>,
}
struct SceneState {
scene: Handle<Scene>,
nodes: Vec<NodeState>,
}
let mut scenes_state = Vec::new();
for (scene_handle, scene) in self.scenes.pair_iter_mut() {
let mut scene_state = SceneState {
scene: scene_handle,
nodes: Default::default(),
};
for index in 0..scene.graph.capacity() {
let handle = scene.graph.handle_from_index(index);
let Some(node) = scene.graph.try_get_mut(handle) else {
continue;
};
let mut node_state = NodeState {
node: handle,
ticket: None,
binary_blob: Default::default(),
scripts: Default::default(),
};
if Self::is_node_belongs_to_plugin(
&self.serialization_context,
node,
state.as_loaded_ref().plugin(),
) {
// The entire node belongs to plugin, serialize it entirely.
// Take the node out of the graph first.
let (ticket, node) = scene.graph.take_reserve(handle);
let mut container = NodeContainer::new(node);
let mut visitor = Self::make_writing_visitor();
container
.visit("Node", &mut visitor)
.map_err(|e| e.to_string())?;
node_state.binary_blob =
visitor.save_binary_to_vec().map_err(|e| e.to_string())?;
node_state.ticket = Some(ticket);
} else {
// The node does not belong to the plugin, try to check its scripts.
for (script_index, record) in node.scripts.iter_mut().enumerate() {
if let Some(script) = record.script.as_ref() {
if Self::is_script_belongs_to_plugin(
&self.serialization_context,
script,
state.as_loaded_ref().plugin(),
) {
// Take the script out of the node and serialize it. The script will be
// dropped and destroyed.
let mut script = record.script.take();
let mut visitor = Self::make_writing_visitor();
visit_opt_script("Script", &mut script, &mut visitor)
.map_err(|e| e.to_string())?;
let binary_blob =
visitor.save_binary_to_vec().map_err(|e| e.to_string())?;
node_state.scripts.push(ScriptState {
index: script_index,
binary_blob,
})
}
}
}
}
if !node_state.binary_blob.is_empty() || !node_state.scripts.is_empty() {
scene_state.nodes.push(node_state);
}
}
if !scene_state.nodes.is_empty() {
scenes_state.push(scene_state);
if let Some(data) = hotreload::SceneState::try_create_from_plugin(
scene_handle,
scene,
&self.serialization_context,
state.as_loaded_ref().plugin(),
)? {
scenes_state.push(data);
}
}
@@ -2826,6 +2691,26 @@ impl Engine {
block_on(join_all(resources_to_reload));
}
// Check every prefab for plugin content.
let mut prefab_scenes = Vec::new();
let rm_state = self.resource_manager.state();
for resource in rm_state.resources().iter() {
if let Some(model) = resource.try_cast::<Model>() {
let mut model_state = model.state();
if let Some(data) = model_state.data() {
if let Some(scene_state) = hotreload::SceneState::try_create_from_plugin(
Handle::NONE,
&mut data.scene,
&self.serialization_context,
state.as_loaded_ref().plugin(),
)? {
prefab_scenes.push((model.clone(), scene_state));
}
}
}
}
drop(rm_state);
// Unload custom render passes (if any).
if let GraphicsContext::Initialized(ref mut graphics_context) = self.graphics_context {
let render_passes = graphics_context.renderer.render_passes().to_vec();
@@ -2838,7 +2723,7 @@ impl Engine {
// Unload the plugin.
if let DynamicPluginState::Loaded(dynamic) = state {
let mut visitor = Self::make_writing_visitor();
let mut visitor = hotreload::make_writing_visitor();
dynamic
.plugin_mut()
.visit("Plugin", &mut visitor)
@@ -2887,7 +2772,7 @@ impl Engine {
dynamic.plugin(),
);
let mut visitor = Self::make_reading_visitor(
let mut visitor = hotreload::make_reading_visitor(
binary_blob,
&self.serialization_context,
&self.resource_manager,
@@ -2906,57 +2791,28 @@ impl Engine {
Log::info(format!("Plugin {plugin_index} was reloaded successfully!"));
}
// Deserialize prefab scene content.
for (model, scene_state) in prefab_scenes {
let mut model_state = model.state();
if let Some(data) = model_state.data() {
scene_state.deserialize_into_scene(
&mut data.scene,
&self.serialization_context,
&self.resource_manager,
&self.widget_constructors,
)?;
}
}
// Deserialize scene content.
for scene_state in scenes_state {
let scene = &mut self.scenes[scene_state.scene];
let script_message_sender = scene.graph.script_message_sender.clone();
for node_state in scene_state.nodes {
let node = &mut scene.graph[node_state.node];
if node_state.binary_blob.is_empty() {
// Only scripts needs to be reloaded.
for script in node_state.scripts {
let mut visitor = Self::make_reading_visitor(
&script.binary_blob,
&self.serialization_context,
&self.resource_manager,
&self.widget_constructors,
)
.map_err(|e| e.to_string())?;
let mut opt_script: Option<Script> = None;
visit_opt_script("Script", &mut opt_script, &mut visitor)
.map_err(|e| e.to_string())?;
node.scripts[script.index].script = opt_script;
Log::info(format!(
"Script {} of node {} was successfully deserialized.",
script.index, node_state.node
));
}
} else {
let mut visitor = Self::make_reading_visitor(
&node_state.binary_blob,
&self.serialization_context,
&self.resource_manager,
&self.widget_constructors,
)
.map_err(|e| e.to_string())?;
let mut container = NodeContainer::default();
container
.visit("Node", &mut visitor)
.map_err(|e| e.to_string())?;
if let Some(mut new_node) = container.take() {
new_node.script_message_sender = Some(script_message_sender.clone());
*node = new_node;
Log::info(format!(
"Node {} was successfully deserialized.",
node_state.node
));
}
}
}
scene_state.deserialize_into_scene(
scene,
&self.serialization_context,
&self.resource_manager,
&self.widget_constructors,
)?;
}
// Call `on_loaded` for plugins, so they could restore some runtime non-serializable state.