Skip to main content

fyrox_resource/
constructor.rs

1// Copyright (c) 2019-present Dmitry Stepanov and Fyrox Engine contributors.
2//
3// Permission is hereby granted, free of charge, to any person obtaining a copy
4// of this software and associated documentation files (the "Software"), to deal
5// in the Software without restriction, including without limitation the rights
6// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7// copies of the Software, and to permit persons to whom the Software is
8// furnished to do so, subject to the following conditions:
9//
10// The above copyright notice and this permission notice shall be included in all
11// copies or substantial portions of the Software.
12//
13// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19// SOFTWARE.
20
21//! A module for creating resources by their UUIDs. It is used to make resource system type-agnostic
22//! yet serializable/deserializable. Type UUID is saved together with resource state and used later
23//! on deserialization to create a default instance of corresponding resource.
24
25use crate::{
26    core::{parking_lot::Mutex, uuid::Uuid, SafeLock, TypeUuidProvider},
27    ResourceData,
28};
29use fxhash::FxHashMap;
30
31/// A simple type alias for boxed resource constructor.
32pub struct ResourceDataConstructor {
33    /// Type name of the resource, produced by this constructor.
34    pub type_name: String,
35    /// Boxed callback, that is able to produce a resource in the default state.
36    pub callback: Box<dyn FnMut() -> Box<dyn ResourceData> + Send>,
37}
38
39impl ResourceDataConstructor {
40    /// Creates a new resource instance in the default state.
41    pub fn create_instance(&mut self) -> Box<dyn ResourceData> {
42        (self.callback)()
43    }
44}
45
46/// A special container that is able to create resources by their type UUID.
47#[derive(Default)]
48pub struct ResourceConstructorContainer {
49    /// Map of `Type UUID -> Constructor`
50    pub map: Mutex<FxHashMap<Uuid, ResourceDataConstructor>>,
51}
52
53impl ResourceConstructorContainer {
54    /// Creates default resource data constructor container.
55    pub fn new() -> Self {
56        ResourceConstructorContainer::default()
57    }
58
59    /// Adds new type constructor for a given type and return previous constructor for the type
60    /// (if any).
61    pub fn add<T>(&self)
62    where
63        T: ResourceData + Default + TypeUuidProvider,
64    {
65        let previous = self.map.safe_lock().insert(
66            <T as TypeUuidProvider>::type_uuid(),
67            ResourceDataConstructor {
68                callback: Box::new(|| Box::<T>::default()),
69                type_name: std::any::type_name::<T>().to_owned(),
70            },
71        );
72
73        assert!(previous.is_none());
74    }
75
76    /// Adds custom type constructor.
77    pub fn add_custom(&self, type_uuid: Uuid, constructor: ResourceDataConstructor) {
78        self.map.safe_lock().insert(type_uuid, constructor);
79    }
80
81    /// Unregisters type constructor.
82    pub fn remove(&self, type_uuid: Uuid) {
83        self.map.safe_lock().remove(&type_uuid);
84    }
85
86    /// Makes an attempt to create a resource data using provided type UUID. It may fail if there is no
87    /// resource data constructor for specified type UUID.
88    pub fn try_create(&self, type_uuid: &Uuid) -> Option<Box<dyn ResourceData>> {
89        self.map
90            .safe_lock()
91            .get_mut(type_uuid)
92            .map(|c| c.create_instance())
93    }
94
95    /// Returns total amount of constructors.
96    pub fn len(&self) -> usize {
97        self.map.safe_lock().len()
98    }
99
100    /// Returns true if the container is empty.
101    pub fn is_empty(&self) -> bool {
102        self.len() == 0
103    }
104}
105
106#[cfg(test)]
107mod test {
108    use fyrox_core::reflect::prelude::*;
109    use fyrox_core::visitor::{Visit, VisitResult, Visitor};
110    use std::error::Error;
111    use std::path::Path;
112
113    use super::*;
114
115    #[derive(Debug, Default, Clone, Reflect, Visit)]
116    struct Stub {}
117
118    impl ResourceData for Stub {
119        fn type_uuid(&self) -> Uuid {
120            Uuid::default()
121        }
122
123        fn save(&mut self, _path: &Path) -> Result<(), Box<dyn Error>> {
124            Err("Saving is not supported!".to_string().into())
125        }
126
127        fn can_be_saved(&self) -> bool {
128            false
129        }
130
131        fn try_clone_box(&self) -> Option<Box<dyn ResourceData>> {
132            Some(Box::new(self.clone()))
133        }
134    }
135
136    impl TypeUuidProvider for Stub {
137        fn type_uuid() -> Uuid {
138            Uuid::default()
139        }
140    }
141
142    #[test]
143    fn resource_constructor_container_new() {
144        let c = ResourceConstructorContainer::new();
145
146        assert_eq!(c.len(), 0);
147
148        c.add::<Stub>();
149        assert_eq!(c.len(), 1);
150    }
151
152    #[test]
153    fn resource_constructor_container_add_custom() {
154        let c = ResourceConstructorContainer::new();
155
156        assert!(c.is_empty());
157
158        c.add_custom(
159            Uuid::default(),
160            ResourceDataConstructor {
161                callback: Box::new(|| Box::<Stub>::default()),
162                type_name: std::any::type_name::<Stub>().to_owned(),
163            },
164        );
165        assert_eq!(c.len(), 1);
166
167        c.remove(Uuid::default());
168        assert!(c.is_empty());
169    }
170
171    #[test]
172    fn resource_constructor_container_try_create() {
173        let c = ResourceConstructorContainer::new();
174        c.add::<Stub>();
175
176        let res = c.try_create(&Uuid::default());
177        assert!(res.is_some());
178    }
179}