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