1use crate::state::ResourceDataWrapper;
24use crate::ResourceHeaderGuard;
25use crate::{
26 core::{
27 parking_lot::Mutex, reflect::prelude::*, uuid, uuid::Uuid, visitor::prelude::*,
28 TypeUuidProvider,
29 },
30 manager::ResourceManager,
31 state::{LoadError, ResourceState},
32 Resource, ResourceData, ResourceLoadError, TypedResourceData,
33};
34use fxhash::FxHasher64;
35use fyrox_core::log::Log;
36use fyrox_core::parking_lot::MutexGuard;
37use fyrox_core::SafeLock;
38use serde::{Deserialize, Serialize};
39use std::fmt::Write;
40use std::{
41 error::Error,
42 fmt::{Debug, Display, Formatter},
43 future::Future,
44 hash::{Hash, Hasher},
45 marker::PhantomData,
46 path::{Path, PathBuf},
47 pin::Pin,
48 sync::Arc,
49 task::{Context, Poll},
50};
51
52const MISSING_RESOURCE_MANAGER: &str =
53 "Resource data constructor container must be provided when serializing resources!";
54
55#[derive(Default, Reflect, Debug, Visit, Copy, Clone, PartialEq, Eq, Hash)]
57pub enum ResourceKind {
58 #[default]
60 Embedded,
61 External,
72}
73
74impl ResourceKind {
75 #[inline]
77 pub fn make_external(&mut self) {
78 *self = ResourceKind::External;
79 }
80
81 #[inline]
83 pub fn make_embedded(&mut self) {
84 *self = ResourceKind::Embedded;
85 }
86
87 #[inline]
89 pub fn is_embedded(&self) -> bool {
90 matches!(self, Self::Embedded)
91 }
92
93 #[inline]
95 pub fn is_external(&self) -> bool {
96 !self.is_embedded()
97 }
98}
99
100impl Display for ResourceKind {
101 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
102 match self {
103 ResourceKind::Embedded => {
104 write!(f, "Embedded")
105 }
106 ResourceKind::External => {
107 write!(f, "External")
108 }
109 }
110 }
111}
112
113#[derive(Reflect, Clone, Debug)]
116pub struct ResourceHeader {
117 pub uuid: Uuid,
119 pub kind: ResourceKind,
121 pub state: ResourceState,
123}
124
125impl Default for ResourceHeader {
126 fn default() -> Self {
127 Self {
128 uuid: Uuid::new_v4(),
129 kind: Default::default(),
130 state: Default::default(),
131 }
132 }
133}
134
135impl From<Uuid> for ResourceHeader {
136 fn from(uuid: Uuid) -> Self {
137 Self {
138 uuid,
139 kind: ResourceKind::External,
140 state: ResourceState::Unloaded,
141 }
142 }
143}
144
145impl ResourceHeader {
146 pub fn type_uuid(&self) -> Option<Uuid> {
148 if let ResourceState::Ok { data } = &self.state {
149 Some(data.type_uuid())
150 } else {
151 None
152 }
153 }
154}
155
156impl Display for ResourceHeader {
157 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
158 Display::fmt(&self.uuid, f)?;
159 f.write_char(':')?;
160 match self.kind {
161 ResourceKind::Embedded => f.write_str("Embed")?,
162 ResourceKind::External => f.write_str("Extern")?,
163 }
164 f.write_char(':')?;
165 match &self.state {
166 ResourceState::Unloaded => f.write_str("Unloaded"),
167 ResourceState::Pending { .. } => f.write_str("Pending"),
168 ResourceState::LoadError { path, error } => write!(f, "Error({path:?}, {error})"),
169 ResourceState::Ok { .. } => f.write_str("Ok"),
170 }
171 }
172}
173
174impl Visit for ResourceHeader {
175 fn visit(&mut self, name: &str, visitor: &mut Visitor) -> VisitResult {
176 let mut region = visitor.enter_region(name)?;
177 if region.is_reading() {
178 self.kind = ResourceKind::Embedded;
179 let mut actual_type_uuid = Uuid::default();
180 actual_type_uuid.visit("TypeUuid", &mut region)?;
181 let resource_manager = region
182 .blackboard
183 .get::<ResourceManager>()
184 .expect(MISSING_RESOURCE_MANAGER)
185 .clone();
186 let Some(mut data) = resource_manager
187 .state()
188 .constructors_container
189 .try_create(&actual_type_uuid)
190 else {
191 return Err(VisitError::User(format!(
192 "There's no constructor registered for type {actual_type_uuid}!"
193 )));
194 };
195 data.visit("Data", &mut region)?;
196 self.state = ResourceState::Ok {
197 data: ResourceDataWrapper(data),
198 };
199 Ok(())
200 } else {
201 match (&self.kind, &mut self.state) {
202 (ResourceKind::Embedded, ResourceState::Ok { data }) => {
203 let mut type_uuid = data.type_uuid();
204 type_uuid.visit("TypeUuid", &mut region)?;
205 data.visit("Data", &mut region)
206 }
207 (ResourceKind::External, _) => {
208 Err(VisitError::User("Writing an external resource".into()))
209 }
210 _ => Err(VisitError::User(
211 "Writing an embedded resource that is not ok.".into(),
212 )),
213 }
214 }
215 }
216}
217
218#[derive(Default, Clone, Reflect, TypeUuidProvider, Deserialize)]
258#[serde(from = "Uuid")]
259#[type_uuid(id = "21613484-7145-4d1c-87d8-62fa767560ab")]
260pub struct UntypedResource(pub Arc<Mutex<ResourceHeader>>);
261
262impl Serialize for UntypedResource {
263 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
264 where
265 S: serde::Serializer,
266 {
267 let header = self.lock();
268 if header.kind == ResourceKind::Embedded {
269 panic!("Embedded resources cannot be serialized.");
270 }
271 header.uuid.serialize(serializer)
272 }
273}
274
275impl Visit for UntypedResource {
276 fn visit(&mut self, name: &str, visitor: &mut Visitor) -> VisitResult {
277 let result = self.visit_with_type_uuid(name, None, visitor);
278 if let Err(err) = &result {
279 Log::err(format!("Resource error for untyped resource: {err}"));
280 if let Ok(region) = visitor.enter_region(name) {
281 region.debug();
282 }
283 self.commit_error(PathBuf::default(), err.to_string());
284 }
285 result
286 }
287}
288
289impl Display for UntypedResource {
290 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
291 if let Some(header) = self.0.try_lock() {
292 Display::fmt(&header, f)
293 } else {
294 f.write_str("locked")
295 }
296 }
297}
298
299impl Debug for UntypedResource {
300 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
301 write!(f, "UntypedResource({self})")
302 }
303}
304
305impl PartialEq for UntypedResource {
306 fn eq(&self, other: &Self) -> bool {
307 Arc::ptr_eq(&self.0, &other.0)
308 }
309}
310
311impl Eq for UntypedResource {}
312
313impl Hash for UntypedResource {
314 fn hash<H: Hasher>(&self, state: &mut H) {
315 Arc::as_ptr(&self.0).hash(state);
316 }
317}
318
319impl From<Uuid> for UntypedResource {
320 fn from(uuid: Uuid) -> Self {
321 ResourceHeader::from(uuid).into()
322 }
323}
324
325impl From<ResourceHeader> for UntypedResource {
326 fn from(header: ResourceHeader) -> Self {
327 Self(Arc::new(Mutex::new(header)))
328 }
329}
330
331impl UntypedResource {
332 pub fn visit_with_type_uuid(
334 &mut self,
335 name: &str,
336 type_uuid: Option<Uuid>,
337 visitor: &mut Visitor,
338 ) -> VisitResult {
339 let mut region = visitor.enter_region(name)?;
340 if region.is_reading() {
341 let mut uuid = Uuid::default();
342 uuid.visit("Uuid", &mut region)?;
343 self.read_visit(uuid, type_uuid, &mut region)?;
344 drop(region);
345 let resource_manager = visitor
346 .blackboard
347 .get::<ResourceManager>()
348 .expect("Resource manager must be available when deserializing resources!")
349 .clone();
350 resource_manager.state().request_resource(self);
351 Ok(())
352 } else {
353 self.resource_uuid().visit("Uuid", &mut region)?;
354 let header_guard = self.lock();
355 let is_embedded = header_guard.kind.is_embedded();
356 let is_ok = header_guard.state.is_ok();
357 drop(header_guard);
358 if is_embedded && is_ok {
359 self.0.visit("Embedded", &mut region)
360 } else if is_embedded {
361 true.visit("Default", &mut region)
362 } else {
363 Ok(())
364 }
365 }
366 }
367 fn read_visit(
368 &mut self,
369 resource_uuid: Uuid,
370 type_uuid: Option<Uuid>,
371 visitor: &mut Visitor,
372 ) -> VisitResult {
373 let mut is_default = false;
374 if is_default.visit("Default", visitor).is_ok() && is_default {
375 *self = Self::default();
376 self.lock().uuid = resource_uuid;
377 Ok(())
378 } else if visitor.has_region("Embedded") {
379 self.0.visit("Embedded", visitor)?;
380 self.lock().uuid = resource_uuid;
381 if let (Some(expected), Some(actual)) = (type_uuid, self.lock().type_uuid()) {
382 if expected != actual {
383 return Err(format!(
384 "Unable to deserialize untyped resource into its typed \
385 version, because types do not match! Untyped resource has \
386 {actual} type, but the required type is {expected}.",
387 )
388 .into());
389 }
390 }
391 Ok(())
392 } else {
393 *self = resource_uuid.into();
394 Ok(())
395 }
396 }
397 pub fn typed_lock<T: TypedResourceData>(&self) -> ResourceHeaderGuard<'_, T> {
399 self.lock().into()
400 }
401 pub fn lock(&self) -> MutexGuard<'_, ResourceHeader> {
403 self.0.safe_lock()
404 }
405 pub fn try_typed_lock<T: TypedResourceData>(&self) -> Option<ResourceHeaderGuard<'_, T>> {
407 self.try_lock().map(|g| g.into())
408 }
409 pub fn try_lock(&self) -> Option<MutexGuard<'_, ResourceHeader>> {
411 self.0.try_lock()
412 }
413 pub fn new_unloaded(resource_uuid: Uuid) -> Self {
415 ResourceHeader {
416 uuid: resource_uuid,
417 kind: ResourceKind::External,
418 state: ResourceState::Unloaded,
419 }
420 .into()
421 }
422 pub fn new_pending(resource_uuid: Uuid, kind: ResourceKind) -> Self {
424 ResourceHeader {
425 uuid: resource_uuid,
426 kind,
427 state: ResourceState::new_pending(),
428 }
429 .into()
430 }
431
432 pub fn new_ok<T>(resource_uuid: Uuid, kind: ResourceKind, data: T) -> Self
435 where
436 T: ResourceData,
437 {
438 ResourceHeader {
439 uuid: resource_uuid,
440 kind,
441 state: ResourceState::new_ok(data),
442 }
443 .into()
444 }
445
446 pub fn new_ok_untyped(
448 resource_uuid: Uuid,
449 kind: ResourceKind,
450 data: Box<dyn ResourceData>,
451 ) -> Self {
452 ResourceHeader {
453 uuid: resource_uuid,
454 kind,
455 state: ResourceState::new_ok_untyped(data),
456 }
457 .into()
458 }
459
460 pub fn new_embedded<T: ResourceData>(data: T) -> Self {
463 Self::new_ok(Uuid::new_v4(), ResourceKind::Embedded, data)
464 }
465
466 pub fn new_load_error(kind: ResourceKind, path: PathBuf, error: LoadError) -> Self {
468 ResourceHeader {
469 uuid: Uuid::new_v4(),
470 kind,
471 state: ResourceState::new_load_error(path, error),
472 }
473 .into()
474 }
475
476 pub fn resource_uuid(&self) -> Uuid {
479 self.lock().uuid
480 }
481
482 pub fn type_uuid(&self) -> Option<Uuid> {
484 let header = self.lock();
485 match header.state {
486 ResourceState::Ok { ref data, .. } => Some(data.type_uuid()),
487 _ => None,
488 }
489 }
490
491 pub fn type_uuid_non_blocking(&self) -> Option<Uuid> {
494 let header = self.try_lock()?;
495 match header.state {
496 ResourceState::Ok { ref data, .. } => Some(data.type_uuid()),
497 _ => None,
498 }
499 }
500
501 pub fn data_type_name(&self) -> Option<String> {
504 match self.lock().state {
505 ResourceState::Ok { ref data, .. } => Some(Reflect::type_name(&**data).to_string()),
506 _ => None,
507 }
508 }
509
510 pub fn data_type_name_or_unknown(&self) -> String {
513 self.data_type_name()
514 .unwrap_or_else(|| "Unknown".to_string())
515 }
516
517 pub fn is_unloaded(&self) -> bool {
519 matches!(self.lock().state, ResourceState::Unloaded)
520 }
521 pub fn is_loading(&self) -> bool {
523 matches!(self.lock().state, ResourceState::Pending { .. })
524 }
525
526 pub fn is_ok(&self) -> bool {
528 matches!(self.lock().state, ResourceState::Ok { .. })
529 }
530
531 pub fn is_failed_to_load(&self) -> bool {
533 matches!(self.lock().state, ResourceState::LoadError { .. })
534 }
535
536 pub fn is_embedded(&self) -> bool {
539 self.lock().kind.is_embedded()
540 }
541
542 #[inline]
544 pub fn use_count(&self) -> usize {
545 Arc::strong_count(&self.0)
546 }
547
548 #[inline]
550 pub fn key(&self) -> u64 {
551 let mut hasher = FxHasher64::default();
552 self.hash(&mut hasher);
553 hasher.finish()
554 }
555
556 pub fn kind(&self) -> ResourceKind {
558 self.lock().kind
559 }
560
561 pub fn set_kind(&self, new_kind: ResourceKind) {
563 self.lock().kind = new_kind;
564 }
565
566 pub fn save(&self, path: &Path) -> Result<(), Box<dyn Error>> {
568 match self.lock().state {
569 ResourceState::Pending { .. }
570 | ResourceState::LoadError { .. }
571 | ResourceState::Unloaded => Err("Unable to save unloaded resource!".into()),
572 ResourceState::Ok { ref mut data, .. } => data.save(path),
573 }
574 }
575
576 pub fn try_cast<T>(&self) -> Option<Resource<T>>
578 where
579 T: TypedResourceData,
580 {
581 if self.type_uuid() == Some(<T as TypeUuidProvider>::type_uuid()) {
582 Some(Resource {
583 untyped: self.clone(),
584 phantom: PhantomData::<T>,
585 })
586 } else {
587 None
588 }
589 }
590
591 pub fn make_pending(&mut self) {
593 self.lock().state = ResourceState::new_pending();
594 }
595 #[inline]
598 pub fn commit(&self, state: ResourceState) {
599 self.lock().state.commit(state);
600 }
601
602 pub fn commit_ok<T: ResourceData>(&self, data: T) {
604 self.lock().state.commit_ok(data);
605 }
606
607 pub fn commit_error<E: ResourceLoadError>(&mut self, path: PathBuf, error: E) {
609 self.lock().state.commit_error(path, error);
610 }
611}
612
613impl Future for UntypedResource {
614 type Output = Result<Self, LoadError>;
615
616 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
617 let mut guard = self.lock();
618 match guard.state {
619 ResourceState::Pending { ref mut wakers, .. } => {
620 wakers.add_waker(cx.waker());
621 Poll::Pending
622 }
623 ResourceState::Unloaded => Poll::Ready(Err(LoadError::new(
624 "Unloaded resource is not loading".to_string(),
625 ))),
626 ResourceState::LoadError { ref error, .. } => Poll::Ready(Err(error.clone())),
627 ResourceState::Ok { .. } => Poll::Ready(Ok(self.clone())),
628 }
629 }
630}
631
632#[cfg(test)]
633mod test {
634 use futures::task::noop_waker;
635 use fyrox_core::futures;
636 use std::error::Error;
637 use std::task::{self};
638
639 use crate::io::FsResourceIo;
640
641 use super::*;
642
643 #[derive(Debug, Default, Reflect, Visit, Clone, Copy)]
644 struct Stub {}
645
646 impl ResourceData for Stub {
647 fn type_uuid(&self) -> Uuid {
648 Uuid::default()
649 }
650
651 fn save(&mut self, _path: &Path) -> Result<(), Box<dyn Error>> {
652 Err("Saving is not supported!".to_string().into())
653 }
654
655 fn can_be_saved(&self) -> bool {
656 false
657 }
658
659 fn try_clone_box(&self) -> Option<Box<dyn ResourceData>> {
660 Some(Box::new(*self))
661 }
662 }
663
664 impl TypeUuidProvider for Stub {
665 fn type_uuid() -> Uuid {
666 Uuid::default()
667 }
668 }
669
670 impl ResourceLoadError for str {}
671
672 #[test]
673 fn visit_for_untyped_resource() {
674 let mut r = UntypedResource::default();
675 let mut visitor = Visitor::default();
676
677 assert!(r.visit("name", &mut visitor).is_ok());
678
679 let mut cursor = std::io::Cursor::new(Vec::<u8>::new());
680 visitor
681 .save_binary_to_memory(&mut cursor)
682 .expect("Failed to write binary for visitor");
683 cursor.set_position(0);
684 let mut visitor = Visitor::load_binary_from_memory(cursor.get_ref())
685 .expect("Failed to read binary for visitor");
686 visitor.blackboard.register(Arc::new(ResourceManager::new(
687 Arc::new(FsResourceIo),
688 Arc::default(),
689 )));
690 assert!(r.visit("name", &mut visitor).is_ok());
691 assert!(r.is_embedded());
692 assert!(r.is_failed_to_load());
693 }
694
695 #[test]
696 fn untyped_resource_use_count() {
697 let r = UntypedResource::default();
698
699 assert_eq!(r.use_count(), 1);
700 }
701
702 #[test]
703 fn untyped_resource_try_cast() {
704 let r = UntypedResource::default();
705 let r2 = UntypedResource::new_ok(Uuid::new_v4(), ResourceKind::External, Stub {});
706
707 assert!(r.try_cast::<Stub>().is_none());
708 assert!(r2.try_cast::<Stub>().is_some());
709 }
710
711 #[test]
712 fn untyped_resource_poll() {
713 let stub = Stub {};
714
715 let waker = noop_waker();
716 let mut cx = task::Context::from_waker(&waker);
717
718 let mut r = UntypedResource::from(ResourceHeader {
719 uuid: Uuid::new_v4(),
720 kind: ResourceKind::External,
721 state: ResourceState::Ok {
722 data: ResourceDataWrapper(Box::new(stub)),
723 },
724 });
725 assert!(Pin::new(&mut r).poll(&mut cx).is_ready());
726
727 let mut r = UntypedResource::from(ResourceHeader {
728 uuid: Uuid::new_v4(),
729 kind: ResourceKind::External,
730 state: ResourceState::LoadError {
731 path: Default::default(),
732 error: Default::default(),
733 },
734 });
735 assert!(Pin::new(&mut r).poll(&mut cx).is_ready());
736 }
737}