1use crate::{
24 core::{uuid::Uuid, TypeUuidProvider},
25 io::ResourceIo,
26 options::BaseImportOptions,
27 state::LoadError,
28 ResourceData, TypedResourceData,
29};
30use fyrox_core::io::FileError;
31use fyrox_core::platform::TargetPlatform;
32use fyrox_core::visitor::{Format, Visitor};
33use std::{
34 any::Any,
35 future::Future,
36 path::{Path, PathBuf},
37 pin::Pin,
38 sync::Arc,
39};
40
41#[cfg(target_arch = "wasm32")]
42#[doc(hidden)]
43pub trait BaseResourceLoader: Any {}
44#[cfg(target_arch = "wasm32")]
45impl<T: Any> BaseResourceLoader for T {}
46
47#[cfg(not(target_arch = "wasm32"))]
48#[doc(hidden)]
49pub trait BaseResourceLoader: Any + Send {}
50#[cfg(not(target_arch = "wasm32"))]
51impl<T: Any + Send> BaseResourceLoader for T {}
52
53fn convert_ascii_to_binary<F>(
54 src_path: PathBuf,
55 dest_path: PathBuf,
56 is_native_extension: F,
57 _platform: TargetPlatform,
58 io: Arc<dyn ResourceIo>,
59) -> Pin<Box<dyn Future<Output = Result<(), FileError>>>>
60where
61 F: Fn(&str) -> bool,
62{
63 if src_path
64 .extension()
65 .and_then(|src_ext| src_ext.to_str())
66 .is_some_and(is_native_extension)
67 {
68 Box::pin(async move {
69 let data = io.load_file(&src_path).await?;
70 match Visitor::detect_format_from_slice(&data) {
71 Format::Unknown => Err(FileError::Custom("Unknown format!".to_string())),
72 Format::Binary => {
73 Ok(io.copy_file(&src_path, &dest_path).await?)
75 }
76 Format::Ascii => {
77 let visitor = Visitor::load_from_memory(&data).map_err(|err| {
79 FileError::Custom(format!(
80 "Unable to load {}. Reason: {err}",
81 src_path.display()
82 ))
83 })?;
84 visitor.save_binary_to_file(dest_path).map_err(|err| {
85 FileError::Custom(format!(
86 "Unable to save {}. Reason: {err}",
87 src_path.display()
88 ))
89 })?;
90 Ok(())
91 }
92 }
93 })
94 } else {
95 Box::pin(async move { io.copy_file(&src_path, &dest_path).await })
96 }
97}
98
99pub trait ResourceLoader: BaseResourceLoader {
101 fn extensions(&self) -> &[&str];
104
105 fn is_native_extension(&self, #[allow(unused_variables)] ext: &str) -> bool {
109 false
110 }
111
112 fn supports_extension(&self, ext: &str) -> bool {
114 self.extensions()
115 .iter()
116 .any(|e| fyrox_core::cmp_strings_case_insensitive(e, ext))
117 }
118
119 fn data_type_uuid(&self) -> Uuid;
121
122 fn load(&self, path: PathBuf, io: Arc<dyn ResourceIo>) -> BoxedLoaderFuture;
124
125 fn convert(
131 &self,
132 src_path: PathBuf,
133 dest_path: PathBuf,
134 #[allow(unused_variables)] platform: TargetPlatform,
135 io: Arc<dyn ResourceIo>,
136 ) -> Pin<Box<dyn Future<Output = Result<(), FileError>>>> {
137 convert_ascii_to_binary(
138 src_path,
139 dest_path,
140 |ext| self.is_native_extension(ext),
141 platform,
142 io,
143 )
144 }
145
146 fn try_load_import_settings(
148 &self,
149 #[allow(unused_variables)] resource_path: PathBuf,
150 #[allow(unused_variables)] io: Arc<dyn ResourceIo>,
151 ) -> BoxedImportOptionsLoaderFuture {
152 Box::pin(async move { None })
153 }
154
155 fn default_import_options(&self) -> Option<Box<dyn BaseImportOptions>> {
157 None
158 }
159}
160
161pub struct LoaderPayload(pub(crate) Box<dyn ResourceData>);
163
164impl LoaderPayload {
165 pub fn new<T: ResourceData>(data: T) -> Self {
167 Self(Box::new(data))
168 }
169}
170
171#[cfg(target_arch = "wasm32")]
173pub type BoxedLoaderFuture = Pin<Box<dyn Future<Output = Result<LoaderPayload, LoadError>>>>;
174
175#[cfg(not(target_arch = "wasm32"))]
177pub type BoxedLoaderFuture = Pin<Box<dyn Future<Output = Result<LoaderPayload, LoadError>> + Send>>;
178
179pub type BoxedImportOptionsLoaderFuture =
181 Pin<Box<dyn Future<Output = Option<Box<dyn BaseImportOptions>>>>>;
182
183#[derive(Default)]
185pub struct ResourceLoadersContainer {
186 loaders: Vec<Box<dyn ResourceLoader>>,
187}
188
189impl ResourceLoadersContainer {
190 pub fn new() -> Self {
192 Self::default()
193 }
194
195 pub fn set<T>(&mut self, loader: T) -> Option<T>
198 where
199 T: ResourceLoader,
200 {
201 if let Some(existing_loader) = self
202 .loaders
203 .iter_mut()
204 .find_map(|l| (&mut **l as &mut dyn Any).downcast_mut::<T>())
205 {
206 Some(std::mem::replace(existing_loader, loader))
207 } else {
208 self.loaders.push(Box::new(loader));
209 None
210 }
211 }
212
213 pub fn try_replace<Prev, New>(&mut self, new_loader: New) -> Option<Prev>
216 where
217 Prev: ResourceLoader,
218 New: ResourceLoader,
219 {
220 if let Some(pos) = self
221 .loaders
222 .iter()
223 .position(|l| (&**l as &dyn Any).is::<Prev>())
224 {
225 let prev_untyped = std::mem::replace(&mut self.loaders[pos], Box::new(new_loader));
226 (prev_untyped as Box<dyn Any>)
227 .downcast::<Prev>()
228 .ok()
229 .map(|boxed| *boxed)
230 } else {
231 None
232 }
233 }
234
235 pub fn find<T>(&self) -> Option<&T>
237 where
238 T: ResourceLoader,
239 {
240 self.loaders
241 .iter()
242 .find_map(|loader| (&**loader as &dyn Any).downcast_ref())
243 }
244
245 pub fn find_mut<T>(&mut self) -> Option<&mut T>
247 where
248 T: ResourceLoader,
249 {
250 self.loaders
251 .iter_mut()
252 .find_map(|loader| (&mut **loader as &mut dyn Any).downcast_mut())
253 }
254
255 pub fn len(&self) -> usize {
257 self.loaders.len()
258 }
259
260 pub fn is_empty(&self) -> bool {
262 self.loaders.is_empty()
263 }
264
265 pub fn iter(&self) -> impl Iterator<Item = &dyn ResourceLoader> {
267 self.loaders.iter().map(|boxed| &**boxed)
268 }
269
270 pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut dyn ResourceLoader> {
272 self.loaders.iter_mut().map(|boxed| &mut **boxed)
273 }
274
275 pub fn is_supported_resource(&self, path: &Path) -> bool {
278 if let Some(extension) = path.extension().and_then(|ext| ext.to_str()) {
279 self.loaders
280 .iter()
281 .any(|loader| loader.supports_extension(extension))
282 } else {
283 false
284 }
285 }
286
287 pub fn is_extension_matches_type<T>(&self, path: &Path) -> bool
290 where
291 T: TypedResourceData,
292 {
293 path.extension().is_some_and(|extension| {
294 self.loaders
295 .iter()
296 .find(|loader| loader.supports_extension(&extension.to_string_lossy()))
297 .is_some_and(|loader| {
298 loader.data_type_uuid() == <T as TypeUuidProvider>::type_uuid()
299 })
300 })
301 }
302
303 pub fn loader_for(&self, path: &Path) -> Option<&dyn ResourceLoader> {
305 path.extension().and_then(|extension| {
306 self.loaders
307 .iter()
308 .find(|loader| loader.supports_extension(&extension.to_string_lossy()))
309 .map(|l| &**l)
310 })
311 }
312}
313
314#[cfg(test)]
315mod test {
316 use super::*;
317
318 #[derive(Eq, PartialEq, Debug)]
319 struct MyResourceLoader;
320
321 impl ResourceLoader for MyResourceLoader {
322 fn extensions(&self) -> &[&str] {
323 &[]
324 }
325
326 fn data_type_uuid(&self) -> Uuid {
327 Default::default()
328 }
329
330 fn load(&self, _path: PathBuf, _io: Arc<dyn ResourceIo>) -> BoxedLoaderFuture {
331 todo!()
332 }
333 }
334
335 #[test]
336 fn resource_loader_container_new() {
337 let container = ResourceLoadersContainer::new();
338 assert!(container.loaders.is_empty());
339
340 let container = ResourceLoadersContainer::default();
341 assert!(container.loaders.is_empty());
342 }
343
344 #[test]
345 fn resource_loader_container_set() {
346 let mut container = ResourceLoadersContainer::new();
347 let res = container.set(MyResourceLoader);
348 let res2 = container.set(MyResourceLoader);
349 assert_eq!(res, None);
350 assert_eq!(res2, Some(MyResourceLoader));
351
352 assert_eq!(container.len(), 1);
353 }
354
355 #[test]
356 fn resource_loader_container_find() {
357 let mut container = ResourceLoadersContainer::new();
358
359 let res = container.find::<MyResourceLoader>();
360 assert_eq!(res, None);
361
362 container.set(MyResourceLoader);
363 let res = container.find::<MyResourceLoader>();
364
365 assert_eq!(res, Some(&MyResourceLoader));
366 }
367
368 #[test]
369 fn resource_loader_container_find_mut() {
370 let mut container = ResourceLoadersContainer::new();
371
372 let res = container.find_mut::<MyResourceLoader>();
373 assert_eq!(res, None);
374
375 container.set(MyResourceLoader);
376 let res = container.find_mut::<MyResourceLoader>();
377
378 assert_eq!(res, Some(&mut MyResourceLoader));
379 }
380
381 #[test]
382 fn resource_loader_container_getters() {
383 let mut container = ResourceLoadersContainer::new();
384 assert!(container.is_empty());
385 assert_eq!(container.len(), 0);
386
387 container.set(MyResourceLoader);
388 container.set(MyResourceLoader);
389 assert!(!container.is_empty());
390 assert_eq!(container.len(), 1);
391 }
392}