fyrox_resource/
manager.rs

1// Copyright (c) 2019-present Dmitry Stepanov and Fyrox Engine contributors.
2//
3// Permission is hereby granted, free of charge, to any person obtaining a copy
4// of this software and associated documentation files (the "Software"), to deal
5// in the Software without restriction, including without limitation the rights
6// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7// copies of the Software, and to permit persons to whom the Software is
8// furnished to do so, subject to the following conditions:
9//
10// The above copyright notice and this permission notice shall be included in all
11// copies or substantial portions of the Software.
12//
13// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19// SOFTWARE.
20
21//! Resource manager controls loading and lifetime of resource in the engine. See [`ResourceManager`]
22//! docs for more info.
23
24pub use crate::builtin::*;
25use crate::{
26    constructor::ResourceConstructorContainer,
27    core::{
28        append_extension, err,
29        futures::future::join_all,
30        info,
31        io::FileError,
32        log::Log,
33        notify, ok_or_continue,
34        parking_lot::{Mutex, MutexGuard},
35        task::TaskPool,
36        watcher::FileSystemWatcher,
37        SafeLock, TypeUuidProvider, Uuid,
38    },
39    entry::{TimedEntry, DEFAULT_RESOURCE_LIFETIME},
40    event::{ResourceEvent, ResourceEventBroadcaster},
41    io::ResourceIo,
42    loader::{ResourceLoader, ResourceLoadersContainer},
43    metadata::ResourceMetadata,
44    options::OPTIONS_EXTENSION,
45    registry::{RegistryUpdate, ResourceRegistry, ResourceRegistryRefMut, ResourceRegistryStatus},
46    state::{LoadError, ResourceDataWrapper, ResourceState},
47    untyped::ResourceKind,
48    Resource, TypedResourceData, UntypedResource,
49};
50use fxhash::FxHashSet;
51use fyrox_core::{
52    futures::executor::block_on, make_relative_path, notify::Event, ok_or_return, some_or_continue,
53    some_or_return,
54};
55use std::{
56    fmt::{Debug, Display, Formatter},
57    io::Error,
58    marker::PhantomData,
59    path::{Path, PathBuf},
60    sync::Arc,
61    time::Duration,
62};
63
64/// A set of resources that can be waited for.
65#[must_use]
66#[derive(Default)]
67pub struct ResourceWaitContext {
68    resources: Vec<UntypedResource>,
69}
70
71impl ResourceWaitContext {
72    /// Wait until all resources are loaded (or failed to load).
73    #[must_use]
74    pub fn is_all_loaded(&self) -> bool {
75        for resource in self.resources.iter() {
76            if resource.is_loading() {
77                return false;
78            }
79        }
80        true
81    }
82}
83
84/// Internal state of the resource manager.
85pub struct ResourceManagerState {
86    /// A set of resource loaders. Use this field to register your own resource loader.
87    pub loaders: Arc<Mutex<ResourceLoadersContainer>>,
88    /// Event broadcaster can be used to "subscribe" for events happening inside the container.
89    pub event_broadcaster: ResourceEventBroadcaster,
90    /// A container for resource constructors.
91    pub constructors_container: ResourceConstructorContainer,
92    /// A set of built-in resources, that will be used to resolve references on deserialization.
93    pub built_in_resources: BuiltInResourcesContainer,
94    /// File system abstraction interface. Could be used to support virtual file systems.
95    pub resource_io: Arc<dyn ResourceIo>,
96    /// Resource registry, contains associations `UUID -> File Path`. Any access to the registry
97    /// must be async, use task pool for this.
98    pub resource_registry: Arc<Mutex<ResourceRegistry>>,
99
100    resources: Vec<TimedEntry<UntypedResource>>,
101    task_pool: Arc<TaskPool>,
102    watcher: Option<FileSystemWatcher>,
103}
104
105/// Resource manager controls loading and lifetime of resource in the engine. Resource manager can hold
106/// resources of arbitrary types via type erasure mechanism.
107///
108/// ## Built-in Resources
109///
110/// Built-in resources are special kinds of resources, whose data is packed in the executable (i.e. via
111/// [`include_bytes`] macro). Such resources reference the data that cannot be "loaded" from external
112/// source. To support such kind of resource the manager provides `built_in_resources` hash map where
113/// you can register your own built-in resource and access existing ones.
114///
115/// ## Internals
116///
117/// It is a simple wrapper over [`ResourceManagerState`] that can be shared (cloned). In other words,
118/// it is just a strong reference to the inner state.
119#[derive(Clone)]
120pub struct ResourceManager {
121    state: Arc<Mutex<ResourceManagerState>>,
122}
123
124impl Debug for ResourceManager {
125    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
126        write!(f, "ResourceManager")
127    }
128}
129
130/// An error that may occur during texture registration.
131#[derive(Debug, PartialEq, Eq)]
132pub enum ResourceRegistrationError {
133    /// Resource saving has failed.
134    UnableToRegister,
135    /// Resource was in invalid state (Pending, LoadErr)
136    InvalidState,
137    /// Resource is already registered.
138    AlreadyRegistered,
139    /// An error occurred on an attempt to write resource metadata.
140    UnableToCreateMetadata,
141}
142
143impl Display for ResourceRegistrationError {
144    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
145        match self {
146            ResourceRegistrationError::UnableToRegister => {
147                write!(f, "Unable to register the resource!")
148            }
149            ResourceRegistrationError::InvalidState => {
150                write!(f, "A resource was in invalid state!")
151            }
152            ResourceRegistrationError::AlreadyRegistered => {
153                write!(f, "A resource is already registered!")
154            }
155            ResourceRegistrationError::UnableToCreateMetadata => {
156                write!(
157                    f,
158                    "An error occurred on an attempt to write resource metadata!"
159                )
160            }
161        }
162    }
163}
164
165/// All the required and validated data that is needed to move a resource from the path A to the path B.
166pub struct ResourceMoveContext {
167    relative_src_path: PathBuf,
168    relative_dest_path: PathBuf,
169    resource_uuid: Uuid,
170}
171
172/// A possible set of errors that may occur during resource movement.
173#[derive(Debug)]
174pub enum ResourceMovementError {
175    /// An IO error.
176    Io(std::io::Error),
177    /// A file error.
178    FileError(FileError),
179    /// The resource at the `src_path` already exist at the `dest_path`.
180    AlreadyExist {
181        /// Source path of the resource.
182        src_path: PathBuf,
183        /// The path at which a resource with the same name is located.
184        dest_path: PathBuf,
185    },
186    /// The new path for a resource is invalid.
187    DestinationPathIsInvalid {
188        /// Source path of the resource.
189        src_path: PathBuf,
190        /// The invalid destination path.
191        dest_path: PathBuf,
192    },
193    /// Resource registry location is unknown (the registry wasn't saved yet).
194    ResourceRegistryLocationUnknown {
195        /// A path of the resource being moved.
196        resource_path: PathBuf,
197    },
198    /// The resource is not in the registry.
199    NotInRegistry {
200        /// A path of the resource being moved.
201        resource_path: PathBuf,
202    },
203    /// Attempting to move a resource outside the registry.
204    OutsideOfRegistry {
205        /// An absolute path of the resource being moved.
206        absolute_src_path: PathBuf,
207        /// An absolute path of the destination folder.
208        absolute_dest_dir: PathBuf,
209        /// An absolute path of the resource registry.
210        absolute_registry_dir: PathBuf,
211    },
212    /// A resource has no path. It is either an embedded resource or in an invalid
213    /// state (failed to load or still loading).
214    NoPath(UntypedResource),
215}
216
217impl From<FileError> for ResourceMovementError {
218    fn from(value: FileError) -> Self {
219        Self::FileError(value)
220    }
221}
222
223impl From<std::io::Error> for ResourceMovementError {
224    fn from(value: Error) -> Self {
225        Self::Io(value)
226    }
227}
228
229impl Display for ResourceMovementError {
230    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
231        match self {
232            ResourceMovementError::Io(err) => {
233                write!(f, "Io error: {err}")
234            }
235            ResourceMovementError::FileError(err) => {
236                write!(f, "File error: {err}")
237            }
238            ResourceMovementError::AlreadyExist {
239                src_path,
240                dest_path,
241            } => {
242                write!(
243                    f,
244                    "Unable to move the {} resource, because the destination \
245                    path {} points to an existing file!",
246                    src_path.display(),
247                    dest_path.display()
248                )
249            }
250            ResourceMovementError::DestinationPathIsInvalid {
251                src_path,
252                dest_path,
253            } => {
254                write!(
255                    f,
256                    "Unable to move the {} resource, because the destination \
257                    path {} is invalid!",
258                    src_path.display(),
259                    dest_path.display()
260                )
261            }
262            ResourceMovementError::ResourceRegistryLocationUnknown { resource_path } => {
263                write!(
264                    f,
265                    "Unable to move the {} resource, because the registry location is unknown!",
266                    resource_path.display()
267                )
268            }
269            ResourceMovementError::NotInRegistry { resource_path } => {
270                write!(
271                    f,
272                    "Unable to move the {} resource, because it is not in the registry!",
273                    resource_path.display()
274                )
275            }
276            ResourceMovementError::OutsideOfRegistry {
277                absolute_src_path,
278                absolute_dest_dir,
279                absolute_registry_dir,
280            } => {
281                write!(
282                    f,
283                    "Unable to move the {} resource to {} path, because \
284            the new path is located outside the resource registry path {}!",
285                    absolute_src_path.display(),
286                    absolute_dest_dir.display(),
287                    absolute_registry_dir.display()
288                )
289            }
290            ResourceMovementError::NoPath(resource) => {
291                write!(
292                    f,
293                    "Unable to move {} resource, because it does not have a \
294                file system path!",
295                    resource.key()
296                )
297            }
298        }
299    }
300}
301
302/// A set of potential errors that may occur when moving a folder with resources.
303#[derive(Debug)]
304pub enum FolderMovementError {
305    /// An IO error.
306    Io(std::io::Error),
307    /// A file error.
308    FileError(FileError),
309    /// A [`ResourceMovementError`].
310    ResourceMovementError(ResourceMovementError),
311    /// The folder is not in the registry.
312    NotInRegistry {
313        /// A path of the folder being moved.
314        dest_dir: PathBuf,
315    },
316    /// Trying to move a folder into one of its own sub-folders.
317    HierarchyError {
318        /// Path of the folder being moved.
319        src_dir: PathBuf,
320        /// Destination directory.
321        dest_dir: PathBuf,
322    },
323}
324
325impl From<FileError> for FolderMovementError {
326    fn from(value: FileError) -> Self {
327        Self::FileError(value)
328    }
329}
330
331impl From<std::io::Error> for FolderMovementError {
332    fn from(value: Error) -> Self {
333        Self::Io(value)
334    }
335}
336
337impl From<ResourceMovementError> for FolderMovementError {
338    fn from(value: ResourceMovementError) -> Self {
339        Self::ResourceMovementError(value)
340    }
341}
342
343impl Display for FolderMovementError {
344    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
345        match self {
346            FolderMovementError::Io(err) => {
347                write!(f, "Io error: {err}")
348            }
349            FolderMovementError::FileError(err) => {
350                write!(f, "File error: {err}")
351            }
352            FolderMovementError::ResourceMovementError(err) => {
353                write!(f, "{err}")
354            }
355            FolderMovementError::NotInRegistry { dest_dir } => {
356                write!(
357                    f,
358                    "Unable to move the {} folder, because it is not in the registry!",
359                    dest_dir.display()
360                )
361            }
362            FolderMovementError::HierarchyError { src_dir, dest_dir } => {
363                write!(
364                    f,
365                    "Trying to move a folder into one of its own sub-folders. \
366                    Source folder is {}, destination folder is {}",
367                    src_dir.display(),
368                    dest_dir.display()
369                )
370            }
371        }
372    }
373}
374
375impl ResourceManager {
376    /// Creates a resource manager with default settings and loaders.
377    pub fn new(io: Arc<dyn ResourceIo>, task_pool: Arc<TaskPool>) -> Self {
378        Self {
379            state: Arc::new(Mutex::new(ResourceManagerState::new(io, task_pool))),
380        }
381    }
382
383    /// Returns a guarded reference to the internal state of resource manager. This is blocking
384    /// method and it may deadlock if used incorrectly (trying to get the state lock one more time
385    /// when there's an existing lock in the same thread, multi-threading-related deadlock and so on).
386    pub fn state(&self) -> MutexGuard<'_, ResourceManagerState> {
387        self.state.safe_lock()
388    }
389
390    /// Returns a guarded reference to the internal state of resource manager. This method will try
391    /// to acquire the state lock for the given time and if it fails, returns `None`.
392    pub fn try_get_state(&self, timeout: Duration) -> Option<MutexGuard<'_, ResourceManagerState>> {
393        self.state.try_lock_for(timeout)
394    }
395
396    /// Returns the ResourceIo used by this resource manager
397    pub fn resource_io(&self) -> Arc<dyn ResourceIo> {
398        let state = self.state();
399        state.resource_io.clone()
400    }
401
402    /// Returns the task pool used by this resource manager.
403    pub fn task_pool(&self) -> Arc<TaskPool> {
404        let state = self.state();
405        state.task_pool()
406    }
407
408    /// Registers a new built-in resource, so it becomes accessible via [`Self::request`].
409    pub fn register_built_in_resource<T: TypedResourceData>(
410        &self,
411        resource: BuiltInResource<T>,
412    ) -> Option<UntypedBuiltInResource> {
413        self.state().register_built_in_resource(resource)
414    }
415
416    /// The same as [`Self::find`], but returns [`None`] if type UUID of `T` does not match the actual type UUID
417    /// of the resource.
418    ///
419    /// ## Panic
420    ///
421    /// This method does not panic.
422    pub fn try_find<T>(&self, path: impl AsRef<Path>) -> Option<Resource<T>>
423    where
424        T: TypedResourceData,
425    {
426        let mut state = self.state();
427
428        let untyped = state.find(path.as_ref());
429
430        let data_type_uuid_matches = untyped
431            .type_uuid_non_blocking()
432            .is_some_and(|uuid| uuid == <T as TypeUuidProvider>::type_uuid());
433
434        if !data_type_uuid_matches {
435            let has_loader_for_extension = state
436                .loaders
437                .safe_lock()
438                .is_extension_matches_type::<T>(path.as_ref());
439
440            if !has_loader_for_extension {
441                return None;
442            }
443        }
444
445        Some(Resource {
446            untyped,
447            phantom: PhantomData::<T>,
448        })
449    }
450
451    /// Find the resource for the given path without loading the resource.
452    /// A new unloaded resource is created if one does not already exist.
453    /// If the path is not in the registry then a new UUID is generated and
454    /// the path is added to the registry.
455    /// If the resource is not already loading, then it will be returned in
456    /// the [`ResourceState::Unloaded`] state, and [`Self::request_resource`] may
457    /// be used to begin the loading process.
458    ///
459    /// ## Panic
460    ///
461    /// This method will panic if type UUID of `T` does not match the actual type UUID of the resource. If this
462    /// is undesirable, use [`Self::try_find`] instead.
463    pub fn find<T>(&self, path: impl AsRef<Path>) -> Resource<T>
464    where
465        T: TypedResourceData,
466    {
467        let path = path.as_ref();
468        let mut state = self.state();
469
470        let untyped = state.find(path);
471
472        let data_type_uuid_matches = untyped
473            .type_uuid_non_blocking()
474            .is_some_and(|uuid| uuid == <T as TypeUuidProvider>::type_uuid());
475
476        if !data_type_uuid_matches {
477            let has_loader_for_extension = state
478                .loaders
479                .safe_lock()
480                .is_extension_matches_type::<T>(path);
481
482            if !has_loader_for_extension {
483                panic!(
484                    "Unable to get a resource of type {} from {path:?}! The resource has no \
485                    associated loader for its extension and its actual data has some other \
486                    data type!",
487                    <T as TypeUuidProvider>::type_uuid(),
488                )
489            }
490        }
491
492        Resource {
493            untyped,
494            phantom: PhantomData::<T>,
495        }
496    }
497
498    /// Find the resource for the given UUID without loading the resource.
499    /// If the resource is not already loading, then it will be returned in
500    /// the [`ResourceState::Unloaded`] state, and [`Self::request_resource`] may
501    /// be used to begin the loading process.
502    ///
503    /// ## Panic
504    ///
505    /// This method will panic if type UUID of `T` does not match the actual type UUID of the resource. If this
506    /// is undesirable, use [`Self::try_find`] instead.
507    pub fn find_uuid<T>(&self, uuid: Uuid) -> Resource<T>
508    where
509        T: TypedResourceData,
510    {
511        let mut state = self.state();
512
513        let untyped = state.find_uuid(uuid);
514
515        if let Some(type_uuid) = untyped.type_uuid_non_blocking() {
516            if type_uuid != <T as TypeUuidProvider>::type_uuid() {
517                panic!(
518                    "Unable to get a resource of type {} from {uuid} UUID! Its actual data has some other \
519                    data type with type UUID {type_uuid}!",
520                    <T as TypeUuidProvider>::type_uuid(),
521                )
522            }
523        }
524
525        Resource {
526            untyped,
527            phantom: PhantomData::<T>,
528        }
529    }
530
531    /// Requests a resource of the given type located at the given path. This method is non-blocking, instead
532    /// it immediately returns the typed resource wrapper. Loading of the resource is managed automatically in
533    /// a separate thread (or thread pool) on PC, and JS micro-task (the same thread) on WebAssembly.
534    ///
535    /// ## Type Guarantees
536    ///
537    /// There's no strict guarantees that the requested resource will be of the requested type. This
538    /// is because the resource system is fully async and does not have access to type information in
539    /// most cases. Initial type checking is not very reliable and can be "fooled" pretty easily,
540    /// simply because it just checks if there's a registered loader for a specific extension.
541    ///
542    /// ## Sharing
543    ///
544    /// If the resource at the given path is already was requested (no matter in which state the actual resource
545    /// is), this method will return the existing instance. This way the resource manager guarantees that the actual
546    /// resource data will be loaded once, and it can be shared.
547    ///
548    /// ## Waiting
549    ///
550    /// If you need to wait until the resource is loaded, use `.await` on the result of the method. Every resource
551    /// implements `Future` trait and can be used in `async` contexts.
552    ///
553    /// ## Resource state
554    ///
555    /// Keep in mind, that the resource itself is a small state machine. It could be in three main states:
556    ///
557    /// - [`ResourceState::Pending`] - a resource is in the queue to load or still loading.
558    /// - [`ResourceState::LoadError`] - a resource is failed to load.
559    /// - [`ResourceState::Ok`] - a resource is successfully loaded.
560    ///
561    /// Actual resource state can be fetched by [`Resource::state`] method. If you know for sure that the resource
562    /// is already loaded, then you can use [`Resource::data_ref`] to obtain a reference to the actual resource data.
563    /// Keep in mind, that this method will panic if the resource non in `Ok` state.
564    ///
565    /// ## Panic
566    ///
567    /// This method will panic, if type UUID of `T` does not match the actual type UUID of the resource. If this
568    /// is undesirable, use [`Self::try_request`] instead.
569    pub fn request<T>(&self, path: impl AsRef<Path>) -> Resource<T>
570    where
571        T: TypedResourceData,
572    {
573        let path = path.as_ref();
574        let mut state = self.state();
575
576        let untyped = state.request(path);
577
578        let data_type_uuid_matches = untyped
579            .type_uuid_non_blocking()
580            .is_some_and(|uuid| uuid == <T as TypeUuidProvider>::type_uuid());
581
582        if !data_type_uuid_matches {
583            let has_loader_for_extension = state
584                .loaders
585                .safe_lock()
586                .is_extension_matches_type::<T>(path);
587
588            if !has_loader_for_extension {
589                panic!(
590                    "Unable to get a resource of type {} from {path:?}! The resource has no \
591                    associated loader for its extension and its actual data has some other \
592                    data type!",
593                    <T as TypeUuidProvider>::type_uuid(),
594                )
595            }
596        }
597
598        Resource {
599            untyped,
600            phantom: PhantomData::<T>,
601        }
602    }
603
604    /// Requests that given resource should begin loading, if not already loading or loaded.
605    /// This method is non-blocking, instead it modifies the given resource and returns.
606    /// Loading of the resource is managed automatically in a separate thread (or thread pool) on PC,
607    /// and JS micro-task (the same thread) on WebAssembly.
608    ///
609    /// ## Type Guarantees
610    ///
611    /// There's no strict guarantees that the requested resource will be of the requested type. This
612    /// is because the resource system is fully async and does not have access to type information in
613    /// most cases. Initial type checking is not very reliable and can be "fooled" pretty easily,
614    /// simply because it just checks if there's a registered loader for a specific extension.
615    ///
616    /// ## Sharing
617    ///
618    /// If a resource with given resource's UUID was already requested (no matter in which state the actual resource
619    /// is), this method will modify the given resource to be a shared reference to the already loading resource.
620    /// This way the resource manager guarantees that the actual resource data will be loaded once, and it can be shared.
621    ///
622    /// ## Resource state
623    ///
624    /// Keep in mind, that the resource itself is a small state machine. It could be in these states:
625    ///
626    /// - [`ResourceState::Unloaded`] - a resource that has not started loading. Calling this method
627    ///   updates the resource into the `Pending` state.
628    /// - [`ResourceState::Pending`] - a resource is in the queue to load or still loading.
629    /// - [`ResourceState::LoadError`] - a resource is failed to load.
630    /// - [`ResourceState::Ok`] - a resource is successfully loaded.
631    ///
632    /// Actual resource state can be fetched by [`Resource::state`] method. If you know for sure that the resource
633    /// is already loaded, then you can use [`Resource::data_ref`] to obtain a reference to the actual resource data.
634    /// Keep in mind, that this method will panic if the resource non in `Ok` state.
635    ///
636    /// ## Panic
637    ///
638    /// This method will panic, if type UUID of `T` does not match the actual type UUID of the resource. If this
639    /// is undesirable, use [`Self::try_request`] instead.
640    pub fn request_resource<T>(&self, resource: &mut Resource<T>)
641    where
642        T: TypedResourceData,
643    {
644        let mut state = self.state();
645
646        state.request_resource(&mut resource.untyped);
647
648        if let Some(type_uuid) = resource.untyped.type_uuid_non_blocking() {
649            let needed_type_uuid = <T as TypeUuidProvider>::type_uuid();
650            if type_uuid != needed_type_uuid {
651                panic!(
652                    "Unable to get a resource of type {needed_type_uuid} from resource UUID {}! The resource is \
653                    loaded but its actual data has type {type_uuid}!",
654                    resource.resource_uuid(),
655                );
656            }
657        }
658    }
659
660    /// Add the given resource to the resource manager, based on the resource's UUID,
661    /// without initiating the loading of the resource. The given resource is modified
662    /// to be a reference to the shared data of an existing resource with the same UUID.
663    pub fn add_resource<T>(&self, resource: &mut Resource<T>)
664    where
665        T: TypedResourceData,
666    {
667        let mut state = self.state();
668
669        state.add_resource(&mut resource.untyped);
670
671        if let Some(type_uuid) = resource.untyped.type_uuid_non_blocking() {
672            let needed_type_uuid = <T as TypeUuidProvider>::type_uuid();
673            if type_uuid != needed_type_uuid {
674                panic!(
675                    "Unable to add a resource of type {needed_type_uuid} from resource UUID {}! The resource is \
676                    loaded but its actual data has type {type_uuid}!",
677                    resource.resource_uuid(),
678                );
679            }
680        }
681    }
682
683    /// The same as [`Self::request`], but returns [`None`] if type UUID of `T` does not match the actual type UUID
684    /// of the resource.
685    ///
686    /// ## Panic
687    ///
688    /// This method does not panic.
689    pub fn try_request<T>(&self, path: impl AsRef<Path>) -> Option<Resource<T>>
690    where
691        T: TypedResourceData,
692    {
693        let mut state = self.state();
694        let untyped = state.request(path.as_ref());
695        if untyped
696            .type_uuid_non_blocking()
697            .is_some_and(|uuid| uuid == <T as TypeUuidProvider>::type_uuid())
698            || state
699                .loaders
700                .safe_lock()
701                .is_extension_matches_type::<T>(path.as_ref())
702        {
703            Some(Resource {
704                untyped,
705                phantom: PhantomData::<T>,
706            })
707        } else {
708            None
709        }
710    }
711
712    /// Tries to fetch a path of the given untyped resource. The path may be missing in a few cases:
713    ///
714    /// 1) The resource is in invalid state (not in [`ResourceState::Ok`]).
715    /// 2) The resource wasn't registered in the resource registry.
716    /// 3) The resource registry wasn't loaded.
717    pub fn resource_path(&self, resource: impl AsRef<UntypedResource>) -> Option<PathBuf> {
718        self.state().resource_path(resource.as_ref())
719    }
720
721    /// Tries to fetch a resource path associated with the given UUID. Returns [`None`] if there's
722    /// no resource with the given UUID.
723    pub fn uuid_to_resource_path(&self, resource_uuid: Uuid) -> Option<PathBuf> {
724        self.state().uuid_to_resource_path(resource_uuid)
725    }
726
727    /// Same as [`Self::request`], but returns untyped resource.
728    pub fn request_untyped<P>(&self, path: P) -> UntypedResource
729    where
730        P: AsRef<Path>,
731    {
732        self.state().request(path)
733    }
734
735    /// Tries to update the registry if possible on the current platform, and if not - try to load
736    /// an existing one. Some platforms do not have a file system, so the registry must be prepared
737    /// on a platform that **does** have it and then saved to be loaded later on. For example,
738    /// WebAssembly platform does not have a file system and the resource manager will try to load
739    /// an existing registry instead of updating it.
740    pub fn update_or_load_registry(&self) {
741        self.state().update_or_load_registry();
742    }
743
744    /// Adds a new resource loader of the given type.
745    pub fn add_loader<T: ResourceLoader>(&self, loader: T) -> Option<T> {
746        self.state().add_loader(loader)
747    }
748
749    /// Add the given resource to the manager and registers the resource as an external resource with the given
750    /// path, updating the metadata file with the resource's UUID and updating the registry file with the resource's path.
751    /// Calling this should only be necessary after newly creating the file in the given path by saving the resource
752    /// to the file, otherwise the resource's path should already have been discovered by
753    /// [`ResourceManager::update_or_load_registry`].
754    /// If the manager already has a resource with this resource's UUID, return [`ResourceRegistrationError::AlreadyRegistered`].
755    pub fn register(
756        &self,
757        resource: UntypedResource,
758        path: impl AsRef<Path>,
759    ) -> Result<(), ResourceRegistrationError> {
760        self.state().register(resource, path)
761    }
762
763    /// Checks whether the given resource is a built-in resource instance or not.
764    pub fn is_built_in_resource(&self, resource: impl AsRef<UntypedResource>) -> bool {
765        self.state()
766            .built_in_resources
767            .is_built_in_resource(resource)
768    }
769
770    /// Creates a resource movement context.
771    #[allow(clippy::await_holding_lock)]
772    pub async fn make_resource_move_context(
773        &self,
774        src_path: impl AsRef<Path>,
775        dest_path: impl AsRef<Path>,
776        overwrite_existing: bool,
777    ) -> Result<ResourceMoveContext, ResourceMovementError> {
778        self.state()
779            .make_resource_move_context(src_path, dest_path, overwrite_existing)
780            .await
781    }
782
783    /// Returns `true` if a resource at the `src_path` can be moved to the `dest_path`, false -
784    /// otherwise. Source path must be a valid resource path, and the dest path must have a valid
785    /// new directory part of the path.
786    #[allow(clippy::await_holding_lock)]
787    pub async fn can_resource_be_moved(
788        &self,
789        src_path: impl AsRef<Path>,
790        dest_path: impl AsRef<Path>,
791        overwrite_existing: bool,
792    ) -> bool {
793        self.state()
794            .can_resource_be_moved(src_path, dest_path, overwrite_existing)
795            .await
796    }
797
798    /// Tries to move a resource at the given path to the new path. The path of the resource must be
799    /// registered in the resource registry for the resource to be moveable. This method can also be
800    /// used to rename the source file of a resource.
801    #[allow(clippy::await_holding_lock)]
802    pub async fn move_resource_by_path(
803        &self,
804        src_path: impl AsRef<Path>,
805        dest_path: impl AsRef<Path>,
806        overwrite_existing: bool,
807    ) -> Result<(), ResourceMovementError> {
808        self.state()
809            .move_resource_by_path(src_path, dest_path, overwrite_existing)
810            .await
811    }
812
813    /// Attempts to move a resource from its current location to the new path. The resource must
814    /// be registered in the resource registry to be moveable. This method can also be used to
815    /// rename the source file of a resource.
816    pub async fn move_resource(
817        &self,
818        resource: impl AsRef<UntypedResource>,
819        new_path: impl AsRef<Path>,
820        overwrite_existing: bool,
821    ) -> Result<(), ResourceMovementError> {
822        let resource_path = self.resource_path(resource).ok_or_else(|| {
823            FileError::Custom(
824                "Cannot move the resource because it does not have a path!".to_string(),
825            )
826        })?;
827
828        self.move_resource_by_path(resource_path, new_path, overwrite_existing)
829            .await
830    }
831
832    /// Reloads all loaded resources. Normally it should never be called, because it is **very** heavy
833    /// method! This method is asynchronous, it uses all available CPU power to reload resources as
834    /// fast as possible.
835    pub async fn reload_resources(&self) {
836        let resources = self.state().reload_resources();
837        join_all(resources).await;
838    }
839
840    /// Checks if there's a loader for the given resource path.
841    pub fn is_supported_resource(&self, path: &Path) -> bool {
842        self.state().is_supported_resource(path)
843    }
844
845    /// Checks if the given path is located inside the folder tracked by the resource registry.
846    pub fn is_path_in_registry(&self, path: &Path) -> bool {
847        self.state().is_path_in_registry(path)
848    }
849
850    /// Tries to move a folder to some other folder.
851    #[allow(clippy::await_holding_lock)]
852    pub async fn try_move_folder(
853        &self,
854        src_dir: &Path,
855        dest_dir: &Path,
856        overwrite_existing: bool,
857    ) -> Result<(), FolderMovementError> {
858        self.state()
859            .try_move_folder(src_dir, dest_dir, overwrite_existing)
860            .await
861    }
862
863    /// Tries to resave all the native resources registered in the resource registry. This method could
864    /// be useful for assets migration to a newer version.
865    ///
866    /// # Platform-specific
867    ///
868    /// Does nothing on WebAssembly.
869    pub async fn resave_native_resources(&self) {
870        #[cfg(not(target_arch = "wasm32"))]
871        {
872            let paths = self.state().collect_native_resources();
873            let resources = join_all(paths.iter().map(|path| self.request_untyped(path))).await;
874            for (resource, path) in resources.into_iter().zip(paths) {
875                match resource {
876                    Ok(resource) => match resource.save(&path) {
877                        Ok(_) => {
878                            info!("The {} resource was resaved successfully!", path.display());
879                        }
880                        Err(err) => {
881                            err!(
882                                "Unable to resave the {} resource. Reason: {:?}",
883                                path.display(),
884                                err
885                            )
886                        }
887                    },
888                    Err(err) => {
889                        err!(
890                            "Unable to resave the {} resource. Reason: {:?}",
891                            path.display(),
892                            err
893                        );
894                    }
895                }
896            }
897        }
898    }
899}
900
901impl ResourceManagerState {
902    pub(crate) fn new(io: Arc<dyn ResourceIo>, task_pool: Arc<TaskPool>) -> Self {
903        Self {
904            resources: Default::default(),
905            loaders: Default::default(),
906            event_broadcaster: Default::default(),
907            constructors_container: Default::default(),
908            watcher: None,
909            built_in_resources: Default::default(),
910            resource_registry: Arc::new(Mutex::new(ResourceRegistry::new(io.clone()))),
911            task_pool,
912            resource_io: io,
913        }
914    }
915
916    /// Tries to update the registry if possible on the current platform, and if not - try to load
917    /// an existing one. Some platforms do not have a file system, so the registry must be prepared
918    /// on a platform that **does** have it and then saved to be loaded later on. For example,
919    /// WebAssembly platform does not have a file system and the resource manager will try to load
920    /// an existing registry instead of updating it.
921    pub fn update_or_load_registry(&self) {
922        let resource_io = self.resource_io.clone();
923        let resource_registry = self.resource_registry.clone();
924        #[allow(unused_variables)]
925        let excluded_folders = resource_registry.safe_lock().excluded_folders.clone();
926        let registry_status = resource_registry.safe_lock().status_flag();
927        registry_status.mark_as_loading();
928        #[allow(unused_variables)]
929        let task_loaders = self.loaders.clone();
930        let path = resource_registry.safe_lock().path().to_path_buf();
931
932        // Try to update the registry first.
933        #[cfg(not(any(target_arch = "wasm32", target_os = "android")))]
934        fyrox_core::futures::executor::block_on(async move {
935            let new_data =
936                ResourceRegistry::scan(resource_io.clone(), task_loaders, &path, excluded_folders)
937                    .await;
938            let mut registry_lock = resource_registry.safe_lock();
939            registry_lock.modify().set_container(new_data);
940            registry_status.mark_as_loaded();
941        });
942
943        #[cfg(any(target_arch = "wasm32", target_os = "android"))]
944        self.task_pool.spawn_task(async move {
945            use crate::registry::RegistryContainerExt;
946            // Then load the registry.
947            info!("Trying to load or update the registry at {path:?}...");
948            match crate::registry::RegistryContainer::load_from_file(&path, &*resource_io).await {
949                Ok(registry) => {
950                    let mut registry_lock = resource_registry.safe_lock();
951                    registry_lock.modify().set_container(registry);
952                    info!("Resource registry was loaded from {path:?} successfully!");
953                }
954                Err(error) => {
955                    err!("Unable to load resource registry! Reason: {error}.");
956                }
957            };
958            registry_status.mark_as_loaded();
959        });
960    }
961
962    /// Tries to find all the native resources registered in the resource registry, returns a collection
963    /// of paths to such resources.
964    pub fn collect_native_resources(&self) -> Vec<PathBuf> {
965        let loaders = self.loaders.safe_lock();
966        let registry = self.resource_registry.safe_lock();
967        let mut paths = Vec::new();
968        for entry in registry.inner().values() {
969            let ext = some_or_continue!(entry.extension().and_then(|ext| ext.to_str()));
970            for loader in loaders.iter() {
971                if loader.is_native_extension(ext) {
972                    paths.push(entry.clone());
973                }
974            }
975        }
976        paths
977    }
978
979    /// Returns the task pool used by this resource manager.
980    pub fn task_pool(&self) -> Arc<TaskPool> {
981        self.task_pool.clone()
982    }
983
984    /// Set the IO source that the resource manager should use when
985    /// loading assets
986    pub fn set_resource_io(&mut self, resource_io: Arc<dyn ResourceIo>) {
987        self.resource_io = resource_io;
988    }
989
990    /// Sets resource watcher which will track any modifications in file system and forcing
991    /// the manager to reload changed resources. By default there is no watcher, since it
992    /// may be an undesired effect to reload resources at runtime. This is very useful thing
993    /// for fast iterative development.
994    pub fn set_watcher(&mut self, watcher: Option<FileSystemWatcher>) {
995        self.watcher = watcher;
996    }
997
998    /// Returns total amount of registered resources.
999    pub fn count_registered_resources(&self) -> usize {
1000        self.resources.len()
1001    }
1002
1003    /// Returns percentage of loading progress. This method is useful to show progress on
1004    /// loading screen in your game. This method could be used alone if your game depends
1005    /// only on external resources, or if your game doing some heavy calculations this value
1006    /// can be combined with progress of your tasks.
1007    pub fn loading_progress(&self) -> usize {
1008        let registered = self.count_registered_resources();
1009        if registered > 0 {
1010            self.count_loaded_resources() * 100 / registered
1011        } else {
1012            100
1013        }
1014    }
1015
1016    fn try_get_event(&self) -> Option<Event> {
1017        self.watcher.as_ref()?.try_get_event()
1018    }
1019
1020    /// Handle events in the file system relating to adding, removing, or modifying resources.
1021    /// This may involve updating the registry to reflect changes to the resources, and it may
1022    /// involve creating new meta files for resources that are missing meta files.
1023    pub fn process_filesystem_events(&mut self) {
1024        let mut modified_files = FxHashSet::default();
1025        while let Some(mut evt) = self.try_get_event() {
1026            if evt.need_rescan() {
1027                info!("Filesystem watcher has forced a rescan!");
1028                self.update_or_load_registry();
1029                self.reload_resources();
1030            } else {
1031                use notify::event::{CreateKind, ModifyKind, RemoveKind, RenameMode};
1032                use notify::EventKind;
1033                match evt.kind {
1034                    EventKind::Create(CreateKind::Any | CreateKind::File) => {
1035                        self.on_create_event(evt.paths.first())
1036                    }
1037                    EventKind::Modify(ModifyKind::Name(RenameMode::From)) => {
1038                        self.on_remove_event(evt.paths.first())
1039                    }
1040                    EventKind::Modify(ModifyKind::Name(RenameMode::To)) => {
1041                        self.on_create_event(evt.paths.first())
1042                    }
1043                    EventKind::Modify(ModifyKind::Name(RenameMode::Both)) => {
1044                        self.on_remove_event(evt.paths.first());
1045                        self.on_create_event(evt.paths.get(1));
1046                    }
1047                    EventKind::Modify(ModifyKind::Any | ModifyKind::Data(_)) => {
1048                        let path = evt.paths.get_mut(0).map(std::mem::take);
1049                        modified_files.insert(path);
1050                    }
1051                    EventKind::Remove(RemoveKind::Any | RemoveKind::File) => {
1052                        self.on_remove_event(evt.paths.first())
1053                    }
1054                    _ => (),
1055                }
1056            }
1057        }
1058        for path in modified_files {
1059            self.on_file_content_event(path.as_ref())
1060        }
1061    }
1062
1063    fn on_create_event(&mut self, path: Option<&PathBuf>) {
1064        let path = some_or_return!(path);
1065        let mut relative_path = ok_or_return!(fyrox_core::make_relative_path(path));
1066        let ext = some_or_return!(relative_path.extension());
1067        let mut registry = self.resource_registry.safe_lock();
1068        if registry
1069            .excluded_folders
1070            .iter()
1071            .any(|folder| relative_path.starts_with(folder))
1072        {
1073            return;
1074        }
1075        // An event for a created file does not guarantee that the file actually exists.
1076        // It might have been deleted or renamed between being created and us receiving the event.
1077        if !block_on(self.resource_io.exists(&relative_path)) {
1078            return;
1079        }
1080        if ext == ResourceMetadata::EXTENSION {
1081            // Remove the meta extension from the path to get the path of the resource.
1082            relative_path.set_extension("");
1083            match registry.modify().read_metadata(relative_path.clone()) {
1084                Ok(RegistryUpdate {
1085                    changed,
1086                    value: metadata,
1087                }) => {
1088                    if changed {
1089                        info!(
1090                        "The resource {relative_path:?} was registered successfully with {} id from a newly created meta file!",
1091                        metadata.resource_id
1092                        )
1093                    }
1094                }
1095                Err(err) => {
1096                    err!(
1097                        "Unable to read the metadata for resource {relative_path:?}. Reason: {err}",
1098                    )
1099                }
1100            }
1101        } else if self
1102            .loaders
1103            .safe_lock()
1104            .is_supported_resource(&relative_path)
1105        {
1106            Self::on_new_resource_file(registry.modify(), relative_path.clone());
1107            drop(registry);
1108            if self.try_reload_resource_from_path(&relative_path) {
1109                info!(
1110                    "File {relative_path:?} was created, trying to reload a respective resource...",
1111                );
1112            }
1113        }
1114    }
1115    /// A new resource file has been discovered through an event. Check if there is a corresponding meta file,
1116    /// and if not then create the meta file with a randomly generated UUID.
1117    fn on_new_resource_file(mut registry: ResourceRegistryRefMut<'_>, relative_path: PathBuf) {
1118        match registry.read_metadata(relative_path.clone()) {
1119            Ok(RegistryUpdate {
1120                changed,
1121                value: metadata,
1122            }) => {
1123                if changed {
1124                    info!(
1125                    "The newly created resource {relative_path:?} was registered successfully with {} id from the meta file!",
1126                    metadata.resource_id
1127                    )
1128                }
1129            }
1130            Err(_) => {
1131                let uuid = Uuid::new_v4();
1132                match registry.write_metadata(uuid, relative_path.clone()) {
1133                    Ok(RegistryUpdate {
1134                        changed,
1135                        value: old_path,
1136                    }) => {
1137                        assert!(old_path.is_none());
1138                        if changed {
1139                            info!("The newly created resource {relative_path:?} was registered successfully with new id: {uuid}");
1140                        }
1141                    }
1142                    Err(err) => {
1143                        err!("Unable to register the resource {relative_path:?}. Reason: {err}")
1144                    }
1145                }
1146            }
1147        }
1148    }
1149    fn on_remove_event(&mut self, path: Option<&PathBuf>) {
1150        let path = some_or_return!(path);
1151        let mut relative_path = ok_or_return!(fyrox_core::make_relative_path(path));
1152        let ext = some_or_return!(relative_path.extension());
1153        // For the purposes of updating the registry, only the removal of meta files is relevant.
1154        if ext != ResourceMetadata::EXTENSION {
1155            return;
1156        }
1157        // An event for a deleted file does not guarantee that the file does not exist.
1158        // It might have been created again between being deleted and us receiving the event.
1159        // If the file exists now, ignore the remove event.
1160        if block_on(self.resource_io.exists(&relative_path)) {
1161            return;
1162        }
1163
1164        let mut registry = self.resource_registry.safe_lock();
1165        if registry
1166            .excluded_folders
1167            .iter()
1168            .any(|folder| relative_path.starts_with(folder))
1169        {
1170            return;
1171        }
1172        // Remove the meta extension from the path to get the path of the resource.
1173        relative_path.set_extension("");
1174        // Check whether the resource file exists, and if not then there is nothing more to do.
1175        if !block_on(self.resource_io.exists(&relative_path)) {
1176            return;
1177        }
1178        // Recreate the meta file, using the current UUID for the resource's path, if possible.
1179        let uuid = match registry.path_to_uuid(&relative_path) {
1180            Some(uuid) => {
1181                info!("The meta file for {relative_path:?} was removed, but its UUID is still in memory: {uuid}");
1182                uuid
1183            }
1184            None => {
1185                info!("The meta file for {relative_path:?} was removed and its UUID is lost!");
1186                Uuid::new_v4()
1187            }
1188        };
1189        let result = registry
1190            .modify()
1191            .write_metadata(uuid, relative_path.clone());
1192        match result {
1193            Ok(RegistryUpdate { changed, .. }) => {
1194                if changed {
1195                    info!(
1196                        "The resource {relative_path:?} was registered successfully with {uuid} id after its meta file was removed!",
1197                    )
1198                } else {
1199                    info!(
1200                        "The meta file for resource {relative_path:?} was recreated with {uuid} id!",
1201                    )
1202                }
1203            }
1204            Err(err) => {
1205                err!(
1206                    "Unable to register the resource {relative_path:?} after its meta file was removed. Reason: {err}",
1207                )
1208            }
1209        }
1210    }
1211    fn on_file_content_event(&mut self, path: Option<&PathBuf>) {
1212        let path = some_or_return!(path);
1213        let mut relative_path = ok_or_return!(fyrox_core::make_relative_path(path));
1214        let ext = some_or_return!(relative_path.extension());
1215        let mut registry = self.resource_registry.safe_lock();
1216        if registry
1217            .excluded_folders
1218            .iter()
1219            .any(|folder| relative_path.starts_with(folder))
1220        {
1221            return;
1222        }
1223        // An event for a modified file does not guarantee that the file actually exists.
1224        // It might have been deleted or renamed between being modified and us receiving the event.
1225        if !block_on(self.resource_io.exists(&relative_path)) {
1226            return;
1227        }
1228
1229        if ext == ResourceMetadata::EXTENSION {
1230            // Remove the meta extension from the path to get the path of the resource.
1231            relative_path.set_extension("");
1232            match registry.modify().read_metadata(relative_path.clone()) {
1233                Ok(RegistryUpdate {
1234                    changed,
1235                    value: metadata,
1236                }) => {
1237                    if changed {
1238                        info!(
1239                            "The resource {relative_path:?} was registered successfully with {} id after meta file modification",
1240                            metadata.resource_id
1241                        )
1242                    }
1243                }
1244                Err(err) => {
1245                    err!(
1246                        "Unable to read the metadata for resource {relative_path:?} after meta file modification. Reason: {err}",
1247                    )
1248                }
1249            }
1250        } else if self
1251            .loaders
1252            .safe_lock()
1253            .is_supported_resource(&relative_path)
1254        {
1255            drop(registry);
1256            if self.try_reload_resource_from_path(&relative_path) {
1257                info!(
1258                    "File {relative_path:?} was changed, trying to reload a respective resource...",
1259                );
1260            }
1261        }
1262    }
1263
1264    /// Update resource containers and do hot-reloading.
1265    ///
1266    /// Resources are removed if they're not used
1267    /// or reloaded if they have changed in disk.
1268    ///
1269    /// Normally, this is called from `Engine::update()`.
1270    /// You should only call this manually if you don't use that method.
1271    pub fn update(&mut self, dt: f32) {
1272        self.resources.retain_mut(|resource| {
1273            // One usage means that the resource has single owner, and that owner
1274            // is this container. Such resources have limited life time, if the time
1275            // runs out before it gets shared again, the resource will be deleted.
1276            if resource.value.use_count() <= 1 {
1277                resource.time_to_live -= dt;
1278                if resource.time_to_live <= 0.0 {
1279                    let registry = self.resource_registry.safe_lock();
1280                    let resource_uuid = resource.resource_uuid();
1281                    if let Some(path) = registry.uuid_to_path(resource_uuid) {
1282                        info!("Resource {path:?} destroyed because it is not used anymore!",);
1283                        self.event_broadcaster
1284                            .broadcast(ResourceEvent::Removed(path.to_path_buf()));
1285                    }
1286
1287                    false
1288                } else {
1289                    // Keep resource alive for short period of time.
1290                    true
1291                }
1292            } else {
1293                // Make sure to reset timer if a resource is used by more than one owner.
1294                resource.time_to_live = DEFAULT_RESOURCE_LIFETIME;
1295
1296                // Keep resource alive while it has more than one owner.
1297                true
1298            }
1299        });
1300        self.process_filesystem_events();
1301    }
1302
1303    fn add_resource_and_notify(&mut self, resource: UntypedResource) {
1304        self.event_broadcaster
1305            .broadcast(ResourceEvent::Added(resource.clone()));
1306
1307        self.resources.push(TimedEntry {
1308            value: resource,
1309            time_to_live: DEFAULT_RESOURCE_LIFETIME,
1310        });
1311    }
1312
1313    /// Tries to find a resource by its path. Returns None if no resource was found.
1314    ///
1315    /// # Complexity
1316    ///
1317    /// O(n)
1318    pub fn find_by_uuid(&self, uuid: Uuid) -> Option<&UntypedResource> {
1319        self.resources
1320            .iter()
1321            .find(|entry| entry.value.resource_uuid() == uuid)
1322            .map(|entry| &entry.value)
1323    }
1324
1325    /// Tries to find a resource by a path. Returns None if no resource was found.
1326    ///
1327    /// # Complexity
1328    ///
1329    /// O(n)
1330    pub fn find_by_path(&self, path: &Path) -> Option<&UntypedResource> {
1331        let registry = self.resource_registry.safe_lock();
1332        self.resources.iter().find_map(|entry| {
1333            if registry.uuid_to_path(entry.resource_uuid()) == Some(path) {
1334                return Some(&entry.value);
1335            }
1336            None
1337        })
1338    }
1339
1340    /// Returns total amount of resources in the container.
1341    pub fn len(&self) -> usize {
1342        self.resources.len()
1343    }
1344
1345    /// Returns true if the resource manager has no resources.
1346    pub fn is_empty(&self) -> bool {
1347        self.resources.is_empty()
1348    }
1349
1350    /// Creates an iterator over resources in the manager.
1351    pub fn iter(&self) -> impl Iterator<Item = &UntypedResource> {
1352        self.resources.iter().map(|entry| &entry.value)
1353    }
1354
1355    /// Immediately destroys all resources in the manager that are not used anywhere else.
1356    pub fn destroy_unused_resources(&mut self) {
1357        self.resources
1358            .retain(|resource| resource.value.use_count() > 1);
1359    }
1360
1361    /// Returns total amount of resources that still loading.
1362    pub fn count_pending_resources(&self) -> usize {
1363        self.resources.iter().filter(|r| r.is_loading()).count()
1364    }
1365
1366    /// Returns total amount of completely loaded resources.
1367    pub fn count_loaded_resources(&self) -> usize {
1368        self.resources.iter().filter(|r| r.is_ok()).count()
1369    }
1370
1371    /// Returns a set of resource handled by this container.
1372    pub fn resources(&self) -> Vec<UntypedResource> {
1373        self.resources.iter().map(|t| t.value.clone()).collect()
1374    }
1375
1376    /// Registers a new built-in resource, so it becomes accessible via [`Self::request`].
1377    pub fn register_built_in_resource<T: TypedResourceData>(
1378        &mut self,
1379        resource: BuiltInResource<T>,
1380    ) -> Option<UntypedBuiltInResource> {
1381        self.built_in_resources.add(resource)
1382    }
1383
1384    /// Find the resource for the given UUID without loading the resource.
1385    /// Searches the resource manager to find a resource with the given UUID, including built-in resources.
1386    /// If no resource is found, a new unloaded external resource is returned for the given UUID,
1387    /// because it is presumed that this is a real UUID for some resource that is not currently managed
1388    /// and therefore it should be added to the manager.
1389    pub fn find_uuid(&mut self, uuid: Uuid) -> UntypedResource {
1390        if let Some(built_in_resource) = self.built_in_resources.find_by_uuid(uuid) {
1391            return built_in_resource.resource.clone();
1392        }
1393
1394        if let Some(existing) = self.find_by_uuid(uuid) {
1395            existing.clone()
1396        } else {
1397            let resource = UntypedResource::new_unloaded(uuid);
1398            self.add_resource_and_notify(resource.clone());
1399            resource
1400        }
1401    }
1402
1403    /// Searches the resource manager and the registry to find a resource with the given path,
1404    /// including built-in resources. If no resource is found, a new UUID is generated and the
1405    /// path is added to the registry and an unloaded resource is returned.
1406    pub fn find<P>(&mut self, path: P) -> UntypedResource
1407    where
1408        P: AsRef<Path>,
1409    {
1410        if let Some(built_in_resource) = self.built_in_resources.get(path.as_ref()) {
1411            return built_in_resource.resource.clone();
1412        }
1413
1414        let path = ResourceRegistry::normalize_path(path);
1415
1416        if let Some(existing) = self.find_by_resource_path(&path) {
1417            existing.clone()
1418        } else {
1419            let mut registry = self.resource_registry.safe_lock();
1420            let uuid = if let Some(uuid) = registry.path_to_uuid(&path) {
1421                uuid
1422            } else {
1423                let uuid = Uuid::new_v4();
1424                registry.modify().register(uuid, path);
1425                uuid
1426            };
1427            drop(registry);
1428            let resource = UntypedResource::new_unloaded(uuid);
1429            self.add_resource_and_notify(resource.clone());
1430            resource
1431        }
1432    }
1433
1434    /// Tries to load the resource at the given path.
1435    pub fn request<P>(&mut self, path: P) -> UntypedResource
1436    where
1437        P: AsRef<Path>,
1438    {
1439        if let Some(built_in_resource) = self.built_in_resources.get(path.as_ref()) {
1440            return built_in_resource.resource.clone();
1441        }
1442
1443        let path = ResourceRegistry::normalize_path(path);
1444
1445        self.find_or_load(path)
1446    }
1447
1448    /// Tries to load the resource for the given UUID.
1449    pub fn request_uuid(&mut self, uuid: Uuid) -> UntypedResource {
1450        let mut resource = uuid.into();
1451        self.request_resource(&mut resource);
1452        resource
1453    }
1454
1455    /// Use the registry to find a resource with the given path, blocking until the registry is loaded if necessary.
1456    fn find_by_resource_path(&self, path_to_search: &Path) -> Option<&UntypedResource> {
1457        let registry = self.resource_registry.safe_lock();
1458        self.resources
1459            .iter()
1460            .find(move |entry| registry.uuid_to_path(entry.resource_uuid()) == Some(path_to_search))
1461            .map(|entry| &entry.value)
1462    }
1463
1464    /// If a resource exists for the given path, return it.
1465    /// Otherwise, check the registry for a UUID for the path, create
1466    /// a resource, begin loading, and return the resource.
1467    /// If the given path does not correspond to any registered UUID,
1468    /// create and return an error resource.
1469    fn find_or_load(&mut self, path: PathBuf) -> UntypedResource {
1470        match self.find_by_resource_path(&path) {
1471            Some(existing) => existing.clone(),
1472            None => self.load_resource(path),
1473        }
1474    }
1475
1476    fn load_resource(&mut self, path: PathBuf) -> UntypedResource {
1477        let mut registry = self.resource_registry.safe_lock();
1478        let uuid = if let Some(uuid) = registry.path_to_uuid(&path) {
1479            uuid
1480        } else {
1481            let uuid = Uuid::new_v4();
1482            registry.modify().register(uuid, path);
1483            uuid
1484        };
1485        drop(registry);
1486        let resource = UntypedResource::new_pending(uuid, ResourceKind::External);
1487        self.add_resource_and_notify(resource.clone());
1488        self.spawn_loading_task(resource.clone(), false);
1489        resource
1490    }
1491
1492    /// Add a task to the task pool to load the given resource.
1493    /// Panic if the given resource is unregistered or embedded.
1494    fn spawn_loading_task(&self, mut resource: UntypedResource, reload: bool) {
1495        let event_broadcaster = self.event_broadcaster.clone();
1496        let loaders = self.loaders.clone();
1497        let registry = self.resource_registry.clone();
1498        let io = self.resource_io.clone();
1499        let registry_status = registry.safe_lock().status_flag();
1500
1501        self.task_pool.spawn_task(async move {
1502            // Wait until the registry is fully loaded.
1503            let registry_status = registry_status.await;
1504            if registry_status == ResourceRegistryStatus::Unknown {
1505                resource.commit_error(
1506                    PathBuf::default(),
1507                    LoadError::new("The resource registry is unavailable!".to_string()),
1508                );
1509                return;
1510            }
1511
1512            let Some(path) = registry
1513                .safe_lock()
1514                .uuid_to_path(resource.resource_uuid())
1515                .map(|p| p.to_path_buf())
1516            else {
1517                let error = format!(
1518                    "Resource {} failed to load. The path was not found \
1519                        in the registry!",
1520                    resource.resource_uuid(),
1521                );
1522
1523                resource.commit_error(PathBuf::default(), error);
1524                return;
1525            };
1526
1527            // Try to find a loader for the resource.
1528            let loader_future = loaders
1529                .safe_lock()
1530                .loader_for(&path)
1531                .map(|loader| loader.load(path.clone(), io));
1532
1533            if let Some(loader_future) = loader_future {
1534                match loader_future.await {
1535                    Ok(data) => {
1536                        let data = data.0;
1537
1538                        let mut header = resource.lock();
1539
1540                        assert!(header.kind.is_external());
1541
1542                        header.state.commit(ResourceState::Ok {
1543                            data: ResourceDataWrapper(data),
1544                        });
1545
1546                        drop(header);
1547
1548                        event_broadcaster.broadcast_loaded_or_reloaded(resource, reload);
1549
1550                        Log::info(format!(
1551                            "Resource {} was loaded successfully!",
1552                            path.display()
1553                        ));
1554                    }
1555                    Err(error) => {
1556                        if reload {
1557                            if resource.is_ok() {
1558                                info!("Resource {path:?} failed to reload, keeping the existing version. Reason: {error}");
1559                            }
1560                            else
1561                            {
1562                                info!("Resource {path:?} failed to reload. Reason: {error}");
1563                                resource.commit_error(path.to_path_buf(), error);
1564                            }
1565                        }
1566                        else
1567                        {
1568                            info!("Resource {path:?} failed to load. Reason: {error}");
1569                            resource.commit_error(path.to_path_buf(), error);
1570                        }
1571                    }
1572                }
1573            } else {
1574                let error = format!("There's no resource loader for {path:?} resource!",);
1575                resource.commit_error(path, error);
1576            }
1577        });
1578    }
1579
1580    /// Tries to fetch a path of the given untyped resource. The path may be missing in a few cases:
1581    ///
1582    /// 1) The resource wasn't registered in the resource registry.
1583    /// 2) The resource registry wasn't loaded.
1584    ///
1585    /// ## Built-in resources
1586    ///
1587    /// As a last resort, this method tries to find a built-in resource descriptor corresponding
1588    /// to the given resource and returns its "path". In reality, it is just a string id, since
1589    /// built-in resources are stored inside the binary.
1590    pub fn resource_path(&self, resource: &UntypedResource) -> Option<PathBuf> {
1591        self.uuid_to_resource_path(resource.resource_uuid())
1592    }
1593
1594    /// Tries to fetch a resource path associated with the given UUID. Returns [`None`] if there's
1595    /// no resource with the given UUID.
1596    ///
1597    /// ## Built-in resources
1598    ///
1599    /// As a last resort, this method tries to find a built-in resource descriptor corresponding
1600    /// to the given resource uuid and returns its "path". In reality, it is just a string id, since
1601    /// built-in resources are stored inside the binary.
1602    pub fn uuid_to_resource_path(&self, resource_uuid: Uuid) -> Option<PathBuf> {
1603        if let Some(path) = self
1604            .resource_registry
1605            .safe_lock()
1606            .uuid_to_path_buf(resource_uuid)
1607        {
1608            Some(path)
1609        } else {
1610            self.built_in_resources
1611                .find_by_uuid(resource_uuid)
1612                .map(|built_in_resource| built_in_resource.id.clone())
1613        }
1614    }
1615
1616    /// Adds a new resource loader of the given type.
1617    pub fn add_loader<T: ResourceLoader>(&self, loader: T) -> Option<T> {
1618        self.loaders.safe_lock().set(loader)
1619    }
1620
1621    /// Tries to load the given resource, based on the resource's UUID,
1622    /// and adds the resource to the manager if it is not already in the manager.
1623    pub fn request_resource(&mut self, resource: &mut UntypedResource) {
1624        if let Some(r) = self.find_by_uuid(resource.resource_uuid()) {
1625            // We are already managing a resource with this UUID, so modify the given
1626            // resource to point to our resource.
1627            *resource = r.clone();
1628            if resource.is_unloaded() {
1629                // If the resource is in the unloaded state, start it loading, because it has been requested.
1630                resource.make_pending();
1631                self.spawn_loading_task(resource.clone(), false);
1632            }
1633        } else if let Some(r) = self
1634            .built_in_resources
1635            .find_by_uuid(resource.resource_uuid())
1636        {
1637            // A built-in resource has this UUID, so modify the given resource to
1638            // point to the built-in resource.
1639            *resource = r.resource.clone();
1640        } else if resource.is_ok() || resource.is_embedded() {
1641            // This is an unknown resource, but it is ready to use, so add it to our managed
1642            // resources.
1643            self.add_resource_and_notify(resource.clone());
1644        } else {
1645            // This is an unknown resource and it is not ready to use, so add it to our managed
1646            // resources and begin the loading process.
1647            resource.make_pending();
1648            self.add_resource_and_notify(resource.clone());
1649            self.spawn_loading_task(resource.clone(), false);
1650        }
1651    }
1652
1653    /// Add the given resource to the resource manager, based on the resource's UUID,
1654    /// without initiating the loading of the resource. The given resource may be modified
1655    /// to be a reference to the shared data of an existing resource with the same UUID.
1656    pub fn add_resource(&mut self, resource: &mut UntypedResource) {
1657        if let Some(r) = self.find_by_uuid(resource.resource_uuid()) {
1658            // We are already managing a resource with this UUID, so modify the given
1659            // resource to point to our resource.
1660            *resource = r.clone();
1661        } else if let Some(r) = self
1662            .built_in_resources
1663            .find_by_uuid(resource.resource_uuid())
1664        {
1665            // A built-in resource has this UUID, so modify the given resource to
1666            // point to the built in resource.
1667            *resource = r.resource.clone();
1668        } else {
1669            // This is an unknown resource, so add it to our managed resources.
1670            self.add_resource_and_notify(resource.clone());
1671        }
1672    }
1673
1674    /// Add the given resource to the manager and registers the resource as an external resource with the given
1675    /// path, updating the metadata file with the resource's UUID and updating the registry file with the resource's path.
1676    /// Calling this should only be necessary after newly creating the file in the given path by saving the resource
1677    /// to the file, otherwise the resource's path should already have been discovered by
1678    /// [`ResourceManagerState::update_or_load_registry`].
1679    /// If the manager already has a resource with this resource's UUID, return [`ResourceRegistrationError::AlreadyRegistered`].
1680    pub fn register(
1681        &mut self,
1682        resource: UntypedResource,
1683        path: impl AsRef<Path>,
1684    ) -> Result<(), ResourceRegistrationError> {
1685        let resource_uuid = resource.resource_uuid();
1686        if self.find_by_uuid(resource_uuid).is_some() {
1687            return Err(ResourceRegistrationError::AlreadyRegistered);
1688        }
1689        let path = ResourceRegistry::normalize_path(path);
1690        {
1691            let mut header = resource.lock();
1692            header.kind.make_external();
1693            let mut registry = self.resource_registry.safe_lock();
1694            let mut ctx = registry.modify();
1695            if ctx.write_metadata(resource_uuid, path).is_err() {
1696                return Err(ResourceRegistrationError::UnableToCreateMetadata);
1697            }
1698        }
1699        self.add_resource_and_notify(resource);
1700        Ok(())
1701    }
1702
1703    /// Reloads a single resource. Does nothing in case of built-in resources.
1704    /// Log an error if the resource cannot be reloaded.
1705    pub fn reload_resource(&mut self, resource: UntypedResource) {
1706        if self.built_in_resources.is_built_in_resource(&resource) {
1707            return;
1708        }
1709        let mut header = resource.lock();
1710        if !header.state.is_loading() {
1711            if !header.state.is_ok() {
1712                header.state.switch_to_pending_state();
1713            }
1714            drop(header);
1715            self.spawn_loading_task(resource, true)
1716        }
1717    }
1718
1719    /// Reloads all resources in the container. Returns a list of resources that will be reloaded.
1720    /// You can use the list to wait until all resources are loading.
1721    pub fn reload_resources(&mut self) -> Vec<UntypedResource> {
1722        let resources = self
1723            .resources
1724            .iter()
1725            .map(|r| r.value.clone())
1726            .collect::<Vec<_>>();
1727
1728        for resource in resources.iter().cloned() {
1729            self.reload_resource(resource);
1730        }
1731
1732        resources
1733    }
1734
1735    /// Wait until all resources are loaded (or failed to load).
1736    pub fn get_wait_context(&self) -> ResourceWaitContext {
1737        ResourceWaitContext {
1738            resources: self
1739                .resources
1740                .iter()
1741                .map(|e| e.value.clone())
1742                .collect::<Vec<_>>(),
1743        }
1744    }
1745
1746    /// Tries to reload a resource at the given path, and returns true if a reload will
1747    /// actually begin. Returns false if the resource was already loading or
1748    /// cannot be reloaded.
1749    pub fn try_reload_resource_from_path(&mut self, path: &Path) -> bool {
1750        // Do not try to reload unsupported resources.
1751        if !self.loaders.safe_lock().is_supported_resource(path) {
1752            return false;
1753        }
1754
1755        let Some(resource) = self.find_by_resource_path(path) else {
1756            return false;
1757        };
1758        let header = resource.lock();
1759        if header.state.is_loading() {
1760            return false;
1761        }
1762        drop(header);
1763        self.reload_resource(resource.clone());
1764        true
1765    }
1766
1767    /// Creates a resource movement context.
1768    #[allow(clippy::await_holding_lock)]
1769    pub async fn make_resource_move_context(
1770        &self,
1771        src_path: impl AsRef<Path>,
1772        dest_path: impl AsRef<Path>,
1773        overwrite_existing: bool,
1774    ) -> Result<ResourceMoveContext, ResourceMovementError> {
1775        let src_path = src_path.as_ref();
1776        let dest_path = dest_path.as_ref();
1777
1778        let relative_src_path = fyrox_core::make_relative_path(src_path)?;
1779        let relative_dest_path = fyrox_core::make_relative_path(dest_path)?;
1780
1781        if let Some(file_stem) = relative_dest_path.file_stem() {
1782            if !self.resource_io.is_valid_file_name(file_stem) {
1783                return Err(ResourceMovementError::DestinationPathIsInvalid {
1784                    src_path: relative_src_path.clone(),
1785                    dest_path: relative_dest_path.clone(),
1786                });
1787            }
1788        }
1789
1790        if !overwrite_existing && self.resource_io.exists(&relative_dest_path).await {
1791            return Err(ResourceMovementError::AlreadyExist {
1792                src_path: relative_src_path.clone(),
1793                dest_path: relative_dest_path.clone(),
1794            });
1795        }
1796
1797        let registry_lock_guard = self.resource_registry.safe_lock();
1798        let absolute_registry_dir = if let Some(directory) = registry_lock_guard.directory() {
1799            fyrox_core::replace_slashes(self.resource_io.canonicalize_path(directory).await?)
1800        } else {
1801            return Err(ResourceMovementError::ResourceRegistryLocationUnknown {
1802                resource_path: relative_src_path.clone(),
1803            });
1804        };
1805        let resource_uuid = registry_lock_guard
1806            .path_to_uuid(&relative_src_path)
1807            .ok_or_else(|| ResourceMovementError::NotInRegistry {
1808                resource_path: relative_src_path.clone(),
1809            })?;
1810
1811        let relative_dest_dir = relative_dest_path.parent().unwrap_or(Path::new("."));
1812        let absolute_dest_dir = fyrox_core::replace_slashes(
1813            self.resource_io
1814                .canonicalize_path(relative_dest_dir)
1815                .await?,
1816        );
1817
1818        let absolute_src_path = fyrox_core::replace_slashes(
1819            self.resource_io
1820                .canonicalize_path(&relative_src_path)
1821                .await?,
1822        );
1823        if !absolute_dest_dir.starts_with(&absolute_registry_dir) {
1824            return Err(ResourceMovementError::OutsideOfRegistry {
1825                absolute_src_path,
1826                absolute_dest_dir,
1827                absolute_registry_dir,
1828            });
1829        }
1830
1831        drop(registry_lock_guard);
1832
1833        Ok(ResourceMoveContext {
1834            relative_src_path,
1835            relative_dest_path,
1836            resource_uuid,
1837        })
1838    }
1839
1840    /// Returns `true` if a resource at the `src_path` can be moved to the `dest_path`, false -
1841    /// otherwise. Source path must be a valid resource path, and the dest path must have a valid
1842    /// new directory part of the path.
1843    pub async fn can_resource_be_moved(
1844        &self,
1845        src_path: impl AsRef<Path>,
1846        dest_path: impl AsRef<Path>,
1847        overwrite_existing: bool,
1848    ) -> bool {
1849        self.make_resource_move_context(src_path, dest_path, overwrite_existing)
1850            .await
1851            .is_ok()
1852    }
1853
1854    /// Tries to move a resource at the given path to the new path. The path of the resource must be
1855    /// registered in the resource registry for the resource to be moveable. This method can also be
1856    /// used to rename the source file of a resource.
1857    pub async fn move_resource_by_path(
1858        &self,
1859        src_path: impl AsRef<Path>,
1860        dest_path: impl AsRef<Path>,
1861        overwrite_existing: bool,
1862    ) -> Result<(), ResourceMovementError> {
1863        let ResourceMoveContext {
1864            relative_src_path,
1865            relative_dest_path,
1866
1867            resource_uuid,
1868        } = self
1869            .make_resource_move_context(src_path, dest_path, overwrite_existing)
1870            .await?;
1871
1872        // Move the file with its optional import options and mandatory metadata.
1873        self.resource_io
1874            .move_file(&relative_src_path, &relative_dest_path)
1875            .await?;
1876
1877        let current_path = self
1878            .resource_registry
1879            .safe_lock()
1880            .modify()
1881            .register(resource_uuid, relative_dest_path.to_path_buf());
1882        assert_eq!(current_path.value.as_ref(), Some(&relative_src_path));
1883
1884        let options_path = append_extension(&relative_src_path, OPTIONS_EXTENSION);
1885        if self.resource_io.exists(&options_path).await {
1886            let new_options_path = append_extension(&relative_dest_path, OPTIONS_EXTENSION);
1887            self.resource_io
1888                .move_file(&options_path, &new_options_path)
1889                .await?;
1890        }
1891
1892        let metadata_path = append_extension(&relative_src_path, ResourceMetadata::EXTENSION);
1893        if self.resource_io.exists(&metadata_path).await {
1894            let new_metadata_path =
1895                append_extension(&relative_dest_path, ResourceMetadata::EXTENSION);
1896            self.resource_io
1897                .move_file(&metadata_path, &new_metadata_path)
1898                .await?;
1899        }
1900
1901        Ok(())
1902    }
1903
1904    /// Attempts to move a resource from its current location to the new path. The resource must
1905    /// be registered in the resource registry to be moveable. This method can also be used to
1906    /// rename the source file of a resource.
1907    pub async fn move_resource(
1908        &self,
1909        resource: impl AsRef<UntypedResource>,
1910        new_path: impl AsRef<Path>,
1911        overwrite_existing: bool,
1912    ) -> Result<(), ResourceMovementError> {
1913        let resource_path = self.resource_path(resource.as_ref()).ok_or_else(|| {
1914            FileError::Custom(
1915                "Cannot move the resource because it does not have a path!".to_string(),
1916            )
1917        })?;
1918
1919        self.move_resource_by_path(resource_path, new_path, overwrite_existing)
1920            .await
1921    }
1922
1923    /// Checks if there's a loader for the given resource path.
1924    pub fn is_supported_resource(&self, path: &Path) -> bool {
1925        let ext = some_or_return!(path.extension(), false);
1926        let ext = some_or_return!(ext.to_str(), false);
1927
1928        self.loaders
1929            .safe_lock()
1930            .iter()
1931            .any(|loader| loader.supports_extension(ext))
1932    }
1933
1934    /// Checks if the given path is located inside the folder tracked by the resource registry.
1935    pub fn is_path_in_registry(&self, path: &Path) -> bool {
1936        let registry = self.resource_registry.safe_lock();
1937        if let Some(registry_directory) = registry.directory() {
1938            if let Ok(canonical_registry_path) = registry_directory.canonicalize() {
1939                if let Ok(canonical_path) = path.canonicalize() {
1940                    return canonical_path.starts_with(canonical_registry_path);
1941                }
1942            }
1943        }
1944        false
1945    }
1946
1947    /// Tries to move a folder to some other folder.
1948    pub async fn try_move_folder(
1949        &self,
1950        src_dir: &Path,
1951        dest_dir: &Path,
1952        overwrite_existing: bool,
1953    ) -> Result<(), FolderMovementError> {
1954        if dest_dir.starts_with(src_dir) {
1955            return Err(FolderMovementError::HierarchyError {
1956                src_dir: src_dir.to_path_buf(),
1957                dest_dir: dest_dir.to_path_buf(),
1958            });
1959        }
1960
1961        // Early validation to prevent error spam when trying to move a folder out of the
1962        // assets directory.
1963        if !self.is_path_in_registry(dest_dir) {
1964            return Err(FolderMovementError::NotInRegistry {
1965                dest_dir: dest_dir.to_path_buf(),
1966            });
1967        }
1968
1969        // At this point we have a folder dropped on some other folder. In this case
1970        // we need to move all the assets from the dropped folder to a new subfolder (with the same
1971        // name as the dropped folder) of the other folder first. After that we can move the rest
1972        // of the files and finally delete the dropped folder.
1973        let mut what_where_stack = vec![(src_dir.to_path_buf(), dest_dir.to_path_buf())];
1974        while let Some((src_dir, target_dir)) = what_where_stack.pop() {
1975            let src_dir_name = some_or_continue!(src_dir.file_name());
1976
1977            let target_sub_dir = target_dir.join(src_dir_name);
1978            if !self.resource_io.exists(&target_sub_dir).await {
1979                std::fs::create_dir(&target_sub_dir)?;
1980            }
1981
1982            let target_sub_dir_normalized = ok_or_continue!(make_relative_path(&target_sub_dir));
1983
1984            for path in self.resource_io.walk_directory(&src_dir, 1).await? {
1985                if path.is_file() {
1986                    let file_name = some_or_continue!(path.file_name());
1987                    if self.is_supported_resource(&path) {
1988                        let dest_path = target_sub_dir_normalized.join(file_name);
1989                        self.move_resource_by_path(path, &dest_path, overwrite_existing)
1990                            .await?;
1991                    }
1992                } else if path.is_dir() && path != src_dir {
1993                    // Sub-folders will be processed after all assets from current dir
1994                    // were moved.
1995                    what_where_stack.push((path, target_sub_dir.clone()));
1996                }
1997            }
1998        }
1999
2000        std::fs::remove_dir_all(src_dir)?;
2001
2002        Ok(())
2003    }
2004}
2005
2006#[cfg(test)]
2007mod test {
2008    use super::*;
2009    use crate::io::FsResourceIo;
2010    use crate::{
2011        loader::{BoxedLoaderFuture, LoaderPayload, ResourceLoader},
2012        ResourceData,
2013    };
2014    use fyrox_core::{
2015        reflect::prelude::*,
2016        uuid::{uuid, Uuid},
2017        visitor::{Visit, VisitResult, Visitor},
2018        TypeUuidProvider,
2019    };
2020    use std::{error::Error, fs::File, time::Duration};
2021
2022    #[derive(Debug, Default, Clone, Reflect, Visit)]
2023    struct Stub {}
2024
2025    impl TypeUuidProvider for Stub {
2026        fn type_uuid() -> Uuid {
2027            uuid!("9d873ff4-3126-47e1-a492-7cd8e7168239")
2028        }
2029    }
2030
2031    impl ResourceData for Stub {
2032        fn type_uuid(&self) -> Uuid {
2033            <Self as TypeUuidProvider>::type_uuid()
2034        }
2035
2036        fn save(&mut self, _path: &Path) -> Result<(), Box<dyn Error>> {
2037            Err("Saving is not supported!".to_string().into())
2038        }
2039
2040        fn can_be_saved(&self) -> bool {
2041            false
2042        }
2043
2044        fn try_clone_box(&self) -> Option<Box<dyn ResourceData>> {
2045            Some(Box::new(self.clone()))
2046        }
2047    }
2048
2049    impl ResourceLoader for Stub {
2050        fn extensions(&self) -> &[&str] {
2051            &["txt"]
2052        }
2053
2054        fn data_type_uuid(&self) -> Uuid {
2055            <Stub as TypeUuidProvider>::type_uuid()
2056        }
2057
2058        fn load(&self, _path: PathBuf, _io: Arc<dyn ResourceIo>) -> BoxedLoaderFuture {
2059            Box::pin(async move { Ok(LoaderPayload::new(Stub::default())) })
2060        }
2061    }
2062
2063    fn new_resource_manager() -> ResourceManagerState {
2064        ResourceManagerState::new(Arc::new(FsResourceIo), Arc::new(Default::default()))
2065    }
2066
2067    fn remove_file_if_exists(path: &Path) -> std::io::Result<()> {
2068        match std::fs::remove_file(path) {
2069            Ok(()) => Ok(()),
2070            Err(e) => match e.kind() {
2071                std::io::ErrorKind::NotFound => Ok(()),
2072                _ => Err(e),
2073            },
2074        }
2075    }
2076
2077    #[test]
2078    fn resource_wait_context_is_all_loaded() {
2079        assert!(ResourceWaitContext::default().is_all_loaded());
2080
2081        let cx = ResourceWaitContext {
2082            resources: vec![
2083                UntypedResource::new_pending(Default::default(), ResourceKind::External),
2084                UntypedResource::new_load_error(
2085                    ResourceKind::External,
2086                    Default::default(),
2087                    LoadError::default(),
2088                ),
2089            ],
2090        };
2091        assert!(!cx.is_all_loaded());
2092    }
2093
2094    #[test]
2095    fn resource_manager_state_new() {
2096        let state = new_resource_manager();
2097
2098        assert!(state.resources.is_empty());
2099        assert!(state.loaders.safe_lock().is_empty());
2100        assert!(state.built_in_resources.is_empty());
2101        assert!(state.constructors_container.is_empty());
2102        assert!(state.watcher.is_none());
2103        assert!(state.is_empty());
2104    }
2105
2106    #[test]
2107    fn resource_manager_state_set_watcher() {
2108        let mut state = new_resource_manager();
2109        assert!(state.watcher.is_none());
2110
2111        let path = PathBuf::from("test.txt");
2112        if File::create(path.clone()).is_ok() {
2113            let watcher = FileSystemWatcher::new(path.clone(), Duration::from_secs(1));
2114            state.set_watcher(watcher.ok());
2115            assert!(state.watcher.is_some());
2116        }
2117    }
2118
2119    #[test]
2120    fn resource_manager_state_push() {
2121        std::fs::create_dir_all("data").expect("Could not create data directory.");
2122        let mut state = new_resource_manager();
2123
2124        assert_eq!(state.count_loaded_resources(), 0);
2125        assert_eq!(state.count_pending_resources(), 0);
2126        assert_eq!(state.count_registered_resources(), 0);
2127        assert_eq!(state.len(), 0);
2128
2129        assert!(state
2130            .register(
2131                UntypedResource::new_pending(Default::default(), ResourceKind::External),
2132                "foo.bar",
2133            )
2134            .is_ok());
2135        assert!(state
2136            .register(
2137                UntypedResource::new_load_error(
2138                    ResourceKind::External,
2139                    Default::default(),
2140                    LoadError::default()
2141                ),
2142                "foo.bar",
2143            )
2144            .is_ok());
2145        assert!(state
2146            .register(
2147                UntypedResource::new_ok(Uuid::new_v4(), Default::default(), Stub {}),
2148                "foo.bar",
2149            )
2150            .is_ok());
2151
2152        assert_eq!(state.count_registered_resources(), 3);
2153        assert_eq!(state.len(), 3);
2154    }
2155
2156    #[test]
2157    fn resource_manager_state_loading_progress() {
2158        let mut state = new_resource_manager();
2159
2160        assert_eq!(state.loading_progress(), 100);
2161
2162        state
2163            .register(
2164                UntypedResource::new_ok(Uuid::new_v4(), Default::default(), Stub {}),
2165                "foo.bar",
2166            )
2167            .unwrap();
2168
2169        assert_eq!(state.loading_progress(), 100);
2170    }
2171
2172    #[test]
2173    fn resource_manager_state_find() {
2174        let mut state = new_resource_manager();
2175
2176        let path = Path::new("foo.txt");
2177
2178        assert!(state.find_by_path(path).is_none());
2179
2180        let resource = UntypedResource::new_ok(Uuid::new_v4(), Default::default(), Stub {});
2181        state.register(resource.clone(), path).unwrap();
2182
2183        assert_eq!(state.find_by_path(path), Some(&resource));
2184    }
2185
2186    #[test]
2187    fn resource_manager_state_resources() {
2188        let mut state = new_resource_manager();
2189
2190        assert_eq!(state.resources(), Vec::new());
2191
2192        let r1 = UntypedResource::new_ok(Uuid::new_v4(), ResourceKind::External, Stub {});
2193        let r2 = UntypedResource::new_ok(Uuid::new_v4(), ResourceKind::External, Stub {});
2194        let r3 = UntypedResource::new_ok(Uuid::new_v4(), ResourceKind::External, Stub {});
2195        state.register(r1.clone(), "foo1.txt").unwrap();
2196        state.register(r2.clone(), "foo2.txt").unwrap();
2197        state.register(r3.clone(), "foo3.txt").unwrap();
2198
2199        assert_eq!(state.resources(), vec![r1.clone(), r2.clone(), r3.clone()]);
2200        assert!(state.iter().eq([&r1, &r2, &r3]));
2201    }
2202
2203    #[test]
2204    fn resource_manager_state_destroy_unused_resources() {
2205        let mut state = new_resource_manager();
2206
2207        state
2208            .register(
2209                UntypedResource::new_ok(Uuid::new_v4(), ResourceKind::External, Stub {}),
2210                "foo1.txt",
2211            )
2212            .unwrap();
2213        assert_eq!(state.len(), 1);
2214
2215        state.destroy_unused_resources();
2216        assert_eq!(state.len(), 0);
2217    }
2218
2219    #[test]
2220    fn resource_manager_state_request() {
2221        let mut state = new_resource_manager();
2222        let path = PathBuf::from("test.txt");
2223
2224        let resource = UntypedResource::new_ok(Uuid::new_v4(), ResourceKind::External, Stub {});
2225        state.register(resource.clone(), &path).unwrap();
2226
2227        let res = state.request(&path);
2228        assert_eq!(res, resource);
2229
2230        let res = state.request(path);
2231
2232        assert_eq!(res.kind(), ResourceKind::External);
2233        assert!(!res.is_loading());
2234    }
2235
2236    #[test]
2237    fn resource_manager_state_get_wait_context() {
2238        let mut state = new_resource_manager();
2239
2240        let resource = UntypedResource::new_ok(Uuid::new_v4(), ResourceKind::External, Stub {});
2241        state.add_resource_and_notify(resource.clone());
2242        let cx = state.get_wait_context();
2243
2244        assert!(cx.resources.eq(&vec![resource]));
2245    }
2246
2247    #[test]
2248    fn resource_manager_new() {
2249        let manager = ResourceManager::new(Arc::new(FsResourceIo), Arc::new(Default::default()));
2250
2251        assert!(manager.state.safe_lock().is_empty());
2252        assert!(manager.state().is_empty());
2253    }
2254
2255    #[test]
2256    fn resource_manager_register() {
2257        std::fs::create_dir_all("data").expect("Could not create data directory.");
2258        let manager = ResourceManager::new(Arc::new(FsResourceIo), Arc::new(Default::default()));
2259        let path = PathBuf::from("test.txt");
2260        let metapath = append_extension(&path, ResourceMetadata::EXTENSION);
2261        remove_file_if_exists(&metapath).unwrap();
2262
2263        let resource = UntypedResource::new_pending(Default::default(), ResourceKind::External);
2264        let res = manager.register(resource.clone(), path.clone());
2265        assert!(res.is_ok());
2266
2267        let metadata = block_on(ResourceMetadata::load_from_file_async(
2268            &metapath,
2269            &*manager.resource_io(),
2270        ))
2271        .expect("Reading meta file failed");
2272        assert_eq!(resource.resource_uuid(), metadata.resource_id);
2273
2274        let uuid = Uuid::new_v4();
2275        let resource = UntypedResource::new_ok(uuid, ResourceKind::External, Stub {});
2276        let res = manager.register(resource.clone(), path.clone());
2277        assert!(res.is_ok());
2278
2279        assert_eq!(resource.resource_uuid(), uuid);
2280        let metadata = block_on(ResourceMetadata::load_from_file_async(
2281            &metapath,
2282            &*manager.resource_io(),
2283        ))
2284        .expect("Reading meta file failed");
2285        assert_eq!(metadata.resource_id, uuid);
2286    }
2287
2288    #[test]
2289    fn resource_manager_request_untyped() {
2290        let manager = ResourceManager::new(Arc::new(FsResourceIo), Arc::new(Default::default()));
2291        let resource = UntypedResource::new_ok(Uuid::new_v4(), Default::default(), Stub {});
2292        let res = manager.register(resource.clone(), PathBuf::from("foo.txt"));
2293        assert!(res.is_ok());
2294
2295        let res = manager.request_untyped(Path::new("foo.txt"));
2296        assert_eq!(res, resource);
2297    }
2298
2299    #[test]
2300    fn display_for_resource_registration_error() {
2301        assert_eq!(
2302            format!("{}", ResourceRegistrationError::AlreadyRegistered),
2303            "A resource is already registered!"
2304        );
2305        assert_eq!(
2306            format!("{}", ResourceRegistrationError::InvalidState),
2307            "A resource was in invalid state!"
2308        );
2309        assert_eq!(
2310            format!("{}", ResourceRegistrationError::UnableToRegister),
2311            "Unable to register the resource!"
2312        );
2313    }
2314
2315    #[test]
2316    fn debug_for_resource_registration_error() {
2317        assert_eq!(
2318            format!("{:?}", ResourceRegistrationError::AlreadyRegistered),
2319            "AlreadyRegistered"
2320        );
2321        assert_eq!(
2322            format!("{:?}", ResourceRegistrationError::InvalidState),
2323            "InvalidState"
2324        );
2325        assert_eq!(
2326            format!("{:?}", ResourceRegistrationError::UnableToRegister),
2327            "UnableToRegister"
2328        );
2329    }
2330}