fyrox_resource/
untyped.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//! A module for untyped resources. See [`UntypedResource`] docs for more info.
22
23use crate::{
24    core::{
25        math::curve::Curve, parking_lot::Mutex, reflect::prelude::*, uuid, uuid::Uuid,
26        visitor::prelude::*, visitor::RegionGuard, TypeUuidProvider,
27    },
28    manager::ResourceManager,
29    state::{LoadError, ResourceState},
30    Resource, ResourceData, ResourceLoadError, TypedResourceData, CURVE_RESOURCE_UUID,
31    MODEL_RESOURCE_UUID, SHADER_RESOURCE_UUID, SOUND_BUFFER_RESOURCE_UUID, TEXTURE_RESOURCE_UUID,
32};
33use std::{
34    error::Error,
35    ffi::OsStr,
36    fmt::{Debug, Display, Formatter},
37    future::Future,
38    hash::{Hash, Hasher},
39    marker::PhantomData,
40    path::{Path, PathBuf},
41    pin::Pin,
42    sync::Arc,
43    task::{Context, Poll},
44};
45
46// Heuristic function to guess resource uuid based on inner content of a resource.
47fn guess_uuid(region: &mut RegionGuard) -> Uuid {
48    assert!(region.is_reading());
49
50    let mut region = region.enter_region("Details").unwrap();
51
52    let mut mip_count = 0u32;
53    if mip_count.visit("MipCount", &mut region).is_ok() {
54        return TEXTURE_RESOURCE_UUID;
55    }
56
57    let mut curve = Curve::default();
58    if curve.visit("Curve", &mut region).is_ok() {
59        return CURVE_RESOURCE_UUID;
60    }
61
62    let mut id = 0u32;
63    if id.visit("Id", &mut region).is_ok() {
64        return SOUND_BUFFER_RESOURCE_UUID;
65    }
66
67    let mut path = PathBuf::new();
68    if path.visit("Path", &mut region).is_ok() {
69        let ext = path.extension().unwrap_or_default().to_ascii_lowercase();
70        if ext == OsStr::new("rgs") || ext == OsStr::new("fbx") {
71            return MODEL_RESOURCE_UUID;
72        } else if ext == OsStr::new("shader")
73            || path == OsStr::new("Standard")
74            || path == OsStr::new("StandardTwoSides")
75            || path == OsStr::new("StandardTerrain")
76        {
77            return SHADER_RESOURCE_UUID;
78        }
79    }
80
81    Default::default()
82}
83
84/// Kind of a resource. It defines how the resource manager will treat a resource content on serialization.
85#[derive(Default, Reflect, Debug, Visit, Clone, PartialEq, Eq, Hash)]
86pub enum ResourceKind {
87    /// The content of embedded resources will be fully serialized.
88    #[default]
89    Embedded,
90    /// The content of external resources will not be serialized, instead only the path to the content
91    /// will be serialized and the content will be loaded from it when needed.
92    ///
93    /// ## Built-in Resources
94    ///
95    /// This resource kind could also be used to create built-in resources (the data of which is
96    /// embedded directly in the executable using [`include_bytes`] macro). All that is needed is to
97    /// create a static resource variable and register it in built-in resources of the resource manager.
98    /// In this case, the path becomes an identifier and it must be unique. See [`ResourceManager`] docs
99    /// for more info about built-in resources.
100    External(PathBuf),
101}
102
103impl From<Option<PathBuf>> for ResourceKind {
104    fn from(value: Option<PathBuf>) -> Self {
105        match value {
106            None => Self::Embedded,
107            Some(path) => Self::External(path),
108        }
109    }
110}
111
112impl From<PathBuf> for ResourceKind {
113    fn from(value: PathBuf) -> Self {
114        Self::External(value)
115    }
116}
117
118impl<'a> From<&'a str> for ResourceKind {
119    fn from(value: &'a str) -> Self {
120        Self::External(value.into())
121    }
122}
123
124impl ResourceKind {
125    /// Switches the resource kind to [`Self::External`].
126    #[inline]
127    pub fn make_external(&mut self, path: PathBuf) {
128        *self = ResourceKind::External(path);
129    }
130
131    /// Switches the resource kind to [`Self::Embedded`]
132    #[inline]
133    pub fn make_embedded(&mut self) {
134        *self = ResourceKind::Embedded;
135    }
136
137    /// Checks, if the resource kind is [`Self::Embedded`]
138    #[inline]
139    pub fn is_embedded(&self) -> bool {
140        matches!(self, Self::Embedded)
141    }
142
143    /// Checks, if the resource kind is [`Self::External`]
144    #[inline]
145    pub fn is_external(&self) -> bool {
146        !self.is_embedded()
147    }
148
149    /// Tries to fetch a resource path, returns [`None`] for [`Self::Embedded`] resources.
150    #[inline]
151    pub fn path(&self) -> Option<&Path> {
152        match self {
153            ResourceKind::Embedded => None,
154            ResourceKind::External(path) => Some(path),
155        }
156    }
157
158    /// Tries to fetch a resource path, returns [`None`] for [`Self::Embedded`] resources.
159    #[inline]
160    pub fn path_owned(&self) -> Option<PathBuf> {
161        self.path().map(|p| p.to_path_buf())
162    }
163
164    /// Tries to convert the resource kind into its path, returns [`None`] for [`Self::Embedded`]
165    /// resources.
166    #[inline]
167    pub fn into_path(self) -> Option<PathBuf> {
168        match self {
169            ResourceKind::Embedded => None,
170            ResourceKind::External(path) => Some(path),
171        }
172    }
173}
174
175impl Display for ResourceKind {
176    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
177        match self {
178            ResourceKind::Embedded => {
179                write!(f, "Embedded")
180            }
181            ResourceKind::External(path) => {
182                write!(f, "External ({})", path.display())
183            }
184        }
185    }
186}
187
188/// Header of a resource, it contains a common data about the resource, such as its data type uuid,
189/// its kind, etc.
190#[derive(Reflect, Debug)]
191pub struct ResourceHeader {
192    /// UUID of the internal data type.
193    pub type_uuid: Uuid,
194    /// Kind of the resource. See [`ResourceKind`] for more info.
195    pub kind: ResourceKind,
196    /// Actual state of the resource. See [`ResourceState`] for more info.
197    pub state: ResourceState,
198}
199
200impl Visit for ResourceHeader {
201    fn visit(&mut self, name: &str, visitor: &mut Visitor) -> VisitResult {
202        let mut region = visitor.enter_region(name)?;
203
204        if region.is_reading() {
205            let mut id: u32 = 0;
206
207            if id.visit("Id", &mut region).is_ok() {
208                // Reading old version, convert it to the new.
209
210                let mut type_uuid = Uuid::default();
211                if type_uuid.visit("TypeUuid", &mut region).is_err() {
212                    // We might be reading the old version, try to guess an actual type uuid by
213                    // the inner content of the resource data.
214                    type_uuid = guess_uuid(&mut region);
215                };
216
217                // We're interested only in embedded resources.
218                if id == 2 {
219                    let resource_manager = region.blackboard.get::<ResourceManager>().expect(
220                        "Resource data constructor container must be \
221                provided when serializing resources!",
222                    );
223                    let resource_manager_state = resource_manager.state();
224
225                    if let Some(mut instance) = resource_manager_state
226                        .constructors_container
227                        .try_create(&type_uuid)
228                    {
229                        drop(resource_manager_state);
230
231                        if let Ok(mut details_region) = region.enter_region("Details") {
232                            if type_uuid == SOUND_BUFFER_RESOURCE_UUID {
233                                let mut sound_region = details_region.enter_region("0")?;
234                                let mut path = PathBuf::new();
235                                path.visit("Path", &mut sound_region).unwrap();
236                                self.kind.make_external(path);
237                            } else {
238                                let mut path = PathBuf::new();
239                                path.visit("Path", &mut details_region).unwrap();
240                                self.kind.make_external(path);
241                            }
242                        }
243
244                        instance.visit("Details", &mut region)?;
245
246                        self.state = ResourceState::Ok(instance);
247
248                        return Ok(());
249                    } else {
250                        return Err(VisitError::User(format!(
251                            "There's no constructor registered for type {type_uuid}!"
252                        )));
253                    }
254                } else {
255                    self.state = ResourceState::LoadError {
256                        error: LoadError::new("Old resource"),
257                    };
258                }
259
260                return Ok(());
261            }
262        }
263
264        self.kind.visit("Kind", &mut region)?;
265        self.type_uuid.visit("TypeUuid", &mut region)?;
266
267        if self.kind == ResourceKind::Embedded {
268            self.state.visit("State", &mut region)?;
269        }
270
271        Ok(())
272    }
273}
274
275/// Untyped resource is a universal way of storing arbitrary resource types. Internally it wraps
276/// [`ResourceState`] in a `Arc<Mutex<>` so the untyped resource becomes shareable. In most of the
277/// cases you don't need to deal with untyped resources, use typed [`Resource`] wrapper instead.
278/// Untyped resource could be useful in cases when you need to collect a set of resources of different
279/// types in a single collection and do something with them.
280///
281/// ## Handle
282///
283/// Since untyped resources stores the actual data in a shared storage, the resource instance could
284/// be considered as a handle. Such "handles" have special behaviour on serialization and
285/// deserialization to keep pointing to the same storage.
286///
287/// ## Serialization and Deserialization
288///
289/// Every resource writes its own kind, type uuid of the data and optionally the data itself.
290///
291/// Serialization/deserialization of the data is different depending on the actual resource kind
292/// (see [`ResourceKind`]):
293///
294/// 1) [`ResourceKind::Embedded`] - the resource data will be serialized together with the resource
295/// handle. The data will be loaded back on deserialization stage from the backing storage.
296/// 2) [`ResourceKind::External`] - the resource data won't be serialized and will be reloaded from
297/// the external source.
298///
299/// When the resource is deserialized, the resource system at first looks for an already loaded
300/// resource with the same kind and if it is found, replaces current instance with the loaded one.
301/// If not - loads the resource and also replaces the instance. This step is crucial for uniqueness
302/// of the resource handles.
303///
304/// To put everything simple: when you save a resource handle, it writes only path to it, then when
305/// you load it you need to make sure that all references to a resource points to the same resource
306/// instance.
307///
308/// ## Default state
309///
310/// Default state of every untyped resource is [`ResourceState::LoadError`] with a warning message,
311/// that the resource is in default state. This is a trade-off to prevent wrapping internals into
312/// `Option`, that in some cases could lead to convoluted code with lots of `unwrap`s and state
313/// assumptions.
314#[derive(Clone, Reflect, TypeUuidProvider)]
315#[type_uuid(id = "21613484-7145-4d1c-87d8-62fa767560ab")]
316pub struct UntypedResource(pub Arc<Mutex<ResourceHeader>>);
317
318impl Visit for UntypedResource {
319    fn visit(&mut self, name: &str, visitor: &mut Visitor) -> VisitResult {
320        self.0.visit(name, visitor)?;
321
322        // Try to restore the shallow handle on deserialization for external resources.
323        if visitor.is_reading() && !self.is_embedded() {
324            let resource_manager = visitor
325                .blackboard
326                .get::<ResourceManager>()
327                .expect("Resource manager must be available when deserializing resources!");
328
329            let path = self.kind().path_owned().unwrap();
330            self.0 = resource_manager.request_untyped(path).0;
331        }
332
333        Ok(())
334    }
335}
336
337impl Default for UntypedResource {
338    fn default() -> Self {
339        Self(Arc::new(Mutex::new(ResourceHeader {
340            kind: Default::default(),
341            type_uuid: Default::default(),
342            state: ResourceState::new_load_error(LoadError::new(
343                "Default resource state of unknown type.",
344            )),
345        })))
346    }
347}
348
349impl Debug for UntypedResource {
350    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
351        writeln!(f, "Resource")
352    }
353}
354
355impl PartialEq for UntypedResource {
356    fn eq(&self, other: &Self) -> bool {
357        std::ptr::eq(&*self.0, &*other.0)
358    }
359}
360
361impl Eq for UntypedResource {}
362
363impl Hash for UntypedResource {
364    fn hash<H: Hasher>(&self, state: &mut H) {
365        state.write_u64(&*self.0 as *const _ as u64)
366    }
367}
368
369impl UntypedResource {
370    /// Creates new untyped resource in pending state using the given path and type uuid.
371    pub fn new_pending(kind: ResourceKind, type_uuid: Uuid) -> Self {
372        Self(Arc::new(Mutex::new(ResourceHeader {
373            kind,
374            type_uuid,
375            state: ResourceState::new_pending(),
376        })))
377    }
378
379    /// Creates new untyped resource in ok (fully loaded) state using the given data of any type, that
380    /// implements [`ResourceData`] trait.
381    pub fn new_ok<T>(kind: ResourceKind, data: T) -> Self
382    where
383        T: ResourceData,
384    {
385        Self(Arc::new(Mutex::new(ResourceHeader {
386            kind,
387            type_uuid: data.type_uuid(),
388            state: ResourceState::new_ok(data),
389        })))
390    }
391
392    /// Creates new untyped resource in error state.
393    pub fn new_load_error(kind: ResourceKind, error: LoadError, type_uuid: Uuid) -> Self {
394        Self(Arc::new(Mutex::new(ResourceHeader {
395            kind,
396            type_uuid,
397            state: ResourceState::new_load_error(error),
398        })))
399    }
400
401    /// Returns actual unique type id of underlying resource data.
402    pub fn type_uuid(&self) -> Uuid {
403        self.0.lock().type_uuid
404    }
405
406    /// Returns true if the resource is still loading.
407    pub fn is_loading(&self) -> bool {
408        matches!(self.0.lock().state, ResourceState::Pending { .. })
409    }
410
411    /// Returns true if the resource is procedural (its data is generated at runtime, not stored in an external
412    /// file).
413    pub fn is_embedded(&self) -> bool {
414        self.0.lock().kind.is_embedded()
415    }
416
417    /// Returns exact amount of users of the resource.
418    #[inline]
419    pub fn use_count(&self) -> usize {
420        Arc::strong_count(&self.0)
421    }
422
423    /// Returns a pointer as numeric value which can be used as a hash.
424    #[inline]
425    pub fn key(&self) -> usize {
426        (&*self.0 as *const _) as usize
427    }
428
429    /// Returns path of the untyped resource.
430    pub fn kind(&self) -> ResourceKind {
431        self.0.lock().kind.clone()
432    }
433
434    /// Set a new path for the untyped resource.
435    pub fn set_kind(&self, new_kind: ResourceKind) {
436        self.0.lock().kind = new_kind;
437    }
438
439    /// Tries to save the resource to the specified path.
440    pub fn save(&self, path: &Path) -> Result<(), Box<dyn Error>> {
441        let mut guard = self.0.lock();
442        match guard.state {
443            ResourceState::Pending { .. } | ResourceState::LoadError { .. } => {
444                Err("Unable to save unloaded resource!".into())
445            }
446            ResourceState::Ok(ref mut data) => data.save(path),
447        }
448    }
449
450    /// Tries to save the resource back to its external location. This method will fail on attempt
451    /// to save embedded resource, because embedded resources does not have external location.
452    pub fn save_back(&self) -> Result<(), Box<dyn Error>> {
453        match self.kind() {
454            ResourceKind::Embedded => Err("Embedded resource cannot be saved!".into()),
455            ResourceKind::External(path) => self.save(&path),
456        }
457    }
458
459    /// Tries to cast untyped resource to a particular type.
460    pub fn try_cast<T>(&self) -> Option<Resource<T>>
461    where
462        T: TypedResourceData,
463    {
464        if self.type_uuid() == <T as TypeUuidProvider>::type_uuid() {
465            Some(Resource {
466                untyped: self.clone(),
467                phantom: PhantomData::<T>,
468            })
469        } else {
470            None
471        }
472    }
473
474    /// Changes ResourceState::Pending state to ResourceState::Ok(data) with given `data`.
475    /// Additionally, it wakes all futures.
476    #[inline]
477    pub fn commit(&self, state: ResourceState) {
478        self.0.lock().state.commit(state);
479    }
480
481    /// Changes internal state to [`ResourceState::Ok`]
482    pub fn commit_ok<T: ResourceData>(&self, data: T) {
483        let mut guard = self.0.lock();
484        guard.type_uuid = data.type_uuid();
485        guard.state.commit_ok(data);
486    }
487
488    /// Changes internal state to [`ResourceState::LoadError`].
489    pub fn commit_error<E: ResourceLoadError>(&self, error: E) {
490        self.0.lock().state.commit_error(error);
491    }
492}
493
494impl Future for UntypedResource {
495    type Output = Result<Self, LoadError>;
496
497    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
498        let state = self.0.clone();
499        let mut guard = state.lock();
500        match guard.state {
501            ResourceState::Pending { ref mut wakers, .. } => {
502                // Collect wakers, so we'll be able to wake task when worker thread finish loading.
503                let cx_waker = cx.waker();
504                if let Some(pos) = wakers.iter().position(|waker| waker.will_wake(cx_waker)) {
505                    wakers[pos].clone_from(cx_waker);
506                } else {
507                    wakers.push(cx_waker.clone())
508                }
509
510                Poll::Pending
511            }
512            ResourceState::LoadError { ref error, .. } => Poll::Ready(Err(error.clone())),
513            ResourceState::Ok(_) => Poll::Ready(Ok(self.clone())),
514        }
515    }
516}
517
518#[cfg(test)]
519mod test {
520    use futures::task::noop_waker;
521    use fyrox_core::futures;
522    use std::error::Error;
523    use std::task::{self};
524
525    use super::*;
526
527    #[derive(Debug, Default, Reflect, Visit, Clone, Copy)]
528    struct Stub {}
529
530    impl ResourceData for Stub {
531        fn type_uuid(&self) -> Uuid {
532            Uuid::default()
533        }
534
535        fn save(&mut self, _path: &Path) -> Result<(), Box<dyn Error>> {
536            Err("Saving is not supported!".to_string().into())
537        }
538
539        fn can_be_saved(&self) -> bool {
540            false
541        }
542    }
543
544    impl TypeUuidProvider for Stub {
545        fn type_uuid() -> Uuid {
546            Uuid::default()
547        }
548    }
549
550    impl ResourceLoadError for str {}
551
552    #[test]
553    fn visit_for_untyped_resource() {
554        let mut r = UntypedResource::default();
555        let mut visitor = Visitor::default();
556
557        assert!(r.visit("name", &mut visitor).is_ok());
558    }
559
560    #[test]
561    fn debug_for_untyped_resource() {
562        let r = UntypedResource::default();
563
564        assert_eq!(format!("{r:?}"), "Resource\n");
565    }
566
567    #[test]
568    fn untyped_resource_new_pending() {
569        let r = UntypedResource::new_pending(PathBuf::from("/foo").into(), Uuid::default());
570
571        assert_eq!(r.0.lock().type_uuid, Uuid::default());
572        assert_eq!(
573            r.0.lock().kind,
574            ResourceKind::External(PathBuf::from("/foo"))
575        );
576    }
577
578    #[test]
579    fn untyped_resource_new_load_error() {
580        let r = UntypedResource::new_load_error(
581            PathBuf::from("/foo").into(),
582            Default::default(),
583            Uuid::default(),
584        );
585
586        assert_eq!(r.0.lock().type_uuid, Uuid::default());
587        assert_eq!(
588            r.0.lock().kind,
589            ResourceKind::External(PathBuf::from("/foo"))
590        );
591    }
592
593    #[test]
594    fn untyped_resource_use_count() {
595        let r = UntypedResource::default();
596
597        assert_eq!(r.use_count(), 1);
598    }
599
600    #[test]
601    fn untyped_resource_try_cast() {
602        let r = UntypedResource::default();
603        let r2 = UntypedResource::new_pending(
604            PathBuf::from("/foo").into(),
605            Uuid::from_u128(0xa1a2a3a4b1b2c1c2d1d2d3d4d5d6d7d8u128),
606        );
607
608        assert!(r.try_cast::<Stub>().is_some());
609        assert!(r2.try_cast::<Stub>().is_none());
610    }
611
612    #[test]
613    fn untyped_resource_commit() {
614        let path = PathBuf::from("/foo");
615        let stub = Stub {};
616
617        let r = UntypedResource::new_pending(path.clone().into(), Default::default());
618        assert_eq!(r.0.lock().kind, ResourceKind::External(path.clone()));
619
620        r.commit(ResourceState::Ok(Box::new(stub)));
621        assert_eq!(r.0.lock().kind, ResourceKind::External(path));
622    }
623
624    #[test]
625    fn untyped_resource_commit_ok() {
626        let path = PathBuf::from("/foo");
627        let stub = Stub {};
628
629        let r = UntypedResource::new_pending(path.clone().into(), Default::default());
630        assert_eq!(r.0.lock().kind, ResourceKind::External(path.clone()));
631
632        r.commit_ok(stub);
633        assert_eq!(r.0.lock().kind, ResourceKind::External(path));
634    }
635
636    #[test]
637    fn untyped_resource_commit_error() {
638        let path = PathBuf::from("/foo");
639        let path2 = PathBuf::from("/bar");
640
641        let r = UntypedResource::new_pending(path.clone().into(), Default::default());
642        assert_eq!(r.0.lock().kind, ResourceKind::External(path));
643        assert_ne!(r.0.lock().kind, ResourceKind::External(path2));
644    }
645
646    #[test]
647    fn untyped_resource_poll() {
648        let path = PathBuf::from("/foo");
649        let stub = Stub {};
650
651        let waker = noop_waker();
652        let mut cx = task::Context::from_waker(&waker);
653
654        let mut r = UntypedResource(Arc::new(Mutex::new(ResourceHeader {
655            kind: path.clone().into(),
656            type_uuid: Uuid::default(),
657            state: ResourceState::Ok(Box::new(stub)),
658        })));
659        assert!(Pin::new(&mut r).poll(&mut cx).is_ready());
660
661        let mut r = UntypedResource(Arc::new(Mutex::new(ResourceHeader {
662            kind: path.clone().into(),
663            type_uuid: Uuid::default(),
664            state: ResourceState::LoadError {
665                error: Default::default(),
666            },
667        })));
668        assert!(Pin::new(&mut r).poll(&mut cx).is_ready());
669    }
670}