Skip to main content

pebble/assets/
storage.rs

1use std::collections::HashMap;
2
3use slotmap::{SlotMap, new_key_type};
4
5use crate::assets::{handle::Handle, upload::AssetSource};
6
7new_key_type! {
8    pub struct RawAssetHandle;
9}
10
11struct AssetEntry<T: AssetSource> {
12    source: T,
13    processed: Option<T::Processed>,
14}
15
16/// Holds every loaded asset of type `T` — both its CPU-side source and (once
17/// uploaded) its GPU-side processed form, together. Registering
18/// `AssetPlugin::<Backend, T>::new()` drains newly-inserted/dirty entries
19/// automatically each tick; `insert`/`get`/`get_source` are the everyday API.
20pub struct Assets<T: AssetSource> {
21    storage: SlotMap<RawAssetHandle, AssetEntry<T>>,
22    handles: HashMap<String, RawAssetHandle>,
23    queue: Vec<RawAssetHandle>,
24}
25
26impl<T: AssetSource> Assets<T> {
27    pub fn new() -> Self {
28        Self {
29            storage: SlotMap::with_key(),
30            handles: HashMap::new(),
31            queue: Vec::new(),
32        }
33    }
34
35    /// Inserts `source` under `name`, queuing it for upload. Inserting
36    /// under a name that already exists replaces that entry (and marks it
37    /// dirty again) instead of creating a second one.
38    pub fn insert(&mut self, name: &str, source: T) -> Handle<T> {
39        if let Some(&existing) = self.handles.get(name) {
40            if let Some(entry) = self.storage.get_mut(existing) {
41                entry.source = source;
42                entry.processed = None;
43                if !self.queue.contains(&existing) {
44                    self.queue.push(existing);
45                }
46                return Handle::new(existing);
47            }
48        }
49        let handle = self.storage.insert(AssetEntry { source, processed: None });
50        self.handles.insert(name.to_string(), handle);
51        self.queue.push(handle);
52        Handle::new(handle)
53    }
54
55    /// The GPU-side processed value, if it's finished uploading.
56    pub fn get(&self, handle: Handle<T>) -> Option<&T::Processed> {
57        self.storage.get(handle.id)?.processed.as_ref()
58    }
59
60    /// The CPU-side source value — e.g. a `Mesh`'s vertices/indices, for
61    /// building a collision mesh from the same data used to render it.
62    pub fn get_source(&self, handle: Handle<T>) -> Option<&T> {
63        self.storage.get(handle.id).map(|entry| &entry.source)
64    }
65
66    pub fn get_source_mut(&mut self, handle: Handle<T>) -> Option<&mut T> {
67        self.storage.get_mut(handle.id).map(|entry| &mut entry.source)
68    }
69
70    /// Whether this handle's GPU-side upload has finished.
71    pub fn is_ready(&self, handle: Handle<T>) -> bool {
72        self.storage.get(handle.id).is_some_and(|entry| entry.processed.is_some())
73    }
74
75    /// Whether `handle` still refers to a live entry.
76    pub fn contains(&self, handle: Handle<T>) -> bool {
77        self.storage.contains_key(handle.id)
78    }
79
80    /// Re-queues an entry for upload — e.g. after mutating it via
81    /// [`get_source_mut`](Self::get_source_mut).
82    pub fn mark_dirty(&mut self, handle: Handle<T>) {
83        if self.storage.contains_key(handle.id) && !self.queue.contains(&handle.id) {
84            self.queue.push(handle.id);
85        }
86    }
87
88    /// Same as [`get`](Self::get), looked up by the name it was
89    /// [`insert`](Self::insert)ed under.
90    pub fn get_by_name(&self, name: &str) -> Option<&T::Processed> {
91        let handle = self.handles.get(name)?;
92        self.storage.get(*handle)?.processed.as_ref()
93    }
94
95    /// Same as [`get_source`](Self::get_source), looked up by name.
96    pub fn get_source_by_name(&self, name: &str) -> Option<&T> {
97        let handle = self.handles.get(name)?;
98        Some(&self.storage.get(*handle)?.source)
99    }
100
101    /// The [`Handle<T>`] an asset was inserted under, by name.
102    pub fn get_handle_by_name(&self, name: &str) -> Option<Handle<T>> {
103        self.handles.get(name).copied().map(Handle::new)
104    }
105
106    /// Every currently-loaded asset of this type, source data included —
107    /// e.g. to build a static collision world from every loaded `Mesh`, or
108    /// a debug asset browser. Ready-or-not — pair with
109    /// [`is_ready`](Self::is_ready) if you only want finished uploads.
110    pub fn iter(&self) -> impl Iterator<Item = (Handle<T>, &T)> {
111        self.storage.iter().map(|(id, entry)| (Handle::new(id), &entry.source))
112    }
113
114    pub fn remove(&mut self, handle: Handle<T>) -> Option<T> {
115        self.queue.retain(|&id| id != handle.id);
116        self.handles.retain(|_, &mut id| id != handle.id);
117        self.storage.remove(handle.id).map(|entry| entry.source)
118    }
119
120    pub fn remove_by_name(&mut self, name: &str) -> Option<T> {
121        let id = self.handles.remove(name)?;
122        self.queue.retain(|&h| h != id);
123        self.storage.remove(id).map(|entry| entry.source)
124    }
125
126    pub(crate) fn set_processed(&mut self, handle: RawAssetHandle, processed: T::Processed) {
127        if let Some(entry) = self.storage.get_mut(handle) {
128            entry.processed = Some(processed);
129        }
130    }
131
132    pub(crate) fn take_dirty(&mut self) -> Vec<RawAssetHandle> {
133        std::mem::take(&mut self.queue)
134    }
135
136    pub(crate) fn requeue(&mut self, handles: Vec<RawAssetHandle>) {
137        self.queue.extend(handles);
138    }
139}
140
141impl<T: AssetSource> Default for Assets<T> {
142    fn default() -> Self {
143        Self::new()
144    }
145}