fyrox_resource/
lib.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 management
22
23#![forbid(unsafe_code)]
24#![allow(clippy::doc_lazy_continuation)]
25#![allow(clippy::mutable_key_type)]
26#![warn(missing_docs)]
27
28use crate::{
29    core::{
30        combine_uuids,
31        parking_lot::MutexGuard,
32        reflect::prelude::*,
33        uuid::{uuid, Uuid},
34        visitor::prelude::*,
35        TypeUuidProvider,
36    },
37    state::{LoadError, ResourceState},
38    untyped::{ResourceHeader, ResourceKind, UntypedResource},
39};
40use fxhash::FxHashSet;
41pub use fyrox_core as core;
42use serde::{Deserialize, Serialize};
43use std::{
44    any::Any,
45    error::Error,
46    fmt::Display,
47    fmt::{Debug, Formatter},
48    future::Future,
49    hash::{Hash, Hasher},
50    marker::PhantomData,
51    ops::{Deref, DerefMut},
52    path::Path,
53    path::PathBuf,
54    pin::Pin,
55    task::{Context, Poll},
56};
57
58pub mod builtin;
59pub mod constructor;
60pub mod entry;
61pub mod event;
62pub mod graph;
63pub mod io;
64pub mod loader;
65pub mod manager;
66pub mod metadata;
67pub mod options;
68pub mod registry;
69pub mod state;
70pub mod untyped;
71
72/// Type UUID of texture resource. It is defined here to load old versions of resources.
73pub const TEXTURE_RESOURCE_UUID: Uuid = uuid!("02c23a44-55fa-411a-bc39-eb7a5eadf15c");
74/// Type UUID of model resource. It is defined here to load old versions of resources.
75pub const MODEL_RESOURCE_UUID: Uuid = uuid!("44cd768f-b4ca-4804-a98c-0adf85577ada");
76/// Type UUID of sound buffer resource. It is defined here to load old versions of resources.
77pub const SOUND_BUFFER_RESOURCE_UUID: Uuid = uuid!("f6a077b7-c8ff-4473-a95b-0289441ea9d8");
78/// Type UUID of shader resource. It is defined here to load old versions of resources.
79pub const SHADER_RESOURCE_UUID: Uuid = uuid!("f1346417-b726-492a-b80f-c02096c6c019");
80/// Type UUID of curve resource. It is defined here to load old versions of resources.
81pub const CURVE_RESOURCE_UUID: Uuid = uuid!("f28b949f-28a2-4b68-9089-59c234f58b6b");
82/// Type UUID of HRIR sphere resource. It is defined here to load old versions of resources.
83pub const HRIR_SPHERE_RESOURCE_UUID: Uuid = uuid!("c92a0fa3-0ed3-49a9-be44-8f06271c6be2");
84/// Type UUID of font resource. It is defined here to load old versions of resources.
85pub const FONT_RESOURCE_UUID: Uuid = uuid!("692fec79-103a-483c-bb0b-9fc3a349cb48");
86
87/// A trait for resource data.
88pub trait ResourceData: Debug + Visit + Send + Reflect {
89    /// Returns unique data type id.
90    fn type_uuid(&self) -> Uuid;
91
92    /// Saves the resource data a file at the specified path. This method is free to
93    /// decide how the resource data is saved. This is needed, because there are multiple formats
94    /// that defines various kinds of resources. For example, a rectangular texture could be saved
95    /// into a bunch of formats, such as png, bmp, tga, jpg etc., but in the engine it is single
96    /// Texture resource. In any case, produced file should be compatible with a respective resource
97    /// loader.
98    fn save(&mut self, #[allow(unused_variables)] path: &Path) -> Result<(), Box<dyn Error>>;
99
100    /// Returns `true` if the resource data can be saved to a file, `false` - otherwise. Not every
101    /// resource type supports saving, for example there might be temporary resource type that is
102    /// used only at runtime which does not need saving at all.
103    fn can_be_saved(&self) -> bool;
104
105    /// Tries to clone the resource data. This method can return `None` if the underlying type is
106    /// non-cloneable.
107    fn try_clone_box(&self) -> Option<Box<dyn ResourceData>>;
108}
109
110/// Extension trait for a resource data of a particular type, which adds additional functionality,
111/// such as: a way to get default state of the data (`Default` impl), a way to get data's type uuid.
112/// The trait has automatic implementation for any type that implements
113/// ` ResourceData + Default + TypeUuidProvider` traits.
114pub trait TypedResourceData: ResourceData + Default + TypeUuidProvider {}
115
116impl<T> TypedResourceData for T where T: ResourceData + Default + TypeUuidProvider {}
117
118/// A trait for resource load error.
119pub trait ResourceLoadError: 'static + Debug + Display + Send + Sync {}
120
121impl<T> ResourceLoadError for T where T: 'static + Debug + Display + Send + Sync {}
122
123/// Provides typed access to a resource state.
124pub struct ResourceHeaderGuard<'a, T>
125where
126    T: TypedResourceData,
127{
128    guard: MutexGuard<'a, ResourceHeader>,
129    phantom: PhantomData<T>,
130}
131
132impl<'a, T> From<MutexGuard<'a, ResourceHeader>> for ResourceHeaderGuard<'a, T>
133where
134    T: TypedResourceData,
135{
136    fn from(guard: MutexGuard<'a, ResourceHeader>) -> Self {
137        Self {
138            guard,
139            phantom: PhantomData,
140        }
141    }
142}
143
144impl<T> ResourceHeaderGuard<'_, T>
145where
146    T: TypedResourceData,
147{
148    /// The UUID that universally identifies the resource.
149    pub fn resource_uuid(&self) -> Uuid {
150        self.guard.uuid
151    }
152
153    /// Returns resource kind of the locked resource.
154    pub fn kind(&self) -> ResourceKind {
155        self.guard.kind
156    }
157
158    /// Tries to fetch the underlying data of the resource type. This operation will fail if the
159    /// locked resource is not in [`ResourceState::Ok`] or if its actual data does not match the
160    /// type of the resource.
161    pub fn data(&mut self) -> Option<&mut T> {
162        if let ResourceState::Ok { ref mut data, .. } = self.guard.state {
163            (&mut **data as &mut dyn Any).downcast_mut::<T>()
164        } else {
165            None
166        }
167    }
168
169    /// Tries to fetch the underlying data of the resource type. This operation will fail if the
170    /// locked resource is not in [`ResourceState::Ok`] or if its actual data does not match the
171    /// type of the resource.
172    pub fn data_ref(&self) -> Option<&T> {
173        if let ResourceState::Ok { ref data, .. } = self.guard.state {
174            (&**data as &dyn Any).downcast_ref::<T>()
175        } else {
176            None
177        }
178    }
179
180    /// Tries to fetch the underlying data of the resource type. This operation will fail if the
181    /// locked resource is not in [`ResourceState::Ok`] or if its actual data does not match the
182    /// type of the resource.
183    pub fn data_ref_with_id(&self) -> Option<(&T, &Uuid)> {
184        let uuid = &self.guard.uuid;
185        if let ResourceState::Ok { ref data } = self.guard.state {
186            (&**data as &dyn Any)
187                .downcast_ref::<T>()
188                .map(|typed| (typed, uuid))
189        } else {
190            None
191        }
192    }
193}
194
195/// A resource of particular data type. It is a typed wrapper around [`UntypedResource`] which
196/// does type checks at runtime. See [`UntypedResource`] for more info.
197///
198/// ## Default State
199///
200/// Default state of the resource will be [`ResourceState::Ok`] with `T::default`.
201#[derive(Reflect, Serialize, Deserialize)]
202#[serde(
203    bound = "T: TypedResourceData",
204    from = "UntypedResource",
205    into = "UntypedResource"
206)]
207pub struct Resource<T: Debug> {
208    untyped: UntypedResource,
209    #[reflect(hidden)]
210    phantom: PhantomData<T>,
211}
212
213impl<T: Debug> Debug for Resource<T> {
214    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
215        write!(
216            f,
217            "Resource<{}>({})",
218            std::any::type_name::<T>(),
219            self.untyped
220        )
221    }
222}
223
224impl<T: TypedResourceData> AsRef<UntypedResource> for Resource<T> {
225    fn as_ref(&self) -> &UntypedResource {
226        &self.untyped
227    }
228}
229
230impl AsRef<UntypedResource> for UntypedResource {
231    fn as_ref(&self) -> &UntypedResource {
232        self
233    }
234}
235
236impl<T: TypedResourceData> TypeUuidProvider for Resource<T> {
237    fn type_uuid() -> Uuid {
238        combine_uuids(
239            uuid!("790b1a1c-a997-46c4-ac3b-8565501f0052"),
240            <T as TypeUuidProvider>::type_uuid(),
241        )
242    }
243}
244
245impl<T> Visit for Resource<T>
246where
247    T: TypedResourceData,
248{
249    fn visit(&mut self, name: &str, visitor: &mut Visitor) -> VisitResult {
250        self.untyped
251            .visit_with_type_uuid(name, Some(<T as TypeUuidProvider>::type_uuid()), visitor)
252    }
253}
254
255impl<T> PartialEq for Resource<T>
256where
257    T: TypedResourceData,
258{
259    fn eq(&self, other: &Self) -> bool {
260        self.untyped == other.untyped
261    }
262}
263
264impl<T> Eq for Resource<T> where T: TypedResourceData {}
265
266impl<T> Hash for Resource<T>
267where
268    T: TypedResourceData,
269{
270    fn hash<H: Hasher>(&self, state: &mut H) {
271        self.untyped.hash(state)
272    }
273}
274
275impl<T> Resource<T>
276where
277    T: TypedResourceData,
278{
279    /// Create a summary of this resource handle, wht UUID, embedded/external, and Ok, Pending, Error, etc.
280    pub fn summary(&self) -> String {
281        format!("{}", self.untyped)
282    }
283    /// Creates new resource in pending state.
284    #[inline]
285    pub fn new_pending(uuid: Uuid, kind: ResourceKind) -> Self {
286        Self {
287            untyped: UntypedResource::new_pending(uuid, kind),
288            phantom: PhantomData,
289        }
290    }
291
292    /// Creates new resource in ok state (fully loaded).
293    #[inline]
294    pub fn new_ok(resource_uuid: Uuid, kind: ResourceKind, data: T) -> Self {
295        Self {
296            untyped: UntypedResource::new_ok(resource_uuid, kind, data),
297            phantom: PhantomData,
298        }
299    }
300
301    /// Creates a new embedded resource in ok state (fully loaded).
302    #[inline]
303    pub fn new_embedded(data: T) -> Self {
304        Self {
305            untyped: UntypedResource::new_ok(Uuid::new_v4(), ResourceKind::Embedded, data),
306            phantom: PhantomData,
307        }
308    }
309
310    /// Creates new resource in error state.
311    #[inline]
312    pub fn new_load_error(kind: ResourceKind, path: PathBuf, error: LoadError) -> Self {
313        Self {
314            untyped: UntypedResource::new_load_error(kind, path, error),
315            phantom: PhantomData,
316        }
317    }
318
319    /// Converts self to internal value.
320    #[inline]
321    pub fn into_untyped(self) -> UntypedResource {
322        self.untyped
323    }
324
325    /// Locks internal mutex provides access to the state.
326    #[inline]
327    pub fn state(&self) -> ResourceHeaderGuard<'_, T> {
328        self.untyped.typed_lock()
329    }
330
331    /// Tries to lock internal mutex provides access to the state.
332    #[inline]
333    pub fn try_acquire_state(&self) -> Option<ResourceHeaderGuard<'_, T>> {
334        self.untyped.try_typed_lock()
335    }
336
337    /// Locks the resource and provides access to its header. See [`ResourceHeader`] docs for more info.
338    #[inline]
339    pub fn header(&self) -> MutexGuard<'_, ResourceHeader> {
340        self.untyped.lock()
341    }
342
343    /// Returns true if the resource is still loading.
344    #[inline]
345    pub fn is_loading(&self) -> bool {
346        self.untyped.is_loading()
347    }
348
349    /// Returns true if the resource is fully loaded and ready for use.
350    #[inline]
351    pub fn is_ok(&self) -> bool {
352        self.untyped.is_ok()
353    }
354
355    /// Returns true if the resource is failed to load.
356    #[inline]
357    pub fn is_failed_to_load(&self) -> bool {
358        self.untyped.is_failed_to_load()
359    }
360
361    /// Returns exact amount of users of the resource.
362    #[inline]
363    pub fn use_count(&self) -> usize {
364        self.untyped.use_count()
365    }
366
367    /// Returns a pointer as numeric value which can be used as a hash.
368    #[inline]
369    pub fn key(&self) -> u64 {
370        self.untyped.key()
371    }
372
373    /// Returns kind of the resource, if the resource is registered.
374    #[inline]
375    pub fn kind(&self) -> ResourceKind {
376        self.untyped.kind()
377    }
378
379    /// The UUID of the resource. All resources must have a UUID, even if they are not loaded
380    /// because the UUID is how the resource manager knows the path to load from.
381    #[inline]
382    pub fn resource_uuid(&self) -> Uuid {
383        self.untyped.resource_uuid()
384    }
385
386    /// Sets a new kind of the resource.
387    #[inline]
388    pub fn set_path(&mut self, new_kind: ResourceKind) {
389        self.untyped.set_kind(new_kind);
390    }
391
392    /// Allows you to get a reference to the resource data. The returned object implements [`Deref`]
393    /// and [`DerefMut`] traits, and basically acts like a reference to the resource value.
394    ///
395    /// # Panic
396    ///
397    /// An attempt to dereference the returned object will result in panic if the resource is not
398    /// loaded yet, or there was a loading error. Usually this is ok because you should chain this
399    /// call like this `resource.await?.data_ref()`. Every resource implements [`Future`] trait,
400    /// and it returns Result, so if you'll await the future then you'll get Result, so call to
401    /// `data_ref` will be fine.
402    ///
403    /// You can also use [`ResourceDataRef::as_loaded_ref`] and [`ResourceDataRef::as_loaded_mut`]
404    /// methods that perform checked access to the resource internals.
405    #[inline]
406    pub fn data_ref(&self) -> ResourceDataRef<'_, T> {
407        ResourceDataRef {
408            guard: self.untyped.lock(),
409            phantom: Default::default(),
410        }
411    }
412
413    /// Tries to save the resource to the specified path.
414    pub fn save(&self, path: &Path) -> Result<(), Box<dyn Error>> {
415        self.untyped.save(path)
416    }
417}
418
419impl<T> Default for Resource<T>
420where
421    T: TypedResourceData,
422{
423    #[inline]
424    fn default() -> Self {
425        Self {
426            untyped: UntypedResource::new_ok(Uuid::new_v4(), ResourceKind::Embedded, T::default()),
427            phantom: Default::default(),
428        }
429    }
430}
431
432impl<T: Debug> Clone for Resource<T> {
433    #[inline]
434    fn clone(&self) -> Self {
435        Self {
436            untyped: self.untyped.clone(),
437            phantom: Default::default(),
438        }
439    }
440}
441
442impl<T> From<Uuid> for Resource<T>
443where
444    T: TypedResourceData,
445{
446    fn from(uuid: Uuid) -> Self {
447        UntypedResource::from(uuid).into()
448    }
449}
450
451impl<T> From<UntypedResource> for Resource<T>
452where
453    T: TypedResourceData,
454{
455    #[inline]
456    fn from(untyped: UntypedResource) -> Self {
457        if let Some(type_uuid) = untyped.type_uuid() {
458            let expected = <T as TypeUuidProvider>::type_uuid();
459            if type_uuid != expected {
460                panic!("Resource type mismatch. Expected: {expected}. Found: {type_uuid}");
461            }
462        }
463        Self {
464            untyped,
465            phantom: Default::default(),
466        }
467    }
468}
469
470#[allow(clippy::from_over_into)]
471impl<T> Into<UntypedResource> for Resource<T>
472where
473    T: TypedResourceData,
474{
475    #[inline]
476    fn into(self) -> UntypedResource {
477        self.untyped
478    }
479}
480
481impl<T> Future for Resource<T>
482where
483    T: TypedResourceData,
484{
485    type Output = Result<Self, LoadError>;
486
487    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
488        let mut inner = self.untyped.clone();
489        Pin::new(&mut inner)
490            .poll(cx)
491            .map(|r| r.map(|_| self.clone()))
492    }
493}
494
495#[doc(hidden)]
496pub struct ResourceDataRef<'a, T>
497where
498    T: TypedResourceData,
499{
500    guard: MutexGuard<'a, ResourceHeader>,
501    phantom: PhantomData<T>,
502}
503
504impl<T> ResourceDataRef<'_, T>
505where
506    T: TypedResourceData,
507{
508    #[inline]
509    pub fn as_loaded_ref(&self) -> Option<&T> {
510        match self.guard.state {
511            ResourceState::Ok { ref data, .. } => (&**data as &dyn Any).downcast_ref(),
512            _ => None,
513        }
514    }
515
516    #[inline]
517    pub fn as_loaded_mut(&mut self) -> Option<&mut T> {
518        match self.guard.state {
519            ResourceState::Ok { ref mut data, .. } => (&mut **data as &mut dyn Any).downcast_mut(),
520            _ => None,
521        }
522    }
523}
524
525impl<T> Debug for ResourceDataRef<'_, T>
526where
527    T: TypedResourceData,
528{
529    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
530        match self.guard.state {
531            ResourceState::Unloaded => {
532                write!(
533                    f,
534                    "Attempt to get reference to resource data while it is unloaded!"
535                )
536            }
537            ResourceState::Pending { .. } => {
538                write!(
539                    f,
540                    "Attempt to get reference to resource data while it is loading!"
541                )
542            }
543            ResourceState::LoadError { .. } => {
544                write!(
545                    f,
546                    "Attempt to get reference to resource data which failed to load!"
547                )
548            }
549            ResourceState::Ok { ref data, .. } => data.fmt(f),
550        }
551    }
552}
553
554impl<T> Deref for ResourceDataRef<'_, T>
555where
556    T: TypedResourceData,
557{
558    type Target = T;
559
560    fn deref(&self) -> &Self::Target {
561        match self.guard.state {
562            ResourceState::Unloaded => {
563                panic!(
564                    "Attempt to get reference to resource data while it is unloaded! Type {}",
565                    std::any::type_name::<T>()
566                )
567            }
568            ResourceState::Pending { .. } => {
569                panic!(
570                    "Attempt to get reference to resource data while it is loading! Type {}",
571                    std::any::type_name::<T>()
572                )
573            }
574            ResourceState::LoadError {
575                ref path,
576                ref error,
577            } => {
578                let path = if path.as_os_str().is_empty() {
579                    "Unknown".to_string()
580                } else {
581                    format!("{path:?}")
582                };
583                panic!("Attempt to get reference to resource data which failed to load! Type {}. Path: {path}. Error: {error:?}", std::any::type_name::<T>())
584            }
585            ResourceState::Ok { ref data } => (&**data as &dyn Any)
586                .downcast_ref()
587                .expect("Type mismatch!"),
588        }
589    }
590}
591
592impl<T> DerefMut for ResourceDataRef<'_, T>
593where
594    T: TypedResourceData,
595{
596    fn deref_mut(&mut self) -> &mut Self::Target {
597        let header = &mut *self.guard;
598        match header.state {
599            ResourceState::Unloaded => {
600                panic!("Attempt to get reference to resource data while it is unloaded!")
601            }
602            ResourceState::Pending { .. } => {
603                panic!("Attempt to get reference to resource data while it is loading!")
604            }
605            ResourceState::LoadError { .. } => {
606                panic!("Attempt to get reference to resource data which failed to load!")
607            }
608            ResourceState::Ok { ref mut data, .. } => (&mut **data as &mut dyn Any)
609                .downcast_mut()
610                .expect("Type mismatch!"),
611        }
612    }
613}
614
615/// Collects all resources used by a given entity. Internally, it uses reflection to iterate over
616/// each field of every descendant sub-object of the entity. This function could be used to collect
617/// all resources used by an object, which could be useful if you're building a resource dependency
618/// analyzer.
619pub fn collect_used_resources(
620    entity: &dyn Reflect,
621    resources_collection: &mut FxHashSet<UntypedResource>,
622) {
623    #[inline(always)]
624    fn type_is<T: Reflect>(entity: &dyn Reflect) -> bool {
625        let mut types_match = false;
626        entity.downcast_ref::<T>(&mut |v| {
627            types_match = v.is_some();
628        });
629        types_match
630    }
631
632    // Skip potentially large chunks of numeric data, that definitely cannot contain any resources.
633    // TODO: This is a brute-force solution which does not include all potential types with plain
634    // data.
635    let mut finished = type_is::<Vec<u8>>(entity)
636        || type_is::<Vec<u16>>(entity)
637        || type_is::<Vec<u32>>(entity)
638        || type_is::<Vec<u64>>(entity)
639        || type_is::<Vec<i8>>(entity)
640        || type_is::<Vec<i16>>(entity)
641        || type_is::<Vec<i32>>(entity)
642        || type_is::<Vec<i64>>(entity)
643        || type_is::<Vec<f32>>(entity)
644        || type_is::<Vec<f64>>(entity);
645
646    if finished {
647        return;
648    }
649
650    entity.downcast_ref::<UntypedResource>(&mut |v| {
651        if let Some(resource) = v {
652            resources_collection.insert(resource.clone());
653            finished = true;
654        }
655    });
656
657    if finished {
658        return;
659    }
660
661    entity.as_array(&mut |array| {
662        if let Some(array) = array {
663            for i in 0..array.reflect_len() {
664                if let Some(item) = array.reflect_index(i) {
665                    collect_used_resources(item, resources_collection)
666                }
667            }
668
669            finished = true;
670        }
671    });
672
673    if finished {
674        return;
675    }
676
677    entity.as_inheritable_variable(&mut |inheritable| {
678        if let Some(inheritable) = inheritable {
679            collect_used_resources(inheritable.inner_value_ref(), resources_collection);
680
681            finished = true;
682        }
683    });
684
685    if finished {
686        return;
687    }
688
689    entity.as_hash_map(&mut |hash_map| {
690        if let Some(hash_map) = hash_map {
691            for i in 0..hash_map.reflect_len() {
692                if let Some((key, value)) = hash_map.reflect_get_at(i) {
693                    collect_used_resources(key, resources_collection);
694                    collect_used_resources(value, resources_collection);
695                }
696            }
697
698            finished = true;
699        }
700    });
701
702    if finished {
703        return;
704    }
705
706    entity.fields_ref(&mut |fields| {
707        for field in fields {
708            collect_used_resources(field.value.field_value_as_reflect(), resources_collection);
709        }
710    })
711}
712
713#[cfg(test)]
714mod tests {
715    use super::*;
716    use crate::{
717        io::{FsResourceIo, ResourceIo},
718        loader::{BoxedLoaderFuture, LoaderPayload, ResourceLoader, ResourceLoadersContainer},
719        manager::ResourceManager,
720        metadata::ResourceMetadata,
721        registry::ResourceRegistry,
722        state::LoadError,
723        ResourceData,
724    };
725    use fyrox_core::{
726        append_extension, futures::executor::block_on, io::FileError, parking_lot::Mutex,
727        reflect::prelude::*, task::TaskPool, uuid, visitor::prelude::*, SafeLock, TypeUuidProvider,
728        Uuid,
729    };
730    use ron::ser::PrettyConfig;
731    use serde::{Deserialize, Serialize};
732    use std::{
733        error::Error,
734        fs::File,
735        io::Write,
736        ops::Range,
737        path::{Path, PathBuf},
738        sync::Arc,
739    };
740
741    #[derive(Serialize, Deserialize, Default, Debug, Clone, Visit, Reflect, TypeUuidProvider)]
742    #[type_uuid(id = "241d14c7-079e-4395-a63c-364f0fc3e6ea")]
743    struct MyData {
744        data: u32,
745    }
746
747    impl MyData {
748        pub async fn load_from_file(
749            path: &Path,
750            resource_io: &dyn ResourceIo,
751        ) -> Result<Self, FileError> {
752            resource_io.load_file(path).await.and_then(|metadata| {
753                ron::de::from_bytes::<Self>(&metadata).map_err(|err| {
754                    FileError::Custom(format!(
755                        "Unable to deserialize the resource metadata. Reason: {err:?}"
756                    ))
757                })
758            })
759        }
760    }
761
762    impl ResourceData for MyData {
763        fn type_uuid(&self) -> Uuid {
764            <Self as TypeUuidProvider>::type_uuid()
765        }
766
767        fn save(&mut self, path: &Path) -> Result<(), Box<dyn Error>> {
768            let string = ron::ser::to_string_pretty(self, PrettyConfig::default())
769                .map_err(|err| {
770                    FileError::Custom(format!(
771                        "Unable to serialize resource metadata for {} resource! Reason: {}",
772                        path.display(),
773                        err
774                    ))
775                })
776                .map_err(|_| "error".to_string())?;
777            let mut file = File::create(path)?;
778            file.write_all(string.as_bytes())?;
779            Ok(())
780        }
781
782        fn can_be_saved(&self) -> bool {
783            true
784        }
785
786        fn try_clone_box(&self) -> Option<Box<dyn ResourceData>> {
787            Some(Box::new(self.clone()))
788        }
789    }
790
791    struct MyDataLoader {}
792
793    impl MyDataLoader {
794        const EXT: &'static str = "my_data";
795    }
796
797    impl ResourceLoader for MyDataLoader {
798        fn extensions(&self) -> &[&str] {
799            &[Self::EXT]
800        }
801
802        fn data_type_uuid(&self) -> Uuid {
803            <MyData as TypeUuidProvider>::type_uuid()
804        }
805
806        fn load(&self, path: PathBuf, io: Arc<dyn ResourceIo>) -> BoxedLoaderFuture {
807            Box::pin(async move {
808                let my_data = MyData::load_from_file(&path, io.as_ref())
809                    .await
810                    .map_err(LoadError::new)?;
811                Ok(LoaderPayload::new(my_data))
812            })
813        }
814    }
815
816    const TEST_FOLDER1: &str = "./test_output1";
817    const TEST_FOLDER2: &str = "./test_output2";
818    const TEST_FOLDER3: &str = "./test_output3";
819
820    fn make_file_path(root: &str, n: usize) -> PathBuf {
821        Path::new(root).join(format!("test{n}.{}", MyDataLoader::EXT))
822    }
823
824    fn make_metadata_file_path(root: &str, n: usize) -> PathBuf {
825        Path::new(root).join(format!(
826            "test{n}.{}.{}",
827            MyDataLoader::EXT,
828            ResourceMetadata::EXTENSION
829        ))
830    }
831
832    fn write_test_resources(root: &str, indices: Range<usize>) {
833        let path = Path::new(root);
834        if !std::fs::exists(path).unwrap() {
835            std::fs::create_dir_all(path).unwrap();
836        }
837
838        for i in indices {
839            MyData { data: i as u32 }
840                .save(&make_file_path(root, i))
841                .unwrap();
842        }
843    }
844
845    #[test]
846    fn test_serialize() {
847        let uuid = uuid!("6d1aadb5-42e1-485b-910b-fa4d81b61855");
848        let typed = Resource::<MyData>::from(uuid);
849        let untyped = UntypedResource::from(uuid);
850        let s = ron::ser::to_string(&typed).unwrap();
851        assert_eq!(&ron::ser::to_string(&uuid).unwrap(), &s);
852        assert_eq!(&ron::ser::to_string(&untyped).unwrap(), &s);
853        let output_uuid = ron::de::from_str::<Uuid>(&s).unwrap();
854        assert_eq!(output_uuid, uuid);
855        let untyped = ron::de::from_str::<UntypedResource>(&s).unwrap();
856        assert_eq!(untyped.resource_uuid(), uuid);
857        assert_eq!(untyped.kind(), ResourceKind::External);
858        let output = ron::de::from_str::<Resource<MyData>>(&s).unwrap();
859        assert_eq!(output.resource_uuid(), uuid);
860        assert_eq!(output.kind(), ResourceKind::External);
861    }
862
863    #[test]
864    fn test_registry_scan() {
865        write_test_resources(TEST_FOLDER1, 0..2);
866
867        assert!(std::fs::exists(make_file_path(TEST_FOLDER1, 0)).unwrap());
868        assert!(std::fs::exists(make_file_path(TEST_FOLDER1, 1)).unwrap());
869
870        let io = Arc::new(FsResourceIo);
871
872        let mut loaders = ResourceLoadersContainer::new();
873        loaders.set(MyDataLoader {});
874        let loaders = Arc::new(Mutex::new(loaders));
875
876        let registry = block_on(ResourceRegistry::scan(
877            io,
878            loaders,
879            Path::new(TEST_FOLDER1).join("resources.registry"),
880            Default::default(),
881        ));
882
883        assert!(std::fs::exists(make_metadata_file_path(TEST_FOLDER1, 0)).unwrap());
884        assert!(std::fs::exists(make_metadata_file_path(TEST_FOLDER1, 1)).unwrap());
885
886        assert_eq!(registry.len(), 2);
887    }
888
889    #[test]
890    fn test_resource_manager_request_simple() {
891        write_test_resources(TEST_FOLDER2, 2..4);
892        let resource_manager =
893            ResourceManager::new(Arc::new(FsResourceIo), Arc::new(TaskPool::new()));
894        resource_manager
895            .state()
896            .resource_registry
897            .safe_lock()
898            .set_path(Path::new(TEST_FOLDER2).join("resources.registry"));
899        resource_manager.add_loader(MyDataLoader {});
900        resource_manager.update_or_load_registry();
901        let path1 = make_file_path(TEST_FOLDER2, 2);
902        let path2 = make_file_path(TEST_FOLDER2, 3);
903        let res1 = resource_manager.request::<MyData>(&path1);
904        let res1_2 = resource_manager.request::<MyData>(&path1);
905        assert_eq!(res1.key(), res1_2.key());
906        let res2 = resource_manager.request::<MyData>(path2);
907        assert_ne!(res1.key(), res2.key());
908        assert_eq!(block_on(res1).unwrap().data_ref().data, 2);
909        assert_eq!(block_on(res2).unwrap().data_ref().data, 3);
910    }
911
912    #[test]
913    fn test_move_resource() {
914        write_test_resources(TEST_FOLDER3, 0..2);
915        let resource_manager =
916            ResourceManager::new(Arc::new(FsResourceIo), Arc::new(TaskPool::new()));
917        resource_manager
918            .state()
919            .resource_registry
920            .safe_lock()
921            .set_path(Path::new(TEST_FOLDER3).join("resources.registry"));
922        resource_manager.add_loader(MyDataLoader {});
923        resource_manager.update_or_load_registry();
924        let path1 = make_file_path(TEST_FOLDER3, 0);
925        let path2 = make_file_path(TEST_FOLDER3, 1);
926        let res1 = resource_manager.request::<MyData>(path1);
927        let res2 = resource_manager.request::<MyData>(path2);
928        assert_eq!(block_on(res1.clone()).unwrap().data_ref().data, 0);
929        assert_eq!(block_on(res2.clone()).unwrap().data_ref().data, 1);
930        let new_res1_path = ResourceRegistry::normalize_path(make_file_path(TEST_FOLDER3, 3));
931        let new_res2_path = ResourceRegistry::normalize_path(make_file_path(TEST_FOLDER3, 4));
932        block_on(resource_manager.move_resource(res1.as_ref(), &new_res1_path, true)).unwrap();
933        block_on(resource_manager.move_resource(res2.as_ref(), &new_res2_path, true)).unwrap();
934        assert_eq!(
935            resource_manager.resource_path(res1.as_ref()).unwrap(),
936            new_res1_path
937        );
938        assert!(
939            std::fs::exists(append_extension(new_res1_path, ResourceMetadata::EXTENSION)).unwrap()
940        );
941        assert_eq!(
942            resource_manager.resource_path(res2.as_ref()).unwrap(),
943            new_res2_path
944        );
945        assert!(
946            std::fs::exists(append_extension(new_res2_path, ResourceMetadata::EXTENSION)).unwrap()
947        );
948    }
949}