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
24use crate::{
25    collect_used_resources,
26    constructor::ResourceConstructorContainer,
27    core::{
28        append_extension,
29        futures::future::join_all,
30        io::FileLoadError,
31        log::Log,
32        make_relative_path, notify,
33        parking_lot::{Mutex, MutexGuard},
34        task::TaskPool,
35        watcher::FileSystemWatcher,
36        TypeUuidProvider,
37    },
38    entry::{TimedEntry, DEFAULT_RESOURCE_LIFETIME},
39    event::{ResourceEvent, ResourceEventBroadcaster},
40    io::{FsResourceIo, ResourceIo},
41    loader::{ResourceLoader, ResourceLoadersContainer},
42    options::OPTIONS_EXTENSION,
43    state::{LoadError, ResourceState},
44    untyped::ResourceKind,
45    Resource, ResourceData, TypedResourceData, UntypedResource,
46};
47use fxhash::{FxHashMap, FxHashSet};
48use rayon::prelude::*;
49use std::borrow::Cow;
50use std::ops::{Deref, DerefMut};
51use std::{
52    fmt::{Debug, Display, Formatter},
53    marker::PhantomData,
54    path::{Path, PathBuf},
55    sync::Arc,
56};
57
58/// A set of resources that can be waited for.
59#[must_use]
60#[derive(Default)]
61pub struct ResourceWaitContext {
62    resources: Vec<UntypedResource>,
63}
64
65impl ResourceWaitContext {
66    /// Wait until all resources are loaded (or failed to load).
67    #[must_use]
68    pub fn is_all_loaded(&self) -> bool {
69        let mut loaded_count = 0;
70        for resource in self.resources.iter() {
71            if !matches!(resource.0.lock().state, ResourceState::Pending { .. }) {
72                loaded_count += 1;
73            }
74        }
75        loaded_count == self.resources.len()
76    }
77}
78
79/// Data source of a built-in resource.
80#[derive(Clone)]
81pub struct DataSource {
82    /// File extension, associated with the data source.
83    pub extension: Cow<'static, str>,
84    /// The actual data.
85    pub bytes: Cow<'static, [u8]>,
86}
87
88impl DataSource {
89    pub fn new(path: &'static str, data: &'static [u8]) -> Self {
90        Self {
91            extension: Cow::Borrowed(
92                Path::new(path)
93                    .extension()
94                    .and_then(|ext| ext.to_str())
95                    .unwrap_or(""),
96            ),
97            bytes: Cow::Borrowed(data),
98        }
99    }
100}
101
102#[macro_export]
103macro_rules! embedded_data_source {
104    ($path:expr) => {
105        $crate::manager::DataSource::new($path, include_bytes!($path))
106    };
107}
108
109#[derive(Clone)]
110pub struct UntypedBuiltInResource {
111    /// Initial data, from which the resource is created from.
112    pub data_source: Option<DataSource>,
113    /// Ready-to-use ("loaded") resource.
114    pub resource: UntypedResource,
115}
116
117pub struct BuiltInResource<T>
118where
119    T: TypedResourceData,
120{
121    /// Initial data, from which the resource is created from.
122    pub data_source: Option<DataSource>,
123    /// Ready-to-use ("loaded") resource.
124    pub resource: Resource<T>,
125}
126
127impl<T: TypedResourceData> Clone for BuiltInResource<T> {
128    fn clone(&self) -> Self {
129        Self {
130            data_source: self.data_source.clone(),
131            resource: self.resource.clone(),
132        }
133    }
134}
135
136impl<T: TypedResourceData> BuiltInResource<T> {
137    pub fn new<F>(data_source: DataSource, make: F) -> Self
138    where
139        F: FnOnce(&[u8]) -> Resource<T>,
140    {
141        let resource = make(&data_source.bytes);
142        Self {
143            resource,
144            data_source: Some(data_source),
145        }
146    }
147
148    pub fn new_no_source(resource: Resource<T>) -> Self {
149        Self {
150            data_source: None,
151            resource,
152        }
153    }
154
155    pub fn resource(&self) -> Resource<T> {
156        self.resource.clone()
157    }
158}
159
160impl<T: TypedResourceData> From<BuiltInResource<T>> for UntypedBuiltInResource {
161    fn from(value: BuiltInResource<T>) -> Self {
162        Self {
163            data_source: value.data_source,
164            resource: value.resource.into(),
165        }
166    }
167}
168
169#[derive(Default, Clone)]
170pub struct BuiltInResourcesContainer {
171    inner: FxHashMap<PathBuf, UntypedBuiltInResource>,
172}
173
174impl BuiltInResourcesContainer {
175    pub fn add<T>(&mut self, resource: BuiltInResource<T>)
176    where
177        T: TypedResourceData,
178    {
179        self.add_untyped(resource.into())
180    }
181
182    pub fn add_untyped(&mut self, resource: UntypedBuiltInResource) {
183        self.inner
184            .insert(resource.resource.kind().path_owned().unwrap(), resource);
185    }
186}
187
188impl Deref for BuiltInResourcesContainer {
189    type Target = FxHashMap<PathBuf, UntypedBuiltInResource>;
190
191    fn deref(&self) -> &Self::Target {
192        &self.inner
193    }
194}
195
196impl DerefMut for BuiltInResourcesContainer {
197    fn deref_mut(&mut self) -> &mut Self::Target {
198        &mut self.inner
199    }
200}
201
202/// Internal state of the resource manager.
203pub struct ResourceManagerState {
204    /// A set of resource loaders. Use this field to register your own resource loader.
205    pub loaders: ResourceLoadersContainer,
206    /// Event broadcaster can be used to "subscribe" for events happening inside the container.
207    pub event_broadcaster: ResourceEventBroadcaster,
208    /// A container for resource constructors.
209    pub constructors_container: ResourceConstructorContainer,
210    /// A set of built-in resources, that will be used to resolve references on deserialization.
211    pub built_in_resources: BuiltInResourcesContainer,
212    /// File system abstraction interface. Could be used to support virtual file systems.
213    pub resource_io: Arc<dyn ResourceIo>,
214
215    resources: Vec<TimedEntry<UntypedResource>>,
216    task_pool: Arc<TaskPool>,
217    watcher: Option<FileSystemWatcher>,
218}
219
220/// Resource manager controls loading and lifetime of resource in the engine. Resource manager can hold
221/// resources of arbitrary types via type erasure mechanism.
222///
223/// ## Built-in Resources
224///
225/// Built-in resources are special kinds of resources, whose data is packed in the executable (i.e. via
226/// [`include_bytes`] macro). Such resources reference the data that cannot be "loaded" from external
227/// source. To support such kind of resource the manager provides `built_in_resources` hash map where
228/// you can register your own built-in resource and access existing ones.
229///
230/// ## Internals
231///
232/// It is a simple wrapper over [`ResourceManagerState`] that can be shared (cloned). In other words,
233/// it is just a strong reference to the inner state.
234#[derive(Clone)]
235pub struct ResourceManager {
236    state: Arc<Mutex<ResourceManagerState>>,
237}
238
239impl Debug for ResourceManager {
240    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
241        write!(f, "ResourceManager")
242    }
243}
244
245/// An error that may occur during texture registration.
246#[derive(Debug)]
247pub enum ResourceRegistrationError {
248    /// Resource saving has failed.
249    UnableToRegister,
250    /// Resource was in invalid state (Pending, LoadErr)
251    InvalidState,
252    /// Resource is already registered.
253    AlreadyRegistered,
254}
255
256impl Display for ResourceRegistrationError {
257    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
258        match self {
259            ResourceRegistrationError::UnableToRegister => {
260                write!(f, "Unable to register the resource!")
261            }
262            ResourceRegistrationError::InvalidState => {
263                write!(f, "A resource was in invalid state!")
264            }
265            ResourceRegistrationError::AlreadyRegistered => {
266                write!(f, "A resource is already registered!")
267            }
268        }
269    }
270}
271
272impl ResourceManager {
273    /// Creates a resource manager with default settings and loaders.
274    pub fn new(task_pool: Arc<TaskPool>) -> Self {
275        Self {
276            state: Arc::new(Mutex::new(ResourceManagerState::new(task_pool))),
277        }
278    }
279
280    /// Returns a guarded reference to internal state of resource manager.
281    pub fn state(&self) -> MutexGuard<'_, ResourceManagerState> {
282        self.state.lock()
283    }
284
285    /// Returns the ResourceIo used by this resource manager
286    pub fn resource_io(&self) -> Arc<dyn ResourceIo> {
287        let state = self.state();
288        state.resource_io.clone()
289    }
290
291    /// Returns the task pool used by this resource manager.
292    pub fn task_pool(&self) -> Arc<TaskPool> {
293        let state = self.state();
294        state.task_pool()
295    }
296
297    /// Requests a resource of the given type located at the given path. This method is non-blocking, instead
298    /// it immediately returns the typed resource wrapper. Loading of the resource is managed automatically in
299    /// a separate thread (or thread pool) on PC, and JS micro-task (the same thread) on WebAssembly.
300    ///
301    /// ## Sharing
302    ///
303    /// If the resource at the given path is already was requested (no matter in which state the actual resource
304    /// is), this method will return the existing instance. This way the resource manager guarantees that the actual
305    /// resource data will be loaded once, and it can be shared.
306    ///
307    /// ## Waiting
308    ///
309    /// If you need to wait until the resource is loaded, use `.await` on the result of the method. Every resource
310    /// implements `Future` trait and can be used in `async` contexts.
311    ///
312    /// ## Resource state
313    ///
314    /// Keep in mind, that the resource itself is a small state machine. It could be in three main states:
315    ///
316    /// - [`ResourceState::Pending`] - a resource is in the queue to load or still loading.
317    /// - [`ResourceState::LoadError`] - a resource is failed to load.
318    /// - [`ResourceState::Ok`] - a resource is successfully loaded.
319    ///
320    /// Actual resource state can be fetched by [`Resource::state`] method. If you know for sure that the resource
321    /// is already loaded, then you can use [`Resource::data_ref`] to obtain a reference to the actual resource data.
322    /// Keep in mind, that this method will panic if the resource non in `Ok` state.
323    ///
324    /// ## Panic
325    ///
326    /// This method will panic, if type UUID of `T` does not match the actual type UUID of the resource. If this
327    /// is undesirable, use [`Self::try_request`] instead.
328    pub fn request<T>(&self, path: impl AsRef<Path>) -> Resource<T>
329    where
330        T: TypedResourceData,
331    {
332        let untyped = self.state().request(path);
333        let actual_type_uuid = untyped.type_uuid();
334        assert_eq!(actual_type_uuid, <T as TypeUuidProvider>::type_uuid());
335        Resource {
336            untyped,
337            phantom: PhantomData::<T>,
338        }
339    }
340
341    /// The same as [`Self::request`], but returns [`None`] if type UUID of `T` does not match the actual type UUID
342    /// of the resource.
343    ///
344    /// ## Panic
345    ///
346    /// This method does not panic.
347    pub fn try_request<T>(&self, path: impl AsRef<Path>) -> Option<Resource<T>>
348    where
349        T: TypedResourceData,
350    {
351        let untyped = self.state().request(path);
352        let actual_type_uuid = untyped.type_uuid();
353        if actual_type_uuid == <T as TypeUuidProvider>::type_uuid() {
354            Some(Resource {
355                untyped,
356                phantom: PhantomData::<T>,
357            })
358        } else {
359            None
360        }
361    }
362
363    /// Same as [`Self::request`], but returns untyped resource.
364    pub fn request_untyped<P>(&self, path: P) -> UntypedResource
365    where
366        P: AsRef<Path>,
367    {
368        self.state().request(path)
369    }
370
371    /// Saves given resources in the specified path and registers it in resource manager, so
372    /// it will be accessible through it later.
373    pub fn register<P, F>(
374        &self,
375        resource: UntypedResource,
376        path: P,
377        mut on_register: F,
378    ) -> Result<(), ResourceRegistrationError>
379    where
380        P: AsRef<Path>,
381        F: FnMut(&mut dyn ResourceData, &Path) -> bool,
382    {
383        let mut state = self.state();
384        if let Some(resource) = state.find(path.as_ref()) {
385            let resource_state = resource.0.lock();
386            if let ResourceState::Ok(_) = resource_state.state {
387                return Err(ResourceRegistrationError::AlreadyRegistered);
388            }
389        }
390
391        state.unregister(path.as_ref());
392
393        let mut header = resource.0.lock();
394        header.kind.make_external(path.as_ref().to_path_buf());
395        if let ResourceState::Ok(ref mut data) = header.state {
396            if !on_register(&mut **data, path.as_ref()) {
397                Err(ResourceRegistrationError::UnableToRegister)
398            } else {
399                drop(header);
400                state.push(resource);
401                Ok(())
402            }
403        } else {
404            Err(ResourceRegistrationError::InvalidState)
405        }
406    }
407
408    /// Attempts to move a resource from its current location to the new path.
409    pub async fn move_resource(
410        &self,
411        resource: UntypedResource,
412        new_path: impl AsRef<Path>,
413        working_directory: impl AsRef<Path>,
414        mut filter: impl FnMut(&UntypedResource) -> bool,
415    ) -> Result<(), FileLoadError> {
416        let new_path = new_path.as_ref().to_owned();
417        let io = self.state().resource_io.clone();
418        let existing_path = resource
419            .kind()
420            .into_path()
421            .ok_or_else(|| FileLoadError::Custom("Cannot move embedded resource!".to_string()))?;
422
423        let canonical_existing_path = io.canonicalize_path(&existing_path).await?;
424
425        // Collect all resources referencing the resource.
426        let resources = io
427            .walk_directory(working_directory.as_ref())
428            .await?
429            .map(|p| self.request_untyped(p))
430            .collect::<Vec<_>>();
431        // Filter out all faulty resources.
432        let resources_to_fix = join_all(resources)
433            .await
434            .into_iter()
435            .filter_map(|r| r.ok())
436            .filter(|r| r != &resource && filter(r))
437            .collect::<Vec<_>>();
438
439        // Do the heavy work in parallel.
440        let mut pairs = resources_to_fix
441            .par_iter()
442            .filter_map(|loaded_resource| {
443                let mut guard = loaded_resource.0.lock();
444                if let ResourceState::Ok(ref mut data) = guard.state {
445                    let mut used_resources = FxHashSet::default();
446                    (**data).as_reflect(&mut |reflect| {
447                        collect_used_resources(reflect, &mut used_resources);
448                    });
449                    Some((loaded_resource, used_resources))
450                } else {
451                    None
452                }
453            })
454            .collect::<Vec<_>>();
455
456        // Filter out all resources that does not have references to the moved resource.
457        for (_, used_resources) in pairs.iter_mut() {
458            let mut used_resources_with_references = FxHashSet::default();
459            for resource in used_resources.iter() {
460                // Filter out embedded resources.
461                if let Some(path) = resource.kind().into_path() {
462                    if let Ok(canonical_resource_path) = io.canonicalize_path(&path).await {
463                        // We compare the canonical paths here to check for the same file, not for the
464                        // same path. Remember that there could be any number of paths leading to the
465                        // same file (i.e. "foo/bar/baz.txt" and "foo/bar/../bar/baz.txt" leads to the
466                        // same file, but the paths are different).
467                        if canonical_resource_path == canonical_existing_path {
468                            used_resources_with_references.insert(resource.clone());
469                        }
470                    }
471                }
472            }
473            *used_resources = used_resources_with_references;
474        }
475
476        for (loaded_resource, used_resources) in pairs {
477            if !used_resources.is_empty() {
478                for resource in used_resources {
479                    resource.set_kind(ResourceKind::External(new_path.clone()));
480                }
481
482                let mut header = loaded_resource.0.lock();
483                if let Some(loaded_resource_path) = header.kind.path_owned() {
484                    if let ResourceState::Ok(ref mut data) = header.state {
485                        // Save the resource back.
486                        match data.save(&loaded_resource_path) {
487                            Ok(_) => Log::info(format!(
488                                "Resource {} was saved successfully!",
489                                header.kind
490                            )),
491                            Err(err) => Log::err(format!(
492                                "Unable to save {} resource. Reason: {:?}",
493                                header.kind, err
494                            )),
495                        };
496                    }
497                }
498            }
499        }
500
501        // Move the file with its optional import options.
502        io.move_file(&existing_path, &new_path).await?;
503        let options_path = append_extension(&existing_path, OPTIONS_EXTENSION);
504        if io.exists(&options_path).await {
505            let new_options_path = append_extension(&new_path, OPTIONS_EXTENSION);
506            io.move_file(&options_path, &new_options_path).await?;
507        }
508
509        Ok(())
510    }
511
512    /// Reloads all loaded resources. Normally it should never be called, because it is **very** heavy
513    /// method! This method is asynchronous, it uses all available CPU power to reload resources as
514    /// fast as possible.
515    pub async fn reload_resources(&self) {
516        let resources = self.state().reload_resources();
517        join_all(resources).await;
518    }
519}
520
521impl ResourceManagerState {
522    pub(crate) fn new(task_pool: Arc<TaskPool>) -> Self {
523        Self {
524            resources: Default::default(),
525            task_pool,
526            loaders: Default::default(),
527            event_broadcaster: Default::default(),
528            constructors_container: Default::default(),
529            watcher: None,
530            built_in_resources: Default::default(),
531            // Use the file system resource io by default
532            resource_io: Arc::new(FsResourceIo),
533        }
534    }
535
536    /// Returns the task pool used by this resource manager.
537    pub fn task_pool(&self) -> Arc<TaskPool> {
538        self.task_pool.clone()
539    }
540
541    /// Set the IO source that the resource manager should use when
542    /// loading assets
543    pub fn set_resource_io(&mut self, resource_io: Arc<dyn ResourceIo>) {
544        self.resource_io = resource_io;
545    }
546
547    /// Sets resource watcher which will track any modifications in file system and forcing
548    /// the manager to reload changed resources. By default there is no watcher, since it
549    /// may be an undesired effect to reload resources at runtime. This is very useful thing
550    /// for fast iterative development.
551    pub fn set_watcher(&mut self, watcher: Option<FileSystemWatcher>) {
552        self.watcher = watcher;
553    }
554
555    /// Returns total amount of registered resources.
556    pub fn count_registered_resources(&self) -> usize {
557        self.resources.len()
558    }
559
560    /// Returns percentage of loading progress. This method is useful to show progress on
561    /// loading screen in your game. This method could be used alone if your game depends
562    /// only on external resources, or if your game doing some heavy calculations this value
563    /// can be combined with progress of your tasks.
564    pub fn loading_progress(&self) -> usize {
565        let registered = self.count_registered_resources();
566        if registered > 0 {
567            self.count_loaded_resources() * 100 / registered
568        } else {
569            100
570        }
571    }
572
573    /// Update resource containers and do hot-reloading.
574    ///
575    /// Resources are removed if they're not used
576    /// or reloaded if they have changed in disk.
577    ///
578    /// Normally, this is called from `Engine::update()`.
579    /// You should only call this manually if you don't use that method.
580    pub fn update(&mut self, dt: f32) {
581        self.resources.retain_mut(|resource| {
582            // One usage means that the resource has single owner, and that owner
583            // is this container. Such resources have limited life time, if the time
584            // runs out before it gets shared again, the resource will be deleted.
585            if resource.value.use_count() <= 1 {
586                resource.time_to_live -= dt;
587                if resource.time_to_live <= 0.0 {
588                    if let Some(path) = resource.0.lock().kind.path_owned() {
589                        Log::info(format!(
590                            "Resource {} destroyed because it is not used anymore!",
591                            path.display()
592                        ));
593
594                        self.event_broadcaster
595                            .broadcast(ResourceEvent::Removed(path));
596                    }
597
598                    false
599                } else {
600                    // Keep resource alive for short period of time.
601                    true
602                }
603            } else {
604                // Make sure to reset timer if a resource is used by more than one owner.
605                resource.time_to_live = DEFAULT_RESOURCE_LIFETIME;
606
607                // Keep resource alive while it has more than one owner.
608                true
609            }
610        });
611
612        if let Some(watcher) = self.watcher.as_ref() {
613            if let Some(evt) = watcher.try_get_event() {
614                if let notify::EventKind::Modify(_) = evt.kind {
615                    for path in evt.paths {
616                        if let Ok(relative_path) = make_relative_path(path) {
617                            if self.try_reload_resource_from_path(&relative_path) {
618                                Log::info(format!(
619                                        "File {} was changed, trying to reload a respective resource...",
620                                        relative_path.display()
621                                    ));
622
623                                break;
624                            }
625                        }
626                    }
627                }
628            }
629        }
630    }
631
632    /// Adds a new resource in the container.
633    pub fn push(&mut self, resource: UntypedResource) {
634        self.event_broadcaster
635            .broadcast(ResourceEvent::Added(resource.clone()));
636
637        self.resources.push(TimedEntry {
638            value: resource,
639            time_to_live: DEFAULT_RESOURCE_LIFETIME,
640        });
641    }
642
643    /// Tries to find a resources by its path. Returns None if no resource was found.
644    ///
645    /// # Complexity
646    ///
647    /// O(n)
648    pub fn find<P: AsRef<Path>>(&self, path: P) -> Option<&UntypedResource> {
649        for resource in self.resources.iter() {
650            if let Some(resource_path) = resource.0.lock().kind.path() {
651                if resource_path == path.as_ref() {
652                    return Some(&resource.value);
653                }
654            }
655        }
656        None
657    }
658
659    /// Returns total amount of resources in the container.
660    pub fn len(&self) -> usize {
661        self.resources.len()
662    }
663
664    /// Returns true if the resource manager has no resources.
665    pub fn is_empty(&self) -> bool {
666        self.resources.is_empty()
667    }
668
669    /// Creates an iterator over resources in the manager.
670    pub fn iter(&self) -> impl Iterator<Item = &UntypedResource> {
671        self.resources.iter().map(|entry| &entry.value)
672    }
673
674    /// Immediately destroys all resources in the manager that are not used anywhere else.
675    pub fn destroy_unused_resources(&mut self) {
676        self.resources
677            .retain(|resource| resource.value.use_count() > 1);
678    }
679
680    /// Returns total amount of resources that still loading.
681    pub fn count_pending_resources(&self) -> usize {
682        self.resources.iter().fold(0, |counter, resource| {
683            if let ResourceState::Pending { .. } = resource.0.lock().state {
684                counter + 1
685            } else {
686                counter
687            }
688        })
689    }
690
691    /// Returns total amount of completely loaded resources.
692    pub fn count_loaded_resources(&self) -> usize {
693        self.resources.iter().fold(0, |counter, resource| {
694            if let ResourceState::Ok(_) = resource.0.lock().state {
695                counter + 1
696            } else {
697                counter
698            }
699        })
700    }
701
702    /// Returns a set of resource handled by this container.
703    pub fn resources(&self) -> Vec<UntypedResource> {
704        self.resources.iter().map(|t| t.value.clone()).collect()
705    }
706
707    /// Tries to load a resources at a given path.
708    pub fn request<P>(&mut self, path: P) -> UntypedResource
709    where
710        P: AsRef<Path>,
711    {
712        if let Some(built_in_resource) = self.built_in_resources.get(path.as_ref()) {
713            return built_in_resource.resource.clone();
714        }
715
716        match self.find(path.as_ref()) {
717            Some(existing) => existing.clone(),
718            None => {
719                let path = path.as_ref().to_owned();
720                let kind = ResourceKind::External(path.clone());
721
722                if let Some(loader) = self.find_loader(path.as_ref()) {
723                    let resource = UntypedResource::new_pending(kind, loader.data_type_uuid());
724                    self.spawn_loading_task(path, resource.clone(), loader, false);
725                    self.push(resource.clone());
726                    resource
727                } else {
728                    let err =
729                        LoadError::new(format!("There's no resource loader for {kind} resource!",));
730                    UntypedResource::new_load_error(kind, err, Default::default())
731                }
732            }
733        }
734    }
735
736    fn find_loader(&self, path: &Path) -> Option<&dyn ResourceLoader> {
737        path.extension().and_then(|extension| {
738            self.loaders
739                .iter()
740                .find(|loader| loader.supports_extension(&extension.to_string_lossy()))
741        })
742    }
743
744    fn spawn_loading_task(
745        &self,
746        path: PathBuf,
747        resource: UntypedResource,
748        loader: &dyn ResourceLoader,
749        reload: bool,
750    ) {
751        let event_broadcaster = self.event_broadcaster.clone();
752        let loader_future = loader.load(path.clone(), self.resource_io.clone());
753        self.task_pool.spawn_task(async move {
754            match loader_future.await {
755                Ok(data) => {
756                    let data = data.0;
757
758                    Log::info(format!(
759                        "Resource {} was loaded successfully!",
760                        path.display()
761                    ));
762
763                    // Separate scope to keep mutex locking time at minimum.
764                    {
765                        let mut mutex_guard = resource.0.lock();
766                        assert_eq!(mutex_guard.type_uuid, data.type_uuid());
767                        assert!(mutex_guard.kind.is_external());
768                        mutex_guard.state.commit(ResourceState::Ok(data));
769                    }
770
771                    event_broadcaster.broadcast_loaded_or_reloaded(resource, reload);
772                }
773                Err(error) => {
774                    Log::info(format!(
775                        "Resource {} failed to load. Reason: {:?}",
776                        path.display(),
777                        error
778                    ));
779
780                    resource.commit_error(error);
781                }
782            }
783        });
784    }
785
786    /// Reloads a single resource.
787    pub fn reload_resource(&mut self, resource: UntypedResource) {
788        let mut header = resource.0.lock();
789
790        if !header.state.is_loading() {
791            if let Some(path) = header.kind.path_owned() {
792                if let Some(loader) = self.find_loader(&path) {
793                    header.state.switch_to_pending_state();
794                    drop(header);
795
796                    self.spawn_loading_task(path, resource, loader, true);
797                } else {
798                    let msg = format!(
799                        "There's no resource loader for {} resource!",
800                        path.display()
801                    );
802                    Log::err(&msg);
803                    resource.commit_error(msg)
804                }
805            } else {
806                Log::err("Cannot reload embedded resource.")
807            }
808        }
809    }
810
811    /// Reloads all resources in the container. Returns a list of resources that will be reloaded.
812    /// You can use the list to wait until all resources are loading.
813    pub fn reload_resources(&mut self) -> Vec<UntypedResource> {
814        let resources = self
815            .resources
816            .iter()
817            .map(|r| r.value.clone())
818            .collect::<Vec<_>>();
819
820        for resource in resources.iter().cloned() {
821            self.reload_resource(resource);
822        }
823
824        resources
825    }
826
827    /// Wait until all resources are loaded (or failed to load).
828    pub fn get_wait_context(&self) -> ResourceWaitContext {
829        ResourceWaitContext {
830            resources: self
831                .resources
832                .iter()
833                .map(|e| e.value.clone())
834                .collect::<Vec<_>>(),
835        }
836    }
837
838    /// Tries to reload a resource at the given path.
839    pub fn try_reload_resource_from_path(&mut self, path: &Path) -> bool {
840        if let Some(resource) = self.find(path).cloned() {
841            self.reload_resource(resource);
842            true
843        } else {
844            false
845        }
846    }
847
848    /// Forgets that a resource at the given path was ever loaded, thus making it possible to reload it
849    /// again as a new instance.
850    pub fn unregister(&mut self, path: &Path) {
851        if let Some(position) = self
852            .resources
853            .iter()
854            .position(|r| r.kind().path() == Some(path))
855        {
856            self.resources.remove(position);
857        }
858    }
859}
860
861#[cfg(test)]
862mod test {
863    use std::error::Error;
864    use std::{fs::File, time::Duration};
865
866    use crate::loader::{BoxedLoaderFuture, LoaderPayload, ResourceLoader};
867
868    use super::*;
869
870    use fyrox_core::uuid::{uuid, Uuid};
871    use fyrox_core::{
872        reflect::{FieldInfo, Reflect},
873        visitor::{Visit, VisitResult, Visitor},
874        TypeUuidProvider,
875    };
876
877    #[derive(Debug, Default, Reflect, Visit)]
878    struct Stub {}
879
880    impl TypeUuidProvider for Stub {
881        fn type_uuid() -> Uuid {
882            uuid!("9d873ff4-3126-47e1-a492-7cd8e7168239")
883        }
884    }
885
886    impl ResourceData for Stub {
887        fn type_uuid(&self) -> Uuid {
888            <Self as TypeUuidProvider>::type_uuid()
889        }
890
891        fn save(&mut self, _path: &Path) -> Result<(), Box<dyn Error>> {
892            Err("Saving is not supported!".to_string().into())
893        }
894
895        fn can_be_saved(&self) -> bool {
896            false
897        }
898    }
899
900    impl ResourceLoader for Stub {
901        fn extensions(&self) -> &[&str] {
902            &["txt"]
903        }
904
905        fn data_type_uuid(&self) -> Uuid {
906            <Stub as TypeUuidProvider>::type_uuid()
907        }
908
909        fn load(&self, _path: PathBuf, _io: Arc<dyn ResourceIo>) -> BoxedLoaderFuture {
910            Box::pin(async move { Ok(LoaderPayload::new(Stub::default())) })
911        }
912    }
913
914    fn new_resource_manager() -> ResourceManagerState {
915        ResourceManagerState::new(Arc::new(Default::default()))
916    }
917
918    #[test]
919    fn resource_wait_context_is_all_loaded() {
920        assert!(ResourceWaitContext::default().is_all_loaded());
921
922        let path = PathBuf::from("test.txt");
923        let type_uuid = Uuid::default();
924
925        let cx = ResourceWaitContext {
926            resources: vec![
927                UntypedResource::new_pending(path.clone().into(), type_uuid),
928                UntypedResource::new_load_error(path.clone().into(), Default::default(), type_uuid),
929            ],
930        };
931        assert!(!cx.is_all_loaded());
932    }
933
934    #[test]
935    fn resource_manager_state_new() {
936        let state = new_resource_manager();
937
938        assert!(state.resources.is_empty());
939        assert!(state.loaders.is_empty());
940        assert!(state.built_in_resources.is_empty());
941        assert!(state.constructors_container.is_empty());
942        assert!(state.watcher.is_none());
943        assert!(state.is_empty());
944    }
945
946    #[test]
947    fn resource_manager_state_set_watcher() {
948        let mut state = new_resource_manager();
949        assert!(state.watcher.is_none());
950
951        let path = PathBuf::from("test.txt");
952        if File::create(path.clone()).is_ok() {
953            let watcher = FileSystemWatcher::new(path.clone(), Duration::from_secs(1));
954            state.set_watcher(watcher.ok());
955            assert!(state.watcher.is_some());
956        }
957    }
958
959    #[test]
960    fn resource_manager_state_push() {
961        let mut state = new_resource_manager();
962
963        assert_eq!(state.count_loaded_resources(), 0);
964        assert_eq!(state.count_pending_resources(), 0);
965        assert_eq!(state.count_registered_resources(), 0);
966        assert_eq!(state.len(), 0);
967
968        let path = PathBuf::from("test.txt");
969        let type_uuid = Uuid::default();
970        state.push(UntypedResource::new_pending(path.clone().into(), type_uuid));
971        state.push(UntypedResource::new_load_error(
972            path.clone().into(),
973            Default::default(),
974            type_uuid,
975        ));
976        state.push(UntypedResource::new_ok(Default::default(), Stub {}));
977
978        assert_eq!(state.count_loaded_resources(), 1);
979        assert_eq!(state.count_pending_resources(), 1);
980        assert_eq!(state.count_registered_resources(), 3);
981        assert_eq!(state.len(), 3);
982    }
983
984    #[test]
985    fn resource_manager_state_loading_progress() {
986        let mut state = new_resource_manager();
987
988        assert_eq!(state.loading_progress(), 100);
989
990        let path = PathBuf::from("test.txt");
991        let type_uuid = Uuid::default();
992        state.push(UntypedResource::new_pending(path.clone().into(), type_uuid));
993        state.push(UntypedResource::new_load_error(
994            path.clone().into(),
995            Default::default(),
996            type_uuid,
997        ));
998        state.push(UntypedResource::new_ok(Default::default(), Stub {}));
999
1000        assert_eq!(state.loading_progress(), 33);
1001    }
1002
1003    #[test]
1004    fn resource_manager_state_find() {
1005        let mut state = new_resource_manager();
1006
1007        assert!(state.find(Path::new("foo.txt")).is_none());
1008
1009        let path = PathBuf::from("test.txt");
1010        let type_uuid = Uuid::default();
1011        let resource = UntypedResource::new_pending(path.clone().into(), type_uuid);
1012        state.push(resource.clone());
1013
1014        assert_eq!(state.find(path), Some(&resource));
1015    }
1016
1017    #[test]
1018    fn resource_manager_state_resources() {
1019        let mut state = new_resource_manager();
1020
1021        assert_eq!(state.resources(), Vec::new());
1022
1023        let path = PathBuf::from("test.txt");
1024        let type_uuid = Uuid::default();
1025        let r1 = UntypedResource::new_pending(path.clone().into(), type_uuid);
1026        let r2 =
1027            UntypedResource::new_load_error(path.clone().into(), Default::default(), type_uuid);
1028        let r3 = UntypedResource::new_ok(Default::default(), Stub {});
1029        state.push(r1.clone());
1030        state.push(r2.clone());
1031        state.push(r3.clone());
1032
1033        assert_eq!(state.resources(), vec![r1.clone(), r2.clone(), r3.clone()]);
1034        assert!(state.iter().eq([&r1, &r2, &r3]));
1035    }
1036
1037    #[test]
1038    fn resource_manager_state_destroy_unused_resources() {
1039        let mut state = new_resource_manager();
1040
1041        state.push(UntypedResource::new_pending(
1042            PathBuf::from("test.txt").into(),
1043            Uuid::default(),
1044        ));
1045        assert_eq!(state.len(), 1);
1046
1047        state.destroy_unused_resources();
1048        assert_eq!(state.len(), 0);
1049    }
1050
1051    #[test]
1052    fn resource_manager_state_request() {
1053        let mut state = new_resource_manager();
1054        let path = PathBuf::from("test.txt");
1055        let type_uuid = Uuid::default();
1056
1057        let resource =
1058            UntypedResource::new_load_error(path.clone().into(), Default::default(), type_uuid);
1059        state.push(resource.clone());
1060
1061        let res = state.request(path);
1062        assert_eq!(res, resource);
1063
1064        let path = PathBuf::from("foo.txt");
1065        let res = state.request(path.clone());
1066
1067        assert_eq!(res.kind(), ResourceKind::External(path.clone()));
1068        assert_eq!(res.type_uuid(), type_uuid);
1069        assert!(!res.is_loading());
1070    }
1071
1072    #[test]
1073    fn resource_manager_state_try_reload_resource_from_path() {
1074        let mut state = new_resource_manager();
1075        state.loaders.set(Stub {});
1076
1077        let resource = UntypedResource::new_load_error(
1078            PathBuf::from("test.txt").into(),
1079            Default::default(),
1080            Uuid::default(),
1081        );
1082        state.push(resource.clone());
1083
1084        assert!(!state.try_reload_resource_from_path(Path::new("foo.txt")));
1085
1086        assert!(state.try_reload_resource_from_path(Path::new("test.txt")));
1087        assert!(resource.is_loading());
1088    }
1089
1090    #[test]
1091    fn resource_manager_state_get_wait_context() {
1092        let mut state = new_resource_manager();
1093
1094        let resource = UntypedResource::new_ok(Default::default(), Stub {});
1095        state.push(resource.clone());
1096        let cx = state.get_wait_context();
1097
1098        assert!(cx.resources.eq(&vec![resource]));
1099    }
1100
1101    #[test]
1102    fn resource_manager_new() {
1103        let manager = ResourceManager::new(Arc::new(Default::default()));
1104
1105        assert!(manager.state.lock().is_empty());
1106        assert!(manager.state().is_empty());
1107    }
1108
1109    #[test]
1110    fn resource_manager_register() {
1111        let manager = ResourceManager::new(Arc::new(Default::default()));
1112        let path = PathBuf::from("test.txt");
1113        let type_uuid = Uuid::default();
1114
1115        let resource = UntypedResource::new_pending(path.clone().into(), type_uuid);
1116        let res = manager.register(resource.clone(), path.clone(), |_, __| true);
1117        assert!(res.is_err());
1118
1119        let resource = UntypedResource::new_ok(Default::default(), Stub {});
1120        let res = manager.register(resource.clone(), path.clone(), |_, __| true);
1121        assert!(res.is_ok());
1122    }
1123
1124    #[test]
1125    fn resource_manager_request() {
1126        let manager = ResourceManager::new(Arc::new(Default::default()));
1127        let untyped = UntypedResource::new_ok(Default::default(), Stub {});
1128        let res = manager.register(untyped.clone(), PathBuf::from("foo.txt"), |_, __| true);
1129        assert!(res.is_ok());
1130
1131        let res: Resource<Stub> = manager.request(Path::new("foo.txt"));
1132        assert_eq!(
1133            res,
1134            Resource {
1135                untyped,
1136                phantom: PhantomData::<Stub>
1137            }
1138        );
1139    }
1140
1141    #[test]
1142    fn resource_manager_request_untyped() {
1143        let manager = ResourceManager::new(Arc::new(Default::default()));
1144        let resource = UntypedResource::new_ok(Default::default(), Stub {});
1145        let res = manager.register(resource.clone(), PathBuf::from("foo.txt"), |_, __| true);
1146        assert!(res.is_ok());
1147
1148        let res = manager.request_untyped(Path::new("foo.txt"));
1149        assert_eq!(res, resource);
1150    }
1151
1152    #[test]
1153    fn display_for_resource_registration_error() {
1154        assert_eq!(
1155            format!("{}", ResourceRegistrationError::AlreadyRegistered),
1156            "A resource is already registered!"
1157        );
1158        assert_eq!(
1159            format!("{}", ResourceRegistrationError::InvalidState),
1160            "A resource was in invalid state!"
1161        );
1162        assert_eq!(
1163            format!("{}", ResourceRegistrationError::UnableToRegister),
1164            "Unable to register the resource!"
1165        );
1166    }
1167
1168    #[test]
1169    fn debug_for_resource_registration_error() {
1170        assert_eq!(
1171            format!("{:?}", ResourceRegistrationError::AlreadyRegistered),
1172            "AlreadyRegistered"
1173        );
1174        assert_eq!(
1175            format!("{:?}", ResourceRegistrationError::InvalidState),
1176            "InvalidState"
1177        );
1178        assert_eq!(
1179            format!("{:?}", ResourceRegistrationError::UnableToRegister),
1180            "UnableToRegister"
1181        );
1182    }
1183}