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 -1
View File
@@ -685,6 +685,11 @@ impl AssetBrowser {
.path
.clone();
if asset_path.as_os_str().is_empty() {
err!("Selecting AssetItem with empty path: {selected_asset}");
return;
}
if let Some(selection) = entry.selection.as_ref::<AssetSelection>() {
if selection.contains(&asset_path) {
return;
@@ -830,7 +835,7 @@ impl AssetBrowser {
}
drop(registry);
for path in paths {
for path in paths.into_iter().filter(|p| !p.as_os_str().is_empty()) {
self.add_asset(&path, ui, &engine.resource_manager, &sender);
}
}
+1 -1
View File
@@ -77,7 +77,7 @@ impl AssetPreviewCache {
generated_counter: &mut usize,
engine: &mut Engine,
) -> Option<AssetPreviewTexture> {
let resource_uuid = resource.resource_uuid()?;
let resource_uuid = resource.resource_uuid();
if let (false, Some(cached_preview)) = (force_update, self.container.get(&resource_uuid)) {
return Some(cached_preview.clone());
+8 -5
View File
@@ -59,7 +59,7 @@ struct SaveResourceCommand {
impl SaveResourceCommand {
fn save(&self) {
let mut guard = self.resource.0.safe_lock();
let mut guard = self.resource.lock();
if let Some(data) = guard.state.data_mut() {
Log::verify(data.save(&self.path));
}
@@ -129,7 +129,10 @@ impl AssetSelection {
}
pub fn selected_path(&self) -> Option<&Path> {
self.resources.first().map(|r| r.path.as_path())
self.resources
.first()
.map(|r| r.path.as_path())
.filter(|p| !p.as_os_str().is_empty())
}
pub fn selected_import_options(&self) -> Option<RefMut<Box<dyn BaseImportOptions>>> {
@@ -163,7 +166,7 @@ impl SelectionContainer for AssetSelection {
block_on(self.resource_manager.request_untyped(&resource.path))
{
if !self.resource_manager.is_built_in_resource(&resource) {
let guard = resource.0.safe_lock();
let guard = resource.lock();
if let Some(data) = guard.state.data_ref() {
callback(&*data.0 as &dyn Reflect, false)
}
@@ -205,7 +208,7 @@ impl SelectionContainer for AssetSelection {
let resource2 = resource.clone();
if !self.resource_manager.is_built_in_resource(&resource) {
if let Some(command) = make_command(args, move |_| {
let mut guard = resource.0.safe_lock();
let mut guard = resource.lock();
let data = &mut **guard.state.data_mut()?;
// SAFETY: This is safe, because the closure owns its own copy of
// resource strong ref, and the entity getter is used only once per
@@ -264,7 +267,7 @@ impl SelectionContainer for AssetSelection {
path.to_string(),
value,
move |_| {
let mut guard = resource.0.safe_lock();
let mut guard = resource.lock();
let data = &mut **guard.state.data_mut()?;
// SAFETY: This is safe, because the closure owns its own copy of
// resource strong ref, and the entity getter is used only once per
+1 -1
View File
@@ -337,7 +337,7 @@ impl<'a> AssetSelectorBuilder<'a> {
.collect::<Vec<_>>();
supported_resource_paths.extend(state.built_in_resources.values().filter_map(|res| {
let resource_state = res.resource.0.safe_lock();
let resource_state = res.resource.lock();
if self
.asset_types
.contains(&resource_state.state.data_ref()?.type_uuid())
+16
View File
@@ -2313,6 +2313,9 @@ impl Editor {
let current_working_directory = std::env::current_dir().unwrap();
if current_working_directory != working_directory {
Log::info(format!(
"Changing working directory to {working_directory:?}"
));
std::env::set_current_dir(working_directory.clone()).unwrap();
self.engine.resource_manager.update_or_load_registry();
self.reload_settings();
@@ -2964,7 +2967,19 @@ impl Editor {
self.engine.destroy_graphics_context().unwrap();
}
fn on_focus_changed(&mut self) {
if self.focused {
self.engine
.resource_manager
.state()
.process_filesystem_events();
}
}
pub fn run(mut self, event_loop: EventLoop<()>) {
Log::info("Initializing resource registry.");
self.engine.resource_manager.update_or_load_registry();
for_each_plugin!(self.plugins => on_start(&mut self));
event_loop
@@ -3041,6 +3056,7 @@ impl Editor {
}
WindowEvent::Focused(focused) => {
self.focused = *focused;
self.on_focus_changed();
}
WindowEvent::Moved(new_position) => {
// Allow the window to go outside the screen bounds by a little. This
+3 -1
View File
@@ -218,9 +218,10 @@ pub struct MaterialFieldEditorBuilder {
}
fn make_name(resource_manager: &ResourceManager, material: &MaterialResource) -> String {
let resource_uuid = material.resource_uuid();
let header = material.header();
match header.state {
ResourceState::Ok { resource_uuid, .. } => {
ResourceState::Ok { .. } => {
if let Some(path) = resource_manager
.state()
.resource_registry
@@ -241,6 +242,7 @@ fn make_name(resource_manager: &ResourceManager, material: &MaterialResource) ->
)
}
}
ResourceState::Unloaded => "Material not loading".into(),
ResourceState::LoadError { ref error, .. } => {
format!("Loading failed: {error:?}")
}
+10
View File
@@ -628,6 +628,16 @@ impl Visitor {
self.version
}
/// Returns true of a region with the given name exists as a child of the current node.
/// When reading, `has_region` returning true means that `enter_region` will succeed.
/// When writing, `has_region` returning true means that `enter_region` will fail.
pub fn has_region(&self, name: &str) -> bool {
let node = self.nodes.borrow(self.current_node);
node.children
.iter()
.any(|child| self.nodes.borrow(*child).name == name)
}
/// If [Visitor::is_reading], find a node with the given name that is a child of the current
/// node, and return a Visitor for the found node. Return an error if no node with that name exists.
///
+3
View File
@@ -1273,6 +1273,9 @@ pub trait SceneGraph: BaseSceneGraph {
instances
}
// Iterate through every node and try to find a corresponding node in the parent resource for that node.
// If a node has a parent resource but no correspoding node in the parent resource, then delete the node.
// Otherwise, use reflection to make the unmodified variables of the node match the values in the corresponding node.
fn restore_original_handles_and_inherit_properties<F>(
&mut self,
ignored_types: &[TypeId],
+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()),
+13 -8
View File
@@ -1143,17 +1143,22 @@ impl MaterialResourceExtension for MaterialResource {
fn deep_copy(&self) -> MaterialResource {
let material_state = self.header();
let kind = material_state.kind;
match material_state.state {
ResourceState::Pending { ref path, .. } => {
MaterialResource::new_pending(path.clone(), kind)
ResourceState::Unloaded => self.resource_uuid().into(),
ResourceState::Pending { .. } => {
MaterialResource::new_pending(self.resource_uuid(), ResourceKind::External)
}
ResourceState::LoadError { ref error, .. } => {
MaterialResource::new_load_error(kind, error.clone())
}
ResourceState::Ok { ref data, .. } => MaterialResource::new_ok(
ResourceState::LoadError {
ref error,
ref path,
} => MaterialResource::new_load_error(
ResourceKind::External,
path.clone(),
error.clone(),
),
ResourceState::Ok { ref data } => MaterialResource::new_ok(
Uuid::new_v4(),
kind,
ResourceKind::Embedded,
(&**data as &dyn Any)
.downcast_ref::<Material>()
.unwrap()
+1 -10
View File
@@ -146,8 +146,6 @@ where
pub id: PathBuf,
/// Initial data, from which the resource is created from.
pub data_source: Option<DataSource>,
/// Uuid of the resource.
pub resource_uuid: Uuid,
/// Ready-to-use ("loaded") resource.
pub resource: Resource<T>,
}
@@ -157,7 +155,6 @@ impl<T: TypedResourceData> Clone for BuiltInResource<T> {
Self {
id: self.id.clone(),
data_source: self.data_source.clone(),
resource_uuid: self.resource_uuid,
resource: self.resource.clone(),
}
}
@@ -173,9 +170,6 @@ impl<T: TypedResourceData> BuiltInResource<T> {
let resource = make(&data_source.bytes);
Self {
id: id.as_ref().to_path_buf(),
resource_uuid: resource
.resource_uuid()
.expect("the resource must be in ok state"),
resource,
data_source: Some(data_source),
}
@@ -186,9 +180,6 @@ impl<T: TypedResourceData> BuiltInResource<T> {
Self {
id: id.as_ref().to_path_buf(),
data_source: None,
resource_uuid: resource
.resource_uuid()
.expect("the resource must be in ok state"),
resource,
}
}
@@ -204,7 +195,7 @@ impl<T: TypedResourceData> From<BuiltInResource<T>> for UntypedBuiltInResource {
Self {
id: value.id,
data_source: value.data_source,
resource_uuid: value.resource_uuid,
resource_uuid: value.resource.resource_uuid(),
resource: value.resource.into(),
}
}
+5 -4
View File
@@ -22,7 +22,6 @@
use crate::{collect_used_resources, state::ResourceState, untyped::UntypedResource};
use fxhash::FxHashSet;
use fyrox_core::SafeLock;
/// A node of [`ResourceDependencyGraph`].
pub struct ResourceGraphNode {
@@ -41,7 +40,7 @@ impl ResourceGraphNode {
// Look for dependent resources.
let mut dependent_resources = FxHashSet::default();
let header = resource.0.safe_lock();
let header = resource.lock();
if let ResourceState::Ok { ref data, .. } = header.state {
(**data).as_reflect(&mut |entity| {
collect_used_resources(entity, &mut dependent_resources);
@@ -116,6 +115,8 @@ impl ResourceDependencyGraph {
}
#[cfg(test)]
mod test {
use fyrox_core::Uuid;
use super::*;
use crate::untyped::ResourceKind;
@@ -132,11 +133,11 @@ mod test {
fn resource_graph_node_pretty_print() {
let mut s = String::new();
let mut node = ResourceGraphNode::new(&UntypedResource::new_pending(
Default::default(),
Uuid::new_v4(),
ResourceKind::External,
));
let node2 = ResourceGraphNode::new(&UntypedResource::new_pending(
Default::default(),
Uuid::new_v4(),
ResourceKind::External,
));
node.children.push(node2);
+73 -72
View File
@@ -27,6 +27,7 @@
use crate::{
core::{
combine_uuids,
parking_lot::MutexGuard,
reflect::prelude::*,
uuid::{uuid, Uuid},
@@ -38,17 +39,17 @@ use crate::{
};
use fxhash::FxHashSet;
pub use fyrox_core as core;
use fyrox_core::{combine_uuids, log::Log, SafeLock};
use std::path::PathBuf;
use std::{any::Any, fmt::Display};
use std::{
any::Any,
error::Error,
fmt::Display,
fmt::{Debug, Formatter},
future::Future,
hash::{Hash, Hasher},
marker::PhantomData,
ops::{Deref, DerefMut},
path::Path,
path::PathBuf,
pin::Pin,
task::{Context, Poll},
};
@@ -77,6 +78,10 @@ pub const SOUND_BUFFER_RESOURCE_UUID: Uuid = uuid!("f6a077b7-c8ff-4473-a95b-0289
pub const SHADER_RESOURCE_UUID: Uuid = uuid!("f1346417-b726-492a-b80f-c02096c6c019");
/// Type UUID of curve resource. It is defined here to load old versions of resources.
pub const CURVE_RESOURCE_UUID: Uuid = uuid!("f28b949f-28a2-4b68-9089-59c234f58b6b");
/// Type UUID of HRIR sphere resource. It is defined here to load old versions of resources.
pub const HRIR_SPHERE_RESOURCE_UUID: Uuid = uuid!("c92a0fa3-0ed3-49a9-be44-8f06271c6be2");
/// Type UUID of font resource. It is defined here to load old versions of resources.
pub const FONT_RESOURCE_UUID: Uuid = uuid!("692fec79-103a-483c-bb0b-9fc3a349cb48");
/// A trait for resource data.
pub trait ResourceData: Debug + Visit + Send + Reflect {
@@ -123,10 +128,27 @@ where
phantom: PhantomData<T>,
}
impl<'a, T> From<MutexGuard<'a, ResourceHeader>> for ResourceHeaderGuard<'a, T>
where
T: TypedResourceData,
{
fn from(guard: MutexGuard<'a, ResourceHeader>) -> Self {
Self {
guard,
phantom: PhantomData,
}
}
}
impl<T> ResourceHeaderGuard<'_, T>
where
T: TypedResourceData,
{
/// The UUID that universally identifies the resource.
pub fn resource_uuid(&self) -> Uuid {
self.guard.uuid
}
/// Returns resource kind of the locked resource.
pub fn kind(&self) -> ResourceKind {
self.guard.kind
@@ -158,14 +180,11 @@ where
/// locked resource is not in [`ResourceState::Ok`] or if its actual data does not match the
/// type of the resource.
pub fn data_ref_with_id(&self) -> Option<(&T, &Uuid)> {
if let ResourceState::Ok {
ref data,
ref resource_uuid,
} = self.guard.state
{
let uuid = &self.guard.uuid;
if let ResourceState::Ok { ref data } = self.guard.state {
(&**data as &dyn Any)
.downcast_ref::<T>()
.map(|typed| (typed, resource_uuid))
.map(|typed| (typed, uuid))
} else {
None
}
@@ -222,44 +241,8 @@ where
T: TypedResourceData,
{
fn visit(&mut self, name: &str, visitor: &mut Visitor) -> VisitResult {
// Untyped -> Typed compatibility. Useful in cases when a field was UntypedResource, and
// then it changed to the typed version. Strictly speaking, there's no real separation
// between typed and untyped resources on serialization/deserialization and this operation
// is valid until data types are matching.
if visitor.is_reading() {
let mut untyped = UntypedResource::default();
if untyped.visit(name, visitor).is_ok() {
let untyped_data_type = untyped.type_uuid();
if untyped_data_type == Some(<T as TypeUuidProvider>::type_uuid()) {
self.untyped = untyped;
return Ok(());
} else {
Log::err(format!(
"Unable to deserialize untyped resource into its typed \
version, because types do not match! Untyped resource has \
{:?} type, but the required type is {}",
untyped_data_type,
<T as TypeUuidProvider>::type_uuid(),
))
}
}
}
let mut region = visitor.enter_region(name)?;
// Backward compatibility.
if region.is_reading() {
let mut old_option_wrapper: Option<UntypedResource> = None;
if old_option_wrapper.visit("State", &mut region).is_ok() {
self.untyped = old_option_wrapper.unwrap();
} else {
self.untyped.visit("State", &mut region)?;
}
} else {
self.untyped.visit("State", &mut region)?;
}
Ok(())
self.untyped
.visit_with_type_uuid(name, Some(<T as TypeUuidProvider>::type_uuid()), visitor)
}
}
@@ -293,9 +276,9 @@ where
}
/// Creates new resource in pending state.
#[inline]
pub fn new_pending(path: PathBuf, kind: ResourceKind) -> Self {
pub fn new_pending(uuid: Uuid, kind: ResourceKind) -> Self {
Self {
untyped: UntypedResource::new_pending(path, kind),
untyped: UntypedResource::new_pending(uuid, kind),
phantom: PhantomData,
}
}
@@ -320,9 +303,9 @@ where
/// Creates new resource in error state.
#[inline]
pub fn new_load_error(kind: ResourceKind, error: LoadError) -> Self {
pub fn new_load_error(kind: ResourceKind, path: PathBuf, error: LoadError) -> Self {
Self {
untyped: UntypedResource::new_load_error(kind, error),
untyped: UntypedResource::new_load_error(kind, path, error),
phantom: PhantomData,
}
}
@@ -336,44 +319,37 @@ where
/// Locks internal mutex provides access to the state.
#[inline]
pub fn state(&self) -> ResourceHeaderGuard<'_, T> {
let guard = self.untyped.0.lock();
ResourceHeaderGuard {
guard,
phantom: Default::default(),
}
self.untyped.typed_lock()
}
/// Tries to lock internal mutex provides access to the state.
#[inline]
pub fn try_acquire_state(&self) -> Option<ResourceHeaderGuard<'_, T>> {
self.untyped.0.try_lock().map(|guard| ResourceHeaderGuard {
guard,
phantom: Default::default(),
})
self.untyped.try_typed_lock()
}
/// Locks the resource and provides access to its header. See [`ResourceHeader`] docs for more info.
#[inline]
pub fn header(&self) -> MutexGuard<'_, ResourceHeader> {
self.untyped.0.lock()
self.untyped.lock()
}
/// Returns true if the resource is still loading.
#[inline]
pub fn is_loading(&self) -> bool {
matches!(self.untyped.0.lock().state, ResourceState::Pending { .. })
self.untyped.is_loading()
}
/// Returns true if the resource is fully loaded and ready for use.
#[inline]
pub fn is_ok(&self) -> bool {
matches!(self.untyped.0.lock().state, ResourceState::Ok { .. })
self.untyped.is_ok()
}
/// Returns true if the resource is failed to load.
#[inline]
pub fn is_failed_to_load(&self) -> bool {
matches!(self.untyped.0.lock().state, ResourceState::LoadError { .. })
self.untyped.is_failed_to_load()
}
/// Returns exact amount of users of the resource.
@@ -385,19 +361,19 @@ where
/// Returns a pointer as numeric value which can be used as a hash.
#[inline]
pub fn key(&self) -> u64 {
self.untyped.key() as u64
self.untyped.key()
}
/// Returns kind of the resource.
/// Returns kind of the resource, if the resource is registered.
#[inline]
pub fn kind(&self) -> ResourceKind {
self.untyped.kind()
}
/// Tries to get a resource uuid (if any). Uuid is available only for fully loaded resources
/// (in [`ResourceState::Ok`] state).
/// The UUID of the resource. All resources must have a UUID, even if they are not loaded
/// because the UUID is how the resource manager knows the path to load from.
#[inline]
pub fn resource_uuid(&self) -> Option<Uuid> {
pub fn resource_uuid(&self) -> Uuid {
self.untyped.resource_uuid()
}
@@ -423,7 +399,7 @@ where
#[inline]
pub fn data_ref(&self) -> ResourceDataRef<'_, T> {
ResourceDataRef {
guard: self.untyped.0.safe_lock(),
guard: self.untyped.lock(),
phantom: Default::default(),
}
}
@@ -441,7 +417,7 @@ where
#[inline]
fn default() -> Self {
Self {
untyped: UntypedResource::new_ok(Default::default(), Default::default(), T::default()),
untyped: UntypedResource::new_ok(Uuid::new_v4(), ResourceKind::Embedded, T::default()),
phantom: Default::default(),
}
}
@@ -457,6 +433,15 @@ impl<T: Debug> Clone for Resource<T> {
}
}
impl<T> From<Uuid> for Resource<T>
where
T: TypedResourceData,
{
fn from(uuid: Uuid) -> Self {
UntypedResource::from(uuid).into()
}
}
impl<T> From<UntypedResource> for Resource<T>
where
T: TypedResourceData,
@@ -535,6 +520,12 @@ where
{
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self.guard.state {
ResourceState::Unloaded => {
write!(
f,
"Attempt to get reference to resource data while it is unloaded!"
)
}
ResourceState::Pending { .. } => {
write!(
f,
@@ -560,6 +551,12 @@ where
fn deref(&self) -> &Self::Target {
match self.guard.state {
ResourceState::Unloaded => {
panic!(
"Attempt to get reference to resource data while it is unloaded! Type {}",
std::any::type_name::<T>()
)
}
ResourceState::Pending { .. } => {
panic!(
"Attempt to get reference to resource data while it is loading! Type {}",
@@ -577,7 +574,7 @@ where
};
panic!("Attempt to get reference to resource data which failed to load! Type {}. Path: {path}. Error: {error:?}", std::any::type_name::<T>())
}
ResourceState::Ok { ref data, .. } => (&**data as &dyn Any)
ResourceState::Ok { ref data } => (&**data as &dyn Any)
.downcast_ref()
.expect("Type mismatch!"),
}
@@ -591,6 +588,9 @@ where
fn deref_mut(&mut self) -> &mut Self::Target {
let header = &mut *self.guard;
match header.state {
ResourceState::Unloaded => {
panic!("Attempt to get reference to resource data while it is unloaded!")
}
ResourceState::Pending { .. } => {
panic!("Attempt to get reference to resource data while it is loading!")
}
@@ -704,6 +704,7 @@ pub fn collect_used_resources(
#[cfg(test)]
mod tests {
use super::*;
use crate::{
io::{FsResourceIo, ResourceIo},
loader::{BoxedLoaderFuture, LoaderPayload, ResourceLoader, ResourceLoadersContainer},
File diff suppressed because it is too large Load Diff
+120 -81
View File
@@ -32,7 +32,7 @@ use crate::{
state::WakersList,
};
use fxhash::FxHashSet;
use fyrox_core::SafeLock;
use fyrox_core::{futures::executor::block_on, SafeLock};
use ron::ser::PrettyConfig;
use std::{
collections::BTreeMap,
@@ -77,7 +77,7 @@ impl RegistryContainerExt for RegistryContainer {
resource_io.load_file(path).await.and_then(|metadata| {
ron::de::from_bytes::<Self>(&metadata).map_err(|err| {
FileError::Custom(format!(
"Unable to deserialize the resource registry. Reason: {err:?}"
"Unable to deserialize the resource registry. Reason: {err}"
))
})
})
@@ -221,30 +221,49 @@ async fn make_relative_path_async<P: AsRef<Path>>(
/// [`std::mem::forget`] after you've finished working with the mutable reference.
pub struct ResourceRegistryRefMut<'a> {
registry: &'a mut ResourceRegistry,
changed: bool,
}
/// A value returned from an update operation to a [`ResourceRegistryRefMut`],
/// including a flag that indicates whether the operation changed the registry.
pub struct RegistryUpdate<T> {
/// True if the registry was changed.
pub changed: bool,
/// The value produced by the operation.
pub value: T,
}
impl ResourceRegistryRefMut<'_> {
/// Read the metadata from the file at the given path.
pub fn read_metadata(
&mut self,
path: PathBuf,
) -> Result<RegistryUpdate<ResourceMetadata>, FileError> {
let value = block_on(ResourceMetadata::load_from_file_async(
&append_extension(&path, ResourceMetadata::EXTENSION),
&*self.registry.io,
))?;
let changed = self.register(value.resource_id, path).changed;
Ok(RegistryUpdate { changed, value })
}
/// Writes the new metadata file for a resource at the given path and registers the resource
/// in the registry.
pub fn write_metadata(
&mut self,
uuid: Uuid,
path: impl AsRef<Path>,
) -> Result<Option<PathBuf>, FileError> {
path: PathBuf,
) -> Result<RegistryUpdate<Option<PathBuf>>, FileError> {
ResourceMetadata { resource_id: uuid }.save_sync(
&append_extension(path.as_ref(), ResourceMetadata::EXTENSION),
&append_extension(&path, ResourceMetadata::EXTENSION),
&*self.registry.io,
)?;
Ok(self.register(uuid, path.as_ref().to_path_buf()))
Ok(self.register(uuid, path))
}
/// Unregisters the resource at the given path (if any) from the registry and deletes its
/// associated metadata file.
pub fn remove_metadata(&mut self, path: impl AsRef<Path>) -> Result<(), FileError> {
if let Some(uuid) = self.registry.path_to_uuid(path.as_ref()) {
self.unregister(uuid);
if self.unregister_path(&path).is_some() {
let metadata_path = append_extension(path.as_ref(), ResourceMetadata::EXTENSION);
self.registry.io.delete_file_sync(&metadata_path)?;
@@ -252,38 +271,82 @@ impl ResourceRegistryRefMut<'_> {
Ok(())
} else {
Err(FileError::Custom(format!(
"The {} resource is not registered in the registry!",
path.as_ref().display()
"The {:?} resource is not registered in the registry!",
path.as_ref()
)))
}
}
/// Registers a new pair `UUID -> Path`.
pub fn register(&mut self, uuid: Uuid, path: PathBuf) -> Option<PathBuf> {
self.registry.paths.insert(uuid, path)
/// Registers a new pair `UUID -> Path`, and returns the former path for this UUID.
pub fn register(&mut self, uuid: Uuid, path: PathBuf) -> RegistryUpdate<Option<PathBuf>> {
if path.as_os_str().is_empty() {
panic!("Registering empty path.");
}
use std::collections::btree_map::Entry;
match self.registry.paths.entry(uuid) {
Entry::Vacant(entry) => {
info!("Registered: {uuid} -> {path:?}");
self.changed = true;
entry.insert(path);
RegistryUpdate {
changed: true,
value: None,
}
}
Entry::Occupied(mut entry) => {
let changed = entry.get() != &path;
if changed {
info!("Registry update: {uuid} -> {path:?}");
self.changed = true;
}
let value = Some(entry.insert(path));
RegistryUpdate { changed, value }
}
}
}
/// Unregisters a resource path with the given UUID.
/// Unregisters a resource path with the given UUID, and returns the former path for the Uuid, if it was registered.
pub fn unregister(&mut self, uuid: Uuid) -> Option<PathBuf> {
self.registry.paths.remove(&uuid)
let former = self.registry.paths.remove(&uuid);
if former.is_some() {
info!("Registry remove UUID: {uuid} -> {former:?}");
self.changed = true;
}
former
}
/// Unregisters a resource path.
pub fn unregister_path(&mut self, path: &Path) -> Option<Uuid> {
/// Unregisters a resource path, and returns the UUID if the given path was previously registered.
pub fn unregister_path(&mut self, path: impl AsRef<Path>) -> Option<Uuid> {
let path = path.as_ref();
let uuid = self.registry.path_to_uuid(path)?;
info!("Registry remove path: {uuid} -> {path:?}");
self.changed = true;
self.registry.paths.remove(&uuid);
Some(uuid)
}
/// Completely replaces the internal storage.
pub fn set_container(&mut self, registry_container: RegistryContainer) {
if self.registry.paths == registry_container {
return;
}
// Log the new registrations.
let current = &self.registry.paths;
for (uuid, path) in &registry_container {
if current.get(uuid) != Some(path) {
info!("Resource {path:?} was registered with {uuid} UUID.");
}
}
self.changed = true;
self.registry.paths = registry_container;
}
}
impl Drop for ResourceRegistryRefMut<'_> {
fn drop(&mut self) {
self.registry.save_sync();
if self.changed {
self.registry.save_sync();
}
}
}
@@ -291,8 +354,17 @@ impl Drop for ResourceRegistryRefMut<'_> {
/// `UUID -> Resource Path`.
#[derive(Clone)]
pub struct ResourceRegistry {
/// The path to which the resource registry is (or may) be saved.
path: PathBuf,
/// The UUIDs registered for each resource and the corresponding path of the resource.
/// These UUIDs should be read from the registry file or from the meta file associated with the resource.
/// They may also be randomly generated if a path is requested that does not have a meta file, in which
/// case the random UUID is dynamically added to the registry.
paths: RegistryContainer,
/// A shared flag that can be used to fetch the current status of a resource registry. This
/// supports the [`Future`] trait, which means that you can `.await` it in an async context to wait
/// until the registry is fully loaded (or failed to load). Any access to the registry in an async
/// context must be guarded with such `.await` call.
status: ResourceRegistryStatusFlag,
io: Arc<dyn ResourceIo>,
/// A list of folder that should be excluded when scanning the project folder for supported
@@ -387,16 +459,13 @@ impl ResourceRegistry {
match self.paths.save(&self.path, &*self.io).await {
Err(error) => {
err!(
"Unable to write the resource registry at the {} path! Reason: {:?}",
self.path.display(),
"Unable to write the resource registry at the {:?} path! Reason: {:?}",
self.path,
error
)
}
Ok(_) => {
info!(
"The registry was successfully saved to {}!",
self.path.display()
)
info!("The registry was successfully saved to {:?}!", self.path)
}
}
}
@@ -425,7 +494,10 @@ impl ResourceRegistry {
/// Begins registry modification. See [`ResourceRegistryRefMut`] docs for more info.
pub fn modify(&mut self) -> ResourceRegistryRefMut<'_> {
ResourceRegistryRefMut { registry: self }
ResourceRegistryRefMut {
changed: false,
registry: self,
}
}
/// Tries to get a path associated with the given resource UUID.
@@ -452,7 +524,7 @@ impl ResourceRegistry {
/// Searches for supported resources starting from the given path and builds a mapping `UUID -> Path`.
/// If a supported resource does not have a metadata file besides it, this method will automatically
/// add it with a new UUID and add the resource to the registry.
/// create the metadata file with a new UUID and add the resource to the registry.
///
/// This method does **not** load any resource, instead it checks extension of every file in the
/// given directory, and if there's a loader for it, "remember" the resource.
@@ -486,21 +558,14 @@ impl ResourceRegistry {
Ok(path) => path,
Err(err) => {
warn!(
"Unable to make relative path from {} path! The resource won't be \
included in the registry! Reason: {:?}",
fs_path.display(),
err
"Unable to make relative path from {fs_path:?} path! The resource won't be \
included in the registry! Reason: {err:?}",
);
continue;
}
};
if excluded_folders.contains(&path) {
warn!(
"Skipping {} folder, because it is in the excluded folders list!",
path.display()
);
continue;
}
@@ -514,60 +579,34 @@ impl ResourceRegistry {
}
if !loaders.safe_lock().is_supported_resource(&path) {
if path
.extension()
.is_some_and(|ext| ext != "meta" && ext != "registry")
{
info!(
"Skipping {} file, because there's no loader for it.",
path.display()
);
}
continue;
}
let metadata_path = append_extension(&path, ResourceMetadata::EXTENSION);
let metadata =
match ResourceMetadata::load_from_file_async(&metadata_path, &*resource_io).await {
Ok(metadata) => metadata,
Err(err) => {
warn!(
"Unable to load metadata for {} resource. Reason: {:?}, The metadata \
let metadata = match ResourceMetadata::load_from_file_async(
&metadata_path,
&*resource_io,
)
.await
{
Ok(metadata) => metadata,
Err(err) => {
warn!(
"Unable to load metadata for {path:?} resource. Reason: {err}. The metadata \
file will be added/recreated, do **NOT** delete it! Add it to the \
version control!",
path.display(),
err
);
let new_metadata = ResourceMetadata::new_with_random_id();
if let Err(err) =
new_metadata.save_async(&metadata_path, &*resource_io).await
{
warn!(
"Unable to save resource {} metadata. Reason: {:?}",
path.display(),
err
);
}
new_metadata
let new_metadata = ResourceMetadata::new_with_random_id();
if let Err(err) = new_metadata.save_async(&metadata_path, &*resource_io).await {
warn!("Unable to save resource {path:?} metadata. Reason: {err}");
}
};
new_metadata
}
};
if container
.insert(metadata.resource_id, path.clone())
.is_some()
{
warn!(
"Resource UUID collision occurred for {} resource!",
path.display()
);
if let Some(former) = container.insert(metadata.resource_id, path.clone()) {
warn!("Resource UUID collision between {path:?} and {former:?}");
}
info!(
"Resource {} was registered with {} UUID.",
path.display(),
metadata.resource_id
);
}
container
+70 -147
View File
@@ -20,16 +20,10 @@
//! A module that handles resource states.
use crate::untyped::ResourceKind;
use crate::{
core::{reflect::prelude::*, uuid::Uuid, visitor::prelude::*},
manager::ResourceManager,
ResourceData, ResourceLoadError, TypedResourceData,
};
use crate::{core::reflect::prelude::*, ResourceData, ResourceLoadError, TypedResourceData};
use fyrox_core::reflect::ReflectHandle;
use fyrox_core::warn;
use std::any::{Any, TypeId};
use std::fmt::Display;
use std::fmt::{Debug, Display};
use std::path::PathBuf;
use std::{
ops::{Deref, DerefMut},
@@ -277,18 +271,15 @@ impl Clone for ResourceDataWrapper {
/// to get the UUID earlier: the UUID is stored in a metadata file which exists only if the resource
/// is present. It is somewhat possible to get a UUID when a resource is failed to load, but not in
/// 100% cases.
#[derive(Debug, Clone, Reflect)]
#[derive(Clone, Reflect)]
pub enum ResourceState {
/// Resource is not loaded. In some situations, having a handle to a resource
/// can be sufficient even without the resource's data.
Unloaded,
/// Resource is loading from external resource or in the queue to load.
Pending {
/// List of wakers to wake future when resource is fully loaded.
wakers: WakersList,
/// A resource path (explicit or implicit). It is used at the loading stage to get a
/// real path in the file system. Since resource registry loading is async (especially
/// on WASM), it is impossible to fetch the uuid by path immediately. Instead, the resource
/// system offloads this task to resource loading tasks, which are able to wait until the
/// registry is fully loaded.
path: PathBuf,
},
/// An error has occurred during the load.
LoadError {
@@ -300,19 +291,45 @@ pub enum ResourceState {
},
/// Actual resource data when it is fully loaded.
Ok {
/// Unique id of the resource.
resource_uuid: Uuid,
/// Actual data of the resource.
data: ResourceDataWrapper,
},
}
impl Debug for ResourceState {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Unloaded => write!(f, "Unloaded"),
Self::Pending { .. } => write!(f, "Pending"),
Self::LoadError { path, error } => write!(f, "LoadError {:?}: {:?}", error, path),
Self::Ok { .. } => write!(f, "Ok"),
}
}
}
impl Display for ResourceState {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Unloaded => write!(f, "Unloaded"),
Self::Pending { .. } => write!(f, "Pending"),
Self::LoadError { path, error } => {
if path.as_os_str().is_empty() {
write!(f, "{} (for unknown path)", error)
} else {
write!(f, "{} (for {:?})", error, path)
}
}
Self::Ok { .. } => write!(f, "Ok"),
}
}
}
impl Default for ResourceState {
fn default() -> Self {
Self::LoadError {
path: Default::default(),
error: Default::default(),
}
ResourceState::new_load_error(
Default::default(),
LoadError::new("Default resource state of unknown type."),
)
}
}
@@ -325,78 +342,11 @@ impl Drop for ResourceState {
}
impl ResourceState {
pub(crate) fn visit(
&mut self,
kind: ResourceKind,
name: &str,
visitor: &mut Visitor,
) -> VisitResult {
if visitor.is_reading() {
let mut type_uuid = Uuid::default();
type_uuid.visit("TypeUuid", visitor)?;
let mut resource_uuid = Uuid::default();
if resource_uuid.visit("ResourceUuid", visitor).is_err() {
warn!(
"A resource of type {type_uuid} has no uuid! It looks like a resource in \
the old format; trying to read it..."
);
}
let resource_manager = visitor.blackboard.get::<ResourceManager>().expect(
"Resource data constructor container must be \
provided when serializing resources!",
);
let resource_manager_state = resource_manager.state();
if let Some(mut instance) = resource_manager_state
.constructors_container
.try_create(&type_uuid)
{
drop(resource_manager_state);
if kind == ResourceKind::Embedded {
instance.visit(name, visitor)?;
}
*self = Self::Ok {
resource_uuid,
data: ResourceDataWrapper(instance),
};
} else {
return Err(VisitError::User(format!(
"There's no constructor registered for type {type_uuid}!"
)));
}
Ok(())
} else if let Self::Ok {
resource_uuid,
data,
} = self
{
resource_uuid.visit("ResourceUuid", visitor)?;
let mut type_uuid = data.type_uuid();
type_uuid.visit("TypeUuid", visitor)?;
if kind == ResourceKind::Embedded {
data.visit(name, visitor)?;
}
Ok(())
} else {
// Do not save other variants, because they're needed only for runtime purposes.
Ok(())
}
}
/// Creates new resource in pending state.
#[inline]
pub fn new_pending(path: PathBuf) -> Self {
pub fn new_pending() -> Self {
Self::Pending {
wakers: Default::default(),
path,
}
}
@@ -408,42 +358,34 @@ impl ResourceState {
/// Creates new resource in [`ResourceState::Ok`] state.
#[inline]
pub fn new_ok<T: ResourceData>(resource_uuid: Uuid, data: T) -> Self {
pub fn new_ok<T: ResourceData>(data: T) -> Self {
Self::Ok {
resource_uuid,
data: ResourceDataWrapper(Box::new(data)),
}
}
/// Creates a new resource in [`ResourceState::Ok`] state using arbitrary data.
#[inline]
pub fn new_ok_untyped(resource_uuid: Uuid, data: Box<dyn ResourceData>) -> Self {
pub fn new_ok_untyped(data: Box<dyn ResourceData>) -> Self {
Self::Ok {
resource_uuid,
data: ResourceDataWrapper(data),
}
}
/// Tries to get a resource uuid. The uuid is available only for resource in [`ResourceState::Ok`]
/// state.
#[inline]
pub fn resource_uuid(&self) -> Option<Uuid> {
match self {
ResourceState::Ok { resource_uuid, .. } => Some(*resource_uuid),
_ => None,
}
}
/// Checks whether the resource is still loading or not.
pub fn is_loading(&self) -> bool {
matches!(self, ResourceState::Pending { .. })
}
/// Checks whether the resource is loaded without errors.
pub fn is_ok(&self) -> bool {
matches!(self, ResourceState::Ok { .. })
}
/// Switches the internal state of the resource to [`ResourceState::Pending`].
pub fn switch_to_pending_state(&mut self, path: PathBuf) {
pub fn switch_to_pending_state(&mut self) {
*self = ResourceState::Pending {
wakers: Default::default(),
path,
};
}
@@ -456,7 +398,7 @@ impl ResourceState {
let wakers = if let ResourceState::Pending { ref mut wakers, .. } = self {
std::mem::take(wakers)
} else {
Default::default()
WakersList::default()
};
*self = state;
@@ -467,9 +409,8 @@ impl ResourceState {
}
/// Changes internal state to [`ResourceState::Ok`]
pub fn commit_ok<T: ResourceData>(&mut self, resource_uuid: Uuid, data: T) {
pub fn commit_ok<T: ResourceData>(&mut self, data: T) {
self.commit(ResourceState::Ok {
resource_uuid,
data: ResourceDataWrapper(Box::new(data)),
})
}
@@ -485,7 +426,9 @@ impl ResourceState {
/// Tries to get the resource data. Will fail if the resource is not in [`ResourceState::Ok`].
pub fn data_ref(&self) -> Option<&ResourceDataWrapper> {
match self {
ResourceState::Pending { .. } | ResourceState::LoadError { .. } => None,
ResourceState::Pending { .. }
| ResourceState::LoadError { .. }
| ResourceState::Unloaded => None,
ResourceState::Ok { data, .. } => Some(data),
}
}
@@ -493,7 +436,9 @@ impl ResourceState {
/// Tries to get the resource data. Will fail if the resource is not in [`ResourceState::Ok`].
pub fn data_mut(&mut self) -> Option<&mut ResourceDataWrapper> {
match self {
ResourceState::Pending { .. } | ResourceState::LoadError { .. } => None,
ResourceState::Pending { .. }
| ResourceState::LoadError { .. }
| ResourceState::Unloaded => None,
ResourceState::Ok { data, .. } => Some(data),
}
}
@@ -502,7 +447,9 @@ impl ResourceState {
/// [`ResourceState::Ok`].
pub fn data_ref_of_type<T: TypedResourceData>(&self) -> Option<&T> {
match self {
ResourceState::Pending { .. } | ResourceState::LoadError { .. } => None,
ResourceState::Pending { .. }
| ResourceState::LoadError { .. }
| ResourceState::Unloaded => None,
ResourceState::Ok { data, .. } => (&**data as &dyn Any).downcast_ref::<T>(),
}
}
@@ -511,7 +458,9 @@ impl ResourceState {
/// [`ResourceState::Ok`].
pub fn data_mut_of_type<T: TypedResourceData>(&mut self) -> Option<&mut T> {
match self {
ResourceState::Pending { .. } | ResourceState::LoadError { .. } => None,
ResourceState::Pending { .. }
| ResourceState::LoadError { .. }
| ResourceState::Unloaded => None,
ResourceState::Ok { data, .. } => (&mut **data as &mut dyn Any).downcast_mut::<T>(),
}
}
@@ -521,6 +470,8 @@ impl ResourceState {
mod test {
use fyrox_core::{
reflect::{FieldRef, Reflect},
uuid::Uuid,
visitor::prelude::*,
TypeUuidProvider,
};
use std::error::Error;
@@ -557,7 +508,7 @@ mod test {
#[test]
fn resource_state_new_pending() {
let state = ResourceState::new_pending(Default::default());
let state = ResourceState::new_pending();
assert!(matches!(state, ResourceState::Pending { .. }));
assert!(state.is_loading());
@@ -573,8 +524,7 @@ mod test {
#[test]
fn resource_state_new_ok() {
let uuid = Uuid::new_v4();
let state = ResourceState::new_ok(uuid, Stub {});
let state = ResourceState::new_ok(Stub {});
assert!(matches!(state, ResourceState::Ok { .. }));
assert!(!state.is_loading());
}
@@ -582,48 +532,21 @@ mod test {
#[test]
fn resource_state_switch_to_pending_state() {
// from Ok
let mut state = ResourceState::new_ok(Uuid::new_v4(), Stub {});
state.switch_to_pending_state(Default::default());
let mut state = ResourceState::new_ok(Stub {});
state.switch_to_pending_state();
assert!(matches!(state, ResourceState::Pending { .. }));
// from LoadError
let mut state = ResourceState::new_load_error(Default::default(), Default::default());
state.switch_to_pending_state(Default::default());
state.switch_to_pending_state();
assert!(matches!(state, ResourceState::Pending { .. }));
// from Pending
let mut state = ResourceState::new_pending(Default::default());
state.switch_to_pending_state(Default::default());
let mut state = ResourceState::new_pending();
state.switch_to_pending_state();
assert!(matches!(state, ResourceState::Pending { .. }));
}
#[test]
fn visit_for_resource_state() {
// Visit Pending
let mut state = ResourceState::new_pending(Default::default());
let mut visitor = Visitor::default();
assert!(state
.visit(ResourceKind::External, "name", &mut visitor)
.is_ok());
// Visit LoadError
let mut state = ResourceState::new_load_error(Default::default(), Default::default());
let mut visitor = Visitor::default();
assert!(state
.visit(ResourceKind::External, "name", &mut visitor)
.is_ok());
// Visit Ok
let mut state = ResourceState::new_ok(Uuid::new_v4(), Stub {});
let mut visitor = Visitor::default();
assert!(state
.visit(ResourceKind::External, "name", &mut visitor)
.is_ok());
}
}
+495 -246
View File
@@ -24,15 +24,19 @@ use crate::state::ResourceDataWrapper;
use crate::{
core::{
math::curve::Curve, parking_lot::Mutex, reflect::prelude::*, uuid, uuid::Uuid,
visitor::prelude::*, visitor::RegionGuard, TypeUuidProvider,
visitor::prelude::*, TypeUuidProvider,
},
manager::ResourceManager,
registry::ResourceRegistryStatus,
state::{LoadError, ResourceState},
Resource, ResourceData, ResourceLoadError, TypedResourceData, CURVE_RESOURCE_UUID,
MODEL_RESOURCE_UUID, SHADER_RESOURCE_UUID, SOUND_BUFFER_RESOURCE_UUID, TEXTURE_RESOURCE_UUID,
};
use fyrox_core::err;
use crate::{ResourceHeaderGuard, FONT_RESOURCE_UUID, HRIR_SPHERE_RESOURCE_UUID};
use fxhash::FxHasher64;
use fyrox_core::io::FileError;
use fyrox_core::log::Log;
use fyrox_core::parking_lot::MutexGuard;
use fyrox_core::SafeLock;
use std::fmt::Write;
use std::{
error::Error,
@@ -47,11 +51,167 @@ use std::{
task::{Context, Poll},
};
const MISSING_RESOURCE_MANAGER: &str =
"Resource data constructor container must be provided when serializing resources!";
/// The UUIDs for resources that cannot be visited as embedded data because none of their
/// fields are visited. This is used by `LegacyHeader` to avoid mistakenly interpretting
/// a visitor node as an embedded resource.
/// Being on this list does *not* prevent future versions of these resources from being
/// embedded. It is purely a heuristic to assist with reading legacy resources.
const INVALID_EMBEDDED_RESOURCES: &[Uuid] = &[
SOUND_BUFFER_RESOURCE_UUID,
MODEL_RESOURCE_UUID,
HRIR_SPHERE_RESOURCE_UUID,
FONT_RESOURCE_UUID,
];
#[derive(Default, Debug, Visit, Clone, PartialEq, Eq, Hash)]
enum OldResourceKind {
#[default]
Embedded,
External(PathBuf),
}
#[derive(Debug)]
enum LegacyHeader {
Path(PathBuf),
Uuid(Uuid),
Data(Box<dyn ResourceData>),
}
impl LegacyHeader {
fn visit_path(&mut self, name: &str, visitor: &mut Visitor) -> VisitResult {
let mut path = PathBuf::default();
path.visit(name, visitor)?;
if path.as_os_str().is_empty() {
return Err(VisitError::FileLoadError(FileError::Custom(
"Empty path".to_string(),
)));
}
*self = Self::Path(path);
Ok(())
}
fn visit_details(&mut self, type_uuid: Uuid, visitor: &mut Visitor) -> VisitResult {
let mut region = visitor.enter_region("Details")?;
if type_uuid == SOUND_BUFFER_RESOURCE_UUID {
let mut sound_region = region.enter_region("0")?;
self.visit_path("Path", &mut sound_region)
} else {
self.visit_path("Path", &mut region)
}
}
fn take_data(&mut self, uuid: Uuid) -> Option<Box<dyn ResourceData>> {
if let Self::Data(data) = std::mem::replace(self, Self::Uuid(uuid)) {
Some(data)
} else {
None
}
}
fn is_valid_embedded_type_uuid(uuid: Uuid) -> bool {
INVALID_EMBEDDED_RESOURCES.contains(&uuid)
}
}
impl Default for LegacyHeader {
fn default() -> Self {
Self::Path(PathBuf::default())
}
}
impl Visit for LegacyHeader {
fn visit(&mut self, name: &str, visitor: &mut Visitor) -> VisitResult {
assert!(visitor.is_reading());
let mut region = visitor.enter_region(name)?;
let mut type_uuid = Uuid::default();
if type_uuid.visit("TypeUuid", &mut region).is_err() {
// We might be reading the old version, try to guess an actual type uuid by
// the inner content of the resource data.
type_uuid = guess_uuid(&mut region);
};
let resource_manager = region
.blackboard
.get::<ResourceManager>()
.expect(MISSING_RESOURCE_MANAGER);
let resource_manager_state = resource_manager.state();
let Some(mut instance) = resource_manager_state
.constructors_container
.try_create(&type_uuid)
else {
return Err(VisitError::User(format!(
"There's no constructor registered for type {type_uuid}!"
)));
};
drop(resource_manager_state);
let mut id: u32 = 0;
if id.visit("Id", &mut region).is_ok() {
// We're interested only in embedded resources.
if id == 2 {
let result = self.visit_details(type_uuid, &mut region);
if let Err(err0) = result {
if Self::is_valid_embedded_type_uuid(type_uuid) {
let result = instance.visit("Details", &mut region);
if let Err(err1) = result {
let result = instance.visit("State", &mut region);
if let Err(err2) = result {
return Err(err0.multiple(err1).multiple(err2));
}
}
*self = Self::Data(instance);
Ok(())
} else {
Err(err0)
}
} else {
result
}
} else {
Err(VisitError::User("Old resource".into()))
}
} else {
let mut uuid = Uuid::default();
if uuid.visit("ResourceUuid", &mut region).is_ok() && !uuid.is_nil() {
*self = Self::Uuid(uuid);
return Ok(());
}
let mut old_kind = OldResourceKind::Embedded;
old_kind.visit("Kind", &mut region)?;
match old_kind {
OldResourceKind::External(path) => {
if path.as_os_str().is_empty() {
return Err(VisitError::FileLoadError(FileError::Custom(
"Empty path".to_string(),
)));
}
*self = Self::Path(path);
}
OldResourceKind::Embedded => {
instance.visit("State", &mut region)?;
*self = Self::Data(instance);
}
}
Ok(())
}
}
}
// Heuristic function to guess resource uuid based on inner content of a resource.
fn guess_uuid(region: &mut RegionGuard) -> Uuid {
fn guess_uuid(region: &mut Visitor) -> Uuid {
assert!(region.is_reading());
let mut region = region.enter_region("Details").unwrap();
let guard = region.enter_region("Details");
let mut region = match guard {
Ok(region) => region,
Err(ref err) => {
Log::err(err.to_string());
drop(guard);
Log::info(region.debug());
return TEXTURE_RESOURCE_UUID;
}
};
let mut mip_count = 0u32;
if mip_count.visit("MipCount", &mut region).is_ok() {
@@ -108,13 +268,6 @@ pub enum ResourceKind {
External,
}
#[derive(Default, Debug, Visit, Clone, PartialEq, Eq, Hash)]
enum OldResourceKind {
#[default]
Embedded,
External(PathBuf),
}
impl ResourceKind {
/// Switches the resource kind to [`Self::External`].
#[inline]
@@ -158,23 +311,56 @@ impl Display for ResourceKind {
/// its kind, etc.
#[derive(Reflect, Clone, Debug)]
pub struct ResourceHeader {
/// The unique identifier of this resource.
pub uuid: Uuid,
/// Kind of the resource. See [`ResourceKind`] for more info.
pub kind: ResourceKind,
/// Actual state of the resource. See [`ResourceState`] for more info.
pub state: ResourceState,
}
// TODO: Remove in Fyrox 1.0
old_format_path: Option<PathBuf>,
impl Default for ResourceHeader {
fn default() -> Self {
Self {
uuid: Uuid::new_v4(),
kind: Default::default(),
state: Default::default(),
}
}
}
impl From<Uuid> for ResourceHeader {
fn from(uuid: Uuid) -> Self {
Self {
uuid,
kind: ResourceKind::External,
state: ResourceState::Unloaded,
}
}
}
impl ResourceHeader {
/// The type of the data, if this resource is Ok.
pub fn type_uuid(&self) -> Option<Uuid> {
if let ResourceState::Ok { data } = &self.state {
Some(data.type_uuid())
} else {
None
}
}
}
impl Display for ResourceHeader {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
Display::fmt(&self.uuid, f)?;
f.write_char(':')?;
match self.kind {
ResourceKind::Embedded => f.write_str("Embed")?,
ResourceKind::External => f.write_str("Extern")?,
}
f.write_char(':')?;
match &self.state {
ResourceState::Unloaded => f.write_str("Unloaded"),
ResourceState::Pending { .. } => f.write_str("Pending"),
ResourceState::LoadError { path, error } => write!(f, "Error({path:?}, {error})"),
ResourceState::Ok { .. } => f.write_str("Ok"),
@@ -185,87 +371,44 @@ impl Display for ResourceHeader {
impl Visit for ResourceHeader {
fn visit(&mut self, name: &str, visitor: &mut Visitor) -> VisitResult {
let mut region = visitor.enter_region(name)?;
if region.is_reading() {
let mut id: u32 = 0;
if id.visit("Id", &mut region).is_ok() {
// Reading old version, convert it to the new.
let mut type_uuid = Uuid::default();
if type_uuid.visit("TypeUuid", &mut region).is_err() {
// We might be reading the old version, try to guess an actual type uuid by
// the inner content of the resource data.
type_uuid = guess_uuid(&mut region);
};
// We're interested only in embedded resources.
if id == 2 {
let resource_manager = region.blackboard.get::<ResourceManager>().expect(
"Resource data constructor container must be \
provided when serializing resources!",
);
let resource_manager_state = resource_manager.state();
if let Some(mut instance) = resource_manager_state
.constructors_container
.try_create(&type_uuid)
{
drop(resource_manager_state);
if let Ok(mut details_region) = region.enter_region("Details") {
if type_uuid == SOUND_BUFFER_RESOURCE_UUID {
let mut sound_region = details_region.enter_region("0")?;
let mut path = PathBuf::new();
path.visit("Path", &mut sound_region).unwrap();
self.kind.make_external();
self.old_format_path = Some(path);
} else {
let mut path = PathBuf::new();
path.visit("Path", &mut details_region).unwrap();
self.kind.make_external();
self.old_format_path = Some(path);
}
}
instance.visit("Details", &mut region)?;
self.state = ResourceState::Ok {
data: ResourceDataWrapper(instance),
// The old format does not contain an uuid.
resource_uuid: Uuid::nil(),
};
return Ok(());
} else {
return Err(VisitError::User(format!(
"There's no constructor registered for type {type_uuid}!"
)));
}
} else {
self.state = ResourceState::LoadError {
path: Default::default(),
error: LoadError::new("Old resource"),
};
self.kind = ResourceKind::Embedded;
let mut actual_type_uuid = Uuid::default();
actual_type_uuid.visit("TypeUuid", &mut region)?;
let resource_manager = region
.blackboard
.get::<ResourceManager>()
.expect(MISSING_RESOURCE_MANAGER)
.clone();
let Some(mut data) = resource_manager
.state()
.constructors_container
.try_create(&actual_type_uuid)
else {
return Err(VisitError::User(format!(
"There's no constructor registered for type {actual_type_uuid}!"
)));
};
data.visit("Data", &mut region)?;
self.state = ResourceState::Ok {
data: ResourceDataWrapper(data),
};
Ok(())
} else {
match (&self.kind, &mut self.state) {
(ResourceKind::Embedded, ResourceState::Ok { data }) => {
let mut type_uuid = data.type_uuid();
type_uuid.visit("TypeUuid", &mut region)?;
data.visit("Data", &mut region)
}
return Ok(());
} else {
let mut old_kind = OldResourceKind::Embedded;
if old_kind.visit("Kind", &mut region).is_ok() {
if let OldResourceKind::External(path) = old_kind {
self.old_format_path = Some(path);
}
(ResourceKind::External, _) => {
Err(VisitError::User("Writing an external resource".into()))
}
_ => Err(VisitError::User(
"Writing an embedded resource that is not ok.".into(),
)),
}
}
self.kind.visit("Kind", &mut region)?;
if !region.is_reading() || region.find_field("TypeUuid").is_some() {
self.state.visit(self.kind, "State", &mut region)?;
}
Ok(())
}
}
@@ -308,101 +451,21 @@ impl Visit for ResourceHeader {
/// that the resource is in default state. This is a trade-off to prevent wrapping internals into
/// `Option`, that in some cases could lead to convoluted code with lots of `unwrap`s and state
/// assumptions.
#[derive(Clone, Reflect, TypeUuidProvider)]
#[derive(Default, Clone, Reflect, TypeUuidProvider)]
#[type_uuid(id = "21613484-7145-4d1c-87d8-62fa767560ab")]
pub struct UntypedResource(pub Arc<Mutex<ResourceHeader>>);
impl Visit for UntypedResource {
fn visit(&mut self, name: &str, visitor: &mut Visitor) -> VisitResult {
self.0.visit(name, visitor)?;
// Try to restore the shallow handle on deserialization for external resources.
if visitor.is_reading() {
let inner_lock = self.0.lock();
if let (ResourceKind::External, &ResourceState::Ok { resource_uuid, .. }) =
(inner_lock.kind, &inner_lock.state)
{
let resource_manager = visitor
.blackboard
.get::<ResourceManager>()
.expect("Resource manager must be available when deserializing resources!");
let status_flag = resource_manager
.state()
.resource_registry
.lock()
.status_flag();
// The resource registry **MUST** be loaded at this stage. This assertion should never
// trigger in normal circumstances, because resource references normally stored inside
// some other resources and resource loading is guarded with `registry.await` that
// waits until the registry is fully loaded.
//
// There are two major ways that will trigger this assertion:
// 1) The registry is not loaded.
// 2) You're trying to deserialize the resource handle manually without a proper
// environment.
assert_eq!(
status_flag.status(),
ResourceRegistryStatus::Loaded,
"Resource registry is not loaded"
);
let path = match inner_lock.old_format_path {
None => {
let rm_state = resource_manager.state();
let registry_lock = rm_state.resource_registry.lock();
let path = registry_lock.uuid_to_path_buf(resource_uuid);
// Minimize duration of lock on registry.
drop(registry_lock);
match path {
Some(path) => Some(path),
None => {
// As a last resort - try to find a built-in resource with this id.
// Beware that find_by_uuid causes resource header locking.
rm_state
.built_in_resources
.find_by_uuid(resource_uuid)
.map(|r| r.id.clone())
}
}
}
Some(ref path) => Some(path.clone()),
};
if let Some(path) = path {
drop(inner_lock);
self.0 = resource_manager.request_untyped(path).0;
} else {
let kind = inner_lock.kind;
drop(inner_lock);
let message = format!(
"Unable to restore resource handle of {resource_uuid} uuid!\
The uuid wasn't found in the resource registry!"
);
err!("{}", message);
*self = UntypedResource::new_load_error(kind, LoadError::new(message));
}
let result = self.visit_with_type_uuid(name, None, visitor);
if let Err(err) = &result {
Log::err(format!("Resource error for untyped resource: {err}"));
if let Ok(region) = visitor.enter_region(name) {
region.debug();
}
self.commit_error(PathBuf::default(), err.to_string());
}
Ok(())
}
}
impl Default for UntypedResource {
fn default() -> Self {
Self(Arc::new(Mutex::new(ResourceHeader {
kind: Default::default(),
state: ResourceState::new_load_error(
Default::default(),
LoadError::new("Default resource state of unknown type."),
),
old_format_path: None,
})))
result
}
}
@@ -418,13 +481,13 @@ impl Display for UntypedResource {
impl Debug for UntypedResource {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
writeln!(f, "Resource")
write!(f, "UntypedResource({self})")
}
}
impl PartialEq for UntypedResource {
fn eq(&self, other: &Self) -> bool {
std::ptr::eq(&*self.0, &*other.0)
Arc::ptr_eq(&self.0, &other.0)
}
}
@@ -432,18 +495,177 @@ impl Eq for UntypedResource {}
impl Hash for UntypedResource {
fn hash<H: Hasher>(&self, state: &mut H) {
state.write_u64(&*self.0 as *const _ as u64)
Arc::as_ptr(&self.0).hash(state);
}
}
impl From<Uuid> for UntypedResource {
fn from(uuid: Uuid) -> Self {
ResourceHeader::from(uuid).into()
}
}
impl From<ResourceHeader> for UntypedResource {
fn from(header: ResourceHeader) -> Self {
Self(Arc::new(Mutex::new(header)))
}
}
impl UntypedResource {
/// Creates new untyped resource in pending state using the given path and type uuid.
pub fn new_pending(path: PathBuf, kind: ResourceKind) -> Self {
Self(Arc::new(Mutex::new(ResourceHeader {
/// Visit this resource handle with the given UUID for the type of the resource data.
pub fn visit_with_type_uuid(
&mut self,
name: &str,
type_uuid: Option<Uuid>,
visitor: &mut Visitor,
) -> VisitResult {
let mut region = visitor.enter_region(name)?;
if region.is_reading() {
let mut uuid = Uuid::default();
match uuid.visit("Uuid", &mut region) {
Ok(()) => {
self.read_visit(uuid, type_uuid, &mut region)?;
drop(region);
}
Err(_) => {
drop(region);
self.legacy_visit(name, visitor)?;
}
}
let resource_manager = visitor
.blackboard
.get::<ResourceManager>()
.expect("Resource manager must be available when deserializing resources!")
.clone();
resource_manager.state().request_resource(self);
Ok(())
} else {
self.resource_uuid().visit("Uuid", &mut region)?;
let header_guard = self.lock();
let is_embedded = header_guard.kind.is_embedded();
let is_ok = header_guard.state.is_ok();
drop(header_guard);
if is_embedded && is_ok {
self.0.visit("Embedded", &mut region)
} else if is_embedded {
true.visit("Default", &mut region)
} else {
Ok(())
}
}
}
fn legacy_visit(&mut self, name: &str, visitor: &mut Visitor) -> VisitResult {
let mut header: Arc<Mutex<LegacyHeader>> = Default::default();
let result = header.visit(name, visitor);
if let Err(err1) = result {
header = Arc::default();
if let Ok(mut region) = visitor.enter_region(name) {
let mut region = if region.has_region("Value") {
region.enter_region("Value").unwrap()
} else {
region
};
let result = header.visit("State", &mut region);
if let Err(err2) = result {
if let Ok(mut region) = region.enter_region("State") {
let result = header.visit("Data", &mut region);
if let Err(err3) = result {
return Err(err1.multiple(err2).multiple(err3));
}
} else {
return Err(err1.multiple(err2));
}
}
} else {
return Err(err1);
}
}
let resource_manager = visitor
.blackboard
.get::<ResourceManager>()
.expect("Resource manager must be available when deserializing resources!")
.clone();
let mut state = resource_manager.state();
let mut header = header.try_lock().expect("header locked");
match *header {
LegacyHeader::Path(ref path) => {
Log::info(format!("Requesting {path:?}"));
*self = state.request(path);
}
LegacyHeader::Uuid(uuid) => {
*self = uuid.into();
}
LegacyHeader::Data(_) => {
let uuid = Uuid::new_v4();
let data = header.take_data(uuid).unwrap();
*self = UntypedResource::new_ok_untyped(uuid, ResourceKind::Embedded, data);
}
}
Ok(())
}
fn read_visit(
&mut self,
resource_uuid: Uuid,
type_uuid: Option<Uuid>,
visitor: &mut Visitor,
) -> VisitResult {
let mut is_default = false;
if is_default.visit("Default", visitor).is_ok() && is_default {
*self = Self::default();
self.lock().uuid = resource_uuid;
Ok(())
} else if visitor.has_region("Embedded") {
self.0.visit("Embedded", visitor)?;
self.lock().uuid = resource_uuid;
if let (Some(expected), Some(actual)) = (type_uuid, self.lock().type_uuid()) {
if expected != actual {
return Err(format!(
"Unable to deserialize untyped resource into its typed \
version, because types do not match! Untyped resource has \
{actual} type, but the required type is {expected}.",
)
.into());
}
}
Ok(())
} else {
*self = resource_uuid.into();
Ok(())
}
}
/// Lock the shared header of this resource.
pub fn typed_lock<T: TypedResourceData>(&self) -> ResourceHeaderGuard<'_, T> {
self.lock().into()
}
/// Lock the shared header of this resource.
pub fn lock(&self) -> MutexGuard<'_, ResourceHeader> {
self.0.safe_lock()
}
/// Attempt to lock the shared header. None if the header is already locked.
pub fn try_typed_lock<T: TypedResourceData>(&self) -> Option<ResourceHeaderGuard<'_, T>> {
self.try_lock().map(|g| g.into())
}
/// Attempt to lock the shared header. None if the header is already locked.
pub fn try_lock(&self) -> Option<MutexGuard<'_, ResourceHeader>> {
self.0.try_lock()
}
/// Creates new untyped resource in unloaded state with the given UUID.
pub fn new_unloaded(resource_uuid: Uuid) -> Self {
ResourceHeader {
uuid: resource_uuid,
kind: ResourceKind::External,
state: ResourceState::Unloaded,
}
.into()
}
/// Creates new untyped resource in pending state with the given UUID.
pub fn new_pending(resource_uuid: Uuid, kind: ResourceKind) -> Self {
ResourceHeader {
uuid: resource_uuid,
kind,
state: ResourceState::new_pending(path),
old_format_path: None,
})))
state: ResourceState::new_pending(),
}
.into()
}
/// Creates new untyped resource in ok (fully loaded) state using the given data of any type, that
@@ -452,11 +674,12 @@ impl UntypedResource {
where
T: ResourceData,
{
Self(Arc::new(Mutex::new(ResourceHeader {
ResourceHeader {
uuid: resource_uuid,
kind,
state: ResourceState::new_ok(resource_uuid, data),
old_format_path: None,
})))
state: ResourceState::new_ok(data),
}
.into()
}
/// Creates new untyped resource in ok (fully loaded) state using the given data.
@@ -465,11 +688,12 @@ impl UntypedResource {
kind: ResourceKind,
data: Box<dyn ResourceData>,
) -> Self {
Self(Arc::new(Mutex::new(ResourceHeader {
ResourceHeader {
uuid: resource_uuid,
kind,
state: ResourceState::new_ok_untyped(resource_uuid, data),
old_format_path: None,
})))
state: ResourceState::new_ok_untyped(data),
}
.into()
}
/// Creates new untyped resource in ok (fully loaded) state using the given data of any type, that
@@ -479,27 +703,24 @@ impl UntypedResource {
}
/// Creates new untyped resource in error state.
pub fn new_load_error(kind: ResourceKind, error: LoadError) -> Self {
Self(Arc::new(Mutex::new(ResourceHeader {
pub fn new_load_error(kind: ResourceKind, path: PathBuf, error: LoadError) -> Self {
ResourceHeader {
uuid: Uuid::new_v4(),
kind,
state: ResourceState::new_load_error(Default::default(), error),
old_format_path: None,
})))
state: ResourceState::new_load_error(path, error),
}
.into()
}
/// Tries to get a resource uuid (if any). Uuid is available only for fully loaded resources
/// (in [`ResourceState::Ok`] state).
pub fn resource_uuid(&self) -> Option<Uuid> {
let header = self.0.lock();
match header.state {
ResourceState::Ok { resource_uuid, .. } => Some(resource_uuid),
_ => None,
}
/// The UUID of the resource. All resources must have a UUID, even if they are not loaded
/// because the UUID is how the resource manager knows the path to load from.
pub fn resource_uuid(&self) -> Uuid {
self.lock().uuid
}
/// Returns actual unique type id of underlying resource data.
pub fn type_uuid(&self) -> Option<Uuid> {
let header = self.0.lock();
let header = self.lock();
match header.state {
ResourceState::Ok { ref data, .. } => Some(data.type_uuid()),
_ => None,
@@ -509,7 +730,7 @@ impl UntypedResource {
/// Tries to get an actual unique type id of underlying resource data. Returns `None` if the
/// resource cannot be locked or if it is not loaded.
pub fn type_uuid_non_blocking(&self) -> Option<Uuid> {
let header = self.0.try_lock()?;
let header = self.try_lock()?;
match header.state {
ResourceState::Ok { ref data, .. } => Some(data.type_uuid()),
_ => None,
@@ -519,8 +740,7 @@ impl UntypedResource {
/// Tries to get a type name of the resource data. Data type name is available only for fully
/// loaded resources (in [`ResourceState::Ok`] state).
pub fn data_type_name(&self) -> Option<String> {
let header = self.0.lock();
match header.state {
match self.lock().state {
ResourceState::Ok { ref data, .. } => Some(Reflect::type_name(&**data).to_string()),
_ => None,
}
@@ -533,15 +753,29 @@ impl UntypedResource {
.unwrap_or_else(|| "Unknown".to_string())
}
/// Returns true if the resource has not been requested.
pub fn is_unloaded(&self) -> bool {
matches!(self.lock().state, ResourceState::Unloaded)
}
/// Returns true if the resource is still loading.
pub fn is_loading(&self) -> bool {
matches!(self.0.lock().state, ResourceState::Pending { .. })
matches!(self.lock().state, ResourceState::Pending { .. })
}
/// Returns true if the resource is completely loaded.
pub fn is_ok(&self) -> bool {
matches!(self.lock().state, ResourceState::Ok { .. })
}
/// Returns true if the resource failed to load.
pub fn is_failed_to_load(&self) -> bool {
matches!(self.lock().state, ResourceState::LoadError { .. })
}
/// Returns true if the resource is procedural (its data is generated at runtime, not stored in an external
/// file).
pub fn is_embedded(&self) -> bool {
self.0.lock().kind.is_embedded()
self.lock().kind.is_embedded()
}
/// Returns exact amount of users of the resource.
@@ -552,27 +786,28 @@ impl UntypedResource {
/// Returns a pointer as numeric value which can be used as a hash.
#[inline]
pub fn key(&self) -> usize {
(&*self.0 as *const _) as usize
pub fn key(&self) -> u64 {
let mut hasher = FxHasher64::default();
self.hash(&mut hasher);
hasher.finish()
}
/// Returns path of the untyped resource.
pub fn kind(&self) -> ResourceKind {
self.0.lock().kind
self.lock().kind
}
/// Set a new path for the untyped resource.
pub fn set_kind(&self, new_kind: ResourceKind) {
self.0.lock().kind = new_kind;
self.lock().kind = new_kind;
}
/// Tries to save the resource to the specified path.
pub fn save(&self, path: &Path) -> Result<(), Box<dyn Error>> {
let mut guard = self.0.lock();
match guard.state {
ResourceState::Pending { .. } | ResourceState::LoadError { .. } => {
Err("Unable to save unloaded resource!".into())
}
match self.lock().state {
ResourceState::Pending { .. }
| ResourceState::LoadError { .. }
| ResourceState::Unloaded => Err("Unable to save unloaded resource!".into()),
ResourceState::Ok { ref mut data, .. } => data.save(path),
}
}
@@ -592,22 +827,25 @@ impl UntypedResource {
}
}
/// Modify this resource into the [`ResourceState::Pending`] state.
pub fn make_pending(&mut self) {
self.lock().state = ResourceState::new_pending();
}
/// Changes ResourceState::Pending state to ResourceState::Ok(data) with given `data`.
/// Additionally, it wakes all futures.
/// Additionally, it wakes all futures. Panics if the resource is unrequested.
#[inline]
pub fn commit(&self, state: ResourceState) {
self.0.lock().state.commit(state);
self.lock().state.commit(state);
}
/// Changes internal state to [`ResourceState::Ok`]
pub fn commit_ok<T: ResourceData>(&self, resource_uuid: Uuid, data: T) {
let mut guard = self.0.lock();
guard.state.commit_ok(resource_uuid, data);
/// Changes internal state to [`ResourceState::Ok`]. Panics if the resource is unrequested.
pub fn commit_ok<T: ResourceData>(&self, data: T) {
self.lock().state.commit_ok(data);
}
/// Changes internal state to [`ResourceState::LoadError`].
pub fn commit_error<E: ResourceLoadError>(&self, path: PathBuf, error: E) {
self.0.lock().state.commit_error(path, error);
pub fn commit_error<E: ResourceLoadError>(&mut self, path: PathBuf, error: E) {
self.lock().state.commit_error(path, error);
}
}
@@ -615,13 +853,15 @@ impl Future for UntypedResource {
type Output = Result<Self, LoadError>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let state = self.0.clone();
let mut guard = state.lock();
let mut guard = self.lock();
match guard.state {
ResourceState::Pending { ref mut wakers, .. } => {
wakers.add_waker(cx.waker());
Poll::Pending
}
ResourceState::Unloaded => Poll::Ready(Err(LoadError::new(
"Unloaded resource is not loading".to_string(),
))),
ResourceState::LoadError { ref error, .. } => Poll::Ready(Err(error.clone())),
ResourceState::Ok { .. } => Poll::Ready(Ok(self.clone())),
}
@@ -635,6 +875,8 @@ mod test {
use std::error::Error;
use std::task::{self};
use crate::io::FsResourceIo;
use super::*;
#[derive(Debug, Default, Reflect, Visit, Clone, Copy)]
@@ -672,13 +914,21 @@ mod test {
let mut visitor = Visitor::default();
assert!(r.visit("name", &mut visitor).is_ok());
}
#[test]
fn debug_for_untyped_resource() {
let r = UntypedResource::default();
assert_eq!(format!("{r:?}"), "Resource\n");
let mut cursor = std::io::Cursor::new(Vec::<u8>::new());
visitor
.save_binary_to_memory(&mut cursor)
.expect("Failed to write binary for visitor");
cursor.set_position(0);
let mut visitor = Visitor::load_binary_from_memory(cursor.get_ref())
.expect("Failed to read binary for visitor");
visitor.blackboard.register(Arc::new(ResourceManager::new(
Arc::new(FsResourceIo),
Arc::default(),
)));
assert!(r.visit("name", &mut visitor).is_ok());
assert!(r.is_embedded());
assert!(r.is_failed_to_load());
}
#[test]
@@ -704,24 +954,23 @@ mod test {
let waker = noop_waker();
let mut cx = task::Context::from_waker(&waker);
let mut r = UntypedResource(Arc::new(Mutex::new(ResourceHeader {
let mut r = UntypedResource::from(ResourceHeader {
uuid: Uuid::new_v4(),
kind: ResourceKind::External,
state: ResourceState::Ok {
data: ResourceDataWrapper(Box::new(stub)),
resource_uuid: Uuid::new_v4(),
},
old_format_path: None,
})));
});
assert!(Pin::new(&mut r).poll(&mut cx).is_ready());
let mut r = UntypedResource(Arc::new(Mutex::new(ResourceHeader {
let mut r = UntypedResource::from(ResourceHeader {
uuid: Uuid::new_v4(),
kind: ResourceKind::External,
state: ResourceState::LoadError {
path: Default::default(),
error: Default::default(),
},
old_format_path: None,
})));
});
assert!(Pin::new(&mut r).poll(&mut cx).is_ready());
}
}
+3
View File
@@ -162,6 +162,9 @@ impl GenericBuffer {
/// Returns exact time length of the buffer.
#[inline]
pub fn duration(&self) -> Duration {
if self.sample_rate == 0 {
return Duration::ZERO;
}
Duration::from_nanos(
(self.channel_duration_in_samples as u64 * 1_000_000_000u64) / self.sample_rate as u64,
)
+5 -5
View File
@@ -27,7 +27,7 @@ use crate::{
button::{ButtonBuilder, ButtonMessage},
core::{
parking_lot::Mutex, pool::Handle, reflect::prelude::*, type_traits::prelude::*,
uuid_provider, visitor::prelude::*,
uuid_provider, visitor::prelude::*, SafeLock,
},
define_constructor,
file_browser::menu::ItemContextMenu,
@@ -450,7 +450,7 @@ impl Control for FileBrowser {
for entry in entries {
let path = entry.path();
let build = if let Some(filter) = self.filter.as_mut() {
filter.0.borrow_mut().deref_mut().lock()(&path)
filter.0.borrow_mut().deref_mut().safe_lock()(&path)
} else {
true
};
@@ -605,7 +605,7 @@ impl Control for FileBrowser {
ui.node(widget)
.user_data
.as_ref()
.is_some_and(|data| data.lock().downcast_ref::<PathBuf>().is_some())
.is_some_and(|data| data.safe_lock().downcast_ref::<PathBuf>().is_some())
}
}
@@ -617,7 +617,7 @@ fn parent_path(path: &Path) -> PathBuf {
fn filtered_out(filter: &mut Option<Filter>, path: &Path) -> bool {
match filter.as_mut() {
Some(filter) => !filter.0.borrow_mut().deref_mut().lock()(path),
Some(filter) => !filter.0.borrow_mut().deref_mut().safe_lock()(path),
None => false,
}
}
@@ -935,7 +935,7 @@ fn build_all(
#[allow(clippy::blocks_in_conditions)]
if filter
.as_mut()
.is_none_or(|f| f.0.borrow_mut().deref_mut().lock()(&path))
.is_none_or(|f| f.0.borrow_mut().deref_mut().safe_lock()(&path))
{
let is_part_of_final_path = next.as_ref().is_some_and(|next| *next == path);
+49 -4
View File
@@ -29,9 +29,12 @@ use crate::{
HorizontalAlignment, VerticalAlignment,
};
use fyrox_core::log::Log;
use fyrox_resource::state::LoadError;
use fyrox_resource::state::{LoadError, ResourceState};
pub use run::*;
use std::ops::{Range, RangeBounds};
use std::{
ops::{Range, RangeBounds},
path::PathBuf,
};
use strum_macros::{AsRefStr, EnumString, VariantNames};
use textwrapper::*;
@@ -804,14 +807,56 @@ impl FormattedText {
true
}
pub fn are_fonts_loading(&self) -> bool {
if !self.font.is_loading() {
return true;
}
for run in self.runs.iter() {
if let Some(font) = run.font() {
if !font.is_loading() {
return true;
}
}
}
false
}
pub fn font_load_error_list(&self) -> Vec<(PathBuf, LoadError)> {
let mut list = vec![];
if let ResourceState::LoadError { path, error } = &self.font.header().state {
list.push((path.clone(), error.clone()));
}
for run in self.runs.iter() {
if let Some(font) = run.font() {
if let ResourceState::LoadError { path, error } = &font.header().state {
list.push((path.clone(), error.clone()));
}
}
}
list
}
pub fn font_loading_summary(&self) -> String {
use std::fmt::Write;
let mut result = String::default();
write!(result, "Primary font: {}", self.font.header().state).unwrap();
for run in self.runs.iter() {
if let Some(font) = run.font() {
write!(result, "\nRun {:?}: {}", run.range, font.header().state).unwrap();
}
}
result
}
pub fn build(&mut self) -> Vector2<f32> {
let mut lines = std::mem::take(&mut self.lines);
lines.clear();
// Fail early if any font is not available.
if !self.are_fonts_loaded() {
Log::err(format!(
"Text failed to build due to unloaded fonts. {:?}",
self.text()
"Text failed to build due to unloaded fonts. {:?}.\n{}",
self.text(),
self.font_loading_summary(),
));
return Vector2::default();
}
+9 -5
View File
@@ -299,12 +299,16 @@ use crate::{
algebra::{Matrix3, Vector2},
color::Color,
math::Rect,
parking_lot::Mutex,
pool::Ticket,
pool::{Handle, Pool},
reflect::prelude::*,
uuid::uuid,
uuid::Uuid,
uuid_provider,
visitor::prelude::*,
SafeLock, TypeUuidProvider,
},
core::{parking_lot::Mutex, pool::Ticket, uuid::Uuid, uuid_provider, TypeUuidProvider},
draw::{CommandTexture, Draw, DrawingContext},
font::FontResource,
font::BUILT_IN_FONT,
@@ -411,7 +415,7 @@ pub struct RcUiNodeHandle(Arc<Mutex<RcUiNodeHandleInner>>);
impl Debug for RcUiNodeHandle {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let handle = self.0.lock().handle;
let handle = self.0.safe_lock().handle;
writeln!(
f,
@@ -425,8 +429,8 @@ impl Debug for RcUiNodeHandle {
impl PartialEq for RcUiNodeHandle {
fn eq(&self, other: &Self) -> bool {
let a = self.0.lock().handle;
let b = other.0.lock().handle;
let a = self.0.safe_lock().handle;
let b = other.0.safe_lock().handle;
a == b
}
}
@@ -444,7 +448,7 @@ impl RcUiNodeHandle {
/// Returns the inner handle.
#[inline]
pub fn handle(&self) -> Handle<UiNode> {
self.0.lock().handle
self.0.safe_lock().handle
}
}