1use 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
58const 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 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 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
202fn 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#[derive(Default, Reflect, Debug, Visit, Copy, Clone, PartialEq, Eq, Hash)]
255pub enum ResourceKind {
256 #[default]
258 Embedded,
259 External,
270}
271
272impl ResourceKind {
273 #[inline]
275 pub fn make_external(&mut self) {
276 *self = ResourceKind::External;
277 }
278
279 #[inline]
281 pub fn make_embedded(&mut self) {
282 *self = ResourceKind::Embedded;
283 }
284
285 #[inline]
287 pub fn is_embedded(&self) -> bool {
288 matches!(self, Self::Embedded)
289 }
290
291 #[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#[derive(Reflect, Clone, Debug)]
314pub struct ResourceHeader {
315 pub uuid: Uuid,
317 pub kind: ResourceKind,
319 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 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#[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 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 pub fn typed_lock<T: TypedResourceData>(&self) -> ResourceHeaderGuard<'_, T> {
653 self.lock().into()
654 }
655 pub fn lock(&self) -> MutexGuard<'_, ResourceHeader> {
657 self.0.safe_lock()
658 }
659 pub fn try_typed_lock<T: TypedResourceData>(&self) -> Option<ResourceHeaderGuard<'_, T>> {
661 self.try_lock().map(|g| g.into())
662 }
663 pub fn try_lock(&self) -> Option<MutexGuard<'_, ResourceHeader>> {
665 self.0.try_lock()
666 }
667 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 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 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 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 pub fn new_embedded<T: ResourceData>(data: T) -> Self {
717 Self::new_ok(Uuid::new_v4(), ResourceKind::Embedded, data)
718 }
719
720 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 pub fn resource_uuid(&self) -> Uuid {
733 self.lock().uuid
734 }
735
736 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 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 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 pub fn data_type_name_or_unknown(&self) -> String {
767 self.data_type_name()
768 .unwrap_or_else(|| "Unknown".to_string())
769 }
770
771 pub fn is_unloaded(&self) -> bool {
773 matches!(self.lock().state, ResourceState::Unloaded)
774 }
775 pub fn is_loading(&self) -> bool {
777 matches!(self.lock().state, ResourceState::Pending { .. })
778 }
779
780 pub fn is_ok(&self) -> bool {
782 matches!(self.lock().state, ResourceState::Ok { .. })
783 }
784
785 pub fn is_failed_to_load(&self) -> bool {
787 matches!(self.lock().state, ResourceState::LoadError { .. })
788 }
789
790 pub fn is_embedded(&self) -> bool {
793 self.lock().kind.is_embedded()
794 }
795
796 #[inline]
798 pub fn use_count(&self) -> usize {
799 Arc::strong_count(&self.0)
800 }
801
802 #[inline]
804 pub fn key(&self) -> u64 {
805 let mut hasher = FxHasher64::default();
806 self.hash(&mut hasher);
807 hasher.finish()
808 }
809
810 pub fn kind(&self) -> ResourceKind {
812 self.lock().kind
813 }
814
815 pub fn set_kind(&self, new_kind: ResourceKind) {
817 self.lock().kind = new_kind;
818 }
819
820 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 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 pub fn make_pending(&mut self) {
847 self.lock().state = ResourceState::new_pending();
848 }
849 #[inline]
852 pub fn commit(&self, state: ResourceState) {
853 self.lock().state.commit(state);
854 }
855
856 pub fn commit_ok<T: ResourceData>(&self, data: T) {
858 self.lock().state.commit_ok(data);
859 }
860
861 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}