1use crate::{
25 collect_used_resources,
26 constructor::ResourceConstructorContainer,
27 core::{
28 append_extension,
29 futures::future::join_all,
30 io::FileLoadError,
31 log::Log,
32 make_relative_path, notify,
33 parking_lot::{Mutex, MutexGuard},
34 task::TaskPool,
35 watcher::FileSystemWatcher,
36 TypeUuidProvider,
37 },
38 entry::{TimedEntry, DEFAULT_RESOURCE_LIFETIME},
39 event::{ResourceEvent, ResourceEventBroadcaster},
40 io::{FsResourceIo, ResourceIo},
41 loader::{ResourceLoader, ResourceLoadersContainer},
42 options::OPTIONS_EXTENSION,
43 state::{LoadError, ResourceState},
44 untyped::ResourceKind,
45 Resource, ResourceData, TypedResourceData, UntypedResource,
46};
47use fxhash::{FxHashMap, FxHashSet};
48use rayon::prelude::*;
49use std::borrow::Cow;
50use std::ops::{Deref, DerefMut};
51use std::{
52 fmt::{Debug, Display, Formatter},
53 marker::PhantomData,
54 path::{Path, PathBuf},
55 sync::Arc,
56};
57
58#[must_use]
60#[derive(Default)]
61pub struct ResourceWaitContext {
62 resources: Vec<UntypedResource>,
63}
64
65impl ResourceWaitContext {
66 #[must_use]
68 pub fn is_all_loaded(&self) -> bool {
69 let mut loaded_count = 0;
70 for resource in self.resources.iter() {
71 if !matches!(resource.0.lock().state, ResourceState::Pending { .. }) {
72 loaded_count += 1;
73 }
74 }
75 loaded_count == self.resources.len()
76 }
77}
78
79#[derive(Clone)]
81pub struct DataSource {
82 pub extension: Cow<'static, str>,
84 pub bytes: Cow<'static, [u8]>,
86}
87
88impl DataSource {
89 pub fn new(path: &'static str, data: &'static [u8]) -> Self {
90 Self {
91 extension: Cow::Borrowed(
92 Path::new(path)
93 .extension()
94 .and_then(|ext| ext.to_str())
95 .unwrap_or(""),
96 ),
97 bytes: Cow::Borrowed(data),
98 }
99 }
100}
101
102#[macro_export]
103macro_rules! embedded_data_source {
104 ($path:expr) => {
105 $crate::manager::DataSource::new($path, include_bytes!($path))
106 };
107}
108
109#[derive(Clone)]
110pub struct UntypedBuiltInResource {
111 pub data_source: Option<DataSource>,
113 pub resource: UntypedResource,
115}
116
117pub struct BuiltInResource<T>
118where
119 T: TypedResourceData,
120{
121 pub data_source: Option<DataSource>,
123 pub resource: Resource<T>,
125}
126
127impl<T: TypedResourceData> Clone for BuiltInResource<T> {
128 fn clone(&self) -> Self {
129 Self {
130 data_source: self.data_source.clone(),
131 resource: self.resource.clone(),
132 }
133 }
134}
135
136impl<T: TypedResourceData> BuiltInResource<T> {
137 pub fn new<F>(data_source: DataSource, make: F) -> Self
138 where
139 F: FnOnce(&[u8]) -> Resource<T>,
140 {
141 let resource = make(&data_source.bytes);
142 Self {
143 resource,
144 data_source: Some(data_source),
145 }
146 }
147
148 pub fn new_no_source(resource: Resource<T>) -> Self {
149 Self {
150 data_source: None,
151 resource,
152 }
153 }
154
155 pub fn resource(&self) -> Resource<T> {
156 self.resource.clone()
157 }
158}
159
160impl<T: TypedResourceData> From<BuiltInResource<T>> for UntypedBuiltInResource {
161 fn from(value: BuiltInResource<T>) -> Self {
162 Self {
163 data_source: value.data_source,
164 resource: value.resource.into(),
165 }
166 }
167}
168
169#[derive(Default, Clone)]
170pub struct BuiltInResourcesContainer {
171 inner: FxHashMap<PathBuf, UntypedBuiltInResource>,
172}
173
174impl BuiltInResourcesContainer {
175 pub fn add<T>(&mut self, resource: BuiltInResource<T>)
176 where
177 T: TypedResourceData,
178 {
179 self.add_untyped(resource.into())
180 }
181
182 pub fn add_untyped(&mut self, resource: UntypedBuiltInResource) {
183 self.inner
184 .insert(resource.resource.kind().path_owned().unwrap(), resource);
185 }
186}
187
188impl Deref for BuiltInResourcesContainer {
189 type Target = FxHashMap<PathBuf, UntypedBuiltInResource>;
190
191 fn deref(&self) -> &Self::Target {
192 &self.inner
193 }
194}
195
196impl DerefMut for BuiltInResourcesContainer {
197 fn deref_mut(&mut self) -> &mut Self::Target {
198 &mut self.inner
199 }
200}
201
202pub struct ResourceManagerState {
204 pub loaders: ResourceLoadersContainer,
206 pub event_broadcaster: ResourceEventBroadcaster,
208 pub constructors_container: ResourceConstructorContainer,
210 pub built_in_resources: BuiltInResourcesContainer,
212 pub resource_io: Arc<dyn ResourceIo>,
214
215 resources: Vec<TimedEntry<UntypedResource>>,
216 task_pool: Arc<TaskPool>,
217 watcher: Option<FileSystemWatcher>,
218}
219
220#[derive(Clone)]
235pub struct ResourceManager {
236 state: Arc<Mutex<ResourceManagerState>>,
237}
238
239impl Debug for ResourceManager {
240 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
241 write!(f, "ResourceManager")
242 }
243}
244
245#[derive(Debug)]
247pub enum ResourceRegistrationError {
248 UnableToRegister,
250 InvalidState,
252 AlreadyRegistered,
254}
255
256impl Display for ResourceRegistrationError {
257 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
258 match self {
259 ResourceRegistrationError::UnableToRegister => {
260 write!(f, "Unable to register the resource!")
261 }
262 ResourceRegistrationError::InvalidState => {
263 write!(f, "A resource was in invalid state!")
264 }
265 ResourceRegistrationError::AlreadyRegistered => {
266 write!(f, "A resource is already registered!")
267 }
268 }
269 }
270}
271
272impl ResourceManager {
273 pub fn new(task_pool: Arc<TaskPool>) -> Self {
275 Self {
276 state: Arc::new(Mutex::new(ResourceManagerState::new(task_pool))),
277 }
278 }
279
280 pub fn state(&self) -> MutexGuard<'_, ResourceManagerState> {
282 self.state.lock()
283 }
284
285 pub fn resource_io(&self) -> Arc<dyn ResourceIo> {
287 let state = self.state();
288 state.resource_io.clone()
289 }
290
291 pub fn task_pool(&self) -> Arc<TaskPool> {
293 let state = self.state();
294 state.task_pool()
295 }
296
297 pub fn request<T>(&self, path: impl AsRef<Path>) -> Resource<T>
329 where
330 T: TypedResourceData,
331 {
332 let untyped = self.state().request(path);
333 let actual_type_uuid = untyped.type_uuid();
334 assert_eq!(actual_type_uuid, <T as TypeUuidProvider>::type_uuid());
335 Resource {
336 untyped,
337 phantom: PhantomData::<T>,
338 }
339 }
340
341 pub fn try_request<T>(&self, path: impl AsRef<Path>) -> Option<Resource<T>>
348 where
349 T: TypedResourceData,
350 {
351 let untyped = self.state().request(path);
352 let actual_type_uuid = untyped.type_uuid();
353 if actual_type_uuid == <T as TypeUuidProvider>::type_uuid() {
354 Some(Resource {
355 untyped,
356 phantom: PhantomData::<T>,
357 })
358 } else {
359 None
360 }
361 }
362
363 pub fn request_untyped<P>(&self, path: P) -> UntypedResource
365 where
366 P: AsRef<Path>,
367 {
368 self.state().request(path)
369 }
370
371 pub fn register<P, F>(
374 &self,
375 resource: UntypedResource,
376 path: P,
377 mut on_register: F,
378 ) -> Result<(), ResourceRegistrationError>
379 where
380 P: AsRef<Path>,
381 F: FnMut(&mut dyn ResourceData, &Path) -> bool,
382 {
383 let mut state = self.state();
384 if let Some(resource) = state.find(path.as_ref()) {
385 let resource_state = resource.0.lock();
386 if let ResourceState::Ok(_) = resource_state.state {
387 return Err(ResourceRegistrationError::AlreadyRegistered);
388 }
389 }
390
391 state.unregister(path.as_ref());
392
393 let mut header = resource.0.lock();
394 header.kind.make_external(path.as_ref().to_path_buf());
395 if let ResourceState::Ok(ref mut data) = header.state {
396 if !on_register(&mut **data, path.as_ref()) {
397 Err(ResourceRegistrationError::UnableToRegister)
398 } else {
399 drop(header);
400 state.push(resource);
401 Ok(())
402 }
403 } else {
404 Err(ResourceRegistrationError::InvalidState)
405 }
406 }
407
408 pub async fn move_resource(
410 &self,
411 resource: UntypedResource,
412 new_path: impl AsRef<Path>,
413 working_directory: impl AsRef<Path>,
414 mut filter: impl FnMut(&UntypedResource) -> bool,
415 ) -> Result<(), FileLoadError> {
416 let new_path = new_path.as_ref().to_owned();
417 let io = self.state().resource_io.clone();
418 let existing_path = resource
419 .kind()
420 .into_path()
421 .ok_or_else(|| FileLoadError::Custom("Cannot move embedded resource!".to_string()))?;
422
423 let canonical_existing_path = io.canonicalize_path(&existing_path).await?;
424
425 let resources = io
427 .walk_directory(working_directory.as_ref())
428 .await?
429 .map(|p| self.request_untyped(p))
430 .collect::<Vec<_>>();
431 let resources_to_fix = join_all(resources)
433 .await
434 .into_iter()
435 .filter_map(|r| r.ok())
436 .filter(|r| r != &resource && filter(r))
437 .collect::<Vec<_>>();
438
439 let mut pairs = resources_to_fix
441 .par_iter()
442 .filter_map(|loaded_resource| {
443 let mut guard = loaded_resource.0.lock();
444 if let ResourceState::Ok(ref mut data) = guard.state {
445 let mut used_resources = FxHashSet::default();
446 (**data).as_reflect(&mut |reflect| {
447 collect_used_resources(reflect, &mut used_resources);
448 });
449 Some((loaded_resource, used_resources))
450 } else {
451 None
452 }
453 })
454 .collect::<Vec<_>>();
455
456 for (_, used_resources) in pairs.iter_mut() {
458 let mut used_resources_with_references = FxHashSet::default();
459 for resource in used_resources.iter() {
460 if let Some(path) = resource.kind().into_path() {
462 if let Ok(canonical_resource_path) = io.canonicalize_path(&path).await {
463 if canonical_resource_path == canonical_existing_path {
468 used_resources_with_references.insert(resource.clone());
469 }
470 }
471 }
472 }
473 *used_resources = used_resources_with_references;
474 }
475
476 for (loaded_resource, used_resources) in pairs {
477 if !used_resources.is_empty() {
478 for resource in used_resources {
479 resource.set_kind(ResourceKind::External(new_path.clone()));
480 }
481
482 let mut header = loaded_resource.0.lock();
483 if let Some(loaded_resource_path) = header.kind.path_owned() {
484 if let ResourceState::Ok(ref mut data) = header.state {
485 match data.save(&loaded_resource_path) {
487 Ok(_) => Log::info(format!(
488 "Resource {} was saved successfully!",
489 header.kind
490 )),
491 Err(err) => Log::err(format!(
492 "Unable to save {} resource. Reason: {:?}",
493 header.kind, err
494 )),
495 };
496 }
497 }
498 }
499 }
500
501 io.move_file(&existing_path, &new_path).await?;
503 let options_path = append_extension(&existing_path, OPTIONS_EXTENSION);
504 if io.exists(&options_path).await {
505 let new_options_path = append_extension(&new_path, OPTIONS_EXTENSION);
506 io.move_file(&options_path, &new_options_path).await?;
507 }
508
509 Ok(())
510 }
511
512 pub async fn reload_resources(&self) {
516 let resources = self.state().reload_resources();
517 join_all(resources).await;
518 }
519}
520
521impl ResourceManagerState {
522 pub(crate) fn new(task_pool: Arc<TaskPool>) -> Self {
523 Self {
524 resources: Default::default(),
525 task_pool,
526 loaders: Default::default(),
527 event_broadcaster: Default::default(),
528 constructors_container: Default::default(),
529 watcher: None,
530 built_in_resources: Default::default(),
531 resource_io: Arc::new(FsResourceIo),
533 }
534 }
535
536 pub fn task_pool(&self) -> Arc<TaskPool> {
538 self.task_pool.clone()
539 }
540
541 pub fn set_resource_io(&mut self, resource_io: Arc<dyn ResourceIo>) {
544 self.resource_io = resource_io;
545 }
546
547 pub fn set_watcher(&mut self, watcher: Option<FileSystemWatcher>) {
552 self.watcher = watcher;
553 }
554
555 pub fn count_registered_resources(&self) -> usize {
557 self.resources.len()
558 }
559
560 pub fn loading_progress(&self) -> usize {
565 let registered = self.count_registered_resources();
566 if registered > 0 {
567 self.count_loaded_resources() * 100 / registered
568 } else {
569 100
570 }
571 }
572
573 pub fn update(&mut self, dt: f32) {
581 self.resources.retain_mut(|resource| {
582 if resource.value.use_count() <= 1 {
586 resource.time_to_live -= dt;
587 if resource.time_to_live <= 0.0 {
588 if let Some(path) = resource.0.lock().kind.path_owned() {
589 Log::info(format!(
590 "Resource {} destroyed because it is not used anymore!",
591 path.display()
592 ));
593
594 self.event_broadcaster
595 .broadcast(ResourceEvent::Removed(path));
596 }
597
598 false
599 } else {
600 true
602 }
603 } else {
604 resource.time_to_live = DEFAULT_RESOURCE_LIFETIME;
606
607 true
609 }
610 });
611
612 if let Some(watcher) = self.watcher.as_ref() {
613 if let Some(evt) = watcher.try_get_event() {
614 if let notify::EventKind::Modify(_) = evt.kind {
615 for path in evt.paths {
616 if let Ok(relative_path) = make_relative_path(path) {
617 if self.try_reload_resource_from_path(&relative_path) {
618 Log::info(format!(
619 "File {} was changed, trying to reload a respective resource...",
620 relative_path.display()
621 ));
622
623 break;
624 }
625 }
626 }
627 }
628 }
629 }
630 }
631
632 pub fn push(&mut self, resource: UntypedResource) {
634 self.event_broadcaster
635 .broadcast(ResourceEvent::Added(resource.clone()));
636
637 self.resources.push(TimedEntry {
638 value: resource,
639 time_to_live: DEFAULT_RESOURCE_LIFETIME,
640 });
641 }
642
643 pub fn find<P: AsRef<Path>>(&self, path: P) -> Option<&UntypedResource> {
649 for resource in self.resources.iter() {
650 if let Some(resource_path) = resource.0.lock().kind.path() {
651 if resource_path == path.as_ref() {
652 return Some(&resource.value);
653 }
654 }
655 }
656 None
657 }
658
659 pub fn len(&self) -> usize {
661 self.resources.len()
662 }
663
664 pub fn is_empty(&self) -> bool {
666 self.resources.is_empty()
667 }
668
669 pub fn iter(&self) -> impl Iterator<Item = &UntypedResource> {
671 self.resources.iter().map(|entry| &entry.value)
672 }
673
674 pub fn destroy_unused_resources(&mut self) {
676 self.resources
677 .retain(|resource| resource.value.use_count() > 1);
678 }
679
680 pub fn count_pending_resources(&self) -> usize {
682 self.resources.iter().fold(0, |counter, resource| {
683 if let ResourceState::Pending { .. } = resource.0.lock().state {
684 counter + 1
685 } else {
686 counter
687 }
688 })
689 }
690
691 pub fn count_loaded_resources(&self) -> usize {
693 self.resources.iter().fold(0, |counter, resource| {
694 if let ResourceState::Ok(_) = resource.0.lock().state {
695 counter + 1
696 } else {
697 counter
698 }
699 })
700 }
701
702 pub fn resources(&self) -> Vec<UntypedResource> {
704 self.resources.iter().map(|t| t.value.clone()).collect()
705 }
706
707 pub fn request<P>(&mut self, path: P) -> UntypedResource
709 where
710 P: AsRef<Path>,
711 {
712 if let Some(built_in_resource) = self.built_in_resources.get(path.as_ref()) {
713 return built_in_resource.resource.clone();
714 }
715
716 match self.find(path.as_ref()) {
717 Some(existing) => existing.clone(),
718 None => {
719 let path = path.as_ref().to_owned();
720 let kind = ResourceKind::External(path.clone());
721
722 if let Some(loader) = self.find_loader(path.as_ref()) {
723 let resource = UntypedResource::new_pending(kind, loader.data_type_uuid());
724 self.spawn_loading_task(path, resource.clone(), loader, false);
725 self.push(resource.clone());
726 resource
727 } else {
728 let err =
729 LoadError::new(format!("There's no resource loader for {kind} resource!",));
730 UntypedResource::new_load_error(kind, err, Default::default())
731 }
732 }
733 }
734 }
735
736 fn find_loader(&self, path: &Path) -> Option<&dyn ResourceLoader> {
737 path.extension().and_then(|extension| {
738 self.loaders
739 .iter()
740 .find(|loader| loader.supports_extension(&extension.to_string_lossy()))
741 })
742 }
743
744 fn spawn_loading_task(
745 &self,
746 path: PathBuf,
747 resource: UntypedResource,
748 loader: &dyn ResourceLoader,
749 reload: bool,
750 ) {
751 let event_broadcaster = self.event_broadcaster.clone();
752 let loader_future = loader.load(path.clone(), self.resource_io.clone());
753 self.task_pool.spawn_task(async move {
754 match loader_future.await {
755 Ok(data) => {
756 let data = data.0;
757
758 Log::info(format!(
759 "Resource {} was loaded successfully!",
760 path.display()
761 ));
762
763 {
765 let mut mutex_guard = resource.0.lock();
766 assert_eq!(mutex_guard.type_uuid, data.type_uuid());
767 assert!(mutex_guard.kind.is_external());
768 mutex_guard.state.commit(ResourceState::Ok(data));
769 }
770
771 event_broadcaster.broadcast_loaded_or_reloaded(resource, reload);
772 }
773 Err(error) => {
774 Log::info(format!(
775 "Resource {} failed to load. Reason: {:?}",
776 path.display(),
777 error
778 ));
779
780 resource.commit_error(error);
781 }
782 }
783 });
784 }
785
786 pub fn reload_resource(&mut self, resource: UntypedResource) {
788 let mut header = resource.0.lock();
789
790 if !header.state.is_loading() {
791 if let Some(path) = header.kind.path_owned() {
792 if let Some(loader) = self.find_loader(&path) {
793 header.state.switch_to_pending_state();
794 drop(header);
795
796 self.spawn_loading_task(path, resource, loader, true);
797 } else {
798 let msg = format!(
799 "There's no resource loader for {} resource!",
800 path.display()
801 );
802 Log::err(&msg);
803 resource.commit_error(msg)
804 }
805 } else {
806 Log::err("Cannot reload embedded resource.")
807 }
808 }
809 }
810
811 pub fn reload_resources(&mut self) -> Vec<UntypedResource> {
814 let resources = self
815 .resources
816 .iter()
817 .map(|r| r.value.clone())
818 .collect::<Vec<_>>();
819
820 for resource in resources.iter().cloned() {
821 self.reload_resource(resource);
822 }
823
824 resources
825 }
826
827 pub fn get_wait_context(&self) -> ResourceWaitContext {
829 ResourceWaitContext {
830 resources: self
831 .resources
832 .iter()
833 .map(|e| e.value.clone())
834 .collect::<Vec<_>>(),
835 }
836 }
837
838 pub fn try_reload_resource_from_path(&mut self, path: &Path) -> bool {
840 if let Some(resource) = self.find(path).cloned() {
841 self.reload_resource(resource);
842 true
843 } else {
844 false
845 }
846 }
847
848 pub fn unregister(&mut self, path: &Path) {
851 if let Some(position) = self
852 .resources
853 .iter()
854 .position(|r| r.kind().path() == Some(path))
855 {
856 self.resources.remove(position);
857 }
858 }
859}
860
861#[cfg(test)]
862mod test {
863 use std::error::Error;
864 use std::{fs::File, time::Duration};
865
866 use crate::loader::{BoxedLoaderFuture, LoaderPayload, ResourceLoader};
867
868 use super::*;
869
870 use fyrox_core::uuid::{uuid, Uuid};
871 use fyrox_core::{
872 reflect::{FieldInfo, Reflect},
873 visitor::{Visit, VisitResult, Visitor},
874 TypeUuidProvider,
875 };
876
877 #[derive(Debug, Default, Reflect, Visit)]
878 struct Stub {}
879
880 impl TypeUuidProvider for Stub {
881 fn type_uuid() -> Uuid {
882 uuid!("9d873ff4-3126-47e1-a492-7cd8e7168239")
883 }
884 }
885
886 impl ResourceData for Stub {
887 fn type_uuid(&self) -> Uuid {
888 <Self as TypeUuidProvider>::type_uuid()
889 }
890
891 fn save(&mut self, _path: &Path) -> Result<(), Box<dyn Error>> {
892 Err("Saving is not supported!".to_string().into())
893 }
894
895 fn can_be_saved(&self) -> bool {
896 false
897 }
898 }
899
900 impl ResourceLoader for Stub {
901 fn extensions(&self) -> &[&str] {
902 &["txt"]
903 }
904
905 fn data_type_uuid(&self) -> Uuid {
906 <Stub as TypeUuidProvider>::type_uuid()
907 }
908
909 fn load(&self, _path: PathBuf, _io: Arc<dyn ResourceIo>) -> BoxedLoaderFuture {
910 Box::pin(async move { Ok(LoaderPayload::new(Stub::default())) })
911 }
912 }
913
914 fn new_resource_manager() -> ResourceManagerState {
915 ResourceManagerState::new(Arc::new(Default::default()))
916 }
917
918 #[test]
919 fn resource_wait_context_is_all_loaded() {
920 assert!(ResourceWaitContext::default().is_all_loaded());
921
922 let path = PathBuf::from("test.txt");
923 let type_uuid = Uuid::default();
924
925 let cx = ResourceWaitContext {
926 resources: vec![
927 UntypedResource::new_pending(path.clone().into(), type_uuid),
928 UntypedResource::new_load_error(path.clone().into(), Default::default(), type_uuid),
929 ],
930 };
931 assert!(!cx.is_all_loaded());
932 }
933
934 #[test]
935 fn resource_manager_state_new() {
936 let state = new_resource_manager();
937
938 assert!(state.resources.is_empty());
939 assert!(state.loaders.is_empty());
940 assert!(state.built_in_resources.is_empty());
941 assert!(state.constructors_container.is_empty());
942 assert!(state.watcher.is_none());
943 assert!(state.is_empty());
944 }
945
946 #[test]
947 fn resource_manager_state_set_watcher() {
948 let mut state = new_resource_manager();
949 assert!(state.watcher.is_none());
950
951 let path = PathBuf::from("test.txt");
952 if File::create(path.clone()).is_ok() {
953 let watcher = FileSystemWatcher::new(path.clone(), Duration::from_secs(1));
954 state.set_watcher(watcher.ok());
955 assert!(state.watcher.is_some());
956 }
957 }
958
959 #[test]
960 fn resource_manager_state_push() {
961 let mut state = new_resource_manager();
962
963 assert_eq!(state.count_loaded_resources(), 0);
964 assert_eq!(state.count_pending_resources(), 0);
965 assert_eq!(state.count_registered_resources(), 0);
966 assert_eq!(state.len(), 0);
967
968 let path = PathBuf::from("test.txt");
969 let type_uuid = Uuid::default();
970 state.push(UntypedResource::new_pending(path.clone().into(), type_uuid));
971 state.push(UntypedResource::new_load_error(
972 path.clone().into(),
973 Default::default(),
974 type_uuid,
975 ));
976 state.push(UntypedResource::new_ok(Default::default(), Stub {}));
977
978 assert_eq!(state.count_loaded_resources(), 1);
979 assert_eq!(state.count_pending_resources(), 1);
980 assert_eq!(state.count_registered_resources(), 3);
981 assert_eq!(state.len(), 3);
982 }
983
984 #[test]
985 fn resource_manager_state_loading_progress() {
986 let mut state = new_resource_manager();
987
988 assert_eq!(state.loading_progress(), 100);
989
990 let path = PathBuf::from("test.txt");
991 let type_uuid = Uuid::default();
992 state.push(UntypedResource::new_pending(path.clone().into(), type_uuid));
993 state.push(UntypedResource::new_load_error(
994 path.clone().into(),
995 Default::default(),
996 type_uuid,
997 ));
998 state.push(UntypedResource::new_ok(Default::default(), Stub {}));
999
1000 assert_eq!(state.loading_progress(), 33);
1001 }
1002
1003 #[test]
1004 fn resource_manager_state_find() {
1005 let mut state = new_resource_manager();
1006
1007 assert!(state.find(Path::new("foo.txt")).is_none());
1008
1009 let path = PathBuf::from("test.txt");
1010 let type_uuid = Uuid::default();
1011 let resource = UntypedResource::new_pending(path.clone().into(), type_uuid);
1012 state.push(resource.clone());
1013
1014 assert_eq!(state.find(path), Some(&resource));
1015 }
1016
1017 #[test]
1018 fn resource_manager_state_resources() {
1019 let mut state = new_resource_manager();
1020
1021 assert_eq!(state.resources(), Vec::new());
1022
1023 let path = PathBuf::from("test.txt");
1024 let type_uuid = Uuid::default();
1025 let r1 = UntypedResource::new_pending(path.clone().into(), type_uuid);
1026 let r2 =
1027 UntypedResource::new_load_error(path.clone().into(), Default::default(), type_uuid);
1028 let r3 = UntypedResource::new_ok(Default::default(), Stub {});
1029 state.push(r1.clone());
1030 state.push(r2.clone());
1031 state.push(r3.clone());
1032
1033 assert_eq!(state.resources(), vec![r1.clone(), r2.clone(), r3.clone()]);
1034 assert!(state.iter().eq([&r1, &r2, &r3]));
1035 }
1036
1037 #[test]
1038 fn resource_manager_state_destroy_unused_resources() {
1039 let mut state = new_resource_manager();
1040
1041 state.push(UntypedResource::new_pending(
1042 PathBuf::from("test.txt").into(),
1043 Uuid::default(),
1044 ));
1045 assert_eq!(state.len(), 1);
1046
1047 state.destroy_unused_resources();
1048 assert_eq!(state.len(), 0);
1049 }
1050
1051 #[test]
1052 fn resource_manager_state_request() {
1053 let mut state = new_resource_manager();
1054 let path = PathBuf::from("test.txt");
1055 let type_uuid = Uuid::default();
1056
1057 let resource =
1058 UntypedResource::new_load_error(path.clone().into(), Default::default(), type_uuid);
1059 state.push(resource.clone());
1060
1061 let res = state.request(path);
1062 assert_eq!(res, resource);
1063
1064 let path = PathBuf::from("foo.txt");
1065 let res = state.request(path.clone());
1066
1067 assert_eq!(res.kind(), ResourceKind::External(path.clone()));
1068 assert_eq!(res.type_uuid(), type_uuid);
1069 assert!(!res.is_loading());
1070 }
1071
1072 #[test]
1073 fn resource_manager_state_try_reload_resource_from_path() {
1074 let mut state = new_resource_manager();
1075 state.loaders.set(Stub {});
1076
1077 let resource = UntypedResource::new_load_error(
1078 PathBuf::from("test.txt").into(),
1079 Default::default(),
1080 Uuid::default(),
1081 );
1082 state.push(resource.clone());
1083
1084 assert!(!state.try_reload_resource_from_path(Path::new("foo.txt")));
1085
1086 assert!(state.try_reload_resource_from_path(Path::new("test.txt")));
1087 assert!(resource.is_loading());
1088 }
1089
1090 #[test]
1091 fn resource_manager_state_get_wait_context() {
1092 let mut state = new_resource_manager();
1093
1094 let resource = UntypedResource::new_ok(Default::default(), Stub {});
1095 state.push(resource.clone());
1096 let cx = state.get_wait_context();
1097
1098 assert!(cx.resources.eq(&vec![resource]));
1099 }
1100
1101 #[test]
1102 fn resource_manager_new() {
1103 let manager = ResourceManager::new(Arc::new(Default::default()));
1104
1105 assert!(manager.state.lock().is_empty());
1106 assert!(manager.state().is_empty());
1107 }
1108
1109 #[test]
1110 fn resource_manager_register() {
1111 let manager = ResourceManager::new(Arc::new(Default::default()));
1112 let path = PathBuf::from("test.txt");
1113 let type_uuid = Uuid::default();
1114
1115 let resource = UntypedResource::new_pending(path.clone().into(), type_uuid);
1116 let res = manager.register(resource.clone(), path.clone(), |_, __| true);
1117 assert!(res.is_err());
1118
1119 let resource = UntypedResource::new_ok(Default::default(), Stub {});
1120 let res = manager.register(resource.clone(), path.clone(), |_, __| true);
1121 assert!(res.is_ok());
1122 }
1123
1124 #[test]
1125 fn resource_manager_request() {
1126 let manager = ResourceManager::new(Arc::new(Default::default()));
1127 let untyped = UntypedResource::new_ok(Default::default(), Stub {});
1128 let res = manager.register(untyped.clone(), PathBuf::from("foo.txt"), |_, __| true);
1129 assert!(res.is_ok());
1130
1131 let res: Resource<Stub> = manager.request(Path::new("foo.txt"));
1132 assert_eq!(
1133 res,
1134 Resource {
1135 untyped,
1136 phantom: PhantomData::<Stub>
1137 }
1138 );
1139 }
1140
1141 #[test]
1142 fn resource_manager_request_untyped() {
1143 let manager = ResourceManager::new(Arc::new(Default::default()));
1144 let resource = UntypedResource::new_ok(Default::default(), Stub {});
1145 let res = manager.register(resource.clone(), PathBuf::from("foo.txt"), |_, __| true);
1146 assert!(res.is_ok());
1147
1148 let res = manager.request_untyped(Path::new("foo.txt"));
1149 assert_eq!(res, resource);
1150 }
1151
1152 #[test]
1153 fn display_for_resource_registration_error() {
1154 assert_eq!(
1155 format!("{}", ResourceRegistrationError::AlreadyRegistered),
1156 "A resource is already registered!"
1157 );
1158 assert_eq!(
1159 format!("{}", ResourceRegistrationError::InvalidState),
1160 "A resource was in invalid state!"
1161 );
1162 assert_eq!(
1163 format!("{}", ResourceRegistrationError::UnableToRegister),
1164 "Unable to register the resource!"
1165 );
1166 }
1167
1168 #[test]
1169 fn debug_for_resource_registration_error() {
1170 assert_eq!(
1171 format!("{:?}", ResourceRegistrationError::AlreadyRegistered),
1172 "AlreadyRegistered"
1173 );
1174 assert_eq!(
1175 format!("{:?}", ResourceRegistrationError::InvalidState),
1176 "InvalidState"
1177 );
1178 assert_eq!(
1179 format!("{:?}", ResourceRegistrationError::UnableToRegister),
1180 "UnableToRegister"
1181 );
1182 }
1183}