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