1#![forbid(unsafe_code)]
24#![allow(clippy::doc_lazy_continuation)]
25#![allow(clippy::mutable_key_type)]
26#![warn(missing_docs)]
27
28use crate::{
29 core::{
30 combine_uuids,
31 parking_lot::MutexGuard,
32 reflect::prelude::*,
33 uuid::{uuid, Uuid},
34 visitor::prelude::*,
35 TypeUuidProvider,
36 },
37 state::{LoadError, ResourceState},
38 untyped::{ResourceHeader, ResourceKind, UntypedResource},
39};
40use fxhash::FxHashSet;
41pub use fyrox_core as core;
42use serde::{Deserialize, Serialize};
43use std::{
44 any::Any,
45 error::Error,
46 fmt::Display,
47 fmt::{Debug, Formatter},
48 future::Future,
49 hash::{Hash, Hasher},
50 marker::PhantomData,
51 ops::{Deref, DerefMut},
52 path::Path,
53 path::PathBuf,
54 pin::Pin,
55 task::{Context, Poll},
56};
57
58pub mod builtin;
59pub mod constructor;
60pub mod entry;
61pub mod event;
62pub mod graph;
63pub mod io;
64pub mod loader;
65pub mod manager;
66pub mod metadata;
67pub mod options;
68pub mod registry;
69pub mod state;
70pub mod untyped;
71
72pub trait ResourceData: Debug + Visit + Send + Reflect {
74 fn type_uuid(&self) -> Uuid;
76
77 fn save(&mut self, #[allow(unused_variables)] path: &Path) -> Result<(), Box<dyn Error>>;
84
85 fn can_be_saved(&self) -> bool;
89
90 fn try_clone_box(&self) -> Option<Box<dyn ResourceData>>;
93}
94
95pub trait TypedResourceData: ResourceData + Default + TypeUuidProvider {}
100
101impl<T> TypedResourceData for T where T: ResourceData + Default + TypeUuidProvider {}
102
103pub trait ResourceLoadError: 'static + Debug + Display + Send + Sync {}
105
106impl<T> ResourceLoadError for T where T: 'static + Debug + Display + Send + Sync {}
107
108pub struct ResourceHeaderGuard<'a, T>
110where
111 T: TypedResourceData,
112{
113 guard: MutexGuard<'a, ResourceHeader>,
114 phantom: PhantomData<T>,
115}
116
117impl<'a, T> From<MutexGuard<'a, ResourceHeader>> for ResourceHeaderGuard<'a, T>
118where
119 T: TypedResourceData,
120{
121 fn from(guard: MutexGuard<'a, ResourceHeader>) -> Self {
122 Self {
123 guard,
124 phantom: PhantomData,
125 }
126 }
127}
128
129impl<T> ResourceHeaderGuard<'_, T>
130where
131 T: TypedResourceData,
132{
133 pub fn resource_uuid(&self) -> Uuid {
135 self.guard.uuid
136 }
137
138 pub fn kind(&self) -> ResourceKind {
140 self.guard.kind
141 }
142
143 pub fn data(&mut self) -> Option<&mut T> {
147 if let ResourceState::Ok { ref mut data, .. } = self.guard.state {
148 (&mut **data as &mut dyn Any).downcast_mut::<T>()
149 } else {
150 None
151 }
152 }
153
154 pub fn data_ref(&self) -> Option<&T> {
158 if let ResourceState::Ok { ref data, .. } = self.guard.state {
159 (&**data as &dyn Any).downcast_ref::<T>()
160 } else {
161 None
162 }
163 }
164
165 pub fn data_ref_with_id(&self) -> Option<(&T, &Uuid)> {
169 let uuid = &self.guard.uuid;
170 if let ResourceState::Ok { ref data } = self.guard.state {
171 (&**data as &dyn Any)
172 .downcast_ref::<T>()
173 .map(|typed| (typed, uuid))
174 } else {
175 None
176 }
177 }
178}
179
180#[derive(Reflect, Serialize, Deserialize)]
187#[serde(
188 bound = "T: TypedResourceData",
189 from = "UntypedResource",
190 into = "UntypedResource"
191)]
192pub struct Resource<T: Debug> {
193 untyped: UntypedResource,
194 #[reflect(hidden)]
195 phantom: PhantomData<T>,
196}
197
198impl<T: Debug> Debug for Resource<T> {
199 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
200 write!(
201 f,
202 "Resource<{}>({})",
203 std::any::type_name::<T>(),
204 self.untyped
205 )
206 }
207}
208
209impl<T: TypedResourceData> AsRef<UntypedResource> for Resource<T> {
210 fn as_ref(&self) -> &UntypedResource {
211 &self.untyped
212 }
213}
214
215impl AsRef<UntypedResource> for UntypedResource {
216 fn as_ref(&self) -> &UntypedResource {
217 self
218 }
219}
220
221impl<T: TypedResourceData> TypeUuidProvider for Resource<T> {
222 fn type_uuid() -> Uuid {
223 combine_uuids(
224 uuid!("790b1a1c-a997-46c4-ac3b-8565501f0052"),
225 <T as TypeUuidProvider>::type_uuid(),
226 )
227 }
228}
229
230impl<T> Visit for Resource<T>
231where
232 T: TypedResourceData,
233{
234 fn visit(&mut self, name: &str, visitor: &mut Visitor) -> VisitResult {
235 self.untyped
236 .visit_with_type_uuid(name, Some(<T as TypeUuidProvider>::type_uuid()), visitor)
237 }
238}
239
240impl<T> PartialEq for Resource<T>
241where
242 T: TypedResourceData,
243{
244 fn eq(&self, other: &Self) -> bool {
245 self.untyped == other.untyped
246 }
247}
248
249impl<T> Eq for Resource<T> where T: TypedResourceData {}
250
251impl<T> Hash for Resource<T>
252where
253 T: TypedResourceData,
254{
255 fn hash<H: Hasher>(&self, state: &mut H) {
256 self.untyped.hash(state)
257 }
258}
259
260impl<T> Resource<T>
261where
262 T: TypedResourceData,
263{
264 pub fn summary(&self) -> String {
266 format!("{}", self.untyped)
267 }
268 #[inline]
270 pub fn new_pending(uuid: Uuid, kind: ResourceKind) -> Self {
271 Self {
272 untyped: UntypedResource::new_pending(uuid, kind),
273 phantom: PhantomData,
274 }
275 }
276
277 #[inline]
279 pub fn new_ok(resource_uuid: Uuid, kind: ResourceKind, data: T) -> Self {
280 Self {
281 untyped: UntypedResource::new_ok(resource_uuid, kind, data),
282 phantom: PhantomData,
283 }
284 }
285
286 #[inline]
288 pub fn new_embedded(data: T) -> Self {
289 Self {
290 untyped: UntypedResource::new_ok(Uuid::new_v4(), ResourceKind::Embedded, data),
291 phantom: PhantomData,
292 }
293 }
294
295 #[inline]
297 pub fn new_load_error(kind: ResourceKind, path: PathBuf, error: LoadError) -> Self {
298 Self {
299 untyped: UntypedResource::new_load_error(kind, path, error),
300 phantom: PhantomData,
301 }
302 }
303
304 #[inline]
306 pub fn into_untyped(self) -> UntypedResource {
307 self.untyped
308 }
309
310 #[inline]
312 pub fn state(&self) -> ResourceHeaderGuard<'_, T> {
313 self.untyped.typed_lock()
314 }
315
316 #[inline]
318 pub fn try_acquire_state(&self) -> Option<ResourceHeaderGuard<'_, T>> {
319 self.untyped.try_typed_lock()
320 }
321
322 #[inline]
324 pub fn header(&self) -> MutexGuard<'_, ResourceHeader> {
325 self.untyped.lock()
326 }
327
328 #[inline]
330 pub fn is_loading(&self) -> bool {
331 self.untyped.is_loading()
332 }
333
334 #[inline]
336 pub fn is_ok(&self) -> bool {
337 self.untyped.is_ok()
338 }
339
340 #[inline]
342 pub fn is_failed_to_load(&self) -> bool {
343 self.untyped.is_failed_to_load()
344 }
345
346 #[inline]
348 pub fn use_count(&self) -> usize {
349 self.untyped.use_count()
350 }
351
352 #[inline]
354 pub fn key(&self) -> u64 {
355 self.untyped.key()
356 }
357
358 #[inline]
360 pub fn kind(&self) -> ResourceKind {
361 self.untyped.kind()
362 }
363
364 #[inline]
367 pub fn resource_uuid(&self) -> Uuid {
368 self.untyped.resource_uuid()
369 }
370
371 #[inline]
373 pub fn set_path(&mut self, new_kind: ResourceKind) {
374 self.untyped.set_kind(new_kind);
375 }
376
377 #[inline]
391 pub fn data_ref(&self) -> ResourceDataRef<'_, T> {
392 ResourceDataRef {
393 guard: self.untyped.lock(),
394 phantom: Default::default(),
395 }
396 }
397
398 pub fn save(&self, path: &Path) -> Result<(), Box<dyn Error>> {
400 self.untyped.save(path)
401 }
402}
403
404impl<T> Default for Resource<T>
405where
406 T: TypedResourceData,
407{
408 #[inline]
409 fn default() -> Self {
410 Self {
411 untyped: UntypedResource::new_ok(Uuid::new_v4(), ResourceKind::Embedded, T::default()),
412 phantom: Default::default(),
413 }
414 }
415}
416
417impl<T: Debug> Clone for Resource<T> {
418 #[inline]
419 fn clone(&self) -> Self {
420 Self {
421 untyped: self.untyped.clone(),
422 phantom: Default::default(),
423 }
424 }
425}
426
427impl<T> From<Uuid> for Resource<T>
428where
429 T: TypedResourceData,
430{
431 fn from(uuid: Uuid) -> Self {
432 UntypedResource::from(uuid).into()
433 }
434}
435
436impl<T> From<UntypedResource> for Resource<T>
437where
438 T: TypedResourceData,
439{
440 #[inline]
441 fn from(untyped: UntypedResource) -> Self {
442 if let Some(type_uuid) = untyped.type_uuid() {
443 let expected = <T as TypeUuidProvider>::type_uuid();
444 if type_uuid != expected {
445 panic!("Resource type mismatch. Expected: {expected}. Found: {type_uuid}");
446 }
447 }
448 Self {
449 untyped,
450 phantom: Default::default(),
451 }
452 }
453}
454
455#[allow(clippy::from_over_into)]
456impl<T> Into<UntypedResource> for Resource<T>
457where
458 T: TypedResourceData,
459{
460 #[inline]
461 fn into(self) -> UntypedResource {
462 self.untyped
463 }
464}
465
466impl<T> Future for Resource<T>
467where
468 T: TypedResourceData,
469{
470 type Output = Result<Self, LoadError>;
471
472 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
473 let mut inner = self.untyped.clone();
474 Pin::new(&mut inner)
475 .poll(cx)
476 .map(|r| r.map(|_| self.clone()))
477 }
478}
479
480#[doc(hidden)]
481pub struct ResourceDataRef<'a, T>
482where
483 T: TypedResourceData,
484{
485 guard: MutexGuard<'a, ResourceHeader>,
486 phantom: PhantomData<T>,
487}
488
489impl<T> ResourceDataRef<'_, T>
490where
491 T: TypedResourceData,
492{
493 #[inline]
494 pub fn as_loaded_ref(&self) -> Option<&T> {
495 match self.guard.state {
496 ResourceState::Ok { ref data, .. } => (&**data as &dyn Any).downcast_ref(),
497 _ => None,
498 }
499 }
500
501 #[inline]
502 pub fn as_loaded_mut(&mut self) -> Option<&mut T> {
503 match self.guard.state {
504 ResourceState::Ok { ref mut data, .. } => (&mut **data as &mut dyn Any).downcast_mut(),
505 _ => None,
506 }
507 }
508}
509
510impl<T> Debug for ResourceDataRef<'_, T>
511where
512 T: TypedResourceData,
513{
514 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
515 match self.guard.state {
516 ResourceState::Unloaded => {
517 write!(
518 f,
519 "Attempt to get reference to resource data while it is unloaded!"
520 )
521 }
522 ResourceState::Pending { .. } => {
523 write!(
524 f,
525 "Attempt to get reference to resource data while it is loading!"
526 )
527 }
528 ResourceState::LoadError { .. } => {
529 write!(
530 f,
531 "Attempt to get reference to resource data which failed to load!"
532 )
533 }
534 ResourceState::Ok { ref data, .. } => data.fmt(f),
535 }
536 }
537}
538
539impl<T> Deref for ResourceDataRef<'_, T>
540where
541 T: TypedResourceData,
542{
543 type Target = T;
544
545 fn deref(&self) -> &Self::Target {
546 match self.guard.state {
547 ResourceState::Unloaded => {
548 panic!(
549 "Attempt to get reference to resource data while it is unloaded! Type {}",
550 std::any::type_name::<T>()
551 )
552 }
553 ResourceState::Pending { .. } => {
554 panic!(
555 "Attempt to get reference to resource data while it is loading! Type {}",
556 std::any::type_name::<T>()
557 )
558 }
559 ResourceState::LoadError {
560 ref path,
561 ref error,
562 } => {
563 let path = if path.as_os_str().is_empty() {
564 "Unknown".to_string()
565 } else {
566 format!("{path:?}")
567 };
568 panic!("Attempt to get reference to resource data which failed to load! Type {}. Path: {path}. Error: {error:?}", std::any::type_name::<T>())
569 }
570 ResourceState::Ok { ref data } => (&**data as &dyn Any)
571 .downcast_ref()
572 .expect("Type mismatch!"),
573 }
574 }
575}
576
577impl<T> DerefMut for ResourceDataRef<'_, T>
578where
579 T: TypedResourceData,
580{
581 fn deref_mut(&mut self) -> &mut Self::Target {
582 let header = &mut *self.guard;
583 match header.state {
584 ResourceState::Unloaded => {
585 panic!("Attempt to get reference to resource data while it is unloaded!")
586 }
587 ResourceState::Pending { .. } => {
588 panic!("Attempt to get reference to resource data while it is loading!")
589 }
590 ResourceState::LoadError { .. } => {
591 panic!("Attempt to get reference to resource data which failed to load!")
592 }
593 ResourceState::Ok { ref mut data, .. } => (&mut **data as &mut dyn Any)
594 .downcast_mut()
595 .expect("Type mismatch!"),
596 }
597 }
598}
599
600pub fn collect_used_resources(
605 entity: &dyn Reflect,
606 resources_collection: &mut FxHashSet<UntypedResource>,
607) {
608 #[inline(always)]
609 fn type_is<T: Reflect>(entity: &dyn Reflect) -> bool {
610 let mut types_match = false;
611 entity.downcast_ref::<T>(&mut |v| {
612 types_match = v.is_some();
613 });
614 types_match
615 }
616
617 let mut finished = type_is::<Vec<u8>>(entity)
621 || type_is::<Vec<u16>>(entity)
622 || type_is::<Vec<u32>>(entity)
623 || type_is::<Vec<u64>>(entity)
624 || type_is::<Vec<i8>>(entity)
625 || type_is::<Vec<i16>>(entity)
626 || type_is::<Vec<i32>>(entity)
627 || type_is::<Vec<i64>>(entity)
628 || type_is::<Vec<f32>>(entity)
629 || type_is::<Vec<f64>>(entity);
630
631 if finished {
632 return;
633 }
634
635 entity.downcast_ref::<UntypedResource>(&mut |v| {
636 if let Some(resource) = v {
637 resources_collection.insert(resource.clone());
638 finished = true;
639 }
640 });
641
642 if finished {
643 return;
644 }
645
646 entity.as_array(&mut |array| {
647 if let Some(array) = array {
648 for i in 0..array.reflect_len() {
649 if let Some(item) = array.reflect_index(i) {
650 collect_used_resources(item, resources_collection)
651 }
652 }
653
654 finished = true;
655 }
656 });
657
658 if finished {
659 return;
660 }
661
662 entity.as_inheritable_variable(&mut |inheritable| {
663 if let Some(inheritable) = inheritable {
664 collect_used_resources(inheritable.inner_value_ref(), resources_collection);
665
666 finished = true;
667 }
668 });
669
670 if finished {
671 return;
672 }
673
674 entity.as_hash_map(&mut |hash_map| {
675 if let Some(hash_map) = hash_map {
676 for i in 0..hash_map.reflect_len() {
677 if let Some((key, value)) = hash_map.reflect_get_at(i) {
678 collect_used_resources(key, resources_collection);
679 collect_used_resources(value, resources_collection);
680 }
681 }
682
683 finished = true;
684 }
685 });
686
687 if finished {
688 return;
689 }
690
691 entity.fields_ref(&mut |fields| {
692 for field in fields {
693 collect_used_resources(field.value.field_value_as_reflect(), resources_collection);
694 }
695 })
696}
697
698#[cfg(test)]
699mod tests {
700 use super::*;
701 use crate::{
702 io::{FsResourceIo, ResourceIo},
703 loader::{BoxedLoaderFuture, LoaderPayload, ResourceLoader, ResourceLoadersContainer},
704 manager::ResourceManager,
705 metadata::ResourceMetadata,
706 registry::ResourceRegistry,
707 state::LoadError,
708 ResourceData,
709 };
710 use fyrox_core::{
711 append_extension, futures::executor::block_on, io::FileError, parking_lot::Mutex,
712 reflect::prelude::*, task::TaskPool, uuid, visitor::prelude::*, SafeLock, TypeUuidProvider,
713 Uuid,
714 };
715 use ron::ser::PrettyConfig;
716 use serde::{Deserialize, Serialize};
717 use std::{
718 error::Error,
719 fs::File,
720 io::Write,
721 ops::Range,
722 path::{Path, PathBuf},
723 sync::Arc,
724 };
725
726 #[derive(Serialize, Deserialize, Default, Debug, Clone, Visit, Reflect, TypeUuidProvider)]
727 #[type_uuid(id = "241d14c7-079e-4395-a63c-364f0fc3e6ea")]
728 struct MyData {
729 data: u32,
730 }
731
732 impl MyData {
733 pub async fn load_from_file(
734 path: &Path,
735 resource_io: &dyn ResourceIo,
736 ) -> Result<Self, FileError> {
737 resource_io.load_file(path).await.and_then(|metadata| {
738 ron::de::from_bytes::<Self>(&metadata).map_err(|err| {
739 FileError::Custom(format!(
740 "Unable to deserialize the resource metadata. Reason: {err:?}"
741 ))
742 })
743 })
744 }
745 }
746
747 impl ResourceData for MyData {
748 fn type_uuid(&self) -> Uuid {
749 <Self as TypeUuidProvider>::type_uuid()
750 }
751
752 fn save(&mut self, path: &Path) -> Result<(), Box<dyn Error>> {
753 let string = ron::ser::to_string_pretty(self, PrettyConfig::default())
754 .map_err(|err| {
755 FileError::Custom(format!(
756 "Unable to serialize resource metadata for {} resource! Reason: {}",
757 path.display(),
758 err
759 ))
760 })
761 .map_err(|_| "error".to_string())?;
762 let mut file = File::create(path)?;
763 file.write_all(string.as_bytes())?;
764 Ok(())
765 }
766
767 fn can_be_saved(&self) -> bool {
768 true
769 }
770
771 fn try_clone_box(&self) -> Option<Box<dyn ResourceData>> {
772 Some(Box::new(self.clone()))
773 }
774 }
775
776 struct MyDataLoader {}
777
778 impl MyDataLoader {
779 const EXT: &'static str = "my_data";
780 }
781
782 impl ResourceLoader for MyDataLoader {
783 fn extensions(&self) -> &[&str] {
784 &[Self::EXT]
785 }
786
787 fn data_type_uuid(&self) -> Uuid {
788 <MyData as TypeUuidProvider>::type_uuid()
789 }
790
791 fn load(&self, path: PathBuf, io: Arc<dyn ResourceIo>) -> BoxedLoaderFuture {
792 Box::pin(async move {
793 let my_data = MyData::load_from_file(&path, io.as_ref())
794 .await
795 .map_err(LoadError::new)?;
796 Ok(LoaderPayload::new(my_data))
797 })
798 }
799 }
800
801 const TEST_FOLDER1: &str = "./test_output1";
802 const TEST_FOLDER2: &str = "./test_output2";
803 const TEST_FOLDER3: &str = "./test_output3";
804
805 fn make_file_path(root: &str, n: usize) -> PathBuf {
806 Path::new(root).join(format!("test{n}.{}", MyDataLoader::EXT))
807 }
808
809 fn make_metadata_file_path(root: &str, n: usize) -> PathBuf {
810 Path::new(root).join(format!(
811 "test{n}.{}.{}",
812 MyDataLoader::EXT,
813 ResourceMetadata::EXTENSION
814 ))
815 }
816
817 fn write_test_resources(root: &str, indices: Range<usize>) {
818 let path = Path::new(root);
819 if !std::fs::exists(path).unwrap() {
820 std::fs::create_dir_all(path).unwrap();
821 }
822
823 for i in indices {
824 MyData { data: i as u32 }
825 .save(&make_file_path(root, i))
826 .unwrap();
827 }
828 }
829
830 #[test]
831 fn test_serialize() {
832 let uuid = uuid!("6d1aadb5-42e1-485b-910b-fa4d81b61855");
833 let typed = Resource::<MyData>::from(uuid);
834 let untyped = UntypedResource::from(uuid);
835 let s = ron::ser::to_string(&typed).unwrap();
836 assert_eq!(&ron::ser::to_string(&uuid).unwrap(), &s);
837 assert_eq!(&ron::ser::to_string(&untyped).unwrap(), &s);
838 let output_uuid = ron::de::from_str::<Uuid>(&s).unwrap();
839 assert_eq!(output_uuid, uuid);
840 let untyped = ron::de::from_str::<UntypedResource>(&s).unwrap();
841 assert_eq!(untyped.resource_uuid(), uuid);
842 assert_eq!(untyped.kind(), ResourceKind::External);
843 let output = ron::de::from_str::<Resource<MyData>>(&s).unwrap();
844 assert_eq!(output.resource_uuid(), uuid);
845 assert_eq!(output.kind(), ResourceKind::External);
846 }
847
848 #[test]
849 fn test_registry_scan() {
850 write_test_resources(TEST_FOLDER1, 0..2);
851
852 assert!(std::fs::exists(make_file_path(TEST_FOLDER1, 0)).unwrap());
853 assert!(std::fs::exists(make_file_path(TEST_FOLDER1, 1)).unwrap());
854
855 let io = Arc::new(FsResourceIo);
856
857 let mut loaders = ResourceLoadersContainer::new();
858 loaders.set(MyDataLoader {});
859 let loaders = Arc::new(Mutex::new(loaders));
860
861 let registry = block_on(ResourceRegistry::scan(
862 io,
863 loaders,
864 Path::new(TEST_FOLDER1).join("resources.registry"),
865 Default::default(),
866 ));
867
868 assert!(std::fs::exists(make_metadata_file_path(TEST_FOLDER1, 0)).unwrap());
869 assert!(std::fs::exists(make_metadata_file_path(TEST_FOLDER1, 1)).unwrap());
870
871 assert_eq!(registry.len(), 2);
872 }
873
874 #[test]
875 fn test_resource_manager_request_simple() {
876 write_test_resources(TEST_FOLDER2, 2..4);
877 let resource_manager =
878 ResourceManager::new(Arc::new(FsResourceIo), Arc::new(TaskPool::new()));
879 resource_manager
880 .state()
881 .resource_registry
882 .safe_lock()
883 .set_path(Path::new(TEST_FOLDER2).join("resources.registry"));
884 resource_manager.add_loader(MyDataLoader {});
885 resource_manager.update_or_load_registry();
886 let path1 = make_file_path(TEST_FOLDER2, 2);
887 let path2 = make_file_path(TEST_FOLDER2, 3);
888 let res1 = resource_manager.request::<MyData>(&path1);
889 let res1_2 = resource_manager.request::<MyData>(&path1);
890 assert_eq!(res1.key(), res1_2.key());
891 let res2 = resource_manager.request::<MyData>(path2);
892 assert_ne!(res1.key(), res2.key());
893 assert_eq!(block_on(res1).unwrap().data_ref().data, 2);
894 assert_eq!(block_on(res2).unwrap().data_ref().data, 3);
895 }
896
897 #[test]
898 fn test_move_resource() {
899 write_test_resources(TEST_FOLDER3, 0..2);
900 let resource_manager =
901 ResourceManager::new(Arc::new(FsResourceIo), Arc::new(TaskPool::new()));
902 resource_manager
903 .state()
904 .resource_registry
905 .safe_lock()
906 .set_path(Path::new(TEST_FOLDER3).join("resources.registry"));
907 resource_manager.add_loader(MyDataLoader {});
908 resource_manager.update_or_load_registry();
909 let path1 = make_file_path(TEST_FOLDER3, 0);
910 let path2 = make_file_path(TEST_FOLDER3, 1);
911 let res1 = resource_manager.request::<MyData>(path1);
912 let res2 = resource_manager.request::<MyData>(path2);
913 assert_eq!(block_on(res1.clone()).unwrap().data_ref().data, 0);
914 assert_eq!(block_on(res2.clone()).unwrap().data_ref().data, 1);
915 let new_res1_path = resource_manager
916 .resource_io()
917 .canonicalize_path(&make_file_path(TEST_FOLDER3, 3))
918 .unwrap();
919 let new_res2_path = resource_manager
920 .resource_io()
921 .canonicalize_path(&make_file_path(TEST_FOLDER3, 4))
922 .unwrap();
923 block_on(resource_manager.move_resource(res1.as_ref(), &new_res1_path, true)).unwrap();
924 block_on(resource_manager.move_resource(res2.as_ref(), &new_res2_path, true)).unwrap();
925 assert_eq!(
926 resource_manager.resource_path(res1.as_ref()).unwrap(),
927 new_res1_path
928 );
929 assert!(
930 std::fs::exists(append_extension(new_res1_path, ResourceMetadata::EXTENSION)).unwrap()
931 );
932 assert_eq!(
933 resource_manager.resource_path(res2.as_ref()).unwrap(),
934 new_res2_path
935 );
936 assert!(
937 std::fs::exists(append_extension(new_res2_path, ResourceMetadata::EXTENSION)).unwrap()
938 );
939 }
940}