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},
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 fn registry_is_loaded(&self) -> bool {
401 self.state()
402 .resource_registry
403 .safe_lock()
404 .status_flag()
405 .is_loaded()
406 }
407
408 pub fn resource_io(&self) -> Arc<dyn ResourceIo> {
410 let state = self.state();
411 state.resource_io.clone()
412 }
413
414 pub fn task_pool(&self) -> Arc<TaskPool> {
416 let state = self.state();
417 state.task_pool()
418 }
419
420 pub fn registry_folder(&self) -> PathBuf {
422 self.state().registry_folder()
423 }
424
425 pub fn register_built_in_resource<T: TypedResourceData>(
427 &self,
428 resource: BuiltInResource<T>,
429 ) -> Option<UntypedBuiltInResource> {
430 self.state().register_built_in_resource(resource)
431 }
432
433 pub fn try_find<T>(&self, path: impl AsRef<Path>) -> Option<Resource<T>>
440 where
441 T: TypedResourceData,
442 {
443 let mut state = self.state();
444
445 let untyped = state.find(path.as_ref());
446
447 let data_type_uuid_matches = untyped
448 .type_uuid_non_blocking()
449 .is_some_and(|uuid| uuid == <T as TypeUuidProvider>::type_uuid());
450
451 if !data_type_uuid_matches {
452 let has_loader_for_extension = state
453 .loaders
454 .safe_lock()
455 .is_extension_matches_type::<T>(path.as_ref());
456
457 if !has_loader_for_extension {
458 return None;
459 }
460 }
461
462 Some(Resource {
463 untyped,
464 phantom: PhantomData::<T>,
465 })
466 }
467
468 pub fn find<T>(&self, path: impl AsRef<Path>) -> Resource<T>
481 where
482 T: TypedResourceData,
483 {
484 let path = path.as_ref();
485 let mut state = self.state();
486
487 let untyped = state.find(path);
488
489 let data_type_uuid_matches = untyped
490 .type_uuid_non_blocking()
491 .is_some_and(|uuid| uuid == <T as TypeUuidProvider>::type_uuid());
492
493 if !data_type_uuid_matches {
494 let has_loader_for_extension = state
495 .loaders
496 .safe_lock()
497 .is_extension_matches_type::<T>(path);
498
499 if !has_loader_for_extension {
500 panic!(
501 "Unable to get a resource of type {} from {path:?}! The resource has no \
502 associated loader for its extension and its actual data has some other \
503 data type!",
504 <T as TypeUuidProvider>::type_uuid(),
505 )
506 }
507 }
508
509 Resource {
510 untyped,
511 phantom: PhantomData::<T>,
512 }
513 }
514
515 pub fn find_uuid<T>(&self, uuid: Uuid) -> Resource<T>
525 where
526 T: TypedResourceData,
527 {
528 let mut state = self.state();
529
530 let untyped = state.find_uuid(uuid);
531
532 if let Some(type_uuid) = untyped.type_uuid_non_blocking() {
533 if type_uuid != <T as TypeUuidProvider>::type_uuid() {
534 panic!(
535 "Unable to get a resource of type {} from {uuid} UUID! Its actual data has some other \
536 data type with type UUID {type_uuid}!",
537 <T as TypeUuidProvider>::type_uuid(),
538 )
539 }
540 }
541
542 Resource {
543 untyped,
544 phantom: PhantomData::<T>,
545 }
546 }
547
548 pub fn request<T>(&self, path: impl AsRef<Path>) -> Resource<T>
587 where
588 T: TypedResourceData,
589 {
590 let path = path.as_ref();
591 let mut state = self.state();
592
593 let untyped = state.request(path);
594
595 let data_type_uuid_matches = untyped
596 .type_uuid_non_blocking()
597 .is_some_and(|uuid| uuid == <T as TypeUuidProvider>::type_uuid());
598
599 if !data_type_uuid_matches {
600 let has_loader_for_extension = state
601 .loaders
602 .safe_lock()
603 .is_extension_matches_type::<T>(path);
604
605 if !has_loader_for_extension {
606 panic!(
607 "Unable to get a resource of type {} from {path:?}! The resource has no \
608 associated loader for its extension and its actual data has some other \
609 data type!",
610 <T as TypeUuidProvider>::type_uuid(),
611 )
612 }
613 }
614
615 Resource {
616 untyped,
617 phantom: PhantomData::<T>,
618 }
619 }
620
621 pub fn request_resource<T>(&self, resource: &mut Resource<T>)
658 where
659 T: TypedResourceData,
660 {
661 let mut state = self.state();
662
663 state.request_resource(&mut resource.untyped);
664
665 if let Some(type_uuid) = resource.untyped.type_uuid_non_blocking() {
666 let needed_type_uuid = <T as TypeUuidProvider>::type_uuid();
667 if type_uuid != needed_type_uuid {
668 panic!(
669 "Unable to get a resource of type {needed_type_uuid} from resource UUID {}! The resource is \
670 loaded but its actual data has type {type_uuid}!",
671 resource.resource_uuid(),
672 );
673 }
674 }
675 }
676
677 pub fn add_resource<T>(&self, resource: &mut Resource<T>)
681 where
682 T: TypedResourceData,
683 {
684 let mut state = self.state();
685
686 state.add_resource(&mut resource.untyped);
687
688 if let Some(type_uuid) = resource.untyped.type_uuid_non_blocking() {
689 let needed_type_uuid = <T as TypeUuidProvider>::type_uuid();
690 if type_uuid != needed_type_uuid {
691 panic!(
692 "Unable to add a resource of type {needed_type_uuid} from resource UUID {}! The resource is \
693 loaded but its actual data has type {type_uuid}!",
694 resource.resource_uuid(),
695 );
696 }
697 }
698 }
699
700 pub fn try_request<T>(&self, path: impl AsRef<Path>) -> Option<Resource<T>>
707 where
708 T: TypedResourceData,
709 {
710 let mut state = self.state();
711 let untyped = state.request(path.as_ref());
712 if untyped
713 .type_uuid_non_blocking()
714 .is_some_and(|uuid| uuid == <T as TypeUuidProvider>::type_uuid())
715 || state
716 .loaders
717 .safe_lock()
718 .is_extension_matches_type::<T>(path.as_ref())
719 {
720 Some(Resource {
721 untyped,
722 phantom: PhantomData::<T>,
723 })
724 } else {
725 None
726 }
727 }
728
729 pub fn resource_path(&self, resource: impl AsRef<UntypedResource>) -> Option<PathBuf> {
735 self.state().resource_path(resource.as_ref())
736 }
737
738 pub fn uuid_to_resource_path(&self, resource_uuid: Uuid) -> Option<PathBuf> {
741 self.state().uuid_to_resource_path(resource_uuid)
742 }
743
744 pub fn request_untyped<P>(&self, path: P) -> UntypedResource
746 where
747 P: AsRef<Path>,
748 {
749 self.state().request(path)
750 }
751
752 pub fn update_or_load_registry(&self) {
758 self.state().update_or_load_registry();
759 }
760
761 pub fn add_loader<T: ResourceLoader>(&self, loader: T) -> Option<T> {
763 self.state().add_loader(loader)
764 }
765
766 pub fn register(
773 &self,
774 resource: UntypedResource,
775 path: impl AsRef<Path>,
776 ) -> Result<(), ResourceRegistrationError> {
777 self.state().register(resource, path)
778 }
779
780 pub fn is_built_in_resource_path(&self, resource: impl AsRef<Path>) -> bool {
782 self.state()
783 .built_in_resources
784 .is_built_in_resource_path(resource)
785 }
786
787 pub fn is_built_in_resource(&self, resource: impl AsRef<UntypedResource>) -> bool {
789 self.state()
790 .built_in_resources
791 .is_built_in_resource(resource)
792 }
793
794 #[allow(clippy::await_holding_lock)]
796 pub async fn make_resource_move_context(
797 &self,
798 src_path: impl AsRef<Path>,
799 dest_path: impl AsRef<Path>,
800 overwrite_existing: bool,
801 ) -> Result<ResourceMoveContext, ResourceMovementError> {
802 self.state()
803 .make_resource_move_context(src_path, dest_path, overwrite_existing)
804 .await
805 }
806
807 #[allow(clippy::await_holding_lock)]
811 pub async fn can_resource_be_moved(
812 &self,
813 src_path: impl AsRef<Path>,
814 dest_path: impl AsRef<Path>,
815 overwrite_existing: bool,
816 ) -> bool {
817 self.state()
818 .can_resource_be_moved(src_path, dest_path, overwrite_existing)
819 .await
820 }
821
822 #[allow(clippy::await_holding_lock)]
826 pub async fn move_resource_by_path(
827 &self,
828 src_path: impl AsRef<Path>,
829 dest_path: impl AsRef<Path>,
830 overwrite_existing: bool,
831 ) -> Result<(), ResourceMovementError> {
832 self.state()
833 .move_resource_by_path(src_path, dest_path, overwrite_existing)
834 .await
835 }
836
837 pub async fn move_resource(
841 &self,
842 resource: impl AsRef<UntypedResource>,
843 new_path: impl AsRef<Path>,
844 overwrite_existing: bool,
845 ) -> Result<(), ResourceMovementError> {
846 let resource_path = self.resource_path(resource).ok_or_else(|| {
847 FileError::Custom(
848 "Cannot move the resource because it does not have a path!".to_string(),
849 )
850 })?;
851
852 self.move_resource_by_path(resource_path, new_path, overwrite_existing)
853 .await
854 }
855
856 pub async fn reload_resources(&self) {
860 let resources = self.state().reload_resources();
861 join_all(resources).await;
862 }
863
864 pub fn is_supported_resource(&self, path: &Path) -> bool {
866 self.state().is_supported_resource(path)
867 }
868
869 pub fn is_path_in_registry(&self, path: &Path) -> bool {
871 self.state().is_path_in_registry(path)
872 }
873
874 #[allow(clippy::await_holding_lock)]
876 pub async fn try_move_folder(
877 &self,
878 src_dir: &Path,
879 dest_dir: &Path,
880 overwrite_existing: bool,
881 ) -> Result<(), FolderMovementError> {
882 self.state()
883 .try_move_folder(src_dir, dest_dir, overwrite_existing)
884 .await
885 }
886
887 pub async fn resave_native_resources(&self) {
894 #[cfg(not(target_arch = "wasm32"))]
895 {
896 let paths = self.state().collect_native_resources();
897 let resources = join_all(paths.iter().map(|path| self.request_untyped(path))).await;
898 for (resource, path) in resources.into_iter().zip(paths) {
899 match resource {
900 Ok(resource) => match resource.save(&path) {
901 Ok(_) => {
902 info!("The {} resource was resaved successfully!", path.display());
903 }
904 Err(err) => {
905 err!(
906 "Unable to resave the {} resource. Reason: {:?}",
907 path.display(),
908 err
909 )
910 }
911 },
912 Err(err) => {
913 err!(
914 "Unable to resave the {} resource. Reason: {:?}",
915 path.display(),
916 err
917 );
918 }
919 }
920 }
921 }
922 }
923}
924
925impl ResourceManagerState {
926 pub(crate) fn new(io: Arc<dyn ResourceIo>, task_pool: Arc<TaskPool>) -> Self {
927 Self {
928 resources: Default::default(),
929 loaders: Default::default(),
930 event_broadcaster: Default::default(),
931 constructors_container: Default::default(),
932 watcher: None,
933 built_in_resources: Default::default(),
934 resource_registry: Arc::new(Mutex::new(ResourceRegistry::new(io.clone()))),
935 task_pool,
936 resource_io: io,
937 }
938 }
939
940 pub fn update_or_load_registry(&self) {
946 let resource_io = self.resource_io.clone();
947 let resource_registry = self.resource_registry.clone();
948 #[allow(unused_variables)]
949 let excluded_folders = resource_registry.safe_lock().excluded_folders.clone();
950 let registry_status = resource_registry.safe_lock().status_flag();
951 registry_status.mark_as_unloaded();
952 #[allow(unused_variables)]
953 let task_loaders = self.loaders.clone();
954 let path = resource_registry.safe_lock().path().to_path_buf();
955
956 #[cfg(not(any(target_arch = "wasm32", target_os = "android")))]
958 if resource_io.can_read_directories() && resource_io.can_write() {
959 block_on(async move {
960 let new_data = ResourceRegistry::scan(
961 resource_io.clone(),
962 task_loaders,
963 &path,
964 excluded_folders,
965 )
966 .await;
967 let mut registry_lock = resource_registry.safe_lock();
968 registry_lock.modify().set_container(new_data);
969 if !registry_lock.exists_sync() {
972 registry_lock.save_sync();
973 }
974 registry_status.mark_as_loaded();
975 });
976 }
977
978 #[cfg(any(target_arch = "wasm32", target_os = "android"))]
979 if !resource_io.can_read_directories() || !resource_io.can_write() {
980 self.task_pool.spawn_task(async move {
981 use crate::registry::RegistryContainerExt;
982 info!("Trying to load or update the registry at {path:?}...");
984 match crate::registry::RegistryContainer::load_from_file(&path, &*resource_io).await
985 {
986 Ok(registry) => {
987 let mut registry_lock = resource_registry.safe_lock();
988 registry_lock.modify().set_container(registry);
989 info!("Resource registry was loaded from {path:?} successfully!");
990 }
991 Err(error) => {
992 err!("Unable to load resource registry! Reason: {error}.");
993 }
994 };
995 registry_status.mark_as_loaded();
996 });
997 }
998 }
999
1000 pub fn collect_native_resources(&self) -> Vec<PathBuf> {
1003 let loaders = self.loaders.safe_lock();
1004 let registry = self.resource_registry.safe_lock();
1005 let mut paths = Vec::new();
1006 for entry in registry.inner().values() {
1007 let ext = some_or_continue!(entry.extension().and_then(|ext| ext.to_str()));
1008 for loader in loaders.iter() {
1009 if loader.is_native_extension(ext) {
1010 paths.push(entry.clone());
1011 }
1012 }
1013 }
1014 paths
1015 }
1016
1017 pub fn task_pool(&self) -> Arc<TaskPool> {
1019 self.task_pool.clone()
1020 }
1021
1022 pub fn set_resource_io(&mut self, resource_io: Arc<dyn ResourceIo>) {
1025 self.resource_io = resource_io;
1026 }
1027
1028 pub fn set_watcher(&mut self, watcher: Option<FileSystemWatcher>) {
1033 self.watcher = watcher;
1034 }
1035
1036 pub fn count_registered_resources(&self) -> usize {
1038 self.resources.len()
1039 }
1040
1041 pub fn loading_progress(&self) -> usize {
1046 let registered = self.count_registered_resources();
1047 if registered > 0 {
1048 self.count_loaded_resources() * 100 / registered
1049 } else {
1050 100
1051 }
1052 }
1053
1054 fn try_get_event(&self) -> Option<Event> {
1055 self.watcher.as_ref()?.try_get_event()
1056 }
1057
1058 pub fn process_filesystem_events(&mut self) {
1062 let mut modified_files = FxHashSet::default();
1063 while let Some(mut evt) = self.try_get_event() {
1064 if evt.need_rescan() {
1065 info!("Filesystem watcher has forced a rescan!");
1066 self.update_or_load_registry();
1067 self.reload_resources();
1068 } else {
1069 use notify::event::{CreateKind, ModifyKind, RemoveKind, RenameMode};
1070 use notify::EventKind;
1071 match evt.kind {
1072 EventKind::Create(CreateKind::Any | CreateKind::File) => {
1073 self.on_create_event(evt.paths.first())
1074 }
1075 EventKind::Modify(ModifyKind::Name(RenameMode::From)) => {
1076 self.on_remove_event(evt.paths.first())
1077 }
1078 EventKind::Modify(ModifyKind::Name(RenameMode::To)) => {
1079 self.on_create_event(evt.paths.first())
1080 }
1081 EventKind::Modify(ModifyKind::Name(RenameMode::Both)) => {
1082 self.on_remove_event(evt.paths.first());
1083 self.on_create_event(evt.paths.get(1));
1084 }
1085 EventKind::Modify(ModifyKind::Any | ModifyKind::Data(_)) => {
1086 let path = evt.paths.get_mut(0).map(std::mem::take);
1087 modified_files.insert(path);
1088 }
1089 EventKind::Remove(RemoveKind::Any | RemoveKind::File) => {
1090 self.on_remove_event(evt.paths.first())
1091 }
1092 _ => (),
1093 }
1094 }
1095 }
1096 for path in modified_files {
1097 self.on_file_content_event(path.as_ref())
1098 }
1099 }
1100
1101 fn on_create_event(&mut self, path: Option<&PathBuf>) {
1102 let path = some_or_return!(path);
1103 let mut relative_path = ok_or_return!(fyrox_core::make_relative_path(path));
1104 if relative_path.is_dir() {
1105 info!("Reloading registry due to new directory.");
1106 self.update_or_load_registry();
1107 return;
1108 }
1109 let ext = some_or_return!(relative_path.extension());
1110 let mut registry = self.resource_registry.safe_lock();
1111 if registry
1112 .excluded_folders
1113 .iter()
1114 .any(|folder| relative_path.starts_with(folder))
1115 {
1116 return;
1117 }
1118 if !block_on(self.resource_io.exists(&relative_path)) {
1121 return;
1122 }
1123 if ext == ResourceMetadata::EXTENSION {
1124 relative_path.set_extension("");
1126 match registry.modify().read_metadata(relative_path.clone()) {
1127 Ok(RegistryUpdate {
1128 changed,
1129 value: metadata,
1130 }) => {
1131 if changed {
1132 info!(
1133 "The resource {relative_path:?} was registered successfully with {} id from a newly created meta file!",
1134 metadata.resource_id
1135 )
1136 }
1137 }
1138 Err(err) => {
1139 err!(
1140 "Unable to read the metadata for resource {relative_path:?}. Reason: {err}",
1141 )
1142 }
1143 }
1144 } else if self
1145 .loaders
1146 .safe_lock()
1147 .is_supported_resource(&relative_path)
1148 {
1149 Self::on_new_resource_file(registry.modify(), relative_path.clone());
1150 drop(registry);
1151 if self.try_reload_resource_from_path(&relative_path) {
1152 info!(
1153 "File {relative_path:?} was created, trying to reload a respective resource...",
1154 );
1155 }
1156 }
1157 }
1158 fn on_new_resource_file(mut registry: ResourceRegistryRefMut<'_>, relative_path: PathBuf) {
1161 match registry.read_metadata(relative_path.clone()) {
1162 Ok(RegistryUpdate {
1163 changed,
1164 value: metadata,
1165 }) => {
1166 if changed {
1167 info!(
1168 "The newly created resource {relative_path:?} was registered successfully with {} id from the meta file!",
1169 metadata.resource_id
1170 )
1171 }
1172 }
1173 Err(_) => {
1174 let uuid = Uuid::new_v4();
1175 match registry.write_metadata(uuid, relative_path.clone()) {
1176 Ok(RegistryUpdate {
1177 changed,
1178 value: old_path,
1179 }) => {
1180 assert!(old_path.is_none());
1181 if changed {
1182 info!("The newly created resource {relative_path:?} was registered successfully with new id: {uuid}");
1183 }
1184 }
1185 Err(err) => {
1186 err!("Unable to register the resource {relative_path:?}. Reason: {err}")
1187 }
1188 }
1189 }
1190 }
1191 }
1192 fn on_remove_event(&mut self, path: Option<&PathBuf>) {
1193 let path = some_or_return!(path);
1194 let mut relative_path = ok_or_return!(fyrox_core::make_relative_path(path));
1195 let ext = some_or_return!(relative_path.extension());
1196 if ext != ResourceMetadata::EXTENSION {
1198 return;
1199 }
1200 if block_on(self.resource_io.exists(&relative_path)) {
1204 return;
1205 }
1206
1207 let mut registry = self.resource_registry.safe_lock();
1208 if registry
1209 .excluded_folders
1210 .iter()
1211 .any(|folder| relative_path.starts_with(folder))
1212 {
1213 return;
1214 }
1215 relative_path.set_extension("");
1217 if !block_on(self.resource_io.exists(&relative_path)) {
1219 return;
1220 }
1221 let uuid = match registry.path_to_uuid(&relative_path) {
1223 Some(uuid) => {
1224 info!("The meta file for {relative_path:?} was removed, but its UUID is still in memory: {uuid}");
1225 uuid
1226 }
1227 None => {
1228 info!("The meta file for {relative_path:?} was removed and its UUID is lost!");
1229 Uuid::new_v4()
1230 }
1231 };
1232 let result = registry
1233 .modify()
1234 .write_metadata(uuid, relative_path.clone());
1235 match result {
1236 Ok(RegistryUpdate { changed, .. }) => {
1237 if changed {
1238 info!(
1239 "The resource {relative_path:?} was registered successfully with {uuid} id after its meta file was removed!",
1240 )
1241 } else {
1242 info!(
1243 "The meta file for resource {relative_path:?} was recreated with {uuid} id!",
1244 )
1245 }
1246 }
1247 Err(err) => {
1248 err!(
1249 "Unable to register the resource {relative_path:?} after its meta file was removed. Reason: {err}",
1250 )
1251 }
1252 }
1253 }
1254 fn on_file_content_event(&mut self, path: Option<&PathBuf>) {
1255 let path = some_or_return!(path);
1256 let mut relative_path = ok_or_return!(fyrox_core::make_relative_path(path));
1257 let ext = some_or_return!(relative_path.extension());
1258 let mut registry = self.resource_registry.safe_lock();
1259 if registry
1260 .excluded_folders
1261 .iter()
1262 .any(|folder| relative_path.starts_with(folder))
1263 {
1264 return;
1265 }
1266 if !block_on(self.resource_io.exists(&relative_path)) {
1269 return;
1270 }
1271
1272 if ext == ResourceMetadata::EXTENSION {
1273 relative_path.set_extension("");
1275 match registry.modify().read_metadata(relative_path.clone()) {
1276 Ok(RegistryUpdate {
1277 changed,
1278 value: metadata,
1279 }) => {
1280 if changed {
1281 info!(
1282 "The resource {relative_path:?} was registered successfully with {} id after meta file modification",
1283 metadata.resource_id
1284 )
1285 }
1286 }
1287 Err(err) => {
1288 err!(
1289 "Unable to read the metadata for resource {relative_path:?} after meta file modification. Reason: {err}",
1290 )
1291 }
1292 }
1293 } else if self
1294 .loaders
1295 .safe_lock()
1296 .is_supported_resource(&relative_path)
1297 {
1298 drop(registry);
1299 if self.try_reload_resource_from_path(&relative_path) {
1300 info!(
1301 "File {relative_path:?} was changed, trying to reload a respective resource...",
1302 );
1303 }
1304 }
1305 }
1306
1307 pub fn update(&mut self, dt: f32) {
1315 self.resources.retain_mut(|resource| {
1316 if resource.value.use_count() <= 1 {
1320 resource.time_to_live -= dt;
1321 if resource.time_to_live <= 0.0 {
1322 let registry = self.resource_registry.safe_lock();
1323 let resource_uuid = resource.resource_uuid();
1324 if let Some(path) = registry.uuid_to_path(resource_uuid) {
1325 info!("Resource {path:?} destroyed because it is not used anymore!",);
1326 self.event_broadcaster
1327 .broadcast(ResourceEvent::Removed(path.to_path_buf()));
1328 }
1329
1330 false
1331 } else {
1332 true
1334 }
1335 } else {
1336 resource.time_to_live = DEFAULT_RESOURCE_LIFETIME;
1338
1339 true
1341 }
1342 });
1343 }
1344
1345 fn add_resource_and_notify(&mut self, resource: UntypedResource) {
1346 self.event_broadcaster
1347 .broadcast(ResourceEvent::Added(resource.clone()));
1348
1349 self.resources.push(TimedEntry {
1350 value: resource,
1351 time_to_live: DEFAULT_RESOURCE_LIFETIME,
1352 });
1353 }
1354
1355 pub fn find_by_uuid(&self, uuid: Uuid) -> Option<&UntypedResource> {
1361 self.resources
1362 .iter()
1363 .find(|entry| entry.value.resource_uuid() == uuid)
1364 .map(|entry| &entry.value)
1365 }
1366
1367 pub fn find_by_path(&self, path: &Path) -> Option<&UntypedResource> {
1373 let registry = self.resource_registry.safe_lock();
1374 self.resources.iter().find_map(|entry| {
1375 if registry.uuid_to_path(entry.resource_uuid()) == Some(path) {
1376 return Some(&entry.value);
1377 }
1378 None
1379 })
1380 }
1381
1382 pub fn len(&self) -> usize {
1384 self.resources.len()
1385 }
1386
1387 pub fn is_empty(&self) -> bool {
1389 self.resources.is_empty()
1390 }
1391
1392 pub fn iter(&self) -> impl Iterator<Item = &UntypedResource> {
1394 self.resources.iter().map(|entry| &entry.value)
1395 }
1396
1397 pub fn destroy_unused_resources(&mut self) {
1399 self.resources
1400 .retain(|resource| resource.value.use_count() > 1);
1401 }
1402
1403 pub fn count_pending_resources(&self) -> usize {
1405 self.resources.iter().filter(|r| r.is_loading()).count()
1406 }
1407
1408 pub fn count_loaded_resources(&self) -> usize {
1410 self.resources.iter().filter(|r| r.is_ok()).count()
1411 }
1412
1413 pub fn resources(&self) -> Vec<UntypedResource> {
1415 self.resources.iter().map(|t| t.value.clone()).collect()
1416 }
1417
1418 pub fn register_built_in_resource<T: TypedResourceData>(
1420 &mut self,
1421 resource: BuiltInResource<T>,
1422 ) -> Option<UntypedBuiltInResource> {
1423 self.built_in_resources.add(resource)
1424 }
1425
1426 pub fn find_uuid(&mut self, uuid: Uuid) -> UntypedResource {
1432 if let Some(built_in_resource) = self.built_in_resources.find_by_uuid(uuid) {
1433 return built_in_resource.resource.clone();
1434 }
1435
1436 if let Some(existing) = self.find_by_uuid(uuid) {
1437 existing.clone()
1438 } else {
1439 let resource = UntypedResource::new_unloaded(uuid);
1440 self.add_resource_and_notify(resource.clone());
1441 resource
1442 }
1443 }
1444
1445 pub fn find<P>(&mut self, path: P) -> UntypedResource
1454 where
1455 P: AsRef<Path>,
1456 {
1457 let path = path.as_ref();
1458 if let Some(built_in_resource) = self.built_in_resources.get(path) {
1459 return built_in_resource.resource.clone();
1460 }
1461
1462 let path = self.resource_io.canonicalize_path(path).unwrap();
1463
1464 if let Some(existing) = self.find_by_resource_path(&path) {
1465 existing.clone()
1466 } else {
1467 let uuid = match self.find_uuid_or_register_new(path.clone()) {
1468 Ok(uuid) => uuid,
1469 Err(err) => {
1470 return UntypedResource::new_load_error(ResourceKind::External, path, err)
1471 }
1472 };
1473 let resource = UntypedResource::new_unloaded(uuid);
1474 self.add_resource_and_notify(resource.clone());
1475 resource
1476 }
1477 }
1478
1479 pub fn request<P>(&mut self, path: P) -> UntypedResource
1486 where
1487 P: AsRef<Path>,
1488 {
1489 let path = path.as_ref();
1490 if let Some(built_in_resource) = self.built_in_resources.get(path) {
1491 return built_in_resource.resource.clone();
1492 }
1493
1494 let path = self.resource_io.canonicalize_path(path).unwrap();
1495
1496 self.find_or_load(path)
1497 }
1498
1499 pub fn request_uuid(&mut self, uuid: Uuid) -> UntypedResource {
1501 let mut resource = uuid.into();
1502 self.request_resource(&mut resource);
1503 resource
1504 }
1505
1506 fn find_by_resource_path(&self, path_to_search: &Path) -> Option<&UntypedResource> {
1508 let registry = self.resource_registry.safe_lock();
1509 self.resources
1510 .iter()
1511 .find(move |entry| registry.uuid_to_path(entry.resource_uuid()) == Some(path_to_search))
1512 .map(|entry| &entry.value)
1513 }
1514
1515 fn find_or_load(&mut self, path: PathBuf) -> UntypedResource {
1521 match self.find_by_resource_path(&path) {
1522 Some(existing) => existing.clone(),
1523 None => self.load_resource(path),
1524 }
1525 }
1526
1527 fn find_uuid_or_register_new(&mut self, path: PathBuf) -> Result<Uuid, LoadError> {
1528 let mut registry = self.resource_registry.safe_lock();
1529 if let Some(uuid) = registry.path_to_uuid(&path) {
1530 Ok(uuid)
1531 } else if self.is_supported_resource(&path) {
1532 let uuid = Uuid::new_v4();
1536 registry.modify().register(uuid, path);
1537 Ok(uuid)
1538 } else {
1539 Err(LoadError::new(format!(
1540 "Unable to load resource {} because it is not supported!",
1541 path.display()
1542 )))
1543 }
1544 }
1545
1546 fn load_resource(&mut self, path: PathBuf) -> UntypedResource {
1547 let uuid = match self.find_uuid_or_register_new(path.clone()) {
1548 Ok(uuid) => uuid,
1549 Err(err) => return UntypedResource::new_load_error(ResourceKind::External, path, err),
1550 };
1551 let resource = UntypedResource::new_pending(uuid, ResourceKind::External);
1552 self.add_resource_and_notify(resource.clone());
1553 self.spawn_loading_task(resource.clone(), false);
1554 resource
1555 }
1556
1557 fn spawn_loading_task(&self, mut resource: UntypedResource, reload: bool) {
1560 let event_broadcaster = self.event_broadcaster.clone();
1561 let loaders = self.loaders.clone();
1562 let registry = self.resource_registry.clone();
1563 let io = self.resource_io.clone();
1564
1565 if !registry.safe_lock().status_flag().is_loaded() {
1566 resource.commit_error(
1567 PathBuf::default(),
1568 LoadError::new("The resource registry is unavailable!".to_string()),
1569 );
1570
1571 err!("The resource registry is unavailable!");
1572
1573 return;
1574 }
1575
1576 self.task_pool.spawn_task(async move {
1577 let Some(path) = registry
1578 .safe_lock()
1579 .uuid_to_path(resource.resource_uuid())
1580 .map(|p| p.to_path_buf())
1581 else {
1582 let error = format!(
1583 "Resource {} failed to load. The path was not found \
1584 in the registry!",
1585 resource.resource_uuid(),
1586 );
1587 resource.commit_error(PathBuf::default(), error);
1588 return;
1589 };
1590
1591 let loader_future = loaders
1593 .safe_lock()
1594 .loader_for(&path)
1595 .map(|loader| loader.load(path.clone(), io));
1596
1597 if let Some(loader_future) = loader_future {
1598 match loader_future.await {
1599 Ok(data) => {
1600 let data = data.0;
1601
1602 let mut header = resource.lock();
1603
1604 assert!(header.kind.is_external());
1605
1606 header.state.commit(ResourceState::Ok {
1607 data: ResourceDataWrapper(data),
1608 });
1609
1610 drop(header);
1611
1612 event_broadcaster.broadcast_loaded_or_reloaded(resource, reload);
1613
1614 Log::info(format!(
1615 "Resource {} was loaded successfully!",
1616 path.display()
1617 ));
1618 }
1619 Err(error) => {
1620 if reload {
1621 if resource.is_ok() {
1622 info!("Resource {path:?} failed to reload, keeping the existing version. Reason: {error}");
1623 }
1624 else
1625 {
1626 info!("Resource {path:?} failed to reload. Reason: {error}");
1627 resource.commit_error(path.to_path_buf(), error);
1628 }
1629 }
1630 else
1631 {
1632 info!("Resource {path:?} failed to load. Reason: {error}");
1633 resource.commit_error(path.to_path_buf(), error);
1634 }
1635 }
1636 }
1637 } else {
1638 let error = format!("There's no resource loader for {path:?} resource!",);
1639 resource.commit_error(path, error);
1640 }
1641 });
1642 }
1643
1644 pub fn resource_path(&self, resource: &UntypedResource) -> Option<PathBuf> {
1655 self.uuid_to_resource_path(resource.resource_uuid())
1656 }
1657
1658 pub fn uuid_to_resource_path(&self, resource_uuid: Uuid) -> Option<PathBuf> {
1667 if let Some(path) = self
1668 .resource_registry
1669 .safe_lock()
1670 .uuid_to_path_buf(resource_uuid)
1671 {
1672 Some(path)
1673 } else {
1674 self.built_in_resources
1675 .find_by_uuid(resource_uuid)
1676 .map(|built_in_resource| built_in_resource.id.clone())
1677 }
1678 }
1679
1680 pub fn registry_folder(&self) -> PathBuf {
1682 self.resource_registry
1683 .lock()
1684 .path()
1685 .parent()
1686 .and_then(|p| p.to_path_buf().canonicalize().ok())
1687 .unwrap_or_default()
1688 }
1689
1690 pub fn add_loader<T: ResourceLoader>(&self, loader: T) -> Option<T> {
1692 self.loaders.safe_lock().set(loader)
1693 }
1694
1695 pub fn request_resource(&mut self, resource: &mut UntypedResource) {
1698 if let Some(r) = self.find_by_uuid(resource.resource_uuid()) {
1699 *resource = r.clone();
1702 if resource.is_unloaded() {
1703 resource.make_pending();
1705 self.spawn_loading_task(resource.clone(), false);
1706 }
1707 } else if let Some(r) = self
1708 .built_in_resources
1709 .find_by_uuid(resource.resource_uuid())
1710 {
1711 *resource = r.resource.clone();
1714 } else if resource.is_ok() || resource.is_embedded() {
1715 self.add_resource_and_notify(resource.clone());
1718 } else {
1719 resource.make_pending();
1722 self.add_resource_and_notify(resource.clone());
1723 self.spawn_loading_task(resource.clone(), false);
1724 }
1725 }
1726
1727 pub fn add_resource(&mut self, resource: &mut UntypedResource) {
1731 if let Some(r) = self.find_by_uuid(resource.resource_uuid()) {
1732 *resource = r.clone();
1735 } else if let Some(r) = self
1736 .built_in_resources
1737 .find_by_uuid(resource.resource_uuid())
1738 {
1739 *resource = r.resource.clone();
1742 } else {
1743 self.add_resource_and_notify(resource.clone());
1745 }
1746 }
1747
1748 pub fn register(
1760 &mut self,
1761 resource: UntypedResource,
1762 path: impl AsRef<Path>,
1763 ) -> Result<(), ResourceRegistrationError> {
1764 let resource_uuid = resource.resource_uuid();
1765 if self.find_by_uuid(resource_uuid).is_some() {
1766 return Err(ResourceRegistrationError::AlreadyRegistered);
1767 }
1768 let path = self.resource_io.canonicalize_path(path.as_ref()).unwrap();
1769 {
1770 let mut header = resource.lock();
1771 header.kind.make_external();
1772 let mut registry = self.resource_registry.safe_lock();
1773 let mut ctx = registry.modify();
1774 if ctx.write_metadata(resource_uuid, path).is_err() {
1775 return Err(ResourceRegistrationError::UnableToCreateMetadata);
1776 }
1777 }
1778 self.add_resource_and_notify(resource);
1779 Ok(())
1780 }
1781
1782 pub fn reload_resource(&mut self, resource: UntypedResource) {
1785 if self.built_in_resources.is_built_in_resource(&resource) {
1786 return;
1787 }
1788 let mut header = resource.lock();
1789 if !header.state.is_loading() {
1790 if !header.state.is_ok() {
1791 header.state.switch_to_pending_state();
1792 }
1793 drop(header);
1794 self.spawn_loading_task(resource, true)
1795 }
1796 }
1797
1798 pub fn reload_resources(&mut self) -> Vec<UntypedResource> {
1801 let resources = self
1802 .resources
1803 .iter()
1804 .map(|r| r.value.clone())
1805 .collect::<Vec<_>>();
1806
1807 for resource in resources.iter().cloned() {
1808 self.reload_resource(resource);
1809 }
1810
1811 resources
1812 }
1813
1814 pub fn get_wait_context(&self) -> ResourceWaitContext {
1816 ResourceWaitContext {
1817 resources: self
1818 .resources
1819 .iter()
1820 .map(|e| e.value.clone())
1821 .collect::<Vec<_>>(),
1822 }
1823 }
1824
1825 pub fn try_reload_resource_from_path(&mut self, path: &Path) -> bool {
1829 if !self.loaders.safe_lock().is_supported_resource(path) {
1831 return false;
1832 }
1833
1834 let Some(resource) = self.find_by_resource_path(path) else {
1835 return false;
1836 };
1837 let header = resource.lock();
1838 if header.state.is_loading() {
1839 return false;
1840 }
1841 drop(header);
1842 self.reload_resource(resource.clone());
1843 true
1844 }
1845
1846 #[allow(clippy::await_holding_lock)]
1848 pub async fn make_resource_move_context(
1849 &self,
1850 src_path: impl AsRef<Path>,
1851 dest_path: impl AsRef<Path>,
1852 overwrite_existing: bool,
1853 ) -> Result<ResourceMoveContext, ResourceMovementError> {
1854 let src_path = src_path.as_ref();
1855 let dest_path = dest_path.as_ref();
1856
1857 let relative_src_path = self.resource_io.canonicalize_path(src_path)?;
1858 let relative_dest_path = self.resource_io.canonicalize_path(dest_path)?;
1859
1860 if let Some(file_stem) = relative_dest_path.file_stem() {
1861 if !self.resource_io.is_valid_file_name(file_stem) {
1862 return Err(ResourceMovementError::DestinationPathIsInvalid {
1863 src_path: relative_src_path.clone(),
1864 dest_path: relative_dest_path.clone(),
1865 });
1866 }
1867 }
1868
1869 if !overwrite_existing && self.resource_io.exists(&relative_dest_path).await {
1870 return Err(ResourceMovementError::AlreadyExist {
1871 src_path: relative_src_path.clone(),
1872 dest_path: relative_dest_path.clone(),
1873 });
1874 }
1875
1876 let registry_lock_guard = self.resource_registry.safe_lock();
1877 let registry_dir = if let Some(directory) = registry_lock_guard.directory() {
1878 self.resource_io.canonicalize_path(directory)?
1879 } else {
1880 return Err(ResourceMovementError::ResourceRegistryLocationUnknown {
1881 resource_path: relative_src_path.clone(),
1882 });
1883 };
1884 let resource_uuid = registry_lock_guard
1885 .path_to_uuid(&relative_src_path)
1886 .ok_or_else(|| ResourceMovementError::NotInRegistry {
1887 resource_path: relative_src_path.clone(),
1888 })?;
1889
1890 let Some(relative_dest_dir) = relative_dest_path.parent() else {
1891 return Err(ResourceMovementError::DestinationPathIsInvalid {
1892 src_path: relative_src_path.clone(),
1893 dest_path: relative_dest_path.clone(),
1894 });
1895 };
1896 if !relative_dest_dir.starts_with(®istry_dir) {
1897 return Err(ResourceMovementError::OutsideOfRegistry {
1898 absolute_src_path: relative_src_path,
1899 absolute_dest_dir: relative_dest_dir.to_path_buf(),
1900 absolute_registry_dir: registry_dir,
1901 });
1902 }
1903
1904 drop(registry_lock_guard);
1905
1906 Ok(ResourceMoveContext {
1907 relative_src_path,
1908 relative_dest_path,
1909 resource_uuid,
1910 })
1911 }
1912
1913 pub async fn can_resource_be_moved(
1917 &self,
1918 src_path: impl AsRef<Path>,
1919 dest_path: impl AsRef<Path>,
1920 overwrite_existing: bool,
1921 ) -> bool {
1922 self.make_resource_move_context(src_path, dest_path, overwrite_existing)
1923 .await
1924 .is_ok()
1925 }
1926
1927 pub async fn move_resource_by_path(
1931 &self,
1932 src_path: impl AsRef<Path>,
1933 dest_path: impl AsRef<Path>,
1934 overwrite_existing: bool,
1935 ) -> Result<(), ResourceMovementError> {
1936 let ResourceMoveContext {
1937 relative_src_path,
1938 relative_dest_path,
1939
1940 resource_uuid,
1941 } = self
1942 .make_resource_move_context(src_path, dest_path, overwrite_existing)
1943 .await?;
1944
1945 self.resource_io
1947 .move_file(&relative_src_path, &relative_dest_path)
1948 .await?;
1949
1950 let current_path = self
1951 .resource_registry
1952 .safe_lock()
1953 .modify()
1954 .register(resource_uuid, relative_dest_path.to_path_buf());
1955 assert_eq!(current_path.value.as_ref(), Some(&relative_src_path));
1956
1957 let options_path = append_extension(&relative_src_path, OPTIONS_EXTENSION);
1958 if self.resource_io.exists(&options_path).await {
1959 let new_options_path = append_extension(&relative_dest_path, OPTIONS_EXTENSION);
1960 self.resource_io
1961 .move_file(&options_path, &new_options_path)
1962 .await?;
1963 }
1964
1965 let metadata_path = append_extension(&relative_src_path, ResourceMetadata::EXTENSION);
1966 if self.resource_io.exists(&metadata_path).await {
1967 let new_metadata_path =
1968 append_extension(&relative_dest_path, ResourceMetadata::EXTENSION);
1969 self.resource_io
1970 .move_file(&metadata_path, &new_metadata_path)
1971 .await?;
1972 }
1973
1974 Ok(())
1975 }
1976
1977 pub async fn move_resource(
1981 &self,
1982 resource: impl AsRef<UntypedResource>,
1983 new_path: impl AsRef<Path>,
1984 overwrite_existing: bool,
1985 ) -> Result<(), ResourceMovementError> {
1986 let resource_path = self.resource_path(resource.as_ref()).ok_or_else(|| {
1987 FileError::Custom(
1988 "Cannot move the resource because it does not have a path!".to_string(),
1989 )
1990 })?;
1991
1992 self.move_resource_by_path(resource_path, new_path, overwrite_existing)
1993 .await
1994 }
1995
1996 pub fn is_supported_resource(&self, path: &Path) -> bool {
1998 let ext = some_or_return!(path.extension(), false);
1999 let ext = some_or_return!(ext.to_str(), false);
2000
2001 self.loaders
2002 .safe_lock()
2003 .iter()
2004 .any(|loader| loader.supports_extension(ext))
2005 }
2006
2007 pub fn is_path_in_registry(&self, path: &Path) -> bool {
2009 let registry = self.resource_registry.safe_lock();
2010 if let Some(registry_directory) = registry.directory() {
2011 if let Ok(canonical_registry_path) = registry_directory.canonicalize() {
2012 if let Ok(canonical_path) = path.canonicalize() {
2013 return canonical_path.starts_with(canonical_registry_path);
2014 }
2015 }
2016 }
2017 false
2018 }
2019
2020 pub async fn try_move_folder(
2022 &self,
2023 src_dir: &Path,
2024 dest_dir: &Path,
2025 overwrite_existing: bool,
2026 ) -> Result<(), FolderMovementError> {
2027 if dest_dir.starts_with(src_dir) {
2028 return Err(FolderMovementError::HierarchyError {
2029 src_dir: src_dir.to_path_buf(),
2030 dest_dir: dest_dir.to_path_buf(),
2031 });
2032 }
2033
2034 if !self.is_path_in_registry(dest_dir) {
2037 return Err(FolderMovementError::NotInRegistry {
2038 dest_dir: dest_dir.to_path_buf(),
2039 });
2040 }
2041
2042 let mut what_where_stack = vec![(src_dir.to_path_buf(), dest_dir.to_path_buf())];
2047 while let Some((src_dir, target_dir)) = what_where_stack.pop() {
2048 let src_dir_name = some_or_continue!(src_dir.file_name());
2049
2050 let target_sub_dir = target_dir.join(src_dir_name);
2051 if !self.resource_io.exists(&target_sub_dir).await {
2052 std::fs::create_dir(&target_sub_dir)?;
2053 }
2054
2055 let target_sub_dir_normalized = ok_or_continue!(make_relative_path(&target_sub_dir));
2056
2057 for path in self.resource_io.walk_directory(&src_dir, 1).await? {
2058 if path.is_file() {
2059 let file_name = some_or_continue!(path.file_name());
2060 if self.is_supported_resource(&path) {
2061 let dest_path = target_sub_dir_normalized.join(file_name);
2062 self.move_resource_by_path(path, &dest_path, overwrite_existing)
2063 .await?;
2064 }
2065 } else if path.is_dir() && path != src_dir {
2066 what_where_stack.push((path, target_sub_dir.clone()));
2069 }
2070 }
2071 }
2072
2073 std::fs::remove_dir_all(src_dir)?;
2074
2075 Ok(())
2076 }
2077}
2078
2079#[cfg(test)]
2080mod test {
2081 use super::*;
2082 use crate::io::FsResourceIo;
2083 use crate::{
2084 loader::{BoxedLoaderFuture, LoaderPayload, ResourceLoader},
2085 ResourceData,
2086 };
2087 use fyrox_core::{
2088 reflect::prelude::*,
2089 uuid::{uuid, Uuid},
2090 visitor::{Visit, VisitResult, Visitor},
2091 TypeUuidProvider,
2092 };
2093 use std::{error::Error, fs::File, time::Duration};
2094
2095 #[derive(Debug, Default, Clone, Reflect, Visit)]
2096 struct Stub {}
2097
2098 impl TypeUuidProvider for Stub {
2099 fn type_uuid() -> Uuid {
2100 uuid!("9d873ff4-3126-47e1-a492-7cd8e7168239")
2101 }
2102 }
2103
2104 impl ResourceData for Stub {
2105 fn type_uuid(&self) -> Uuid {
2106 <Self as TypeUuidProvider>::type_uuid()
2107 }
2108
2109 fn save(&mut self, _path: &Path) -> Result<(), Box<dyn Error>> {
2110 Err("Saving is not supported!".to_string().into())
2111 }
2112
2113 fn can_be_saved(&self) -> bool {
2114 false
2115 }
2116
2117 fn try_clone_box(&self) -> Option<Box<dyn ResourceData>> {
2118 Some(Box::new(self.clone()))
2119 }
2120 }
2121
2122 impl ResourceLoader for Stub {
2123 fn extensions(&self) -> &[&str] {
2124 &["txt"]
2125 }
2126
2127 fn data_type_uuid(&self) -> Uuid {
2128 <Stub as TypeUuidProvider>::type_uuid()
2129 }
2130
2131 fn load(&self, _path: PathBuf, _io: Arc<dyn ResourceIo>) -> BoxedLoaderFuture {
2132 Box::pin(async move { Ok(LoaderPayload::new(Stub::default())) })
2133 }
2134 }
2135
2136 fn new_resource_manager() -> ResourceManagerState {
2137 ResourceManagerState::new(Arc::new(FsResourceIo), Arc::new(Default::default()))
2138 }
2139
2140 fn remove_file_if_exists(path: &Path) -> std::io::Result<()> {
2141 match std::fs::remove_file(path) {
2142 Ok(()) => Ok(()),
2143 Err(e) => match e.kind() {
2144 std::io::ErrorKind::NotFound => Ok(()),
2145 _ => Err(e),
2146 },
2147 }
2148 }
2149
2150 #[test]
2151 fn resource_wait_context_is_all_loaded() {
2152 assert!(ResourceWaitContext::default().is_all_loaded());
2153
2154 let cx = ResourceWaitContext {
2155 resources: vec![
2156 UntypedResource::new_pending(Default::default(), ResourceKind::External),
2157 UntypedResource::new_load_error(
2158 ResourceKind::External,
2159 Default::default(),
2160 LoadError::default(),
2161 ),
2162 ],
2163 };
2164 assert!(!cx.is_all_loaded());
2165 }
2166
2167 #[test]
2168 fn resource_manager_state_new() {
2169 let state = new_resource_manager();
2170
2171 assert!(state.resources.is_empty());
2172 assert!(state.loaders.safe_lock().is_empty());
2173 assert!(state.built_in_resources.is_empty());
2174 assert!(state.constructors_container.is_empty());
2175 assert!(state.watcher.is_none());
2176 assert!(state.is_empty());
2177 }
2178
2179 #[test]
2180 fn resource_manager_state_set_watcher() {
2181 let mut state = new_resource_manager();
2182 assert!(state.watcher.is_none());
2183
2184 let path = PathBuf::from("test.txt");
2185 if File::create(path.clone()).is_ok() {
2186 let watcher = FileSystemWatcher::new(path.clone(), Duration::from_secs(1));
2187 state.set_watcher(watcher.ok());
2188 assert!(state.watcher.is_some());
2189 }
2190 }
2191
2192 #[test]
2193 fn resource_manager_state_push() {
2194 std::fs::create_dir_all("data").expect("Could not create data directory.");
2195 let mut state = new_resource_manager();
2196
2197 assert_eq!(state.count_loaded_resources(), 0);
2198 assert_eq!(state.count_pending_resources(), 0);
2199 assert_eq!(state.count_registered_resources(), 0);
2200 assert_eq!(state.len(), 0);
2201
2202 assert!(state
2203 .register(
2204 UntypedResource::new_pending(Default::default(), ResourceKind::External),
2205 "foo.bar",
2206 )
2207 .is_ok());
2208 assert!(state
2209 .register(
2210 UntypedResource::new_load_error(
2211 ResourceKind::External,
2212 Default::default(),
2213 LoadError::default()
2214 ),
2215 "foo.bar",
2216 )
2217 .is_ok());
2218 assert!(state
2219 .register(
2220 UntypedResource::new_ok(Uuid::new_v4(), Default::default(), Stub {}),
2221 "foo.bar",
2222 )
2223 .is_ok());
2224
2225 assert_eq!(state.count_registered_resources(), 3);
2226 assert_eq!(state.len(), 3);
2227 }
2228
2229 #[test]
2230 fn resource_manager_state_loading_progress() {
2231 let mut state = new_resource_manager();
2232
2233 assert_eq!(state.loading_progress(), 100);
2234
2235 state
2236 .register(
2237 UntypedResource::new_ok(Uuid::new_v4(), Default::default(), Stub {}),
2238 "foo.bar",
2239 )
2240 .unwrap();
2241
2242 assert_eq!(state.loading_progress(), 100);
2243 }
2244
2245 #[test]
2246 fn resource_manager_state_find() {
2247 let mut state = new_resource_manager();
2248
2249 let path = Path::new("foo.txt");
2250
2251 assert!(state.find_by_path(path).is_none());
2252
2253 let resource = UntypedResource::new_ok(Uuid::new_v4(), Default::default(), Stub {});
2254 state.register(resource.clone(), path).unwrap();
2255
2256 assert_eq!(state.find_by_path(path), Some(&resource));
2257 }
2258
2259 #[test]
2260 fn resource_manager_state_resources() {
2261 let mut state = new_resource_manager();
2262
2263 assert_eq!(state.resources(), Vec::new());
2264
2265 let r1 = UntypedResource::new_ok(Uuid::new_v4(), ResourceKind::External, Stub {});
2266 let r2 = UntypedResource::new_ok(Uuid::new_v4(), ResourceKind::External, Stub {});
2267 let r3 = UntypedResource::new_ok(Uuid::new_v4(), ResourceKind::External, Stub {});
2268 state.register(r1.clone(), "foo1.txt").unwrap();
2269 state.register(r2.clone(), "foo2.txt").unwrap();
2270 state.register(r3.clone(), "foo3.txt").unwrap();
2271
2272 assert_eq!(state.resources(), vec![r1.clone(), r2.clone(), r3.clone()]);
2273 assert!(state.iter().eq([&r1, &r2, &r3]));
2274 }
2275
2276 #[test]
2277 fn resource_manager_state_destroy_unused_resources() {
2278 let mut state = new_resource_manager();
2279
2280 state
2281 .register(
2282 UntypedResource::new_ok(Uuid::new_v4(), ResourceKind::External, Stub {}),
2283 "foo1.txt",
2284 )
2285 .unwrap();
2286 assert_eq!(state.len(), 1);
2287
2288 state.destroy_unused_resources();
2289 assert_eq!(state.len(), 0);
2290 }
2291
2292 #[test]
2293 fn resource_manager_state_request() {
2294 let mut state = new_resource_manager();
2295 let path = PathBuf::from("test.txt");
2296
2297 let resource = UntypedResource::new_ok(Uuid::new_v4(), ResourceKind::External, Stub {});
2298 state.register(resource.clone(), &path).unwrap();
2299
2300 let res = state.request(&path);
2301 assert_eq!(res, resource);
2302
2303 let res = state.request(path);
2304
2305 assert_eq!(res.kind(), ResourceKind::External);
2306 assert!(!res.is_loading());
2307 }
2308
2309 #[test]
2310 fn resource_manager_state_get_wait_context() {
2311 let mut state = new_resource_manager();
2312
2313 let resource = UntypedResource::new_ok(Uuid::new_v4(), ResourceKind::External, Stub {});
2314 state.add_resource_and_notify(resource.clone());
2315 let cx = state.get_wait_context();
2316
2317 assert!(cx.resources.eq(&vec![resource]));
2318 }
2319
2320 #[test]
2321 fn resource_manager_new() {
2322 let manager = ResourceManager::new(Arc::new(FsResourceIo), Arc::new(Default::default()));
2323
2324 assert!(manager.state.safe_lock().is_empty());
2325 assert!(manager.state().is_empty());
2326 }
2327
2328 #[test]
2329 fn resource_manager_register() {
2330 std::fs::create_dir_all("data2").expect("Could not create data directory.");
2331 let manager = ResourceManager::new(Arc::new(FsResourceIo), Arc::new(Default::default()));
2332 manager.state().resource_registry.lock().set_path("data2");
2333 let path = PathBuf::from("data2/test.txt");
2334 let metapath = append_extension(&path, ResourceMetadata::EXTENSION);
2335 remove_file_if_exists(&metapath).unwrap();
2336
2337 let resource = UntypedResource::new_pending(Default::default(), ResourceKind::External);
2338 let res = manager.register(resource.clone(), path.clone());
2339 assert!(res.is_ok());
2340
2341 let metadata = block_on(ResourceMetadata::load_from_file_async(
2342 &metapath,
2343 &*manager.resource_io(),
2344 ))
2345 .expect("Reading meta file failed");
2346 assert_eq!(resource.resource_uuid(), metadata.resource_id);
2347
2348 let uuid = Uuid::new_v4();
2349 let resource = UntypedResource::new_ok(uuid, ResourceKind::External, Stub {});
2350 let res = manager.register(resource.clone(), path.clone());
2351 assert!(res.is_ok());
2352
2353 assert_eq!(resource.resource_uuid(), uuid);
2354 let metadata = block_on(ResourceMetadata::load_from_file_async(
2355 &metapath,
2356 &*manager.resource_io(),
2357 ))
2358 .expect("Reading meta file failed");
2359 assert_eq!(metadata.resource_id, uuid);
2360 }
2361
2362 #[test]
2363 fn resource_manager_request_untyped() {
2364 let manager = ResourceManager::new(Arc::new(FsResourceIo), Arc::new(Default::default()));
2365 let resource = UntypedResource::new_ok(Uuid::new_v4(), Default::default(), Stub {});
2366 let res = manager.register(resource.clone(), PathBuf::from("foo.txt"));
2367 assert!(res.is_ok());
2368
2369 let res = manager.request_untyped(Path::new("foo.txt"));
2370 assert_eq!(res, resource);
2371 }
2372
2373 #[test]
2374 fn display_for_resource_registration_error() {
2375 assert_eq!(
2376 format!("{}", ResourceRegistrationError::AlreadyRegistered),
2377 "A resource is already registered!"
2378 );
2379 assert_eq!(
2380 format!("{}", ResourceRegistrationError::InvalidState),
2381 "A resource was in invalid state!"
2382 );
2383 assert_eq!(
2384 format!("{}", ResourceRegistrationError::UnableToRegister),
2385 "Unable to register the resource!"
2386 );
2387 }
2388
2389 #[test]
2390 fn debug_for_resource_registration_error() {
2391 assert_eq!(
2392 format!("{:?}", ResourceRegistrationError::AlreadyRegistered),
2393 "AlreadyRegistered"
2394 );
2395 assert_eq!(
2396 format!("{:?}", ResourceRegistrationError::InvalidState),
2397 "InvalidState"
2398 );
2399 assert_eq!(
2400 format!("{:?}", ResourceRegistrationError::UnableToRegister),
2401 "UnableToRegister"
2402 );
2403 }
2404}