mirror of
https://github.com/FyroxEngine/Fyrox.git
synced 2026-10-12 02:02:20 +00:00
Merge pull request #856 from b-guild/resource
Improving resource handles to be UUID-based
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:?}")
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
///
|
||||
|
||||
@@ -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],
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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()
|
||||
));
|
||||
|
||||
|
||||
@@ -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
@@ -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.
|
||||
|
||||
@@ -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}")
|
||||
|
||||
@@ -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,
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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()),
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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},
|
||||
|
||||
+779
-352
File diff suppressed because it is too large
Load Diff
+120
-81
@@ -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 ®istry_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
@@ -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
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user