1pub use crate::builtin::*;
25use crate::{
26 constructor::ResourceConstructorContainer,
27 core::{
28 append_extension, err,
29 futures::future::join_all,
30 info,
31 io::FileError,
32 log::Log,
33 notify, ok_or_continue,
34 parking_lot::{Mutex, MutexGuard},
35 task::TaskPool,
36 watcher::FileSystemWatcher,
37 SafeLock, TypeUuidProvider, Uuid,
38 },
39 entry::{TimedEntry, DEFAULT_RESOURCE_LIFETIME},
40 event::{ResourceEvent, ResourceEventBroadcaster},
41 io::ResourceIo,
42 loader::{ResourceLoader, ResourceLoadersContainer},
43 metadata::ResourceMetadata,
44 options::OPTIONS_EXTENSION,
45 registry::{RegistryUpdate, ResourceRegistry, ResourceRegistryRefMut, ResourceRegistryStatus},
46 state::{LoadError, ResourceDataWrapper, ResourceState},
47 untyped::ResourceKind,
48 Resource, TypedResourceData, UntypedResource,
49};
50use fxhash::FxHashSet;
51use fyrox_core::{
52 futures::executor::block_on, make_relative_path, notify::Event, ok_or_return, some_or_continue,
53 some_or_return,
54};
55use std::{
56 fmt::{Debug, Display, Formatter},
57 io::Error,
58 marker::PhantomData,
59 path::{Path, PathBuf},
60 sync::Arc,
61 time::Duration,
62};
63
64#[must_use]
66#[derive(Default)]
67pub struct ResourceWaitContext {
68 resources: Vec<UntypedResource>,
69}
70
71impl ResourceWaitContext {
72 #[must_use]
74 pub fn is_all_loaded(&self) -> bool {
75 for resource in self.resources.iter() {
76 if resource.is_loading() {
77 return false;
78 }
79 }
80 true
81 }
82}
83
84pub struct ResourceManagerState {
86 pub loaders: Arc<Mutex<ResourceLoadersContainer>>,
88 pub event_broadcaster: ResourceEventBroadcaster,
90 pub constructors_container: ResourceConstructorContainer,
92 pub built_in_resources: BuiltInResourcesContainer,
94 pub resource_io: Arc<dyn ResourceIo>,
96 pub resource_registry: Arc<Mutex<ResourceRegistry>>,
99
100 resources: Vec<TimedEntry<UntypedResource>>,
101 task_pool: Arc<TaskPool>,
102 watcher: Option<FileSystemWatcher>,
103}
104
105#[derive(Clone)]
120pub struct ResourceManager {
121 state: Arc<Mutex<ResourceManagerState>>,
122}
123
124impl Debug for ResourceManager {
125 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
126 write!(f, "ResourceManager")
127 }
128}
129
130#[derive(Debug, PartialEq, Eq)]
132pub enum ResourceRegistrationError {
133 UnableToRegister,
135 InvalidState,
137 AlreadyRegistered,
139 UnableToCreateMetadata,
141}
142
143impl Display for ResourceRegistrationError {
144 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
145 match self {
146 ResourceRegistrationError::UnableToRegister => {
147 write!(f, "Unable to register the resource!")
148 }
149 ResourceRegistrationError::InvalidState => {
150 write!(f, "A resource was in invalid state!")
151 }
152 ResourceRegistrationError::AlreadyRegistered => {
153 write!(f, "A resource is already registered!")
154 }
155 ResourceRegistrationError::UnableToCreateMetadata => {
156 write!(
157 f,
158 "An error occurred on an attempt to write resource metadata!"
159 )
160 }
161 }
162 }
163}
164
165pub struct ResourceMoveContext {
167 relative_src_path: PathBuf,
168 relative_dest_path: PathBuf,
169 resource_uuid: Uuid,
170}
171
172#[derive(Debug)]
174pub enum ResourceMovementError {
175 Io(std::io::Error),
177 FileError(FileError),
179 AlreadyExist {
181 src_path: PathBuf,
183 dest_path: PathBuf,
185 },
186 DestinationPathIsInvalid {
188 src_path: PathBuf,
190 dest_path: PathBuf,
192 },
193 ResourceRegistryLocationUnknown {
195 resource_path: PathBuf,
197 },
198 NotInRegistry {
200 resource_path: PathBuf,
202 },
203 OutsideOfRegistry {
205 absolute_src_path: PathBuf,
207 absolute_dest_dir: PathBuf,
209 absolute_registry_dir: PathBuf,
211 },
212 NoPath(UntypedResource),
215}
216
217impl From<FileError> for ResourceMovementError {
218 fn from(value: FileError) -> Self {
219 Self::FileError(value)
220 }
221}
222
223impl From<std::io::Error> for ResourceMovementError {
224 fn from(value: Error) -> Self {
225 Self::Io(value)
226 }
227}
228
229impl Display for ResourceMovementError {
230 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
231 match self {
232 ResourceMovementError::Io(err) => {
233 write!(f, "Io error: {err}")
234 }
235 ResourceMovementError::FileError(err) => {
236 write!(f, "File error: {err}")
237 }
238 ResourceMovementError::AlreadyExist {
239 src_path,
240 dest_path,
241 } => {
242 write!(
243 f,
244 "Unable to move the {} resource, because the destination \
245 path {} points to an existing file!",
246 src_path.display(),
247 dest_path.display()
248 )
249 }
250 ResourceMovementError::DestinationPathIsInvalid {
251 src_path,
252 dest_path,
253 } => {
254 write!(
255 f,
256 "Unable to move the {} resource, because the destination \
257 path {} is invalid!",
258 src_path.display(),
259 dest_path.display()
260 )
261 }
262 ResourceMovementError::ResourceRegistryLocationUnknown { resource_path } => {
263 write!(
264 f,
265 "Unable to move the {} resource, because the registry location is unknown!",
266 resource_path.display()
267 )
268 }
269 ResourceMovementError::NotInRegistry { resource_path } => {
270 write!(
271 f,
272 "Unable to move the {} resource, because it is not in the registry!",
273 resource_path.display()
274 )
275 }
276 ResourceMovementError::OutsideOfRegistry {
277 absolute_src_path,
278 absolute_dest_dir,
279 absolute_registry_dir,
280 } => {
281 write!(
282 f,
283 "Unable to move the {} resource to {} path, because \
284 the new path is located outside the resource registry path {}!",
285 absolute_src_path.display(),
286 absolute_dest_dir.display(),
287 absolute_registry_dir.display()
288 )
289 }
290 ResourceMovementError::NoPath(resource) => {
291 write!(
292 f,
293 "Unable to move {} resource, because it does not have a \
294 file system path!",
295 resource.key()
296 )
297 }
298 }
299 }
300}
301
302#[derive(Debug)]
304pub enum FolderMovementError {
305 Io(std::io::Error),
307 FileError(FileError),
309 ResourceMovementError(ResourceMovementError),
311 NotInRegistry {
313 dest_dir: PathBuf,
315 },
316 HierarchyError {
318 src_dir: PathBuf,
320 dest_dir: PathBuf,
322 },
323}
324
325impl From<FileError> for FolderMovementError {
326 fn from(value: FileError) -> Self {
327 Self::FileError(value)
328 }
329}
330
331impl From<std::io::Error> for FolderMovementError {
332 fn from(value: Error) -> Self {
333 Self::Io(value)
334 }
335}
336
337impl From<ResourceMovementError> for FolderMovementError {
338 fn from(value: ResourceMovementError) -> Self {
339 Self::ResourceMovementError(value)
340 }
341}
342
343impl Display for FolderMovementError {
344 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
345 match self {
346 FolderMovementError::Io(err) => {
347 write!(f, "Io error: {err}")
348 }
349 FolderMovementError::FileError(err) => {
350 write!(f, "File error: {err}")
351 }
352 FolderMovementError::ResourceMovementError(err) => {
353 write!(f, "{err}")
354 }
355 FolderMovementError::NotInRegistry { dest_dir } => {
356 write!(
357 f,
358 "Unable to move the {} folder, because it is not in the registry!",
359 dest_dir.display()
360 )
361 }
362 FolderMovementError::HierarchyError { src_dir, dest_dir } => {
363 write!(
364 f,
365 "Trying to move a folder into one of its own sub-folders. \
366 Source folder is {}, destination folder is {}",
367 src_dir.display(),
368 dest_dir.display()
369 )
370 }
371 }
372 }
373}
374
375impl ResourceManager {
376 pub fn new(io: Arc<dyn ResourceIo>, task_pool: Arc<TaskPool>) -> Self {
378 Self {
379 state: Arc::new(Mutex::new(ResourceManagerState::new(io, task_pool))),
380 }
381 }
382
383 pub fn state(&self) -> MutexGuard<'_, ResourceManagerState> {
387 self.state.safe_lock()
388 }
389
390 pub fn try_get_state(&self, timeout: Duration) -> Option<MutexGuard<'_, ResourceManagerState>> {
393 self.state.try_lock_for(timeout)
394 }
395
396 pub fn resource_io(&self) -> Arc<dyn ResourceIo> {
398 let state = self.state();
399 state.resource_io.clone()
400 }
401
402 pub fn task_pool(&self) -> Arc<TaskPool> {
404 let state = self.state();
405 state.task_pool()
406 }
407
408 pub fn register_built_in_resource<T: TypedResourceData>(
410 &self,
411 resource: BuiltInResource<T>,
412 ) -> Option<UntypedBuiltInResource> {
413 self.state().register_built_in_resource(resource)
414 }
415
416 pub fn try_find<T>(&self, path: impl AsRef<Path>) -> Option<Resource<T>>
423 where
424 T: TypedResourceData,
425 {
426 let mut state = self.state();
427
428 let untyped = state.find(path.as_ref());
429
430 let data_type_uuid_matches = untyped
431 .type_uuid_non_blocking()
432 .is_some_and(|uuid| uuid == <T as TypeUuidProvider>::type_uuid());
433
434 if !data_type_uuid_matches {
435 let has_loader_for_extension = state
436 .loaders
437 .safe_lock()
438 .is_extension_matches_type::<T>(path.as_ref());
439
440 if !has_loader_for_extension {
441 return None;
442 }
443 }
444
445 Some(Resource {
446 untyped,
447 phantom: PhantomData::<T>,
448 })
449 }
450
451 pub fn find<T>(&self, path: impl AsRef<Path>) -> Resource<T>
464 where
465 T: TypedResourceData,
466 {
467 let path = path.as_ref();
468 let mut state = self.state();
469
470 let untyped = state.find(path);
471
472 let data_type_uuid_matches = untyped
473 .type_uuid_non_blocking()
474 .is_some_and(|uuid| uuid == <T as TypeUuidProvider>::type_uuid());
475
476 if !data_type_uuid_matches {
477 let has_loader_for_extension = state
478 .loaders
479 .safe_lock()
480 .is_extension_matches_type::<T>(path);
481
482 if !has_loader_for_extension {
483 panic!(
484 "Unable to get a resource of type {} from {path:?}! The resource has no \
485 associated loader for its extension and its actual data has some other \
486 data type!",
487 <T as TypeUuidProvider>::type_uuid(),
488 )
489 }
490 }
491
492 Resource {
493 untyped,
494 phantom: PhantomData::<T>,
495 }
496 }
497
498 pub fn find_uuid<T>(&self, uuid: Uuid) -> Resource<T>
508 where
509 T: TypedResourceData,
510 {
511 let mut state = self.state();
512
513 let untyped = state.find_uuid(uuid);
514
515 if let Some(type_uuid) = untyped.type_uuid_non_blocking() {
516 if type_uuid != <T as TypeUuidProvider>::type_uuid() {
517 panic!(
518 "Unable to get a resource of type {} from {uuid} UUID! Its actual data has some other \
519 data type with type UUID {type_uuid}!",
520 <T as TypeUuidProvider>::type_uuid(),
521 )
522 }
523 }
524
525 Resource {
526 untyped,
527 phantom: PhantomData::<T>,
528 }
529 }
530
531 pub fn request<T>(&self, path: impl AsRef<Path>) -> Resource<T>
570 where
571 T: TypedResourceData,
572 {
573 let path = path.as_ref();
574 let mut state = self.state();
575
576 let untyped = state.request(path);
577
578 let data_type_uuid_matches = untyped
579 .type_uuid_non_blocking()
580 .is_some_and(|uuid| uuid == <T as TypeUuidProvider>::type_uuid());
581
582 if !data_type_uuid_matches {
583 let has_loader_for_extension = state
584 .loaders
585 .safe_lock()
586 .is_extension_matches_type::<T>(path);
587
588 if !has_loader_for_extension {
589 panic!(
590 "Unable to get a resource of type {} from {path:?}! The resource has no \
591 associated loader for its extension and its actual data has some other \
592 data type!",
593 <T as TypeUuidProvider>::type_uuid(),
594 )
595 }
596 }
597
598 Resource {
599 untyped,
600 phantom: PhantomData::<T>,
601 }
602 }
603
604 pub fn request_resource<T>(&self, resource: &mut Resource<T>)
641 where
642 T: TypedResourceData,
643 {
644 let mut state = self.state();
645
646 state.request_resource(&mut resource.untyped);
647
648 if let Some(type_uuid) = resource.untyped.type_uuid_non_blocking() {
649 let needed_type_uuid = <T as TypeUuidProvider>::type_uuid();
650 if type_uuid != needed_type_uuid {
651 panic!(
652 "Unable to get a resource of type {needed_type_uuid} from resource UUID {}! The resource is \
653 loaded but its actual data has type {type_uuid}!",
654 resource.resource_uuid(),
655 );
656 }
657 }
658 }
659
660 pub fn add_resource<T>(&self, resource: &mut Resource<T>)
664 where
665 T: TypedResourceData,
666 {
667 let mut state = self.state();
668
669 state.add_resource(&mut resource.untyped);
670
671 if let Some(type_uuid) = resource.untyped.type_uuid_non_blocking() {
672 let needed_type_uuid = <T as TypeUuidProvider>::type_uuid();
673 if type_uuid != needed_type_uuid {
674 panic!(
675 "Unable to add a resource of type {needed_type_uuid} from resource UUID {}! The resource is \
676 loaded but its actual data has type {type_uuid}!",
677 resource.resource_uuid(),
678 );
679 }
680 }
681 }
682
683 pub fn try_request<T>(&self, path: impl AsRef<Path>) -> Option<Resource<T>>
690 where
691 T: TypedResourceData,
692 {
693 let mut state = self.state();
694 let untyped = state.request(path.as_ref());
695 if untyped
696 .type_uuid_non_blocking()
697 .is_some_and(|uuid| uuid == <T as TypeUuidProvider>::type_uuid())
698 || state
699 .loaders
700 .safe_lock()
701 .is_extension_matches_type::<T>(path.as_ref())
702 {
703 Some(Resource {
704 untyped,
705 phantom: PhantomData::<T>,
706 })
707 } else {
708 None
709 }
710 }
711
712 pub fn resource_path(&self, resource: impl AsRef<UntypedResource>) -> Option<PathBuf> {
718 self.state().resource_path(resource.as_ref())
719 }
720
721 pub fn uuid_to_resource_path(&self, resource_uuid: Uuid) -> Option<PathBuf> {
724 self.state().uuid_to_resource_path(resource_uuid)
725 }
726
727 pub fn request_untyped<P>(&self, path: P) -> UntypedResource
729 where
730 P: AsRef<Path>,
731 {
732 self.state().request(path)
733 }
734
735 pub fn update_or_load_registry(&self) {
741 self.state().update_or_load_registry();
742 }
743
744 pub fn add_loader<T: ResourceLoader>(&self, loader: T) -> Option<T> {
746 self.state().add_loader(loader)
747 }
748
749 pub fn register(
756 &self,
757 resource: UntypedResource,
758 path: impl AsRef<Path>,
759 ) -> Result<(), ResourceRegistrationError> {
760 self.state().register(resource, path)
761 }
762
763 pub fn is_built_in_resource(&self, resource: impl AsRef<UntypedResource>) -> bool {
765 self.state()
766 .built_in_resources
767 .is_built_in_resource(resource)
768 }
769
770 #[allow(clippy::await_holding_lock)]
772 pub async fn make_resource_move_context(
773 &self,
774 src_path: impl AsRef<Path>,
775 dest_path: impl AsRef<Path>,
776 overwrite_existing: bool,
777 ) -> Result<ResourceMoveContext, ResourceMovementError> {
778 self.state()
779 .make_resource_move_context(src_path, dest_path, overwrite_existing)
780 .await
781 }
782
783 #[allow(clippy::await_holding_lock)]
787 pub async fn can_resource_be_moved(
788 &self,
789 src_path: impl AsRef<Path>,
790 dest_path: impl AsRef<Path>,
791 overwrite_existing: bool,
792 ) -> bool {
793 self.state()
794 .can_resource_be_moved(src_path, dest_path, overwrite_existing)
795 .await
796 }
797
798 #[allow(clippy::await_holding_lock)]
802 pub async fn move_resource_by_path(
803 &self,
804 src_path: impl AsRef<Path>,
805 dest_path: impl AsRef<Path>,
806 overwrite_existing: bool,
807 ) -> Result<(), ResourceMovementError> {
808 self.state()
809 .move_resource_by_path(src_path, dest_path, overwrite_existing)
810 .await
811 }
812
813 pub async fn move_resource(
817 &self,
818 resource: impl AsRef<UntypedResource>,
819 new_path: impl AsRef<Path>,
820 overwrite_existing: bool,
821 ) -> Result<(), ResourceMovementError> {
822 let resource_path = self.resource_path(resource).ok_or_else(|| {
823 FileError::Custom(
824 "Cannot move the resource because it does not have a path!".to_string(),
825 )
826 })?;
827
828 self.move_resource_by_path(resource_path, new_path, overwrite_existing)
829 .await
830 }
831
832 pub async fn reload_resources(&self) {
836 let resources = self.state().reload_resources();
837 join_all(resources).await;
838 }
839
840 pub fn is_supported_resource(&self, path: &Path) -> bool {
842 self.state().is_supported_resource(path)
843 }
844
845 pub fn is_path_in_registry(&self, path: &Path) -> bool {
847 self.state().is_path_in_registry(path)
848 }
849
850 #[allow(clippy::await_holding_lock)]
852 pub async fn try_move_folder(
853 &self,
854 src_dir: &Path,
855 dest_dir: &Path,
856 overwrite_existing: bool,
857 ) -> Result<(), FolderMovementError> {
858 self.state()
859 .try_move_folder(src_dir, dest_dir, overwrite_existing)
860 .await
861 }
862
863 pub async fn resave_native_resources(&self) {
870 #[cfg(not(target_arch = "wasm32"))]
871 {
872 let paths = self.state().collect_native_resources();
873 let resources = join_all(paths.iter().map(|path| self.request_untyped(path))).await;
874 for (resource, path) in resources.into_iter().zip(paths) {
875 match resource {
876 Ok(resource) => match resource.save(&path) {
877 Ok(_) => {
878 info!("The {} resource was resaved successfully!", path.display());
879 }
880 Err(err) => {
881 err!(
882 "Unable to resave the {} resource. Reason: {:?}",
883 path.display(),
884 err
885 )
886 }
887 },
888 Err(err) => {
889 err!(
890 "Unable to resave the {} resource. Reason: {:?}",
891 path.display(),
892 err
893 );
894 }
895 }
896 }
897 }
898 }
899}
900
901impl ResourceManagerState {
902 pub(crate) fn new(io: Arc<dyn ResourceIo>, task_pool: Arc<TaskPool>) -> Self {
903 Self {
904 resources: Default::default(),
905 loaders: Default::default(),
906 event_broadcaster: Default::default(),
907 constructors_container: Default::default(),
908 watcher: None,
909 built_in_resources: Default::default(),
910 resource_registry: Arc::new(Mutex::new(ResourceRegistry::new(io.clone()))),
911 task_pool,
912 resource_io: io,
913 }
914 }
915
916 pub fn update_or_load_registry(&self) {
922 let resource_io = self.resource_io.clone();
923 let resource_registry = self.resource_registry.clone();
924 #[allow(unused_variables)]
925 let excluded_folders = resource_registry.safe_lock().excluded_folders.clone();
926 let registry_status = resource_registry.safe_lock().status_flag();
927 registry_status.mark_as_loading();
928 #[allow(unused_variables)]
929 let task_loaders = self.loaders.clone();
930 let path = resource_registry.safe_lock().path().to_path_buf();
931
932 #[cfg(not(any(target_arch = "wasm32", target_os = "android")))]
934 fyrox_core::futures::executor::block_on(async move {
935 let new_data =
936 ResourceRegistry::scan(resource_io.clone(), task_loaders, &path, excluded_folders)
937 .await;
938 let mut registry_lock = resource_registry.safe_lock();
939 registry_lock.modify().set_container(new_data);
940 registry_status.mark_as_loaded();
941 });
942
943 #[cfg(any(target_arch = "wasm32", target_os = "android"))]
944 self.task_pool.spawn_task(async move {
945 use crate::registry::RegistryContainerExt;
946 info!("Trying to load or update the registry at {path:?}...");
948 match crate::registry::RegistryContainer::load_from_file(&path, &*resource_io).await {
949 Ok(registry) => {
950 let mut registry_lock = resource_registry.safe_lock();
951 registry_lock.modify().set_container(registry);
952 info!("Resource registry was loaded from {path:?} successfully!");
953 }
954 Err(error) => {
955 err!("Unable to load resource registry! Reason: {error}.");
956 }
957 };
958 registry_status.mark_as_loaded();
959 });
960 }
961
962 pub fn collect_native_resources(&self) -> Vec<PathBuf> {
965 let loaders = self.loaders.safe_lock();
966 let registry = self.resource_registry.safe_lock();
967 let mut paths = Vec::new();
968 for entry in registry.inner().values() {
969 let ext = some_or_continue!(entry.extension().and_then(|ext| ext.to_str()));
970 for loader in loaders.iter() {
971 if loader.is_native_extension(ext) {
972 paths.push(entry.clone());
973 }
974 }
975 }
976 paths
977 }
978
979 pub fn task_pool(&self) -> Arc<TaskPool> {
981 self.task_pool.clone()
982 }
983
984 pub fn set_resource_io(&mut self, resource_io: Arc<dyn ResourceIo>) {
987 self.resource_io = resource_io;
988 }
989
990 pub fn set_watcher(&mut self, watcher: Option<FileSystemWatcher>) {
995 self.watcher = watcher;
996 }
997
998 pub fn count_registered_resources(&self) -> usize {
1000 self.resources.len()
1001 }
1002
1003 pub fn loading_progress(&self) -> usize {
1008 let registered = self.count_registered_resources();
1009 if registered > 0 {
1010 self.count_loaded_resources() * 100 / registered
1011 } else {
1012 100
1013 }
1014 }
1015
1016 fn try_get_event(&self) -> Option<Event> {
1017 self.watcher.as_ref()?.try_get_event()
1018 }
1019
1020 pub fn process_filesystem_events(&mut self) {
1024 let mut modified_files = FxHashSet::default();
1025 while let Some(mut evt) = self.try_get_event() {
1026 if evt.need_rescan() {
1027 info!("Filesystem watcher has forced a rescan!");
1028 self.update_or_load_registry();
1029 self.reload_resources();
1030 } else {
1031 use notify::event::{CreateKind, ModifyKind, RemoveKind, RenameMode};
1032 use notify::EventKind;
1033 match evt.kind {
1034 EventKind::Create(CreateKind::Any | CreateKind::File) => {
1035 self.on_create_event(evt.paths.first())
1036 }
1037 EventKind::Modify(ModifyKind::Name(RenameMode::From)) => {
1038 self.on_remove_event(evt.paths.first())
1039 }
1040 EventKind::Modify(ModifyKind::Name(RenameMode::To)) => {
1041 self.on_create_event(evt.paths.first())
1042 }
1043 EventKind::Modify(ModifyKind::Name(RenameMode::Both)) => {
1044 self.on_remove_event(evt.paths.first());
1045 self.on_create_event(evt.paths.get(1));
1046 }
1047 EventKind::Modify(ModifyKind::Any | ModifyKind::Data(_)) => {
1048 let path = evt.paths.get_mut(0).map(std::mem::take);
1049 modified_files.insert(path);
1050 }
1051 EventKind::Remove(RemoveKind::Any | RemoveKind::File) => {
1052 self.on_remove_event(evt.paths.first())
1053 }
1054 _ => (),
1055 }
1056 }
1057 }
1058 for path in modified_files {
1059 self.on_file_content_event(path.as_ref())
1060 }
1061 }
1062
1063 fn on_create_event(&mut self, path: Option<&PathBuf>) {
1064 let path = some_or_return!(path);
1065 let mut relative_path = ok_or_return!(fyrox_core::make_relative_path(path));
1066 let ext = some_or_return!(relative_path.extension());
1067 let mut registry = self.resource_registry.safe_lock();
1068 if registry
1069 .excluded_folders
1070 .iter()
1071 .any(|folder| relative_path.starts_with(folder))
1072 {
1073 return;
1074 }
1075 if !block_on(self.resource_io.exists(&relative_path)) {
1078 return;
1079 }
1080 if ext == ResourceMetadata::EXTENSION {
1081 relative_path.set_extension("");
1083 match registry.modify().read_metadata(relative_path.clone()) {
1084 Ok(RegistryUpdate {
1085 changed,
1086 value: metadata,
1087 }) => {
1088 if changed {
1089 info!(
1090 "The resource {relative_path:?} was registered successfully with {} id from a newly created meta file!",
1091 metadata.resource_id
1092 )
1093 }
1094 }
1095 Err(err) => {
1096 err!(
1097 "Unable to read the metadata for resource {relative_path:?}. Reason: {err}",
1098 )
1099 }
1100 }
1101 } else if self
1102 .loaders
1103 .safe_lock()
1104 .is_supported_resource(&relative_path)
1105 {
1106 Self::on_new_resource_file(registry.modify(), relative_path.clone());
1107 drop(registry);
1108 if self.try_reload_resource_from_path(&relative_path) {
1109 info!(
1110 "File {relative_path:?} was created, trying to reload a respective resource...",
1111 );
1112 }
1113 }
1114 }
1115 fn on_new_resource_file(mut registry: ResourceRegistryRefMut<'_>, relative_path: PathBuf) {
1118 match registry.read_metadata(relative_path.clone()) {
1119 Ok(RegistryUpdate {
1120 changed,
1121 value: metadata,
1122 }) => {
1123 if changed {
1124 info!(
1125 "The newly created resource {relative_path:?} was registered successfully with {} id from the meta file!",
1126 metadata.resource_id
1127 )
1128 }
1129 }
1130 Err(_) => {
1131 let uuid = Uuid::new_v4();
1132 match registry.write_metadata(uuid, relative_path.clone()) {
1133 Ok(RegistryUpdate {
1134 changed,
1135 value: old_path,
1136 }) => {
1137 assert!(old_path.is_none());
1138 if changed {
1139 info!("The newly created resource {relative_path:?} was registered successfully with new id: {uuid}");
1140 }
1141 }
1142 Err(err) => {
1143 err!("Unable to register the resource {relative_path:?}. Reason: {err}")
1144 }
1145 }
1146 }
1147 }
1148 }
1149 fn on_remove_event(&mut self, path: Option<&PathBuf>) {
1150 let path = some_or_return!(path);
1151 let mut relative_path = ok_or_return!(fyrox_core::make_relative_path(path));
1152 let ext = some_or_return!(relative_path.extension());
1153 if ext != ResourceMetadata::EXTENSION {
1155 return;
1156 }
1157 if block_on(self.resource_io.exists(&relative_path)) {
1161 return;
1162 }
1163
1164 let mut registry = self.resource_registry.safe_lock();
1165 if registry
1166 .excluded_folders
1167 .iter()
1168 .any(|folder| relative_path.starts_with(folder))
1169 {
1170 return;
1171 }
1172 relative_path.set_extension("");
1174 if !block_on(self.resource_io.exists(&relative_path)) {
1176 return;
1177 }
1178 let uuid = match registry.path_to_uuid(&relative_path) {
1180 Some(uuid) => {
1181 info!("The meta file for {relative_path:?} was removed, but its UUID is still in memory: {uuid}");
1182 uuid
1183 }
1184 None => {
1185 info!("The meta file for {relative_path:?} was removed and its UUID is lost!");
1186 Uuid::new_v4()
1187 }
1188 };
1189 let result = registry
1190 .modify()
1191 .write_metadata(uuid, relative_path.clone());
1192 match result {
1193 Ok(RegistryUpdate { changed, .. }) => {
1194 if changed {
1195 info!(
1196 "The resource {relative_path:?} was registered successfully with {uuid} id after its meta file was removed!",
1197 )
1198 } else {
1199 info!(
1200 "The meta file for resource {relative_path:?} was recreated with {uuid} id!",
1201 )
1202 }
1203 }
1204 Err(err) => {
1205 err!(
1206 "Unable to register the resource {relative_path:?} after its meta file was removed. Reason: {err}",
1207 )
1208 }
1209 }
1210 }
1211 fn on_file_content_event(&mut self, path: Option<&PathBuf>) {
1212 let path = some_or_return!(path);
1213 let mut relative_path = ok_or_return!(fyrox_core::make_relative_path(path));
1214 let ext = some_or_return!(relative_path.extension());
1215 let mut registry = self.resource_registry.safe_lock();
1216 if registry
1217 .excluded_folders
1218 .iter()
1219 .any(|folder| relative_path.starts_with(folder))
1220 {
1221 return;
1222 }
1223 if !block_on(self.resource_io.exists(&relative_path)) {
1226 return;
1227 }
1228
1229 if ext == ResourceMetadata::EXTENSION {
1230 relative_path.set_extension("");
1232 match registry.modify().read_metadata(relative_path.clone()) {
1233 Ok(RegistryUpdate {
1234 changed,
1235 value: metadata,
1236 }) => {
1237 if changed {
1238 info!(
1239 "The resource {relative_path:?} was registered successfully with {} id after meta file modification",
1240 metadata.resource_id
1241 )
1242 }
1243 }
1244 Err(err) => {
1245 err!(
1246 "Unable to read the metadata for resource {relative_path:?} after meta file modification. Reason: {err}",
1247 )
1248 }
1249 }
1250 } else if self
1251 .loaders
1252 .safe_lock()
1253 .is_supported_resource(&relative_path)
1254 {
1255 drop(registry);
1256 if self.try_reload_resource_from_path(&relative_path) {
1257 info!(
1258 "File {relative_path:?} was changed, trying to reload a respective resource...",
1259 );
1260 }
1261 }
1262 }
1263
1264 pub fn update(&mut self, dt: f32) {
1272 self.resources.retain_mut(|resource| {
1273 if resource.value.use_count() <= 1 {
1277 resource.time_to_live -= dt;
1278 if resource.time_to_live <= 0.0 {
1279 let registry = self.resource_registry.safe_lock();
1280 let resource_uuid = resource.resource_uuid();
1281 if let Some(path) = registry.uuid_to_path(resource_uuid) {
1282 info!("Resource {path:?} destroyed because it is not used anymore!",);
1283 self.event_broadcaster
1284 .broadcast(ResourceEvent::Removed(path.to_path_buf()));
1285 }
1286
1287 false
1288 } else {
1289 true
1291 }
1292 } else {
1293 resource.time_to_live = DEFAULT_RESOURCE_LIFETIME;
1295
1296 true
1298 }
1299 });
1300 self.process_filesystem_events();
1301 }
1302
1303 fn add_resource_and_notify(&mut self, resource: UntypedResource) {
1304 self.event_broadcaster
1305 .broadcast(ResourceEvent::Added(resource.clone()));
1306
1307 self.resources.push(TimedEntry {
1308 value: resource,
1309 time_to_live: DEFAULT_RESOURCE_LIFETIME,
1310 });
1311 }
1312
1313 pub fn find_by_uuid(&self, uuid: Uuid) -> Option<&UntypedResource> {
1319 self.resources
1320 .iter()
1321 .find(|entry| entry.value.resource_uuid() == uuid)
1322 .map(|entry| &entry.value)
1323 }
1324
1325 pub fn find_by_path(&self, path: &Path) -> Option<&UntypedResource> {
1331 let registry = self.resource_registry.safe_lock();
1332 self.resources.iter().find_map(|entry| {
1333 if registry.uuid_to_path(entry.resource_uuid()) == Some(path) {
1334 return Some(&entry.value);
1335 }
1336 None
1337 })
1338 }
1339
1340 pub fn len(&self) -> usize {
1342 self.resources.len()
1343 }
1344
1345 pub fn is_empty(&self) -> bool {
1347 self.resources.is_empty()
1348 }
1349
1350 pub fn iter(&self) -> impl Iterator<Item = &UntypedResource> {
1352 self.resources.iter().map(|entry| &entry.value)
1353 }
1354
1355 pub fn destroy_unused_resources(&mut self) {
1357 self.resources
1358 .retain(|resource| resource.value.use_count() > 1);
1359 }
1360
1361 pub fn count_pending_resources(&self) -> usize {
1363 self.resources.iter().filter(|r| r.is_loading()).count()
1364 }
1365
1366 pub fn count_loaded_resources(&self) -> usize {
1368 self.resources.iter().filter(|r| r.is_ok()).count()
1369 }
1370
1371 pub fn resources(&self) -> Vec<UntypedResource> {
1373 self.resources.iter().map(|t| t.value.clone()).collect()
1374 }
1375
1376 pub fn register_built_in_resource<T: TypedResourceData>(
1378 &mut self,
1379 resource: BuiltInResource<T>,
1380 ) -> Option<UntypedBuiltInResource> {
1381 self.built_in_resources.add(resource)
1382 }
1383
1384 pub fn find_uuid(&mut self, uuid: Uuid) -> UntypedResource {
1390 if let Some(built_in_resource) = self.built_in_resources.find_by_uuid(uuid) {
1391 return built_in_resource.resource.clone();
1392 }
1393
1394 if let Some(existing) = self.find_by_uuid(uuid) {
1395 existing.clone()
1396 } else {
1397 let resource = UntypedResource::new_unloaded(uuid);
1398 self.add_resource_and_notify(resource.clone());
1399 resource
1400 }
1401 }
1402
1403 pub fn find<P>(&mut self, path: P) -> UntypedResource
1407 where
1408 P: AsRef<Path>,
1409 {
1410 if let Some(built_in_resource) = self.built_in_resources.get(path.as_ref()) {
1411 return built_in_resource.resource.clone();
1412 }
1413
1414 let path = ResourceRegistry::normalize_path(path);
1415
1416 if let Some(existing) = self.find_by_resource_path(&path) {
1417 existing.clone()
1418 } else {
1419 let mut registry = self.resource_registry.safe_lock();
1420 let uuid = if let Some(uuid) = registry.path_to_uuid(&path) {
1421 uuid
1422 } else {
1423 let uuid = Uuid::new_v4();
1424 registry.modify().register(uuid, path);
1425 uuid
1426 };
1427 drop(registry);
1428 let resource = UntypedResource::new_unloaded(uuid);
1429 self.add_resource_and_notify(resource.clone());
1430 resource
1431 }
1432 }
1433
1434 pub fn request<P>(&mut self, path: P) -> UntypedResource
1436 where
1437 P: AsRef<Path>,
1438 {
1439 if let Some(built_in_resource) = self.built_in_resources.get(path.as_ref()) {
1440 return built_in_resource.resource.clone();
1441 }
1442
1443 let path = ResourceRegistry::normalize_path(path);
1444
1445 self.find_or_load(path)
1446 }
1447
1448 pub fn request_uuid(&mut self, uuid: Uuid) -> UntypedResource {
1450 let mut resource = uuid.into();
1451 self.request_resource(&mut resource);
1452 resource
1453 }
1454
1455 fn find_by_resource_path(&self, path_to_search: &Path) -> Option<&UntypedResource> {
1457 let registry = self.resource_registry.safe_lock();
1458 self.resources
1459 .iter()
1460 .find(move |entry| registry.uuid_to_path(entry.resource_uuid()) == Some(path_to_search))
1461 .map(|entry| &entry.value)
1462 }
1463
1464 fn find_or_load(&mut self, path: PathBuf) -> UntypedResource {
1470 match self.find_by_resource_path(&path) {
1471 Some(existing) => existing.clone(),
1472 None => self.load_resource(path),
1473 }
1474 }
1475
1476 fn load_resource(&mut self, path: PathBuf) -> UntypedResource {
1477 let mut registry = self.resource_registry.safe_lock();
1478 let uuid = if let Some(uuid) = registry.path_to_uuid(&path) {
1479 uuid
1480 } else {
1481 let uuid = Uuid::new_v4();
1482 registry.modify().register(uuid, path);
1483 uuid
1484 };
1485 drop(registry);
1486 let resource = UntypedResource::new_pending(uuid, ResourceKind::External);
1487 self.add_resource_and_notify(resource.clone());
1488 self.spawn_loading_task(resource.clone(), false);
1489 resource
1490 }
1491
1492 fn spawn_loading_task(&self, mut resource: UntypedResource, reload: bool) {
1495 let event_broadcaster = self.event_broadcaster.clone();
1496 let loaders = self.loaders.clone();
1497 let registry = self.resource_registry.clone();
1498 let io = self.resource_io.clone();
1499 let registry_status = registry.safe_lock().status_flag();
1500
1501 self.task_pool.spawn_task(async move {
1502 let registry_status = registry_status.await;
1504 if registry_status == ResourceRegistryStatus::Unknown {
1505 resource.commit_error(
1506 PathBuf::default(),
1507 LoadError::new("The resource registry is unavailable!".to_string()),
1508 );
1509 return;
1510 }
1511
1512 let Some(path) = registry
1513 .safe_lock()
1514 .uuid_to_path(resource.resource_uuid())
1515 .map(|p| p.to_path_buf())
1516 else {
1517 let error = format!(
1518 "Resource {} failed to load. The path was not found \
1519 in the registry!",
1520 resource.resource_uuid(),
1521 );
1522
1523 resource.commit_error(PathBuf::default(), error);
1524 return;
1525 };
1526
1527 let loader_future = loaders
1529 .safe_lock()
1530 .loader_for(&path)
1531 .map(|loader| loader.load(path.clone(), io));
1532
1533 if let Some(loader_future) = loader_future {
1534 match loader_future.await {
1535 Ok(data) => {
1536 let data = data.0;
1537
1538 let mut header = resource.lock();
1539
1540 assert!(header.kind.is_external());
1541
1542 header.state.commit(ResourceState::Ok {
1543 data: ResourceDataWrapper(data),
1544 });
1545
1546 drop(header);
1547
1548 event_broadcaster.broadcast_loaded_or_reloaded(resource, reload);
1549
1550 Log::info(format!(
1551 "Resource {} was loaded successfully!",
1552 path.display()
1553 ));
1554 }
1555 Err(error) => {
1556 if reload {
1557 if resource.is_ok() {
1558 info!("Resource {path:?} failed to reload, keeping the existing version. Reason: {error}");
1559 }
1560 else
1561 {
1562 info!("Resource {path:?} failed to reload. Reason: {error}");
1563 resource.commit_error(path.to_path_buf(), error);
1564 }
1565 }
1566 else
1567 {
1568 info!("Resource {path:?} failed to load. Reason: {error}");
1569 resource.commit_error(path.to_path_buf(), error);
1570 }
1571 }
1572 }
1573 } else {
1574 let error = format!("There's no resource loader for {path:?} resource!",);
1575 resource.commit_error(path, error);
1576 }
1577 });
1578 }
1579
1580 pub fn resource_path(&self, resource: &UntypedResource) -> Option<PathBuf> {
1591 self.uuid_to_resource_path(resource.resource_uuid())
1592 }
1593
1594 pub fn uuid_to_resource_path(&self, resource_uuid: Uuid) -> Option<PathBuf> {
1603 if let Some(path) = self
1604 .resource_registry
1605 .safe_lock()
1606 .uuid_to_path_buf(resource_uuid)
1607 {
1608 Some(path)
1609 } else {
1610 self.built_in_resources
1611 .find_by_uuid(resource_uuid)
1612 .map(|built_in_resource| built_in_resource.id.clone())
1613 }
1614 }
1615
1616 pub fn add_loader<T: ResourceLoader>(&self, loader: T) -> Option<T> {
1618 self.loaders.safe_lock().set(loader)
1619 }
1620
1621 pub fn request_resource(&mut self, resource: &mut UntypedResource) {
1624 if let Some(r) = self.find_by_uuid(resource.resource_uuid()) {
1625 *resource = r.clone();
1628 if resource.is_unloaded() {
1629 resource.make_pending();
1631 self.spawn_loading_task(resource.clone(), false);
1632 }
1633 } else if let Some(r) = self
1634 .built_in_resources
1635 .find_by_uuid(resource.resource_uuid())
1636 {
1637 *resource = r.resource.clone();
1640 } else if resource.is_ok() || resource.is_embedded() {
1641 self.add_resource_and_notify(resource.clone());
1644 } else {
1645 resource.make_pending();
1648 self.add_resource_and_notify(resource.clone());
1649 self.spawn_loading_task(resource.clone(), false);
1650 }
1651 }
1652
1653 pub fn add_resource(&mut self, resource: &mut UntypedResource) {
1657 if let Some(r) = self.find_by_uuid(resource.resource_uuid()) {
1658 *resource = r.clone();
1661 } else if let Some(r) = self
1662 .built_in_resources
1663 .find_by_uuid(resource.resource_uuid())
1664 {
1665 *resource = r.resource.clone();
1668 } else {
1669 self.add_resource_and_notify(resource.clone());
1671 }
1672 }
1673
1674 pub fn register(
1681 &mut self,
1682 resource: UntypedResource,
1683 path: impl AsRef<Path>,
1684 ) -> Result<(), ResourceRegistrationError> {
1685 let resource_uuid = resource.resource_uuid();
1686 if self.find_by_uuid(resource_uuid).is_some() {
1687 return Err(ResourceRegistrationError::AlreadyRegistered);
1688 }
1689 let path = ResourceRegistry::normalize_path(path);
1690 {
1691 let mut header = resource.lock();
1692 header.kind.make_external();
1693 let mut registry = self.resource_registry.safe_lock();
1694 let mut ctx = registry.modify();
1695 if ctx.write_metadata(resource_uuid, path).is_err() {
1696 return Err(ResourceRegistrationError::UnableToCreateMetadata);
1697 }
1698 }
1699 self.add_resource_and_notify(resource);
1700 Ok(())
1701 }
1702
1703 pub fn reload_resource(&mut self, resource: UntypedResource) {
1706 if self.built_in_resources.is_built_in_resource(&resource) {
1707 return;
1708 }
1709 let mut header = resource.lock();
1710 if !header.state.is_loading() {
1711 if !header.state.is_ok() {
1712 header.state.switch_to_pending_state();
1713 }
1714 drop(header);
1715 self.spawn_loading_task(resource, true)
1716 }
1717 }
1718
1719 pub fn reload_resources(&mut self) -> Vec<UntypedResource> {
1722 let resources = self
1723 .resources
1724 .iter()
1725 .map(|r| r.value.clone())
1726 .collect::<Vec<_>>();
1727
1728 for resource in resources.iter().cloned() {
1729 self.reload_resource(resource);
1730 }
1731
1732 resources
1733 }
1734
1735 pub fn get_wait_context(&self) -> ResourceWaitContext {
1737 ResourceWaitContext {
1738 resources: self
1739 .resources
1740 .iter()
1741 .map(|e| e.value.clone())
1742 .collect::<Vec<_>>(),
1743 }
1744 }
1745
1746 pub fn try_reload_resource_from_path(&mut self, path: &Path) -> bool {
1750 if !self.loaders.safe_lock().is_supported_resource(path) {
1752 return false;
1753 }
1754
1755 let Some(resource) = self.find_by_resource_path(path) else {
1756 return false;
1757 };
1758 let header = resource.lock();
1759 if header.state.is_loading() {
1760 return false;
1761 }
1762 drop(header);
1763 self.reload_resource(resource.clone());
1764 true
1765 }
1766
1767 #[allow(clippy::await_holding_lock)]
1769 pub async fn make_resource_move_context(
1770 &self,
1771 src_path: impl AsRef<Path>,
1772 dest_path: impl AsRef<Path>,
1773 overwrite_existing: bool,
1774 ) -> Result<ResourceMoveContext, ResourceMovementError> {
1775 let src_path = src_path.as_ref();
1776 let dest_path = dest_path.as_ref();
1777
1778 let relative_src_path = fyrox_core::make_relative_path(src_path)?;
1779 let relative_dest_path = fyrox_core::make_relative_path(dest_path)?;
1780
1781 if let Some(file_stem) = relative_dest_path.file_stem() {
1782 if !self.resource_io.is_valid_file_name(file_stem) {
1783 return Err(ResourceMovementError::DestinationPathIsInvalid {
1784 src_path: relative_src_path.clone(),
1785 dest_path: relative_dest_path.clone(),
1786 });
1787 }
1788 }
1789
1790 if !overwrite_existing && self.resource_io.exists(&relative_dest_path).await {
1791 return Err(ResourceMovementError::AlreadyExist {
1792 src_path: relative_src_path.clone(),
1793 dest_path: relative_dest_path.clone(),
1794 });
1795 }
1796
1797 let registry_lock_guard = self.resource_registry.safe_lock();
1798 let absolute_registry_dir = if let Some(directory) = registry_lock_guard.directory() {
1799 fyrox_core::replace_slashes(self.resource_io.canonicalize_path(directory).await?)
1800 } else {
1801 return Err(ResourceMovementError::ResourceRegistryLocationUnknown {
1802 resource_path: relative_src_path.clone(),
1803 });
1804 };
1805 let resource_uuid = registry_lock_guard
1806 .path_to_uuid(&relative_src_path)
1807 .ok_or_else(|| ResourceMovementError::NotInRegistry {
1808 resource_path: relative_src_path.clone(),
1809 })?;
1810
1811 let relative_dest_dir = relative_dest_path.parent().unwrap_or(Path::new("."));
1812 let absolute_dest_dir = fyrox_core::replace_slashes(
1813 self.resource_io
1814 .canonicalize_path(relative_dest_dir)
1815 .await?,
1816 );
1817
1818 let absolute_src_path = fyrox_core::replace_slashes(
1819 self.resource_io
1820 .canonicalize_path(&relative_src_path)
1821 .await?,
1822 );
1823 if !absolute_dest_dir.starts_with(&absolute_registry_dir) {
1824 return Err(ResourceMovementError::OutsideOfRegistry {
1825 absolute_src_path,
1826 absolute_dest_dir,
1827 absolute_registry_dir,
1828 });
1829 }
1830
1831 drop(registry_lock_guard);
1832
1833 Ok(ResourceMoveContext {
1834 relative_src_path,
1835 relative_dest_path,
1836 resource_uuid,
1837 })
1838 }
1839
1840 pub async fn can_resource_be_moved(
1844 &self,
1845 src_path: impl AsRef<Path>,
1846 dest_path: impl AsRef<Path>,
1847 overwrite_existing: bool,
1848 ) -> bool {
1849 self.make_resource_move_context(src_path, dest_path, overwrite_existing)
1850 .await
1851 .is_ok()
1852 }
1853
1854 pub async fn move_resource_by_path(
1858 &self,
1859 src_path: impl AsRef<Path>,
1860 dest_path: impl AsRef<Path>,
1861 overwrite_existing: bool,
1862 ) -> Result<(), ResourceMovementError> {
1863 let ResourceMoveContext {
1864 relative_src_path,
1865 relative_dest_path,
1866
1867 resource_uuid,
1868 } = self
1869 .make_resource_move_context(src_path, dest_path, overwrite_existing)
1870 .await?;
1871
1872 self.resource_io
1874 .move_file(&relative_src_path, &relative_dest_path)
1875 .await?;
1876
1877 let current_path = self
1878 .resource_registry
1879 .safe_lock()
1880 .modify()
1881 .register(resource_uuid, relative_dest_path.to_path_buf());
1882 assert_eq!(current_path.value.as_ref(), Some(&relative_src_path));
1883
1884 let options_path = append_extension(&relative_src_path, OPTIONS_EXTENSION);
1885 if self.resource_io.exists(&options_path).await {
1886 let new_options_path = append_extension(&relative_dest_path, OPTIONS_EXTENSION);
1887 self.resource_io
1888 .move_file(&options_path, &new_options_path)
1889 .await?;
1890 }
1891
1892 let metadata_path = append_extension(&relative_src_path, ResourceMetadata::EXTENSION);
1893 if self.resource_io.exists(&metadata_path).await {
1894 let new_metadata_path =
1895 append_extension(&relative_dest_path, ResourceMetadata::EXTENSION);
1896 self.resource_io
1897 .move_file(&metadata_path, &new_metadata_path)
1898 .await?;
1899 }
1900
1901 Ok(())
1902 }
1903
1904 pub async fn move_resource(
1908 &self,
1909 resource: impl AsRef<UntypedResource>,
1910 new_path: impl AsRef<Path>,
1911 overwrite_existing: bool,
1912 ) -> Result<(), ResourceMovementError> {
1913 let resource_path = self.resource_path(resource.as_ref()).ok_or_else(|| {
1914 FileError::Custom(
1915 "Cannot move the resource because it does not have a path!".to_string(),
1916 )
1917 })?;
1918
1919 self.move_resource_by_path(resource_path, new_path, overwrite_existing)
1920 .await
1921 }
1922
1923 pub fn is_supported_resource(&self, path: &Path) -> bool {
1925 let ext = some_or_return!(path.extension(), false);
1926 let ext = some_or_return!(ext.to_str(), false);
1927
1928 self.loaders
1929 .safe_lock()
1930 .iter()
1931 .any(|loader| loader.supports_extension(ext))
1932 }
1933
1934 pub fn is_path_in_registry(&self, path: &Path) -> bool {
1936 let registry = self.resource_registry.safe_lock();
1937 if let Some(registry_directory) = registry.directory() {
1938 if let Ok(canonical_registry_path) = registry_directory.canonicalize() {
1939 if let Ok(canonical_path) = path.canonicalize() {
1940 return canonical_path.starts_with(canonical_registry_path);
1941 }
1942 }
1943 }
1944 false
1945 }
1946
1947 pub async fn try_move_folder(
1949 &self,
1950 src_dir: &Path,
1951 dest_dir: &Path,
1952 overwrite_existing: bool,
1953 ) -> Result<(), FolderMovementError> {
1954 if dest_dir.starts_with(src_dir) {
1955 return Err(FolderMovementError::HierarchyError {
1956 src_dir: src_dir.to_path_buf(),
1957 dest_dir: dest_dir.to_path_buf(),
1958 });
1959 }
1960
1961 if !self.is_path_in_registry(dest_dir) {
1964 return Err(FolderMovementError::NotInRegistry {
1965 dest_dir: dest_dir.to_path_buf(),
1966 });
1967 }
1968
1969 let mut what_where_stack = vec![(src_dir.to_path_buf(), dest_dir.to_path_buf())];
1974 while let Some((src_dir, target_dir)) = what_where_stack.pop() {
1975 let src_dir_name = some_or_continue!(src_dir.file_name());
1976
1977 let target_sub_dir = target_dir.join(src_dir_name);
1978 if !self.resource_io.exists(&target_sub_dir).await {
1979 std::fs::create_dir(&target_sub_dir)?;
1980 }
1981
1982 let target_sub_dir_normalized = ok_or_continue!(make_relative_path(&target_sub_dir));
1983
1984 for path in self.resource_io.walk_directory(&src_dir, 1).await? {
1985 if path.is_file() {
1986 let file_name = some_or_continue!(path.file_name());
1987 if self.is_supported_resource(&path) {
1988 let dest_path = target_sub_dir_normalized.join(file_name);
1989 self.move_resource_by_path(path, &dest_path, overwrite_existing)
1990 .await?;
1991 }
1992 } else if path.is_dir() && path != src_dir {
1993 what_where_stack.push((path, target_sub_dir.clone()));
1996 }
1997 }
1998 }
1999
2000 std::fs::remove_dir_all(src_dir)?;
2001
2002 Ok(())
2003 }
2004}
2005
2006#[cfg(test)]
2007mod test {
2008 use super::*;
2009 use crate::io::FsResourceIo;
2010 use crate::{
2011 loader::{BoxedLoaderFuture, LoaderPayload, ResourceLoader},
2012 ResourceData,
2013 };
2014 use fyrox_core::{
2015 reflect::prelude::*,
2016 uuid::{uuid, Uuid},
2017 visitor::{Visit, VisitResult, Visitor},
2018 TypeUuidProvider,
2019 };
2020 use std::{error::Error, fs::File, time::Duration};
2021
2022 #[derive(Debug, Default, Clone, Reflect, Visit)]
2023 struct Stub {}
2024
2025 impl TypeUuidProvider for Stub {
2026 fn type_uuid() -> Uuid {
2027 uuid!("9d873ff4-3126-47e1-a492-7cd8e7168239")
2028 }
2029 }
2030
2031 impl ResourceData for Stub {
2032 fn type_uuid(&self) -> Uuid {
2033 <Self as TypeUuidProvider>::type_uuid()
2034 }
2035
2036 fn save(&mut self, _path: &Path) -> Result<(), Box<dyn Error>> {
2037 Err("Saving is not supported!".to_string().into())
2038 }
2039
2040 fn can_be_saved(&self) -> bool {
2041 false
2042 }
2043
2044 fn try_clone_box(&self) -> Option<Box<dyn ResourceData>> {
2045 Some(Box::new(self.clone()))
2046 }
2047 }
2048
2049 impl ResourceLoader for Stub {
2050 fn extensions(&self) -> &[&str] {
2051 &["txt"]
2052 }
2053
2054 fn data_type_uuid(&self) -> Uuid {
2055 <Stub as TypeUuidProvider>::type_uuid()
2056 }
2057
2058 fn load(&self, _path: PathBuf, _io: Arc<dyn ResourceIo>) -> BoxedLoaderFuture {
2059 Box::pin(async move { Ok(LoaderPayload::new(Stub::default())) })
2060 }
2061 }
2062
2063 fn new_resource_manager() -> ResourceManagerState {
2064 ResourceManagerState::new(Arc::new(FsResourceIo), Arc::new(Default::default()))
2065 }
2066
2067 fn remove_file_if_exists(path: &Path) -> std::io::Result<()> {
2068 match std::fs::remove_file(path) {
2069 Ok(()) => Ok(()),
2070 Err(e) => match e.kind() {
2071 std::io::ErrorKind::NotFound => Ok(()),
2072 _ => Err(e),
2073 },
2074 }
2075 }
2076
2077 #[test]
2078 fn resource_wait_context_is_all_loaded() {
2079 assert!(ResourceWaitContext::default().is_all_loaded());
2080
2081 let cx = ResourceWaitContext {
2082 resources: vec![
2083 UntypedResource::new_pending(Default::default(), ResourceKind::External),
2084 UntypedResource::new_load_error(
2085 ResourceKind::External,
2086 Default::default(),
2087 LoadError::default(),
2088 ),
2089 ],
2090 };
2091 assert!(!cx.is_all_loaded());
2092 }
2093
2094 #[test]
2095 fn resource_manager_state_new() {
2096 let state = new_resource_manager();
2097
2098 assert!(state.resources.is_empty());
2099 assert!(state.loaders.safe_lock().is_empty());
2100 assert!(state.built_in_resources.is_empty());
2101 assert!(state.constructors_container.is_empty());
2102 assert!(state.watcher.is_none());
2103 assert!(state.is_empty());
2104 }
2105
2106 #[test]
2107 fn resource_manager_state_set_watcher() {
2108 let mut state = new_resource_manager();
2109 assert!(state.watcher.is_none());
2110
2111 let path = PathBuf::from("test.txt");
2112 if File::create(path.clone()).is_ok() {
2113 let watcher = FileSystemWatcher::new(path.clone(), Duration::from_secs(1));
2114 state.set_watcher(watcher.ok());
2115 assert!(state.watcher.is_some());
2116 }
2117 }
2118
2119 #[test]
2120 fn resource_manager_state_push() {
2121 std::fs::create_dir_all("data").expect("Could not create data directory.");
2122 let mut state = new_resource_manager();
2123
2124 assert_eq!(state.count_loaded_resources(), 0);
2125 assert_eq!(state.count_pending_resources(), 0);
2126 assert_eq!(state.count_registered_resources(), 0);
2127 assert_eq!(state.len(), 0);
2128
2129 assert!(state
2130 .register(
2131 UntypedResource::new_pending(Default::default(), ResourceKind::External),
2132 "foo.bar",
2133 )
2134 .is_ok());
2135 assert!(state
2136 .register(
2137 UntypedResource::new_load_error(
2138 ResourceKind::External,
2139 Default::default(),
2140 LoadError::default()
2141 ),
2142 "foo.bar",
2143 )
2144 .is_ok());
2145 assert!(state
2146 .register(
2147 UntypedResource::new_ok(Uuid::new_v4(), Default::default(), Stub {}),
2148 "foo.bar",
2149 )
2150 .is_ok());
2151
2152 assert_eq!(state.count_registered_resources(), 3);
2153 assert_eq!(state.len(), 3);
2154 }
2155
2156 #[test]
2157 fn resource_manager_state_loading_progress() {
2158 let mut state = new_resource_manager();
2159
2160 assert_eq!(state.loading_progress(), 100);
2161
2162 state
2163 .register(
2164 UntypedResource::new_ok(Uuid::new_v4(), Default::default(), Stub {}),
2165 "foo.bar",
2166 )
2167 .unwrap();
2168
2169 assert_eq!(state.loading_progress(), 100);
2170 }
2171
2172 #[test]
2173 fn resource_manager_state_find() {
2174 let mut state = new_resource_manager();
2175
2176 let path = Path::new("foo.txt");
2177
2178 assert!(state.find_by_path(path).is_none());
2179
2180 let resource = UntypedResource::new_ok(Uuid::new_v4(), Default::default(), Stub {});
2181 state.register(resource.clone(), path).unwrap();
2182
2183 assert_eq!(state.find_by_path(path), Some(&resource));
2184 }
2185
2186 #[test]
2187 fn resource_manager_state_resources() {
2188 let mut state = new_resource_manager();
2189
2190 assert_eq!(state.resources(), Vec::new());
2191
2192 let r1 = UntypedResource::new_ok(Uuid::new_v4(), ResourceKind::External, Stub {});
2193 let r2 = UntypedResource::new_ok(Uuid::new_v4(), ResourceKind::External, Stub {});
2194 let r3 = UntypedResource::new_ok(Uuid::new_v4(), ResourceKind::External, Stub {});
2195 state.register(r1.clone(), "foo1.txt").unwrap();
2196 state.register(r2.clone(), "foo2.txt").unwrap();
2197 state.register(r3.clone(), "foo3.txt").unwrap();
2198
2199 assert_eq!(state.resources(), vec![r1.clone(), r2.clone(), r3.clone()]);
2200 assert!(state.iter().eq([&r1, &r2, &r3]));
2201 }
2202
2203 #[test]
2204 fn resource_manager_state_destroy_unused_resources() {
2205 let mut state = new_resource_manager();
2206
2207 state
2208 .register(
2209 UntypedResource::new_ok(Uuid::new_v4(), ResourceKind::External, Stub {}),
2210 "foo1.txt",
2211 )
2212 .unwrap();
2213 assert_eq!(state.len(), 1);
2214
2215 state.destroy_unused_resources();
2216 assert_eq!(state.len(), 0);
2217 }
2218
2219 #[test]
2220 fn resource_manager_state_request() {
2221 let mut state = new_resource_manager();
2222 let path = PathBuf::from("test.txt");
2223
2224 let resource = UntypedResource::new_ok(Uuid::new_v4(), ResourceKind::External, Stub {});
2225 state.register(resource.clone(), &path).unwrap();
2226
2227 let res = state.request(&path);
2228 assert_eq!(res, resource);
2229
2230 let res = state.request(path);
2231
2232 assert_eq!(res.kind(), ResourceKind::External);
2233 assert!(!res.is_loading());
2234 }
2235
2236 #[test]
2237 fn resource_manager_state_get_wait_context() {
2238 let mut state = new_resource_manager();
2239
2240 let resource = UntypedResource::new_ok(Uuid::new_v4(), ResourceKind::External, Stub {});
2241 state.add_resource_and_notify(resource.clone());
2242 let cx = state.get_wait_context();
2243
2244 assert!(cx.resources.eq(&vec![resource]));
2245 }
2246
2247 #[test]
2248 fn resource_manager_new() {
2249 let manager = ResourceManager::new(Arc::new(FsResourceIo), Arc::new(Default::default()));
2250
2251 assert!(manager.state.safe_lock().is_empty());
2252 assert!(manager.state().is_empty());
2253 }
2254
2255 #[test]
2256 fn resource_manager_register() {
2257 std::fs::create_dir_all("data").expect("Could not create data directory.");
2258 let manager = ResourceManager::new(Arc::new(FsResourceIo), Arc::new(Default::default()));
2259 let path = PathBuf::from("test.txt");
2260 let metapath = append_extension(&path, ResourceMetadata::EXTENSION);
2261 remove_file_if_exists(&metapath).unwrap();
2262
2263 let resource = UntypedResource::new_pending(Default::default(), ResourceKind::External);
2264 let res = manager.register(resource.clone(), path.clone());
2265 assert!(res.is_ok());
2266
2267 let metadata = block_on(ResourceMetadata::load_from_file_async(
2268 &metapath,
2269 &*manager.resource_io(),
2270 ))
2271 .expect("Reading meta file failed");
2272 assert_eq!(resource.resource_uuid(), metadata.resource_id);
2273
2274 let uuid = Uuid::new_v4();
2275 let resource = UntypedResource::new_ok(uuid, ResourceKind::External, Stub {});
2276 let res = manager.register(resource.clone(), path.clone());
2277 assert!(res.is_ok());
2278
2279 assert_eq!(resource.resource_uuid(), uuid);
2280 let metadata = block_on(ResourceMetadata::load_from_file_async(
2281 &metapath,
2282 &*manager.resource_io(),
2283 ))
2284 .expect("Reading meta file failed");
2285 assert_eq!(metadata.resource_id, uuid);
2286 }
2287
2288 #[test]
2289 fn resource_manager_request_untyped() {
2290 let manager = ResourceManager::new(Arc::new(FsResourceIo), Arc::new(Default::default()));
2291 let resource = UntypedResource::new_ok(Uuid::new_v4(), Default::default(), Stub {});
2292 let res = manager.register(resource.clone(), PathBuf::from("foo.txt"));
2293 assert!(res.is_ok());
2294
2295 let res = manager.request_untyped(Path::new("foo.txt"));
2296 assert_eq!(res, resource);
2297 }
2298
2299 #[test]
2300 fn display_for_resource_registration_error() {
2301 assert_eq!(
2302 format!("{}", ResourceRegistrationError::AlreadyRegistered),
2303 "A resource is already registered!"
2304 );
2305 assert_eq!(
2306 format!("{}", ResourceRegistrationError::InvalidState),
2307 "A resource was in invalid state!"
2308 );
2309 assert_eq!(
2310 format!("{}", ResourceRegistrationError::UnableToRegister),
2311 "Unable to register the resource!"
2312 );
2313 }
2314
2315 #[test]
2316 fn debug_for_resource_registration_error() {
2317 assert_eq!(
2318 format!("{:?}", ResourceRegistrationError::AlreadyRegistered),
2319 "AlreadyRegistered"
2320 );
2321 assert_eq!(
2322 format!("{:?}", ResourceRegistrationError::InvalidState),
2323 "InvalidState"
2324 );
2325 assert_eq!(
2326 format!("{:?}", ResourceRegistrationError::UnableToRegister),
2327 "UnableToRegister"
2328 );
2329 }
2330}