crayon/res/utils/
pool.rs

1//! # ResourcePool
2//!
3//! The `ResourcePool` is a standardized resource manager that defines a set of interface for creation,
4//! destruction, sharing and lifetime management. It is used in all the built-in crayon modules.
5//!
6//! ## Handle
7//!
8//! We are using a unique `Handle` object to represent a resource object safely. This approach
9//! has several advantages, since it helps for saving state externally. E.G.:
10//!
11//! 1. It allows for the resource to be destroyed without leaving dangling pointers.
12//! 2. Its perfectly safe to store and share the `Handle` even the underlying resource is
13//! loading on the background thread.
14//!
15//! In some systems, actual resource objects are private and opaque, application will usually
16//! not have direct access to a resource object in form of reference.
17//!
18//! ## Ownership & Lifetime
19//!
20//! For the sake of simplicity, the refenerce-counting technique is used for providing shared ownership
21//! of a resource.
22//!
23//! Everytime you create a resource at runtime, the `ResourcePool` will increases the reference count of
24//! the resource by 1. And when you are done with the resource, its the user's responsibility to
25//! drop the ownership of the resource. And when the last ownership to a given resource is dropped,
26//! the corresponding resource is also destroyed.
27
28use failure::Error;
29use std::sync::{Arc, Mutex};
30use uuid::Uuid;
31
32use crate::utils::prelude::{FastHashMap, HandleLike, ObjectPool};
33
34use super::state::ResourceState;
35
36pub trait ResourceLoader: Send + Sync {
37    type Handle: Send;
38    type Intermediate: Send;
39    type Resource: Send;
40
41    fn load(&self, _: Self::Handle, _: &[u8]) -> Result<Self::Intermediate, Error>;
42    fn create(&self, _: Self::Handle, _: Self::Intermediate) -> Result<Self::Resource, Error>;
43    fn delete(&self, _: Self::Handle, _: Self::Resource);
44}
45
46// The `ResourcePool` is a standardized resources manager that defines a set of interface for creation,
47// destruction, sharing and lifetime management. It is used in all the built-in crayon modules.
48pub struct ResourcePool<H, Loader>
49where
50    H: HandleLike + 'static,
51    Loader: ResourceLoader<Handle = H> + Clone + 'static,
52{
53    items: ObjectPool<H, Item<Loader::Resource>>,
54    requests: FastHashMap<H, Arc<Mutex<ResourceAsyncState<Loader::Intermediate>>>>,
55    registry: FastHashMap<Uuid, H>,
56    loader: Loader,
57}
58
59impl<H, Loader> ResourcePool<H, Loader>
60where
61    H: HandleLike + 'static,
62    Loader: ResourceLoader<Handle = H> + Clone + 'static,
63{
64    /// Create a new and empty `ResourcePool`.
65    pub fn new(loader: Loader) -> Self {
66        ResourcePool {
67            items: ObjectPool::new(),
68            registry: FastHashMap::default(),
69            requests: FastHashMap::default(),
70            loader,
71        }
72    }
73
74    pub fn advance(&mut self) -> Result<(), Error> {
75        let items = &mut self.items;
76        let loader = &self.loader;
77
78        self.requests.retain(|&handle, req| {
79            let mut req = req.lock().unwrap();
80            if let ResourceAsyncState::NotReady = *req {
81                return true;
82            }
83
84            let mut tmp = ResourceAsyncState::NotReady;
85            std::mem::swap(&mut *req, &mut tmp);
86
87            match tmp {
88                ResourceAsyncState::Err(err) => {
89                    warn!("{:?}", err);
90                    if let Some(item) = items.get_mut(handle) {
91                        item.error = Some(err);
92                    }
93                }
94                ResourceAsyncState::Ok(intermediate) => {
95                    if let Some(item) = items.get_mut(handle) {
96                        match loader.create(handle, intermediate) {
97                            Ok(resource) => item.resource = Some(resource),
98                            Err(err) => {
99                                warn!("{:?}", err);
100                                item.error = Some(err);
101                            }
102                        }
103                    }
104                }
105                _ => unreachable!(),
106            }
107
108            false
109        });
110
111        Ok(())
112    }
113
114    /// Create a resource with provided value instance.
115    ///
116    /// A associated `Handle` is returned.
117    #[inline]
118    pub fn create(&mut self, params: Loader::Intermediate) -> Result<H, Error> {
119        let handle = self.alloc(None);
120        match self.loader.create(handle, params) {
121            Ok(value) => {
122                self.items.get_mut(handle).unwrap().resource = Some(value);
123                Ok(handle)
124            }
125            Err(error) => {
126                self.delete(handle);
127                Err(error)
128            }
129        }
130    }
131
132    /// Create a resource from file asynchronously.
133    #[inline]
134    pub fn create_from<T: AsRef<str>>(&mut self, url: T) -> Result<H, Error> {
135        let url = url.as_ref();
136        let uuid = crate::res::find(url)
137            .ok_or_else(|| format_err!("Could not found resource '{}'.", url))?;
138        self.create_from_uuid(uuid)
139    }
140
141    /// Create a named resource from file asynchronously.
142    #[inline]
143    pub fn create_from_uuid(&mut self, uuid: Uuid) -> Result<H, Error> {
144        if let Some(&handle) = self.registry.get(&uuid) {
145            self.items.get_mut(handle).unwrap().rc += 1;
146            return Ok(handle);
147        }
148
149        let handle = self.alloc(Some(uuid));
150
151        let rx = Arc::new(Mutex::new(ResourceAsyncState::NotReady));
152        let tx = rx.clone();
153        let loader = self.loader.clone();
154
155        let result = crate::res::load_with_callback(uuid, move |rsp| match rsp {
156            Ok(bytes) => {
157                let itermediate = loader.load(handle, &bytes);
158
159                match itermediate {
160                    Ok(item) => {
161                        *tx.lock().unwrap() = ResourceAsyncState::Ok(item);
162                    }
163                    Err(err) => {
164                        *tx.lock().unwrap() = ResourceAsyncState::Err(err);
165                    }
166                }
167            }
168
169            Err(err) => {
170                *tx.lock().unwrap() = ResourceAsyncState::Err(err);
171            }
172        });
173
174        match result {
175            Ok(_) => {
176                self.requests.insert(handle, rx);
177                Ok(handle)
178            }
179            Err(err) => {
180                self.delete(handle);
181                Err(err)
182            }
183        }
184    }
185
186    /// Deletes a resource from loadery.
187    pub fn delete(&mut self, handle: H) {
188        let disposed = self
189            .items
190            .get_mut(handle)
191            .map(|e| {
192                e.rc -= 1;
193                e.rc == 0
194            })
195            .unwrap_or(false);
196
197        if disposed {
198            let e = self.items.free(handle).unwrap();
199
200            if let Some(uuid) = e.uuid {
201                self.registry.remove(&uuid);
202            }
203
204            if let Some(resource) = e.resource {
205                self.loader.delete(handle, resource);
206            }
207        }
208    }
209
210    /// Get the resource state.
211    #[inline]
212    pub fn state(&self, handle: H) -> ResourceState {
213        self.items
214            .get(handle)
215            .map(|e| {
216                if e.resource.is_some() {
217                    ResourceState::Ok
218                } else if e.error.is_some() {
219                    ResourceState::Err
220                } else {
221                    ResourceState::NotReady
222                }
223            })
224            .unwrap_or(ResourceState::NotReady)
225    }
226
227    /// Checks if the handle is still avaiable in this pool.
228    #[inline]
229    pub fn contains(&self, handle: H) -> bool {
230        self.items.contains(handle)
231    }
232
233    /// Return immutable reference to internal value with name `Handle`.
234    #[inline]
235    pub fn resource(&self, handle: H) -> Option<&Loader::Resource> {
236        self.items.get(handle).and_then(|e| e.resource.as_ref())
237    }
238
239    /// Return mutable reference to internal value with name `Handle`.
240    #[inline]
241    pub fn resource_mut(&mut self, handle: H) -> Option<&mut Loader::Resource> {
242        self.items.get_mut(handle).and_then(|e| e.resource.as_mut())
243    }
244
245    #[inline]
246    fn alloc(&mut self, uuid: Option<Uuid>) -> H {
247        let entry = Item {
248            rc: 1,
249            uuid,
250            resource: None,
251            error: None,
252        };
253
254        let handle = self.items.create(entry);
255
256        if let Some(uuid) = uuid {
257            self.registry.insert(uuid, handle);
258        }
259
260        handle
261    }
262}
263
264struct Item<T> {
265    rc: u32,
266    uuid: Option<Uuid>,
267    resource: Option<T>,
268    error: Option<Error>,
269}
270
271enum ResourceAsyncState<T> {
272    Ok(T),
273    Err(Error),
274    NotReady,
275}