Merge pull request #856 from b-guild/resource

Improving resource handles to be UUID-based
This commit is contained in:
Dmitry Stepanov
2025-10-17 18:57:33 +03:00
committed by GitHub
34 changed files with 1813 additions and 1019 deletions
+6
View File
@@ -207,6 +207,9 @@ impl Executor {
/// Runs the executor - starts your game.
pub fn run(self) {
Log::info("Initializing resource registry.");
self.engine.resource_manager.update_or_load_registry();
let engine = self.engine;
let event_loop = self.event_loop;
let throttle_threshold = self.throttle_threshold;
@@ -373,6 +376,9 @@ fn run_normal(
}
Event::WindowEvent { event, .. } => {
match event {
WindowEvent::Focused(true) => {
engine.resource_manager.state().process_filesystem_events();
}
WindowEvent::CloseRequested => active_event_loop.exit(),
WindowEvent::Resized(size) => {
if let Err(e) = engine.set_frame_size(size.into()) {
+34 -38
View File
@@ -40,7 +40,6 @@ use crate::{
manager::{ResourceManager, ResourceWaitContext},
state::ResourceState,
untyped::{ResourceKind, UntypedResource},
Resource,
},
core::{
algebra::Vector2,
@@ -51,6 +50,7 @@ use crate::{
reflect::Reflect,
task::TaskPool,
variable::try_inherit_properties,
SafeLock,
},
engine::{error::EngineError, task::TaskPoolHandler},
event::Event,
@@ -360,6 +360,7 @@ struct LoadingScene {
}
struct SceneLoadingResult {
uuid: Uuid,
path: PathBuf,
result: Result<(Scene, Vec<u8>), VisitError>,
}
@@ -399,6 +400,7 @@ impl AsyncSceneLoader {
let sender = self.sender.clone();
let serialization_context = self.serialization_context.clone();
let resource_manager = self.resource_manager.clone();
let uuid = resource_manager.find::<Model>(&path).resource_uuid();
// Aquire the resource IO from the resource manager
let io = resource_manager.resource_io();
@@ -415,12 +417,14 @@ impl AsyncSceneLoader {
Ok((loader, data)) => {
let scene = loader.finish().await;
Log::verify(sender.send(SceneLoadingResult {
uuid,
path,
result: Ok((scene, data)),
}));
}
Err(e) => {
Log::verify(sender.send(SceneLoadingResult {
uuid,
path,
result: Err(e),
}));
@@ -1059,7 +1063,7 @@ impl ResourceGraphVertex {
pub fn resolve(&self) {
Log::info(format!(
"Resolving {:?} resource from dependency graph...",
"Resolving {} resource from dependency graph...",
self.resource.resource_uuid()
));
@@ -1351,7 +1355,7 @@ pub(crate) fn initialize_resource_manager_loaders(
state.constructors_container.add::<AnimationTracksData>();
state.constructors_container.add::<Style>();
let mut loaders = state.loaders.lock();
let mut loaders = state.loaders.safe_lock();
let gltf_loader = super::resource::gltf::GltfLoader {
resource_manager: resource_manager.clone(),
default_import_options: Default::default(),
@@ -1483,8 +1487,6 @@ impl Engine {
let user_interfaces =
UiContainer::new_with_ui(UserInterface::new(Vector2::new(100.0, 100.0)));
resource_manager.update_or_load_registry();
Ok(Self {
graphics_context: GraphicsContext::Uninitialized(graphics_context_params),
model_events_receiver: tx,
@@ -1745,31 +1747,26 @@ impl Engine {
match loading_result.result {
Ok((mut scene, data)) => {
if request.options.derived {
let model = self
.resource_manager
.find_uuid::<Model>(loading_result.uuid);
// Create a resource, that will point to the scene we've loaded the
// scene from and force scene nodes to inherit data from them.
let model = Resource::new_ok(
Uuid::new_v4(),
ResourceKind::External,
Model {
mapping: NodeMapping::UseHandles,
// We have to create a full copy of the scene, because otherwise
// some methods (`Base::root_resource` in particular) won't work
// correctly.
scene: scene
.clone_ex(
scene.graph.get_root(),
&mut |_, _| true,
&mut |_, _| {},
&mut |_, _, _| {},
)
.0,
},
);
Log::verify(
self.resource_manager
.register(model.clone().into_untyped(), request.path.clone()),
);
let data = Model {
mapping: NodeMapping::UseHandles,
// We have to create a full copy of the scene, because otherwise
// some methods (`Base::root_resource` in particular) won't work
// correctly.
scene: scene
.clone_ex(
scene.graph.get_root(),
&mut |_, _| true,
&mut |_, _| {},
&mut |_, _, _| {},
)
.0,
};
model.header().state.commit_ok(data);
for (handle, node) in scene.graph.pair_iter_mut() {
node.set_inheritance_data(handle, model.clone());
@@ -1835,9 +1832,8 @@ impl Engine {
// Notify plugins about a scene, that is failed to load.
if self.plugins_enabled {
Log::err(format!(
"Unable to load scene {}. Reason: {:?}",
loading_result.path.display(),
error
"Unable to load scene {:?}. Reason: {}",
loading_result.path, error
));
for plugin in self.plugins.iter_mut() {
@@ -2417,7 +2413,7 @@ impl Engine {
if let ResourceEvent::Reloaded(resource) = event {
if let Some(model) = resource.try_cast::<Model>() {
Log::info(format!(
"A model resource {:?} was reloaded, propagating changes...",
"A model resource {} was reloaded, propagating changes...",
model.resource_uuid()
));
@@ -2545,10 +2541,6 @@ impl Engine {
widget_constructors,
resource_manager,
});
// New plugins may add custom resources and we must re-scan the data folder to include
// such resources in the registry.
resource_manager.update_or_load_registry();
}
fn register_plugin(&self, plugin: &dyn Plugin) {
@@ -2717,7 +2709,7 @@ impl Engine {
let mut resources_to_reload = FxHashSet::default();
let mut state = self.resource_manager.state();
for resource in state.resources().iter() {
let data = resource.0.lock();
let data = resource.lock();
if let ResourceState::Ok { ref data, .. } = data.state {
data.as_reflect(&mut |reflect| {
if reflect.assembly_name() == plugin_assembly_name {
@@ -2784,6 +2776,10 @@ impl Engine {
plugin,
);
// New plugins may add custom resources and we must re-scan the data folder to include
// such resources in the registry.
self.resource_manager.update_or_load_registry();
let mut visitor = hotreload::make_reading_visitor(
&binary_blob,
&self.serialization_context,
@@ -2801,7 +2797,7 @@ impl Engine {
// Deserialize prefab scene content.
for (model, scene_state) in prefab_scenes {
Log::info(format!(
"Deserializing {:?} prefab content...",
"Deserializing {} prefab content...",
model.resource_uuid()
));
+2 -2
View File
@@ -567,10 +567,10 @@ impl RenderDataBundle {
{
err_once!(
self.data.key() as usize,
"There's no render pass {} in {} shader! \
"There's no render pass {} in {:?} shader! \
If it is not needed, add it to disabled passes.",
render_context.render_pass_name,
shader_state.kind()
shader_data.definition.name,
);
}
}
+6 -4
View File
@@ -195,12 +195,13 @@ impl TextureCache {
resource_manager: &ResourceManager,
texture: &TextureResource,
) -> Result<(), FrameworkError> {
let uuid = texture.resource_uuid();
let texture = texture.state();
if let Some((texture, uuid)) = texture.data_ref_with_id() {
if let Some(texture) = texture.data_ref() {
self.cache.get_entry_mut_or_insert_with(
&texture.cache_index,
Default::default(),
|| create_gpu_texture(server, resource_manager, uuid, texture),
|| create_gpu_texture(server, resource_manager, &uuid, texture),
)?;
Ok(())
} else {
@@ -216,12 +217,13 @@ impl TextureCache {
resource_manager: &ResourceManager,
texture_resource: &TextureResource,
) -> Option<&TextureRenderData> {
let uuid = texture_resource.resource_uuid();
let texture_data_guard = texture_resource.state();
if let Some((texture, uuid)) = texture_data_guard.data_ref_with_id() {
if let Some(texture) = texture_data_guard.data_ref() {
match self.cache.get_mut_or_insert_with(
&texture.cache_index,
Default::default(),
|| create_gpu_texture(server, resource_manager, uuid, texture),
|| create_gpu_texture(server, resource_manager, &uuid, texture),
) {
Ok(entry) => {
// Check if some value has changed in resource.
+15 -3
View File
@@ -353,7 +353,8 @@ pub type ModelResource = Resource<Model>;
/// Extension trait for model resources.
pub trait ModelResourceExtension: Sized {
/// Tries to instantiate model from given resource.
/// Tries to instantiate model from given resource. This is for internal use only and does not
/// set `is_resource_instance_root`.
fn instantiate_from<Pre>(
model: ModelResource,
model_data: &Model,
@@ -465,6 +466,18 @@ impl AnimationSource for UserInterface {
}
impl ModelResourceExtension for ModelResource {
/// Copy the given model into the given graph.
/// * `model`: The resource handle of the model, which put into the created nodes as their
/// [`Base::resource`](crate::scene::base::Base::resource) to indicate which model
/// the node was instantiated from.
/// * `model_data`: A borrow of the data contained in `model`.
/// * `handle`: The handle of the node within `model` that should be instantiated, typically
/// the root of the model.
/// * `dest_graph`: A mutable borrow of the node graph that will contain the newly created copy.
/// * `pre_processing_callback`: A function that takes a node handle and a mutable node.
/// The handle belongs to the original node in the model. The node is a copy of the original
/// node, except that parent handle and child handles have been removed. This is a node
/// that will be inserted into `dest_graph`.
fn instantiate_from<Pre>(
model: ModelResource,
model_data: &Model,
@@ -495,7 +508,6 @@ impl ModelResourceExtension for ModelResource {
Log::err(format!(
"Instantiating a model from a resource that is not loaded: {self:?}"
));
panic!();
}
InstantiationContext {
model: self,
@@ -748,7 +760,7 @@ impl Display for ModelLoadError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
ModelLoadError::Visit(v) => {
write!(f, "An error occurred while reading a data source {v:?}")
write!(f, "An error occurred while reading a data source: {v}")
}
ModelLoadError::NotSupported(v) => {
write!(f, "Model format is not supported: {v}")
+3 -3
View File
@@ -486,9 +486,9 @@ pub struct Base {
#[reflect(setter = "set_frustum_culling")]
frustum_culling: InheritableVariable<bool>,
// When `true` it means that this node is instance of `resource`.
// More precisely - this node is root of whole descendant nodes
// hierarchy which was instantiated from resource.
/// When `true` it means that this node is instance of `resource`.
/// More precisely - this node is root of whole descendant nodes
/// hierarchy which was instantiated from resource.
#[reflect(read_only)]
pub(crate) is_resource_instance_root: bool,
+3 -3
View File
@@ -36,7 +36,7 @@ use crate::{
reflect::prelude::*,
variable::{InheritableVariable, VariableFlags},
visitor::prelude::*,
BiDirHashMap, ImmutableString,
BiDirHashMap, ImmutableString, SafeLock,
},
graph::{BaseSceneGraph, SceneGraphNode},
scene::{
@@ -741,7 +741,7 @@ impl PhysicsWorld {
/// Draws physics world. Very useful for debugging, it allows you to see where are
/// rigid bodies, which colliders they have and so on.
pub fn draw(&self, context: &mut SceneDrawingContext) {
self.debug_render_pipeline.lock().render(
self.debug_render_pipeline.safe_lock().render(
context,
&self.bodies,
&self.colliders,
@@ -966,7 +966,7 @@ impl PhysicsWorld {
// 1) `get_mut` is **very** expensive because it forces physics engine to recalculate contacts
// and a lot of other stuff, this is why we need `anything_changed` flag.
if rigid_body_node.native.get() != RigidBodyHandle::invalid() {
let mut actions = rigid_body_node.actions.lock();
let mut actions = rigid_body_node.actions.safe_lock();
if rigid_body_node.need_sync_model() || !actions.is_empty() {
if let Some(native) = self.bodies.get_mut(rigid_body_node.native.get()) {
// Sync native rigid body's properties with scene node's in case if they
+48 -2
View File
@@ -243,6 +243,9 @@ pub struct SubGraph {
pub parent: Handle<Node>,
}
/// Perform remapping for every node in the destination graph that is included
/// in the given `old_new_mapping`. This uses reflection to update every node handle
/// that is part of the data of each node.
fn remap_handles(old_new_mapping: &NodeHandleMap<Node>, dest_graph: &mut Graph) {
// Iterate over instantiated nodes and remap handles.
for (_, &new_node_handle) in old_new_mapping.inner().iter() {
@@ -611,6 +614,20 @@ impl Graph {
/// and when you fire from rocket launcher you just need to create a copy of such
/// "prefab".
///
/// * `node_handle`: The handle of the node to copy.
/// * `dest_graph`: A mutable borrow of the node graph that will contain the newly created copy.
/// * `filter`: A function that takes a node handle and a borrow of a node and returns a bool.
/// The is called with the handle and node of each child of `node_handle` to determine if
/// that child should be recursively copied, and then the same filter is applied to each recursive step.
/// * `pre_processing_callback`: A function that takes a node handle and a mutable node.
/// The handle belongs to the original node in this graph. The node is a copy of the original
/// node, except that parent handle and child handles have been removed. This is a node
/// that will be inserted into `dest_graph`.
/// * `post_processing_callback`: A function much like `pre_processing_callback` except that
/// it is called after the new node and all its children have been inserted into the graph.
/// The function takes (new handle, original handle, node) as parameters. The node has handles
/// for its newly copied children, but no handle for its parent.
///
/// # Implementation notes
///
/// Returns tuple where first element is handle to copy of node, and second element -
@@ -722,6 +739,21 @@ impl Graph {
clone
}
/// * `node_handle`: The handle of the node to copy.
/// * `dest_graph`: A mutable borrow of the node graph that will contain the newly created copy.
/// * `old_new_mapping`: A mutable hashmap of node handles which records which original nodes were copied to
/// become which new nodes.
/// * `filter`: A function that takes a node handle and a borrow of a node and returns a bool.
/// The is called with the handle and node of each child of `node_handle` to determine if
/// that child should be recursively copied, and then the same filter is applied to each recursive step.
/// * `pre_processing_callback`: A function that takes a node handle and a mutable node.
/// The handle belongs to the original node in this graph. The node is a copy of the original
/// node, except that parent handle and child handles have been removed. This is a node
/// that will be inserted into `dest_graph`.
/// * `post_processing_callback`: A function much like `pre_processing_callback` except that
/// it is called after the new node and all its children have been inserted into the graph.
/// The function takes (new handle, original handle, node) as parameters. The node has handles
/// for its newly copied children, but no handle for its parent.
fn copy_node_raw<F, Pre, Post>(
&self,
root_handle: Handle<Node>,
@@ -764,6 +796,8 @@ impl Graph {
dest_copy_handle
}
/// Reconnects every node to the graph by calling its [`Base::on_connected_to_graph`](crate::scene::base::Base::on_connected_to_graph)
/// method, giving the node its `self_handle` and the graph's `message_sender` and `script_message_sender`.
fn restore_dynamic_node_data(&mut self) {
for (handle, node) in self.pool.pair_iter_mut() {
node.on_connected_to_graph(
@@ -774,8 +808,8 @@ impl Graph {
}
}
// Fix property flags for scenes made before inheritance system was fixed. By default, all inheritable properties
// must be marked as modified in nodes without any parent resource.
/// Fix property flags for scenes made before inheritance system was fixed. By default, all inheritable properties
/// must be marked as modified in nodes without any parent resource.
pub(crate) fn mark_ancestor_nodes_as_modified(&mut self) {
for node in self.linear_iter_mut() {
if node.resource.is_none() {
@@ -784,11 +818,15 @@ impl Graph {
}
}
/// Synchronizes the state of the graph with external resources.
pub(crate) fn resolve(&mut self) {
Log::writeln(MessageKind::Information, "Resolving graph...");
self.restore_dynamic_node_data();
self.mark_ancestor_nodes_as_modified();
// Since the parent resources may have changed, iterate through every node and try to find
// the corresponding node in its resource, either by name or by handle depending on the resource's
// 'NodeMapping`. If a corresponding node cannot be found in the resource, then delete the node.
self.restore_original_handles_and_inherit_properties(
&[TypeId::of::<navmesh::Container>()],
|resource_node, node| {
@@ -796,9 +834,17 @@ impl Graph {
},
);
self.update_hierarchical_data();
// Create a list of (handle, resource) pairs where the handle points to a node that is the root of some instance
// of the resource within this graph. At the same time, modify each node so that its unmodified values match the
// corresponding values from within the instance's resource, so if the resource has changed the nodes will now
// reflect that change.
let instances = self.restore_integrity(|model, model_data, handle, dest_graph| {
ModelResource::instantiate_from(model, model_data, handle, dest_graph, &mut |_, _| {})
});
// Iterate through the list of (handle, resource) pairs and use reflection to replace each handle within each instance
// with the handle that corresponds to the associated copy in this graph. This is necessary because after
// `restore_original_handles_and_inherit_properties` the handles will have been reset back to their original values
// which refer to nodes *within* the resource instead of nodes in this graph.
self.remap_handles(&instances);
self.apply_lightmap();
+3 -3
View File
@@ -36,7 +36,7 @@ use crate::{
uuid_provider,
variable::{InheritableVariable, VariableFlags},
visitor::prelude::*,
BiDirHashMap,
BiDirHashMap, SafeLock,
},
scene::{
self,
@@ -1228,7 +1228,7 @@ impl PhysicsWorld {
/// Draws physics world. Very useful for debugging, it allows you to see where are
/// rigid bodies, which colliders they have and so on.
pub fn draw(&self, context: &mut SceneDrawingContext) {
self.debug_render_pipeline.lock().render(
self.debug_render_pipeline.safe_lock().render(
context,
&self.bodies,
&self.colliders,
@@ -1456,7 +1456,7 @@ impl PhysicsWorld {
// 1) `get_mut` is **very** expensive because it forces physics engine to recalculate contacts
// and a lot of other stuff, this is why we need `anything_changed` flag.
if rigid_body_node.native.get() != RigidBodyHandle::invalid() {
let mut actions = rigid_body_node.actions.lock();
let mut actions = rigid_body_node.actions.safe_lock();
if rigid_body_node.need_sync_model() || !actions.is_empty() {
if let Some(native) = self.bodies.get_mut(rigid_body_node.native.get()) {
// Sync native rigid body's properties with scene node's in case if they
+4 -3
View File
@@ -32,6 +32,7 @@ use crate::{
type_traits::prelude::*,
variable::InheritableVariable,
visitor::prelude::*,
SafeLock,
},
graph::{BaseSceneGraph, SceneGraph},
graphics::ElementRange,
@@ -196,7 +197,7 @@ struct BatchContainerWrapper(Mutex<BatchContainer>);
impl Clone for BatchContainerWrapper {
fn clone(&self) -> Self {
Self(Mutex::new(self.0.lock().clone()))
Self(Mutex::new(self.0.safe_lock().clone()))
}
}
@@ -631,7 +632,7 @@ impl NodeTrait for Mesh {
let mut bounding_box = AxisAlignedBoundingBox::default();
if let BatchingMode::Static = *self.batching_mode {
let container = self.batch_container.0.lock();
let container = self.batch_container.0.safe_lock();
for batch in container.batches.values() {
let data = batch.data.data_ref();
extend_aabb_from_vertex_buffer(&data.vertex_buffer, &mut bounding_box);
@@ -698,7 +699,7 @@ impl NodeTrait for Mesh {
let sorting_index = ctx.calculate_sorting_index(self.global_position());
if let BatchingMode::Static = *self.batching_mode {
let mut container = self.batch_container.0.lock();
let mut container = self.batch_container.0.safe_lock();
if container.batches.is_empty() {
container.fill(self.handle(), ctx);
+5 -1
View File
@@ -1162,7 +1162,11 @@ pub trait SurfaceResourceExtension {
impl SurfaceResourceExtension for SurfaceResource {
fn deep_clone(&self) -> Self {
Self::new_ok(Uuid::new_v4(), self.kind(), self.data_ref().clone())
Self::new_ok(
Uuid::new_v4(),
ResourceKind::Embedded,
self.data_ref().clone(),
)
}
}
+3 -4
View File
@@ -65,6 +65,7 @@ use crate::{
type_traits::prelude::*,
variable::InheritableVariable,
visitor::{error::VisitError, Visit, VisitResult, Visitor},
SafeLock,
},
engine::SerializationContext,
graph::NodeHandleMap,
@@ -82,7 +83,6 @@ use crate::{
utils::navmesh::Navmesh,
};
use fxhash::FxHashSet;
use fyrox_core::SafeLock;
use std::{
fmt::{Display, Formatter},
ops::{Index, IndexMut},
@@ -417,8 +417,8 @@ impl SceneLoader {
.filter(|res| {
let uuid = res.resource_uuid();
let state = self.resource_manager.state();
let registry = state.resource_registry.lock();
uuid.and_then(|uuid| registry.uuid_to_path(uuid)) == Some(&path)
let registry = state.resource_registry.safe_lock();
registry.uuid_to_path(uuid) == Some(&path)
})
.cloned()
.collect::<Vec<_>>();
@@ -577,7 +577,6 @@ impl Scene {
let mut region = visitor.enter_region(region_name)?;
self.graph.visit("Graph", &mut region)?;
self.enabled.visit("Enabled", &mut region)?;
let _ = self
.rendering_options
+1 -1
View File
@@ -826,7 +826,7 @@ mod test {
let registry = resource_manager.state().resource_registry.clone();
let mut registry = registry.safe_lock();
let mut ctx = registry.modify();
ctx.write_metadata(Uuid::new_v4(), derived_asset_path)
ctx.write_metadata(Uuid::new_v4(), derived_asset_path.to_path_buf())
.unwrap();
}
+10 -9
View File
@@ -267,7 +267,7 @@ impl Sound {
*self.panning
}
/// Sets playback status.
/// Sets playback status.
pub fn set_status(&mut self, status: Status) -> Status {
let prev = self.status();
match status {
@@ -456,15 +456,16 @@ impl NodeTrait for Sound {
let header = buffer.header();
match header.state {
ResourceState::Pending { .. } | ResourceState::Ok { .. } => Ok(()),
ResourceState::LoadError { ref error, .. } => {
match &error.0 {
None => Err("Sound buffer is failed to load, the reason is unknown!"
.to_string()),
Some(err) => {
Err(format!("Sound buffer is failed to load. Reason: {err:?}"))
}
}
ResourceState::Unloaded => {
Err("Sound buffer is unloaded because it was never requested.".to_string())
}
ResourceState::LoadError { ref error, .. } => match &error.0 {
None => {
Err("Sound buffer is failed to load, the reason is unknown!"
.to_string())
}
Some(err) => Err(format!("Sound buffer is failed to load. Reason: {err}")),
},
}
}
None => Err("Sound buffer is not set, the sound won't play!".to_string()),