motion_core/asset/
handle.rs1use super::*;
4
5#[derive(Clone, Copy, Debug, PartialEq, Eq)]
7pub enum AssetStatus {
8 Unloaded,
10 Loading,
12 Ready,
14 Failed,
16}
17
18pub(super) struct Entry<A: Asset> {
19 pub(super) pool: ResourcePool,
20 pub(super) source: Source,
21 pub(super) context: LoadContext,
22 pub(super) result: OnceLock<Result<Arc<A>, Arc<crate::Error>>>,
23 pub(super) loading: AtomicBool,
24 pub(super) charged: AtomicUsize,
25}
26
27impl<A: Asset> Drop for Entry<A> {
28 fn drop(&mut self) {
29 self.pool
30 .0
31 .bytes
32 .fetch_sub(self.charged.load(Ordering::Relaxed), Ordering::Relaxed);
33 let key = (
34 TypeId::of::<A>(),
35 self.context.root.as_ref().clone(),
36 self.context
37 .fonts
38 .as_ref()
39 .map_or(0, crate::asset::environment::FontEnvironment::snapshot_id),
40 self.context.namespace,
41 self.source.clone(),
42 );
43 let mut cache = self
44 .pool
45 .0
46 .entries
47 .lock()
48 .unwrap_or_else(std::sync::PoisonError::into_inner);
49 if cache
50 .get(&key)
51 .is_some_and(|entry| entry.strong_count() == 0)
52 {
53 cache.remove(&key);
54 }
55 }
56}
57
58pub struct Handle<A: Asset> {
60 pub(super) entry: Arc<Entry<A>>,
61 pub(super) diagnostics: Option<RequestDiagnostics>,
62}
63
64impl<A: Asset> Clone for Handle<A> {
65 fn clone(&self) -> Self {
66 Self {
67 entry: self.entry.clone(),
68 diagnostics: self.diagnostics.clone(),
69 }
70 }
71}
72
73impl<A: Asset> std::fmt::Debug for Handle<A> {
74 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
75 formatter
76 .debug_struct("Handle")
77 .field("kind", &A::KIND)
78 .field("source", &self.entry.source)
79 .field("status", &self.status())
80 .finish()
81 }
82}
83
84impl<A: Asset> Handle<A> {
85 pub fn status(&self) -> AssetStatus {
87 match self.entry.result.get() {
88 Some(Ok(_)) => AssetStatus::Ready,
89 Some(Err(_)) => AssetStatus::Failed,
90 None if self.entry.loading.load(Ordering::Acquire) => AssetStatus::Loading,
91 None => AssetStatus::Unloaded,
92 }
93 }
94
95 pub fn error(&self) -> Option<Arc<crate::Error>> {
97 self.entry
98 .result
99 .get()
100 .and_then(|result| result.as_ref().err().cloned())
101 }
102
103 pub fn get(&self) -> crate::Result<Arc<A>> {
107 match &self.diagnostics {
108 Some(diagnostics) => self.get_with(diagnostics),
109 None => self.get_with(&RequestDiagnostics::default()),
110 }
111 }
112
113 pub fn get_with(&self, diagnostics: &RequestDiagnostics) -> crate::Result<Arc<A>> {
115 let _guard = if self.entry.result.get().is_none() {
116 let identity = Arc::as_ptr(&self.entry) as usize;
117 let accepted = RESOLVING.with(|entries| {
118 let mut entries = entries.borrow_mut();
119 if entries.len() >= 64 || entries.contains(&identity) {
120 false
121 } else {
122 entries.push(identity);
123 true
124 }
125 });
126 if !accepted {
127 return Err(crate::Error::asset(
128 A::KIND,
129 self.source_name(),
130 io::Error::other("Recursive resource resolution or load depth exceeds 64"),
131 ));
132 }
133 Some(ResolveGuard)
134 } else {
135 None
136 };
137 let result = self.entry.result.get_or_init(|| {
138 self.entry.loading.store(true, Ordering::Release);
139 let decoded =
140 std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| -> crate::Result<A> {
141 let bytes: Arc<[u8]> = match &self.entry.source {
142 Source::Bytes { data, .. } => data.clone(),
143 Source::File(path) => {
144 let metadata = std::fs::metadata(path)?;
145 if metadata.len() > MAX_ENCODED as u64 {
146 return Err(io::Error::new(
147 io::ErrorKind::InvalidData,
148 "Asset source exceeds 64 MiB",
149 )
150 .into());
151 }
152 if !metadata.is_file() {
153 return Err(io::Error::new(
154 io::ErrorKind::InvalidInput,
155 "Asset source must be a regular file",
156 )
157 .into());
158 }
159 let file = std::fs::File::open(path)?;
160 if !file.metadata()?.is_file() {
161 return Err(io::Error::new(
162 io::ErrorKind::InvalidInput,
163 "Asset source must be a regular file",
164 )
165 .into());
166 }
167 let mut bytes = Vec::new();
168 file.take(MAX_ENCODED as u64 + 1).read_to_end(&mut bytes)?;
169 bytes.into()
170 }
171 };
172 if bytes.len() > MAX_ENCODED {
173 return Err(io::Error::new(
174 io::ErrorKind::InvalidData,
175 "Asset source exceeds 64 MiB",
176 )
177 .into());
178 }
179 let asset = A::decode(&bytes, &self.entry.context)?;
180 let charge = asset
181 .retained_bytes()
182 .checked_add(if matches!(self.entry.source, Source::Bytes { .. }) {
183 bytes.len()
184 } else {
185 0
186 })
187 .ok_or_else(|| {
188 io::Error::new(
189 io::ErrorKind::OutOfMemory,
190 "Asset payload accounting overflow",
191 )
192 })?
193 .max(bytes.len());
194 let additional = charge - self.entry.charged.load(Ordering::Acquire);
195 self.entry
196 .pool
197 .0
198 .bytes
199 .try_update(Ordering::AcqRel, Ordering::Acquire, |current| {
200 current
201 .checked_add(additional)
202 .filter(|total| *total <= self.entry.pool.0.max_bytes)
203 })
204 .map_err(|_| {
205 io::Error::new(
206 io::ErrorKind::OutOfMemory,
207 "Shared asset-cache logical payload exceeds its allowance",
208 )
209 })?;
210 self.entry.charged.store(charge, Ordering::Release);
211 Ok(asset)
212 }));
213 let decoded = decoded.unwrap_or_else(|payload| {
214 let message = payload
215 .downcast_ref::<String>()
216 .cloned()
217 .or_else(|| {
218 payload
219 .downcast_ref::<&str>()
220 .map(|message| (*message).to_owned())
221 })
222 .unwrap_or_else(|| "non-string decoder panic".into());
223 Err(io::Error::other(format!("Asset decoder panicked: {message}")).into())
224 });
225
226 decoded
227 .map(Arc::new)
228 .map_err(|error| Arc::new(crate::Error::asset(A::KIND, self.source_name(), error)))
229 });
230 if let Err(error) = result {
231 diagnostics.record(error.clone());
232 }
233 result
234 .as_ref()
235 .map(Arc::clone)
236 .map_err(|error| crate::Error::asset(A::KIND, self.source_name(), error.clone()))
237 }
238
239 pub fn source_name(&self) -> String {
241 match &self.entry.source {
242 Source::File(path) => path.display().to_string(),
243 Source::Bytes { key, .. } => key.to_string(),
244 }
245 }
246}