Skip to main content

pebble/assets/
storage.rs

1use slotmap::{Key as _, SecondaryMap, SlotMap, new_key_type};
2use std::collections::HashMap;
3
4new_key_type! {
5    /// Untyped slot-map key for an asset entry.
6    ///
7    /// Prefer the typed [`Handle<T>`](crate::assets::handle::Handle) over this
8    /// in most code. `RawAssetHandle` is used internally by the storage and
9    /// sync systems.
10    pub struct RawAssetHandle;
11}
12
13/// Storage for raw CPU-side assets of type `T`.
14///
15/// Assets are inserted by name and looked up by either name or
16/// [`RawAssetHandle`]. When an asset is inserted or updated its handle is
17/// pushed onto the *dirty queue*, which the sync system drains each tick to
18/// upload changed assets to the GPU.
19pub struct Assets<T: 'static + Send + Sync> {
20    storage: SlotMap<RawAssetHandle, T>,
21    handles: HashMap<String, RawAssetHandle>,
22    queue: Vec<RawAssetHandle>,
23    removed: Vec<RawAssetHandle>,
24}
25
26impl<T: 'static + Send + Sync> Assets<T> {
27    pub fn new() -> Self {
28        Self {
29            storage: SlotMap::with_key(),
30            handles: HashMap::new(),
31            queue: Vec::new(),
32            removed: Vec::new(),
33        }
34    }
35
36    /// Insert `asset` under `name`, returning its handle.
37    ///
38    /// If an asset with the same name already exists, its data is replaced
39    /// **in-place**: the same slot-map entry (and therefore the same handle)
40    /// is reused, so any code that already holds a [`Handle`](crate::assets::handle::Handle)
41    /// or [`RawAssetHandle`] for this asset continues to work correctly.
42    /// The handle is re-queued so the sync system re-uploads the new data.
43    pub fn insert(&mut self, name: &str, asset: T) -> RawAssetHandle {
44        if let Some(&existing) = self.handles.get(name) {
45            // Replace data in-place so existing handles stay valid.
46            if let Some(slot) = self.storage.get_mut(existing) {
47                *slot = asset;
48                // Only queue once — don't duplicate if already dirty.
49                if !self.queue.contains(&existing) {
50                    self.queue.push(existing);
51                }
52                tracing::debug!(
53                    "Assets<{}>: replaced data for {:?} ({name}) in-place",
54                    std::any::type_name::<T>(),
55                    existing
56                );
57                return existing;
58            }
59        }
60
61        let handle = self.storage.insert(asset);
62        self.handles.insert(name.to_string(), handle);
63        self.queue.push(handle);
64        handle
65    }
66
67    /// Look up an asset by its raw handle.
68    pub fn get(&self, handle: RawAssetHandle) -> Option<&T> {
69        if handle.is_null() {
70            tracing::warn!(
71                "Assets<{}>: get() called with a null/default handle — \
72                 did you forget to insert the asset and store the returned handle?",
73                std::any::type_name::<T>()
74            );
75            return None;
76        }
77        let result = self.storage.get(handle);
78        if result.is_none() {
79            tracing::warn!(
80                "Assets<{}>: get() called with a stale handle {:?} — \
81                 the asset was likely removed or replaced since this handle was obtained",
82                std::any::type_name::<T>(),
83                handle
84            );
85        }
86        result
87    }
88
89    /// Mutably look up an asset by its raw handle.
90    pub fn get_mut(&mut self, handle: RawAssetHandle) -> Option<&mut T> {
91        if handle.is_null() {
92            tracing::warn!(
93                "Assets<{}>: get_mut() called with a null/default handle — \
94                 did you forget to insert the asset and store the returned handle?",
95                std::any::type_name::<T>()
96            );
97            return None;
98        }
99        let result = self.storage.get_mut(handle);
100        if result.is_none() {
101            tracing::warn!(
102                "Assets<{}>: get_mut() called with a stale handle {:?} — \
103                 the asset was likely removed or replaced since this handle was obtained",
104                std::any::type_name::<T>(),
105                handle
106            );
107        }
108        result
109    }
110
111    /// Look up an asset by its name.
112    pub fn get_by_name(&self, name: &str) -> Option<&T> {
113        let result = self.handles
114            .get(name)
115            .and_then(|&handle| self.storage.get(handle));
116        if result.is_none() {
117            tracing::debug!(
118                "Assets<{}>: get_by_name({:?}) found no asset — \
119                 the name may not have been inserted yet",
120                std::any::type_name::<T>(),
121                name
122            );
123        }
124        result
125    }
126
127    /// Mutably look up an asset by its name.
128    pub fn get_mut_by_name(&mut self, name: &str) -> Option<&mut T> {
129        let handle = self.handles.get(name).copied();
130        if handle.is_none() {
131            tracing::debug!(
132                "Assets<{}>: get_mut_by_name({:?}) found no asset — \
133                 the name may not have been inserted yet",
134                std::any::type_name::<T>(),
135                name
136            );
137            return None;
138        }
139        self.storage.get_mut(handle.unwrap())
140    }
141
142    /// Look up an asset handle by its name.
143    pub fn get_handle_by_name(&self, name: &str) -> Option<RawAssetHandle> {
144        self.handles.get(name).copied()
145    }
146
147    /// Drain and return all handles currently in the dirty queue.
148    ///
149    /// Called by the asset sync system each tick.
150    pub fn take_dirty(&mut self) -> Vec<RawAssetHandle> {
151        std::mem::take(&mut self.queue)
152    }
153
154    /// Remove an asset by handle, returning the value if it existed.
155    pub fn remove(&mut self, handle: RawAssetHandle) -> Option<T> {
156        if handle.is_null() {
157            tracing::warn!(
158                "Assets<{}>: remove() called with a null/default handle — no-op",
159                std::any::type_name::<T>()
160            );
161            return None;
162        }
163        let value = self.storage.remove(handle)?;
164
165        self.handles.retain(|_, h| *h != handle);
166        self.queue.retain(|h| *h != handle);
167        self.removed.push(handle);
168
169        Some(value)
170    }
171
172    /// Remove an asset by name, returning the value if it existed.
173    pub fn remove_by_name(&mut self, name: &str) -> Option<T> {
174        let handle = self.handles.remove(name)?;
175        self.queue.retain(|h| *h != handle);
176        self.removed.push(handle);
177        self.storage.remove(handle)
178    }
179
180    /// Drain and return all handles removed since the last call.
181    ///
182    /// Called by the asset sync system each tick to evict stale processed assets.
183    pub fn take_removed(&mut self) -> Vec<RawAssetHandle> {
184        std::mem::take(&mut self.removed)
185    }
186
187    /// Returns `true` if the dirty queue is empty.
188    pub fn dirty_is_empty(&self) -> bool {
189        self.queue.is_empty()
190    }
191
192    /// Returns the number of handles currently in the dirty queue.
193    pub fn dirty_len(&self) -> usize {
194        self.queue.len()
195    }
196
197    /// Push `handles` back onto the dirty queue so they are retried next tick.
198    pub fn requeue(&mut self, handles: Vec<RawAssetHandle>) {
199        self.queue.extend(handles);
200    }
201
202    /// Iterate over all assets by handle.
203    pub fn iter(&self) -> impl Iterator<Item = (RawAssetHandle, &T)> {
204        self.storage.iter()
205    }
206
207    /// Mutably iterate over all assets by handle.
208    pub fn iter_mut(&mut self) -> impl Iterator<Item = (RawAssetHandle, &mut T)> {
209        self.storage.iter_mut()
210    }
211
212    /// Iterate over `(name, handle)` pairs for every named asset.
213    pub fn names(&self) -> impl Iterator<Item = (&str, RawAssetHandle)> {
214        self.handles
215            .iter()
216            .map(|(name, &handle)| (name.as_str(), handle))
217    }
218
219    pub fn name_for_handle(&self, handle: RawAssetHandle) -> Option<&str> {
220        self.handles
221            .iter()
222            .find(|(_, h)| **h == handle)
223            .map(|(name, _)| name.as_str())
224    }
225}
226
227impl<'a, T: 'static + Send + Sync> IntoIterator for &'a Assets<T> {
228    type Item = (RawAssetHandle, &'a T);
229    type IntoIter = slotmap::basic::Iter<'a, RawAssetHandle, T>;
230
231    fn into_iter(self) -> Self::IntoIter {
232        self.storage.iter()
233    }
234}
235
236impl<'a, T: 'static + Send + Sync> IntoIterator for &'a mut Assets<T> {
237    type Item = (RawAssetHandle, &'a mut T);
238    type IntoIter = slotmap::basic::IterMut<'a, RawAssetHandle, T>;
239
240    fn into_iter(self) -> Self::IntoIter {
241        self.storage.iter_mut()
242    }
243}
244
245/// Storage for backend-processed (GPU) assets indexed by the same
246/// [`RawAssetHandle`] as their source in [`Assets`].
247///
248/// Populated by the asset sync system after a successful [`Asset::upload`](crate::assets::upload::Asset::upload).
249pub struct ProcessedAssets<T: 'static + Send + Sync> {
250    storage: SecondaryMap<RawAssetHandle, T>,
251    pub(crate) names: HashMap<String, RawAssetHandle>,
252}
253
254impl<T: 'static + Send + Sync> ProcessedAssets<T> {
255    pub fn new() -> Self {
256        Self {
257            storage: SecondaryMap::new(),
258            names: HashMap::new(),
259        }
260    }
261
262    /// Store a processed asset, returning the previous value if one existed.
263    pub fn insert(&mut self, handle: RawAssetHandle, asset: T) -> Option<T> {
264        self.storage.insert(handle, asset)
265    }
266
267    /// Look up a processed asset by handle.
268    pub fn get(&self, handle: RawAssetHandle) -> Option<&T> {
269        if handle.is_null() {
270            tracing::warn!(
271                "ProcessedAssets<{}>: get() called with a null/default handle — \
272                 did you forget to insert the source asset and store the returned handle?",
273                std::any::type_name::<T>()
274            );
275            return None;
276        }
277        let result = self.storage.get(handle);
278        if result.is_none() {
279            tracing::debug!(
280                "ProcessedAssets<{}>: get() for handle {:?} returned nothing — \
281                 the asset may still be pending upload or was removed",
282                std::any::type_name::<T>(),
283                handle
284            );
285        }
286        result
287    }
288
289    /// Mutably look up a processed asset by handle.
290    pub fn get_mut(&mut self, handle: RawAssetHandle) -> Option<&mut T> {
291        if handle.is_null() {
292            tracing::warn!(
293                "ProcessedAssets<{}>: get_mut() called with a null/default handle — \
294                 did you forget to insert the source asset and store the returned handle?",
295                std::any::type_name::<T>()
296            );
297            return None;
298        }
299        let result = self.storage.get_mut(handle);
300        if result.is_none() {
301            tracing::debug!(
302                "ProcessedAssets<{}>: get_mut() for handle {:?} returned nothing — \
303                 the asset may still be pending upload or was removed",
304                std::any::type_name::<T>(),
305                handle
306            );
307        }
308        result
309    }
310
311    /// Remove a processed asset by handle, returning the value if it existed.
312    pub fn remove(&mut self, handle: RawAssetHandle) -> Option<T> {
313        self.names.retain(|_, h| *h != handle);
314        self.storage.remove(handle)
315    }
316
317    /// Returns `true` if a processed asset exists for `handle`.
318    pub fn contains(&self, handle: RawAssetHandle) -> bool {
319        self.storage.contains_key(handle)
320    }
321
322    /// Iterate over all processed assets by handle.
323    pub fn iter(&self) -> impl Iterator<Item = (RawAssetHandle, &T)> {
324        self.storage.iter()
325    }
326
327    /// Mutably iterate over all processed assets by handle.
328    pub fn iter_mut(&mut self) -> impl Iterator<Item = (RawAssetHandle, &mut T)> {
329        self.storage.iter_mut()
330    }
331
332    pub fn get_by_name(&self, name: &str) -> Option<&T> {
333        let handle = self.names.get(name)?;
334        self.storage.get(*handle)
335    }
336}
337
338impl<'a, T: 'static + Send + Sync> IntoIterator for &'a ProcessedAssets<T> {
339    type Item = (RawAssetHandle, &'a T);
340    type IntoIter = slotmap::secondary::Iter<'a, RawAssetHandle, T>;
341
342    fn into_iter(self) -> Self::IntoIter {
343        self.storage.iter()
344    }
345}
346
347impl<'a, T: 'static + Send + Sync> IntoIterator for &'a mut ProcessedAssets<T> {
348    type Item = (RawAssetHandle, &'a mut T);
349    type IntoIter = slotmap::secondary::IterMut<'a, RawAssetHandle, T>;
350
351    fn into_iter(self) -> Self::IntoIter {
352        self.storage.iter_mut()
353    }
354}