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(missing_docs)]
25#![allow(clippy::doc_lazy_continuation)]
26#![allow(clippy::mutable_key_type)]
27
28use crate::{
29    core::{
30        parking_lot::MutexGuard,
31        reflect::prelude::*,
32        uuid::{uuid, Uuid},
33        visitor::prelude::*,
34        TypeUuidProvider,
35    },
36    state::ResourceState,
37    untyped::UntypedResource,
38};
39use fxhash::FxHashSet;
40use std::{
41    error::Error,
42    fmt::{Debug, Formatter},
43    future::Future,
44    hash::{Hash, Hasher},
45    marker::PhantomData,
46    ops::{Deref, DerefMut},
47    path::Path,
48    pin::Pin,
49    task::{Context, Poll},
50};
51
52use crate::state::LoadError;
53use crate::untyped::{ResourceHeader, ResourceKind};
54pub use fyrox_core as core;
55use fyrox_core::log::Log;
56use fyrox_core::{combine_uuids, Downcast};
57
58pub mod constructor;
59pub mod entry;
60pub mod event;
61pub mod graph;
62pub mod io;
63pub mod loader;
64pub mod manager;
65pub mod options;
66pub mod state;
67pub mod untyped;
68
69/// Type UUID of texture resource. It is defined here to load old versions of resources.
70pub const TEXTURE_RESOURCE_UUID: Uuid = uuid!("02c23a44-55fa-411a-bc39-eb7a5eadf15c");
71/// Type UUID of model resource. It is defined here to load old versions of resources.
72pub const MODEL_RESOURCE_UUID: Uuid = uuid!("44cd768f-b4ca-4804-a98c-0adf85577ada");
73/// Type UUID of sound buffer resource. It is defined here to load old versions of resources.
74pub const SOUND_BUFFER_RESOURCE_UUID: Uuid = uuid!("f6a077b7-c8ff-4473-a95b-0289441ea9d8");
75/// Type UUID of shader resource. It is defined here to load old versions of resources.
76pub const SHADER_RESOURCE_UUID: Uuid = uuid!("f1346417-b726-492a-b80f-c02096c6c019");
77/// Type UUID of curve resource. It is defined here to load old versions of resources.
78pub const CURVE_RESOURCE_UUID: Uuid = uuid!("f28b949f-28a2-4b68-9089-59c234f58b6b");
79
80/// A trait for resource data.
81pub trait ResourceData: Downcast + Debug + Visit + Send + Reflect {
82    /// Returns unique data type id.
83    fn type_uuid(&self) -> Uuid;
84
85    /// Saves the resource data a file at the specified path. This method is free to
86    /// decide how the resource data is saved. This is needed, because there are multiple formats
87    /// that defines various kinds of resources. For example, a rectangular texture could be saved
88    /// into a bunch of formats, such as png, bmp, tga, jpg etc., but in the engine it is single
89    /// Texture resource. In any case, produced file should be compatible with a respective resource
90    /// loader.
91    fn save(&mut self, #[allow(unused_variables)] path: &Path) -> Result<(), Box<dyn Error>>;
92
93    /// Returns `true` if the resource data can be saved to a file, `false` - otherwise. Not every
94    /// resource type supports saving, for example there might be temporary resource type that is
95    /// used only at runtime which does not need saving at all.
96    fn can_be_saved(&self) -> bool;
97}
98
99/// Extension trait for a resource data of a particular type, which adds additional functionality,
100/// such as: a way to get default state of the data (`Default` impl), a way to get data's type uuid.
101/// The trait has automatic implementation for any type that implements
102/// ` ResourceData + Default + TypeUuidProvider` traits.
103pub trait TypedResourceData: ResourceData + Default + TypeUuidProvider {}
104
105impl<T> TypedResourceData for T where T: ResourceData + Default + TypeUuidProvider {}
106
107/// A trait for resource load error.
108pub trait ResourceLoadError: 'static + Debug + Send + Sync {}
109
110impl<T> ResourceLoadError for T where T: 'static + Debug + Send + Sync {}
111
112/// Provides typed access to a resource state.
113pub struct ResourceHeaderGuard<'a, T>
114where
115    T: TypedResourceData,
116{
117    guard: MutexGuard<'a, ResourceHeader>,
118    phantom: PhantomData<T>,
119}
120
121impl<T> ResourceHeaderGuard<'_, T>
122where
123    T: TypedResourceData,
124{
125    pub fn kind(&self) -> &ResourceKind {
126        &self.guard.kind
127    }
128
129    pub fn data(&mut self) -> Option<&mut T> {
130        if let ResourceState::Ok(ref mut data) = self.guard.state {
131            Downcast::as_any_mut(&mut **data).downcast_mut::<T>()
132        } else {
133            None
134        }
135    }
136
137    pub fn data_ref(&self) -> Option<&T> {
138        if let ResourceState::Ok(ref data) = self.guard.state {
139            Downcast::as_any(&**data).downcast_ref::<T>()
140        } else {
141            None
142        }
143    }
144}
145
146/// A resource of particular data type. It is a typed wrapper around [`UntypedResource`] which
147/// does type checks at runtime. See [`UntypedResource`] for more info.
148///
149/// ## Default State
150///
151/// Default state of the resource will be [`ResourceState::Ok`] with `T::default`.
152#[derive(Debug, Reflect)]
153pub struct Resource<T>
154where
155    T: TypedResourceData,
156{
157    untyped: UntypedResource,
158    #[reflect(hidden)]
159    phantom: PhantomData<T>,
160}
161
162impl<T: TypedResourceData> TypeUuidProvider for Resource<T> {
163    fn type_uuid() -> Uuid {
164        combine_uuids(
165            uuid!("790b1a1c-a997-46c4-ac3b-8565501f0052"),
166            <T as TypeUuidProvider>::type_uuid(),
167        )
168    }
169}
170
171impl<T> Visit for Resource<T>
172where
173    T: TypedResourceData,
174{
175    fn visit(&mut self, name: &str, visitor: &mut Visitor) -> VisitResult {
176        // Untyped -> Typed compatibility. Useful in cases when a field was UntypedResource, and
177        // then it changed to the typed version. Strictly speaking, there's no real separation
178        // between typed and untyped resources on serialization/deserialization and this operation
179        // is valid until data types are matching.
180        if visitor.is_reading() {
181            let mut untyped = UntypedResource::default();
182            if untyped.visit(name, visitor).is_ok() {
183                let untyped_data_type = untyped.type_uuid();
184                if untyped_data_type == <T as TypeUuidProvider>::type_uuid() {
185                    self.untyped = untyped;
186                    return Ok(());
187                } else {
188                    Log::err(format!(
189                        "Unable to deserialize untyped resource into its typed \
190                     version, because types do not match! Untyped resource has \
191                     {untyped_data_type} type, but the required type is {}",
192                        <T as TypeUuidProvider>::type_uuid(),
193                    ))
194                }
195            }
196        }
197
198        let mut region = visitor.enter_region(name)?;
199
200        // Backward compatibility.
201        if region.is_reading() {
202            let mut old_option_wrapper: Option<UntypedResource> = None;
203            if old_option_wrapper.visit("State", &mut region).is_ok() {
204                self.untyped = old_option_wrapper.unwrap();
205            } else {
206                self.untyped.visit("State", &mut region)?;
207            }
208        } else {
209            self.untyped.visit("State", &mut region)?;
210        }
211
212        Ok(())
213    }
214}
215
216impl<T> PartialEq for Resource<T>
217where
218    T: TypedResourceData,
219{
220    fn eq(&self, other: &Self) -> bool {
221        self.untyped == other.untyped
222    }
223}
224
225impl<T> Eq for Resource<T> where T: TypedResourceData {}
226
227impl<T> Hash for Resource<T>
228where
229    T: TypedResourceData,
230{
231    fn hash<H: Hasher>(&self, state: &mut H) {
232        self.untyped.hash(state)
233    }
234}
235
236impl<T> Resource<T>
237where
238    T: TypedResourceData,
239{
240    /// Creates new resource in pending state.
241    #[inline]
242    pub fn new_pending(kind: ResourceKind) -> Self {
243        Self {
244            untyped: UntypedResource::new_pending(kind, <T as TypeUuidProvider>::type_uuid()),
245            phantom: PhantomData,
246        }
247    }
248
249    /// Creates new resource in ok state (fully loaded).
250    #[inline]
251    pub fn new_ok(kind: ResourceKind, data: T) -> Self {
252        Self {
253            untyped: UntypedResource::new_ok(kind, data),
254            phantom: PhantomData,
255        }
256    }
257
258    /// Creates new resource in error state.
259    #[inline]
260    pub fn new_load_error(kind: ResourceKind, error: LoadError) -> Self {
261        Self {
262            untyped: UntypedResource::new_load_error(
263                kind,
264                error,
265                <T as TypeUuidProvider>::type_uuid(),
266            ),
267            phantom: PhantomData,
268        }
269    }
270
271    /// Converts self to internal value.
272    #[inline]
273    pub fn into_untyped(self) -> UntypedResource {
274        self.untyped
275    }
276
277    /// Locks internal mutex provides access to the state.
278    #[inline]
279    pub fn state(&self) -> ResourceHeaderGuard<'_, T> {
280        let guard = self.untyped.0.lock();
281        ResourceHeaderGuard {
282            guard,
283            phantom: Default::default(),
284        }
285    }
286
287    /// Tries to lock internal mutex provides access to the state.
288    #[inline]
289    pub fn try_acquire_state(&self) -> Option<ResourceHeaderGuard<'_, T>> {
290        self.untyped.0.try_lock().map(|guard| ResourceHeaderGuard {
291            guard,
292            phantom: Default::default(),
293        })
294    }
295
296    #[inline]
297    pub fn header(&self) -> MutexGuard<'_, ResourceHeader> {
298        self.untyped.0.lock()
299    }
300
301    /// Returns true if the resource is still loading.
302    #[inline]
303    pub fn is_loading(&self) -> bool {
304        matches!(self.untyped.0.lock().state, ResourceState::Pending { .. })
305    }
306
307    /// Returns true if the resource is fully loaded and ready for use.
308    #[inline]
309    pub fn is_ok(&self) -> bool {
310        matches!(self.untyped.0.lock().state, ResourceState::Ok(_))
311    }
312
313    /// Returns true if the resource is failed to load.
314    #[inline]
315    pub fn is_failed_to_load(&self) -> bool {
316        matches!(self.untyped.0.lock().state, ResourceState::LoadError { .. })
317    }
318
319    /// Returns exact amount of users of the resource.
320    #[inline]
321    pub fn use_count(&self) -> usize {
322        self.untyped.use_count()
323    }
324
325    /// Returns a pointer as numeric value which can be used as a hash.
326    #[inline]
327    pub fn key(&self) -> u64 {
328        self.untyped.key() as u64
329    }
330
331    /// Returns kind of the resource.
332    #[inline]
333    pub fn kind(&self) -> ResourceKind {
334        self.untyped.kind()
335    }
336
337    /// Sets a new kind of the resource.
338    #[inline]
339    pub fn set_path(&mut self, new_kind: ResourceKind) {
340        self.untyped.set_kind(new_kind);
341    }
342
343    /// Allows you to obtain reference to the resource data.
344    ///
345    /// # Panic
346    ///
347    /// An attempt to use method result will panic if resource is not loaded yet, or
348    /// there was load error. Usually this is ok because normally you'd chain this call
349    /// like this `resource.await?.data_ref()`. Every resource implements Future trait
350    /// and it returns Result, so if you'll await future then you'll get Result, so
351    /// call to `data_ref` will be fine.
352    #[inline]
353    pub fn data_ref(&self) -> ResourceDataRef<'_, T> {
354        ResourceDataRef {
355            guard: self.untyped.0.lock(),
356            phantom: Default::default(),
357        }
358    }
359
360    /// Tries to save the resource to the specified path.
361    pub fn save(&self, path: &Path) -> Result<(), Box<dyn Error>> {
362        self.untyped.save(path)
363    }
364
365    /// Tries to save the resource back to its external location. This method will fail on attempt
366    /// to save embedded resource, because embedded resources does not have external location.
367    pub fn save_back(&self) -> Result<(), Box<dyn Error>> {
368        self.untyped.save_back()
369    }
370}
371
372impl<T> Default for Resource<T>
373where
374    T: TypedResourceData,
375{
376    #[inline]
377    fn default() -> Self {
378        Self {
379            untyped: UntypedResource::new_ok(Default::default(), T::default()),
380            phantom: Default::default(),
381        }
382    }
383}
384
385impl<T> Clone for Resource<T>
386where
387    T: TypedResourceData,
388{
389    #[inline]
390    fn clone(&self) -> Self {
391        Self {
392            untyped: self.untyped.clone(),
393            phantom: Default::default(),
394        }
395    }
396}
397
398impl<T> From<UntypedResource> for Resource<T>
399where
400    T: TypedResourceData,
401{
402    #[inline]
403    fn from(untyped: UntypedResource) -> Self {
404        assert_eq!(untyped.type_uuid(), <T as TypeUuidProvider>::type_uuid());
405        Self {
406            untyped,
407            phantom: Default::default(),
408        }
409    }
410}
411
412#[allow(clippy::from_over_into)]
413impl<T> Into<UntypedResource> for Resource<T>
414where
415    T: TypedResourceData,
416{
417    #[inline]
418    fn into(self) -> UntypedResource {
419        self.untyped
420    }
421}
422
423impl<T> Future for Resource<T>
424where
425    T: TypedResourceData,
426{
427    type Output = Result<Self, LoadError>;
428
429    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
430        let mut inner = self.untyped.clone();
431        Pin::new(&mut inner)
432            .poll(cx)
433            .map(|r| r.map(|_| self.clone()))
434    }
435}
436
437#[doc(hidden)]
438pub struct ResourceDataRef<'a, T>
439where
440    T: TypedResourceData,
441{
442    guard: MutexGuard<'a, ResourceHeader>,
443    phantom: PhantomData<T>,
444}
445
446impl<T> ResourceDataRef<'_, T>
447where
448    T: TypedResourceData,
449{
450    #[inline]
451    pub fn as_loaded_ref(&self) -> Option<&T> {
452        match self.guard.state {
453            ResourceState::Ok(ref data) => Downcast::as_any(&**data).downcast_ref(),
454            _ => None,
455        }
456    }
457
458    #[inline]
459    pub fn as_loaded_mut(&mut self) -> Option<&mut T> {
460        match self.guard.state {
461            ResourceState::Ok(ref mut data) => Downcast::as_any_mut(&mut **data).downcast_mut(),
462            _ => None,
463        }
464    }
465}
466
467impl<T> Debug for ResourceDataRef<'_, T>
468where
469    T: TypedResourceData,
470{
471    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
472        match self.guard.state {
473            ResourceState::Pending { .. } => {
474                write!(
475                    f,
476                    "Attempt to get reference to resource data while it is not loaded! Path is {}",
477                    self.guard.kind
478                )
479            }
480            ResourceState::LoadError { .. } => {
481                write!(
482                    f,
483                    "Attempt to get reference to resource data which failed to load! Path is {}",
484                    self.guard.kind
485                )
486            }
487            ResourceState::Ok(ref data) => data.fmt(f),
488        }
489    }
490}
491
492impl<T> Deref for ResourceDataRef<'_, T>
493where
494    T: TypedResourceData,
495{
496    type Target = T;
497
498    fn deref(&self) -> &Self::Target {
499        match self.guard.state {
500            ResourceState::Pending { .. } => {
501                panic!(
502                    "Attempt to get reference to resource data while it is not loaded! Path is {}",
503                    self.guard.kind
504                )
505            }
506            ResourceState::LoadError { .. } => {
507                panic!(
508                    "Attempt to get reference to resource data which failed to load! Path is {}",
509                    self.guard.kind
510                )
511            }
512            ResourceState::Ok(ref data) => Downcast::as_any(&**data)
513                .downcast_ref()
514                .expect("Type mismatch!"),
515        }
516    }
517}
518
519impl<T> DerefMut for ResourceDataRef<'_, T>
520where
521    T: TypedResourceData,
522{
523    fn deref_mut(&mut self) -> &mut Self::Target {
524        let header = &mut *self.guard;
525        match header.state {
526            ResourceState::Pending { .. } => {
527                panic!(
528                    "Attempt to get reference to resource data while it is not loaded! Path is {}",
529                    header.kind
530                )
531            }
532            ResourceState::LoadError { .. } => {
533                panic!(
534                    "Attempt to get reference to resource data which failed to load! Path is {}",
535                    header.kind
536                )
537            }
538            ResourceState::Ok(ref mut data) => Downcast::as_any_mut(&mut **data)
539                .downcast_mut()
540                .expect("Type mismatch!"),
541        }
542    }
543}
544
545/// Collects all resources used by a given entity. Internally, it uses reflection to iterate over
546/// each field of every descendant sub-object of the entity. This function could be used to collect
547/// all resources used by an object, which could be useful if you're building a resource dependency
548/// analyzer.
549pub fn collect_used_resources(
550    entity: &dyn Reflect,
551    resources_collection: &mut FxHashSet<UntypedResource>,
552) {
553    #[inline(always)]
554    fn type_is<T: Reflect>(entity: &dyn Reflect) -> bool {
555        let mut types_match = false;
556        entity.downcast_ref::<T>(&mut |v| {
557            types_match = v.is_some();
558        });
559        types_match
560    }
561
562    // Skip potentially large chunks of numeric data, that definitely cannot contain any resources.
563    // TODO: This is a brute-force solution which does not include all potential types with plain
564    // data.
565    let mut finished = type_is::<Vec<u8>>(entity)
566        || type_is::<Vec<u16>>(entity)
567        || type_is::<Vec<u32>>(entity)
568        || type_is::<Vec<u64>>(entity)
569        || type_is::<Vec<i8>>(entity)
570        || type_is::<Vec<i16>>(entity)
571        || type_is::<Vec<i32>>(entity)
572        || type_is::<Vec<i64>>(entity)
573        || type_is::<Vec<f32>>(entity)
574        || type_is::<Vec<f64>>(entity);
575
576    if finished {
577        return;
578    }
579
580    entity.downcast_ref::<UntypedResource>(&mut |v| {
581        if let Some(resource) = v {
582            resources_collection.insert(resource.clone());
583            finished = true;
584        }
585    });
586
587    if finished {
588        return;
589    }
590
591    entity.as_array(&mut |array| {
592        if let Some(array) = array {
593            for i in 0..array.reflect_len() {
594                if let Some(item) = array.reflect_index(i) {
595                    collect_used_resources(item, resources_collection)
596                }
597            }
598
599            finished = true;
600        }
601    });
602
603    if finished {
604        return;
605    }
606
607    entity.as_inheritable_variable(&mut |inheritable| {
608        if let Some(inheritable) = inheritable {
609            collect_used_resources(inheritable.inner_value_ref(), resources_collection);
610
611            finished = true;
612        }
613    });
614
615    if finished {
616        return;
617    }
618
619    entity.as_hash_map(&mut |hash_map| {
620        if let Some(hash_map) = hash_map {
621            for i in 0..hash_map.reflect_len() {
622                if let Some((key, value)) = hash_map.reflect_get_at(i) {
623                    collect_used_resources(key, resources_collection);
624                    collect_used_resources(value, resources_collection);
625                }
626            }
627
628            finished = true;
629        }
630    });
631
632    if finished {
633        return;
634    }
635
636    entity.fields(&mut |fields| {
637        for field in fields {
638            collect_used_resources(*field, resources_collection);
639        }
640    })
641}