Skip to main content

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