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
165#[derive(Debug)]
167pub struct ResourceMoveContext {
168 relative_src_path: PathBuf,
169 relative_dest_path: PathBuf,
170 resource_uuid: Uuid,
171}
172
173#[derive(Debug)]
175pub enum ResourceMovementError {
176 Io(std::io::Error),
178 FileError(FileError),
180 AlreadyExist {
182 src_path: PathBuf,
184 dest_path: PathBuf,
186 },
187 DestinationPathIsInvalid {
189 src_path: PathBuf,
191 dest_path: PathBuf,
193 },
194 ResourceRegistryLocationUnknown {
196 resource_path: PathBuf,
198 },
199 NotInRegistry {
201 resource_path: PathBuf,
203 },
204 OutsideOfRegistry {
206 absolute_src_path: PathBuf,
208 absolute_dest_dir: PathBuf,
210 absolute_registry_dir: PathBuf,
212 },
213 NoPath(UntypedResource),
216}
217
218impl From<FileError> for ResourceMovementError {
219 fn from(value: FileError) -> Self {
220 Self::FileError(value)
221 }
222}
223
224impl From<std::io::Error> for ResourceMovementError {
225 fn from(value: Error) -> Self {
226 Self::Io(value)
227 }
228}
229
230impl Display for ResourceMovementError {
231 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
232 match self {
233 ResourceMovementError::Io(err) => {
234 write!(f, "Io error: {err}")
235 }
236 ResourceMovementError::FileError(err) => {
237 write!(f, "File error: {err}")
238 }
239 ResourceMovementError::AlreadyExist {
240 src_path,
241 dest_path,
242 } => {
243 write!(
244 f,
245 "Unable to move the {} resource, because the destination \
246 path {} points to an existing file!",
247 src_path.display(),
248 dest_path.display()
249 )
250 }
251 ResourceMovementError::DestinationPathIsInvalid {
252 src_path,
253 dest_path,
254 } => {
255 write!(
256 f,
257 "Unable to move the {} resource, because the destination \
258 path {} is invalid!",
259 src_path.display(),
260 dest_path.display()
261 )
262 }
263 ResourceMovementError::ResourceRegistryLocationUnknown { resource_path } => {
264 write!(
265 f,
266 "Unable to move the {} resource, because the registry location is unknown!",
267 resource_path.display()
268 )
269 }
270 ResourceMovementError::NotInRegistry { resource_path } => {
271 write!(
272 f,
273 "Unable to move the {} resource, because it is not in the registry!",
274 resource_path.display()
275 )
276 }
277 ResourceMovementError::OutsideOfRegistry {
278 absolute_src_path,
279 absolute_dest_dir,
280 absolute_registry_dir,
281 } => {
282 write!(
283 f,
284 "Unable to move the {} resource to {} path, because \
285 the new path is located outside the resource registry path {}!",
286 absolute_src_path.display(),
287 absolute_dest_dir.display(),
288 absolute_registry_dir.display()
289 )
290 }
291 ResourceMovementError::NoPath(resource) => {
292 write!(
293 f,
294 "Unable to move {} resource, because it does not have a \
295 file system path!",
296 resource.key()
297 )
298 }
299 }
300 }
301}
302
303#[derive(Debug)]
305pub enum FolderMovementError {
306 Io(std::io::Error),
308 FileError(FileError),
310 ResourceMovementError(ResourceMovementError),
312 NotInRegistry {
314 dest_dir: PathBuf,
316 },
317 HierarchyError {
319 src_dir: PathBuf,
321 dest_dir: PathBuf,
323 },
324}
325
326impl From<FileError> for FolderMovementError {
327 fn from(value: FileError) -> Self {
328 Self::FileError(value)
329 }
330}
331
332impl From<std::io::Error> for FolderMovementError {
333 fn from(value: Error) -> Self {
334 Self::Io(value)
335 }
336}
337
338impl From<ResourceMovementError> for FolderMovementError {
339 fn from(value: ResourceMovementError) -> Self {
340 Self::ResourceMovementError(value)
341 }
342}
343
344impl Display for FolderMovementError {
345 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
346 match self {
347 FolderMovementError::Io(err) => {
348 write!(f, "Io error: {err}")
349 }
350 FolderMovementError::FileError(err) => {
351 write!(f, "File error: {err}")
352 }
353 FolderMovementError::ResourceMovementError(err) => {
354 write!(f, "{err}")
355 }
356 FolderMovementError::NotInRegistry { dest_dir } => {
357 write!(
358 f,
359 "Unable to move the {} folder, because it is not in the registry!",
360 dest_dir.display()
361 )
362 }
363 FolderMovementError::HierarchyError { src_dir, dest_dir } => {
364 write!(
365 f,
366 "Trying to move a folder into one of its own sub-folders. \
367 Source folder is {}, destination folder is {}",
368 src_dir.display(),
369 dest_dir.display()
370 )
371 }
372 }
373 }
374}
375
376impl ResourceManager {
377 pub fn new(io: Arc<dyn ResourceIo>, task_pool: Arc<TaskPool>) -> Self {
379 Self {
380 state: Arc::new(Mutex::new(ResourceManagerState::new(io, task_pool))),
381 }
382 }
383
384 pub fn state(&self) -> MutexGuard<'_, ResourceManagerState> {
388 self.state.safe_lock()
389 }
390
391 pub fn try_get_state(&self, timeout: Duration) -> Option<MutexGuard<'_, ResourceManagerState>> {
394 self.state.try_lock_for(timeout)
395 }
396
397 pub async fn registry_is_loaded(&self) -> bool {
401 let flag = self
402 .state()
403 .resource_registry
404 .safe_lock()
405 .status_flag()
406 .clone();
407 flag.await == ResourceRegistryStatus::Loaded
408 }
409
410 pub fn resource_io(&self) -> Arc<dyn ResourceIo> {
412 let state = self.state();
413 state.resource_io.clone()
414 }
415
416 pub fn task_pool(&self) -> Arc<TaskPool> {
418 let state = self.state();
419 state.task_pool()
420 }
421
422 pub fn registry_folder(&self) -> PathBuf {
424 self.state().registry_folder()
425 }
426
427 pub fn register_built_in_resource<T: TypedResourceData>(
429 &self,
430 resource: BuiltInResource<T>,
431 ) -> Option<UntypedBuiltInResource> {
432 self.state().register_built_in_resource(resource)
433 }
434
435 pub fn try_find<T>(&self, path: impl AsRef<Path>) -> Option<Resource<T>>
442 where
443 T: TypedResourceData,
444 {
445 let mut state = self.state();
446
447 let untyped = state.find(path.as_ref());
448
449 let data_type_uuid_matches = untyped
450 .type_uuid_non_blocking()
451 .is_some_and(|uuid| uuid == <T as TypeUuidProvider>::type_uuid());
452
453 if !data_type_uuid_matches {
454 let has_loader_for_extension = state
455 .loaders
456 .safe_lock()
457 .is_extension_matches_type::<T>(path.as_ref());
458
459 if !has_loader_for_extension {
460 return None;
461 }
462 }
463
464 Some(Resource {
465 untyped,
466 phantom: PhantomData::<T>,
467 })
468 }
469
470 pub fn find<T>(&self, path: impl AsRef<Path>) -> Resource<T>
483 where
484 T: TypedResourceData,
485 {
486 let path = path.as_ref();
487 let mut state = self.state();
488
489 let untyped = state.find(path);
490
491 let data_type_uuid_matches = untyped
492 .type_uuid_non_blocking()
493 .is_some_and(|uuid| uuid == <T as TypeUuidProvider>::type_uuid());
494
495 if !data_type_uuid_matches {
496 let has_loader_for_extension = state
497 .loaders
498 .safe_lock()
499 .is_extension_matches_type::<T>(path);
500
501 if !has_loader_for_extension {
502 panic!(
503 "Unable to get a resource of type {} from {path:?}! The resource has no \
504 associated loader for its extension and its actual data has some other \
505 data type!",
506 <T as TypeUuidProvider>::type_uuid(),
507 )
508 }
509 }
510
511 Resource {
512 untyped,
513 phantom: PhantomData::<T>,
514 }
515 }
516
517 pub fn find_uuid<T>(&self, uuid: Uuid) -> Resource<T>
527 where
528 T: TypedResourceData,
529 {
530 let mut state = self.state();
531
532 let untyped = state.find_uuid(uuid);
533
534 if let Some(type_uuid) = untyped.type_uuid_non_blocking() {
535 if type_uuid != <T as TypeUuidProvider>::type_uuid() {
536 panic!(
537 "Unable to get a resource of type {} from {uuid} UUID! Its actual data has some other \
538 data type with type UUID {type_uuid}!",
539 <T as TypeUuidProvider>::type_uuid(),
540 )
541 }
542 }
543
544 Resource {
545 untyped,
546 phantom: PhantomData::<T>,
547 }
548 }
549
550 pub fn request<T>(&self, path: impl AsRef<Path>) -> Resource<T>
589 where
590 T: TypedResourceData,
591 {
592 let path = path.as_ref();
593 let mut state = self.state();
594
595 let untyped = state.request(path);
596
597 let data_type_uuid_matches = untyped
598 .type_uuid_non_blocking()
599 .is_some_and(|uuid| uuid == <T as TypeUuidProvider>::type_uuid());
600
601 if !data_type_uuid_matches {
602 let has_loader_for_extension = state
603 .loaders
604 .safe_lock()
605 .is_extension_matches_type::<T>(path);
606
607 if !has_loader_for_extension {
608 panic!(
609 "Unable to get a resource of type {} from {path:?}! The resource has no \
610 associated loader for its extension and its actual data has some other \
611 data type!",
612 <T as TypeUuidProvider>::type_uuid(),
613 )
614 }
615 }
616
617 Resource {
618 untyped,
619 phantom: PhantomData::<T>,
620 }
621 }
622
623 pub fn request_resource<T>(&self, resource: &mut Resource<T>)
660 where
661 T: TypedResourceData,
662 {
663 let mut state = self.state();
664
665 state.request_resource(&mut resource.untyped);
666
667 if let Some(type_uuid) = resource.untyped.type_uuid_non_blocking() {
668 let needed_type_uuid = <T as TypeUuidProvider>::type_uuid();
669 if type_uuid != needed_type_uuid {
670 panic!(
671 "Unable to get a resource of type {needed_type_uuid} from resource UUID {}! The resource is \
672 loaded but its actual data has type {type_uuid}!",
673 resource.resource_uuid(),
674 );
675 }
676 }
677 }
678
679 pub fn add_resource<T>(&self, resource: &mut Resource<T>)
683 where
684 T: TypedResourceData,
685 {
686 let mut state = self.state();
687
688 state.add_resource(&mut resource.untyped);
689
690 if let Some(type_uuid) = resource.untyped.type_uuid_non_blocking() {
691 let needed_type_uuid = <T as TypeUuidProvider>::type_uuid();
692 if type_uuid != needed_type_uuid {
693 panic!(
694 "Unable to add a resource of type {needed_type_uuid} from resource UUID {}! The resource is \
695 loaded but its actual data has type {type_uuid}!",
696 resource.resource_uuid(),
697 );
698 }
699 }
700 }
701
702 pub fn try_request<T>(&self, path: impl AsRef<Path>) -> Option<Resource<T>>
709 where
710 T: TypedResourceData,
711 {
712 let mut state = self.state();
713 let untyped = state.request(path.as_ref());
714 if untyped
715 .type_uuid_non_blocking()
716 .is_some_and(|uuid| uuid == <T as TypeUuidProvider>::type_uuid())
717 || state
718 .loaders
719 .safe_lock()
720 .is_extension_matches_type::<T>(path.as_ref())
721 {
722 Some(Resource {
723 untyped,
724 phantom: PhantomData::<T>,
725 })
726 } else {
727 None
728 }
729 }
730
731 pub fn resource_path(&self, resource: impl AsRef<UntypedResource>) -> Option<PathBuf> {
737 self.state().resource_path(resource.as_ref())
738 }
739
740 pub fn uuid_to_resource_path(&self, resource_uuid: Uuid) -> Option<PathBuf> {
743 self.state().uuid_to_resource_path(resource_uuid)
744 }
745
746 pub fn request_untyped<P>(&self, path: P) -> UntypedResource
748 where
749 P: AsRef<Path>,
750 {
751 self.state().request(path)
752 }
753
754 pub fn update_or_load_registry(&self) {
760 self.state().update_or_load_registry();
761 }
762
763 pub fn add_loader<T: ResourceLoader>(&self, loader: T) -> Option<T> {
765 self.state().add_loader(loader)
766 }
767
768 pub fn register(
775 &self,
776 resource: UntypedResource,
777 path: impl AsRef<Path>,
778 ) -> Result<(), ResourceRegistrationError> {
779 self.state().register(resource, path)
780 }
781
782 pub fn is_built_in_resource_path(&self, resource: impl AsRef<Path>) -> bool {
784 self.state()
785 .built_in_resources
786 .is_built_in_resource_path(resource)
787 }
788
789 pub fn is_built_in_resource(&self, resource: impl AsRef<UntypedResource>) -> bool {
791 self.state()
792 .built_in_resources
793 .is_built_in_resource(resource)
794 }
795
796 #[allow(clippy::await_holding_lock)]
798 pub async fn make_resource_move_context(
799 &self,
800 src_path: impl AsRef<Path>,
801 dest_path: impl AsRef<Path>,
802 overwrite_existing: bool,
803 ) -> Result<ResourceMoveContext, ResourceMovementError> {
804 self.state()
805 .make_resource_move_context(src_path, dest_path, overwrite_existing)
806 .await
807 }
808
809 #[allow(clippy::await_holding_lock)]
813 pub async fn can_resource_be_moved(
814 &self,
815 src_path: impl AsRef<Path>,
816 dest_path: impl AsRef<Path>,
817 overwrite_existing: bool,
818 ) -> bool {
819 self.state()
820 .can_resource_be_moved(src_path, dest_path, overwrite_existing)
821 .await
822 }
823
824 #[allow(clippy::await_holding_lock)]
828 pub async fn move_resource_by_path(
829 &self,
830 src_path: impl AsRef<Path>,
831 dest_path: impl AsRef<Path>,
832 overwrite_existing: bool,
833 ) -> Result<(), ResourceMovementError> {
834 self.state()
835 .move_resource_by_path(src_path, dest_path, overwrite_existing)
836 .await
837 }
838
839 pub async fn move_resource(
843 &self,
844 resource: impl AsRef<UntypedResource>,
845 new_path: impl AsRef<Path>,
846 overwrite_existing: bool,
847 ) -> Result<(), ResourceMovementError> {
848 let resource_path = self.resource_path(resource).ok_or_else(|| {
849 FileError::Custom(
850 "Cannot move the resource because it does not have a path!".to_string(),
851 )
852 })?;
853
854 self.move_resource_by_path(resource_path, new_path, overwrite_existing)
855 .await
856 }
857
858 pub async fn reload_resources(&self) {
862 let resources = self.state().reload_resources();
863 join_all(resources).await;
864 }
865
866 pub fn is_supported_resource(&self, path: &Path) -> bool {
868 self.state().is_supported_resource(path)
869 }
870
871 pub fn is_path_in_registry(&self, path: &Path) -> bool {
873 self.state().is_path_in_registry(path)
874 }
875
876 #[allow(clippy::await_holding_lock)]
878 pub async fn try_move_folder(
879 &self,
880 src_dir: &Path,
881 dest_dir: &Path,
882 overwrite_existing: bool,
883 ) -> Result<(), FolderMovementError> {
884 self.state()
885 .try_move_folder(src_dir, dest_dir, overwrite_existing)
886 .await
887 }
888
889 pub async fn resave_native_resources(&self) {
896 #[cfg(not(target_arch = "wasm32"))]
897 {
898 let paths = self.state().collect_native_resources();
899 let resources = join_all(paths.iter().map(|path| self.request_untyped(path))).await;
900 for (resource, path) in resources.into_iter().zip(paths) {
901 match resource {
902 Ok(resource) => match resource.save(&path) {
903 Ok(_) => {
904 info!("The {} resource was resaved successfully!", path.display());
905 }
906 Err(err) => {
907 err!(
908 "Unable to resave the {} resource. Reason: {:?}",
909 path.display(),
910 err
911 )
912 }
913 },
914 Err(err) => {
915 err!(
916 "Unable to resave the {} resource. Reason: {:?}",
917 path.display(),
918 err
919 );
920 }
921 }
922 }
923 }
924 }
925}
926
927impl ResourceManagerState {
928 pub(crate) fn new(io: Arc<dyn ResourceIo>, task_pool: Arc<TaskPool>) -> Self {
929 Self {
930 resources: Default::default(),
931 loaders: Default::default(),
932 event_broadcaster: Default::default(),
933 constructors_container: Default::default(),
934 watcher: None,
935 built_in_resources: Default::default(),
936 resource_registry: Arc::new(Mutex::new(ResourceRegistry::new(io.clone()))),
937 task_pool,
938 resource_io: io,
939 }
940 }
941
942 pub fn update_or_load_registry(&self) {
948 let resource_io = self.resource_io.clone();
949 let resource_registry = self.resource_registry.clone();
950 #[allow(unused_variables)]
951 let excluded_folders = resource_registry.safe_lock().excluded_folders.clone();
952 let registry_status = resource_registry.safe_lock().status_flag();
953 registry_status.mark_as_loading();
954 #[allow(unused_variables)]
955 let task_loaders = self.loaders.clone();
956 let path = resource_registry.safe_lock().path().to_path_buf();
957
958 #[cfg(not(any(target_arch = "wasm32", target_os = "android")))]
960 if resource_io.can_read_directories() && resource_io.can_write() {
961 block_on(async move {
962 let new_data = ResourceRegistry::scan(
963 resource_io.clone(),
964 task_loaders,
965 &path,
966 excluded_folders,
967 )
968 .await;
969 let mut registry_lock = resource_registry.safe_lock();
970 registry_lock.modify().set_container(new_data);
971 if !registry_lock.exists_sync() {
974 registry_lock.save_sync();
975 }
976 registry_status.mark_as_loaded();
977 });
978 }
979
980 #[cfg(any(target_arch = "wasm32", target_os = "android"))]
981 if !resource_io.can_read_directories() || !resource_io.can_write() {
982 self.task_pool.spawn_task(async move {
983 use crate::registry::RegistryContainerExt;
984 info!("Trying to load or update the registry at {path:?}...");
986 match crate::registry::RegistryContainer::load_from_file(&path, &*resource_io).await
987 {
988 Ok(registry) => {
989 let mut registry_lock = resource_registry.safe_lock();
990 registry_lock.modify().set_container(registry);
991 info!("Resource registry was loaded from {path:?} successfully!");
992 }
993 Err(error) => {
994 err!("Unable to load resource registry! Reason: {error}.");
995 }
996 };
997 registry_status.mark_as_loaded();
998 });
999 }
1000 }
1001
1002 pub fn collect_native_resources(&self) -> Vec<PathBuf> {
1005 let loaders = self.loaders.safe_lock();
1006 let registry = self.resource_registry.safe_lock();
1007 let mut paths = Vec::new();
1008 for entry in registry.inner().values() {
1009 let ext = some_or_continue!(entry.extension().and_then(|ext| ext.to_str()));
1010 for loader in loaders.iter() {
1011 if loader.is_native_extension(ext) {
1012 paths.push(entry.clone());
1013 }
1014 }
1015 }
1016 paths
1017 }
1018
1019 pub fn task_pool(&self) -> Arc<TaskPool> {
1021 self.task_pool.clone()
1022 }
1023
1024 pub fn set_resource_io(&mut self, resource_io: Arc<dyn ResourceIo>) {
1027 self.resource_io = resource_io;
1028 }
1029
1030 pub fn set_watcher(&mut self, watcher: Option<FileSystemWatcher>) {
1035 self.watcher = watcher;
1036 }
1037
1038 pub fn count_registered_resources(&self) -> usize {
1040 self.resources.len()
1041 }
1042
1043 pub fn loading_progress(&self) -> usize {
1048 let registered = self.count_registered_resources();
1049 if registered > 0 {
1050 self.count_loaded_resources() * 100 / registered
1051 } else {
1052 100
1053 }
1054 }
1055
1056 fn try_get_event(&self) -> Option<Event> {
1057 self.watcher.as_ref()?.try_get_event()
1058 }
1059
1060 pub fn process_filesystem_events(&mut self) {
1064 let mut modified_files = FxHashSet::default();
1065 while let Some(mut evt) = self.try_get_event() {
1066 if evt.need_rescan() {
1067 info!("Filesystem watcher has forced a rescan!");
1068 self.update_or_load_registry();
1069 self.reload_resources();
1070 } else {
1071 use notify::event::{CreateKind, ModifyKind, RemoveKind, RenameMode};
1072 use notify::EventKind;
1073 match evt.kind {
1074 EventKind::Create(CreateKind::Any | CreateKind::File) => {
1075 self.on_create_event(evt.paths.first())
1076 }
1077 EventKind::Modify(ModifyKind::Name(RenameMode::From)) => {
1078 self.on_remove_event(evt.paths.first())
1079 }
1080 EventKind::Modify(ModifyKind::Name(RenameMode::To)) => {
1081 self.on_create_event(evt.paths.first())
1082 }
1083 EventKind::Modify(ModifyKind::Name(RenameMode::Both)) => {
1084 self.on_remove_event(evt.paths.first());
1085 self.on_create_event(evt.paths.get(1));
1086 }
1087 EventKind::Modify(ModifyKind::Any | ModifyKind::Data(_)) => {
1088 let path = evt.paths.get_mut(0).map(std::mem::take);
1089 modified_files.insert(path);
1090 }
1091 EventKind::Remove(RemoveKind::Any | RemoveKind::File) => {
1092 self.on_remove_event(evt.paths.first())
1093 }
1094 _ => (),
1095 }
1096 }
1097 }
1098 for path in modified_files {
1099 self.on_file_content_event(path.as_ref())
1100 }
1101 }
1102
1103 fn on_create_event(&mut self, path: Option<&PathBuf>) {
1104 let path = some_or_return!(path);
1105 let mut relative_path = ok_or_return!(fyrox_core::make_relative_path(path));
1106 if relative_path.is_dir() {
1107 info!("Reloading registry due to new directory.");
1108 self.update_or_load_registry();
1109 return;
1110 }
1111 let ext = some_or_return!(relative_path.extension());
1112 let mut registry = self.resource_registry.safe_lock();
1113 if registry
1114 .excluded_folders
1115 .iter()
1116 .any(|folder| relative_path.starts_with(folder))
1117 {
1118 return;
1119 }
1120 if !block_on(self.resource_io.exists(&relative_path)) {
1123 return;
1124 }
1125 if ext == ResourceMetadata::EXTENSION {
1126 relative_path.set_extension("");
1128 match registry.modify().read_metadata(relative_path.clone()) {
1129 Ok(RegistryUpdate {
1130 changed,
1131 value: metadata,
1132 }) => {
1133 if changed {
1134 info!(
1135 "The resource {relative_path:?} was registered successfully with {} id from a newly created meta file!",
1136 metadata.resource_id
1137 )
1138 }
1139 }
1140 Err(err) => {
1141 err!(
1142 "Unable to read the metadata for resource {relative_path:?}. Reason: {err}",
1143 )
1144 }
1145 }
1146 } else if self
1147 .loaders
1148 .safe_lock()
1149 .is_supported_resource(&relative_path)
1150 {
1151 Self::on_new_resource_file(registry.modify(), relative_path.clone());
1152 drop(registry);
1153 if self.try_reload_resource_from_path(&relative_path) {
1154 info!(
1155 "File {relative_path:?} was created, trying to reload a respective resource...",
1156 );
1157 }
1158 }
1159 }
1160 fn on_new_resource_file(mut registry: ResourceRegistryRefMut<'_>, relative_path: PathBuf) {
1163 match registry.read_metadata(relative_path.clone()) {
1164 Ok(RegistryUpdate {
1165 changed,
1166 value: metadata,
1167 }) => {
1168 if changed {
1169 info!(
1170 "The newly created resource {relative_path:?} was registered successfully with {} id from the meta file!",
1171 metadata.resource_id
1172 )
1173 }
1174 }
1175 Err(_) => {
1176 let uuid = Uuid::new_v4();
1177 match registry.write_metadata(uuid, relative_path.clone()) {
1178 Ok(RegistryUpdate {
1179 changed,
1180 value: old_path,
1181 }) => {
1182 assert!(old_path.is_none());
1183 if changed {
1184 info!("The newly created resource {relative_path:?} was registered successfully with new id: {uuid}");
1185 }
1186 }
1187 Err(err) => {
1188 err!("Unable to register the resource {relative_path:?}. Reason: {err}")
1189 }
1190 }
1191 }
1192 }
1193 }
1194 fn on_remove_event(&mut self, path: Option<&PathBuf>) {
1195 let path = some_or_return!(path);
1196 let mut relative_path = ok_or_return!(fyrox_core::make_relative_path(path));
1197 let ext = some_or_return!(relative_path.extension());
1198 if ext != ResourceMetadata::EXTENSION {
1200 return;
1201 }
1202 if block_on(self.resource_io.exists(&relative_path)) {
1206 return;
1207 }
1208
1209 let mut registry = self.resource_registry.safe_lock();
1210 if registry
1211 .excluded_folders
1212 .iter()
1213 .any(|folder| relative_path.starts_with(folder))
1214 {
1215 return;
1216 }
1217 relative_path.set_extension("");
1219 if !block_on(self.resource_io.exists(&relative_path)) {
1221 return;
1222 }
1223 let uuid = match registry.path_to_uuid(&relative_path) {
1225 Some(uuid) => {
1226 info!("The meta file for {relative_path:?} was removed, but its UUID is still in memory: {uuid}");
1227 uuid
1228 }
1229 None => {
1230 info!("The meta file for {relative_path:?} was removed and its UUID is lost!");
1231 Uuid::new_v4()
1232 }
1233 };
1234 let result = registry
1235 .modify()
1236 .write_metadata(uuid, relative_path.clone());
1237 match result {
1238 Ok(RegistryUpdate { changed, .. }) => {
1239 if changed {
1240 info!(
1241 "The resource {relative_path:?} was registered successfully with {uuid} id after its meta file was removed!",
1242 )
1243 } else {
1244 info!(
1245 "The meta file for resource {relative_path:?} was recreated with {uuid} id!",
1246 )
1247 }
1248 }
1249 Err(err) => {
1250 err!(
1251 "Unable to register the resource {relative_path:?} after its meta file was removed. Reason: {err}",
1252 )
1253 }
1254 }
1255 }
1256 fn on_file_content_event(&mut self, path: Option<&PathBuf>) {
1257 let path = some_or_return!(path);
1258 let mut relative_path = ok_or_return!(fyrox_core::make_relative_path(path));
1259 let ext = some_or_return!(relative_path.extension());
1260 let mut registry = self.resource_registry.safe_lock();
1261 if registry
1262 .excluded_folders
1263 .iter()
1264 .any(|folder| relative_path.starts_with(folder))
1265 {
1266 return;
1267 }
1268 if !block_on(self.resource_io.exists(&relative_path)) {
1271 return;
1272 }
1273
1274 if ext == ResourceMetadata::EXTENSION {
1275 relative_path.set_extension("");
1277 match registry.modify().read_metadata(relative_path.clone()) {
1278 Ok(RegistryUpdate {
1279 changed,
1280 value: metadata,
1281 }) => {
1282 if changed {
1283 info!(
1284 "The resource {relative_path:?} was registered successfully with {} id after meta file modification",
1285 metadata.resource_id
1286 )
1287 }
1288 }
1289 Err(err) => {
1290 err!(
1291 "Unable to read the metadata for resource {relative_path:?} after meta file modification. Reason: {err}",
1292 )
1293 }
1294 }
1295 } else if self
1296 .loaders
1297 .safe_lock()
1298 .is_supported_resource(&relative_path)
1299 {
1300 drop(registry);
1301 if self.try_reload_resource_from_path(&relative_path) {
1302 info!(
1303 "File {relative_path:?} was changed, trying to reload a respective resource...",
1304 );
1305 }
1306 }
1307 }
1308
1309 pub fn update(&mut self, dt: f32) {
1317 self.resources.retain_mut(|resource| {
1318 if resource.value.use_count() <= 1 {
1322 resource.time_to_live -= dt;
1323 if resource.time_to_live <= 0.0 {
1324 let registry = self.resource_registry.safe_lock();
1325 let resource_uuid = resource.resource_uuid();
1326 if let Some(path) = registry.uuid_to_path(resource_uuid) {
1327 info!("Resource {path:?} destroyed because it is not used anymore!",);
1328 self.event_broadcaster
1329 .broadcast(ResourceEvent::Removed(path.to_path_buf()));
1330 }
1331
1332 false
1333 } else {
1334 true
1336 }
1337 } else {
1338 resource.time_to_live = DEFAULT_RESOURCE_LIFETIME;
1340
1341 true
1343 }
1344 });
1345 }
1346
1347 fn add_resource_and_notify(&mut self, resource: UntypedResource) {
1348 self.event_broadcaster
1349 .broadcast(ResourceEvent::Added(resource.clone()));
1350
1351 self.resources.push(TimedEntry {
1352 value: resource,
1353 time_to_live: DEFAULT_RESOURCE_LIFETIME,
1354 });
1355 }
1356
1357 pub fn find_by_uuid(&self, uuid: Uuid) -> Option<&UntypedResource> {
1363 self.resources
1364 .iter()
1365 .find(|entry| entry.value.resource_uuid() == uuid)
1366 .map(|entry| &entry.value)
1367 }
1368
1369 pub fn find_by_path(&self, path: &Path) -> Option<&UntypedResource> {
1375 let registry = self.resource_registry.safe_lock();
1376 self.resources.iter().find_map(|entry| {
1377 if registry.uuid_to_path(entry.resource_uuid()) == Some(path) {
1378 return Some(&entry.value);
1379 }
1380 None
1381 })
1382 }
1383
1384 pub fn len(&self) -> usize {
1386 self.resources.len()
1387 }
1388
1389 pub fn is_empty(&self) -> bool {
1391 self.resources.is_empty()
1392 }
1393
1394 pub fn iter(&self) -> impl Iterator<Item = &UntypedResource> {
1396 self.resources.iter().map(|entry| &entry.value)
1397 }
1398
1399 pub fn destroy_unused_resources(&mut self) {
1401 self.resources
1402 .retain(|resource| resource.value.use_count() > 1);
1403 }
1404
1405 pub fn count_pending_resources(&self) -> usize {
1407 self.resources.iter().filter(|r| r.is_loading()).count()
1408 }
1409
1410 pub fn count_loaded_resources(&self) -> usize {
1412 self.resources.iter().filter(|r| r.is_ok()).count()
1413 }
1414
1415 pub fn resources(&self) -> Vec<UntypedResource> {
1417 self.resources.iter().map(|t| t.value.clone()).collect()
1418 }
1419
1420 pub fn register_built_in_resource<T: TypedResourceData>(
1422 &mut self,
1423 resource: BuiltInResource<T>,
1424 ) -> Option<UntypedBuiltInResource> {
1425 self.built_in_resources.add(resource)
1426 }
1427
1428 pub fn find_uuid(&mut self, uuid: Uuid) -> UntypedResource {
1434 if let Some(built_in_resource) = self.built_in_resources.find_by_uuid(uuid) {
1435 return built_in_resource.resource.clone();
1436 }
1437
1438 if let Some(existing) = self.find_by_uuid(uuid) {
1439 existing.clone()
1440 } else {
1441 let resource = UntypedResource::new_unloaded(uuid);
1442 self.add_resource_and_notify(resource.clone());
1443 resource
1444 }
1445 }
1446
1447 pub fn find<P>(&mut self, path: P) -> UntypedResource
1456 where
1457 P: AsRef<Path>,
1458 {
1459 let path = path.as_ref();
1460 if let Some(built_in_resource) = self.built_in_resources.get(path) {
1461 return built_in_resource.resource.clone();
1462 }
1463
1464 let path = self.resource_io.canonicalize_path(path).unwrap();
1465
1466 if let Some(existing) = self.find_by_resource_path(&path) {
1467 existing.clone()
1468 } else {
1469 let uuid = match self.find_uuid_or_register_new(path.clone()) {
1470 Ok(uuid) => uuid,
1471 Err(err) => {
1472 return UntypedResource::new_load_error(ResourceKind::External, path, err)
1473 }
1474 };
1475 let resource = UntypedResource::new_unloaded(uuid);
1476 self.add_resource_and_notify(resource.clone());
1477 resource
1478 }
1479 }
1480
1481 pub fn request<P>(&mut self, path: P) -> UntypedResource
1488 where
1489 P: AsRef<Path>,
1490 {
1491 let path = path.as_ref();
1492 if let Some(built_in_resource) = self.built_in_resources.get(path) {
1493 return built_in_resource.resource.clone();
1494 }
1495
1496 let path = self.resource_io.canonicalize_path(path).unwrap();
1497
1498 self.find_or_load(path)
1499 }
1500
1501 pub fn request_uuid(&mut self, uuid: Uuid) -> UntypedResource {
1503 let mut resource = uuid.into();
1504 self.request_resource(&mut resource);
1505 resource
1506 }
1507
1508 fn find_by_resource_path(&self, path_to_search: &Path) -> Option<&UntypedResource> {
1510 let registry = self.resource_registry.safe_lock();
1511 self.resources
1512 .iter()
1513 .find(move |entry| registry.uuid_to_path(entry.resource_uuid()) == Some(path_to_search))
1514 .map(|entry| &entry.value)
1515 }
1516
1517 fn find_or_load(&mut self, path: PathBuf) -> UntypedResource {
1523 match self.find_by_resource_path(&path) {
1524 Some(existing) => existing.clone(),
1525 None => self.load_resource(path),
1526 }
1527 }
1528
1529 fn find_uuid_or_register_new(&mut self, path: PathBuf) -> Result<Uuid, LoadError> {
1530 let mut registry = self.resource_registry.safe_lock();
1531 if let Some(uuid) = registry.path_to_uuid(&path) {
1532 Ok(uuid)
1533 } else if self.is_supported_resource(&path) {
1534 let uuid = Uuid::new_v4();
1538 registry.modify().register(uuid, path);
1539 Ok(uuid)
1540 } else {
1541 Err(LoadError::new(format!(
1542 "Unable to load resource {} because it is not supported!",
1543 path.display()
1544 )))
1545 }
1546 }
1547
1548 fn load_resource(&mut self, path: PathBuf) -> UntypedResource {
1549 let uuid = match self.find_uuid_or_register_new(path.clone()) {
1550 Ok(uuid) => uuid,
1551 Err(err) => return UntypedResource::new_load_error(ResourceKind::External, path, err),
1552 };
1553 let resource = UntypedResource::new_pending(uuid, ResourceKind::External);
1554 self.add_resource_and_notify(resource.clone());
1555 self.spawn_loading_task(resource.clone(), false);
1556 resource
1557 }
1558
1559 fn spawn_loading_task(&self, mut resource: UntypedResource, reload: bool) {
1562 let event_broadcaster = self.event_broadcaster.clone();
1563 let loaders = self.loaders.clone();
1564 let registry = self.resource_registry.clone();
1565 let io = self.resource_io.clone();
1566 let registry_status = registry.safe_lock().status_flag();
1567
1568 self.task_pool.spawn_task(async move {
1569 let registry_status = registry_status.await;
1571
1572 if registry_status == ResourceRegistryStatus::Unknown {
1573 resource.commit_error(
1574 PathBuf::default(),
1575 LoadError::new("The resource registry is unavailable!".to_string()),
1576 );
1577 return;
1578 }
1579
1580 let Some(path) = registry
1581 .safe_lock()
1582 .uuid_to_path(resource.resource_uuid())
1583 .map(|p| p.to_path_buf())
1584 else {
1585 let error = format!(
1586 "Resource {} failed to load. The path was not found \
1587 in the registry!",
1588 resource.resource_uuid(),
1589 );
1590 resource.commit_error(PathBuf::default(), error);
1591 return;
1592 };
1593
1594 let loader_future = loaders
1596 .safe_lock()
1597 .loader_for(&path)
1598 .map(|loader| loader.load(path.clone(), io));
1599
1600 if let Some(loader_future) = loader_future {
1601 match loader_future.await {
1602 Ok(data) => {
1603 let data = data.0;
1604
1605 let mut header = resource.lock();
1606
1607 assert!(header.kind.is_external());
1608
1609 header.state.commit(ResourceState::Ok {
1610 data: ResourceDataWrapper(data),
1611 });
1612
1613 drop(header);
1614
1615 event_broadcaster.broadcast_loaded_or_reloaded(resource, reload);
1616
1617 Log::info(format!(
1618 "Resource {} was loaded successfully!",
1619 path.display()
1620 ));
1621 }
1622 Err(error) => {
1623 if reload {
1624 if resource.is_ok() {
1625 info!("Resource {path:?} failed to reload, keeping the existing version. Reason: {error}");
1626 }
1627 else
1628 {
1629 info!("Resource {path:?} failed to reload. Reason: {error}");
1630 resource.commit_error(path.to_path_buf(), error);
1631 }
1632 }
1633 else
1634 {
1635 info!("Resource {path:?} failed to load. Reason: {error}");
1636 resource.commit_error(path.to_path_buf(), error);
1637 }
1638 }
1639 }
1640 } else {
1641 let error = format!("There's no resource loader for {path:?} resource!",);
1642 resource.commit_error(path, error);
1643 }
1644 });
1645 }
1646
1647 pub fn resource_path(&self, resource: &UntypedResource) -> Option<PathBuf> {
1658 self.uuid_to_resource_path(resource.resource_uuid())
1659 }
1660
1661 pub fn uuid_to_resource_path(&self, resource_uuid: Uuid) -> Option<PathBuf> {
1670 if let Some(path) = self
1671 .resource_registry
1672 .safe_lock()
1673 .uuid_to_path_buf(resource_uuid)
1674 {
1675 Some(path)
1676 } else {
1677 self.built_in_resources
1678 .find_by_uuid(resource_uuid)
1679 .map(|built_in_resource| built_in_resource.id.clone())
1680 }
1681 }
1682
1683 pub fn registry_folder(&self) -> PathBuf {
1685 self.resource_registry
1686 .lock()
1687 .path()
1688 .parent()
1689 .and_then(|p| p.to_path_buf().canonicalize().ok())
1690 .unwrap_or_default()
1691 }
1692
1693 pub fn add_loader<T: ResourceLoader>(&self, loader: T) -> Option<T> {
1695 self.loaders.safe_lock().set(loader)
1696 }
1697
1698 pub fn request_resource(&mut self, resource: &mut UntypedResource) {
1701 if let Some(r) = self.find_by_uuid(resource.resource_uuid()) {
1702 *resource = r.clone();
1705 if resource.is_unloaded() {
1706 resource.make_pending();
1708 self.spawn_loading_task(resource.clone(), false);
1709 }
1710 } else if let Some(r) = self
1711 .built_in_resources
1712 .find_by_uuid(resource.resource_uuid())
1713 {
1714 *resource = r.resource.clone();
1717 } else if resource.is_ok() || resource.is_embedded() {
1718 self.add_resource_and_notify(resource.clone());
1721 } else {
1722 resource.make_pending();
1725 self.add_resource_and_notify(resource.clone());
1726 self.spawn_loading_task(resource.clone(), false);
1727 }
1728 }
1729
1730 pub fn add_resource(&mut self, resource: &mut UntypedResource) {
1734 if let Some(r) = self.find_by_uuid(resource.resource_uuid()) {
1735 *resource = r.clone();
1738 } else if let Some(r) = self
1739 .built_in_resources
1740 .find_by_uuid(resource.resource_uuid())
1741 {
1742 *resource = r.resource.clone();
1745 } else {
1746 self.add_resource_and_notify(resource.clone());
1748 }
1749 }
1750
1751 pub fn register(
1763 &mut self,
1764 resource: UntypedResource,
1765 path: impl AsRef<Path>,
1766 ) -> Result<(), ResourceRegistrationError> {
1767 let resource_uuid = resource.resource_uuid();
1768 if self.find_by_uuid(resource_uuid).is_some() {
1769 return Err(ResourceRegistrationError::AlreadyRegistered);
1770 }
1771 let path = self.resource_io.canonicalize_path(path.as_ref()).unwrap();
1772 {
1773 let mut header = resource.lock();
1774 header.kind.make_external();
1775 let mut registry = self.resource_registry.safe_lock();
1776 let mut ctx = registry.modify();
1777 if ctx.write_metadata(resource_uuid, path).is_err() {
1778 return Err(ResourceRegistrationError::UnableToCreateMetadata);
1779 }
1780 }
1781 self.add_resource_and_notify(resource);
1782 Ok(())
1783 }
1784
1785 pub fn reload_resource(&mut self, resource: UntypedResource) {
1788 if self.built_in_resources.is_built_in_resource(&resource) {
1789 return;
1790 }
1791 let mut header = resource.lock();
1792 if !header.state.is_loading() {
1793 if !header.state.is_ok() {
1794 header.state.switch_to_pending_state();
1795 }
1796 drop(header);
1797 self.spawn_loading_task(resource, true)
1798 }
1799 }
1800
1801 pub fn reload_resources(&mut self) -> Vec<UntypedResource> {
1804 let resources = self
1805 .resources
1806 .iter()
1807 .map(|r| r.value.clone())
1808 .collect::<Vec<_>>();
1809
1810 for resource in resources.iter().cloned() {
1811 self.reload_resource(resource);
1812 }
1813
1814 resources
1815 }
1816
1817 pub fn get_wait_context(&self) -> ResourceWaitContext {
1819 ResourceWaitContext {
1820 resources: self
1821 .resources
1822 .iter()
1823 .map(|e| e.value.clone())
1824 .collect::<Vec<_>>(),
1825 }
1826 }
1827
1828 pub fn try_reload_resource_from_path(&mut self, path: &Path) -> bool {
1832 if !self.loaders.safe_lock().is_supported_resource(path) {
1834 return false;
1835 }
1836
1837 let Some(resource) = self.find_by_resource_path(path) else {
1838 return false;
1839 };
1840 let header = resource.lock();
1841 if header.state.is_loading() {
1842 return false;
1843 }
1844 drop(header);
1845 self.reload_resource(resource.clone());
1846 true
1847 }
1848
1849 #[allow(clippy::await_holding_lock)]
1851 pub async fn make_resource_move_context(
1852 &self,
1853 src_path: impl AsRef<Path>,
1854 dest_path: impl AsRef<Path>,
1855 overwrite_existing: bool,
1856 ) -> Result<ResourceMoveContext, ResourceMovementError> {
1857 let src_path = src_path.as_ref();
1858 let dest_path = dest_path.as_ref();
1859
1860 let relative_src_path = self.resource_io.canonicalize_path(src_path)?;
1861 let relative_dest_path = self.resource_io.canonicalize_path(dest_path)?;
1862
1863 if let Some(file_stem) = relative_dest_path.file_stem() {
1864 if !self.resource_io.is_valid_file_name(file_stem) {
1865 return Err(ResourceMovementError::DestinationPathIsInvalid {
1866 src_path: relative_src_path.clone(),
1867 dest_path: relative_dest_path.clone(),
1868 });
1869 }
1870 }
1871
1872 if !overwrite_existing && self.resource_io.exists(&relative_dest_path).await {
1873 return Err(ResourceMovementError::AlreadyExist {
1874 src_path: relative_src_path.clone(),
1875 dest_path: relative_dest_path.clone(),
1876 });
1877 }
1878
1879 let registry_lock_guard = self.resource_registry.safe_lock();
1880 let registry_dir = if let Some(directory) = registry_lock_guard.directory() {
1881 self.resource_io.canonicalize_path(directory)?
1882 } else {
1883 return Err(ResourceMovementError::ResourceRegistryLocationUnknown {
1884 resource_path: relative_src_path.clone(),
1885 });
1886 };
1887 let resource_uuid = registry_lock_guard
1888 .path_to_uuid(&relative_src_path)
1889 .ok_or_else(|| ResourceMovementError::NotInRegistry {
1890 resource_path: relative_src_path.clone(),
1891 })?;
1892
1893 let Some(relative_dest_dir) = relative_dest_path.parent() else {
1894 return Err(ResourceMovementError::DestinationPathIsInvalid {
1895 src_path: relative_src_path.clone(),
1896 dest_path: relative_dest_path.clone(),
1897 });
1898 };
1899 if !relative_dest_dir.starts_with(®istry_dir) {
1900 return Err(ResourceMovementError::OutsideOfRegistry {
1901 absolute_src_path: relative_src_path,
1902 absolute_dest_dir: relative_dest_dir.to_path_buf(),
1903 absolute_registry_dir: registry_dir,
1904 });
1905 }
1906
1907 drop(registry_lock_guard);
1908
1909 Ok(ResourceMoveContext {
1910 relative_src_path,
1911 relative_dest_path,
1912 resource_uuid,
1913 })
1914 }
1915
1916 pub async fn can_resource_be_moved(
1920 &self,
1921 src_path: impl AsRef<Path>,
1922 dest_path: impl AsRef<Path>,
1923 overwrite_existing: bool,
1924 ) -> bool {
1925 self.make_resource_move_context(src_path, dest_path, overwrite_existing)
1926 .await
1927 .is_ok()
1928 }
1929
1930 pub async fn move_resource_by_path(
1934 &self,
1935 src_path: impl AsRef<Path>,
1936 dest_path: impl AsRef<Path>,
1937 overwrite_existing: bool,
1938 ) -> Result<(), ResourceMovementError> {
1939 let ResourceMoveContext {
1940 relative_src_path,
1941 relative_dest_path,
1942
1943 resource_uuid,
1944 } = self
1945 .make_resource_move_context(src_path, dest_path, overwrite_existing)
1946 .await?;
1947
1948 self.resource_io
1950 .move_file(&relative_src_path, &relative_dest_path)
1951 .await?;
1952
1953 let current_path = self
1954 .resource_registry
1955 .safe_lock()
1956 .modify()
1957 .register(resource_uuid, relative_dest_path.to_path_buf());
1958 assert_eq!(current_path.value.as_ref(), Some(&relative_src_path));
1959
1960 let options_path = append_extension(&relative_src_path, OPTIONS_EXTENSION);
1961 if self.resource_io.exists(&options_path).await {
1962 let new_options_path = append_extension(&relative_dest_path, OPTIONS_EXTENSION);
1963 self.resource_io
1964 .move_file(&options_path, &new_options_path)
1965 .await?;
1966 }
1967
1968 let metadata_path = append_extension(&relative_src_path, ResourceMetadata::EXTENSION);
1969 if self.resource_io.exists(&metadata_path).await {
1970 let new_metadata_path =
1971 append_extension(&relative_dest_path, ResourceMetadata::EXTENSION);
1972 self.resource_io
1973 .move_file(&metadata_path, &new_metadata_path)
1974 .await?;
1975 }
1976
1977 Ok(())
1978 }
1979
1980 pub async fn move_resource(
1984 &self,
1985 resource: impl AsRef<UntypedResource>,
1986 new_path: impl AsRef<Path>,
1987 overwrite_existing: bool,
1988 ) -> Result<(), ResourceMovementError> {
1989 let resource_path = self.resource_path(resource.as_ref()).ok_or_else(|| {
1990 FileError::Custom(
1991 "Cannot move the resource because it does not have a path!".to_string(),
1992 )
1993 })?;
1994
1995 self.move_resource_by_path(resource_path, new_path, overwrite_existing)
1996 .await
1997 }
1998
1999 pub fn is_supported_resource(&self, path: &Path) -> bool {
2001 let ext = some_or_return!(path.extension(), false);
2002 let ext = some_or_return!(ext.to_str(), false);
2003
2004 self.loaders
2005 .safe_lock()
2006 .iter()
2007 .any(|loader| loader.supports_extension(ext))
2008 }
2009
2010 pub fn is_path_in_registry(&self, path: &Path) -> bool {
2012 let registry = self.resource_registry.safe_lock();
2013 if let Some(registry_directory) = registry.directory() {
2014 if let Ok(canonical_registry_path) = registry_directory.canonicalize() {
2015 if let Ok(canonical_path) = path.canonicalize() {
2016 return canonical_path.starts_with(canonical_registry_path);
2017 }
2018 }
2019 }
2020 false
2021 }
2022
2023 pub async fn try_move_folder(
2025 &self,
2026 src_dir: &Path,
2027 dest_dir: &Path,
2028 overwrite_existing: bool,
2029 ) -> Result<(), FolderMovementError> {
2030 if dest_dir.starts_with(src_dir) {
2031 return Err(FolderMovementError::HierarchyError {
2032 src_dir: src_dir.to_path_buf(),
2033 dest_dir: dest_dir.to_path_buf(),
2034 });
2035 }
2036
2037 if !self.is_path_in_registry(dest_dir) {
2040 return Err(FolderMovementError::NotInRegistry {
2041 dest_dir: dest_dir.to_path_buf(),
2042 });
2043 }
2044
2045 let mut what_where_stack = vec![(src_dir.to_path_buf(), dest_dir.to_path_buf())];
2050 while let Some((src_dir, target_dir)) = what_where_stack.pop() {
2051 let src_dir_name = some_or_continue!(src_dir.file_name());
2052
2053 let target_sub_dir = target_dir.join(src_dir_name);
2054 if !self.resource_io.exists(&target_sub_dir).await {
2055 std::fs::create_dir(&target_sub_dir)?;
2056 }
2057
2058 let target_sub_dir_normalized = ok_or_continue!(make_relative_path(&target_sub_dir));
2059
2060 for path in self.resource_io.walk_directory(&src_dir, 1).await? {
2061 if path.is_file() {
2062 let file_name = some_or_continue!(path.file_name());
2063 if self.is_supported_resource(&path) {
2064 let dest_path = target_sub_dir_normalized.join(file_name);
2065 self.move_resource_by_path(path, &dest_path, overwrite_existing)
2066 .await?;
2067 }
2068 } else if path.is_dir() && path != src_dir {
2069 what_where_stack.push((path, target_sub_dir.clone()));
2072 }
2073 }
2074 }
2075
2076 std::fs::remove_dir_all(src_dir)?;
2077
2078 Ok(())
2079 }
2080}
2081
2082#[cfg(test)]
2083mod test {
2084 use super::*;
2085 use crate::io::FsResourceIo;
2086 use crate::{
2087 loader::{BoxedLoaderFuture, LoaderPayload, ResourceLoader},
2088 ResourceData,
2089 };
2090 use fyrox_core::{
2091 reflect::prelude::*,
2092 uuid::{uuid, Uuid},
2093 visitor::{Visit, VisitResult, Visitor},
2094 TypeUuidProvider,
2095 };
2096 use std::{error::Error, fs::File, time::Duration};
2097
2098 #[derive(Debug, Default, Clone, Reflect, Visit)]
2099 struct Stub {}
2100
2101 impl TypeUuidProvider for Stub {
2102 fn type_uuid() -> Uuid {
2103 uuid!("9d873ff4-3126-47e1-a492-7cd8e7168239")
2104 }
2105 }
2106
2107 impl ResourceData for Stub {
2108 fn type_uuid(&self) -> Uuid {
2109 <Self as TypeUuidProvider>::type_uuid()
2110 }
2111
2112 fn save(&mut self, _path: &Path) -> Result<(), Box<dyn Error>> {
2113 Err("Saving is not supported!".to_string().into())
2114 }
2115
2116 fn can_be_saved(&self) -> bool {
2117 false
2118 }
2119
2120 fn try_clone_box(&self) -> Option<Box<dyn ResourceData>> {
2121 Some(Box::new(self.clone()))
2122 }
2123 }
2124
2125 impl ResourceLoader for Stub {
2126 fn extensions(&self) -> &[&str] {
2127 &["txt"]
2128 }
2129
2130 fn data_type_uuid(&self) -> Uuid {
2131 <Stub as TypeUuidProvider>::type_uuid()
2132 }
2133
2134 fn load(&self, _path: PathBuf, _io: Arc<dyn ResourceIo>) -> BoxedLoaderFuture {
2135 Box::pin(async move { Ok(LoaderPayload::new(Stub::default())) })
2136 }
2137 }
2138
2139 fn new_resource_manager() -> ResourceManagerState {
2140 ResourceManagerState::new(Arc::new(FsResourceIo), Arc::new(Default::default()))
2141 }
2142
2143 fn remove_file_if_exists(path: &Path) -> std::io::Result<()> {
2144 match std::fs::remove_file(path) {
2145 Ok(()) => Ok(()),
2146 Err(e) => match e.kind() {
2147 std::io::ErrorKind::NotFound => Ok(()),
2148 _ => Err(e),
2149 },
2150 }
2151 }
2152
2153 #[test]
2154 fn resource_wait_context_is_all_loaded() {
2155 assert!(ResourceWaitContext::default().is_all_loaded());
2156
2157 let cx = ResourceWaitContext {
2158 resources: vec![
2159 UntypedResource::new_pending(Default::default(), ResourceKind::External),
2160 UntypedResource::new_load_error(
2161 ResourceKind::External,
2162 Default::default(),
2163 LoadError::default(),
2164 ),
2165 ],
2166 };
2167 assert!(!cx.is_all_loaded());
2168 }
2169
2170 #[test]
2171 fn resource_manager_state_new() {
2172 let state = new_resource_manager();
2173
2174 assert!(state.resources.is_empty());
2175 assert!(state.loaders.safe_lock().is_empty());
2176 assert!(state.built_in_resources.is_empty());
2177 assert!(state.constructors_container.is_empty());
2178 assert!(state.watcher.is_none());
2179 assert!(state.is_empty());
2180 }
2181
2182 #[test]
2183 fn resource_manager_state_set_watcher() {
2184 let mut state = new_resource_manager();
2185 assert!(state.watcher.is_none());
2186
2187 let path = PathBuf::from("test.txt");
2188 if File::create(path.clone()).is_ok() {
2189 let watcher = FileSystemWatcher::new(path.clone(), Duration::from_secs(1));
2190 state.set_watcher(watcher.ok());
2191 assert!(state.watcher.is_some());
2192 }
2193 }
2194
2195 #[test]
2196 fn resource_manager_state_push() {
2197 std::fs::create_dir_all("data").expect("Could not create data directory.");
2198 let mut state = new_resource_manager();
2199
2200 assert_eq!(state.count_loaded_resources(), 0);
2201 assert_eq!(state.count_pending_resources(), 0);
2202 assert_eq!(state.count_registered_resources(), 0);
2203 assert_eq!(state.len(), 0);
2204
2205 assert!(state
2206 .register(
2207 UntypedResource::new_pending(Default::default(), ResourceKind::External),
2208 "foo.bar",
2209 )
2210 .is_ok());
2211 assert!(state
2212 .register(
2213 UntypedResource::new_load_error(
2214 ResourceKind::External,
2215 Default::default(),
2216 LoadError::default()
2217 ),
2218 "foo.bar",
2219 )
2220 .is_ok());
2221 assert!(state
2222 .register(
2223 UntypedResource::new_ok(Uuid::new_v4(), Default::default(), Stub {}),
2224 "foo.bar",
2225 )
2226 .is_ok());
2227
2228 assert_eq!(state.count_registered_resources(), 3);
2229 assert_eq!(state.len(), 3);
2230 }
2231
2232 #[test]
2233 fn resource_manager_state_loading_progress() {
2234 let mut state = new_resource_manager();
2235
2236 assert_eq!(state.loading_progress(), 100);
2237
2238 state
2239 .register(
2240 UntypedResource::new_ok(Uuid::new_v4(), Default::default(), Stub {}),
2241 "foo.bar",
2242 )
2243 .unwrap();
2244
2245 assert_eq!(state.loading_progress(), 100);
2246 }
2247
2248 #[test]
2249 fn resource_manager_state_find() {
2250 let mut state = new_resource_manager();
2251
2252 let path = Path::new("foo.txt");
2253
2254 assert!(state.find_by_path(path).is_none());
2255
2256 let resource = UntypedResource::new_ok(Uuid::new_v4(), Default::default(), Stub {});
2257 state.register(resource.clone(), path).unwrap();
2258
2259 assert_eq!(state.find_by_path(path), Some(&resource));
2260 }
2261
2262 #[test]
2263 fn resource_manager_state_resources() {
2264 let mut state = new_resource_manager();
2265
2266 assert_eq!(state.resources(), Vec::new());
2267
2268 let r1 = UntypedResource::new_ok(Uuid::new_v4(), ResourceKind::External, Stub {});
2269 let r2 = UntypedResource::new_ok(Uuid::new_v4(), ResourceKind::External, Stub {});
2270 let r3 = UntypedResource::new_ok(Uuid::new_v4(), ResourceKind::External, Stub {});
2271 state.register(r1.clone(), "foo1.txt").unwrap();
2272 state.register(r2.clone(), "foo2.txt").unwrap();
2273 state.register(r3.clone(), "foo3.txt").unwrap();
2274
2275 assert_eq!(state.resources(), vec![r1.clone(), r2.clone(), r3.clone()]);
2276 assert!(state.iter().eq([&r1, &r2, &r3]));
2277 }
2278
2279 #[test]
2280 fn resource_manager_state_destroy_unused_resources() {
2281 let mut state = new_resource_manager();
2282
2283 state
2284 .register(
2285 UntypedResource::new_ok(Uuid::new_v4(), ResourceKind::External, Stub {}),
2286 "foo1.txt",
2287 )
2288 .unwrap();
2289 assert_eq!(state.len(), 1);
2290
2291 state.destroy_unused_resources();
2292 assert_eq!(state.len(), 0);
2293 }
2294
2295 #[test]
2296 fn resource_manager_state_request() {
2297 let mut state = new_resource_manager();
2298 let path = PathBuf::from("test.txt");
2299
2300 let resource = UntypedResource::new_ok(Uuid::new_v4(), ResourceKind::External, Stub {});
2301 state.register(resource.clone(), &path).unwrap();
2302
2303 let res = state.request(&path);
2304 assert_eq!(res, resource);
2305
2306 let res = state.request(path);
2307
2308 assert_eq!(res.kind(), ResourceKind::External);
2309 assert!(!res.is_loading());
2310 }
2311
2312 #[test]
2313 fn resource_manager_state_get_wait_context() {
2314 let mut state = new_resource_manager();
2315
2316 let resource = UntypedResource::new_ok(Uuid::new_v4(), ResourceKind::External, Stub {});
2317 state.add_resource_and_notify(resource.clone());
2318 let cx = state.get_wait_context();
2319
2320 assert!(cx.resources.eq(&vec![resource]));
2321 }
2322
2323 #[test]
2324 fn resource_manager_new() {
2325 let manager = ResourceManager::new(Arc::new(FsResourceIo), Arc::new(Default::default()));
2326
2327 assert!(manager.state.safe_lock().is_empty());
2328 assert!(manager.state().is_empty());
2329 }
2330
2331 #[test]
2332 fn resource_manager_register() {
2333 std::fs::create_dir_all("data2").expect("Could not create data directory.");
2334 let manager = ResourceManager::new(Arc::new(FsResourceIo), Arc::new(Default::default()));
2335 manager.state().resource_registry.lock().set_path("data2");
2336 let path = PathBuf::from("data2/test.txt");
2337 let metapath = append_extension(&path, ResourceMetadata::EXTENSION);
2338 remove_file_if_exists(&metapath).unwrap();
2339
2340 let resource = UntypedResource::new_pending(Default::default(), ResourceKind::External);
2341 let res = manager.register(resource.clone(), path.clone());
2342 assert!(res.is_ok());
2343
2344 let metadata = block_on(ResourceMetadata::load_from_file_async(
2345 &metapath,
2346 &*manager.resource_io(),
2347 ))
2348 .expect("Reading meta file failed");
2349 assert_eq!(resource.resource_uuid(), metadata.resource_id);
2350
2351 let uuid = Uuid::new_v4();
2352 let resource = UntypedResource::new_ok(uuid, ResourceKind::External, Stub {});
2353 let res = manager.register(resource.clone(), path.clone());
2354 assert!(res.is_ok());
2355
2356 assert_eq!(resource.resource_uuid(), uuid);
2357 let metadata = block_on(ResourceMetadata::load_from_file_async(
2358 &metapath,
2359 &*manager.resource_io(),
2360 ))
2361 .expect("Reading meta file failed");
2362 assert_eq!(metadata.resource_id, uuid);
2363 }
2364
2365 #[test]
2366 fn resource_manager_request_untyped() {
2367 let manager = ResourceManager::new(Arc::new(FsResourceIo), Arc::new(Default::default()));
2368 let resource = UntypedResource::new_ok(Uuid::new_v4(), Default::default(), Stub {});
2369 let res = manager.register(resource.clone(), PathBuf::from("foo.txt"));
2370 assert!(res.is_ok());
2371
2372 let res = manager.request_untyped(Path::new("foo.txt"));
2373 assert_eq!(res, resource);
2374 }
2375
2376 #[test]
2377 fn display_for_resource_registration_error() {
2378 assert_eq!(
2379 format!("{}", ResourceRegistrationError::AlreadyRegistered),
2380 "A resource is already registered!"
2381 );
2382 assert_eq!(
2383 format!("{}", ResourceRegistrationError::InvalidState),
2384 "A resource was in invalid state!"
2385 );
2386 assert_eq!(
2387 format!("{}", ResourceRegistrationError::UnableToRegister),
2388 "Unable to register the resource!"
2389 );
2390 }
2391
2392 #[test]
2393 fn debug_for_resource_registration_error() {
2394 assert_eq!(
2395 format!("{:?}", ResourceRegistrationError::AlreadyRegistered),
2396 "AlreadyRegistered"
2397 );
2398 assert_eq!(
2399 format!("{:?}", ResourceRegistrationError::InvalidState),
2400 "InvalidState"
2401 );
2402 assert_eq!(
2403 format!("{:?}", ResourceRegistrationError::UnableToRegister),
2404 "UnableToRegister"
2405 );
2406 }
2407}