1use crate::{
24 core::{
25 math::curve::Curve, parking_lot::Mutex, reflect::prelude::*, uuid, uuid::Uuid,
26 visitor::prelude::*, visitor::RegionGuard, TypeUuidProvider,
27 },
28 manager::ResourceManager,
29 state::{LoadError, ResourceState},
30 Resource, ResourceData, ResourceLoadError, TypedResourceData, CURVE_RESOURCE_UUID,
31 MODEL_RESOURCE_UUID, SHADER_RESOURCE_UUID, SOUND_BUFFER_RESOURCE_UUID, TEXTURE_RESOURCE_UUID,
32};
33use std::{
34 error::Error,
35 ffi::OsStr,
36 fmt::{Debug, Display, Formatter},
37 future::Future,
38 hash::{Hash, Hasher},
39 marker::PhantomData,
40 path::{Path, PathBuf},
41 pin::Pin,
42 sync::Arc,
43 task::{Context, Poll},
44};
45
46fn guess_uuid(region: &mut RegionGuard) -> Uuid {
48 assert!(region.is_reading());
49
50 let mut region = region.enter_region("Details").unwrap();
51
52 let mut mip_count = 0u32;
53 if mip_count.visit("MipCount", &mut region).is_ok() {
54 return TEXTURE_RESOURCE_UUID;
55 }
56
57 let mut curve = Curve::default();
58 if curve.visit("Curve", &mut region).is_ok() {
59 return CURVE_RESOURCE_UUID;
60 }
61
62 let mut id = 0u32;
63 if id.visit("Id", &mut region).is_ok() {
64 return SOUND_BUFFER_RESOURCE_UUID;
65 }
66
67 let mut path = PathBuf::new();
68 if path.visit("Path", &mut region).is_ok() {
69 let ext = path.extension().unwrap_or_default().to_ascii_lowercase();
70 if ext == OsStr::new("rgs") || ext == OsStr::new("fbx") {
71 return MODEL_RESOURCE_UUID;
72 } else if ext == OsStr::new("shader")
73 || path == OsStr::new("Standard")
74 || path == OsStr::new("StandardTwoSides")
75 || path == OsStr::new("StandardTerrain")
76 {
77 return SHADER_RESOURCE_UUID;
78 }
79 }
80
81 Default::default()
82}
83
84#[derive(Default, Reflect, Debug, Visit, Clone, PartialEq, Eq, Hash)]
86pub enum ResourceKind {
87 #[default]
89 Embedded,
90 External(PathBuf),
101}
102
103impl From<Option<PathBuf>> for ResourceKind {
104 fn from(value: Option<PathBuf>) -> Self {
105 match value {
106 None => Self::Embedded,
107 Some(path) => Self::External(path),
108 }
109 }
110}
111
112impl From<PathBuf> for ResourceKind {
113 fn from(value: PathBuf) -> Self {
114 Self::External(value)
115 }
116}
117
118impl<'a> From<&'a str> for ResourceKind {
119 fn from(value: &'a str) -> Self {
120 Self::External(value.into())
121 }
122}
123
124impl ResourceKind {
125 #[inline]
127 pub fn make_external(&mut self, path: PathBuf) {
128 *self = ResourceKind::External(path);
129 }
130
131 #[inline]
133 pub fn make_embedded(&mut self) {
134 *self = ResourceKind::Embedded;
135 }
136
137 #[inline]
139 pub fn is_embedded(&self) -> bool {
140 matches!(self, Self::Embedded)
141 }
142
143 #[inline]
145 pub fn is_external(&self) -> bool {
146 !self.is_embedded()
147 }
148
149 #[inline]
151 pub fn path(&self) -> Option<&Path> {
152 match self {
153 ResourceKind::Embedded => None,
154 ResourceKind::External(path) => Some(path),
155 }
156 }
157
158 #[inline]
160 pub fn path_owned(&self) -> Option<PathBuf> {
161 self.path().map(|p| p.to_path_buf())
162 }
163
164 #[inline]
167 pub fn into_path(self) -> Option<PathBuf> {
168 match self {
169 ResourceKind::Embedded => None,
170 ResourceKind::External(path) => Some(path),
171 }
172 }
173}
174
175impl Display for ResourceKind {
176 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
177 match self {
178 ResourceKind::Embedded => {
179 write!(f, "Embedded")
180 }
181 ResourceKind::External(path) => {
182 write!(f, "External ({})", path.display())
183 }
184 }
185 }
186}
187
188#[derive(Reflect, Debug)]
191pub struct ResourceHeader {
192 pub type_uuid: Uuid,
194 pub kind: ResourceKind,
196 pub state: ResourceState,
198}
199
200impl Visit for ResourceHeader {
201 fn visit(&mut self, name: &str, visitor: &mut Visitor) -> VisitResult {
202 let mut region = visitor.enter_region(name)?;
203
204 if region.is_reading() {
205 let mut id: u32 = 0;
206
207 if id.visit("Id", &mut region).is_ok() {
208 let mut type_uuid = Uuid::default();
211 if type_uuid.visit("TypeUuid", &mut region).is_err() {
212 type_uuid = guess_uuid(&mut region);
215 };
216
217 if id == 2 {
219 let resource_manager = region.blackboard.get::<ResourceManager>().expect(
220 "Resource data constructor container must be \
221 provided when serializing resources!",
222 );
223 let resource_manager_state = resource_manager.state();
224
225 if let Some(mut instance) = resource_manager_state
226 .constructors_container
227 .try_create(&type_uuid)
228 {
229 drop(resource_manager_state);
230
231 if let Ok(mut details_region) = region.enter_region("Details") {
232 if type_uuid == SOUND_BUFFER_RESOURCE_UUID {
233 let mut sound_region = details_region.enter_region("0")?;
234 let mut path = PathBuf::new();
235 path.visit("Path", &mut sound_region).unwrap();
236 self.kind.make_external(path);
237 } else {
238 let mut path = PathBuf::new();
239 path.visit("Path", &mut details_region).unwrap();
240 self.kind.make_external(path);
241 }
242 }
243
244 instance.visit("Details", &mut region)?;
245
246 self.state = ResourceState::Ok(instance);
247
248 return Ok(());
249 } else {
250 return Err(VisitError::User(format!(
251 "There's no constructor registered for type {type_uuid}!"
252 )));
253 }
254 } else {
255 self.state = ResourceState::LoadError {
256 error: LoadError::new("Old resource"),
257 };
258 }
259
260 return Ok(());
261 }
262 }
263
264 self.kind.visit("Kind", &mut region)?;
265 self.type_uuid.visit("TypeUuid", &mut region)?;
266
267 if self.kind == ResourceKind::Embedded {
268 self.state.visit("State", &mut region)?;
269 }
270
271 Ok(())
272 }
273}
274
275#[derive(Clone, Reflect, TypeUuidProvider)]
315#[type_uuid(id = "21613484-7145-4d1c-87d8-62fa767560ab")]
316pub struct UntypedResource(pub Arc<Mutex<ResourceHeader>>);
317
318impl Visit for UntypedResource {
319 fn visit(&mut self, name: &str, visitor: &mut Visitor) -> VisitResult {
320 self.0.visit(name, visitor)?;
321
322 if visitor.is_reading() && !self.is_embedded() {
324 let resource_manager = visitor
325 .blackboard
326 .get::<ResourceManager>()
327 .expect("Resource manager must be available when deserializing resources!");
328
329 let path = self.kind().path_owned().unwrap();
330 self.0 = resource_manager.request_untyped(path).0;
331 }
332
333 Ok(())
334 }
335}
336
337impl Default for UntypedResource {
338 fn default() -> Self {
339 Self(Arc::new(Mutex::new(ResourceHeader {
340 kind: Default::default(),
341 type_uuid: Default::default(),
342 state: ResourceState::new_load_error(LoadError::new(
343 "Default resource state of unknown type.",
344 )),
345 })))
346 }
347}
348
349impl Debug for UntypedResource {
350 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
351 writeln!(f, "Resource")
352 }
353}
354
355impl PartialEq for UntypedResource {
356 fn eq(&self, other: &Self) -> bool {
357 std::ptr::eq(&*self.0, &*other.0)
358 }
359}
360
361impl Eq for UntypedResource {}
362
363impl Hash for UntypedResource {
364 fn hash<H: Hasher>(&self, state: &mut H) {
365 state.write_u64(&*self.0 as *const _ as u64)
366 }
367}
368
369impl UntypedResource {
370 pub fn new_pending(kind: ResourceKind, type_uuid: Uuid) -> Self {
372 Self(Arc::new(Mutex::new(ResourceHeader {
373 kind,
374 type_uuid,
375 state: ResourceState::new_pending(),
376 })))
377 }
378
379 pub fn new_ok<T>(kind: ResourceKind, data: T) -> Self
382 where
383 T: ResourceData,
384 {
385 Self(Arc::new(Mutex::new(ResourceHeader {
386 kind,
387 type_uuid: data.type_uuid(),
388 state: ResourceState::new_ok(data),
389 })))
390 }
391
392 pub fn new_load_error(kind: ResourceKind, error: LoadError, type_uuid: Uuid) -> Self {
394 Self(Arc::new(Mutex::new(ResourceHeader {
395 kind,
396 type_uuid,
397 state: ResourceState::new_load_error(error),
398 })))
399 }
400
401 pub fn type_uuid(&self) -> Uuid {
403 self.0.lock().type_uuid
404 }
405
406 pub fn is_loading(&self) -> bool {
408 matches!(self.0.lock().state, ResourceState::Pending { .. })
409 }
410
411 pub fn is_embedded(&self) -> bool {
414 self.0.lock().kind.is_embedded()
415 }
416
417 #[inline]
419 pub fn use_count(&self) -> usize {
420 Arc::strong_count(&self.0)
421 }
422
423 #[inline]
425 pub fn key(&self) -> usize {
426 (&*self.0 as *const _) as usize
427 }
428
429 pub fn kind(&self) -> ResourceKind {
431 self.0.lock().kind.clone()
432 }
433
434 pub fn set_kind(&self, new_kind: ResourceKind) {
436 self.0.lock().kind = new_kind;
437 }
438
439 pub fn save(&self, path: &Path) -> Result<(), Box<dyn Error>> {
441 let mut guard = self.0.lock();
442 match guard.state {
443 ResourceState::Pending { .. } | ResourceState::LoadError { .. } => {
444 Err("Unable to save unloaded resource!".into())
445 }
446 ResourceState::Ok(ref mut data) => data.save(path),
447 }
448 }
449
450 pub fn save_back(&self) -> Result<(), Box<dyn Error>> {
453 match self.kind() {
454 ResourceKind::Embedded => Err("Embedded resource cannot be saved!".into()),
455 ResourceKind::External(path) => self.save(&path),
456 }
457 }
458
459 pub fn try_cast<T>(&self) -> Option<Resource<T>>
461 where
462 T: TypedResourceData,
463 {
464 if self.type_uuid() == <T as TypeUuidProvider>::type_uuid() {
465 Some(Resource {
466 untyped: self.clone(),
467 phantom: PhantomData::<T>,
468 })
469 } else {
470 None
471 }
472 }
473
474 #[inline]
477 pub fn commit(&self, state: ResourceState) {
478 self.0.lock().state.commit(state);
479 }
480
481 pub fn commit_ok<T: ResourceData>(&self, data: T) {
483 let mut guard = self.0.lock();
484 guard.type_uuid = data.type_uuid();
485 guard.state.commit_ok(data);
486 }
487
488 pub fn commit_error<E: ResourceLoadError>(&self, error: E) {
490 self.0.lock().state.commit_error(error);
491 }
492}
493
494impl Future for UntypedResource {
495 type Output = Result<Self, LoadError>;
496
497 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
498 let state = self.0.clone();
499 let mut guard = state.lock();
500 match guard.state {
501 ResourceState::Pending { ref mut wakers, .. } => {
502 let cx_waker = cx.waker();
504 if let Some(pos) = wakers.iter().position(|waker| waker.will_wake(cx_waker)) {
505 wakers[pos].clone_from(cx_waker);
506 } else {
507 wakers.push(cx_waker.clone())
508 }
509
510 Poll::Pending
511 }
512 ResourceState::LoadError { ref error, .. } => Poll::Ready(Err(error.clone())),
513 ResourceState::Ok(_) => Poll::Ready(Ok(self.clone())),
514 }
515 }
516}
517
518#[cfg(test)]
519mod test {
520 use futures::task::noop_waker;
521 use fyrox_core::futures;
522 use std::error::Error;
523 use std::task::{self};
524
525 use super::*;
526
527 #[derive(Debug, Default, Reflect, Visit, Clone, Copy)]
528 struct Stub {}
529
530 impl ResourceData for Stub {
531 fn type_uuid(&self) -> Uuid {
532 Uuid::default()
533 }
534
535 fn save(&mut self, _path: &Path) -> Result<(), Box<dyn Error>> {
536 Err("Saving is not supported!".to_string().into())
537 }
538
539 fn can_be_saved(&self) -> bool {
540 false
541 }
542 }
543
544 impl TypeUuidProvider for Stub {
545 fn type_uuid() -> Uuid {
546 Uuid::default()
547 }
548 }
549
550 impl ResourceLoadError for str {}
551
552 #[test]
553 fn visit_for_untyped_resource() {
554 let mut r = UntypedResource::default();
555 let mut visitor = Visitor::default();
556
557 assert!(r.visit("name", &mut visitor).is_ok());
558 }
559
560 #[test]
561 fn debug_for_untyped_resource() {
562 let r = UntypedResource::default();
563
564 assert_eq!(format!("{r:?}"), "Resource\n");
565 }
566
567 #[test]
568 fn untyped_resource_new_pending() {
569 let r = UntypedResource::new_pending(PathBuf::from("/foo").into(), Uuid::default());
570
571 assert_eq!(r.0.lock().type_uuid, Uuid::default());
572 assert_eq!(
573 r.0.lock().kind,
574 ResourceKind::External(PathBuf::from("/foo"))
575 );
576 }
577
578 #[test]
579 fn untyped_resource_new_load_error() {
580 let r = UntypedResource::new_load_error(
581 PathBuf::from("/foo").into(),
582 Default::default(),
583 Uuid::default(),
584 );
585
586 assert_eq!(r.0.lock().type_uuid, Uuid::default());
587 assert_eq!(
588 r.0.lock().kind,
589 ResourceKind::External(PathBuf::from("/foo"))
590 );
591 }
592
593 #[test]
594 fn untyped_resource_use_count() {
595 let r = UntypedResource::default();
596
597 assert_eq!(r.use_count(), 1);
598 }
599
600 #[test]
601 fn untyped_resource_try_cast() {
602 let r = UntypedResource::default();
603 let r2 = UntypedResource::new_pending(
604 PathBuf::from("/foo").into(),
605 Uuid::from_u128(0xa1a2a3a4b1b2c1c2d1d2d3d4d5d6d7d8u128),
606 );
607
608 assert!(r.try_cast::<Stub>().is_some());
609 assert!(r2.try_cast::<Stub>().is_none());
610 }
611
612 #[test]
613 fn untyped_resource_commit() {
614 let path = PathBuf::from("/foo");
615 let stub = Stub {};
616
617 let r = UntypedResource::new_pending(path.clone().into(), Default::default());
618 assert_eq!(r.0.lock().kind, ResourceKind::External(path.clone()));
619
620 r.commit(ResourceState::Ok(Box::new(stub)));
621 assert_eq!(r.0.lock().kind, ResourceKind::External(path));
622 }
623
624 #[test]
625 fn untyped_resource_commit_ok() {
626 let path = PathBuf::from("/foo");
627 let stub = Stub {};
628
629 let r = UntypedResource::new_pending(path.clone().into(), Default::default());
630 assert_eq!(r.0.lock().kind, ResourceKind::External(path.clone()));
631
632 r.commit_ok(stub);
633 assert_eq!(r.0.lock().kind, ResourceKind::External(path));
634 }
635
636 #[test]
637 fn untyped_resource_commit_error() {
638 let path = PathBuf::from("/foo");
639 let path2 = PathBuf::from("/bar");
640
641 let r = UntypedResource::new_pending(path.clone().into(), Default::default());
642 assert_eq!(r.0.lock().kind, ResourceKind::External(path));
643 assert_ne!(r.0.lock().kind, ResourceKind::External(path2));
644 }
645
646 #[test]
647 fn untyped_resource_poll() {
648 let path = PathBuf::from("/foo");
649 let stub = Stub {};
650
651 let waker = noop_waker();
652 let mut cx = task::Context::from_waker(&waker);
653
654 let mut r = UntypedResource(Arc::new(Mutex::new(ResourceHeader {
655 kind: path.clone().into(),
656 type_uuid: Uuid::default(),
657 state: ResourceState::Ok(Box::new(stub)),
658 })));
659 assert!(Pin::new(&mut r).poll(&mut cx).is_ready());
660
661 let mut r = UntypedResource(Arc::new(Mutex::new(ResourceHeader {
662 kind: path.clone().into(),
663 type_uuid: Uuid::default(),
664 state: ResourceState::LoadError {
665 error: Default::default(),
666 },
667 })));
668 assert!(Pin::new(&mut r).poll(&mut cx).is_ready());
669 }
670}