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::state::ResourceDataWrapper;
24use crate::{
25    core::{
26        math::curve::Curve, parking_lot::Mutex, reflect::prelude::*, uuid, uuid::Uuid,
27        visitor::prelude::*, TypeUuidProvider,
28    },
29    manager::ResourceManager,
30    state::{LoadError, ResourceState},
31    Resource, ResourceData, ResourceLoadError, TypedResourceData, CURVE_RESOURCE_UUID,
32    MODEL_RESOURCE_UUID, SHADER_RESOURCE_UUID, SOUND_BUFFER_RESOURCE_UUID, TEXTURE_RESOURCE_UUID,
33};
34use crate::{ResourceHeaderGuard, FONT_RESOURCE_UUID, HRIR_SPHERE_RESOURCE_UUID};
35use fxhash::FxHasher64;
36use fyrox_core::io::FileError;
37use fyrox_core::log::Log;
38use fyrox_core::parking_lot::MutexGuard;
39use fyrox_core::SafeLock;
40use serde::{Deserialize, Serialize};
41use std::fmt::Write;
42use std::{
43    error::Error,
44    ffi::OsStr,
45    fmt::{Debug, Display, Formatter},
46    future::Future,
47    hash::{Hash, Hasher},
48    marker::PhantomData,
49    path::{Path, PathBuf},
50    pin::Pin,
51    sync::Arc,
52    task::{Context, Poll},
53};
54
55const MISSING_RESOURCE_MANAGER: &str =
56    "Resource data constructor container must be provided when serializing resources!";
57
58/// The UUIDs for resources that cannot be visited as embedded data because none of their
59/// fields are visited. This is used by `LegacyHeader` to avoid mistakenly interpretting
60/// a visitor node as an embedded resource.
61/// Being on this list does *not* prevent future versions of these resources from being
62/// embedded. It is purely a heuristic to assist with reading legacy resources.
63const INVALID_EMBEDDED_RESOURCES: &[Uuid] = &[
64    SOUND_BUFFER_RESOURCE_UUID,
65    MODEL_RESOURCE_UUID,
66    HRIR_SPHERE_RESOURCE_UUID,
67    FONT_RESOURCE_UUID,
68];
69
70#[derive(Default, Debug, Visit, Clone, PartialEq, Eq, Hash)]
71enum OldResourceKind {
72    #[default]
73    Embedded,
74    External(PathBuf),
75}
76
77#[derive(Debug)]
78enum LegacyHeader {
79    Path(PathBuf),
80    Uuid(Uuid),
81    Data(Box<dyn ResourceData>),
82}
83
84impl LegacyHeader {
85    fn visit_path(&mut self, name: &str, visitor: &mut Visitor) -> VisitResult {
86        let mut path = PathBuf::default();
87        path.visit(name, visitor)?;
88        if path.as_os_str().is_empty() {
89            return Err(VisitError::FileLoadError(FileError::Custom(
90                "Empty path".to_string(),
91            )));
92        }
93        *self = Self::Path(path);
94        Ok(())
95    }
96    fn visit_details(&mut self, type_uuid: Uuid, visitor: &mut Visitor) -> VisitResult {
97        let mut region = visitor.enter_region("Details")?;
98        if type_uuid == SOUND_BUFFER_RESOURCE_UUID {
99            let mut sound_region = region.enter_region("0")?;
100            self.visit_path("Path", &mut sound_region)
101        } else {
102            self.visit_path("Path", &mut region)
103        }
104    }
105    fn take_data(&mut self, uuid: Uuid) -> Option<Box<dyn ResourceData>> {
106        if let Self::Data(data) = std::mem::replace(self, Self::Uuid(uuid)) {
107            Some(data)
108        } else {
109            None
110        }
111    }
112    fn is_valid_embedded_type_uuid(uuid: Uuid) -> bool {
113        INVALID_EMBEDDED_RESOURCES.contains(&uuid)
114    }
115}
116
117impl Default for LegacyHeader {
118    fn default() -> Self {
119        Self::Path(PathBuf::default())
120    }
121}
122
123impl Visit for LegacyHeader {
124    fn visit(&mut self, name: &str, visitor: &mut Visitor) -> VisitResult {
125        assert!(visitor.is_reading());
126        let mut region = visitor.enter_region(name)?;
127
128        let mut type_uuid = Uuid::default();
129        if type_uuid.visit("TypeUuid", &mut region).is_err() {
130            // We might be reading the old version, try to guess an actual type uuid by
131            // the inner content of the resource data.
132            type_uuid = guess_uuid(&mut region);
133        };
134        let resource_manager = region
135            .blackboard
136            .get::<ResourceManager>()
137            .expect(MISSING_RESOURCE_MANAGER);
138        let resource_manager_state = resource_manager.state();
139        let Some(mut instance) = resource_manager_state
140            .constructors_container
141            .try_create(&type_uuid)
142        else {
143            return Err(VisitError::User(format!(
144                "There's no constructor registered for type {type_uuid}!"
145            )));
146        };
147        drop(resource_manager_state);
148
149        let mut id: u32 = 0;
150
151        if id.visit("Id", &mut region).is_ok() {
152            // We're interested only in embedded resources.
153            if id == 2 {
154                let result = self.visit_details(type_uuid, &mut region);
155                if let Err(err0) = result {
156                    if Self::is_valid_embedded_type_uuid(type_uuid) {
157                        let result = instance.visit("Details", &mut region);
158                        if let Err(err1) = result {
159                            let result = instance.visit("State", &mut region);
160                            if let Err(err2) = result {
161                                return Err(err0.multiple(err1).multiple(err2));
162                            }
163                        }
164                        *self = Self::Data(instance);
165                        Ok(())
166                    } else {
167                        Err(err0)
168                    }
169                } else {
170                    result
171                }
172            } else {
173                Err(VisitError::User("Old resource".into()))
174            }
175        } else {
176            let mut uuid = Uuid::default();
177            if uuid.visit("ResourceUuid", &mut region).is_ok() && !uuid.is_nil() {
178                *self = Self::Uuid(uuid);
179                return Ok(());
180            }
181            let mut old_kind = OldResourceKind::Embedded;
182            old_kind.visit("Kind", &mut region)?;
183            match old_kind {
184                OldResourceKind::External(path) => {
185                    if path.as_os_str().is_empty() {
186                        return Err(VisitError::FileLoadError(FileError::Custom(
187                            "Empty path".to_string(),
188                        )));
189                    }
190                    *self = Self::Path(path);
191                }
192                OldResourceKind::Embedded => {
193                    instance.visit("State", &mut region)?;
194                    *self = Self::Data(instance);
195                }
196            }
197            Ok(())
198        }
199    }
200}
201
202// Heuristic function to guess resource uuid based on inner content of a resource.
203fn guess_uuid(region: &mut Visitor) -> Uuid {
204    assert!(region.is_reading());
205
206    let guard = region.enter_region("Details");
207    let mut region = match guard {
208        Ok(region) => region,
209        Err(ref err) => {
210            Log::err(err.to_string());
211            drop(guard);
212            Log::info(region.debug());
213            return TEXTURE_RESOURCE_UUID;
214        }
215    };
216
217    let mut mip_count = 0u32;
218    if mip_count.visit("MipCount", &mut region).is_ok() {
219        return TEXTURE_RESOURCE_UUID;
220    }
221
222    let mut curve = Curve::default();
223    if curve.visit("Curve", &mut region).is_ok() {
224        return CURVE_RESOURCE_UUID;
225    }
226
227    let mut id = 0u32;
228    if id.visit("Id", &mut region).is_ok() {
229        return SOUND_BUFFER_RESOURCE_UUID;
230    }
231
232    let mut path = PathBuf::new();
233    if path.visit("Path", &mut region).is_ok() {
234        let ext = path.extension().unwrap_or_default().to_ascii_lowercase();
235        if ext == OsStr::new("rgs")
236            || ext == OsStr::new("fbx")
237            || ext == OsStr::new("gltf")
238            || ext == OsStr::new("glb")
239        {
240            return MODEL_RESOURCE_UUID;
241        } else if ext == OsStr::new("shader")
242            || path == OsStr::new("Standard")
243            || path == OsStr::new("StandardTwoSides")
244            || path == OsStr::new("StandardTerrain")
245        {
246            return SHADER_RESOURCE_UUID;
247        }
248    }
249
250    Default::default()
251}
252
253/// Kind of a resource. It defines how the resource manager will treat a resource content on serialization.
254#[derive(Default, Reflect, Debug, Visit, Copy, Clone, PartialEq, Eq, Hash)]
255pub enum ResourceKind {
256    /// The content of embedded resources will be fully serialized.
257    #[default]
258    Embedded,
259    /// The content of external resources will not be serialized, instead only the path to the content
260    /// will be serialized and the content will be loaded from it when needed.
261    ///
262    /// ## Built-in Resources
263    ///
264    /// This resource kind could also be used to create built-in resources (the data of which is
265    /// embedded directly in the executable using [`include_bytes`] macro). All that is needed is to
266    /// create a static resource variable and register it in built-in resources of the resource manager.
267    /// In this case, the path becomes an identifier and it must be unique. See [`ResourceManager`] docs
268    /// for more info about built-in resources.
269    External,
270}
271
272impl ResourceKind {
273    /// Switches the resource kind to [`Self::External`].
274    #[inline]
275    pub fn make_external(&mut self) {
276        *self = ResourceKind::External;
277    }
278
279    /// Switches the resource kind to [`Self::Embedded`]
280    #[inline]
281    pub fn make_embedded(&mut self) {
282        *self = ResourceKind::Embedded;
283    }
284
285    /// Checks, if the resource kind is [`Self::Embedded`]
286    #[inline]
287    pub fn is_embedded(&self) -> bool {
288        matches!(self, Self::Embedded)
289    }
290
291    /// Checks, if the resource kind is [`Self::External`]
292    #[inline]
293    pub fn is_external(&self) -> bool {
294        !self.is_embedded()
295    }
296}
297
298impl Display for ResourceKind {
299    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
300        match self {
301            ResourceKind::Embedded => {
302                write!(f, "Embedded")
303            }
304            ResourceKind::External => {
305                write!(f, "External")
306            }
307        }
308    }
309}
310
311/// Header of a resource, it contains a common data about the resource, such as its data type uuid,
312/// its kind, etc.
313#[derive(Reflect, Clone, Debug)]
314pub struct ResourceHeader {
315    /// The unique identifier of this resource.
316    pub uuid: Uuid,
317    /// Kind of the resource. See [`ResourceKind`] for more info.
318    pub kind: ResourceKind,
319    /// Actual state of the resource. See [`ResourceState`] for more info.
320    pub state: ResourceState,
321}
322
323impl Default for ResourceHeader {
324    fn default() -> Self {
325        Self {
326            uuid: Uuid::new_v4(),
327            kind: Default::default(),
328            state: Default::default(),
329        }
330    }
331}
332
333impl From<Uuid> for ResourceHeader {
334    fn from(uuid: Uuid) -> Self {
335        Self {
336            uuid,
337            kind: ResourceKind::External,
338            state: ResourceState::Unloaded,
339        }
340    }
341}
342
343impl ResourceHeader {
344    /// The type of the data, if this resource is Ok.
345    pub fn type_uuid(&self) -> Option<Uuid> {
346        if let ResourceState::Ok { data } = &self.state {
347            Some(data.type_uuid())
348        } else {
349            None
350        }
351    }
352}
353
354impl Display for ResourceHeader {
355    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
356        Display::fmt(&self.uuid, f)?;
357        f.write_char(':')?;
358        match self.kind {
359            ResourceKind::Embedded => f.write_str("Embed")?,
360            ResourceKind::External => f.write_str("Extern")?,
361        }
362        f.write_char(':')?;
363        match &self.state {
364            ResourceState::Unloaded => f.write_str("Unloaded"),
365            ResourceState::Pending { .. } => f.write_str("Pending"),
366            ResourceState::LoadError { path, error } => write!(f, "Error({path:?}, {error})"),
367            ResourceState::Ok { .. } => f.write_str("Ok"),
368        }
369    }
370}
371
372impl Visit for ResourceHeader {
373    fn visit(&mut self, name: &str, visitor: &mut Visitor) -> VisitResult {
374        let mut region = visitor.enter_region(name)?;
375        if region.is_reading() {
376            self.kind = ResourceKind::Embedded;
377            let mut actual_type_uuid = Uuid::default();
378            actual_type_uuid.visit("TypeUuid", &mut region)?;
379            let resource_manager = region
380                .blackboard
381                .get::<ResourceManager>()
382                .expect(MISSING_RESOURCE_MANAGER)
383                .clone();
384            let Some(mut data) = resource_manager
385                .state()
386                .constructors_container
387                .try_create(&actual_type_uuid)
388            else {
389                return Err(VisitError::User(format!(
390                    "There's no constructor registered for type {actual_type_uuid}!"
391                )));
392            };
393            data.visit("Data", &mut region)?;
394            self.state = ResourceState::Ok {
395                data: ResourceDataWrapper(data),
396            };
397            Ok(())
398        } else {
399            match (&self.kind, &mut self.state) {
400                (ResourceKind::Embedded, ResourceState::Ok { data }) => {
401                    let mut type_uuid = data.type_uuid();
402                    type_uuid.visit("TypeUuid", &mut region)?;
403                    data.visit("Data", &mut region)
404                }
405                (ResourceKind::External, _) => {
406                    Err(VisitError::User("Writing an external resource".into()))
407                }
408                _ => Err(VisitError::User(
409                    "Writing an embedded resource that is not ok.".into(),
410                )),
411            }
412        }
413    }
414}
415
416/// Untyped resource is a universal way of storing arbitrary resource types. Internally it wraps
417/// [`ResourceState`] in a `Arc<Mutex<>` so the untyped resource becomes shareable. In most of the
418/// cases you don't need to deal with untyped resources, use typed [`Resource`] wrapper instead.
419/// Untyped resource could be useful in cases when you need to collect a set of resources of different
420/// types in a single collection and do something with them.
421///
422/// ## Handle
423///
424/// Since untyped resources stores the actual data in a shared storage, the resource instance could
425/// be considered as a handle. Such "handles" have special behaviour on serialization and
426/// deserialization to keep pointing to the same storage.
427///
428/// ## Serialization and Deserialization
429///
430/// Every resource writes its own kind, type uuid of the data and optionally the data itself.
431///
432/// Serialization/deserialization of the data is different depending on the actual resource kind
433/// (see [`ResourceKind`]):
434///
435/// 1) [`ResourceKind::Embedded`] - the resource data will be serialized together with the resource
436/// handle. The data will be loaded back on deserialization stage from the backing storage.
437/// 2) [`ResourceKind::External`] - the resource data won't be serialized and will be reloaded from
438/// the external source.
439///
440/// When the resource is deserialized, the resource system at first looks for an already loaded
441/// resource with the same kind and if it is found, replaces current instance with the loaded one.
442/// If not - loads the resource and also replaces the instance. This step is crucial for uniqueness
443/// of the resource handles.
444///
445/// To put everything simple: when you save a resource handle, it writes only path to it, then when
446/// you load it you need to make sure that all references to a resource points to the same resource
447/// instance.
448///
449/// ## Default state
450///
451/// Default state of every untyped resource is [`ResourceState::LoadError`] with a warning message,
452/// that the resource is in default state. This is a trade-off to prevent wrapping internals into
453/// `Option`, that in some cases could lead to convoluted code with lots of `unwrap`s and state
454/// assumptions.
455#[derive(Default, Clone, Reflect, TypeUuidProvider, Deserialize)]
456#[serde(from = "Uuid")]
457#[type_uuid(id = "21613484-7145-4d1c-87d8-62fa767560ab")]
458pub struct UntypedResource(pub Arc<Mutex<ResourceHeader>>);
459
460impl Serialize for UntypedResource {
461    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
462    where
463        S: serde::Serializer,
464    {
465        let header = self.lock();
466        if header.kind == ResourceKind::Embedded {
467            panic!("Embedded resources cannot be serialized.");
468        }
469        header.uuid.serialize(serializer)
470    }
471}
472
473impl Visit for UntypedResource {
474    fn visit(&mut self, name: &str, visitor: &mut Visitor) -> VisitResult {
475        let result = self.visit_with_type_uuid(name, None, visitor);
476        if let Err(err) = &result {
477            Log::err(format!("Resource error for untyped resource: {err}"));
478            if let Ok(region) = visitor.enter_region(name) {
479                region.debug();
480            }
481            self.commit_error(PathBuf::default(), err.to_string());
482        }
483        result
484    }
485}
486
487impl Display for UntypedResource {
488    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
489        if let Some(header) = self.0.try_lock() {
490            Display::fmt(&header, f)
491        } else {
492            f.write_str("locked")
493        }
494    }
495}
496
497impl Debug for UntypedResource {
498    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
499        write!(f, "UntypedResource({self})")
500    }
501}
502
503impl PartialEq for UntypedResource {
504    fn eq(&self, other: &Self) -> bool {
505        Arc::ptr_eq(&self.0, &other.0)
506    }
507}
508
509impl Eq for UntypedResource {}
510
511impl Hash for UntypedResource {
512    fn hash<H: Hasher>(&self, state: &mut H) {
513        Arc::as_ptr(&self.0).hash(state);
514    }
515}
516
517impl From<Uuid> for UntypedResource {
518    fn from(uuid: Uuid) -> Self {
519        ResourceHeader::from(uuid).into()
520    }
521}
522
523impl From<ResourceHeader> for UntypedResource {
524    fn from(header: ResourceHeader) -> Self {
525        Self(Arc::new(Mutex::new(header)))
526    }
527}
528
529impl UntypedResource {
530    /// Visit this resource handle with the given UUID for the type of the resource data.
531    pub fn visit_with_type_uuid(
532        &mut self,
533        name: &str,
534        type_uuid: Option<Uuid>,
535        visitor: &mut Visitor,
536    ) -> VisitResult {
537        let mut region = visitor.enter_region(name)?;
538        if region.is_reading() {
539            let mut uuid = Uuid::default();
540            match uuid.visit("Uuid", &mut region) {
541                Ok(()) => {
542                    self.read_visit(uuid, type_uuid, &mut region)?;
543                    drop(region);
544                }
545                Err(_) => {
546                    drop(region);
547                    self.legacy_visit(name, visitor)?;
548                }
549            }
550            let resource_manager = visitor
551                .blackboard
552                .get::<ResourceManager>()
553                .expect("Resource manager must be available when deserializing resources!")
554                .clone();
555            resource_manager.state().request_resource(self);
556            Ok(())
557        } else {
558            self.resource_uuid().visit("Uuid", &mut region)?;
559            let header_guard = self.lock();
560            let is_embedded = header_guard.kind.is_embedded();
561            let is_ok = header_guard.state.is_ok();
562            drop(header_guard);
563            if is_embedded && is_ok {
564                self.0.visit("Embedded", &mut region)
565            } else if is_embedded {
566                true.visit("Default", &mut region)
567            } else {
568                Ok(())
569            }
570        }
571    }
572    fn legacy_visit(&mut self, name: &str, visitor: &mut Visitor) -> VisitResult {
573        let mut header: Arc<Mutex<LegacyHeader>> = Default::default();
574        let result = header.visit(name, visitor);
575        if let Err(err1) = result {
576            header = Arc::default();
577            if let Ok(mut region) = visitor.enter_region(name) {
578                let mut region = if region.has_region("Value") {
579                    region.enter_region("Value").unwrap()
580                } else {
581                    region
582                };
583                let result = header.visit("State", &mut region);
584                if let Err(err2) = result {
585                    if let Ok(mut region) = region.enter_region("State") {
586                        let result = header.visit("Data", &mut region);
587                        if let Err(err3) = result {
588                            return Err(err1.multiple(err2).multiple(err3));
589                        }
590                    } else {
591                        return Err(err1.multiple(err2));
592                    }
593                }
594            } else {
595                return Err(err1);
596            }
597        }
598        let resource_manager = visitor
599            .blackboard
600            .get::<ResourceManager>()
601            .expect("Resource manager must be available when deserializing resources!")
602            .clone();
603        let mut state = resource_manager.state();
604        let mut header = header.try_lock().expect("header locked");
605        match *header {
606            LegacyHeader::Path(ref path) => {
607                Log::info(format!("Requesting {path:?}"));
608                *self = state.request(path);
609            }
610            LegacyHeader::Uuid(uuid) => {
611                *self = uuid.into();
612            }
613            LegacyHeader::Data(_) => {
614                let uuid = Uuid::new_v4();
615                let data = header.take_data(uuid).unwrap();
616                *self = UntypedResource::new_ok_untyped(uuid, ResourceKind::Embedded, data);
617            }
618        }
619        Ok(())
620    }
621    fn read_visit(
622        &mut self,
623        resource_uuid: Uuid,
624        type_uuid: Option<Uuid>,
625        visitor: &mut Visitor,
626    ) -> VisitResult {
627        let mut is_default = false;
628        if is_default.visit("Default", visitor).is_ok() && is_default {
629            *self = Self::default();
630            self.lock().uuid = resource_uuid;
631            Ok(())
632        } else if visitor.has_region("Embedded") {
633            self.0.visit("Embedded", visitor)?;
634            self.lock().uuid = resource_uuid;
635            if let (Some(expected), Some(actual)) = (type_uuid, self.lock().type_uuid()) {
636                if expected != actual {
637                    return Err(format!(
638                        "Unable to deserialize untyped resource into its typed \
639                    version, because types do not match! Untyped resource has \
640                    {actual} type, but the required type is {expected}.",
641                    )
642                    .into());
643                }
644            }
645            Ok(())
646        } else {
647            *self = resource_uuid.into();
648            Ok(())
649        }
650    }
651    /// Lock the shared header of this resource.
652    pub fn typed_lock<T: TypedResourceData>(&self) -> ResourceHeaderGuard<'_, T> {
653        self.lock().into()
654    }
655    /// Lock the shared header of this resource.
656    pub fn lock(&self) -> MutexGuard<'_, ResourceHeader> {
657        self.0.safe_lock()
658    }
659    /// Attempt to lock the shared header. None if the header is already locked.
660    pub fn try_typed_lock<T: TypedResourceData>(&self) -> Option<ResourceHeaderGuard<'_, T>> {
661        self.try_lock().map(|g| g.into())
662    }
663    /// Attempt to lock the shared header. None if the header is already locked.
664    pub fn try_lock(&self) -> Option<MutexGuard<'_, ResourceHeader>> {
665        self.0.try_lock()
666    }
667    /// Creates new untyped resource in unloaded state with the given UUID.
668    pub fn new_unloaded(resource_uuid: Uuid) -> Self {
669        ResourceHeader {
670            uuid: resource_uuid,
671            kind: ResourceKind::External,
672            state: ResourceState::Unloaded,
673        }
674        .into()
675    }
676    /// Creates new untyped resource in pending state with the given UUID.
677    pub fn new_pending(resource_uuid: Uuid, kind: ResourceKind) -> Self {
678        ResourceHeader {
679            uuid: resource_uuid,
680            kind,
681            state: ResourceState::new_pending(),
682        }
683        .into()
684    }
685
686    /// Creates new untyped resource in ok (fully loaded) state using the given data of any type, that
687    /// implements [`ResourceData`] trait.
688    pub fn new_ok<T>(resource_uuid: Uuid, kind: ResourceKind, data: T) -> Self
689    where
690        T: ResourceData,
691    {
692        ResourceHeader {
693            uuid: resource_uuid,
694            kind,
695            state: ResourceState::new_ok(data),
696        }
697        .into()
698    }
699
700    /// Creates new untyped resource in ok (fully loaded) state using the given data.
701    pub fn new_ok_untyped(
702        resource_uuid: Uuid,
703        kind: ResourceKind,
704        data: Box<dyn ResourceData>,
705    ) -> Self {
706        ResourceHeader {
707            uuid: resource_uuid,
708            kind,
709            state: ResourceState::new_ok_untyped(data),
710        }
711        .into()
712    }
713
714    /// Creates new untyped resource in ok (fully loaded) state using the given data of any type, that
715    /// implements [`ResourceData`] trait. The resource kind is set to [`ResourceKind::Embedded`].
716    pub fn new_embedded<T: ResourceData>(data: T) -> Self {
717        Self::new_ok(Uuid::new_v4(), ResourceKind::Embedded, data)
718    }
719
720    /// Creates new untyped resource in error state.
721    pub fn new_load_error(kind: ResourceKind, path: PathBuf, error: LoadError) -> Self {
722        ResourceHeader {
723            uuid: Uuid::new_v4(),
724            kind,
725            state: ResourceState::new_load_error(path, error),
726        }
727        .into()
728    }
729
730    /// The UUID of the resource. All resources must have a UUID, even if they are not loaded
731    /// because the UUID is how the resource manager knows the path to load from.
732    pub fn resource_uuid(&self) -> Uuid {
733        self.lock().uuid
734    }
735
736    /// Returns actual unique type id of underlying resource data.
737    pub fn type_uuid(&self) -> Option<Uuid> {
738        let header = self.lock();
739        match header.state {
740            ResourceState::Ok { ref data, .. } => Some(data.type_uuid()),
741            _ => None,
742        }
743    }
744
745    /// Tries to get an actual unique type id of underlying resource data. Returns `None` if the
746    /// resource cannot be locked or if it is not loaded.
747    pub fn type_uuid_non_blocking(&self) -> Option<Uuid> {
748        let header = self.try_lock()?;
749        match header.state {
750            ResourceState::Ok { ref data, .. } => Some(data.type_uuid()),
751            _ => None,
752        }
753    }
754
755    /// Tries to get a type name of the resource data. Data type name is available only for fully
756    /// loaded resources (in [`ResourceState::Ok`] state).
757    pub fn data_type_name(&self) -> Option<String> {
758        match self.lock().state {
759            ResourceState::Ok { ref data, .. } => Some(Reflect::type_name(&**data).to_string()),
760            _ => None,
761        }
762    }
763
764    /// Same as [`Self::data_type_name`], but returns `Unknown` string if the resource is not in
765    /// [`ResourceState::Ok`] state.
766    pub fn data_type_name_or_unknown(&self) -> String {
767        self.data_type_name()
768            .unwrap_or_else(|| "Unknown".to_string())
769    }
770
771    /// Returns true if the resource has not been requested.
772    pub fn is_unloaded(&self) -> bool {
773        matches!(self.lock().state, ResourceState::Unloaded)
774    }
775    /// Returns true if the resource is still loading.
776    pub fn is_loading(&self) -> bool {
777        matches!(self.lock().state, ResourceState::Pending { .. })
778    }
779
780    /// Returns true if the resource is completely loaded.
781    pub fn is_ok(&self) -> bool {
782        matches!(self.lock().state, ResourceState::Ok { .. })
783    }
784
785    /// Returns true if the resource failed to load.
786    pub fn is_failed_to_load(&self) -> bool {
787        matches!(self.lock().state, ResourceState::LoadError { .. })
788    }
789
790    /// Returns true if the resource is procedural (its data is generated at runtime, not stored in an external
791    /// file).
792    pub fn is_embedded(&self) -> bool {
793        self.lock().kind.is_embedded()
794    }
795
796    /// Returns exact amount of users of the resource.
797    #[inline]
798    pub fn use_count(&self) -> usize {
799        Arc::strong_count(&self.0)
800    }
801
802    /// Returns a pointer as numeric value which can be used as a hash.
803    #[inline]
804    pub fn key(&self) -> u64 {
805        let mut hasher = FxHasher64::default();
806        self.hash(&mut hasher);
807        hasher.finish()
808    }
809
810    /// Returns path of the untyped resource.
811    pub fn kind(&self) -> ResourceKind {
812        self.lock().kind
813    }
814
815    /// Set a new path for the untyped resource.
816    pub fn set_kind(&self, new_kind: ResourceKind) {
817        self.lock().kind = new_kind;
818    }
819
820    /// Tries to save the resource to the specified path.
821    pub fn save(&self, path: &Path) -> Result<(), Box<dyn Error>> {
822        match self.lock().state {
823            ResourceState::Pending { .. }
824            | ResourceState::LoadError { .. }
825            | ResourceState::Unloaded => Err("Unable to save unloaded resource!".into()),
826            ResourceState::Ok { ref mut data, .. } => data.save(path),
827        }
828    }
829
830    /// Tries to cast untyped resource to a particular type.
831    pub fn try_cast<T>(&self) -> Option<Resource<T>>
832    where
833        T: TypedResourceData,
834    {
835        if self.type_uuid() == Some(<T as TypeUuidProvider>::type_uuid()) {
836            Some(Resource {
837                untyped: self.clone(),
838                phantom: PhantomData::<T>,
839            })
840        } else {
841            None
842        }
843    }
844
845    /// Modify this resource into the [`ResourceState::Pending`] state.
846    pub fn make_pending(&mut self) {
847        self.lock().state = ResourceState::new_pending();
848    }
849    /// Changes ResourceState::Pending state to ResourceState::Ok(data) with given `data`.
850    /// Additionally, it wakes all futures. Panics if the resource is unrequested.
851    #[inline]
852    pub fn commit(&self, state: ResourceState) {
853        self.lock().state.commit(state);
854    }
855
856    /// Changes internal state to [`ResourceState::Ok`]. Panics if the resource is unrequested.
857    pub fn commit_ok<T: ResourceData>(&self, data: T) {
858        self.lock().state.commit_ok(data);
859    }
860
861    /// Changes internal state to [`ResourceState::LoadError`].
862    pub fn commit_error<E: ResourceLoadError>(&mut self, path: PathBuf, error: E) {
863        self.lock().state.commit_error(path, error);
864    }
865}
866
867impl Future for UntypedResource {
868    type Output = Result<Self, LoadError>;
869
870    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
871        let mut guard = self.lock();
872        match guard.state {
873            ResourceState::Pending { ref mut wakers, .. } => {
874                wakers.add_waker(cx.waker());
875                Poll::Pending
876            }
877            ResourceState::Unloaded => Poll::Ready(Err(LoadError::new(
878                "Unloaded resource is not loading".to_string(),
879            ))),
880            ResourceState::LoadError { ref error, .. } => Poll::Ready(Err(error.clone())),
881            ResourceState::Ok { .. } => Poll::Ready(Ok(self.clone())),
882        }
883    }
884}
885
886#[cfg(test)]
887mod test {
888    use futures::task::noop_waker;
889    use fyrox_core::futures;
890    use std::error::Error;
891    use std::task::{self};
892
893    use crate::io::FsResourceIo;
894
895    use super::*;
896
897    #[derive(Debug, Default, Reflect, Visit, Clone, Copy)]
898    struct Stub {}
899
900    impl ResourceData for Stub {
901        fn type_uuid(&self) -> Uuid {
902            Uuid::default()
903        }
904
905        fn save(&mut self, _path: &Path) -> Result<(), Box<dyn Error>> {
906            Err("Saving is not supported!".to_string().into())
907        }
908
909        fn can_be_saved(&self) -> bool {
910            false
911        }
912
913        fn try_clone_box(&self) -> Option<Box<dyn ResourceData>> {
914            Some(Box::new(*self))
915        }
916    }
917
918    impl TypeUuidProvider for Stub {
919        fn type_uuid() -> Uuid {
920            Uuid::default()
921        }
922    }
923
924    impl ResourceLoadError for str {}
925
926    #[test]
927    fn visit_for_untyped_resource() {
928        let mut r = UntypedResource::default();
929        let mut visitor = Visitor::default();
930
931        assert!(r.visit("name", &mut visitor).is_ok());
932
933        let mut cursor = std::io::Cursor::new(Vec::<u8>::new());
934        visitor
935            .save_binary_to_memory(&mut cursor)
936            .expect("Failed to write binary for visitor");
937        cursor.set_position(0);
938        let mut visitor = Visitor::load_binary_from_memory(cursor.get_ref())
939            .expect("Failed to read binary for visitor");
940        visitor.blackboard.register(Arc::new(ResourceManager::new(
941            Arc::new(FsResourceIo),
942            Arc::default(),
943        )));
944        assert!(r.visit("name", &mut visitor).is_ok());
945        assert!(r.is_embedded());
946        assert!(r.is_failed_to_load());
947    }
948
949    #[test]
950    fn untyped_resource_use_count() {
951        let r = UntypedResource::default();
952
953        assert_eq!(r.use_count(), 1);
954    }
955
956    #[test]
957    fn untyped_resource_try_cast() {
958        let r = UntypedResource::default();
959        let r2 = UntypedResource::new_ok(Uuid::new_v4(), ResourceKind::External, Stub {});
960
961        assert!(r.try_cast::<Stub>().is_none());
962        assert!(r2.try_cast::<Stub>().is_some());
963    }
964
965    #[test]
966    fn untyped_resource_poll() {
967        let stub = Stub {};
968
969        let waker = noop_waker();
970        let mut cx = task::Context::from_waker(&waker);
971
972        let mut r = UntypedResource::from(ResourceHeader {
973            uuid: Uuid::new_v4(),
974            kind: ResourceKind::External,
975            state: ResourceState::Ok {
976                data: ResourceDataWrapper(Box::new(stub)),
977            },
978        });
979        assert!(Pin::new(&mut r).poll(&mut cx).is_ready());
980
981        let mut r = UntypedResource::from(ResourceHeader {
982            uuid: Uuid::new_v4(),
983            kind: ResourceKind::External,
984            state: ResourceState::LoadError {
985                path: Default::default(),
986                error: Default::default(),
987            },
988        });
989        assert!(Pin::new(&mut r).poll(&mut cx).is_ready());
990    }
991}