1use slotmap::{Key as _, SlotMap, new_key_type};
2use std::collections::HashMap;
3
4use crate::assets::{handle::Handle, upload::AssetSource};
5
6new_key_type! {
7 pub struct RawAssetHandle;
13}
14
15fn warn_if_null<T>(handle: RawAssetHandle, method: &str, on_null: &str) -> bool {
16 if handle.is_null() {
17 tracing::warn!(
18 "Assets<{}>: {method}() called with a null/default handle — {on_null}",
19 std::any::type_name::<T>()
20 );
21 true
22 } else {
23 false
24 }
25}
26
27struct AssetEntry<T: AssetSource> {
28 source: T,
29 processed: Option<T::Processed>,
30}
31
32pub struct Assets<T: AssetSource> {
42 storage: SlotMap<RawAssetHandle, AssetEntry<T>>,
43 handles: HashMap<String, RawAssetHandle>,
44 queue: Vec<RawAssetHandle>,
45 removed: Vec<RawAssetHandle>,
46}
47
48impl<T: AssetSource> Assets<T> {
49 pub fn new() -> Self {
50 Self {
51 storage: SlotMap::with_key(),
52 handles: HashMap::new(),
53 queue: Vec::new(),
54 removed: Vec::new(),
55 }
56 }
57
58 pub fn insert(&mut self, name: &str, source: T) -> Handle<T> {
64 if let Some(&existing) = self.handles.get(name) {
65 if let Some(entry) = self.storage.get_mut(existing) {
66 entry.source = source;
67 entry.processed = None;
68 if !self.queue.contains(&existing) {
69 self.queue.push(existing);
70 }
71 tracing::debug!(
72 "Assets<{}>: replaced source for {:?} ({name}) in-place",
73 std::any::type_name::<T>(),
74 existing
75 );
76 return Handle::new(existing);
77 }
78 }
79 let handle = self.storage.insert(AssetEntry { source, processed: None });
80 self.handles.insert(name.to_string(), handle);
81 self.queue.push(handle);
82 Handle::new(handle)
83 }
84
85 pub fn get(&self, handle: Handle<T>) -> Option<&T::Processed> {
90 let id = handle.id;
91 if warn_if_null::<T>(id, "get", "did you forget to insert the asset and store the returned handle?") {
92 return None;
93 }
94 match self.storage.get(id) {
95 None => {
96 tracing::warn!(
97 "Assets<{}>: get() called with a stale handle {:?} — \
98 the asset was likely removed since this handle was obtained",
99 std::any::type_name::<T>(),
100 id
101 );
102 None
103 }
104 Some(entry) => {
105 if entry.processed.is_none() {
106 tracing::debug!(
107 "Assets<{}>: get() for {:?} returned None — \
108 the asset may still be pending upload",
109 std::any::type_name::<T>(),
110 id
111 );
112 }
113 entry.processed.as_ref()
114 }
115 }
116 }
117
118 pub fn get_source(&self, handle: Handle<T>) -> Option<&T> {
120 let id = handle.id;
121 if warn_if_null::<T>(id, "get_source", "did you forget to insert the asset and store the returned handle?") {
122 return None;
123 }
124 let result = self.storage.get(id).map(|e| &e.source);
125 if result.is_none() {
126 tracing::warn!(
127 "Assets<{}>: get_source() called with a stale handle {:?}",
128 std::any::type_name::<T>(),
129 id
130 );
131 }
132 result
133 }
134
135 pub fn get_source_mut(&mut self, handle: Handle<T>) -> Option<&mut T> {
137 let id = handle.id;
138 if warn_if_null::<T>(id, "get_source_mut", "did you forget to insert the asset and store the returned handle?") {
139 return None;
140 }
141 let result = self.storage.get_mut(id).map(|e| &mut e.source);
142 if result.is_none() {
143 tracing::warn!(
144 "Assets<{}>: get_source_mut() called with a stale handle {:?}",
145 std::any::type_name::<T>(),
146 id
147 );
148 }
149 result
150 }
151
152 pub fn is_ready(&self, handle: Handle<T>) -> bool {
155 self.storage.get(handle.id).is_some_and(|e| e.processed.is_some())
156 }
157
158 pub fn contains(&self, handle: Handle<T>) -> bool {
161 self.storage.contains_key(handle.id)
162 }
163
164 pub fn get_by_name(&self, name: &str) -> Option<&T::Processed> {
168 let handle = self.handles.get(name)?;
169 self.storage.get(*handle)?.processed.as_ref()
170 }
171
172 pub fn get_source_by_name(&self, name: &str) -> Option<&T> {
174 let handle = self.handles.get(name)?;
175 Some(&self.storage.get(*handle)?.source)
176 }
177
178 pub fn get_handle_by_name(&self, name: &str) -> Option<Handle<T>> {
180 self.handles.get(name).copied().map(Handle::new)
181 }
182
183 pub fn replace(&mut self, handle: Handle<T>, source: T) -> bool {
187 let id = handle.id;
188 if warn_if_null::<T>(id, "replace", "no-op") {
189 return false;
190 }
191 let Some(entry) = self.storage.get_mut(id) else {
192 tracing::warn!(
193 "Assets<{}>: replace() called with a stale handle {:?} — no-op",
194 std::any::type_name::<T>(),
195 id
196 );
197 return false;
198 };
199 entry.source = source;
200 entry.processed = None;
201 if !self.queue.contains(&id) {
202 self.queue.push(id);
203 }
204 tracing::debug!(
205 "Assets<{}>: replaced source for {:?}{} via handle",
206 std::any::type_name::<T>(),
207 id,
208 self.name_for_handle(id).map(|n| format!(" ({n})")).unwrap_or_default()
209 );
210 true
211 }
212
213 pub fn mark_dirty(&mut self, handle: Handle<T>) {
217 let id = handle.id;
218 if warn_if_null::<T>(id, "mark_dirty", "no-op") {
219 return;
220 }
221 if self.storage.contains_key(id) && !self.queue.contains(&id) {
222 self.queue.push(id);
223 }
224 }
225
226 pub fn remove(&mut self, handle: Handle<T>) -> Option<T> {
229 let id = handle.id;
230 if warn_if_null::<T>(id, "remove", "no-op") {
231 return None;
232 }
233 let entry = self.storage.remove(id)?;
234 self.handles.retain(|_, h| *h != id);
235 self.queue.retain(|h| *h != id);
236 self.removed.push(id);
237 Some(entry.source)
238 }
239
240 pub fn remove_by_name(&mut self, name: &str) -> Option<T> {
242 let id = self.handles.remove(name)?;
243 self.queue.retain(|h| *h != id);
244 self.removed.push(id);
245 Some(self.storage.remove(id)?.source)
246 }
247
248 pub fn iter(&self) -> impl Iterator<Item = (RawAssetHandle, &T::Processed)> {
250 self.storage
251 .iter()
252 .filter_map(|(h, e)| e.processed.as_ref().map(|p| (h, p)))
253 }
254
255 pub fn iter_source(&self) -> impl Iterator<Item = (RawAssetHandle, &T)> {
257 self.storage.iter().map(|(h, e)| (h, &e.source))
258 }
259
260 pub fn names(&self) -> impl Iterator<Item = (&str, RawAssetHandle)> {
262 self.handles.iter().map(|(name, &handle)| (name.as_str(), handle))
263 }
264
265 pub(crate) fn get_source_quiet(&self, handle: RawAssetHandle) -> Option<&T> {
272 self.storage.get(handle).map(|e| &e.source)
273 }
274
275 pub(crate) fn set_processed(&mut self, handle: RawAssetHandle, processed: T::Processed) {
277 if let Some(entry) = self.storage.get_mut(handle) {
278 entry.processed = Some(processed);
279 }
280 }
281
282 pub(crate) fn take_dirty(&mut self) -> Vec<RawAssetHandle> {
284 std::mem::take(&mut self.queue)
285 }
286
287 pub(crate) fn take_removed(&mut self) -> Vec<RawAssetHandle> {
289 std::mem::take(&mut self.removed)
290 }
291
292 pub(crate) fn requeue(&mut self, handles: Vec<RawAssetHandle>) {
294 self.queue.extend(handles);
295 }
296
297 pub(crate) fn dirty_is_empty(&self) -> bool {
299 self.queue.is_empty()
300 }
301
302 pub(crate) fn dirty_len(&self) -> usize {
304 self.queue.len()
305 }
306
307 pub(crate) fn name_for_handle(&self, handle: RawAssetHandle) -> Option<&str> {
310 self.handles
311 .iter()
312 .find(|(_, h)| **h == handle)
313 .map(|(name, _)| name.as_str())
314 }
315}