Skip to main content

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